@pascal-app/editor 1.0.4 → 1.0.6
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/README.md +106 -0
- package/package.json +40 -38
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/bake-thumbnail.tsx +6 -15
- package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
- package/src/components/editor/construction-reveal-audio.tsx +40 -0
- package/src/components/editor/custom-camera-controls.tsx +81 -8
- package/src/components/editor/editor-layout-mobile.tsx +1 -1
- package/src/components/editor/editor-layout-v2.tsx +1 -1
- package/src/components/editor/export-manager.tsx +14 -19
- package/src/components/editor/first-person/build-collider-world.test.ts +269 -0
- package/src/components/editor/first-person/build-collider-world.ts +22 -5
- package/src/components/editor/first-person/ground-holes.test.ts +128 -0
- package/src/components/editor/first-person/ground-holes.ts +130 -0
- package/src/components/editor/first-person/mechanism-targets.test.ts +89 -2
- package/src/components/editor/first-person/mechanism-targets.ts +52 -0
- package/src/components/editor/first-person-controls.tsx +19 -11
- package/src/components/editor/floor-aware-camera-controls.test.ts +24 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +72 -23
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/group-actions.ts +2 -0
- package/src/components/editor/group-move-3d.ts +2 -0
- package/src/components/editor/group-rotate-handle.tsx +2 -0
- package/src/components/editor/group-transform-shared.test.ts +460 -0
- package/src/components/editor/index.tsx +166 -79
- package/src/components/editor/riser-diagram-panel.tsx +14 -7
- package/src/components/editor/room-highlight.tsx +13 -5
- package/src/components/editor/room-labels.test.ts +621 -128
- package/src/components/editor/room-labels.tsx +1090 -190
- package/src/components/editor/selection-manager.tsx +38 -117
- package/src/components/editor/slab-hole-highlights.tsx +2 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +3 -2
- package/src/components/editor/snapshot-capture.test.ts +69 -58
- package/src/components/editor/snapshot-capture.ts +7 -1
- package/src/components/editor/thumbnail-generator.test.ts +350 -4
- package/src/components/editor/thumbnail-generator.tsx +128 -54
- package/src/components/editor/use-floorplan-background-placement.ts +3 -1
- package/src/components/editor/views/plugins-view.tsx +1 -8
- package/src/components/editor/views/view-bar.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +140 -17
- package/src/components/editor/wall-snap-beacon-layer.tsx +5 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +3 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +1 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +29 -7
- package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
- package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
- package/src/components/point-ask/bubble.tsx +538 -0
- package/src/components/point-ask/hover-chip.tsx +91 -0
- package/src/components/point-ask/mode-pill.tsx +92 -0
- package/src/components/point-ask/optional-motion.ts +22 -0
- package/src/components/point-ask/outline-corners.tsx +93 -0
- package/src/components/point-ask/pins.tsx +711 -0
- package/src/components/point-ask/point-ask-layer.tsx +83 -0
- package/src/components/point-ask/point-ask-styles.ts +105 -0
- package/src/components/point-ask/region-marquee-styles.ts +15 -0
- package/src/components/point-ask/region-marquee.tsx +64 -0
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- package/src/components/systems/roof/roof-edit-system.tsx +1 -0
- package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
- package/src/components/tools/distribution/connection-compatibility.ts +43 -0
- package/src/components/tools/distribution/distribution-public.ts +9 -0
- package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
- package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
- package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
- package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
- package/src/components/tools/distribution/draw-alignment.ts +35 -0
- package/src/components/tools/distribution/ports.test.ts +56 -0
- package/src/components/tools/distribution/ports.ts +378 -0
- package/src/components/tools/distribution/run-cursor.test.ts +135 -0
- package/src/components/tools/distribution/run-cursor.ts +36 -0
- package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
- package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
- package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
- package/src/components/tools/distribution/run-port-snap.ts +25 -0
- package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
- package/src/components/tools/distribution/run-screen-direction.ts +30 -0
- package/src/components/tools/elevator/elevator-tool.tsx +3 -6
- package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
- package/src/components/tools/item/placement-history.test.tsx +38 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +3 -6
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- package/src/components/tools/item/use-draft-node.test.tsx +94 -94
- package/src/components/tools/item/use-placement-coordinator.tsx +55 -49
- package/src/components/tools/registry/item-surface-move.test.ts +62 -63
- package/src/components/tools/registry/item-surface-move.ts +2 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
- package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +63 -29
- package/src/components/tools/tool-manager.tsx +14 -5
- package/src/components/tools/wall/wall-drafting.test.ts +170 -57
- package/src/components/ui/action-menu/action-button.tsx +6 -1
- package/src/components/ui/action-menu/camera-actions.tsx +7 -3
- package/src/components/ui/action-menu/control-modes.tsx +13 -7
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
- package/src/components/ui/action-menu/index.tsx +56 -33
- package/src/components/ui/action-menu/measurement-control.tsx +11 -9
- package/src/components/ui/action-menu/placement.ts +20 -0
- package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
- package/src/components/ui/action-menu/view-toggles.tsx +47 -42
- package/src/components/ui/build-panel/build-panel.tsx +10 -3
- package/src/components/ui/command-palette/editor-commands.tsx +3 -12
- package/src/components/ui/command-palette/index.tsx +45 -15
- package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
- package/src/components/ui/controls/action-button.tsx +5 -2
- package/src/components/ui/controls/metric-control.tsx +1 -1
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +1 -1
- package/src/components/ui/controls/slider-control.tsx +1 -1
- package/src/components/ui/controls/toggle-control.tsx +3 -3
- package/src/components/ui/controls/tool-options-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +150 -45
- package/src/components/ui/helpers/contextual-helper-panel.tsx +32 -7
- package/src/components/ui/helpers/helper-manager.test.tsx +54 -2
- package/src/components/ui/helpers/helper-manager.tsx +7 -1
- package/src/components/ui/helpers/item-helper.tsx +9 -1
- package/src/components/ui/panels/collections/collections-popover.tsx +8 -8
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +6 -3
- package/src/components/ui/panels/multi-selection-panel.tsx +120 -3
- package/src/components/ui/panels/panel-wrapper.tsx +53 -27
- package/src/components/ui/panels/parametric-field-control.tsx +2 -2
- package/src/components/ui/panels/parametric-inspector.tsx +3 -3
- package/src/components/ui/panels/room-panel.tsx +65 -0
- package/src/components/ui/primitives/badge.tsx +8 -0
- package/src/components/ui/primitives/sidebar.tsx +1 -1
- package/src/components/ui/right-stack.tsx +21 -2
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +3 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
- package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +107 -23
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +5 -12
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -10
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -2
- package/src/components/ui/sidebar/tab-bar.tsx +4 -4
- package/src/components/viewer/viewer-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-rooms.tsx +16 -0
- package/src/hooks/use-auto-save.integration.test.tsx +96 -38
- package/src/hooks/use-auto-save.ts +26 -1
- package/src/hooks/use-ceiling-events.test.ts +123 -118
- package/src/hooks/use-grid-events.test.ts +105 -105
- package/src/hooks/use-keyboard.test.ts +65 -16
- package/src/hooks/use-keyboard.ts +29 -12
- package/src/hooks/use-placement-notice.test.ts +24 -1
- package/src/hooks/use-placement-notice.ts +37 -6
- package/src/index.tsx +71 -24
- package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
- package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
- package/src/lib/add-storey.test.ts +177 -0
- package/src/lib/add-storey.ts +59 -0
- package/src/lib/below-grade.test.tsx +171 -0
- package/src/lib/below-grade.ts +133 -0
- package/src/lib/camera-facing.test.ts +24 -0
- package/src/lib/camera-facing.ts +24 -0
- package/src/lib/direct-manipulation.ts +2 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.ts +9 -2
- package/src/lib/editor-views.ts +35 -1
- package/src/lib/export-texture-utils.ts +2 -4
- package/src/lib/floor-foundation-support.test.ts +1 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +31 -16
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +39 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +4 -0
- package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
- package/src/lib/floorplan/sheet-export.test.ts +24 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +34 -45
- package/src/lib/floorplan/site-plan/coverage.ts +2 -4
- package/src/lib/floorplan/site-plan/front-edge.ts +2 -1
- package/src/lib/floorplan/site-plan/geometry.test.ts +9 -15
- package/src/lib/floorplan/site-plan/geometry.ts +1 -416
- package/src/lib/floorplan/site-plan/index.ts +0 -16
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +117 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -2
- package/src/lib/floorplan/site-plan/site-parts.ts +14 -106
- package/src/lib/floorplan/site-plan/yards.test.ts +2 -1
- package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
- package/src/lib/follow-pascal-exterior.test.ts +171 -0
- package/src/lib/follow-pascal-exterior.ts +39 -0
- package/src/lib/follow-pascal-presentation.test.ts +203 -0
- package/src/lib/follow-pascal-presentation.ts +129 -0
- package/src/lib/follow-pascal-sight.ts +84 -0
- package/src/lib/follow-pascal-targets.test.ts +406 -0
- package/src/lib/follow-pascal-targets.ts +198 -0
- package/src/lib/follow-pascal.test.ts +350 -0
- package/src/lib/follow-pascal.ts +284 -0
- package/src/lib/fresh-planar-placement.test.ts +112 -0
- package/src/lib/fresh-planar-placement.ts +57 -3
- package/src/lib/geometry-script/author.ts +6 -1
- package/src/lib/geometry-script/client.ts +8 -1
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +1 -0
- package/src/lib/glb-export.test.ts +187 -162
- package/src/lib/glb-export.ts +4 -1
- package/src/lib/history.test.ts +12 -7
- package/src/lib/interaction/scope.ts +4 -0
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- package/src/lib/lot/clean-ring.test.ts +2 -1
- package/src/lib/lot/drop-in.ts +9 -24
- package/src/lib/lot/lot-patch.test.ts +1 -1
- package/src/lib/lot/lot-patch.ts +2 -2
- package/src/lib/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -0
- package/src/lib/orbit-floor-clearance.test.ts +36 -0
- package/src/lib/orbit-floor-clearance.ts +48 -0
- package/src/lib/paint-pass-through.test.ts +11 -0
- package/src/lib/paint-pass-through.ts +4 -2
- package/src/lib/paint-scope.ts +1 -0
- package/src/lib/parametric-node-update.ts +16 -7
- package/src/lib/plugin-chat.ts +15 -0
- package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
- package/src/lib/point-ask/__fixtures__/house.ts +81 -0
- package/src/lib/point-ask/ask-created.test.ts +195 -0
- package/src/lib/point-ask/ask-flow.test.ts +380 -0
- package/src/lib/point-ask/build-context.test.ts +173 -0
- package/src/lib/point-ask/build-context.ts +94 -0
- package/src/lib/point-ask/capture-crop.ts +129 -0
- package/src/lib/point-ask/choreography.ts +69 -0
- package/src/lib/point-ask/compare.test.ts +145 -0
- package/src/lib/point-ask/compare.ts +73 -0
- package/src/lib/point-ask/describe-target.test.ts +165 -0
- package/src/lib/point-ask/describe-target.ts +234 -0
- package/src/lib/point-ask/dictation.ts +79 -0
- package/src/lib/point-ask/edge-marker.test.ts +61 -0
- package/src/lib/point-ask/edge-marker.ts +27 -0
- package/src/lib/point-ask/frame-loop.ts +25 -0
- package/src/lib/point-ask/history-marks.test.ts +94 -0
- package/src/lib/point-ask/history-marks.ts +39 -0
- package/src/lib/point-ask/host-api.ts +32 -0
- package/src/lib/point-ask/outline-box.test.ts +30 -0
- package/src/lib/point-ask/outline-box.ts +11 -0
- package/src/lib/point-ask/overlay-metrics.ts +6 -0
- package/src/lib/point-ask/pick.test.ts +220 -0
- package/src/lib/point-ask/pick.ts +121 -0
- package/src/lib/point-ask/pin-machine.test.ts +494 -0
- package/src/lib/point-ask/pin-machine.ts +217 -0
- package/src/lib/point-ask/placement.test.ts +203 -0
- package/src/lib/point-ask/placement.ts +141 -0
- package/src/lib/point-ask/pointer-tracker.ts +34 -0
- package/src/lib/point-ask/projector.ts +83 -0
- package/src/lib/point-ask/region.test.ts +157 -0
- package/src/lib/point-ask/region.ts +136 -0
- package/src/lib/point-ask/retry.test.ts +217 -0
- package/src/lib/point-ask/retry.ts +37 -0
- package/src/lib/point-ask/scene-commit.test.ts +312 -0
- package/src/lib/point-ask/scene-commit.ts +32 -0
- package/src/lib/point-ask/session.ts +1391 -0
- package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
- package/src/lib/point-ask/spring-keyframes.ts +38 -0
- package/src/lib/point-ask/spring.test.ts +63 -0
- package/src/lib/point-ask/spring.ts +107 -0
- package/src/lib/point-ask/suggestions.test.ts +80 -0
- package/src/lib/point-ask/suggestions.ts +125 -0
- package/src/lib/point-ask/target-scope.test.ts +622 -0
- package/src/lib/point-ask/target-scope.ts +338 -0
- package/src/lib/point-ask/types.ts +48 -0
- package/src/lib/portable-export.test.ts +34 -1
- package/src/lib/portable-export.ts +119 -53
- package/src/lib/print-feature-thickness.ts +10 -1
- package/src/lib/print-roof-solids.test.ts +39 -1
- package/src/lib/print-roof-solids.ts +43 -10
- package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
- package/src/lib/room-assembly-overlay.ts +2 -3
- package/src/lib/room-selection.test.ts +45 -1
- package/src/lib/scene-clipboard.test.ts +246 -1
- package/src/lib/scene-clipboard.ts +44 -14
- package/src/lib/selection-routing.ts +1 -1
- package/src/lib/sfx-bus.ts +13 -0
- package/src/lib/sfx-player.test.ts +70 -9
- package/src/lib/sfx-player.ts +6 -0
- package/src/lib/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.ts +2 -1
- package/src/lib/viewer-selection.ts +22 -11
- package/src/lib/wall-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -0
- package/src/lib/window-changes.ts +35 -0
- package/src/store/use-audio.test.ts +146 -0
- package/src/store/use-audio.tsx +127 -3
- package/src/store/use-construction-reveal.test.ts +173 -0
- package/src/store/use-construction-reveal.ts +145 -0
- package/src/store/use-corner-snap-hint.test.ts +24 -0
- package/src/store/use-corner-snap-hint.ts +47 -0
- package/src/store/use-follow-pascal.test.ts +62 -0
- package/src/store/use-follow-pascal.ts +53 -0
- package/src/store/use-point-ask.ts +247 -0
- package/src/store/use-stair-build-preview.ts +25 -5
- package/src/store/view-layout.test.ts +27 -1
- package/src/store/view-layout.ts +4 -1
- package/src/test-utils/render-current-markup.tsx +16 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
- package/src/components/tools/stair/stair-click-guard.ts +0 -72
- package/src/components/tools/stair/stair-defaults.ts +0 -15
- package/src/components/tools/stair/stair-tool.tsx +0 -682
|
@@ -274,7 +274,9 @@ export function useFloorplanBackgroundPlacement({
|
|
|
274
274
|
)
|
|
275
275
|
const fenceGridBase = worldGridSnap(planPoint, fenceStep)
|
|
276
276
|
const fenceLocked =
|
|
277
|
-
|
|
277
|
+
event.altKey ||
|
|
278
|
+
fenceSnapped[0] !== fenceGridBase[0] ||
|
|
279
|
+
fenceSnapped[1] !== fenceGridBase[1]
|
|
278
280
|
const snappedPoint = fenceLocked
|
|
279
281
|
? fenceSnapped
|
|
280
282
|
: alignFloorplanDraftPoint(fenceSnapped, {
|
|
@@ -30,6 +30,7 @@ import { openSidebarPanel } from '../../../lib/sidebar-panel'
|
|
|
30
30
|
import { cn } from '../../../lib/utils'
|
|
31
31
|
import useEditor from '../../../store/use-editor'
|
|
32
32
|
import { IconRefGlyph } from '../../ui/icon-ref'
|
|
33
|
+
import { Badge } from '../../ui/primitives/badge'
|
|
33
34
|
import { Button } from '../../ui/primitives/button'
|
|
34
35
|
import { PLUGINS_VIEW_ID } from './use-editor-views'
|
|
35
36
|
|
|
@@ -101,14 +102,6 @@ function PluginIcon({ panel, size }: { panel: EditorHostPanel; size: 'md' | 'lg'
|
|
|
101
102
|
)
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
function Badge({ label }: { label: string }) {
|
|
105
|
-
return (
|
|
106
|
-
<span className="rounded-[5px] border border-border px-1.5 py-px font-mono font-medium text-[10px] text-muted-foreground uppercase tracking-wide">
|
|
107
|
-
{label}
|
|
108
|
-
</span>
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
105
|
function StatusLine({ entry, projectName }: { entry: PluginEntry; projectName?: string }) {
|
|
113
106
|
return (
|
|
114
107
|
<span className="flex min-w-0 items-center gap-1.5 text-muted-foreground text-xs">
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { afterEach, expect, test } from 'bun:test'
|
|
2
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
3
|
+
import { renderCurrentMarkup } from '../../../test-utils/render-current-markup'
|
|
4
|
+
import { GroundModeToggle } from './view-bar'
|
|
5
|
+
|
|
6
|
+
const saved = useViewer.getState()
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
useViewer.setState(saved, true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('over a level at grade the bar says nothing about the ground', () => {
|
|
13
|
+
useViewer.setState({ belowGrade: false })
|
|
14
|
+
expect(renderCurrentMarkup(<GroundModeToggle />)).toBe('')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('over a level below grade the bar says the ground is veiled, the ground on Auto', () => {
|
|
18
|
+
useViewer.setState({ belowGrade: true, gradeStanding: 'below-grade', groundMode: 'auto' })
|
|
19
|
+
const bar = renderCurrentMarkup(<GroundModeToggle />)
|
|
20
|
+
expect(bar).toContain('Below grade · ground veiled')
|
|
21
|
+
expect(bar).toMatch(/aria-pressed="true"[^>]*>Auto</)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('the person who keeps the ground solid is told so', () => {
|
|
25
|
+
useViewer.setState({ belowGrade: true, gradeStanding: 'below-grade', groundMode: 'solid' })
|
|
26
|
+
const bar = renderCurrentMarkup(<GroundModeToggle />)
|
|
27
|
+
expect(bar).toContain('Below grade · ground solid')
|
|
28
|
+
expect(bar).toMatch(/aria-pressed="true"[^>]*>Solid</)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('a level only partly below grade is named as such, its ground veiled where the hill stands', () => {
|
|
32
|
+
useViewer.setState({
|
|
33
|
+
belowGrade: true,
|
|
34
|
+
gradeStanding: 'partly-below-grade',
|
|
35
|
+
groundWindowVeiled: true,
|
|
36
|
+
groundMode: 'auto',
|
|
37
|
+
})
|
|
38
|
+
expect(renderCurrentMarkup(<GroundModeToggle />)).toContain('Partly below grade · ground veiled')
|
|
39
|
+
// From the walk-out side nothing is in the way: the ground reads solid, the toggle stays.
|
|
40
|
+
useViewer.setState({ groundWindowVeiled: false })
|
|
41
|
+
expect(renderCurrentMarkup(<GroundModeToggle />)).toContain('Partly below grade · ground solid')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test("a hillside in the eye's way over a level at grade says only that the ground is veiled", () => {
|
|
45
|
+
useViewer.setState({
|
|
46
|
+
belowGrade: false,
|
|
47
|
+
gradeStanding: 'at-grade',
|
|
48
|
+
groundWindowVeiled: true,
|
|
49
|
+
groundMode: 'auto',
|
|
50
|
+
})
|
|
51
|
+
expect(renderCurrentMarkup(<GroundModeToggle />)).toContain('Ground veiled')
|
|
52
|
+
useViewer.setState({ groundMode: 'solid' })
|
|
53
|
+
expect(renderCurrentMarkup(<GroundModeToggle />)).toContain('Ground solid')
|
|
54
|
+
})
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { type GroundMode, useViewer } from '@pascal-app/viewer'
|
|
4
|
+
import { ArrowLeftRight, Check, Columns2, MoreHorizontal, Pin, PinOff, X } from 'lucide-react'
|
|
5
|
+
import { type ReactNode, useEffect, useRef } from 'react'
|
|
5
6
|
import { useIsMobile } from '../../../hooks/use-mobile'
|
|
6
7
|
import { type EditorViewDescriptor, isSceneView, isViewPinned } from '../../../lib/editor-views'
|
|
7
8
|
import { cn } from '../../../lib/utils'
|
|
@@ -24,8 +25,9 @@ const GROUP = cn(
|
|
|
24
25
|
)
|
|
25
26
|
const SEGMENT =
|
|
26
27
|
'flex items-center justify-center gap-1.5 px-2.5 font-medium text-xs transition-colors'
|
|
27
|
-
const ACTIVE = 'bg-white/10 text-foreground'
|
|
28
|
-
const IDLE =
|
|
28
|
+
const ACTIVE = 'bg-black/6 dark:bg-white/10 text-foreground'
|
|
29
|
+
const IDLE =
|
|
30
|
+
'text-muted-foreground/70 hover:bg-black/5 dark:hover:bg-white/8 hover:text-muted-foreground'
|
|
29
31
|
|
|
30
32
|
/** The views of one pane: its pinned views, the one it shows, and a menu with the rest. */
|
|
31
33
|
export function ViewTabs({ pane, className }: { pane: ViewPaneIndex; className?: string }) {
|
|
@@ -34,23 +36,139 @@ export function ViewTabs({ pane, className }: { pane: ViewPaneIndex; className?:
|
|
|
34
36
|
const pinnedViews = useEditor((s) => s.pinnedViews)
|
|
35
37
|
const current = layout.panes[pane]
|
|
36
38
|
const tabs = views.filter((view) => view.id === current || isViewPinned(view, pinnedViews))
|
|
39
|
+
const activeIndex = tabs.findIndex((view) => view.id === current)
|
|
40
|
+
const tabList = useRef<HTMLDivElement>(null)
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const list = tabList.current
|
|
43
|
+
const tab = list?.children.item(activeIndex)
|
|
44
|
+
if (!list || !(tab instanceof HTMLElement)) return
|
|
45
|
+
const reveal = () => {
|
|
46
|
+
const bounds = list.getBoundingClientRect()
|
|
47
|
+
const target = tab.getBoundingClientRect()
|
|
48
|
+
if (target.right > bounds.right) list.scrollLeft += target.right - bounds.right
|
|
49
|
+
else if (target.left < bounds.left) list.scrollLeft -= bounds.left - target.left
|
|
50
|
+
}
|
|
51
|
+
reveal()
|
|
52
|
+
const observer = new ResizeObserver(reveal)
|
|
53
|
+
observer.observe(list)
|
|
54
|
+
observer.observe(tab)
|
|
55
|
+
return () => observer.disconnect()
|
|
56
|
+
}, [activeIndex])
|
|
37
57
|
|
|
38
58
|
return (
|
|
39
|
-
<div className={cn(GROUP, className)}
|
|
40
|
-
|
|
59
|
+
<div className={cn(GROUP, 'min-w-0 shrink', className)}>
|
|
60
|
+
<div
|
|
61
|
+
aria-label={`Pane ${pane + 1} views`}
|
|
62
|
+
className="flex min-w-0 overflow-x-auto [scrollbar-width:none]"
|
|
63
|
+
role="tablist"
|
|
64
|
+
ref={tabList}
|
|
65
|
+
>
|
|
66
|
+
{tabs.map((view) => (
|
|
67
|
+
<div
|
|
68
|
+
className={cn('group flex shrink-0 items-center', view.id === current ? ACTIVE : IDLE)}
|
|
69
|
+
key={view.id}
|
|
70
|
+
>
|
|
71
|
+
<button
|
|
72
|
+
aria-selected={view.id === current}
|
|
73
|
+
className={cn(
|
|
74
|
+
SEGMENT,
|
|
75
|
+
'outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring',
|
|
76
|
+
view.onClose && 'pr-1',
|
|
77
|
+
)}
|
|
78
|
+
onClick={() => useEditor.getState().setPaneView(pane, view.id)}
|
|
79
|
+
onKeyDown={(event) => {
|
|
80
|
+
if (event.key === 'Delete' && view.onClose) {
|
|
81
|
+
event.preventDefault()
|
|
82
|
+
view.onClose()
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
const offset = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0
|
|
86
|
+
if (!offset && event.key !== 'Home' && event.key !== 'End') return
|
|
87
|
+
event.preventDefault()
|
|
88
|
+
const index = tabs.indexOf(view)
|
|
89
|
+
const target =
|
|
90
|
+
event.key === 'Home'
|
|
91
|
+
? 0
|
|
92
|
+
: event.key === 'End'
|
|
93
|
+
? tabs.length - 1
|
|
94
|
+
: (index + offset + tabs.length) % tabs.length
|
|
95
|
+
const next = tabs[target]
|
|
96
|
+
if (next) {
|
|
97
|
+
useEditor.getState().setPaneView(pane, next.id)
|
|
98
|
+
const buttons = event.currentTarget
|
|
99
|
+
.closest('[role="tablist"]')
|
|
100
|
+
?.querySelectorAll<HTMLButtonElement>('[role="tab"]')
|
|
101
|
+
buttons?.[target]?.focus()
|
|
102
|
+
}
|
|
103
|
+
}}
|
|
104
|
+
tabIndex={view.id === current ? 0 : -1}
|
|
105
|
+
title={view.label}
|
|
106
|
+
role="tab"
|
|
107
|
+
type="button"
|
|
108
|
+
>
|
|
109
|
+
<IconRefGlyph icon={view.icon} size={14} />
|
|
110
|
+
<span className="max-w-36 truncate">{view.label}</span>
|
|
111
|
+
</button>
|
|
112
|
+
{view.onClose && (
|
|
113
|
+
<button
|
|
114
|
+
aria-label={`Close ${view.label}`}
|
|
115
|
+
className="mr-1 flex size-6 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
|
|
116
|
+
onClick={view.onClose}
|
|
117
|
+
type="button"
|
|
118
|
+
>
|
|
119
|
+
<X className="size-3" />
|
|
120
|
+
</button>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
<MoreViewsMenu current={current} pane={pane} views={views} />
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const GROUND_MODES = [
|
|
131
|
+
{ mode: 'auto', label: 'Auto', title: 'Veil the ground over a level below grade' },
|
|
132
|
+
{ mode: 'solid', label: 'Solid', title: 'Keep the ground solid' },
|
|
133
|
+
] as const satisfies ReadonlyArray<{ mode: GroundMode; label: string; title: string }>
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Over a level below grade the ground is veiled so its rooms show, and the bar says so: the
|
|
137
|
+
* person can keep the ground solid instead (Ground: Auto / Solid). The same word covers the
|
|
138
|
+
* ground window — a hillside in the eye's way, veiled only where it hides the house — over
|
|
139
|
+
* a level at grade or only partly below it.
|
|
140
|
+
*/
|
|
141
|
+
export function GroundModeToggle() {
|
|
142
|
+
const belowGrade = useViewer((s) => s.belowGrade)
|
|
143
|
+
const gradeStanding = useViewer((s) => s.gradeStanding)
|
|
144
|
+
const groundWindowVeiled = useViewer((s) => s.groundWindowVeiled)
|
|
145
|
+
const groundMode = useViewer((s) => s.groundMode)
|
|
146
|
+
if (!(belowGrade || groundWindowVeiled)) return null
|
|
147
|
+
const veiled =
|
|
148
|
+
groundMode === 'auto' &&
|
|
149
|
+
((belowGrade && gradeStanding !== 'partly-below-grade') || groundWindowVeiled)
|
|
150
|
+
const ground = veiled ? 'veiled' : 'solid'
|
|
151
|
+
const label =
|
|
152
|
+
gradeStanding === 'partly-below-grade'
|
|
153
|
+
? `Partly below grade · ground ${ground}`
|
|
154
|
+
: gradeStanding === 'below-grade'
|
|
155
|
+
? `Below grade · ground ${ground}`
|
|
156
|
+
: `Ground ${ground}`
|
|
157
|
+
return (
|
|
158
|
+
<div aria-label="Ground" className={GROUP} role="group">
|
|
159
|
+
<span className="flex items-center px-2.5 text-muted-foreground text-xs">{label}</span>
|
|
160
|
+
{GROUND_MODES.map(({ mode, label, title }) => (
|
|
41
161
|
<button
|
|
42
|
-
aria-
|
|
43
|
-
className={cn(SEGMENT,
|
|
44
|
-
key={
|
|
45
|
-
onClick={() =>
|
|
46
|
-
|
|
162
|
+
aria-pressed={groundMode === mode}
|
|
163
|
+
className={cn(SEGMENT, groundMode === mode ? ACTIVE : IDLE)}
|
|
164
|
+
key={mode}
|
|
165
|
+
onClick={() => useViewer.getState().setGroundMode(mode)}
|
|
166
|
+
title={title}
|
|
47
167
|
type="button"
|
|
48
168
|
>
|
|
49
|
-
|
|
50
|
-
<span>{view.label}</span>
|
|
169
|
+
{label}
|
|
51
170
|
</button>
|
|
52
171
|
))}
|
|
53
|
-
<MoreViewsMenu current={current} pane={pane} views={views} />
|
|
54
172
|
</div>
|
|
55
173
|
)
|
|
56
174
|
}
|
|
@@ -109,6 +227,7 @@ export function ViewBarRow({ leading }: { leading?: ReactNode }) {
|
|
|
109
227
|
style={{ left: `${left * 100}%`, width: `${(right - left) * 100}%` }}
|
|
110
228
|
>
|
|
111
229
|
<ViewTabs pane={pane} />
|
|
230
|
+
{isSceneView(layout.panes[pane]) && <GroundModeToggle />}
|
|
112
231
|
{pane === 0 && leading}
|
|
113
232
|
<span className="flex-1" />
|
|
114
233
|
{isLast && !isMobile && <SplitControls />}
|
|
@@ -162,7 +281,11 @@ function MoreViewsMenu({
|
|
|
162
281
|
return (
|
|
163
282
|
<DropdownMenu>
|
|
164
283
|
<DropdownMenuTrigger asChild>
|
|
165
|
-
<button
|
|
284
|
+
<button
|
|
285
|
+
aria-label="More views"
|
|
286
|
+
className={cn(SEGMENT, IDLE, 'shrink-0 px-2')}
|
|
287
|
+
type="button"
|
|
288
|
+
>
|
|
166
289
|
<MoreHorizontal className="h-3.5 w-3.5" />
|
|
167
290
|
</button>
|
|
168
291
|
</DropdownMenuTrigger>
|
|
@@ -179,12 +302,12 @@ function MoreViewsMenu({
|
|
|
179
302
|
<IconRefGlyph icon={view.icon} size={16} />
|
|
180
303
|
<span className="min-w-0 flex-1 truncate">{view.label}</span>
|
|
181
304
|
{view.id === current && <Check className="h-3.5 w-3.5 text-muted-foreground" />}
|
|
182
|
-
{!isSceneView(view.id) && (
|
|
305
|
+
{!isSceneView(view.id) && !view.onClose && !view.alwaysPinned && (
|
|
183
306
|
<button
|
|
184
307
|
aria-label={pinned ? `Unpin ${view.label}` : `Pin ${view.label}`}
|
|
185
308
|
aria-pressed={pinned}
|
|
186
309
|
className={cn(
|
|
187
|
-
'flex h-6 w-6 items-center justify-center rounded-md transition-colors hover:bg-white/10',
|
|
310
|
+
'flex h-6 w-6 items-center justify-center rounded-md transition-colors hover:bg-black/6 dark:hover:bg-white/10',
|
|
188
311
|
pinned ? 'text-foreground' : 'text-muted-foreground/60',
|
|
189
312
|
)}
|
|
190
313
|
onClick={(event) => {
|
|
@@ -5,11 +5,10 @@ import {
|
|
|
5
5
|
type AnyNodeId,
|
|
6
6
|
getWallCurveFrameAt,
|
|
7
7
|
getWallCurveLength,
|
|
8
|
-
getWallPlaneTop,
|
|
9
8
|
getWallThickness,
|
|
9
|
+
getWallTop,
|
|
10
10
|
isCurvedWall,
|
|
11
11
|
resolveLevelId,
|
|
12
|
-
resolveWallTop,
|
|
13
12
|
sceneRegistry,
|
|
14
13
|
spatialGridManager,
|
|
15
14
|
useScene,
|
|
@@ -159,8 +158,10 @@ function getWallTopY(wall: WallNode, nodes: Readonly<Record<string, AnyNode>>) {
|
|
|
159
158
|
undefined,
|
|
160
159
|
wall.justification,
|
|
161
160
|
)
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
return (
|
|
162
|
+
getWallTop(wall, levelId, nodes as Record<string, AnyNode>, support.elevation) +
|
|
163
|
+
WALL_TOP_HIGHLIGHT_LIFT
|
|
164
|
+
)
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
function buildHighlightSegment(start: [number, number], end: [number, number]) {
|
|
@@ -25,6 +25,7 @@ import { clientToPlan } from '../../lib/floorplan/plan-coords'
|
|
|
25
25
|
import { isHistoryShortcut } from '../../lib/history'
|
|
26
26
|
import { formatLinearMeasurement } from '../../lib/measurements'
|
|
27
27
|
import { sfxEmitter } from '../../lib/sfx-bus'
|
|
28
|
+
import { isTypingTarget } from '../../lib/typing-target'
|
|
28
29
|
import useAlignmentGuides from '../../store/use-alignment-guides'
|
|
29
30
|
import useEditor, {
|
|
30
31
|
isAlignmentGuideActive,
|
|
@@ -368,6 +369,7 @@ export function startFloorplanGroupMove(
|
|
|
368
369
|
// Escape arm (registered earlier on window in the bubble phase), which
|
|
369
370
|
// would otherwise clear the multi-selection mid-cancel.
|
|
370
371
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
372
|
+
if (isTypingTarget(e.target)) return
|
|
371
373
|
const key = e.key.toLowerCase()
|
|
372
374
|
if ((key === 'r' || key === 't') && !e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) {
|
|
373
375
|
// Armed but still under the drag threshold: engage first (exactly what
|
|
@@ -569,6 +571,7 @@ export function startFloorplanGroupRotate(event: {
|
|
|
569
571
|
|
|
570
572
|
// Capture phase so Escape wins over the global `use-keyboard` arm.
|
|
571
573
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
574
|
+
if (isTypingTarget(e.target)) return
|
|
572
575
|
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
573
576
|
// Deleting mid-rotate: revert first, then let the global Delete arm run.
|
|
574
577
|
cancel()
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type FloorplanMode,
|
|
13
13
|
isFloorplanToolAvailableInMode,
|
|
14
14
|
} from '../../lib/floorplan/floorplan-mode'
|
|
15
|
+
import { selectPlacedNode } from '../../lib/level-selection'
|
|
15
16
|
import useEditor from '../../store/use-editor'
|
|
16
17
|
import useFloorplanMode from '../../store/use-floorplan-mode'
|
|
17
18
|
import useInteractionScope, { useReshapingNode } from '../../store/use-interaction-scope'
|
|
@@ -65,8 +66,7 @@ export function FloorplanRegisteredToolLayer() {
|
|
|
65
66
|
const metricNotation = useViewer((state) => state.metricNotation)
|
|
66
67
|
const sceneApi = useMemo(() => createSceneApi(useScene), [])
|
|
67
68
|
const selectNode = useCallback(
|
|
68
|
-
(id: Parameters<FloorplanToolContext['selectNode']>[0]) =>
|
|
69
|
-
useViewer.getState().setSelection({ selectedIds: [id] }),
|
|
69
|
+
(id: Parameters<FloorplanToolContext['selectNode']>[0]) => selectPlacedNode(id),
|
|
70
70
|
[],
|
|
71
71
|
)
|
|
72
72
|
const finishTool = useCallback(() => {
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
import { movementSfxStepKey } from '../../lib/sfx/movement-tick'
|
|
40
40
|
import { sfxEmitter } from '../../lib/sfx-bus'
|
|
41
41
|
import { surfaceAttachmentId } from '../../lib/surface-attachment'
|
|
42
|
+
import { isTypingTarget } from '../../lib/typing-target'
|
|
42
43
|
import { resolveAlignmentForFloorplanView } from '../../lib/world-grid-snap'
|
|
43
44
|
import useAlignmentGuides from '../../store/use-alignment-guides'
|
|
44
45
|
import useEditor, {
|
|
@@ -497,6 +498,7 @@ export function FloorplanRegistryMoveOverlay() {
|
|
|
497
498
|
}
|
|
498
499
|
|
|
499
500
|
const onKey = (event: KeyboardEvent) => {
|
|
501
|
+
if (isTypingTarget(event.target)) return
|
|
500
502
|
// R flips a directional kind's facing mid-placement (door / window:
|
|
501
503
|
// front ↔ back). The session records the flip and re-runs its last
|
|
502
504
|
// apply so the 2D symbol updates immediately; kinds without a facing
|
|
@@ -516,10 +518,6 @@ export function FloorplanRegistryMoveOverlay() {
|
|
|
516
518
|
if (event.metaKey || event.ctrlKey) return
|
|
517
519
|
if (!(session.flipSide && hasMovedSinceStart && pointerOverFloorplan)) return
|
|
518
520
|
if (event.repeat) return
|
|
519
|
-
const t = event.target as HTMLElement | null
|
|
520
|
-
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) {
|
|
521
|
-
return
|
|
522
|
-
}
|
|
523
521
|
event.preventDefault()
|
|
524
522
|
event.stopImmediatePropagation()
|
|
525
523
|
ownWrite(() => session.flipSide?.())
|
|
@@ -964,6 +962,7 @@ export function FloorplanRegistryMoveOverlay() {
|
|
|
964
962
|
}
|
|
965
963
|
|
|
966
964
|
const onKey = (event: KeyboardEvent) => {
|
|
965
|
+
if (isTypingTarget(event.target)) return
|
|
967
966
|
if (event.key !== 'Escape' && !isHistoryShortcut(event)) return
|
|
968
967
|
if (isHistoryShortcut(event)) {
|
|
969
968
|
// ⌘Z mid-move cancels like Escape — keep it from reaching the
|
|
@@ -126,7 +126,7 @@ describe('commitDimensionValue', () => {
|
|
|
126
126
|
expect(applied).toEqual([])
|
|
127
127
|
expect(updated).toHaveLength(1)
|
|
128
128
|
expect(updated[0]?.id).toBe('dim_1' as AnyNodeId)
|
|
129
|
-
expect(typeof (updated[0]
|
|
129
|
+
expect(typeof (updated[0]!.data as { textOverride?: unknown }).textOverride).toBe('string')
|
|
130
130
|
})
|
|
131
131
|
})
|
|
132
132
|
|
|
@@ -29,7 +29,14 @@ import {
|
|
|
29
29
|
useLiveTransforms,
|
|
30
30
|
useScene,
|
|
31
31
|
} from '@pascal-app/core'
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
beginPerfAction,
|
|
34
|
+
cancelPerfAction,
|
|
35
|
+
commitPerfAction,
|
|
36
|
+
useRevealPlanActive,
|
|
37
|
+
useRevealWaiting,
|
|
38
|
+
useViewer,
|
|
39
|
+
} from '@pascal-app/viewer'
|
|
33
40
|
import {
|
|
34
41
|
type ComponentProps,
|
|
35
42
|
memo,
|
|
@@ -151,6 +158,7 @@ import {
|
|
|
151
158
|
resolveFloorplanLabelAngle,
|
|
152
159
|
updateSvgFloorplanLabelOrientations,
|
|
153
160
|
} from './floorplan-label-angle'
|
|
161
|
+
import { useFloorplanReveal } from './floorplan-reveal'
|
|
154
162
|
|
|
155
163
|
/**
|
|
156
164
|
* Registry-driven floor-plan layer.
|
|
@@ -2027,6 +2035,10 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2027
2035
|
wallDimensionReference,
|
|
2028
2036
|
visibilityRootId,
|
|
2029
2037
|
}: FloorplanRegistryEntryProps): React.ReactElement | null {
|
|
2038
|
+
// A node an agent just built is drawn on its construction-reveal turn, as in 3D.
|
|
2039
|
+
const revealWaiting = useRevealWaiting(nodeId)
|
|
2040
|
+
const revealActive = useRevealPlanActive(nodeId)
|
|
2041
|
+
const entryRef = useRef<SVGGElement>(null)
|
|
2030
2042
|
const selected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
2031
2043
|
const highlighted = useViewer((state) => state.previewSelectedIds.includes(nodeId))
|
|
2032
2044
|
const multiSelected = useViewer(
|
|
@@ -2046,7 +2058,14 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2046
2058
|
const phase = useEditor((state) => state.phase)
|
|
2047
2059
|
const hoverTargetId =
|
|
2048
2060
|
phase === 'site' ? (resolveCanvasBuildingId(node, nodes) ?? selectionProxyId) : selectionProxyId
|
|
2049
|
-
|
|
2061
|
+
// What a chat chip points at (`pointedIds`) lights as a hover does, as it does in 3D: the node
|
|
2062
|
+
// itself, or what a hover on it would light.
|
|
2063
|
+
const hovered = useViewer(
|
|
2064
|
+
(state) =>
|
|
2065
|
+
state.hoveredId === hoverTargetId ||
|
|
2066
|
+
state.pointedIds.includes(nodeId) ||
|
|
2067
|
+
state.pointedIds.includes(hoverTargetId),
|
|
2068
|
+
)
|
|
2050
2069
|
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
2051
2070
|
const referencedAnnotationRole = useViewer((state) =>
|
|
2052
2071
|
floorplanEntryReferencedAnnotationRole(node, nodes, new Set(state.selection.selectedIds)),
|
|
@@ -2215,7 +2234,8 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2215
2234
|
visibleGeometry && suppressHandles && pass !== 'base'
|
|
2216
2235
|
? stripHandleChrome(visibleGeometry)
|
|
2217
2236
|
: visibleGeometry
|
|
2218
|
-
|
|
2237
|
+
useFloorplanReveal(nodeId, entryRef, cacheEntry, revealWaiting, revealActive)
|
|
2238
|
+
if (revealWaiting || !geometry) return null
|
|
2219
2239
|
|
|
2220
2240
|
const entryClick = isMarqueeSelectionActive ? undefined : onClickStop
|
|
2221
2241
|
const entryPointerDown = isMarqueeSelectionActive ? undefined : handlePointerDown
|
|
@@ -2224,6 +2244,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({
|
|
|
2224
2244
|
<g
|
|
2225
2245
|
className="floorplan-registry-entry"
|
|
2226
2246
|
data-node-id={nodeId}
|
|
2247
|
+
ref={entryRef}
|
|
2227
2248
|
onClick={entryClick}
|
|
2228
2249
|
onPointerDown={entryPointerDown}
|
|
2229
2250
|
onPointerEnter={handlePointerEnter}
|
|
@@ -2669,6 +2690,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2669
2690
|
}
|
|
2670
2691
|
case 'endpoint-handle': {
|
|
2671
2692
|
if (!palette) return <></>
|
|
2693
|
+
const endpointUnitsPerPixel = g.screenSized ? unitsPerPixel : handleUnitsPerPixel
|
|
2672
2694
|
const handleId = makeHandleId(nodeId, g.payload)
|
|
2673
2695
|
const doubleClickAffordance = floorplanHandleDoubleClickAffordance(g)
|
|
2674
2696
|
const isHovered = hoveredHandleId === handleId
|
|
@@ -2694,10 +2716,10 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2694
2716
|
: palette.endpointHandleFill
|
|
2695
2717
|
const outerRadius =
|
|
2696
2718
|
(isActive ? ENDPOINT_HANDLE_ACTIVE_RADIUS_PX : ENDPOINT_HANDLE_SELECTED_RADIUS_PX) *
|
|
2697
|
-
|
|
2719
|
+
endpointUnitsPerPixel
|
|
2698
2720
|
const dotRadius =
|
|
2699
2721
|
(isActive ? ENDPOINT_HANDLE_ACTIVE_DOT_RADIUS_PX : ENDPOINT_HANDLE_DOT_RADIUS_PX) *
|
|
2700
|
-
|
|
2722
|
+
endpointUnitsPerPixel
|
|
2701
2723
|
return (
|
|
2702
2724
|
<g
|
|
2703
2725
|
key={keyHint}
|
|
@@ -2713,7 +2735,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2713
2735
|
r={outerRadius}
|
|
2714
2736
|
stroke={hoverStroke}
|
|
2715
2737
|
strokeOpacity={isActive ? 0.24 : 0.16}
|
|
2716
|
-
strokeWidth={ENDPOINT_HOVER_GLOW_STROKE_WIDTH_PX *
|
|
2738
|
+
strokeWidth={ENDPOINT_HOVER_GLOW_STROKE_WIDTH_PX * endpointUnitsPerPixel}
|
|
2717
2739
|
style={{ opacity: isHovered || isActive ? 1 : 0, transition: HOVER_TRANSITION }}
|
|
2718
2740
|
vectorEffect="non-scaling-stroke"
|
|
2719
2741
|
/>
|
|
@@ -2725,7 +2747,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({
|
|
|
2725
2747
|
r={outerRadius}
|
|
2726
2748
|
stroke={hoverStroke}
|
|
2727
2749
|
strokeOpacity={isActive ? 0.72 : 0.52}
|
|
2728
|
-
strokeWidth={ENDPOINT_HOVER_RING_STROKE_WIDTH_PX *
|
|
2750
|
+
strokeWidth={ENDPOINT_HOVER_RING_STROKE_WIDTH_PX * endpointUnitsPerPixel}
|
|
2729
2751
|
style={{ opacity: isHovered || isActive ? 1 : 0, transition: HOVER_TRANSITION }}
|
|
2730
2752
|
vectorEffect="non-scaling-stroke"
|
|
2731
2753
|
/>
|