How to Implement Marker Operations for Timeline Navigation and Annotation in Clypra
Clypra manages timeline markers through a Zustand store (src/store/timelineStore.ts) that provides type-safe CRUD actions for bookmarking timecodes, enabling rapid navigation and persistent annotation across editing sessions.
Clypra is an open-source video editing platform built with React and TypeScript that leverages centralized state management for timeline interactions. Implementing marker operations for timeline navigation and annotation in Clypra requires understanding the TimelineMarker type and the store actions defined in the timeline store. These markers serve as lightweight bookmarks that allow users to annotate specific timecodes and instantly jump between key moments in the editing timeline.
Core Data Model and Type Definition
The foundation of Clypra's marker system is the TimelineMarker interface defined in src/store/timelineStore.ts. Each marker represents a specific point in time with associated metadata.
export type TimelineMarker = {
id: string; // unique id, generated with `generateId("marker")`
time: number; // time in seconds on the timeline
name: string; // descriptive label shown in the UI
color: string; // UI colour for the marker thumb
};
Markers are stored in the markers array within the timeline state (line 111). The system maintains this array in chronological order to optimize navigation performance and rendering.
Creating and Managing Markers
The timeline store exposes three primary actions for marker manipulation: addMarker, removeMarker, and updateMarker.
Adding Markers
The addMarker(time, name?, color?) method generates a unique identifier using generateId("marker"), constructs a TimelineMarker object, and inserts it into the state while preserving chronological order. The implementation defaults to the name "Marker" and the color "purple" when optional parameters are omitted.
// src/store/timelineStore.ts – lines 1330‑1334
addMarker: (time, name = "Marker", color = "purple") => {
const id = generateId("marker");
const marker: TimelineMarker = { id, time, name, color };
return {
markers: [...state.markers, marker].sort((a, b) => a.time - b.time),
};
},
This method returns the newly created marker's id as a string, allowing immediate reference for UI selection or subsequent updates.
Removing Markers
To delete a bookmark, invoke removeMarker(markerId), which filters the array to exclude the matching identifier:
// src/store/timelineStore.ts – lines 1340‑1342
removeMarker: (markerId) => ({
markers: state.markers.filter((m) => m.id !== markerId),
}),
Updating Markers
The updateMarker(markerId, updates) method merges partial updates into the existing marker object using immutable spread operations:
// src/store/timelineStore.ts – lines 1347‑1350
updateMarker: (markerId, updates) => ({
markers: state.markers.map((m) =>
m.id === markerId ? { ...m, ...updates } : m
),
}),
This approach enables efficient modification of individual properties such as renaming or recoloring without replacing the entire marker object.
Navigating to Markers
While the store maintains marker data, navigation requires integration with the playback system. Access the marker array directly from the store state and use the seek method from the playback hook to jump to specific timecodes.
import { useTimelineStore } from '@/store/timelineStore';
import { usePlayback } from '@/hooks/usePlayback';
function goToMarker(markerId: string) {
const { markers } = useTimelineStore.getState();
const { seek } = usePlayback();
const marker = markers.find(m => m.id === markerId);
if (marker) {
seek(marker.time);
}
}
Persistence and Project Serialization
Markers achieve session longevity through integration with Clypra's project serialization system. During project hydration (lines 268‑287), the store reconstructs the marker array from saved payloads:
const finalMarkers: TimelineMarker[] = (payload as any)?.markers ?? [];
// Applied to state...
markers: finalMarkers,
The src/types/serialization.ts file defines the schema that includes the markers array, while src/store/projectStore.ts handles conversion to and from the Rust-backed project format. This ensures markers survive application restarts and project exports.
Keyboard Shortcuts and UI Integration
Clypra provides ergonomic keyboard access to marker creation. The useKeyboardShortcuts.ts hook (line 463) registers the M key to instantaneously create a marker at the current playhead position:
// Implementation retrieves current time from playback clock
// and invokes addMarker(currentTime)
UI components consume the timeline store through the useTimelineStore hook, rendering each marker as a colored thumb on the timeline ruler. Clicking these elements triggers the seek operation, enabling visual navigation across the editing timeline.
Practical Implementation Examples
The following patterns demonstrate common marker operations within React components.
Adding a marker at the current playhead:
import { useTimelineStore } from '@/store/timelineStore';
import { usePlaybackClock } from '@/hooks/usePlaybackClock';
function addCurrentTimeMarker() {
const addMarker = useTimelineStore(state => state.addMarker);
const { currentTime } = usePlaybackClock();
const markerId = addMarker(currentTime, 'Key Moment', 'teal');
console.log('Created marker', markerId);
}
Deleting and updating markers:
import { useTimelineStore } from '@/store/timelineStore';
function deleteMarker(markerId: string) {
const removeMarker = useTimelineStore(state => state.removeMarker);
removeMarker(markerId);
}
function renameMarker(markerId: string, newName: string) {
const updateMarker = useTimelineStore(state => state.updateMarker);
updateMarker(markerId, { name: newName });
}
Summary
- Clypra's marker system is implemented in
src/store/timelineStore.tsusing the Zustand state management library. - The
TimelineMarkertype requiresid,time,name, andcolorproperties, with IDs generated viagenerateId("marker"). - CRUD operations include
addMarker(returns string ID),removeMarker(filters by ID), andupdateMarker(merges partial updates). - Chronological sorting occurs automatically when adding markers, ensuring the array remains ordered by time.
- Persistence is handled through project serialization in
src/types/serialization.tsandsrc/store/projectStore.ts. - Keyboard shortcut "M" creates markers instantly at the playhead position via
useKeyboardShortcuts.ts.
Frequently Asked Questions
How do I programmatically create a marker at the current playback position?
Access the currentTime from usePlaybackClock() and pass it to addMarker() from the timeline store. The method returns the new marker's ID immediately. You can optionally specify a custom name and color; otherwise, it defaults to "Marker" and "purple".
Can markers be updated after creation without removing and re-adding them?
Yes. The updateMarker(markerId, updates) action accepts a partial object containing only the properties you wish to change, such as { name: "New Label" } or { color: "red" }. The store merges these changes immutably while preserving the marker's ID and time position.
Where are markers stored when saving a Clypra project?
Markers persist within the project file through the serialization layer defined in src/types/serialization.ts. When loading a project, the hydration logic in timelineStore.ts (lines 268‑287) extracts the markers array from the payload and injects it into the Zustand state, maintaining full fidelity across sessions.
What keyboard shortcut adds markers in the Clypra interface?
Press M to instantly create a marker at the current playhead position. This binding is registered in src/hooks/useKeyboardShortcuts.ts (line 463) and automatically queries the playback clock to determine the correct timecode for the new marker.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →