diff options
Diffstat (limited to 'apps/web/src/components')
| -rw-r--r-- | apps/web/src/components/delete-project-dialog.tsx | 53 | ||||
| -rw-r--r-- | apps/web/src/components/editor/media-panel.tsx | 15 | ||||
| -rw-r--r-- | apps/web/src/components/editor/timeline-clip.tsx | 3 | ||||
| -rw-r--r-- | apps/web/src/components/rename-project-dialog.tsx | 73 | ||||
| -rw-r--r-- | apps/web/src/components/storage-provider.tsx | 81 |
5 files changed, 217 insertions, 8 deletions
diff --git a/apps/web/src/components/delete-project-dialog.tsx b/apps/web/src/components/delete-project-dialog.tsx new file mode 100644 index 00000000..db709647 --- /dev/null +++ b/apps/web/src/components/delete-project-dialog.tsx @@ -0,0 +1,53 @@ +import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+
+export function DeleteProjectDialog({
+ isOpen,
+ onOpenChange,
+ onConfirm,
+}: {
+ isOpen: boolean;
+ onOpenChange: (open: boolean) => void;
+ onConfirm: () => void;
+}) {
+ return (
+ <Dialog open={isOpen} onOpenChange={onOpenChange}>
+ <DialogContent
+ onOpenAutoFocus={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ >
+ <DialogHeader>
+ <DialogTitle>Delete Project</DialogTitle>
+ <DialogDescription>
+ Are you sure you want to delete this project? This action cannot be
+ undone.
+ </DialogDescription>
+ </DialogHeader>
+ <DialogFooter>
+ <Button
+ variant="outline"
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ onOpenChange(false);
+ }}
+ >
+ Cancel
+ </Button>
+ <Button variant="destructive" onClick={onConfirm}>
+ Delete
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ );
+}
diff --git a/apps/web/src/components/editor/media-panel.tsx b/apps/web/src/components/editor/media-panel.tsx index f717234d..dd12faff 100644 --- a/apps/web/src/components/editor/media-panel.tsx +++ b/apps/web/src/components/editor/media-panel.tsx @@ -32,7 +32,9 @@ export function MediaPanel() { setProgress(p) ); // Add each processed media item to the store - processedItems.forEach((item) => addMediaItem(item)); + for (const item of processedItems) { + await addMediaItem(item); + } } catch (error) { // Show error toast if processing fails console.error("Error processing files:", error); @@ -56,12 +58,11 @@ export function MediaPanel() { e.target.value = ""; // Reset input }; - const handleRemove = (e: React.MouseEvent, id: string) => { + const handleRemove = async (e: React.MouseEvent, id: string) => { // Remove a media item from the store e.stopPropagation(); - - // Remove tracks automatically when delete media + // Remove tracks automatically when delete media const { tracks, removeTrack } = useTimelineStore.getState(); tracks.forEach((track) => { const clipsToRemove = track.clips.filter((clip) => clip.mediaId === id); @@ -69,12 +70,14 @@ export function MediaPanel() { useTimelineStore.getState().removeClipFromTrack(track.id, clip.id); }); // Only remove track if it becomes empty and has no other clips - const updatedTrack = useTimelineStore.getState().tracks.find(t => t.id === track.id); + const updatedTrack = useTimelineStore + .getState() + .tracks.find((t) => t.id === track.id); if (updatedTrack && updatedTrack.clips.length === 0) { removeTrack(track.id); } }); - removeMediaItem(id); + await removeMediaItem(id); }; const formatDuration = (duration: number) => { diff --git a/apps/web/src/components/editor/timeline-clip.tsx b/apps/web/src/components/editor/timeline-clip.tsx index b9d3b9a6..9b5f20f6 100644 --- a/apps/web/src/components/editor/timeline-clip.tsx +++ b/apps/web/src/components/editor/timeline-clip.tsx @@ -34,7 +34,6 @@ export function TimelineClip({ track, zoomLevel, isSelected, - onContextMenu, onClipMouseDown, onClipClick, }: TimelineClipProps) { @@ -299,7 +298,7 @@ export function TimelineClip({ )} ${isSelected ? "ring-2 ring-primary ring-offset-1" : ""}`} onClick={(e) => onClipClick && onClipClick(e, clip)} onMouseDown={handleClipMouseDown} - onContextMenu={(e) => onContextMenu && onContextMenu(e, clip.id)} + onContextMenu={(e) => onClipMouseDown && onClipMouseDown(e, clip)} > <div className="absolute inset-1 flex items-center p-1"> {renderClipContent()} diff --git a/apps/web/src/components/rename-project-dialog.tsx b/apps/web/src/components/rename-project-dialog.tsx new file mode 100644 index 00000000..d84bad71 --- /dev/null +++ b/apps/web/src/components/rename-project-dialog.tsx @@ -0,0 +1,73 @@ +import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Input } from "@/components/ui/input";
+import { useState } from "react";
+
+export function RenameProjectDialog({
+ isOpen,
+ onOpenChange,
+ onConfirm,
+ projectName,
+}: {
+ isOpen: boolean;
+ onOpenChange: (open: boolean) => void;
+ onConfirm: (name: string) => void;
+ projectName: string;
+}) {
+ const [name, setName] = useState(projectName);
+
+ // Reset the name when dialog opens - this is better UX than syncing with every prop change
+ const handleOpenChange = (open: boolean) => {
+ if (open) {
+ setName(projectName);
+ }
+ onOpenChange(open);
+ };
+
+ return (
+ <Dialog open={isOpen} onOpenChange={handleOpenChange}>
+ <DialogContent>
+ <DialogHeader>
+ <DialogTitle>Rename Project</DialogTitle>
+ <DialogDescription>
+ Enter a new name for your project.
+ </DialogDescription>
+ </DialogHeader>
+
+ <Input
+ value={name}
+ onChange={(e) => setName(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ onConfirm(name);
+ }
+ }}
+ placeholder="Enter a new name"
+ className="mt-4"
+ />
+
+ <DialogFooter>
+ <Button
+ variant="outline"
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ onOpenChange(false);
+ }}
+ >
+ Cancel
+ </Button>
+ <Button onClick={() => onConfirm(name)}>Rename</Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ );
+}
diff --git a/apps/web/src/components/storage-provider.tsx b/apps/web/src/components/storage-provider.tsx new file mode 100644 index 00000000..e007ca96 --- /dev/null +++ b/apps/web/src/components/storage-provider.tsx @@ -0,0 +1,81 @@ +"use client";
+
+import { createContext, useContext, useEffect, useState } from "react";
+import { useProjectStore } from "@/stores/project-store";
+import { useMediaStore } from "@/stores/media-store";
+import { storageService } from "@/lib/storage/storage-service";
+import { toast } from "sonner";
+
+interface StorageContextType {
+ isInitialized: boolean;
+ isLoading: boolean;
+ hasSupport: boolean;
+ error: string | null;
+}
+
+const StorageContext = createContext<StorageContextType | null>(null);
+
+export function useStorage() {
+ const context = useContext(StorageContext);
+ if (!context) {
+ throw new Error("useStorage must be used within StorageProvider");
+ }
+ return context;
+}
+
+interface StorageProviderProps {
+ children: React.ReactNode;
+}
+
+export function StorageProvider({ children }: StorageProviderProps) {
+ const [status, setStatus] = useState<StorageContextType>({
+ isInitialized: false,
+ isLoading: true,
+ hasSupport: false,
+ error: null,
+ });
+
+ const loadAllProjects = useProjectStore((state) => state.loadAllProjects);
+ const loadAllMedia = useMediaStore((state) => state.loadAllMedia);
+
+ useEffect(() => {
+ const initializeStorage = async () => {
+ setStatus((prev) => ({ ...prev, isLoading: true }));
+
+ try {
+ // Check browser support
+ const hasSupport = storageService.isFullySupported();
+
+ if (!hasSupport) {
+ toast.warning(
+ "Storage not fully supported. Some features may not work."
+ );
+ }
+
+ // Load saved data in parallel
+ await Promise.all([loadAllProjects(), loadAllMedia()]);
+
+ setStatus({
+ isInitialized: true,
+ isLoading: false,
+ hasSupport,
+ error: null,
+ });
+ } catch (error) {
+ console.error("Failed to initialize storage:", error);
+ setStatus({
+ isInitialized: false,
+ isLoading: false,
+ hasSupport: storageService.isFullySupported(),
+ error: error instanceof Error ? error.message : "Unknown error",
+ });
+ }
+ };
+
+ initializeStorage();
+ }, [loadAllProjects, loadAllMedia]);
+
+ return (
+ <StorageContext.Provider value={status}>{children}</StorageContext.Provider>
+ );
+}
|
