Skip to content

Commit 7b9471d

Browse files
author
Akos Kitta
committed
fix: generalized dialog with progress
Signed-off-by: Akos Kitta <[email protected]>
1 parent 0e39f9e commit 7b9471d

File tree

3 files changed

+145
-144
lines changed

3 files changed

+145
-144
lines changed

arduino-ide-extension/src/browser/contributions/delete-sketch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { Widget } from '@phosphor/widgets';
21
import * as remote from '@theia/core/electron-shared/@electron/remote';
32
import { Dialog } from '@theia/core/lib/browser/dialogs';
43
import { NavigatableWidget } from '@theia/core/lib/browser/navigatable-types';
@@ -7,6 +6,7 @@ import { WindowService } from '@theia/core/lib/browser/window/window-service';
76
import { nls } from '@theia/core/lib/common/nls';
87
import type { MaybeArray } from '@theia/core/lib/common/types';
98
import URI from '@theia/core/lib/common/uri';
9+
import type { Widget } from '@theia/core/shared/@phosphor/widgets';
1010
import { inject, injectable } from '@theia/core/shared/inversify';
1111
import { SketchesError } from '../../common/protocol';
1212
import { Sketch } from '../contributions/contribution';
Lines changed: 36 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,10 @@
1-
import { DialogError } from '@theia/core/lib/browser/dialogs';
21
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
3-
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
42
import { CompositeTreeNode } from '@theia/core/lib/browser/tree';
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';
3+
import { DisposableCollection } from '@theia/core/lib/common/disposable';
114
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
12-
import {
13-
Progress,
14-
ProgressUpdate,
15-
} from '@theia/core/lib/common/message-service-protocol';
5+
import { Progress } from '@theia/core/lib/common/message-service-protocol';
166
import { nls } from '@theia/core/lib/common/nls';
177
import { injectable } from '@theia/core/shared/inversify';
18-
import { WorkspaceInputDialogProps } from '@theia/workspace/lib/browser/workspace-input-dialog';
19-
import { v4 } from 'uuid';
208
import { CreateUri } from '../create/create-uri';
219
import {
2210
ConflictError,
@@ -26,7 +14,7 @@ import {
2614
isNotFound,
2715
} from '../create/typings';
2816
import { ArduinoMenus } from '../menu/arduino-menus';
29-
import { WorkspaceInputDialog } from '../theia/workspace/workspace-input-dialog';
17+
import { WorkspaceInputDialogWithProgress } from '../theia/workspace/workspace-input-dialog';
3018
import { CloudSketchbookTree } from '../widgets/cloud-sketchbook/cloud-sketchbook-tree';
3119
import { CloudSketchbookTreeModel } from '../widgets/cloud-sketchbook/cloud-sketchbook-tree-model';
3220
import { SketchbookCommands } from '../widgets/sketchbook/sketchbook-commands';
@@ -85,7 +73,39 @@ export class NewCloudSketch extends CloudSketchContribution {
8573
return undefined;
8674
}
8775

88-
private createNewWithProgress(
76+
private async openWizard(
77+
rootNode: CompositeTreeNode,
78+
treeModel: CloudSketchbookTreeModel,
79+
initialValue?: string | undefined
80+
): Promise<unknown> {
81+
const existingNames = rootNode.children
82+
.filter(CloudSketchbookTree.CloudSketchDirNode.is)
83+
.map(({ fileStat }) => fileStat.name);
84+
return new WorkspaceInputDialogWithProgress(
85+
{
86+
title: nls.localize(
87+
'arduino/newCloudSketch/newSketchTitle',
88+
'Name of a new Remote Sketch'
89+
),
90+
parentUri: CreateUri.root,
91+
initialValue,
92+
validate: (input) => {
93+
if (existingNames.includes(input)) {
94+
return nls.localize(
95+
'arduino/newCloudSketch/sketchAlreadyExists',
96+
"Remote sketch '{0}' already exists.",
97+
input
98+
);
99+
}
100+
return Sketch.validateCloudSketchFolderName(input) ?? '';
101+
},
102+
},
103+
this.labelProvider,
104+
(value) => this.createNewSketchWithProgress(value, treeModel)
105+
).open();
106+
}
107+
108+
private createNewSketchWithProgress(
89109
value: string,
90110
treeModel: CloudSketchbookTreeModel
91111
): (progress: Progress) => Promise<unknown> {
@@ -165,38 +185,6 @@ export class NewCloudSketch extends CloudSketchContribution {
165185
{ node }
166186
);
167187
}
168-
169-
private async openWizard(
170-
rootNode: CompositeTreeNode,
171-
treeModel: CloudSketchbookTreeModel,
172-
initialValue?: string | undefined
173-
): Promise<unknown> {
174-
const existingNames = rootNode.children
175-
.filter(CloudSketchbookTree.CloudSketchDirNode.is)
176-
.map(({ fileStat }) => fileStat.name);
177-
return new NewCloudSketchDialog(
178-
{
179-
title: nls.localize(
180-
'arduino/newCloudSketch/newSketchTitle',
181-
'Name of a new Remote Sketch'
182-
),
183-
parentUri: CreateUri.root,
184-
initialValue,
185-
validate: (input) => {
186-
if (existingNames.includes(input)) {
187-
return nls.localize(
188-
'arduino/newCloudSketch/sketchAlreadyExists',
189-
"Remote sketch '{0}' already exists.",
190-
input
191-
);
192-
}
193-
return Sketch.validateCloudSketchFolderName(input) ?? '';
194-
},
195-
},
196-
this.labelProvider,
197-
(value) => this.createNewWithProgress(value, treeModel)
198-
).open();
199-
}
200188
}
201189
export namespace NewCloudSketch {
202190
export namespace Commands {
@@ -205,94 +193,3 @@ export namespace NewCloudSketch {
205193
};
206194
}
207195
}
208-
209-
class NewCloudSketchDialog extends WorkspaceInputDialog {
210-
constructor(
211-
protected override readonly props: WorkspaceInputDialogProps,
212-
protected override readonly labelProvider: LabelProvider,
213-
private readonly withProgress: (
214-
value: string
215-
) => (progress: Progress) => Promise<unknown>
216-
) {
217-
super(props, labelProvider);
218-
}
219-
protected override async accept(): Promise<void> {
220-
if (!this.resolve) {
221-
return;
222-
}
223-
this.acceptCancellationSource.cancel();
224-
this.acceptCancellationSource = new CancellationTokenSource();
225-
const token = this.acceptCancellationSource.token;
226-
const value = this.value;
227-
const error = await this.isValid(value, 'open');
228-
if (token.isCancellationRequested) {
229-
return;
230-
}
231-
if (!DialogError.getResult(error)) {
232-
this.setErrorMessage(error);
233-
} else {
234-
const spinner = document.createElement('div');
235-
spinner.classList.add('spinner');
236-
const disposables = new DisposableCollection();
237-
try {
238-
this.toggleButtons(true);
239-
disposables.push(Disposable.create(() => this.toggleButtons(false)));
240-
241-
const closeParent = this.closeCrossNode.parentNode;
242-
closeParent?.removeChild(this.closeCrossNode);
243-
disposables.push(
244-
Disposable.create(() => {
245-
closeParent?.appendChild(this.closeCrossNode);
246-
})
247-
);
248-
249-
this.errorMessageNode.classList.add('progress');
250-
disposables.push(
251-
Disposable.create(() =>
252-
this.errorMessageNode.classList.remove('progress')
253-
)
254-
);
255-
256-
const errorParent = this.errorMessageNode.parentNode;
257-
errorParent?.insertBefore(spinner, this.errorMessageNode);
258-
disposables.push(
259-
Disposable.create(() => errorParent?.removeChild(spinner))
260-
);
261-
262-
const cancellationSource = new CancellationTokenSource();
263-
const progress: Progress = {
264-
id: v4(),
265-
cancel: () => cancellationSource.cancel(),
266-
report: (update: ProgressUpdate) => {
267-
this.setProgressMessage(update);
268-
},
269-
result: Promise.resolve(value),
270-
};
271-
await this.withProgress(value)(progress);
272-
} finally {
273-
disposables.dispose();
274-
}
275-
this.resolve(value);
276-
Widget.detach(this);
277-
}
278-
}
279-
280-
private toggleButtons(disabled: boolean): void {
281-
if (this.acceptButton) {
282-
this.acceptButton.disabled = disabled;
283-
}
284-
if (this.closeButton) {
285-
this.closeButton.disabled = disabled;
286-
}
287-
}
288-
289-
private setProgressMessage(update: ProgressUpdate): void {
290-
if (update.work && update.work.done === update.work.total) {
291-
this.errorMessageNode.innerText = '';
292-
} else {
293-
if (update.message) {
294-
this.errorMessageNode.innerText = update.message;
295-
}
296-
}
297-
}
298-
}

arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
1-
import { inject } from '@theia/core/shared/inversify';
2-
import { MaybePromise } from '@theia/core/lib/common/types';
3-
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
41
import { DialogError, DialogMode } from '@theia/core/lib/browser/dialogs';
2+
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
3+
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
4+
import {
5+
Disposable,
6+
DisposableCollection,
7+
} from '@theia/core/lib/common/disposable';
8+
import {
9+
Progress,
10+
ProgressUpdate,
11+
} from '@theia/core/lib/common/message-service-protocol';
12+
import { nls } from '@theia/core/lib/common/nls';
13+
import { MaybePromise } from '@theia/core/lib/common/types';
14+
import { Widget } from '@theia/core/shared/@phosphor/widgets';
15+
import { inject } from '@theia/core/shared/inversify';
516
import {
617
WorkspaceInputDialog as TheiaWorkspaceInputDialog,
718
WorkspaceInputDialogProps,
819
} from '@theia/workspace/lib/browser/workspace-input-dialog';
9-
import { nls } from '@theia/core/lib/common';
20+
import { v4 } from 'uuid';
1021

1122
export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog {
1223
protected wasTouched = false;
@@ -54,3 +65,96 @@ export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog {
5465
return this.closeButton;
5566
}
5667
}
68+
69+
export class WorkspaceInputDialogWithProgress extends WorkspaceInputDialog {
70+
constructor(
71+
protected override readonly props: WorkspaceInputDialogProps,
72+
protected override readonly labelProvider: LabelProvider,
73+
private readonly createTask: (
74+
value: string
75+
) => (progress: Progress) => Promise<unknown>
76+
) {
77+
super(props, labelProvider);
78+
}
79+
80+
protected override async accept(): Promise<void> {
81+
if (!this.resolve) {
82+
return;
83+
}
84+
this.acceptCancellationSource.cancel();
85+
this.acceptCancellationSource = new CancellationTokenSource();
86+
const token = this.acceptCancellationSource.token;
87+
const value = this.value;
88+
const error = await this.isValid(value, 'open');
89+
if (token.isCancellationRequested) {
90+
return;
91+
}
92+
if (!DialogError.getResult(error)) {
93+
this.setErrorMessage(error);
94+
} else {
95+
const spinner = document.createElement('div');
96+
spinner.classList.add('spinner');
97+
const disposables = new DisposableCollection();
98+
try {
99+
this.toggleButtons(true);
100+
disposables.push(Disposable.create(() => this.toggleButtons(false)));
101+
102+
const closeParent = this.closeCrossNode.parentNode;
103+
closeParent?.removeChild(this.closeCrossNode);
104+
disposables.push(
105+
Disposable.create(() => {
106+
closeParent?.appendChild(this.closeCrossNode);
107+
})
108+
);
109+
110+
this.errorMessageNode.classList.add('progress');
111+
disposables.push(
112+
Disposable.create(() =>
113+
this.errorMessageNode.classList.remove('progress')
114+
)
115+
);
116+
117+
const errorParent = this.errorMessageNode.parentNode;
118+
errorParent?.insertBefore(spinner, this.errorMessageNode);
119+
disposables.push(
120+
Disposable.create(() => errorParent?.removeChild(spinner))
121+
);
122+
123+
const cancellationSource = new CancellationTokenSource();
124+
const progress: Progress = {
125+
id: v4(),
126+
cancel: () => cancellationSource.cancel(),
127+
report: (update: ProgressUpdate) => {
128+
this.setProgressMessage(update);
129+
},
130+
result: Promise.resolve(value),
131+
};
132+
const task = this.createTask(value);
133+
await task(progress);
134+
} finally {
135+
disposables.dispose();
136+
}
137+
this.resolve(value);
138+
Widget.detach(this);
139+
}
140+
}
141+
142+
private toggleButtons(disabled: boolean): void {
143+
if (this.acceptButton) {
144+
this.acceptButton.disabled = disabled;
145+
}
146+
if (this.closeButton) {
147+
this.closeButton.disabled = disabled;
148+
}
149+
}
150+
151+
private setProgressMessage(update: ProgressUpdate): void {
152+
if (update.work && update.work.done === update.work.total) {
153+
this.errorMessageNode.innerText = '';
154+
} else {
155+
if (update.message) {
156+
this.errorMessageNode.innerText = update.message;
157+
}
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)