diff --git a/src/components/App.tsx b/src/components/App.tsx index b9ec15c9..b3262a8c 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -28,6 +28,8 @@ import '@blueprintjs/popover2/lib/css/blueprint-popover2.css' import '$src/css/main.css' import '$src/css/windows.css' import '$src/css/scrollbars.css' +import { reaction } from 'mobx' +import { ReactiveProperties } from '$src/types' const App = observer(() => { const { appState } = useStores('appState') @@ -112,6 +114,47 @@ const App = observer(() => { } }, []) + // Install menu reactions to update native menu when needed + useEffect(() => { + return reaction( + (): ReactiveProperties => { + const view = appState.getActiveView() + const activeCache = view.getVisibleCache() + + return { + // if any of these elements have changed + // we'll have to update native menus + status: activeCache.status, + path: activeCache.path, + selectedLength: activeCache.selected.length, + // enable when FsZip is merged + isReadonly: false, + isIndirect: false, + // isReadonly: activeCache.getFS().options.readonly, + // isIndirect: activeCache.getFS().options.indirect, + isOverlayOpen: document.body.classList.contains('bp4-overlay-open'), + activeViewTabNums: view.caches.length, + isExplorer: appState.isExplorer, + language: i18n.language, + // missing: about opened, tab: is it needed? + } + }, + (value) => { + console.log('something changed!') + ipcRenderer.invoke('updateMenus', t('APP_MENUS', { returnObjects: true }), value) + }, + { + equals: (value: ReactiveProperties, previousValue: ReactiveProperties) => { + console.log(JSON.stringify(value) === JSON.stringify(previousValue)) + console.log(JSON.stringify(value)) + console.log(JSON.stringify(previousValue)) + + return JSON.stringify(value) === JSON.stringify(previousValue) + }, + }, + ) + }, []) + useEventListener('keydown', onShortcutsCombo, { capture: true }) useEventListener( 'copy', diff --git a/src/components/menus/FileContextMenu.tsx b/src/components/menus/FileContextMenu.tsx index c7b0a6b6..b93bae35 100644 --- a/src/components/menus/FileContextMenu.tsx +++ b/src/components/menus/FileContextMenu.tsx @@ -15,6 +15,7 @@ const FileContextMenu = ({ fileUnderMouse }: Props) => { const clipboard = appState.clipboard const cache = appState.getActiveCache() + // TODO: disable delete/paste when cahce.fs.readonly is true const numFilesInClipboard = clipboard.files.length const isInSelection = fileUnderMouse && !!cache.selected.find((file) => sameID(file.id, fileUnderMouse.id)) const isPasteEnabled = numFilesInClipboard && ((!fileUnderMouse && !cache.error) || fileUnderMouse?.isDir) diff --git a/src/electron/appMenus.ts b/src/electron/appMenus.ts index 3ac51601..b18ad40c 100644 --- a/src/electron/appMenus.ts +++ b/src/electron/appMenus.ts @@ -1,6 +1,7 @@ import { clipboard, Menu, BrowserWindow, MenuItemConstructorOptions, MenuItem, app, ipcMain, dialog } from 'electron' import { isMac, isLinux, VERSIONS } from '$src/electron/osSupport' +import { ReactiveProperties } from '$src/types' const ACCELERATOR_EVENT = 'menu_accelerator' @@ -84,8 +85,19 @@ export class AppMenu { } } - getMenuTemplate(): MenuItemConstructorOptions[] { + getMenuTemplate({ + activeViewTabNums, + isReadonly, + isIndirect, + isOverlayOpen, + isExplorer, + path, + selectedLength, + status, + }: ReactiveProperties): MenuItemConstructorOptions[] { const menuStrings = this.menuStrings + const explorerWithoutOverlay = !isOverlayOpen && isExplorer + const explorerWithoutOverlayCanWrite = explorerWithoutOverlay && !isReadonly && status === 'ok' let windowMenuIndex = 4 const template = [ @@ -97,17 +109,20 @@ export class AppMenu { label: menuStrings['NEW_TAB'], click: this.sendComboEvent, accelerator: 'CmdOrCtrl+T', + enabled: explorerWithoutOverlay, }, { label: menuStrings['CLOSE_TAB'], click: this.sendComboEvent, accelerator: 'CmdOrCtrl+W', + enabled: explorerWithoutOverlay && activeViewTabNums > 1, }, { type: 'separator' }, { label: menuStrings['MAKEDIR'], accelerator: 'CmdOrCtrl+N', click: this.sendComboEvent, + enabled: explorerWithoutOverlayCanWrite, }, { label: menuStrings['RENAME'], @@ -118,17 +133,20 @@ export class AppMenu { Object.assign({ combo: 'rename', data: undefined }), ) }, + enabled: explorerWithoutOverlayCanWrite && selectedLength === 1, }, { label: menuStrings['DELETE'], accelerator: 'CmdOrCtrl+D', click: this.sendComboEvent, + enabled: explorerWithoutOverlayCanWrite && selectedLength > 0, }, { type: 'separator' }, { label: menuStrings['OPEN_TERMINAL'], accelerator: 'CmdOrCtrl+K', click: this.sendComboEvent, + enabled: explorerWithoutOverlay && !isIndirect, }, ], }, @@ -313,13 +331,13 @@ export class AppMenu { return template as MenuItemConstructorOptions[] } - createMenu(menuStrings: LocaleString, lang: string): void { + createMenu(menuStrings: LocaleString, props: ReactiveProperties): void { this.menuStrings = menuStrings - this.lang = lang + this.lang = props.language - const menuTemplate = this.getMenuTemplate() + const template = this.getMenuTemplate(props) - const menu = Menu.buildFromTemplate(menuTemplate) + const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu) } diff --git a/src/electron/main.ts b/src/electron/main.ts index 830d5d45..b740c364 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -7,6 +7,7 @@ import { AppMenu, LocaleString } from '$src/electron/appMenus' import { isLinux } from '$src/electron/osSupport' import { WindowSettings } from '$src/electron//windowSettings' import { Remote } from '$src/electron/remote' +import { ReactiveProperties } from '$src/types' const ENV_E2E = !!process.env.E2E const HTML_PATH = `${__dirname}/index.html` @@ -155,7 +156,7 @@ const ElectronApp = { * - reloadIgnoringCache: need to reload the main window (dev only) * - exit: wants to exit the app * - openTerminal(cmd): should open a new terminal process using specified cmd line - * - languageChanged(strings): language has been changed so menus need to be updated + * - updateMenus(strings): language has been changed so menus need to be updated * - selectAll: wants to generate a selectAll event */ installIpcMainListeners() { @@ -186,9 +187,10 @@ const ElectronApp = { }) }) - ipcMain.handle('languageChanged', (e: Event, strings: LocaleString, lang: string) => { + ipcMain.handle('updateMenus', (e: Event, strings: LocaleString, props: ReactiveProperties) => { + console.log('** need to update menus :)') if (this.appMenu) { - this.appMenu.createMenu(strings, lang) + this.appMenu.createMenu(strings, props) } else { console.log('languageChanged but app not ready :(') } diff --git a/src/locale/i18n.ts b/src/locale/i18n.ts index 3d159e6a..d9e2d50b 100644 --- a/src/locale/i18n.ts +++ b/src/locale/i18n.ts @@ -37,9 +37,9 @@ const i18n = { i18next, } -i18next.on('languageChanged', (lang: string): void => { - ipcRenderer.invoke('languageChanged', i18next.t('APP_MENUS', { returnObjects: true }), lang) -}) +// i18next.on('languageChanged', (lang: string): void => { +// ipcRenderer.invoke('languageChanged', i18next.t('APP_MENUS', { returnObjects: true }), lang) +// }) const languageList = Object.keys(locales) diff --git a/src/state/appState.tsx b/src/state/appState.tsx index 99bce615..44b723f4 100644 --- a/src/state/appState.tsx +++ b/src/state/appState.tsx @@ -417,9 +417,13 @@ export class AppState { } } - getActiveCache(): FileState { + getActiveView(): ViewState { const winState = this.winStates[0] - const view = winState.getActiveView() + return winState.getActiveView() + } + + getActiveCache(): FileState { + const view = this.getActiveView() return this.isExplorer ? view.caches.find((cache) => cache.isVisible === true) : null } diff --git a/src/types/index.ts b/src/types/index.ts index e17118db..55635a9f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,5 @@ import { FileDescriptor } from '$src/services/Fs' -import { FileState } from '$src/state/fileState' +import { FileState, TStatus } from '$src/state/fileState' import { IconName } from '@blueprintjs/icons' import { IpcRendererEvent } from 'electron/renderer' @@ -31,3 +31,16 @@ export interface DraggedObject { } export type ArrowKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight' + +// Properties that trigger an update of native menus +export interface ReactiveProperties { + activeViewTabNums: number + isReadonly: boolean + isIndirect: boolean + isOverlayOpen: boolean + isExplorer: boolean + path: string + selectedLength: number + status: TStatus + language: string +}