Skip to content

Commit

Permalink
tree: use dot decoration on folders
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 committed Aug 26, 2021
1 parent 47dfd87 commit b9d18a8
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 b9d18a8

Please sign in to comment.