Files
arduino-ide/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx
Akos Kitta d68bc4abdb feat: rename, deletion, and validation support
Closes #1599
Closes #1825
Closes #649
Closes #1847
Closes #1882

Co-authored-by: Akos Kitta <a.kitta@arduino.cc>
Co-authored-by: per1234 <accounts@perglass.com>

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
2023-02-15 14:09:36 +01:00

229 lines
6.7 KiB
TypeScript

import * as React from '@theia/core/shared/react';
import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/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 {
CurrentSketch,
SketchesServiceClientImpl,
} from '../../sketches-service-client-impl';
import { SelectableTreeNode } from '@theia/core/lib/browser/tree/tree-selection';
import { Sketch } from '../../contributions/contribution';
import { nls } from '@theia/core/lib/common';
const customTreeProps: TreeProps = {
leftPadding: 26,
expansionTogglePadding: 6,
};
@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) override readonly props: TreeProps,
@inject(SketchbookTreeModel) override readonly model: SketchbookTreeModel,
@inject(ContextMenuRenderer)
override 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 = nls.localize(
'arduino/sketch/titleLocalSketchbook',
'Local Sketchbook'
);
this.title.closable = false;
this.addClass('tree-container'); // Adds `height: 100%` to the tree. Otherwise you cannot see it.
}
@postConstruct()
protected override async init(): Promise<void> {
super.init();
// cache the current open sketch uri
const currentSketch = await this.sketchServiceClient.currentSketch();
this.currentSketchUri =
(CurrentSketch.isValid(currentSketch) && currentSketch.uri) || '';
}
protected override 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 override renderIcon(
node: TreeNode,
props: NodeProps
): React.ReactNode {
if (SketchbookTree.SketchDirNode.is(node) || Sketch.isSketchFile(node.id)) {
return <div className="sketch-folder-icon file-icon"></div>;
}
const icon = this.toNodeIcon(node);
if (icon) {
return <div className={icon + ' file-icon'}></div>;
}
return undefined;
}
protected override renderTailDecorations(
node: TreeNode,
props: NodeProps
): React.ReactNode {
return (
<React.Fragment>
{super.renderTailDecorations(node, props)}
{this.renderInlineCommands(node)}
</React.Fragment>
);
}
protected hoveredNodeId: string | undefined;
protected setHoverNodeId(id: string | undefined): void {
this.hoveredNodeId = id;
this.update();
}
protected override createNodeAttributes(
node: TreeNode,
props: NodeProps
): React.Attributes & React.HTMLAttributes<HTMLElement> {
return {
...super.createNodeAttributes(node, props),
draggable: false,
onMouseOver: () => this.setHoverNodeId(node.id),
onMouseOut: () => this.setHoverNodeId(undefined),
};
}
protected renderInlineCommands(node: TreeNode): React.ReactNode {
if (SketchbookTree.SketchDirNode.is(node) && node.commands) {
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',
'sketchbook-commands-icons',
].join(' ');
return (
<div
key={`${commandId}--${node.id}`}
className={className}
title={command?.label || command.id}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
this.commandRegistry.executeCommand(
commandId,
Object.assign(args, { event: event.nativeEvent })
);
}}
/>
);
}
return undefined;
}
protected override handleClickEvent(
node: TreeNode | undefined,
event: React.MouseEvent<HTMLElement>
): 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 override doToggle(event: React.MouseEvent<HTMLElement>): 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();
}
protected override getPaddingLeft(node: TreeNode, props: NodeProps): number {
return (
props.depth * customTreeProps.leftPadding +
(this.needsExpansionTogglePadding(node)
? customTreeProps.expansionTogglePadding
: 0)
);
}
}