Establish wlayout for coordinating backend layout actions (#282)

This commit is contained in:
Evan Simkowitz
2024-08-27 18:38:57 -07:00
committed by GitHub
parent ee0bc0a377
commit c9c555452a
15 changed files with 294 additions and 237 deletions
-52
View File
@@ -7,13 +7,11 @@ import {
getLayoutModelForTabById,
LayoutTreeActionType,
LayoutTreeInsertNodeAction,
LayoutTreeInsertNodeAtIndexAction,
newLayoutNode,
} from "@/layout/index";
import { getWebServerEndpoint, getWSServerEndpoint } from "@/util/endpoints";
import { fetch } from "@/util/fetchutil";
import * as util from "@/util/util";
import { fireAndForget } from "@/util/util";
import * as jotai from "jotai";
import * as rxjs from "rxjs";
import { modalsModel } from "./modalmodel";
@@ -360,56 +358,6 @@ function handleWSEventMessage(msg: WSEventType) {
handleIncomingRpcMessage(rpcMsg, handleWaveEvent);
return;
}
if (msg.eventtype == "layoutaction") {
const layoutAction: LayoutActionData = msg.data;
const tabId = layoutAction.tabid;
const layoutModel = getLayoutModelForTabById(tabId);
switch (layoutAction.actiontype) {
case LayoutTreeActionType.InsertNode: {
const insertNodeAction: LayoutTreeInsertNodeAction = {
type: LayoutTreeActionType.InsertNode,
node: newLayoutNode(undefined, undefined, undefined, {
blockId: layoutAction.blockid,
}),
magnified: layoutAction.magnified,
};
layoutModel.treeReducer(insertNodeAction);
break;
}
case LayoutTreeActionType.DeleteNode: {
const leaf = layoutModel?.getNodeByBlockId(layoutAction.blockid);
if (leaf) {
fireAndForget(() => layoutModel.closeNode(leaf.id));
} else {
console.error(
"Cannot apply eventbus layout action DeleteNode, could not find leaf node with blockId",
layoutAction.blockid
);
}
break;
}
case LayoutTreeActionType.InsertNodeAtIndex: {
if (!layoutAction.indexarr) {
console.error("Cannot apply eventbus layout action InsertNodeAtIndex, indexarr field is missing.");
break;
}
const insertAction: LayoutTreeInsertNodeAtIndexAction = {
type: LayoutTreeActionType.InsertNodeAtIndex,
node: newLayoutNode(undefined, layoutAction.nodesize, undefined, {
blockId: layoutAction.blockid,
}),
indexArr: layoutAction.indexarr,
magnified: layoutAction.magnified,
};
layoutModel.treeReducer(insertAction);
break;
}
default:
console.warn("unsupported layout action", layoutAction);
break;
}
return;
}
// we send to two subjects just eventType and eventType|oref
// we don't use getORefSubject here because we don't want to create a new subject
const eventSubject = eventSubjects.get(msg.eventtype);
-6
View File
@@ -26,9 +26,6 @@ class ClientServiceType {
AgreeTos(): Promise<void> {
return WOS.callBackendService("client", "AgreeTos", Array.from(arguments))
}
BootstrapStarterLayout(): Promise<void> {
return WOS.callBackendService("client", "BootstrapStarterLayout", Array.from(arguments))
}
FocusWindow(arg2: string): Promise<void> {
return WOS.callBackendService("client", "FocusWindow", Array.from(arguments))
}
@@ -103,9 +100,6 @@ class ObjectServiceType {
CreateBlock(blockDef: BlockDef, rtOpts: RuntimeOpts): Promise<string> {
return WOS.callBackendService("object", "CreateBlock", Array.from(arguments))
}
CreateBlock_NoUI(arg2: string, arg3: BlockDef, arg4: RuntimeOpts): Promise<Block> {
return WOS.callBackendService("object", "CreateBlock_NoUI", Array.from(arguments))
}
// @returns object updates
DeleteBlock(blockId: string): Promise<void> {
+5
View File
@@ -29,6 +29,7 @@ export function withLayoutTreeStateAtomFromTab(tabAtom: Atom<Tab>): WritableLayo
rootNode: layoutStateData?.rootnode,
focusedNodeId: layoutStateData?.focusednodeid,
magnifiedNodeId: layoutStateData?.magnifiednodeid,
pendingBackendActions: layoutStateData?.pendingbackendactions,
generation: get(generationAtom),
};
return layoutTreeState;
@@ -41,6 +42,10 @@ export function withLayoutTreeStateAtomFromTab(tabAtom: Atom<Tab>): WritableLayo
waveObjVal.rootnode = value.rootNode;
waveObjVal.magnifiednodeid = value.magnifiedNodeId;
waveObjVal.focusednodeid = value.focusedNodeId;
waveObjVal.leaforder = value.leafOrder; // only set leaforder, never get it, since this value is driven by the frontend
waveObjVal.pendingbackendactions = value.pendingBackendActions?.length
? value.pendingBackendActions
: undefined;
set(generationAtom, value.generation);
set(stateAtom, waveObjVal);
}
+62 -7
View File
@@ -6,7 +6,7 @@ import { Atom, atom, Getter, PrimitiveAtom, Setter } from "jotai";
import { splitAtom } from "jotai/utils";
import { createRef, CSSProperties } from "react";
import { debounce } from "throttle-debounce";
import { balanceNode, findNode, walkNodes } from "./layoutNode";
import { balanceNode, findNode, newLayoutNode, walkNodes } from "./layoutNode";
import {
computeMoveNode,
deleteNode,
@@ -369,17 +369,72 @@ export class LayoutModel {
* Callback that is invoked when the tree state has been updated on the backend. This ensures the model is updated if the atom is not fully loaded when the model is first instantiated.
* @param force Whether to force the tree state to update, regardless of whether the state is already up to date.
*/
updateTreeState(force = false) {
async updateTreeState(force = false) {
const treeState = this.getter(this.treeStateAtom);
// Only update the local tree state if it is different from the one in the backend. This function is called even when the update was initiated by the LayoutModel, so we need to filter out false positives or we'll enter an infinite loop.
if (
force ||
!this.treeState?.rootNode ||
!this.treeState?.generation ||
treeState?.generation > this.treeState.generation
treeState?.generation > this.treeState.generation ||
treeState?.pendingBackendActions?.length
) {
this.treeState = treeState;
this.updateTree();
if (this.treeState.pendingBackendActions?.length) {
const actions = this.treeState.pendingBackendActions;
this.treeState.pendingBackendActions = undefined;
for (const action of actions) {
switch (action.actiontype) {
case LayoutTreeActionType.InsertNode: {
const insertNodeAction: LayoutTreeInsertNodeAction = {
type: LayoutTreeActionType.InsertNode,
node: newLayoutNode(undefined, undefined, undefined, {
blockId: action.blockid,
}),
magnified: action.magnified,
};
this.treeReducer(insertNodeAction);
break;
}
case LayoutTreeActionType.DeleteNode: {
const leaf = this?.getNodeByBlockId(action.blockid);
if (leaf) {
await this.closeNode(leaf.id);
} else {
console.error(
"Cannot apply eventbus layout action DeleteNode, could not find leaf node with blockId",
action.blockid
);
}
break;
}
case LayoutTreeActionType.InsertNodeAtIndex: {
if (!action.indexarr) {
console.error(
"Cannot apply eventbus layout action InsertNodeAtIndex, indexarr field is missing."
);
break;
}
const insertAction: LayoutTreeInsertNodeAtIndexAction = {
type: LayoutTreeActionType.InsertNodeAtIndex,
node: newLayoutNode(undefined, action.nodesize, undefined, {
blockId: action.blockid,
}),
indexArr: action.indexarr,
magnified: action.magnified,
};
this.treeReducer(insertAction);
break;
}
default:
console.warn("unsupported layout action", action);
break;
}
}
} else {
this.updateTree();
}
}
}
@@ -407,9 +462,9 @@ export class LayoutModel {
this.leafs,
newLeafs.sort((a, b) => a.id.localeCompare(b.id))
);
const newLeafOrder = getLeafOrder(newLeafs, newAdditionalProps);
this.setter(this.leafOrder, newLeafOrder);
this.validateFocusedNode(newLeafOrder);
this.treeState.leafOrder = getLeafOrder(newLeafs, newAdditionalProps);
this.setter(this.leafOrder, this.treeState.leafOrder);
this.validateFocusedNode(this.treeState.leafOrder);
this.cleanupNodeModels();
}
};
+2 -1
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { atoms, globalStore, WOS } from "@/app/store/global";
import { fireAndForget } from "@/util/util";
import useResizeObserver from "@react-hook/resize-observer";
import { Atom, useAtomValue } from "jotai";
import { CSSProperties, useEffect, useState } from "react";
@@ -23,7 +24,7 @@ export function getLayoutModelForTab(tabAtom: Atom<Tab>): LayoutModel {
}
const layoutTreeStateAtom = withLayoutTreeStateAtomFromTab(tabAtom);
const layoutModel = new LayoutModel(layoutTreeStateAtom, globalStore.get, globalStore.set);
globalStore.sub(layoutTreeStateAtom, () => layoutModel.updateTreeState());
globalStore.sub(layoutTreeStateAtom, () => fireAndForget(() => layoutModel.updateTreeState()));
layoutModelMap.set(tabId, layoutModel);
return layoutModel;
}
+5
View File
@@ -249,6 +249,11 @@ export type LayoutTreeState = {
rootNode: LayoutNode;
focusedNodeId?: string;
magnifiedNodeId?: string;
/**
* A computed ordered list of leafs in the layout. This value is driven by the LayoutModel and should not be read when updated from the backend.
*/
leafOrder?: string[];
pendingBackendActions: LayoutActionData[];
generation: number;
};
+2 -1
View File
@@ -209,7 +209,6 @@ declare global {
// waveobj.LayoutActionData
type LayoutActionData = {
tabid: string;
actiontype: string;
blockid: string;
nodesize?: number;
@@ -222,6 +221,8 @@ declare global {
rootnode?: any;
magnifiednodeid?: string;
focusednodeid?: string;
leaforder?: string[];
pendingbackendactions?: LayoutActionData[];
};
// waveobj.MetaTSType