-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: keyboard shortcut overlay pipeline #1636
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 8 commits
1cc08cd
8204609
ac5acd2
04b0100
920c971
742e2de
6cd7cf4
839612b
8dee855
e022a56
5d65e78
fdd5b3c
2f6031b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,9 +2,18 @@ import { Select as KSelect } from "@kobalte/core/select"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ToggleButton as KToggleButton } from "@kobalte/core/toggle-button"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createElementBounds } from "@solid-primitives/bounds"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { debounce } from "@solid-primitives/scheduled"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { invoke } from "@tauri-apps/api/core"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Menu } from "@tauri-apps/api/menu"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { cx } from "cva"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createEffect, createSignal, onMount, Show } from "solid-js"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createEffect, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createMemo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createResource, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createSignal, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| For, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onMount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Show, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "solid-js"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Tooltip from "~/components/Tooltip"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { captionsStore } from "~/store/captions"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -444,8 +453,34 @@ const gridStyle = { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function PreviewCanvas() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { latestFrame, canvasControls, performanceMode, setPerformanceMode } = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEditorContext(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| latestFrame, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| canvasControls, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| performanceMode, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setPerformanceMode, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| project, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| editorState, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } = useEditorContext(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type KeyboardOverlayEvent = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active_modifiers: string[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time_ms: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| down: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type SegmentKeyboardEvents = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| segment_index: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
chindris-mihai-alexandru marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| events: KeyboardOverlayEvent[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [keyboardEventsBySegment] = createResource(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await invoke<SegmentKeyboardEvents[]>("get_keyboard_events"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hasRenderedFrame = () => canvasControls()?.hasRenderedFrame() ?? false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -477,6 +512,200 @@ function PreviewCanvas() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentSourceTime = createMemo(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const timeline = project.timeline?.segments ?? []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (timeline.length === 0) return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const timelineTime = Math.max( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| editorState.previewTime ?? editorState.playbackTime, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let consumed = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let timelineIndex = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timelineIndex < timeline.length; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timelineIndex++ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const segment = timeline[timelineIndex]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!segment) continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const duration = (segment.end - segment.start) / segment.timescale; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (timelineTime <= consumed + duration) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const elapsed = timelineTime - consumed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recordingSegmentIndex: segment.recordingSegment ?? timelineIndex, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sourceTimeSec: segment.start + elapsed * segment.timescale, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| consumed += duration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const last = timeline[timeline.length - 1]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!last) return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recordingSegmentIndex: last.recordingSegment ?? timeline.length - 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sourceTimeSec: last.end, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const activeShortcut = createMemo(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const recentWindowMs = 850; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const modifierKeys = new Set([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Meta", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "MetaLeft", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "MetaRight", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Command", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Cmd", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Ctrl", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Control", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ControlLeft", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ControlRight", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Alt", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Option", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Opt", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "AltLeft", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "AltRight", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Shift", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ShiftLeft", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ShiftRight", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const modifierOrder = new Map([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ["⌃", 0], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ["⌥", 1], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ["⇧", 2], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ["⌘", 3], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isModifierKey = (key: string) => modifierKeys.has(key); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const normalizeModifier = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| modifier: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): "⌘" | "⌃" | "⌥" | "⇧" | null => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch (modifier) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Meta": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Command": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Cmd": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Super": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Win": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "⌘"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Ctrl": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Control": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+563
to
+594
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. modifier normalization logic is duplicated in three places (TypeScript UI, Rust rendering layer, Rust recording) - consider extracting to a shared module to prevent divergence Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src/routes/editor/Player.tsx
Line: 563-594
Comment:
modifier normalization logic is duplicated in three places (TypeScript UI, Rust rendering layer, Rust recording) - consider extracting to a shared module to prevent divergence
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reduced drift by aligning shortcut behavior across paths in 8dee855 (same shortcut-modifier gating in editor preview and renderer). Full cross-language dedupe is larger since this spans TS + Rust, so I kept this PR scoped and consistent. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "⌃"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Alt": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Option": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Opt": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "AltGraph": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "⌥"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Shift": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "⇧"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const normalizeKey = (key: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch (key) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Left": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "←"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Right": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "→"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Up": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "↑"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Down": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "↓"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Enter": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Return": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "Return"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Escape": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "Esc"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Backspace": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "Delete"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Delete": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "Del"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "CapsLock": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "Caps"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "PageUp": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "Page Up"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "PageDown": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "Page Down"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case "Space": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "Space"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return key.toUpperCase(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const source = currentSourceTime(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const segments = keyboardEventsBySegment(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!source || !segments) return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const segmentEvents = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| segments.find((s) => s.segment_index === source.recordingSegmentIndex) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
chindris-mihai-alexandru marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ?.events ?? []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (segmentEvents.length === 0) return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const nowMs = source.sourceTimeSec * 1000; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const active = new Map<string, { label: string; downTime: number }>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let lastRecent: { label: string; downTime: number } | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const event of segmentEvents) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (event.time_ms > nowMs) break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isModifierKey(event.key)) continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const normalizedModifiers = event.active_modifiers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter((modifier) => modifier !== event.key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(normalizeModifier) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (modifier): modifier is "⌘" | "⌃" | "⌥" | "⇧" => modifier !== null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .sort() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter((value, index, values) => values.indexOf(value) === index) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .sort( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (a, b) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (modifierOrder.get(a) ?? Number.POSITIVE_INFINITY) - | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (modifierOrder.get(b) ?? Number.POSITIVE_INFINITY), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const label = [...normalizedModifiers, normalizeKey(event.key)].join( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| " + ", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+657
to
+676
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This currently displays single-key presses too (no Ctrl/Alt/Meta), which can accidentally surface typed content. If the intent is “shortcut overlay”, consider only tracking keydowns when there’s at least one chord modifier, while still processing keyup to clear state.
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implemented in 8dee855. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (event.down) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const state = { label, downTime: event.time_ms }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active.set(event.key, state); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!lastRecent || state.downTime > lastRecent.downTime) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lastRecent = state; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| active.delete(event.key); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const activeValues = [...active.values()].sort( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (a, b) => b.downTime - a.downTime, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (activeValues.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label: activeValues[0]?.label ?? "", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| opacity: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scale: 1.05, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (lastRecent && nowMs - lastRecent.downTime <= recentWindowMs) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const remaining = 1 - (nowMs - lastRecent.downTime) / recentWindowMs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const clamped = Math.min(Math.max(remaining, 0), 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label: lastRecent.label, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| opacity: clamped, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scale: 1 + 0.05 * clamped, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updateDebouncedBounds = debounce( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (width: number, height: number) => setDebouncedBounds({ width, height }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 100, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -557,6 +786,32 @@ function PreviewCanvas() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={{ contain: "layout style" }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onContextMenu={handleContextMenu} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Show when={activeShortcut()}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {(shortcut) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="absolute left-0 right-0 z-20 flex justify-center bottom-3 pointer-events-none"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class="rounded-md border border-gray-6 bg-gray-1/95 px-3 py-1.5 shadow-lg backdrop-blur-sm transition-[opacity,transform] duration-75" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| opacity: shortcut().opacity, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transform: `scale(${shortcut().scale})`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="flex items-center gap-1.5 text-[11px] text-gray-10 uppercase tracking-wide"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span>Shortcut</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div class="mt-0.5 flex flex-wrap items-center gap-1"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <For each={shortcut().label.split(" + ")}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {(part) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <kbd class="rounded border border-gray-6 bg-gray-2 px-1.5 py-0.5 text-[11px] font-mono font-medium text-gray-12 shadow-sm"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {part} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </kbd> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </For> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Show> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class="flex overflow-hidden absolute inset-0 justify-center items-center h-full" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={{ visibility: hasFrame() ? "visible" : "hidden" }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Swallowing cursor-event load errors makes it hard to debug corrupted/partial recordings. Consider logging the error (still returning an empty list so the UI keeps working).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented in e022a56.
get_keyboard_eventsnow logs cursor-event load failures withwarn!(including path and error) and still returns an empty vector for resilience.