Skip to content

Commit

Permalink
feat: introduced cloud state in sketchbook view
Browse files Browse the repository at this point in the history
Closes #1879
Closes #1899

Signed-off-by: Akos Kitta <[email protected]>
  • Loading branch information
Akos Kitta committed Feb 24, 2023
1 parent 01ee045 commit 9dade8e
Show file tree
Hide file tree
Showing 38 changed files with 1,085 additions and 432 deletions.
2 changes: 1 addition & 1 deletion arduino-ide-extension/arduino-icons.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions arduino-ide-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"glob": "^7.1.6",
"google-protobuf": "^3.20.1",
"hash.js": "^1.1.7",
"is-online": "^9.0.1",
"js-yaml": "^3.13.1",
"just-diff": "^5.1.1",
"jwt-decode": "^3.1.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ import { EditorCommandContribution as TheiaEditorCommandContribution } from '@th
import {
FrontendConnectionStatusService,
ApplicationConnectionStatusContribution,
DaemonPort,
IsOnline,
} from './theia/core/connection-status-service';
import {
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
Expand Down Expand Up @@ -738,6 +740,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
Contribution.configure(bind, ValidateSketch);
Contribution.configure(bind, RenameCloudSketch);
Contribution.configure(bind, Account);
Contribution.configure(bind, CloudSketchbookContribution);

bindContributionProvider(bind, StartupTaskProvider);
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
Expand Down Expand Up @@ -916,8 +919,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(CreateFsProvider).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(CreateFsProvider);
bind(FileServiceContribution).toService(CreateFsProvider);
bind(CloudSketchbookContribution).toSelf().inSingletonScope();
bind(CommandContribution).toService(CloudSketchbookContribution);
bind(LocalCacheFsProvider).toSelf().inSingletonScope();
bind(FileServiceContribution).toService(LocalCacheFsProvider);
bind(CloudSketchbookCompositeWidget).toSelf();
Expand Down Expand Up @@ -1021,4 +1022,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {

bind(SidebarBottomMenuWidget).toSelf();
rebind(TheiaSidebarBottomMenuWidget).toService(SidebarBottomMenuWidget);
bind(DaemonPort).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(DaemonPort);
bind(IsOnline).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(IsOnline);
});
18 changes: 14 additions & 4 deletions arduino-ide-extension/src/browser/contributions/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { inject, injectable } from '@theia/core/shared/inversify';
import { CloudUserCommands, LEARN_MORE_URL } from '../auth/cloud-user-commands';
import { CreateFeatures } from '../create/create-features';
import { ArduinoMenus } from '../menu/arduino-menus';
import { ApplicationConnectionStatusContribution } from '../theia/core/connection-status-service';
import {
Command,
CommandRegistry,
Expand All @@ -29,6 +30,8 @@ export class Account extends Contribution {
private readonly windowService: WindowService;
@inject(CreateFeatures)
private readonly createFeatures: CreateFeatures;
@inject(ApplicationConnectionStatusContribution)
private readonly connectionStatus: ApplicationConnectionStatusContribution;

private readonly toDispose = new DisposableCollection();
private app: FrontendApplication;
Expand All @@ -50,21 +53,28 @@ export class Account extends Contribution {
override registerCommands(registry: CommandRegistry): void {
const openExternal = (url: string) =>
this.windowService.openNewWindow(url, { external: true });
const loggedIn = () => Boolean(this.createFeatures.session);
const loggedInWithInternetConnection = () =>
loggedIn() && this.connectionStatus.offlineStatus !== 'internet';
registry.registerCommand(Account.Commands.LEARN_MORE, {
execute: () => openExternal(LEARN_MORE_URL),
isEnabled: () => !Boolean(this.createFeatures.session),
isEnabled: () => !loggedIn(),
isVisible: () => !loggedIn(),
});
registry.registerCommand(Account.Commands.GO_TO_PROFILE, {
execute: () => openExternal('https://id.arduino.cc/'),
isEnabled: () => Boolean(this.createFeatures.session),
isEnabled: () => loggedInWithInternetConnection(),
isVisible: () => loggedIn(),
});
registry.registerCommand(Account.Commands.GO_TO_CLOUD_EDITOR, {
execute: () => openExternal('https://create.arduino.cc/editor'),
isEnabled: () => Boolean(this.createFeatures.session),
isEnabled: () => loggedInWithInternetConnection(),
isVisible: () => loggedIn(),
});
registry.registerCommand(Account.Commands.GO_TO_IOT_CLOUD, {
execute: () => openExternal('https://create.arduino.cc/iot/'),
isEnabled: () => Boolean(this.createFeatures.session),
isEnabled: () => loggedInWithInternetConnection(),
isVisible: () => loggedIn(),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export abstract class CloudSketchContribution extends SketchContribution {
);
}
try {
await treeModel.sketchbookTree().pull({ node });
await treeModel.sketchbookTree().pull({ node }, true);
return node;
} catch (err) {
if (isNotFound(err)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { MessageService } from '@theia/core/lib/common/message-service';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';

import {
MenuModelRegistry,
MenuContribution,
Expand Down Expand Up @@ -58,7 +57,7 @@ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { ExecuteWithProgress } from '../../common/protocol/progressible';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { BoardsDataStore } from '../boards/boards-data-store';
import { NotificationManager } from '../theia/messages/notifications-manager';
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
import { WorkspaceService } from '../theia/workspace/workspace-service';
import { MainMenuManager } from '../../common/main-menu-manager';
Expand Down Expand Up @@ -295,7 +294,7 @@ export abstract class CoreServiceContribution extends SketchContribution {
}

private notificationId(message: string, ...actions: string[]): string {
return this.notificationManager.getMessageId({
return this.notificationManager['getMessageId']({
text: message,
actions,
type: MessageType.Error,
Expand Down
53 changes: 5 additions & 48 deletions arduino-ide-extension/src/browser/create/create-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,24 @@ import { MaybePromise } from '@theia/core/lib/common/types';
import { inject, injectable } from '@theia/core/shared/inversify';
import { fetch } from 'cross-fetch';
import { SketchesService } from '../../common/protocol';
import { unit8ArrayToString } from '../../common/utils';
import { ArduinoPreferences } from '../arduino-preferences';
import { AuthenticationClientService } from '../auth/authentication-client-service';
import { SketchCache } from '../widgets/cloud-sketchbook/cloud-sketch-cache';
import * as createPaths from './create-paths';
import { posix } from './create-paths';
import { Create, CreateError } from './typings';

export interface ResponseResultProvider {
interface ResponseResultProvider {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(response: Response): Promise<any>;
}
export namespace ResponseResultProvider {
namespace ResponseResultProvider {
export const NOOP: ResponseResultProvider = async () => undefined;
export const TEXT: ResponseResultProvider = (response) => response.text();
export const JSON: ResponseResultProvider = (response) => response.json();
}

// TODO: check if this is still needed: https://github.com/electron/electron/issues/18733
// The original issue was reported for Electron 5.x and 6.x. Theia uses 15.x
export function Utf8ArrayToStr(array: Uint8Array): string {
let out, i, c;
let char2, char3;

out = '';
const len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12:
case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(
((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)
);
break;
}
}

return out;
}

type ResourceType = 'f' | 'd';

@injectable()
Expand Down Expand Up @@ -330,10 +288,9 @@ export class CreateApi {
if (sketch) {
const url = new URL(`${this.domain()}/sketches/${sketch.id}`);
const headers = await this.headers();

// parse the secret file
const secrets = (
typeof content === 'string' ? content : Utf8ArrayToStr(content)
typeof content === 'string' ? content : unit8ArrayToString(content)
)
.split(/\r?\n/)
.reduce((prev, curr) => {
Expand Down Expand Up @@ -397,7 +354,7 @@ export class CreateApi {
const headers = await this.headers();

let data: string =
typeof content === 'string' ? content : Utf8ArrayToStr(content);
typeof content === 'string' ? content : unit8ArrayToString(content);
data = await this.toggleSecretsInclude(posixPath, data, 'remove');

const payload = { data: btoa(data) };
Expand Down
64 changes: 60 additions & 4 deletions arduino-ide-extension/src/browser/create/create-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<string, CloudSketchState>();
private readonly onDidChangeSessionEmitter = new Emitter<
AuthenticationSession | undefined
>();
private readonly onDidChangeEnabledEmitter = new Emitter<boolean>();
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;
Expand Down Expand Up @@ -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`.
Expand All @@ -83,7 +136,10 @@ export class CreateFeatures implements FrontendApplicationContribution {
);
return undefined;
}
return dataDirUri.isEqualOrParent(new URI(sketch.uri));
return dataDirUri
.resolve('RemoteSketchbook')
.resolve('ArduinoCloud')
.isEqualOrParent(new URI(sketch.uri));
}

cloudUri(sketch: Sketch): URI | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { CreateUri } from './create-uri';
import { SketchesService } from '../../common/protocol';
import { ArduinoPreferences } from '../arduino-preferences';
import { Create } from './typings';
import { stringToUint8Array } from '../../common/utils';

@injectable()
export class CreateFsProvider
Expand Down Expand Up @@ -154,7 +155,7 @@ export class CreateFsProvider

async readFile(uri: URI): Promise<Uint8Array> {
const content = await this.getCreateApi.readFile(uri.path.toString());
return new TextEncoder().encode(content);
return stringToUint8Array(content);
}

async writeFile(
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions arduino-ide-extension/src/browser/icons/arduino-cloud.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9dade8e

Please sign in to comment.