mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-17 20:51:55 +01:00
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:
parent
49072364e9
commit
f3f272a47b
@ -11,7 +11,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 8px;
|
border-radius: var(--block-border-radius);
|
||||||
|
|
||||||
.block-frame-icon {
|
.block-frame-icon {
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
@ -62,7 +62,7 @@
|
|||||||
background-color: var(--block-bg-color);
|
background-color: var(--block-bg-color);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 8px;
|
border-radius: var(--block-border-radius);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
@ -75,7 +75,7 @@
|
|||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
font: var(--header-font);
|
font: var(--header-font);
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
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 {
|
.block-frame-preicon-button {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
@ -251,8 +251,8 @@
|
|||||||
background-color: rgba(0, 0, 0, 0.7);
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
border-bottom-left-radius: 8px;
|
border-bottom-left-radius: var(--block-border-radius);
|
||||||
border-bottom-right-radius: 8px;
|
border-bottom-right-radius: var(--block-border-radius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,7 +265,7 @@
|
|||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
border-radius: 8px;
|
border-radius: var(--block-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.block-focused {
|
&.block-focused {
|
||||||
|
@ -21,8 +21,9 @@ import * as React from "react";
|
|||||||
|
|
||||||
import "./block.less";
|
import "./block.less";
|
||||||
|
|
||||||
interface LayoutComponentModel {
|
export interface LayoutComponentModel {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
onMagnifyToggle?: () => void;
|
||||||
dragHandleRef?: React.RefObject<HTMLDivElement>;
|
dragHandleRef?: React.RefObject<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,51 +164,52 @@ function handleHeaderContextMenu(
|
|||||||
e: React.MouseEvent<HTMLDivElement>,
|
e: React.MouseEvent<HTMLDivElement>,
|
||||||
blockData: Block,
|
blockData: Block,
|
||||||
viewModel: ViewModel,
|
viewModel: ViewModel,
|
||||||
|
onMagnifyToggle: () => void,
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
) {
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
let menu: ContextMenuItem[] = [];
|
let menu: ContextMenuItem[] = [
|
||||||
menu.push({
|
{
|
||||||
label: "Focus Block",
|
label: "Magnify Block",
|
||||||
click: () => {
|
click: () => {
|
||||||
alert("Not Implemented");
|
onMagnifyToggle();
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
menu.push({
|
{
|
||||||
label: "Minimize",
|
label: "Minimize",
|
||||||
click: () => {
|
click: () => {
|
||||||
alert("Not Implemented");
|
alert("Not Implemented");
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
menu.push({
|
{
|
||||||
label: "Move to New Window",
|
label: "Move to New Window",
|
||||||
click: () => {
|
click: () => {
|
||||||
let currentTabId = globalStore.get(atoms.activeTabId);
|
const currentTabId = globalStore.get(atoms.activeTabId);
|
||||||
try {
|
try {
|
||||||
services.WindowService.MoveBlockToNewWindow(currentTabId, blockData.oid);
|
services.WindowService.MoveBlockToNewWindow(currentTabId, blockData.oid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("error moving block to new window", e);
|
console.error("error moving block to new window", e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
menu.push({ type: "separator" });
|
{ type: "separator" },
|
||||||
menu.push({
|
{
|
||||||
label: "Copy BlockId",
|
label: "Copy BlockId",
|
||||||
click: () => {
|
click: () => {
|
||||||
navigator.clipboard.writeText(blockData.oid);
|
navigator.clipboard.writeText(blockData.oid);
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
];
|
||||||
const extraItems = viewModel?.getSettingsMenuItems?.();
|
const extraItems = viewModel?.getSettingsMenuItems?.();
|
||||||
if (extraItems && extraItems.length > 0) {
|
if (extraItems && extraItems.length > 0) menu.push({ type: "separator" }, ...extraItems);
|
||||||
menu.push({ type: "separator" });
|
menu.push(
|
||||||
menu.push(...extraItems);
|
{ type: "separator" },
|
||||||
}
|
{
|
||||||
menu.push({ type: "separator" });
|
|
||||||
menu.push({
|
|
||||||
label: "Close Block",
|
label: "Close Block",
|
||||||
click: onClose,
|
click: onClose,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
ContextMenuModel.showContextMenu(menu, e);
|
ContextMenuModel.showContextMenu(menu, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,7 +296,8 @@ const BlockFrame_Default_Component = ({
|
|||||||
elemtype: "iconbutton",
|
elemtype: "iconbutton",
|
||||||
icon: "cog",
|
icon: "cog",
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
click: (e) => handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onClose),
|
click: (e) =>
|
||||||
|
handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onMagnifyToggle, layoutModel?.onClose),
|
||||||
};
|
};
|
||||||
endIconsElem.push(
|
endIconsElem.push(
|
||||||
<IconButton key="settings" decl={settingsDecl} className="block-frame-endicon-button block-frame-settings" />
|
<IconButton key="settings" decl={settingsDecl} className="block-frame-endicon-button block-frame-settings" />
|
||||||
@ -394,7 +397,15 @@ const BlockFrame_Default_Component = ({
|
|||||||
<div
|
<div
|
||||||
className="block-frame-default-header"
|
className="block-frame-default-header"
|
||||||
ref={layoutModel?.dragHandleRef}
|
ref={layoutModel?.dragHandleRef}
|
||||||
onContextMenu={(e) => handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onClose)}
|
onContextMenu={(e) =>
|
||||||
|
handleHeaderContextMenu(
|
||||||
|
e,
|
||||||
|
blockData,
|
||||||
|
viewModel,
|
||||||
|
layoutModel?.onMagnifyToggle,
|
||||||
|
layoutModel?.onClose
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{preIconButtonElem}
|
{preIconButtonElem}
|
||||||
<div className="block-frame-default-header-iconview">
|
<div className="block-frame-default-header-iconview">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright 2023, Command Line Inc.
|
// Copyright 2023, Command Line Inc.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// 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 { getApi } from "@/store/global";
|
||||||
import * as services from "@/store/services";
|
import * as services from "@/store/services";
|
||||||
import * as WOS from "@/store/wos";
|
import * as WOS from "@/store/wos";
|
||||||
@ -26,14 +26,16 @@ const TabContent = React.memo(({ tabId }: { tabId: string }) => {
|
|||||||
function renderBlock(
|
function renderBlock(
|
||||||
tabData: TabLayoutData,
|
tabData: TabLayoutData,
|
||||||
ready: boolean,
|
ready: boolean,
|
||||||
|
onMagnifyToggle: () => void,
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
dragHandleRef: React.RefObject<HTMLDivElement>
|
dragHandleRef: React.RefObject<HTMLDivElement>
|
||||||
) {
|
) {
|
||||||
if (!tabData.blockId || !ready) {
|
if (!tabData.blockId || !ready) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const layoutModel = {
|
const layoutModel: LayoutComponentModel = {
|
||||||
onClose: onClose,
|
onClose: onClose,
|
||||||
|
onMagnifyToggle: onMagnifyToggle,
|
||||||
dragHandleRef: dragHandleRef,
|
dragHandleRef: dragHandleRef,
|
||||||
};
|
};
|
||||||
return <Block key={tabData.blockId} blockId={tabData.blockId} layoutModel={layoutModel} preview={false} />;
|
return <Block key={tabData.blockId} blockId={tabData.blockId} layoutModel={layoutModel} preview={false} />;
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
--success-color: rgb(78, 154, 6);
|
--success-color: rgb(78, 154, 6);
|
||||||
--hover-bg-color: rgba(255, 255, 255, 0.1);
|
--hover-bg-color: rgba(255, 255, 255, 0.1);
|
||||||
--block-bg-color: rgba(0, 0, 0, 0.5);
|
--block-bg-color: rgba(0, 0, 0, 0.5);
|
||||||
|
--block-bg-solid-color: rgb(0, 0, 0);
|
||||||
|
--block-border-radius: 8px;
|
||||||
|
|
||||||
/* scrollbar colors */
|
/* scrollbar colors */
|
||||||
--scrollbar-background-color: transparent;
|
--scrollbar-background-color: transparent;
|
||||||
|
@ -40,7 +40,7 @@ import {
|
|||||||
} from "./model";
|
} from "./model";
|
||||||
import { NodeRefMap } from "./nodeRefMap";
|
import { NodeRefMap } from "./nodeRefMap";
|
||||||
import "./tilelayout.less";
|
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
|
* 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);
|
const leafRef = nodeRefs.get(leaf.id);
|
||||||
// console.log("current leafRef", leafRef.current);
|
// console.log("current leafRef", leafRef.current);
|
||||||
if (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 leafBounding = leafRef.current.getBoundingClientRect();
|
||||||
const transform = createTransform({
|
const transform = setTransform({
|
||||||
top: leafBounding.top - overlayBoundingRect.top,
|
top: leafBounding.top - overlayBoundingRect.top,
|
||||||
left: leafBounding.left - overlayBoundingRect.left,
|
left: leafBounding.left - overlayBoundingRect.left,
|
||||||
width: leafBounding.width,
|
width: leafBounding.width,
|
||||||
height: leafBounding.height,
|
height: leafBounding.height,
|
||||||
});
|
});
|
||||||
newLayoutLeafTransforms[leafRef.current.id] = transform;
|
newLayoutLeafTransforms[leaf.id] = transform;
|
||||||
} else {
|
} else {
|
||||||
console.warn("missing leaf", leaf.id);
|
console.warn("missing leaf", leaf.id);
|
||||||
}
|
}
|
||||||
@ -199,7 +213,7 @@ function TileLayoutComponent<T>({ layoutTreeStateAtom, contents, getCursorPoint
|
|||||||
const newOverlayOffset = displayBoundingRect.top + 2 * displayBoundingRect.height;
|
const newOverlayOffset = displayBoundingRect.top + 2 * displayBoundingRect.height;
|
||||||
// console.log("overlayOffset", newOverlayOffset);
|
// console.log("overlayOffset", newOverlayOffset);
|
||||||
setOverlayTransform(
|
setOverlayTransform(
|
||||||
createTransform(
|
setTransform(
|
||||||
{
|
{
|
||||||
top: activeDrag || showOverlay ? 0 : newOverlayOffset,
|
top: activeDrag || showOverlay ? 0 : newOverlayOffset,
|
||||||
left: 0,
|
left: 0,
|
||||||
@ -246,6 +260,18 @@ function TileLayoutComponent<T>({ layoutTreeStateAtom, contents, getCursorPoint
|
|||||||
[contents.onNodeDelete, dispatch]
|
[contents.onNodeDelete, dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onLeafMagnifyToggle = useCallback(
|
||||||
|
(node: LayoutNode<T>) => {
|
||||||
|
const action = {
|
||||||
|
type: LayoutTreeActionType.MagnifyNodeToggle,
|
||||||
|
nodeId: node.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch(action);
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<div className={clsx("tile-layout", contents.className, { animate })} onPointerOver={onPointerOver}>
|
<div className={clsx("tile-layout", contents.className, { animate })} onPointerOver={onPointerOver}>
|
||||||
@ -253,6 +279,7 @@ function TileLayoutComponent<T>({ layoutTreeStateAtom, contents, getCursorPoint
|
|||||||
<DisplayNodesWrapper
|
<DisplayNodesWrapper
|
||||||
contents={contents}
|
contents={contents}
|
||||||
ready={animate}
|
ready={animate}
|
||||||
|
onLeafMagnifyToggle={onLeafMagnifyToggle}
|
||||||
onLeafClose={onLeafClose}
|
onLeafClose={onLeafClose}
|
||||||
layoutTreeState={layoutTreeState}
|
layoutTreeState={layoutTreeState}
|
||||||
layoutLeafTransforms={layoutLeafTransforms}
|
layoutLeafTransforms={layoutLeafTransforms}
|
||||||
@ -303,6 +330,13 @@ interface DisplayNodesWrapperProps<T> {
|
|||||||
* @param node The node that is closed.
|
* @param node The node that is closed.
|
||||||
*/
|
*/
|
||||||
onLeafClose: (node: LayoutNode<T>) => void;
|
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.
|
* 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(
|
const DisplayNodesWrapper = memo(
|
||||||
<T,>({ layoutTreeState, contents, onLeafClose, layoutLeafTransforms, ready }: DisplayNodesWrapperProps<T>) => {
|
<T,>({
|
||||||
|
layoutTreeState,
|
||||||
|
contents,
|
||||||
|
onLeafClose,
|
||||||
|
onLeafMagnifyToggle,
|
||||||
|
layoutLeafTransforms,
|
||||||
|
ready,
|
||||||
|
}: DisplayNodesWrapperProps<T>) => {
|
||||||
if (!layoutLeafTransforms) {
|
if (!layoutLeafTransforms) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return layoutTreeState.leafs.map((leaf) => {
|
return layoutTreeState.leafs.map((leaf) => {
|
||||||
return (
|
return (
|
||||||
<DisplayNode
|
<DisplayNode
|
||||||
|
className={clsx({ magnified: layoutTreeState.magnifiedNodeId === leaf.id })}
|
||||||
key={leaf.id}
|
key={leaf.id}
|
||||||
layoutNode={leaf}
|
layoutNode={leaf}
|
||||||
contents={contents}
|
contents={contents}
|
||||||
transform={layoutLeafTransforms[leaf.id]}
|
transform={layoutLeafTransforms[leaf.id]}
|
||||||
onLeafClose={onLeafClose}
|
onLeafClose={onLeafClose}
|
||||||
|
onLeafMagnifyToggle={onLeafMagnifyToggle}
|
||||||
ready={ready}
|
ready={ready}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -344,6 +387,12 @@ interface DisplayNodeProps<T> {
|
|||||||
*/
|
*/
|
||||||
contents: TileLayoutContents<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.
|
* A callback that is called when a leaf node gets closed.
|
||||||
* @param node The node that is 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.
|
* Determines whether a leaf's contents should be displayed to the user.
|
||||||
*/
|
*/
|
||||||
ready: boolean;
|
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.
|
* A series of CSS properties used to display a leaf node with the correct dimensions and position, as determined from its corresponding OverlayNode.
|
||||||
*/
|
*/
|
||||||
@ -364,7 +419,16 @@ const dragItemType = "TILE_ITEM";
|
|||||||
/**
|
/**
|
||||||
* The draggable and displayable portion of a leaf node in a layout tree.
|
* 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 DisplayNode = memo(
|
||||||
|
<T,>({
|
||||||
|
layoutNode,
|
||||||
|
contents,
|
||||||
|
transform,
|
||||||
|
onLeafMagnifyToggle,
|
||||||
|
onLeafClose,
|
||||||
|
ready,
|
||||||
|
className,
|
||||||
|
}: DisplayNodeProps<T>) => {
|
||||||
const tileNodeRef = useRef<HTMLDivElement>(null);
|
const tileNodeRef = useRef<HTMLDivElement>(null);
|
||||||
const dragHandleRef = useRef<HTMLDivElement>(null);
|
const dragHandleRef = useRef<HTMLDivElement>(null);
|
||||||
const previewRef = useRef<HTMLDivElement>(null);
|
const previewRef = useRef<HTMLDivElement>(null);
|
||||||
@ -436,11 +500,15 @@ const DisplayNode = memo(<T,>({ layoutNode, contents, transform, onLeafClose, re
|
|||||||
onLeafClose(layoutNode);
|
onLeafClose(layoutNode);
|
||||||
}, [layoutNode, onLeafClose]);
|
}, [layoutNode, onLeafClose]);
|
||||||
|
|
||||||
|
const onMagnifyToggle = useCallback(() => {
|
||||||
|
onLeafMagnifyToggle(layoutNode);
|
||||||
|
}, [layoutNode, onLeafMagnifyToggle]);
|
||||||
|
|
||||||
const leafContent = useMemo(() => {
|
const leafContent = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
layoutNode.data && (
|
layoutNode.data && (
|
||||||
<div key="leaf" className="tile-leaf">
|
<div key="leaf" className="tile-leaf">
|
||||||
{contents.renderContent(layoutNode.data, ready, onClose, dragHandleRef)}
|
{contents.renderContent(layoutNode.data, ready, onMagnifyToggle, onClose, dragHandleRef)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -448,7 +516,7 @@ const DisplayNode = memo(<T,>({ layoutNode, contents, transform, onLeafClose, re
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx("tile-node", { dragging: isDragging })}
|
className={clsx("tile-node", className, { dragging: isDragging })}
|
||||||
ref={tileNodeRef}
|
ref={tileNodeRef}
|
||||||
id={layoutNode.id}
|
id={layoutNode.id}
|
||||||
style={{
|
style={{
|
||||||
@ -461,7 +529,8 @@ const DisplayNode = memo(<T,>({ layoutNode, contents, transform, onLeafClose, re
|
|||||||
{previewElement}
|
{previewElement}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
interface OverlayNodeProps<T> {
|
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 }} />;
|
newPlaceholderOverlay = <div className="placeholder" style={{ ...placeholderTransform }} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -907,7 +976,7 @@ const Placeholder = <T,>({ layoutTreeState, overlayContainerRef, nodeRefsAtom, s
|
|||||||
width: targetBoundingRect.width,
|
width: targetBoundingRect.width,
|
||||||
};
|
};
|
||||||
|
|
||||||
const placeholderTransform = createTransform(placeholderDimensions);
|
const placeholderTransform = setTransform(placeholderDimensions);
|
||||||
newPlaceholderOverlay = <div className="placeholder" style={{ ...placeholderTransform }} />;
|
newPlaceholderOverlay = <div className="placeholder" style={{ ...placeholderTransform }} />;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -78,7 +78,7 @@ function getLayoutNodeWaveObjAtomFromTab<T>(
|
|||||||
const tabValue = get(tabAtom);
|
const tabValue = get(tabAtom);
|
||||||
// console.log("getLayoutNodeWaveObjAtomFromTab tabValue", tabValue);
|
// console.log("getLayoutNodeWaveObjAtomFromTab tabValue", tabValue);
|
||||||
if (!tabValue) return;
|
if (!tabValue) return;
|
||||||
const layoutNodeOref = WOS.makeORef("layout", tabValue.layoutNode);
|
const layoutNodeOref = WOS.makeORef("layout", tabValue.layoutnode);
|
||||||
// console.log("getLayoutNodeWaveObjAtomFromTab oref", layoutNodeOref);
|
// console.log("getLayoutNodeWaveObjAtomFromTab oref", layoutNodeOref);
|
||||||
return WOS.getWaveObjectAtom<LayoutNodeWaveObj<T>>(layoutNodeOref);
|
return WOS.getWaveObjectAtom<LayoutNodeWaveObj<T>>(layoutNodeOref);
|
||||||
}
|
}
|
||||||
@ -86,13 +86,16 @@ function getLayoutNodeWaveObjAtomFromTab<T>(
|
|||||||
export function withLayoutStateAtomFromTab<T>(tabAtom: Atom<Tab>): WritableLayoutTreeStateAtom<T> {
|
export function withLayoutStateAtomFromTab<T>(tabAtom: Atom<Tab>): WritableLayoutTreeStateAtom<T> {
|
||||||
const pendingActionAtom = atom<LayoutTreeAction>(null) as PrimitiveAtom<LayoutTreeAction>;
|
const pendingActionAtom = atom<LayoutTreeAction>(null) as PrimitiveAtom<LayoutTreeAction>;
|
||||||
const generationAtom = atom(0) as PrimitiveAtom<number>;
|
const generationAtom = atom(0) as PrimitiveAtom<number>;
|
||||||
|
|
||||||
return atom(
|
return atom(
|
||||||
(get) => {
|
(get) => {
|
||||||
const waveObjAtom = getLayoutNodeWaveObjAtomFromTab<T>(tabAtom, get);
|
const waveObjAtom = getLayoutNodeWaveObjAtomFromTab<T>(tabAtom, get);
|
||||||
if (!waveObjAtom) return null;
|
if (!waveObjAtom) return null;
|
||||||
const layoutState = newLayoutTreeState(get(waveObjAtom)?.node);
|
const waveObj = get(waveObjAtom);
|
||||||
|
const layoutState = newLayoutTreeState(waveObj?.node);
|
||||||
layoutState.pendingAction = get(pendingActionAtom);
|
layoutState.pendingAction = get(pendingActionAtom);
|
||||||
layoutState.generation = get(generationAtom);
|
layoutState.generation = get(generationAtom);
|
||||||
|
layoutState.magnifiedNodeId = waveObj?.magnifiednodeid;
|
||||||
return layoutState;
|
return layoutState;
|
||||||
},
|
},
|
||||||
(get, set, value) => {
|
(get, set, value) => {
|
||||||
@ -100,7 +103,11 @@ export function withLayoutStateAtomFromTab<T>(tabAtom: Atom<Tab>): WritableLayou
|
|||||||
if (get(generationAtom) !== value.generation) {
|
if (get(generationAtom) !== value.generation) {
|
||||||
const waveObjAtom = getLayoutNodeWaveObjAtomFromTab<T>(tabAtom, get);
|
const waveObjAtom = getLayoutNodeWaveObjAtomFromTab<T>(tabAtom, get);
|
||||||
if (!waveObjAtom) return;
|
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(generationAtom, value.generation);
|
||||||
set(waveObjAtom, newWaveObj);
|
set(waveObjAtom, newWaveObj);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
LayoutTreeComputeMoveNodeAction,
|
LayoutTreeComputeMoveNodeAction,
|
||||||
LayoutTreeDeleteNodeAction,
|
LayoutTreeDeleteNodeAction,
|
||||||
LayoutTreeInsertNodeAction,
|
LayoutTreeInsertNodeAction,
|
||||||
|
LayoutTreeMagnifyNodeToggleAction,
|
||||||
LayoutTreeMoveNodeAction,
|
LayoutTreeMoveNodeAction,
|
||||||
LayoutTreeResizeNodeAction,
|
LayoutTreeResizeNodeAction,
|
||||||
LayoutTreeSetPendingAction,
|
LayoutTreeSetPendingAction,
|
||||||
@ -108,6 +109,10 @@ function layoutTreeStateReducerInner<T>(layoutTreeState: LayoutTreeState<T>, act
|
|||||||
resizeNode(layoutTreeState, action as LayoutTreeResizeNodeAction);
|
resizeNode(layoutTreeState, action as LayoutTreeResizeNodeAction);
|
||||||
layoutTreeState.generation++;
|
layoutTreeState.generation++;
|
||||||
break;
|
break;
|
||||||
|
case LayoutTreeActionType.MagnifyNodeToggle:
|
||||||
|
magnifyNodeToggle(layoutTreeState, action as LayoutTreeMagnifyNodeToggleAction);
|
||||||
|
layoutTreeState.generation++;
|
||||||
|
break;
|
||||||
default: {
|
default: {
|
||||||
console.error("Invalid reducer action", layoutTreeState, action);
|
console.error("Invalid reducer action", layoutTreeState, action);
|
||||||
}
|
}
|
||||||
@ -460,3 +465,20 @@ function resizeNode<T>(layoutTreeState: LayoutTreeState<T>, action: LayoutTreeRe
|
|||||||
node.size = resize.size;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -42,6 +42,7 @@ export enum LayoutTreeActionType {
|
|||||||
ResizeNode = "resize",
|
ResizeNode = "resize",
|
||||||
InsertNode = "insert",
|
InsertNode = "insert",
|
||||||
DeleteNode = "delete",
|
DeleteNode = "delete",
|
||||||
|
MagnifyNodeToggle = "magnify",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,6 +164,18 @@ export interface LayoutTreeResizeNodeAction extends LayoutTreeAction {
|
|||||||
resizeOperations: ResizeNodeOperation[];
|
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.
|
* Represents the state of a layout tree.
|
||||||
*
|
*
|
||||||
@ -173,6 +186,7 @@ export type LayoutTreeState<T> = {
|
|||||||
leafs: LayoutNode<T>[];
|
leafs: LayoutNode<T>[];
|
||||||
pendingAction: LayoutTreeAction;
|
pendingAction: LayoutTreeAction;
|
||||||
generation: number;
|
generation: number;
|
||||||
|
magnifiedNodeId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -200,6 +214,7 @@ export type WritableLayoutTreeStateAtom<T> = WritableAtom<LayoutTreeState<T>, [v
|
|||||||
export type ContentRenderer<T> = (
|
export type ContentRenderer<T> = (
|
||||||
data: T,
|
data: T,
|
||||||
ready: boolean,
|
ready: boolean,
|
||||||
|
onMagnifyToggle: () => void,
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
dragHandleRef: React.RefObject<HTMLDivElement>
|
dragHandleRef: React.RefObject<HTMLDivElement>
|
||||||
) => React.ReactNode;
|
) => React.ReactNode;
|
||||||
@ -208,6 +223,7 @@ export type PreviewRenderer<T> = (data: T) => React.ReactElement;
|
|||||||
|
|
||||||
export interface LayoutNodeWaveObj<T> extends WaveObj {
|
export interface LayoutNodeWaveObj<T> extends WaveObj {
|
||||||
node: LayoutNode<T>;
|
node: LayoutNode<T>;
|
||||||
|
magnifiednodeid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DefaultNodeSize = 10;
|
export const DefaultNodeSize = 10;
|
||||||
|
@ -66,10 +66,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tile-node {
|
.tile-node {
|
||||||
|
border-radius: var(--block-border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&.dragging {
|
&.dragging {
|
||||||
filter: blur(8px);
|
filter: blur(8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.magnified {
|
||||||
|
background-color: var(--block-bg-solid-color);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
.tile-preview-container {
|
.tile-preview-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10000px;
|
top: 10000px;
|
||||||
@ -89,7 +97,7 @@
|
|||||||
.placeholder {
|
.placeholder {
|
||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
transition-timing-function: ease-in;
|
transition-timing-function: ease-in;
|
||||||
transition-property: transform, width, height;
|
transition-property: transform, width, height, background-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +114,6 @@
|
|||||||
.placeholder {
|
.placeholder {
|
||||||
background-color: var(--accent-color);
|
background-color: var(--accent-color);
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
border-radius: 5px;
|
border-radius: calc(var(--block-border-radius) + 2px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
frontend/types/gotypes.d.ts
vendored
3
frontend/types/gotypes.d.ts
vendored
@ -176,6 +176,7 @@ declare global {
|
|||||||
// wstore.LayoutNode
|
// wstore.LayoutNode
|
||||||
type LayoutNode = WaveObj & {
|
type LayoutNode = WaveObj & {
|
||||||
node?: any;
|
node?: any;
|
||||||
|
magnifiednodeid?: string;
|
||||||
meta?: MetaType;
|
meta?: MetaType;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -315,7 +316,7 @@ declare global {
|
|||||||
// wstore.Tab
|
// wstore.Tab
|
||||||
type Tab = WaveObj & {
|
type Tab = WaveObj & {
|
||||||
name: string;
|
name: string;
|
||||||
layoutNode: string;
|
layoutnode: string;
|
||||||
blockids: string[];
|
blockids: string[];
|
||||||
meta: MetaType;
|
meta: MetaType;
|
||||||
};
|
};
|
||||||
|
@ -207,7 +207,7 @@ type Tab struct {
|
|||||||
OID string `json:"oid"`
|
OID string `json:"oid"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
LayoutNode string `json:"layoutNode"`
|
LayoutNode string `json:"layoutnode"`
|
||||||
BlockIds []string `json:"blockids"`
|
BlockIds []string `json:"blockids"`
|
||||||
Meta map[string]any `json:"meta"`
|
Meta map[string]any `json:"meta"`
|
||||||
}
|
}
|
||||||
@ -228,6 +228,7 @@ type LayoutNode struct {
|
|||||||
OID string `json:"oid"`
|
OID string `json:"oid"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
Node any `json:"node,omitempty"`
|
Node any `json:"node,omitempty"`
|
||||||
|
MagnifiedNodeId string `json:"magnifiednodeid,omitempty"`
|
||||||
Meta map[string]any `json:"meta,omitempty"`
|
Meta map[string]any `json:"meta,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user