bak
This commit is contained in:
1 parent
e4aa0dce86
commit
cc6b11e61d
23 files changed
+2047
-524
No files matched your search
+95
-168
@@ -1,19 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import {
|
||||
PhCode,
|
||||
PhDownloadSimple,
|
||||
PhEngine,
|
||||
PhFileHtml,
|
||||
PhFilmSlate,
|
||||
PhX,
|
||||
} from '@phosphor-icons/vue'
|
||||
import { useSceneStore } from '@/stores/scene'
|
||||
import { downloadExampleHtml, downloadRuntime, downloadSceneExport } from '@/lib/exportPlayer'
|
||||
import { downloadSceneAsVideo, pickMimeType } from '@/lib/videoExport'
|
||||
import { downloadFromServer } from '@/lib/serverExport'
|
||||
import { Modal } from '@/components/ui'
|
||||
|
||||
const props = defineProps<{ modelValue: boolean }>()
|
||||
defineProps<{ modelValue: boolean }>()
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
||||
|
||||
const store = useSceneStore()
|
||||
@@ -34,17 +34,17 @@ function sceneClone() {
|
||||
|
||||
function exportScene() {
|
||||
downloadSceneExport(sceneClone())
|
||||
close()
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
function exportRuntime() {
|
||||
downloadRuntime()
|
||||
close()
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
function exportHtml() {
|
||||
downloadExampleHtml(sceneClone())
|
||||
close()
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
async function exportWebm() {
|
||||
@@ -111,174 +111,110 @@ function dismissError() {
|
||||
progress.value = null
|
||||
}
|
||||
|
||||
const isLocked = () => !!progress.value && !progress.value.error
|
||||
const isLocked = computed(() => !!progress.value && !progress.value.error)
|
||||
|
||||
function close() {
|
||||
if (isLocked()) return
|
||||
emit('update:modelValue', false)
|
||||
function onUpdate(v: boolean) {
|
||||
if (!v && isLocked.value) return
|
||||
emit('update:modelValue', v)
|
||||
}
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && props.modelValue) close()
|
||||
}
|
||||
|
||||
onMounted(() => document.addEventListener('keydown', onKeyDown))
|
||||
onBeforeUnmount(() => document.removeEventListener('keydown', onKeyDown))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div v-if="modelValue" class="backdrop" @mousedown.self="close">
|
||||
<div class="dialog" role="dialog" aria-modal="true">
|
||||
<header class="dialog-header">
|
||||
<h2>Export</h2>
|
||||
<button class="close-btn" :disabled="isLocked()" title="Schließen" @click="close">
|
||||
<PhX :size="14" weight="bold" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div v-if="!progress" class="options">
|
||||
<button class="opt" @click="exportRuntime">
|
||||
<div class="opt-icon"><PhEngine :size="20" /></div>
|
||||
<div class="opt-text">
|
||||
<div class="opt-title">
|
||||
animatr-runtime.js
|
||||
<span class="badge">1× pro Seite</span>
|
||||
</div>
|
||||
<div class="opt-desc">Engine — einmal einbinden, beliebig viele Szenen teilen sich die Runtime</div>
|
||||
</div>
|
||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||
</button>
|
||||
|
||||
<button class="opt" @click="exportScene">
|
||||
<div class="opt-icon"><PhCode :size="20" /></div>
|
||||
<div class="opt-text">
|
||||
<div class="opt-title">
|
||||
animation.js
|
||||
<span class="badge">Szene</span>
|
||||
</div>
|
||||
<div class="opt-desc">Nur Szenen-Daten — benötigt geladene Runtime</div>
|
||||
</div>
|
||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||
</button>
|
||||
|
||||
<button class="opt" @click="exportHtml">
|
||||
<div class="opt-icon"><PhFileHtml :size="20" /></div>
|
||||
<div class="opt-text">
|
||||
<div class="opt-title">Beispiel HTML</div>
|
||||
<div class="opt-desc">Standalone HTML — Browser öffnen und fertig</div>
|
||||
</div>
|
||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||
</button>
|
||||
|
||||
<button class="opt" @click="exportMp4Server">
|
||||
<div class="opt-icon"><PhFilmSlate :size="20" /></div>
|
||||
<div class="opt-text">
|
||||
<div class="opt-title">
|
||||
MP4 Video
|
||||
<span class="badge accent">Server · Frame-exakt</span>
|
||||
</div>
|
||||
<div class="opt-desc">H.264, beliebige Auflösung — benötigt Render-Server</div>
|
||||
</div>
|
||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||
</button>
|
||||
|
||||
<button class="opt" :disabled="!videoSupported" @click="exportWebm">
|
||||
<div class="opt-icon"><PhFilmSlate :size="20" /></div>
|
||||
<div class="opt-text">
|
||||
<div class="opt-title">
|
||||
WebM Video
|
||||
<span class="badge">Browser · Realtime</span>
|
||||
</div>
|
||||
<div class="opt-desc">
|
||||
{{ videoSupported ? 'Ohne Server — Aufnahme in Echtzeit' : 'Browser unterstützt MediaRecorder nicht' }}
|
||||
</div>
|
||||
</div>
|
||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||
</button>
|
||||
<Modal
|
||||
:model-value="modelValue"
|
||||
title="Export"
|
||||
size="md"
|
||||
:dismissable="!isLocked"
|
||||
:close-on-backdrop="!isLocked"
|
||||
:close-on-esc="!isLocked"
|
||||
@update:model-value="onUpdate"
|
||||
>
|
||||
<div v-if="!progress" class="options">
|
||||
<button class="opt" @click="exportRuntime">
|
||||
<div class="opt-icon"><PhEngine :size="20" /></div>
|
||||
<div class="opt-text">
|
||||
<div class="opt-title">
|
||||
animatr-runtime.js
|
||||
<span class="badge">1× pro Seite</span>
|
||||
</div>
|
||||
<div class="opt-desc">Engine — einmal einbinden, beliebig viele Szenen teilen sich die Runtime</div>
|
||||
</div>
|
||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||
</button>
|
||||
|
||||
<div v-else-if="!progress.error" class="rendering">
|
||||
<div class="r-label">
|
||||
<PhFilmSlate :size="14" />
|
||||
<span class="r-text">{{ progress.label }}</span>
|
||||
<span class="r-percent">{{ Math.round(progress.progress * 100) }}%</span>
|
||||
</div>
|
||||
<div class="r-bar">
|
||||
<div class="r-bar-fill" :style="{ width: progress.progress * 100 + '%' }"></div>
|
||||
</div>
|
||||
<div class="r-actions">
|
||||
<button class="secondary" @click="cancel">Abbrechen</button>
|
||||
</div>
|
||||
<button class="opt" @click="exportScene">
|
||||
<div class="opt-icon"><PhCode :size="20" /></div>
|
||||
<div class="opt-text">
|
||||
<div class="opt-title">
|
||||
animation.js
|
||||
<span class="badge">Szene</span>
|
||||
</div>
|
||||
<div class="opt-desc">Nur Szenen-Daten — benötigt geladene Runtime</div>
|
||||
</div>
|
||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||
</button>
|
||||
|
||||
<div v-else class="error-view">
|
||||
<div class="error-title">Export fehlgeschlagen</div>
|
||||
<div class="error-msg">{{ progress.error }}</div>
|
||||
<div class="error-actions">
|
||||
<button class="primary" @click="dismissError">Zurück</button>
|
||||
</div>
|
||||
<button class="opt" @click="exportHtml">
|
||||
<div class="opt-icon"><PhFileHtml :size="20" /></div>
|
||||
<div class="opt-text">
|
||||
<div class="opt-title">Beispiel HTML</div>
|
||||
<div class="opt-desc">Standalone HTML — Browser öffnen und fertig</div>
|
||||
</div>
|
||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||
</button>
|
||||
|
||||
<button class="opt" @click="exportMp4Server">
|
||||
<div class="opt-icon"><PhFilmSlate :size="20" /></div>
|
||||
<div class="opt-text">
|
||||
<div class="opt-title">
|
||||
MP4 Video
|
||||
<span class="badge accent">Server · Frame-exakt</span>
|
||||
</div>
|
||||
<div class="opt-desc">H.264, beliebige Auflösung — benötigt Render-Server</div>
|
||||
</div>
|
||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||
</button>
|
||||
|
||||
<button class="opt" :disabled="!videoSupported" @click="exportWebm">
|
||||
<div class="opt-icon"><PhFilmSlate :size="20" /></div>
|
||||
<div class="opt-text">
|
||||
<div class="opt-title">
|
||||
WebM Video
|
||||
<span class="badge">Browser · Realtime</span>
|
||||
</div>
|
||||
<div class="opt-desc">
|
||||
{{ videoSupported ? 'Ohne Server — Aufnahme in Echtzeit' : 'Browser unterstützt MediaRecorder nicht' }}
|
||||
</div>
|
||||
</div>
|
||||
<PhDownloadSimple :size="14" class="opt-arrow" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!progress.error" class="rendering">
|
||||
<div class="r-label">
|
||||
<PhFilmSlate :size="14" />
|
||||
<span class="r-text">{{ progress.label }}</span>
|
||||
<span class="r-percent">{{ Math.round(progress.progress * 100) }}%</span>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
<div class="r-bar">
|
||||
<div class="r-bar-fill" :style="{ width: progress.progress * 100 + '%' }"></div>
|
||||
</div>
|
||||
<div class="r-actions">
|
||||
<button class="secondary" @click="cancel">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="error-view">
|
||||
<div class="error-title">Export fehlgeschlagen</div>
|
||||
<div class="error-msg">{{ progress.error }}</div>
|
||||
<div class="error-actions">
|
||||
<button class="primary" @click="dismissError">Zurück</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
z-index: 900;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: min(520px, calc(100% - 32px));
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--shadow-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 64px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.dialog-header h2 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.close-btn:hover:not(:disabled) {
|
||||
background: var(--panel-alt);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -466,13 +402,4 @@ onBeforeUnmount(() => document.removeEventListener('keydown', onKeyDown))
|
||||
.error-actions .primary:hover:not(:disabled) {
|
||||
background: var(--accent-soft-strong);
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 140ms var(--ease);
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user