working lambda editor
This commit is contained in:
parent
ccc1e182cf
commit
8288d08648
@ -2,7 +2,7 @@
|
||||
|
||||
.code {
|
||||
width: 100%;
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.syntax-error {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ import { parse } from "../../interpreter";
|
||||
interface CodeEditorState {
|
||||
view: EditorView;
|
||||
editorElement: HTMLElement;
|
||||
syntaxError: HTMLElement;
|
||||
canvas: HTMLCanvasElement;
|
||||
closeButton: HTMLButtonElement;
|
||||
}
|
||||
|
||||
const highlightEffect = StateEffect.define<Range<Decoration>[]>();
|
||||
@ -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 =
|
||||
"<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) {
|
||||
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<LambdaSpawn>(
|
||||
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(
|
||||
"<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.openCodeEditor();
|
||||
};
|
||||
|
||||
this.addComponent(new Interactable(interaction));
|
||||
|
Loading…
Reference in New Issue
Block a user