Skip to content

Commit 5b7a045

Browse files
author
Akos Kitta
committed
fix: disabled rename/delete cloud sketch folder if not logged in
Signed-off-by: Akos Kitta <[email protected]>
1 parent 30ddd95 commit 5b7a045

File tree

5 files changed

+192
-91
lines changed

5 files changed

+192
-91
lines changed

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

+47-88
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
1-
import { inject, injectable } from '@theia/core/shared/inversify';
21
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
3-
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
4-
import { WorkspaceCommands } from '@theia/workspace/lib/browser';
52
import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer';
3+
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
64
import {
75
Disposable,
86
DisposableCollection,
97
} from '@theia/core/lib/common/disposable';
8+
import { nls } from '@theia/core/lib/common/nls';
9+
import { inject, injectable } from '@theia/core/shared/inversify';
10+
import { WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
11+
import { ArduinoMenus } from '../menu/arduino-menus';
12+
import { CurrentSketch } from '../sketches-service-client-impl';
1013
import {
11-
URI,
12-
SketchContribution,
1314
Command,
1415
CommandRegistry,
15-
MenuModelRegistry,
1616
KeybindingRegistry,
17-
TabBarToolbarRegistry,
17+
MenuModelRegistry,
1818
open,
19+
SketchContribution,
20+
TabBarToolbarRegistry,
21+
URI,
1922
} from './contribution';
20-
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
21-
import { CurrentSketch } from '../sketches-service-client-impl';
22-
import { nls } from '@theia/core/lib/common';
2323

2424
@injectable()
2525
export class SketchControl extends SketchContribution {
2626
@inject(ApplicationShell)
2727
private readonly shell: ApplicationShell;
28-
2928
@inject(MenuModelRegistry)
3029
private readonly menuRegistry: MenuModelRegistry;
31-
3230
@inject(ContextMenuRenderer)
3331
private readonly contextMenuRenderer: ContextMenuRenderer;
3432

@@ -43,97 +41,57 @@ export class SketchControl extends SketchContribution {
4341
this.shell.getWidgets('main').indexOf(widget) !== -1,
4442
execute: async () => {
4543
this.toDisposeBeforeCreateNewContextMenu.dispose();
46-
const sketch = await this.sketchServiceClient.currentSketch();
47-
if (!CurrentSketch.isValid(sketch)) {
48-
return;
49-
}
5044

45+
let parentElement: HTMLElement | undefined = undefined;
5146
const target = document.getElementById(
5247
SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id
5348
);
54-
if (!(target instanceof HTMLElement)) {
55-
return;
49+
if (target instanceof HTMLElement) {
50+
parentElement = target.parentElement ?? undefined;
5651
}
57-
const { parentElement } = target;
5852
if (!parentElement) {
5953
return;
6054
}
6155

62-
const { mainFileUri, rootFolderFileUris } = sketch;
63-
const uris = [mainFileUri, ...rootFolderFileUris];
56+
const sketch = await this.sketchServiceClient.currentSketch();
57+
if (!CurrentSketch.isValid(sketch)) {
58+
return;
59+
}
6460

65-
const parentSketchUri = this.editorManager.currentEditor
66-
?.getResourceUri()
67-
?.toString();
68-
const parentSketch = await this.sketchesService.getSketchFolder(
69-
parentSketchUri || ''
61+
this.menuRegistry.registerMenuAction(
62+
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
63+
{
64+
commandId: WorkspaceCommands.FILE_RENAME.id,
65+
label: nls.localize('vscode/fileActions/rename', 'Rename'),
66+
order: '1',
67+
}
7068
);
71-
72-
// if the current file is in the current opened sketch, show extra menus
73-
if (sketch && parentSketch && parentSketch.uri === sketch.uri) {
74-
this.menuRegistry.registerMenuAction(
75-
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
76-
{
77-
commandId: WorkspaceCommands.FILE_RENAME.id,
78-
label: nls.localize('vscode/fileActions/rename', 'Rename'),
79-
order: '1',
80-
}
81-
);
82-
this.toDisposeBeforeCreateNewContextMenu.push(
83-
Disposable.create(() =>
84-
this.menuRegistry.unregisterMenuAction(
85-
WorkspaceCommands.FILE_RENAME
86-
)
69+
this.toDisposeBeforeCreateNewContextMenu.push(
70+
Disposable.create(() =>
71+
this.menuRegistry.unregisterMenuAction(
72+
WorkspaceCommands.FILE_RENAME
8773
)
88-
);
89-
} else {
90-
const renamePlaceholder = new PlaceholderMenuNode(
91-
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
92-
nls.localize('vscode/fileActions/rename', 'Rename')
93-
);
94-
this.menuRegistry.registerMenuNode(
95-
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
96-
renamePlaceholder
97-
);
98-
this.toDisposeBeforeCreateNewContextMenu.push(
99-
Disposable.create(() =>
100-
this.menuRegistry.unregisterMenuNode(renamePlaceholder.id)
101-
)
102-
);
103-
}
74+
)
75+
);
10476

105-
if (sketch && parentSketch && parentSketch.uri === sketch.uri) {
106-
this.menuRegistry.registerMenuAction(
107-
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
108-
{
109-
commandId: WorkspaceCommands.FILE_DELETE.id, // TODO: customize delete. Wipe sketch if deleting main file. Close window.
110-
label: nls.localize('vscode/fileActions/delete', 'Delete'),
111-
order: '2',
112-
}
113-
);
114-
this.toDisposeBeforeCreateNewContextMenu.push(
115-
Disposable.create(() =>
116-
this.menuRegistry.unregisterMenuAction(
117-
WorkspaceCommands.FILE_DELETE
118-
)
119-
)
120-
);
121-
} else {
122-
const deletePlaceholder = new PlaceholderMenuNode(
123-
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
124-
nls.localize('vscode/fileActions/delete', 'Delete')
125-
);
126-
this.menuRegistry.registerMenuNode(
127-
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
128-
deletePlaceholder
129-
);
130-
this.toDisposeBeforeCreateNewContextMenu.push(
131-
Disposable.create(() =>
132-
this.menuRegistry.unregisterMenuNode(deletePlaceholder.id)
77+
this.menuRegistry.registerMenuAction(
78+
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
79+
{
80+
commandId: WorkspaceCommands.FILE_DELETE.id,
81+
label: nls.localize('vscode/fileActions/delete', 'Delete'),
82+
order: '2',
83+
}
84+
);
85+
this.toDisposeBeforeCreateNewContextMenu.push(
86+
Disposable.create(() =>
87+
this.menuRegistry.unregisterMenuAction(
88+
WorkspaceCommands.FILE_DELETE
13389
)
134-
);
135-
}
90+
)
91+
);
13692

93+
const { mainFileUri, rootFolderFileUris } = sketch;
94+
const uris = [mainFileUri, ...rootFolderFileUris];
13795
for (let i = 0; i < uris.length; i++) {
13896
const uri = new URI(uris[i]);
13997

@@ -169,6 +127,7 @@ export class SketchControl extends SketchContribution {
169127
parentElement.getBoundingClientRect().top +
170128
parentElement.offsetHeight,
171129
},
130+
showDisabled: true,
172131
};
173132
this.contextMenuRenderer.render(options);
174133
},

Diff for: arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts

+78
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut
251251
delegate,
252252
this.selectionService,
253253
this.shell,
254+
this.sketchesServiceClient,
255+
this.configServiceClient,
256+
this.createFeatures,
254257
{ multi }
255258
);
256259
}
@@ -356,6 +359,9 @@ class UriAwareCommandHandlerWithCurrentEditorFallback<
356359
delegate: UriCommandHandler<T>,
357360
selectionService: SelectionService,
358361
private readonly shell: ApplicationShell,
362+
private readonly sketchesServiceClient: SketchesServiceClientImpl,
363+
private readonly configServiceClient: ConfigServiceClient,
364+
private readonly createFeatures: CreateFeatures,
359365
options?: UriAwareCommandHandler.Options
360366
) {
361367
super(selectionService, delegate, options);
@@ -373,6 +379,24 @@ class UriAwareCommandHandlerWithCurrentEditorFallback<
373379
return uri;
374380
}
375381

382+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
383+
override isEnabled(...args: any[]): boolean {
384+
const [uri, ...others] = this.getArgsWithUri(...args);
385+
if (uri) {
386+
if (!this.isInSketch(uri)) {
387+
return false;
388+
}
389+
if (this.affectsCloudSketchFolderWhenSignedOut(uri)) {
390+
return false;
391+
}
392+
if (this.handler.isEnabled) {
393+
return this.handler.isEnabled(uri, ...others);
394+
}
395+
return true;
396+
}
397+
return false;
398+
}
399+
376400
// The `currentEditor` is broken after a rename. (https://github.com/eclipse-theia/theia/issues/12139)
377401
// `ApplicationShell#currentWidget` might provide a wrong result just as the `getFocusedCodeEditor` and `getFocusedCodeEditor` of the `MonacoEditorService`
378402
// Try to extract the URI from the current title of the main panel if it's an editor widget.
@@ -382,4 +406,58 @@ class UriAwareCommandHandlerWithCurrentEditorFallback<
382406
? owner.editor.getResourceUri()
383407
: undefined;
384408
}
409+
410+
private isInSketch(uri: T | undefined): boolean {
411+
if (!uri) {
412+
return false;
413+
}
414+
const sketch = this.sketchesServiceClient.tryGetCurrentSketch();
415+
if (!CurrentSketch.isValid(sketch)) {
416+
return false;
417+
}
418+
if (this.isMulti() && Array.isArray(uri)) {
419+
return uri.every((u) => Sketch.isInSketch(u, sketch));
420+
}
421+
if (!this.isMulti() && uri instanceof URI) {
422+
return Sketch.isInSketch(uri, sketch);
423+
}
424+
return false;
425+
}
426+
427+
/**
428+
* If the user is not logged in, deleting/renaming the main sketch file or the sketch folder of a cloud sketch is disabled.
429+
*/
430+
private affectsCloudSketchFolderWhenSignedOut(uri: T | undefined): boolean {
431+
return (
432+
!Boolean(this.createFeatures.session) &&
433+
Boolean(this.isCurrentSketchCloud()) &&
434+
this.affectsSketchFolder(uri)
435+
);
436+
}
437+
438+
private affectsSketchFolder(uri: T | undefined): boolean {
439+
if (!uri) {
440+
return false;
441+
}
442+
const sketch = this.sketchesServiceClient.tryGetCurrentSketch();
443+
if (!CurrentSketch.isValid(sketch)) {
444+
return false;
445+
}
446+
if (this.isMulti() && Array.isArray(uri)) {
447+
return uri.map((u) => u.toString()).includes(sketch.mainFileUri);
448+
}
449+
if (!this.isMulti()) {
450+
return sketch.mainFileUri === uri.toString();
451+
}
452+
return false;
453+
}
454+
455+
private isCurrentSketchCloud(): boolean | undefined {
456+
const sketch = this.sketchesServiceClient.tryGetCurrentSketch();
457+
if (!CurrentSketch.isValid(sketch)) {
458+
return false;
459+
}
460+
const dataDirUri = this.configServiceClient.tryGetDataDirUri();
461+
return this.createFeatures.isCloud(sketch, dataDirUri);
462+
}
385463
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { webFrame } from '@theia/core/electron-shared/electron/';
2+
import {
3+
ContextMenuAccess,
4+
coordinateFromAnchor,
5+
RenderContextMenuOptions,
6+
} from '@theia/core/lib/browser/context-menu-renderer';
7+
import {
8+
ElectronContextMenuAccess,
9+
ElectronContextMenuRenderer as TheiaElectronContextMenuRenderer,
10+
} from '@theia/core/lib/electron-browser/menu/electron-context-menu-renderer';
11+
import { injectable } from '@theia/core/shared/inversify';
12+
13+
@injectable()
14+
export class ElectronContextMenuRenderer extends TheiaElectronContextMenuRenderer {
15+
protected override doRender(
16+
options: RenderContextMenuOptions
17+
): ContextMenuAccess {
18+
if (this.useNativeStyle) {
19+
const { menuPath, anchor, args, onHide, context } = options;
20+
const menu = this['electronMenuFactory'].createElectronContextMenu(
21+
menuPath,
22+
args,
23+
context,
24+
this.showDisabled(options)
25+
);
26+
const { x, y } = coordinateFromAnchor(anchor);
27+
const zoom = webFrame.getZoomFactor();
28+
// TODO: Remove the offset once Electron fixes https://github.com/electron/electron/issues/31641
29+
const offset = process.platform === 'win32' ? 0 : 2;
30+
// x and y values must be Ints or else there is a conversion error
31+
menu.popup({
32+
x: Math.round(x * zoom) + offset,
33+
y: Math.round(y * zoom) + offset,
34+
});
35+
// native context menu stops the event loop, so there is no keyboard events
36+
this.context.resetAltPressed();
37+
if (onHide) {
38+
menu.once('menu-will-close', () => onHide());
39+
}
40+
return new ElectronContextMenuAccess(menu);
41+
} else {
42+
return super.doRender(options);
43+
}
44+
}
45+
46+
/**
47+
* Theia does not allow selectively control whether disabled menu items are visible or not. This is a workaround.
48+
* Attach the `showDisabled: true` to the `RenderContextMenuOptions` object, and you can control it.
49+
* https://github.com/eclipse-theia/theia/blob/d59d5279b93e5050c2cbdd4b6726cab40187c50e/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts#L134.
50+
*/
51+
private showDisabled(options: RenderContextMenuOptions): boolean {
52+
if ('showDisabled' in options) {
53+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
54+
const object = options as any;
55+
return Boolean(object['showDisabled']);
56+
}
57+
return false;
58+
}
59+
}

Diff for: arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,12 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
7474
menuPath: MenuPath,
7575
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7676
args?: any[],
77-
context?: HTMLElement
77+
context?: HTMLElement,
78+
showDisabled?: boolean
7879
): Electron.Menu {
7980
const menuModel = this.menuProvider.getMenu(menuPath);
8081
const template = this.fillMenuTemplate([], menuModel, args, {
81-
showDisabled: false,
82+
showDisabled,
8283
context,
8384
rootMenuPath: menuPath,
8485
});

Diff for: arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
import { ContainerModule } from '@theia/core/shared/inversify';
1+
import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer';
22
import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
33
import { ElectronMenuContribution as TheiaElectronMenuContribution } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution';
4+
import { ContainerModule } from '@theia/core/shared/inversify';
45
import { MainMenuManager } from '../../../common/main-menu-manager';
6+
import { ElectronContextMenuRenderer } from './electron-context-menu-renderer';
57
import { ElectronMainMenuFactory } from './electron-main-menu-factory';
68
import { ElectronMenuContribution } from './electron-menu-contribution';
79

810
export default new ContainerModule((bind, unbind, isBound, rebind) => {
911
bind(ElectronMenuContribution).toSelf().inSingletonScope();
1012
bind(MainMenuManager).toService(ElectronMenuContribution);
13+
bind(ElectronContextMenuRenderer).toSelf().inSingletonScope();
14+
rebind(ContextMenuRenderer).toService(ElectronContextMenuRenderer);
1115
rebind(TheiaElectronMenuContribution).toService(ElectronMenuContribution);
1216
bind(ElectronMainMenuFactory).toSelf().inSingletonScope();
1317
rebind(TheiaElectronMainMenuFactory).toService(ElectronMainMenuFactory);

0 commit comments

Comments
 (0)