1
- import { MenuModelRegistry } from '@theia/core/lib/common/menu ';
1
+ import { DialogError } from '@theia/core/lib/browser/dialogs ';
2
2
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
3
+ import { LabelProvider } from '@theia/core/lib/browser/label-provider';
3
4
import { CompositeTreeNode } from '@theia/core/lib/browser/tree';
4
- import { DisposableCollection } from '@theia/core/lib/common/disposable';
5
+ import { Widget } from '@theia/core/lib/browser/widgets/widget';
6
+ import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
7
+ import {
8
+ Disposable,
9
+ DisposableCollection,
10
+ } from '@theia/core/lib/common/disposable';
11
+ import { MenuModelRegistry } from '@theia/core/lib/common/menu';
12
+ import {
13
+ Progress,
14
+ ProgressUpdate,
15
+ } from '@theia/core/lib/common/message-service-protocol';
5
16
import { nls } from '@theia/core/lib/common/nls';
6
17
import { inject, injectable } from '@theia/core/shared/inversify';
18
+ import { WorkspaceInputDialogProps } from '@theia/workspace/lib/browser/workspace-input-dialog';
19
+ import { v4 } from 'uuid';
7
20
import { MainMenuManager } from '../../common/main-menu-manager';
8
21
import type { AuthenticationSession } from '../../node/auth/types';
9
22
import { AuthenticationClientService } from '../auth/authentication-client-service';
@@ -90,7 +103,7 @@ export class NewCloudSketch extends Contribution {
90
103
91
104
private async createNewSketch(
92
105
initialValue?: string | undefined
93
- ): Promise<URI | undefined > {
106
+ ): Promise<unknown > {
94
107
const widget = await this.widgetContribution.widget;
95
108
const treeModel = this.treeModelFrom(widget);
96
109
if (!treeModel) {
@@ -102,34 +115,50 @@ export class NewCloudSketch extends Contribution {
102
115
if (!rootNode) {
103
116
return undefined;
104
117
}
118
+ return this.openWizard(rootNode, treeModel, initialValue);
119
+ }
105
120
106
- const newSketchName = await this.newSketchName(rootNode, initialValue);
107
- if (!newSketchName) {
108
- return undefined;
109
- }
110
- let result: Create.Sketch | undefined | 'conflict';
111
- try {
112
- result = await this.createApi.createSketch(newSketchName);
113
- } catch (err) {
114
- if (isConflict(err)) {
115
- result = 'conflict';
116
- } else {
117
- throw err;
121
+ private withProgress(
122
+ value: string,
123
+ treeModel: CloudSketchbookTreeModel
124
+ ): (progress: Progress) => Promise<unknown> {
125
+ return async (progress: Progress) => {
126
+ let result: Create.Sketch | undefined | 'conflict';
127
+ try {
128
+ progress.report({
129
+ message: nls.localize(
130
+ 'arduino/cloudSketch/creating',
131
+ "Creating remote sketch '{0}'...",
132
+ value
133
+ ),
134
+ });
135
+ result = await this.createApi.createSketch(value);
136
+ } catch (err) {
137
+ if (isConflict(err)) {
138
+ result = 'conflict';
139
+ } else {
140
+ throw err;
141
+ }
142
+ } finally {
143
+ if (result) {
144
+ progress.report({
145
+ message: nls.localize(
146
+ 'arduino/cloudSketch/synchronizing',
147
+ "Synchronizing sketchbook, pulling '{0}'...",
148
+ value
149
+ ),
150
+ });
151
+ await treeModel.refresh();
152
+ }
153
+ }
154
+ if (result === 'conflict') {
155
+ return this.createNewSketch(value);
118
156
}
119
- } finally {
120
157
if (result) {
121
- await treeModel.refresh( );
158
+ return this.open(treeModel, result );
122
159
}
123
- }
124
-
125
- if (result === 'conflict') {
126
- return this.createNewSketch(newSketchName);
127
- }
128
-
129
- if (result) {
130
- return this.open(treeModel, result);
131
- }
132
- return undefined;
160
+ return undefined;
161
+ };
133
162
}
134
163
135
164
private async open(
@@ -183,14 +212,15 @@ export class NewCloudSketch extends Contribution {
183
212
return undefined;
184
213
}
185
214
186
- private async newSketchName (
215
+ private async openWizard (
187
216
rootNode: CompositeTreeNode,
217
+ treeModel: CloudSketchbookTreeModel,
188
218
initialValue?: string | undefined
189
- ): Promise<string | undefined > {
219
+ ): Promise<unknown > {
190
220
const existingNames = rootNode.children
191
221
.filter(CloudSketchbookTree.CloudSketchDirNode.is)
192
222
.map(({ fileStat }) => fileStat.name);
193
- return new WorkspaceInputDialog (
223
+ return new NewCloudSketchDialog (
194
224
{
195
225
title: nls.localize(
196
226
'arduino/newCloudSketch/newSketchTitle',
@@ -216,7 +246,8 @@ export class NewCloudSketch extends Contribution {
216
246
);
217
247
},
218
248
},
219
- this.labelProvider
249
+ this.labelProvider,
250
+ (value) => this.withProgress(value, treeModel)
220
251
).open();
221
252
}
222
253
}
@@ -245,3 +276,97 @@ function isErrorWithStatusOf(
245
276
}
246
277
return false;
247
278
}
279
+
280
+ @injectable()
281
+ class NewCloudSketchDialog extends WorkspaceInputDialog {
282
+ constructor(
283
+ @inject(WorkspaceInputDialogProps)
284
+ protected override readonly props: WorkspaceInputDialogProps,
285
+ @inject(LabelProvider)
286
+ protected override readonly labelProvider: LabelProvider,
287
+ private readonly withProgress: (
288
+ value: string
289
+ ) => (progress: Progress) => Promise<unknown>
290
+ ) {
291
+ super(props, labelProvider);
292
+ }
293
+ protected override async accept(): Promise<void> {
294
+ if (!this.resolve) {
295
+ return;
296
+ }
297
+ this.acceptCancellationSource.cancel();
298
+ this.acceptCancellationSource = new CancellationTokenSource();
299
+ const token = this.acceptCancellationSource.token;
300
+ const value = this.value;
301
+ const error = await this.isValid(value, 'open');
302
+ if (token.isCancellationRequested) {
303
+ return;
304
+ }
305
+ if (!DialogError.getResult(error)) {
306
+ this.setErrorMessage(error);
307
+ } else {
308
+ const spinner = document.createElement('div');
309
+ spinner.classList.add('spinner');
310
+ const disposables = new DisposableCollection();
311
+ try {
312
+ this.toggleButtons(true);
313
+ disposables.push(Disposable.create(() => this.toggleButtons(false)));
314
+
315
+ const closeParent = this.closeCrossNode.parentNode;
316
+ closeParent?.removeChild(this.closeCrossNode);
317
+ disposables.push(
318
+ Disposable.create(() => {
319
+ closeParent?.appendChild(this.closeCrossNode);
320
+ })
321
+ );
322
+
323
+ this.errorMessageNode.classList.add('progress');
324
+ disposables.push(
325
+ Disposable.create(() =>
326
+ this.errorMessageNode.classList.remove('progress')
327
+ )
328
+ );
329
+
330
+ const errorParent = this.errorMessageNode.parentNode;
331
+ errorParent?.insertBefore(spinner, this.errorMessageNode);
332
+ disposables.push(
333
+ Disposable.create(() => errorParent?.removeChild(spinner))
334
+ );
335
+
336
+ const cancellationSource = new CancellationTokenSource();
337
+ const progress: Progress = {
338
+ id: v4(),
339
+ cancel: () => cancellationSource.cancel(),
340
+ report: (update: ProgressUpdate) => {
341
+ this.setProgressMessage(update);
342
+ },
343
+ result: Promise.resolve(value),
344
+ };
345
+ await this.withProgress(value)(progress);
346
+ } finally {
347
+ disposables.dispose();
348
+ }
349
+ this.resolve(value);
350
+ Widget.detach(this);
351
+ }
352
+ }
353
+
354
+ private toggleButtons(disabled: boolean): void {
355
+ if (this.acceptButton) {
356
+ this.acceptButton.disabled = disabled;
357
+ }
358
+ if (this.closeButton) {
359
+ this.closeButton.disabled = disabled;
360
+ }
361
+ }
362
+
363
+ private setProgressMessage(update: ProgressUpdate): void {
364
+ if (update.work && update.work.done === update.work.total) {
365
+ this.errorMessageNode.innerText = '';
366
+ } else {
367
+ if (update.message) {
368
+ this.errorMessageNode.innerText = update.message;
369
+ }
370
+ }
371
+ }
372
+ }
0 commit comments