Skip to content

Commit e56f5cd

Browse files
committed
Sketchbook explorer
1 parent 0c5c736 commit e56f5cd

File tree

10 files changed

+280
-41
lines changed

10 files changed

+280
-41
lines changed

arduino-ide-extension/src/browser/data/arduino.color-theme.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@
8585
],
8686
"colors": {
8787
"list.highlightForeground": "#005c5f",
88-
"list.activeSelectionBackground": "#005c5f",
88+
"list.activeSelectionForeground": "#424242",
89+
"list.activeSelectionBackground": "#DAE3E3",
90+
"list.inactiveSelectionForeground": "#424242",
91+
"list.inactiveSelectionBackground": "#DAE3E3",
92+
"list.hoverBackground": "#ECF1F1",
8993
"progressBar.background": "#005c5f",
9094
"editor.background": "#ffffff",
9195
"editorCursor.foreground": "#434f54",

arduino-ide-extension/src/browser/settings.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface Settings extends Index {
3434
verboseOnUpload: boolean; // `arduino.upload.verbose`
3535
verifyAfterUpload: boolean; // `arduino.upload.verify`
3636
enableLsLogs: boolean; // `arduino.language.log`
37+
sketchbookShowAllFiles: boolean; // `arduino.sketchbook.showAllFiles`
3738

3839
sketchbookPath: string; // CLI
3940
additionalUrls: string[]; // CLI
@@ -94,7 +95,8 @@ export class SettingsService {
9495
verboseOnUpload,
9596
verifyAfterUpload,
9697
enableLsLogs,
97-
cliConfig
98+
sketchbookShowAllFiles,
99+
cliConfig,
98100
] = await Promise.all([
99101
this.preferenceService.get<number>('editor.fontSize', 12),
100102
this.preferenceService.get<string>('workbench.colorTheme', 'arduino-theme'),
@@ -112,6 +114,7 @@ export class SettingsService {
112114
this.preferenceService.get<boolean>('arduino.upload.verbose', true),
113115
this.preferenceService.get<boolean>('arduino.upload.verify', true),
114116
this.preferenceService.get<boolean>('arduino.language.log', true),
117+
this.preferenceService.get<boolean>('arduino.sketchbook.showAllFiles', false),
115118
this.configService.getConfiguration()
116119
]);
117120
const { additionalUrls, sketchDirUri, network } = cliConfig;
@@ -129,6 +132,7 @@ export class SettingsService {
129132
verboseOnUpload,
130133
verifyAfterUpload,
131134
enableLsLogs,
135+
sketchbookShowAllFiles,
132136
additionalUrls,
133137
sketchbookPath,
134138
network
@@ -194,7 +198,8 @@ export class SettingsService {
194198
enableLsLogs,
195199
sketchbookPath,
196200
additionalUrls,
197-
network
201+
network,
202+
sketchbookShowAllFiles
198203
} = this._settings;
199204
const [config, sketchDirUri] = await Promise.all([
200205
this.configService.getConfiguration(),
@@ -217,6 +222,7 @@ export class SettingsService {
217222
this.preferenceService.set('arduino.upload.verbose', verboseOnUpload, PreferenceScope.User),
218223
this.preferenceService.set('arduino.upload.verify', verifyAfterUpload, PreferenceScope.User),
219224
this.preferenceService.set('arduino.language.log', enableLsLogs, PreferenceScope.User),
225+
this.preferenceService.set('arduino.sketchbook.showAllFiles', sketchbookShowAllFiles, PreferenceScope.User),
220226
this.configService.setConfiguration(config)
221227
]);
222228
this.onDidChangeEmitter.fire(this._settings);
@@ -370,6 +376,13 @@ export class SettingsComponent extends React.Component<SettingsComponent.Props,
370376
onChange={this.autoSaveDidChange} />
371377
Auto save
372378
</label>
379+
<label className='flex-line'>
380+
<input
381+
type='checkbox'
382+
checked={this.state.sketchbookShowAllFiles === true}
383+
onChange={this.sketchbookShowAllFilesDidChange} />
384+
Sketchbook show files
385+
</label>
373386
<label className='flex-line'>
374387
<input
375388
type='checkbox'
@@ -564,6 +577,10 @@ export class SettingsComponent extends React.Component<SettingsComponent.Props,
564577
this.setState({ checkForUpdates: event.target.checked });
565578
};
566579

580+
protected sketchbookShowAllFilesDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
581+
this.setState({ sketchbookShowAllFiles: event.target.checked });
582+
};
583+
567584
protected autoSaveDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
568585
this.setState({ autoSave: event.target.checked ? 'on' : 'off' });
569586
};
Lines changed: 10 additions & 0 deletions
Loading

arduino-ide-extension/src/browser/style/sketchbook.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,13 @@
1818
.sketchbook-trees-container {
1919
height: 99%;
2020
}
21+
22+
.sketchbook-tree__opts {
23+
background: url('./sketchbook-opts-icon.svg') center center no-repeat;
24+
width: var(--theia-icon-size);
25+
height: var(--theia-icon-size);
26+
}
27+
28+
#arduino-sketchbook-tree-widget .theia-TreeNodeSegmentGrow {
29+
flex: 1;
30+
}

arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution
2121
CommonCommands.SELECT_ICON_THEME,
2222
CommonCommands.SELECT_COLOR_THEME,
2323
CommonCommands.ABOUT_COMMAND,
24+
CommonCommands.CLOSE_TAB,
25+
CommonCommands.CLOSE_OTHER_TABS,
26+
CommonCommands.CLOSE_ALL_TABS,
27+
CommonCommands.COLLAPSE_PANEL,
2428
CommonCommands.SAVE_WITHOUT_FORMATTING // Patched for https://github.com/eclipse-theia/theia/pull/8877
2529
]) {
2630
registry.unregisterMenuAction(command);

arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-commands.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,32 @@ import { Command } from '@theia/core/lib/common/command';
22

33
export namespace SketchbookCommands {
44

5-
export const OPEN: Command = {
6-
id: 'arduino-sketchbook--open-sketch',
7-
label: 'Open Sketch',
8-
iconClass: 'fa fa-play-circle'
9-
};
10-
115
export const OPEN_NEW_WINDOW: Command = {
126
id: 'arduino-sketchbook--open-sketch-new-window',
137
label: 'Open Sketch in New Window',
14-
iconClass: 'fa fa-check'
158
};
169

10+
export const REVEAL_IN_FINDER: Command = {
11+
id: 'arduino-sketchbook--reveal-in-finder',
12+
label: 'Open Folder',
13+
};
14+
15+
export const OPEN_SKETCHBOOK_CONTEXT_MENU: Command = {
16+
id: 'arduino-sketchbook--open-sketch-context-menu',
17+
label: 'Contextual menu',
18+
iconClass: 'sketchbook-tree__opts'
19+
};
20+
21+
export const SKETCHBOOK_HIDE_FILES: Command = {
22+
id: 'arduino-sketchbook--hide-files',
23+
label: 'Contextual menu',
24+
};
25+
26+
export const SKETCHBOOK_SHOW_FILES: Command = {
27+
id: 'arduino-sketchbook--show-files',
28+
label: 'Contextual menu',
29+
};
30+
31+
32+
1733
}

arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-model.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { inject, injectable } from 'inversify';
22
import URI from '@theia/core/lib/common/uri';
3-
import { FileTreeModel } from '@theia/filesystem/lib/browser';
3+
import { FileNode, FileTreeModel } from '@theia/filesystem/lib/browser';
44
import { FileService } from '@theia/filesystem/lib/browser/file-service';
55
import { ConfigService } from '../../../common/protocol';
66
import { SketchbookTree } from './sketchbook-tree';
77
import { ArduinoPreferences } from '../../arduino-preferences';
8+
import { SelectableTreeNode, TreeNode } from '@theia/core/lib/browser/tree';
9+
import { SketchbookCommands } from './sketchbook-commands';
10+
import { OpenerService, open } from '@theia/core/lib/browser';
11+
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
12+
import { CommandRegistry } from '@theia/core/lib/common/command';
813

914
@injectable()
1015
export class SketchbookTreeModel extends FileTreeModel {
@@ -15,14 +20,82 @@ export class SketchbookTreeModel extends FileTreeModel {
1520
@inject(ArduinoPreferences)
1621
protected readonly arduinoPreferences: ArduinoPreferences;
1722

23+
@inject(CommandRegistry)
24+
protected readonly commandRegistry: CommandRegistry;
25+
1826
@inject(ConfigService)
1927
protected readonly configService: ConfigService;
2028

29+
@inject(OpenerService)
30+
protected readonly openerService: OpenerService;
31+
32+
@inject(SketchesServiceClientImpl)
33+
protected readonly sketchServiceClient: SketchesServiceClientImpl;
34+
2135
async updateRoot(): Promise<void> {
2236
const config = await this.configService.getConfiguration();
2337
const fileStat = await this.fileService.resolve(new URI(config.sketchDirUri));
2438
const showAllFiles = this.arduinoPreferences['arduino.sketchbook.showAllFiles'];
2539
this.tree.root = SketchbookTree.RootNode.create(fileStat, showAllFiles);
2640
}
2741

42+
// selectNode gets called when the user single-clicks on an item
43+
// when this happens, we want to open the file if it belongs to the currently open sketch
44+
async selectNode(node: Readonly<SelectableTreeNode>): Promise<void> {
45+
46+
super.selectNode(node);
47+
if (FileNode.is(node)) {
48+
open(this.openerService, node.uri);
49+
}
50+
51+
}
52+
53+
protected async doOpenNode(node: TreeNode): Promise<void> {
54+
// if it's a sketch dir, or a file from another sketch, open in new window
55+
if (!(await this.isFileInsideCurrentSketch(node))) {
56+
const sketchRoot = this.recursivelyFindSketchRoot(node);
57+
if (sketchRoot) {
58+
this.commandRegistry.executeCommand(SketchbookCommands.OPEN_NEW_WINDOW.id, { node: sketchRoot })
59+
}
60+
return;
61+
}
62+
63+
if (node.visible === false) {
64+
return;
65+
} else if (FileNode.is(node)) {
66+
open(this.openerService, node.uri);
67+
} else {
68+
super.doOpenNode(node);
69+
}
70+
}
71+
72+
private async isFileInsideCurrentSketch(node: TreeNode): Promise<boolean> {
73+
74+
// it's a directory, not a file
75+
if (!FileNode.is(node)) {
76+
return false;
77+
}
78+
79+
// check if the node is a file that belongs to another sketch
80+
const sketch = await this.sketchServiceClient.currentSketch();
81+
if (sketch && node.uri.toString().indexOf(sketch.uri) !== 0) {
82+
return false;
83+
}
84+
return true;
85+
}
86+
87+
private recursivelyFindSketchRoot(node: TreeNode): TreeNode | false {
88+
89+
if (node && SketchbookTree.SketchDirNode.is(node)) {
90+
return node;
91+
}
92+
93+
if (node && node.parent) {
94+
return this.recursivelyFindSketchRoot(node.parent);
95+
}
96+
97+
// can't find a root, return false
98+
return false;
99+
}
100+
28101
}

arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-render
99
import { SketchbookTree } from './sketchbook-tree';
1010
import { SketchbookTreeModel } from './sketchbook-tree-model';
1111
import { ArduinoPreferences } from '../../arduino-preferences';
12+
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
13+
import { SelectableTreeNode } from '@theia/core/lib/browser/tree/tree-selection';
1214

1315
@injectable()
1416
export class SketchbookTreeWidget extends FileTreeWidget {
@@ -19,6 +21,11 @@ export class SketchbookTreeWidget extends FileTreeWidget {
1921
@inject(ArduinoPreferences)
2022
protected readonly arduinoPreferences: ArduinoPreferences;
2123

24+
@inject(SketchesServiceClientImpl)
25+
protected readonly sketchServiceClient: SketchesServiceClientImpl;
26+
27+
private currentSketchUri = '';
28+
2229
constructor(
2330
@inject(TreeProps) readonly props: TreeProps,
2431
@inject(SketchbookTreeModel) readonly model: SketchbookTreeModel,
@@ -41,6 +48,9 @@ export class SketchbookTreeWidget extends FileTreeWidget {
4148
}
4249
}));
4350
this.updateModel();
51+
// cache the current open sketch uri
52+
const currentSketch = await this.sketchServiceClient.currentSketch();
53+
this.currentSketchUri = currentSketch && currentSketch.uri || '';
4454
}
4555

4656
async updateModel(): Promise<void> {
@@ -80,7 +90,7 @@ export class SketchbookTreeWidget extends FileTreeWidget {
8090
}
8191

8292
protected renderInlineCommands(node: TreeNode, props: NodeProps): React.ReactNode {
83-
if (SketchbookTree.SketchDirNode.is(node) && node.commands && node.id === this.hoveredNodeId) {
93+
if (SketchbookTree.SketchDirNode.is(node) && (node.commands && node.id === this.hoveredNodeId || this.currentSketchUri === node?.uri.toString())) {
8494
return Array.from(new Set(node.commands)).map(command => this.renderInlineCommand(command.id, node));
8595
}
8696
return undefined;
@@ -99,11 +109,48 @@ export class SketchbookTreeWidget extends FileTreeWidget {
99109
onClick={event => {
100110
event.preventDefault();
101111
event.stopPropagation();
102-
this.commandRegistry.executeCommand(commandId, Object.assign(args, { event: event.nativeEvent }))
112+
this.commandRegistry.executeCommand(commandId, Object.assign(args, { event: event.nativeEvent }));
103113
}}
104114
/>;
105115
}
106116
return undefined;
107117
}
108118

119+
protected handleClickEvent(node: TreeNode | undefined, event: React.MouseEvent<HTMLElement>): void {
120+
121+
if (node) {
122+
if (!!this.props.multiSelect) {
123+
const shiftMask = this.hasShiftMask(event);
124+
const ctrlCmdMask = this.hasCtrlCmdMask(event);
125+
if (SelectableTreeNode.is(node)) {
126+
if (shiftMask) {
127+
this.model.selectRange(node);
128+
} else if (ctrlCmdMask) {
129+
this.model.toggleNode(node);
130+
} else {
131+
this.model.selectNode(node);
132+
}
133+
}
134+
} else {
135+
if (SelectableTreeNode.is(node)) {
136+
this.model.selectNode(node);
137+
}
138+
}
139+
event.stopPropagation();
140+
}
141+
142+
}
143+
144+
protected doToggle(event: React.MouseEvent<HTMLElement>): void {
145+
146+
const nodeId = event.currentTarget.getAttribute('data-node-id');
147+
if (nodeId) {
148+
const node = this.model.getNode(nodeId);
149+
if (node && this.isExpandable(node)) {
150+
this.model.toggleNodeExpansion(node);
151+
}
152+
}
153+
event.stopPropagation();
154+
}
155+
109156
}

arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ export class SketchbookTree extends FileTree {
2727
if (!SketchbookTree.RootNode.is(root)) {
2828
return [];
2929
}
30-
const children = await Promise.all((await super.resolveChildren(parent)).map(node => this.maybeDecorateNode(node, root.showAllFiles)));
30+
const children = (await Promise.all((await super.resolveChildren(parent)).map(node => this.maybeDecorateNode(node, root.showAllFiles)))).filter(node => {
31+
// filter out hidden nodes
32+
if (DirNode.is(node) || FileStatNode.is(node)) {
33+
return node.fileStat.name.indexOf('.') !== 0
34+
}
35+
return true;
36+
});
3137
if (SketchbookTree.RootNode.is(parent)) {
3238
return children.filter(DirNode.is).filter(node => ['libraries', 'hardware'].indexOf(this.labelProvider.getName(node)) === -1);
3339
}
@@ -41,7 +47,7 @@ export class SketchbookTree extends FileTree {
4147
if (DirNode.is(node)) {
4248
const sketch = await this.sketchesService.maybeLoadSketch(node.uri.toString());
4349
if (sketch) {
44-
Object.assign(node, { type: 'sketch', commands: [SketchbookCommands.OPEN, SketchbookCommands.OPEN_NEW_WINDOW] });
50+
Object.assign(node, { type: 'sketch', commands: [SketchbookCommands.OPEN_SKETCHBOOK_CONTEXT_MENU] });
4551
if (!showAllFiles) {
4652
delete (node as any).expanded;
4753
}

0 commit comments

Comments
 (0)