From 2da6d6dafbe1647f55fe09637a03135e4069d624 Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Tue, 19 Aug 2025 14:22:10 +0200 Subject: [PATCH 1/4] Make audio-input respect readonly --- .../field-editors/audio-input.svelte | 46 +++++++++++-------- .../field-editors/multi-ws-input.svelte | 4 +- .../field-editors/rich-multi-ws-input.svelte | 4 +- frontend/viewer/src/locales/en.po | 4 ++ frontend/viewer/src/locales/es.po | 4 ++ frontend/viewer/src/locales/fr.po | 4 ++ frontend/viewer/src/locales/id.po | 4 ++ frontend/viewer/src/locales/ko.po | 4 ++ 8 files changed, 54 insertions(+), 20 deletions(-) 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..0917921a28 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, audioId: string | undefined, onchange?: (audioId: string | undefined) => void; + readonly?: boolean; } = $props(); const projectContext = useProjectContext(); @@ -253,9 +255,15 @@ {#if supportsAudio} {#if !audioId} - + {#if !readonly} + + {:else} +
+ {$t`No audio`} +
+ {/if} {:else if isNotFoundAudioId(audioId)}
{$t`Audio file not included in Send & Receive`} @@ -294,21 +302,23 @@ / {/if} - - - {#snippet child({props})} -
{/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 dc97e4246a..e3163f356f 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:264 +msgid "No audio" +msgstr "No audio" + #: src/lib/history/HistoryView.svelte:78 msgid "No change name" msgstr "No change name" diff --git a/frontend/viewer/src/locales/es.po b/frontend/viewer/src/locales/es.po index b5d71ea617..f7b0098124 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:264 +msgid "No audio" +msgstr "" + #: src/lib/history/HistoryView.svelte:78 msgid "No change name" msgstr "Sin cambio de nombre" diff --git a/frontend/viewer/src/locales/fr.po b/frontend/viewer/src/locales/fr.po index c727252a43..5caa321c3b 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:264 +msgid "No audio" +msgstr "" + #: src/lib/history/HistoryView.svelte:78 msgid "No change name" msgstr "Pas de changement de nom" diff --git a/frontend/viewer/src/locales/id.po b/frontend/viewer/src/locales/id.po index d01cd790c9..9248bd62be 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:264 +msgid "No audio" +msgstr "" + #: src/lib/history/HistoryView.svelte:78 msgid "No change name" msgstr "Tidak ada perubahan nama" diff --git a/frontend/viewer/src/locales/ko.po b/frontend/viewer/src/locales/ko.po index 18174780b9..d167bb7278 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:264 +msgid "No audio" +msgstr "" + #: src/lib/history/HistoryView.svelte:78 msgid "No change name" msgstr "이름 변경 안 함" From 3988ad40f162646d37dafdc205e01a2d12489aed Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 20 Aug 2025 11:03:57 +0700 Subject: [PATCH 2/4] add a save as button to audio input --- .../lib/components/audio/audio-editor.svelte | 1 + .../field-editors/audio-input.svelte | 50 ++++++++++++------- 2 files changed, 34 insertions(+), 17 deletions(-) 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 0917921a28..60dc1c1d94 100644 --- a/frontend/viewer/src/lib/components/field-editors/audio-input.svelte +++ b/frontend/viewer/src/lib/components/field-editors/audio-input.svelte @@ -52,6 +52,7 @@ import {ReadFileResult} from '$lib/dotnet-types/generated-types/MiniLcm/Media/ReadFileResult'; import {useDialogsService} from '$lib/services/dialogs-service'; import * as ResponsiveMenu from '$lib/components/responsive-menu'; + import * as stream from 'node:stream'; const handled = Symbol(); let { @@ -60,7 +61,7 @@ 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; @@ -89,23 +90,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 { @@ -184,6 +186,7 @@ let loadedAudioId = $state(); + let filename = $state(''); let audio = $state(); let audioRuned = $derived(audio ? new AudioRuned(audio) : null); useEventListener(() => audio, 'ended', () => playerState = 'paused'); @@ -235,6 +238,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.'); @@ -302,23 +315,26 @@ / {/if} - {#if !readonly} - - - {#snippet child({props})} -