1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00
joplin/packages/lib/services/commands/MenuUtils.ts

163 lines
4.8 KiB
TypeScript

import { MenuItemLocation } from '../plugins/api/types';
import CommandService from '../CommandService';
import KeymapService from '../KeymapService';
import { PluginStates, utils as pluginUtils } from '../plugins/reducer';
import propsHaveChanged from './propsHaveChanged';
const { createSelectorCreator, defaultMemoize } = require('reselect');
const { createCachedSelector } = require('re-reselect');
export interface MenuItem {
id?: string;
label?: string;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
click?: Function;
role?: any;
type?: string;
accelerator?: string;
checked?: boolean;
enabled?: boolean;
}
interface MenuItems {
[key: string]: MenuItem;
}
interface MenuItemProps {
[key: string]: any;
}
interface MenuItemPropsCache {
[key: string]: any;
}
interface MenuItemCache {
[key: string]: MenuItems;
}
const createShallowObjectEqualSelector = createSelectorCreator(
defaultMemoize,
(prev: any, next: any) => {
if (Object.keys(prev).length !== Object.keys(next).length) return false;
for (const n in prev) {
if (prev[n] !== next[n]) return false;
}
return true;
},
);
// This selector ensures that for the given command names, the same toolbar
// button array is returned if the underlying toolbar buttons have not changed.
const selectObjectByCommands = createCachedSelector(
(state: any) => state.array,
(array: any[]) => array,
)({
keySelector: (_state: any, commandNames: string[]) => {
return commandNames.join('_');
},
selectorCreator: createShallowObjectEqualSelector,
});
export default class MenuUtils {
private service_: CommandService;
private menuItemCache_: MenuItemCache = {};
private menuItemPropsCache_: MenuItemPropsCache = {};
public constructor(service: CommandService) {
this.service_ = service;
}
private get service(): CommandService {
return this.service_;
}
private get keymapService(): KeymapService {
return KeymapService.instance();
}
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
public commandToMenuItem(commandName: string, onClick: Function): MenuItem {
const command = this.service.commandByName(commandName);
const item: MenuItem = {
id: command.declaration.name,
label: this.service.label(commandName),
click: () => onClick(command.declaration.name),
enabled: true,
};
if (command.declaration.role) item.role = command.declaration.role;
if (this.keymapService && this.keymapService.acceleratorExists(commandName)) {
item.accelerator = this.keymapService.getAccelerator(commandName);
}
return item;
}
public commandToStatefulMenuItem(commandName: string, ...args: any[]): MenuItem {
const whenClauseContext = this.service.currentWhenClauseContext();
const menuItem = this.commandToMenuItem(commandName, () => {
return this.service.execute(commandName, ...args);
});
menuItem.enabled = this.service.isEnabled(commandName, whenClauseContext);
return menuItem;
}
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
public commandsToMenuItems(commandNames: string[], onClick: Function, locale: string): MenuItems {
const key = `${this.keymapService.lastSaveTime}_${commandNames.join('_')}_${locale}`;
if (this.menuItemCache_[key]) return this.menuItemCache_[key];
const output: MenuItems = {};
for (const commandName of commandNames) {
output[commandName] = this.commandToMenuItem(commandName, onClick);
}
this.menuItemCache_ = {
[key]: output,
};
return output;
}
public commandsToMenuItemProps(commandNames: string[], whenClauseContext: any): MenuItemProps {
const output: MenuItemProps = {};
for (const commandName of commandNames) {
const newProps = {
enabled: this.service.isEnabled(commandName, whenClauseContext),
};
if (newProps === null || propsHaveChanged(this.menuItemPropsCache_[commandName], newProps)) {
output[commandName] = newProps;
this.menuItemPropsCache_[commandName] = newProps;
} else {
output[commandName] = this.menuItemPropsCache_[commandName];
}
}
return selectObjectByCommands({ array: output }, commandNames);
}
public pluginContextMenuItems(plugins: PluginStates, location: MenuItemLocation): MenuItem[] {
const output: MenuItem[] = [];
const pluginViewInfos = pluginUtils.viewInfosByType(plugins, 'menuItem');
const whenClauseContext = this.service.currentWhenClauseContext();
for (const info of pluginViewInfos) {
if (info.view.location !== location) continue;
const menuItem = this.commandToStatefulMenuItem(info.view.commandName);
menuItem.enabled = this.service.isEnabled(info.view.commandName, whenClauseContext);
output.push(menuItem);
}
if (output.length) output.splice(0, 0, { type: 'separator' } as any);
return output;
}
}