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/project-info-dialog.tsx166
-rw-r--r--apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx458
-rw-r--r--apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx82
3 files changed, 353 insertions, 353 deletions
diff --git a/apps/web/src/components/editor/dialogs/project-info-dialog.tsx b/apps/web/src/components/editor/dialogs/project-info-dialog.tsx
index 42f99c79..fad711e3 100644
--- a/apps/web/src/components/editor/dialogs/project-info-dialog.tsx
+++ b/apps/web/src/components/editor/dialogs/project-info-dialog.tsx
@@ -1,83 +1,83 @@
-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 } from "@/lib/time";
-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 durationFormatted =
- project.duration > 0
- ? formatTimeCode({
- timeInSeconds: project.duration,
- format: project.duration >= 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>
- );
-}
+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 } from "@/lib/time";
+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 durationFormatted =
+ project.duration > 0
+ ? formatTimeCode({
+ timeInSeconds: project.duration,
+ format: project.duration >= 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/shortcuts-dialog.tsx b/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx
index 9a353a1c..70802b5b 100644
--- a/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx
+++ b/apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx
@@ -1,229 +1,229 @@
-"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>
- );
-}
+"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
index d391928c..0e854a93 100644
--- a/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx
+++ b/apps/web/src/components/editor/dialogs/storage-persistence-dialog.tsx
@@ -1,41 +1,41 @@
-"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>
- );
-}
+"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>
+ );
+}