Skip to content

Commit 415d93d

Browse files
authored
desktop app: reactivity of red markers when trimming + shortcuts even if elements are focused (#1331)
1 parent e9846b8 commit 415d93d

File tree

2 files changed

+121
-105
lines changed

2 files changed

+121
-105
lines changed

apps/desktop/src/routes/editor/Player.tsx

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -146,50 +146,57 @@ export function Player() {
146146
};
147147

148148
// Register keyboard shortcuts in one place
149-
useEditorShortcuts(
150-
() => document.activeElement === document.body,
151-
[
152-
{
153-
combo: "S",
154-
handler: () =>
155-
setEditorState(
156-
"timeline",
157-
"interactMode",
158-
editorState.timeline.interactMode === "split" ? "seek" : "split",
159-
),
160-
},
161-
{
162-
combo: "Mod+=",
163-
handler: () =>
164-
editorState.timeline.transform.updateZoom(
165-
editorState.timeline.transform.zoom / 1.1,
166-
editorState.playbackTime,
167-
),
168-
},
169-
{
170-
combo: "Mod+-",
171-
handler: () =>
172-
editorState.timeline.transform.updateZoom(
173-
editorState.timeline.transform.zoom * 1.1,
174-
editorState.playbackTime,
175-
),
176-
},
177-
{
178-
combo: "Space",
179-
handler: async () => {
180-
const prevTime = editorState.previewTime;
181-
182-
if (!editorState.playing) {
183-
if (prevTime !== null) setEditorState("playbackTime", prevTime);
184-
185-
await commands.seekTo(Math.floor(editorState.playbackTime * FPS));
186-
}
149+
useEditorShortcuts(() => {
150+
const el = document.activeElement;
151+
if (!el) return true;
152+
const tagName = el.tagName.toLowerCase();
153+
const isContentEditable = el.getAttribute("contenteditable") === "true";
154+
return !(
155+
tagName === "input" ||
156+
tagName === "textarea" ||
157+
isContentEditable
158+
);
159+
}, [
160+
{
161+
combo: "S",
162+
handler: () =>
163+
setEditorState(
164+
"timeline",
165+
"interactMode",
166+
editorState.timeline.interactMode === "split" ? "seek" : "split",
167+
),
168+
},
169+
{
170+
combo: "Mod+=",
171+
handler: () =>
172+
editorState.timeline.transform.updateZoom(
173+
editorState.timeline.transform.zoom / 1.1,
174+
editorState.playbackTime,
175+
),
176+
},
177+
{
178+
combo: "Mod+-",
179+
handler: () =>
180+
editorState.timeline.transform.updateZoom(
181+
editorState.timeline.transform.zoom * 1.1,
182+
editorState.playbackTime,
183+
),
184+
},
185+
{
186+
combo: "Space",
187+
handler: async () => {
188+
const prevTime = editorState.previewTime;
189+
190+
if (!editorState.playing) {
191+
if (prevTime !== null) setEditorState("playbackTime", prevTime);
192+
193+
await commands.seekTo(Math.floor(editorState.playbackTime * FPS));
194+
}
187195

188-
await handlePlayPauseClick();
189-
},
196+
await handlePlayPauseClick();
190197
},
191-
],
192-
);
198+
},
199+
]);
193200

194201
return (
195202
<div class="flex flex-col flex-1 rounded-xl border bg-gray-1 dark:bg-gray-2 border-gray-3">

apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx

Lines changed: 72 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -309,30 +309,34 @@ export function ClipTrack(
309309
if (m.type === "single") return m.value;
310310
})()}
311311
>
312-
{(marker) => (
313-
<div class="overflow-hidden -top-8 z-10 h-7 rounded-full -translate-x-1/2">
314-
<CutOffsetButton
315-
value={(() => {
316-
const m = marker();
317-
return m.type === "time" ? m.time : 0;
318-
})()}
319-
onClick={() => {
320-
setProject(
321-
"timeline",
322-
"segments",
323-
produce((s) => {
324-
if (marker().type === "reset") {
325-
s[i() - 1].end = s[i()].end;
326-
s.splice(i(), 1);
327-
} else {
328-
s[i() - 1].end = s[i()].start;
329-
}
330-
}),
331-
);
332-
}}
333-
/>
334-
</div>
335-
)}
312+
{(markerValue) => {
313+
const value = createMemo(() => {
314+
const m = markerValue();
315+
return m.type === "time" ? m.time : 0;
316+
});
317+
318+
return (
319+
<div class="overflow-hidden -top-8 z-10 h-7 rounded-full -translate-x-1/2">
320+
<CutOffsetButton
321+
value={value()}
322+
onClick={() => {
323+
setProject(
324+
"timeline",
325+
"segments",
326+
produce((s) => {
327+
if (markerValue().type === "reset") {
328+
s[i() - 1].end = s[i()].end;
329+
s.splice(i(), 1);
330+
} else {
331+
s[i() - 1].end = s[i()].start;
332+
}
333+
}),
334+
);
335+
}}
336+
/>
337+
</div>
338+
);
339+
}}
336340
</Match>
337341
<Match
338342
when={(() => {
@@ -345,16 +349,16 @@ export function ClipTrack(
345349
return m.right;
346350
})()}
347351
>
348-
{(marker) => {
349-
const markerValue = marker();
352+
{(markerValue) => {
353+
const value = createMemo(() => {
354+
const m = markerValue();
355+
return m.type === "time" ? m.time : 0;
356+
});
357+
350358
return (
351359
<div class="flex absolute -top-8 flex-row w-0 h-7 rounded-full">
352360
<CutOffsetButton
353-
value={
354-
markerValue.type === "time"
355-
? markerValue.time
356-
: 0
357-
}
361+
value={value()}
358362
class="-left-px absolute rounded-r-full !pl-1.5 rounded-tl-full"
359363
onClick={() => {
360364
setProject(
@@ -684,34 +688,38 @@ export function ClipTrack(
684688
return m.left;
685689
})()}
686690
>
687-
{(marker) => (
688-
<div
689-
class="absolute w-0 z-10 h-full *:absolute"
690-
style={{
691-
transform: `translateX(${segmentX() + segmentWidth()}px)`,
692-
}}
693-
>
694-
<div class="w-[2px] bottom-0 -top-2 rounded-full from-red-300 to-transparent bg-gradient-to-b -translate-x-1/2" />
695-
<div class="flex absolute -top-8 flex-row w-0 h-7 rounded-full">
696-
<CutOffsetButton
697-
value={(() => {
698-
const m = marker();
699-
return m.type === "time" ? m.time : 0;
700-
})()}
701-
class="-right-px absolute rounded-l-full !pr-1.5 rounded-tr-full"
702-
onClick={() => {
703-
setProject(
704-
"timeline",
705-
"segments",
706-
i(),
707-
"end",
708-
segmentRecording().display.duration,
709-
);
710-
}}
711-
/>
691+
{(markerValue) => {
692+
const value = createMemo(() => {
693+
const m = markerValue();
694+
return m.type === "time" ? m.time : 0;
695+
});
696+
697+
return (
698+
<div
699+
class="absolute w-0 z-10 h-full *:absolute"
700+
style={{
701+
transform: `translateX(${segmentX() + segmentWidth()}px)`,
702+
}}
703+
>
704+
<div class="w-[2px] bottom-0 -top-2 rounded-full from-red-300 to-transparent bg-gradient-to-b -translate-x-1/2" />
705+
<div class="flex absolute -top-8 flex-row w-0 h-7 rounded-full">
706+
<CutOffsetButton
707+
value={value()}
708+
class="-right-px absolute rounded-l-full !pr-1.5 rounded-tr-full"
709+
onClick={() => {
710+
setProject(
711+
"timeline",
712+
"segments",
713+
i(),
714+
"end",
715+
segmentRecording().display.duration,
716+
);
717+
}}
718+
/>
719+
</div>
712720
</div>
713-
</div>
714-
)}
721+
);
722+
}}
715723
</Show>
716724
</>
717725
);
@@ -770,11 +778,12 @@ function CutOffsetButton(props: {
770778
)}
771779
onClick={() => props.onClick?.()}
772780
>
773-
{props.value === 0 ? (
774-
<IconCapScissors class="size-3.5" />
775-
) : (
776-
formatTime(props.value)
777-
)}
781+
<Show
782+
when={props.value !== 0}
783+
fallback={<IconCapScissors class="size-3.5" />}
784+
>
785+
{formatTime(props.value)}
786+
</Show>
778787
</button>
779788
);
780789
}

0 commit comments

Comments
 (0)