import React from "react"; import { observable, computed, action, IObservableValue, makeObservable, runInAction } from "mobx"; import { observer } from "mobx-react"; import classNames from "classnames"; import { Toolbar } from "eez-studio-ui/toolbar"; import { IconAction, ButtonAction } from "eez-studio-ui/action"; import { confirm } from "eez-studio-ui/dialog-electron"; import { Icon } from "eez-studio-ui/icon"; import { Table, IColumn, IRow } from "eez-studio-ui/table"; import { extensions } from "eez-studio-shared/extensions/extensions"; import { IShortcut, IShortcutsStore, IGroupsStore } from "shortcuts/interfaces"; import { DEFAULT_TOOLBAR_BUTTON_COLOR } from "shortcuts/toolbar-button-colors"; import { showShortcutDialog } from "shortcuts/shortcut-dialog"; import { SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX, FROM_EXTENSION_GROUP_NAME, SHORTCUTS_GROUP_NAME_FOR_INSTRUMENT_PREFIX } from "shortcuts/shortcuts-store"; import { isSameShortcutFromDifferentExtension } from "./isSameShortcutFromDifferentExtension"; import { isArray } from "eez-studio-shared/util"; //////////////////////////////////////////////////////////////////////////////// const selectedShortcutId = observable.box(); export function selectShortcutById( shortcutsStore: IShortcutsStore, id: string ) { if (selectedShortcutId) { const shortcut = shortcutsStore.shortcuts.get(selectedShortcutId.get()); if (shortcut) { action(() => (shortcut.selected = false))(); } } selectedShortcutId.set(id); if (selectedShortcutId) { const shortcut = shortcutsStore.shortcuts.get(selectedShortcutId.get()); if (shortcut) { action(() => (shortcut.selected = true))(); } } } //////////////////////////////////////////////////////////////////////////////// const showExtensionShortcuts = observable.box(); //////////////////////////////////////////////////////////////////////////////// export const ShortcutsToolbarButtons = observer( class ShortcutsToolbarButtons extends React.Component< { shortcutsStore: IShortcutsStore; groupsStore?: IGroupsStore; shortcutsOrGroups?: IObservableValue; }, {} > { constructor(props: any) { super(props); makeObservable(this, { showGroups: action }); showExtensionShortcuts.set( window.localStorage.getItem( "Shortcuts_showExtensionShortcuts" ) == "false" ? false : true ); } addShortcut = () => { showShortcutDialog( this.props.shortcutsStore, this.props.groupsStore, { name: "", action: { type: this.props.shortcutsStore.isScpiInstrument ? "scpi-commands" : "commands", data: "" }, keybinding: "", groupName: "", showInToolbar: true, toolbarButtonPosition: 1, toolbarButtonColor: DEFAULT_TOOLBAR_BUTTON_COLOR, requiresConfirmation: false, selected: false }, shortcut => { let id = this.props.shortcutsStore.addShortcut!(shortcut); selectShortcutById(this.props.shortcutsStore, id); setTimeout(() => { let element = document.querySelector(`.shortcut-${id}`); if (element) { element.scrollIntoView(); } }, 10); } ); }; showGroups = () => { this.props.shortcutsOrGroups!.set(false); }; render() { let buttons = []; if (this.props.groupsStore) { buttons.push(
); } if (this.props.shortcutsStore.addShortcut) { buttons.push( ); } if (this.props.shortcutsOrGroups) { buttons.push( ); } return buttons; } } ); //////////////////////////////////////////////////////////////////////////////// const Keybinding = observer( class Keybinding extends React.Component<{ keybinding: string }, {}> { render() { return this.props.keybinding .split("+") .map(part => (
{part}
)) .reduce( ( result: JSX.Element[], element: JSX.Element, index: number, array: JSX.Element[] ) => { result.push(element); if (index < array.length - 1) { result.push( + ); } return result; }, [] ); } } ); //////////////////////////////////////////////////////////////////////////////// class ShortcutRow implements IRow { constructor( private props: { shortcutsStore: IShortcutsStore; groupsStore?: IGroupsStore; shortcut: IShortcut | IShortcut[]; } ) { makeObservable(this, { shortcut: computed, colorComponent: computed, group: computed, groupComponent: computed, keybindingComponent: computed, action: computed, confirmationComponent: computed, toolbar: computed, toolbarComponent: computed, isExtensionShortcut: computed, actions: computed, className: computed }); } get shortcut() { if (isArray(this.props.shortcut)) { return this.props.shortcut[0]; } else { return this.props.shortcut; } } get id() { return this.shortcut.id; } get selected() { return this.shortcut.selected; } get color() { return this.shortcut.showInToolbar ? this.shortcut.toolbarButtonColor : "transparent"; } get colorComponent() { return (
); } get name() { return this.shortcut.name; } getExtension(shortcut: IShortcut) { return extensions.get( shortcut.groupName.substr( SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX.length ) ); } getGroupName(shortcut: IShortcut) { let groupName = shortcut.groupName; if (!groupName) { return ""; } if (groupName.startsWith(SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX)) { if ( this.props.groupsStore && this.props.groupsStore.isGroupEnabled ) { return FROM_EXTENSION_GROUP_NAME; } const extension = this.getExtension(shortcut); if (extension) { return extension.displayName || extension.name; } } if (groupName.startsWith(SHORTCUTS_GROUP_NAME_FOR_INSTRUMENT_PREFIX)) { return ""; } return groupName; } get group() { if (isArray(this.props.shortcut)) { return this.props.shortcut .map(shortcut => this.getExtension(shortcut)) .filter(extension => !!extension) .map(extension => extension!.displayName || extension!.name) .filter((value, index, self) => self.indexOf(value) === index) // unique .join(","); } else { return this.getGroupName(this.props.shortcut); } } get groupComponent() { if (isArray(this.props.shortcut)) { return this.props.shortcut .map(shortcut => this.getExtension(shortcut)) .filter(extension => !!extension) .filter((value, index, self) => self.indexOf(value) === index) // unique .map(extension => (
{extension!.displayName || extension!.name}
)); } else { return this.getGroupName(this.props.shortcut); } } get keybinding() { return this.shortcut.keybinding || ""; } get keybindingComponent() { return ; } get action() { return this.shortcut.action.type === "scpi-commands" ? "SCPI" : this.shortcut.action.type === "commands" ? "Commands" : this.shortcut.action.type === "javascript" ? "JavaScript" : "MicroPython"; } get confirmation() { return this.shortcut.requiresConfirmation ? 1 : 0; } get confirmationComponent() { return ( this.shortcut.requiresConfirmation && ); } get toolbar() { return this.shortcut.showInToolbar ? 1 : 0; } get toolbarComponent() { return this.shortcut.showInToolbar && ; } get toolbarPosition() { return this.shortcut.toolbarButtonPosition; } get isExtensionShortcut() { if (isArray(this.props.shortcut)) { return true; } else { return ( !!this.shortcut.groupName && this.shortcut.groupName.startsWith( SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX ) ); } } get actions() { return ( this.props.shortcutsStore.addShortcut && ( { showShortcutDialog( this.props.shortcutsStore, this.props.groupsStore, this.shortcut, shortcut => { this.props.shortcutsStore.updateShortcut!( shortcut ); } ); }} /> { confirm("Are you sure?", undefined, () => { this.props.shortcutsStore.deleteShortcut!( this.shortcut ); }); }} /> ) ); } get className() { return classNames(`shortcut-${this.id}`, { selected: this.props.shortcutsStore.addShortcut && this.selected }); } onClick = () => { if (this.props.shortcutsStore.addShortcut) { selectShortcutById(this.props.shortcutsStore, this.id); } }; onDoubleClick = () => { if (this.props.shortcutsStore.addShortcut) { showShortcutDialog( this.props.shortcutsStore, this.props.groupsStore, this.shortcut, shortcut => { this.props.shortcutsStore.updateShortcut!(shortcut); } ); } }; } export const Shortcuts = observer( class Shortcuts extends React.Component< { shortcutsStore: IShortcutsStore; groupsStore?: IGroupsStore; }, {} > { constructor(props: { shortcutsStore: IShortcutsStore; groupsStore?: IGroupsStore; }) { super(props); makeObservable(this, { columns: computed, rows: computed }); } get columns() { let result: IColumn[] = []; result.push({ name: "color", title: "", sortEnabled: true }); result.push({ name: "name", title: "Name", sortEnabled: true }); if (this.props.groupsStore) { result.push({ name: "group", title: showExtensionShortcuts.get() ? "Group / Extension" : "Group", sortEnabled: true }); } result.push({ name: "keybinding", title: "Keybinding", sortEnabled: true }); result.push({ name: "action", title: "Action", sortEnabled: true }); result.push({ name: "confirmation", title: "Confirmation", sortEnabled: true }); result.push({ name: "toolbar", title: "Show in Shortcuts bar", sortEnabled: true }); result.push({ name: "toolbarPosition", title: "Shortcuts bar position", sortEnabled: true }); if (this.props.shortcutsStore.addShortcut) { result.push({ name: "actions", title: "", sortEnabled: false }); } return result; } get rows() { let result: (IShortcut | IShortcut[])[] = []; const sorted = Array.from( this.props.shortcutsStore.shortcuts.values() ) .filter( shortcut => !this.props.groupsStore || showExtensionShortcuts.get() || !shortcut.groupName.startsWith( SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX ) ) .sort((s1, s2) => { let name1 = s1.name.toLocaleLowerCase(); let name2 = s2.name.toLocaleLowerCase(); return name1 < name2 ? -1 : name1 > name2 ? 1 : 0; }); if (this.props.groupsStore) { // combine duplicate shortcuts for (let i = 0; i < sorted.length; i++) { let j; for (j = i; j + 1 < sorted.length; j++) { if ( !isSameShortcutFromDifferentExtension( sorted[i], sorted[j + 1] ) ) { break; } } if (j > i) { let shortcuts: IShortcut[] = []; for (let k = i; k <= j; k++) { shortcuts.push(sorted[k]); } result.push(shortcuts); i = j; } else { result.push(sorted[i]); } } } else { result = sorted; } return result.map( shortcut => new ShortcutRow({ shortcutsStore: this.props.shortcutsStore, groupsStore: this.props.groupsStore, shortcut }) ); } render() { return ( ); } } );