@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,126 @@
1
+ import { type AnyNodeId, type CeilingNode, useScene } from '@pascal-app/core'
2
+ import { useViewer } from '@pascal-app/viewer'
3
+ import Image from 'next/image'
4
+ import { useEffect, useState } from 'react'
5
+ import useEditor from './../../../../../store/use-editor'
6
+ import { InlineRenameInput } from './inline-rename-input'
7
+ import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
8
+ import { TreeNodeActions } from './tree-node-actions'
9
+
10
+ interface CeilingTreeNodeProps {
11
+ node: CeilingNode
12
+ depth: number
13
+ isLast?: boolean
14
+ }
15
+
16
+ export function CeilingTreeNode({ node, depth, isLast }: CeilingTreeNodeProps) {
17
+ const [expanded, setExpanded] = useState(false)
18
+ const [isEditing, setIsEditing] = useState(false)
19
+ const selectedIds = useViewer((state) => state.selection.selectedIds)
20
+ const isSelected = selectedIds.includes(node.id)
21
+ const isHovered = useViewer((state) => state.hoveredId === node.id)
22
+ const setSelection = useViewer((state) => state.setSelection)
23
+ const setHoveredId = useViewer((state) => state.setHoveredId)
24
+
25
+ useEffect(() => {
26
+ if (selectedIds.length === 0) return
27
+ const nodes = useScene.getState().nodes
28
+ let isDescendant = false
29
+ for (const id of selectedIds) {
30
+ let current = nodes[id as AnyNodeId]
31
+ while (current?.parentId) {
32
+ if (current.parentId === node.id) {
33
+ isDescendant = true
34
+ break
35
+ }
36
+ current = nodes[current.parentId as AnyNodeId]
37
+ }
38
+ if (isDescendant) break
39
+ }
40
+ if (isDescendant) {
41
+ setExpanded(true)
42
+ }
43
+ }, [selectedIds, node.id])
44
+
45
+ const handleClick = (e: React.MouseEvent) => {
46
+ e.stopPropagation()
47
+ const handled = handleTreeSelection(e, node.id, selectedIds, setSelection)
48
+ if (!handled && useEditor.getState().phase === 'furnish') {
49
+ useEditor.getState().setPhase('structure')
50
+ }
51
+ }
52
+
53
+ const handleDoubleClick = () => {
54
+ focusTreeNode(node.id)
55
+ }
56
+
57
+ const handleMouseEnter = () => {
58
+ setHoveredId(node.id)
59
+ }
60
+
61
+ const handleMouseLeave = () => {
62
+ setHoveredId(null)
63
+ }
64
+
65
+ // Calculate approximate area from polygon
66
+ const area = calculatePolygonArea(node.polygon).toFixed(1)
67
+ const defaultName = `Ceiling (${area}m²)`
68
+
69
+ return (
70
+ <TreeNodeWrapper
71
+ actions={<TreeNodeActions node={node} />}
72
+ depth={depth}
73
+ expanded={expanded}
74
+ hasChildren={node.children.length > 0}
75
+ icon={
76
+ <Image alt="" className="object-contain" height={14} src="/icons/ceiling.png" width={14} />
77
+ }
78
+ isHovered={isHovered}
79
+ isLast={isLast}
80
+ isSelected={isSelected}
81
+ isVisible={node.visible !== false}
82
+ label={
83
+ <InlineRenameInput
84
+ defaultName={defaultName}
85
+ isEditing={isEditing}
86
+ node={node}
87
+ onStartEditing={() => setIsEditing(true)}
88
+ onStopEditing={() => setIsEditing(false)}
89
+ />
90
+ }
91
+ nodeId={node.id}
92
+ onClick={handleClick}
93
+ onDoubleClick={handleDoubleClick}
94
+ onMouseEnter={handleMouseEnter}
95
+ onMouseLeave={handleMouseLeave}
96
+ onToggle={() => setExpanded(!expanded)}
97
+ >
98
+ {node.children.map((childId, index) => (
99
+ <TreeNode
100
+ depth={depth + 1}
101
+ isLast={index === node.children.length - 1}
102
+ key={childId}
103
+ nodeId={childId}
104
+ />
105
+ ))}
106
+ </TreeNodeWrapper>
107
+ )
108
+ }
109
+
110
+ /**
111
+ * Calculate the area of a polygon using the shoelace formula
112
+ */
113
+ function calculatePolygonArea(polygon: Array<[number, number]>): number {
114
+ if (polygon.length < 3) return 0
115
+
116
+ let area = 0
117
+ const n = polygon.length
118
+
119
+ for (let i = 0; i < n; i++) {
120
+ const j = (i + 1) % n
121
+ area += polygon[i]?.[0] * polygon[j]?.[1]
122
+ area -= polygon[j]?.[0] * polygon[i]?.[1]
123
+ }
124
+
125
+ return Math.abs(area) / 2
126
+ }
@@ -0,0 +1,64 @@
1
+ 'use client'
2
+
3
+ import type { DoorNode } from '@pascal-app/core'
4
+ import { useViewer } from '@pascal-app/viewer'
5
+ import Image from 'next/image'
6
+ import { useState } from 'react'
7
+ import useEditor from './../../../../../store/use-editor'
8
+ import { InlineRenameInput } from './inline-rename-input'
9
+ import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
10
+ import { TreeNodeActions } from './tree-node-actions'
11
+
12
+ interface DoorTreeNodeProps {
13
+ node: DoorNode
14
+ depth: number
15
+ isLast?: boolean
16
+ }
17
+
18
+ export function DoorTreeNode({ node, depth, isLast }: DoorTreeNodeProps) {
19
+ const [isEditing, setIsEditing] = useState(false)
20
+ const selectedIds = useViewer((state) => state.selection.selectedIds)
21
+ const isSelected = selectedIds.includes(node.id)
22
+ const isHovered = useViewer((state) => state.hoveredId === node.id)
23
+ const setSelection = useViewer((state) => state.setSelection)
24
+ const setHoveredId = useViewer((state) => state.setHoveredId)
25
+
26
+ const defaultName = 'Door'
27
+
28
+ return (
29
+ <TreeNodeWrapper
30
+ actions={<TreeNodeActions node={node} />}
31
+ depth={depth}
32
+ expanded={false}
33
+ hasChildren={false}
34
+ icon={
35
+ <Image alt="" className="object-contain" height={14} src="/icons/door.png" width={14} />
36
+ }
37
+ isHovered={isHovered}
38
+ isLast={isLast}
39
+ isSelected={isSelected}
40
+ isVisible={node.visible !== false}
41
+ label={
42
+ <InlineRenameInput
43
+ defaultName={defaultName}
44
+ isEditing={isEditing}
45
+ node={node}
46
+ onStartEditing={() => setIsEditing(true)}
47
+ onStopEditing={() => setIsEditing(false)}
48
+ />
49
+ }
50
+ nodeId={node.id}
51
+ onClick={(e: React.MouseEvent) => {
52
+ e.stopPropagation()
53
+ const handled = handleTreeSelection(e, node.id, selectedIds, setSelection)
54
+ if (!handled && useEditor.getState().phase === 'furnish') {
55
+ useEditor.getState().setPhase('structure')
56
+ }
57
+ }}
58
+ onDoubleClick={() => focusTreeNode(node.id)}
59
+ onMouseEnter={() => setHoveredId(node.id)}
60
+ onMouseLeave={() => setHoveredId(null)}
61
+ onToggle={() => {}}
62
+ />
63
+ )
64
+ }