mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
Add upload and share functionality and minor improvements
This commit is contained in:
@@ -12,7 +12,8 @@ import {
|
||||
mdiFolderOpen,
|
||||
mdiPlay,
|
||||
mdiReload,
|
||||
mdiShare
|
||||
mdiShare,
|
||||
mdiUpload
|
||||
} from "@mdi/js";
|
||||
import {VBtn, VCard, VCardText, VSlider, VSpacer, VToolbar, VToolbarTitle, VTooltip} from "vuetify/components";
|
||||
// @ts-expect-error
|
||||
@@ -25,6 +26,7 @@ import {NetworkUpdateEvent, NetworkUpdateEventModel} from "../misc/network.ts";
|
||||
import {settings} from "../misc/settings.ts";
|
||||
// @ts-expect-error
|
||||
import playgroundStartupCode from './PlaygroundStartup.py?raw';
|
||||
import {uploadFile} from "./upload-file.ts";
|
||||
|
||||
const model = defineModel<{ code: string, firstTime: boolean }>({required: true}); // Initial code should only be set on first load!
|
||||
const emit = defineEmits<{ close: [], updateModel: [NetworkUpdateEvent] }>()
|
||||
@@ -139,9 +141,13 @@ function onModelData(modelData: string) {
|
||||
const binaryData = Base64.toUint8Array(modelData.slice(i + 1)); // Extract the base64 part
|
||||
console.assert(binaryData.slice(0, 4).toString() == "103,108,84,70", // Ugly...
|
||||
"Invalid GLTF binary data received: " + binaryData.slice(0, 4).toString());
|
||||
// - Save for upload and share link feature
|
||||
builtModelsGlb[modelMetadata.name] = binaryData;
|
||||
// - Create a Blob from the binary data to be used as a URL
|
||||
const blob = new Blob([binaryData], {type: 'model/gltf-binary'});
|
||||
modelMetadata.url = URL.createObjectURL(blob); // Set the hacked URL in the model metadata XXX: revoked on App.vue
|
||||
} else {
|
||||
delete builtModelsGlb[modelMetadata.name]; // Remove from built models if it's a remove request
|
||||
}
|
||||
// - Emit the event with the model metadata and URL
|
||||
let networkUpdateEvent = new NetworkUpdateEvent([modelMetadata], () => {
|
||||
@@ -158,15 +164,14 @@ function resetWorker(loadSnapshot: Uint8Array | undefined = undefined) {
|
||||
setupPyodide(false, loadSnapshot); // Reinitialize Pyodide
|
||||
}
|
||||
|
||||
function shareLink() {
|
||||
function shareLinkCommon(added: Record<string, string>, forgotten: Array<string>) {
|
||||
const baseUrl = window.location
|
||||
const searchParams = new URLSearchParams(baseUrl.search);
|
||||
searchParams.delete('pg_code_url'); // Remove any existing pg_code parameter
|
||||
searchParams.delete('pg_code'); // Remove any existing pg_code parameter
|
||||
for (const k of forgotten) searchParams.delete(k);
|
||||
const hashParams = new URLSearchParams(baseUrl.hash.slice(1)); // Keep all previous URL parameters
|
||||
hashParams.delete('pg_code_url') // Would overwrite the pg_code parameter
|
||||
hashParams.set('pg_code', b64UrlEncode(gzip(model.value.code, {level: 9}))); // Compress and encode the code
|
||||
const shareUrl = `${baseUrl.origin}${baseUrl.pathname}?${searchParams}#${hashParams}`; // Prefer hash to GET
|
||||
for (const k of forgotten) hashParams.delete(k);
|
||||
for (const k in added) hashParams.append(k, added[k]); // Prefer hash to GET
|
||||
const shareUrl = `${baseUrl.origin}${baseUrl.pathname}?${searchParams}#${hashParams}`;
|
||||
output(`Share link ready: ${shareUrl}\n`)
|
||||
if (navigator.clipboard?.writeText === undefined) {
|
||||
output("Clipboard API not available. Please copy the link manually.\n");
|
||||
@@ -178,6 +183,35 @@ function shareLink() {
|
||||
}
|
||||
}
|
||||
|
||||
function shareLink() {
|
||||
shareLinkCommon({'pg_code': b64UrlEncode(gzip(model.value.code, {level: 9}))}, ['pg_code']);
|
||||
}
|
||||
|
||||
const builtModelsGlb: Record<string, Uint8Array> = {}; // Store built models to support uploading
|
||||
async function uploadAndShareLink() {
|
||||
try {
|
||||
output("Uploading files...\n");
|
||||
|
||||
// Upload code.py
|
||||
const codeBlob = new Blob([model.value.code], {type: 'text/x-python'});
|
||||
const newParams: Record<string, string> = {
|
||||
'pg_code': await uploadFile('code.py', new Uint8Array(await codeBlob.arrayBuffer()))
|
||||
};
|
||||
|
||||
// Upload all models
|
||||
for (const name in builtModelsGlb) {
|
||||
const glb: any = builtModelsGlb[name];
|
||||
newParams['preload'] = await uploadFile(name + '.glb', glb);
|
||||
}
|
||||
|
||||
// Build share URL
|
||||
return shareLinkCommon(newParams, ['pg_code'])
|
||||
} catch (e) {
|
||||
output(`Error uploading/sharing files: ${e}. Falling back to private share link.\n`);
|
||||
return shareLink(); // Fallback to private share link if upload fails
|
||||
}
|
||||
}
|
||||
|
||||
function saveSnapshot() {
|
||||
throw new Error("Not implemented yet!"); // TODO: Implement snapshot saving
|
||||
}
|
||||
@@ -230,38 +264,48 @@ onMounted(() => {
|
||||
location="bottom">Opacity of the editor (0 = hidden, 1 = fully visible)</v-tooltip>
|
||||
</span>
|
||||
|
||||
<span style="margin-right: -8px;"><!-- This span is only needed to force tooltip to work while button is disabled -->
|
||||
<span style="padding-left: 12px; width: 48px;"><!-- This span is only needed to force tooltip to work while button is disabled -->
|
||||
<v-btn icon disabled @click="saveSnapshot()">
|
||||
<svg-icon :path="mdiContentSave" type="mdi"/>
|
||||
</v-btn>
|
||||
<v-tooltip activator="parent"
|
||||
location="bottom">Save current state to a snapshot for fast startup (WIP)</v-tooltip>
|
||||
</span>
|
||||
<span style="margin-left: -8px"><!-- This span is only needed to force tooltip to work while button is disabled -->
|
||||
<span style="padding-right: 12px; width: 48px;"><!-- This span is only needed to force tooltip to work while button is disabled -->
|
||||
<v-btn icon disabled @click="loadSnapshot()">
|
||||
<svg-icon :path="mdiFolderOpen" type="mdi"/>
|
||||
</v-btn>
|
||||
<v-tooltip activator="parent" location="bottom">Load snapshot for fast startup (WIP)</v-tooltip>
|
||||
</span>
|
||||
|
||||
<v-btn icon @click="resetWorker()">
|
||||
<v-btn icon @click="shareLink()" style="padding-left: 12px;">
|
||||
<svg-icon :path="mdiShare" type="mdi"/>
|
||||
<v-tooltip activator="parent" location="bottom">Share link that automatically runs the code.<br/>Only people
|
||||
with the link can see the code.
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click="uploadAndShareLink()" style="padding-right: 12px">
|
||||
<svg-icon :path="mdiShare" type="mdi" style="position: absolute; scale: 75%; top: 6px;"/>
|
||||
<svg-icon :path="mdiUpload" type="mdi" style="position: absolute; scale: 75%; bottom: 6px;"/>
|
||||
<v-tooltip activator="parent" location="bottom">Uploads all models and code and then shares a link to them.<br/>Useful
|
||||
to view the models while the playground loads, but uses third-party storage.
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click="resetWorker()" style="padding-left: 12px;">
|
||||
<svg-icon :path="mdiReload" type="mdi"/>
|
||||
<v-tooltip activator="parent" location="bottom">Reset Pyodide worker (this forgets all previous state and will
|
||||
take a little while)
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click="runCode()" :disabled="running">
|
||||
<v-btn icon @click="runCode()" :disabled="running" style="padding-right: 12px">
|
||||
<svg-icon :path="mdiPlay" type="mdi"/>
|
||||
<Loading v-if="running" style="position: absolute; top: -16%; left: -16%"/><!-- Ugly positioning -->
|
||||
<Loading v-if="running" style="position: absolute; top: -16%; left: -28%"/><!-- Ugly positioning -->
|
||||
<v-tooltip activator="parent" location="bottom">Run code</v-tooltip>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click="shareLink()">
|
||||
<svg-icon :path="mdiShare" type="mdi"/>
|
||||
<v-tooltip activator="parent" location="bottom">Share link that auto-runs the code</v-tooltip>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click="emit('close')">
|
||||
<svg-icon :path="mdiClose" type="mdi"/>
|
||||
<v-tooltip activator="parent" location="bottom">Close (Pyodide remains loaded)</v-tooltip>
|
||||
|
||||
Reference in New Issue
Block a user