Skip to content

Commit

Permalink
tree: use dot decoration on folders (#9473)
Browse files Browse the repository at this point in the history
The following commit updates the rendering of tail decorations for
composite tree nodes (nodes that contain children nodes like folders).

The following updates the rendering of tail decorations for composite
tree nodes (nodes that contain children nodes like folders).  The
updates include :
- rendering a generic icon decoration (dot) for composite nodes with
children with decoration data.
- updates to the rendering logic to only render the decoration data
with the highest priority so no duplicate generic icons are present.
  • Loading branch information
OmarSdt-EC authored and RomanNikitenko committed Sep 16, 2021
1 parent bb08af5 commit 2621099
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 20 deletions.
47 changes: 30 additions & 17 deletions packages/core/src/browser/tree/tree-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -745,26 +745,30 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
* @param node the tree node.
* @param icon the icon.
*/
protected decorateIcon(node: TreeNode, icon: React.ReactNode | null): React.ReactNode {
// eslint-disable-next-line no-null/no-null
if (icon === null) {
// eslint-disable-next-line no-null/no-null
return null;
protected decorateIcon(node: TreeNode, icon: React.ReactNode): React.ReactNode {
if (!icon) {
return;
}

const overlayIcons: React.ReactNode[] = [];
new Map(this.getDecorationData(node, 'iconOverlay').reverse().filter(notEmpty)
.map(overlay => [overlay.position, overlay] as [TreeDecoration.IconOverlayPosition, TreeDecoration.IconOverlay | TreeDecoration.IconClassOverlay]))
.forEach((overlay, position) => {
const iconClasses = [TreeDecoration.Styles.DECORATOR_SIZE_CLASS, TreeDecoration.IconOverlayPosition.getStyle(position)];
// if multiple overlays have the same overlay.position attribute, we'll de-duplicate those and only process the first one from the decoration array
const seenPositions = new Set<TreeDecoration.IconOverlayPosition>();
const overlays = this.getDecorationData(node, 'iconOverlay').filter(notEmpty);

for (const overlay of overlays) {
if (!seenPositions.has(overlay.position)) {
seenPositions.add(overlay.position);
const iconClasses = [TreeDecoration.Styles.DECORATOR_SIZE_CLASS, TreeDecoration.IconOverlayPosition.getStyle(overlay.position)];
const style = (color?: string) => color === undefined ? {} : { color };

if (overlay.background) {
overlayIcons.push(<span key={node.id + 'bg'} className={this.getIconClass(overlay.background.shape, iconClasses)} style={style(overlay.background.color)}>
</span>);
overlayIcons.push(<span key={node.id + 'bg'} className={this.getIconClass(overlay.background.shape, iconClasses)}
style={style(overlay.background.color)}></span>);
}
const overlayIcon = (overlay as TreeDecoration.IconOverlay).icon || (overlay as TreeDecoration.IconClassOverlay).iconClass;

const overlayIcon = 'icon' in overlay ? overlay.icon : overlay.iconClass;
overlayIcons.push(<span key={node.id} className={this.getIconClass(overlayIcon, iconClasses)} style={style(overlay.color)}></span>);
});
}
}

if (overlayIcons.length > 0) {
return <div className={TreeDecoration.Styles.ICON_WRAPPER_CLASS}>{icon}{overlayIcons}</div>;
Expand All @@ -779,14 +783,23 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
* @param props the node properties.
*/
protected renderTailDecorations(node: TreeNode, props: NodeProps): React.ReactNode {
const tailDecorations = this.getDecorationData(node, 'tailDecorations').filter(notEmpty).reduce((acc, current) => acc.concat(current), []);
if (tailDecorations.length === 0) {
return;
}
return this.renderTailDecorationsForNode(node, props, tailDecorations);
}

protected renderTailDecorationsForNode(node: TreeNode, props: NodeProps, tailDecorations:
(TreeDecoration.TailDecoration | TreeDecoration.TailDecorationIcon | TreeDecoration.TailDecorationIconClass)[]): React.ReactNode {
return <React.Fragment>
{this.getDecorationData(node, 'tailDecorations').filter(notEmpty).reduce((acc, current) => acc.concat(current), []).map((decoration, index) => {
{tailDecorations.map((decoration, index) => {
const { tooltip } = decoration;
const { data, fontData } = decoration as TreeDecoration.TailDecoration;
const color = (decoration as TreeDecoration.TailDecorationIcon).color;
const icon = (decoration as TreeDecoration.TailDecorationIcon).icon || (decoration as TreeDecoration.TailDecorationIconClass).iconClass;
const className = [TREE_NODE_SEGMENT_CLASS, TREE_NODE_TAIL_CLASS].join(' ');
const style = fontData ? this.applyFontStyles({}, fontData) : color ? { color } : undefined;
const icon = (decoration as TreeDecoration.TailDecorationIcon).icon || (decoration as TreeDecoration.TailDecorationIconClass).iconClass;
const content = data ? data : icon ? <span key={node.id + 'icon' + index} className={this.getIconClass(icon)}></span> : '';
return <div key={node.id + className + index} className={className} style={style} title={tooltip}>
{content}
Expand All @@ -803,7 +816,7 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
*
* @returns the icon class name.
*/
private getIconClass(iconName: string | string[], additionalClasses: string[] = []): string {
protected getIconClass(iconName: string | string[], additionalClasses: string[] = []): string {
const iconClass = (typeof iconName === 'string') ? ['a', 'fa', `fa-${iconName}`] : ['a'].concat(iconName);
return iconClass.concat(additionalClasses).join(' ');
}
Expand Down
40 changes: 37 additions & 3 deletions packages/navigator/src/browser/navigator-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import { Message } from '@theia/core/shared/@phosphor/messaging';
import URI from '@theia/core/lib/common/uri';
import { CommandService, SelectionService } from '@theia/core/lib/common';
import { CorePreferences, Key, TreeModel, SelectableTreeNode, OpenerService } from '@theia/core/lib/browser';
import { CommandService, notEmpty, SelectionService } from '@theia/core/lib/common';
import {
CorePreferences, Key, TreeModel, SelectableTreeNode,
TREE_NODE_SEGMENT_CLASS, TREE_NODE_TAIL_CLASS,
TreeDecoration, NodeProps, OpenerService
} from '@theia/core/lib/browser';
import {
ContextMenuRenderer, ExpandableTreeNode,
TreeProps, TreeNode
} from '@theia/core/lib/browser';
import { FileTreeWidget, FileNode, DirNode } from '@theia/filesystem/lib/browser';
import { FileTreeWidget, FileNode, DirNode, FileStatNode } from '@theia/filesystem/lib/browser';
import { WorkspaceService, WorkspaceCommands } from '@theia/workspace/lib/browser';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
import { WorkspaceNode, WorkspaceRootNode } from './navigator-tree';
Expand Down Expand Up @@ -152,6 +156,36 @@ export class FileNavigatorWidget extends FileTreeWidget {
return super.renderTree(model);
}

protected renderTailDecorations(node: TreeNode, props: NodeProps): React.ReactNode {
const tailDecorations = this.getDecorationData(node, 'tailDecorations').filter(notEmpty).reduce((acc, current) => acc.concat(current), []);

if (tailDecorations.length === 0) {
return;
}

// Handle rendering of directories versus file nodes.
if (FileStatNode.is(node) && node.fileStat.isDirectory) {
return this.renderTailDecorationsForDirectoryNode(node, props, tailDecorations);
} else {
return this.renderTailDecorationsForNode(node, props, tailDecorations);
}
}

protected renderTailDecorationsForDirectoryNode(node: TreeNode, props: NodeProps, tailDecorations:
(TreeDecoration.TailDecoration | TreeDecoration.TailDecorationIcon | TreeDecoration.TailDecorationIconClass)[]): React.ReactNode {
// If the node represents a directory, we just want to use the decorationData with the highest priority (last element).
const decoration = tailDecorations[tailDecorations.length - 1];
const { tooltip, fontData } = decoration as TreeDecoration.TailDecoration;
const color = (decoration as TreeDecoration.TailDecorationIcon).color;
const className = [TREE_NODE_SEGMENT_CLASS, TREE_NODE_TAIL_CLASS].join(' ');
const style = fontData ? this.applyFontStyles({}, fontData) : color ? { color } : undefined;
const content = <span className={this.getIconClass('circle', [TreeDecoration.Styles.DECORATOR_SIZE_CLASS])}></span>;

return <div className={className} style={style} title={tooltip}>
{content}
</div>;
}

protected shouldShowWelcomeView(): boolean {
return this.model.root === undefined;
}
Expand Down

0 comments on commit 2621099

Please sign in to comment.