feat: add clip management with I/O marking, timeline markers, and clip list

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-21 11:05:45 -04:00
parent 36701feb9e
commit c13755e5d1
6 changed files with 513 additions and 6 deletions

View File

@@ -0,0 +1,95 @@
import { getClipColor } from '$lib/utils/colors';
export interface Clip {
id: string;
startTime: number;
endTime: number;
label: string;
color: string;
}
export const clipStore = $state({
clips: [] as Clip[],
selectedClipId: null as string | null,
pendingInPoint: null as number | null,
nextClipNumber: 1,
});
export function getClips(): Clip[] {
return clipStore.clips;
}
export function getSelectedClipId(): string | null {
return clipStore.selectedClipId;
}
export function getPendingInPoint(): number | null {
return clipStore.pendingInPoint;
}
export function selectClip(id: string | null) {
clipStore.selectedClipId = id;
clipStore.pendingInPoint = null;
}
export function addClip(startTime: number, endTime: number): Clip {
const start = Math.min(startTime, endTime);
const end = Math.max(startTime, endTime);
const clip: Clip = {
id: crypto.randomUUID(),
startTime: start,
endTime: end,
label: `Clip ${clipStore.nextClipNumber}`,
color: getClipColor(clipStore.nextClipNumber - 1),
};
clipStore.nextClipNumber++;
clipStore.clips = [...clipStore.clips, clip];
clipStore.selectedClipId = clip.id;
return clip;
}
export function removeClip(id: string) {
clipStore.clips = clipStore.clips.filter((c) => c.id !== id);
if (clipStore.selectedClipId === id) {
clipStore.selectedClipId = null;
}
}
export function updateClip(
id: string,
changes: Partial<Pick<Clip, 'startTime' | 'endTime' | 'label'>>
) {
clipStore.clips = clipStore.clips.map((c) => {
if (c.id !== id) return c;
const updated = { ...c, ...changes };
if (updated.startTime > updated.endTime) {
[updated.startTime, updated.endTime] = [updated.endTime, updated.startTime];
}
return updated;
});
}
export function markInPoint(time: number) {
if (clipStore.selectedClipId) {
updateClip(clipStore.selectedClipId, { startTime: time });
} else {
clipStore.pendingInPoint = time;
}
}
export function markOutPoint(time: number) {
if (clipStore.selectedClipId) {
updateClip(clipStore.selectedClipId, { endTime: time });
} else if (clipStore.pendingInPoint !== null) {
addClip(clipStore.pendingInPoint, time);
clipStore.pendingInPoint = null;
}
}
export function clearAll() {
clipStore.clips = [];
clipStore.selectedClipId = null;
clipStore.pendingInPoint = null;
clipStore.nextClipNumber = 1;
}