@pascal-app/editor 0.9.1 → 0.9.2
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 +12 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
- package/src/components/editor/bake-exporter.tsx +47 -0
- package/src/components/editor/custom-camera-controls.tsx +63 -25
- package/src/components/editor/editor-layout-mobile.tsx +5 -0
- package/src/components/editor/editor-layout-v2.tsx +18 -0
- package/src/components/editor/export-manager.tsx +71 -63
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +36 -3
- package/src/components/editor/first-person/build-collider-world.ts +32 -5
- package/src/components/editor/first-person-controls.tsx +118 -7
- package/src/components/editor/floating-action-menu.tsx +410 -33
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-background-selection.test.ts +58 -0
- package/src/components/editor/floorplan-background-selection.ts +3 -2
- package/src/components/editor/floorplan-panel.tsx +1804 -849
- package/src/components/editor/grid.tsx +166 -39
- package/src/components/editor/group-actions.ts +457 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +102 -64
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +192 -1
- package/src/components/editor/group-transform-shared.ts +240 -7
- package/src/components/editor/handles/handle-arrow.tsx +81 -23
- package/src/components/editor/handles/use-handle-drag.ts +37 -4
- package/src/components/editor/index.tsx +248 -27
- package/src/components/editor/measurement-pill.tsx +62 -22
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +366 -173
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +70 -0
- package/src/components/editor/quick-measurement-hud.tsx +28 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +812 -466
- package/src/components/editor/site-edge-labels.tsx +4 -4
- package/src/components/editor/slab-hole-highlights.tsx +13 -5
- package/src/components/editor/snapshot-capture-overlay.tsx +255 -115
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +136 -43
- package/src/components/editor/use-floorplan-background-placement.ts +113 -45
- package/src/components/editor/use-floorplan-scene-data.ts +5 -5
- package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
- package/src/components/editor/wall-measurement-label.tsx +3 -13
- package/src/components/editor/wall-move-side-handles.tsx +36 -21
- package/src/components/editor/wall-snap-beacon-layer.tsx +160 -6
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +1 -1
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +169 -4
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +300 -59
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +1704 -342
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +419 -60
- package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
- package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-tool.tsx +34 -8
- package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
- package/src/components/tools/fence/fence-drafting.ts +80 -8
- package/src/components/tools/item/move-tool.tsx +17 -13
- package/src/components/tools/item/placement-math.test.ts +13 -1
- package/src/components/tools/item/placement-math.ts +28 -2
- package/src/components/tools/item/placement-strategies.ts +246 -5
- package/src/components/tools/item/placement-types.ts +13 -1
- package/src/components/tools/item/use-draft-node.ts +31 -1
- package/src/components/tools/item/use-placement-coordinator.tsx +761 -121
- package/src/components/tools/registry/move-registry-node-tool.tsx +561 -99
- package/src/components/tools/roof/roof-tool.tsx +320 -94
- package/src/components/tools/select/box-select-state.ts +18 -7
- package/src/components/tools/select/box-select-tool.tsx +176 -40
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
- package/src/components/tools/select/select-candidates.ts +1 -1
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/placement-box.tsx +183 -1
- package/src/components/tools/shared/polygon-editor.tsx +276 -35
- package/src/components/tools/site/site-boundary-editor.tsx +88 -36
- package/src/components/tools/stair/stair-defaults.ts +1 -0
- package/src/components/tools/stair/stair-tool.tsx +82 -11
- package/src/components/tools/tool-manager.tsx +86 -25
- package/src/components/tools/wall/wall-drafting.test.ts +330 -0
- package/src/components/tools/wall/wall-drafting.ts +180 -103
- package/src/components/tools/wall/wall-snap-geometry.test.ts +44 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +67 -6
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +82 -88
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +40 -93
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +2 -4
- package/src/components/ui/action-menu/measurement-control.tsx +188 -0
- package/src/components/ui/action-menu/structure-tools.tsx +22 -13
- package/src/components/ui/action-menu/view-toggles.tsx +42 -90
- package/src/components/ui/command-palette/editor-commands.tsx +2 -2
- package/src/components/ui/command-palette/index.tsx +4 -3
- package/src/components/ui/controls/material-paint-panel.tsx +86 -17
- package/src/components/ui/controls/material-picker.tsx +83 -152
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +118 -44
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/floating-level-selector.tsx +1 -1
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +227 -14
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/node-display.ts +17 -17
- package/src/components/ui/panels/panel-manager.tsx +22 -13
- package/src/components/ui/panels/panel-wrapper.tsx +24 -3
- package/src/components/ui/panels/parametric-inspector.tsx +15 -2
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +71 -40
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +60 -6
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +40 -17
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +42 -28
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer-overlay.tsx +85 -10
- package/src/components/viewer-zone-system.tsx +6 -1
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +54 -7
- package/src/hooks/use-ceiling-events.ts +3 -2
- package/src/hooks/use-drag-action.ts +4 -1
- package/src/hooks/use-keyboard.ts +385 -40
- package/src/index.tsx +219 -6
- package/src/lib/active-placement-surface.ts +35 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +192 -0
- package/src/lib/direct-manipulation.ts +109 -0
- package/src/lib/editor-api.ts +23 -35
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +18 -12
- package/src/lib/floorplan/floorplan-export.tsx +364 -0
- package/src/lib/floorplan/geometry.ts +53 -0
- package/src/lib/floorplan/index.ts +3 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/fresh-planar-placement.test.ts +240 -3
- package/src/lib/fresh-planar-placement.ts +68 -1
- package/src/lib/glb-export.test.ts +441 -0
- package/src/lib/glb-export.ts +874 -0
- package/src/lib/history.ts +9 -0
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +51 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +211 -0
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +36 -49
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +183 -0
- package/src/lib/measurements.ts +199 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/planar-cursor-placement.test.ts +57 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +6 -6
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +196 -0
- package/src/lib/scene-clipboard.ts +73 -8
- package/src/lib/scene.ts +28 -5
- package/src/lib/selection-routing.test.ts +298 -0
- package/src/lib/selection-routing.ts +175 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +50 -13
- package/src/lib/sfx-player.test.ts +124 -0
- package/src/lib/sfx-player.ts +111 -66
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +138 -0
- package/src/lib/snapping-mode.ts +179 -0
- package/src/lib/stair-duplication.ts +4 -4
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/world-grid-snap.ts +37 -5
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-editor.tsx +484 -114
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +21 -0
- package/src/store/use-floorplan-draft-preview.ts +101 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-interaction-scope.test.ts +186 -0
- package/src/store/use-interaction-scope.ts +132 -0
- package/src/store/use-measurement-draft.test.ts +530 -0
- package/src/store/use-measurement-draft.ts +543 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-placement-preview.ts +11 -3
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +43 -0
- package/src/store/use-wall-snap-indicator.ts +2 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/group-move-handle.tsx +0 -316
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/lib/level-name.ts +0 -11
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { useScene } from '@pascal-app/core'
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
5
|
+
import {
|
|
6
|
+
lingoUnitSpec,
|
|
7
|
+
measurementHint,
|
|
8
|
+
parseMeasurement,
|
|
9
|
+
} from '../../../lib/measurement-parser'
|
|
10
|
+
import { useLinearDisplay } from '../../../lib/use-linear-display'
|
|
5
11
|
import { cn } from '../../../lib/utils'
|
|
6
12
|
|
|
7
13
|
interface SliderControlProps {
|
|
@@ -59,10 +65,17 @@ export function SliderControl({
|
|
|
59
65
|
unit = '',
|
|
60
66
|
restoreOnCommit = true,
|
|
61
67
|
}: SliderControlProps) {
|
|
68
|
+
// Display/storage conversion so the value honors the metric/imperial toggle.
|
|
69
|
+
// `value`, `onChange`, `onCommit`, `min`/`max`/`clamp` are always in the
|
|
70
|
+
// stored unit (meters for `unit === 'm'`); the step, drag deltas, text field
|
|
71
|
+
// and rendered number are in the DISPLAY unit (feet when imperial). For
|
|
72
|
+
// metric and non-length units these conversions are the identity.
|
|
73
|
+
const { isImperial, displayUnit, toDisplay, toStored } = useLinearDisplay(unit, precision)
|
|
74
|
+
|
|
62
75
|
const [isEditing, setIsEditing] = useState(false)
|
|
63
76
|
const [isDragging, setIsDragging] = useState(false)
|
|
64
77
|
const [isHovered, setIsHovered] = useState(false)
|
|
65
|
-
const [inputValue, setInputValue] = useState(value.toFixed(precision))
|
|
78
|
+
const [inputValue, setInputValue] = useState(toDisplay(value).toFixed(precision))
|
|
66
79
|
|
|
67
80
|
const dragRef = useRef<{
|
|
68
81
|
// Original value at drag start — preserved across modifier re-anchors so
|
|
@@ -80,12 +93,23 @@ export function SliderControl({
|
|
|
80
93
|
valueRef.current = value
|
|
81
94
|
|
|
82
95
|
const clamp = useCallback((val: number) => Math.min(Math.max(val, min), max), [min, max])
|
|
96
|
+
// Apply a signed display-unit delta to a stored value, rounding in the
|
|
97
|
+
// display unit and clamping in the stored unit.
|
|
98
|
+
const applyDisplayDelta = useCallback(
|
|
99
|
+
(storedValue: number, displayDelta: number, displayStep: number) =>
|
|
100
|
+
clamp(
|
|
101
|
+
toStored(
|
|
102
|
+
Number.parseFloat((toDisplay(storedValue) + displayDelta).toFixed(stepPrecision(displayStep))),
|
|
103
|
+
),
|
|
104
|
+
),
|
|
105
|
+
[clamp, toDisplay, toStored],
|
|
106
|
+
)
|
|
83
107
|
|
|
84
108
|
useEffect(() => {
|
|
85
109
|
if (!isEditing) {
|
|
86
|
-
setInputValue(value.toFixed(precision))
|
|
110
|
+
setInputValue(toDisplay(value).toFixed(precision))
|
|
87
111
|
}
|
|
88
|
-
}, [value, precision, isEditing])
|
|
112
|
+
}, [value, precision, isEditing, toDisplay])
|
|
89
113
|
|
|
90
114
|
// Wheel support on the label
|
|
91
115
|
useEffect(() => {
|
|
@@ -96,14 +120,13 @@ export function SliderControl({
|
|
|
96
120
|
e.preventDefault()
|
|
97
121
|
const direction = e.deltaY < 0 ? 1 : -1
|
|
98
122
|
const s = getAdjustedStep(step, e)
|
|
99
|
-
const
|
|
100
|
-
const final = Number.parseFloat(newValue.toFixed(stepPrecision(s)))
|
|
123
|
+
const final = applyDisplayDelta(valueRef.current, direction * s, s)
|
|
101
124
|
if (final !== valueRef.current) onChange(final)
|
|
102
125
|
onCommit?.(final)
|
|
103
126
|
}
|
|
104
127
|
el.addEventListener('wheel', handleWheel, { passive: false })
|
|
105
128
|
return () => el.removeEventListener('wheel', handleWheel)
|
|
106
|
-
}, [isEditing, step,
|
|
129
|
+
}, [isEditing, step, applyDisplayDelta, onChange, onCommit])
|
|
107
130
|
|
|
108
131
|
// Arrow key support while hovered
|
|
109
132
|
useEffect(() => {
|
|
@@ -115,15 +138,14 @@ export function SliderControl({
|
|
|
115
138
|
if (direction !== 0) {
|
|
116
139
|
e.preventDefault()
|
|
117
140
|
const s = getAdjustedStep(step, e)
|
|
118
|
-
const
|
|
119
|
-
const final = Number.parseFloat(newValue.toFixed(stepPrecision(s)))
|
|
141
|
+
const final = applyDisplayDelta(valueRef.current, direction * s, s)
|
|
120
142
|
if (final !== valueRef.current) onChange(final)
|
|
121
143
|
onCommit?.(final)
|
|
122
144
|
}
|
|
123
145
|
}
|
|
124
146
|
window.addEventListener('keydown', handleKeyDown)
|
|
125
147
|
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
126
|
-
}, [isHovered, isEditing, step,
|
|
148
|
+
}, [isHovered, isEditing, step, applyDisplayDelta, onChange, onCommit])
|
|
127
149
|
|
|
128
150
|
const handleLabelPointerDown = useCallback(
|
|
129
151
|
(e: React.PointerEvent<HTMLDivElement>) => {
|
|
@@ -160,15 +182,13 @@ export function SliderControl({
|
|
|
160
182
|
const dx = e.clientX - anchorX
|
|
161
183
|
const s = step * multiplier
|
|
162
184
|
// 4 px per step at default sensitivity
|
|
163
|
-
const newValue =
|
|
164
|
-
Number.parseFloat((anchorValue + (dx / 4) * s).toFixed(stepPrecision(s))),
|
|
165
|
-
)
|
|
185
|
+
const newValue = applyDisplayDelta(anchorValue, (dx / 4) * s, s)
|
|
166
186
|
if (newValue !== valueRef.current) {
|
|
167
187
|
valueRef.current = newValue
|
|
168
188
|
onChange(newValue)
|
|
169
189
|
}
|
|
170
190
|
},
|
|
171
|
-
[step,
|
|
191
|
+
[step, applyDisplayDelta, onChange],
|
|
172
192
|
)
|
|
173
193
|
|
|
174
194
|
const handleLabelPointerUp = useCallback(
|
|
@@ -195,49 +215,65 @@ export function SliderControl({
|
|
|
195
215
|
|
|
196
216
|
const handleValueClick = useCallback(() => {
|
|
197
217
|
setIsEditing(true)
|
|
198
|
-
setInputValue(value.toFixed(precision))
|
|
199
|
-
}, [value, precision])
|
|
218
|
+
setInputValue(toDisplay(value).toFixed(precision))
|
|
219
|
+
}, [value, precision, toDisplay])
|
|
200
220
|
|
|
201
221
|
const submitValue = useCallback(() => {
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
222
|
+
const spec = lingoUnitSpec(unit)
|
|
223
|
+
let stored = spec
|
|
224
|
+
? parseMeasurement(inputValue, spec, {
|
|
225
|
+
bareUnit: isImperial ? 'ft' : spec.unitId,
|
|
226
|
+
system: isImperial ? 'us' : 'metric',
|
|
227
|
+
})
|
|
228
|
+
: null
|
|
229
|
+
if (stored === null) {
|
|
230
|
+
// Fallback: a bare number typed in the DISPLAY unit → convert to stored.
|
|
231
|
+
const numValue = Number.parseFloat(inputValue)
|
|
232
|
+
stored = Number.isFinite(numValue) ? toStored(numValue) : null
|
|
233
|
+
}
|
|
234
|
+
if (stored === null) {
|
|
235
|
+
setInputValue(toDisplay(value).toFixed(precision))
|
|
205
236
|
} else {
|
|
206
|
-
const nextValue = clamp(Number.parseFloat(
|
|
237
|
+
const nextValue = clamp(toStored(Number.parseFloat(toDisplay(stored).toFixed(precision))))
|
|
207
238
|
onChange(nextValue)
|
|
208
239
|
onCommit?.(nextValue)
|
|
209
240
|
}
|
|
210
241
|
setIsEditing(false)
|
|
211
|
-
}, [inputValue, onChange, onCommit, clamp, precision, value])
|
|
242
|
+
}, [inputValue, unit, isImperial, onChange, onCommit, clamp, precision, value, toDisplay, toStored])
|
|
243
|
+
|
|
244
|
+
const spec = lingoUnitSpec(unit)
|
|
245
|
+
const hint =
|
|
246
|
+
isEditing && spec
|
|
247
|
+
? measurementHint(inputValue, spec, {
|
|
248
|
+
bareUnit: isImperial ? 'ft' : spec.unitId,
|
|
249
|
+
system: isImperial ? 'us' : 'metric',
|
|
250
|
+
displayUnit: isImperial ? 'ft' : spec.unitId,
|
|
251
|
+
precision,
|
|
252
|
+
clamp,
|
|
253
|
+
})
|
|
254
|
+
: null
|
|
212
255
|
|
|
213
256
|
const handleInputKeyDown = useCallback(
|
|
214
257
|
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
215
258
|
if (e.key === 'Enter') {
|
|
216
259
|
submitValue()
|
|
217
260
|
} else if (e.key === 'Escape') {
|
|
218
|
-
setInputValue(value.toFixed(precision))
|
|
261
|
+
setInputValue(toDisplay(value).toFixed(precision))
|
|
219
262
|
setIsEditing(false)
|
|
220
|
-
} else if (e.key === 'ArrowUp') {
|
|
263
|
+
} else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
221
264
|
e.preventDefault()
|
|
265
|
+
const direction = e.key === 'ArrowUp' ? 1 : -1
|
|
222
266
|
const adjustedStep = getAdjustedStep(step, e)
|
|
223
|
-
const newV =
|
|
224
|
-
Number.parseFloat((value + adjustedStep).toFixed(stepPrecision(adjustedStep))),
|
|
225
|
-
)
|
|
267
|
+
const newV = applyDisplayDelta(value, direction * adjustedStep, adjustedStep)
|
|
226
268
|
onChange(newV)
|
|
227
|
-
setInputValue(newV.toFixed(precision))
|
|
228
|
-
} else if (e.key === 'ArrowDown') {
|
|
229
|
-
e.preventDefault()
|
|
230
|
-
const adjustedStep = getAdjustedStep(step, e)
|
|
231
|
-
const newV = clamp(
|
|
232
|
-
Number.parseFloat((value - adjustedStep).toFixed(stepPrecision(adjustedStep))),
|
|
233
|
-
)
|
|
234
|
-
onChange(newV)
|
|
235
|
-
setInputValue(newV.toFixed(precision))
|
|
269
|
+
setInputValue(toDisplay(newV).toFixed(precision))
|
|
236
270
|
}
|
|
237
271
|
},
|
|
238
|
-
[submitValue, value, precision, step,
|
|
272
|
+
[submitValue, value, precision, step, applyDisplayDelta, onChange, toDisplay],
|
|
239
273
|
)
|
|
240
274
|
|
|
275
|
+
const displayValue = toDisplay(value)
|
|
276
|
+
|
|
241
277
|
return (
|
|
242
278
|
<div
|
|
243
279
|
className={cn(
|
|
@@ -279,6 +315,11 @@ export function SliderControl({
|
|
|
279
315
|
<div className="flex items-center text-xs">
|
|
280
316
|
{isEditing ? (
|
|
281
317
|
<>
|
|
318
|
+
{hint && (
|
|
319
|
+
<span className="mr-1 shrink-0 whitespace-nowrap text-[10px] text-muted-foreground/50 tabular-nums">
|
|
320
|
+
{hint}
|
|
321
|
+
</span>
|
|
322
|
+
)}
|
|
282
323
|
<input
|
|
283
324
|
autoFocus
|
|
284
325
|
className="w-14 bg-transparent p-0 text-right font-mono text-foreground outline-none selection:bg-primary/30"
|
|
@@ -288,7 +329,16 @@ export function SliderControl({
|
|
|
288
329
|
type="text"
|
|
289
330
|
value={inputValue}
|
|
290
331
|
/>
|
|
291
|
-
{
|
|
332
|
+
{displayUnit && (
|
|
333
|
+
<span
|
|
334
|
+
className={cn(
|
|
335
|
+
'ml-[1px] transition-opacity duration-150',
|
|
336
|
+
hint ? 'opacity-0' : 'text-muted-foreground/40',
|
|
337
|
+
)}
|
|
338
|
+
>
|
|
339
|
+
{displayUnit}
|
|
340
|
+
</span>
|
|
341
|
+
)}
|
|
292
342
|
</>
|
|
293
343
|
) : (
|
|
294
344
|
<div
|
|
@@ -296,9 +346,9 @@ export function SliderControl({
|
|
|
296
346
|
onClick={handleValueClick}
|
|
297
347
|
>
|
|
298
348
|
<span className="font-mono tabular-nums tracking-tight" suppressHydrationWarning>
|
|
299
|
-
{Number(
|
|
349
|
+
{Number(displayValue.toFixed(precision)).toFixed(precision)}
|
|
300
350
|
</span>
|
|
301
|
-
{
|
|
351
|
+
{displayUnit && <span className="ml-[1px] text-muted-foreground">{displayUnit}</span>}
|
|
302
352
|
</div>
|
|
303
353
|
)}
|
|
304
354
|
</div>
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
buildLevelDuplicateCreateOps,
|
|
42
42
|
type LevelDuplicatePreset,
|
|
43
43
|
} from '../../lib/level-duplication'
|
|
44
|
-
import { getDefaultLevelName, getLevelDisplayName } from '
|
|
44
|
+
import { getDefaultLevelName, getLevelDisplayName } from '@pascal-app/core'
|
|
45
45
|
import { deleteLevelWithFallbackSelection } from '../../lib/level-selection'
|
|
46
46
|
import {
|
|
47
47
|
getEditorClipboardSnapshot,
|
|
@@ -1,32 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
2
2
|
|
|
3
3
|
interface BuildingHelperProps {
|
|
4
4
|
showRotate?: boolean
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
// Rotate is one hint with both keys (R / T) — never two separate
|
|
8
|
+
// counterclockwise / clockwise rows — to match every other placement helper.
|
|
7
9
|
export function BuildingHelper({ showRotate }: BuildingHelperProps) {
|
|
8
10
|
return (
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<div className="flex items-center gap-2 text-sm">
|
|
17
|
-
<ShortcutToken value="R" />
|
|
18
|
-
<span className="text-muted-foreground">Rotate counterclockwise</span>
|
|
19
|
-
</div>
|
|
20
|
-
<div className="flex items-center gap-2 text-sm">
|
|
21
|
-
<ShortcutToken value="T" />
|
|
22
|
-
<span className="text-muted-foreground">Rotate clockwise</span>
|
|
23
|
-
</div>
|
|
24
|
-
</>
|
|
25
|
-
)}
|
|
26
|
-
<div className="flex items-center gap-2 text-sm">
|
|
27
|
-
<ShortcutToken value="Esc" />
|
|
28
|
-
<span className="text-muted-foreground">Cancel</span>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
11
|
+
<ContextualHelperPanel
|
|
12
|
+
hints={[
|
|
13
|
+
{ keys: ['Left click'], label: 'Place building' },
|
|
14
|
+
...(showRotate ? [{ keys: ['R', 'T'], label: 'Rotate' }] : []),
|
|
15
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
16
|
+
]}
|
|
17
|
+
/>
|
|
31
18
|
)
|
|
32
19
|
}
|