playground: basic editor functionality ready

This commit is contained in:
Yeicor
2025-07-19 21:49:02 +02:00
parent 667a08d2c6
commit fc32393635
10 changed files with 1115 additions and 380 deletions

View File

@@ -1129,6 +1129,35 @@ BSD-3-Clause
----------- -----------
The following npm package may be included in this product:
- ws@8.18.3
This package contains the following license:
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
Copyright (c) 2013 Arnout Kazemier and contributors
Copyright (c) 2016 Luigi Pinca and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-----------
The following npm package may be included in this product: The following npm package may be included in this product:
- color-string@1.9.1 - color-string@1.9.1
@@ -1537,6 +1566,126 @@ SOFTWARE.
----------- -----------
The following npm package may be included in this product:
- state-local@1.0.7
This package contains the following license:
MIT License
Copyright (c) 2020 Suren Atoyan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----------
The following npm package may be included in this product:
- vue-demi@0.14.10
This package contains the following license:
MIT License
Copyright (c) 2020-present, Anthony Fu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----------
The following npm package may be included in this product:
- @monaco-editor/loader@1.5.0
This package contains the following license:
MIT License
Copyright (c) 2021 Suren Atoyan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----------
The following npm package may be included in this product:
- @guolao/vue-monaco-editor@1.5.5
This package contains the following license:
MIT License
Copyright (c) 2022 guolao
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----------
The following npm package may be included in this product: The following npm package may be included in this product:
- @monogrid/gainmap-js@3.1.0 - @monogrid/gainmap-js@3.1.0
@@ -1597,6 +1746,16 @@ SOFTWARE.
----------- -----------
The following npm package may be included in this product:
- pyodide@0.28.0
This package contains the following license:
MPL-2.0
-----------
The following npm packages may be included in this product: The following npm packages may be included in this product:
- @mdi/js@7.4.47 - @mdi/js@7.4.47
@@ -1771,6 +1930,36 @@ THE SOFTWARE.
----------- -----------
The following npm package may be included in this product:
- monaco-editor@0.52.2
This package contains the following license:
The MIT License (MIT)
Copyright (c) 2016 - present Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----------
The following npm package may be included in this product: The following npm package may be included in this product:
- vuetify@3.9.0 - vuetify@3.9.0

View File

@@ -38,6 +38,9 @@ export async function settings() {
"12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==" : "12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==" :
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEW6urpaLVq8AAAACklEQVQI" + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEW6urpaLVq8AAAACklEQVQI" +
"12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=="), "12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=="),
// Playground settings
code: "", // Automatically loaded and executed code for the playground
}; };
// Auto-override any settings from the URL // Auto-override any settings from the URL

View File

@@ -0,0 +1,195 @@
<script setup lang="ts">
import {setupMonaco} from "./monaco.ts";
import {VueMonacoEditor} from '@guolao/vue-monaco-editor'
import {ref, shallowRef} from "vue";
import Loading from "../misc/Loading.vue";
import {asyncRun, pyodideWorker, resetState} from "./pyodide-worker-api.ts";
import {mdiCircleOpacity, mdiClose, mdiLockReset, mdiPlay, mdiReload, mdiRun} from "@mdi/js";
import {VBtn, VCard, VCardText, VSlider, VSpacer, VToolbar, VToolbarTitle} from "vuetify/components";
import SvgIcon from '@jamescoyle/vue-icon';
const props = defineProps<{ initialCode: string }>();
const emit = defineEmits<{ (e: 'close'): void }>()
// ============ LOAD MONACO EDITOR ============
setupMonaco() // Must be called before using the editor
const code = ref(props.initialCode); // TODO: Default code as input (and autorun!)
const outputText = ref(``);
function output(text: string) {
outputText.value += text; // Append to output
console.log(text); // Also log to console
setTimeout(() => { // Scroll to bottom? TODO: Test
const consoleElement = document.querySelector('.playground-console pre');
if (consoleElement) {
consoleElement.scrollTop = consoleElement.scrollHeight;
}
}, 0);
}
const MONACO_EDITOR_OPTIONS = {
automaticLayout: true,
formatOnType: true,
formatOnPaste: true,
}
const editorTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? `vs-dark` : `vs`
const editor = shallowRef()
const handleMount = (editorInstance: typeof VueMonacoEditor) => (editor.value = editorInstance)
const opacity = ref(0.9); // Opacity for the editor
// ============ LOAD PYODIDE (ASYNC) ============
const running = ref(true);
async function setupPyodide() {
if (opacity.value == 0.0) opacity.value = 0.9; // User doesn't know how to show code again, reset after reopening
let firstTime = pyodideWorker === null;
if (firstTime) {
resetState();
output("Loading packages...\n");
await asyncRun(`import micropip, asyncio
micropip.set_index_urls(["https://yeicor.github.io/OCP.wasm", "https://pypi.org/simple"])
await (micropip.install("lib3mf"))
micropip.add_mock_package("py-lib3mf", "2.4.1", modules={"py_lib3mf": '''from lib3mf import *'''})
await (micropip.install(["build123d"]))`, output);
output("Preimporting build123d...\n");
await asyncRun(`import build123d`);
if (props.initialCode != "") {
await runCode();
opacity.value = 0.0; // Hide editor after running initial code
} else {
output("Ready for custom code.\n");
}
} else {
output("Reusing existing Pyodide instance...\n");
}
running.value = false; // Indicate that Pyodide is ready
}
setupPyodide()
async function runCode() {
output("Running code...\n");
try {
running.value = true;
await asyncRun(code.value, output);
} catch (e) {
output(`Error running initial code: ${e}\n`);
} finally {
running.value = false; // Indicate that Pyodide is ready
}
}
</script>
<template>
<v-card class="popup-card"
:style="opacity == 0 ? `position: absolute; top: calc(-50vh + 24px); width: calc(100vw - 64px);` : ``">
<v-toolbar class="popup">
<v-toolbar-title>Playground</v-toolbar-title>
<v-spacer></v-spacer>
<svg-icon :path="mdiCircleOpacity" type="mdi"></svg-icon>
<v-slider v-model="opacity" :max="1" :min="0" :step="0.1"
style="max-width: 100px; height: 32px; margin-right: 16px;"></v-slider>
<!-- TODO: snapshots... -->
<v-btn icon @click="resetState()">
<svg-icon :path="mdiReload" type="mdi"/>
</v-btn>
<v-btn icon @click="runCode()">
<svg-icon :path="mdiPlay" type="mdi"/>
</v-btn>
<v-btn icon @click="emit('close')">
<svg-icon :path="mdiClose" type="mdi"/>
</v-btn>
</v-toolbar>
<v-card-text class="popup-card-text" :style="opacity == 0 ? `display: none` : ``">
<!-- Only show content if opacity is greater than 0 -->
<div class="playground-container">
<div class="playground-editor">
<VueMonacoEditor
v-model:value="code"
:theme="editorTheme"
:options="MONACO_EDITOR_OPTIONS"
language="python"
@mount="handleMount"
/>
</div>
<div class="playground-console">
<h3>Console Output</h3>
<pre>{{ outputText }}</pre> <!-- Placeholder for console output -->
<Loading v-if="running"/>
</div>
</div>
</v-card-text>
</v-card>
</template>
<style scoped>
.popup-card {
background-color: #00000000; /* Transparent background */
}
.popup-card-text {
background-color: #1e1e1e; /* Matches the Monaco editor background */
opacity: v-bind(opacity);
}
.playground-container {
display: flex;
flex-direction: row;
}
.playground-editor {
flex: 1;
height: calc(100vh - 150px);
}
.playground-console {
flex: 0.5;
padding: 10px;
overflow-y: auto;
min-width: 100px;
height: calc(100vh - 150px);
}
.playground-console pre {
margin: 0;
white-space: pre-wrap;
word-break: break-all;
}
@media (min-height: 100vw) {
/* Adjust layout for vertical space */
.playground-container {
flex-direction: column;
}
.playground-editor {
flex: 1;
min-height: 60vh;
}
.playground-editor > * {
min-height: 60vh;
}
.playground-console {
max-height: calc(40vh - 150px);
}
}
/* TODO: Adjust more colors on bright mode */
</style>
<style>
/* https://stackoverflow.com/questions/47017753/monaco-editor-dynamically-resizable/71876526#71876526 */
.monaco-editor {
position: absolute !important;
}
</style>

View File

@@ -13,13 +13,14 @@ import {
import OrientationGizmo from "./OrientationGizmo.vue"; import OrientationGizmo from "./OrientationGizmo.vue";
import type {PerspectiveCamera} from "three/src/cameras/PerspectiveCamera.js"; import type {PerspectiveCamera} from "three/src/cameras/PerspectiveCamera.js";
import {OrthographicCamera} from "three/src/cameras/OrthographicCamera.js"; import {OrthographicCamera} from "three/src/cameras/OrthographicCamera.js";
import {mdiClose, mdiCrosshairsGps, mdiDownload, mdiGithub, mdiLicense, mdiProjector} from '@mdi/js' import {mdiClose, mdiCrosshairsGps, mdiDownload, mdiGithub, mdiLicense, mdiProjector, mdiScriptTextPlay} from '@mdi/js'
import SvgIcon from '@jamescoyle/vue-icon'; import SvgIcon from '@jamescoyle/vue-icon';
import type {ModelViewerElement} from '@google/model-viewer'; import type {ModelViewerElement} from '@google/model-viewer';
import Loading from "../misc/Loading.vue"; import Loading from "../misc/Loading.vue";
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue"; import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
import {defineAsyncComponent, ref, type Ref} from "vue"; import {defineAsyncComponent, ref, type Ref} from "vue";
import type {SelectionInfo} from "./selection"; import type {SelectionInfo} from "./selection";
import {settings} from "../misc/settings.ts";
const SelectionComponent = defineAsyncComponent({ const SelectionComponent = defineAsyncComponent({
loader: () => import("./Selection.vue"), loader: () => import("./Selection.vue"),
@@ -34,10 +35,23 @@ const LicensesDialogContent = defineAsyncComponent({
delay: 0, delay: 0,
}); });
const PlaygroundDialogContent = defineAsyncComponent({
loader: () => import("./PlaygroundDialogContent.vue"),
loadingComponent: Loading,
delay: 0,
});
let props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null }>(); let props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null }>();
const emit = defineEmits<{ findModel: [string] }>() const emit = defineEmits<{ findModel: [string] }>()
const sett = ref(null);
const showPlaygroundDialog = ref(false);
(async () => {
sett.value = await settings();
return showPlaygroundDialog.value = sett.value.code != "";
})();
let selection: Ref<Array<SelectionInfo>> = ref([]); let selection: Ref<Array<SelectionInfo>> = ref([]);
let selectionFaceCount = () => selection.value.filter((s) => s.kind == 'face').length let selectionFaceCount = () => selection.value.filter((s) => s.kind == 'face').length
let selectionEdgeCount = () => selection.value.filter((s) => s.kind == 'edge').length let selectionEdgeCount = () => selection.value.filter((s) => s.kind == 'edge').length
@@ -146,11 +160,23 @@ window.addEventListener('keydown', (event) => {
<v-divider/> <v-divider/>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<h5>Extras</h5> <h5>Extras</h5>
<v-dialog v-model="showPlaygroundDialog" persistent :scrim="false" attach="body">
<template v-slot:activator="{ props }">
<v-btn icon v-bind="props" style="width: 100%">
<v-tooltip activator="parent">Open a python editor and build models directly in the browser!</v-tooltip>
<svg-icon :path="mdiScriptTextPlay" type="mdi"/>
&nbsp;Sandbox
</v-btn>
</template>
<template v-slot:default="{ isActive }">
<playground-dialog-content v-if="sett != null" :initial-code="sett.code" @close="isActive.value = false"/>
</template>
</v-dialog>
<v-btn icon @click="downloadSceneGlb"> <v-btn icon @click="downloadSceneGlb">
<v-tooltip activator="parent">(D)ownload Scene</v-tooltip> <v-tooltip activator="parent">(D)ownload Scene</v-tooltip>
<svg-icon :path="mdiDownload" type="mdi"/> <svg-icon :path="mdiDownload" type="mdi"/>
</v-btn> </v-btn>
<v-dialog id="licenses-dialog" fullscreen> <v-dialog>
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-btn icon v-bind="props"> <v-btn icon v-bind="props">
<v-tooltip activator="parent">Show Licenses</v-tooltip> <v-tooltip activator="parent">Show Licenses</v-tooltip>
@@ -158,11 +184,10 @@ window.addEventListener('keydown', (event) => {
</v-btn> </v-btn>
</template> </template>
<template v-slot:default="{ isActive }"> <template v-slot:default="{ isActive }">
<v-card> <v-card style="height: 90vh">
<v-toolbar> <v-toolbar class="popup">
<v-toolbar-title>Licenses</v-toolbar-title> <v-toolbar-title>Licenses</v-toolbar-title>
<v-spacer> <v-spacer></v-spacer>
</v-spacer>
<v-btn icon @click="isActive.value = false"> <v-btn icon @click="isActive.value = false">
<svg-icon :path="mdiClose" type="mdi"/> <svg-icon :path="mdiClose" type="mdi"/>
</v-btn> </v-btn>
@@ -199,4 +224,17 @@ window.addEventListener('keydown', (event) => {
h5 { h5 {
font-size: 14px; font-size: 14px;
} }
.v-toolbar {
position: sticky !important;
top: 0;
}
.v-toolbar.popup {
height: 32px;
}
.v-toolbar.popup > div {
height: 32px !important;
}
</style> </style>

35
frontend/tools/monaco.ts Normal file
View File

@@ -0,0 +1,35 @@
import {loader} from "@guolao/vue-monaco-editor"
import * as monaco from "monaco-editor"
//@ts-ignore
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"
//@ts-ignore
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker"
//@ts-ignore
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker"
//@ts-ignore
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker"
//@ts-ignore
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === "json") {
return new jsonWorker()
}
if (label === "css" || label === "scss" || label === "less") {
return new cssWorker()
}
if (label === "html" || label === "handlebars" || label === "razor") {
return new htmlWorker()
}
if (label === "typescript" || label === "javascript") {
return new tsWorker()
}
return new editorWorker()
}
}
export function setupMonaco() {
loader.config({monaco})
}

View File

@@ -0,0 +1,44 @@
// Each message needs a unique id to identify the response. In a real example,
// we might use a real uuid package
let lastId = 1;
function getId() {
return lastId++;
}
// Add an id to msg, send it to worker, then wait for a response with the same id.
// When we get such a response, use it to resolve the promise.
function requestResponse(worker: Worker, code: String, output: (msg: string) => void) {
return new Promise((resolve) => {
const idWorker = getId();
worker.addEventListener("message", function listener(event) {
if (event.data?.stdout) {
output(event.data.stdout + "\n");
return;
}
if (event.data?.stderr) {
output(event.data.stderr + "\n");
return;
}
if (event.data?.id !== idWorker) return;
// This listener is done so remove it.
worker.removeEventListener("message", listener);
// Filter the id out of the result
const {id, ...rest} = event.data;
resolve(rest);
});
worker.postMessage({id: idWorker, code});
});
}
export function asyncRun(code: String, output: (msg: string) => void) {
return requestResponse(pyodideWorker as Worker, code, output);
}
export function resetState() {
// Reset the worker state by terminating it and creating a new one.
if (pyodideWorker) pyodideWorker.terminate();
pyodideWorker = new Worker(new URL('./pyodide-worker.ts', import.meta.url), {type: "module"});
}
export let pyodideWorker: Worker | null = null;

View File

@@ -0,0 +1,27 @@
import {loadPyodide, version} from "pyodide";
let pyodideReadyPromise = loadPyodide({
indexURL: `https://cdn.jsdelivr.net/pyodide/v${version}/full/`, // FIXME: Local deployment?
packages: ["micropip", "sqlite3"], // Preloaded faster here...
stdout: (msg) => self.postMessage({stdout: msg}),
stderr: (msg) => self.postMessage({stderr: msg}),
stdin: () => {
console.warn("Input requested by Python code, but stdin is not supported in this playground.");
return "";
},
});
self.onmessage = async (event) => {
// make sure loading is done
const pyodide = await pyodideReadyPromise;
const {id, code} = event.data;
// Now load any packages we need, run the code, and send the result back.
await pyodide.loadPackagesFromImports(code);
try {
// Execute the python code in this context
const result = await pyodide.runPythonAsync(code);
self.postMessage({result, id});
} catch (error: any) {
self.postMessage({error: error.message, id});
}
};

View File

@@ -19,9 +19,12 @@
"@gltf-transform/extensions": "^4.1.0", "@gltf-transform/extensions": "^4.1.0",
"@gltf-transform/functions": "^4.1.0", "@gltf-transform/functions": "^4.1.0",
"@google/model-viewer": "^4.0.0", "@google/model-viewer": "^4.0.0",
"@guolao/vue-monaco-editor": "^1.5.5",
"@jamescoyle/vue-icon": "^0.1.2", "@jamescoyle/vue-icon": "^0.1.2",
"@mdi/js": "^7.4.47", "@mdi/js": "^7.4.47",
"@mdi/svg": "^7.4.47", "@mdi/svg": "^7.4.47",
"monaco-editor": "^0.52.2",
"pyodide": "^0.28.0",
"three": "^0.178.0", "three": "^0.178.0",
"three-mesh-bvh": "^0.9.0", "three-mesh-bvh": "^0.9.0",
"three-orientation-gizmo": "https://github.com/jrj2211/three-orientation-gizmo", "three-orientation-gizmo": "https://github.com/jrj2211/three-orientation-gizmo",
@@ -42,6 +45,7 @@
"terser": "^5.36.0", "terser": "^5.36.0",
"typescript": "~5.8.0", "typescript": "~5.8.0",
"vite": "^7.0.0", "vite": "^7.0.0",
"vue-tsc": "^3.0.0" "vue-tsc": "^3.0.0",
"vite-plugin-static-copy": "^3.1.1"
} }
} }

View File

@@ -5,6 +5,8 @@ import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx' import vueJsx from '@vitejs/plugin-vue-jsx'
import {name, version} from './package.json' import {name, version} from './package.json'
import {execSync} from 'child_process' import {execSync} from 'child_process'
import { viteStaticCopy } from "vite-plugin-static-copy";
import { dirname, join } from "path";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
@@ -18,7 +20,9 @@ export default defineConfig({
} }
}), }),
vueJsx(), vueJsx(),
viteStaticCopyPyodide(),
], ],
optimizeDeps: { exclude: ["pyodide"] },
resolve: { resolve: {
alias: { alias: {
// @ts-ignore // @ts-ignore
@@ -28,8 +32,13 @@ export default defineConfig({
build: { build: {
assetsDir: '.', // Support deploying to a subdirectory using relative URLs assetsDir: '.', // Support deploying to a subdirectory using relative URLs
cssCodeSplit: false, // Small enough to inline cssCodeSplit: false, // Small enough to inline
chunkSizeWarningLimit: 1024, // Three.js is big. Draco is even bigger but not likely to be used. chunkSizeWarningLimit: 1024, // KB. Three.js is big. Draco is even bigger but not likely to be used.
sourcemap: true, // For debugging production sourcemap: true, // For debugging production
rollupOptions: {
output: {
experimentalMinChunkSize: 512000, // 512KB (avoid too many small chunks)
}
}
}, },
define: { define: {
__APP_NAME__: JSON.stringify(name), __APP_NAME__: JSON.stringify(name),
@@ -38,3 +47,22 @@ export default defineConfig({
__APP_GIT_DIRTY__: JSON.stringify(execSync('git diff --quiet || echo dirty').toString().trim()), __APP_GIT_DIRTY__: JSON.stringify(execSync('git diff --quiet || echo dirty').toString().trim()),
} }
}) })
function viteStaticCopyPyodide() {
const PYODIDE_EXCLUDE = [
"!**/*.{md,html}",
"!**/*.d.ts",
"!**/*.whl",
"!**/node_modules",
];
const pyodideDir = dirname(fileURLToPath(import.meta.resolve("pyodide")));
return viteStaticCopy({
targets: [
{
src: [join(pyodideDir, "*")].concat(PYODIDE_EXCLUDE),
dest: "assets",
},
],
});
}

916
yarn.lock

File diff suppressed because it is too large Load Diff