summaryrefslogtreecommitdiff
path: root/apps/web/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/components')
-rw-r--r--apps/web/src/components/editor/media-panel/views/media.tsx16
-rw-r--r--apps/web/src/components/editor/timeline-track.tsx61
-rw-r--r--apps/web/src/components/editor/timeline.tsx9
-rw-r--r--apps/web/src/components/storage-provider.tsx7
4 files changed, 66 insertions, 27 deletions
diff --git a/apps/web/src/components/editor/media-panel/views/media.tsx b/apps/web/src/components/editor/media-panel/views/media.tsx
index f975496e..f7da684d 100644
--- a/apps/web/src/components/editor/media-panel/views/media.tsx
+++ b/apps/web/src/components/editor/media-panel/views/media.tsx
@@ -24,9 +24,11 @@ import {
SelectValue,
} from "@/components/ui/select";
import { DraggableMediaItem } from "@/components/ui/draggable-item";
+import { useProjectStore } from "@/stores/project-store";
export function MediaView() {
const { mediaItems, addMediaItem, removeMediaItem } = useMediaStore();
+ const { activeProject } = useProjectStore();
const fileInputRef = useRef<HTMLInputElement>(null);
const [isProcessing, setIsProcessing] = useState(false);
const [progress, setProgress] = useState(0);
@@ -35,6 +37,11 @@ export function MediaView() {
const processFiles = async (files: FileList | File[]) => {
if (!files || files.length === 0) return;
+ if (!activeProject) {
+ toast.error("No active project");
+ return;
+ }
+
setIsProcessing(true);
setProgress(0);
try {
@@ -44,7 +51,7 @@ export function MediaView() {
);
// Add each processed media item to the store
for (const item of processedItems) {
- await addMediaItem(item);
+ await addMediaItem(activeProject.id, item);
}
} catch (error) {
// Show error toast if processing fails
@@ -73,6 +80,11 @@ export function MediaView() {
// Remove a media item from the store
e.stopPropagation();
+ if (!activeProject) {
+ toast.error("No active project");
+ return;
+ }
+
// Remove elements automatically when delete media
const { tracks, removeTrack } = useTimelineStore.getState();
tracks.forEach((track) => {
@@ -92,7 +104,7 @@ export function MediaView() {
removeTrack(track.id);
}
});
- await removeMediaItem(id);
+ await removeMediaItem(activeProject.id, id);
};
const formatDuration = (duration: number) => {
diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx
index eb58e1ee..2751fa5a 100644
--- a/apps/web/src/components/editor/timeline-track.tsx
+++ b/apps/web/src/components/editor/timeline-track.tsx
@@ -13,11 +13,12 @@ import {
ContextMenuSeparator,
ContextMenuTrigger,
} from "../ui/context-menu";
-import {
+import {
TimelineTrack,
sortTracksByOrder,
ensureMainTrack,
- getMainTrack
+ getMainTrack,
+ canElementGoOnTrack,
} from "@/types/timeline";
import { usePlaybackStore } from "@/stores/playback-store";
import type {
@@ -572,7 +573,9 @@ export function TimelineTrackContent({
// dropPosition === "on" but track is not text type
// Insert above main track if main track exists, otherwise at top
if (mainTrack) {
- const mainTrackIndex = tracks.findIndex(t => t.id === mainTrack.id);
+ const mainTrackIndex = tracks.findIndex(
+ (t) => t.id === mainTrack.id
+ );
insertIndex = mainTrackIndex;
} else {
insertIndex = 0; // Top of timeline
@@ -648,9 +651,11 @@ export function TimelineTrackContent({
const isVideoOrImage =
dragData.type === "video" || dragData.type === "image";
const isAudio = dragData.type === "audio";
- const isCompatible =
- (track.type === "media" && isVideoOrImage) ||
- (track.type === "audio" && isAudio);
+ const isCompatible = isVideoOrImage
+ ? canElementGoOnTrack("media", track.type)
+ : isAudio
+ ? canElementGoOnTrack("media", track.type)
+ : false;
let targetTrack = tracks.find((t) => t.id === targetTrackId);
@@ -662,7 +667,7 @@ export function TimelineTrackContent({
if (isVideoOrImage) {
// For video/image, check if we need a main track or additional media track
const mainTrack = getMainTrack(tracks);
-
+
if (!mainTrack) {
// No main track exists, create it
const updatedTracks = ensureMainTrack(tracks);
@@ -672,22 +677,32 @@ export function TimelineTrackContent({
targetTrack = newMainTrack;
} else {
// Main track was created but somehow has elements, create new media track
- const mainTrackIndex = updatedTracks.findIndex(t => t.id === newMainTrack?.id);
+ const mainTrackIndex = updatedTracks.findIndex(
+ (t) => t.id === newMainTrack?.id
+ );
targetTrackId = insertTrackAt("media", mainTrackIndex);
- const updatedTracksAfterInsert = useTimelineStore.getState().tracks;
- const newTargetTrack = updatedTracksAfterInsert.find(t => t.id === targetTrackId);
+ const updatedTracksAfterInsert =
+ useTimelineStore.getState().tracks;
+ const newTargetTrack = updatedTracksAfterInsert.find(
+ (t) => t.id === targetTrackId
+ );
if (!newTargetTrack) return;
targetTrack = newTargetTrack;
}
- } else if (mainTrack.elements.length === 0 && dropPosition === "on") {
+ } else if (
+ mainTrack.elements.length === 0 &&
+ dropPosition === "on"
+ ) {
// Main track exists and is empty, use it
targetTrackId = mainTrack.id;
targetTrack = mainTrack;
} else {
// Create new media track above main track
- const mainTrackIndex = tracks.findIndex(t => t.id === mainTrack.id);
+ const mainTrackIndex = tracks.findIndex(
+ (t) => t.id === mainTrack.id
+ );
let insertIndex: number;
-
+
if (dropPosition === "above") {
insertIndex = currentTrackIndex;
} else if (dropPosition === "below") {
@@ -696,10 +711,12 @@ export function TimelineTrackContent({
// Insert above main track
insertIndex = mainTrackIndex;
}
-
+
targetTrackId = insertTrackAt("media", insertIndex);
const updatedTracks = useTimelineStore.getState().tracks;
- const newTargetTrack = updatedTracks.find(t => t.id === targetTrackId);
+ const newTargetTrack = updatedTracks.find(
+ (t) => t.id === targetTrackId
+ );
if (!newTargetTrack) return;
targetTrack = newTargetTrack;
}
@@ -707,7 +724,7 @@ export function TimelineTrackContent({
// Audio tracks go at the bottom
const mainTrack = getMainTrack(tracks);
let insertIndex: number;
-
+
if (dropPosition === "above") {
insertIndex = currentTrackIndex;
} else if (dropPosition === "below") {
@@ -715,16 +732,20 @@ export function TimelineTrackContent({
} else {
// Insert after main track (bottom area)
if (mainTrack) {
- const mainTrackIndex = tracks.findIndex(t => t.id === mainTrack.id);
+ const mainTrackIndex = tracks.findIndex(
+ (t) => t.id === mainTrack.id
+ );
insertIndex = mainTrackIndex + 1;
} else {
insertIndex = tracks.length; // Bottom of timeline
}
}
-
+
targetTrackId = insertTrackAt("audio", insertIndex);
const updatedTracks = useTimelineStore.getState().tracks;
- const newTargetTrack = updatedTracks.find(t => t.id === targetTrackId);
+ const newTargetTrack = updatedTracks.find(
+ (t) => t.id === targetTrackId
+ );
if (!newTargetTrack) return;
targetTrack = newTargetTrack;
}
@@ -805,7 +826,7 @@ export function TimelineTrackContent({
? wouldOverlap
? "Cannot drop - would overlap"
: "Drop element here"
- : "Drop media here"}
+ : ""}
</div>
) : (
<>
diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx
index 283f6b04..f98ab5ec 100644
--- a/apps/web/src/components/editor/timeline.tsx
+++ b/apps/web/src/components/editor/timeline.tsx
@@ -31,6 +31,7 @@ import {
import { useTimelineStore } from "@/stores/timeline-store";
import { useMediaStore } from "@/stores/media-store";
import { usePlaybackStore } from "@/stores/playback-store";
+import { useProjectStore } from "@/stores/project-store";
import { processMediaFiles } from "@/lib/media-processing";
import { toast } from "sonner";
import { useState, useRef, useEffect, useCallback } from "react";
@@ -65,6 +66,7 @@ export function Timeline() {
redo,
} = useTimelineStore();
const { mediaItems, addMediaItem } = useMediaStore();
+ const { activeProject } = useProjectStore();
const {
currentTime,
duration,
@@ -377,6 +379,11 @@ export function Timeline() {
}
} else if (e.dataTransfer.files?.length > 0) {
// Handle file drops by creating new tracks
+ if (!activeProject) {
+ toast.error("No active project");
+ return;
+ }
+
setIsProcessing(true);
setProgress(0);
try {
@@ -385,7 +392,7 @@ export function Timeline() {
(p) => setProgress(p)
);
for (const processedItem of processedItems) {
- await addMediaItem(processedItem);
+ await addMediaItem(activeProject.id, processedItem);
const currentMediaItems = useMediaStore.getState().mediaItems;
const addedItem = currentMediaItems.find(
(item) =>
diff --git a/apps/web/src/components/storage-provider.tsx b/apps/web/src/components/storage-provider.tsx
index e007ca96..48423b5c 100644
--- a/apps/web/src/components/storage-provider.tsx
+++ b/apps/web/src/components/storage-provider.tsx
@@ -36,7 +36,6 @@ export function StorageProvider({ children }: StorageProviderProps) {
});
const loadAllProjects = useProjectStore((state) => state.loadAllProjects);
- const loadAllMedia = useMediaStore((state) => state.loadAllMedia);
useEffect(() => {
const initializeStorage = async () => {
@@ -52,8 +51,8 @@ export function StorageProvider({ children }: StorageProviderProps) {
);
}
- // Load saved data in parallel
- await Promise.all([loadAllProjects(), loadAllMedia()]);
+ // Load saved projects (media will be loaded when a project is loaded)
+ await loadAllProjects();
setStatus({
isInitialized: true,
@@ -73,7 +72,7 @@ export function StorageProvider({ children }: StorageProviderProps) {
};
initializeStorage();
- }, [loadAllProjects, loadAllMedia]);
+ }, [loadAllProjects]);
return (
<StorageContext.Provider value={status}>{children}</StorageContext.Provider>