Skip to content

Commit

Permalink
appMenus: added new menu to switch icon/table view
Browse files Browse the repository at this point in the history
  • Loading branch information
warpdesign committed May 13, 2024
1 parent 53198b6 commit a4bc6ac
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 72 deletions.
2 changes: 1 addition & 1 deletion src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ const App = observer(() => {
isPrefsOpen,
isShortcutsOpen,
isExplorer,
activeView,
} = appState

const cache = appState.getActiveCache()
Expand Down Expand Up @@ -79,6 +78,7 @@ const App = observer(() => {
filesLength: activeCache.files.length,
clipboardLength: appState.clipboard.files.length,
activeViewId: activeView.viewId,
viewMode: activeView.getVisibleCache().viewmode,
// missing: about opened, tab: is it needed?
}
}, [appState])
Expand Down
133 changes: 77 additions & 56 deletions src/components/dialogs/ShortcutsDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as React from 'react'
import React, { useEffect, useState } from 'react'
import { Dialog, Classes, Button, KeyCombo, InputGroup, Callout } from '@blueprintjs/core'
import type { TFunction } from 'i18next'
import { useTranslation } from 'react-i18next'

import { isMac } from '$src/utils/platform'
import CONFIG from '$src/config/appConfig'
import { getKeyboardLayoutMap } from '$src/utils/keyboard'

interface ShortcutsProps {
isOpen: boolean
Expand All @@ -16,49 +17,59 @@ interface Combo {
label: string
}

export const buildShortcuts = (t: TFunction<'translation', undefined>) => ({
[t('SHORTCUT.GROUP.GLOBAL')]: [
{ combo: 'alt + mod + l', label: t('SHORTCUT.MAIN.DOWNLOADS_TAB') },
{ combo: 'alt + mod + e', label: t('SHORTCUT.MAIN.EXPLORER_TAB') },
{ combo: 'ctrl + shift + right', label: t('SHORTCUT.MAIN.NEXT_VIEW') },
{ combo: 'ctrl + shift + left', label: t('SHORTCUT.MAIN.PREVIOUS_VIEW') },
{ combo: 'mod + r', label: t('SHORTCUT.MAIN.RELOAD_VIEW') },
{ combo: 'escape', label: t('SHORTCUT.LOG.TOGGLE') },
{ combo: 'mod + s', label: t('SHORTCUT.MAIN.KEYBOARD_SHORTCUTS') },
{ combo: 'mod + ,', label: t('SHORTCUT.MAIN.PREFERENCES') },
{ combo: 'alt + mod + i', label: t('SHORTCUT.OPEN_DEVTOOLS') },
{ combo: 'mod + q', label: t('SHORTCUT.MAIN.QUIT') },
{ combo: 'mod + alt + shift + v', label: t('NAV.SPLITVIEW') },
],
[t('SHORTCUT.GROUP.ACTIVE_VIEW')]: [
{ combo: 'space', label: t('SHORTCUT.ACTIVE_VIEW.OPEN_PREVIEW') },
{ combo: (isMac && 'mod + left') || 'alt + left', label: t('SHORTCUT.ACTIVE_VIEW.BACKWARD_HISTORY') },
{ combo: (isMac && 'mod + right') || 'alt + right', label: t('SHORTCUT.ACTIVE_VIEW.FORWARD_HISTORY') },
{ combo: 'meta + c', label: t('SHORTCUT.ACTIVE_VIEW.COPY') },
{ combo: 'meta + v', label: t('SHORTCUT.ACTIVE_VIEW.PASTE') },
{ combo: 'mod + shift + c', label: t('SHORTCUT.ACTIVE_VIEW.COPY_PATH') },
{ combo: 'mod + shift + n', label: t('SHORTCUT.ACTIVE_VIEW.COPY_FILENAME') },
{ combo: 'mod + o', label: t('SHORTCUT.ACTIVE_VIEW.OPEN_FILE') },
{
combo: isMac ? 'mod + alt + o' : 'mod + shift + o',
label: t('SHORTCUT.ACTIVE_VIEW.OPEN_FILE_INACTIVE_VIEW'),
},
{ combo: 'mod + a', label: t('SHORTCUT.ACTIVE_VIEW.SELECT_ALL') },
{ combo: 'mod + i', label: t('SHORTCUT.ACTIVE_VIEW.SELECT_INVERT') },
{ combo: 'mod + l', label: t('SHORTCUT.ACTIVE_VIEW.FOCUS_PATH') },
{ combo: 'mod + n', label: t('COMMON.MAKEDIR') },
{ combo: 'mod + d', label: t('SHORTCUT.ACTIVE_VIEW.DELETE') },
{ combo: 'mod + k', label: t('SHORTCUT.ACTIVE_VIEW.OPEN_TERMINAL') },
{ combo: 'backspace', label: t('SHORTCUT.ACTIVE_VIEW.PARENT_DIRECTORY') },
{ combo: 'mod + u', label: t('APP_MENUS.TOGGLE_HIDDEN_FILES') },
],
[t('SHORTCUT.GROUP.TABS')]: [
{ combo: 'ctrl + tab', label: t('APP_MENUS.SELECT_NEXT_TAB') },
{ combo: 'ctrl + shift + tab', label: t('APP_MENUS.SELECT_PREVIOUS_TAB') },
{ combo: 'mod + t', label: t('APP_MENUS.NEW_TAB') },
{ combo: 'mod + w', label: t('SHORTCUT.TABS.CLOSE_ACTIVE_TAB') },
],
})
interface Shortcuts {
[key: string]: Combo[]
}

export const buildShortcuts = async (t: TFunction<'translation', undefined>): Promise<Shortcuts> => {
const keyboardLayoutMap = await getKeyboardLayoutMap()

return {
[t('SHORTCUT.GROUP.GLOBAL')]: [
{ combo: 'alt + mod + l', label: t('SHORTCUT.MAIN.DOWNLOADS_TAB') },
{ combo: 'alt + mod + e', label: t('SHORTCUT.MAIN.EXPLORER_TAB') },
{ combo: 'ctrl + shift + right', label: t('SHORTCUT.MAIN.NEXT_VIEW') },
{ combo: 'ctrl + shift + left', label: t('SHORTCUT.MAIN.PREVIOUS_VIEW') },
{ combo: 'mod + r', label: t('SHORTCUT.MAIN.RELOAD_VIEW') },
{ combo: 'escape', label: t('SHORTCUT.LOG.TOGGLE') },
{ combo: 'mod + s', label: t('SHORTCUT.MAIN.KEYBOARD_SHORTCUTS') },
{ combo: 'mod + ,', label: t('SHORTCUT.MAIN.PREFERENCES') },
{ combo: 'alt + mod + i', label: t('SHORTCUT.OPEN_DEVTOOLS') },
{ combo: 'mod + q', label: t('SHORTCUT.MAIN.QUIT') },
{ combo: 'mod + alt + shift + v', label: t('NAV.SPLITVIEW') },
],
[t('SHORTCUT.GROUP.ACTIVE_VIEW')]: [
{ combo: `mod + ${keyboardLayoutMap['Digit1']}`, label: t('SHORTCUT.ACTIVE_VIEW.ICON_MODE') },
{ combo: `mod + ${keyboardLayoutMap['Digit2']}`, label: t('SHORTCUT.ACTIVE_VIEW.TABLE_MODE') },
{ combo: 'space', label: t('SHORTCUT.ACTIVE_VIEW.OPEN_PREVIEW') },
{ combo: (isMac && 'mod + left') || 'alt + left', label: t('SHORTCUT.ACTIVE_VIEW.BACKWARD_HISTORY') },
{ combo: (isMac && 'mod + right') || 'alt + right', label: t('SHORTCUT.ACTIVE_VIEW.FORWARD_HISTORY') },
{ combo: 'meta + c', label: t('SHORTCUT.ACTIVE_VIEW.COPY') },
{ combo: 'meta + v', label: t('SHORTCUT.ACTIVE_VIEW.PASTE') },
{ combo: 'mod + shift + c', label: t('SHORTCUT.ACTIVE_VIEW.COPY_PATH') },
{ combo: 'mod + shift + n', label: t('SHORTCUT.ACTIVE_VIEW.COPY_FILENAME') },
{ combo: 'mod + o', label: t('SHORTCUT.ACTIVE_VIEW.OPEN_FILE') },
{
combo: isMac ? 'mod + alt + o' : 'mod + shift + o',
label: t('SHORTCUT.ACTIVE_VIEW.OPEN_FILE_INACTIVE_VIEW'),
},
{ combo: 'mod + a', label: t('SHORTCUT.ACTIVE_VIEW.SELECT_ALL') },
{ combo: 'mod + i', label: t('SHORTCUT.ACTIVE_VIEW.SELECT_INVERT') },
{ combo: 'mod + l', label: t('SHORTCUT.ACTIVE_VIEW.FOCUS_PATH') },
{ combo: 'mod + n', label: t('COMMON.MAKEDIR') },
{ combo: 'mod + d', label: t('SHORTCUT.ACTIVE_VIEW.DELETE') },
{ combo: 'mod + k', label: t('SHORTCUT.ACTIVE_VIEW.OPEN_TERMINAL') },
{ combo: 'backspace', label: t('SHORTCUT.ACTIVE_VIEW.PARENT_DIRECTORY') },
{ combo: 'mod + u', label: t('APP_MENUS.TOGGLE_HIDDEN_FILES') },
],
[t('SHORTCUT.GROUP.TABS')]: [
{ combo: 'ctrl + tab', label: t('APP_MENUS.SELECT_NEXT_TAB') },
{ combo: 'ctrl + shift + tab', label: t('APP_MENUS.SELECT_PREVIOUS_TAB') },
{ combo: 'mod + t', label: t('APP_MENUS.NEW_TAB') },
{ combo: 'mod + w', label: t('SHORTCUT.TABS.CLOSE_ACTIVE_TAB') },
],
}
}

const renderShortcuts = (shortcuts: Combo[]) =>
shortcuts.map((shortcut) => (
Expand All @@ -72,20 +83,30 @@ const renderTitle = (title: string) => <h4 className={Classes.HEADING}>{title}</

const ShortcutsDialog = ({ isOpen, onClose }: ShortcutsProps) => {
const { t, i18n } = useTranslation()
const [shortcutsList, setShortcutsList] = React.useState(() => buildShortcuts(t))
const [filter, setFilter] = React.useState('')
const labels = Object.keys(shortcutsList)
const shortcuts: { [x: string]: Combo[] } = {}
const [shortcutsList, setShortcutsList] = useState<Shortcuts>({})
const [filter, setFilter] = useState('')
const sections = Object.keys(shortcutsList)
const regex = new RegExp(filter, 'i')
for (const label of labels) {
shortcuts[label] = shortcutsList[label].filter((shortcut) => shortcut.label.match(regex))
const visibleShortcuts: Shortcuts = {}
for (const section of sections) {
visibleShortcuts[section] = shortcutsList[section].filter((shortcut) => shortcut.label.match(regex))
}

const isEmpty = labels.every((label) => shortcuts[label].length === 0)
React.useEffect(() => {
setShortcutsList(() => buildShortcuts(t))
const isEmpty = sections.every((label) => visibleShortcuts[label].length === 0)

useEffect(() => {
;(async () => {
const shortcutsList = await buildShortcuts(t)
setShortcutsList(shortcutsList)
})()
}, [i18n.language])

// useEffect(() => {
// (async () => {
// () => buildShortcuts(t)
// })()
// }, [])

return (
<Dialog
icon="lightbulb"
Expand All @@ -112,11 +133,11 @@ const ShortcutsDialog = ({ isOpen, onClose }: ShortcutsProps) => {
<Callout>{t('DIALOG.SHORTCUTS.NO_RESULTS')}</Callout>
) : (
<>
{labels.map((label) =>
shortcuts[label].length ? (
{sections.map((label) =>
visibleShortcuts[label].length ? (
<React.Fragment key={`title_${label}`}>
{renderTitle(label)}
{renderShortcuts(shortcuts[label])}
{renderShortcuts(visibleShortcuts[label])}
</React.Fragment>
) : null,
)}
Expand Down
12 changes: 12 additions & 0 deletions src/components/shortcuts/MenuAccelerators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,16 @@ class MenuAcceleratorsClass extends React.Component<Props> {
this.appState.startEditingFile(this.getActiveFileCache())
}

onToggleIconViewMode = (): void => {
const cache = this.getActiveFileCache()
cache.viewmode !== 'icons' && cache.setViewMode('icons')
}

onToggleTableViewMode = (): void => {
const cache = this.getActiveFileCache()
cache.viewmode !== 'details' && cache.setViewMode('details')
}

renderMenuAccelerators(): React.ReactElement {
return (
<Accelerators>
Expand All @@ -203,6 +213,8 @@ class MenuAcceleratorsClass extends React.Component<Props> {
<Accelerator combo={isMac ? 'Cmd+Right' : 'Alt+Right'} onClick={this.onForward}></Accelerator>
<Accelerator combo="Backspace" onClick={this.onParent}></Accelerator>
<Accelerator combo="rename" onClick={this.onRename}></Accelerator>
<Accelerator combo="CmdOrCtrl+1" onClick={this.onToggleIconViewMode}></Accelerator>
<Accelerator combo="CmdOrCtrl+2" onClick={this.onToggleTableViewMode}></Accelerator>
</Accelerators>
)
}
Expand Down
23 changes: 21 additions & 2 deletions src/electron/appMenus.ts
Original file line number Diff line number Diff line change
@@ -1,7 +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'
import { KeyboardLayoutMap, ReactiveProperties } from '$src/types'

const ACCELERATOR_EVENT = 'menu_accelerator'

Expand All @@ -17,7 +17,6 @@ export class AppMenu {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sendComboEvent = (menuItem: MenuItem & { accelerator: string }) => {
const accel = menuItem.accelerator || ''
console.log('sending', menuItem.label, accel)
this.win.webContents.send(ACCELERATOR_EVENT, Object.assign({ combo: accel, data: undefined }))
}

Expand Down Expand Up @@ -95,10 +94,13 @@ export class AppMenu {
clipboardLength,
filesLength,
status,
viewMode,
}: ReactiveProperties): MenuItemConstructorOptions[] {
const menuStrings = this.menuStrings
const explorerWithoutOverlay = !isOverlayOpen && isExplorer
const explorerWithoutOverlayCanWrite = explorerWithoutOverlay && !isReadonly && status === 'ok'
const isIconViewMode = viewMode === 'icons'

let windowMenuIndex = 4

const template = [
Expand Down Expand Up @@ -196,6 +198,23 @@ export class AppMenu {
{
label: menuStrings['TITLE_VIEW'],
submenu: [
{
type: 'checkbox',
label: menuStrings['TOGGLE_ICONVIEW_MODE'],
accelerator: 'CmdOrCtrl+1',
click: this.sendComboEvent,
enabled: explorerWithoutOverlay,
checked: isIconViewMode,
},
{
type: 'checkbox',
label: menuStrings['TOGGLE_TABLEVIEW_MODE'],
accelerator: 'CmdOrCtrl+2',
click: this.sendComboEvent,
enabled: explorerWithoutOverlay,
checked: !isIconViewMode,
},
{ type: 'separator' },
{
label: menuStrings['TOGGLE_SPLITVIEW'],
accelerator: 'CmdOrCtrl+Shift+Alt+V',
Expand Down
24 changes: 16 additions & 8 deletions src/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AppMenu } 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'
import { KeyboardLayoutMap, ReactiveProperties } from '$src/types'

const ENV_E2E = !!process.env.E2E
const HTML_PATH = `${__dirname}/index.html`
Expand Down Expand Up @@ -187,13 +187,21 @@ const ElectronApp = {
})
})

ipcMain.handle('updateMenus', (e: Event, strings: Record<string, string>, props: ReactiveProperties) => {
if (this.appMenu) {
this.appMenu.createMenu(strings, props)
} else {
console.log('languageChanged but app not ready :(')
}
})
ipcMain.handle(
'updateMenus',
(
e: Event,
strings: Record<string, string>,
props: ReactiveProperties,
keyboardLayoutMap: KeyboardLayoutMap,
) => {
if (this.appMenu) {
this.appMenu.createMenu(strings, props, keyboardLayoutMap)
} else {
console.log('languageChanged but app not ready :(')
}
},
)

ipcMain.handle('selectAll', () => {
if (this.mainWindow) {
Expand Down
5 changes: 3 additions & 2 deletions src/events/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactiveProperties } from '$src/types'
import { KeyboardLayoutMap, ReactiveProperties } from '$src/types'
import { ipcRenderer } from 'electron'

export const triggerUpdateMenus = (strings: Record<string, string>, props: ReactiveProperties) =>
export const triggerUpdateMenus = async (strings: Record<string, string>, props: ReactiveProperties) => {
ipcRenderer.invoke('updateMenus', strings, props)
}
6 changes: 5 additions & 1 deletion src/locale/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@
"CLOSE_ACTIVE_TAB": "Close active tab"
},
"ACTIVE_VIEW": {
"TABLE_MODE": "Table view",
"ICON_MODE": "Icons view",
"COPY": "Copy selected items to clipboard",
"PASTE": "Paste selected items into current folder",
"VIEW_HISTORY": "Show nav history (debug)",
Expand Down Expand Up @@ -266,7 +268,9 @@
"GO_PARENT": "Parent folder",
"GO_BACK": "Back",
"GO_FORWARD": "Forward",
"TOGGLE_HIDDEN_FILES": "Show/Hide Hidden Files"
"TOGGLE_HIDDEN_FILES": "Show/Hide Hidden Files",
"TOGGLE_ICONVIEW_MODE": "as Icons",
"TOGGLE_TABLEVIEW_MODE": "as List"
}
}
}
8 changes: 6 additions & 2 deletions src/locale/lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@
"CLOSE_ACTIVE_TAB": "Fermer l'onglet actif"
},
"ACTIVE_VIEW": {
"TABLE_MODE": "Vue liste",
"ICON_MODE": "Vue icônes",
"COPY": "Copier les éléments sélectionnés dans le presse-papier",
"PASTE": "Coller les éléments sélectionnés dans le presse-papier",
"VIEW_HISTORY": "Afficher l'historique de navigation (debug)",
Expand Down Expand Up @@ -252,7 +254,7 @@
"PASTE": "Coller",
"RELOAD_VIEW": "Recharger Vue Active",
"FORCE_RELOAD_APP": "Recharger l'App",
"KEYBOARD_SHORTCUTS": "List des Raccourcis",
"KEYBOARD_SHORTCUTS": "Liste des Raccourcis",
"ABOUT_TITLE": "React-Explorer",
"ABOUT_CONTENT": "Version: ${version}\nCommit: ${hash}\nDate: ${date}\nElectron: ${electron}\nChrome: ${chrome}\nNode: ${node}\nSE: ${platform} ${arch} ${release}",
"ZOOM": "Zoom",
Expand All @@ -266,7 +268,9 @@
"GO_PARENT": "Dossier parent",
"GO_BACK": "Précédent",
"GO_FORWARD": "Suivant",
"TOGGLE_HIDDEN_FILES": "Voir/Cacher Fichiers Cachés"
"TOGGLE_HIDDEN_FILES": "Voir/Cacher Fichiers Cachés",
"TOGGLE_ICONVIEW_MODE": "Par icônes",
"TOGGLE_TABLEVIEW_MODE": "Par liste"
}
}
}
4 changes: 4 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ViewModeName } from '$src/hooks/useViewMode'
import { FileDescriptor } from '$src/services/Fs'
import { FileState, TStatus } from '$src/state/fileState'
import { IconName } from '@blueprintjs/icons'
Expand Down Expand Up @@ -49,4 +50,7 @@ export interface ReactiveProperties {
filesLength: number
status: TStatus
language: string
viewMode: ViewModeName
}

export type KeyboardLayoutMap = Record<string, string>

0 comments on commit a4bc6ac

Please sign in to comment.