140 lines
3.7 KiB
Vue
140 lines
3.7 KiB
Vue
<template>
|
|
<div class="code-editor">
|
|
<div ref="editorContainer" class="code-editor" />
|
|
<slot />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, watch } from 'vue';
|
|
import loader, { Monaco } from '@monaco-editor/loader';
|
|
|
|
import { ImportedLib } from '../utils/useXOCashLibs';
|
|
|
|
export type CodeEditorProps = {
|
|
// List of .d.ts files to import into the editor.
|
|
libs?: Record<string, ImportedLib>;
|
|
|
|
// Color scheme of the editor
|
|
isDark?: boolean;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<CodeEditorProps>(), {
|
|
libs: () => ({}),
|
|
isDark: false,
|
|
});
|
|
|
|
const code = defineModel<string>();
|
|
|
|
const monacoInstance = ref<Monaco | null>(null);
|
|
const editorContainer = ref<HTMLElement | null>(null);
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'execute'): void;
|
|
}>();
|
|
|
|
watch(props.libs, async () => {
|
|
updateEditorTypes();
|
|
});
|
|
|
|
|
|
function updateEditorTypes() {
|
|
monacoInstance.value?.languages.typescript.typescriptDefaults.setExtraLibs(
|
|
Object.entries(props.libs).map(([name, content]) => {
|
|
// Dont add the library if it is empty
|
|
if (!content) return null;
|
|
|
|
const moduleContent = `declare module "${name}" { ${content.libTypes} }`;
|
|
|
|
return { uri: `inmemory://model/${name}.d.ts`, content: moduleContent };
|
|
}).filter((lib) => lib !== null)
|
|
);
|
|
}
|
|
|
|
onMounted(() => {
|
|
// Load Monaco Editor
|
|
loader.init().then((monaco) => {
|
|
monacoInstance.value = monaco;
|
|
|
|
// Throw if our editor container is not found
|
|
if (!editorContainer.value) {
|
|
throw new Error('Editor container not found');
|
|
}
|
|
|
|
// Disable unnecessary validations
|
|
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
|
|
noSemanticValidation: false,
|
|
noSyntaxValidation: false,
|
|
diagnosticCodesToIgnore: [ 1375 ] // Ignore "top-level await" error
|
|
});
|
|
|
|
// Add our typescript compiler options
|
|
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
|
target: monaco.languages.typescript.ScriptTarget.ESNext,
|
|
allowNonTsExtensions: true,
|
|
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
|
module: monaco.languages.typescript.ModuleKind.ESNext,
|
|
noEmit: true,
|
|
lib: ["esnext", "dom"],
|
|
checkJs: true,
|
|
isolatedModules: true,
|
|
allowSyntheticDefaultImports: true
|
|
});
|
|
|
|
// Enable eager mode. Tries to move LSP on worker thread
|
|
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);
|
|
|
|
// Add extra libs to the editor
|
|
updateEditorTypes();
|
|
|
|
// Define a theme to change the background color of the editor
|
|
monaco.editor.defineTheme('xo-sandbox', {
|
|
base: props.isDark ? 'vs-dark' : 'vs',
|
|
inherit: true,
|
|
rules: [],
|
|
colors: {
|
|
"editor.background": props.isDark ? '#161618' : '#f5f5f5'
|
|
}
|
|
});
|
|
|
|
// Create our editor into the container
|
|
const editor = monaco.editor.create(editorContainer.value, {
|
|
value: code.value,
|
|
language: 'typescript',
|
|
theme: 'xo-sandbox',
|
|
automaticLayout: true,
|
|
scrollBeyondLastLine: false,
|
|
suggest: {
|
|
showClasses: true,
|
|
showFunctions: true,
|
|
showVariables: true,
|
|
showModules: true,
|
|
showProperties: true,
|
|
showWords: true,
|
|
},
|
|
});
|
|
|
|
// Add control + S keybinding to execute code
|
|
editor.addAction({
|
|
id: `execute-code`,
|
|
label: 'Execute Code',
|
|
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
|
|
run: () => {
|
|
emit('execute')
|
|
}
|
|
});
|
|
|
|
// Update the code value when the editor content changes
|
|
editor.onDidChangeModelContent(() => {
|
|
code.value = editor.getValue();
|
|
});
|
|
});
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.code-editor {
|
|
height: 100%;
|
|
width: 100%;
|
|
}
|
|
</style> |