mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-15 07:16:38 +00:00
feat: progress for the remote sketch creation
Closes #1668 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
d24a3911f8
commit
1a7784a540
@ -1,9 +1,22 @@
|
|||||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
import { DialogError } from '@theia/core/lib/browser/dialogs';
|
||||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||||
|
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||||
import { CompositeTreeNode } from '@theia/core/lib/browser/tree';
|
import { CompositeTreeNode } from '@theia/core/lib/browser/tree';
|
||||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
import { Widget } from '@theia/core/lib/browser/widgets/widget';
|
||||||
|
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
|
||||||
|
import {
|
||||||
|
Disposable,
|
||||||
|
DisposableCollection,
|
||||||
|
} from '@theia/core/lib/common/disposable';
|
||||||
|
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||||
|
import {
|
||||||
|
Progress,
|
||||||
|
ProgressUpdate,
|
||||||
|
} from '@theia/core/lib/common/message-service-protocol';
|
||||||
import { nls } from '@theia/core/lib/common/nls';
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { WorkspaceInputDialogProps } from '@theia/workspace/lib/browser/workspace-input-dialog';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||||
import type { AuthenticationSession } from '../../node/auth/types';
|
import type { AuthenticationSession } from '../../node/auth/types';
|
||||||
import { AuthenticationClientService } from '../auth/authentication-client-service';
|
import { AuthenticationClientService } from '../auth/authentication-client-service';
|
||||||
@ -90,7 +103,7 @@ export class NewCloudSketch extends Contribution {
|
|||||||
|
|
||||||
private async createNewSketch(
|
private async createNewSketch(
|
||||||
initialValue?: string | undefined
|
initialValue?: string | undefined
|
||||||
): Promise<URI | undefined> {
|
): Promise<unknown> {
|
||||||
const widget = await this.widgetContribution.widget;
|
const widget = await this.widgetContribution.widget;
|
||||||
const treeModel = this.treeModelFrom(widget);
|
const treeModel = this.treeModelFrom(widget);
|
||||||
if (!treeModel) {
|
if (!treeModel) {
|
||||||
@ -102,34 +115,50 @@ export class NewCloudSketch extends Contribution {
|
|||||||
if (!rootNode) {
|
if (!rootNode) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
return this.openWizard(rootNode, treeModel, initialValue);
|
||||||
|
}
|
||||||
|
|
||||||
const newSketchName = await this.newSketchName(rootNode, initialValue);
|
private withProgress(
|
||||||
if (!newSketchName) {
|
value: string,
|
||||||
return undefined;
|
treeModel: CloudSketchbookTreeModel
|
||||||
}
|
): (progress: Progress) => Promise<unknown> {
|
||||||
let result: Create.Sketch | undefined | 'conflict';
|
return async (progress: Progress) => {
|
||||||
try {
|
let result: Create.Sketch | undefined | 'conflict';
|
||||||
result = await this.createApi.createSketch(newSketchName);
|
try {
|
||||||
} catch (err) {
|
progress.report({
|
||||||
if (isConflict(err)) {
|
message: nls.localize(
|
||||||
result = 'conflict';
|
'arduino/cloudSketch/creating',
|
||||||
} else {
|
"Creating remote sketch '{0}'...",
|
||||||
throw err;
|
value
|
||||||
|
),
|
||||||
|
});
|
||||||
|
result = await this.createApi.createSketch(value);
|
||||||
|
} catch (err) {
|
||||||
|
if (isConflict(err)) {
|
||||||
|
result = 'conflict';
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (result) {
|
||||||
|
progress.report({
|
||||||
|
message: nls.localize(
|
||||||
|
'arduino/cloudSketch/synchronizing',
|
||||||
|
"Synchronizing sketchbook, pulling '{0}'...",
|
||||||
|
value
|
||||||
|
),
|
||||||
|
});
|
||||||
|
await treeModel.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result === 'conflict') {
|
||||||
|
return this.createNewSketch(value);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
if (result) {
|
if (result) {
|
||||||
await treeModel.refresh();
|
return this.open(treeModel, result);
|
||||||
}
|
}
|
||||||
}
|
return undefined;
|
||||||
|
};
|
||||||
if (result === 'conflict') {
|
|
||||||
return this.createNewSketch(newSketchName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
return this.open(treeModel, result);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async open(
|
private async open(
|
||||||
@ -183,14 +212,15 @@ export class NewCloudSketch extends Contribution {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async newSketchName(
|
private async openWizard(
|
||||||
rootNode: CompositeTreeNode,
|
rootNode: CompositeTreeNode,
|
||||||
|
treeModel: CloudSketchbookTreeModel,
|
||||||
initialValue?: string | undefined
|
initialValue?: string | undefined
|
||||||
): Promise<string | undefined> {
|
): Promise<unknown> {
|
||||||
const existingNames = rootNode.children
|
const existingNames = rootNode.children
|
||||||
.filter(CloudSketchbookTree.CloudSketchDirNode.is)
|
.filter(CloudSketchbookTree.CloudSketchDirNode.is)
|
||||||
.map(({ fileStat }) => fileStat.name);
|
.map(({ fileStat }) => fileStat.name);
|
||||||
return new WorkspaceInputDialog(
|
return new NewCloudSketchDialog(
|
||||||
{
|
{
|
||||||
title: nls.localize(
|
title: nls.localize(
|
||||||
'arduino/newCloudSketch/newSketchTitle',
|
'arduino/newCloudSketch/newSketchTitle',
|
||||||
@ -216,7 +246,8 @@ export class NewCloudSketch extends Contribution {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
this.labelProvider
|
this.labelProvider,
|
||||||
|
(value) => this.withProgress(value, treeModel)
|
||||||
).open();
|
).open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,3 +276,97 @@ function isErrorWithStatusOf(
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
class NewCloudSketchDialog extends WorkspaceInputDialog {
|
||||||
|
constructor(
|
||||||
|
@inject(WorkspaceInputDialogProps)
|
||||||
|
protected override readonly props: WorkspaceInputDialogProps,
|
||||||
|
@inject(LabelProvider)
|
||||||
|
protected override readonly labelProvider: LabelProvider,
|
||||||
|
private readonly withProgress: (
|
||||||
|
value: string
|
||||||
|
) => (progress: Progress) => Promise<unknown>
|
||||||
|
) {
|
||||||
|
super(props, labelProvider);
|
||||||
|
}
|
||||||
|
protected override async accept(): Promise<void> {
|
||||||
|
if (!this.resolve) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.acceptCancellationSource.cancel();
|
||||||
|
this.acceptCancellationSource = new CancellationTokenSource();
|
||||||
|
const token = this.acceptCancellationSource.token;
|
||||||
|
const value = this.value;
|
||||||
|
const error = await this.isValid(value, 'open');
|
||||||
|
if (token.isCancellationRequested) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!DialogError.getResult(error)) {
|
||||||
|
this.setErrorMessage(error);
|
||||||
|
} else {
|
||||||
|
const spinner = document.createElement('div');
|
||||||
|
spinner.classList.add('spinner');
|
||||||
|
const disposables = new DisposableCollection();
|
||||||
|
try {
|
||||||
|
this.toggleButtons(true);
|
||||||
|
disposables.push(Disposable.create(() => this.toggleButtons(false)));
|
||||||
|
|
||||||
|
const closeParent = this.closeCrossNode.parentNode;
|
||||||
|
closeParent?.removeChild(this.closeCrossNode);
|
||||||
|
disposables.push(
|
||||||
|
Disposable.create(() => {
|
||||||
|
closeParent?.appendChild(this.closeCrossNode);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.errorMessageNode.classList.add('progress');
|
||||||
|
disposables.push(
|
||||||
|
Disposable.create(() =>
|
||||||
|
this.errorMessageNode.classList.remove('progress')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const errorParent = this.errorMessageNode.parentNode;
|
||||||
|
errorParent?.insertBefore(spinner, this.errorMessageNode);
|
||||||
|
disposables.push(
|
||||||
|
Disposable.create(() => errorParent?.removeChild(spinner))
|
||||||
|
);
|
||||||
|
|
||||||
|
const cancellationSource = new CancellationTokenSource();
|
||||||
|
const progress: Progress = {
|
||||||
|
id: v4(),
|
||||||
|
cancel: () => cancellationSource.cancel(),
|
||||||
|
report: (update: ProgressUpdate) => {
|
||||||
|
this.setProgressMessage(update);
|
||||||
|
},
|
||||||
|
result: Promise.resolve(value),
|
||||||
|
};
|
||||||
|
await this.withProgress(value)(progress);
|
||||||
|
} finally {
|
||||||
|
disposables.dispose();
|
||||||
|
}
|
||||||
|
this.resolve(value);
|
||||||
|
Widget.detach(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleButtons(disabled: boolean): void {
|
||||||
|
if (this.acceptButton) {
|
||||||
|
this.acceptButton.disabled = disabled;
|
||||||
|
}
|
||||||
|
if (this.closeButton) {
|
||||||
|
this.closeButton.disabled = disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setProgressMessage(update: ProgressUpdate): void {
|
||||||
|
if (update.work && update.work.done === update.work.total) {
|
||||||
|
this.errorMessageNode.innerText = '';
|
||||||
|
} else {
|
||||||
|
if (update.message) {
|
||||||
|
this.errorMessageNode.innerText = update.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-Widget.dialogOverlay .dialogControl .spinner,
|
||||||
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow .spinner {
|
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow .spinner {
|
||||||
background: var(--theia-icon-loading) center center no-repeat;
|
background: var(--theia-icon-loading) center center no-repeat;
|
||||||
animation: theia-spin 1.25s linear infinite;
|
animation: theia-spin 1.25s linear infinite;
|
||||||
@ -63,11 +64,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow:first-child {
|
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow:first-child {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fl1{
|
.fl1 {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,3 +86,8 @@
|
|||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-Widget.dialogOverlay .error.progress {
|
||||||
|
color: var(--theia-button-background);
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
@ -120,7 +120,9 @@
|
|||||||
"visitArduinoCloud": "Visit Arduino Cloud to create Cloud Sketches."
|
"visitArduinoCloud": "Visit Arduino Cloud to create Cloud Sketches."
|
||||||
},
|
},
|
||||||
"cloudSketch": {
|
"cloudSketch": {
|
||||||
"new": "New Remote Sketch"
|
"creating": "Creating remote sketch '{0}'...",
|
||||||
|
"new": "New Remote Sketch",
|
||||||
|
"synchronizing": "Synchronizing sketchbook, pulling '{0}'..."
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"all": "All",
|
"all": "All",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user