summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/editor-header.tsx
diff options
context:
space:
mode:
authorMaze <167211895+mazeincoding@users.noreply.github.com>2026-01-31 00:20:04 +0100
committerGitHub <noreply@github.com>2026-01-31 00:20:04 +0100
commit7bf0984698e43b9aa2dd879f65951318ed410390 (patch)
treeccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/components/editor/editor-header.tsx
parent0173db99448d1c4c6415dcf15272e17551b48b04 (diff)
downloadopencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/components/editor/editor-header.tsx')
-rw-r--r--apps/web/src/components/editor/editor-header.tsx279
1 files changed, 168 insertions, 111 deletions
diff --git a/apps/web/src/components/editor/editor-header.tsx b/apps/web/src/components/editor/editor-header.tsx
index 5363d0c7..d74d78d7 100644
--- a/apps/web/src/components/editor/editor-header.tsx
+++ b/apps/web/src/components/editor/editor-header.tsx
@@ -1,130 +1,187 @@
"use client";
import { Button } from "../ui/button";
-import { ChevronDown, ArrowLeft, SquarePen, Trash } from "lucide-react";
-import { HeaderBase } from "../header-base";
-import { useProjectStore } from "@/stores/project-store";
-import { KeyboardShortcutsHelp } from "../keyboard-shortcuts-help";
+import { ChevronDown } from "lucide-react";
import { useState } from "react";
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import Link from "next/link";
-import { RenameProjectDialog } from "../rename-project-dialog";
-import { DeleteProjectDialog } from "../delete-project-dialog";
+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 { PanelPresetSelector } from "./panel-preset-selector";
import { ExportButton } from "./export-button";
import { ThemeToggle } from "../theme-toggle";
+import { SOCIAL_LINKS } from "@/constants/site-constants";
+import { toast } from "sonner";
+import { useEditor } from "@/hooks/use-editor";
+import {
+ ArrowLeft02Icon,
+ Edit03Icon,
+ Delete02Icon,
+ CommandIcon,
+} from "@hugeicons/core-free-icons";
+import { HugeiconsIcon } from "@hugeicons/react";
+import { ShortcutsDialog } from "./dialogs/shortcuts-dialog";
export function EditorHeader() {
- const { activeProject, renameProject, deleteProject } = useProjectStore();
- const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
- const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);
- const router = useRouter();
+ return (
+ <header className="bg-background flex h-[3.2rem] items-center justify-between px-3 pt-0.5">
+ <div className="flex items-center gap-2">
+ <ProjectDropdown />
+ </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 = editor.project.getActive();
- const handleNameSave = async (newName: string) => {
- console.log("handleNameSave", newName);
- if (activeProject && newName.trim() && newName !== activeProject.name) {
- try {
- await renameProject(activeProject.id, newName.trim());
- setIsRenameDialogOpen(false);
- } catch (error) {
- console.error("Failed to rename project:", error);
- }
- }
- };
+ const handleExit = async () => {
+ if (isExiting) return;
+ setIsExiting(true);
- const handleDelete = () => {
- if (activeProject) {
- deleteProject(activeProject.id);
- setIsDeleteDialogOpen(false);
- router.push("/projects");
- }
- };
+ 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 leftContent = (
- <div className="flex items-center gap-2">
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button
- variant="secondary"
- className="h-auto py-1.5 px-2.5 flex items-center justify-center"
- >
- <ChevronDown className="text-muted-foreground" />
- <span className="text-[0.85rem] mr-2">{activeProject?.name}</span>
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="start" className="w-40 z-100">
- <Link href="/projects">
- <DropdownMenuItem className="flex items-center gap-1.5">
- <ArrowLeft className="h-4 w-4" />
- Projects
- </DropdownMenuItem>
- </Link>
- <DropdownMenuItem
- className="flex items-center gap-1.5"
- onClick={() => setIsRenameDialogOpen(true)}
- >
- <SquarePen className="h-4 w-4" />
- Rename project
- </DropdownMenuItem>
- <DropdownMenuItem
- variant="destructive"
- className="flex items-center gap-1.5"
- onClick={() => setIsDeleteDialogOpen(true)}
- >
- <Trash className="h-4 w-4" />
- Delete Project
- </DropdownMenuItem>
- <DropdownMenuSeparator />
- <DropdownMenuItem asChild>
- <Link
- href="https://discord.gg/zmR9N35cjK"
- target="_blank"
- rel="noopener noreferrer"
- className="flex items-center gap-1.5"
- >
- <FaDiscord className="h-4 w-4" />
- Discord
- </Link>
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- <RenameProjectDialog
- isOpen={isRenameDialogOpen}
- onOpenChange={setIsRenameDialogOpen}
- onConfirm={handleNameSave}
- projectName={activeProject?.name || ""}
- />
- <DeleteProjectDialog
- isOpen={isDeleteDialogOpen}
- onOpenChange={setIsDeleteDialogOpen}
- onConfirm={handleDelete}
- projectName={activeProject?.name || ""}
- />
- </div>
- );
+ 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 rightContent = (
- <nav className="flex items-center gap-2">
- <PanelPresetSelector />
- <KeyboardShortcutsHelp />
- <ExportButton />
- <ThemeToggle />
- </nav>
- );
+ 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 (
- <HeaderBase
- leftContent={leftContent}
- rightContent={rightContent}
- className="bg-background h-[3.2rem] px-3 items-center mt-0.5"
- />
- );
+ return (
+ <>
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button
+ variant="secondary"
+ className="flex h-auto items-center justify-center px-2.5 py-1.5"
+ >
+ <ChevronDown className="text-muted-foreground" />
+ <span className="mr-2 text-[0.85rem]">
+ {activeProject?.metadata.name}
+ </span>
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="start" className="z-100 w-52">
+ <DropdownMenuItem
+ className="flex items-center gap-1.5"
+ onClick={handleExit}
+ disabled={isExiting}
+ >
+ <HugeiconsIcon icon={ArrowLeft02Icon} className="size-4" />
+ Exit project
+ </DropdownMenuItem>
+ <DropdownMenuItem
+ className="flex items-center gap-1.5"
+ onClick={() => setOpenDialog("rename")}
+ >
+ <HugeiconsIcon icon={Edit03Icon} className="size-4" />
+ Rename project
+ </DropdownMenuItem>
+ <DropdownMenuItem
+ variant="destructive"
+ className="flex items-center gap-1.5"
+ onClick={() => setOpenDialog("delete")}
+ >
+ <HugeiconsIcon icon={Delete02Icon} className="size-4" />
+ Delete project
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
+ <DropdownMenuItem
+ className="flex items-center gap-1.5"
+ onClick={() => setOpenDialog("shortcuts")}
+ >
+ <HugeiconsIcon icon={CommandIcon} className="size-4" />
+ Keyboard shortcuts
+ </DropdownMenuItem>
+ <DropdownMenuItem asChild>
+ <Link
+ href={SOCIAL_LINKS.discord}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="flex items-center gap-1.5"
+ >
+ <FaDiscord className="size-4" />
+ 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)}
+ />
+ </>
+ );
}