Skip to content

Commit 9dade8e

Browse files
author
Akos Kitta
committed
feat: introduced cloud state in sketchbook view
Closes #1879 Closes #1899 Signed-off-by: Akos Kitta <[email protected]>
1 parent 01ee045 commit 9dade8e

38 files changed

+1085
-432
lines changed

Diff for: arduino-ide-extension/arduino-icons.json

+1-1
Large diffs are not rendered by default.

Diff for: arduino-ide-extension/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"glob": "^7.1.6",
7878
"google-protobuf": "^3.20.1",
7979
"hash.js": "^1.1.7",
80+
"is-online": "^9.0.1",
8081
"js-yaml": "^3.13.1",
8182
"just-diff": "^5.1.1",
8283
"jwt-decode": "^3.1.2",

Diff for: arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ import { EditorCommandContribution as TheiaEditorCommandContribution } from '@th
9090
import {
9191
FrontendConnectionStatusService,
9292
ApplicationConnectionStatusContribution,
93+
DaemonPort,
94+
IsOnline,
9395
} from './theia/core/connection-status-service';
9496
import {
9597
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
@@ -738,6 +740,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
738740
Contribution.configure(bind, ValidateSketch);
739741
Contribution.configure(bind, RenameCloudSketch);
740742
Contribution.configure(bind, Account);
743+
Contribution.configure(bind, CloudSketchbookContribution);
741744

742745
bindContributionProvider(bind, StartupTaskProvider);
743746
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
@@ -916,8 +919,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
916919
bind(CreateFsProvider).toSelf().inSingletonScope();
917920
bind(FrontendApplicationContribution).toService(CreateFsProvider);
918921
bind(FileServiceContribution).toService(CreateFsProvider);
919-
bind(CloudSketchbookContribution).toSelf().inSingletonScope();
920-
bind(CommandContribution).toService(CloudSketchbookContribution);
921922
bind(LocalCacheFsProvider).toSelf().inSingletonScope();
922923
bind(FileServiceContribution).toService(LocalCacheFsProvider);
923924
bind(CloudSketchbookCompositeWidget).toSelf();
@@ -1021,4 +1022,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
10211022

10221023
bind(SidebarBottomMenuWidget).toSelf();
10231024
rebind(TheiaSidebarBottomMenuWidget).toService(SidebarBottomMenuWidget);
1025+
bind(DaemonPort).toSelf().inSingletonScope();
1026+
bind(FrontendApplicationContribution).toService(DaemonPort);
1027+
bind(IsOnline).toSelf().inSingletonScope();
1028+
bind(FrontendApplicationContribution).toService(IsOnline);
10241029
});

Diff for: arduino-ide-extension/src/browser/contributions/account.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { inject, injectable } from '@theia/core/shared/inversify';
88
import { CloudUserCommands, LEARN_MORE_URL } from '../auth/cloud-user-commands';
99
import { CreateFeatures } from '../create/create-features';
1010
import { ArduinoMenus } from '../menu/arduino-menus';
11+
import { ApplicationConnectionStatusContribution } from '../theia/core/connection-status-service';
1112
import {
1213
Command,
1314
CommandRegistry,
@@ -29,6 +30,8 @@ export class Account extends Contribution {
2930
private readonly windowService: WindowService;
3031
@inject(CreateFeatures)
3132
private readonly createFeatures: CreateFeatures;
33+
@inject(ApplicationConnectionStatusContribution)
34+
private readonly connectionStatus: ApplicationConnectionStatusContribution;
3235

3336
private readonly toDispose = new DisposableCollection();
3437
private app: FrontendApplication;
@@ -50,21 +53,28 @@ export class Account extends Contribution {
5053
override registerCommands(registry: CommandRegistry): void {
5154
const openExternal = (url: string) =>
5255
this.windowService.openNewWindow(url, { external: true });
56+
const loggedIn = () => Boolean(this.createFeatures.session);
57+
const loggedInWithInternetConnection = () =>
58+
loggedIn() && this.connectionStatus.offlineStatus !== 'internet';
5359
registry.registerCommand(Account.Commands.LEARN_MORE, {
5460
execute: () => openExternal(LEARN_MORE_URL),
55-
isEnabled: () => !Boolean(this.createFeatures.session),
61+
isEnabled: () => !loggedIn(),
62+
isVisible: () => !loggedIn(),
5663
});
5764
registry.registerCommand(Account.Commands.GO_TO_PROFILE, {
5865
execute: () => openExternal('https://id.arduino.cc/'),
59-
isEnabled: () => Boolean(this.createFeatures.session),
66+
isEnabled: () => loggedInWithInternetConnection(),
67+
isVisible: () => loggedIn(),
6068
});
6169
registry.registerCommand(Account.Commands.GO_TO_CLOUD_EDITOR, {
6270
execute: () => openExternal('https://create.arduino.cc/editor'),
63-
isEnabled: () => Boolean(this.createFeatures.session),
71+
isEnabled: () => loggedInWithInternetConnection(),
72+
isVisible: () => loggedIn(),
6473
});
6574
registry.registerCommand(Account.Commands.GO_TO_IOT_CLOUD, {
6675
execute: () => openExternal('https://create.arduino.cc/iot/'),
67-
isEnabled: () => Boolean(this.createFeatures.session),
76+
isEnabled: () => loggedInWithInternetConnection(),
77+
isVisible: () => loggedIn(),
6878
});
6979
}
7080

Diff for: arduino-ide-extension/src/browser/contributions/cloud-contribution.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export abstract class CloudSketchContribution extends SketchContribution {
9393
);
9494
}
9595
try {
96-
await treeModel.sketchbookTree().pull({ node });
96+
await treeModel.sketchbookTree().pull({ node }, true);
9797
return node;
9898
} catch (err) {
9999
if (isNotFound(err)) {

Diff for: arduino-ide-extension/src/browser/contributions/contribution.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
1414
import { MessageService } from '@theia/core/lib/common/message-service';
1515
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
1616
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
17-
1817
import {
1918
MenuModelRegistry,
2019
MenuContribution,
@@ -58,7 +57,7 @@ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
5857
import { ExecuteWithProgress } from '../../common/protocol/progressible';
5958
import { BoardsServiceProvider } from '../boards/boards-service-provider';
6059
import { BoardsDataStore } from '../boards/boards-data-store';
61-
import { NotificationManager } from '../theia/messages/notifications-manager';
60+
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
6261
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
6362
import { WorkspaceService } from '../theia/workspace/workspace-service';
6463
import { MainMenuManager } from '../../common/main-menu-manager';
@@ -295,7 +294,7 @@ export abstract class CoreServiceContribution extends SketchContribution {
295294
}
296295

297296
private notificationId(message: string, ...actions: string[]): string {
298-
return this.notificationManager.getMessageId({
297+
return this.notificationManager['getMessageId']({
299298
text: message,
300299
actions,
301300
type: MessageType.Error,

Diff for: arduino-ide-extension/src/browser/create/create-api.ts

+5-48
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,24 @@ import { MaybePromise } from '@theia/core/lib/common/types';
22
import { inject, injectable } from '@theia/core/shared/inversify';
33
import { fetch } from 'cross-fetch';
44
import { SketchesService } from '../../common/protocol';
5+
import { unit8ArrayToString } from '../../common/utils';
56
import { ArduinoPreferences } from '../arduino-preferences';
67
import { AuthenticationClientService } from '../auth/authentication-client-service';
78
import { SketchCache } from '../widgets/cloud-sketchbook/cloud-sketch-cache';
89
import * as createPaths from './create-paths';
910
import { posix } from './create-paths';
1011
import { Create, CreateError } from './typings';
1112

12-
export interface ResponseResultProvider {
13+
interface ResponseResultProvider {
1314
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1415
(response: Response): Promise<any>;
1516
}
16-
export namespace ResponseResultProvider {
17+
namespace ResponseResultProvider {
1718
export const NOOP: ResponseResultProvider = async () => undefined;
1819
export const TEXT: ResponseResultProvider = (response) => response.text();
1920
export const JSON: ResponseResultProvider = (response) => response.json();
2021
}
2122

22-
// TODO: check if this is still needed: https://github.com/electron/electron/issues/18733
23-
// The original issue was reported for Electron 5.x and 6.x. Theia uses 15.x
24-
export function Utf8ArrayToStr(array: Uint8Array): string {
25-
let out, i, c;
26-
let char2, char3;
27-
28-
out = '';
29-
const len = array.length;
30-
i = 0;
31-
while (i < len) {
32-
c = array[i++];
33-
switch (c >> 4) {
34-
case 0:
35-
case 1:
36-
case 2:
37-
case 3:
38-
case 4:
39-
case 5:
40-
case 6:
41-
case 7:
42-
// 0xxxxxxx
43-
out += String.fromCharCode(c);
44-
break;
45-
case 12:
46-
case 13:
47-
// 110x xxxx 10xx xxxx
48-
char2 = array[i++];
49-
out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
50-
break;
51-
case 14:
52-
// 1110 xxxx 10xx xxxx 10xx xxxx
53-
char2 = array[i++];
54-
char3 = array[i++];
55-
out += String.fromCharCode(
56-
((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)
57-
);
58-
break;
59-
}
60-
}
61-
62-
return out;
63-
}
64-
6523
type ResourceType = 'f' | 'd';
6624

6725
@injectable()
@@ -330,10 +288,9 @@ export class CreateApi {
330288
if (sketch) {
331289
const url = new URL(`${this.domain()}/sketches/${sketch.id}`);
332290
const headers = await this.headers();
333-
334291
// parse the secret file
335292
const secrets = (
336-
typeof content === 'string' ? content : Utf8ArrayToStr(content)
293+
typeof content === 'string' ? content : unit8ArrayToString(content)
337294
)
338295
.split(/\r?\n/)
339296
.reduce((prev, curr) => {
@@ -397,7 +354,7 @@ export class CreateApi {
397354
const headers = await this.headers();
398355

399356
let data: string =
400-
typeof content === 'string' ? content : Utf8ArrayToStr(content);
357+
typeof content === 'string' ? content : unit8ArrayToString(content);
401358
data = await this.toggleSecretsInclude(posixPath, data, 'remove');
402359

403360
const payload = { data: btoa(data) };

Diff for: arduino-ide-extension/src/browser/create/create-features.ts

+60-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { AuthenticationSession } from '../../node/auth/types';
88
import { ArduinoPreferences } from '../arduino-preferences';
99
import { AuthenticationClientService } from '../auth/authentication-client-service';
1010
import { LocalCacheFsProvider } from '../local-cache/local-cache-fs-provider';
11+
import { CreateUri } from './create-uri';
12+
13+
export type CloudSketchState = 'push' | 'pull';
1114

1215
@injectable()
1316
export class CreateFeatures implements FrontendApplicationContribution {
@@ -18,13 +21,22 @@ export class CreateFeatures implements FrontendApplicationContribution {
1821
@inject(LocalCacheFsProvider)
1922
private readonly localCacheFsProvider: LocalCacheFsProvider;
2023

24+
/**
25+
* The keys are the Create URI of the sketches.
26+
*/
27+
private readonly _cloudSketchStates = new Map<string, CloudSketchState>();
2128
private readonly onDidChangeSessionEmitter = new Emitter<
2229
AuthenticationSession | undefined
2330
>();
2431
private readonly onDidChangeEnabledEmitter = new Emitter<boolean>();
32+
private readonly onDidChangeCloudSketchStateEmitter = new Emitter<{
33+
uri: URI;
34+
state: CloudSketchState | undefined;
35+
}>();
2536
private readonly toDispose = new DisposableCollection(
2637
this.onDidChangeSessionEmitter,
27-
this.onDidChangeEnabledEmitter
38+
this.onDidChangeEnabledEmitter,
39+
this.onDidChangeCloudSketchStateEmitter
2840
);
2941
private _enabled: boolean;
3042
private _session: AuthenticationSession | undefined;
@@ -64,14 +76,55 @@ export class CreateFeatures implements FrontendApplicationContribution {
6476
return this.onDidChangeEnabledEmitter.event;
6577
}
6678

67-
get enabled(): boolean {
68-
return this._enabled;
79+
get onDidChangeCloudSketchState(): Event<{
80+
uri: URI;
81+
state: CloudSketchState | undefined;
82+
}> {
83+
return this.onDidChangeCloudSketchStateEmitter.event;
6984
}
7085

7186
get session(): AuthenticationSession | undefined {
7287
return this._session;
7388
}
7489

90+
get enabled(): boolean {
91+
return this._enabled;
92+
}
93+
94+
get cloudSketchStates(): {
95+
uri: URI;
96+
state: CloudSketchState | undefined;
97+
}[] {
98+
return Array.from(this._cloudSketchStates.entries()).map(
99+
([uri, state]) => ({ uri: new URI(uri), state })
100+
);
101+
}
102+
103+
cloudSketchState(uri: URI): CloudSketchState | undefined {
104+
return this._cloudSketchStates.get(uri.toString());
105+
}
106+
107+
setCloudSketchState(uri: URI, state: CloudSketchState | undefined): void {
108+
if (uri.scheme !== CreateUri.scheme) {
109+
throw new Error(
110+
`Expected a URI with '${uri.scheme}' scheme. Got: ${uri.toString()}`
111+
);
112+
}
113+
const key = uri.toString();
114+
if (!state) {
115+
if (!this._cloudSketchStates.delete(key)) {
116+
console.warn(
117+
`Could not reset the cloud sketch state of ${key}. No state existed for the the cloud sketch.`
118+
);
119+
} else {
120+
this.onDidChangeCloudSketchStateEmitter.fire({ uri, state: undefined });
121+
}
122+
} else {
123+
this._cloudSketchStates.set(key, state);
124+
this.onDidChangeCloudSketchStateEmitter.fire({ uri, state });
125+
}
126+
}
127+
75128
/**
76129
* `true` if the sketch is under `directories.data/RemoteSketchbook`. Otherwise, `false`.
77130
* Returns with `undefined` if `dataDirUri` is `undefined`.
@@ -83,7 +136,10 @@ export class CreateFeatures implements FrontendApplicationContribution {
83136
);
84137
return undefined;
85138
}
86-
return dataDirUri.isEqualOrParent(new URI(sketch.uri));
139+
return dataDirUri
140+
.resolve('RemoteSketchbook')
141+
.resolve('ArduinoCloud')
142+
.isEqualOrParent(new URI(sketch.uri));
87143
}
88144

89145
cloudUri(sketch: Sketch): URI | undefined {

Diff for: arduino-ide-extension/src/browser/create/create-fs-provider.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { CreateUri } from './create-uri';
2929
import { SketchesService } from '../../common/protocol';
3030
import { ArduinoPreferences } from '../arduino-preferences';
3131
import { Create } from './typings';
32+
import { stringToUint8Array } from '../../common/utils';
3233

3334
@injectable()
3435
export class CreateFsProvider
@@ -154,7 +155,7 @@ export class CreateFsProvider
154155

155156
async readFile(uri: URI): Promise<Uint8Array> {
156157
const content = await this.getCreateApi.readFile(uri.path.toString());
157-
return new TextEncoder().encode(content);
158+
return stringToUint8Array(content);
158159
}
159160

160161
async writeFile(
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)