Skip to content

Commit

Permalink
fix: disabled rename/delete cloud sketch folder if not logged in
Browse files Browse the repository at this point in the history
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
  • Loading branch information
Akos Kitta committed Feb 3, 2023
1 parent 30ddd95 commit 5b7a045
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 91 deletions.
135 changes: 47 additions & 88 deletions arduino-ide-extension/src/browser/contributions/sketch-control.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
import { inject, injectable } from '@theia/core/shared/inversify';
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
import { WorkspaceCommands } from '@theia/workspace/lib/browser';
import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
import {
Disposable,
DisposableCollection,
} from '@theia/core/lib/common/disposable';
import { nls } from '@theia/core/lib/common/nls';
import { inject, injectable } from '@theia/core/shared/inversify';
import { WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
import { ArduinoMenus } from '../menu/arduino-menus';
import { CurrentSketch } from '../sketches-service-client-impl';
import {
URI,
SketchContribution,
Command,
CommandRegistry,
MenuModelRegistry,
KeybindingRegistry,
TabBarToolbarRegistry,
MenuModelRegistry,
open,
SketchContribution,
TabBarToolbarRegistry,
URI,
} from './contribution';
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
import { CurrentSketch } from '../sketches-service-client-impl';
import { nls } from '@theia/core/lib/common';

@injectable()
export class SketchControl extends SketchContribution {
@inject(ApplicationShell)
private readonly shell: ApplicationShell;

@inject(MenuModelRegistry)
private readonly menuRegistry: MenuModelRegistry;

@inject(ContextMenuRenderer)
private readonly contextMenuRenderer: ContextMenuRenderer;

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

let parentElement: HTMLElement | undefined = undefined;
const target = document.getElementById(
SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id
);
if (!(target instanceof HTMLElement)) {
return;
if (target instanceof HTMLElement) {
parentElement = target.parentElement ?? undefined;
}
const { parentElement } = target;
if (!parentElement) {
return;
}

const { mainFileUri, rootFolderFileUris } = sketch;
const uris = [mainFileUri, ...rootFolderFileUris];
const sketch = await this.sketchServiceClient.currentSketch();
if (!CurrentSketch.isValid(sketch)) {
return;
}

const parentSketchUri = this.editorManager.currentEditor
?.getResourceUri()
?.toString();
const parentSketch = await this.sketchesService.getSketchFolder(
parentSketchUri || ''
this.menuRegistry.registerMenuAction(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
{
commandId: WorkspaceCommands.FILE_RENAME.id,
label: nls.localize('vscode/fileActions/rename', 'Rename'),
order: '1',
}
);

// if the current file is in the current opened sketch, show extra menus
if (sketch && parentSketch && parentSketch.uri === sketch.uri) {
this.menuRegistry.registerMenuAction(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
{
commandId: WorkspaceCommands.FILE_RENAME.id,
label: nls.localize('vscode/fileActions/rename', 'Rename'),
order: '1',
}
);
this.toDisposeBeforeCreateNewContextMenu.push(
Disposable.create(() =>
this.menuRegistry.unregisterMenuAction(
WorkspaceCommands.FILE_RENAME
)
this.toDisposeBeforeCreateNewContextMenu.push(
Disposable.create(() =>
this.menuRegistry.unregisterMenuAction(
WorkspaceCommands.FILE_RENAME
)
);
} else {
const renamePlaceholder = new PlaceholderMenuNode(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
nls.localize('vscode/fileActions/rename', 'Rename')
);
this.menuRegistry.registerMenuNode(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
renamePlaceholder
);
this.toDisposeBeforeCreateNewContextMenu.push(
Disposable.create(() =>
this.menuRegistry.unregisterMenuNode(renamePlaceholder.id)
)
);
}
)
);

if (sketch && parentSketch && parentSketch.uri === sketch.uri) {
this.menuRegistry.registerMenuAction(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
{
commandId: WorkspaceCommands.FILE_DELETE.id, // TODO: customize delete. Wipe sketch if deleting main file. Close window.
label: nls.localize('vscode/fileActions/delete', 'Delete'),
order: '2',
}
);
this.toDisposeBeforeCreateNewContextMenu.push(
Disposable.create(() =>
this.menuRegistry.unregisterMenuAction(
WorkspaceCommands.FILE_DELETE
)
)
);
} else {
const deletePlaceholder = new PlaceholderMenuNode(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
nls.localize('vscode/fileActions/delete', 'Delete')
);
this.menuRegistry.registerMenuNode(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
deletePlaceholder
);
this.toDisposeBeforeCreateNewContextMenu.push(
Disposable.create(() =>
this.menuRegistry.unregisterMenuNode(deletePlaceholder.id)
this.menuRegistry.registerMenuAction(
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
{
commandId: WorkspaceCommands.FILE_DELETE.id,
label: nls.localize('vscode/fileActions/delete', 'Delete'),
order: '2',
}
);
this.toDisposeBeforeCreateNewContextMenu.push(
Disposable.create(() =>
this.menuRegistry.unregisterMenuAction(
WorkspaceCommands.FILE_DELETE
)
);
}
)
);

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

Expand Down Expand Up @@ -169,6 +127,7 @@ export class SketchControl extends SketchContribution {
parentElement.getBoundingClientRect().top +
parentElement.offsetHeight,
},
showDisabled: true,
};
this.contextMenuRenderer.render(options);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut
delegate,
this.selectionService,
this.shell,
this.sketchesServiceClient,
this.configServiceClient,
this.createFeatures,
{ multi }
);
}
Expand Down Expand Up @@ -356,6 +359,9 @@ class UriAwareCommandHandlerWithCurrentEditorFallback<
delegate: UriCommandHandler<T>,
selectionService: SelectionService,
private readonly shell: ApplicationShell,
private readonly sketchesServiceClient: SketchesServiceClientImpl,
private readonly configServiceClient: ConfigServiceClient,
private readonly createFeatures: CreateFeatures,
options?: UriAwareCommandHandler.Options
) {
super(selectionService, delegate, options);
Expand All @@ -373,6 +379,24 @@ class UriAwareCommandHandlerWithCurrentEditorFallback<
return uri;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
override isEnabled(...args: any[]): boolean {
const [uri, ...others] = this.getArgsWithUri(...args);
if (uri) {
if (!this.isInSketch(uri)) {
return false;
}
if (this.affectsCloudSketchFolderWhenSignedOut(uri)) {
return false;
}
if (this.handler.isEnabled) {
return this.handler.isEnabled(uri, ...others);
}
return true;
}
return false;
}

// The `currentEditor` is broken after a rename. (https://github.com/eclipse-theia/theia/issues/12139)
// `ApplicationShell#currentWidget` might provide a wrong result just as the `getFocusedCodeEditor` and `getFocusedCodeEditor` of the `MonacoEditorService`
// Try to extract the URI from the current title of the main panel if it's an editor widget.
Expand All @@ -382,4 +406,58 @@ class UriAwareCommandHandlerWithCurrentEditorFallback<
? owner.editor.getResourceUri()
: undefined;
}

private isInSketch(uri: T | undefined): boolean {
if (!uri) {
return false;
}
const sketch = this.sketchesServiceClient.tryGetCurrentSketch();
if (!CurrentSketch.isValid(sketch)) {
return false;
}
if (this.isMulti() && Array.isArray(uri)) {
return uri.every((u) => Sketch.isInSketch(u, sketch));
}
if (!this.isMulti() && uri instanceof URI) {
return Sketch.isInSketch(uri, sketch);
}
return false;
}

/**
* If the user is not logged in, deleting/renaming the main sketch file or the sketch folder of a cloud sketch is disabled.
*/
private affectsCloudSketchFolderWhenSignedOut(uri: T | undefined): boolean {
return (
!Boolean(this.createFeatures.session) &&
Boolean(this.isCurrentSketchCloud()) &&
this.affectsSketchFolder(uri)
);
}

private affectsSketchFolder(uri: T | undefined): boolean {
if (!uri) {
return false;
}
const sketch = this.sketchesServiceClient.tryGetCurrentSketch();
if (!CurrentSketch.isValid(sketch)) {
return false;
}
if (this.isMulti() && Array.isArray(uri)) {
return uri.map((u) => u.toString()).includes(sketch.mainFileUri);
}
if (!this.isMulti()) {
return sketch.mainFileUri === uri.toString();
}
return false;
}

private isCurrentSketchCloud(): boolean | undefined {
const sketch = this.sketchesServiceClient.tryGetCurrentSketch();
if (!CurrentSketch.isValid(sketch)) {
return false;
}
const dataDirUri = this.configServiceClient.tryGetDataDirUri();
return this.createFeatures.isCloud(sketch, dataDirUri);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { webFrame } from '@theia/core/electron-shared/electron/';
import {
ContextMenuAccess,
coordinateFromAnchor,
RenderContextMenuOptions,
} from '@theia/core/lib/browser/context-menu-renderer';
import {
ElectronContextMenuAccess,
ElectronContextMenuRenderer as TheiaElectronContextMenuRenderer,
} from '@theia/core/lib/electron-browser/menu/electron-context-menu-renderer';
import { injectable } from '@theia/core/shared/inversify';

@injectable()
export class ElectronContextMenuRenderer extends TheiaElectronContextMenuRenderer {
protected override doRender(
options: RenderContextMenuOptions
): ContextMenuAccess {
if (this.useNativeStyle) {
const { menuPath, anchor, args, onHide, context } = options;
const menu = this['electronMenuFactory'].createElectronContextMenu(
menuPath,
args,
context,
this.showDisabled(options)
);
const { x, y } = coordinateFromAnchor(anchor);
const zoom = webFrame.getZoomFactor();
// TODO: Remove the offset once Electron fixes https://github.com/electron/electron/issues/31641
const offset = process.platform === 'win32' ? 0 : 2;
// x and y values must be Ints or else there is a conversion error
menu.popup({
x: Math.round(x * zoom) + offset,
y: Math.round(y * zoom) + offset,
});
// native context menu stops the event loop, so there is no keyboard events
this.context.resetAltPressed();
if (onHide) {
menu.once('menu-will-close', () => onHide());
}
return new ElectronContextMenuAccess(menu);
} else {
return super.doRender(options);
}
}

/**
* Theia does not allow selectively control whether disabled menu items are visible or not. This is a workaround.
* Attach the `showDisabled: true` to the `RenderContextMenuOptions` object, and you can control it.
* https://github.com/eclipse-theia/theia/blob/d59d5279b93e5050c2cbdd4b6726cab40187c50e/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts#L134.
*/
private showDisabled(options: RenderContextMenuOptions): boolean {
if ('showDisabled' in options) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const object = options as any;
return Boolean(object['showDisabled']);
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
menuPath: MenuPath,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: any[],
context?: HTMLElement
context?: HTMLElement,
showDisabled?: boolean
): Electron.Menu {
const menuModel = this.menuProvider.getMenu(menuPath);
const template = this.fillMenuTemplate([], menuModel, args, {
showDisabled: false,
showDisabled,
context,
rootMenuPath: menuPath,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { ContainerModule } from '@theia/core/shared/inversify';
import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer';
import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
import { ElectronMenuContribution as TheiaElectronMenuContribution } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution';
import { ContainerModule } from '@theia/core/shared/inversify';
import { MainMenuManager } from '../../../common/main-menu-manager';
import { ElectronContextMenuRenderer } from './electron-context-menu-renderer';
import { ElectronMainMenuFactory } from './electron-main-menu-factory';
import { ElectronMenuContribution } from './electron-menu-contribution';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ElectronMenuContribution).toSelf().inSingletonScope();
bind(MainMenuManager).toService(ElectronMenuContribution);
bind(ElectronContextMenuRenderer).toSelf().inSingletonScope();
rebind(ContextMenuRenderer).toService(ElectronContextMenuRenderer);
rebind(TheiaElectronMenuContribution).toService(ElectronMenuContribution);
bind(ElectronMainMenuFactory).toSelf().inSingletonScope();
rebind(TheiaElectronMainMenuFactory).toService(ElectronMainMenuFactory);
Expand Down

0 comments on commit 5b7a045

Please sign in to comment.