| @@ -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.value | 225 | 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 composition | 252 | // 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()); |