1- import { MenuModelRegistry } from '@theia/core/lib/common/menu ' ;
1+ import { DialogError } from '@theia/core/lib/browser/dialogs ' ;
22import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding' ;
3+ import { LabelProvider } from '@theia/core/lib/browser/label-provider' ;
34import { 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' ;
516import { nls } from '@theia/core/lib/common/nls' ;
617import { inject , injectable } from '@theia/core/shared/inversify' ;
18+ import { WorkspaceInputDialogProps } from '@theia/workspace/lib/browser/workspace-input-dialog' ;
19+ import { v4 } from 'uuid' ;
720import { MainMenuManager } from '../../common/main-menu-manager' ;
821import type { AuthenticationSession } from '../../node/auth/types' ;
922import { AuthenticationClientService } from '../auth/authentication-client-service' ;
@@ -90,7 +103,7 @@ export class NewCloudSketch extends Contribution {
90103
91104 private async createNewSketch (
92105 initialValue ?: string | undefined
93- ) : Promise < URI | undefined > {
106+ ) : Promise < unknown > {
94107 const widget = await this . widgetContribution . widget ;
95108 const treeModel = this . treeModelFrom ( widget ) ;
96109 if ( ! treeModel ) {
@@ -102,34 +115,50 @@ export class NewCloudSketch extends Contribution {
102115 if ( ! rootNode ) {
103116 return undefined ;
104117 }
118+ return this . openWizard ( rootNode , treeModel , initialValue ) ;
119+ }
105120
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 ) ;
118156 }
119- } finally {
120157 if ( result ) {
121- await treeModel . refresh ( ) ;
158+ return this . open ( treeModel , result ) ;
122159 }
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+ } ;
133162 }
134163
135164 private async open (
@@ -183,14 +212,15 @@ export class NewCloudSketch extends Contribution {
183212 return undefined ;
184213 }
185214
186- private async newSketchName (
215+ private async openWizard (
187216 rootNode : CompositeTreeNode ,
217+ treeModel : CloudSketchbookTreeModel ,
188218 initialValue ?: string | undefined
189- ) : Promise < string | undefined > {
219+ ) : Promise < unknown > {
190220 const existingNames = rootNode . children
191221 . filter ( CloudSketchbookTree . CloudSketchDirNode . is )
192222 . map ( ( { fileStat } ) => fileStat . name ) ;
193- return new WorkspaceInputDialog (
223+ return new NewCloudSketchDialog (
194224 {
195225 title : nls . localize (
196226 'arduino/newCloudSketch/newSketchTitle' ,
@@ -216,7 +246,8 @@ export class NewCloudSketch extends Contribution {
216246 ) ;
217247 } ,
218248 } ,
219- this . labelProvider
249+ this . labelProvider ,
250+ ( value ) => this . withProgress ( value , treeModel )
220251 ) . open ( ) ;
221252 }
222253}
@@ -245,3 +276,97 @@ function isErrorWithStatusOf(
245276 }
246277 return false ;
247278}
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