From 74edfd3e5cedeb78977d6ef88c52f04e91238707 Mon Sep 17 00:00:00 2001 From: Ruben Thoms Date: Thu, 5 Oct 2023 17:12:59 +0200 Subject: [PATCH] Further implementation --- frontend/src/App.tsx | 2 +- frontend/src/framework/GuiMessageBroker.ts | 2 + frontend/src/framework/Workbench.ts | 7 ++ .../src/framework/internal/GlobalCursor.tsx | 65 +++++++++++++++++++ .../dataChannelVisualization.tsx | 23 ++++--- .../private-components/inputChannelNode.tsx | 11 +++- .../ViewWrapper/viewWrapper.tsx | 10 ++- 7 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 frontend/src/framework/internal/GlobalCursor.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 76f62c195..726862728 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { DrawerContent, GuiState } from "@framework/GuiMessageBroker"; +import { DrawerContent, GuiState, useGuiValue } from "@framework/GuiMessageBroker"; import { LayoutElement, Workbench } from "@framework/Workbench"; import { DataChannelVisualization } from "@framework/internal/components/Content/private-components/DataChannelVisualization"; import { NavBar } from "@framework/internal/components/NavBar"; diff --git a/frontend/src/framework/GuiMessageBroker.ts b/frontend/src/framework/GuiMessageBroker.ts index ff4355e51..2926ad23f 100644 --- a/frontend/src/framework/GuiMessageBroker.ts +++ b/frontend/src/framework/GuiMessageBroker.ts @@ -15,6 +15,7 @@ export enum GuiState { SettingsPanelWidthInPercent = "settingsPanelWidthInPercent", LoadingEnsembleSet = "loadingEnsembleSet", ActiveModuleInstanceId = "activeModuleInstanceId", + DataChannelConnectionLayerVisible = "dataChannelConnectionLayerVisible", } export enum GuiEvent { @@ -72,6 +73,7 @@ type GuiStateValueTypes = { [GuiState.SettingsPanelWidthInPercent]: number; [GuiState.LoadingEnsembleSet]: boolean; [GuiState.ActiveModuleInstanceId]: string; + [GuiState.DataChannelConnectionLayerVisible]: boolean; }; const defaultStates: Map = new Map(); diff --git a/frontend/src/framework/Workbench.ts b/frontend/src/framework/Workbench.ts index 6d20caf8b..31c484361 100644 --- a/frontend/src/framework/Workbench.ts +++ b/frontend/src/framework/Workbench.ts @@ -11,6 +11,7 @@ import { Template } from "./TemplateRegistry"; import { WorkbenchServices } from "./WorkbenchServices"; import { WorkbenchSession } from "./WorkbenchSession"; import { loadEnsembleSetMetadataFromBackend } from "./internal/EnsembleSetLoader"; +import { GlobalCursor } from "./internal/GlobalCursor"; import { PrivateWorkbenchServices } from "./internal/PrivateWorkbenchServices"; import { PrivateWorkbenchSettings } from "./internal/PrivateWorkbenchSettings"; import { WorkbenchSessionPrivate } from "./internal/WorkbenchSessionPrivate"; @@ -36,6 +37,7 @@ export class Workbench { private _workbenchSettings: PrivateWorkbenchSettings; private _broadcaster: Broadcaster; private _guiMessageBroker: GuiMessageBroker; + private _globalCursor: GlobalCursor; private _subscribersMap: { [key: string]: Set<() => void> }; private _layout: LayoutElement[]; private _perModuleRunningInstanceNumber: Record; @@ -47,6 +49,7 @@ export class Workbench { this._workbenchSettings = new PrivateWorkbenchSettings(); this._broadcaster = new Broadcaster(); this._guiMessageBroker = new GuiMessageBroker(); + this._globalCursor = new GlobalCursor(); this._subscribersMap = {}; this._layout = []; this._perModuleRunningInstanceNumber = {}; @@ -85,6 +88,10 @@ export class Workbench { return this._guiMessageBroker; } + getGlobalCursor(): GlobalCursor { + return this._globalCursor; + } + private notifySubscribers(event: WorkbenchEvents): void { const subscribers = this._subscribersMap[event]; if (!subscribers) return; diff --git a/frontend/src/framework/internal/GlobalCursor.tsx b/frontend/src/framework/internal/GlobalCursor.tsx new file mode 100644 index 000000000..a437471bc --- /dev/null +++ b/frontend/src/framework/internal/GlobalCursor.tsx @@ -0,0 +1,65 @@ +export enum GlobalCursorType { + Default = "default", + Copy = "copy", + Pointer = "pointer", + Grab = "grab", + Grabbing = "grabbing", + Crosshair = "crosshair", + Move = "move", + Text = "text", + Wait = "wait", + Help = "help", + Progress = "progress", + Cell = "cell", + VerticalText = "vertical-text", + Alias = "alias", + ContextMenu = "context-menu", + NoDrop = "no-drop", + NotAllowed = "not-allowed", +} + +export class GlobalCursor { + private _cursorStack: GlobalCursorType[]; + private _lastCursor: GlobalCursorType = GlobalCursorType.Default; + + constructor() { + this._cursorStack = []; + } + + private updateBodyCursor() { + if (this._lastCursor !== GlobalCursorType.Default) { + const oldCursorClass = `cursor-${this._lastCursor}`; + document.body.classList.remove(oldCursorClass); + } + + if (this._cursorStack.length === 0) { + this._lastCursor = GlobalCursorType.Default; + return; + } + + const newCursor = this._cursorStack[this._cursorStack.length - 1]; + const newCursorClass = `cursor-${newCursor}`; + document.body.classList.add(newCursorClass); + this._lastCursor = newCursor; + } + + setOverrideCursor(cursorType: GlobalCursorType) { + this._cursorStack.push(cursorType); + this.updateBodyCursor(); + } + + restoreOverrideCursor() { + if (this._cursorStack.length > 0) { + this._cursorStack.pop(); + this.updateBodyCursor(); + } + } + + changeOverrideCursor(cursorType: GlobalCursorType) { + if (this._cursorStack.length > 0) { + this._cursorStack.pop(); + } + this._cursorStack.push(cursorType); + this.updateBodyCursor(); + } +} diff --git a/frontend/src/framework/internal/components/Content/private-components/DataChannelVisualization/dataChannelVisualization.tsx b/frontend/src/framework/internal/components/Content/private-components/DataChannelVisualization/dataChannelVisualization.tsx index a57e11d71..6213d856d 100644 --- a/frontend/src/framework/internal/components/Content/private-components/DataChannelVisualization/dataChannelVisualization.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/DataChannelVisualization/dataChannelVisualization.tsx @@ -1,7 +1,8 @@ import React from "react"; -import { GuiEvent, GuiEventPayloads } from "@framework/GuiMessageBroker"; +import { GuiEvent, GuiEventPayloads, GuiState, useGuiState } from "@framework/GuiMessageBroker"; import { Workbench } from "@framework/Workbench"; +import { GlobalCursorType } from "@framework/internal/GlobalCursor"; import { useElementBoundingRect } from "@lib/hooks/useElementBoundingRect"; import { Point } from "@lib/utils/geometry"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; @@ -27,7 +28,10 @@ export const DataChannelVisualization: React.FC = const [originPoint, setOriginPoint] = React.useState({ x: 0, y: 0 }); const [currentPointerPosition, setCurrentPointerPosition] = React.useState({ x: 0, y: 0 }); const [currentChannelName, setCurrentChannelName] = React.useState(null); - const [showDataChannelConnections, setShowDataChannelConnections] = React.useState(false); + const [showDataChannelConnections, setShowDataChannelConnections] = useGuiState( + props.workbench.getGuiMessageBroker(), + GuiState.DataChannelConnectionLayerVisible + ); const [highlightedDataChannelConnection, setHighlightedDataChannelConnection] = React.useState<{ moduleInstanceId: string; dataChannelName: string; @@ -58,7 +62,7 @@ export const DataChannelVisualization: React.FC = }; setVisible(true); setOriginPoint(currentOriginPoint); - document.body.classList.add("cursor-crosshair"); + props.workbench.getGlobalCursor().setOverrideCursor(GlobalCursorType.Crosshair); mousePressed = true; setCurrentPointerPosition(currentOriginPoint); setCurrentChannelName(null); @@ -79,9 +83,7 @@ export const DataChannelVisualization: React.FC = setVisible(false); setEditDataChannelConnectionsForModuleInstanceId(null); setShowDataChannelConnections(false); - document.body.classList.remove("cursor-crosshair"); - document.body.classList.remove("cursor-not-allowed"); - document.body.classList.remove("cursor-copy"); + props.workbench.getGlobalCursor().restoreOverrideCursor(); } function handlePointerUp() { @@ -118,18 +120,15 @@ export const DataChannelVisualization: React.FC = } function handleNodeHover(payload: GuiEventPayloads[GuiEvent.DataChannelNodeHover]) { - document.body.classList.remove("cursor-crosshair"); if (payload.connectionAllowed) { - document.body.classList.add("cursor-copy"); + props.workbench.getGlobalCursor().setOverrideCursor(GlobalCursorType.Copy); } else { - document.body.classList.add("cursor-not-allowed"); + props.workbench.getGlobalCursor().setOverrideCursor(GlobalCursorType.NotAllowed); } } function handleNodeUnhover() { - document.body.classList.remove("cursor-copy"); - document.body.classList.remove("cursor-not-allowed"); - document.body.classList.add("cursor-crosshair"); + props.workbench.getGlobalCursor().restoreOverrideCursor(); } function handleEditDataChannelConnectionsRequest( diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/inputChannelNode.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/inputChannelNode.tsx index 572488746..ee9bd3102 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/inputChannelNode.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/inputChannelNode.tsx @@ -59,6 +59,7 @@ export const InputChannelNode: React.FC = (props) => { }, [props.moduleInstanceId, props.inputName, props.workbench]); React.useEffect(() => { + console.debug("effect"); let isHovered = false; let isConnectable = false; let moduleInstanceId = ""; @@ -111,6 +112,7 @@ export const InputChannelNode: React.FC = (props) => { } function handlePointerUp(e: PointerEvent) { + console.debug("pointer up", isHovered, isConnectable); if (isHovered) { if (isConnectable) { props.onChannelConnect(props.inputName, moduleInstanceId, pointerEventToPoint(e)); @@ -130,6 +132,7 @@ export const InputChannelNode: React.FC = (props) => { setConnectable(false); isConnectable = false; setEditDataChannelConnections(false); + console.debug("done"); } function handlePointerMove(e: PointerEvent) { @@ -137,10 +140,14 @@ export const InputChannelNode: React.FC = (props) => { if (boundingRect && rectContainsPoint(boundingRect, pointerEventToPoint(e))) { setHovered(true); isHovered = true; + console.debug("hovered"); return; } - setHovered(false); - isHovered = false; + if (isHovered) { + setHovered(false); + isHovered = false; + console.debug("unhovered"); + } } const removeDataChannelOriginPointerDownHandler = guiMessageBroker.subscribeToEvent( diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx index bbd95e8d9..b205245b4 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { DrawerContent, GuiEvent, GuiState, useGuiState } from "@framework/GuiMessageBroker"; +import { DrawerContent, GuiEvent, GuiState, useGuiState, useGuiValue } from "@framework/GuiMessageBroker"; import { ModuleInstance } from "@framework/ModuleInstance"; import { Workbench } from "@framework/Workbench"; import { Point, pointDifference, pointRelativeToDomRect, pointerEventToPoint } from "@lib/utils/geometry"; @@ -38,6 +38,11 @@ export const ViewWrapper: React.FC = (props) => { const guiMessageBroker = props.workbench.getGuiMessageBroker(); + const dataChannelConnectionsLayerVisible = useGuiValue( + guiMessageBroker, + GuiState.DataChannelConnectionLayerVisible + ); + const timeRef = React.useRef(null); const [currentInputName, setCurrentInputName] = React.useState(null); @@ -86,6 +91,9 @@ export const ViewWrapper: React.FC = (props) => { } function handlePointerUp() { + if (dataChannelConnectionsLayerVisible) { + return; + } if (drawerContent === DrawerContent.ModulesList) { if (!timeRef.current || Date.now() - timeRef.current < 800) { handleModuleClick();