handle deselect

This commit is contained in:
wass08
2026-01-21 11:48:26 +09:00
parent 09515107cb
commit 9117d42841
@@ -4,71 +4,110 @@ import { useViewer } from "@pascal-app/viewer";
import { useEffect } from "react"; import { useEffect } from "react";
import useEditor from "@/store/use-editor"; import useEditor from "@/store/use-editor";
const PHASE_CONFIG = { const SELECTION_STRATEGIES = {
site: ["building"], site: {
structure: ["wall", "slab", "door", "window", "column", "stair"], types: ["building"],
furnish: ["item"], // furniture, decor, etc. handleSelect: (node: any, isShift: boolean) => {
} as const; useViewer.getState().setSelection({ buildingId: node.id });
},
handleDeselect: () => {
useViewer.getState().setSelection({ buildingId: null });
},
isValid: (node: any) => node.type === "building",
},
type PhaseType = keyof typeof PHASE_CONFIG; structure: {
types: ["wall", "item"],
handleSelect: (node: any, isShift: boolean) => {
const { selection, setSelection } = useViewer.getState();
const nextIds = isShift
? selection.selectedIds.includes(node.id)
? selection.selectedIds.filter((id) => id !== node.id)
: [...selection.selectedIds, node.id]
: [node.id];
setSelection({ selectedIds: nextIds });
},
handleDeselect: () => {
useViewer.getState().setSelection({ selectedIds: [] });
},
isValid: (node: any) => {
if (node.type === "wall") return true;
if (node.type === "item") {
return node.category === "door" || node.category === "window";
}
return false;
},
},
furnish: {
types: ["item"],
handleSelect: (node: any, isShift: boolean) => {
const { selection, setSelection } = useViewer.getState();
const nextIds = isShift
? selection.selectedIds.includes(node.id)
? selection.selectedIds.filter((id) => id !== node.id)
: [...selection.selectedIds, node.id]
: [node.id];
setSelection({ selectedIds: nextIds });
},
handleDeselect: () => {
useViewer.getState().setSelection({ selectedIds: [] });
},
isValid: (node: any) => {
return (
node.type === "item" &&
node.category !== "door" &&
node.category !== "window"
);
},
},
};
export const SelectionManager = () => { export const SelectionManager = () => {
const phase = useEditor((s) => s.phase); const phase = useEditor((s) => s.phase);
const mode = useEditor((s) => s.mode); const mode = useEditor((s) => s.mode);
const { setSelection, selection } = useViewer();
useEffect(() => { useEffect(() => {
if (mode !== "select") return; if (mode !== "select") return;
const allowedTypes = PHASE_CONFIG[phase] || []; const strategy = SELECTION_STRATEGIES[phase];
if (!strategy) return;
const onEnter = (event: any) => { const onEnter = (event: any) => {
// Only hover if the node type is allowed in this phase if (strategy.isValid(event.node)) {
if (allowedTypes.includes(event.node.type)) { useViewer.setState({ hoveredId: event.node.id });
useViewer.getState().setHoveredId(event.node.id);
} }
}; };
const onLeave = () => useViewer.getState().setHoveredId(null); const onLeave = () => useViewer.setState({ hoveredId: null });
const onClick = (event: any) => { const onClick = (event: any) => {
if (!strategy.isValid(event.node)) return;
event.stopPropagation(); event.stopPropagation();
const { id, type } = event.node;
if (!allowedTypes.includes(type)) return;
const isShift = event.nativeEvent?.shiftKey; const isShift = event.nativeEvent?.shiftKey;
strategy.handleSelect(event.node, isShift);
if (isShift) {
const isSelected = selection.selectedIds.includes(id);
const nextIds = isSelected
? selection.selectedIds.filter((i) => i !== id)
: [...selection.selectedIds, id];
setSelection({ selectedIds: nextIds });
} else {
setSelection({ selectedIds: [id] });
}
}; };
// BINDING: Loop through all allowed types for this phase // Bind listeners for all potential types this strategy might care about
allowedTypes.forEach((type) => { strategy.types.forEach((type) => {
emitter.on(`${type}:enter`, onEnter); emitter.on(`${type}:enter`, onEnter);
emitter.on(`${type}:leave`, onLeave); emitter.on(`${type}:leave`, onLeave);
emitter.on(`${type}:click`, onClick); emitter.on(`${type}:click`, onClick);
}); });
const onGridClick = () => setSelection({ selectedIds: [] }); const onGridClick = () => strategy.handleDeselect();
emitter.on("grid:click", onGridClick); emitter.on("grid:click", onGridClick);
return () => { return () => {
allowedTypes.forEach((type) => { strategy.types.forEach((type) => {
emitter.off(`${type}:enter`, onEnter); emitter.off(`${type}:enter`, onEnter);
emitter.off(`${type}:leave`, onLeave); emitter.off(`${type}:leave`, onLeave);
emitter.off(`${type}:click`, onClick); emitter.off(`${type}:click`, onClick);
}); });
emitter.off("grid:click", onGridClick); emitter.off("grid:click", onGridClick);
}; };
}, [phase, mode, selection.selectedIds, setSelection]); }, [phase, mode]);
return <EditorOutlinerSync />; return <EditorOutlinerSync />;
}; };