diff --git a/codeeditor-app/app.html b/codeeditor-app/app.html index e022ccc..0febe63 100644 --- a/codeeditor-app/app.html +++ b/codeeditor-app/app.html @@ -1,11 +1,25 @@
- code editor +

code editor

+ + Javascript + HTML + CSS +
- +
+ + + + + + + + +
- +
diff --git a/codeeditor-app/app.less b/codeeditor-app/app.less index af2d476..99cc228 100644 --- a/codeeditor-app/app.less +++ b/codeeditor-app/app.less @@ -8,10 +8,12 @@ html, body { background: @median-bg-white; } + .flex-horiz { display: flex; flex-direction: row; height: 100%; + overflow: hidden; } .flex-vert { @@ -28,6 +30,10 @@ html, body { position: relative; } +.animate .flex-horiz > *, .animate .flex-vert > * { + transition: all 280ms ease-in-out; +} + .flex-horiz .flex-shrink, .flex-vert .flex-shrink { flex-grow: 0; flex-shrink: 1; @@ -60,6 +66,16 @@ body { .section.top { border-bottom: 1px solid @primary-border-color-white; + padding: 0; +} + +.section.top h1 { + margin: 0; + padding: 10px; + font-family: @primary-font-family; + font-weight: 400; + font-size: 18px; + display: inline-block; } .section.bottom { @@ -69,6 +85,15 @@ body { text-align: right; } +.code-editor-containers { + background: @editor-bg-white; +} + +.code-editor-containers.force-close { + min-width: 0 !important; + max-width: 0 !important; +} + @media (prefers-color-scheme: dark) { body { background: @median-bg-dark; @@ -86,4 +111,8 @@ body { .section.bottom { border-color: @primary-bg-color-dark; } -} \ No newline at end of file + + .code-editor-containers { + background: @editor-bg-dark; + } +} diff --git a/codeeditor-app/boot.ts b/codeeditor-app/boot.ts index 2d4578c..0e4eb2b 100644 --- a/codeeditor-app/boot.ts +++ b/codeeditor-app/boot.ts @@ -6,6 +6,9 @@ import { OutputFrame } from './elements/output-frame/output-frame'; import { NotificationBubbles } from './elements/notification-bubbles/notification-bubbles'; import { NotificationBubble } from './elements/notification-bubbles/notification-bubble'; import { ResizeHandle } from './elements/resize-handle/resize-handle'; +import { CodeEditorContainer } from './elements/code-editor-container/code-editor-container'; +import { WindowControl } from './elements/window-control/window-control'; +import { WindowControlConnector } from './elements/window-control/window-control connector'; window.customElements.define('app-root', AppRoot); window.customElements.define('code-editor', CodeEditor); @@ -13,6 +16,9 @@ window.customElements.define('output-frame', OutputFrame); window.customElements.define('notification-bubbles', NotificationBubbles); window.customElements.define('notification-bubble', NotificationBubble); window.customElements.define('resize-handle', ResizeHandle); +window.customElements.define('code-editor-container', CodeEditorContainer); +window.customElements.define('window-control', WindowControl); +window.customElements.define('window-control-connector', WindowControlConnector); document.body.innerHTML += bootHtml; \ No newline at end of file diff --git a/codeeditor-app/elements/code-editor-container/code-editor-container.less b/codeeditor-app/elements/code-editor-container/code-editor-container.less new file mode 100644 index 0000000..0b1e01b --- /dev/null +++ b/codeeditor-app/elements/code-editor-container/code-editor-container.less @@ -0,0 +1,26 @@ +@import url('../../../shared/theme.less'); + +div > code-editor-container.active:last-child { + min-height: auto !important; + max-height: none !important; + min-width: auto !important; + max-width: none !important; +} + +.code-editor-header { + padding: 10px 18px; + background: lighten(@editor-bg-white, 1%); + color: @primary-text-color-white; + font-size: 13px; + border-style: solid none; + border-color: @primary-bg-color-white; + border-width: 1px; +} + +@media (prefers-color-scheme: dark) { + .code-editor-header { + background: lighten(@editor-bg-dark, 1%); + color: @primary-text-color-dark; + border-color: @primary-bg-color-dark; + } +} \ No newline at end of file diff --git a/codeeditor-app/elements/code-editor-container/code-editor-container.ts b/codeeditor-app/elements/code-editor-container/code-editor-container.ts new file mode 100644 index 0000000..2796983 --- /dev/null +++ b/codeeditor-app/elements/code-editor-container/code-editor-container.ts @@ -0,0 +1,34 @@ +import { BaseElement } from "../../../shared/_base"; +import { CodeEditor } from "../code-editor/code-editor"; +import { ResizeHandle } from "../resize-handle/resize-handle"; +import './code-editor-container.less'; + +export class CodeEditorContainer extends BaseElement { + onInit(): void { + var language = this.getAttribute("language"); + this.classList.add("flex-vert"); + if (language) { + this.setHeader(language); + this.createCodeEditor(language, this.getAttribute("dir")?.toLowerCase().indexOf("v") === 0 ? 'vertical' : 'horizontal'); + } + } + + setHeader(title: string) { + var div = document.createElement("div"); + div.classList.add("code-editor-header"); + div.classList.add("flex-shrink"); + div.innerText = title; + + this.appendChild(div); + } + + createCodeEditor(language: string, expandDir: 'vertical' | 'horizontal') { + var codeEditor = document.createElement("code-editor"); + codeEditor.setAttribute("language", language); + var resizeHandle = document.createElement("resize-handle"); + resizeHandle.setAttribute("dir", expandDir); + + this.appendChild(codeEditor); + this.appendChild(resizeHandle); + } +} \ No newline at end of file diff --git a/codeeditor-app/elements/code-editor/code-editor.ts b/codeeditor-app/elements/code-editor/code-editor.ts index 0dbffc3..25f231b 100644 --- a/codeeditor-app/elements/code-editor/code-editor.ts +++ b/codeeditor-app/elements/code-editor/code-editor.ts @@ -2,24 +2,29 @@ import { BaseElement } from "../../../shared/_base"; import './code-editor.less'; import * as monaco from 'monaco-editor'; import { OutputFrame } from "../output-frame/output-frame"; -import initScript from '!!raw-loader!./injects/editor-init.js'; +import initJs from '!!raw-loader!./injects/editor-init.js'; +import initHtml from '!!raw-loader!./injects/editor-init.html'; +import initCss from '!!raw-loader!./injects/editor-init.css'; import spiralBoxesSolutionScript from '!!raw-loader!./injects/spiral-boxes-solution.js'; import { debounceManager } from "../../../shared/ensure-debounce"; import declarations from '!!raw-loader!./injects/declarations.d.ts'; export class CodeEditor extends BaseElement { - public input: string = initScript; + public input: string = ""; + public language: string = ""; onInit(): void { this.initWorkers(); + this.language = this.getAttribute("language")?.toLocaleLowerCase() ?? "javascript"; + this.input = this.setInitInput(this.language); const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)"); var editor = monaco.editor.create(this, { value: this.input, - language: 'javascript', + language: this.language, automaticLayout: true, contextmenu: false, minimap: { @@ -29,12 +34,45 @@ export class CodeEditor extends BaseElement { theme: prefersDarkScheme.matches ? 'vs-dark' : 'vs-light' }); + + + + var debounce = debounceManager(1000); + editor.onDidChangeModelContent((e) => { + this.input = editor.getValue(); + debounce.ensureDebounce(() => { + this.outputFrame.setScript(this.language, this.input); + }); + }); + + this.waitFor(this.outputFrame, () => { + this.outputFrame.setScript(this.language, this.input); + }); + + } + + setInitInput(language: string | null) { + if (language) { + if (language.toLowerCase() === "javascript") { + this.initJs(); + return initJs; + } + else if (language.toLowerCase() === 'html') { + return initHtml; + } + else if (language.toLowerCase() === 'css') { + return initCss; + } + } + return ""; + } + + initJs() { monaco.languages.typescript.javascriptDefaults.addExtraLib(declarations, 'ts:filename/declarations.d.ts'); monaco.languages.registerCompletionItemProvider("javascript", { - triggerCharacters: ["."], - provideCompletionItems: (model, position, context, token) => ( - { + provideCompletionItems: (model, position, context, token) => { + return { suggestions: [ { label: 'for', @@ -60,24 +98,11 @@ export class CodeEditor extends BaseElement { } ] } - ) + } }); - - - var debounce = debounceManager(1000); - editor.onDidChangeModelContent((e) => { - this.input = editor.getValue(); - debounce.ensureDebounce(() => { - this.outputFrame.setScript(this.input); - }); - }); - - this.waitFor(this.outputFrame, () => { - this.outputFrame.setScript(this.input); - }); - } + initWorkers() { // @ts-ignore self.MonacoEnvironment = { diff --git a/codeeditor-app/elements/code-editor/injects/editor-init.css b/codeeditor-app/elements/code-editor/injects/editor-init.css new file mode 100644 index 0000000..47e1dc8 --- /dev/null +++ b/codeeditor-app/elements/code-editor/injects/editor-init.css @@ -0,0 +1,2 @@ +body { +} \ No newline at end of file diff --git a/codeeditor-app/elements/code-editor/injects/editor-init.html b/codeeditor-app/elements/code-editor/injects/editor-init.html new file mode 100644 index 0000000..094418b --- /dev/null +++ b/codeeditor-app/elements/code-editor/injects/editor-init.html @@ -0,0 +1,2 @@ +
+
\ No newline at end of file diff --git a/codeeditor-app/elements/code-editor/injects/editor-init.js b/codeeditor-app/elements/code-editor/injects/editor-init.js index 2e07dda..a74da39 100644 --- a/codeeditor-app/elements/code-editor/injects/editor-init.js +++ b/codeeditor-app/elements/code-editor/injects/editor-init.js @@ -1,4 +1,17 @@ +//create a box box(10, 10, 20, 20); + +//use random: box(50, rand(100, 200), 20, 20); + +//use random color: box(160, 50, 20, 20, randomColor()); -box(center(), center(), 20, 20); \ No newline at end of file + +//center method for center of canvas: +box(center() - 10, center() - 10, 20, 20); + +//width and height gives canvas size: +box(width() - 20, height() - 20, 20, 20); + +//rect will not be connected to boxes: +rect(300, 200, 40, 40, "green"); \ No newline at end of file diff --git a/codeeditor-app/elements/notification-bubbles/notification-bubbles.ts b/codeeditor-app/elements/notification-bubbles/notification-bubbles.ts index dc6a479..d886910 100644 --- a/codeeditor-app/elements/notification-bubbles/notification-bubbles.ts +++ b/codeeditor-app/elements/notification-bubbles/notification-bubbles.ts @@ -4,7 +4,7 @@ import { NotificationBubble } from "./notification-bubble"; import './notification-bubbles.less'; export class NotificationBubbles extends BaseElement { - add(text: string, type: 'log' | 'info' | 'warn' | 'error' | 'syntax-error' | 'script' | 'time' | 'response') { + add(text: string, type: 'log' | 'info' | 'warn' | 'error' | 'syntax-error' | 'script' | 'time' | 'response' | string) { if (text && type) { var bubble = document.createElement("notification-bubble"); diff --git a/codeeditor-app/elements/output-frame/output-frame.ts b/codeeditor-app/elements/output-frame/output-frame.ts index 6580019..ff2215e 100644 --- a/codeeditor-app/elements/output-frame/output-frame.ts +++ b/codeeditor-app/elements/output-frame/output-frame.ts @@ -6,12 +6,14 @@ import { postMessage } from "../../../shared/post-message"; export class OutputFrame extends BaseElement { public Iframe: HTMLIFrameElement | null = null; - public CurrentIframeScriptRemoveResponseListener: Function | null = null; + private CurrentIframeScriptRemoveResponseListener: Function | null = null; constructor() { super(); } + private scripts: any = {}; + onInit(): void { this.reset(); } @@ -19,7 +21,7 @@ export class OutputFrame extends BaseElement { onUpdate(): void { } - reset(resetNotificationBubbles: boolean = true) { + reset(onload: ((ev: Event) => void) | null = null, resetNotificationBubbles: boolean = true) { if (this.CurrentIframeScriptRemoveResponseListener != null) { this.CurrentIframeScriptRemoveResponseListener(); this.CurrentIframeScriptRemoveResponseListener = null; @@ -32,6 +34,13 @@ export class OutputFrame extends BaseElement { this.Iframe = document.createElement("iframe"); this.Iframe.setAttribute("sandbox", "allow-pointer-lock allow-same-origin allow-scripts"); this.Iframe.src = GetOutputFrameUrl(); + + if (onload) { + this.Iframe.onload = ((ev) => { + onload(ev); + }); + } + this.appendChild(this.Iframe); if (resetNotificationBubbles) { @@ -43,41 +52,34 @@ export class OutputFrame extends BaseElement { } setError() { - this.reset(); - if (this.Iframe) { - this.Iframe.src = GetOutputFrameUrl() + "execution-time-error.html"; - } - } - - preventInfiniteLoop(value: string) { - return value.replace("while (true)", "while (false)"); - } - - setScript(value: string) { - this.reset(); - this.onIframeLoaded(() => { - this.CurrentIframeScriptRemoveResponseListener = postMessage(this.Iframe?.contentWindow, "script", value, (executeTimeInMs, exceedTimeInMs) => { - if (executeTimeInMs === -1) { - this.setError(); - } - else { - var bubbles = GetNotificationBubbles(); - bubbles.add("code executed in: " + executeTimeInMs + "ms", "info"); - } - }); + this.reset(() => { + if (this.Iframe) + this.Iframe.src = GetOutputFrameUrl() + "execution-time-error.html"; }); } - onIframeLoaded(fn: Function) { - if (this.Iframe) { - this.Iframe.onload = () => { - fn(); + setScript(language: string, value: string) { + this.scripts[language] = value; + this.reset(() => { + for (const lang in this.scripts) { + if (lang != "javascript") + this.doPostMessage(lang, this.scripts[lang]); } - } - else { - this.waitFor(this.Iframe, () => { - this.onIframeLoaded(fn); - }); - } + + if (this.scripts["javascript"]) + this.doPostMessage("javascript", this.scripts["javascript"]); + }); + } + + doPostMessage(language: string, value: string) { + this.CurrentIframeScriptRemoveResponseListener = postMessage(this.Iframe?.contentWindow, language, this.scripts[language], language == 'javascript' ? (executeTimeInMs, exceedTimeInMs) => { + if (executeTimeInMs === -1) { + this.setError(); + } + else { + var bubbles = GetNotificationBubbles(); + bubbles.add(language + " executed in: " + executeTimeInMs + "ms", "info"); + } + } : undefined); } } \ No newline at end of file diff --git a/codeeditor-app/elements/resize-handle/resize-handle.less b/codeeditor-app/elements/resize-handle/resize-handle.less index 650c2ed..361f49f 100644 --- a/codeeditor-app/elements/resize-handle/resize-handle.less +++ b/codeeditor-app/elements/resize-handle/resize-handle.less @@ -2,20 +2,42 @@ resize-handle { background: transparent; - position: absolute; - top: 0; - bottom: 0; - width: 8px; + position: absolute !important; z-index: 9; cursor: ew-resize; transition: 1s ease-in; + flex: none !important; } -resize-handle:hover { +resize-handle[dir="horizontal"] { + cursor: ew-resize; + width: 8px; + top: 0; + bottom: 0; +} + +resize-handle[dir="vertical"] { + cursor: ns-resize; + height: 8px; + left: 0; + right: 0; + bottom: 0; +} + +resize-handle:hover, resize-handle.active { background: @primary-interaction-highlight-white; transition: none; } +#fixed-resize-overlay { + position: fixed; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 10; +} + @media (prefers-color-scheme: dark) { resize-handle:hover { diff --git a/codeeditor-app/elements/resize-handle/resize-handle.ts b/codeeditor-app/elements/resize-handle/resize-handle.ts index 65ddf91..89f7a3b 100644 --- a/codeeditor-app/elements/resize-handle/resize-handle.ts +++ b/codeeditor-app/elements/resize-handle/resize-handle.ts @@ -4,7 +4,6 @@ import './resize-handle.less'; export class ResizeHandle extends BaseElement { - public isDowned = false; onInit(): void { this.setDragEvents(); @@ -12,54 +11,120 @@ export class ResizeHandle extends BaseElement { setDragEvents() { this.addEventListener("mousedown", (evt: MouseEvent) => { - this.isDowned = true; - this.getIframe().style.display = "none"; - }); + evt.preventDefault(); + evt.stopPropagation(); + this.setOverlay({ + x: evt.pageX, + y: evt.pageY + }); - window.addEventListener("mousemove", (evt: MouseEvent) => { - if (this.isDowned) { - this.setPosition(evt.pageX); - } - }); - - window.addEventListener("mouseup", (evt: MouseEvent) => { - if (this.isDowned) { - this.getIframe().style.display = "block"; - this.isDowned = false; - } }); this.addEventListener("touchstart", (evt: TouchEvent) => { - this.isDowned = true; - this.getIframe().style.display = "none"; - }); - - window.addEventListener("touchmove", (evt: TouchEvent) => { - if (this.isDowned) { - this.setPosition(evt.touches[0].pageX); - } - }); - - window.addEventListener("touchend", (evt: TouchEvent) => { - if (this.isDowned) { - this.getIframe().style.display = "block"; - this.isDowned = false; - } + evt.preventDefault(); + evt.stopPropagation(); + this.setOverlay({ + x: evt.touches[0].pageX, + y: evt.touches[0].pageY + }); }); } - setPosition(x: number) { + setSize(x: number, y: number) { var t = this.getTarget(); - t.style.minWidth = x + "px"; - t.style.maxWidth = x + "px"; + if (t) { + if (this.directionIsHorizontal()) { + t.style.minWidth = x + "px"; + t.style.maxWidth = x + "px"; + } + else { + t.style.minHeight = y + "px"; + t.style.maxHeight = y + "px"; + } + } } getTarget() { - return this.find("code-editor"); + var targetAttr = this.getAttribute("target"); + if (targetAttr) { + var target = document.querySelector(targetAttr); + + if (target) { + return target; + } + } + + return this.parentElement; + } + + directionIsHorizontal() { + var attr = this.getAttribute("dir"); + + if (attr) { + if (attr.toLowerCase().indexOf('v') === 0) { + return false; + } + } + + return true; } getIframe() { return this.find("iframe"); } + + setOverlay(start: { x: number, y: number }) { + var overlay = document.createElement("div"); + overlay.setAttribute("id", "fixed-resize-overlay"); + + var targetStartSize = { width: 0, height: 0 }; + + var target = this.getTarget(); + if (target) { + var bounds = target.getBoundingClientRect(); + + targetStartSize = { + width: bounds.width, + height: bounds.height + }; + } + + overlay.addEventListener("mousemove", (evt: MouseEvent) => { + this.getIframe().style.pointerEvents = "none"; + this.classList.add("active"); + this.setSize(targetStartSize.width + (evt.pageX - start.x), targetStartSize.height + (evt.pageY - start.y)); + }); + + overlay.addEventListener("mouseup", (evt: MouseEvent) => { + this.getIframe().style.pointerEvents = "all"; + this.classList.remove("active"); + this.removeOverlay(); + }); + + overlay.addEventListener("touchmove", (evt: TouchEvent) => { + this.getIframe().style.pointerEvents = "none"; + this.classList.add("active"); + this.setSize(targetStartSize.width + (start.x - evt.touches[0].pageX), targetStartSize.height + (start.y - evt.touches[0].pageY)); + }); + + overlay.addEventListener("touchend", (evt: TouchEvent) => { + this.getIframe().style.pointerEvents = "all"; + this.classList.remove("active"); + this.removeOverlay(); + }); + + document.body.classList.remove("animate"); + document.body.appendChild(overlay); + } + + removeOverlay() { + var overlay = document.getElementById("fixed-resize-overlay"); + + if (overlay) { + overlay.parentElement?.removeChild(overlay); + } + + document.body.classList.add("animate"); + } } \ No newline at end of file diff --git a/codeeditor-app/elements/window-control/window-control connector.ts b/codeeditor-app/elements/window-control/window-control connector.ts new file mode 100644 index 0000000..e846390 --- /dev/null +++ b/codeeditor-app/elements/window-control/window-control connector.ts @@ -0,0 +1,74 @@ +import { BaseElement } from "../../../shared/_base"; +import { CodeEditorContainer } from "../code-editor-container/code-editor-container"; +import './window-control-connector.less'; + +export class WindowControlConnector extends BaseElement { + + onInit(): void { + this.addEventListener("click", () => { + this.toggle(); + }) + } + + toggle() { + if (this.classList.contains("active")) { + this.deactivate(); + } + else { + this.activate(); + } + } + + activate() { + this.classList.add("active"); + var target = this.getTarget(); + + if (target) { + target.classList.add("active"); + target.style.display = "block"; + } + + this.distributeEvenly(); + } + + deactivate() { + this.classList.remove("active"); + var target = this.getTarget(); + + if (target) { + target.classList.remove("active"); + target.style.display = "none"; + } + + this.distributeEvenly(); + } + + private distributeEvenly() { + var elements = this.findVisible("code-editor-container"); + var containers = document.getElementsByClassName("code-editor-containers")[0]; + + if (elements.length === 0) { + containers.classList.add("force-close"); + } + else { + containers.classList.remove("force-close"); + var size = (100 / elements.length) + "%"; + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + + element.style.minHeight = size; + element.style.maxHeight = size; + } + } + } + + private getTarget() { + var attr = this.getAttribute("target"); + if (attr) { + return document.querySelector(attr); + } + + return null; + } + +} \ No newline at end of file diff --git a/codeeditor-app/elements/window-control/window-control-connector.less b/codeeditor-app/elements/window-control/window-control-connector.less new file mode 100644 index 0000000..bde357a --- /dev/null +++ b/codeeditor-app/elements/window-control/window-control-connector.less @@ -0,0 +1,46 @@ + +@import url('../../../shared/theme.less'); + +window-control { + display: inline-block; + margin-left: 20px; +} + +window-control-connector { + position: relative; + font-size: 12px; + display: inline-block; + background: lighten(@editor-bg-white, 1%); + padding: 6px; + padding-right: 16px; + transition: all 200ms; + cursor: pointer; +} + +window-control-connector::after { + position: absolute; + content: "❌"; + font-size: 8px; + transform: rotate(45deg); + filter: grayscale(); + top: 4px; + right: 4px; + transition: all 200ms; + pointer-events: none; +} + +window-control-connector.active { + transform: translateY(7px); +} + +window-control-connector.active::after { + transform: rotate(0); + transform-origin: center; +} + +@media (prefers-color-scheme: dark) { + window-control-connector { + background: lighten(@editor-bg-dark, 1%); + color: @primary-text-color-dark; + } +} \ No newline at end of file diff --git a/codeeditor-app/elements/window-control/window-control.ts b/codeeditor-app/elements/window-control/window-control.ts new file mode 100644 index 0000000..f1474d9 --- /dev/null +++ b/codeeditor-app/elements/window-control/window-control.ts @@ -0,0 +1,18 @@ +import { BaseElement } from "../../../shared/_base"; +import { WindowControlConnector } from "./window-control connector"; + +export class WindowControl extends BaseElement { + + setActive(element: WindowControlConnector) { + for (let i = 0; i < this.children.length; i++) { + const child = this.children[i]; + + if (element == child) { + child.activate(); + } + else { + child.deactivate(); + } + } + } +} \ No newline at end of file diff --git a/codeeditor-app/static/index.html b/codeeditor-app/static/index.html index 19e3113..722a5dd 100644 --- a/codeeditor-app/static/index.html +++ b/codeeditor-app/static/index.html @@ -12,7 +12,7 @@ - + diff --git a/outputframe-app/boot.ts b/outputframe-app/boot.ts index d5fc47a..d5cb429 100644 --- a/outputframe-app/boot.ts +++ b/outputframe-app/boot.ts @@ -2,7 +2,7 @@ import './app.less'; import bootHtml from './app.html'; import { watchConsole } from './injects/watch-console'; import { setOnErrorListener } from './injects/on-error'; -import { createScript } from '../shared/create-script'; +import { createCss, createHtml, createScript } from '../shared/create-script'; import { messageListener } from '../shared/message-listener'; import { postMessage, postResponseMessage } from '../shared/post-message'; import { RenderCanvas } from './elements/render-canvas/render-canvas'; @@ -17,10 +17,16 @@ watchConsole((type, value) => { postMessage(parent, type, value); }); messageListener((type, value, id) => { - if (type == "script") { + if (type == "javascript") { createScript(value); postResponseMessage(parent, id, value); } + else if (type == "html") { + createHtml(value); + } + else if (type == "css") { + createCss(value); + } }); setOnErrorListener((type, value) => { postMessage(parent, type, value); diff --git a/outputframe-app/elements/render-canvas/render-canvas.ts b/outputframe-app/elements/render-canvas/render-canvas.ts index ea062bf..77e94a3 100644 --- a/outputframe-app/elements/render-canvas/render-canvas.ts +++ b/outputframe-app/elements/render-canvas/render-canvas.ts @@ -11,10 +11,10 @@ export class RenderCanvas extends BaseElement { width: 400, height: 400 } - public drawCount: number = 0; - public drawQueue: { points: { x: number, y: number }[], color?: string }[] = []; + private drawCount: number = 0; + private drawQueue: { points: { x: number, y: number }[], color?: string, connected?: boolean }[] = []; - public prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)"); + private prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)"); private theme: any; @@ -44,10 +44,12 @@ export class RenderCanvas extends BaseElement { this.canvas.style.height = smallest + "px"; this.canvas.setAttribute("width", smallest.toString()); this.canvas.setAttribute("height", smallest.toString()); + + this.draw(); } } - drawPoly(points: { x: number, y: number }[], color?: string | CanvasGradient) { + private drawPoly(points: { x: number, y: number }[], color?: string | CanvasGradient) { if (this.ctx && this.canvas) { this.canvas.style.display = "block"; if (color instanceof CanvasGradient) { @@ -77,7 +79,7 @@ export class RenderCanvas extends BaseElement { } } - centerOf(points: { x: number, y: number }[]) { + private centerOf(points: { x: number, y: number }[]) { var x = 0, y = 0, i, @@ -99,7 +101,7 @@ export class RenderCanvas extends BaseElement { return { x: x / f, y: y / f }; }; - areaOf(points: { x: number, y: number }[]) { + private areaOf(points: { x: number, y: number }[]) { var area = 0, i, j, @@ -117,50 +119,42 @@ export class RenderCanvas extends BaseElement { return area; }; - angleBetweenPoints(point1: { x: number, y: number }, point2: { x: number, y: number }) { - // angle in radians - var angleRadians = Math.atan2(point2.y - point1.y, point2.x - point1.x); - - // angle in degrees + private angleBetweenPoints(point1: { x: number, y: number }, point2: { x: number, y: number }) { return Math.atan2(point2.y - point1.y, point2.x - point1.x) * 180 / Math.PI; } - executeDrawQueue() { - let prevShape: any | null = null; + draw() { + let prevConnectedShape: any | null = null; for (let i = 0; i < this.drawQueue.length; i++) { const shape = this.drawQueue[i]; - if (prevShape != null && this.ctx) { - var prevCenter = this.centerOf(prevShape.points); + if (shape.connected && prevConnectedShape != null && this.ctx) { + var prevCenter = this.centerOf(prevConnectedShape.points); var thisCenter = this.centerOf(shape.points); - var middle = { - x: (prevCenter.x + thisCenter.x) / 2, - y: (prevCenter.y + thisCenter.y) / 2 - }; - var grd = this.ctx.createLinearGradient(this.unitsToPx(prevCenter.x), this.unitsToPx(prevCenter.y), this.unitsToPx(thisCenter.x), this.unitsToPx(thisCenter.y)); - grd.addColorStop(0, prevShape?.color ?? this.getColor()); + grd.addColorStop(0, prevConnectedShape?.color ?? this.getColor()); grd.addColorStop(1, shape?.color ?? this.getColor()); this.drawPoly([prevCenter, thisCenter], grd); - - //this.drawPoly([prevCenter, middle], prevShape.color); - //this.drawPoly([middle, thisCenter], shape.color); } - prevShape = shape; + if (shape.connected) { + prevConnectedShape = shape; + } } for (let i = 0; i < this.drawQueue.length; i++) { const shape = this.drawQueue[i]; this.drawPoly(shape.points, shape.color); var thisCenter = this.centerOf(shape.points); - this.drawText(thisCenter.x, thisCenter.y, (i + 1).toString(), this.unitsToPx((shape.points[1].x - shape.points[0].x) * 0.6), shape.color); + if (shape.connected) { + this.drawText(thisCenter.x, thisCenter.y, (i + 1).toString(), this.unitsToPx((shape.points[1].x - shape.points[0].x) * 0.6), shape.color); + } } } - drawText(x: number, y: number, text: string, size: number = 20, color?: string) { + private drawText(x: number, y: number, text: string, size: number = 20, color?: string) { if (this.ctx && this.canvas) { this.ctx.moveTo(x, y); this.ctx.fillStyle = this.getColor(color); @@ -171,27 +165,23 @@ export class RenderCanvas extends BaseElement { } } - getColor(color?: string) { + private getColor(color?: string) { return color ? color : (this.prefersDarkScheme.matches ? this.theme["@draw-color-dark"] : this.theme["@draw-color-white"]); } - getBackgroundColor() { + private getBackgroundColor() { return this.prefersDarkScheme.matches ? this.theme["@median-bg-dark"] : this.theme["@median-bg-white"]; } - drawRect(x: number, y: number, width: number, height: number, color?: string) { - this.drawPoly([{ x: x, y: y }, { x: x + width, y: y }, { x: x + width, y: y + height }, { x: x, y: y + height }], color); - } - - queuePoly(points: { x: number, y: number }[], color?: string) { + poly(points: { x: number, y: number }[], color?: string) { this.drawQueue.push({ points: points, color: color }); } - queueRect(x: number, y: number, width: number, height: number, color?: string) { - this.drawQueue.push({ points: [{ x: x, y: y }, { x: x + width, y: y }, { x: x + width, y: y + height }, { x: x, y: y + height }], color: color }); + rect(x: number, y: number, width: number, height: number, color?: string, connected?: boolean) { + this.drawQueue.push({ points: [{ x: x, y: y }, { x: x + width, y: y }, { x: x + width, y: y + height }, { x: x, y: y + height }], color: color, connected: connected }); } - unitsToPx(unitNumber: number) { + private unitsToPx(unitNumber: number) { var newPPU = Math.min(this.width, this.height) / this.units.width; return Math.floor(unitNumber * newPPU); } diff --git a/outputframe-app/injects/dom-helpers.ts b/outputframe-app/injects/dom-helpers.ts index b94bf72..f1b1b07 100644 --- a/outputframe-app/injects/dom-helpers.ts +++ b/outputframe-app/injects/dom-helpers.ts @@ -6,12 +6,12 @@ export function clear() { export function rect(x: number, y: number, width: number, height: number, color?: string): void { var renderCanvas = document.getElementsByTagName("render-canvas")[0]; - renderCanvas.drawRect(x, y, width, height, color); + renderCanvas.rect(x, y, width, height, color, false); } export function box(x: number, y: number, width: number, height: number, color?: string): void { var renderCanvas = document.getElementsByTagName("render-canvas")[0]; - renderCanvas.queueRect(x, y, width, height, color); + renderCanvas.rect(x, y, width, height, color, true); } export function getWidth(): number { @@ -35,7 +35,7 @@ export function getRandom(min: number, max: number): number { export function executeDrawQueue(): void { var renderCanvas = document.getElementsByTagName("render-canvas")[0]; - renderCanvas.executeDrawQueue(); + renderCanvas.draw(); } export function getRandomColor(): string { diff --git a/shared/_base.ts b/shared/_base.ts index c90c3e5..82bb140 100644 --- a/shared/_base.ts +++ b/shared/_base.ts @@ -67,6 +67,22 @@ export class BaseElement extends HTMLElement { return null; } + + findVisible(tagName: string) { + var result = []; + + var elements = document.getElementsByTagName(tagName); + + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + + if (element.style.display !== "none") { + result.push(element); + } + } + + return result; + } hasChild(element: Element, startsWidth: boolean = false): boolean { var ele = this; diff --git a/shared/create-script.ts b/shared/create-script.ts index a75b403..2119c3f 100644 --- a/shared/create-script.ts +++ b/shared/create-script.ts @@ -8,4 +8,18 @@ export function createScript(value: string) { document.body.appendChild(script); executeDrawQueue(); +} + +export function createHtml(value: string) { + var div: HTMLDivElement = document.createElement("div"); + div.innerHTML = value; + + document.body.appendChild(div); +} + +export function createCss(value: string) { + var style: HTMLStyleElement = document.createElement("style"); + style.innerHTML = value; + + document.body.appendChild(style); } \ No newline at end of file diff --git a/shared/message-listener.ts b/shared/message-listener.ts index 75c3a29..58c0f72 100644 --- a/shared/message-listener.ts +++ b/shared/message-listener.ts @@ -1,4 +1,4 @@ -export function messageListener(fn: (type: 'script' | 'log' | 'info' | 'warn' | 'error' | 'syntax-error' | 'time' | 'response', value: string, id: number) => void): Function { +export function messageListener(fn: (type: 'script' | 'log' | 'info' | 'warn' | 'error' | 'syntax-error' | 'time' | 'response' | string, value: string, id: number) => void): Function { var evt = function(e: any) { if (!e.data || e.data === '' || e.data.type === 'webpackOk') {