From 8288d086480d88ac4237f19c194d5829157bac38 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Mon, 4 Mar 2024 17:06:57 -0700 Subject: [PATCH] working lambda editor --- src/css/editor.css | 2 +- src/css/modal.css | 2 +- src/css/style.css | 11 +- src/engine/entities/LambdaFactory.ts | 224 +++++++++++++++------------ 4 files changed, 124 insertions(+), 115 deletions(-) diff --git a/src/css/editor.css b/src/css/editor.css index 9b1588e..8ce5a81 100644 --- a/src/css/editor.css +++ b/src/css/editor.css @@ -2,7 +2,7 @@ .code { width: 100%; - font-size: 1.5rem; + font-size: 1.25rem; } .syntax-error { diff --git a/src/css/modal.css b/src/css/modal.css index c10070e..802dc4d 100644 --- a/src/css/modal.css +++ b/src/css/modal.css @@ -22,7 +22,7 @@ margin: auto; padding: 20px; border: 1px solid var(--yellow); - width: 40%; + width: 60%; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); animation: scaleUp 0.25s; border-radius: 8px; diff --git a/src/css/style.css b/src/css/style.css index c14682b..35fdc31 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -1,6 +1,7 @@ @import url("./theme.css"); @import url("./tf.css"); @import url("./modal.css"); +@import url("./editor.css"); @font-face { font-family: "scientifica"; @@ -95,10 +96,6 @@ a:visited { margin: 0; } -.code { - width: 100%; -} - button { padding: 0.5rem; border: none; @@ -108,12 +105,6 @@ button { cursor: pointer; transition: background 0.2s ease-in-out; } - button:hover { background-color: var(--blue); } - -.syntax-error { - color: var(--red); - background-color: var(--yellow); -} diff --git a/src/engine/entities/LambdaFactory.ts b/src/engine/entities/LambdaFactory.ts index 49436fe..1c897eb 100644 --- a/src/engine/entities/LambdaFactory.ts +++ b/src/engine/entities/LambdaFactory.ts @@ -35,6 +35,9 @@ import { parse } from "../../interpreter"; interface CodeEditorState { view: EditorView; editorElement: HTMLElement; + syntaxError: HTMLElement; + canvas: HTMLCanvasElement; + closeButton: HTMLButtonElement; } const highlightEffect = StateEffect.define[]>(); @@ -54,12 +57,16 @@ const highlightExtension = StateField.define({ }, provide: (f) => EditorView.decorations.from(f), }); + const FontSizeTheme = EditorView.theme({ $: { - fontSize: "20pt", + fontSize: "16pt", }, }); const FontSizeThemeExtension: Extension = [FontSizeTheme]; +const syntaxErrorDecoration = Decoration.mark({ + class: "syntax-error", +}); export class LambdaFactory extends Entity { private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( @@ -67,13 +74,11 @@ export class LambdaFactory extends Entity { ) as SpriteSpec; private codeEditorState: CodeEditorState | null; - private code: string; private spawns: number; constructor(gridPosition: Coord2D, code: string, spawns: number) { super(EntityNames.LambdaFactory); - this.code = code; this.spawns = spawns; this.codeEditorState = null; @@ -95,7 +100,7 @@ export class LambdaFactory extends Entity { this.addComponent(new Colliding()); - this.addComponent(new LambdaSpawn(this.spawns, this.code)); + this.addComponent(new LambdaSpawn(this.spawns, code)); this.addComponent(new Grid(gridPosition)); @@ -134,115 +139,128 @@ export class LambdaFactory extends Entity { this.addComponent(text); } + private openCodeEditor() { + const modalContent = + "

"; + openModal(modalContent); + + const { code } = this.getComponent(ComponentNames.LambdaSpawn); + const startState = EditorState.create({ + doc: code, + extensions: [ + basicSetup, + keymap.of(defaultKeymap), + rainbowBrackets(), + highlightExtension, + FontSizeThemeExtension, + ], + }); + + const codeBox = document.getElementById("code")!; + const syntaxError = document.getElementById("syntax-error")!; + const canvas = document.getElementById( + Miscellaneous.CANVAS_ID, + ) as HTMLCanvasElement; + const closeButton = document.getElementById( + "close-modal", + ) as HTMLButtonElement; + closeButton.addEventListener("click", () => this.saveAndCloseCodeEditor()); + + const editorView = new EditorView({ + state: startState, + parent: codeBox, + }); + editorView.focus(); + + this.codeEditorState = { + view: editorView, + editorElement: codeBox, + syntaxError, + canvas, + closeButton, + }; + } + + private refreshCodeEditorText(text: string) { + if (!this.codeEditorState) { + return; + } + const { view } = this.codeEditorState; + + view.dispatch({ + changes: { + from: 0, + to: text.length, + insert: "", + }, + }); + view.dispatch({ + changes: { + from: 0, + to: 0, + insert: text, + }, + }); + } + + private saveAndCloseCodeEditor() { + if (!this.codeEditorState) { + return; + } + + const { canvas, view, editorElement, syntaxError } = this.codeEditorState; + const text = view.state.doc.toString(); + this.refreshCodeEditorText(text); + syntaxError.innerText = ""; + + try { + parse(text); + } catch (e: any) { + if (!e.location) { + return; + } + const { + location: { + start: { offset: start }, + end: { offset: end }, + }, + } = e; + + view.dispatch({ + effects: highlightEffect.of([ + syntaxErrorDecoration.range(start === end ? start - 1 : start, end), + ]), + }); + + syntaxError.innerText = e.message; + return; + } + + const spawner = this.getComponent(ComponentNames.LambdaSpawn); + spawner.code = text; + this.addComponent(spawner); + + view.destroy(); + editorElement.innerHTML = ""; + this.codeEditorState = null; + closeModal(); + + canvas.focus(); + } + private onHighlight(direction: Direction) { if (direction === Direction.LEFT || direction === Direction.RIGHT) { this.addComponent(new Interactable(() => this.spawnNewLambda(direction))); return; } - let modalOpen = false; - let editorView: EditorView | null = null; - - const syntaxErrorDecoration = Decoration.mark({ - class: "syntax-error", - }); - - const close = () => { - if (editorView) { - const text = editorView.state.doc.toString(); - - // remove all text from the editor - editorView.dispatch({ - changes: { - from: 0, - to: text.length, - insert: "", - }, - }); - // add the new text to the editor - editorView.dispatch({ - changes: { - from: 0, - to: 0, - insert: text, - }, - }); - - try { - parse(text); - } catch (e: any) { - if (!e.location) { - return; - } - const { - location: { - start: { offset: start }, - end: { offset: end }, - }, - } = e; - - editorView.dispatch({ - effects: highlightEffect.of([ - syntaxErrorDecoration.range( - start === end ? start - 1 : start, - end, - ), - ]), - }); - - document.getElementById("syntax-error")!.innerText = e.message; - - return; - } - - const spawner = this.getComponent( - ComponentNames.LambdaSpawn, - ); - spawner.code = text; - this.addComponent(spawner); - } - - modalOpen = false; - closeModal(); - - document.getElementById(Miscellaneous.CANVAS_ID)!.focus(); - return; - }; - const interaction = () => { - if (modalOpen) { - close(); + if (this.codeEditorState) { + this.saveAndCloseCodeEditor(); return; } - modalOpen = true; - openModal( - "

", - ); - const spawner = this.getComponent( - ComponentNames.LambdaSpawn, - ); - - const startState = EditorState.create({ - doc: spawner.code, - extensions: [ - basicSetup, - keymap.of(defaultKeymap), - rainbowBrackets(), - highlightExtension, - FontSizeThemeExtension, - ], - }); - - const codeBox = document.getElementById("code")!; - editorView = new EditorView({ - state: startState, - parent: codeBox, - }); - - editorView.focus(); - - document.getElementById("close-modal")!.addEventListener("click", close); + this.openCodeEditor(); }; this.addComponent(new Interactable(interaction));