From 3e89d2998588f8e35c524e523ba2ee853f0f6475 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 20 Apr 2026 11:31:17 +0200 Subject: refactor: restructure files to their domains, new preview overlay system, and dep graph --- .../project/components/delete-project-dialog.tsx | 84 ++++++++++++++++++++++ .../src/project/components/migration-dialog.tsx | 44 ++++++++++++ .../src/project/components/project-info-dialog.tsx | 81 +++++++++++++++++++++ .../project/components/rename-project-dialog.tsx | 72 +++++++++++++++++++ apps/web/src/project/types.ts | 54 ++++++++++++++ 5 files changed, 335 insertions(+) create mode 100644 apps/web/src/project/components/delete-project-dialog.tsx create mode 100644 apps/web/src/project/components/migration-dialog.tsx create mode 100644 apps/web/src/project/components/project-info-dialog.tsx create mode 100644 apps/web/src/project/components/rename-project-dialog.tsx create mode 100644 apps/web/src/project/types.ts (limited to 'apps/web/src/project') diff --git a/apps/web/src/project/components/delete-project-dialog.tsx b/apps/web/src/project/components/delete-project-dialog.tsx new file mode 100644 index 00000000..0086384e --- /dev/null +++ b/apps/web/src/project/components/delete-project-dialog.tsx @@ -0,0 +1,84 @@ +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogBody, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; + +export function DeleteProjectDialog({ + isOpen, + onOpenChange, + onConfirm, + projectNames, +}: { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + onConfirm: () => void; + projectNames: string[]; +}) { + const count = projectNames.length; + const isSingle = count === 1; + const singleName = isSingle ? projectNames[0] : null; + + return ( + + { + event.preventDefault(); + event.stopPropagation(); + }} + > + + + {singleName ? ( + <> + {"Delete '"} + + {singleName} + + {"'?"} + + ) : ( + `Delete ${count} projects?` + )} + + + + + Warning + + This will permanently delete{" "} + {singleName ? `"${singleName}"` : `${count} projects`} and all + associated files. + + +
+ + +
+
+ + + + +
+
+ ); +} diff --git a/apps/web/src/project/components/migration-dialog.tsx b/apps/web/src/project/components/migration-dialog.tsx new file mode 100644 index 00000000..4aac26d5 --- /dev/null +++ b/apps/web/src/project/components/migration-dialog.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { useEditor } from "@/editor/use-editor"; +import { Loader2 } from "lucide-react"; + +export function MigrationDialog() { + const editor = useEditor(); + const migrationState = editor.project.getMigrationState(); + + if (!migrationState.isMigrating) return null; + + const title = migrationState.projectName + ? "Updating project" + : "Updating projects"; + const description = migrationState.projectName + ? `Upgrading "${migrationState.projectName}" from v${migrationState.fromVersion} to v${migrationState.toVersion}` + : `Upgrading projects from v${migrationState.fromVersion} to v${migrationState.toVersion}`; + + return ( + + event.preventDefault()} + onEscapeKeyDown={(event) => event.preventDefault()} + > + + {title} + {description} + + +
+ +
+
+
+ ); +} diff --git a/apps/web/src/project/components/project-info-dialog.tsx b/apps/web/src/project/components/project-info-dialog.tsx new file mode 100644 index 00000000..f0ea364e --- /dev/null +++ b/apps/web/src/project/components/project-info-dialog.tsx @@ -0,0 +1,81 @@ +import { + Dialog, + DialogBody, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import type { TProjectMetadata } from "@/project/types"; +import { formatDate } from "@/utils/date"; +import { formatTimecode, mediaTimeToSeconds } from "opencut-wasm"; +import { Button } from "@/components/ui/button"; + +function InfoRow({ + label, + value, +}: { + label: string; + value: string | React.ReactNode; +}) { + return ( +
+ {label} + {value} +
+ ); +} + +export function ProjectInfoDialog({ + isOpen, + onOpenChange, + project, +}: { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + project: TProjectMetadata; +}) { + const durationSeconds = mediaTimeToSeconds({ time: project.duration }); + const durationFormatted = + project.duration > 0 + ? (formatTimecode({ time: project.duration, format: durationSeconds >= 3600 ? "HH:MM:SS" : "MM:SS" }) ?? "") + : "0:00"; + + return ( + + event.preventDefault()}> + + + {project.name} + + + + + + + + + {project.id.slice(0, 8)} + + } + /> + + + + + + + + ); +} diff --git a/apps/web/src/project/components/rename-project-dialog.tsx b/apps/web/src/project/components/rename-project-dialog.tsx new file mode 100644 index 00000000..73024d71 --- /dev/null +++ b/apps/web/src/project/components/rename-project-dialog.tsx @@ -0,0 +1,72 @@ +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogBody, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { useState } from "react"; +import { Label } from "@/components/ui/label"; + +export function RenameProjectDialog({ + isOpen, + onOpenChange, + onConfirm, + projectName, +}: { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + onConfirm: (newName: string) => void; + projectName: string; +}) { + const [name, setName] = useState(projectName); + + const handleOpenChange = (open: boolean) => { + if (open) { + setName(projectName); + } + onOpenChange(open); + }; + + return ( + + + + Rename project + + + + + setName(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + onConfirm(name); + } + }} + placeholder="Enter a new name" + /> + + + + + + + + + ); +} diff --git a/apps/web/src/project/types.ts b/apps/web/src/project/types.ts new file mode 100644 index 00000000..42e844f6 --- /dev/null +++ b/apps/web/src/project/types.ts @@ -0,0 +1,54 @@ +import type { FrameRate } from "opencut-wasm"; +import type { TScene } from "@/timeline/types"; + +export type TBackground = + | { + type: "color"; + color: string; + } + | { + type: "blur"; + blurIntensity: number; + }; + +export interface TCanvasSize { + width: number; + height: number; +} + +export interface TProjectMetadata { + id: string; + name: string; + thumbnail?: string; + duration: number; + createdAt: Date; + updatedAt: Date; +} + +export interface TProjectSettings { + fps: FrameRate; + canvasSize: TCanvasSize; + canvasSizeMode?: "preset" | "custom"; + lastCustomCanvasSize?: TCanvasSize | null; + originalCanvasSize?: TCanvasSize | null; + background: TBackground; +} + +export interface TTimelineViewState { + zoomLevel: number; + scrollLeft: number; + playheadTime: number; +} + +export interface TProject { + metadata: TProjectMetadata; + scenes: TScene[]; + currentSceneId: string; + settings: TProjectSettings; + version: number; + timelineViewState?: TTimelineViewState; +} + +export type TProjectSortKey = "createdAt" | "updatedAt" | "name" | "duration"; +export type TSortOrder = "asc" | "desc"; +export type TProjectSortOption = `${TProjectSortKey}-${TSortOrder}`; -- cgit v1.3.1