diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-03-27 00:55:15 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-03-29 15:56:48 +0200 |
| commit | 4d127a038bf768eded68aae3cfa5c1b12241bdac (patch) | |
| tree | 16bbb801caf6770546cff4fdb37b6408ec9ead98 /apps/web/src/components/editor/editor-header.tsx | |
| parent | 82817cb50cb0487350573d6129130aec705f924b (diff) | |
| download | opencut-4d127a038bf768eded68aae3cfa5c1b12241bdac.tar.gz | |
chore: normalize line endings
Diffstat (limited to 'apps/web/src/components/editor/editor-header.tsx')
| -rw-r--r-- | apps/web/src/components/editor/editor-header.tsx | 490 |
1 files changed, 245 insertions, 245 deletions
diff --git a/apps/web/src/components/editor/editor-header.tsx b/apps/web/src/components/editor/editor-header.tsx index 8dd730b5..460e8ed6 100644 --- a/apps/web/src/components/editor/editor-header.tsx +++ b/apps/web/src/components/editor/editor-header.tsx @@ -1,245 +1,245 @@ -"use client"; - -import { Button } from "../ui/button"; -import { useRef, useState } from "react"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "../ui/dropdown-menu"; -import Link from "next/link"; -import { RenameProjectDialog } from "./dialogs/rename-project-dialog"; -import { DeleteProjectDialog } from "./dialogs/delete-project-dialog"; -import { useRouter } from "next/navigation"; -import { FaDiscord } from "react-icons/fa6"; -import { ExportButton } from "./export-button"; -import { ThemeToggle } from "../theme-toggle"; -import { DEFAULT_LOGO_URL, SOCIAL_LINKS } from "@/constants/site-constants"; -import { toast } from "sonner"; -import { useEditor } from "@/hooks/use-editor"; -import { CommandIcon, Logout05Icon } from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { ShortcutsDialog } from "./dialogs/shortcuts-dialog"; -import Image from "next/image"; -import { cn } from "@/utils/ui"; - -export function EditorHeader() { - return ( - <header className="bg-background flex h-[3.4rem] items-center justify-between px-3 pt-0.5"> - <div className="flex items-center gap-1"> - <ProjectDropdown /> - <EditableProjectName /> - </div> - <nav className="flex items-center gap-2"> - <ExportButton /> - <ThemeToggle /> - </nav> - </header> - ); -} - -function ProjectDropdown() { - const [openDialog, setOpenDialog] = useState< - "delete" | "rename" | "shortcuts" | null - >(null); - const [isExiting, setIsExiting] = useState(false); - const router = useRouter(); - const editor = useEditor(); - const activeProject = useEditor((e) => e.project.getActive()); - - const handleExit = async () => { - if (isExiting) return; - setIsExiting(true); - - try { - await editor.project.prepareExit(); - editor.project.closeProject(); - } catch (error) { - console.error("Failed to prepare project exit:", error); - } finally { - editor.project.closeProject(); - router.push("/projects"); - } - }; - - const handleSaveProjectName = async (newName: string) => { - if ( - activeProject && - newName.trim() && - newName !== activeProject.metadata.name - ) { - try { - await editor.project.renameProject({ - id: activeProject.metadata.id, - name: newName.trim(), - }); - } catch (error) { - toast.error("Failed to rename project", { - description: - error instanceof Error ? error.message : "Please try again", - }); - } finally { - setOpenDialog(null); - } - } - }; - - const handleDeleteProject = async () => { - if (activeProject) { - try { - await editor.project.deleteProjects({ - ids: [activeProject.metadata.id], - }); - router.push("/projects"); - } catch (error) { - toast.error("Failed to delete project", { - description: - error instanceof Error ? error.message : "Please try again", - }); - } finally { - setOpenDialog(null); - } - } - }; - - return ( - <> - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button variant="ghost" size="icon" className="p-1 rounded-sm size-8"> - <Image - src={DEFAULT_LOGO_URL} - alt="Project thumbnail" - width={32} - height={32} - className="invert dark:invert-0 size-5" - /> - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent align="start" className="z-100 w-44"> - <DropdownMenuItem - onClick={handleExit} - disabled={isExiting} - icon={<HugeiconsIcon icon={Logout05Icon} />} - > - Exit project - </DropdownMenuItem> - - <DropdownMenuItem - onClick={() => setOpenDialog("shortcuts")} - icon={<HugeiconsIcon icon={CommandIcon} />} - > - Shortcuts - </DropdownMenuItem> - - <DropdownMenuSeparator /> - - <DropdownMenuItem asChild icon={<FaDiscord className="size-4!" />}> - <Link - href={SOCIAL_LINKS.discord} - target="_blank" - rel="noopener noreferrer" - > - Discord - </Link> - </DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - <RenameProjectDialog - isOpen={openDialog === "rename"} - onOpenChange={(isOpen) => setOpenDialog(isOpen ? "rename" : null)} - onConfirm={(newName) => handleSaveProjectName(newName)} - projectName={activeProject?.metadata.name || ""} - /> - <DeleteProjectDialog - isOpen={openDialog === "delete"} - onOpenChange={(isOpen) => setOpenDialog(isOpen ? "delete" : null)} - onConfirm={handleDeleteProject} - projectNames={[activeProject?.metadata.name || ""]} - /> - <ShortcutsDialog - isOpen={openDialog === "shortcuts"} - onOpenChange={(isOpen) => setOpenDialog(isOpen ? "shortcuts" : null)} - /> - </> - ); -} - -function EditableProjectName() { - const editor = useEditor(); - const activeProject = useEditor((e) => e.project.getActive()); - const [isEditing, setIsEditing] = useState(false); - const inputRef = useRef<HTMLInputElement>(null); - const originalNameRef = useRef(""); - - const projectName = activeProject?.metadata.name || ""; - - const startEditing = () => { - if (isEditing) return; - originalNameRef.current = projectName; - setIsEditing(true); - - requestAnimationFrame(() => { - inputRef.current?.select(); - }); - }; - - const saveEdit = async () => { - if (!inputRef.current || !activeProject) return; - const newName = inputRef.current.value.trim(); - setIsEditing(false); - - if (!newName) { - inputRef.current.value = originalNameRef.current; - return; - } - - if (newName !== originalNameRef.current) { - try { - await editor.project.renameProject({ - id: activeProject.metadata.id, - name: newName, - }); - } catch (error) { - toast.error("Failed to rename project", { - description: - error instanceof Error ? error.message : "Please try again", - }); - } - } - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - inputRef.current?.blur(); - } else if (event.key === "Escape") { - event.preventDefault(); - if (inputRef.current) { - inputRef.current.value = originalNameRef.current; - inputRef.current.setSelectionRange(0, 0); - } - setIsEditing(false); - inputRef.current?.blur(); - } - }; - - return ( - <input - ref={inputRef} - type="text" - defaultValue={projectName} - readOnly={!isEditing} - onClick={startEditing} - onBlur={saveEdit} - onKeyDown={handleKeyDown} - style={{ fieldSizing: "content" }} - className={cn( - "text-[0.9rem] h-8 px-2 py-1 rounded-sm bg-transparent outline-none cursor-pointer hover:bg-accent hover:text-accent-foreground", - isEditing && "ring-1 ring-ring cursor-text hover:bg-transparent", - )} - /> - ); -} +"use client";
+
+import { Button } from "../ui/button";
+import { useRef, useState } from "react";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "../ui/dropdown-menu";
+import Link from "next/link";
+import { RenameProjectDialog } from "./dialogs/rename-project-dialog";
+import { DeleteProjectDialog } from "./dialogs/delete-project-dialog";
+import { useRouter } from "next/navigation";
+import { FaDiscord } from "react-icons/fa6";
+import { ExportButton } from "./export-button";
+import { ThemeToggle } from "../theme-toggle";
+import { DEFAULT_LOGO_URL, SOCIAL_LINKS } from "@/constants/site-constants";
+import { toast } from "sonner";
+import { useEditor } from "@/hooks/use-editor";
+import { CommandIcon, Logout05Icon } from "@hugeicons/core-free-icons";
+import { HugeiconsIcon } from "@hugeicons/react";
+import { ShortcutsDialog } from "./dialogs/shortcuts-dialog";
+import Image from "next/image";
+import { cn } from "@/utils/ui";
+
+export function EditorHeader() {
+ return (
+ <header className="bg-background flex h-[3.4rem] items-center justify-between px-3 pt-0.5">
+ <div className="flex items-center gap-1">
+ <ProjectDropdown />
+ <EditableProjectName />
+ </div>
+ <nav className="flex items-center gap-2">
+ <ExportButton />
+ <ThemeToggle />
+ </nav>
+ </header>
+ );
+}
+
+function ProjectDropdown() {
+ const [openDialog, setOpenDialog] = useState<
+ "delete" | "rename" | "shortcuts" | null
+ >(null);
+ const [isExiting, setIsExiting] = useState(false);
+ const router = useRouter();
+ const editor = useEditor();
+ const activeProject = useEditor((e) => e.project.getActive());
+
+ const handleExit = async () => {
+ if (isExiting) return;
+ setIsExiting(true);
+
+ try {
+ await editor.project.prepareExit();
+ editor.project.closeProject();
+ } catch (error) {
+ console.error("Failed to prepare project exit:", error);
+ } finally {
+ editor.project.closeProject();
+ router.push("/projects");
+ }
+ };
+
+ const handleSaveProjectName = async (newName: string) => {
+ if (
+ activeProject &&
+ newName.trim() &&
+ newName !== activeProject.metadata.name
+ ) {
+ try {
+ await editor.project.renameProject({
+ id: activeProject.metadata.id,
+ name: newName.trim(),
+ });
+ } catch (error) {
+ toast.error("Failed to rename project", {
+ description:
+ error instanceof Error ? error.message : "Please try again",
+ });
+ } finally {
+ setOpenDialog(null);
+ }
+ }
+ };
+
+ const handleDeleteProject = async () => {
+ if (activeProject) {
+ try {
+ await editor.project.deleteProjects({
+ ids: [activeProject.metadata.id],
+ });
+ router.push("/projects");
+ } catch (error) {
+ toast.error("Failed to delete project", {
+ description:
+ error instanceof Error ? error.message : "Please try again",
+ });
+ } finally {
+ setOpenDialog(null);
+ }
+ }
+ };
+
+ return (
+ <>
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="ghost" size="icon" className="p-1 rounded-sm size-8">
+ <Image
+ src={DEFAULT_LOGO_URL}
+ alt="Project thumbnail"
+ width={32}
+ height={32}
+ className="invert dark:invert-0 size-5"
+ />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="start" className="z-100 w-44">
+ <DropdownMenuItem
+ onClick={handleExit}
+ disabled={isExiting}
+ icon={<HugeiconsIcon icon={Logout05Icon} />}
+ >
+ Exit project
+ </DropdownMenuItem>
+
+ <DropdownMenuItem
+ onClick={() => setOpenDialog("shortcuts")}
+ icon={<HugeiconsIcon icon={CommandIcon} />}
+ >
+ Shortcuts
+ </DropdownMenuItem>
+
+ <DropdownMenuSeparator />
+
+ <DropdownMenuItem asChild icon={<FaDiscord className="size-4!" />}>
+ <Link
+ href={SOCIAL_LINKS.discord}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ Discord
+ </Link>
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ <RenameProjectDialog
+ isOpen={openDialog === "rename"}
+ onOpenChange={(isOpen) => setOpenDialog(isOpen ? "rename" : null)}
+ onConfirm={(newName) => handleSaveProjectName(newName)}
+ projectName={activeProject?.metadata.name || ""}
+ />
+ <DeleteProjectDialog
+ isOpen={openDialog === "delete"}
+ onOpenChange={(isOpen) => setOpenDialog(isOpen ? "delete" : null)}
+ onConfirm={handleDeleteProject}
+ projectNames={[activeProject?.metadata.name || ""]}
+ />
+ <ShortcutsDialog
+ isOpen={openDialog === "shortcuts"}
+ onOpenChange={(isOpen) => setOpenDialog(isOpen ? "shortcuts" : null)}
+ />
+ </>
+ );
+}
+
+function EditableProjectName() {
+ const editor = useEditor();
+ const activeProject = useEditor((e) => e.project.getActive());
+ const [isEditing, setIsEditing] = useState(false);
+ const inputRef = useRef<HTMLInputElement>(null);
+ const originalNameRef = useRef("");
+
+ const projectName = activeProject?.metadata.name || "";
+
+ const startEditing = () => {
+ if (isEditing) return;
+ originalNameRef.current = projectName;
+ setIsEditing(true);
+
+ requestAnimationFrame(() => {
+ inputRef.current?.select();
+ });
+ };
+
+ const saveEdit = async () => {
+ if (!inputRef.current || !activeProject) return;
+ const newName = inputRef.current.value.trim();
+ setIsEditing(false);
+
+ if (!newName) {
+ inputRef.current.value = originalNameRef.current;
+ return;
+ }
+
+ if (newName !== originalNameRef.current) {
+ try {
+ await editor.project.renameProject({
+ id: activeProject.metadata.id,
+ name: newName,
+ });
+ } catch (error) {
+ toast.error("Failed to rename project", {
+ description:
+ error instanceof Error ? error.message : "Please try again",
+ });
+ }
+ }
+ };
+
+ const handleKeyDown = (event: React.KeyboardEvent) => {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ inputRef.current?.blur();
+ } else if (event.key === "Escape") {
+ event.preventDefault();
+ if (inputRef.current) {
+ inputRef.current.value = originalNameRef.current;
+ inputRef.current.setSelectionRange(0, 0);
+ }
+ setIsEditing(false);
+ inputRef.current?.blur();
+ }
+ };
+
+ return (
+ <input
+ ref={inputRef}
+ type="text"
+ defaultValue={projectName}
+ readOnly={!isEditing}
+ onClick={startEditing}
+ onBlur={saveEdit}
+ onKeyDown={handleKeyDown}
+ style={{ fieldSizing: "content" }}
+ className={cn(
+ "text-[0.9rem] h-8 px-2 py-1 rounded-sm bg-transparent outline-none cursor-pointer hover:bg-accent hover:text-accent-foreground",
+ isEditing && "ring-1 ring-ring cursor-text hover:bg-transparent",
+ )}
+ />
+ );
+}
|
