working lambda editor
This commit is contained in:
parent
ccc1e182cf
commit
8288d08648
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
.code {
|
.code {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 1.5rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.syntax-error {
|
.syntax-error {
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
|
@ -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));
|
||||||
|
Loading…
Reference in New Issue
Block a user