Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

appMenus: added new menu to switch icon/table view #445

Merged
merged 1 commit into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
Loading
Loading