Maus-Schema aus App.tsx in eigenen Hook extrahiert (state/useMouseSelectionHandlers.ts).
Übersetzt rohe Pick-/Marquee-/Kontextmenü-Events aus PlanView/Viewport3D in
Selektions-Mutationen (onPlanSelect/onPlanMarquee/onPlanContextMenu/
onViewportSelect{Wall,Stair,Ceiling,Opening}/onViewport3dPick/
onViewportContextMenu) + zwei eng verwandte Doppelklick-Trigger
(onOpenStampEditor, onEditDrawingText) + commitTextEdit. Reine Verschiebung,
keine Verhaltensänderung. Der useImageImportHandling()-Aufruf, der bisher
mitten in diesem Abschnitt sass (unabhängig vom Rest), ist dabei direkt hinter
den neuen Hook-Aufruf gerutscht statt mit extrahiert zu werden.
App.tsx: 3655 → 3278 Zeilen.
tsc -b sauber, vitest 891/891 grün, vite build ok.
This commit is contained in:
+52
-429
@@ -47,13 +47,8 @@ import { LockConflictDialog } from "./ui/LockConflictDialog";
|
||||
import { Toast } from "./ui/Toast";
|
||||
import { isTauriRuntime } from "./io/projectFile";
|
||||
import { checkForUpdate } from "./update/checkForUpdate";
|
||||
import type {
|
||||
PlanViewHandle,
|
||||
PlanSelection,
|
||||
MarqueeSelection,
|
||||
} from "./plan/PlanView";
|
||||
import type { DisplayResolver, ViewportContextInfo } from "./viewport/Viewport3D";
|
||||
import type { Pick3dHit } from "./viewport/Wasm3DViewport";
|
||||
import type { PlanViewHandle } from "./plan/PlanView";
|
||||
import type { DisplayResolver } from "./viewport/Viewport3D";
|
||||
import { ResourceManager } from "./ui/ResourceManager";
|
||||
import type { ResourceManagerHandlers, TabId as ResourceTabId } from "./ui/ResourceManager";
|
||||
import { LayoutSheetView } from "./ui/LayoutSheet";
|
||||
@@ -119,6 +114,7 @@ import { useSelectionState } from "./state/useSelectionState";
|
||||
import { useGripEditing } from "./state/useGripEditing";
|
||||
import { usePanelHostBase } from "./state/usePanelHostBase";
|
||||
import { useDockDragState } from "./state/useDockDragState";
|
||||
import { useMouseSelectionHandlers } from "./state/useMouseSelectionHandlers";
|
||||
import { useProjectFileActions } from "./state/useProjectFileActions";
|
||||
import { useProjectActions } from "./state/useProjectActions";
|
||||
import {
|
||||
@@ -167,7 +163,6 @@ import { DragOverlay } from "./panels/panelDrag";
|
||||
import { itemDisplay } from "./panels/displayMode";
|
||||
import { PanelHostContext } from "./panels/types";
|
||||
import { hostWithMode } from "./panels/host";
|
||||
import { hitTestWall } from "./viewport/planHitTest";
|
||||
import { deriveSelection } from "./state/selectionInfo";
|
||||
import type { Selection } from "./state/selectionInfo";
|
||||
import { centroid as roomCentroid } from "./geometry/roomArea";
|
||||
@@ -2224,42 +2219,55 @@ export default function App() {
|
||||
transformDragInfoRef.current = null;
|
||||
};
|
||||
|
||||
// ── Maus-Schema: Auswahl + Kontextmenü aus Grundriss / 3D ─────────────────
|
||||
// Links-Klick im Grundriss: genau eine Wand wählen (Menge ersetzen) bzw.
|
||||
// Leerraum → Auswahl löschen.
|
||||
// Doppelklick auf einen Raum-Stempel: den Raum wählen + den Stempel-Editor
|
||||
// öffnen (Rich-Text-Popup).
|
||||
const onOpenStampEditor = (roomId: string) => {
|
||||
setSelectedRoomIds([roomId]);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
setStampEditorRoomId(roomId);
|
||||
};
|
||||
// Doppelklick auf ein Text-2D-Element: selektieren + Inhalts-Editor öffnen.
|
||||
const onEditDrawingText = (drawingId: string) => {
|
||||
const d = project.drawings2d.find((x) => x.id === drawingId);
|
||||
if (!d || d.geom.shape !== "text") return;
|
||||
setSelectedDrawingId(drawingId);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
setEditTextId(drawingId);
|
||||
};
|
||||
|
||||
// Maus-Schema: Auswahl + Kontextmenü aus Grundriss / 3D — siehe
|
||||
// state/useMouseSelectionHandlers.ts.
|
||||
const {
|
||||
onOpenStampEditor,
|
||||
onEditDrawingText,
|
||||
commitTextEdit,
|
||||
onPlanSelect,
|
||||
onPlanMarquee,
|
||||
onPlanContextMenu,
|
||||
onViewportSelectWall,
|
||||
onViewportSelectStair,
|
||||
onViewportSelectCeiling,
|
||||
onViewportSelectOpening,
|
||||
onViewport3dPick,
|
||||
onViewportContextMenu,
|
||||
} = useMouseSelectionHandlers({
|
||||
project,
|
||||
setProject,
|
||||
activeLevelId: activeLevel.id,
|
||||
visibleCodes,
|
||||
categoryDisplay,
|
||||
selectionState: {
|
||||
selectedWallIds,
|
||||
setSelectedWallIds,
|
||||
selectedDrawingIds,
|
||||
setSelectedDrawingIds,
|
||||
setSelectedDrawingId,
|
||||
selectedCeilingIds,
|
||||
setSelectedCeilingIds,
|
||||
selectedOpeningIds,
|
||||
setSelectedOpeningIds,
|
||||
selectedStairIds,
|
||||
setSelectedStairIds,
|
||||
selectedRoomIds,
|
||||
setSelectedRoomIds,
|
||||
selectedExtrudedSolidIds,
|
||||
setSelectedExtrudedSolidIds,
|
||||
selectedColumnIds,
|
||||
setSelectedColumnIds,
|
||||
selectedRoofIds,
|
||||
setSelectedRoofIds,
|
||||
selectedContextObjectIds,
|
||||
setSelectedContextObjectIds,
|
||||
selectedSectionLineId,
|
||||
setSelectedSectionLineId,
|
||||
},
|
||||
contextMenuState: { setMenu, setStampEditorRoomId, editTextId, setEditTextId },
|
||||
});
|
||||
|
||||
// „Bild einfügen" (Werkzeug „image") — siehe state/useImportHandling.ts.
|
||||
const { onImageOrPdfFileChosen } = useImageImportHandling({
|
||||
@@ -2280,391 +2288,6 @@ export default function App() {
|
||||
setSelectedContextObjectIds,
|
||||
setSelectedSectionLineId,
|
||||
});
|
||||
// Bearbeitetes Rich-Text-Dokument übernehmen (immutabel; nur bei Text-Geometrie).
|
||||
// `text` bleibt als Reintext-Spiegel bestehen (DXF-Export, Suche, Alt-Konsumenten).
|
||||
const commitTextEdit = (doc: RichTextDoc) => {
|
||||
const id = editTextId;
|
||||
if (!id) return;
|
||||
const text = plainText(doc).trim();
|
||||
if (text !== "") {
|
||||
setProject((p) => ({
|
||||
...p,
|
||||
drawings2d: p.drawings2d.map((d) =>
|
||||
d.id === id && d.geom.shape === "text"
|
||||
? { ...d, geom: { ...d.geom, doc, text: plainText(doc) } }
|
||||
: d,
|
||||
),
|
||||
}));
|
||||
}
|
||||
setEditTextId(null);
|
||||
};
|
||||
// Kategorie-Code des getroffenen Elements (für die Sperr-Prüfung unten) —
|
||||
// nur die Kanäle, deren Modell-Typ eine categoryCode trägt (ExtrudedSolid/
|
||||
// Kontext-Objekte bewusst ohne, siehe deren Typdefinitionen).
|
||||
const categoryCodeOfSelection = (sel: PlanSelection): string | undefined => {
|
||||
if (sel.wallId) return project.walls.find((w) => w.id === sel.wallId)?.categoryCode;
|
||||
if (sel.ceilingId)
|
||||
return (project.ceilings ?? []).find((c) => c.id === sel.ceilingId)?.categoryCode;
|
||||
if (sel.openingId)
|
||||
return (project.openings ?? []).find((o) => o.id === sel.openingId)?.categoryCode;
|
||||
if (sel.stairId)
|
||||
return (project.stairs ?? []).find((s) => s.id === sel.stairId)?.categoryCode;
|
||||
if (sel.roomId) return (project.rooms ?? []).find((r) => r.id === sel.roomId)?.categoryCode;
|
||||
if (sel.columnId)
|
||||
return (project.columns ?? []).find((c) => c.id === sel.columnId)?.categoryCode;
|
||||
if (sel.roofId) return (project.roofs ?? []).find((r) => r.id === sel.roofId)?.categoryCode;
|
||||
if (sel.drawingId)
|
||||
return project.drawings2d.find((d) => d.id === sel.drawingId)?.categoryCode;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const onPlanSelect = (sel: PlanSelection) => {
|
||||
// Darstellungsmodus „Andere grau & gesperrt": ein gesperrtes Element lässt
|
||||
// sich nicht anwählen (und damit auch nicht verschieben/bearbeiten) — der
|
||||
// Klick wird ignoriert, als läge dort nichts.
|
||||
const hitCode = categoryCodeOfSelection(sel);
|
||||
if (hitCode !== undefined && categoryDisplay(hitCode).locked) return;
|
||||
// Shift+Klick: Mehrfach-Auswahl — getroffenes Element zu-/abschalten, den
|
||||
// Rest behalten (Wände und 2D-Zeichnungen unabhängig, gemischte Auswahl
|
||||
// erlaubt). Leerraum-Klick mit Shift lässt die Auswahl unverändert.
|
||||
if (sel.shift) {
|
||||
// Schnittlinie ist Einzelauswahl — Shift+Klick togglet sie an/aus.
|
||||
if (sel.sectionLineId) {
|
||||
setSelectedSectionLineId(
|
||||
selectedSectionLineId === sel.sectionLineId ? null : sel.sectionLineId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (sel.wallId) {
|
||||
const id = sel.wallId;
|
||||
setSelectedWallIds(
|
||||
selectedWallIds.includes(id)
|
||||
? selectedWallIds.filter((w) => w !== id)
|
||||
: [...selectedWallIds, id],
|
||||
);
|
||||
} else if (sel.drawingId) {
|
||||
const id = sel.drawingId;
|
||||
setSelectedDrawingIds(
|
||||
selectedDrawingIds.includes(id)
|
||||
? selectedDrawingIds.filter((d) => d !== id)
|
||||
: [...selectedDrawingIds, id],
|
||||
);
|
||||
} else if (sel.ceilingId) {
|
||||
const id = sel.ceilingId;
|
||||
setSelectedCeilingIds(
|
||||
selectedCeilingIds.includes(id)
|
||||
? selectedCeilingIds.filter((c) => c !== id)
|
||||
: [...selectedCeilingIds, id],
|
||||
);
|
||||
} else if (sel.openingId) {
|
||||
const id = sel.openingId;
|
||||
setSelectedOpeningIds(
|
||||
selectedOpeningIds.includes(id)
|
||||
? selectedOpeningIds.filter((o) => o !== id)
|
||||
: [...selectedOpeningIds, id],
|
||||
);
|
||||
} else if (sel.stairId) {
|
||||
const id = sel.stairId;
|
||||
setSelectedStairIds(
|
||||
selectedStairIds.includes(id)
|
||||
? selectedStairIds.filter((s) => s !== id)
|
||||
: [...selectedStairIds, id],
|
||||
);
|
||||
} else if (sel.roomId) {
|
||||
const id = sel.roomId;
|
||||
setSelectedRoomIds(
|
||||
selectedRoomIds.includes(id)
|
||||
? selectedRoomIds.filter((r) => r !== id)
|
||||
: [...selectedRoomIds, id],
|
||||
);
|
||||
} else if (sel.extrudedSolidId) {
|
||||
const id = sel.extrudedSolidId;
|
||||
setSelectedExtrudedSolidIds(
|
||||
selectedExtrudedSolidIds.includes(id)
|
||||
? selectedExtrudedSolidIds.filter((x) => x !== id)
|
||||
: [...selectedExtrudedSolidIds, id],
|
||||
);
|
||||
} else if (sel.columnId) {
|
||||
const id = sel.columnId;
|
||||
setSelectedColumnIds(
|
||||
selectedColumnIds.includes(id)
|
||||
? selectedColumnIds.filter((x) => x !== id)
|
||||
: [...selectedColumnIds, id],
|
||||
);
|
||||
} else if (sel.roofId) {
|
||||
const id = sel.roofId;
|
||||
setSelectedRoofIds(
|
||||
selectedRoofIds.includes(id)
|
||||
? selectedRoofIds.filter((x) => x !== id)
|
||||
: [...selectedRoofIds, id],
|
||||
);
|
||||
} else if (sel.contextObjectId) {
|
||||
const id = sel.contextObjectId;
|
||||
setSelectedContextObjectIds(
|
||||
selectedContextObjectIds.includes(id)
|
||||
? selectedContextObjectIds.filter((x) => x !== id)
|
||||
: [...selectedContextObjectIds, id],
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Ohne Shift: ersetzen. Öffnung hat Vorrang (ihr Symbol liegt in der Lücke),
|
||||
// dann Wand, dann 2D-Element, dann Decke; sonst leeren. Die Kanäle schließen
|
||||
// sich gegenseitig aus.
|
||||
// Alle Kanäle ausser dem gewählten leeren — als Helfer, damit jeder Zweig
|
||||
// (inkl. des neuen Dach-Kanals) konsistent bleibt.
|
||||
const clearOthers = (keep: string) => {
|
||||
if (keep !== "wall") setSelectedWallIds([]);
|
||||
if (keep !== "drawing") setSelectedDrawingId(null);
|
||||
if (keep !== "ceiling") setSelectedCeilingIds([]);
|
||||
if (keep !== "opening") setSelectedOpeningIds([]);
|
||||
if (keep !== "stair") setSelectedStairIds([]);
|
||||
if (keep !== "room") setSelectedRoomIds([]);
|
||||
if (keep !== "extrudedSolid") setSelectedExtrudedSolidIds([]);
|
||||
if (keep !== "column") setSelectedColumnIds([]);
|
||||
if (keep !== "roof") setSelectedRoofIds([]);
|
||||
if (keep !== "contextObject") setSelectedContextObjectIds([]);
|
||||
if (keep !== "sectionLine") setSelectedSectionLineId(null);
|
||||
};
|
||||
if (sel.sectionLineId) {
|
||||
setSelectedSectionLineId(sel.sectionLineId);
|
||||
clearOthers("sectionLine");
|
||||
} else if (sel.openingId) {
|
||||
setSelectedOpeningIds([sel.openingId]);
|
||||
clearOthers("opening");
|
||||
} else if (sel.wallId) {
|
||||
setSelectedWallIds([sel.wallId]);
|
||||
clearOthers("wall");
|
||||
} else if (sel.columnId) {
|
||||
setSelectedColumnIds([sel.columnId]);
|
||||
clearOthers("column");
|
||||
} else if (sel.drawingId) {
|
||||
setSelectedDrawingId(sel.drawingId);
|
||||
clearOthers("drawing");
|
||||
} else if (sel.extrudedSolidId) {
|
||||
setSelectedExtrudedSolidIds([sel.extrudedSolidId]);
|
||||
clearOthers("extrudedSolid");
|
||||
} else if (sel.ceilingId) {
|
||||
setSelectedCeilingIds([sel.ceilingId]);
|
||||
clearOthers("ceiling");
|
||||
} else if (sel.stairId) {
|
||||
setSelectedStairIds([sel.stairId]);
|
||||
clearOthers("stair");
|
||||
} else if (sel.roomId) {
|
||||
setSelectedRoomIds([sel.roomId]);
|
||||
clearOthers("room");
|
||||
} else if (sel.roofId) {
|
||||
setSelectedRoofIds([sel.roofId]);
|
||||
clearOthers("roof");
|
||||
} else if (sel.contextObjectId) {
|
||||
setSelectedContextObjectIds([sel.contextObjectId]);
|
||||
clearOthers("contextObject");
|
||||
} else {
|
||||
clearOthers("");
|
||||
}
|
||||
};
|
||||
// Links-Drag (Marquee) im Grundriss: die getroffene Wand-MENGE übernehmen
|
||||
// (nach CAD-Konvention von PlanView bestimmt). Ersetzt die bisherige Auswahl.
|
||||
const onPlanMarquee = (sel: MarqueeSelection) => {
|
||||
setSelectedWallIds(sel.wallIds);
|
||||
setSelectedDrawingIds(sel.drawingIds ?? []);
|
||||
// Decken/Öffnungen/Treppen/Räume/Extrusionen/Dächer sind (noch) nicht Teil
|
||||
// des Marquee → beim Aufziehen abwählen.
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
};
|
||||
// Rechts-Klick im Grundriss: Wand-Kontextmenü. Trifft der Klick eine Wand, die
|
||||
// NICHT bereits in der Auswahl ist, wird die Auswahl auf sie gesetzt (damit die
|
||||
// Aktionen einen Bezug haben); innerhalb einer bestehenden Mehrfachauswahl
|
||||
// bleibt die Menge erhalten.
|
||||
const onPlanContextMenu = (info: {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
model: Vec2;
|
||||
}) => {
|
||||
const wallId = hitTestWall(project, activeLevel.id, visibleCodes, info.model);
|
||||
if (wallId && !selectedWallIds.includes(wallId)) setSelectedWallIds([wallId]);
|
||||
setMenu({ kind: "plan", x: info.clientX, y: info.clientY, wallId });
|
||||
};
|
||||
// Links-Klick im 3D: per Raycast getroffene Wand wählen (Einzelauswahl).
|
||||
const onViewportSelectWall = (wallId: string | null) => {
|
||||
setSelectedWallIds(wallId ? [wallId] : []);
|
||||
if (wallId) {
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
}
|
||||
};
|
||||
// Links-Klick im 3D: per Raycast getroffene Treppe wählen (Einzelauswahl).
|
||||
const onViewportSelectStair = (stairId: string | null) => {
|
||||
setSelectedStairIds(stairId ? [stairId] : []);
|
||||
if (stairId) {
|
||||
setSelectedWallIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
}
|
||||
};
|
||||
// Links-Klick im 3D: per Raycast getroffene Decke wählen (Einzelauswahl).
|
||||
const onViewportSelectCeiling = (ceilingId: string | null) => {
|
||||
setSelectedCeilingIds(ceilingId ? [ceilingId] : []);
|
||||
if (ceilingId) {
|
||||
setSelectedWallIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
}
|
||||
};
|
||||
// Links-Klick im 3D: per Raycast getroffene Öffnung wählen (Einzelauswahl).
|
||||
const onViewportSelectOpening = (openingId: string | null) => {
|
||||
setSelectedOpeningIds(openingId ? [openingId] : []);
|
||||
if (openingId) {
|
||||
setSelectedWallIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
}
|
||||
};
|
||||
// Links-KLICK im Nordstern-3D-Viewport (WASM, TS-Raycast): getroffenes Bauteil
|
||||
// wählen, sodass das Attribute-/Objekt-Info-Panel es zeigt (erster Schritt zur
|
||||
// 3D-Editierbarkeit). `additive` (Shift/Ctrl) = zur Auswahl DERSELBEN Art
|
||||
// hinzufügen, sonst ersetzen; beim Setzen die ANDEREN Arten-Arrays leeren
|
||||
// (Einzel-Selektion über Arten hinweg, konsistent mit dem 2D-Verhalten).
|
||||
// Leertreffer (kein Bauteil) → alle Selektionen leeren.
|
||||
const onViewport3dPick = (hit: Pick3dHit, additive: boolean) => {
|
||||
if (!hit) {
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
return;
|
||||
}
|
||||
if (hit.kind === "contextObject") {
|
||||
setSelectedContextObjectIds([hit.id]);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
return;
|
||||
}
|
||||
// Öffnung (Fenster/Tür) und Treppe sind jetzt auch im WASM-Viewport pickbar
|
||||
// (raycast3d/pickGeometry). Jede Art ersetzt die Auswahl exklusiv; nur Wände
|
||||
// unterstützen additive Mehrfachauswahl (wie bisher).
|
||||
if (hit.kind === "opening") {
|
||||
setSelectedOpeningIds([hit.id]);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
return;
|
||||
}
|
||||
if (hit.kind === "stair") {
|
||||
setSelectedStairIds([hit.id]);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
return;
|
||||
}
|
||||
if (hit.kind === "roof") {
|
||||
setSelectedRoofIds([hit.id]);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
return;
|
||||
}
|
||||
if (hit.kind === "wall") {
|
||||
setSelectedWallIds(
|
||||
additive ? Array.from(new Set([...selectedWallIds, hit.id])) : [hit.id],
|
||||
);
|
||||
setSelectedCeilingIds([]);
|
||||
} else {
|
||||
setSelectedCeilingIds(
|
||||
additive ? Array.from(new Set([...selectedCeilingIds, hit.id])) : [hit.id],
|
||||
);
|
||||
setSelectedWallIds([]);
|
||||
}
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
};
|
||||
// Rechts-Klick im 3D: Wand-Kontextmenü an der Cursorposition.
|
||||
const onViewportContextMenu = (info: ViewportContextInfo) => {
|
||||
if (info.wallId && !selectedWallIds.includes(info.wallId)) {
|
||||
setSelectedWallIds([info.wallId]);
|
||||
}
|
||||
setMenu({
|
||||
kind: "viewport",
|
||||
x: info.clientX,
|
||||
y: info.clientY,
|
||||
wallId: info.wallId,
|
||||
});
|
||||
};
|
||||
|
||||
// App-weites Drag & Drop einer DXF/DWG-Datei — siehe state/useImportHandling.ts.
|
||||
const { onAppDragEnter, onAppDragOver, onAppDragLeave, onAppDrop } = useFileDropImport(
|
||||
|
||||
@@ -0,0 +1,522 @@
|
||||
// Maus-Schema: Auswahl + Kontextmenü aus Grundriss / 3D. Übersetzt rohe Pick-/
|
||||
// Marquee-/Kontextmenü-Events aus PlanView/Viewport3D in Selektions-Mutationen
|
||||
// (+ ein paar eng verwandte Doppelklick-Trigger: Raum-Stempel-Editor, Text-
|
||||
// Inhalts-Editor). Reine Verschiebung aus App.tsx, keine Verhaltensänderung.
|
||||
//
|
||||
// Extrahiert aus App.tsx. Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).
|
||||
|
||||
import type { Project, Vec2 } from "../model/types";
|
||||
import type { CategoryDisplayResolver } from "../plan/generatePlan";
|
||||
import type { DisplayResolver } from "../viewport/Viewport3D";
|
||||
import type { PlanSelection, MarqueeSelection } from "../plan/PlanView";
|
||||
import type { Pick3dHit } from "../viewport/Wasm3DViewport";
|
||||
import type { ViewportContextInfo } from "../viewport/Viewport3D";
|
||||
import type { RichTextDoc } from "../text/richText";
|
||||
import { plainText } from "../text/richText";
|
||||
import { hitTestWall } from "../viewport/planHitTest";
|
||||
import type { useSelectionState } from "./useSelectionState";
|
||||
import type { useContextMenuState } from "../menus/useContextMenuState";
|
||||
|
||||
export function useMouseSelectionHandlers(opts: {
|
||||
project: Project;
|
||||
setProject: (updater: (p: Project) => Project) => void;
|
||||
activeLevelId: string;
|
||||
visibleCodes: Set<string>;
|
||||
categoryDisplay: CategoryDisplayResolver & DisplayResolver;
|
||||
selectionState: Pick<
|
||||
ReturnType<typeof useSelectionState>,
|
||||
| "selectedWallIds"
|
||||
| "setSelectedWallIds"
|
||||
| "selectedDrawingIds"
|
||||
| "setSelectedDrawingIds"
|
||||
| "setSelectedDrawingId"
|
||||
| "selectedCeilingIds"
|
||||
| "setSelectedCeilingIds"
|
||||
| "selectedOpeningIds"
|
||||
| "setSelectedOpeningIds"
|
||||
| "selectedStairIds"
|
||||
| "setSelectedStairIds"
|
||||
| "selectedRoomIds"
|
||||
| "setSelectedRoomIds"
|
||||
| "selectedExtrudedSolidIds"
|
||||
| "setSelectedExtrudedSolidIds"
|
||||
| "selectedColumnIds"
|
||||
| "setSelectedColumnIds"
|
||||
| "selectedRoofIds"
|
||||
| "setSelectedRoofIds"
|
||||
| "selectedContextObjectIds"
|
||||
| "setSelectedContextObjectIds"
|
||||
| "selectedSectionLineId"
|
||||
| "setSelectedSectionLineId"
|
||||
>;
|
||||
contextMenuState: Pick<
|
||||
ReturnType<typeof useContextMenuState>,
|
||||
"setMenu" | "setStampEditorRoomId" | "editTextId" | "setEditTextId"
|
||||
>;
|
||||
}) {
|
||||
const { project, setProject, activeLevelId, visibleCodes, categoryDisplay, selectionState, contextMenuState } = opts;
|
||||
const {
|
||||
selectedWallIds,
|
||||
setSelectedWallIds,
|
||||
selectedDrawingIds,
|
||||
setSelectedDrawingIds,
|
||||
setSelectedDrawingId,
|
||||
selectedCeilingIds,
|
||||
setSelectedCeilingIds,
|
||||
selectedOpeningIds,
|
||||
setSelectedOpeningIds,
|
||||
selectedStairIds,
|
||||
setSelectedStairIds,
|
||||
selectedRoomIds,
|
||||
setSelectedRoomIds,
|
||||
selectedExtrudedSolidIds,
|
||||
setSelectedExtrudedSolidIds,
|
||||
selectedColumnIds,
|
||||
setSelectedColumnIds,
|
||||
selectedRoofIds,
|
||||
setSelectedRoofIds,
|
||||
selectedContextObjectIds,
|
||||
setSelectedContextObjectIds,
|
||||
selectedSectionLineId,
|
||||
setSelectedSectionLineId,
|
||||
} = selectionState;
|
||||
const { setMenu, setStampEditorRoomId, editTextId, setEditTextId } = contextMenuState;
|
||||
|
||||
// ── Maus-Schema: Auswahl + Kontextmenü aus Grundriss / 3D ─────────────────
|
||||
// Links-Klick im Grundriss: genau eine Wand wählen (Menge ersetzen) bzw.
|
||||
// Leerraum → Auswahl löschen.
|
||||
// Doppelklick auf einen Raum-Stempel: den Raum wählen + den Stempel-Editor
|
||||
// öffnen (Rich-Text-Popup).
|
||||
const onOpenStampEditor = (roomId: string) => {
|
||||
setSelectedRoomIds([roomId]);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
setStampEditorRoomId(roomId);
|
||||
};
|
||||
// Doppelklick auf ein Text-2D-Element: selektieren + Inhalts-Editor öffnen.
|
||||
const onEditDrawingText = (drawingId: string) => {
|
||||
const d = project.drawings2d.find((x) => x.id === drawingId);
|
||||
if (!d || d.geom.shape !== "text") return;
|
||||
setSelectedDrawingId(drawingId);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
setEditTextId(drawingId);
|
||||
};
|
||||
|
||||
// Bearbeitetes Rich-Text-Dokument übernehmen (immutabel; nur bei Text-Geometrie).
|
||||
// `text` bleibt als Reintext-Spiegel bestehen (DXF-Export, Suche, Alt-Konsumenten).
|
||||
const commitTextEdit = (doc: RichTextDoc) => {
|
||||
const id = editTextId;
|
||||
if (!id) return;
|
||||
const text = plainText(doc).trim();
|
||||
if (text !== "") {
|
||||
setProject((p) => ({
|
||||
...p,
|
||||
drawings2d: p.drawings2d.map((d) =>
|
||||
d.id === id && d.geom.shape === "text"
|
||||
? { ...d, geom: { ...d.geom, doc, text: plainText(doc) } }
|
||||
: d,
|
||||
),
|
||||
}));
|
||||
}
|
||||
setEditTextId(null);
|
||||
};
|
||||
// Kategorie-Code des getroffenen Elements (für die Sperr-Prüfung unten) —
|
||||
// nur die Kanäle, deren Modell-Typ eine categoryCode trägt (ExtrudedSolid/
|
||||
// Kontext-Objekte bewusst ohne, siehe deren Typdefinitionen).
|
||||
const categoryCodeOfSelection = (sel: PlanSelection): string | undefined => {
|
||||
if (sel.wallId) return project.walls.find((w) => w.id === sel.wallId)?.categoryCode;
|
||||
if (sel.ceilingId)
|
||||
return (project.ceilings ?? []).find((c) => c.id === sel.ceilingId)?.categoryCode;
|
||||
if (sel.openingId)
|
||||
return (project.openings ?? []).find((o) => o.id === sel.openingId)?.categoryCode;
|
||||
if (sel.stairId)
|
||||
return (project.stairs ?? []).find((s) => s.id === sel.stairId)?.categoryCode;
|
||||
if (sel.roomId) return (project.rooms ?? []).find((r) => r.id === sel.roomId)?.categoryCode;
|
||||
if (sel.columnId)
|
||||
return (project.columns ?? []).find((c) => c.id === sel.columnId)?.categoryCode;
|
||||
if (sel.roofId) return (project.roofs ?? []).find((r) => r.id === sel.roofId)?.categoryCode;
|
||||
if (sel.drawingId)
|
||||
return project.drawings2d.find((d) => d.id === sel.drawingId)?.categoryCode;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const onPlanSelect = (sel: PlanSelection) => {
|
||||
// Darstellungsmodus „Andere grau & gesperrt": ein gesperrtes Element lässt
|
||||
// sich nicht anwählen (und damit auch nicht verschieben/bearbeiten) — der
|
||||
// Klick wird ignoriert, als läge dort nichts.
|
||||
const hitCode = categoryCodeOfSelection(sel);
|
||||
if (hitCode !== undefined && categoryDisplay(hitCode).locked) return;
|
||||
// Shift+Klick: Mehrfach-Auswahl — getroffenes Element zu-/abschalten, den
|
||||
// Rest behalten (Wände und 2D-Zeichnungen unabhängig, gemischte Auswahl
|
||||
// erlaubt). Leerraum-Klick mit Shift lässt die Auswahl unverändert.
|
||||
if (sel.shift) {
|
||||
// Schnittlinie ist Einzelauswahl — Shift+Klick togglet sie an/aus.
|
||||
if (sel.sectionLineId) {
|
||||
setSelectedSectionLineId(
|
||||
selectedSectionLineId === sel.sectionLineId ? null : sel.sectionLineId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (sel.wallId) {
|
||||
const id = sel.wallId;
|
||||
setSelectedWallIds(
|
||||
selectedWallIds.includes(id)
|
||||
? selectedWallIds.filter((w) => w !== id)
|
||||
: [...selectedWallIds, id],
|
||||
);
|
||||
} else if (sel.drawingId) {
|
||||
const id = sel.drawingId;
|
||||
setSelectedDrawingIds(
|
||||
selectedDrawingIds.includes(id)
|
||||
? selectedDrawingIds.filter((d) => d !== id)
|
||||
: [...selectedDrawingIds, id],
|
||||
);
|
||||
} else if (sel.ceilingId) {
|
||||
const id = sel.ceilingId;
|
||||
setSelectedCeilingIds(
|
||||
selectedCeilingIds.includes(id)
|
||||
? selectedCeilingIds.filter((c) => c !== id)
|
||||
: [...selectedCeilingIds, id],
|
||||
);
|
||||
} else if (sel.openingId) {
|
||||
const id = sel.openingId;
|
||||
setSelectedOpeningIds(
|
||||
selectedOpeningIds.includes(id)
|
||||
? selectedOpeningIds.filter((o) => o !== id)
|
||||
: [...selectedOpeningIds, id],
|
||||
);
|
||||
} else if (sel.stairId) {
|
||||
const id = sel.stairId;
|
||||
setSelectedStairIds(
|
||||
selectedStairIds.includes(id)
|
||||
? selectedStairIds.filter((s) => s !== id)
|
||||
: [...selectedStairIds, id],
|
||||
);
|
||||
} else if (sel.roomId) {
|
||||
const id = sel.roomId;
|
||||
setSelectedRoomIds(
|
||||
selectedRoomIds.includes(id)
|
||||
? selectedRoomIds.filter((r) => r !== id)
|
||||
: [...selectedRoomIds, id],
|
||||
);
|
||||
} else if (sel.extrudedSolidId) {
|
||||
const id = sel.extrudedSolidId;
|
||||
setSelectedExtrudedSolidIds(
|
||||
selectedExtrudedSolidIds.includes(id)
|
||||
? selectedExtrudedSolidIds.filter((x) => x !== id)
|
||||
: [...selectedExtrudedSolidIds, id],
|
||||
);
|
||||
} else if (sel.columnId) {
|
||||
const id = sel.columnId;
|
||||
setSelectedColumnIds(
|
||||
selectedColumnIds.includes(id)
|
||||
? selectedColumnIds.filter((x) => x !== id)
|
||||
: [...selectedColumnIds, id],
|
||||
);
|
||||
} else if (sel.roofId) {
|
||||
const id = sel.roofId;
|
||||
setSelectedRoofIds(
|
||||
selectedRoofIds.includes(id)
|
||||
? selectedRoofIds.filter((x) => x !== id)
|
||||
: [...selectedRoofIds, id],
|
||||
);
|
||||
} else if (sel.contextObjectId) {
|
||||
const id = sel.contextObjectId;
|
||||
setSelectedContextObjectIds(
|
||||
selectedContextObjectIds.includes(id)
|
||||
? selectedContextObjectIds.filter((x) => x !== id)
|
||||
: [...selectedContextObjectIds, id],
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Ohne Shift: ersetzen. Öffnung hat Vorrang (ihr Symbol liegt in der Lücke),
|
||||
// dann Wand, dann 2D-Element, dann Decke; sonst leeren. Die Kanäle schließen
|
||||
// sich gegenseitig aus.
|
||||
// Alle Kanäle ausser dem gewählten leeren — als Helfer, damit jeder Zweig
|
||||
// (inkl. des neuen Dach-Kanals) konsistent bleibt.
|
||||
const clearOthers = (keep: string) => {
|
||||
if (keep !== "wall") setSelectedWallIds([]);
|
||||
if (keep !== "drawing") setSelectedDrawingId(null);
|
||||
if (keep !== "ceiling") setSelectedCeilingIds([]);
|
||||
if (keep !== "opening") setSelectedOpeningIds([]);
|
||||
if (keep !== "stair") setSelectedStairIds([]);
|
||||
if (keep !== "room") setSelectedRoomIds([]);
|
||||
if (keep !== "extrudedSolid") setSelectedExtrudedSolidIds([]);
|
||||
if (keep !== "column") setSelectedColumnIds([]);
|
||||
if (keep !== "roof") setSelectedRoofIds([]);
|
||||
if (keep !== "contextObject") setSelectedContextObjectIds([]);
|
||||
if (keep !== "sectionLine") setSelectedSectionLineId(null);
|
||||
};
|
||||
if (sel.sectionLineId) {
|
||||
setSelectedSectionLineId(sel.sectionLineId);
|
||||
clearOthers("sectionLine");
|
||||
} else if (sel.openingId) {
|
||||
setSelectedOpeningIds([sel.openingId]);
|
||||
clearOthers("opening");
|
||||
} else if (sel.wallId) {
|
||||
setSelectedWallIds([sel.wallId]);
|
||||
clearOthers("wall");
|
||||
} else if (sel.columnId) {
|
||||
setSelectedColumnIds([sel.columnId]);
|
||||
clearOthers("column");
|
||||
} else if (sel.drawingId) {
|
||||
setSelectedDrawingId(sel.drawingId);
|
||||
clearOthers("drawing");
|
||||
} else if (sel.extrudedSolidId) {
|
||||
setSelectedExtrudedSolidIds([sel.extrudedSolidId]);
|
||||
clearOthers("extrudedSolid");
|
||||
} else if (sel.ceilingId) {
|
||||
setSelectedCeilingIds([sel.ceilingId]);
|
||||
clearOthers("ceiling");
|
||||
} else if (sel.stairId) {
|
||||
setSelectedStairIds([sel.stairId]);
|
||||
clearOthers("stair");
|
||||
} else if (sel.roomId) {
|
||||
setSelectedRoomIds([sel.roomId]);
|
||||
clearOthers("room");
|
||||
} else if (sel.roofId) {
|
||||
setSelectedRoofIds([sel.roofId]);
|
||||
clearOthers("roof");
|
||||
} else if (sel.contextObjectId) {
|
||||
setSelectedContextObjectIds([sel.contextObjectId]);
|
||||
clearOthers("contextObject");
|
||||
} else {
|
||||
clearOthers("");
|
||||
}
|
||||
};
|
||||
// Links-Drag (Marquee) im Grundriss: die getroffene Wand-MENGE übernehmen
|
||||
// (nach CAD-Konvention von PlanView bestimmt). Ersetzt die bisherige Auswahl.
|
||||
const onPlanMarquee = (sel: MarqueeSelection) => {
|
||||
setSelectedWallIds(sel.wallIds);
|
||||
setSelectedDrawingIds(sel.drawingIds ?? []);
|
||||
// Decken/Öffnungen/Treppen/Räume/Extrusionen/Dächer sind (noch) nicht Teil
|
||||
// des Marquee → beim Aufziehen abwählen.
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
};
|
||||
// Rechts-Klick im Grundriss: Wand-Kontextmenü. Trifft der Klick eine Wand, die
|
||||
// NICHT bereits in der Auswahl ist, wird die Auswahl auf sie gesetzt (damit die
|
||||
// Aktionen einen Bezug haben); innerhalb einer bestehenden Mehrfachauswahl
|
||||
// bleibt die Menge erhalten.
|
||||
const onPlanContextMenu = (info: {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
model: Vec2;
|
||||
}) => {
|
||||
const wallId = hitTestWall(project, activeLevelId, visibleCodes, info.model);
|
||||
if (wallId && !selectedWallIds.includes(wallId)) setSelectedWallIds([wallId]);
|
||||
setMenu({ kind: "plan", x: info.clientX, y: info.clientY, wallId });
|
||||
};
|
||||
// Links-Klick im 3D: per Raycast getroffene Wand wählen (Einzelauswahl).
|
||||
const onViewportSelectWall = (wallId: string | null) => {
|
||||
setSelectedWallIds(wallId ? [wallId] : []);
|
||||
if (wallId) {
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
}
|
||||
};
|
||||
// Links-Klick im 3D: per Raycast getroffene Treppe wählen (Einzelauswahl).
|
||||
const onViewportSelectStair = (stairId: string | null) => {
|
||||
setSelectedStairIds(stairId ? [stairId] : []);
|
||||
if (stairId) {
|
||||
setSelectedWallIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
}
|
||||
};
|
||||
// Links-Klick im 3D: per Raycast getroffene Decke wählen (Einzelauswahl).
|
||||
const onViewportSelectCeiling = (ceilingId: string | null) => {
|
||||
setSelectedCeilingIds(ceilingId ? [ceilingId] : []);
|
||||
if (ceilingId) {
|
||||
setSelectedWallIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
}
|
||||
};
|
||||
// Links-Klick im 3D: per Raycast getroffene Öffnung wählen (Einzelauswahl).
|
||||
const onViewportSelectOpening = (openingId: string | null) => {
|
||||
setSelectedOpeningIds(openingId ? [openingId] : []);
|
||||
if (openingId) {
|
||||
setSelectedWallIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
}
|
||||
};
|
||||
// Links-KLICK im Nordstern-3D-Viewport (WASM, TS-Raycast): getroffenes Bauteil
|
||||
// wählen, sodass das Attribute-/Objekt-Info-Panel es zeigt (erster Schritt zur
|
||||
// 3D-Editierbarkeit). `additive` (Shift/Ctrl) = zur Auswahl DERSELBEN Art
|
||||
// hinzufügen, sonst ersetzen; beim Setzen die ANDEREN Arten-Arrays leeren
|
||||
// (Einzel-Selektion über Arten hinweg, konsistent mit dem 2D-Verhalten).
|
||||
// Leertreffer (kein Bauteil) → alle Selektionen leeren.
|
||||
const onViewport3dPick = (hit: Pick3dHit, additive: boolean) => {
|
||||
if (!hit) {
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
return;
|
||||
}
|
||||
if (hit.kind === "contextObject") {
|
||||
setSelectedContextObjectIds([hit.id]);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
return;
|
||||
}
|
||||
// Öffnung (Fenster/Tür) und Treppe sind jetzt auch im WASM-Viewport pickbar
|
||||
// (raycast3d/pickGeometry). Jede Art ersetzt die Auswahl exklusiv; nur Wände
|
||||
// unterstützen additive Mehrfachauswahl (wie bisher).
|
||||
if (hit.kind === "opening") {
|
||||
setSelectedOpeningIds([hit.id]);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
return;
|
||||
}
|
||||
if (hit.kind === "stair") {
|
||||
setSelectedStairIds([hit.id]);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
return;
|
||||
}
|
||||
if (hit.kind === "roof") {
|
||||
setSelectedRoofIds([hit.id]);
|
||||
setSelectedWallIds([]);
|
||||
setSelectedCeilingIds([]);
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
return;
|
||||
}
|
||||
if (hit.kind === "wall") {
|
||||
setSelectedWallIds(
|
||||
additive ? Array.from(new Set([...selectedWallIds, hit.id])) : [hit.id],
|
||||
);
|
||||
setSelectedCeilingIds([]);
|
||||
} else {
|
||||
setSelectedCeilingIds(
|
||||
additive ? Array.from(new Set([...selectedCeilingIds, hit.id])) : [hit.id],
|
||||
);
|
||||
setSelectedWallIds([]);
|
||||
}
|
||||
setSelectedDrawingId(null);
|
||||
setSelectedOpeningIds([]);
|
||||
setSelectedStairIds([]);
|
||||
setSelectedContextObjectIds([]);
|
||||
setSelectedRoomIds([]);
|
||||
setSelectedExtrudedSolidIds([]);
|
||||
setSelectedColumnIds([]);
|
||||
setSelectedRoofIds([]);
|
||||
setSelectedSectionLineId(null);
|
||||
};
|
||||
// Rechts-Klick im 3D: Wand-Kontextmenü an der Cursorposition.
|
||||
const onViewportContextMenu = (info: ViewportContextInfo) => {
|
||||
if (info.wallId && !selectedWallIds.includes(info.wallId)) {
|
||||
setSelectedWallIds([info.wallId]);
|
||||
}
|
||||
setMenu({
|
||||
kind: "viewport",
|
||||
x: info.clientX,
|
||||
y: info.clientY,
|
||||
wallId: info.wallId,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onOpenStampEditor,
|
||||
onEditDrawingText,
|
||||
commitTextEdit,
|
||||
onPlanSelect,
|
||||
onPlanMarquee,
|
||||
onPlanContextMenu,
|
||||
onViewportSelectWall,
|
||||
onViewportSelectStair,
|
||||
onViewportSelectCeiling,
|
||||
onViewportSelectOpening,
|
||||
onViewport3dPick,
|
||||
onViewportContextMenu,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user