working lambda editor

This commit is contained in:
Elizabeth Hunt 2024-03-04 17:06:57 -07:00
parent ccc1e182cf
commit 8288d08648
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
4 changed files with 124 additions and 115 deletions

View File

@ -2,7 +2,7 @@
.code { .code {
width: 100%; width: 100%;
font-size: 1.5rem; font-size: 1.25rem;
} }
.syntax-error { .syntax-error {

View File

@ -22,7 +22,7 @@
margin: auto; margin: auto;
padding: 20px; padding: 20px;
border: 1px solid var(--yellow); border: 1px solid var(--yellow);
width: 40%; width: 60%;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
animation: scaleUp 0.25s; animation: scaleUp 0.25s;
border-radius: 8px; border-radius: 8px;

View File

@ -1,6 +1,7 @@
@import url("./theme.css"); @import url("./theme.css");
@import url("./tf.css"); @import url("./tf.css");
@import url("./modal.css"); @import url("./modal.css");
@import url("./editor.css");
@font-face { @font-face {
font-family: "scientifica"; font-family: "scientifica";
@ -95,10 +96,6 @@ a:visited {
margin: 0; margin: 0;
} }
.code {
width: 100%;
}
button { button {
padding: 0.5rem; padding: 0.5rem;
border: none; border: none;
@ -108,12 +105,6 @@ button {
cursor: pointer; cursor: pointer;
transition: background 0.2s ease-in-out; transition: background 0.2s ease-in-out;
} }
button:hover { button:hover {
background-color: var(--blue); background-color: var(--blue);
} }
.syntax-error {
color: var(--red);
background-color: var(--yellow);
}

View File

@ -35,6 +35,9 @@ import { parse } from "../../interpreter";
interface CodeEditorState { interface CodeEditorState {
view: EditorView; view: EditorView;
editorElement: HTMLElement; editorElement: HTMLElement;
syntaxError: HTMLElement;
canvas: HTMLCanvasElement;
closeButton: HTMLButtonElement;
} }
const highlightEffect = StateEffect.define<Range<Decoration>[]>(); const highlightEffect = StateEffect.define<Range<Decoration>[]>();
@ -54,12 +57,16 @@ const highlightExtension = StateField.define({
}, },
provide: (f) => EditorView.decorations.from(f), provide: (f) => EditorView.decorations.from(f),
}); });
const FontSizeTheme = EditorView.theme({ const FontSizeTheme = EditorView.theme({
$: { $: {
fontSize: "20pt", fontSize: "16pt",
}, },
}); });
const FontSizeThemeExtension: Extension = [FontSizeTheme]; const FontSizeThemeExtension: Extension = [FontSizeTheme];
const syntaxErrorDecoration = Decoration.mark({
class: "syntax-error",
});
export class LambdaFactory extends Entity { export class LambdaFactory extends Entity {
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
@ -67,13 +74,11 @@ export class LambdaFactory extends Entity {
) as SpriteSpec; ) as SpriteSpec;
private codeEditorState: CodeEditorState | null; private codeEditorState: CodeEditorState | null;
private code: string;
private spawns: number; private spawns: number;
constructor(gridPosition: Coord2D, code: string, spawns: number) { constructor(gridPosition: Coord2D, code: string, spawns: number) {
super(EntityNames.LambdaFactory); super(EntityNames.LambdaFactory);
this.code = code;
this.spawns = spawns; this.spawns = spawns;
this.codeEditorState = null; this.codeEditorState = null;
@ -95,7 +100,7 @@ export class LambdaFactory extends Entity {
this.addComponent(new Colliding()); this.addComponent(new Colliding());
this.addComponent(new LambdaSpawn(this.spawns, this.code)); this.addComponent(new LambdaSpawn(this.spawns, code));
this.addComponent(new Grid(gridPosition)); this.addComponent(new Grid(gridPosition));
@ -134,115 +139,128 @@ export class LambdaFactory extends Entity {
this.addComponent(text); this.addComponent(text);
} }
private openCodeEditor() {
const modalContent =
"<div class='code'><div id='code'></div><br><p id='syntax-error' class='error'></p><button id='close-modal'>Save</button></div>";
openModal(modalContent);
const { code } = this.getComponent<LambdaSpawn>(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<LambdaSpawn>(ComponentNames.LambdaSpawn);
spawner.code = text;
this.addComponent(spawner);
view.destroy();
editorElement.innerHTML = "";
this.codeEditorState = null;
closeModal();
canvas.focus();
}
private onHighlight(direction: Direction) { private onHighlight(direction: Direction) {
if (direction === Direction.LEFT || direction === Direction.RIGHT) { if (direction === Direction.LEFT || direction === Direction.RIGHT) {
this.addComponent(new Interactable(() => this.spawnNewLambda(direction))); this.addComponent(new Interactable(() => this.spawnNewLambda(direction)));
return; 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<LambdaSpawn>(
ComponentNames.LambdaSpawn,
);
spawner.code = text;
this.addComponent(spawner);
}
modalOpen = false;
closeModal();
document.getElementById(Miscellaneous.CANVAS_ID)!.focus();
return;
};
const interaction = () => { const interaction = () => {
if (modalOpen) { if (this.codeEditorState) {
close(); this.saveAndCloseCodeEditor();
return; return;
} }
modalOpen = true;
openModal( this.openCodeEditor();
"<div class='code'><div id='code'></div><br><p id='syntax-error' class='error'></p><button id='close-modal'>Save</button></div>",
);
const spawner = this.getComponent<LambdaSpawn>(
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.addComponent(new Interactable(interaction)); this.addComponent(new Interactable(interaction));