diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
| commit | 3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch) | |
| tree | 57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/components/editor/dialogs | |
| parent | 729d10592f4e802f91c555c309ff3ba4b119f843 (diff) | |
| download | opencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz | |
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/components/editor/dialogs')
6 files changed, 0 insertions, 551 deletions
diff --git a/apps/web/src/components/editor/dialogs/delete-project-dialog.tsx b/apps/web/src/components/editor/dialogs/delete-project-dialog.tsx deleted file mode 100644 index 0086384e..00000000 --- a/apps/web/src/components/editor/dialogs/delete-project-dialog.tsx +++ /dev/null @@ -1,84 +0,0 @@ -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 ( - <Dialog open={isOpen} onOpenChange={onOpenChange}> - <DialogContent - onOpenAutoFocus={(event) => { - event.preventDefault(); - event.stopPropagation(); - }} - > - <DialogHeader> - <DialogTitle> - {singleName ? ( - <> - {"Delete '"} - <span className="inline-block max-w-[300px] truncate align-bottom"> - {singleName} - </span> - {"'?"} - </> - ) : ( - `Delete ${count} projects?` - )} - </DialogTitle> - </DialogHeader> - <DialogBody> - <Alert variant="destructive"> - <AlertTitle>Warning</AlertTitle> - <AlertDescription> - This will permanently delete{" "} - {singleName ? `"${singleName}"` : `${count} projects`} and all - associated files. - </AlertDescription> - </Alert> - <div className="flex flex-col gap-3"> - <Label className="text-xs font-semibold text-slate-500"> - Type "DELETE" to confirm - </Label> - <Input - type="text" - placeholder="DELETE" - size="lg" - variant="destructive" - /> - </div> - </DialogBody> - <DialogFooter> - <Button variant="outline" onClick={() => onOpenChange(false)}> - Cancel - </Button> - <Button variant="destructive" onClick={onConfirm}> - Delete project - </Button> - </DialogFooter> - </DialogContent> - </Dialog> - ); -} diff --git a/apps/web/src/components/editor/dialogs/migration-dialog.tsx b/apps/web/src/components/editor/dialogs/migration-dialog.tsx deleted file mode 100644 index 4c9550c2..00000000 --- a/apps/web/src/components/editor/dialogs/migration-dialog.tsx +++ /dev/null @@ -1,44 +0,0 @@ -"use client"; - -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { useEditor } from "@/hooks/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 ( - <Dialog open={true}> - <DialogContent - className="sm:max-w-md" - onPointerDownOutside={(event) => event.preventDefault()} - onEscapeKeyDown={(event) => event.preventDefault()} - > - <DialogHeader> - <DialogTitle>{title}</DialogTitle> - <DialogDescription>{description}</DialogDescription> - </DialogHeader> - - <div className="flex items-center justify-center py-4"> - <Loader2 className="text-muted-foreground size-8 animate-spin" /> - </div> - </DialogContent> - </Dialog> - ); -} diff --git a/apps/web/src/components/editor/dialogs/project-info-dialog.tsx b/apps/web/src/components/editor/dialogs/project-info-dialog.tsx deleted file mode 100644 index ed4eb990..00000000 --- a/apps/web/src/components/editor/dialogs/project-info-dialog.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { - Dialog, - DialogBody, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import type { TProjectMetadata } from "@/lib/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 ( - <div className="flex justify-between items-center py-0 last:pb-0"> - <span className="text-muted-foreground text-sm">{label}</span> - <span className="text-sm font-medium">{value}</span> - </div> - ); -} - -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 ( - <Dialog open={isOpen} onOpenChange={onOpenChange}> - <DialogContent onOpenAutoFocus={(event) => event.preventDefault()}> - <DialogHeader> - <DialogTitle className="truncate max-w-[350px]"> - {project.name} - </DialogTitle> - </DialogHeader> - - <DialogBody className="flex flex-col"> - <InfoRow label="Duration" value={durationFormatted} /> - <InfoRow - label="Created" - value={formatDate({ date: project.createdAt })} - /> - <InfoRow - label="Modified" - value={formatDate({ date: project.updatedAt })} - /> - <InfoRow - label="Project ID" - value={ - <code className="text-xs bg-muted px-1.5 py-0.5 rounded"> - {project.id.slice(0, 8)} - </code> - } - /> - </DialogBody> - <DialogFooter> - <Button variant="outline" onClick={() => onOpenChange(false)}> - Close - </Button> - <Button onClick={() => onOpenChange(false)}>Done</Button> - </DialogFooter> - </DialogContent> - </Dialog> - ); -} diff --git a/apps/web/src/components/editor/dialogs/rename-project-dialog.tsx b/apps/web/src/components/editor/dialogs/rename-project-dialog.tsx deleted file mode 100644 index 73024d71..00000000 --- a/apps/web/src/components/editor/dialogs/rename-project-dialog.tsx +++ /dev/null @@ -1,72 +0,0 @@ -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 ( - <Dialog open={isOpen} onOpenChange={handleOpenChange}> - <DialogContent> - <DialogHeader> - <DialogTitle>Rename project</DialogTitle> - </DialogHeader> - - <DialogBody className="gap-3"> - <Label>New name</Label> - <Input - value={name} - onChange={(e) => setName(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - onConfirm(name); - } - }} - placeholder="Enter a new name" - /> - </DialogBody> - - <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/editor/dialogs/shortcuts-dialog.tsx b/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx deleted file mode 100644 index 70802b5b..00000000 --- a/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx +++ /dev/null @@ -1,229 +0,0 @@ -"use client";
-
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import {
- type KeyboardShortcut,
- useKeyboardShortcutsHelp,
-} from "@/hooks/use-keyboard-shortcuts-help";
-import { useKeybindingsStore } from "@/stores/keybindings-store";
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogBody,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-
-export function ShortcutsDialog({
- isOpen,
- onOpenChange,
-}: {
- isOpen: boolean;
- onOpenChange: (open: boolean) => void;
-}) {
- const [recordingShortcut, setRecordingShortcut] =
- useState<KeyboardShortcut | null>(null);
-
- const {
- updateKeybinding,
- removeKeybinding,
- getKeybindingString,
- validateKeybinding,
- getKeybindingsForAction,
- setIsRecording,
- resetToDefaults,
- isRecording,
- } = useKeybindingsStore();
-
- const { shortcuts } = useKeyboardShortcutsHelp();
-
- const categories = Array.from(new Set(shortcuts.map((s) => s.category)));
-
- useEffect(() => {
- if (!isRecording || !recordingShortcut) return;
-
- const handleKeyDown = (e: KeyboardEvent) => {
- e.preventDefault();
- e.stopPropagation();
-
- const keyString = getKeybindingString(e);
- if (keyString) {
- const conflict = validateKeybinding(
- keyString,
- recordingShortcut.action,
- );
- if (conflict) {
- toast.error(
- `Key "${keyString}" is already bound to "${conflict.existingAction}"`,
- );
- setRecordingShortcut(null);
- return;
- }
-
- const oldKeys = getKeybindingsForAction(recordingShortcut.action);
- for (const key of oldKeys) {
- removeKeybinding(key);
- }
-
- updateKeybinding(keyString, recordingShortcut.action);
-
- setIsRecording(false);
- setRecordingShortcut(null);
- }
- };
-
- const handleClickOutside = () => {
- setRecordingShortcut(null);
- setIsRecording(false);
- };
-
- document.addEventListener("keydown", handleKeyDown);
- document.addEventListener("click", handleClickOutside);
-
- return () => {
- document.removeEventListener("keydown", handleKeyDown);
- document.removeEventListener("click", handleClickOutside);
- };
- }, [
- recordingShortcut,
- getKeybindingString,
- updateKeybinding,
- removeKeybinding,
- validateKeybinding,
- getKeybindingsForAction,
- setIsRecording,
- isRecording,
- ]);
-
- const handleStartRecording = (shortcut: KeyboardShortcut) => {
- setRecordingShortcut(shortcut);
- setIsRecording(true);
- };
-
- return (
- <Dialog open={isOpen} onOpenChange={onOpenChange}>
- <DialogContent className="flex max-h-[80vh] max-w-2xl flex-col p-0">
- <DialogHeader>
- <DialogTitle>Keyboard shortcuts</DialogTitle>
- </DialogHeader>
-
- <DialogBody className="scrollbar-thin grow overflow-y-auto">
- <div className="flex flex-col gap-6">
- {categories.map((category) => (
- <div key={category} className="flex flex-col gap-1">
- <h3 className="text-muted-foreground text-xs font-medium tracking-wide uppercase">
- {category}
- </h3>
- <div className="flex flex-col gap-1">
- {shortcuts
- .filter((shortcut) => shortcut.category === category)
- .map((shortcut) => (
- <ShortcutItem
- key={shortcut.action}
- shortcut={shortcut}
- isRecording={
- shortcut.action === recordingShortcut?.action
- }
- onStartRecording={() => handleStartRecording(shortcut)}
- />
- ))}
- </div>
- </div>
- ))}
- </div>
- </DialogBody>
- <DialogFooter>
- <Button variant="destructive" onClick={resetToDefaults}>
- Reset to default
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
-
-function ShortcutItem({
- shortcut,
- isRecording,
- onStartRecording,
-}: {
- shortcut: KeyboardShortcut;
- isRecording: boolean;
- onStartRecording: (params: { shortcut: KeyboardShortcut }) => void;
-}) {
- const displayKeys = shortcut.keys.filter((key: string) => {
- if (
- key.includes("Cmd") &&
- shortcut.keys.includes(key.replace("Cmd", "Ctrl"))
- )
- return false;
-
- return true;
- });
-
- return (
- <div className="flex items-center justify-between">
- <div className="flex items-center gap-3">
- {shortcut.icon && (
- <div className="text-muted-foreground">{shortcut.icon}</div>
- )}
- <span className="text-sm">{shortcut.description}</span>
- </div>
- <div className="flex items-center gap-2">
- {displayKeys.map((key: string, index: number) => (
- <div key={key} className="flex items-center gap-2">
- <div className="flex items-center gap-1">
- {key.split("+").map((keyPart: string, partIndex: number) => {
- const keyId = `${shortcut.id}-${index}-${partIndex}`;
- return (
- <EditableShortcutKey
- key={keyId}
- isRecording={isRecording}
- onStartRecording={() => onStartRecording({ shortcut })}
- >
- {keyPart}
- </EditableShortcutKey>
- );
- })}
- </div>
- {index < displayKeys.length - 1 && (
- <span className="text-muted-foreground text-xs">or</span>
- )}
- </div>
- ))}
- </div>
- </div>
- );
-}
-
-function EditableShortcutKey({
- children,
- isRecording,
- onStartRecording,
-}: {
- children: React.ReactNode;
- isRecording: boolean;
- onStartRecording: () => void;
-}) {
- const handleClick = (e: React.MouseEvent) => {
- e.preventDefault();
- e.stopPropagation();
- onStartRecording();
- };
-
- return (
- <Button
- variant="outline"
- size="sm"
- onClick={handleClick}
- title={
- isRecording ? "Press any key combination..." : "Click to edit shortcut"
- }
- >
- {children}
- </Button>
- );
-}
diff --git a/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx b/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx deleted file mode 100644 index 0e854a93..00000000 --- a/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx +++ /dev/null @@ -1,41 +0,0 @@ -"use client";
-
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogBody,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { useStoragePersistence } from "@/hooks/use-storage-persistence";
-
-export function StoragePersistenceDialog() {
- const { showDialog, onConfirm, onDismiss } = useStoragePersistence();
-
- return (
- <Dialog open={showDialog} onOpenChange={(open) => !open && onDismiss()}>
- <DialogContent className="sm:max-w-md">
- <DialogHeader>
- <DialogTitle>Don't lose your projects</DialogTitle>
- </DialogHeader>
- <DialogBody>
- <p className="text-base text-muted-foreground">
- Your browser can automatically delete your projects when storage
- runs low.
- </p>
- <p className="text-base text-muted-foreground">
- Allow OpenCut to protect them?
- </p>
- </DialogBody>
- <DialogFooter>
- <Button variant="outline" onClick={onDismiss}>
- Not now
- </Button>
- <Button onClick={onConfirm}>Allow</Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-}
|
