Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4da5d57

Browse files
authored
[atl-1433][atl-1433] improve local sketchbook explorer (#446)
1 parent 4e6f9ae commit 4da5d57

File tree

6 files changed

+287
-43
lines changed

6 files changed

+287
-43
lines changed

‎arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ export class FilterableListContainer<
9494
}
9595

9696
protected sort(items: T[]): T[] {
97-
// debugger;
9897
const { itemLabel, itemDeprecated } = this.props;
9998
return items.sort((left, right) => {
10099
// always put deprecated items at the bottom of the list

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

Lines changed: 226 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
1-
import { inject, injectable } from 'inversify';
1+
import { inject, injectable,postConstruct } from 'inversify';
22
import URI from '@theia/core/lib/common/uri';
33
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';
8+
import {
9+
CompositeTreeNode,
10+
ExpandableTreeNode,
11+
SelectableTreeNode,
12+
TreeNode,
13+
} from '@theia/core/lib/browser/tree';
914
import { SketchbookCommands } from './sketchbook-commands';
1015
import { OpenerService, open } from '@theia/core/lib/browser';
1116
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
1217
import { CommandRegistry } from '@theia/core/lib/common/command';
18+
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
19+
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
20+
import { ProgressService } from '@theia/core/lib/common/progress-service';
21+
import {
22+
WorkspaceNode,
23+
WorkspaceRootNode,
24+
} from '@theia/navigator/lib/browser/navigator-tree';
25+
import { Deferred } from '@theia/core/lib/common/promise-util';
26+
import { Disposable } from '@theia/core/lib/common/disposable';
1327

1428
@injectable()
1529
export class SketchbookTreeModel extends FileTreeModel {
@@ -31,14 +45,217 @@ export class SketchbookTreeModel extends FileTreeModel {
3145
@inject(SketchesServiceClientImpl)
3246
protected readonly sketchServiceClient: SketchesServiceClientImpl;
3347

34-
async updateRoot(): Promise<void> {
35-
const config = await this.configService.getConfiguration();
36-
const fileStat = await this.fileService.resolve(
37-
new URI(config.sketchDirUri)
48+
@inject(SketchbookTree) protected readonly tree: SketchbookTree;
49+
@inject(WorkspaceService)
50+
protected readonly workspaceService: WorkspaceService;
51+
@inject(FrontendApplicationStateService)
52+
protected readonly applicationState: FrontendApplicationStateService;
53+
54+
@inject(ProgressService)
55+
protected readonly progressService: ProgressService;
56+
57+
@postConstruct()
58+
protected init(): void {
59+
super.init();
60+
this.reportBusyProgress();
61+
this.initializeRoot();
62+
}
63+
64+
protected readonly pendingBusyProgress = new Map<string, Deferred<void>>();
65+
protected reportBusyProgress(): void {
66+
this.toDispose.push(
67+
this.onDidChangeBusy((node) => {
68+
const pending = this.pendingBusyProgress.get(node.id);
69+
if (pending) {
70+
if (!node.busy) {
71+
pending.resolve();
72+
this.pendingBusyProgress.delete(node.id);
73+
}
74+
return;
75+
}
76+
if (node.busy) {
77+
const progress = new Deferred<void>();
78+
this.pendingBusyProgress.set(node.id, progress);
79+
this.progressService.withProgress(
80+
'',
81+
'explorer',
82+
() => progress.promise
83+
);
84+
}
85+
})
86+
);
87+
this.toDispose.push(
88+
Disposable.create(() => {
89+
for (const pending of this.pendingBusyProgress.values()) {
90+
pending.resolve();
91+
}
92+
this.pendingBusyProgress.clear();
93+
})
94+
);
95+
}
96+
97+
protected async initializeRoot(): Promise<void> {
98+
await Promise.all([
99+
this.applicationState.reachedState('initialized_layout'),
100+
this.workspaceService.roots,
101+
]);
102+
await this.updateRoot();
103+
if (this.toDispose.disposed) {
104+
return;
105+
}
106+
this.toDispose.push(
107+
this.workspaceService.onWorkspaceChanged(() => this.updateRoot())
38108
);
39-
const showAllFiles =
40-
this.arduinoPreferences['arduino.sketchbook.showAllFiles'];
41-
this.tree.root = SketchbookTree.RootNode.create(fileStat, showAllFiles);
109+
this.toDispose.push(
110+
this.workspaceService.onWorkspaceLocationChanged(() => this.updateRoot())
111+
);
112+
this.toDispose.push(
113+
this.arduinoPreferences.onPreferenceChanged(({ preferenceName }) => {
114+
if (preferenceName === 'arduino.sketchbook.showAllFiles') {
115+
this.updateRoot();
116+
}
117+
})
118+
);
119+
120+
if (this.selectedNodes.length) {
121+
return;
122+
}
123+
const root = this.root;
124+
if (CompositeTreeNode.is(root) && root.children.length === 1) {
125+
const child = root.children[0];
126+
if (
127+
SelectableTreeNode.is(child) &&
128+
!child.selected &&
129+
ExpandableTreeNode.is(child)
130+
) {
131+
this.selectNode(child);
132+
this.expandNode(child);
133+
}
134+
}
135+
}
136+
137+
previewNode(node: TreeNode): void {
138+
if (FileNode.is(node)) {
139+
open(this.openerService, node.uri, {
140+
mode: 'reveal',
141+
preview: true,
142+
});
143+
}
144+
}
145+
146+
*getNodesByUri(uri: URI): IterableIterator<TreeNode> {
147+
const workspace = this.root;
148+
if (WorkspaceNode.is(workspace)) {
149+
for (const root of workspace.children) {
150+
const id = this.tree.createId(root, uri);
151+
const node = this.getNode(id);
152+
if (node) {
153+
yield node;
154+
}
155+
}
156+
}
157+
}
158+
159+
public async updateRoot(): Promise<void> {
160+
this.root = await this.createRoot();
161+
}
162+
163+
protected async createRoot(): Promise<TreeNode | undefined> {
164+
const config = await this.configService.getConfiguration();
165+
const stat = await this.fileService.resolve(new URI(config.sketchDirUri));
166+
167+
if (this.workspaceService.opened) {
168+
const isMulti = stat ? !stat.isDirectory : false;
169+
const workspaceNode = isMulti
170+
? this.createMultipleRootNode()
171+
: WorkspaceNode.createRoot();
172+
workspaceNode.children.push(
173+
await this.tree.createWorkspaceRoot(stat, workspaceNode)
174+
);
175+
176+
return workspaceNode;
177+
}
178+
}
179+
180+
/**
181+
* Create multiple root node used to display
182+
* the multiple root workspace name.
183+
*
184+
* @returns `WorkspaceNode`
185+
*/
186+
protected createMultipleRootNode(): WorkspaceNode {
187+
const workspace = this.workspaceService.workspace;
188+
let name = workspace ? workspace.resource.path.name : 'untitled';
189+
name += ' (Workspace)';
190+
return WorkspaceNode.createRoot(name);
191+
}
192+
193+
/**
194+
* Move the given source file or directory to the given target directory.
195+
*/
196+
async move(source: TreeNode, target: TreeNode): Promise<URI | undefined> {
197+
if (source.parent && WorkspaceRootNode.is(source)) {
198+
// do not support moving a root folder
199+
return undefined;
200+
}
201+
return super.move(source, target);
202+
}
203+
204+
/**
205+
* Reveals node in the navigator by given file uri.
206+
*
207+
* @param uri uri to file which should be revealed in the navigator
208+
* @returns file tree node if the file with given uri was revealed, undefined otherwise
209+
*/
210+
async revealFile(uri: URI): Promise<TreeNode | undefined> {
211+
if (!uri.path.isAbsolute) {
212+
return undefined;
213+
}
214+
let node = this.getNodeClosestToRootByUri(uri);
215+
216+
// success stop condition
217+
// we have to reach workspace root because expanded node could be inside collapsed one
218+
if (WorkspaceRootNode.is(node)) {
219+
if (ExpandableTreeNode.is(node)) {
220+
if (!node.expanded) {
221+
node = await this.expandNode(node);
222+
}
223+
return node;
224+
}
225+
// shouldn't happen, root node is always directory, i.e. expandable
226+
return undefined;
227+
}
228+
229+
// fail stop condition
230+
if (uri.path.isRoot) {
231+
// file system root is reached but workspace root wasn't found, it means that
232+
// given uri is not in workspace root folder or points to not existing file.
233+
return undefined;
234+
}
235+
236+
if (await this.revealFile(uri.parent)) {
237+
if (node === undefined) {
238+
// get node if it wasn't mounted into navigator tree before expansion
239+
node = this.getNodeClosestToRootByUri(uri);
240+
}
241+
if (ExpandableTreeNode.is(node) && !node.expanded) {
242+
node = await this.expandNode(node);
243+
}
244+
return node;
245+
}
246+
return undefined;
247+
}
248+
249+
protected getNodeClosestToRootByUri(uri: URI): TreeNode | undefined {
250+
const nodes = [...this.getNodesByUri(uri)];
251+
return nodes.length > 0
252+
? nodes.reduce(
253+
(
254+
node1,
255+
node2 // return the node closest to the workspace root
256+
) => (node1.id.length >= node2.id.length ? node1 : node2)
257+
)
258+
: undefined;
42259
}
43260

44261
// selectNode gets called when the user single-clicks on an item

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,11 @@ export class SketchbookTreeWidget extends FileTreeWidget {
4848
@postConstruct()
4949
protected async init(): Promise<void> {
5050
super.init();
51-
this.toDispose.push(
52-
this.arduinoPreferences.onPreferenceChanged(({ preferenceName }) => {
53-
if (preferenceName === 'arduino.sketchbook.showAllFiles') {
54-
this.updateModel();
55-
}
56-
})
57-
);
58-
this.updateModel();
5951
// cache the current open sketch uri
6052
const currentSketch = await this.sketchServiceClient.currentSketch();
6153
this.currentSketchUri = (currentSketch && currentSketch.uri) || '';
6254
}
6355

64-
async updateModel(): Promise<void> {
65-
return this.model.updateRoot();
66-
}
67-
6856
protected createNodeClassNames(node: TreeNode, props: NodeProps): string[] {
6957
const classNames = super.createNodeClassNames(node, props);
7058

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

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,37 @@
11
import { inject, injectable } from 'inversify';
22
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
33
import { Command } from '@theia/core/lib/common/command';
4-
import { TreeNode, CompositeTreeNode } from '@theia/core/lib/browser/tree';
5-
import {
6-
DirNode,
7-
FileStatNode,
8-
FileTree,
9-
} from '@theia/filesystem/lib/browser/file-tree';
4+
import { CompositeTreeNode, TreeNode } from '@theia/core/lib/browser/tree';
5+
import { DirNode, FileStatNode } from '@theia/filesystem/lib/browser/file-tree';
106
import { SketchesService } from '../../../common/protocol';
117
import { FileStat } from '@theia/filesystem/lib/common/files';
128
import { SketchbookCommands } from './sketchbook-commands';
9+
import {
10+
FileNavigatorTree,
11+
WorkspaceNode,
12+
} from '@theia/navigator/lib/browser/navigator-tree';
13+
import { ArduinoPreferences } from '../../arduino-preferences';
1314

1415
@injectable()
15-
export class SketchbookTree extends FileTree {
16+
export class SketchbookTree extends FileNavigatorTree {
1617
@inject(LabelProvider)
1718
protected readonly labelProvider: LabelProvider;
1819

1920
@inject(SketchesService)
2021
protected readonly sketchesService: SketchesService;
2122

23+
@inject(ArduinoPreferences)
24+
protected readonly arduinoPreferences: ArduinoPreferences;
25+
2226
async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
23-
if (!FileStatNode.is(parent)) {
24-
return super.resolveChildren(parent);
25-
}
26-
const { root } = this;
27-
if (!root) {
28-
return [];
29-
}
30-
if (!SketchbookTree.RootNode.is(root)) {
31-
return [];
32-
}
27+
const showAllFiles =
28+
this.arduinoPreferences['arduino.sketchbook.showAllFiles'];
29+
3330
const children = (
3431
await Promise.all(
3532
(
3633
await super.resolveChildren(parent)
37-
).map((node) => this.maybeDecorateNode(node, root.showAllFiles))
34+
).map((node) => this.maybeDecorateNode(node, showAllFiles))
3835
)
3936
).filter((node) => {
4037
// filter out hidden nodes
@@ -43,7 +40,9 @@ export class SketchbookTree extends FileTree {
4340
}
4441
return true;
4542
});
46-
if (SketchbookTree.RootNode.is(parent)) {
43+
44+
// filter out hardware and libraries
45+
if (WorkspaceNode.is(parent.parent)) {
4746
return children
4847
.filter(DirNode.is)
4948
.filter(
@@ -53,10 +52,14 @@ export class SketchbookTree extends FileTree {
5352
) === -1
5453
);
5554
}
56-
if (SketchbookTree.SketchDirNode.is(parent)) {
57-
return children.filter(FileStatNode.is);
55+
56+
// return the Arduino directory containing all user sketches
57+
if (WorkspaceNode.is(parent)) {
58+
return children;
5859
}
60+
5961
return children;
62+
// return this.filter.filter(super.resolveChildren(parent));
6063
}
6164

6265
protected async maybeDecorateNode(
@@ -74,6 +77,9 @@ export class SketchbookTree extends FileTree {
7477
});
7578
if (!showAllFiles) {
7679
delete (node as any).expanded;
80+
node.children = [];
81+
} else {
82+
node.expanded = false;
7783
}
7884
return node;
7985
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /