Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New UX for connecting data channels #247

Merged
merged 32 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f157b4d
First approach
rubenthoms Jun 29, 2023
264a3ab
Further improvements
rubenthoms Jun 29, 2023
fae882b
Properly working version
rubenthoms Jun 30, 2023
28f691f
Improvements and adjustments
rubenthoms Jun 30, 2023
c869469
Refactored names, improved UX and function signatures
rubenthoms Jul 3, 2023
de16eec
Started implementing GuiState
rubenthoms Jul 4, 2023
2bbc299
Implemented GuiState utilities
rubenthoms Jul 5, 2023
c8836b1
Merge branch 'main' into improved-data-channels
rubenthoms Jul 18, 2023
6810f5e
Started adjusting to new main
rubenthoms Jul 18, 2023
cc4e68a
Further adjusted to main and fixed issues
rubenthoms Jul 19, 2023
95592f7
Fixed issue
rubenthoms Jul 21, 2023
0844cf9
Merge remote-tracking branch 'equinor/main' into improved-data-channels
rubenthoms Aug 17, 2023
8ea2e24
Adjusted to new main
rubenthoms Aug 17, 2023
0333145
Merge remote-tracking branch 'origin/main' into improved-data-channels
rubenthoms Oct 4, 2023
a382bcc
Started adjusting to new main
rubenthoms Oct 4, 2023
9b9112e
Further adjustments
rubenthoms Oct 4, 2023
74edfd3
Further implementation
rubenthoms Oct 5, 2023
9f4eaaa
Further implementation and bug fixes
rubenthoms Oct 6, 2023
3e2fbda
Changed icons
rubenthoms Oct 6, 2023
7fb4930
Adjustments
rubenthoms Oct 9, 2023
9c20768
Additional adjustments and simplifications
rubenthoms Oct 9, 2023
311b6f5
Merge remote-tracking branch 'origin/main' into improved-data-channels
rubenthoms Oct 12, 2023
0d7cbc4
Refactoring and bug fixes
rubenthoms Oct 12, 2023
4a5c3f6
Merge remote-tracking branch 'equinor/main' into improved-data-channels
rubenthoms Oct 13, 2023
93692b9
Fixed bug related to templates, adjusted modules, fixed linting
rubenthoms Oct 13, 2023
fce0dea
Merge remote-tracking branch 'equinor/main' into improved-data-channels
rubenthoms Oct 13, 2023
daf4d0a
Improved header spacing
rubenthoms Oct 13, 2023
5f09194
Adjusted according to review discussion
rubenthoms Oct 13, 2023
59fed68
Fixed typo
rubenthoms Oct 13, 2023
0e4a19f
Removed unused import
rubenthoms Oct 13, 2023
7d9a568
Removed debug code
rubenthoms Oct 13, 2023
6ef9ada
Added functionality for removing input channels on module remove
rubenthoms Oct 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function App() {
const workbench = React.useRef<Workbench>(new Workbench());
const queryClient = useQueryClient();

React.useEffect(() => {
React.useEffect(function handleMount() {
if (!workbench.current.loadLayoutFromLocalStorage()) {
workbench.current.makeLayout(layout);
}
Expand All @@ -32,8 +32,9 @@ function App() {
});
}

return function () {
return function handleUnmount() {
workbench.current.clearLayout();
workbench.current.resetModuleInstanceNumbers();
};
}, []);

Expand Down
31 changes: 23 additions & 8 deletions frontend/src/framework/Broadcaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,17 @@ export type BroadcastChannelData = {
value: number | string;
};

export type InputBroadcastChannelDef = {
name: string;
displayName: string;
keyCategories?: BroadcastChannelKeyCategory[];
};

export function checkChannelCompatibility(
channelDef1: BroadcastChannelDef,
channelDef: BroadcastChannelDef,
channelKeyCategory: BroadcastChannelKeyCategory
): boolean {
if (channelDef1.key !== channelKeyCategory) {
if (channelDef.key !== channelKeyCategory) {
return false;
}

Expand All @@ -89,15 +95,17 @@ export function checkChannelCompatibility(

export class BroadcastChannel {
private _name: string;
private _displayName: string;
private _metaData: BroadcastChannelMeta | null;
private _moduleInstanceId: string;
private _subscribers: Set<(data: BroadcastChannelData[], metaData: BroadcastChannelMeta) => void>;
private _cachedData: BroadcastChannelData[] | null;
private _dataDef: BroadcastChannelDef;
private _dataGenerator: (() => BroadcastChannelData[]) | null;

constructor(name: string, def: BroadcastChannelDef, moduleInstanceId: string) {
constructor(name: string, displayName: string, def: BroadcastChannelDef, moduleInstanceId: string) {
this._name = name;
this._displayName = displayName;
this._subscribers = new Set();
this._cachedData = null;
this._dataDef = def;
Expand Down Expand Up @@ -142,6 +150,10 @@ export class BroadcastChannel {
return this._name;
}

getDisplayName(): string {
return this._displayName;
}

getDataDef(): BroadcastChannelDef {
return this._dataDef;
}
Expand Down Expand Up @@ -173,17 +185,15 @@ export class BroadcastChannel {
}

subscribe(
callbackChannelDataChanged: (data: BroadcastChannelData[], metaData: BroadcastChannelMeta) => void
callbackChannelDataChanged: (data: BroadcastChannelData[] | null, metaData: BroadcastChannelMeta | null) => void
): () => void {
this._subscribers.add(callbackChannelDataChanged);

if (this._subscribers.size === 1 && this._dataGenerator) {
this._cachedData = this.generateAndVerifyData(this._dataGenerator);
}

if (this._cachedData && this._metaData) {
callbackChannelDataChanged(this._cachedData, this._metaData);
}
callbackChannelDataChanged(this._cachedData ?? null, this._metaData ?? null);

return () => {
this._subscribers.delete(callbackChannelDataChanged);
Expand All @@ -202,10 +212,11 @@ export class Broadcaster {

registerChannel(
channelName: string,
displayName: string,
channelDef: BroadcastChannelDef,
moduleInstanceId: string
): BroadcastChannel {
const channel = new BroadcastChannel(channelName, channelDef, moduleInstanceId);
const channel = new BroadcastChannel(channelName, displayName, channelDef, moduleInstanceId);
this._channels.push(channel);
this.notifySubscribersAboutChannelsChanges();
return channel;
Expand All @@ -216,6 +227,10 @@ export class Broadcaster {
this.notifySubscribersAboutChannelsChanges();
}

getChannelsForModuleInstance(moduleInstanceId: string): BroadcastChannel[] {
return this._channels.filter((c) => c.getModuleInstanceId() === moduleInstanceId);
}

getChannel(channelName: string): BroadcastChannel | null {
const channel = this._channels.find((c) => c.getName() === channelName);
if (!channel) {
Expand Down
45 changes: 43 additions & 2 deletions frontend/src/framework/GuiMessageBroker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from "react";

import { Point } from "@lib/utils/geometry";

import { GlobalCursor } from "./internal/GlobalCursor";

export enum DrawerContent {
ModuleSettings = "ModuleSettings",
ModulesList = "ModulesList",
Expand All @@ -15,12 +17,23 @@ export enum GuiState {
SettingsPanelWidthInPercent = "settingsPanelWidthInPercent",
LoadingEnsembleSet = "loadingEnsembleSet",
ActiveModuleInstanceId = "activeModuleInstanceId",
DataChannelConnectionLayerVisible = "dataChannelConnectionLayerVisible",
}

export enum GuiEvent {
ModuleHeaderPointerDown = "moduleHeaderPointerDown",
NewModulePointerDown = "newModulePointerDown",
RemoveModuleInstanceRequest = "removeModuleInstanceRequest",
EditDataChannelConnectionsForModuleInstanceRequest = "editDataChannelConnectionsForModuleInstanceRequest",
ShowDataChannelConnectionsRequest = "showDataChannelConnectionsRequest",
HideDataChannelConnectionsRequest = "hideDataChannelConnectionsRequest",
HighlightDataChannelConnectionRequest = "highlightDataChannelConnectionRequest",
UnhighlightDataChannelConnectionRequest = "unhighlightDataChannelConnectionRequest",
DataChannelPointerUp = "dataChannelPointerUp",
DataChannelOriginPointerDown = "dataChannelOriginPointerDown",
DataChannelConnectionsChange = "dataChannelConnectionsChange",
DataChannelNodeHover = "dataChannelNodeHover",
DataChannelNodeUnhover = "dataChannelNodeUnhover",
}

export type GuiEventPayloads = {
Expand All @@ -37,13 +50,28 @@ export type GuiEventPayloads = {
[GuiEvent.RemoveModuleInstanceRequest]: {
moduleInstanceId: string;
};
[GuiEvent.EditDataChannelConnectionsForModuleInstanceRequest]: {
moduleInstanceId: string;
};
[GuiEvent.HighlightDataChannelConnectionRequest]: {
moduleInstanceId: string;
dataChannelName: string;
};
[GuiEvent.DataChannelOriginPointerDown]: {
moduleInstanceId: string;
originElement: HTMLElement;
};
[GuiEvent.DataChannelNodeHover]: {
connectionAllowed: boolean;
};
};

type GuiStateValueTypes = {
[GuiState.DrawerContent]: DrawerContent;
[GuiState.SettingsPanelWidthInPercent]: number;
[GuiState.LoadingEnsembleSet]: boolean;
[GuiState.ActiveModuleInstanceId]: string;
[GuiState.DataChannelConnectionLayerVisible]: boolean;
};

const defaultStates: Map<GuiState, any> = new Map();
Expand All @@ -58,15 +86,21 @@ export class GuiMessageBroker {
private _eventListeners: Map<GuiEvent, Set<(event: any) => void>>;
private _stateSubscribers: Map<GuiState, Set<(state: any) => void>>;
private _storedValues: Map<GuiState, any>;
private _globalCursor: GlobalCursor;

constructor() {
this._eventListeners = new Map();
this._stateSubscribers = new Map();
this._storedValues = defaultStates;
this._globalCursor = new GlobalCursor();

this.loadPersistentStates();
}

getGlobalCursor(): GlobalCursor {
return this._globalCursor;
}

private loadPersistentStates() {
persistentStates.forEach((state) => {
const value = localStorage.getItem(state);
Expand All @@ -84,7 +118,12 @@ export class GuiMessageBroker {
}
}

subscribeToEvent<T extends GuiEvent>(event: T, callback: (payload: GuiEventPayloads[T]) => void) {
subscribeToEvent<T extends Exclude<GuiEvent, keyof GuiEventPayloads>>(event: T, callback: () => void): () => void;
subscribeToEvent<T extends keyof GuiEventPayloads>(
event: T,
callback: (payload: GuiEventPayloads[T]) => void
): () => void;
subscribeToEvent<T extends GuiEvent>(event: T, callback: (payload?: any) => void): () => void {
const eventListeners = this._eventListeners.get(event) || new Set();
eventListeners.add(callback);
this._eventListeners.set(event, eventListeners);
Expand All @@ -94,7 +133,9 @@ export class GuiMessageBroker {
};
}

publishEvent<T extends GuiEvent>(event: T, payload: GuiEventPayloads[T]) {
publishEvent<T extends Exclude<GuiEvent, keyof GuiEventPayloads>>(event: T): void;
publishEvent<T extends keyof GuiEventPayloads>(event: T, payload: GuiEventPayloads[T]): void;
publishEvent<T extends GuiEvent>(event: T, payload?: any): void {
const eventListeners = this._eventListeners.get(event);
if (eventListeners) {
eventListeners.forEach((callback) => callback({ ...payload }));
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/framework/Module.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";

import { cloneDeep } from "lodash";

import { BroadcastChannelsDef } from "./Broadcaster";
import { BroadcastChannelsDef, InputBroadcastChannelDef } from "./Broadcaster";
import { InitialSettings } from "./InitialSettings";
import { ModuleContext } from "./ModuleContext";
import { ModuleInstance } from "./ModuleInstance";
Expand Down Expand Up @@ -45,12 +45,14 @@ export class Module<StateType extends StateBaseType> {
private _channelsDef: BroadcastChannelsDef;
private _drawPreviewFunc: DrawPreviewFunc | null;
private _description: string | null;
private _inputChannelDefs: InputBroadcastChannelDef[];

constructor(
name: string,
defaultTitle: string,
syncableSettingKeys: SyncSettingKey[] = [],
broadcastChannelsDef: BroadcastChannelsDef = {},
inputChannelDefs: InputBroadcastChannelDef[] = [],
drawPreviewFunc: DrawPreviewFunc | null = null,
description: string | null = null
) {
Expand All @@ -64,6 +66,7 @@ export class Module<StateType extends StateBaseType> {
this._workbench = null;
this._syncableSettingKeys = syncableSettingKeys;
this._channelsDef = broadcastChannelsDef;
this._inputChannelDefs = inputChannelDefs;
this._drawPreviewFunc = drawPreviewFunc;
this._description = description;
}
Expand Down Expand Up @@ -115,7 +118,13 @@ export class Module<StateType extends StateBaseType> {
throw new Error("Module must be added to a workbench before making an instance");
}

const instance = new ModuleInstance<StateType>(this, instanceNumber, this._channelsDef, this._workbench);
const instance = new ModuleInstance<StateType>(
this,
instanceNumber,
this._channelsDef,
this._workbench,
this._inputChannelDefs
);
this._moduleInstances.push(instance);
this.maybeImportSelf();
return instance;
Expand Down
47 changes: 42 additions & 5 deletions frontend/src/framework/ModuleContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";

import { BroadcastChannel } from "./Broadcaster";
import { BroadcastChannel, InputBroadcastChannelDef } from "./Broadcaster";
import { InitialSettings } from "./InitialSettings";
import { ModuleInstance } from "./ModuleInstance";
import { ModuleInstanceStatusController } from "./ModuleInstanceStatusController";
import { StateBaseType, StateStore, useSetStoreValue, useStoreState, useStoreValue } from "./StateStore";
Expand Down Expand Up @@ -50,15 +51,51 @@ export class ModuleContext<S extends StateBaseType> {
return keyArr;
}

getChannel(channelName: string): BroadcastChannel {
return this._moduleInstance.getBroadcastChannel(channelName);
}

setInstanceTitle(title: string): void {
this._moduleInstance.setTitle(title);
}

getStatusController(): ModuleInstanceStatusController {
return this._moduleInstance.getStatusController();
}

getChannel(channelName: string): BroadcastChannel {
return this._moduleInstance.getBroadcastChannel(channelName);
}

getInputChannel(name: string): BroadcastChannel | null {
return this._moduleInstance.getInputChannel(name);
}

setInputChannel(inputName: string, channelName: string): void {
this._moduleInstance.setInputChannel(inputName, channelName);
}

getInputChannelDef(name: string): InputBroadcastChannelDef | undefined {
return this._moduleInstance.getInputChannelDefs().find((channelDef) => channelDef.name === name);
}

useInputChannel(name: string, initialSettings?: InitialSettings): BroadcastChannel | null {
const [channel, setChannel] = React.useState<BroadcastChannel | null>(null);

React.useEffect(() => {
if (initialSettings) {
const setting = initialSettings.get(name, "string");
if (setting) {
this._moduleInstance.setInputChannel(name, setting);
}
}
}, [initialSettings]);

React.useEffect(() => {
function handleNewChannel(newChannel: BroadcastChannel | null) {
setChannel(newChannel);
}

const unsubscribeFunc = this._moduleInstance.subscribeToInputChannelChange(name, handleNewChannel);
return unsubscribeFunc;
}, [name]);

return channel;
}
}
Loading