From a5713f73d810b96fae9f1f9e742d86fa1112d2ec Mon Sep 17 00:00:00 2001 From: Crauzer <0xcrauzer@proton.me> Date: Sun, 22 Feb 2026 19:53:13 +0100 Subject: [PATCH] refactor: workshop project page --- src/modules/workshop/api/index.ts | 1 + src/modules/workshop/api/useProjectActions.ts | 86 ++++++++++ .../workshop/components/ProjectHeader.tsx | 75 +++++++++ src/modules/workshop/components/index.ts | 1 + src/routes/workshop/$projectName.tsx | 157 +++--------------- 5 files changed, 185 insertions(+), 135 deletions(-) create mode 100644 src/modules/workshop/api/useProjectActions.ts create mode 100644 src/modules/workshop/components/ProjectHeader.tsx diff --git a/src/modules/workshop/api/index.ts b/src/modules/workshop/api/index.ts index b3ac1e8..0f8da55 100644 --- a/src/modules/workshop/api/index.ts +++ b/src/modules/workshop/api/index.ts @@ -3,6 +3,7 @@ export { useCreateProject } from "./useCreateProject"; export { useDeleteProject } from "./useDeleteProject"; export { useImportFromModpkg } from "./useImportFromModpkg"; export { usePackProject } from "./usePackProject"; +export { useProjectActions } from "./useProjectActions"; export { projectThumbnailOptions, useProjectThumbnail } from "./useProjectThumbnail"; export { useRenameProject } from "./useRenameProject"; export { useSaveProjectConfig } from "./useSaveProjectConfig"; diff --git a/src/modules/workshop/api/useProjectActions.ts b/src/modules/workshop/api/useProjectActions.ts new file mode 100644 index 0000000..f802914 --- /dev/null +++ b/src/modules/workshop/api/useProjectActions.ts @@ -0,0 +1,86 @@ +import type { NavigateFn } from "@tanstack/react-router"; +import { useState } from "react"; + +import { api, type PackResult, type WorkshopProject } from "@/lib/tauri"; + +import { useDeleteProject } from "./useDeleteProject"; +import { usePackProject } from "./usePackProject"; +import { useValidateProject } from "./useValidateProject"; + +export function useProjectActions(project: WorkshopProject | undefined, navigate: NavigateFn) { + const [packDialogOpen, setPackDialogOpen] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [packResult, setPackResult] = useState(null); + + const deleteProject = useDeleteProject(); + const packProject = usePackProject(); + const { data: validation, isLoading: validationLoading } = useValidateProject( + project?.path ?? "", + packDialogOpen, + ); + + function handlePack(format: "modpkg" | "fantome") { + if (!project) return; + packProject.mutate( + { projectPath: project.path, format }, + { + onSuccess: setPackResult, + onError: (err) => console.error("Failed to pack project:", err.message), + }, + ); + } + + function handleOpenPackDialog() { + setPackResult(null); + setPackDialogOpen(true); + } + + function handleClosePackDialog() { + setPackDialogOpen(false); + setPackResult(null); + } + + function handleDeleteProject() { + if (!project) return; + deleteProject.mutate(project.path, { + onSuccess: () => { + navigate({ to: "/workshop" }); + }, + onError: (err) => console.error("Failed to delete project:", err.message), + }); + } + + function openDeleteDialog() { + setDeleteDialogOpen(true); + } + + function closeDeleteDialog() { + setDeleteDialogOpen(false); + } + + async function handleOpenLocation() { + if (!project) return; + try { + await api.revealInExplorer(project.path); + } catch (error) { + console.error("Failed to open location:", error); + } + } + + return { + packDialogOpen, + packResult, + deleteDialogOpen, + validation: validation ?? null, + validationLoading, + isPacking: packProject.isPending, + isDeleting: deleteProject.isPending, + handlePack, + handleOpenPackDialog, + handleClosePackDialog, + handleDeleteProject, + handleOpenLocation, + openDeleteDialog, + closeDeleteDialog, + }; +} diff --git a/src/modules/workshop/components/ProjectHeader.tsx b/src/modules/workshop/components/ProjectHeader.tsx new file mode 100644 index 0000000..4459627 --- /dev/null +++ b/src/modules/workshop/components/ProjectHeader.tsx @@ -0,0 +1,75 @@ +import { Link } from "@tanstack/react-router"; +import { LuArrowLeft, LuEllipsisVertical, LuFolderOpen, LuPackage, LuTrash2 } from "react-icons/lu"; + +import { Button, IconButton, Menu } from "@/components"; +import type { WorkshopProject } from "@/lib/tauri"; + +interface ProjectHeaderProps { + project: WorkshopProject; + onPack: () => void; + onDelete: () => void; + onOpenLocation: () => void; +} + +export function ProjectHeader({ project, onPack, onDelete, onOpenLocation }: ProjectHeaderProps) { + return ( +
+ + } + variant="ghost" + size="sm" + aria-label="Back to Workshop" + /> + + +
+
+

{project.displayName}

+ + v{project.version} + +
+
+ +
+ + + } + variant="ghost" + size="sm" + /> + } + /> + + + + } onClick={onOpenLocation}> + Open Location + + + } + variant="danger" + onClick={onDelete} + > + Delete + + + + + +
+
+ ); +} diff --git a/src/modules/workshop/components/index.ts b/src/modules/workshop/components/index.ts index a27a85e..36ffa1c 100644 --- a/src/modules/workshop/components/index.ts +++ b/src/modules/workshop/components/index.ts @@ -11,5 +11,6 @@ export { PackDialog } from "./PackDialog"; export { ProjectCard } from "./ProjectCard"; export { ProjectProvider, useProjectContext } from "./ProjectContext"; export { ProjectGrid } from "./ProjectGrid"; +export { ProjectHeader } from "./ProjectHeader"; export type { ViewMode } from "./WorkshopToolbar"; export { WorkshopToolbar } from "./WorkshopToolbar"; diff --git a/src/routes/workshop/$projectName.tsx b/src/routes/workshop/$projectName.tsx index 1ff8017..e2e865d 100644 --- a/src/routes/workshop/$projectName.tsx +++ b/src/routes/workshop/$projectName.tsx @@ -1,18 +1,14 @@ import { createFileRoute, Link, Outlet, useNavigate } from "@tanstack/react-router"; -import { invoke } from "@tauri-apps/api/core"; -import { useState } from "react"; -import { LuArrowLeft, LuEllipsisVertical, LuFolderOpen, LuPackage, LuTrash2 } from "react-icons/lu"; +import { LuArrowLeft } from "react-icons/lu"; -import { Button, IconButton, Menu, NavTabs } from "@/components"; -import type { PackResult } from "@/lib/tauri"; +import { Button, NavTabs } from "@/components"; import { DeleteConfirmDialog, LoadingState, PackDialog, + ProjectHeader, ProjectProvider, - useDeleteProject, - usePackProject, - useValidateProject, + useProjectActions, useWorkshopProjects, } from "@/modules/workshop"; @@ -27,17 +23,7 @@ function ProjectDetailLayout() { const { data: projects, isLoading } = useWorkshopProjects(); const project = projects?.find((p) => p.name === projectName); - // Dialog state - const [packDialogOpen, setPackDialogOpen] = useState(false); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [packResult, setPackResult] = useState(null); - - const deleteProject = useDeleteProject(); - const packProject = usePackProject(); - const { data: validation, isLoading: validationLoading } = useValidateProject( - project?.path ?? "", - packDialogOpen, - ); + const actions = useProjectActions(project, navigate); if (isLoading) { return ; @@ -56,41 +42,6 @@ function ProjectDetailLayout() { ); } - function handlePack(format: "modpkg" | "fantome") { - if (!project) return; - packProject.mutate( - { projectPath: project.path, format }, - { - onSuccess: setPackResult, - onError: (err) => console.error("Failed to pack project:", err.message), - }, - ); - } - - function handleClosePackDialog() { - setPackDialogOpen(false); - setPackResult(null); - } - - function handleDeleteProject() { - if (!project) return; - deleteProject.mutate(project.path, { - onSuccess: () => { - navigate({ to: "/workshop" }); - }, - onError: (err) => console.error("Failed to delete project:", err.message), - }); - } - - async function handleOpenLocation() { - if (!project) return; - try { - await invoke("reveal_in_explorer", { path: project.path }); - } catch (error) { - console.error("Failed to open location:", error); - } - } - const tabs = [ { to: "/workshop/$projectName", params: { projectName }, label: "Overview", exact: true }, { to: "/workshop/$projectName/strings", params: { projectName }, label: "Strings" }, @@ -100,101 +51,37 @@ function ProjectDetailLayout() { return (
- {/* Header */} -
- - } - variant="ghost" - size="sm" - aria-label="Back to Workshop" - /> - - -
-
-

- {project.displayName} -

- - v{project.version} - -
-
- -
- - - } - variant="ghost" - size="sm" - /> - } - /> - - - - } - onClick={handleOpenLocation} - > - Open Location - - - } - variant="danger" - onClick={() => setDeleteDialogOpen(true)} - > - Delete - - - - - -
-
+ - {/* NavTabs */} - {/* Content */}
- {/* Dialogs */} setDeleteDialogOpen(false)} - onConfirm={handleDeleteProject} - isPending={deleteProject.isPending} + onClose={actions.closeDeleteDialog} + onConfirm={actions.handleDeleteProject} + isPending={actions.isDeleting} />
);