summaryrefslogtreecommitdiff
path: root/apps/web/src/stores
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-07-06 20:45:29 +0200
committerMaze Winther <mazewinther@gmail.com>2025-07-06 20:45:29 +0200
commit40c7fbb4f86130c83fbd9029e83c5e7550e3219d (patch)
tree054d918d282358c084f4fc08462913833de644a5 /apps/web/src/stores
parent0e32c732ddc335d35e6a7f8547f4af68bfd9c369 (diff)
downloadopencut-40c7fbb4f86130c83fbd9029e83c5e7550e3219d.tar.gz
refactor: move to a typed-tracks system and add support for text
Diffstat (limited to 'apps/web/src/stores')
-rw-r--r--apps/web/src/stores/media-store.ts38
-rw-r--r--apps/web/src/stores/timeline-store.ts387
2 files changed, 265 insertions, 160 deletions
diff --git a/apps/web/src/stores/media-store.ts b/apps/web/src/stores/media-store.ts
index 48932db5..ab413b14 100644
--- a/apps/web/src/stores/media-store.ts
+++ b/apps/web/src/stores/media-store.ts
@@ -1,16 +1,25 @@
import { create } from "zustand";
import { storageService } from "@/lib/storage/storage-service";
+export type MediaType = "image" | "video" | "audio";
+
export interface MediaItem {
id: string;
name: string;
- type: "image" | "video" | "audio";
+ type: MediaType;
file: File;
- url: string; // Object URL for preview
+ url?: string; // Object URL for preview
thumbnailUrl?: string; // For video thumbnails
duration?: number; // For video/audio duration
width?: number; // For video/image width
height?: number; // For video/image height
+ // Text-specific properties
+ content?: string; // Text content
+ fontSize?: number; // Font size
+ fontFamily?: string; // Font family
+ color?: string; // Text color
+ backgroundColor?: string; // Background color
+ textAlign?: "left" | "center" | "right"; // Text alignment
}
interface MediaStore {
@@ -25,7 +34,7 @@ interface MediaStore {
}
// Helper function to determine file type
-export const getFileType = (file: File): "image" | "video" | "audio" | null => {
+export const getFileType = (file: File): MediaType | null => {
const { type } = file;
if (type.startsWith("image/")) {
@@ -46,7 +55,7 @@ export const getImageDimensions = (
file: File
): Promise<{ width: number; height: number }> => {
return new Promise((resolve, reject) => {
- const img = new Image();
+ const img = new window.Image();
img.addEventListener("load", () => {
const width = img.naturalWidth;
@@ -69,8 +78,8 @@ export const generateVideoThumbnail = (
file: File
): Promise<{ thumbnailUrl: string; width: number; height: number }> => {
return new Promise((resolve, reject) => {
- const video = document.createElement("video");
- const canvas = document.createElement("canvas");
+ const video = document.createElement("video") as HTMLVideoElement;
+ const canvas = document.createElement("canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d");
if (!ctx) {
@@ -115,7 +124,7 @@ export const getMediaDuration = (file: File): Promise<number> => {
return new Promise((resolve, reject) => {
const element = document.createElement(
file.type.startsWith("video/") ? "video" : "audio"
- ) as HTMLVideoElement | HTMLAudioElement;
+ ) as HTMLVideoElement;
element.addEventListener("loadedmetadata", () => {
resolve(element.duration);
@@ -162,17 +171,17 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
console.error("Failed to save media item:", error);
// Remove from local state if save failed
set((state) => ({
- mediaItems: state.mediaItems.filter((item) => item.id !== newItem.id),
+ mediaItems: state.mediaItems.filter((media) => media.id !== newItem.id),
}));
}
},
- removeMediaItem: async (id) => {
+ removeMediaItem: async (id: string) => {
const state = get();
- const item = state.mediaItems.find((item) => item.id === id);
+ const item = state.mediaItems.find((media) => media.id === id);
// Cleanup object URLs to prevent memory leaks
- if (item) {
+ if (item && item.url) {
URL.revokeObjectURL(item.url);
if (item.thumbnailUrl) {
URL.revokeObjectURL(item.thumbnailUrl);
@@ -181,7 +190,7 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
// Remove from local state immediately
set((state) => ({
- mediaItems: state.mediaItems.filter((item) => item.id !== id),
+ mediaItems: state.mediaItems.filter((media) => media.id !== id),
}));
// Remove from persistent storage
@@ -189,7 +198,6 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
await storageService.deleteMediaItem(id);
} catch (error) {
console.error("Failed to delete media item:", error);
- // Could re-add to local state here if needed
}
},
@@ -211,7 +219,9 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
// Cleanup all object URLs
state.mediaItems.forEach((item) => {
- URL.revokeObjectURL(item.url);
+ if (item.url) {
+ URL.revokeObjectURL(item.url);
+ }
if (item.thumbnailUrl) {
URL.revokeObjectURL(item.thumbnailUrl);
}
diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts
index 01f4fea6..ef08fffd 100644
--- a/apps/web/src/stores/timeline-store.ts
+++ b/apps/web/src/stores/timeline-store.ts
@@ -1,10 +1,14 @@
import { create } from "zustand";
-import type { TrackType } from "@/types/timeline";
+import type {
+ TrackType,
+ TimelineElement,
+ CreateTimelineElement,
+} from "@/types/timeline";
import { useEditorStore } from "./editor-store";
import { useMediaStore, getMediaAspectRatio } from "./media-store";
-// Helper function to manage clip naming with suffixes
-const getClipNameWithSuffix = (
+// Helper function to manage element naming with suffixes
+const getElementNameWithSuffix = (
originalName: string,
suffix: string
): string => {
@@ -18,21 +22,11 @@ const getClipNameWithSuffix = (
return `${baseName} (${suffix})`;
};
-export interface TimelineClip {
- id: string;
- mediaId: string;
- name: string;
- duration: number;
- startTime: number;
- trimStart: number;
- trimEnd: number;
-}
-
export interface TimelineTrack {
id: string;
name: string;
type: TrackType;
- clips: TimelineClip[];
+ elements: TimelineElement[];
muted?: boolean;
}
@@ -42,28 +36,30 @@ interface TimelineStore {
redoStack: TimelineTrack[][];
// Multi-selection
- selectedClips: { trackId: string; clipId: string }[];
- selectClip: (trackId: string, clipId: string, multi?: boolean) => void;
- deselectClip: (trackId: string, clipId: string) => void;
- clearSelectedClips: () => void;
- setSelectedClips: (clips: { trackId: string; clipId: string }[]) => void;
+ selectedElements: { trackId: string; elementId: string }[];
+ selectElement: (trackId: string, elementId: string, multi?: boolean) => void;
+ deselectElement: (trackId: string, elementId: string) => void;
+ clearSelectedElements: () => void;
+ setSelectedElements: (
+ elements: { trackId: string; elementId: string }[]
+ ) => void;
// Drag state
dragState: {
isDragging: boolean;
- clipId: string | null;
+ elementId: string | null;
trackId: string | null;
startMouseX: number;
- startClipTime: number;
+ startElementTime: number;
clickOffsetTime: number;
currentTime: number;
};
setDragState: (dragState: Partial<TimelineStore["dragState"]>) => void;
startDrag: (
- clipId: string,
+ elementId: string,
trackId: string,
startMouseX: number,
- startClipTime: number,
+ startElementTime: number,
clickOffsetTime: number
) => void;
updateDragTime: (currentTime: number) => void;
@@ -71,44 +67,45 @@ interface TimelineStore {
// Actions
addTrack: (type: TrackType) => string;
+ insertTrackAt: (type: TrackType, index: number) => string;
removeTrack: (trackId: string) => void;
- addClipToTrack: (trackId: string, clip: Omit<TimelineClip, "id">) => void;
- removeClipFromTrack: (trackId: string, clipId: string) => void;
- moveClipToTrack: (
+ addElementToTrack: (trackId: string, element: CreateTimelineElement) => void;
+ removeElementFromTrack: (trackId: string, elementId: string) => void;
+ moveElementToTrack: (
fromTrackId: string,
toTrackId: string,
- clipId: string
+ elementId: string
) => void;
- updateClipTrim: (
+ updateElementTrim: (
trackId: string,
- clipId: string,
+ elementId: string,
trimStart: number,
trimEnd: number
) => void;
- updateClipStartTime: (
+ updateElementStartTime: (
trackId: string,
- clipId: string,
+ elementId: string,
startTime: number
) => void;
toggleTrackMute: (trackId: string) => void;
- // Split operations for clips
- splitClip: (
+ // Split operations for elements
+ splitElement: (
trackId: string,
- clipId: string,
+ elementId: string,
splitTime: number
) => string | null;
splitAndKeepLeft: (
trackId: string,
- clipId: string,
+ elementId: string,
splitTime: number
) => void;
splitAndKeepRight: (
trackId: string,
- clipId: string,
+ elementId: string,
splitTime: number
) => void;
- separateAudio: (trackId: string, clipId: string) => string | null;
+ separateAudio: (trackId: string, elementId: string) => string | null;
// Computed values
getTotalDuration: () => number;
@@ -123,10 +120,10 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
tracks: [],
history: [],
redoStack: [],
- selectedClips: [],
+ selectedElements: [],
pushHistory: () => {
- const { tracks, history, redoStack } = get();
+ const { tracks, history } = get();
set({
history: [...history, JSON.parse(JSON.stringify(tracks))],
redoStack: [],
@@ -144,46 +141,62 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
});
},
- selectClip: (trackId, clipId, multi = false) => {
+ selectElement: (trackId, elementId, multi = false) => {
set((state) => {
- const exists = state.selectedClips.some(
- (c) => c.trackId === trackId && c.clipId === clipId
+ const exists = state.selectedElements.some(
+ (c) => c.trackId === trackId && c.elementId === elementId
);
if (multi) {
return exists
? {
- selectedClips: state.selectedClips.filter(
- (c) => !(c.trackId === trackId && c.clipId === clipId)
+ selectedElements: state.selectedElements.filter(
+ (c) => !(c.trackId === trackId && c.elementId === elementId)
),
}
- : { selectedClips: [...state.selectedClips, { trackId, clipId }] };
+ : {
+ selectedElements: [
+ ...state.selectedElements,
+ { trackId, elementId },
+ ],
+ };
} else {
- return { selectedClips: [{ trackId, clipId }] };
+ return { selectedElements: [{ trackId, elementId }] };
}
});
},
- deselectClip: (trackId, clipId) => {
+ deselectElement: (trackId, elementId) => {
set((state) => ({
- selectedClips: state.selectedClips.filter(
- (c) => !(c.trackId === trackId && c.clipId === clipId)
+ selectedElements: state.selectedElements.filter(
+ (c) => !(c.trackId === trackId && c.elementId === elementId)
),
}));
},
- clearSelectedClips: () => {
- set({ selectedClips: [] });
+ clearSelectedElements: () => {
+ set({ selectedElements: [] });
},
- setSelectedClips: (clips) => set({ selectedClips: clips }),
+ setSelectedElements: (elements) => set({ selectedElements: elements }),
addTrack: (type) => {
get().pushHistory();
+
+ // Generate proper track name based on type
+ const trackName =
+ type === "media"
+ ? "Media Track"
+ : type === "text"
+ ? "Text Track"
+ : type === "audio"
+ ? "Audio Track"
+ : "Track";
+
const newTrack: TimelineTrack = {
id: crypto.randomUUID(),
- name: `${type.charAt(0).toUpperCase() + type.slice(1)} Track`,
+ name: trackName,
type,
- clips: [],
+ elements: [],
muted: false,
};
set((state) => ({
@@ -192,6 +205,35 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
return newTrack.id;
},
+ insertTrackAt: (type, index) => {
+ get().pushHistory();
+
+ // Generate proper track name based on type
+ const trackName =
+ type === "media"
+ ? "Media Track"
+ : type === "text"
+ ? "Text Track"
+ : type === "audio"
+ ? "Audio Track"
+ : "Track";
+
+ const newTrack: TimelineTrack = {
+ id: crypto.randomUUID(),
+ name: trackName,
+ type,
+ elements: [],
+ muted: false,
+ };
+
+ set((state) => {
+ const newTracks = [...state.tracks];
+ newTracks.splice(index, 0, newTrack);
+ return { tracks: newTracks };
+ });
+ return newTrack.id;
+ },
+
removeTrack: (trackId) => {
get().pushHistory();
set((state) => ({
@@ -199,31 +241,64 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
}));
},
- addClipToTrack: (trackId, clipData) => {
+ addElementToTrack: (trackId, elementData) => {
get().pushHistory();
- // Check if this is the first clip being added to the timeline
+ // Validate element type matches track type
+ const track = get().tracks.find((t) => t.id === trackId);
+ if (!track) {
+ console.error("Track not found:", trackId);
+ return;
+ }
+
+ // Validate element can be added to this track type
+ if (track.type === "media" && elementData.type !== "media") {
+ console.error("Media track only accepts media elements");
+ return;
+ }
+ if (track.type === "text" && elementData.type !== "text") {
+ console.error("Text track only accepts text elements");
+ return;
+ }
+ if (track.type === "audio" && elementData.type !== "media") {
+ console.error("Audio track only accepts media elements");
+ return;
+ }
+
+ // For media elements, validate mediaId exists
+ if (elementData.type === "media" && !elementData.mediaId) {
+ console.error("Media element must have mediaId");
+ return;
+ }
+
+ // For text elements, validate required text properties
+ if (elementData.type === "text" && !elementData.content) {
+ console.error("Text element must have content");
+ return;
+ }
+
+ // Check if this is the first element being added to the timeline
const currentState = get();
- const totalClipsInTimeline = currentState.tracks.reduce(
- (total, track) => total + track.clips.length,
+ const totalElementsInTimeline = currentState.tracks.reduce(
+ (total, track) => total + track.elements.length,
0
);
- const isFirstClip = totalClipsInTimeline === 0;
+ const isFirstElement = totalElementsInTimeline === 0;
- const newClip: TimelineClip = {
- ...clipData,
+ const newElement: TimelineElement = {
+ ...elementData,
id: crypto.randomUUID(),
- startTime: clipData.startTime || 0,
+ startTime: elementData.startTime || 0,
trimStart: 0,
trimEnd: 0,
- };
+ } as TimelineElement; // Type assertion since we trust the caller passes valid data
- // If this is the first clip, automatically set the project canvas size
+ // If this is the first element and it's a media element, automatically set the project canvas size
// to match the media's aspect ratio
- if (isFirstClip && clipData.mediaId) {
+ if (isFirstElement && newElement.type === "media") {
const mediaStore = useMediaStore.getState();
const mediaItem = mediaStore.mediaItems.find(
- (item) => item.id === clipData.mediaId
+ (item) => item.id === newElement.mediaId
);
if (
@@ -240,13 +315,13 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
set((state) => ({
tracks: state.tracks.map((track) =>
track.id === trackId
- ? { ...track, clips: [...track.clips, newClip] }
+ ? { ...track, elements: [...track.elements, newElement] }
: track
),
}));
},
- removeClipFromTrack: (trackId, clipId) => {
+ removeElementFromTrack: (trackId, elementId) => {
get().pushHistory();
set((state) => ({
tracks: state.tracks
@@ -254,21 +329,25 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
track.id === trackId
? {
...track,
- clips: track.clips.filter((clip) => clip.id !== clipId),
+ elements: track.elements.filter(
+ (element) => element.id !== elementId
+ ),
}
: track
)
- .filter((track) => track.clips.length > 0),
+ .filter((track) => track.elements.length > 0),
}));
},
- moveClipToTrack: (fromTrackId, toTrackId, clipId) => {
+ moveElementToTrack: (fromTrackId, toTrackId, elementId) => {
get().pushHistory();
set((state) => {
const fromTrack = state.tracks.find((track) => track.id === fromTrackId);
- const clipToMove = fromTrack?.clips.find((clip) => clip.id === clipId);
+ const elementToMove = fromTrack?.elements.find(
+ (element) => element.id === elementId
+ );
- if (!clipToMove) return state;
+ if (!elementToMove) return state;
return {
tracks: state.tracks
@@ -276,30 +355,34 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
if (track.id === fromTrackId) {
return {
...track,
- clips: track.clips.filter((clip) => clip.id !== clipId),
+ elements: track.elements.filter(
+ (element) => element.id !== elementId
+ ),
};
} else if (track.id === toTrackId) {
return {
...track,
- clips: [...track.clips, clipToMove],
+ elements: [...track.elements, elementToMove],
};
}
return track;
})
- .filter((track) => track.clips.length > 0),
+ .filter((track) => track.elements.length > 0),
};
});
},
- updateClipTrim: (trackId, clipId, trimStart, trimEnd) => {
+ updateElementTrim: (trackId, elementId, trimStart, trimEnd) => {
get().pushHistory();
set((state) => ({
tracks: state.tracks.map((track) =>
track.id === trackId
? {
...track,
- clips: track.clips.map((clip) =>
- clip.id === clipId ? { ...clip, trimStart, trimEnd } : clip
+ elements: track.elements.map((element) =>
+ element.id === elementId
+ ? { ...element, trimStart, trimEnd }
+ : element
),
}
: track
@@ -307,15 +390,15 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
}));
},
- updateClipStartTime: (trackId, clipId, startTime) => {
+ updateElementStartTime: (trackId, elementId, startTime) => {
get().pushHistory();
set((state) => ({
tracks: state.tracks.map((track) =>
track.id === trackId
? {
...track,
- clips: track.clips.map((clip) =>
- clip.id === clipId ? { ...clip, startTime } : clip
+ elements: track.elements.map((element) =>
+ element.id === elementId ? { ...element, startTime } : element
),
}
: track
@@ -332,47 +415,48 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
}));
},
- splitClip: (trackId, clipId, splitTime) => {
+ splitElement: (trackId, elementId, splitTime) => {
const { tracks } = get();
const track = tracks.find((t) => t.id === trackId);
- const clip = track?.clips.find((c) => c.id === clipId);
+ const element = track?.elements.find((c) => c.id === elementId);
- if (!clip) return null;
+ if (!element) return null;
- const effectiveStart = clip.startTime;
+ const effectiveStart = element.startTime;
const effectiveEnd =
- clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd);
+ element.startTime +
+ (element.duration - element.trimStart - element.trimEnd);
if (splitTime <= effectiveStart || splitTime >= effectiveEnd) return null;
get().pushHistory();
- const relativeTime = splitTime - clip.startTime;
+ const relativeTime = splitTime - element.startTime;
const firstDuration = relativeTime;
const secondDuration =
- clip.duration - clip.trimStart - clip.trimEnd - relativeTime;
+ element.duration - element.trimStart - element.trimEnd - relativeTime;
- const secondClipId = crypto.randomUUID();
+ const secondElementId = crypto.randomUUID();
set((state) => ({
tracks: state.tracks.map((track) =>
track.id === trackId
? {
...track,
- clips: track.clips.flatMap((c) =>
- c.id === clipId
+ elements: track.elements.flatMap((c) =>
+ c.id === elementId
? [
{
...c,
trimEnd: c.trimEnd + secondDuration,
- name: getClipNameWithSuffix(c.name, "left"),
+ name: getElementNameWithSuffix(c.name, "left"),
},
{
...c,
- id: secondClipId,
+ id: secondElementId,
startTime: splitTime,
trimStart: c.trimStart + firstDuration,
- name: getClipNameWithSuffix(c.name, "right"),
+ name: getElementNameWithSuffix(c.name, "right"),
},
]
: [c]
@@ -382,40 +466,41 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
),
}));
- return secondClipId;
+ return secondElementId;
},
- // Split clip and keep only the left portion
- splitAndKeepLeft: (trackId, clipId, splitTime) => {
+ // Split element and keep only the left portion
+ splitAndKeepLeft: (trackId, elementId, splitTime) => {
const { tracks } = get();
const track = tracks.find((t) => t.id === trackId);
- const clip = track?.clips.find((c) => c.id === clipId);
+ const element = track?.elements.find((c) => c.id === elementId);
- if (!clip) return;
+ if (!element) return;
- const effectiveStart = clip.startTime;
+ const effectiveStart = element.startTime;
const effectiveEnd =
- clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd);
+ element.startTime +
+ (element.duration - element.trimStart - element.trimEnd);
if (splitTime <= effectiveStart || splitTime >= effectiveEnd) return;
get().pushHistory();
- const relativeTime = splitTime - clip.startTime;
+ const relativeTime = splitTime - element.startTime;
const durationToRemove =
- clip.duration - clip.trimStart - clip.trimEnd - relativeTime;
+ element.duration - element.trimStart - element.trimEnd - relativeTime;
set((state) => ({
tracks: state.tracks.map((track) =>
track.id === trackId
? {
...track,
- clips: track.clips.map((c) =>
- c.id === clipId
+ elements: track.elements.map((c) =>
+ c.id === elementId
? {
...c,
trimEnd: c.trimEnd + durationToRemove,
- name: getClipNameWithSuffix(c.name, "left"),
+ name: getElementNameWithSuffix(c.name, "left"),
}
: c
),
@@ -425,36 +510,37 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
}));
},
- // Split clip and keep only the right portion
- splitAndKeepRight: (trackId, clipId, splitTime) => {
+ // Split element and keep only the right portion
+ splitAndKeepRight: (trackId, elementId, splitTime) => {
const { tracks } = get();
const track = tracks.find((t) => t.id === trackId);
- const clip = track?.clips.find((c) => c.id === clipId);
+ const element = track?.elements.find((c) => c.id === elementId);
- if (!clip) return;
+ if (!element) return;
- const effectiveStart = clip.startTime;
+ const effectiveStart = element.startTime;
const effectiveEnd =
- clip.startTime + (clip.duration - clip.trimStart - clip.trimEnd);
+ element.startTime +
+ (element.duration - element.trimStart - element.trimEnd);
if (splitTime <= effectiveStart || splitTime >= effectiveEnd) return;
get().pushHistory();
- const relativeTime = splitTime - clip.startTime;
+ const relativeTime = splitTime - element.startTime;
set((state) => ({
tracks: state.tracks.map((track) =>
track.id === trackId
? {
...track,
- clips: track.clips.map((c) =>
- c.id === clipId
+ elements: track.elements.map((c) =>
+ c.id === elementId
? {
...c,
startTime: splitTime,
trimStart: c.trimStart + relativeTime,
- name: getClipNameWithSuffix(c.name, "right"),
+ name: getElementNameWithSuffix(c.name, "right"),
}
: c
),
@@ -464,33 +550,33 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
}));
},
- // Extract audio from video clip to an audio track
- separateAudio: (trackId, clipId) => {
+ // Extract audio from video element to an audio track
+ separateAudio: (trackId, elementId) => {
const { tracks } = get();
const track = tracks.find((t) => t.id === trackId);
- const clip = track?.clips.find((c) => c.id === clipId);
+ const element = track?.elements.find((c) => c.id === elementId);
- if (!clip || track?.type !== "video") return null;
+ if (!element || track?.type !== "media") return null;
get().pushHistory();
// Find existing audio track or prepare to create one
const existingAudioTrack = tracks.find((t) => t.type === "audio");
- const audioClipId = crypto.randomUUID();
+ const audioElementId = crypto.randomUUID();
if (existingAudioTrack) {
- // Add audio clip to existing audio track
+ // Add audio element to existing audio track
set((state) => ({
tracks: state.tracks.map((track) =>
track.id === existingAudioTrack.id
? {
...track,
- clips: [
- ...track.clips,
+ elements: [
+ ...track.elements,
{
- ...clip,
- id: audioClipId,
- name: getClipNameWithSuffix(clip.name, "audio"),
+ ...element,
+ id: audioElementId,
+ name: getElementNameWithSuffix(element.name, "audio"),
},
],
}
@@ -498,16 +584,16 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
),
}));
} else {
- // Create new audio track with the audio clip in a single atomic update
+ // Create new audio track with the audio element in a single atomic update
const newAudioTrack: TimelineTrack = {
id: crypto.randomUUID(),
name: "Audio Track",
type: "audio",
- clips: [
+ elements: [
{
- ...clip,
- id: audioClipId,
- name: getClipNameWithSuffix(clip.name, "audio"),
+ ...element,
+ id: audioElementId,
+ name: getElementNameWithSuffix(element.name, "audio"),
},
],
muted: false,
@@ -518,7 +604,7 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
}));
}
- return audioClipId;
+ return audioElementId;
},
getTotalDuration: () => {
@@ -526,10 +612,13 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
if (tracks.length === 0) return 0;
const trackEndTimes = tracks.map((track) =>
- track.clips.reduce((maxEnd, clip) => {
- const clipEnd =
- clip.startTime + clip.duration - clip.trimStart - clip.trimEnd;
- return Math.max(maxEnd, clipEnd);
+ track.elements.reduce((maxEnd, element) => {
+ const elementEnd =
+ element.startTime +
+ element.duration -
+ element.trimStart -
+ element.trimEnd;
+ return Math.max(maxEnd, elementEnd);
}, 0)
);
@@ -545,10 +634,10 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
dragState: {
isDragging: false,
- clipId: null,
+ elementId: null,
trackId: null,
startMouseX: 0,
- startClipTime: 0,
+ startElementTime: 0,
clickOffsetTime: 0,
currentTime: 0,
},
@@ -558,16 +647,22 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
dragState: { ...state.dragState, ...dragState },
})),
- startDrag: (clipId, trackId, startMouseX, startClipTime, clickOffsetTime) => {
+ startDrag: (
+ elementId,
+ trackId,
+ startMouseX,
+ startElementTime,
+ clickOffsetTime
+ ) => {
set({
dragState: {
isDragging: true,
- clipId,
+ elementId,
trackId,
startMouseX,
- startClipTime,
+ startElementTime,
clickOffsetTime,
- currentTime: startClipTime,
+ currentTime: startElementTime,
},
});
},
@@ -585,10 +680,10 @@ export const useTimelineStore = create<TimelineStore>((set, get) => ({
set({
dragState: {
isDragging: false,
- clipId: null,
+ elementId: null,
trackId: null,
startMouseX: 0,
- startClipTime: 0,
+ startElementTime: 0,
clickOffsetTime: 0,
currentTime: 0,
},