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 <a.kitta@arduino.cc>
1 parent 01ee045 commit 9dade8e

38 files changed

+1085
-432
lines changed

arduino-ide-extension/arduino-icons.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

arduino-ide-extension/package.json

Lines changed: 1 addition & 0 deletions
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",

arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

Lines changed: 7 additions & 2 deletions
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
});

arduino-ide-extension/src/browser/contributions/account.ts

Lines changed: 14 additions & 4 deletions
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

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

Lines changed: 1 addition & 1 deletion
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)) {

arduino-ide-extension/src/browser/contributions/contribution.ts

Lines changed: 2 additions & 3 deletions
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,

arduino-ide-extension/src/browser/create/create-api.ts

Lines changed: 5 additions & 48 deletions
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) };

arduino-ide-extension/src/browser/create/create-features.ts

Lines changed: 60 additions & 4 deletions
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 {

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

Lines changed: 2 additions & 1 deletion
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(

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy