hlassiege/darylpublic Fork
⑂ dfde5ac
Commits
⬇ Clone ▾
git clone https://git.rickub.com/hlassiege/daryl.git
git clone ssh://git@rickub.com/hlassiege/daryl.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

feat(montage): show a beat as queued as soon as its creation is requested

Clicking create gave no feedback until the server answered, inviting repeated clicks. Launched beats now show as queued right away, and a beat already being launched is not sent again.
hlassiege committed 2026-10-11T17:39:58+02:00 Browse files
dfde5ac parent: d975f4e
modified web/src/project/steps/MontageStep.vue +20 -5
@@ -212,7 +212,14 @@ async function removeBeat(id: string) {
212212 SB.edit((list) => list.filter((x) => x.id !== id), 0);
213213 if (beatSel.value === id) beatSel.value = null;
214214 }
215-const anims = computed<Record<string, AnimState>>(() => SB.view.value?.anim_jobs ?? {});
215+/** les beats dont on vient de demander la création : affichés « en file » tout de suite, sans attendre le serveur */
216+const launching = ref<string[]>([]);
217+const anims = computed<Record<string, AnimState>>(() => {
218+ const a = { ...(SB.view.value?.anim_jobs ?? {}) };
219+ for (const id of launching.value)
220+ if (!["queued", "running"].includes(a[id]?.status ?? "")) a[id] = { status: "queued", activity: null, error: null };
221+ return a;
222+});
216223 const ORDER = ["running", "queued", "error"];
217224 /** les créations visibles dans la barre des tâches : en cours d'abord, puis en file, puis ratées */
218225 const tasks = computed(() => pending.value
@@ -229,10 +236,18 @@ const launchable = computed(() => pending.value.filter((b) => !["queued", "runni
229236 const toAnimate = computed(() => launchable.value.filter(needsAnim));
230237 const toPlace = computed(() => launchable.value.filter((b) => !needsAnim(b)));
231238 async function createAnimations(ids: string[]) {
232- const r = await SB.animate(ids);
233- if (r?.skipped.length) toast(tr("editing.storyboard.skipped", r.skipped.length));
234- if (r?.missing.length) toast(tr("editing.storyboard.broll.missingToast", r.missing.length));
235- await M.load();
239+ // un double clic ne relance pas ce qui part déjà
240+ ids = ids.filter((id) => !launching.value.includes(id));
241+ if (!ids.length) return;
242+ launching.value = [...launching.value, ...ids];
243+ try {
244+ const r = await SB.animate(ids);
245+ if (r?.skipped.length) toast(tr("editing.storyboard.skipped", r.skipped.length));
246+ if (r?.missing.length) toast(tr("editing.storyboard.broll.missingToast", r.missing.length));
247+ await M.load();
248+ } finally {
249+ launching.value = launching.value.filter((id) => !ids.includes(id));
250+ }
236251 }
237252 // une animation terminée : la timeline relue pour afficher sa composition
238253 watch(() => Object.keys(SB.view.value?.composed ?? {}).join(","), () => M.load());
@@ -212,7 +212,14 @@ async function removeBeat(id: string) {
212 SB.edit((list) => list.filter((x) => x.id !== id), 0);212 SB.edit((list) => list.filter((x) => x.id !== id), 0);
213 if (beatSel.value === id) beatSel.value = null;213 if (beatSel.value === id) beatSel.value = null;
214 }214 }
215-const anims = computed<Record<string, AnimState>>(() => SB.view.value?.anim_jobs ?? {});215+/** les beats dont on vient de demander la création : affichés « en file » tout de suite, sans attendre le serveur */
216+const launching = ref<string[]>([]);
217+const anims = computed<Record<string, AnimState>>(() => {
218+ const a = { ...(SB.view.value?.anim_jobs ?? {}) };
219+ for (const id of launching.value)
220+ if (!["queued", "running"].includes(a[id]?.status ?? "")) a[id] = { status: "queued", activity: null, error: null };
221+ return a;
222+});
216 const ORDER = ["running", "queued", "error"];223 const ORDER = ["running", "queued", "error"];
217 /** les créations visibles dans la barre des tâches : en cours d'abord, puis en file, puis ratées */224 /** les créations visibles dans la barre des tâches : en cours d'abord, puis en file, puis ratées */
218 const tasks = computed(() => pending.value225 const tasks = computed(() => pending.value
@@ -229,10 +236,18 @@ const launchable = computed(() => pending.value.filter((b) => !["queued", "runni
229 const toAnimate = computed(() => launchable.value.filter(needsAnim));236 const toAnimate = computed(() => launchable.value.filter(needsAnim));
230 const toPlace = computed(() => launchable.value.filter((b) => !needsAnim(b)));237 const toPlace = computed(() => launchable.value.filter((b) => !needsAnim(b)));
231 async function createAnimations(ids: string[]) {238 async function createAnimations(ids: string[]) {
232- const r = await SB.animate(ids);239+ // un double clic ne relance pas ce qui part déjà
233- if (r?.skipped.length) toast(tr("editing.storyboard.skipped", r.skipped.length));240+ ids = ids.filter((id) => !launching.value.includes(id));
234- if (r?.missing.length) toast(tr("editing.storyboard.broll.missingToast", r.missing.length));241+ if (!ids.length) return;
235- await M.load();242+ launching.value = [...launching.value, ...ids];
243+ try {
244+ const r = await SB.animate(ids);
245+ if (r?.skipped.length) toast(tr("editing.storyboard.skipped", r.skipped.length));
246+ if (r?.missing.length) toast(tr("editing.storyboard.broll.missingToast", r.missing.length));
247+ await M.load();
248+ } finally {
249+ launching.value = launching.value.filter((id) => !ids.includes(id));
250+ }
236 }251 }
237 // une animation terminée : la timeline relue pour afficher sa composition252 // une animation terminée : la timeline relue pour afficher sa composition
238 watch(() => Object.keys(SB.view.value?.composed ?? {}).join(","), () => M.load());253 watch(() => Object.keys(SB.view.value?.composed ?? {}).join(","), () => M.load());