hlassiege/darylpublic Fork
⑂ 6c30fa0
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): give storyboard beats their own lane above motion

A beat no longer lands on the motion, zoom or B-roll lane: it gets a
dedicated "Beats" lane, so a beat can sit over a finished motion (to
ask for a change) without colliding with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hlassiege committed 2026-10-11T17:40:56+02:00 Browse files
6c30fa0 parent: dfde5ac
modified web/src/i18n/locales/en/editing.ts +1 -0
@@ -33,6 +33,7 @@ export default {
3333 markers: "Marker",
3434 },
3535 lanes: {
36+ beats: "Beats",
3637 chapters: "Chapters",
3738 texts: "Texts",
3839 motion: "Motion",
@@ -33,6 +33,7 @@ export default {
33 markers: "Marker",33 markers: "Marker",
34 },34 },
35 lanes: {35 lanes: {
36+ beats: "Beats",
36 chapters: "Chapters",37 chapters: "Chapters",
37 texts: "Texts",38 texts: "Texts",
38 motion: "Motion",39 motion: "Motion",
modified web/src/i18n/locales/fr/editing.ts +1 -0
@@ -33,6 +33,7 @@ export default {
3333 markers: "Marqueur",
3434 },
3535 lanes: {
36+ beats: "Beats",
3637 chapters: "Chapitres",
3738 texts: "Textes",
3839 motion: "Motion",
@@ -33,6 +33,7 @@ export default {
33 markers: "Marqueur",33 markers: "Marqueur",
34 },34 },
35 lanes: {35 lanes: {
36+ beats: "Beats",
36 chapters: "Chapitres",37 chapters: "Chapitres",
37 texts: "Textes",38 texts: "Textes",
38 motion: "Motion",39 motion: "Motion",
modified web/src/project/steps/MontageStep.vue +1 -2
@@ -153,7 +153,7 @@ async function comment(text?: string) {
153153 chat.value?.compose(text ?? "");
154154 }
155155
156-// --- storyboard : les beats cochés pas encore faits, en pointillés sur leur piste et en slide sur l'aperçu ;
156+// --- storyboard : les beats cochés pas encore faits, en pointillés sur la piste des beats et en slide sur l'aperçu ;
157157 // on crée leur animation un par un (pour tester vite) ou tous d'un coup ; zooms et B-roll vidéo se posent tels quels.
158158 // On en pose aussi à la main (M) : un motion à sa consigne vide, qu'on tire sur son passage et qu'on écrit sur sa slide
159159 const SB = useStoryboard(project.value.id);
@@ -161,7 +161,6 @@ const pending = computed(() => (SB.view.value?.board?.beats ?? [])
161161 .filter((b) => b.on && !SB.view.value?.composed[b.id]));
162162 const ghosts = computed(() => pending.value.map((b) => ({
163163 id: b.id, at: b.at, dur: b.dur, on: b.on, label: b.show || tr(`editing.storyboard.types.${b.type}`), color: TYPE_COLOR[b.type],
164- track: b.type === "zoom" ? "zooms" as const : b.type === "broll" && b.src && !isImage(b.src) ? "broll" as const : "motion" as const,
165164 })));
166165 /** le beat sélectionné sur la timeline (sa fiche remplace l'inspecteur) */
167166 const beatSel = ref<string | null>(null);
@@ -153,7 +153,7 @@ async function comment(text?: string) {
153 chat.value?.compose(text ?? "");153 chat.value?.compose(text ?? "");
154 }154 }
155 155
156-// --- storyboard : les beats cochés pas encore faits, en pointillés sur leur piste et en slide sur l'aperçu ;156+// --- storyboard : les beats cochés pas encore faits, en pointillés sur la piste des beats et en slide sur l'aperçu ;
157 // on crée leur animation un par un (pour tester vite) ou tous d'un coup ; zooms et B-roll vidéo se posent tels quels.157 // on crée leur animation un par un (pour tester vite) ou tous d'un coup ; zooms et B-roll vidéo se posent tels quels.
158 // On en pose aussi à la main (M) : un motion à sa consigne vide, qu'on tire sur son passage et qu'on écrit sur sa slide158 // On en pose aussi à la main (M) : un motion à sa consigne vide, qu'on tire sur son passage et qu'on écrit sur sa slide
159 const SB = useStoryboard(project.value.id);159 const SB = useStoryboard(project.value.id);
@@ -161,7 +161,6 @@ const pending = computed(() => (SB.view.value?.board?.beats ?? [])
161 .filter((b) => b.on && !SB.view.value?.composed[b.id]));161 .filter((b) => b.on && !SB.view.value?.composed[b.id]));
162 const ghosts = computed(() => pending.value.map((b) => ({162 const ghosts = computed(() => pending.value.map((b) => ({
163 id: b.id, at: b.at, dur: b.dur, on: b.on, label: b.show || tr(`editing.storyboard.types.${b.type}`), color: TYPE_COLOR[b.type],163 id: b.id, at: b.at, dur: b.dur, on: b.on, label: b.show || tr(`editing.storyboard.types.${b.type}`), color: TYPE_COLOR[b.type],
164- track: b.type === "zoom" ? "zooms" as const : b.type === "broll" && b.src && !isImage(b.src) ? "broll" as const : "motion" as const,
165 })));164 })));
166 /** le beat sélectionné sur la timeline (sa fiche remplace l'inspecteur) */165 /** le beat sélectionné sur la timeline (sa fiche remplace l'inspecteur) */
167 const beatSel = ref<string | null>(null);166 const beatSel = ref<string | null>(null);
modified web/src/project/steps/montage/Timeline.vue +6 -4
@@ -5,7 +5,8 @@
55 * rogner (avec aimantation), la lame coupe ; glisser sur une zone vide sélectionne une plage.
66 * Seule la partie visible est dessinée : vignettes et forme d'onde suivent le zoom.
77 * Un média glissé depuis l'inspecteur s'insère dans l'A-roll s'il est lâché sur sa piste, sinon il se pose au-dessus.
8- * Les beats du storyboard pas encore faits (en pointillés sur leur piste) se glissent et se tirent aussi :
8+ * Les beats du storyboard pas encore faits, en pointillés sur leur piste à eux (au-dessus du motion : un beat peut
9+ * couvrir un plan déjà fait, pour le refaire), se glissent et se tirent aussi :
910 * leurs bords s'aimantent aux mots (le début un peu avant un mot, la fin un peu après).
1011 */
1112 import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
@@ -21,8 +22,8 @@ const props = defineProps<{
2122 tl: Timeline; total: number; t: number; tool: "select" | "blade";
2223 sourceDuration: number; analysis: MontageView["analysis"]; peaks: number[] | null;
2324 media: MontageView["media"]; motions: MontageView["motions"]; playing: boolean;
24- /** les beats du storyboard, en pointillés sur leur piste (motion, zooms ou B-roll), à leur couleur */
25- ghosts?: { id: string; at: number; dur: number; label: string; on: boolean; track: "motion" | "zooms" | "broll"; color: string }[];
25+ /** les beats du storyboard pas encore faits, en pointillés sur leur piste, à la couleur de leur type */
26+ ghosts?: { id: string; at: number; dur: number; label: string; on: boolean; color: string }[];
2627 /** le beat sélectionné */
2728 beatSel?: string | null;
2829 /** les mots de la timeline [début, fin], et l'avance / la traîne d'un beat sur ses mots */
@@ -42,6 +43,7 @@ const LABEL_W = 92;
4243 const ROWS = [
4344 { key: "chapters", h: 22 },
4445 { key: "texts", h: 24 },
46+ { key: "beats", h: 26 },
4547 { key: "motion", h: 30 },
4648 { key: "zooms", h: 22 },
4749 { key: "broll", h: 34 },
@@ -582,7 +584,7 @@ function motionState(id: string) {
582584
583585 <!-- storyboard : les beats proposés, sur leur piste -->
584586 <div v-for="v in ghostsV" :key="`g-${v.it.id}`" class="ghost" :class="{ off: !v.it.on, on: beatSel === v.it.id }"
585- :style="{ top: `${tops[v.it.track] + 2}px`, height: `${ROWS.find((r) => r.key === v.it.track)!.h - 4}px`, left: `${x(v.a)}px`,
587+ :style="{ top: `${tops.beats + 2}px`, height: `${rowH.beats - 4}px`, left: `${x(v.a)}px`,
586588 width: `${Math.max(3, x(v.b - v.a))}px`, '--c': v.it.color }"
587589 :title="v.it.label" @pointerdown="downGhost($event, v.it)">
588590 <i class="edge l" @pointerdown="downGhost($event, v.it, 'left')" />
@@ -5,7 +5,8 @@
5 * rogner (avec aimantation), la lame coupe ; glisser sur une zone vide sélectionne une plage.5 * rogner (avec aimantation), la lame coupe ; glisser sur une zone vide sélectionne une plage.
6 * Seule la partie visible est dessinée : vignettes et forme d'onde suivent le zoom.6 * Seule la partie visible est dessinée : vignettes et forme d'onde suivent le zoom.
7 * Un média glissé depuis l'inspecteur s'insère dans l'A-roll s'il est lâché sur sa piste, sinon il se pose au-dessus.7 * Un média glissé depuis l'inspecteur s'insère dans l'A-roll s'il est lâché sur sa piste, sinon il se pose au-dessus.
8- * Les beats du storyboard pas encore faits (en pointillés sur leur piste) se glissent et se tirent aussi :8+ * Les beats du storyboard pas encore faits, en pointillés sur leur piste à eux (au-dessus du motion : un beat peut
9+ * couvrir un plan déjà fait, pour le refaire), se glissent et se tirent aussi :
9 * leurs bords s'aimantent aux mots (le début un peu avant un mot, la fin un peu après).10 * leurs bords s'aimantent aux mots (le début un peu avant un mot, la fin un peu après).
10 */11 */
11 import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";12 import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
@@ -21,8 +22,8 @@ const props = defineProps<{
21 tl: Timeline; total: number; t: number; tool: "select" | "blade";22 tl: Timeline; total: number; t: number; tool: "select" | "blade";
22 sourceDuration: number; analysis: MontageView["analysis"]; peaks: number[] | null;23 sourceDuration: number; analysis: MontageView["analysis"]; peaks: number[] | null;
23 media: MontageView["media"]; motions: MontageView["motions"]; playing: boolean;24 media: MontageView["media"]; motions: MontageView["motions"]; playing: boolean;
24- /** les beats du storyboard, en pointillés sur leur piste (motion, zooms ou B-roll), à leur couleur */25+ /** les beats du storyboard pas encore faits, en pointillés sur leur piste, à la couleur de leur type */
25- ghosts?: { id: string; at: number; dur: number; label: string; on: boolean; track: "motion" | "zooms" | "broll"; color: string }[];26+ ghosts?: { id: string; at: number; dur: number; label: string; on: boolean; color: string }[];
26 /** le beat sélectionné */27 /** le beat sélectionné */
27 beatSel?: string | null;28 beatSel?: string | null;
28 /** les mots de la timeline [début, fin], et l'avance / la traîne d'un beat sur ses mots */29 /** les mots de la timeline [début, fin], et l'avance / la traîne d'un beat sur ses mots */
@@ -42,6 +43,7 @@ const LABEL_W = 92;
42 const ROWS = [43 const ROWS = [
43 { key: "chapters", h: 22 },44 { key: "chapters", h: 22 },
44 { key: "texts", h: 24 },45 { key: "texts", h: 24 },
46+ { key: "beats", h: 26 },
45 { key: "motion", h: 30 },47 { key: "motion", h: 30 },
46 { key: "zooms", h: 22 },48 { key: "zooms", h: 22 },
47 { key: "broll", h: 34 },49 { key: "broll", h: 34 },
@@ -582,7 +584,7 @@ function motionState(id: string) {
582 584
583 <!-- storyboard : les beats proposés, sur leur piste -->585 <!-- storyboard : les beats proposés, sur leur piste -->
584 <div v-for="v in ghostsV" :key="`g-${v.it.id}`" class="ghost" :class="{ off: !v.it.on, on: beatSel === v.it.id }"586 <div v-for="v in ghostsV" :key="`g-${v.it.id}`" class="ghost" :class="{ off: !v.it.on, on: beatSel === v.it.id }"
585- :style="{ top: `${tops[v.it.track] + 2}px`, height: `${ROWS.find((r) => r.key === v.it.track)!.h - 4}px`, left: `${x(v.a)}px`,587+ :style="{ top: `${tops.beats + 2}px`, height: `${rowH.beats - 4}px`, left: `${x(v.a)}px`,
586 width: `${Math.max(3, x(v.b - v.a))}px`, '--c': v.it.color }"588 width: `${Math.max(3, x(v.b - v.a))}px`, '--c': v.it.color }"
587 :title="v.it.label" @pointerdown="downGhost($event, v.it)">589 :title="v.it.label" @pointerdown="downGhost($event, v.it)">
588 <i class="edge l" @pointerdown="downGhost($event, v.it, 'left')" />590 <i class="edge l" @pointerdown="downGhost($event, v.it, 'left')" />