@pascal-app/editor 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/package.json +62 -0
  2. package/src/components/editor/custom-camera-controls.tsx +387 -0
  3. package/src/components/editor/editor-layout-v2.tsx +220 -0
  4. package/src/components/editor/export-manager.tsx +78 -0
  5. package/src/components/editor/first-person-controls.tsx +249 -0
  6. package/src/components/editor/floating-action-menu.tsx +231 -0
  7. package/src/components/editor/floorplan-panel.tsx +9609 -0
  8. package/src/components/editor/grid.tsx +161 -0
  9. package/src/components/editor/index.tsx +928 -0
  10. package/src/components/editor/node-action-menu.tsx +66 -0
  11. package/src/components/editor/preset-thumbnail-generator.tsx +125 -0
  12. package/src/components/editor/selection-manager.tsx +897 -0
  13. package/src/components/editor/site-edge-labels.tsx +90 -0
  14. package/src/components/editor/thumbnail-generator.tsx +166 -0
  15. package/src/components/editor/wall-measurement-label.tsx +258 -0
  16. package/src/components/feedback-dialog.tsx +265 -0
  17. package/src/components/pascal-radio.tsx +280 -0
  18. package/src/components/preview-button.tsx +16 -0
  19. package/src/components/systems/ceiling/ceiling-system.tsx +77 -0
  20. package/src/components/systems/roof/roof-edit-system.tsx +69 -0
  21. package/src/components/systems/stair/stair-edit-system.tsx +69 -0
  22. package/src/components/systems/zone/zone-label-editor-system.tsx +320 -0
  23. package/src/components/systems/zone/zone-system.tsx +87 -0
  24. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +42 -0
  25. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +47 -0
  26. package/src/components/tools/ceiling/ceiling-tool.tsx +465 -0
  27. package/src/components/tools/door/door-math.ts +110 -0
  28. package/src/components/tools/door/door-tool.tsx +293 -0
  29. package/src/components/tools/door/move-door-tool.tsx +373 -0
  30. package/src/components/tools/item/item-tool.tsx +26 -0
  31. package/src/components/tools/item/move-tool.tsx +90 -0
  32. package/src/components/tools/item/placement-math.ts +85 -0
  33. package/src/components/tools/item/placement-strategies.ts +556 -0
  34. package/src/components/tools/item/placement-types.ts +117 -0
  35. package/src/components/tools/item/use-draft-node.ts +227 -0
  36. package/src/components/tools/item/use-placement-coordinator.tsx +877 -0
  37. package/src/components/tools/roof/move-roof-tool.tsx +288 -0
  38. package/src/components/tools/roof/roof-tool.tsx +318 -0
  39. package/src/components/tools/select/box-select-tool.tsx +626 -0
  40. package/src/components/tools/shared/cursor-sphere.tsx +119 -0
  41. package/src/components/tools/shared/polygon-editor.tsx +361 -0
  42. package/src/components/tools/site/site-boundary-editor.tsx +42 -0
  43. package/src/components/tools/slab/slab-boundary-editor.tsx +42 -0
  44. package/src/components/tools/slab/slab-hole-editor.tsx +47 -0
  45. package/src/components/tools/slab/slab-tool.tsx +322 -0
  46. package/src/components/tools/stair/stair-defaults.ts +7 -0
  47. package/src/components/tools/stair/stair-tool.tsx +194 -0
  48. package/src/components/tools/tool-manager.tsx +120 -0
  49. package/src/components/tools/wall/wall-drafting.ts +140 -0
  50. package/src/components/tools/wall/wall-tool.tsx +210 -0
  51. package/src/components/tools/window/move-window-tool.tsx +410 -0
  52. package/src/components/tools/window/window-math.ts +117 -0
  53. package/src/components/tools/window/window-tool.tsx +303 -0
  54. package/src/components/tools/zone/zone-boundary-editor.tsx +39 -0
  55. package/src/components/tools/zone/zone-tool.tsx +364 -0
  56. package/src/components/ui/action-menu/action-button.tsx +59 -0
  57. package/src/components/ui/action-menu/camera-actions.tsx +74 -0
  58. package/src/components/ui/action-menu/control-modes.tsx +240 -0
  59. package/src/components/ui/action-menu/furnish-tools.tsx +102 -0
  60. package/src/components/ui/action-menu/index.tsx +152 -0
  61. package/src/components/ui/action-menu/structure-tools.tsx +100 -0
  62. package/src/components/ui/action-menu/view-toggles.tsx +397 -0
  63. package/src/components/ui/command-palette/editor-commands.tsx +396 -0
  64. package/src/components/ui/command-palette/index.tsx +730 -0
  65. package/src/components/ui/controls/action-button.tsx +33 -0
  66. package/src/components/ui/controls/material-picker.tsx +194 -0
  67. package/src/components/ui/controls/metric-control.tsx +262 -0
  68. package/src/components/ui/controls/panel-section.tsx +65 -0
  69. package/src/components/ui/controls/segmented-control.tsx +45 -0
  70. package/src/components/ui/controls/slider-control.tsx +245 -0
  71. package/src/components/ui/controls/toggle-control.tsx +38 -0
  72. package/src/components/ui/floating-level-selector.tsx +355 -0
  73. package/src/components/ui/helpers/ceiling-helper.tsx +20 -0
  74. package/src/components/ui/helpers/helper-manager.tsx +33 -0
  75. package/src/components/ui/helpers/item-helper.tsx +40 -0
  76. package/src/components/ui/helpers/roof-helper.tsx +16 -0
  77. package/src/components/ui/helpers/slab-helper.tsx +20 -0
  78. package/src/components/ui/helpers/wall-helper.tsx +20 -0
  79. package/src/components/ui/item-catalog/catalog-items.tsx +1580 -0
  80. package/src/components/ui/item-catalog/item-catalog.tsx +219 -0
  81. package/src/components/ui/panels/ceiling-panel.tsx +230 -0
  82. package/src/components/ui/panels/collections/collections-popover.tsx +356 -0
  83. package/src/components/ui/panels/door-panel.tsx +600 -0
  84. package/src/components/ui/panels/item-panel.tsx +306 -0
  85. package/src/components/ui/panels/panel-manager.tsx +59 -0
  86. package/src/components/ui/panels/panel-wrapper.tsx +80 -0
  87. package/src/components/ui/panels/presets/presets-popover.tsx +511 -0
  88. package/src/components/ui/panels/reference-panel.tsx +177 -0
  89. package/src/components/ui/panels/roof-panel.tsx +262 -0
  90. package/src/components/ui/panels/roof-segment-panel.tsx +326 -0
  91. package/src/components/ui/panels/slab-panel.tsx +228 -0
  92. package/src/components/ui/panels/stair-panel.tsx +304 -0
  93. package/src/components/ui/panels/stair-segment-panel.tsx +339 -0
  94. package/src/components/ui/panels/wall-panel.tsx +123 -0
  95. package/src/components/ui/panels/window-panel.tsx +441 -0
  96. package/src/components/ui/primitives/button.tsx +69 -0
  97. package/src/components/ui/primitives/card.tsx +75 -0
  98. package/src/components/ui/primitives/color-dot.tsx +61 -0
  99. package/src/components/ui/primitives/context-menu.tsx +227 -0
  100. package/src/components/ui/primitives/dialog.tsx +129 -0
  101. package/src/components/ui/primitives/dropdown-menu.tsx +228 -0
  102. package/src/components/ui/primitives/error-boundary.tsx +52 -0
  103. package/src/components/ui/primitives/input.tsx +21 -0
  104. package/src/components/ui/primitives/number-input.tsx +187 -0
  105. package/src/components/ui/primitives/opacity-control.tsx +79 -0
  106. package/src/components/ui/primitives/popover.tsx +42 -0
  107. package/src/components/ui/primitives/separator.tsx +28 -0
  108. package/src/components/ui/primitives/sheet.tsx +130 -0
  109. package/src/components/ui/primitives/shortcut-token.tsx +64 -0
  110. package/src/components/ui/primitives/sidebar.tsx +855 -0
  111. package/src/components/ui/primitives/skeleton.tsx +13 -0
  112. package/src/components/ui/primitives/slider.tsx +58 -0
  113. package/src/components/ui/primitives/switch.tsx +29 -0
  114. package/src/components/ui/primitives/tooltip.tsx +57 -0
  115. package/src/components/ui/scene-loader.tsx +40 -0
  116. package/src/components/ui/sidebar/app-sidebar.tsx +103 -0
  117. package/src/components/ui/sidebar/icon-rail.tsx +147 -0
  118. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +100 -0
  119. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +438 -0
  120. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +188 -0
  121. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +80 -0
  122. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +126 -0
  123. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +64 -0
  124. package/src/components/ui/sidebar/panels/site-panel/index.tsx +1543 -0
  125. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +98 -0
  126. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +117 -0
  127. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +65 -0
  128. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +214 -0
  129. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +96 -0
  130. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +216 -0
  131. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +115 -0
  132. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +342 -0
  133. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +271 -0
  134. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +106 -0
  135. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +64 -0
  136. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +87 -0
  137. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +167 -0
  138. package/src/components/ui/sidebar/tab-bar.tsx +39 -0
  139. package/src/components/ui/slider-demo.tsx +36 -0
  140. package/src/components/ui/slider.tsx +81 -0
  141. package/src/components/ui/viewer-toolbar.tsx +342 -0
  142. package/src/components/viewer-overlay.tsx +499 -0
  143. package/src/components/viewer-zone-system.tsx +48 -0
  144. package/src/contexts/presets-context.tsx +121 -0
  145. package/src/hooks/use-auto-save.ts +194 -0
  146. package/src/hooks/use-contextual-tools.ts +52 -0
  147. package/src/hooks/use-grid-events.ts +106 -0
  148. package/src/hooks/use-keyboard.ts +214 -0
  149. package/src/hooks/use-mobile.ts +19 -0
  150. package/src/hooks/use-reduced-motion.ts +20 -0
  151. package/src/index.tsx +33 -0
  152. package/src/lib/constants.ts +3 -0
  153. package/src/lib/level-selection.ts +31 -0
  154. package/src/lib/scene.ts +394 -0
  155. package/src/lib/sfx/index.ts +2 -0
  156. package/src/lib/sfx-bus.ts +49 -0
  157. package/src/lib/sfx-player.ts +60 -0
  158. package/src/lib/utils.ts +43 -0
  159. package/src/store/use-audio.tsx +45 -0
  160. package/src/store/use-command-registry.ts +36 -0
  161. package/src/store/use-editor.tsx +522 -0
  162. package/src/store/use-palette-view-registry.ts +45 -0
  163. package/src/store/use-upload.ts +90 -0
  164. package/src/three-types.ts +3 -0
  165. package/tsconfig.json +9 -0
@@ -0,0 +1,66 @@
1
+ 'use client'
2
+
3
+ import { Copy, Move, Trash2 } from 'lucide-react'
4
+ import type { MouseEventHandler, PointerEventHandler } from 'react'
5
+
6
+ type NodeActionMenuProps = {
7
+ onDelete: MouseEventHandler<HTMLButtonElement>
8
+ onDuplicate?: MouseEventHandler<HTMLButtonElement>
9
+ onMove?: MouseEventHandler<HTMLButtonElement>
10
+ onPointerDown?: PointerEventHandler<HTMLDivElement>
11
+ onPointerUp?: PointerEventHandler<HTMLDivElement>
12
+ onPointerEnter?: PointerEventHandler<HTMLDivElement>
13
+ onPointerLeave?: PointerEventHandler<HTMLDivElement>
14
+ }
15
+
16
+ export function NodeActionMenu({
17
+ onDelete,
18
+ onDuplicate,
19
+ onMove,
20
+ onPointerDown,
21
+ onPointerUp,
22
+ onPointerEnter,
23
+ onPointerLeave,
24
+ }: NodeActionMenuProps) {
25
+ return (
26
+ <div
27
+ className="pointer-events-auto flex items-center gap-1 rounded-lg border border-border bg-background/95 p-1 shadow-xl backdrop-blur-md"
28
+ onPointerDown={onPointerDown}
29
+ onPointerEnter={onPointerEnter}
30
+ onPointerLeave={onPointerLeave}
31
+ onPointerUp={onPointerUp}
32
+ >
33
+ {onMove && (
34
+ <button
35
+ aria-label="Move"
36
+ className="tooltip-trigger rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
37
+ onClick={onMove}
38
+ title="Move"
39
+ type="button"
40
+ >
41
+ <Move className="h-4 w-4" />
42
+ </button>
43
+ )}
44
+ {onDuplicate && (
45
+ <button
46
+ aria-label="Duplicate"
47
+ className="tooltip-trigger rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
48
+ onClick={onDuplicate}
49
+ title="Duplicate"
50
+ type="button"
51
+ >
52
+ <Copy className="h-4 w-4" />
53
+ </button>
54
+ )}
55
+ <button
56
+ aria-label="Delete"
57
+ className="tooltip-trigger rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
58
+ onClick={onDelete}
59
+ title="Delete"
60
+ type="button"
61
+ >
62
+ <Trash2 className="h-4 w-4" />
63
+ </button>
64
+ </div>
65
+ )
66
+ }
@@ -0,0 +1,125 @@
1
+ 'use client'
2
+
3
+ import { emitter, sceneRegistry } from '@pascal-app/core'
4
+ import { useThree } from '@react-three/fiber'
5
+ import { useCallback, useEffect } from 'react'
6
+ import * as THREE from 'three'
7
+ import { usePresetsAdapter } from '../../contexts/presets-context'
8
+
9
+ const THUMBNAIL_SIZE = 1080
10
+ const CAMERA_FOV = 45
11
+
12
+ export const PresetThumbnailGenerator = () => {
13
+ const gl = useThree((state) => state.gl)
14
+ const scene = useThree((state) => state.scene)
15
+ const adapter = usePresetsAdapter()
16
+
17
+ const generate = useCallback(
18
+ async ({ presetId, nodeId }: { presetId: string; nodeId: string }) => {
19
+ const target = sceneRegistry.nodes.get(nodeId)
20
+ if (!target) {
21
+ console.error('❌ PresetThumbnail: node not found', nodeId)
22
+ return
23
+ }
24
+
25
+ // Compute each mesh's transform relative to the target node (cancels world
26
+ // position/rotation), so the item is always rendered at origin with a known
27
+ // neutral orientation regardless of where it's placed in the scene.
28
+ target.updateWorldMatrix(true, true)
29
+ const targetInverse = new THREE.Matrix4().copy(target.matrixWorld).invert()
30
+ const relMatrix = new THREE.Matrix4()
31
+
32
+ const clones: THREE.Object3D[] = []
33
+ target.traverse((obj) => {
34
+ if (
35
+ !(obj instanceof THREE.Mesh || obj instanceof THREE.Line || obj instanceof THREE.Points)
36
+ )
37
+ return
38
+ const c = obj.clone(false) // shallow clone: copies geometry, material, visible — no children
39
+ relMatrix.multiplyMatrices(targetInverse, obj.matrixWorld)
40
+ relMatrix.decompose(c.position, c.quaternion, c.scale)
41
+ scene.add(c)
42
+ clones.push(c)
43
+ })
44
+
45
+ if (clones.length === 0) {
46
+ console.error('❌ PresetThumbnail: no renderable objects found', nodeId)
47
+ return
48
+ }
49
+
50
+ // Combined bounding box across all clones
51
+ const box = new THREE.Box3()
52
+ for (const c of clones) box.expandByObject(c)
53
+
54
+ if (box.isEmpty()) {
55
+ for (const c of clones) scene.remove(c)
56
+ console.error('❌ PresetThumbnail: empty bounding box', nodeId)
57
+ return
58
+ }
59
+
60
+ const sphere = new THREE.Sphere()
61
+ box.getBoundingSphere(sphere)
62
+
63
+ // Camera: aspect matches canvas (center-cropped to square after render)
64
+ const { width, height } = gl.domElement
65
+ const camera = new THREE.PerspectiveCamera(CAMERA_FOV, width / height, 0.01, 1000)
66
+ const dir = new THREE.Vector3(-0.5, 0.5, 0.5).normalize()
67
+ const fovRad = (CAMERA_FOV * Math.PI) / 180
68
+ const dist = (sphere.radius / Math.tan(fovRad / 2)) * 1.3
69
+ camera.position.copy(sphere.center).addScaledVector(dir, dist)
70
+ camera.lookAt(sphere.center)
71
+ camera.updateProjectionMatrix()
72
+
73
+ // Hide all scene geometry except the clones — leave lights, cameras, etc. intact
74
+ const cloneSet = new Set<THREE.Object3D>(clones)
75
+ const snapshot = new Map<THREE.Object3D, boolean>()
76
+ scene.traverse((obj) => {
77
+ if (cloneSet.has(obj)) return
78
+ if (
79
+ !(obj instanceof THREE.Mesh || obj instanceof THREE.Line || obj instanceof THREE.Points)
80
+ )
81
+ return
82
+ snapshot.set(obj, obj.visible)
83
+ obj.visible = false
84
+ })
85
+
86
+ gl.render(scene, camera)
87
+
88
+ // Restore visibility and remove clones
89
+ snapshot.forEach((wasVisible, obj) => {
90
+ obj.visible = wasVisible
91
+ })
92
+ for (const c of clones) scene.remove(c)
93
+
94
+ // Center-crop to square and scale to THUMBNAIL_SIZE
95
+ const minDim = Math.min(width, height)
96
+ const sx = Math.round((width - minDim) / 2)
97
+ const sy = Math.round((height - minDim) / 2)
98
+ const offscreen = document.createElement('canvas')
99
+ offscreen.width = THUMBNAIL_SIZE
100
+ offscreen.height = THUMBNAIL_SIZE
101
+ const ctx = offscreen.getContext('2d')!
102
+ ctx.drawImage(gl.domElement, sx, sy, minDim, minDim, 0, 0, THUMBNAIL_SIZE, THUMBNAIL_SIZE)
103
+
104
+ offscreen.toBlob(async (blob) => {
105
+ if (!blob) {
106
+ console.error('❌ PresetThumbnail: failed to create blob')
107
+ return
108
+ }
109
+ if (!adapter.uploadPresetThumbnail) return
110
+ const thumbnailUrl = await adapter.uploadPresetThumbnail(presetId, blob)
111
+ if (thumbnailUrl) {
112
+ emitter.emit('preset:thumbnail-updated', { presetId, thumbnailUrl })
113
+ }
114
+ }, 'image/png')
115
+ },
116
+ [gl, scene, adapter],
117
+ )
118
+
119
+ useEffect(() => {
120
+ emitter.on('preset:generate-thumbnail', generate)
121
+ return () => emitter.off('preset:generate-thumbnail', generate)
122
+ }, [generate])
123
+
124
+ return null
125
+ }