import * as React from 'react'; import { inject, injectable, postConstruct } from 'inversify'; import { TreeNode } from '@theia/core/lib/browser/tree/tree'; import { CommandRegistry } from '@theia/core/lib/common/command'; import { NodeProps, TreeProps, TREE_NODE_SEGMENT_CLASS, TREE_NODE_TAIL_CLASS, } from '@theia/core/lib/browser/tree/tree-widget'; import { EditorManager } from '@theia/editor/lib/browser/editor-manager'; import { FileTreeWidget } from '@theia/filesystem/lib/browser'; import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer'; import { SketchbookTree } from './sketchbook-tree'; import { SketchbookTreeModel } from './sketchbook-tree-model'; import { ArduinoPreferences } from '../../arduino-preferences'; import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; import { SelectableTreeNode } from '@theia/core/lib/browser/tree/tree-selection'; import { Sketch } from '../../contributions/contribution'; @injectable() export class SketchbookTreeWidget extends FileTreeWidget { @inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry; @inject(ArduinoPreferences) protected readonly arduinoPreferences: ArduinoPreferences; @inject(SketchesServiceClientImpl) protected readonly sketchServiceClient: SketchesServiceClientImpl; protected currentSketchUri = ''; constructor( @inject(TreeProps) readonly props: TreeProps, @inject(SketchbookTreeModel) readonly model: SketchbookTreeModel, @inject(ContextMenuRenderer) readonly contextMenuRenderer: ContextMenuRenderer, @inject(EditorManager) readonly editorManager: EditorManager ) { super(props, model, contextMenuRenderer); this.id = 'arduino-sketchbook-tree-widget'; this.title.iconClass = 'sketchbook-tree-icon'; this.title.caption = 'Local Sketchbook'; this.title.closable = false; } @postConstruct() protected async init(): Promise { super.init(); // cache the current open sketch uri const currentSketch = await this.sketchServiceClient.currentSketch(); this.currentSketchUri = (currentSketch && currentSketch.uri) || ''; } protected createNodeClassNames(node: TreeNode, props: NodeProps): string[] { const classNames = super.createNodeClassNames(node, props); if ( SketchbookTree.SketchDirNode.is(node) && this.currentSketchUri === node?.uri.toString() ) { classNames.push('active-sketch'); } return classNames; } protected renderIcon(node: TreeNode, props: NodeProps): React.ReactNode { if (SketchbookTree.SketchDirNode.is(node) || Sketch.isSketchFile(node.id)) { return
; } const icon = this.toNodeIcon(node); if (icon) { return
; } return undefined; } protected renderTailDecorations( node: TreeNode, props: NodeProps ): React.ReactNode { return ( {super.renderTailDecorations(node, props)} {this.renderInlineCommands(node, props)} ); } protected hoveredNodeId: string | undefined; protected setHoverNodeId(id: string | undefined): void { this.hoveredNodeId = id; this.update(); } protected createNodeAttributes( node: TreeNode, props: NodeProps ): React.Attributes & React.HTMLAttributes { return { ...super.createNodeAttributes(node, props), draggable: false, onMouseOver: () => this.setHoverNodeId(node.id), onMouseOut: () => this.setHoverNodeId(undefined), }; } protected renderInlineCommands( node: TreeNode, props: NodeProps ): React.ReactNode { if ( SketchbookTree.SketchDirNode.is(node) && ((node.commands && node.id === this.hoveredNodeId) || this.currentSketchUri === node?.uri.toString()) ) { return Array.from(new Set(node.commands)).map((command) => this.renderInlineCommand(command.id, node) ); } return undefined; } protected renderInlineCommand( commandId: string, node: SketchbookTree.SketchDirNode, options?: any ): React.ReactNode { const command = this.commandRegistry.getCommand(commandId); const icon = command?.iconClass; const args = { model: this.model, node: node, ...options }; if ( command && icon && this.commandRegistry.isEnabled(commandId, args) && this.commandRegistry.isVisible(commandId, args) ) { const className = [ TREE_NODE_SEGMENT_CLASS, TREE_NODE_TAIL_CLASS, icon, 'theia-tree-view-inline-action', ].join(' '); return (
{ event.preventDefault(); event.stopPropagation(); this.commandRegistry.executeCommand( commandId, Object.assign(args, { event: event.nativeEvent }) ); }} /> ); } return undefined; } protected handleClickEvent( node: TreeNode | undefined, event: React.MouseEvent ): void { if (node) { if (!!this.props.multiSelect) { const shiftMask = this.hasShiftMask(event); const ctrlCmdMask = this.hasCtrlCmdMask(event); if (SelectableTreeNode.is(node)) { if (shiftMask) { this.model.selectRange(node); } else if (ctrlCmdMask) { this.model.toggleNode(node); } else { this.model.selectNode(node); } } } else { if (SelectableTreeNode.is(node)) { this.model.selectNode(node); } } event.stopPropagation(); } } protected doToggle(event: React.MouseEvent): void { const nodeId = event.currentTarget.getAttribute('data-node-id'); if (nodeId) { const node = this.model.getNode(nodeId); if (node && this.isExpandable(node)) { this.model.toggleNodeExpansion(node); } } event.stopPropagation(); } }