Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,612 changes: 15 additions & 2,597 deletions apps/mapgen-studio/src/App.tsx

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions apps/mapgen-studio/src/app/CanvasStage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { Layer } from "@deck.gl/core";
import type { MutableRefObject } from "react";

import { DeckCanvas, type DeckCanvasApi } from "../features/viz/DeckCanvas";
import type { Bounds, VizLayerEntryV1 } from "../features/viz/model";

export type CanvasStageProps = {
apiRef: MutableRefObject<DeckCanvasApi | null>;
onApiReady: () => void;
layers: Layer[];
effectiveLayer: VizLayerEntryV1 | null;
viewportSize: { width: number; height: number };
activeBounds: Bounds | null;
lightMode: boolean;
/** Whether the decorative background grid is rendered behind the canvas. */
backgroundGridEnabled: boolean;
/** True once a viz manifest has been produced; gates the empty-state hint. */
hasManifest: boolean;
};

/**
* `CanvasStage` — the full-bleed deck.gl host (architecture/10 §4).
*
* Purely presentational: it owns the theme-tinted backdrop, the optional
* decorative grid, the `DeckCanvas` mount, and the "Click Run to generate a map"
* empty state. All rendering inputs (layers, effective layer, viewport, bounds,
* deck api ref/ready handler) are passed in by `StudioShell`. This block was
* previously the inline `canvas` JSX inside `AppContent`; it is MOVED here
* verbatim so the DOM, classes, and Deck.gl inputs are unchanged — recoloring the
* hard-coded backdrop hexes is deferred to the design-craft slice, not this
* behavior-preserving decomposition.
*/
export function CanvasStage(props: CanvasStageProps) {
const {
apiRef,
onApiReady,
layers,
effectiveLayer,
viewportSize,
activeBounds,
lightMode,
backgroundGridEnabled,
hasManifest,
} = props;

return (
<div className="absolute inset-0">
<div className={`absolute inset-0 ${lightMode ? "bg-[#f5f5f7]" : "bg-[#0a0a12]"}`} />
{/* Theme-tinted backdrop (kept outside deck.gl so it remains stable and cheap) */}
<div
className="absolute inset-0 opacity-30 pointer-events-none"
style={{
backgroundImage: lightMode
? `
radial-gradient(circle at 30% 40%, #cbd5e0 0%, transparent 55%),
radial-gradient(circle at 70% 60%, #cbd5e0 0%, transparent 45%),
radial-gradient(circle at 50% 80%, #cbd5e0 0%, transparent 35%)
`
: `
radial-gradient(circle at 30% 40%, #2d3748 0%, transparent 55%),
radial-gradient(circle at 70% 60%, #2d3748 0%, transparent 45%),
radial-gradient(circle at 50% 80%, #2d3748 0%, transparent 35%)
`,
}}
/>
{backgroundGridEnabled ? (
<div
className="absolute inset-0 pointer-events-none"
style={{
backgroundImage: `
linear-gradient(${lightMode ? "rgba(0,0,0,0.05)" : "rgba(255,255,255,0.02)"} 1px, transparent 1px),
linear-gradient(90deg, ${lightMode ? "rgba(0,0,0,0.05)" : "rgba(255,255,255,0.02)"} 1px, transparent 1px)
`,
backgroundSize: "56px 56px",
}}
/>
) : null}
<DeckCanvas
apiRef={apiRef}
onApiReady={onApiReady}
layers={layers}
effectiveLayer={effectiveLayer}
viewportSize={viewportSize}
showBackgroundGrid={false}
lightMode={lightMode}
activeBounds={activeBounds}
/>
{!hasManifest ? (
<div className="absolute inset-0 flex items-center justify-center text-[12px] text-[#7a7a8c]">
Click Run to generate a map
</div>
) : null}
</div>
);
}
25 changes: 25 additions & 0 deletions apps/mapgen-studio/src/app/ErrorBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export type ErrorBannerProps = {
/** The error message to surface, or null/undefined to render nothing. */
message: string | null | undefined;
/** Top offset (px) so the banner clears the floating header. */
top: number;
};

/**
* `ErrorBanner` — the centered, dismissable-by-resolution error chrome
* (architecture/10 §4). Purely presentational: it renders the destructive-toned
* banner only when a message is present. Previously an inline conditional in
* `AppContent`'s return; MOVED here verbatim (same classes, same token-driven
* destructive styling) so the rendered DOM is unchanged.
*/
export function ErrorBanner({ message, top }: ErrorBannerProps) {
if (!message) return null;
return (
<div
className="absolute left-1/2 -translate-x-1/2 z-30 max-w-[min(720px,calc(100%-32px))] rounded-lg border border-destructive/40 bg-destructive/15 px-4 py-2 text-xs text-destructive backdrop-blur-sm"
style={{ top }}
>
{message}
</div>
);
}
23 changes: 23 additions & 0 deletions apps/mapgen-studio/src/app/LeftDock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { ReactNode } from "react";

export type LeftDockProps = {
/** Top offset (px) so the dock clears the floating header. */
top: number;
/** The recipe/config authoring panel rendered inside the dock. */
children: ReactNode;
};

/**
* `LeftDock` — the left-anchored floating dock that hosts the recipe authoring
* panel (architecture/10 §4). It owns only the absolute positioning + z-index of
* the left rail; the panel content is composed in by `StudioShell`. Extracting the
* positioning frame from the authoring closure keeps the shell layout declarative
* without changing the rendered DOM (same `absolute left-4 z-10` placement).
*/
export function LeftDock({ top, children }: LeftDockProps) {
return (
<div className="absolute left-4 z-10" style={{ top }}>
{children}
</div>
);
}
23 changes: 23 additions & 0 deletions apps/mapgen-studio/src/app/RightDock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { ReactNode } from "react";

export type RightDockProps = {
/** Top offset (px) so the dock clears the floating header. */
top: number;
/** The explore/inspection panel rendered inside the dock. */
children: ReactNode;
};

/**
* `RightDock` — the right-anchored floating dock that hosts the explore panel
* (architecture/10 §4). It owns only the absolute positioning + z-index of the
* right rail; the panel content is composed in by `StudioShell`. Extracting the
* positioning frame keeps the shell layout declarative without changing the
* rendered DOM (same `absolute right-4 z-10` placement).
*/
export function RightDock({ top, children }: RightDockProps) {
return (
<div className="absolute right-4 z-10" style={{ top }}>
{children}
</div>
);
}
28 changes: 28 additions & 0 deletions apps/mapgen-studio/src/app/StudioProviders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Toaster, TooltipProvider } from "../components/ui";
import { useThemePreference } from "../ui/hooks";

import { StudioShell } from "./StudioShell";

/**
* `StudioProviders` — the provider shell (architecture/10 §4). It owns the
* cross-cutting UI providers (tooltip portal + toast surface) and the theme
* preference wiring, then renders `StudioShell` with the resolved theme.
*
* This is the body of the former `App` wrapper, MOVED here unchanged. The
* `QueryClientProvider` is intentionally NOT owned here — it lives at the module
* root in `main.tsx` (client-data slice) so the query cache survives the
* dev-only StrictMode skip. App.tsx now reduces to re-exporting this shell.
*/
export function StudioProviders() {
const { preference, isLightMode, cyclePreference } = useThemePreference();
return (
<TooltipProvider delayDuration={300}>
<StudioShell
themePreference={preference}
isLightMode={isLightMode}
cyclePreference={cyclePreference}
/>
<Toaster />
</TooltipProvider>
);
}
Loading