* sync: port 2D editing features from monorepo (v0.3.0) ## New Features ### Floorplan Panel (7.5K LOC) - Full 2D editing interface with wall drawing, measurement, and unit display - Interactive floorplan view with pan/zoom, grid snapping - Wall thickness visualization, door/window placement - Zone and slab polygon editing - Guide image overlay support - Metric/imperial unit toggle ### Command Palette Overhaul - Complete rewrite with new command registry system - Keyboard shortcuts UI with ShortcutToken component - Editor-specific commands (floorplan, measurements, camera focus) - Improved search and action organization ### Wall Measurements - Real-time wall length labels in 3D view - Metric/imperial conversion - Wall measurement UI component ### Enhanced Tools - Wall drafting utilities (grid snapping, validation) - Node action menu for quick operations - Improved polygon editing for zones/slabs/sites - Roof segment panel for granular roof control ## Package Changes ### @pascal-app/core@0.3.0 - New wall-footprint.ts: 2D wall footprint calculation - Wall mitering exports for floorplan view - camera-controls:focus event - Space detection undo pause/resume - Mark sibling nodes dirty on deletion (miter recalc) ### @pascal-app/viewer@0.3.0 - ErrorBoundary component for robust item rendering - Broken item fallback UI - Wall renderer: mark dirty on mount - Ground occluder: only lowest level punches through - Unit state (metric/imperial) in viewer store ### @pascal-app/editor@0.1.0 - 48 file changes (16 new, 32 modified) - New stores: useCommandRegistry, usePaletteViewRegistry - Tree node drag-and-drop system - Level selection utilities - Enhanced scene graph operations ## Apps/Editor - GeistPixelSquare font for pixel-perfect UI - Blueprint icon asset - Updated layout and globals for font support ## Security - Zero AI imports or internal package refs - All monorepo-specific code excluded - Clean audit: no API keys or secrets ## Testing - Security audit passed ✅ - All AI/internal code excluded ✅ - Version bumps applied ✅ 79 files modified, 16 files added ~2600 insertions, ~1400 deletions * fix: lint cleanup — suppress intentional dep warnings, fix missing dep, remove stale ignores - Add biome-ignore for 3 intentional useEffect reset patterns (levelId, selectedGuide, selectedId) - Fix actual missing dependency: currentBuildingId in handleSiteEditShortcutSelect callback - Remove 3 stale biome-ignore comments in r3f.d.ts (rule not active) Build and lint pass clean. * fix: type errors — polygon area guards, selection cast, readonly keywords, door guard - ceiling-panel, slab-panel, ceiling-tree-node, slab-tree-node, zone-tree-node: guard polygon[i]/polygon[j] array access before arithmetic (TS2532) - scene.ts: introduce toViewerSelection() helper to cast persisted string IDs to branded template literal types expected by useViewer.setSelection (TS2345) - door-panel: guard early return when node is undefined in setSegmentHeightRatio (TS18048) - editor-commands: remove 'as const' from inline command object, keywords is mutable string[] (TS2322) All type checks pass. Build and lint clean. --------- Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
114 lines
2.9 KiB
TypeScript
114 lines
2.9 KiB
TypeScript
import type { ThreeEvent } from '@react-three/fiber'
|
|
import mitt from 'mitt'
|
|
import type {
|
|
BuildingNode,
|
|
CeilingNode,
|
|
DoorNode,
|
|
ItemNode,
|
|
LevelNode,
|
|
RoofNode,
|
|
RoofSegmentNode,
|
|
SiteNode,
|
|
SlabNode,
|
|
WallNode,
|
|
WindowNode,
|
|
ZoneNode,
|
|
} from '../schema'
|
|
import type { AnyNode } from '../schema/types'
|
|
|
|
// Base event interfaces
|
|
export interface GridEvent {
|
|
position: [number, number, number]
|
|
nativeEvent: ThreeEvent<PointerEvent>
|
|
}
|
|
|
|
export interface NodeEvent<T extends AnyNode = AnyNode> {
|
|
node: T
|
|
position: [number, number, number]
|
|
localPosition: [number, number, number]
|
|
normal?: [number, number, number]
|
|
stopPropagation: () => void
|
|
nativeEvent: ThreeEvent<PointerEvent>
|
|
}
|
|
|
|
export type WallEvent = NodeEvent<WallNode>
|
|
export type ItemEvent = NodeEvent<ItemNode>
|
|
export type SiteEvent = NodeEvent<SiteNode>
|
|
export type BuildingEvent = NodeEvent<BuildingNode>
|
|
export type LevelEvent = NodeEvent<LevelNode>
|
|
export type ZoneEvent = NodeEvent<ZoneNode>
|
|
export type SlabEvent = NodeEvent<SlabNode>
|
|
export type CeilingEvent = NodeEvent<CeilingNode>
|
|
export type RoofEvent = NodeEvent<RoofNode>
|
|
export type RoofSegmentEvent = NodeEvent<RoofSegmentNode>
|
|
export type WindowEvent = NodeEvent<WindowNode>
|
|
export type DoorEvent = NodeEvent<DoorNode>
|
|
|
|
// Event suffixes - exported for use in hooks
|
|
export const eventSuffixes = [
|
|
'click',
|
|
'move',
|
|
'enter',
|
|
'leave',
|
|
'pointerdown',
|
|
'pointerup',
|
|
'context-menu',
|
|
'double-click',
|
|
] as const
|
|
|
|
export type EventSuffix = (typeof eventSuffixes)[number]
|
|
|
|
type NodeEvents<T extends string, E> = {
|
|
[K in `${T}:${EventSuffix}`]: E
|
|
}
|
|
|
|
type GridEvents = {
|
|
[K in `grid:${EventSuffix}`]: GridEvent
|
|
}
|
|
|
|
export interface CameraControlEvent {
|
|
nodeId: AnyNode['id']
|
|
}
|
|
|
|
export interface ThumbnailGenerateEvent {
|
|
projectId: string
|
|
}
|
|
|
|
type CameraControlEvents = {
|
|
'camera-controls:view': CameraControlEvent
|
|
'camera-controls:focus': CameraControlEvent
|
|
'camera-controls:capture': CameraControlEvent
|
|
'camera-controls:top-view': undefined
|
|
'camera-controls:orbit-cw': undefined
|
|
'camera-controls:orbit-ccw': undefined
|
|
'camera-controls:generate-thumbnail': ThumbnailGenerateEvent
|
|
}
|
|
|
|
type ToolEvents = {
|
|
'tool:cancel': undefined
|
|
}
|
|
|
|
type PresetEvents = {
|
|
'preset:generate-thumbnail': { presetId: string; nodeId: string }
|
|
'preset:thumbnail-updated': { presetId: string; thumbnailUrl: string }
|
|
}
|
|
|
|
type EditorEvents = GridEvents &
|
|
NodeEvents<'wall', WallEvent> &
|
|
NodeEvents<'item', ItemEvent> &
|
|
NodeEvents<'site', SiteEvent> &
|
|
NodeEvents<'building', BuildingEvent> &
|
|
NodeEvents<'level', LevelEvent> &
|
|
NodeEvents<'zone', ZoneEvent> &
|
|
NodeEvents<'slab', SlabEvent> &
|
|
NodeEvents<'ceiling', CeilingEvent> &
|
|
NodeEvents<'roof', RoofEvent> &
|
|
NodeEvents<'roof-segment', RoofSegmentEvent> &
|
|
NodeEvents<'window', WindowEvent> &
|
|
NodeEvents<'door', DoorEvent> &
|
|
CameraControlEvents &
|
|
ToolEvents &
|
|
PresetEvents
|
|
|
|
export const emitter = mitt<EditorEvents>()
|