summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/timeline/bookmarks.tsx
blob: cfcb1e235ce00e16a14551faa16a2e7ae4a5dce1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { useEditor } from "@/hooks/use-editor";
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
import { getSnappedSeekTime } from "@/lib/time";
import { Bookmark02Icon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";

interface TimelineBookmarksRowProps {
	zoomLevel: number;
	dynamicTimelineWidth: number;
	handleWheel: (e: React.WheelEvent) => void;
	handleTimelineContentClick: (e: React.MouseEvent) => void;
	handleRulerTrackingMouseDown: (e: React.MouseEvent) => void;
	handleRulerMouseDown: (e: React.MouseEvent) => void;
}

export function TimelineBookmarksRow({
	zoomLevel,
	dynamicTimelineWidth,
	handleWheel,
	handleTimelineContentClick,
	handleRulerTrackingMouseDown,
	handleRulerMouseDown,
}: TimelineBookmarksRowProps) {
	const editor = useEditor();
	const activeScene = editor.scenes.getActiveScene();

	return (
		<div className="relative h-4 flex-1 overflow-hidden">
			<button
				className="relative h-4 w-full cursor-default select-none border-0 bg-transparent p-0"
				style={{
					width: `${dynamicTimelineWidth}px`,
				}}
				aria-label="Timeline ruler"
				type="button"
				onWheel={handleWheel}
				onClick={handleTimelineContentClick}
				onMouseDown={(event) => {
					handleRulerMouseDown(event);
					handleRulerTrackingMouseDown(event);
				}}
			>
				{activeScene.bookmarks.map((time: number) => (
					<TimelineBookmark
						key={`bookmark-row-${time}`}
						time={time}
						zoomLevel={zoomLevel}
					/>
				))}
			</button>
		</div>
	);
}

export function TimelineBookmark({
	time,
	zoomLevel,
}: {
	time: number;
	zoomLevel: number;
}) {
	const editor = useEditor();
	const activeProject = editor.project.getActive();
	const duration = editor.timeline.getTotalDuration();

	const handleBookmarkActivate = ({
		event,
	}: {
		event:
			| React.MouseEvent<HTMLButtonElement>
			| React.KeyboardEvent<HTMLButtonElement>;
	}) => {
		event.stopPropagation();
		const framesPerSecond = activeProject?.settings.fps ?? 30;
		const snappedTime = getSnappedSeekTime({
			rawTime: time,
			duration,
			fps: framesPerSecond,
		});
		editor.playback.seek({ time: snappedTime });
	};

	return (
		<button
			className="absolute top-0 h-10 w-0.5 cursor-pointer border-0 bg-transparent p-0"
			style={{
				left: `${time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel}px`,
			}}
			aria-label={`Seek to bookmark at ${time}s`}
			type="button"
			onMouseDown={(event) => {
				event.preventDefault();
				event.stopPropagation();
			}}
			onClick={(event) => handleBookmarkActivate({ event })}
			onKeyDown={(event) => {
				if (event.key !== "Enter" && event.key !== " ") return;
				event.preventDefault();
				handleBookmarkActivate({ event });
			}}
		>
			<div className="text-primary absolute top-[-1px] left-[-5px]">
				<HugeiconsIcon
					icon={Bookmark02Icon}
					aria-hidden="true"
					className="fill-primary size-3"
				/>
			</div>
		</button>
	);
}