Tab-Drag-Steuerung aus App.tsx in eigenen Hook extrahiert (state/useDockDragState.ts).
Reorder/Einreihen/Stapeln/Lösen (usePanelDrag-Wrapper) + Float-Titel-Drag über Rand-Andockzonen. Reine Verschiebung, keine Verhaltensänderung. App.tsx: 3690 → 3655 Zeilen. tsc -b sauber, vitest 891/891 grün, vite build ok.
This commit is contained in:
+20
-55
@@ -118,6 +118,7 @@ import { useDrawingToolState } from "./state/useDrawingToolState";
|
|||||||
import { useSelectionState } from "./state/useSelectionState";
|
import { useSelectionState } from "./state/useSelectionState";
|
||||||
import { useGripEditing } from "./state/useGripEditing";
|
import { useGripEditing } from "./state/useGripEditing";
|
||||||
import { usePanelHostBase } from "./state/usePanelHostBase";
|
import { usePanelHostBase } from "./state/usePanelHostBase";
|
||||||
|
import { useDockDragState } from "./state/useDockDragState";
|
||||||
import { useProjectFileActions } from "./state/useProjectFileActions";
|
import { useProjectFileActions } from "./state/useProjectFileActions";
|
||||||
import { useProjectActions } from "./state/useProjectActions";
|
import { useProjectActions } from "./state/useProjectActions";
|
||||||
import {
|
import {
|
||||||
@@ -162,10 +163,9 @@ import { reconcileLayout } from "./state/layoutSlice";
|
|||||||
// Panel-System.
|
// Panel-System.
|
||||||
import { Dock } from "./panels/Dock";
|
import { Dock } from "./panels/Dock";
|
||||||
import { FloatingPanel } from "./panels/FloatingPanel";
|
import { FloatingPanel } from "./panels/FloatingPanel";
|
||||||
import { DragOverlay, usePanelDrag } from "./panels/panelDrag";
|
import { DragOverlay } from "./panels/panelDrag";
|
||||||
import { itemDisplay } from "./panels/displayMode";
|
import { itemDisplay } from "./panels/displayMode";
|
||||||
import { PanelHostContext } from "./panels/types";
|
import { PanelHostContext } from "./panels/types";
|
||||||
import type { DockId } from "./panels/types";
|
|
||||||
import { hostWithMode } from "./panels/host";
|
import { hostWithMode } from "./panels/host";
|
||||||
import { hitTestWall } from "./viewport/planHitTest";
|
import { hitTestWall } from "./viewport/planHitTest";
|
||||||
import { deriveSelection } from "./state/selectionInfo";
|
import { deriveSelection } from "./state/selectionInfo";
|
||||||
@@ -1785,61 +1785,26 @@ export default function App() {
|
|||||||
const redockFloat = useStore((s) => s.redockFloat);
|
const redockFloat = useStore((s) => s.redockFloat);
|
||||||
const dockFloatTo = useStore((s) => s.dockFloatTo);
|
const dockFloatTo = useStore((s) => s.dockFloatTo);
|
||||||
|
|
||||||
// ── Tab-Drag-Steuerung (Reorder / Einreihen / Stapeln / Lösen) ────────────
|
// Tab-Drag-Steuerung (Reorder/Einreihen/Stapeln/Lösen) + Float-Titel-Drag
|
||||||
// Der Controller (usePanelDrag) klärt anhand der Bewegung Klick vs. Ziehen
|
// über Rand-Andockzonen — siehe state/useDockDragState.ts.
|
||||||
// und löst beim Loslassen das Ziel auf; die Auflösung selbst macht der Slice.
|
const {
|
||||||
const { drag, startTabDrag } = usePanelDrag({
|
drag,
|
||||||
onActivate: activateTab,
|
startTabDrag,
|
||||||
onJoinGroup: joinGroup,
|
onFloatTitleDrop,
|
||||||
onNewGroup: newGroup,
|
onFloatTitleDrag,
|
||||||
onDropFloat: dropFloat,
|
leftHasTabs,
|
||||||
|
rightHasTabs,
|
||||||
|
dragActive,
|
||||||
|
activeZone,
|
||||||
|
} = useDockDragState({
|
||||||
|
layout,
|
||||||
|
activateTab,
|
||||||
|
joinGroup,
|
||||||
|
newGroup,
|
||||||
|
dropFloat,
|
||||||
|
dockFloatTo,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Zustand für die Rand-Zonen-Hervorhebung beim Titel-Drag eines Fensters.
|
|
||||||
// `floatDragging` schaltet die Zonen ein (sobald die Titelleiste gezogen
|
|
||||||
// wird), `floatDragHover` markiert die gerade überfahrene Zone.
|
|
||||||
const [floatDragging, setFloatDragging] = useState(false);
|
|
||||||
const [floatDragHover, setFloatDragHover] = useState<DockId | null>(null);
|
|
||||||
|
|
||||||
// Titel-Drag eines schwebenden Fensters über eine Rand-Zone: liefert true,
|
|
||||||
// wenn das Fenster angedockt wurde (dann unterbleibt das reine Verschieben).
|
|
||||||
// Trefferprüfung über die Datenattribute der Rand-Zonen (data-drop-zone).
|
|
||||||
const onFloatTitleDrop = (panelId: string, clientX: number, clientY: number): boolean => {
|
|
||||||
setFloatDragging(false);
|
|
||||||
setFloatDragHover(null);
|
|
||||||
const zone = document
|
|
||||||
.elementsFromPoint(clientX, clientY)
|
|
||||||
.find((el) => (el as HTMLElement).dataset?.dropZone !== undefined) as
|
|
||||||
| HTMLElement
|
|
||||||
| undefined;
|
|
||||||
if (!zone) return false;
|
|
||||||
const dock = zone.dataset.dropZone as DockId;
|
|
||||||
dockFloatTo(panelId, dock);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFloatTitleDrag = (_panelId: string, clientX: number, clientY: number) => {
|
|
||||||
if (!floatDragging) setFloatDragging(true);
|
|
||||||
const zone = document
|
|
||||||
.elementsFromPoint(clientX, clientY)
|
|
||||||
.find((el) => (el as HTMLElement).dataset?.dropZone !== undefined) as
|
|
||||||
| HTMLElement
|
|
||||||
| undefined;
|
|
||||||
setFloatDragHover(zone ? (zone.dataset.dropZone as DockId) : null);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ein Dock wird nur gerendert, wenn es Tabs hat — ein leeres Dock soll die
|
|
||||||
// Mitte nicht stauchen. (Rechtes Dock ist standardmäßig leer: Ressourcen ist
|
|
||||||
// ein schwebendes Fenster.)
|
|
||||||
const leftHasTabs = layout.left.groups.length > 0;
|
|
||||||
const rightHasTabs = layout.right.groups.length > 0;
|
|
||||||
// Rand-Andock-Zonen sind aktiv, solange irgendeine Drag-Geste läuft (Tab-Drag
|
|
||||||
// ODER Titel-Drag eines schwebenden Fensters). Die gerade überfahrene Zone
|
|
||||||
// (für die Hervorhebung) kommt aus dem Tab-Drag-Hover bzw. dem Float-Hover.
|
|
||||||
const dragActive = drag !== null || floatDragging;
|
|
||||||
const activeZone: DockId | null =
|
|
||||||
drag && drag.hover.kind === "zone" ? drag.hover.dock : floatDragHover;
|
|
||||||
|
|
||||||
// Handler-Bündel für das schwebende Ressourcen-Fenster (ResourceManager).
|
// Handler-Bündel für das schwebende Ressourcen-Fenster (ResourceManager).
|
||||||
const resourceHandlers: ResourceManagerHandlers = {
|
const resourceHandlers: ResourceManagerHandlers = {
|
||||||
onPatchComponent: patchComponent,
|
onPatchComponent: patchComponent,
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// Tab-Drag-Steuerung (Reorder / Einreihen / Stapeln / Lösen) + Titel-Drag eines
|
||||||
|
// schwebenden Fensters über eine Rand-Andockzone. Der eigentliche Drag-vs-Klick-
|
||||||
|
// Controller ist `usePanelDrag` (panels/panelDrag.tsx); dieser Hook bündelt nur
|
||||||
|
// den App-lokalen Float-Titel-Drag-Zustand + die abgeleiteten Rand-Zonen-Werte.
|
||||||
|
//
|
||||||
|
// Extrahiert aus App.tsx. Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { usePanelDrag } from "../panels/panelDrag";
|
||||||
|
import type { DockId, LayoutState } from "../panels/types";
|
||||||
|
|
||||||
|
export function useDockDragState(opts: {
|
||||||
|
layout: LayoutState;
|
||||||
|
activateTab: (panelId: string) => void;
|
||||||
|
joinGroup: (panelId: string, dock: DockId, group: number, index?: number) => void;
|
||||||
|
newGroup: (panelId: string, dock: DockId, at: number) => void;
|
||||||
|
dropFloat: (panelId: string, x: number, y: number) => void;
|
||||||
|
dockFloatTo: (panelId: string, dock: DockId) => void;
|
||||||
|
}) {
|
||||||
|
const { layout, activateTab, joinGroup, newGroup, dropFloat, dockFloatTo } = opts;
|
||||||
|
|
||||||
|
// Der Controller (usePanelDrag) klärt anhand der Bewegung Klick vs. Ziehen
|
||||||
|
// und löst beim Loslassen das Ziel auf; die Auflösung selbst macht der Slice.
|
||||||
|
const { drag, startTabDrag } = usePanelDrag({
|
||||||
|
onActivate: activateTab,
|
||||||
|
onJoinGroup: joinGroup,
|
||||||
|
onNewGroup: newGroup,
|
||||||
|
onDropFloat: dropFloat,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zustand für die Rand-Zonen-Hervorhebung beim Titel-Drag eines Fensters.
|
||||||
|
// `floatDragging` schaltet die Zonen ein (sobald die Titelleiste gezogen
|
||||||
|
// wird), `floatDragHover` markiert die gerade überfahrene Zone.
|
||||||
|
const [floatDragging, setFloatDragging] = useState(false);
|
||||||
|
const [floatDragHover, setFloatDragHover] = useState<DockId | null>(null);
|
||||||
|
|
||||||
|
// Titel-Drag eines schwebenden Fensters über eine Rand-Zone: liefert true,
|
||||||
|
// wenn das Fenster angedockt wurde (dann unterbleibt das reine Verschieben).
|
||||||
|
// Trefferprüfung über die Datenattribute der Rand-Zonen (data-drop-zone).
|
||||||
|
const onFloatTitleDrop = (panelId: string, clientX: number, clientY: number): boolean => {
|
||||||
|
setFloatDragging(false);
|
||||||
|
setFloatDragHover(null);
|
||||||
|
const zone = document
|
||||||
|
.elementsFromPoint(clientX, clientY)
|
||||||
|
.find((el) => (el as HTMLElement).dataset?.dropZone !== undefined) as
|
||||||
|
| HTMLElement
|
||||||
|
| undefined;
|
||||||
|
if (!zone) return false;
|
||||||
|
const dock = zone.dataset.dropZone as DockId;
|
||||||
|
dockFloatTo(panelId, dock);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFloatTitleDrag = (_panelId: string, clientX: number, clientY: number) => {
|
||||||
|
if (!floatDragging) setFloatDragging(true);
|
||||||
|
const zone = document
|
||||||
|
.elementsFromPoint(clientX, clientY)
|
||||||
|
.find((el) => (el as HTMLElement).dataset?.dropZone !== undefined) as
|
||||||
|
| HTMLElement
|
||||||
|
| undefined;
|
||||||
|
setFloatDragHover(zone ? (zone.dataset.dropZone as DockId) : null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ein Dock wird nur gerendert, wenn es Tabs hat — ein leeres Dock soll die
|
||||||
|
// Mitte nicht stauchen. (Rechtes Dock ist standardmäßig leer: Ressourcen ist
|
||||||
|
// ein schwebendes Fenster.)
|
||||||
|
const leftHasTabs = layout.left.groups.length > 0;
|
||||||
|
const rightHasTabs = layout.right.groups.length > 0;
|
||||||
|
// Rand-Andock-Zonen sind aktiv, solange irgendeine Drag-Geste läuft (Tab-Drag
|
||||||
|
// ODER Titel-Drag eines schwebenden Fensters). Die gerade überfahrene Zone
|
||||||
|
// (für die Hervorhebung) kommt aus dem Tab-Drag-Hover bzw. dem Float-Hover.
|
||||||
|
const dragActive = drag !== null || floatDragging;
|
||||||
|
const activeZone: DockId | null =
|
||||||
|
drag && drag.hover.kind === "zone" ? drag.hover.dock : floatDragHover;
|
||||||
|
|
||||||
|
return {
|
||||||
|
drag,
|
||||||
|
startTabDrag,
|
||||||
|
onFloatTitleDrop,
|
||||||
|
onFloatTitleDrag,
|
||||||
|
leftHasTabs,
|
||||||
|
rightHasTabs,
|
||||||
|
dragActive,
|
||||||
|
activeZone,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user