import React from "react"; import { observable, computed, values, makeObservable, IReactionDisposer, autorun, runInAction } from "mobx"; import { beginTransaction, commitTransaction } from "eez-studio-shared/store"; import { IShortcut, IGroup } from "shortcuts/interfaces"; import { Shortcuts, ShortcutsToolbarButtons } from "shortcuts/shortcuts"; import { Groups, GroupsToolbarButtons } from "shortcuts/groups"; import { shortcuts, addShortcut, updateShortcut, deleteShortcut } from "shortcuts/shortcuts-store"; import { SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX, SHORTCUTS_GROUP_NAME_FOR_INSTRUMENT_PREFIX } from "shortcuts/shortcuts-store"; import { groups, addGroup, updateGroup, deleteGroup } from "shortcuts/groups-store"; import { bindShortcuts } from "shortcuts/mousetrap"; import { changeGroupNameInInstruments, deleteGroupInInstruments } from "instrument/instrument-object"; import { InstrumentAppStore } from "instrument/window/app-store"; import type * as ScriptModule from "instrument/window/script"; import type { IExtension } from "eez-studio-shared/extensions/extension"; import { closestBySelector } from "eez-studio-shared/dom"; //////////////////////////////////////////////////////////////////////////////// class ShortcutsToolbarRegistry { bindShortcutsDispose: (() => void) | undefined; shortcutsToolbarMap: Map = new Map(); activeShortcutsToolbar: HTMLDivElement | undefined; constructor() { makeObservable(this, { shortcutsToolbarMap: observable, activeShortcutsToolbar: observable }); this.findActiveShortcutsToolbar(); } findActiveShortcutsToolbar = () => { let activeShortcutsToolbar: HTMLDivElement | undefined; this.shortcutsToolbarMap.forEach((store, element) => { // if shortcutsToolbar element is not visible then pass const rect = element.getBoundingClientRect(); if (rect.width == 0 || rect.height == 0) { return; } // if there is only one ShortcutsToolbar visible then make it active if (this.shortcutsToolbarMap.size == 1) { activeShortcutsToolbar = element; return; } // if shortcutsToolbar element is inside flexlayout-react tab const tabElement: HTMLElement = closestBySelector( element, ".flexlayout__tab" ); if (tabElement) { // if flexlayout tab is selected const layoutPath = tabElement.getAttribute("data-layout-path"); if (layoutPath) { let i = layoutPath.lastIndexOf("/"); if (i != -1) { let id = layoutPath.substring(0, i); let tabstripElement: HTMLElement | null = document.querySelector( `[data-layout-path="${id}/tabstrip"]` ); if ( tabstripElement && tabstripElement.classList.contains( "flexlayout__tabset-selected" ) ) { activeShortcutsToolbar = element; } } } // if flexlayout tab is not selected but tab contains activeElement if ( activeShortcutsToolbar != element && document.activeElement && tabElement.contains(document.activeElement) ) { activeShortcutsToolbar = element; } } else { activeShortcutsToolbar = element; } }); if (activeShortcutsToolbar != this.activeShortcutsToolbar) { runInAction(() => { this.activeShortcutsToolbar = activeShortcutsToolbar; }); if (shortcutsToolbarRegistry.bindShortcutsDispose) { shortcutsToolbarRegistry.bindShortcutsDispose(); } if (activeShortcutsToolbar) { this.bindShortcutsToKeyboard( this.shortcutsToolbarMap.get(activeShortcutsToolbar)! ); } } requestAnimationFrame(this.findActiveShortcutsToolbar); }; bindShortcutsToKeyboard(shortcutsStore: ShortcutsStore) { shortcutsToolbarRegistry.bindShortcutsDispose = bindShortcuts( shortcutsStore.instrumentShortcuts, (shortcut: IShortcut) => { if (shortcut.action.type === "micropython") { return; } const { executeShortcut } = require("instrument/window/script") as typeof ScriptModule; if (shortcutsStore.appStore.instrument) { executeShortcut(shortcutsStore.appStore, shortcut); } } ); } registerShortcutsToolbar( el: HTMLDivElement, shortcutsStore: ShortcutsStore ) { shortcutsToolbarRegistry.shortcutsToolbarMap.set(el, shortcutsStore); } unregisterShortcutsToolbar(el: HTMLDivElement) { shortcutsToolbarRegistry.shortcutsToolbarMap.delete(el); } } export const shortcutsToolbarRegistry = new ShortcutsToolbarRegistry(); //////////////////////////////////////////////////////////////////////////////// export const shortcutsOrGroups = observable.box(true); export class ShortcutsStore { addShortcutsToDatabaseIfAllAreMissingDispose: IReactionDisposer | undefined; constructor(public appStore: InstrumentAppStore) { makeObservable(this, { shortcuts: computed, isAnyMissingShortcuts: computed }); this.addShortcutsToDatabaseIfAllAreMissingDispose = autorun(() => { const extension = this.appStore.instrument?.extension; if (!extension) { return; } const externsionShortcuts = extension?.properties?.shortcuts; if (!externsionShortcuts) { return; } this.addShortcutsToDatabaseIfAllAreMissingDispose!(); this.addShortcutsToDatabaseIfAllAreMissingDispose = undefined; const anyShortcutInDatabase = externsionShortcuts.find(shortcut => this.isShortcutInDatabase(extension, shortcut) ); if (!anyShortcutInDatabase) { this.addMissingShortcuts(); } }); } get isScpiInstrument() { return this.appStore.instrument.commandsProtocol == "SCPI"; } instrumentShortcuts = computed(() => { let shortcutsMap = new Map(); const instrument = this.appStore.instrument; if (instrument) { values(shortcuts) .filter( shortcut => shortcut.groupName === SHORTCUTS_GROUP_NAME_FOR_INSTRUMENT_PREFIX + instrument.id || (instrument.extension && shortcut.groupName === SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX + instrument.extension.id) || instrument.selectedShortcutGroups.indexOf( shortcut.groupName ) !== -1 ) .forEach(shortcut => shortcutsMap.set(shortcut.id, shortcut)); } return observable.map(shortcutsMap); }); get shortcuts() { return this.instrumentShortcuts.get(); } addShortcut(shortcut: Partial) { beginTransaction("Add shortcut"); if (shortcut.groupName) { if ( !shortcut.groupName.startsWith( SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX ) && !shortcut.groupName.startsWith( SHORTCUTS_GROUP_NAME_FOR_INSTRUMENT_PREFIX ) ) { this.appStore.instrument.addShortcutGroupToInstrument( shortcut.groupName ); } } else { shortcut = Object.assign({}, shortcut, { groupName: SHORTCUTS_GROUP_NAME_FOR_INSTRUMENT_PREFIX + this.appStore.history.oid }); } let id = addShortcut(shortcut); commitTransaction(); return id; } updateShortcut(shortcut: Partial) { beginTransaction("Edit shortcut"); if (shortcut.groupName) { if ( !shortcut.groupName.startsWith( SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX ) && !shortcut.groupName.startsWith( SHORTCUTS_GROUP_NAME_FOR_INSTRUMENT_PREFIX ) ) { this.appStore.instrument.addShortcutGroupToInstrument( shortcut.groupName ); } } else { shortcut = Object.assign({}, shortcut, { groupName: SHORTCUTS_GROUP_NAME_FOR_INSTRUMENT_PREFIX + this.appStore.instrument.id }); } updateShortcut(shortcut); commitTransaction(); } deleteShortcut(shortcut: Partial) { beginTransaction("Delete shortcut"); deleteShortcut(shortcut); commitTransaction(); } isShortcutInDatabase(extension: IExtension, shortcut: IShortcut) { const groupName = SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX + extension.id; return values(shortcuts).find( dbShortcut => dbShortcut.originalId == shortcut.id && dbShortcut.groupName == groupName ); } get isAnyMissingShortcuts() { const extension = this.appStore.instrument?.extension; if (!extension) { return false; } const externsionShortcuts = extension?.properties?.shortcuts; if (!externsionShortcuts) { return false; } return externsionShortcuts.find( shortcut => !this.isShortcutInDatabase(extension, shortcut) ); } addMissingShortcuts() { const extension = this.appStore.instrument?.extension; if (!extension) { return; } const externsionShortcuts = extension?.properties?.shortcuts; if (!externsionShortcuts) { return; } externsionShortcuts.forEach(shortcut => { const groupName = SHORTCUTS_GROUP_NAME_FOR_EXTENSION_PREFIX + extension.id; if (!this.isShortcutInDatabase(extension, shortcut)) { addShortcut( Object.assign({}, shortcut, { id: undefined, groupName, originalId: shortcut.id }) ); } }); } onTerminate() { if (this.addShortcutsToDatabaseIfAllAreMissingDispose) { this.addShortcutsToDatabaseIfAllAreMissingDispose(); } } } export class GroupsStore { constructor(public appStore: InstrumentAppStore) {} get groups() { return groups; } get addGroup() { return addGroup; } updateGroup(changes: Partial) { if (changes.name) { let group = groups.get(changes.id!); if (group && group.name != changes.name) { changeGroupNameInInstruments(group.name, changes.name); } } updateGroup(changes); } deleteGroup(group: Partial) { deleteGroupInInstruments(group.name!); deleteGroup(group); } isGroupEnabled(group: IGroup) { return ( this.appStore.instrument.selectedShortcutGroups.indexOf( group.name ) !== -1 ); } enableGroup(group: IGroup, enable: boolean): void { if (enable) { beginTransaction("Add shortcut group to instrument"); this.appStore.instrument.addShortcutGroupToInstrument(group.name); } else { beginTransaction("Remove shortcut group from instrument"); this.appStore.instrument.removeShortcutGroupFromInstrument( group.name ); } commitTransaction(); } } export function render(appStore: InstrumentAppStore) { return shortcutsOrGroups.get() ? ( ) : ( ); } export function toolbarButtonsRender(appStore: InstrumentAppStore) { return shortcutsOrGroups.get() ? ( ) : ( ); }