mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
playground: basic editor functionality ready
This commit is contained in:
@@ -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:
|
||||
|
||||
- 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:
|
||||
|
||||
- @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:
|
||||
|
||||
- @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:
|
||||
|
||||
- vuetify@3.9.0
|
||||
|
||||
@@ -38,6 +38,9 @@ export async function settings() {
|
||||
"12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==" :
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEW6urpaLVq8AAAACklEQVQI" +
|
||||
"12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=="),
|
||||
|
||||
// Playground settings
|
||||
code: "", // Automatically loaded and executed code for the playground
|
||||
};
|
||||
|
||||
// Auto-override any settings from the URL
|
||||
|
||||
195
frontend/tools/PlaygroundDialogContent.vue
Normal file
195
frontend/tools/PlaygroundDialogContent.vue
Normal 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>
|
||||
@@ -13,13 +13,14 @@ import {
|
||||
import OrientationGizmo from "./OrientationGizmo.vue";
|
||||
import type {PerspectiveCamera} from "three/src/cameras/PerspectiveCamera.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 type {ModelViewerElement} from '@google/model-viewer';
|
||||
import Loading from "../misc/Loading.vue";
|
||||
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
||||
import {defineAsyncComponent, ref, type Ref} from "vue";
|
||||
import type {SelectionInfo} from "./selection";
|
||||
import {settings} from "../misc/settings.ts";
|
||||
|
||||
const SelectionComponent = defineAsyncComponent({
|
||||
loader: () => import("./Selection.vue"),
|
||||
@@ -34,10 +35,23 @@ const LicensesDialogContent = defineAsyncComponent({
|
||||
delay: 0,
|
||||
});
|
||||
|
||||
const PlaygroundDialogContent = defineAsyncComponent({
|
||||
loader: () => import("./PlaygroundDialogContent.vue"),
|
||||
loadingComponent: Loading,
|
||||
delay: 0,
|
||||
});
|
||||
|
||||
|
||||
let props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null }>();
|
||||
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 selectionFaceCount = () => selection.value.filter((s) => s.kind == 'face').length
|
||||
let selectionEdgeCount = () => selection.value.filter((s) => s.kind == 'edge').length
|
||||
@@ -146,11 +160,23 @@ window.addEventListener('keydown', (event) => {
|
||||
<v-divider/>
|
||||
<v-spacer></v-spacer>
|
||||
<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"/>
|
||||
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-tooltip activator="parent">(D)ownload Scene</v-tooltip>
|
||||
<svg-icon :path="mdiDownload" type="mdi"/>
|
||||
</v-btn>
|
||||
<v-dialog id="licenses-dialog" fullscreen>
|
||||
<v-dialog>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn icon v-bind="props">
|
||||
<v-tooltip activator="parent">Show Licenses</v-tooltip>
|
||||
@@ -158,11 +184,10 @@ window.addEventListener('keydown', (event) => {
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-slot:default="{ isActive }">
|
||||
<v-card>
|
||||
<v-toolbar>
|
||||
<v-card style="height: 90vh">
|
||||
<v-toolbar class="popup">
|
||||
<v-toolbar-title>Licenses</v-toolbar-title>
|
||||
<v-spacer>
|
||||
</v-spacer>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="isActive.value = false">
|
||||
<svg-icon :path="mdiClose" type="mdi"/>
|
||||
</v-btn>
|
||||
@@ -199,4 +224,17 @@ window.addEventListener('keydown', (event) => {
|
||||
h5 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.v-toolbar {
|
||||
position: sticky !important;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.v-toolbar.popup {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.v-toolbar.popup > div {
|
||||
height: 32px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
35
frontend/tools/monaco.ts
Normal file
35
frontend/tools/monaco.ts
Normal 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})
|
||||
}
|
||||
44
frontend/tools/pyodide-worker-api.ts
Normal file
44
frontend/tools/pyodide-worker-api.ts
Normal 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;
|
||||
27
frontend/tools/pyodide-worker.ts
Normal file
27
frontend/tools/pyodide-worker.ts
Normal 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});
|
||||
}
|
||||
};
|
||||
@@ -19,9 +19,12 @@
|
||||
"@gltf-transform/extensions": "^4.1.0",
|
||||
"@gltf-transform/functions": "^4.1.0",
|
||||
"@google/model-viewer": "^4.0.0",
|
||||
"@guolao/vue-monaco-editor": "^1.5.5",
|
||||
"@jamescoyle/vue-icon": "^0.1.2",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/svg": "^7.4.47",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"pyodide": "^0.28.0",
|
||||
"three": "^0.178.0",
|
||||
"three-mesh-bvh": "^0.9.0",
|
||||
"three-orientation-gizmo": "https://github.com/jrj2211/three-orientation-gizmo",
|
||||
@@ -42,6 +45,7 @@
|
||||
"terser": "^5.36.0",
|
||||
"typescript": "~5.8.0",
|
||||
"vite": "^7.0.0",
|
||||
"vue-tsc": "^3.0.0"
|
||||
"vue-tsc": "^3.0.0",
|
||||
"vite-plugin-static-copy": "^3.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import {name, version} from './package.json'
|
||||
import {execSync} from 'child_process'
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@@ -18,7 +20,9 @@ export default defineConfig({
|
||||
}
|
||||
}),
|
||||
vueJsx(),
|
||||
viteStaticCopyPyodide(),
|
||||
],
|
||||
optimizeDeps: { exclude: ["pyodide"] },
|
||||
resolve: {
|
||||
alias: {
|
||||
// @ts-ignore
|
||||
@@ -28,8 +32,13 @@ export default defineConfig({
|
||||
build: {
|
||||
assetsDir: '.', // Support deploying to a subdirectory using relative URLs
|
||||
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
|
||||
rollupOptions: {
|
||||
output: {
|
||||
experimentalMinChunkSize: 512000, // 512KB (avoid too many small chunks)
|
||||
}
|
||||
}
|
||||
},
|
||||
define: {
|
||||
__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()),
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
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",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user