diff --git a/arduino-ide-extension/src/browser/create/create-features.ts b/arduino-ide-extension/src/browser/create/create-features.ts index 063e259d5..4a725c299 100644 --- a/arduino-ide-extension/src/browser/create/create-features.ts +++ b/arduino-ide-extension/src/browser/create/create-features.ts @@ -8,6 +8,9 @@ import { AuthenticationSession } from '../../node/auth/types'; import { ArduinoPreferences } from '../arduino-preferences'; import { AuthenticationClientService } from '../auth/authentication-client-service'; import { LocalCacheFsProvider } from '../local-cache/local-cache-fs-provider'; +import { CreateUri } from './create-uri'; + +export type CloudSketchState = 'push' | 'pull'; @injectable() export class CreateFeatures implements FrontendApplicationContribution { @@ -18,13 +21,22 @@ export class CreateFeatures implements FrontendApplicationContribution { @inject(LocalCacheFsProvider) private readonly localCacheFsProvider: LocalCacheFsProvider; + /** + * The keys are the Create URI of the sketches. + */ + private readonly _cloudSketchStates = new Map(); private readonly onDidChangeSessionEmitter = new Emitter< AuthenticationSession | undefined >(); private readonly onDidChangeEnabledEmitter = new Emitter(); + private readonly onDidChangeCloudSketchStateEmitter = new Emitter<{ + uri: URI; + state: CloudSketchState | undefined; + }>(); private readonly toDispose = new DisposableCollection( this.onDidChangeSessionEmitter, - this.onDidChangeEnabledEmitter + this.onDidChangeEnabledEmitter, + this.onDidChangeCloudSketchStateEmitter ); private _enabled: boolean; private _session: AuthenticationSession | undefined; @@ -64,14 +76,55 @@ export class CreateFeatures implements FrontendApplicationContribution { return this.onDidChangeEnabledEmitter.event; } - get enabled(): boolean { - return this._enabled; + get onDidChangeCloudSketchState(): Event<{ + uri: URI; + state: CloudSketchState | undefined; + }> { + return this.onDidChangeCloudSketchStateEmitter.event; } get session(): AuthenticationSession | undefined { return this._session; } + get enabled(): boolean { + return this._enabled; + } + + get cloudSketchStates(): { + uri: URI; + state: CloudSketchState | undefined; + }[] { + return Array.from(this._cloudSketchStates.entries()).map( + ([uri, state]) => ({ uri: new URI(uri), state }) + ); + } + + cloudSketchState(uri: URI): CloudSketchState | undefined { + return this._cloudSketchStates.get(uri.toString()); + } + + setCloudSketchState(uri: URI, state: CloudSketchState | undefined): void { + if (uri.scheme !== CreateUri.scheme) { + throw new Error( + `Expected a URI with '${uri.scheme}' scheme. Got: ${uri.toString()}` + ); + } + const key = uri.toString(); + if (!state) { + if (!this._cloudSketchStates.delete(key)) { + console.warn( + `Could not reset the cloud sketch state of ${key}. No state existed for the the cloud sketch.` + ); + } else { + this.onDidChangeCloudSketchStateEmitter.fire({ uri, state: undefined }); + } + } else { + this._cloudSketchStates.set(key, state); + this.onDidChangeCloudSketchStateEmitter.fire({ uri, state }); + } + } + /** * `true` if the sketch is under `directories.data/RemoteSketchbook`. Otherwise, `false`. * Returns with `undefined` if `dataDirUri` is `undefined`. diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts index 2d9fba204..f58f747e7 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts @@ -1,4 +1,3 @@ -import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; import { ContextMenuRenderer, RenderContextMenuOptions, @@ -34,6 +33,7 @@ import { ApplicationConnectionStatusContribution } from '../../theia/core/connec import { SketchbookCommands } from '../sketchbook/sketchbook-commands'; import { CloudSketchbookCommands } from './cloud-sketchbook-commands'; import { CloudSketchbookTree } from './cloud-sketchbook-tree'; +import { CreateUri } from '../../create/create-uri'; const SKETCHBOOKSYNC__CONTEXT = ['arduino-sketchbook-sync--context']; @@ -61,8 +61,6 @@ export class CloudSketchbookContribution extends CloudSketchContribution { private readonly configServiceClient: ConfigServiceClient; @inject(ApplicationConnectionStatusContribution) private readonly connectionStatus: ApplicationConnectionStatusContribution; - @inject(ContextKeyService) - private readonly contextKeyService: ContextKeyService; private readonly onDidChangeToolbarEmitter = new Emitter(); private readonly toDisposeBeforeNewContextMenu = new DisposableCollection(); @@ -82,17 +80,9 @@ export class CloudSketchbookContribution extends CloudSketchContribution { }), this.createFeatures.onDidChangeSession(() => this.fireToolbarChange()), this.createFeatures.onDidChangeEnabled(() => this.fireToolbarChange()), - this.contextKeyService.onDidChange((event) => { - if ( - event.affects({ - has(candidate: string) { - return candidate === 'cloudSketchState'; - }, - }) - ) { - this.fireToolbarChange(); - } - }), + this.createFeatures.onDidChangeCloudSketchState(() => + this.fireToolbarChange() + ), ]); } @@ -115,7 +105,6 @@ export class CloudSketchbookContribution extends CloudSketchContribution { tooltip: CloudSketchbookCommands.PULL_SKETCH__TOOLBAR.label, priority: -2, onDidChange: this.onDidChangeToolbar, - when: 'cloudSketchState != pulling && cloudSketchState != pushing', }); registry.registerItem({ id: CloudSketchbookCommands.PUSH_SKETCH__TOOLBAR.id, @@ -123,7 +112,6 @@ export class CloudSketchbookContribution extends CloudSketchContribution { tooltip: CloudSketchbookCommands.PUSH_SKETCH__TOOLBAR.label, priority: -1, onDidChange: this.onDidChangeToolbar, - when: 'cloudSketchState != pulling && cloudSketchState != pushing', }); } @@ -341,7 +329,17 @@ export class CloudSketchbookContribution extends CloudSketchContribution { if (this.connectionStatus.offlineStatus === 'internet') { return false; } - return true; + // no pull/push context for the current cloud sketch + const sketch = this.currentCloudSketch; + if (sketch) { + const cloudUri = this.createFeatures.cloudUri(sketch); + if (cloudUri) { + return !this.createFeatures.cloudSketchState( + CreateUri.toUri(cloudUri.path.toString()) + ); + } + } + return false; } private isCloudSketchDirNodeCommandArg( @@ -351,7 +349,8 @@ export class CloudSketchbookContribution extends CloudSketchContribution { } { return ( CloudSketchbookCommands.Arg.is(arg) && - CloudSketchbookTree.CloudSketchDirNode.is(arg.node) + CloudSketchbookTree.CloudSketchDirNode.is(arg.node) && + !this.createFeatures.cloudSketchState(arg.node.remoteUri) ); } diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts index a75684b40..915ac4a4a 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts @@ -37,10 +37,7 @@ import { pullingSketch, pushingSketch, } from '../../contributions/cloud-contribution'; -import { - ContextKey, - ContextKeyService, -} from '@theia/core/lib/browser/context-key-service'; +import { CloudSketchState, CreateFeatures } from '../../create/create-features'; const MESSAGE_TIMEOUT = 5 * 1000; const deepmerge = require('deepmerge').default; @@ -67,19 +64,13 @@ export class CloudSketchbookTree extends SketchbookTree { @inject(ApplicationConnectionStatusContribution) private readonly connectionStatus: ApplicationConnectionStatusContribution; - @inject(ContextKeyService) - private readonly contextKeyService: ContextKeyService; - - private cloudSketchState: ContextKey | undefined; + @inject(CreateFeatures) + private readonly createFeatures: CreateFeatures; protected override init(): void { this.toDispose.push( this.connectionStatus.onOfflineStatusDidChange(() => this.refresh()) ); - this.cloudSketchState = this.contextKeyService.createKey( - 'cloudSketchState', - undefined - ); super.init(); } @@ -149,7 +140,7 @@ export class CloudSketchbookTree extends SketchbookTree { } return this.runWithState( node, - 'pulling', + 'pull', async (node) => { const commandsCopy = node.commands; node.commands = []; @@ -217,7 +208,7 @@ export class CloudSketchbookTree extends SketchbookTree { } return this.runWithState( node, - 'pushing', + 'push', async (node) => { if (!CloudSketchbookTree.CloudSketchTreeNode.isSynced(node)) { throw new Error( @@ -352,11 +343,11 @@ export class CloudSketchbookTree extends SketchbookTree { private async runWithState( node: CloudSketchbookTree.CloudSketchDirNode & Partial, - state: CloudSketchbookTree.CloudSketchDirNode.State, + state: CloudSketchState, task: (node: CloudSketchbookTree.CloudSketchDirNode) => MaybePromise, noProgress = false ): Promise { - this.cloudSketchState?.set(state); + this.createFeatures.setCloudSketchState(node.remoteUri, state); try { const result = await (noProgress ? task(node) @@ -371,18 +362,15 @@ export class CloudSketchbookTree extends SketchbookTree { await this.refresh(node); return result; } finally { - this.cloudSketchState?.set(undefined); + this.createFeatures.setCloudSketchState(node.remoteUri, undefined); } } - private taskMessage( - state: CloudSketchbookTree.CloudSketchDirNode.State, - input: string - ): string { + private taskMessage(state: CloudSketchState, input: string): string { switch (state) { - case 'pulling': + case 'pull': return pullingSketch(input); - case 'pushing': + case 'push': return pushingSketch(input); default: assertUnreachable(state); @@ -700,7 +688,7 @@ export namespace CloudSketchbookTree { export interface CloudSketchDirNode extends Omit, CloudSketchTreeNode { - state?: CloudSketchDirNode.State; + state?: CloudSketchState; isPublic?: boolean; sketchId?: string; commands?: Command[]; @@ -709,7 +697,5 @@ export namespace CloudSketchbookTree { export function is(node: TreeNode | undefined): node is CloudSketchDirNode { return SketchbookTree.SketchDirNode.is(node); } - - export type State = 'pulling' | 'pushing'; } }