From 2e5c23ffac14356be7d94c89e2595d71f3f9d120 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming <35219649+nilscognite@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:51:50 +0100 Subject: [PATCH 1/5] Add info --- .../Architecture/DomainObjectPanel.tsx | 4 +- .../Architecture/IconComponentMapper.tsx | 2 + .../Architecture/TreeView/TreeViewNode.tsx | 3 + .../Architecture/TreeView/TreeViewProps.ts | 5 ++ .../TreeView/components/TreeNodeCaret.tsx | 11 +--- .../TreeView/components/TreeViewInfo.tsx | 55 +++++++++++++++++++ .../TreeView/components/TreeViewLabel.tsx | 13 ++++- .../TreeView/utilities/constants.ts | 3 + 8 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx diff --git a/react-components/src/components/Architecture/DomainObjectPanel.tsx b/react-components/src/components/Architecture/DomainObjectPanel.tsx index 4d7c4e2a9a0..d12c6d707ac 100644 --- a/react-components/src/components/Architecture/DomainObjectPanel.tsx +++ b/react-components/src/components/Architecture/DomainObjectPanel.tsx @@ -36,11 +36,9 @@ export const DomainObjectPanel = (): ReactElement => { useEffect(() => { DomainObjectPanelUpdater.setDomainObjectDelegate(setCurrentDomainObjectInfo); - - // Set in the get string on the copy command if any }, [setCurrentDomainObjectInfo, commands]); - // Fore the getString to be updated + // Force the getString to be updated if (commands !== undefined && info !== undefined) { for (const command of commands) { if (command instanceof CopyToClipboardCommand) diff --git a/react-components/src/components/Architecture/IconComponentMapper.tsx b/react-components/src/components/Architecture/IconComponentMapper.tsx index 09c117cd08e..80a49416493 100644 --- a/react-components/src/components/Architecture/IconComponentMapper.tsx +++ b/react-components/src/components/Architecture/IconComponentMapper.tsx @@ -33,6 +33,7 @@ import { FlipVerticalIcon, GrabIcon, type IconProps, + InfoIcon, LocationIcon, PerspectiveAltIcon, PerspectiveIcon, @@ -89,6 +90,7 @@ const defaultMappings: Array<[IconName, IconType]> = [ ['FlipHorizontal', FlipHorizontalIcon], ['FlipVertical', FlipVerticalIcon], ['Grab', GrabIcon], + ['Info', InfoIcon], ['Location', LocationIcon], ['Perspective', PerspectiveIcon], ['PerspectiveAlt', PerspectiveAltIcon], diff --git a/react-components/src/components/Architecture/TreeView/TreeViewNode.tsx b/react-components/src/components/Architecture/TreeView/TreeViewNode.tsx index ade3007ca90..e63e6f3f4a3 100644 --- a/react-components/src/components/Architecture/TreeView/TreeViewNode.tsx +++ b/react-components/src/components/Architecture/TreeView/TreeViewNode.tsx @@ -26,6 +26,7 @@ import { SELECTED_TEXT_COLOR, TEXT_COLOR } from './utilities/constants'; +import { TreeViewInfo } from './components/TreeViewInfo'; // ================================================== // MAIN COMPONENT @@ -51,6 +52,7 @@ export const TreeViewNode = ({ const hasHover = props.hasHover ?? true; const hasCheckBoxes = props.hasCheckboxes ?? false; const hasIcons = props.hasIcons ?? false; + const hasInfo = props.hasInfo ?? false; const marginLeft = level * gapToChildren + 'px'; // This force to update the component when the node changes @@ -91,6 +93,7 @@ export const TreeViewNode = ({ }}> {hasIcons && } + {hasInfo && } {children !== undefined && diff --git a/react-components/src/components/Architecture/TreeView/TreeViewProps.ts b/react-components/src/components/Architecture/TreeView/TreeViewProps.ts index f1e91fa7a99..d7c4ab64395 100644 --- a/react-components/src/components/Architecture/TreeView/TreeViewProps.ts +++ b/react-components/src/components/Architecture/TreeView/TreeViewProps.ts @@ -19,6 +19,8 @@ export type TreeViewProps = { hoverBackgroundColor?: string; caretColor?: string; hoverCaretColor?: string; + infoColor?: string; + hoverInfoColor?: string; // Sizes gapBetweenItems?: number; @@ -29,12 +31,15 @@ export type TreeViewProps = { hasHover?: boolean; // Default true, If this is set, it uses the hover color for the mouse over effect hasCheckboxes?: boolean; // Default is false hasIcons?: boolean; // Default is false + hasInfo?: boolean; // Default is false loadingLabel?: string; // Default is 'Loading...' loadMoreLabel?: string; // Default is 'Load more...' + maxLabelLength: number; // Event handlers onSelectNode?: TreeNodeAction; onCheckNode?: TreeNodeAction; + onClickInfoBox?: TreeNodeAction; loadNodes?: LoadNodesAction; // The root node of the tree, the root is not rendered. diff --git a/react-components/src/components/Architecture/TreeView/components/TreeNodeCaret.tsx b/react-components/src/components/Architecture/TreeView/components/TreeNodeCaret.tsx index 450495b9d60..abe9341102d 100644 --- a/react-components/src/components/Architecture/TreeView/components/TreeNodeCaret.tsx +++ b/react-components/src/components/Architecture/TreeView/components/TreeNodeCaret.tsx @@ -25,7 +25,7 @@ export const TreeNodeCaret = ({ props: TreeViewProps; }): ReactElement => { const [isHoverOver, setHoverOver] = useState(false); - const color = getCaretColor(node, props, isHoverOver); + const color = getColor(node, props, isHoverOver); const size = props.caretSize ?? CARET_SIZE; const sizePx = size + 'px'; const style = { color, marginTop: '0px', width: sizePx, height: sizePx }; @@ -50,14 +50,7 @@ export const TreeNodeCaret = ({ return ; }; -function getCaretColor( - node: ITreeNode, - props: TreeViewProps, - isHoverOver: boolean -): string | undefined { - if (!node.isParent) { - return 'transparent'; - } +function getColor(node: ITreeNode, props: TreeViewProps, isHoverOver: boolean): string | undefined { if (isHoverOver) { return props.hoverCaretColor ?? HOVER_CARET_COLOR; } diff --git a/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx b/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx new file mode 100644 index 00000000000..4d8b21ad544 --- /dev/null +++ b/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx @@ -0,0 +1,55 @@ +/*! + * Copyright 2024 Cognite AS + */ + +/* eslint-disable react/prop-types */ + +import { useState, type ReactElement } from 'react'; +import { type TreeViewProps } from '../TreeViewProps'; +import { type ITreeNode } from '../../../../architecture/base/treeView/ITreeNode'; +import { InfoIcon } from '@cognite/cogs.js'; +import { HOVER_INFO_COLOR, INFO_COLOR } from '../utilities/constants'; + +// ================================================== +// MAIN COMPONENT +// ================================================== + +export const TreeViewInfo = ({ + node, + props +}: { + node: ITreeNode; + props: TreeViewProps; +}): ReactElement => { + const Icon = InfoIcon; + const [isHoverOver, setHoverOver] = useState(false); + const color = getColor(props, isHoverOver); + return ( + { + onClickInfo(node); + }} + onMouseEnter={() => { + setHoverOver(true); + }} + onMouseLeave={() => { + setHoverOver(false); + }} + /> + ); + + function onClickInfo(node: ITreeNode): void { + if (props.onClickInfoBox === undefined) { + return; + } + props.onClickInfoBox(node); + } +}; + +function getColor(props: TreeViewProps, isHoverOver: boolean): string | undefined { + if (isHoverOver) { + return props.hoverInfoColor ?? HOVER_INFO_COLOR; + } + return props.infoColor ?? INFO_COLOR; +} diff --git a/react-components/src/components/Architecture/TreeView/components/TreeViewLabel.tsx b/react-components/src/components/Architecture/TreeView/components/TreeViewLabel.tsx index d28600a877b..28edadcc365 100644 --- a/react-components/src/components/Architecture/TreeView/components/TreeViewLabel.tsx +++ b/react-components/src/components/Architecture/TreeView/components/TreeViewLabel.tsx @@ -7,7 +7,7 @@ import { type ReactElement } from 'react'; import { type TreeViewProps } from '../TreeViewProps'; import { type ITreeNode } from '../../../../architecture/base/treeView/ITreeNode'; -import { LOADING_LABEL } from '../utilities/constants'; +import { LOADING_LABEL, MAX_LABEL_LENGTH } from '../utilities/constants'; // ================================================== // MAIN COMPONENT @@ -20,7 +20,16 @@ export const TreeViewLabel = ({ node: ITreeNode; props: TreeViewProps; }): ReactElement => { - const label = node.isLoadingChildren ? (props.loadingLabel ?? LOADING_LABEL) : node.label; + let label: string; + if (node.isLoadingChildren) { + label = props.loadingLabel ?? LOADING_LABEL; + } else { + label = node.label; + const maxLabelLength = props.maxLabelLength ?? MAX_LABEL_LENGTH; + if (label.length > maxLabelLength) { + label = label.substring(0, maxLabelLength) + '...'; + } + } if (node.hasBoldLabel) { return {label}; } diff --git a/react-components/src/components/Architecture/TreeView/utilities/constants.ts b/react-components/src/components/Architecture/TreeView/utilities/constants.ts index c517d9c85aa..782545c1b1b 100644 --- a/react-components/src/components/Architecture/TreeView/utilities/constants.ts +++ b/react-components/src/components/Architecture/TreeView/utilities/constants.ts @@ -12,9 +12,12 @@ export const HOVER_TEXT_COLOR = 'black'; export const HOVER_BACKGROUND_COLOR = 'lightgray'; export const CARET_COLOR = 'gray'; export const HOVER_CARET_COLOR = 'highlight'; +export const INFO_COLOR = 'black'; +export const HOVER_INFO_COLOR = 'highlight'; export const CARET_SIZE = 20; export const GAP_TO_CHILDREN = 16; export const GAP_BETWEEN_ITEMS = 4; export const LOADING_LABEL = 'Loading ...'; export const LOAD_MORE_LABEL = 'Load more ...'; export const BACKGROUND_COLOR = 'white'; +export const MAX_LABEL_LENGTH = 25; From bc493206d8c4ff52e569c7af61aa5123fb639dab Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming <35219649+nilscognite@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:59:55 +0100 Subject: [PATCH 2/5] XFix treenode --- .../src/components/Architecture/TreeView/TreeViewNode.tsx | 2 +- .../src/components/Architecture/TreeView/TreeViewProps.ts | 4 ++-- .../Architecture/TreeView/components/TreeViewInfo.tsx | 4 ++-- react-components/stories/TreeView.stories.tsx | 7 +++++++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/react-components/src/components/Architecture/TreeView/TreeViewNode.tsx b/react-components/src/components/Architecture/TreeView/TreeViewNode.tsx index e63e6f3f4a3..01e6c165364 100644 --- a/react-components/src/components/Architecture/TreeView/TreeViewNode.tsx +++ b/react-components/src/components/Architecture/TreeView/TreeViewNode.tsx @@ -93,8 +93,8 @@ export const TreeViewNode = ({ }}> {hasIcons && } - {hasInfo && } + {hasInfo && } {children !== undefined && children.map((node, index) => ( diff --git a/react-components/src/components/Architecture/TreeView/TreeViewProps.ts b/react-components/src/components/Architecture/TreeView/TreeViewProps.ts index d7c4ab64395..12c5ef2e807 100644 --- a/react-components/src/components/Architecture/TreeView/TreeViewProps.ts +++ b/react-components/src/components/Architecture/TreeView/TreeViewProps.ts @@ -34,12 +34,12 @@ export type TreeViewProps = { hasInfo?: boolean; // Default is false loadingLabel?: string; // Default is 'Loading...' loadMoreLabel?: string; // Default is 'Load more...' - maxLabelLength: number; + maxLabelLength?: number; // Event handlers onSelectNode?: TreeNodeAction; onCheckNode?: TreeNodeAction; - onClickInfoBox?: TreeNodeAction; + onClickInfo?: TreeNodeAction; loadNodes?: LoadNodesAction; // The root node of the tree, the root is not rendered. diff --git a/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx b/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx index 4d8b21ad544..9e7dea46370 100644 --- a/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx +++ b/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx @@ -40,10 +40,10 @@ export const TreeViewInfo = ({ ); function onClickInfo(node: ITreeNode): void { - if (props.onClickInfoBox === undefined) { + if (props.onClickInfo === undefined) { return; } - props.onClickInfoBox(node); + props.onClickInfo(node); } }; diff --git a/react-components/stories/TreeView.stories.tsx b/react-components/stories/TreeView.stories.tsx index 9646cfd37fe..5a96a068dba 100644 --- a/react-components/stories/TreeView.stories.tsx +++ b/react-components/stories/TreeView.stories.tsx @@ -49,8 +49,11 @@ export const Main: Story = { onSelectNode={onSingleSelectNode} onCheckNode={onDependentCheckNode} loadNodes={loadNodes} + onClickInfo={onClickInfo} hasCheckboxes hasIcons + maxLabelLength={4} + hasInfo /> Date: Tue, 5 Nov 2024 11:03:15 +0100 Subject: [PATCH 3/5] Update TreeView.stories.tsx --- react-components/stories/TreeView.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react-components/stories/TreeView.stories.tsx b/react-components/stories/TreeView.stories.tsx index 5a96a068dba..c2f5a568662 100644 --- a/react-components/stories/TreeView.stories.tsx +++ b/react-components/stories/TreeView.stories.tsx @@ -176,6 +176,6 @@ function createTreeMock(lazyLoading: boolean): TreeNode { return root; } -function onClickInfo(node: ITreeNode): void { - console.log('Info clicked: ' + node.label); +function onClickInfo(_node: ITreeNode): void { + // console.log('Info clicked: ' + node.label); } From 0f07f784049f8cad5b4e2ec0657236f37a01db9e Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming <35219649+nilscognite@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:16:14 +0100 Subject: [PATCH 4/5] Add Generics --- .../architecture/base/treeView/TreeNode.ts | 36 ++++++++++--------- .../TreeView/components/TreeViewInfo.tsx | 2 +- react-components/stories/TreeView.stories.tsx | 14 ++++---- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/react-components/src/architecture/base/treeView/TreeNode.ts b/react-components/src/architecture/base/treeView/TreeNode.ts index be99c7a7e5c..0bf7403b013 100644 --- a/react-components/src/architecture/base/treeView/TreeNode.ts +++ b/react-components/src/architecture/base/treeView/TreeNode.ts @@ -7,7 +7,7 @@ import { type IconName } from '../utilities/IconName'; import { type ITreeNode } from './ITreeNode'; import { CheckBoxState, type TreeNodeAction, type IconColor, type LoadNodesAction } from './types'; -export class TreeNode implements ITreeNode { +export class TreeNode implements ITreeNode { // ================================================== // INSTANCE FIELDS // ================================================== @@ -24,12 +24,13 @@ export class TreeNode implements ITreeNode { private _isLoadingSiblings: boolean = false; private _needLoadChildren = false; private _needLoadSiblings = false; + public userData: T | undefined = undefined; - protected _children: TreeNode[] | undefined = undefined; - protected _parent: TreeNode | undefined = undefined; + protected _children: Array> | undefined = undefined; + protected _parent: TreeNode | undefined = undefined; // ================================================== - // INSTANCE PROPERTIES (Some are implementation of ITreeNode) + // INSTANCE PROPERTIES (Some are implementation of ITreeNode) // ================================================== public get label(): string { @@ -175,14 +176,15 @@ export class TreeNode implements ITreeNode { // INSTANCE METHODS: Parent children methods // ================================================== - public getRoot(): TreeNode { + // eslint-disable-next-line @typescript-eslint/prefer-return-this-type + public getRoot(): TreeNode { if (this._parent !== undefined) { return this._parent.getRoot(); } return this; } - public addChild(child: TreeNode): void { + public addChild(child: TreeNode): void { if (this._children === undefined) { this._children = []; } @@ -190,7 +192,7 @@ export class TreeNode implements ITreeNode { child._parent = this; } - public insertChild(index: number, child: TreeNode): void { + public insertChild(index: number, child: TreeNode): void { if (this._children === undefined) { this._children = []; } @@ -213,7 +215,7 @@ export class TreeNode implements ITreeNode { if (!(child instanceof TreeNode)) { continue; } - this.addChild(child); + this.addChild(child as TreeNode); } } this.needLoadChildren = false; @@ -240,7 +242,7 @@ export class TreeNode implements ITreeNode { continue; } index++; - parent.insertChild(index, child); + parent.insertChild(index, child as TreeNode); } this.needLoadSiblings = false; parent.update(); @@ -250,8 +252,8 @@ export class TreeNode implements ITreeNode { // INSTANCE METHODS: Get selection and checked nodes // ================================================== - public getSelectedNodes(): TreeNode[] { - const nodes: TreeNode[] = []; + public getSelectedNodes(): Array> { + const nodes: Array> = []; for (const child of this.getThisAndDescendants()) { if (child.isSelected) { nodes.push(child); @@ -260,8 +262,8 @@ export class TreeNode implements ITreeNode { return nodes; } - public getCheckedNodes(): TreeNode[] { - const nodes: TreeNode[] = []; + public getCheckedNodes(): Array> { + const nodes: Array> = []; for (const child of this.getThisAndDescendants()) { if (child.checkBoxState === CheckBoxState.All) { nodes.push(child); @@ -274,7 +276,7 @@ export class TreeNode implements ITreeNode { // INSTANCE METHODS: Iterators // ================================================== - public *getChildren(loadNodes?: LoadNodesAction): Generator { + public *getChildren(loadNodes?: LoadNodesAction): Generator> { if (this.isLoadingChildren) { loadNodes = undefined; } @@ -290,7 +292,7 @@ export class TreeNode implements ITreeNode { } } - public *getDescendants(): Generator { + public *getDescendants(): Generator> { for (const child of this.getChildren()) { yield child; for (const descendant of child.getDescendants()) { @@ -299,14 +301,14 @@ export class TreeNode implements ITreeNode { } } - public *getThisAndDescendants(): Generator { + public *getThisAndDescendants(): Generator> { yield this; for (const descendant of this.getDescendants()) { yield descendant; } } - public *getAncestors(): Generator { + public *getAncestors(): Generator> { let ancestor = this._parent; while (ancestor !== undefined) { yield ancestor; diff --git a/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx b/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx index 9e7dea46370..fd60a4c7d59 100644 --- a/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx +++ b/react-components/src/components/Architecture/TreeView/components/TreeViewInfo.tsx @@ -26,7 +26,7 @@ export const TreeViewInfo = ({ const color = getColor(props, isHoverOver); return ( { onClickInfo(node); }} diff --git a/react-components/stories/TreeView.stories.tsx b/react-components/stories/TreeView.stories.tsx index c2f5a568662..ecabc6707b5 100644 --- a/react-components/stories/TreeView.stories.tsx +++ b/react-components/stories/TreeView.stories.tsx @@ -111,7 +111,7 @@ async function loadNodes( const batchSize = 10; for (let i = 0; i < batchSize && i < totalCount; i++) { - const child = new TreeNode(); + const child = new TreeNode(); child.label = 'Leaf ' + getRandomIntByMax(1000); child.icon = i % 3 === 0 ? 'Cube' : 'CylinderHorizontal'; child.isExpanded = false; @@ -130,13 +130,14 @@ async function loadNodes( return await promise; } -function createTreeMock(lazyLoading: boolean): TreeNode { - const root = new TreeNode(); +function createTreeMock(lazyLoading: boolean): TreeNode { + const root = new TreeNode(); root.label = 'Root'; root.isExpanded = true; for (let i = 1; i <= 100; i++) { - const parent = new TreeNode(); + const parent = new TreeNode(); + parent.userData = 'Index ' + i; parent.label = 'Folder ' + i; parent.isExpanded = true; parent.icon = 'Snow'; @@ -147,7 +148,7 @@ function createTreeMock(lazyLoading: boolean): TreeNode { parent.icon = i % 2 === 0 ? 'CubeFrontRight' : 'CubeFrontLeft'; for (let j = 1; j <= 10; j++) { - const child = new TreeNode(); + const child = new TreeNode(); child.label = 'Child ' + i + '.' + j; switch (j % 3) { case 0: @@ -177,5 +178,6 @@ function createTreeMock(lazyLoading: boolean): TreeNode { } function onClickInfo(_node: ITreeNode): void { - // console.log('Info clicked: ' + node.label); + // const n = _node as TreeNode; + // console.log('Info clicked: ' + n.label + 'UserData: ' + n.userData); } From 5960cb0a65089fa64c4a9171821d239a02b81624 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming <35219649+nilscognite@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:24:34 +0100 Subject: [PATCH 5/5] Update parent --- react-components/package.json | 2 +- .../src/architecture/base/treeView/TreeNode.ts | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/react-components/package.json b/react-components/package.json index c9f98e50777..1ab55177ec7 100644 --- a/react-components/package.json +++ b/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@cognite/reveal-react-components", - "version": "0.65.1", + "version": "0.65.2", "exports": { ".": { "import": "./dist/index.js", diff --git a/react-components/src/architecture/base/treeView/TreeNode.ts b/react-components/src/architecture/base/treeView/TreeNode.ts index 0bf7403b013..b6504dc6695 100644 --- a/react-components/src/architecture/base/treeView/TreeNode.ts +++ b/react-components/src/architecture/base/treeView/TreeNode.ts @@ -172,14 +172,18 @@ export class TreeNode implements ITreeNode { return this._children !== undefined && this._children.length > 0; } + public get parent(): TreeNode | undefined { + return this._parent; + } + // ================================================== // INSTANCE METHODS: Parent children methods // ================================================== // eslint-disable-next-line @typescript-eslint/prefer-return-this-type public getRoot(): TreeNode { - if (this._parent !== undefined) { - return this._parent.getRoot(); + if (this.parent !== undefined) { + return this.parent.getRoot(); } return this; } @@ -228,7 +232,7 @@ export class TreeNode implements ITreeNode { if (siblings === undefined || siblings.length === 0) { return; } - const parent = this._parent; + const parent = this.parent; if (parent === undefined || parent._children === undefined) { return; } @@ -280,7 +284,7 @@ export class TreeNode implements ITreeNode { if (this.isLoadingChildren) { loadNodes = undefined; } - const canLoad = this.isParent && this._parent !== undefined; + const canLoad = this.isParent && this.parent !== undefined; if (canLoad && loadNodes !== undefined && this.needLoadChildren) { void this.loadChildren(loadNodes); } @@ -309,10 +313,10 @@ export class TreeNode implements ITreeNode { } public *getAncestors(): Generator> { - let ancestor = this._parent; + let ancestor = this.parent; while (ancestor !== undefined) { yield ancestor; - ancestor = ancestor._parent; + ancestor = ancestor.parent; } }