diff --git a/frontend/viewer/src/lib/components/audio/audio-editor.svelte b/frontend/viewer/src/lib/components/audio/audio-editor.svelte index 5f3288f1e5..f44c1e79a0 100644 --- a/frontend/viewer/src/lib/components/audio/audio-editor.svelte +++ b/frontend/viewer/src/lib/components/audio/audio-editor.svelte @@ -68,6 +68,7 @@ if (!finalAudio) throw new Error('No audio to download'); const url = URL.createObjectURL(finalAudio); try { + //todo only works on desktop, not mobile const a = document.createElement('a'); a.href = url; a.download = `${finalAudio.name}`; diff --git a/frontend/viewer/src/lib/components/field-editors/audio-input.svelte b/frontend/viewer/src/lib/components/field-editors/audio-input.svelte index d304ae9a83..1d77897317 100644 --- a/frontend/viewer/src/lib/components/field-editors/audio-input.svelte +++ b/frontend/viewer/src/lib/components/field-editors/audio-input.svelte @@ -58,10 +58,12 @@ loader = defaultLoader, audioId = $bindable(), onchange = () => {}, + readonly = false, }: { - loader?: (audioId: string) => Promise, + loader?: (audioId: string) => Promise<{stream: ReadableStream, filename: string} | undefined | typeof handled>, audioId: string | undefined, onchange?: (audioId: string | undefined) => void; + readonly?: boolean; } = $props(); const projectContext = useProjectContext(); @@ -87,23 +89,24 @@ return handled; } - return await file.stream.stream(); + return {stream: await file.stream.stream(), filename: file.fileName ?? ''}; } async function load() { if (!audio || loadedAudioId === audioId || !audioId) return !!audioId; playerState = 'loading'; try { - const stream = await loader(audioId); - if (stream === handled) return false; - if (!stream) { + const result = await loader(audioId); + if (result === handled) return false; + if (!result) { AppNotification.error(`Failed to load audio ${audioId}`); return; } - let blob = await new Response(stream).blob(); + let blob = await new Response(result.stream).blob(); if (audio.src) URL.revokeObjectURL(audio.src); loadedAudioId = undefined; audio.src = URL.createObjectURL(blob); + filename = result.filename; loadedAudioId = audioId; return true; } finally { @@ -182,6 +185,7 @@ let loadedAudioId = $state(); + let filename = $state(''); let audio = $state(); let audioRuned = $derived(audio ? new AudioRuned(audio) : null); useEventListener(() => audio, 'ended', () => playerState = 'paused'); @@ -233,6 +237,16 @@ } } + async function onSaveAs() { + if (!audio) return; + await load(); + //todo sadly this only works on desktop, not mobile, but it's the same with save as with the audio editor. + const a = document.createElement('a'); + a.href = audio.src; + a.download = filename; + a.click(); + } + function onAudioError(event: Event) { if (audioHasKnownFlacSeekError()) { console.log('Ignoring known FLAC seek error. Will try to recover on next play.'); @@ -253,9 +267,15 @@ {#if supportsAudio} {#if !audioId} - + {#if !readonly} + + {:else} +
+ {$t`No audio`} +
+ {/if} {:else if isNotFoundAudioId(audioId)}
{$t`Audio file not included in Send & Receive`} @@ -301,11 +321,16 @@ {/snippet} - - {$t`Replace audio`} - - - {$t`Remove audio`} + {#if !readonly} + + {$t`Replace audio`} + + + {$t`Remove audio`} + + {/if} + + {$t`Save As`} diff --git a/frontend/viewer/src/lib/components/field-editors/multi-ws-input.svelte b/frontend/viewer/src/lib/components/field-editors/multi-ws-input.svelte index eba883e7f1..c8dee3b26f 100644 --- a/frontend/viewer/src/lib/components/field-editors/multi-ws-input.svelte +++ b/frontend/viewer/src/lib/components/field-editors/multi-ws-input.svelte @@ -50,7 +50,9 @@ autocapitalize="off" onchange={() => onchange?.(ws.wsId, value[ws.wsId], value)} /> {:else} - onchange?.(ws.wsId, value[ws.wsId], value)}/> + onchange?.(ws.wsId, value[ws.wsId], value)} + {readonly} /> {/if}
{/each} diff --git a/frontend/viewer/src/lib/components/field-editors/rich-multi-ws-input.svelte b/frontend/viewer/src/lib/components/field-editors/rich-multi-ws-input.svelte index f51237803b..ffa5882ba9 100644 --- a/frontend/viewer/src/lib/components/field-editors/rich-multi-ws-input.svelte +++ b/frontend/viewer/src/lib/components/field-editors/rich-multi-ws-input.svelte @@ -75,7 +75,9 @@ aria-label={ws.abbreviation} /> {:else} - getAudioId(value[ws.wsId]), audioId => setAudioId(audioId, ws.wsId)}/> + getAudioId(value[ws.wsId]), audioId => setAudioId(audioId, ws.wsId)} + {readonly} /> {/if} {/each} diff --git a/frontend/viewer/src/locales/en.po b/frontend/viewer/src/locales/en.po index 346d4b9ed6..b26176aae0 100644 --- a/frontend/viewer/src/locales/en.po +++ b/frontend/viewer/src/locales/en.po @@ -658,6 +658,10 @@ msgstr "New Word" msgid "No activity found" msgstr "No activity found" +#: src/lib/components/field-editors/audio-input.svelte +msgid "No audio" +msgstr "No audio" + #: src/lib/history/HistoryView.svelte msgid "No change name" msgstr "No change name" @@ -811,6 +815,10 @@ msgstr "Reopen" msgid "Replace audio" msgstr "Replace audio" +#: src/lib/components/field-editors/audio-input.svelte +msgid "Save As" +msgstr "Save As" + #: src/lib/components/audio/AudioDialog.svelte msgid "Save audio" msgstr "Save audio" diff --git a/frontend/viewer/src/locales/es.po b/frontend/viewer/src/locales/es.po index 5c4cdbb7fe..1f1ca13175 100644 --- a/frontend/viewer/src/locales/es.po +++ b/frontend/viewer/src/locales/es.po @@ -663,6 +663,10 @@ msgstr "Nueva palabra" msgid "No activity found" msgstr "No se ha encontrado actividad" +#: src/lib/components/field-editors/audio-input.svelte +msgid "No audio" +msgstr "" + #: src/lib/history/HistoryView.svelte msgid "No change name" msgstr "Sin cambio de nombre" @@ -816,6 +820,10 @@ msgstr "Vuelva a abrir" msgid "Replace audio" msgstr "Sustituir audio" +#: src/lib/components/field-editors/audio-input.svelte +msgid "Save As" +msgstr "" + #: src/lib/components/audio/AudioDialog.svelte msgid "Save audio" msgstr "Guardar audio" diff --git a/frontend/viewer/src/locales/fr.po b/frontend/viewer/src/locales/fr.po index 26bb3d6329..359e1b0a74 100644 --- a/frontend/viewer/src/locales/fr.po +++ b/frontend/viewer/src/locales/fr.po @@ -663,6 +663,10 @@ msgstr "Nouveau mot" msgid "No activity found" msgstr "Aucune activité trouvée" +#: src/lib/components/field-editors/audio-input.svelte +msgid "No audio" +msgstr "" + #: src/lib/history/HistoryView.svelte msgid "No change name" msgstr "Pas de changement de nom" @@ -816,6 +820,10 @@ msgstr "Réouverture" msgid "Replace audio" msgstr "Remplacer l'audio" +#: src/lib/components/field-editors/audio-input.svelte +msgid "Save As" +msgstr "" + #: src/lib/components/audio/AudioDialog.svelte msgid "Save audio" msgstr "Sauvegarder l'audio" diff --git a/frontend/viewer/src/locales/id.po b/frontend/viewer/src/locales/id.po index 58dc72b1a4..6ba204f5f9 100644 --- a/frontend/viewer/src/locales/id.po +++ b/frontend/viewer/src/locales/id.po @@ -663,6 +663,10 @@ msgstr "Kata Baru" msgid "No activity found" msgstr "Tidak ada aktivitas yang ditemukan" +#: src/lib/components/field-editors/audio-input.svelte +msgid "No audio" +msgstr "" + #: src/lib/history/HistoryView.svelte msgid "No change name" msgstr "Tidak ada perubahan nama" @@ -816,6 +820,10 @@ msgstr "Buka kembali" msgid "Replace audio" msgstr "Mengganti audio" +#: src/lib/components/field-editors/audio-input.svelte +msgid "Save As" +msgstr "" + #: src/lib/components/audio/AudioDialog.svelte msgid "Save audio" msgstr "Menyimpan audio" diff --git a/frontend/viewer/src/locales/ko.po b/frontend/viewer/src/locales/ko.po index 57f419c371..77607c4ee7 100644 --- a/frontend/viewer/src/locales/ko.po +++ b/frontend/viewer/src/locales/ko.po @@ -663,6 +663,10 @@ msgstr "새 단어" msgid "No activity found" msgstr "활동을 찾을 수 없습니다." +#: src/lib/components/field-editors/audio-input.svelte +msgid "No audio" +msgstr "" + #: src/lib/history/HistoryView.svelte msgid "No change name" msgstr "이름 변경 안 함" @@ -816,6 +820,10 @@ msgstr "다시 열기" msgid "Replace audio" msgstr "오디오 교체" +#: src/lib/components/field-editors/audio-input.svelte +msgid "Save As" +msgstr "" + #: src/lib/components/audio/AudioDialog.svelte msgid "Save audio" msgstr "오디오 저장"