Browse Source

多交互 交互修改

feature/071
yun 1 week ago
parent
commit
238674785b
  1. 6
      src/components/MultiAngleEditor.tsx
  2. 65
      src/components/WorkflowCanvas.tsx
  3. 3
      src/components/__tests__/MultiAngleEditor.test.tsx

6
src/components/MultiAngleEditor.tsx

@ -243,7 +243,7 @@ export function MultiAngleEditor({
horizontal: normalizeHorizontalDragAngle(dragState.startHorizontal + deltaX * CUBE_DRAG_DEGREES_PER_PIXEL), horizontal: normalizeHorizontalDragAngle(dragState.startHorizontal + deltaX * CUBE_DRAG_DEGREES_PER_PIXEL),
vertical: Math.round( vertical: Math.round(
clamp( clamp(
dragState.startVertical - deltaY * CUBE_DRAG_DEGREES_PER_PIXEL, dragState.startVertical + deltaY * CUBE_DRAG_DEGREES_PER_PIXEL,
MULTI_ANGLE_VERTICAL_MIN, MULTI_ANGLE_VERTICAL_MIN,
MULTI_ANGLE_VERTICAL_MAX MULTI_ANGLE_VERTICAL_MAX
) )
@ -315,7 +315,7 @@ export function MultiAngleEditor({
aria-label={t("multiAngle.adjustVerticalUp")} aria-label={t("multiAngle.adjustVerticalUp")}
onPointerDown={(event) => event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()}
onMouseDown={(event) => event.stopPropagation()} onMouseDown={(event) => event.stopPropagation()}
onClick={() => updateVertical(getNextMultiAngleVertical(settings.vertical, 1))} onClick={() => updateVertical(getNextMultiAngleVertical(settings.vertical, -1))}
className="absolute left-1/2 top-3 z-40 flex h-7 w-7 -translate-x-1/2 items-center justify-center rounded-md text-[var(--text-muted)] transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]" className="absolute left-1/2 top-3 z-40 flex h-7 w-7 -translate-x-1/2 items-center justify-center rounded-md text-[var(--text-muted)] transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
> >
<UpOutlined className="text-[12px]" /> <UpOutlined className="text-[12px]" />
@ -325,7 +325,7 @@ export function MultiAngleEditor({
aria-label={t("multiAngle.adjustVerticalDown")} aria-label={t("multiAngle.adjustVerticalDown")}
onPointerDown={(event) => event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()}
onMouseDown={(event) => event.stopPropagation()} onMouseDown={(event) => event.stopPropagation()}
onClick={() => updateVertical(getNextMultiAngleVertical(settings.vertical, -1))} onClick={() => updateVertical(getNextMultiAngleVertical(settings.vertical, 1))}
className="absolute bottom-3 left-1/2 z-40 flex h-7 w-7 -translate-x-1/2 items-center justify-center rounded-md text-[var(--text-muted)] transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]" className="absolute bottom-3 left-1/2 z-40 flex h-7 w-7 -translate-x-1/2 items-center justify-center rounded-md text-[var(--text-muted)] transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
> >
<DownOutlined className="text-[12px]" /> <DownOutlined className="text-[12px]" />

65
src/components/WorkflowCanvas.tsx

@ -98,10 +98,7 @@ const BOX_SELECTION_EXPAND_MIN_DISTANCE = 4;
const MULTI_SELECTION_BOUNDS_PADDING = 40; const MULTI_SELECTION_BOUNDS_PADDING = 40;
const GROUP_NODE_Z_INDEX = 0; const GROUP_NODE_Z_INDEX = 0;
const DEFAULT_NODE_Z_INDEX = 1; const DEFAULT_NODE_Z_INDEX = 1;
const SELECTED_NODE_Z_INDEX = 10000; const INITIAL_ACTIVE_NODE_Z_INDEX = 100;
const DRAGGING_GROUP_Z_INDEX = 9999;
const DRAGGING_GROUP_MEMBER_Z_INDEX = 10001;
const DRAGGING_NODE_Z_INDEX = 10002;
type ScreenPoint = { type ScreenPoint = {
x: number; x: number;
@ -575,6 +572,10 @@ export function WorkflowCanvas() {
const [isMiniMapOpen, setIsMiniMapOpen] = useState(false); const [isMiniMapOpen, setIsMiniMapOpen] = useState(false);
const [zoomPercent, setZoomPercent] = useState(100); const [zoomPercent, setZoomPercent] = useState(100);
const [draggingGroupId, setDraggingGroupId] = useState<string | null>(null); const [draggingGroupId, setDraggingGroupId] = useState<string | null>(null);
const [activeNodeLayer, setActiveNodeLayer] = useState<{ nodeId: string | null; zIndex: number }>({
nodeId: null,
zIndex: INITIAL_ACTIVE_NODE_Z_INDEX,
});
const canvasGridColor = "var(--canvas-grid)"; const canvasGridColor = "var(--canvas-grid)";
const miniMapMaskColor = "var(--surface-overlay)"; const miniMapMaskColor = "var(--surface-overlay)";
const miniMapNodeColor = "var(--text-disabled)"; const miniMapNodeColor = "var(--text-disabled)";
@ -587,6 +588,20 @@ export function WorkflowCanvas() {
const gestureZoomStartRef = useRef<{ zoom: number } | null>(null); const gestureZoomStartRef = useRef<{ zoom: number } | null>(null);
const [boxSelectionRect, setBoxSelectionRect] = useState<FlowRect | null>(null); const [boxSelectionRect, setBoxSelectionRect] = useState<FlowRect | null>(null);
const bringNodeToFront = useCallback((nodeId: string) => {
setActiveNodeLayer((current) => ({
nodeId,
zIndex: current.zIndex + 1,
}));
}, []);
const resetActiveNodeLayer = useCallback(() => {
setActiveNodeLayer((current) => ({
...current,
nodeId: null,
}));
}, []);
const onNodesChange = useCallback((changes: NodeChange<WorkflowNode>[]) => { const onNodesChange = useCallback((changes: NodeChange<WorkflowNode>[]) => {
if (!boxSelectionActiveRef.current) { if (!boxSelectionActiveRef.current) {
storeOnNodesChange(changes); storeOnNodesChange(changes);
@ -878,6 +893,13 @@ export function WorkflowCanvas() {
// Apply visual state that React Flow can turn into DOM classes and z-index. // Apply visual state that React Flow can turn into DOM classes and z-index.
const allNodes = useMemo(() => { const allNodes = useMemo(() => {
const draggingGroupNodeId = draggingGroupId ? `group-node-${draggingGroupId}` : null; const draggingGroupNodeId = draggingGroupId ? `group-node-${draggingGroupId}` : null;
const activeNode = activeNodeLayer.nodeId
? nodes.find((node) => node.id === activeNodeLayer.nodeId)
: undefined;
const activeGroupId = activeNode?.type === "group"
? (activeNode.data as { groupId?: string }).groupId
: undefined;
const activeGroupNodeId = activeGroupId ? `group-node-${activeGroupId}` : null;
return nodes.map((node) => { return nodes.map((node) => {
// Never dim Switch or ConditionalSwitch nodes themselves // Never dim Switch or ConditionalSwitch nodes themselves
@ -897,23 +919,29 @@ export function WorkflowCanvas() {
// Preserve existing className if any, add/remove dimmed/skipped classes // Preserve existing className if any, add/remove dimmed/skipped classes
const baseClass = (node.className || "").replace(/\bswitch-dimmed\b/g, "").replace(/\bnode-skipped\b/g, "").trim(); const baseClass = (node.className || "").replace(/\bswitch-dimmed\b/g, "").replace(/\bnode-skipped\b/g, "").trim();
const newClass = extraClasses ? `${baseClass} ${extraClasses}`.trim() : baseClass; const newClass = extraClasses ? `${baseClass} ${extraClasses}`.trim() : baseClass;
const zIndex = node.type === "group" let zIndex = node.type === "group" ? GROUP_NODE_Z_INDEX : DEFAULT_NODE_Z_INDEX;
? isDraggingGroupShell
? DRAGGING_GROUP_Z_INDEX if (activeGroupId && node.id === activeGroupNodeId) {
: GROUP_NODE_Z_INDEX zIndex = activeNodeLayer.zIndex;
: isDraggingGroupMember } else if (activeGroupId && node.type !== "group" && (node.groupId === activeGroupId || node.parentId === activeGroupNodeId)) {
? DRAGGING_GROUP_MEMBER_Z_INDEX zIndex = activeNodeLayer.zIndex + 1;
: node.dragging } else if (!activeGroupId && node.id === activeNodeLayer.nodeId) {
? DRAGGING_NODE_Z_INDEX zIndex = activeNodeLayer.zIndex;
: node.selected }
? SELECTED_NODE_Z_INDEX
: DEFAULT_NODE_Z_INDEX; if (isDraggingGroupShell) {
zIndex = Math.max(zIndex, activeNodeLayer.zIndex);
} else if (isDraggingGroupMember) {
zIndex = Math.max(zIndex, activeNodeLayer.zIndex + 1);
} else if (node.dragging) {
zIndex = Math.max(zIndex, activeNodeLayer.zIndex + 1);
}
// Only create new node object if className changed // Only create new node object if className changed
if (node.className === newClass && node.zIndex === zIndex) return node; if (node.className === newClass && node.zIndex === zIndex) return node;
return { ...node, className: newClass, zIndex }; return { ...node, className: newClass, zIndex };
}); });
}, [nodes, dimmedNodeIds, draggingGroupId, skippedNodeIds]); }, [nodes, activeNodeLayer, dimmedNodeIds, draggingGroupId, skippedNodeIds]);
const absoluteNodePositions = useMemo(() => { const absoluteNodePositions = useMemo(() => {
const byId = new Map(allNodes.map((node) => [node.id, node])); const byId = new Map(allNodes.map((node) => [node.id, node]));
@ -3049,9 +3077,13 @@ export function WorkflowCanvas() {
onConnectEnd={handleConnectEnd} onConnectEnd={handleConnectEnd}
onMoveStart={() => { isPanningRef.current = true; setHoveredNodeId(null); document.documentElement.classList.add("canvas-interacting"); }} onMoveStart={() => { isPanningRef.current = true; setHoveredNodeId(null); document.documentElement.classList.add("canvas-interacting"); }}
onMoveEnd={() => { isPanningRef.current = false; document.documentElement.classList.remove("canvas-interacting"); syncZoomPercent(); }} onMoveEnd={() => { isPanningRef.current = false; document.documentElement.classList.remove("canvas-interacting"); syncZoomPercent(); }}
onNodeClick={(_event, node) => {
bringNodeToFront(node.id);
}}
onNodeDragStart={(_event, node) => { onNodeDragStart={(_event, node) => {
isDraggingNodeRef.current = true; isDraggingNodeRef.current = true;
document.documentElement.classList.add("canvas-interacting"); document.documentElement.classList.add("canvas-interacting");
bringNodeToFront(node.id);
setDraggingGroupId(node.type === "group" ? (node.data as { groupId?: string }).groupId ?? null : null); setDraggingGroupId(node.type === "group" ? (node.data as { groupId?: string }).groupId ?? null : null);
}} }}
onNodeDragStop={(event, node) => { onNodeDragStop={(event, node) => {
@ -3060,6 +3092,7 @@ export function WorkflowCanvas() {
setDraggingGroupId(null); setDraggingGroupId(null);
handleNodeDragStop(event, node); handleNodeDragStop(event, node);
}} }}
onPaneClick={resetActiveNodeLayer}
onPaneContextMenu={openCanvasContextMenu} onPaneContextMenu={openCanvasContextMenu}
onNodeContextMenu={openNodeContextMenu} onNodeContextMenu={openNodeContextMenu}
onSelectionStart={handleSelectionStart} onSelectionStart={handleSelectionStart}

3
src/components/__tests__/MultiAngleEditor.test.tsx

@ -99,12 +99,13 @@ describe("MultiAngleEditor", () => {
render(<MultiAngleHarness />); render(<MultiAngleHarness />);
const globe = screen.getByRole("application", { name: "Multi-angle sight control" }); const globe = screen.getByRole("application", { name: "Multi-angle sight control" });
fireEvent.change(screen.getByLabelText("Vertical"), { target: { value: "0" } });
fireEvent.pointerDown(globe, { pointerId: 1, pointerType: "mouse", clientX: 100, clientY: 100, buttons: 1 }); fireEvent.pointerDown(globe, { pointerId: 1, pointerType: "mouse", clientX: 100, clientY: 100, buttons: 1 });
fireEvent.pointerMove(globe, { pointerId: 1, pointerType: "mouse", clientX: 150, clientY: 80, buttons: 1 }); fireEvent.pointerMove(globe, { pointerId: 1, pointerType: "mouse", clientX: 150, clientY: 80, buttons: 1 });
await waitFor(() => expect(screen.getByLabelText("Horizontal")).toHaveValue("40")); await waitFor(() => expect(screen.getByLabelText("Horizontal")).toHaveValue("40"));
expect(screen.getByLabelText("Vertical")).toHaveValue("-14"); expect(screen.getByLabelText("Vertical")).toHaveValue("-16");
fireEvent.pointerUp(globe, { pointerId: 1, pointerType: "mouse" }); fireEvent.pointerUp(globe, { pointerId: 1, pointerType: "mouse" });

Loading…
Cancel
Save