summaryrefslogtreecommitdiff
path: root/apps/web/src/timeline/bookmarks
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-26 02:50:04 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-26 02:50:04 +0200
commiteea6d43c82e1fd09d94d73001090dad205b1f1d2 (patch)
tree384c92e467effb708bcc96e25117c064b18464b8 /apps/web/src/timeline/bookmarks
parentf1b45b4328cff6a016720ce7b38b87ac2992dc2d (diff)
downloadopencut-eea6d43c82e1fd09d94d73001090dad205b1f1d2.tar.gz
fix: model editor time as wasm MediaTime ticks end-to-end
Diffstat (limited to 'apps/web/src/timeline/bookmarks')
-rw-r--r--apps/web/src/timeline/bookmarks/components/bookmarks.tsx41
-rw-r--r--apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts46
-rw-r--r--apps/web/src/timeline/bookmarks/preview-overlay-source.tsx5
-rw-r--r--apps/web/src/timeline/bookmarks/utils.ts31
4 files changed, 73 insertions, 50 deletions
diff --git a/apps/web/src/timeline/bookmarks/components/bookmarks.tsx b/apps/web/src/timeline/bookmarks/components/bookmarks.tsx
index 20e22c57..0f0710cb 100644
--- a/apps/web/src/timeline/bookmarks/components/bookmarks.tsx
+++ b/apps/web/src/timeline/bookmarks/components/bookmarks.tsx
@@ -26,6 +26,13 @@ import { Label } from "@/components/ui/label";
import { uppercase } from "@/utils/string";
import { clamp, formatNumberForDisplay } from "@/utils/math";
import { timelineTimeToPixels, timelineTimeToSnappedPixels } from "@/timeline";
+import {
+ type MediaTime,
+ mediaTimeFromSeconds,
+ mediaTimeToSeconds,
+ subMediaTime,
+ ZERO_MEDIA_TIME,
+} from "@/wasm";
const MIN_BOOKMARK_WIDTH_PX = 2;
const BOOKMARK_MARKER_WIDTH_PX = 12;
@@ -39,12 +46,12 @@ function seekToBookmarkTime({
time,
}: {
editor: EditorCore;
- time: number;
+ time: MediaTime;
}) {
const activeProject = editor.project.getActive();
const duration = editor.timeline.getTotalDuration();
const rate = activeProject?.settings.fps ?? DEFAULT_FPS;
- const snappedTime = snappedSeekTime({ time, duration, rate }) ?? time;
+ const snappedTime = (snappedSeekTime({ time, duration, rate }) ?? time) as MediaTime;
editor.playback.seek({ time: snappedTime });
}
@@ -137,7 +144,7 @@ function TimelineBookmark({
const displayTime = isDragging ? dragState.currentTime : bookmark.time;
const time = bookmark.time;
- const bookmarkDuration = bookmark.duration ?? 0;
+ const bookmarkDuration = bookmark.duration ?? ZERO_MEDIA_TIME;
const durationWidth =
bookmarkDuration > 0
? timelineTimeToPixels({ time: bookmarkDuration, zoomLevel })
@@ -182,7 +189,7 @@ function TimelineBookmark({
left: `${bookmarkLeft}px`,
width: `${bookmarkWidth}px`,
}}
- aria-label={`Bookmark at ${formatNumberForDisplay({ value: time, fractionDigits: 1 })}s`}
+ aria-label={`Bookmark at ${formatNumberForDisplay({ value: mediaTimeToSeconds({ time }), fractionDigits: 1 })}s`}
type="button"
onMouseDown={handleMouseDown}
onClick={handleClick}
@@ -286,8 +293,8 @@ function BookmarkPopoverContent({
onPopoverClose,
}: {
bookmark: Bookmark;
- time: number;
- timelineDuration: number;
+ time: MediaTime;
+ timelineDuration: MediaTime;
onPopoverClose: () => void;
}) {
const editor = useEditor();
@@ -314,9 +321,8 @@ function BookmarkPopoverContent({
note,
color,
duration,
- }: Partial<{ note: string; color: string; duration: number }>) => {
- const updates: Partial<{ note: string; color: string; duration: number }> =
- {};
+ }: Partial<Omit<Bookmark, "time">>) => {
+ const updates: Partial<Omit<Bookmark, "time">> = {};
if (note !== undefined && note !== bookmark.note) updates.note = note;
if (
color !== undefined &&
@@ -330,6 +336,15 @@ function BookmarkPopoverContent({
if (Object.keys(updates).length === 0) return;
editor.scenes.updateBookmark({ time, updates });
};
+ const maxDuration = mediaTimeToSeconds({
+ time:
+ timelineDuration > time
+ ? subMediaTime({ a: timelineDuration, b: time })
+ : ZERO_MEDIA_TIME,
+ });
+ const durationSeconds = mediaTimeToSeconds({
+ time: bookmark.duration ?? ZERO_MEDIA_TIME,
+ });
return (
<>
@@ -387,7 +402,7 @@ function BookmarkPopoverContent({
type="number"
min={0}
step={0.1}
- value={bookmark.duration ?? 0}
+ value={durationSeconds}
onChange={(event) => {
const parsed = parseFloat(event.target.value);
const value = Number.isNaN(parsed)
@@ -395,9 +410,11 @@ function BookmarkPopoverContent({
: clamp({
value: parsed,
min: 0,
- max: Math.max(0, timelineDuration - time),
+ max: maxDuration,
});
- handleUpdate({ duration: value });
+ handleUpdate({
+ duration: mediaTimeFromSeconds({ seconds: value }),
+ });
}}
className="h-8 text-sm"
containerClassName="w-full"
diff --git a/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts b/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts
index 1071c6dd..8996330b 100644
--- a/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts
+++ b/apps/web/src/timeline/bookmarks/hooks/use-bookmark-drag.ts
@@ -21,15 +21,16 @@ import { getElementEdgeSnapPoints } from "@/timeline/element-snap-source";
import { getPlayheadSnapPoints } from "@/timeline/playhead-snap-source";
import { getAnimationKeyframeSnapPointsForTimeline } from "@/animation/timeline-snap-points";
import type { Bookmark } from "@/timeline";
+import { type MediaTime, ZERO_MEDIA_TIME } from "@/wasm";
export interface BookmarkDragState {
isDragging: boolean;
- bookmarkTime: number | null;
- currentTime: number;
+ bookmarkTime: MediaTime | null;
+ currentTime: MediaTime;
}
interface PendingBookmarkDrag {
- bookmarkTime: number;
+ bookmarkTime: MediaTime;
startMouseX: number;
startMouseY: number;
}
@@ -58,7 +59,7 @@ export function useBookmarkDrag({
const [dragState, setDragState] = useState<BookmarkDragState>({
isDragging: false,
bookmarkTime: null,
- currentTime: 0,
+ currentTime: ZERO_MEDIA_TIME,
});
const [isPendingDrag, setIsPendingDrag] = useState(false);
const pendingDragRef = useRef<PendingBookmarkDrag | null>(null);
@@ -69,8 +70,8 @@ export function useBookmarkDrag({
bookmarkTime,
initialCurrentTime,
}: {
- bookmarkTime: number;
- initialCurrentTime: number;
+ bookmarkTime: MediaTime;
+ initialCurrentTime: MediaTime;
}) => {
setDragState({
isDragging: true,
@@ -85,7 +86,7 @@ export function useBookmarkDrag({
setDragState({
isDragging: false,
bookmarkTime: null,
- currentTime: 0,
+ currentTime: ZERO_MEDIA_TIME,
});
}, []);
@@ -94,9 +95,9 @@ export function useBookmarkDrag({
rawTime,
excludeBookmarkTime,
}: {
- rawTime: number;
- excludeBookmarkTime: number;
- }): { snappedTime: number; snapPoint: SnapPoint | null } => {
+ rawTime: MediaTime;
+ excludeBookmarkTime: MediaTime;
+ }): { snappedTime: MediaTime; snapPoint: SnapPoint | null } => {
const shouldSnap = snappingEnabled && !isShiftHeldRef.current;
if (!shouldSnap) {
return { snappedTime: rawTime, snapPoint: null };
@@ -116,7 +117,7 @@ export function useBookmarkDrag({
maxSnapDistance: getTimelineSnapThresholdInTicks({ zoomLevel }),
});
return {
- snappedTime: result.snappedTime,
+ snappedTime: result.snappedTime as MediaTime,
snapPoint: result.snapPoint,
};
},
@@ -162,11 +163,14 @@ export function useBookmarkDrag({
zoomLevel,
scrollLeft,
});
- const frameSnappedTime =
+ const clampedTime =
+ mouseTime > duration ? duration : mouseTime;
+ const frameSnappedTime = (
roundToFrame({
- time: Math.max(0, Math.min(mouseTime, duration)),
+ time: clampedTime,
rate: activeProject.settings.fps,
- }) ?? Math.max(0, Math.min(mouseTime, duration));
+ }) ?? clampedTime
+ ) as MediaTime;
const { snappedTime: initialTime } = getSnapResult({
rawTime: frameSnappedTime,
excludeBookmarkTime: bookmarkTime,
@@ -193,10 +197,12 @@ export function useBookmarkDrag({
zoomLevel,
scrollLeft,
});
- const clampedTime = Math.max(0, Math.min(mouseTime, duration));
- const frameSnappedTime =
+ const clampedTime =
+ mouseTime > duration ? duration : mouseTime;
+ const frameSnappedTime = (
roundToFrame({ time: clampedTime, rate: activeProject.settings.fps }) ??
- clampedTime;
+ clampedTime
+ ) as MediaTime;
const snapResult = getSnapResult({
rawTime: frameSnappedTime,
excludeBookmarkTime: dragState.bookmarkTime,
@@ -234,10 +240,8 @@ export function useBookmarkDrag({
return;
}
- const clampedTime = Math.max(
- 0,
- Math.min(dragState.currentTime, duration),
- );
+ const clampedTime =
+ dragState.currentTime > duration ? duration : dragState.currentTime;
editor.scenes.moveBookmark({
fromTime: dragState.bookmarkTime,
diff --git a/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx b/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx
index 8e2b4bcf..2c254c84 100644
--- a/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx
+++ b/apps/web/src/timeline/bookmarks/preview-overlay-source.tsx
@@ -5,6 +5,7 @@ import {
type PreviewOverlaySourceResult,
} from "@/preview/overlays";
import { getBookmarksActiveAtTime } from "./utils";
+import type { MediaTime } from "@/wasm";
export const bookmarkNotesPreviewOverlay: PreviewOverlayDefinition = {
id: "bookmark-notes",
@@ -15,7 +16,7 @@ export const bookmarkNotesPreviewOverlay: PreviewOverlayDefinition = {
function BookmarkNotesOverlay({
bookmarks,
}: {
- bookmarks: Array<{ time: number; note: string; color?: string }>;
+ bookmarks: Array<{ time: MediaTime; note: string; color?: string }>;
}) {
return (
<div className="flex flex-col gap-1.5" aria-live="polite">
@@ -43,7 +44,7 @@ export function getBookmarkPreviewOverlaySource({
isVisible,
}: {
bookmarks: Bookmark[];
- time: number;
+ time: MediaTime;
isVisible: boolean;
}): PreviewOverlaySourceResult {
const bookmarksWithNotes = getBookmarksActiveAtTime({
diff --git a/apps/web/src/timeline/bookmarks/utils.ts b/apps/web/src/timeline/bookmarks/utils.ts
index d05fe3a9..52e5aa60 100644
--- a/apps/web/src/timeline/bookmarks/utils.ts
+++ b/apps/web/src/timeline/bookmarks/utils.ts
@@ -1,13 +1,14 @@
import type { Bookmark } from "@/timeline";
import type { FrameRate } from "opencut-wasm";
import { roundToFrame } from "opencut-wasm";
+import { addMediaTime, type MediaTime } from "@/wasm";
function bookmarkTimeEqual({
bookmarkTime,
frameTime,
}: {
- bookmarkTime: number;
- frameTime: number;
+ bookmarkTime: MediaTime;
+ frameTime: MediaTime;
}): boolean {
return bookmarkTime === frameTime;
}
@@ -17,7 +18,7 @@ export function findBookmarkIndex({
frameTime,
}: {
bookmarks: Bookmark[];
- frameTime: number;
+ frameTime: MediaTime;
}): number {
return bookmarks.findIndex((bookmark) =>
bookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }),
@@ -29,7 +30,7 @@ export function isBookmarkAtTime({
frameTime,
}: {
bookmarks: Bookmark[];
- frameTime: number;
+ frameTime: MediaTime;
}): boolean {
return bookmarks.some((bookmark) =>
bookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }),
@@ -41,7 +42,7 @@ export function toggleBookmarkInArray({
frameTime,
}: {
bookmarks: Bookmark[];
- frameTime: number;
+ frameTime: MediaTime;
}): Bookmark[] {
const bookmarkIndex = findBookmarkIndex({ bookmarks, frameTime });
@@ -58,7 +59,7 @@ export function removeBookmarkFromArray({
frameTime,
}: {
bookmarks: Bookmark[];
- frameTime: number;
+ frameTime: MediaTime;
}): Bookmark[] {
return bookmarks.filter(
(bookmark) =>
@@ -72,7 +73,7 @@ export function updateBookmarkInArray({
updates,
}: {
bookmarks: Bookmark[];
- frameTime: number;
+ frameTime: MediaTime;
updates: Partial<Omit<Bookmark, "time">>;
}): Bookmark[] {
const index = findBookmarkIndex({ bookmarks, frameTime });
@@ -92,8 +93,8 @@ export function moveBookmarkInArray({
toTime,
}: {
bookmarks: Bookmark[];
- fromTime: number;
- toTime: number;
+ fromTime: MediaTime;
+ toTime: MediaTime;
}): Bookmark[] {
const index = findBookmarkIndex({ bookmarks, frameTime: fromTime });
if (index === -1) {
@@ -110,10 +111,10 @@ export function getFrameTime({
time,
fps,
}: {
- time: number;
+ time: MediaTime;
fps: FrameRate;
-}): number {
- return roundToFrame({ time, rate: fps }) ?? time;
+}): MediaTime {
+ return (roundToFrame({ time, rate: fps }) ?? time) as MediaTime;
}
export function getBookmarkAtTime({
@@ -121,7 +122,7 @@ export function getBookmarkAtTime({
frameTime,
}: {
bookmarks: Bookmark[];
- frameTime: number;
+ frameTime: MediaTime;
}): Bookmark | null {
const index = findBookmarkIndex({ bookmarks, frameTime });
return index === -1 ? null : bookmarks[index];
@@ -132,13 +133,13 @@ export function getBookmarksActiveAtTime({
time,
}: {
bookmarks: Bookmark[];
- time: number;
+ time: MediaTime;
}): Bookmark[] {
return bookmarks.filter((bookmark) => {
const start = bookmark.time;
const end =
bookmark.duration != null && bookmark.duration > 0
- ? start + bookmark.duration
+ ? addMediaTime({ a: start, b: bookmark.duration })
: start;
return time >= start && time <= end;
});