diff --git a/src/app/admin/studio/studio-layout-client.tsx b/src/app/admin/studio/studio-layout-client.tsx index ef6183cc..e9ce8fe1 100644 --- a/src/app/admin/studio/studio-layout-client.tsx +++ b/src/app/admin/studio/studio-layout-client.tsx @@ -7,7 +7,6 @@ import { ClipboardCheck, Copy, Dumbbell, - FolderTree, Image as ImageIcon, type LucideIcon, Puzzle, @@ -19,7 +18,7 @@ import { import { usePathname } from "next/navigation"; import { useState } from "react"; import { CatalogManagerDialog } from "@/app/admin/catalog/catalog-manager-dialog-wrapper"; -import { OrganizeImportsDialog } from "@/components/admin/studio/organize-imports-dialog"; +import { OrganizeImportsLauncher } from "@/components/admin/studio/organize-imports-launcher"; import Link from "@/components/link"; import { Popover, @@ -115,7 +114,6 @@ export function StudioLayoutClient({ children: React.ReactNode; }) { const pathname = usePathname() ?? ""; - const [organizeOpen, setOrganizeOpen] = useState(false); return (
{children}
- {canEditCatalog && ( - - )}
); } diff --git a/src/components/admin/studio/organize-imports-launcher.tsx b/src/components/admin/studio/organize-imports-launcher.tsx new file mode 100644 index 00000000..9855796f --- /dev/null +++ b/src/components/admin/studio/organize-imports-launcher.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { FolderTree } from "lucide-react"; +import { lazy, Suspense, useState } from "react"; +import { StudioPanelLoading } from "./studio-panel-loading"; + +const OrganizeImportsDialog = lazy(() => + import("./organize-imports-dialog").then((module) => ({ + default: module.OrganizeImportsDialog, + })), +); + +export function OrganizeImportsLauncher() { + const [open, setOpen] = useState(false); + const [requested, setRequested] = useState(false); + return ( + <> + + {requested && ( + setOpen(false)} /> : null + } + > + + + )} + + ); +} diff --git a/src/lib/studio-bundle.test.ts b/src/lib/studio-bundle.test.ts index b82c7dbc..17d02e28 100644 --- a/src/lib/studio-bundle.test.ts +++ b/src/lib/studio-bundle.test.ts @@ -2,8 +2,7 @@ import path from "node:path"; import { build } from "esbuild"; import { expect, it } from "vitest"; -it("keeps scale preview and generation outside the initial Nitro editor bundle", async () => { - const entry = "src/app/admin/import/furni/nitro-editor-dialog.tsx"; +async function bundleInputs(entry: string) { const result = await build({ entryPoints: [entry], bundle: true, @@ -14,6 +13,17 @@ it("keeps scale preview and generation outside the initial Nitro editor bundle", metafile: true, packages: "external", alias: { "@": path.resolve("src") }, + plugins: [ + { + name: "server-action-boundary", + setup(builder) { + builder.onResolve({ filter: /^@\/actions\// }, ({ path }) => ({ + path, + external: true, + })); + }, + }, + ], }); const outputs = result.metafile.outputs; const start = Object.keys(outputs).find( @@ -34,13 +44,30 @@ it("keeps scale preview and generation outside the initial Nitro editor bundle", const initialInputs = new Set( [...seen].flatMap((key) => Object.keys(outputs[key].inputs)), ); + return { initialInputs, inputs: Object.keys(result.metafile.inputs) }; +} + +it("keeps scale preview and generation outside the initial Nitro editor bundle", async () => { + const { initialInputs, inputs } = await bundleInputs( + "src/app/admin/import/furni/nitro-editor-dialog.tsx", + ); for (const name of [ "nitro-quality-panel.tsx", "nitro-scale32-generator.tsx", ]) { - expect( - Object.keys(result.metafile.inputs).some((input) => input.endsWith(name)), - ).toBe(true); + expect(inputs.some((input) => input.endsWith(name))).toBe(true); + expect([...initialInputs].some((input) => input.endsWith(name))).toBe( + false, + ); + } +}, 30_000); + +it("loads import organization and its icon picker only when requested in Studio", async () => { + const { initialInputs, inputs } = await bundleInputs( + "src/app/admin/studio/studio-layout-client.tsx", + ); + for (const name of ["organize-imports-dialog.tsx", "icon-picker.tsx"]) { + expect(inputs.some((input) => input.endsWith(name))).toBe(true); expect([...initialInputs].some((input) => input.endsWith(name))).toBe( false, );