Skip to content

feat: progress for the remote sketch creation #1673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 156 additions & 31 deletions arduino-ide-extension/src/browser/contributions/new-cloud-sketch.ts
Original file line number Diff line number Diff line change
@@ -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 { LabelProvider } from '@theia/core/lib/browser/label-provider';
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 { 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 type { AuthenticationSession } from '../../node/auth/types';
import { AuthenticationClientService } from '../auth/authentication-client-service';
Expand Down Expand Up @@ -90,7 +103,7 @@ export class NewCloudSketch extends Contribution {

private async createNewSketch(
initialValue?: string | undefined
): Promise<URI | undefined> {
): Promise<unknown> {
const widget = await this.widgetContribution.widget;
const treeModel = this.treeModelFrom(widget);
if (!treeModel) {
Expand All @@ -102,34 +115,50 @@ export class NewCloudSketch extends Contribution {
if (!rootNode) {
return undefined;
}
return this.openWizard(rootNode, treeModel, initialValue);
}

const newSketchName = await this.newSketchName(rootNode, initialValue);
if (!newSketchName) {
return undefined;
}
let result: Create.Sketch | undefined | 'conflict';
try {
result = await this.createApi.createSketch(newSketchName);
} catch (err) {
if (isConflict(err)) {
result = 'conflict';
} else {
throw err;
private withProgress(
value: string,
treeModel: CloudSketchbookTreeModel
): (progress: Progress) => Promise<unknown> {
return async (progress: Progress) => {
let result: Create.Sketch | undefined | 'conflict';
try {
progress.report({
message: nls.localize(
'arduino/cloudSketch/creating',
"Creating remote sketch '{0}'...",
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) {
await treeModel.refresh();
return this.open(treeModel, result);
}
}

if (result === 'conflict') {
return this.createNewSketch(newSketchName);
}

if (result) {
return this.open(treeModel, result);
}
return undefined;
return undefined;
};
}

private async open(
Expand Down Expand Up @@ -183,14 +212,15 @@ export class NewCloudSketch extends Contribution {
return undefined;
}

private async newSketchName(
private async openWizard(
rootNode: CompositeTreeNode,
treeModel: CloudSketchbookTreeModel,
initialValue?: string | undefined
): Promise<string | undefined> {
): Promise<unknown> {
const existingNames = rootNode.children
.filter(CloudSketchbookTree.CloudSketchDirNode.is)
.map(({ fileStat }) => fileStat.name);
return new WorkspaceInputDialog(
return new NewCloudSketchDialog(
{
title: nls.localize(
'arduino/newCloudSketch/newSketchTitle',
Expand All @@ -216,7 +246,8 @@ export class NewCloudSketch extends Contribution {
);
},
},
this.labelProvider
this.labelProvider,
(value) => this.withProgress(value, treeModel)
).open();
}
}
Expand Down Expand Up @@ -245,3 +276,97 @@ function isErrorWithStatusOf(
}
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;
}
}
}
}
10 changes: 8 additions & 2 deletions arduino-ide-extension/src/browser/style/dialogs.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
align-items: center;
}

.p-Widget.dialogOverlay .dialogControl .spinner,
.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow .spinner {
background: var(--theia-icon-loading) center center no-repeat;
animation: theia-spin 1.25s linear infinite;
Expand All @@ -63,11 +64,11 @@
}

.p-Widget.dialogOverlay .dialogBlock .dialogContent .dialogSection .dialogRow:first-child {
margin-top: 0px;
margin-top: 0px;
height: 32px;
}

.fl1{
.fl1 {
flex: 1;
}

Expand All @@ -85,3 +86,8 @@
max-height: 400px;
}
}

.p-Widget.dialogOverlay .error.progress {
color: var(--theia-button-background);
align-self: center;
}
4 changes: 3 additions & 1 deletion i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@
"visitArduinoCloud": "Visit Arduino Cloud to create Cloud Sketches."
},
"cloudSketch": {
"new": "New Remote Sketch"
"creating": "Creating remote sketch '{0}'...",
"new": "New Remote Sketch",
"synchronizing": "Synchronizing sketchbook, pulling '{0}'..."
},
"common": {
"all": "All",
Expand Down