summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/dialogs
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/components/editor/dialogs')
-rw-r--r--apps/web/src/components/editor/dialogs/delete-project-dialog.tsx84
-rw-r--r--apps/web/src/components/editor/dialogs/migration-dialog.tsx44
-rw-r--r--apps/web/src/components/editor/dialogs/project-info-dialog.tsx81
-rw-r--r--apps/web/src/components/editor/dialogs/rename-project-dialog.tsx72
-rw-r--r--apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx229
-rw-r--r--apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx41
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>
- );
-}