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

fix(react-components): TreeView: Add info icon for clicking for further info. Also add support for long names. #4847

Merged
merged 7 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion react-components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cognite/reveal-react-components",
"version": "0.65.1",
"version": "0.65.2",
"exports": {
".": {
"import": "./dist/index.js",
Expand Down
52 changes: 29 additions & 23 deletions react-components/src/architecture/base/treeView/TreeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T = any> implements ITreeNode {
// ==================================================
// INSTANCE FIELDS
// ==================================================
Expand All @@ -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<TreeNode<T>> | undefined = undefined;
protected _parent: TreeNode<T> | undefined = undefined;

// ==================================================
// INSTANCE PROPERTIES (Some are implementation of ITreeNode)
// INSTANCE PROPERTIES (Some are implementation of ITreeNode<T>)
// ==================================================

public get label(): string {
Expand Down Expand Up @@ -171,26 +172,31 @@ export class TreeNode implements ITreeNode {
return this._children !== undefined && this._children.length > 0;
}

public get parent(): TreeNode<T> | undefined {
return this._parent;
}

// ==================================================
// INSTANCE METHODS: Parent children methods
// ==================================================

public getRoot(): TreeNode {
if (this._parent !== undefined) {
return this._parent.getRoot();
// eslint-disable-next-line @typescript-eslint/prefer-return-this-type
public getRoot(): TreeNode<T> {
if (this.parent !== undefined) {
return this.parent.getRoot();
}
return this;
}

public addChild(child: TreeNode): void {
public addChild(child: TreeNode<T>): void {
if (this._children === undefined) {
this._children = [];
}
this._children.push(child);
child._parent = this;
}

public insertChild(index: number, child: TreeNode): void {
public insertChild(index: number, child: TreeNode<T>): void {
if (this._children === undefined) {
this._children = [];
}
Expand All @@ -213,7 +219,7 @@ export class TreeNode implements ITreeNode {
if (!(child instanceof TreeNode)) {
continue;
}
this.addChild(child);
this.addChild(child as TreeNode<T>);
}
}
this.needLoadChildren = false;
Expand All @@ -226,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;
}
Expand All @@ -240,7 +246,7 @@ export class TreeNode implements ITreeNode {
continue;
}
index++;
parent.insertChild(index, child);
parent.insertChild(index, child as TreeNode<T>);
}
this.needLoadSiblings = false;
parent.update();
Expand All @@ -250,8 +256,8 @@ export class TreeNode implements ITreeNode {
// INSTANCE METHODS: Get selection and checked nodes
// ==================================================

public getSelectedNodes(): TreeNode[] {
const nodes: TreeNode[] = [];
public getSelectedNodes(): Array<TreeNode<T>> {
const nodes: Array<TreeNode<T>> = [];
for (const child of this.getThisAndDescendants()) {
if (child.isSelected) {
nodes.push(child);
Expand All @@ -260,8 +266,8 @@ export class TreeNode implements ITreeNode {
return nodes;
}

public getCheckedNodes(): TreeNode[] {
const nodes: TreeNode[] = [];
public getCheckedNodes(): Array<TreeNode<T>> {
const nodes: Array<TreeNode<T>> = [];
for (const child of this.getThisAndDescendants()) {
if (child.checkBoxState === CheckBoxState.All) {
nodes.push(child);
Expand All @@ -274,11 +280,11 @@ export class TreeNode implements ITreeNode {
// INSTANCE METHODS: Iterators
// ==================================================

public *getChildren(loadNodes?: LoadNodesAction): Generator<TreeNode> {
public *getChildren(loadNodes?: LoadNodesAction): Generator<TreeNode<T>> {
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);
}
Expand All @@ -290,7 +296,7 @@ export class TreeNode implements ITreeNode {
}
}

public *getDescendants(): Generator<TreeNode> {
public *getDescendants(): Generator<TreeNode<T>> {
for (const child of this.getChildren()) {
yield child;
for (const descendant of child.getDescendants()) {
Expand All @@ -299,18 +305,18 @@ export class TreeNode implements ITreeNode {
}
}

public *getThisAndDescendants(): Generator<TreeNode> {
public *getThisAndDescendants(): Generator<TreeNode<T>> {
yield this;
for (const descendant of this.getDescendants()) {
yield descendant;
}
}

public *getAncestors(): Generator<TreeNode> {
let ancestor = this._parent;
public *getAncestors(): Generator<TreeNode<T>> {
let ancestor = this.parent;
while (ancestor !== undefined) {
yield ancestor;
ancestor = ancestor._parent;
ancestor = ancestor.parent;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
FlipVerticalIcon,
GrabIcon,
type IconProps,
InfoIcon,
LocationIcon,
PerspectiveAltIcon,
PerspectiveIcon,
Expand Down Expand Up @@ -89,6 +90,7 @@ const defaultMappings: Array<[IconName, IconType]> = [
['FlipHorizontal', FlipHorizontalIcon],
['FlipVertical', FlipVerticalIcon],
['Grab', GrabIcon],
['Info', InfoIcon],
['Location', LocationIcon],
['Perspective', PerspectiveIcon],
['PerspectiveAlt', PerspectiveAltIcon],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
SELECTED_TEXT_COLOR,
TEXT_COLOR
} from './utilities/constants';
import { TreeViewInfo } from './components/TreeViewInfo';

// ==================================================
// MAIN COMPONENT
Expand All @@ -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
Expand Down Expand Up @@ -92,6 +94,7 @@ export const TreeViewNode = ({
{hasIcons && <TreeNodeIcon node={node} color={color} />}
<TreeViewLabel node={node} props={props} />
</div>
{hasInfo && <TreeViewInfo node={node} props={props} />}
</div>
{children !== undefined &&
children.map((node, index) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export type TreeViewProps = {
hoverBackgroundColor?: string;
caretColor?: string;
hoverCaretColor?: string;
infoColor?: string;
hoverInfoColor?: string;

// Sizes
gapBetweenItems?: number;
Expand All @@ -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;
onClickInfo?: TreeNodeAction;
loadNodes?: LoadNodesAction;

// The root node of the tree, the root is not rendered.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand All @@ -50,14 +50,7 @@ export const TreeNodeCaret = ({
return <CaretDownIcon style={style} />;
};

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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<Icon
style={{ color, marginTop: '3px', marginLeft: '6px' }}
onClick={() => {
onClickInfo(node);
}}
onMouseEnter={() => {
setHoverOver(true);
}}
onMouseLeave={() => {
setHoverOver(false);
}}
/>
);

function onClickInfo(node: ITreeNode): void {
if (props.onClickInfo === undefined) {
return;
}
props.onClickInfo(node);
}
};

function getColor(props: TreeViewProps, isHoverOver: boolean): string | undefined {
if (isHoverOver) {
return props.hoverInfoColor ?? HOVER_INFO_COLOR;
}
return props.infoColor ?? INFO_COLOR;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <b>{label}</b>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading
Loading