@pascal-app/editor 1.0.7 → 1.1.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.
- package/package.json +7 -7
- package/src/components/editor/custom-camera-controls.tsx +90 -34
- package/src/components/editor/duplicate-node.ts +2 -1
- package/src/components/editor/first-person-controls.tsx +5 -7
- package/src/components/editor/fit-scene-framing.test.ts +318 -0
- package/src/components/editor/fit-scene-framing.ts +142 -0
- package/src/components/editor/floating-action-menu.tsx +4 -2
- package/src/components/editor/floor-openings-overlay.tsx +1 -1
- package/src/components/editor/floor-region-controls.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +173 -8
- package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
- package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
- package/src/components/editor/footprint-height-handle.tsx +1 -1
- package/src/components/editor/group-transform-shared.ts +3 -1
- package/src/components/editor/index.tsx +20 -0
- package/src/components/editor/measurement-pill.tsx +1 -1
- package/src/components/editor/node-arrow-handles.tsx +1 -1
- package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -2
- package/src/components/editor/open-wall-ends-hint.tsx +1 -1
- package/src/components/editor/quick-measurement-card.tsx +1 -1
- package/src/components/editor/room-controls.tsx +3 -2
- package/src/components/editor/room-handles-2d.tsx +2 -1
- package/src/components/editor/room-handles.tsx +3 -3
- package/src/components/editor/room-labels.tsx +4 -2
- package/src/components/editor/site-edge-labels.tsx +6 -2
- package/src/components/editor/wall-measurement-label.tsx +30 -14
- package/src/components/editor/wall-move-side-handles.tsx +10 -6
- package/src/components/editor/wall-region-handles.tsx +2 -2
- package/src/components/editor/wall-snap-beacon-layer.tsx +53 -32
- package/src/components/editor-2d/floorplan-group-move.tsx +1 -1
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +12 -12
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +1 -2
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +22 -4
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +22 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +14 -2
- package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
- package/src/components/systems/roof/roof-edit-system.tsx +30 -19
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +150 -32
- package/src/components/tools/item/move-tool.tsx +1 -6
- package/src/components/tools/item/use-placement-coordinator.tsx +1 -1
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
- package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
- package/src/components/tools/shared/placement-box.tsx +1 -5
- package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
- package/src/components/tools/shared/segment-angle.ts +0 -4
- package/src/components/tools/tool-manager.tsx +1 -24
- package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
- package/src/components/tools/wall/wall-typed-entry.ts +70 -0
- package/src/components/ui/controls/slider-control.tsx +18 -6
- package/src/components/ui/controls/tool-options-panel.tsx +74 -44
- package/src/components/ui/helpers/helper-manager.test.tsx +53 -0
- package/src/components/ui/helpers/helper-manager.tsx +54 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +1 -1
- package/src/components/ui/panels/floor-opening-panel.tsx +1 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +28 -24
- package/src/components/ui/panels/multi-height-mode.tsx +3 -3
- package/src/components/ui/panels/multi-selection-panel.tsx +1 -1
- package/src/components/ui/panels/panel-manager.tsx +4 -2
- package/src/components/ui/panels/panel-wrapper.tsx +41 -18
- package/src/components/ui/panels/room-construction-rows.tsx +1 -1
- package/src/components/ui/panels/room-openings-field.tsx +1 -1
- package/src/components/ui/panels/room-panel.tsx +1 -1
- package/src/components/ui/right-stack.test.ts +13 -6
- package/src/components/ui/right-stack.tsx +21 -10
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +1 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +1 -1
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +1 -1
- package/src/components/viewer/viewer-rooms.tsx +1 -1
- package/src/i18n/locales/en/messages.po +856 -124
- package/src/i18n/locales/en/messages.ts +1 -1
- package/src/i18n/locales/en-XA/messages.ts +1 -1
- package/src/i18n/locales/es/messages.po +2087 -257
- package/src/i18n/locales/fr/messages.po +2087 -257
- package/src/i18n/locales/ja/messages.po +2087 -257
- package/src/i18n/locales/zh-CN/messages.po +2087 -257
- package/src/index.tsx +74 -47
- package/src/lib/contextual-help.ts +6 -0
- package/src/lib/elevation-guides.ts +21 -6
- package/src/lib/elevator-support.ts +1 -72
- package/src/lib/floor-footprints.test.ts +1 -1
- package/src/lib/floor-footprints.ts +1 -9
- package/src/lib/floor-foundation-support.test.ts +8 -3
- package/src/lib/floor-opening-draft.ts +1 -1
- package/src/lib/floor-region-session.ts +1 -1
- package/src/lib/floorplan/open-wall-ends.ts +2 -1
- package/src/lib/gesture-lifecycle.ts +3 -1
- package/src/lib/interaction/scope.ts +1 -0
- package/src/lib/layer-thickness.ts +1 -1
- package/src/lib/level-selection.test.ts +18 -2
- package/src/lib/level-selection.ts +31 -8
- package/src/lib/linear-display.ts +1 -1
- package/src/lib/live-gesture-round-trip.test.ts +1 -1
- package/src/lib/measurement-label.test.ts +1 -1
- package/src/lib/measurements.test.ts +8 -7
- package/src/lib/measurements.ts +1 -218
- package/src/lib/node-action-movement.test.tsx +115 -0
- package/src/lib/node-action-movement.ts +2 -0
- package/src/lib/placement-metadata.ts +4 -0
- package/src/lib/point-ask/describe-target.ts +6 -8
- package/src/lib/point-ask/region.ts +1 -1
- package/src/lib/print-shell-compiler.ts +12 -1
- package/src/lib/print-wall-solids.test.ts +11 -0
- package/src/lib/print-wall-solids.ts +9 -0
- package/src/lib/room-assembly-overlay.ts +1 -1
- package/src/lib/room-built-on.test.ts +2 -5
- package/src/lib/room-built-on.ts +1 -15
- package/src/lib/room-construction-commands.ts +3 -2
- package/src/lib/room-controls-phase6.test.ts +1 -1
- package/src/lib/room-handle-drag.ts +6 -25
- package/src/lib/room-push-dimensions.test.ts +1 -1
- package/src/lib/scene-bounds.test.ts +144 -0
- package/src/lib/scene-bounds.ts +79 -15
- package/src/lib/scene-clipboard.ts +2 -1
- package/src/lib/snapping-mode.test.ts +23 -1
- package/src/lib/viewer-selection.ts +1 -1
- package/src/lib/wall-region-list.ts +3 -1
- package/src/lib/window-interaction.test.ts +4 -2
- package/src/store/use-editor.tsx +5 -0
- package/src/store/use-placement-typing.test.ts +165 -0
- package/src/store/use-placement-typing.ts +186 -0
- package/src/components/tools/elevator/elevator-defaults.ts +0 -8
- package/src/components/tools/elevator/elevator-tool.tsx +0 -372
- package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
- package/src/lib/door-interaction.ts +0 -102
- package/src/lib/measurement-label.ts +0 -68
- package/src/lib/window-interaction.ts +0 -142
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
THICK_FLOOR_HINT,
|
|
37
37
|
thickFloorAdvice,
|
|
38
38
|
} from '../../../lib/floor-footprints'
|
|
39
|
-
import { formatLinearMeasurement } from '
|
|
39
|
+
import { formatLinearMeasurement } from '@pascal-app/core'
|
|
40
40
|
import { FinishSwatch } from '../controls/finish-swatch'
|
|
41
41
|
import { MetricControl } from '../controls/metric-control'
|
|
42
42
|
import { SegmentedControl } from '../controls/segmented-control'
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
finishFloorOpening,
|
|
15
15
|
openingRoom,
|
|
16
16
|
} from '../../../lib/floor-edit-session'
|
|
17
|
-
import { formatAreaLabel } from '
|
|
17
|
+
import { formatAreaLabel } from '@pascal-app/core'
|
|
18
18
|
import { Button } from '../primitives/button'
|
|
19
19
|
import { Switch } from '../primitives/switch'
|
|
20
20
|
import { PanelWrapper } from './panel-wrapper'
|
|
@@ -12,8 +12,8 @@ interface MobileSelectionBarProps {
|
|
|
12
12
|
node: AnyNode | null
|
|
13
13
|
label?: string
|
|
14
14
|
icon?: string
|
|
15
|
-
onMove
|
|
16
|
-
onDuplicate
|
|
15
|
+
onMove?: () => void
|
|
16
|
+
onDuplicate?: () => void
|
|
17
17
|
onDelete: () => void
|
|
18
18
|
onEdit: () => void
|
|
19
19
|
}
|
|
@@ -58,28 +58,32 @@ export function MobileSelectionBar({
|
|
|
58
58
|
</button>
|
|
59
59
|
|
|
60
60
|
<div className="flex items-center gap-0.5 border-border/40 border-l pl-1">
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
61
|
+
{onMove && (
|
|
62
|
+
<button
|
|
63
|
+
aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.move', message: 'Move' })}
|
|
64
|
+
className={ACTION_BTN}
|
|
65
|
+
onClick={(e) => {
|
|
66
|
+
stop(e)
|
|
67
|
+
onMove()
|
|
68
|
+
}}
|
|
69
|
+
type="button"
|
|
70
|
+
>
|
|
71
|
+
<Move className="h-4 w-4" />
|
|
72
|
+
</button>
|
|
73
|
+
)}
|
|
74
|
+
{onDuplicate && (
|
|
75
|
+
<button
|
|
76
|
+
aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.duplicate', message: 'Duplicate' })}
|
|
77
|
+
className={ACTION_BTN}
|
|
78
|
+
onClick={(e) => {
|
|
79
|
+
stop(e)
|
|
80
|
+
onDuplicate()
|
|
81
|
+
}}
|
|
82
|
+
type="button"
|
|
83
|
+
>
|
|
84
|
+
<Copy className="h-4 w-4" />
|
|
85
|
+
</button>
|
|
86
|
+
)}
|
|
83
87
|
<button
|
|
84
88
|
aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.delete', message: 'Delete' })}
|
|
85
89
|
className={cn(
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import { useViewer } from '@pascal-app/viewer'
|
|
17
17
|
import { useCallback } from 'react'
|
|
18
18
|
import { useShallow } from 'zustand/react/shallow'
|
|
19
|
-
import { formatLinearMeasurement } from '
|
|
19
|
+
import { formatLinearMeasurement } from '@pascal-app/core'
|
|
20
20
|
import { SegmentedControl } from '../controls/segmented-control'
|
|
21
21
|
import { SliderControl } from '../controls/slider-control'
|
|
22
22
|
import {
|
|
@@ -42,7 +42,7 @@ function wallFollowsLevelPatch(wall: WallNode, nodes: Record<string, AnyNode>):
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function effectiveHeight(node: AnyNode, nodes: Record<string, AnyNode>): number {
|
|
45
|
-
if (node.type === 'wall') return getWallEffectiveHeightForNodes(node, nodes)
|
|
45
|
+
if (node.type === 'wall') return getWallEffectiveHeightForNodes(node, nodes, 0)
|
|
46
46
|
if (node.type === 'ceiling') return resolveCeilingHeight(node, nodes)
|
|
47
47
|
const height = (node as { height?: number }).height
|
|
48
48
|
return typeof height === 'number' ? height : 0
|
|
@@ -72,7 +72,7 @@ export function applyMultiHeightMode(
|
|
|
72
72
|
if (isCustom) return {}
|
|
73
73
|
if (node.type === 'ceiling') return { height: ceilingCustomHeight(node, nodes) }
|
|
74
74
|
if (node.type === 'wall') {
|
|
75
|
-
return { height: Math.max(0.1, getWallEffectiveHeightForNodes(node, nodes)) }
|
|
75
|
+
return { height: Math.max(0.1, getWallEffectiveHeightForNodes(node, nodes, 0)) }
|
|
76
76
|
}
|
|
77
77
|
return {}
|
|
78
78
|
}
|
|
@@ -14,7 +14,7 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
14
14
|
import { Building2, Copy, Group, Merge, Split, Trash2, Ungroup } from 'lucide-react'
|
|
15
15
|
import { useMemo } from 'react'
|
|
16
16
|
import { deleteSelection, duplicateSelectionAndPickUp } from '../../editor/group-actions'
|
|
17
|
-
import { formatLinearMeasurement } from '
|
|
17
|
+
import { formatLinearMeasurement } from '@pascal-app/core'
|
|
18
18
|
import { triggerSFX } from '../../../lib/sfx-bus'
|
|
19
19
|
import { writeWindowChanges } from '../../../lib/window-changes'
|
|
20
20
|
import { collectSelectableCandidateIds } from '../../tools/select/select-candidates'
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
type DormerNode,
|
|
12
12
|
type ElevatorNode,
|
|
13
13
|
type FenceNode,
|
|
14
|
+
isMoveLocked,
|
|
15
|
+
isNodeDuplicable,
|
|
14
16
|
type ItemNode,
|
|
15
17
|
type RoofNode,
|
|
16
18
|
type RoofSegmentNode,
|
|
@@ -155,9 +157,9 @@ function MobilePanelLayer({
|
|
|
155
157
|
<MobileSelectionBar
|
|
156
158
|
node={node}
|
|
157
159
|
onDelete={handleDelete}
|
|
158
|
-
onDuplicate={handleDuplicate}
|
|
160
|
+
onDuplicate={isNodeDuplicable(node) ? handleDuplicate : undefined}
|
|
159
161
|
onEdit={() => setIsSheetOpen((v) => !v)}
|
|
160
|
-
onMove={handleMove}
|
|
162
|
+
onMove={isMoveLocked(node) ? undefined : handleMove}
|
|
161
163
|
/>
|
|
162
164
|
)}
|
|
163
165
|
<MobilePanelSheet
|
|
@@ -45,7 +45,7 @@ function clamp(value: number, min: number, max: number): number {
|
|
|
45
45
|
* `data-viewer-bounds`) so it can't slide under the sidebar or top bar.
|
|
46
46
|
* Falls back to the viewport when the marker isn't found.
|
|
47
47
|
*/
|
|
48
|
-
function getDragBounds(el: HTMLElement | null): {
|
|
48
|
+
function getDragBounds(el: HTMLElement | null, inStack = false): {
|
|
49
49
|
left: number
|
|
50
50
|
top: number
|
|
51
51
|
right: number
|
|
@@ -56,7 +56,15 @@ function getDragBounds(el: HTMLElement | null): {
|
|
|
56
56
|
if (!rect) {
|
|
57
57
|
return { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight }
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
const stack = inStack ? el?.closest('[data-right-stack]') : null
|
|
60
|
+
const stackRect = stack?.getBoundingClientRect()
|
|
61
|
+
const cardRect = stack?.querySelector('[data-right-stack-card]')?.getBoundingClientRect()
|
|
62
|
+
return {
|
|
63
|
+
left: rect.left,
|
|
64
|
+
top: stackRect ? stackRect.top - DRAG_MARGIN : rect.top,
|
|
65
|
+
right: rect.right,
|
|
66
|
+
bottom: cardRect && cardRect.height > 0 ? cardRect.top : rect.bottom,
|
|
67
|
+
}
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
/**
|
|
@@ -187,7 +195,7 @@ export function PanelWrapper({
|
|
|
187
195
|
if ((e.target as HTMLElement).closest('button, input, label')) return
|
|
188
196
|
const rect = panelRef.current?.getBoundingClientRect()
|
|
189
197
|
if (!rect) return
|
|
190
|
-
const bounds = getDragBounds(panelRef.current)
|
|
198
|
+
const bounds = getDragBounds(panelRef.current, inStack)
|
|
191
199
|
const base = offset ?? { x: 0, y: 0 }
|
|
192
200
|
dragRef.current = {
|
|
193
201
|
startX: e.clientX,
|
|
@@ -207,7 +215,7 @@ export function PanelWrapper({
|
|
|
207
215
|
setIsDragging(true)
|
|
208
216
|
e.currentTarget.setPointerCapture(e.pointerId)
|
|
209
217
|
},
|
|
210
|
-
[offset],
|
|
218
|
+
[offset, inStack],
|
|
211
219
|
)
|
|
212
220
|
|
|
213
221
|
const handleHeaderPointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
@@ -238,22 +246,37 @@ export function PanelWrapper({
|
|
|
238
246
|
[handleCardToggle],
|
|
239
247
|
)
|
|
240
248
|
|
|
241
|
-
//
|
|
242
|
-
// collapsed — nudge it back inside the viewer bounds.
|
|
249
|
+
// Keep repositioned panels inside the available space as the card or viewer resizes.
|
|
243
250
|
useLayoutEffect(() => {
|
|
244
|
-
if (isMobile
|
|
251
|
+
if (isMobile) return
|
|
245
252
|
const el = panelRef.current
|
|
246
253
|
if (!el) return
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
254
|
+
const fit = () => {
|
|
255
|
+
const rect = el.getBoundingClientRect()
|
|
256
|
+
const bounds = getDragBounds(el, inStack)
|
|
257
|
+
const left = clamp(rect.left, bounds.left + DRAG_MARGIN, bounds.right - rect.width - DRAG_MARGIN)
|
|
258
|
+
const top = clamp(rect.top, bounds.top + DRAG_MARGIN, bounds.bottom - rect.height - DRAG_MARGIN)
|
|
259
|
+
const dx = left - rect.left
|
|
260
|
+
const dy = top - rect.top
|
|
261
|
+
if (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5) {
|
|
262
|
+
setOffset((prev) => ({ x: (prev?.x ?? 0) + dx, y: (prev?.y ?? 0) + dy }))
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
fit()
|
|
266
|
+
const resize = new ResizeObserver(fit)
|
|
267
|
+
resize.observe(el)
|
|
268
|
+
const stack = el.closest('[data-right-stack]')
|
|
269
|
+
if (stack) {
|
|
270
|
+
resize.observe(stack)
|
|
271
|
+
const card = stack.querySelector('[data-right-stack-card]')
|
|
272
|
+
if (card) resize.observe(card)
|
|
273
|
+
}
|
|
274
|
+
window.addEventListener('resize', fit)
|
|
275
|
+
return () => {
|
|
276
|
+
resize.disconnect()
|
|
277
|
+
window.removeEventListener('resize', fit)
|
|
255
278
|
}
|
|
256
|
-
}, [collapsed, isMobile])
|
|
279
|
+
}, [collapsed, isMobile, inStack])
|
|
257
280
|
|
|
258
281
|
return (
|
|
259
282
|
<div
|
|
@@ -264,7 +287,7 @@ export function PanelWrapper({
|
|
|
264
287
|
: inStack
|
|
265
288
|
? // In the shared right column: it grows down and scrolls on itself,
|
|
266
289
|
// leaving the shortcuts card below it its room (`right-stack.tsx`).
|
|
267
|
-
'pointer-events-auto flex max-h-
|
|
290
|
+
'pointer-events-auto flex max-h-[var(--inspector-available-height)] min-h-0 flex-[0_1_auto] flex-col overflow-hidden rounded-xl border border-border/50 bg-sidebar/95 shadow-2xl backdrop-blur-xl dark:text-foreground'
|
|
268
291
|
// Cap height at `100dvh - 154px` so a tall panel's bottom edge
|
|
269
292
|
// aligns flush with the top of the floating bottom action bar.
|
|
270
293
|
// Combined with `top-20` (80px), the panel's bottom sits at
|
|
@@ -291,7 +314,7 @@ export function PanelWrapper({
|
|
|
291
314
|
{!isMobile && (
|
|
292
315
|
<div
|
|
293
316
|
className={cn(
|
|
294
|
-
'relative flex select-none items-center justify-between px-3 py-3',
|
|
317
|
+
'relative flex shrink-0 select-none items-center justify-between px-3 py-3',
|
|
295
318
|
!collapsed && 'border-border/50 border-b',
|
|
296
319
|
isDragging ? 'cursor-grabbing' : 'cursor-grab',
|
|
297
320
|
)}
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
roomFloorOwner,
|
|
35
35
|
roomFootprint,
|
|
36
36
|
} from '../../../lib/floor-footprints'
|
|
37
|
-
import { formatLinearMeasurement } from '
|
|
37
|
+
import { formatLinearMeasurement } from '@pascal-app/core'
|
|
38
38
|
import { startMezzanineDraft } from '../../../lib/mezzanine-draft'
|
|
39
39
|
import { resolveRoomAssemblyHeights } from '../../../lib/room-assembly-overlay'
|
|
40
40
|
import { type FloorChoice, roomBuiltOn, separateFloorSummary } from '../../../lib/room-built-on'
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
useFloorEditSession,
|
|
12
12
|
} from '../../../lib/floor-edit-session'
|
|
13
13
|
import { type OpeningSurface, startOpeningDraft } from '../../../lib/floor-opening-draft'
|
|
14
|
-
import { formatAreaLabel } from '
|
|
14
|
+
import { formatAreaLabel } from '@pascal-app/core'
|
|
15
15
|
import { pill } from './room-construction-rows'
|
|
16
16
|
import { ShapeChoice } from './shape-choice'
|
|
17
17
|
|
|
@@ -17,7 +17,7 @@ import { type KeyboardEvent, useEffect, useId, useMemo, useRef, useState } from
|
|
|
17
17
|
import { startCeilingEdit } from '../../../lib/ceiling-edit-session'
|
|
18
18
|
import { resolveOverlayPolicy } from '../../../lib/interaction/overlay-policy'
|
|
19
19
|
import { finishScriptCode } from '../../../lib/geometry-script/finish-scripts'
|
|
20
|
-
import { formatAreaLabel, formatLinearMeasurement } from '
|
|
20
|
+
import { formatAreaLabel, formatLinearMeasurement } from '@pascal-app/core'
|
|
21
21
|
import { addMezzanineStairs } from '../../../lib/mezzanine-stairs'
|
|
22
22
|
import { roomFloorBase, roomRelativeFloorHeight } from '../../../lib/room-construction-commands'
|
|
23
23
|
import { mezzanineElevationBounds } from '../../../lib/room-handle-drag'
|
|
@@ -14,16 +14,23 @@ test('the inspector keeps 180 px, or its own height if shorter, above the full c
|
|
|
14
14
|
).toEqual({ inspector: 0, card: 240 })
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
test('on a short window the
|
|
17
|
+
test('on a short window the help card scrolls to preserve editing space', () => {
|
|
18
18
|
expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 400 })).toEqual({
|
|
19
|
-
inspector:
|
|
20
|
-
card:
|
|
19
|
+
inspector: 180,
|
|
20
|
+
card: 212,
|
|
21
21
|
})
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
test('
|
|
24
|
+
test('expanded hints cannot squeeze the inspector down to just its header', () => {
|
|
25
25
|
expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 258 })).toEqual({
|
|
26
|
-
inspector:
|
|
27
|
-
card:
|
|
26
|
+
inspector: 180,
|
|
27
|
+
card: 70,
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('a column shorter than the inspector minimum still bounds both cards', () => {
|
|
32
|
+
expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 100 })).toEqual({
|
|
33
|
+
inspector: 92,
|
|
34
|
+
card: 0,
|
|
28
35
|
})
|
|
29
36
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { createContext, type ReactNode, useContext,
|
|
3
|
+
import { createContext, type ReactNode, useContext, useLayoutEffect, useRef } from 'react'
|
|
4
4
|
import { useIsMobile } from '../../hooks/use-mobile'
|
|
5
5
|
import { cn } from '../../lib/utils'
|
|
6
6
|
import { TOP_MENU_CLEARANCE } from './action-menu/placement'
|
|
@@ -20,9 +20,8 @@ const GAP = 8
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* The heights neither panel shrinks below. The inspector keeps 180 px (or its
|
|
23
|
-
* own height, if shorter)
|
|
24
|
-
*
|
|
25
|
-
* very short window it gives way and scrolls on itself — both stay usable.
|
|
23
|
+
* own height, if shorter) and never less than its header row when the column allows.
|
|
24
|
+
* The card uses the remaining room and scrolls on itself on a short window — both stay usable.
|
|
26
25
|
*/
|
|
27
26
|
export function rightStackFloors(args: {
|
|
28
27
|
naturalHeight: number
|
|
@@ -33,9 +32,10 @@ export function rightStackFloors(args: {
|
|
|
33
32
|
const { naturalHeight, headerHeight, cardHeight, stackHeight } = args
|
|
34
33
|
const gap = naturalHeight > 0 && cardHeight > 0 ? GAP : 0
|
|
35
34
|
const header = Math.min(headerHeight, naturalHeight)
|
|
36
|
-
const inspector = Math.
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
const inspector = Math.min(
|
|
36
|
+
naturalHeight,
|
|
37
|
+
stackHeight,
|
|
38
|
+
Math.max(header, Math.min(INSPECTOR_MIN_HEIGHT, stackHeight - gap)),
|
|
39
39
|
)
|
|
40
40
|
const card = Math.max(0, Math.min(cardHeight, stackHeight - gap - inspector))
|
|
41
41
|
return { inspector, card }
|
|
@@ -78,7 +78,7 @@ export function RightStack({
|
|
|
78
78
|
|
|
79
79
|
// `min(180px, its own height)` has no CSS form for a flex item that scrolls,
|
|
80
80
|
// so both floors are measured (`rightStackFloors`).
|
|
81
|
-
|
|
81
|
+
useLayoutEffect(() => {
|
|
82
82
|
if (isMobile) return
|
|
83
83
|
const stack = stackRef.current
|
|
84
84
|
const slot = inspectorRef.current
|
|
@@ -88,18 +88,29 @@ export function RightStack({
|
|
|
88
88
|
const fit = () => {
|
|
89
89
|
frame = 0
|
|
90
90
|
const { natural, header } = inspectorHeights(slot)
|
|
91
|
+
const cardHeight = Array.from(card.children).reduce(
|
|
92
|
+
(height, child) => height + child.scrollHeight + (child instanceof HTMLElement ? child.offsetHeight - child.clientHeight : 0),
|
|
93
|
+
0,
|
|
94
|
+
)
|
|
91
95
|
const floors = rightStackFloors({
|
|
92
96
|
naturalHeight: natural,
|
|
93
97
|
headerHeight: header,
|
|
94
|
-
cardHeight
|
|
98
|
+
cardHeight,
|
|
95
99
|
stackHeight: stack.clientHeight,
|
|
96
100
|
})
|
|
97
101
|
const inspectorMin = `${floors.inspector}px`
|
|
98
102
|
const cardMin = `${floors.card}px`
|
|
103
|
+
// Custom panels may wrap PanelWrapper in a non-flex element. Give the
|
|
104
|
+
// card an explicit inherited limit so those wrappers cannot bypass it.
|
|
105
|
+
const available = Math.max(0, stack.clientHeight - floors.card - (natural > 0 && floors.card > 0 ? GAP : 0))
|
|
106
|
+
const limit = `${available}px`
|
|
107
|
+
if (slot.style.getPropertyValue('--inspector-available-height') !== limit) {
|
|
108
|
+
slot.style.setProperty('--inspector-available-height', limit)
|
|
109
|
+
}
|
|
99
110
|
if (slot.style.minHeight !== inspectorMin) slot.style.minHeight = inspectorMin
|
|
100
111
|
if (card.style.minHeight !== cardMin) card.style.minHeight = cardMin
|
|
101
112
|
// The card is click-through; it takes the pointer only when it has to scroll.
|
|
102
|
-
const pointer = floors.card <
|
|
113
|
+
const pointer = floors.card < cardHeight ? 'auto' : ''
|
|
103
114
|
if (card.style.pointerEvents !== pointer) card.style.pointerEvents = pointer
|
|
104
115
|
}
|
|
105
116
|
const schedule = () => {
|
|
@@ -23,11 +23,7 @@ import {
|
|
|
23
23
|
DialogHeader,
|
|
24
24
|
DialogTitle,
|
|
25
25
|
} from '../../../../../components/ui/primitives/dialog'
|
|
26
|
-
import {
|
|
27
|
-
getAreaUnitLabel,
|
|
28
|
-
type LinearUnit,
|
|
29
|
-
squareMetersToAreaUnit,
|
|
30
|
-
} from '../../../../../lib/measurements'
|
|
26
|
+
import { getAreaUnitLabel, type LinearUnit, squareMetersToAreaUnit } from '@pascal-app/core'
|
|
31
27
|
|
|
32
28
|
export type PendingImport = {
|
|
33
29
|
fileName: string
|
|
@@ -4,7 +4,7 @@ import Image from 'next/image'
|
|
|
4
4
|
import { memo, useCallback, useEffect, useState } from 'react'
|
|
5
5
|
import { useShallow } from 'zustand/react/shallow'
|
|
6
6
|
import { startCeilingEditFromTreeSelection } from './../../../../../lib/ceiling-edit-session'
|
|
7
|
-
import { formatAreaLabel } from '
|
|
7
|
+
import { formatAreaLabel } from '@pascal-app/core'
|
|
8
8
|
import { InlineRenameInput } from './inline-rename-input'
|
|
9
9
|
import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
10
10
|
import { TreeNodeActions } from './tree-node-actions'
|
|
@@ -60,14 +60,7 @@ import {
|
|
|
60
60
|
import { addStoreyOnTop } from './../../../../../lib/add-storey'
|
|
61
61
|
import { offerExteriorWallCopy } from './../../../../../components/ui/level-wall-copy-prompt'
|
|
62
62
|
import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
|
|
63
|
-
import {
|
|
64
|
-
formatAreaLabel,
|
|
65
|
-
getAreaUnitLabel,
|
|
66
|
-
getLinearUnitLabel,
|
|
67
|
-
linearUnitToMeters,
|
|
68
|
-
metersToLinearUnit,
|
|
69
|
-
squareMetersToAreaUnit,
|
|
70
|
-
} from './../../../../../lib/measurements'
|
|
63
|
+
import { formatAreaLabel, getAreaUnitLabel, getLinearUnitLabel, linearUnitToMeters, metersToLinearUnit, squareMetersToAreaUnit } from '@pascal-app/core'
|
|
71
64
|
import { createLocalGuideImage, createLocalScan } from './../../../../../lib/local-guide-image'
|
|
72
65
|
import { editorHostTreeChildrenRegistry } from './../../../../../lib/host-tree-children'
|
|
73
66
|
import { requestRoomDeletion } from './../../../../../lib/room-structure-commands'
|
|
@@ -2,7 +2,7 @@ import { type AnyNodeId, type SlabNode, usePascalI18n, useScene } from '@pascal-
|
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
|
-
import { formatAreaLabel } from '
|
|
5
|
+
import { formatAreaLabel } from '@pascal-app/core'
|
|
6
6
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
7
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
8
8
|
import { TreeNodeActions } from './tree-node-actions'
|
|
@@ -2,7 +2,7 @@ import { useScene, type ZoneNode, usePascalI18n} from '@pascal-app/core'
|
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import { memo, useCallback, useState } from 'react'
|
|
4
4
|
import { ColorDot } from './../../../../../components/ui/primitives/color-dot'
|
|
5
|
-
import { formatAreaLabel } from '
|
|
5
|
+
import { formatAreaLabel } from '@pascal-app/core'
|
|
6
6
|
import {
|
|
7
7
|
selectZoneOrRoom,
|
|
8
8
|
useZoneSelected,
|
|
@@ -27,7 +27,7 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
27
27
|
import { ArrowDown, ArrowUp, Plus, X } from 'lucide-react'
|
|
28
28
|
import { useEffect, useState } from 'react'
|
|
29
29
|
import { formatLayerThickness, parseLayerThickness } from '../../../lib/layer-thickness'
|
|
30
|
-
import type { LinearUnit } from '
|
|
30
|
+
import type { LinearUnit } from '@pascal-app/core'
|
|
31
31
|
import { cn } from '../../../lib/utils'
|
|
32
32
|
import {
|
|
33
33
|
saveProjectWallType,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type AnyNode,
|
|
5
5
|
buildUnitReport,
|
|
6
6
|
polygonInteriorPoint,
|
|
7
|
+
roomFloorElevation,
|
|
7
8
|
sceneRegistry,
|
|
8
9
|
useLocale,
|
|
9
10
|
usePascalI18n,
|
|
@@ -15,7 +16,6 @@ import { useCallback, useEffect, useMemo } from 'react'
|
|
|
15
16
|
import { Vector3 } from 'three'
|
|
16
17
|
import { useShallow } from 'zustand/react/shallow'
|
|
17
18
|
import { resolveRoomAssemblyHeights } from '../../lib/room-assembly-overlay'
|
|
18
|
-
import { roomFloorElevation } from '../../lib/room-handle-drag'
|
|
19
19
|
import { describeZoneOutline, type RoomSelectionRecord } from '../../lib/room-selection'
|
|
20
20
|
import {
|
|
21
21
|
selectViewerRoom,
|