Skip to content

Commit 1f6bf9c

Browse files
authored
fix: respect read-only/version-preview mode in 2D floor plan editor (#8)
1 parent 39328c0 commit 1f6bf9c

3 files changed

Lines changed: 55 additions & 4 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@pascal-app/editor": patch
3+
---
4+
5+
Respect the scene `readOnly` flag in the 2D floor plan. When the scene is
6+
read-only (e.g. version-preview mode, `isVersionPreview`), the registry action
7+
menu is hidden and the interactive edit handles (move / resize / vertex
8+
/ midpoint / edge / rotate) are stripped from the overlay pass, while selection
9+
(hit-lines), labels, and dimensions still render. This mirrors the existing 3D
10+
`noEditing` gating so a locked plan is fully view-only in both views.

packages/editor/src/components/editor-2d/floorplan-registry-action-menu.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,11 @@ export function FloorplanRegistryActionMenu() {
5454
// Only show for registered kinds (skip legacy kinds — they have their
5555
// own FloorplanActionMenuLayer entries).
5656
const selectedKind = useScene((s) => (selectedId ? (s.nodes[selectedId]?.type ?? null) : null))
57+
// Hide the contextual action menu when the scene is read-only (version-preview).
58+
const readOnly = useScene((s) => s.readOnly)
5759
const def = selectedKind ? nodeRegistry.get(selectedKind) : null
5860
const isRegistryKind = !!def
59-
const isVisible = isRegistryKind && !movingNode
61+
const isVisible = isRegistryKind && !movingNode && !readOnly
6062
const isWall = selectedKind === 'wall'
6163

6264
useEffect(() => {

packages/editor/src/components/editor-2d/renderers/floorplan-registry-layer.tsx

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
160160
const setHoveredId = useViewer((s) => s.setHoveredId)
161161
const setSelection = useViewer((s) => s.setSelection)
162162
const nodes = useScene((s) => s.nodes)
163+
// Read-only scene (e.g. version-preview): suppress the interactive 2D edit
164+
// handles in the overlay pass so a locked plan shows no move/resize/vertex
165+
// affordances. Selection (base-pass hit-lines) and labels still render.
166+
const readOnly = useScene((s) => s.readOnly)
163167
// When a building is being moved, its explicit selection may be
164168
// cleared as part of the move handoff. Fall back to the
165169
// mid-drag building id so the dimmed floor keeps rendering
@@ -906,9 +910,13 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
906910
still routes through the same selection-handling `<g>` so a
907911
click on a zone's name selects the zone. */}
908912
<g className="floorplan-registry-overlay">
909-
{entries.map(({ id, overlay }) =>
910-
overlay ? renderEntry(id, overlay, `overlay-${id}`) : null,
911-
)}
913+
{entries.map(({ id, overlay }) => {
914+
if (!overlay) return null
915+
// In read-only mode strip the interactive edit handles (keeping
916+
// labels / dimensions) so locked nodes expose no 2D affordances.
917+
const o = readOnly ? stripEditHandleGeometry(overlay) : overlay
918+
return o ? renderEntry(id, o, `overlay-${id}`) : null
919+
})}
912920
</g>
913921
{/* Transient live-rotation readout — drawn last so the wedge + degree
914922
chip sit above all handle chrome while a rotate-arrow is dragged. */}
@@ -1790,6 +1798,37 @@ const OVERLAY_KINDS = new Set<FloorplanGeometry['kind']>([
17901798
'dimension-label',
17911799
])
17921800

1801+
/**
1802+
* Interactive edit-handle kinds — the move/resize/vertex/rotate affordances.
1803+
* A subset of `OVERLAY_KINDS` that excludes the informational `text` /
1804+
* `dimension` / `dimension-label`, so a read-only scene can hide the handles
1805+
* while keeping measurements and labels visible.
1806+
*/
1807+
const EDIT_HANDLE_KINDS = new Set<FloorplanGeometry['kind']>([
1808+
'endpoint-handle',
1809+
'midpoint-handle',
1810+
'edge-handle',
1811+
'move-handle',
1812+
'move-arrow',
1813+
'rotate-arrow',
1814+
])
1815+
1816+
/**
1817+
* Remove interactive edit-handle geometry from an overlay tree, preserving all
1818+
* other overlay primitives (labels, dimensions). Returns `null` if nothing
1819+
* remains.
1820+
*/
1821+
function stripEditHandleGeometry(g: FloorplanGeometry): FloorplanGeometry | null {
1822+
if (EDIT_HANDLE_KINDS.has(g.kind)) return null
1823+
if (g.kind === 'group') {
1824+
const children = g.children
1825+
.map(stripEditHandleGeometry)
1826+
.filter((child): child is FloorplanGeometry => child !== null)
1827+
return children.length > 0 ? { ...g, children } : null
1828+
}
1829+
return g
1830+
}
1831+
17931832
/**
17941833
* Walk a `FloorplanGeometry` tree and split it into two trees: one with
17951834
* only "base" primitives (polygons, paths, fills, strokes) and one with

0 commit comments

Comments
 (0)