89 lines
2.5 KiB
Vue
89 lines
2.5 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
import { useStore } from '@/store'
|
|
|
|
const { state, packageEstimate, selectedFrames, buildTranscript } = useStore()
|
|
|
|
const busy = ref(false)
|
|
const copied = ref(false)
|
|
|
|
const saveLabel = computed(() => {
|
|
switch (state.saveStatus) {
|
|
case 'saving': return 'saving…'
|
|
case 'saved': return 'saved'
|
|
case 'error': return 'save failed'
|
|
default: return ''
|
|
}
|
|
})
|
|
|
|
function formatBytes(n: number): string {
|
|
if (n < 1024) return `${n} B`
|
|
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`
|
|
return `${(n / (1024 * 1024)).toFixed(1)} MB`
|
|
}
|
|
|
|
async function copyTranscript() {
|
|
await navigator.clipboard.writeText(buildTranscript())
|
|
copied.value = true
|
|
setTimeout(() => { copied.value = false }, 1500)
|
|
}
|
|
|
|
async function downloadPackage() {
|
|
if (!state.currentRunId) return
|
|
busy.value = true
|
|
try {
|
|
const { default: JSZip } = await import('jszip')
|
|
const zip = new JSZip()
|
|
zip.file('transcript.txt', buildTranscript())
|
|
const framesFolder = zip.folder('frames')!
|
|
for (const f of selectedFrames.value) {
|
|
const res = await fetch(f.url)
|
|
framesFolder.file(f.file, await res.blob())
|
|
}
|
|
const blob = await zip.generateAsync({ type: 'blob' })
|
|
const a = document.createElement('a')
|
|
a.href = URL.createObjectURL(blob)
|
|
a.download = `${state.currentRunId}-package.zip`
|
|
a.click()
|
|
URL.revokeObjectURL(a.href)
|
|
} finally {
|
|
busy.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="export-bar">
|
|
<span v-if="saveLabel" class="save" :class="state.saveStatus">{{ saveLabel }}</span>
|
|
<span class="estimate" v-if="state.currentRunId">
|
|
{{ packageEstimate.frames }}/{{ packageEstimate.totalFrames }} frames ·
|
|
{{ formatBytes(packageEstimate.bytes) }}
|
|
</span>
|
|
<button :disabled="!state.currentRunId" @click="copyTranscript">
|
|
{{ copied ? 'copied!' : 'Copy transcript' }}
|
|
</button>
|
|
<button :disabled="!state.currentRunId || busy" @click="downloadPackage">
|
|
{{ busy ? 'building…' : 'Download package' }}
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.export-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-3);
|
|
}
|
|
.estimate {
|
|
color: var(--text-secondary);
|
|
font-size: var(--font-size-sm);
|
|
font-family: var(--font-mono);
|
|
}
|
|
.save {
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
.save.saved { color: var(--status-live); }
|
|
.save.saving { color: var(--status-processing); }
|
|
.save.error { color: var(--status-error); }
|
|
</style>
|