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

Tree : Use of dot decorations on folders (colored according to the priority) #9473

Merged
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
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);
}

paul-marechal marked this conversation as resolved.
Show resolved Hide resolved
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