VibeGame / src /lib /components /Editor.svelte
dylanebert's picture
refactor
bc7e9cd
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import * as monaco from "monaco-editor";
import type { editor } from "monaco-editor";
import { contentManager } from "../services/content-manager";
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
export let language: string = "html";
export let theme: string = "vs-dark";
export let readOnly: boolean = false;
let editorContainer: HTMLDivElement;
let editorInstance: editor.IStandaloneCodeEditor | null = null;
let isInternalUpdate = false;
let contentUnsubscribe: (() => void) | null = null;
(self as any).MonacoEnvironment = {
getWorker: function (_: any, label: string) {
switch (label) {
case "json":
return new jsonWorker();
case "css":
case "scss":
case "less":
return new cssWorker();
case "html":
case "handlebars":
case "razor":
case "xml":
return new htmlWorker();
case "typescript":
case "javascript":
return new tsWorker();
default:
return new editorWorker();
}
},
};
onMount(() => {
const currentContent = contentManager.getCurrentContent();
editorInstance = monaco.editor.create(editorContainer, {
value: currentContent,
language: language,
theme: theme,
minimap: {
enabled: false,
},
fontSize: 13,
fontFamily: '"Monaco", "Menlo", "Ubuntu Mono", monospace',
lineHeight: 20,
wordWrap: "on",
scrollBeyondLastLine: false,
renderWhitespace: "selection",
automaticLayout: true,
tabSize: 2,
insertSpaces: true,
formatOnPaste: true,
formatOnType: true,
suggestOnTriggerCharacters: true,
quickSuggestions: {
other: true,
comments: false,
strings: true,
},
scrollbar: {
verticalScrollbarSize: 10,
horizontalScrollbarSize: 10,
},
});
// Subscribe to ContentManager changes
contentUnsubscribe = contentManager.subscribe((state) => {
if (editorInstance && state.content !== editorInstance.getValue()) {
isInternalUpdate = true;
// Preserve cursor position
const position = editorInstance.getPosition();
const scrollTop = editorInstance.getScrollTop();
editorInstance.setValue(state.content);
// Restore cursor position if possible
if (position) {
editorInstance.setPosition(position);
}
editorInstance.setScrollTop(scrollTop);
isInternalUpdate = false;
}
});
// Handle user changes
editorInstance.onDidChangeModelContent(() => {
if (!isInternalUpdate) {
const newValue = editorInstance?.getValue() || "";
contentManager.updateFromUI(newValue);
}
});
const resizeObserver = new ResizeObserver(() => {
editorInstance?.layout();
});
resizeObserver.observe(editorContainer);
return () => {
resizeObserver.disconnect();
};
});
onDestroy(() => {
if (contentUnsubscribe) {
contentUnsubscribe();
}
editorInstance?.dispose();
});
$: if (editorInstance) {
const model = editorInstance.getModel();
if (model) {
monaco.editor.setModelLanguage(model, language);
}
}
$: if (editorInstance && theme) {
monaco.editor.setTheme(theme);
}
$: if (editorInstance) {
editorInstance.updateOptions({ readOnly });
}
</script>
<div bind:this={editorContainer} class="monaco-editor-container"></div>
<style>
.monaco-editor-container {
width: 100%;
height: 100%;
min-height: 200px;
}
</style>