refactor(editor): delete curving + endpoint reshaping flags
Migrate `curvingWall`, `curvingFence`, `movingWallEndpoint`, `movingFenceEndpoint` (and their setters) off `useEditor` onto the authoritative interaction scope. The `reshaping` scope variant gains an `endpoint` discriminator; existence checks read `useIsCurveReshape()` / `useEndpointReshape()`, and the few sites that need the node (affordance-tool mounts, wall-vs-fence type checks) read it from `useReshapingNode()` — a frozen drag-start snapshot, mirroring the old flags so the tools' own per-frame writes don't feed back. `MovingWallEndpoint` / `MovingFenceEndpoint` move to the kind-owned tools that consume them. `editor-api` is simpler: endpoint engagement is kind-agnostic, and the `engageMove` reshape clears are gone (the scope is single-owner). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2f2c3ef8f9
commit
cfc1fb1672
@@ -20,7 +20,7 @@ import {
|
||||
markToolCancelConsumed,
|
||||
snapScalarToGrid,
|
||||
triggerSFX,
|
||||
useEditor,
|
||||
useInteractionScope,
|
||||
} from '@pascal-app/editor'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
@@ -51,7 +51,9 @@ export const CurveFenceTool: React.FC<{ node: FenceNode }> = ({ node }) => {
|
||||
])
|
||||
|
||||
const exitCurveMode = useCallback(() => {
|
||||
useEditor.getState().setCurvingFence(null)
|
||||
useInteractionScope
|
||||
.getState()
|
||||
.endIf((scope) => scope.kind === 'reshaping' && scope.reshape === 'curve')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -15,11 +15,10 @@ import {
|
||||
getAngleToSegmentReference,
|
||||
getSegmentAngleReferenceAtPoint,
|
||||
MeasurementPill,
|
||||
type MovingFenceEndpoint,
|
||||
triggerSFX,
|
||||
useAlignmentGuides,
|
||||
useDragAction,
|
||||
useEditor,
|
||||
useInteractionScope,
|
||||
} from '@pascal-app/editor'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { Html } from '@react-three/drei'
|
||||
@@ -40,10 +39,14 @@ import { moveFenceEndpointDragAction } from './actions/move-endpoint'
|
||||
* - Angle label between this segment and any neighbour segment sharing
|
||||
* the dragged endpoint — same legacy treatment.
|
||||
*
|
||||
* Mounted by the legacy ToolManager via the `move-endpoint` affordance
|
||||
* key. `target.fence` + `target.endpoint` come from the editor store
|
||||
* (`useEditor.movingFenceEndpoint`).
|
||||
* Mounted by ToolManager via the `move-endpoint` affordance key. ToolManager
|
||||
* reconstructs this `target` from the reshaped node + the scope's endpoint.
|
||||
*/
|
||||
export type MovingFenceEndpoint = {
|
||||
fence: FenceNode
|
||||
endpoint: 'start' | 'end'
|
||||
}
|
||||
|
||||
type SegmentLike = {
|
||||
id: string
|
||||
start: FencePlanPoint
|
||||
@@ -104,7 +107,9 @@ export const MoveFenceEndpointTool: React.FC<{ target: MovingFenceEndpoint }> =
|
||||
const exitMoveMode = (committed: boolean) => {
|
||||
if (committed) triggerSFX('sfx:item-place')
|
||||
useViewer.getState().setSelection({ selectedIds: [fenceId] })
|
||||
useEditor.getState().setMovingFenceEndpoint(null)
|
||||
useInteractionScope
|
||||
.getState()
|
||||
.endIf((scope) => scope.kind === 'reshaping' && scope.reshape === 'endpoint')
|
||||
}
|
||||
|
||||
useDragAction({
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
snapBuildingLocalToWorldGrid,
|
||||
snapScalarToGrid,
|
||||
triggerSFX,
|
||||
useEditor,
|
||||
useInteractionScope,
|
||||
} from '@pascal-app/editor'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
@@ -47,7 +47,9 @@ export const CurveWallTool: React.FC<{ node: WallNode }> = ({ node }) => {
|
||||
])
|
||||
|
||||
const exitCurveMode = useCallback(() => {
|
||||
useEditor.getState().setCurvingWall(null)
|
||||
useInteractionScope
|
||||
.getState()
|
||||
.endIf((scope) => scope.kind === 'reshaping' && scope.reshape === 'curve')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -22,12 +22,11 @@ import {
|
||||
isMagneticSnapActive,
|
||||
isSegmentLongEnough,
|
||||
MeasurementPill,
|
||||
type MovingWallEndpoint,
|
||||
markToolCancelConsumed,
|
||||
snapWallDraftPointDetailed,
|
||||
triggerSFX,
|
||||
useAlignmentGuides,
|
||||
useEditor,
|
||||
useInteractionScope,
|
||||
useWallSnapIndicator,
|
||||
type WallPlanPoint,
|
||||
} from '@pascal-app/editor'
|
||||
@@ -44,9 +43,14 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
* dismisses without committing.
|
||||
*
|
||||
* Mounted via `def.affordanceTools['move-endpoint']` from
|
||||
* `wall/definition.ts`. Editor state trigger is
|
||||
* `useEditor.movingWallEndpoint`.
|
||||
* `wall/definition.ts`. Triggered by an `endpoint` reshape scope; ToolManager
|
||||
* reconstructs this `target` from the reshaped node + the scope's endpoint.
|
||||
*/
|
||||
export type MovingWallEndpoint = {
|
||||
wall: WallNode
|
||||
endpoint: 'start' | 'end'
|
||||
}
|
||||
|
||||
/** Figma-style alignment-snap threshold (meters), matching the item move /
|
||||
* placement tools. 8 cm gives a magnetic pull without fighting grid snap. */
|
||||
const ALIGNMENT_THRESHOLD_M = 0.08
|
||||
@@ -202,7 +206,9 @@ export const MoveWallEndpointTool: React.FC<{ target: MovingWallEndpoint }> = ({
|
||||
const unit = useViewer((s) => s.unit)
|
||||
|
||||
const exitMoveMode = useCallback(() => {
|
||||
useEditor.getState().setMovingWallEndpoint(null)
|
||||
useInteractionScope
|
||||
.getState()
|
||||
.endIf((scope) => scope.kind === 'reshaping' && scope.reshape === 'endpoint')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import {
|
||||
ActionButton,
|
||||
ActionGroup,
|
||||
curveReshapeScope,
|
||||
getLinearUnitLabel,
|
||||
linearControlValueToMeters,
|
||||
metersToLinearUnit,
|
||||
@@ -21,7 +22,7 @@ import {
|
||||
PanelWrapper,
|
||||
SliderControl,
|
||||
triggerSFX,
|
||||
useEditor,
|
||||
useInteractionScope,
|
||||
} from '@pascal-app/editor'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { Spline } from 'lucide-react'
|
||||
@@ -31,7 +32,6 @@ export default function WallPanel() {
|
||||
const selectedId = useViewer((s) => s.selection.selectedIds[0])
|
||||
const unit = useViewer((s) => s.unit)
|
||||
const setSelection = useViewer((s) => s.setSelection)
|
||||
const setCurvingWall = useEditor((s) => s.setCurvingWall)
|
||||
|
||||
const sceneNode = useScene((s) =>
|
||||
selectedId ? (s.nodes[selectedId as AnyNode['id']] as WallNode | undefined) : undefined,
|
||||
@@ -115,9 +115,9 @@ export default function WallPanel() {
|
||||
const handleCurve = useCallback(() => {
|
||||
if (!node) return
|
||||
triggerSFX('sfx:item-pick')
|
||||
setCurvingWall(node)
|
||||
useInteractionScope.getState().begin(curveReshapeScope(node.id))
|
||||
setSelection({ selectedIds: [] })
|
||||
}, [node, setCurvingWall, setSelection])
|
||||
}, [node, setSelection])
|
||||
|
||||
if (!(node && node.type === 'wall' && selectedId)) return null
|
||||
|
||||
|
||||
Reference in New Issue
Block a user