Add action for magnifying a block (#172)

Adds the implementation for the "Magnify Block" context menu item. This
will pop a block out of the layout and bring it to the foreground.

This also cleans up some block styling to make radii more consistent.

<img width="814" alt="image"
src="https://github.com/user-attachments/assets/c81521e1-c91f-4bb5-9eec-ff0eda178268">
This commit is contained in:
Evan Simkowitz 2024-07-30 10:59:53 -07:00 committed by GitHub
parent 49072364e9
commit f3f272a47b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 295 additions and 156 deletions

View File

@ -11,7 +11,7 @@
overflow: hidden;
min-height: 0;
position: relative;
border-radius: 8px;
border-radius: var(--block-border-radius);
.block-frame-icon {
margin-right: 0.5em;
@ -62,7 +62,7 @@
background-color: var(--block-bg-color);
width: 100%;
height: 100%;
border-radius: 8px;
border-radius: var(--block-border-radius);
display: flex;
flex-direction: column;
@ -75,7 +75,7 @@
align-self: stretch;
font: var(--header-font);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px 8px 0 0;
border-radius: var(--block-border-radius) var(--block-border-radius) 0 0;
.block-frame-preicon-button {
opacity: 0.7;
@ -251,8 +251,8 @@
background-color: rgba(0, 0, 0, 0.7);
width: 100%;
flex-grow: 1;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
border-bottom-left-radius: var(--block-border-radius);
border-bottom-right-radius: var(--block-border-radius);
}
}
@ -265,7 +265,7 @@
border: 2px solid transparent;
pointer-events: none;
padding: 2px;
border-radius: 8px;
border-radius: var(--block-border-radius);
}
&.block-focused {

View File

@ -21,8 +21,9 @@ import * as React from "react";
import "./block.less";
interface LayoutComponentModel {
export interface LayoutComponentModel {
onClose?: () => void;
onMagnifyToggle?: () => void;
dragHandleRef?: React.RefObject<HTMLDivElement>;
}
@ -163,51 +164,52 @@ function handleHeaderContextMenu(
e: React.MouseEvent<HTMLDivElement>,
blockData: Block,
viewModel: ViewModel,
onMagnifyToggle: () => void,
onClose: () => void
) {
e.preventDefault();
e.stopPropagation();
let menu: ContextMenuItem[] = [];
menu.push({
label: "Focus Block",
click: () => {
alert("Not Implemented");
let menu: ContextMenuItem[] = [
{
label: "Magnify Block",
click: () => {
onMagnifyToggle();
},
},
});
menu.push({
label: "Minimize",
click: () => {
alert("Not Implemented");
{
label: "Minimize",
click: () => {
alert("Not Implemented");
},
},
});
menu.push({
label: "Move to New Window",
click: () => {
let currentTabId = globalStore.get(atoms.activeTabId);
try {
services.WindowService.MoveBlockToNewWindow(currentTabId, blockData.oid);
} catch (e) {
console.error("error moving block to new window", e);
}
{
label: "Move to New Window",
click: () => {
const currentTabId = globalStore.get(atoms.activeTabId);
try {
services.WindowService.MoveBlockToNewWindow(currentTabId, blockData.oid);
} catch (e) {
console.error("error moving block to new window", e);
}
},
},
});
menu.push({ type: "separator" });
menu.push({
label: "Copy BlockId",
click: () => {
navigator.clipboard.writeText(blockData.oid);
{ type: "separator" },
{
label: "Copy BlockId",
click: () => {
navigator.clipboard.writeText(blockData.oid);
},
},
});
];
const extraItems = viewModel?.getSettingsMenuItems?.();
if (extraItems && extraItems.length > 0) {
menu.push({ type: "separator" });
menu.push(...extraItems);
}
menu.push({ type: "separator" });
menu.push({
label: "Close Block",
click: onClose,
});
if (extraItems && extraItems.length > 0) menu.push({ type: "separator" }, ...extraItems);
menu.push(
{ type: "separator" },
{
label: "Close Block",
click: onClose,
}
);
ContextMenuModel.showContextMenu(menu, e);
}
@ -294,7 +296,8 @@ const BlockFrame_Default_Component = ({
elemtype: "iconbutton",
icon: "cog",
title: "Settings",
click: (e) => handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onClose),
click: (e) =>
handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onMagnifyToggle, layoutModel?.onClose),
};
endIconsElem.push(
<IconButton key="settings" decl={settingsDecl} className="block-frame-endicon-button block-frame-settings" />
@ -394,7 +397,15 @@ const BlockFrame_Default_Component = ({
<div
className="block-frame-default-header"
ref={layoutModel?.dragHandleRef}
onContextMenu={(e) => handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onClose)}
onContextMenu={(e) =>
handleHeaderContextMenu(
e,
blockData,
viewModel,
layoutModel?.onMagnifyToggle,
layoutModel?.onClose
)
}
>
{preIconButtonElem}
<div className="block-frame-default-header-iconview">

View File

@ -1,7 +1,7 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { Block } from "@/app/block/block";
import { Block, LayoutComponentModel } from "@/app/block/block";
import { getApi } from "@/store/global";
import * as services from "@/store/services";
import * as WOS from "@/store/wos";
@ -26,14 +26,16 @@ const TabContent = React.memo(({ tabId }: { tabId: string }) => {
function renderBlock(
tabData: TabLayoutData,
ready: boolean,
onMagnifyToggle: () => void,
onClose: () => void,
dragHandleRef: React.RefObject<HTMLDivElement>
) {
if (!tabData.blockId || !ready) {
return null;
}
const layoutModel = {
const layoutModel: LayoutComponentModel = {
onClose: onClose,
onMagnifyToggle: onMagnifyToggle,
dragHandleRef: dragHandleRef,
};
return <Block key={tabData.blockId} blockId={tabData.blockId} layoutModel={layoutModel} preview={false} />;

View File

@ -20,6 +20,8 @@
--success-color: rgb(78, 154, 6);
--hover-bg-color: rgba(255, 255, 255, 0.1);
--block-bg-color: rgba(0, 0, 0, 0.5);
--block-bg-solid-color: rgb(0, 0, 0);
--block-border-radius: 8px;
/* scrollbar colors */
--scrollbar-background-color: transparent;

View File

@ -40,7 +40,7 @@ import {
} from "./model";
import { NodeRefMap } from "./nodeRefMap";
import "./tilelayout.less";
import { Dimensions, FlexDirection, setTransform as createTransform, determineDropDirection } from "./utils";
import { Dimensions, FlexDirection, determineDropDirection, setTransform } from "./utils";
/**
* contains callbacks and information about the contents (or styling) of of the TileLayout
@ -181,14 +181,28 @@ function TileLayoutComponent<T>({ layoutTreeStateAtom, contents, getCursorPoint
const leafRef = nodeRefs.get(leaf.id);
// console.log("current leafRef", leafRef.current);
if (leafRef?.current) {
if (leaf.id === layoutTreeState.magnifiedNodeId) {
const transform = setTransform(
{
top: displayBoundingRect.height * 0.05,
left: displayBoundingRect.width * 0.05,
width: displayBoundingRect.width * 0.9,
height: displayBoundingRect.height * 0.9,
},
true
);
newLayoutLeafTransforms[leaf.id] = transform;
continue;
}
const leafBounding = leafRef.current.getBoundingClientRect();
const transform = createTransform({
const transform = setTransform({
top: leafBounding.top - overlayBoundingRect.top,
left: leafBounding.left - overlayBoundingRect.left,
width: leafBounding.width,
height: leafBounding.height,
});
newLayoutLeafTransforms[leafRef.current.id] = transform;
newLayoutLeafTransforms[leaf.id] = transform;
} else {
console.warn("missing leaf", leaf.id);
}
@ -199,7 +213,7 @@ function TileLayoutComponent<T>({ layoutTreeStateAtom, contents, getCursorPoint
const newOverlayOffset = displayBoundingRect.top + 2 * displayBoundingRect.height;
// console.log("overlayOffset", newOverlayOffset);
setOverlayTransform(
createTransform(
setTransform(
{
top: activeDrag || showOverlay ? 0 : newOverlayOffset,
left: 0,
@ -246,6 +260,18 @@ function TileLayoutComponent<T>({ layoutTreeStateAtom, contents, getCursorPoint
[contents.onNodeDelete, dispatch]
);
const onLeafMagnifyToggle = useCallback(
(node: LayoutNode<T>) => {
const action = {
type: LayoutTreeActionType.MagnifyNodeToggle,
nodeId: node.id,
};
dispatch(action);
},
[dispatch]
);
return (
<Suspense>
<div className={clsx("tile-layout", contents.className, { animate })} onPointerOver={onPointerOver}>
@ -253,6 +279,7 @@ function TileLayoutComponent<T>({ layoutTreeStateAtom, contents, getCursorPoint
<DisplayNodesWrapper
contents={contents}
ready={animate}
onLeafMagnifyToggle={onLeafMagnifyToggle}
onLeafClose={onLeafClose}
layoutTreeState={layoutTreeState}
layoutLeafTransforms={layoutLeafTransforms}
@ -303,6 +330,13 @@ interface DisplayNodesWrapperProps<T> {
* @param node The node that is closed.
*/
onLeafClose: (node: LayoutNode<T>) => void;
/**
* A callback that is called when a leaf's magnification is being toggled.
* @param node The node that is being magnified or un-magnified.
*/
onLeafMagnifyToggle: (node: LayoutNode<T>) => void;
/**
* A series of CSS properties used to display a leaf node with the correct dimensions and position, as determined from its corresponding OverlayNode.
*/
@ -314,18 +348,27 @@ interface DisplayNodesWrapperProps<T> {
}
const DisplayNodesWrapper = memo(
<T,>({ layoutTreeState, contents, onLeafClose, layoutLeafTransforms, ready }: DisplayNodesWrapperProps<T>) => {
<T,>({
layoutTreeState,
contents,
onLeafClose,
onLeafMagnifyToggle,
layoutLeafTransforms,
ready,
}: DisplayNodesWrapperProps<T>) => {
if (!layoutLeafTransforms) {
return null;
}
return layoutTreeState.leafs.map((leaf) => {
return (
<DisplayNode
className={clsx({ magnified: layoutTreeState.magnifiedNodeId === leaf.id })}
key={leaf.id}
layoutNode={leaf}
contents={contents}
transform={layoutLeafTransforms[leaf.id]}
onLeafClose={onLeafClose}
onLeafMagnifyToggle={onLeafMagnifyToggle}
ready={ready}
/>
);
@ -344,6 +387,12 @@ interface DisplayNodeProps<T> {
*/
contents: TileLayoutContents<T>;
/**
* A callback that is called when a leaf's magnification is being toggled.
* @param node The node that is being magnified or unmagnified.
*/
onLeafMagnifyToggle: (node: LayoutNode<T>) => void;
/**
* A callback that is called when a leaf node gets closed.
* @param node The node that is closed.
@ -353,6 +402,12 @@ interface DisplayNodeProps<T> {
* Determines whether a leaf's contents should be displayed to the user.
*/
ready: boolean;
/**
* Any class names to add to the component.
*/
className?: string;
/**
* A series of CSS properties used to display a leaf node with the correct dimensions and position, as determined from its corresponding OverlayNode.
*/
@ -364,104 +419,118 @@ const dragItemType = "TILE_ITEM";
/**
* The draggable and displayable portion of a leaf node in a layout tree.
*/
const DisplayNode = memo(<T,>({ layoutNode, contents, transform, onLeafClose, ready }: DisplayNodeProps<T>) => {
const tileNodeRef = useRef<HTMLDivElement>(null);
const dragHandleRef = useRef<HTMLDivElement>(null);
const previewRef = useRef<HTMLDivElement>(null);
const DisplayNode = memo(
<T,>({
layoutNode,
contents,
transform,
onLeafMagnifyToggle,
onLeafClose,
ready,
className,
}: DisplayNodeProps<T>) => {
const tileNodeRef = useRef<HTMLDivElement>(null);
const dragHandleRef = useRef<HTMLDivElement>(null);
const previewRef = useRef<HTMLDivElement>(null);
const devicePixelRatio = useDevicePixelRatio();
const devicePixelRatio = useDevicePixelRatio();
const [{ isDragging }, drag, dragPreview] = useDrag(
() => ({
type: dragItemType,
item: () => layoutNode,
collect: (monitor) => ({
isDragging: monitor.isDragging(),
const [{ isDragging }, drag, dragPreview] = useDrag(
() => ({
type: dragItemType,
item: () => layoutNode,
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}),
}),
[layoutNode]
);
[layoutNode]
);
const [previewElementGeneration, setPreviewElementGeneration] = useState(0);
const previewElement = useMemo(() => {
setPreviewElementGeneration(previewElementGeneration + 1);
return (
<div key="preview" className="tile-preview-container">
<div
className="tile-preview"
ref={previewRef}
style={{
width: DragPreviewWidth,
height: DragPreviewHeight,
transform: `scale(${1 / devicePixelRatio})`,
}}
>
{contents.renderPreview?.(layoutNode.data)}
const [previewElementGeneration, setPreviewElementGeneration] = useState(0);
const previewElement = useMemo(() => {
setPreviewElementGeneration(previewElementGeneration + 1);
return (
<div key="preview" className="tile-preview-container">
<div
className="tile-preview"
ref={previewRef}
style={{
width: DragPreviewWidth,
height: DragPreviewHeight,
transform: `scale(${1 / devicePixelRatio})`,
}}
>
{contents.renderPreview?.(layoutNode.data)}
</div>
</div>
);
}, [contents.renderPreview, devicePixelRatio]);
const [previewImage, setPreviewImage] = useState<HTMLImageElement>(null);
const [previewImageGeneration, setPreviewImageGeneration] = useState(0);
const generatePreviewImage = useCallback(() => {
const offsetX = (DragPreviewWidth * devicePixelRatio - DragPreviewWidth) / 2 + 10;
const offsetY = (DragPreviewHeight * devicePixelRatio - DragPreviewHeight) / 2 + 10;
if (previewImage !== null && previewElementGeneration === previewImageGeneration) {
dragPreview(previewImage, { offsetY, offsetX });
} else if (previewRef.current) {
setPreviewImageGeneration(previewElementGeneration);
toPng(previewRef.current).then((url) => {
const img = new Image();
img.src = url;
setPreviewImage(img);
dragPreview(img, { offsetY, offsetX });
});
}
}, [
dragPreview,
previewRef.current,
previewElementGeneration,
previewImageGeneration,
previewImage,
devicePixelRatio,
]);
// Register the tile item as a draggable component
useEffect(() => {
drag(dragHandleRef);
}, [drag, dragHandleRef.current]);
const onClose = useCallback(() => {
onLeafClose(layoutNode);
}, [layoutNode, onLeafClose]);
const onMagnifyToggle = useCallback(() => {
onLeafMagnifyToggle(layoutNode);
}, [layoutNode, onLeafMagnifyToggle]);
const leafContent = useMemo(() => {
return (
layoutNode.data && (
<div key="leaf" className="tile-leaf">
{contents.renderContent(layoutNode.data, ready, onMagnifyToggle, onClose, dragHandleRef)}
</div>
)
);
}, [layoutNode.data, ready, onClose]);
return (
<div
className={clsx("tile-node", className, { dragging: isDragging })}
ref={tileNodeRef}
id={layoutNode.id}
style={{
...transform,
}}
onPointerEnter={generatePreviewImage}
onPointerOver={(event) => event.stopPropagation()}
>
{leafContent}
{previewElement}
</div>
);
}, [contents.renderPreview, devicePixelRatio]);
const [previewImage, setPreviewImage] = useState<HTMLImageElement>(null);
const [previewImageGeneration, setPreviewImageGeneration] = useState(0);
const generatePreviewImage = useCallback(() => {
const offsetX = (DragPreviewWidth * devicePixelRatio - DragPreviewWidth) / 2 + 10;
const offsetY = (DragPreviewHeight * devicePixelRatio - DragPreviewHeight) / 2 + 10;
if (previewImage !== null && previewElementGeneration === previewImageGeneration) {
dragPreview(previewImage, { offsetY, offsetX });
} else if (previewRef.current) {
setPreviewImageGeneration(previewElementGeneration);
toPng(previewRef.current).then((url) => {
const img = new Image();
img.src = url;
setPreviewImage(img);
dragPreview(img, { offsetY, offsetX });
});
}
}, [
dragPreview,
previewRef.current,
previewElementGeneration,
previewImageGeneration,
previewImage,
devicePixelRatio,
]);
// Register the tile item as a draggable component
useEffect(() => {
drag(dragHandleRef);
}, [drag, dragHandleRef.current]);
const onClose = useCallback(() => {
onLeafClose(layoutNode);
}, [layoutNode, onLeafClose]);
const leafContent = useMemo(() => {
return (
layoutNode.data && (
<div key="leaf" className="tile-leaf">
{contents.renderContent(layoutNode.data, ready, onClose, dragHandleRef)}
</div>
)
);
}, [layoutNode.data, ready, onClose]);
return (
<div
className={clsx("tile-node", { dragging: isDragging })}
ref={tileNodeRef}
id={layoutNode.id}
style={{
...transform,
}}
onPointerEnter={generatePreviewImage}
onPointerOver={(event) => event.stopPropagation()}
>
{leafContent}
{previewElement}
</div>
);
});
}
);
interface OverlayNodeProps<T> {
/**
@ -886,7 +955,7 @@ const Placeholder = <T,>({ layoutTreeState, overlayContainerRef, nodeRefsAtom, s
}
}
const placeholderTransform = createTransform(placeholderDimensions);
const placeholderTransform = setTransform(placeholderDimensions);
newPlaceholderOverlay = <div className="placeholder" style={{ ...placeholderTransform }} />;
}
}
@ -907,7 +976,7 @@ const Placeholder = <T,>({ layoutTreeState, overlayContainerRef, nodeRefsAtom, s
width: targetBoundingRect.width,
};
const placeholderTransform = createTransform(placeholderDimensions);
const placeholderTransform = setTransform(placeholderDimensions);
newPlaceholderOverlay = <div className="placeholder" style={{ ...placeholderTransform }} />;
}
break;

View File

@ -78,7 +78,7 @@ function getLayoutNodeWaveObjAtomFromTab<T>(
const tabValue = get(tabAtom);
// console.log("getLayoutNodeWaveObjAtomFromTab tabValue", tabValue);
if (!tabValue) return;
const layoutNodeOref = WOS.makeORef("layout", tabValue.layoutNode);
const layoutNodeOref = WOS.makeORef("layout", tabValue.layoutnode);
// console.log("getLayoutNodeWaveObjAtomFromTab oref", layoutNodeOref);
return WOS.getWaveObjectAtom<LayoutNodeWaveObj<T>>(layoutNodeOref);
}
@ -86,13 +86,16 @@ function getLayoutNodeWaveObjAtomFromTab<T>(
export function withLayoutStateAtomFromTab<T>(tabAtom: Atom<Tab>): WritableLayoutTreeStateAtom<T> {
const pendingActionAtom = atom<LayoutTreeAction>(null) as PrimitiveAtom<LayoutTreeAction>;
const generationAtom = atom(0) as PrimitiveAtom<number>;
return atom(
(get) => {
const waveObjAtom = getLayoutNodeWaveObjAtomFromTab<T>(tabAtom, get);
if (!waveObjAtom) return null;
const layoutState = newLayoutTreeState(get(waveObjAtom)?.node);
const waveObj = get(waveObjAtom);
const layoutState = newLayoutTreeState(waveObj?.node);
layoutState.pendingAction = get(pendingActionAtom);
layoutState.generation = get(generationAtom);
layoutState.magnifiedNodeId = waveObj?.magnifiednodeid;
return layoutState;
},
(get, set, value) => {
@ -100,7 +103,11 @@ export function withLayoutStateAtomFromTab<T>(tabAtom: Atom<Tab>): WritableLayou
if (get(generationAtom) !== value.generation) {
const waveObjAtom = getLayoutNodeWaveObjAtomFromTab<T>(tabAtom, get);
if (!waveObjAtom) return;
const newWaveObj = { ...get(waveObjAtom), node: value.rootNode };
const newWaveObj = {
...get(waveObjAtom),
node: value.rootNode,
magnifiednodeid: value.magnifiedNodeId,
};
set(generationAtom, value.generation);
set(waveObjAtom, newWaveObj);
}

View File

@ -19,6 +19,7 @@ import {
LayoutTreeComputeMoveNodeAction,
LayoutTreeDeleteNodeAction,
LayoutTreeInsertNodeAction,
LayoutTreeMagnifyNodeToggleAction,
LayoutTreeMoveNodeAction,
LayoutTreeResizeNodeAction,
LayoutTreeSetPendingAction,
@ -108,6 +109,10 @@ function layoutTreeStateReducerInner<T>(layoutTreeState: LayoutTreeState<T>, act
resizeNode(layoutTreeState, action as LayoutTreeResizeNodeAction);
layoutTreeState.generation++;
break;
case LayoutTreeActionType.MagnifyNodeToggle:
magnifyNodeToggle(layoutTreeState, action as LayoutTreeMagnifyNodeToggleAction);
layoutTreeState.generation++;
break;
default: {
console.error("Invalid reducer action", layoutTreeState, action);
}
@ -460,3 +465,20 @@ function resizeNode<T>(layoutTreeState: LayoutTreeState<T>, action: LayoutTreeRe
node.size = resize.size;
}
}
function magnifyNodeToggle<T>(layoutTreeState: LayoutTreeState<T>, action: LayoutTreeMagnifyNodeToggleAction) {
console.log("magnifyNodeToggle", layoutTreeState, action);
if (!action.nodeId) {
console.error("invalid magnifyNodeToggle operation. nodeId must be defined.");
return;
}
if (layoutTreeState.rootNode.id === action.nodeId) {
console.warn(`cannot toggle magnification of node ${action.nodeId} because it is the root node.`);
return;
}
if (layoutTreeState.magnifiedNodeId === action.nodeId) {
layoutTreeState.magnifiedNodeId = undefined;
} else {
layoutTreeState.magnifiedNodeId = action.nodeId;
}
}

View File

@ -42,6 +42,7 @@ export enum LayoutTreeActionType {
ResizeNode = "resize",
InsertNode = "insert",
DeleteNode = "delete",
MagnifyNodeToggle = "magnify",
}
/**
@ -163,6 +164,18 @@ export interface LayoutTreeResizeNodeAction extends LayoutTreeAction {
resizeOperations: ResizeNodeOperation[];
}
/**
* Action for toggling magnification of a node from the layout tree.
*/
export interface LayoutTreeMagnifyNodeToggleAction extends LayoutTreeAction {
type: LayoutTreeActionType.MagnifyNodeToggle;
/**
* The id of the node to maximize;
*/
nodeId: string;
}
/**
* Represents the state of a layout tree.
*
@ -173,6 +186,7 @@ export type LayoutTreeState<T> = {
leafs: LayoutNode<T>[];
pendingAction: LayoutTreeAction;
generation: number;
magnifiedNodeId?: string;
};
/**
@ -200,6 +214,7 @@ export type WritableLayoutTreeStateAtom<T> = WritableAtom<LayoutTreeState<T>, [v
export type ContentRenderer<T> = (
data: T,
ready: boolean,
onMagnifyToggle: () => void,
onClose: () => void,
dragHandleRef: React.RefObject<HTMLDivElement>
) => React.ReactNode;
@ -208,6 +223,7 @@ export type PreviewRenderer<T> = (data: T) => React.ReactElement;
export interface LayoutNodeWaveObj<T> extends WaveObj {
node: LayoutNode<T>;
magnifiednodeid: string;
}
export const DefaultNodeSize = 10;

View File

@ -66,10 +66,18 @@
}
.tile-node {
border-radius: var(--block-border-radius);
overflow: hidden;
&.dragging {
filter: blur(8px);
}
&.magnified {
background-color: var(--block-bg-solid-color);
z-index: 10;
}
.tile-preview-container {
position: absolute;
top: 10000px;
@ -89,7 +97,7 @@
.placeholder {
transition-duration: 0.15s;
transition-timing-function: ease-in;
transition-property: transform, width, height;
transition-property: transform, width, height, background-color;
}
}
@ -106,6 +114,6 @@
.placeholder {
background-color: var(--accent-color);
opacity: 0.5;
border-radius: 5px;
border-radius: calc(var(--block-border-radius) + 2px);
}
}

View File

@ -176,6 +176,7 @@ declare global {
// wstore.LayoutNode
type LayoutNode = WaveObj & {
node?: any;
magnifiednodeid?: string;
meta?: MetaType;
};
@ -315,7 +316,7 @@ declare global {
// wstore.Tab
type Tab = WaveObj & {
name: string;
layoutNode: string;
layoutnode: string;
blockids: string[];
meta: MetaType;
};

View File

@ -207,7 +207,7 @@ type Tab struct {
OID string `json:"oid"`
Version int `json:"version"`
Name string `json:"name"`
LayoutNode string `json:"layoutNode"`
LayoutNode string `json:"layoutnode"`
BlockIds []string `json:"blockids"`
Meta map[string]any `json:"meta"`
}
@ -225,10 +225,11 @@ func (t *Tab) GetBlockORefs() []waveobj.ORef {
}
type LayoutNode struct {
OID string `json:"oid"`
Version int `json:"version"`
Node any `json:"node,omitempty"`
Meta map[string]any `json:"meta,omitempty"`
OID string `json:"oid"`
Version int `json:"version"`
Node any `json:"node,omitempty"`
MagnifiedNodeId string `json:"magnifiednodeid,omitempty"`
Meta map[string]any `json:"meta,omitempty"`
}
func (*LayoutNode) GetOType() string {