checkpoint
This commit is contained in:
parent
f4ad269f8b
commit
d74523d15d
136
package-lock.json
generated
136
package-lock.json
generated
@ -8,6 +8,8 @@
|
||||
"name": "the-abstraction-engine",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"codemirror": "^6.0.1",
|
||||
"rainbowbrackets": "github:eriknewland/rainbowbrackets",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
@ -392,6 +394,82 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.13.0.tgz",
|
||||
"integrity": "sha512-SuDrho1klTINfbcMPnyro1ZxU9xJtwDMtb62R8TjL/tOl71IoOsvBo1a9x+hDvHhIzkTcJHy2VC+rmpGgYkRSw==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.3.3.tgz",
|
||||
"integrity": "sha512-dO4hcF0fGT9tu1Pj1D2PvGvxjeGkbC6RGcZw6Qs74TH+Ed1gw98jmUgd2axWvIZEqTeTuFrg1lEB1KV6cK9h1A==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
|
||||
"integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.1.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.5.0.tgz",
|
||||
"integrity": "sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
|
||||
"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
|
||||
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.25.0.tgz",
|
||||
"integrity": "sha512-XnMGOm6qXB8znzCko0N7k97qZayVdvqpA0JebxA5fHtgBjC/XlCPhH9TK92TahsoCKMPQlaTCUep06Dwj/+GXQ==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
|
||||
@ -956,6 +1034,27 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
|
||||
"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ=="
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
|
||||
"integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/lr": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
|
||||
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -1641,6 +1740,20 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
|
||||
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
@ -1668,6 +1781,11 @@
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@ -2814,6 +2932,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/rainbowbrackets": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "git+ssh://git@github.com/eriknewland/rainbowbrackets.git#9352b73c715aac548690936ac393e0cf6e50cb98",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/view": "^6.9.5"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
@ -3039,6 +3165,11 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@ -3221,6 +3352,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -10,6 +10,8 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"codemirror": "^6.0.1",
|
||||
"rainbowbrackets": "github:eriknewland/rainbowbrackets",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
@ -4,8 +4,8 @@ import { Miscellaneous } from "./engine/config";
|
||||
export const App = () => {
|
||||
return (
|
||||
<div className="main">
|
||||
<div id="modal" className="modal">
|
||||
<div className="modal-content">
|
||||
<div id={Miscellaneous.MODAL_ID} className="modal">
|
||||
<div id={Miscellaneous.MODAL_CONTENT_ID} className="modal-content">
|
||||
<span className="close">×</span>
|
||||
<p>Some text in the Modal..</p>
|
||||
</div>
|
||||
|
@ -94,3 +94,26 @@ a:visited {
|
||||
border-radius: 0.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.code {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--yellow);
|
||||
color: var(--bg);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: var(--blue);
|
||||
}
|
||||
|
||||
.syntax-error {
|
||||
color: var(--red);
|
||||
background-color: var(--yellow);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ export class TheAbstractionEngine {
|
||||
const wall = new Wall({ x: 5, y: 3 });
|
||||
this.game.addEntity(wall);
|
||||
|
||||
const factory = new LambdaFactory({ x: 6, y: 6 }, "(λ (x) . x)", 10);
|
||||
const factory = new LambdaFactory({ x: 3, y: 3 }, "(λ (x) . x)", 10);
|
||||
this.game.addEntity(factory);
|
||||
|
||||
const lockedDoor = new LockedDoor({ x: 8, y: 8 });
|
||||
|
@ -60,5 +60,6 @@ export namespace Miscellaneous {
|
||||
export const GRID_CELL_HEIGHT = Math.floor(HEIGHT / GRID_ROWS);
|
||||
|
||||
export const MODAL_ID = "modal";
|
||||
export const MODAL_CONTENT_ID = "modal-content";
|
||||
export const CANVAS_ID = "canvas";
|
||||
}
|
||||
|
@ -19,12 +19,42 @@ import {
|
||||
} from "../components";
|
||||
import { Coord2D, Direction } from "../interfaces";
|
||||
import { openModal, closeModal } from "../utils";
|
||||
import { EditorState, StateField, StateEffect, Range } from "@codemirror/state";
|
||||
import { Decoration, EditorView, keymap } from "@codemirror/view";
|
||||
import { defaultKeymap } from "@codemirror/commands";
|
||||
import rainbowBrackets from "rainbowbrackets";
|
||||
import { basicSetup } from "codemirror";
|
||||
import { parse } from "../../interpreter";
|
||||
|
||||
interface CodeEditorState {
|
||||
view: EditorView;
|
||||
editorElement: HTMLElement;
|
||||
}
|
||||
|
||||
const highlightEffect = StateEffect.define<Range<Decoration>[]>();
|
||||
const highlightExtension = StateField.define({
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(value, transaction) {
|
||||
value = value.map(transaction.changes);
|
||||
|
||||
for (let effect of transaction.effects) {
|
||||
if (effect.is(highlightEffect))
|
||||
value = value.update({ add: effect.value, sort: true });
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
});
|
||||
|
||||
export class LambdaFactory extends Entity {
|
||||
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
||||
Sprites.LAMBDA_FACTORY,
|
||||
) as SpriteSpec;
|
||||
|
||||
private codeEditorState: CodeEditorState | null;
|
||||
private code: string;
|
||||
private spawns: number;
|
||||
|
||||
@ -33,6 +63,7 @@ export class LambdaFactory extends Entity {
|
||||
|
||||
this.code = code;
|
||||
this.spawns = spawns;
|
||||
this.codeEditorState = null;
|
||||
|
||||
this.addComponent(
|
||||
new BoundingBox(
|
||||
@ -82,35 +113,85 @@ export class LambdaFactory extends Entity {
|
||||
this.removeComponent(ComponentNames.Interactable);
|
||||
}
|
||||
|
||||
private onHighlight(direction: Direction) {
|
||||
if (direction === Direction.LEFT || direction === Direction.RIGHT) {
|
||||
const interaction = () => {
|
||||
const spawner = this.getComponent<LambdaSpawn>(
|
||||
ComponentNames.LambdaSpawn,
|
||||
);
|
||||
private spawnNewLambda(direction: Direction) {
|
||||
const spawner = this.getComponent<LambdaSpawn>(ComponentNames.LambdaSpawn);
|
||||
spawner.spawn(direction);
|
||||
|
||||
const text = this.getComponent<Text>(ComponentNames.Text);
|
||||
text.text = spawner.spawnsLeft.toString();
|
||||
this.addComponent(text);
|
||||
};
|
||||
}
|
||||
|
||||
this.addComponent(new Interactable(interaction));
|
||||
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 = () => {
|
||||
modalOpen = false;
|
||||
closeModal();
|
||||
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 = (
|
||||
document.getElementById("code") as HTMLTextAreaElement
|
||||
).value;
|
||||
spawner.code = text;
|
||||
this.addComponent(spawner);
|
||||
}
|
||||
|
||||
modalOpen = false;
|
||||
closeModal();
|
||||
|
||||
document.getElementById(Miscellaneous.CANVAS_ID)!.focus();
|
||||
return;
|
||||
@ -121,22 +202,36 @@ export class LambdaFactory extends Entity {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
modalOpen = true;
|
||||
openModal(this.codeEditor(this.code));
|
||||
|
||||
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,
|
||||
],
|
||||
});
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
private codeEditor(code: string) {
|
||||
return `
|
||||
<div>
|
||||
<textarea id="code" rows="10" cols="50">${code}</textarea>
|
||||
<button id="close-modal">Close</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -2,34 +2,37 @@ import { Miscellaneous } from "../config";
|
||||
|
||||
let modalOpen = false;
|
||||
|
||||
export const openModal = (content: string, id = Miscellaneous.MODAL_ID) => {
|
||||
export const openModal = (
|
||||
content: string,
|
||||
id = Miscellaneous.MODAL_ID,
|
||||
contentId = Miscellaneous.MODAL_CONTENT_ID,
|
||||
) => {
|
||||
const modal = document.getElementById(id);
|
||||
if (modal && !modalOpen) {
|
||||
const modalContent = document.getElementById(contentId);
|
||||
if (modal && !modalOpen && modalContent) {
|
||||
modal.style.display = "flex";
|
||||
modal.style.animation = "fadeIn 0.25s";
|
||||
|
||||
modal.innerHTML = `<div class="modal-content">${content}</div>`;
|
||||
const modalContent = document.querySelector<HTMLElement>(".modal-content");
|
||||
if (modalContent) {
|
||||
modalContent.innerHTML = content;
|
||||
modalContent.style.animation = "scaleUp 0.25s";
|
||||
}
|
||||
|
||||
modalOpen = true;
|
||||
}
|
||||
};
|
||||
|
||||
export const closeModal = (id = Miscellaneous.MODAL_ID) => {
|
||||
export const closeModal = (
|
||||
id = Miscellaneous.MODAL_ID,
|
||||
contentId = Miscellaneous.MODAL_CONTENT_ID,
|
||||
) => {
|
||||
const modal = document.getElementById(id);
|
||||
if (modal && modalOpen) {
|
||||
modal.style.animation = "fadeOut 0.25s";
|
||||
const modalContent = document.getElementById(contentId);
|
||||
|
||||
const modalContent = document.querySelector<HTMLElement>(".modal-content");
|
||||
if (modalContent) {
|
||||
if (modal && modalOpen && modalContent) {
|
||||
modal.style.animation = "fadeOut 0.25s";
|
||||
modalContent.style.animation = "scaleDown 0.25s";
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
modal.innerHTML = "";
|
||||
modalContent.innerHTML = "";
|
||||
modal.style.display = "none";
|
||||
|
||||
modalOpen = false;
|
||||
|
@ -30,6 +30,6 @@ export const isVariable = (term: LambdaTerm): term is Variable => {
|
||||
return typeof term === "string";
|
||||
};
|
||||
|
||||
export const parse = (term: string) => {
|
||||
return peggyParser.parse(term, { library: true });
|
||||
export const parse = (term: string, library = false) => {
|
||||
return peggyParser.parse(term, { peg$library: library });
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { App } from "./App.tsx";
|
||||
import "./css/style.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
|
||||
|
Loading…
Reference in New Issue
Block a user