import type { MudletMap, MudletRoom } from '../mapIO'; import type { Command, HitItem, HoverTarget, LoadedMap, Pending, Selection, SwatchSet, ToolId } from './types'; import type { MapWarning } from './warnings'; import type { PathFindingAlgorithm, RouteSummary } from './pathfinding'; import type { IncomingRooms, PeerInfo } from './peers'; export type RoomClipboard = { /** Rooms captured at copy time; origId preserved for internal-exit remap. */ rooms: Array<{ origId: number; room: MudletRoom; }>; /** Centroid of source rooms in raw Mudlet space — paste offset is computed relative to this. */ origin: { x: number; y: number; z: number; }; }; interface UserSettings { snapToGrid: boolean; panelWidth: number; } export declare function saveUserSettings(patch: Partial): void; export declare function saveSwatchState(sets: SwatchSet[], activeSetId: string | null, activeSwatchId: string | null): void; export type SpreadShrinkState = { mode: 'spread' | 'shrink'; factor: number; centerMode: 'centroid' | 'anchor'; anchorRoomId: number | null; }; /** * Progress of an in-flight map load. Each phase is a long *synchronous* block on * the main thread (~0.5s parse, ~1s scene build for a 27k-room map), so the flag * is set — and the browser given a frame to paint it — before the block starts, * not during it. Cleared once the scene is on screen (see App). */ export type LoadingState = { phase: 'fetching' | 'parsing' | 'preparing'; fileName: string; /** Download completion 0–100. Only ever set while `fetching`, and only when the server sent a length. */ pct: number | null; }; /** * Latest level-of-detail tier the renderer reported for the displayed plane * (mirror of its `lod` event). `null` while no map/plane is drawn — a plane the * renderer skips as empty emits nothing, so scene.setArea clears this first. */ export type LodState = { /** 'vector' = full detail, 'roomsOnly' = exit lines dropped, 'raster' = pixel overview. */ mode: 'vector' | 'roomsOnly' | 'raster'; planeRoomCount: number; visibleEstimate: number; /** False when pointer picking is unavailable — tools that need a hit bail out. */ hitTestActive: boolean; }; /** Route-finder (speedwalk) panel state. `summary.path` is what RouteEffect draws. */ export type RouteState = { fromId: number | null; toId: number | null; algorithm: PathFindingAlgorithm; summary: RouteSummary | null; status: 'idle' | 'found' | 'noPath' | 'sameRoom' | 'missing'; }; export interface EditorState { map: MudletMap | null; loaded: LoadedMap | null; /** Id of the format the current map was loaded with / will be saved as by default. * Resolved against the format registry (see editor/formats.ts). */ formatId: string; currentAreaId: number | null; currentZ: number; activeTool: ToolId; selection: Selection; hover: HoverTarget; pending: Pending; snapToGrid: boolean; gridStep: number; /** When true (Space held), pointer input defers to the renderer's pan regardless of active tool. */ spaceHeld: boolean; undo: Command[]; redo: Command[]; status: string | null; /** Bumped on structural changes (room added/removed, area/z changed) that need a full rebuild. */ structureVersion: number; /** Bumped on every mutation (coord, exits, props) to trigger React re-renders of panels. */ dataVersion: number; /** Snapped cursor position in render-space, tracked by tools that show a snap indicator. */ snapCursor: { x: number; y: number; } | null; /** Last pointer position over the map in render-space (y-down), updated on every pointermove. */ cursorMap: { x: number; y: number; } | null; /** In-memory clipboard of copied rooms. Not persisted across reloads. */ clipboard: RoomClipboard | null; sidebarTab: string; panelCollapsed: boolean; panelExpanded: boolean; /** Side panel width in pixels (persisted). Ignored when the panel is collapsed or modal-expanded. */ panelWidth: number; contextMenu: ContextMenuState; savedUndoLength: number; /** When set, the next area/z navigation pans to this map-space point instead of fitting. Consumed and cleared by App. */ navigateTo: { mapX: number; mapY: number; } | null; /** When set, App pans to this map-space point without changing area/z. Consumed and cleared by App. */ panRequest: { mapX: number; mapY: number; } | null; /** Tracks the last Alt+click position (integer cell) and cycle index for overlapping-element cycling. */ hitCycle: { x: number; y: number; index: number; } | null; /** When true, label resize preserves the aspect ratio at the start of the drag. */ labelAspectRatioLocked: boolean; swatchSets: SwatchSet[]; pluginSwatchSets: SwatchSet[]; activeSwatchSetId: string | null; activeSwatchId: string | null; swatchPaletteOpen: boolean; sessionId: string | null; spreadShrink: SpreadShrinkState | null; /** Route-finder panel + the path RouteEffect renders. */ route: RouteState; warningAckVersion: number; /** Cached map warnings; recomputed in App after each command lands. */ warnings: MapWarning[]; /** Other same-origin editor tabs with a map open — cross-tab copy targets. */ peers: PeerInfo[]; /** Rooms sent from another tab, shown in a banner until placed or dismissed. */ incomingRooms: IncomingRooms | null; /** Renderer LOD tier for the displayed plane — drives the overview badge and tool guards. */ lod: LodState | null; /** In-flight map load, or null. Drives MapLoadingOverlay. */ loading: LoadingState | null; } export type ContextMenuState = { kind: 'customLinePoint'; roomId: number; exitName: string; pointIndex: number; screenX: number; screenY: number; } | { kind: 'room'; roomId: number; screenX: number; screenY: number; } | { kind: 'disambiguate'; hits: HitItem[]; screenX: number; screenY: number; } | { kind: 'label'; areaId: number; labelId: number; screenX: number; screenY: number; } | null; type Listener = (state: EditorState) => void; declare class Store { private state; private listeners; getState: () => EditorState; setState: (patch: Partial | ((s: EditorState) => Partial)) => void; subscribe: (listener: Listener) => (() => void); bumpData: () => void; bumpStructure: () => void; bumpAckVersion: () => void; } export declare const store: Store; export declare function useEditorState(selector: (s: EditorState) => T): T; export {};