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:
95
src/lib/stores/clips.svelte.ts
Normal file
95
src/lib/stores/clips.svelte.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user