Skip to content

Commit 4deaf4f

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
feat: moved login entry point to the side-bar
Closes #1877 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
1 parent d68bc4a commit 4deaf4f

13 files changed

+278
-114
lines changed

Diff for: arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx

+2-7
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ import {
1010
MenuContribution,
1111
MenuModelRegistry,
1212
} from '@theia/core';
13-
import {
14-
FrontendApplication,
15-
FrontendApplicationContribution,
16-
} from '@theia/core/lib/browser';
13+
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
1714
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
1815
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
1916
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
@@ -77,7 +74,7 @@ export class ArduinoFrontendContribution
7774
}
7875
}
7976

80-
onStart(app: FrontendApplication): void {
77+
onStart(): void {
8178
this.electronWindowPreferences.onPreferenceChanged((event) => {
8279
if (event.newValue !== event.oldValue) {
8380
switch (event.preferenceName) {
@@ -98,8 +95,6 @@ export class ArduinoFrontendContribution
9895
webContents.setZoomLevel(zoomLevel);
9996
})
10097
);
101-
// Removes the _Settings_ (cog) icon from the left sidebar
102-
app.shell.leftPanelHandler.removeBottomMenu('settings-menu');
10398
}
10499

105100
registerToolbarItems(registry: TabBarToolbarRegistry): void {

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

+7
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ import { ConfigServiceClient } from './config/config-service-client';
347347
import { ValidateSketch } from './contributions/validate-sketch';
348348
import { RenameCloudSketch } from './contributions/rename-cloud-sketch';
349349
import { CreateFeatures } from './create/create-features';
350+
import { Account } from './contributions/account';
351+
import { SidebarBottomMenuWidget } from './theia/core/sidebar-bottom-menu-widget';
352+
import { SidebarBottomMenuWidget as TheiaSidebarBottomMenuWidget } from '@theia/core/lib/browser/shell/sidebar-bottom-menu-widget';
350353

351354
export default new ContainerModule((bind, unbind, isBound, rebind) => {
352355
// Commands and toolbar items
@@ -734,6 +737,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
734737
Contribution.configure(bind, NewCloudSketch);
735738
Contribution.configure(bind, ValidateSketch);
736739
Contribution.configure(bind, RenameCloudSketch);
740+
Contribution.configure(bind, Account);
737741

738742
bindContributionProvider(bind, StartupTaskProvider);
739743
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
@@ -1014,4 +1018,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
10141018
},
10151019
}))
10161020
.inSingletonScope();
1021+
1022+
bind(SidebarBottomMenuWidget).toSelf();
1023+
rebind(TheiaSidebarBottomMenuWidget).toService(SidebarBottomMenuWidget);
10171024
});

Diff for: arduino-ide-extension/src/browser/auth/authentication-client-service.ts

+4
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,13 @@ export class AuthenticationClientService
8383
registerCommands(registry: CommandRegistry): void {
8484
registry.registerCommand(CloudUserCommands.LOGIN, {
8585
execute: () => this.service.login(),
86+
isEnabled: () => !this._session,
87+
isVisible: () => !this._session,
8688
});
8789
registry.registerCommand(CloudUserCommands.LOGOUT, {
8890
execute: () => this.service.logout(),
91+
isEnabled: () => !!this._session,
92+
isVisible: () => !!this._session,
8993
});
9094
}
9195

Diff for: arduino-ide-extension/src/browser/auth/cloud-user-commands.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Command } from '@theia/core/lib/common/command';
22

3+
export const LEARN_MORE_URL =
4+
'https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-cloud-sketch-sync';
5+
36
export namespace CloudUserCommands {
47
export const LOGIN = Command.toLocalizedCommand(
58
{
@@ -16,9 +19,4 @@ export namespace CloudUserCommands {
1619
},
1720
'arduino/cloud/signOut'
1821
);
19-
20-
export const OPEN_PROFILE_CONTEXT_MENU: Command = {
21-
id: 'arduino-cloud-sketchbook--open-profile-menu',
22-
label: 'Contextual menu',
23-
};
2422
}
+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
2+
import { SidebarMenu } from '@theia/core/lib/browser/shell/sidebar-menu-widget';
3+
import { WindowService } from '@theia/core/lib/browser/window/window-service';
4+
import { DisposableCollection } from '@theia/core/lib/common/disposable';
5+
import { MenuPath } from '@theia/core/lib/common/menu';
6+
import { nls } from '@theia/core/lib/common/nls';
7+
import { inject, injectable } from '@theia/core/shared/inversify';
8+
import { CloudUserCommands, LEARN_MORE_URL } from '../auth/cloud-user-commands';
9+
import { CreateFeatures } from '../create/create-features';
10+
import { ArduinoMenus } from '../menu/arduino-menus';
11+
import {
12+
Command,
13+
CommandRegistry,
14+
Contribution,
15+
MenuModelRegistry,
16+
} from './contribution';
17+
18+
export const accountMenu: SidebarMenu = {
19+
id: 'arduino-accounts-menu',
20+
iconClass: 'codicon codicon-account',
21+
title: nls.localize('arduino/account/menuTitle', 'Arduino Cloud'),
22+
menuPath: ArduinoMenus.ARDUINO_ACCOUNT__CONTEXT,
23+
order: 0,
24+
};
25+
26+
@injectable()
27+
export class Account extends Contribution {
28+
@inject(WindowService)
29+
private readonly windowService: WindowService;
30+
@inject(CreateFeatures)
31+
private readonly createFeatures: CreateFeatures;
32+
33+
private readonly toDispose = new DisposableCollection();
34+
private app: FrontendApplication;
35+
36+
override onStart(app: FrontendApplication): void {
37+
this.app = app;
38+
this.updateSidebarCommand();
39+
this.toDispose.push(
40+
this.createFeatures.onDidChangeEnabled((enabled) =>
41+
this.updateSidebarCommand(enabled)
42+
)
43+
);
44+
}
45+
46+
onStop(): void {
47+
this.toDispose.dispose();
48+
}
49+
50+
override registerCommands(registry: CommandRegistry): void {
51+
const openExternal = (url: string) =>
52+
this.windowService.openNewWindow(url, { external: true });
53+
registry.registerCommand(Account.Commands.LEARN_MORE, {
54+
execute: () => openExternal(LEARN_MORE_URL),
55+
isEnabled: () => !Boolean(this.createFeatures.session),
56+
});
57+
registry.registerCommand(Account.Commands.GO_TO_PROFILE, {
58+
execute: () => openExternal('https://id.arduino.cc/'),
59+
isEnabled: () => Boolean(this.createFeatures.session),
60+
});
61+
registry.registerCommand(Account.Commands.GO_TO_CLOUD_EDITOR, {
62+
execute: () => openExternal('https://create.arduino.cc/editor'),
63+
isEnabled: () => Boolean(this.createFeatures.session),
64+
});
65+
registry.registerCommand(Account.Commands.GO_TO_IOT_CLOUD, {
66+
execute: () => openExternal('https://create.arduino.cc/iot/'),
67+
isEnabled: () => Boolean(this.createFeatures.session),
68+
});
69+
}
70+
71+
override registerMenus(registry: MenuModelRegistry): void {
72+
const register = (
73+
menuPath: MenuPath,
74+
...commands: (Command | [command: Command, menuLabel: string])[]
75+
) =>
76+
commands.forEach((command, index) => {
77+
const commandId = Array.isArray(command) ? command[0].id : command.id;
78+
const label = Array.isArray(command) ? command[1] : command.label;
79+
registry.registerMenuAction(menuPath, {
80+
label,
81+
commandId,
82+
order: String(index),
83+
});
84+
});
85+
86+
register(ArduinoMenus.ARDUINO_ACCOUNT__CONTEXT__SIGN_IN_GROUP, [
87+
CloudUserCommands.LOGIN,
88+
nls.localize('arduino/cloud/signInToCloud', 'Sign in to Arduino Cloud'),
89+
]);
90+
register(ArduinoMenus.ARDUINO_ACCOUNT__CONTEXT__LEARN_MORE_GROUP, [
91+
Account.Commands.LEARN_MORE,
92+
nls.localize('arduino/cloud/learnMore', 'Learn more'),
93+
]);
94+
register(
95+
ArduinoMenus.ARDUINO_ACCOUNT__CONTEXT__GO_TO_GROUP,
96+
[
97+
Account.Commands.GO_TO_PROFILE,
98+
nls.localize('arduino/account/goToProfile', 'Go to Profile'),
99+
],
100+
[
101+
Account.Commands.GO_TO_CLOUD_EDITOR,
102+
nls.localize('arduino/account/goToCloudEditor', 'Go to Cloud Editor'),
103+
],
104+
[
105+
Account.Commands.GO_TO_IOT_CLOUD,
106+
nls.localize('arduino/account/goToIoTCloud', 'Go to IoT Cloud'),
107+
]
108+
);
109+
register(
110+
ArduinoMenus.ARDUINO_ACCOUNT__CONTEXT__SIGN_OUT_GROUP,
111+
CloudUserCommands.LOGOUT
112+
);
113+
}
114+
115+
private updateSidebarCommand(
116+
visible: boolean = this.preferences['arduino.cloud.enabled']
117+
): void {
118+
if (!this.app) {
119+
return;
120+
}
121+
const handler = this.app.shell.leftPanelHandler;
122+
if (visible) {
123+
handler.addBottomMenu(accountMenu);
124+
} else {
125+
handler.removeBottomMenu(accountMenu.id);
126+
}
127+
}
128+
}
129+
130+
export namespace Account {
131+
export namespace Commands {
132+
export const GO_TO_PROFILE: Command = {
133+
id: 'arduino-go-to-profile',
134+
};
135+
export const GO_TO_CLOUD_EDITOR: Command = {
136+
id: 'arduino-go-to-cloud-editor',
137+
};
138+
export const GO_TO_IOT_CLOUD: Command = {
139+
id: 'arduino-go-to-iot-cloud',
140+
};
141+
export const LEARN_MORE: Command = {
142+
id: 'arduino-learn-more',
143+
};
144+
}
145+
}

Diff for: arduino-ide-extension/src/browser/menu/arduino-menus.ts

+19
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,25 @@ export namespace ArduinoMenus {
154154
'2_resources',
155155
];
156156

157+
// -- Account
158+
export const ARDUINO_ACCOUNT__CONTEXT = ['arduino-account--context'];
159+
export const ARDUINO_ACCOUNT__CONTEXT__SIGN_IN_GROUP = [
160+
...ARDUINO_ACCOUNT__CONTEXT,
161+
'0_sign_in',
162+
];
163+
export const ARDUINO_ACCOUNT__CONTEXT__LEARN_MORE_GROUP = [
164+
...ARDUINO_ACCOUNT__CONTEXT,
165+
'1_learn_more',
166+
];
167+
export const ARDUINO_ACCOUNT__CONTEXT__GO_TO_GROUP = [
168+
...ARDUINO_ACCOUNT__CONTEXT,
169+
'2_go_to',
170+
];
171+
export const ARDUINO_ACCOUNT__CONTEXT__SIGN_OUT_GROUP = [
172+
...ARDUINO_ACCOUNT__CONTEXT,
173+
'3_sign_out',
174+
];
175+
157176
// -- ROOT SSL CERTIFICATES
158177
export const ROOT_CERTIFICATES__CONTEXT = [
159178
'arduino-root-certificates--context',

Diff for: arduino-ide-extension/src/browser/style/cloud-sketchbook.css

+2-2
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@
119119

120120
.account-icon {
121121
background: url("./account-icon.svg") center center no-repeat;
122-
width: var(--theia-icon-size);
123-
height: var(--theia-icon-size);
122+
width: var(--theia-private-sidebar-icon-size);
123+
height: var(--theia-private-sidebar-icon-size);
124124
border-radius: 50%;
125125
overflow: hidden;
126126
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { SidebarBottomMenuWidget as TheiaSidebarBottomMenuWidget } from '@theia/core/lib/browser/shell/sidebar-bottom-menu-widget';
2+
import type { SidebarMenu } from '@theia/core/lib/browser/shell/sidebar-menu-widget';
3+
import type { MenuPath } from '@theia/core/lib/common/menu';
4+
import { nls } from '@theia/core/lib/common/nls';
5+
import {
6+
inject,
7+
injectable,
8+
postConstruct,
9+
} from '@theia/core/shared/inversify';
10+
import * as React from '@theia/core/shared/react';
11+
import { accountMenu } from '../../contributions/account';
12+
import { CreateFeatures } from '../../create/create-features';
13+
14+
@injectable()
15+
export class SidebarBottomMenuWidget extends TheiaSidebarBottomMenuWidget {
16+
@inject(CreateFeatures)
17+
private readonly createFeatures: CreateFeatures;
18+
19+
@postConstruct()
20+
protected init(): void {
21+
this.toDispose.push(
22+
this.createFeatures.onDidChangeSession(() => this.update())
23+
);
24+
}
25+
26+
protected override onClick(
27+
e: React.MouseEvent<HTMLElement, MouseEvent>,
28+
menuPath: MenuPath
29+
): void {
30+
const button = e.currentTarget.getBoundingClientRect();
31+
this.contextMenuRenderer.render({
32+
menuPath,
33+
includeAnchorArg: false,
34+
anchor: {
35+
x: button.left + button.width,
36+
// Bogus y coordinate?
37+
// https://github.com/eclipse-theia/theia/discussions/12170
38+
y: button.top,
39+
},
40+
});
41+
}
42+
43+
protected override render(): React.ReactNode {
44+
return (
45+
<React.Fragment>
46+
{this.menus.map((menu) => this.renderMenu(menu))}
47+
</React.Fragment>
48+
);
49+
}
50+
51+
private renderMenu(menu: SidebarMenu): React.ReactNode {
52+
// Removes the _Settings_ (cog) icon from the left sidebar
53+
if (menu.id === 'settings-menu') {
54+
return undefined;
55+
}
56+
const arduinoAccount = menu.id === accountMenu.id;
57+
const picture =
58+
arduinoAccount && this.createFeatures.session?.account.picture;
59+
const className = typeof picture === 'string' ? undefined : menu.iconClass;
60+
return (
61+
<i
62+
key={menu.id}
63+
className={className}
64+
title={menu.title}
65+
onClick={(e) => this.onClick(e, menu.menuPath)}
66+
onMouseDown={this.onMouseDown}
67+
onMouseOut={this.onMouseOut}
68+
>
69+
{picture && (
70+
<div className="account-icon">
71+
<img
72+
src={picture}
73+
alt={nls.localize(
74+
'arduino/cloud/profilePicture',
75+
'Profile picture'
76+
)}
77+
/>
78+
</div>
79+
)}
80+
</i>
81+
);
82+
}
83+
}

Diff for: arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
injectable,
66
postConstruct,
77
} from '@theia/core/shared/inversify';
8-
import { UserStatus } from './cloud-user-status';
8+
import { CloudStatus } from './cloud-user-status';
99
import { nls } from '@theia/core/lib/common/nls';
1010
import { CloudSketchbookTreeWidget } from './cloud-sketchbook-tree-widget';
1111
import { AuthenticationClientService } from '../../auth/authentication-client-service';
@@ -61,7 +61,7 @@ export class CloudSketchbookCompositeWidget extends BaseSketchbookCompositeWidge
6161
onClick={this.onDidClickCreateNew}
6262
/>
6363
)}
64-
<UserStatus
64+
<CloudStatus
6565
model={
6666
this.cloudSketchbookTreeWidget.model as CloudSketchbookTreeModel
6767
}

0 commit comments

Comments
 (0)