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

Desktop: Keyboard shortcut editor #3525

Merged
merged 53 commits into from
Sep 6, 2020
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
f09a896
Blank config screen
Jul 10, 2020
4763157
Basic UI structure
Jul 18, 2020
2b8bf90
Initial keyboard shortcut editing workflow
Jul 20, 2020
5ba4c90
Remove KeymapUtils.ts in favor of including domToElectronKey, domToEl…
Jul 24, 2020
66c5375
Refactor into subfolders
Jul 25, 2020
5a46b5d
Improve styling
Aug 2, 2020
cd5d574
Merge remote-tracking branch 'upstream/dev' into keyboard-shortcut-ed…
Aug 4, 2020
0f91db7
Merge remote-tracking branch 'upstream/dev' into keyboard-shortcut-ed…
Aug 4, 2020
8440a9c
Integrate with KeymapService
Aug 14, 2020
3465af9
Merge remote-tracking branch 'upstream/dev' into keyboard-shortcut-ed…
Aug 14, 2020
fa08c28
Make compatible with macOS
Aug 15, 2020
57a68b9
Fix Goto Anything shortcut not updating
Aug 15, 2020
de493e6
Simple filtering
Aug 15, 2020
51bc13f
Fix false warning when disabling a shortcut
Aug 15, 2020
52dbc6e
Fix command labels not translating
Aug 20, 2020
0b309d9
Rename property theme to themeId
Aug 20, 2020
8ca3aa0
Use handlers
Aug 21, 2020
42c7122
Refresh menu by emitting an event
Aug 21, 2020
1e63636
Wrap content in div
Aug 21, 2020
264dd21
Remove unneccesary parameter
Aug 21, 2020
5294b68
Accessibility label
Aug 21, 2020
c36f805
Remove interface extend
Aug 21, 2020
88122ab
Change label "Reset to default"
Aug 21, 2020
d98878d
Use shim.fsDriver() for file operations
Aug 21, 2020
9758161
Rename getCommands() to getCommandNames()
Aug 21, 2020
1b6ac9f
Custom hook useKeymap
Aug 21, 2020
a12f4a1
Minor refactoring
Aug 21, 2020
3005f58
Remove indirect method calls
Aug 22, 2020
8a70a97
Rename state variable to make the code clear
Aug 22, 2020
cda1a8f
Pass data as an event object to handler functions
Aug 22, 2020
d2aa3f9
Enable options parameter overriding on CommandService.commandByName()…
Aug 22, 2020
c86d97d
Implement error class KeymapError (For highlighting invalid keymap en…
Aug 22, 2020
4e3b675
Cancel button
Aug 22, 2020
670075f
Better errors; Improve styling
Aug 23, 2020
06723da
Remove obsolete tests
Aug 23, 2020
374ddc3
Fix typo in error message
Aug 23, 2020
3252dde
Tweak styling
Aug 23, 2020
d2b346d
Re-enable clearing the shortcut
Aug 23, 2020
206bd48
Keep only one shortcut recorder open at once
Aug 23, 2020
3568c70
Merge remote-tracking branch 'upstream/dev' into keyboard-shortcut-ed…
Aug 23, 2020
43b2bc1
Minor refactoring
Aug 25, 2020
e274acd
Improve UI/UX
Aug 27, 2020
bc36748
Minor refactoring
Aug 28, 2020
989b3d2
Fix not being allowed to disable shortcuts
Aug 28, 2020
6ab5ccc
Import/Export keymap configuration
Aug 30, 2020
471210f
Refactor and code clean-up
Aug 30, 2020
d5732ff
Add label for Search
Aug 30, 2020
8ca20ad
Remove unneccessary translated string
Aug 30, 2020
e6643b9
Remove unnecessary method from useKeymap hook in favor of directly ca…
Aug 30, 2020
a6d793b
Add tooltips
Sep 1, 2020
80b831d
Fix a bug where it failed to find both of duplicate keymap items
Sep 1, 2020
88f5300
Remove search label
Sep 5, 2020
0bd89c9
Merge branch 'dev' into keyboard-shortcut-editor
Sep 5, 2020
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
8 changes: 8 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Modules/TinyMCE/langs/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js
CliClient/build/LinkSelector.js
CliClient/tests-build/synchronizer_LockHandler.js
CliClient/tests-build/synchronizer_MigrationHandler.js
CliClient/tests/synchronizer_LockHandler.js
CliClient/tests/synchronizer_MigrationHandler.js
ElectronClient/commands/focusElement.js
Expand All @@ -71,6 +73,12 @@ ElectronClient/commands/stopExternalEditing.js
ElectronClient/global.d.js
ElectronClient/gui/ErrorBoundary.js
ElectronClient/gui/Header/commands/focusSearch.js
ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js
ElectronClient/gui/KeymapConfig/ShortcutRecorder.js
ElectronClient/gui/KeymapConfig/styles/index.js
ElectronClient/gui/KeymapConfig/utils/getLabel.js
ElectronClient/gui/KeymapConfig/utils/useCommandStatus.js
ElectronClient/gui/KeymapConfig/utils/useKeymap.js
ElectronClient/gui/MainScreen/commands/editAlarm.js
ElectronClient/gui/MainScreen/commands/exportPdf.js
ElectronClient/gui/MainScreen/commands/hideModalMessage.js
Expand Down
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ Tools/commit_hook.txt
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js
CliClient/build/LinkSelector.js
CliClient/tests-build/synchronizer_LockHandler.js
CliClient/tests-build/synchronizer_MigrationHandler.js
CliClient/tests/synchronizer_LockHandler.js
CliClient/tests/synchronizer_MigrationHandler.js
ElectronClient/commands/focusElement.js
Expand All @@ -62,6 +64,12 @@ ElectronClient/commands/stopExternalEditing.js
ElectronClient/global.d.js
ElectronClient/gui/ErrorBoundary.js
ElectronClient/gui/Header/commands/focusSearch.js
ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js
ElectronClient/gui/KeymapConfig/ShortcutRecorder.js
ElectronClient/gui/KeymapConfig/styles/index.js
ElectronClient/gui/KeymapConfig/utils/getLabel.js
ElectronClient/gui/KeymapConfig/utils/useCommandStatus.js
ElectronClient/gui/KeymapConfig/utils/useKeymap.js
ElectronClient/gui/MainScreen/commands/editAlarm.js
ElectronClient/gui/MainScreen/commands/exportPdf.js
ElectronClient/gui/MainScreen/commands/hideModalMessage.js
Expand Down
64 changes: 25 additions & 39 deletions CliClient/tests/services_KeymapService.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ describe('services_KeymapService', () => {
{ command: 'focusElementNoteTitle', accelerator: 'Option+Shift+Cmd+T' },
{ command: 'focusElementNoteBody', accelerator: 'Ctrl+Option+Shift+Cmd+B' },
];

testCases_Darwin.forEach(({ command, accelerator }) => {
keymapService.setAccelerator(command, accelerator);
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
Expand All @@ -131,17 +132,18 @@ describe('services_KeymapService', () => {
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+T' },
{ command: 'focusElementNoteBody', accelerator: 'Ctrl+Alt+Shift+B' },
];

testCases_Linux.forEach(({ command, accelerator }) => {
keymapService.setAccelerator(command, accelerator);
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
});
});
});

describe('resetAccelerator', () => {
describe('getDefaultAccelerator', () => {
beforeEach(() => keymapService.initialize());

it('should reset the Accelerator', () => {
it('should return the default accelerator', () => {
const testCases = [
{ command: 'newNote', accelerator: 'Ctrl+Alt+Shift+N' },
{ command: 'synchronize', accelerator: null /* Disabled */ },
Expand All @@ -154,26 +156,22 @@ describe('services_KeymapService', () => {
];

testCases.forEach(({ command, accelerator }) => {
// Remember the default Accelerator value
const prevAccelerator = keymapService.getAccelerator(command);
// Remember the real default Accelerator value
const defaultAccelerator = keymapService.getAccelerator(command);

// Update the Accelerator,
// Update the Accelerator and then retrieve the default accelerator
keymapService.setAccelerator(command, accelerator);
expect(keymapService.getAccelerator(command)).toEqual(accelerator);

// and then reset it..
keymapService.resetAccelerator(command);
expect(keymapService.getAccelerator(command)).toEqual(prevAccelerator);
expect(keymapService.getDefaultAccelerator(command)).toEqual(defaultAccelerator);
});
});
});

describe('setKeymap', () => {
describe('overrideKeymap', () => {
beforeEach(() => keymapService.initialize());

it('should update the keymap', () => {
keymapService.initialize('darwin');
const customKeymap_Darwin = [
const customKeymapItems_Darwin = [
{ command: 'newNote', accelerator: 'Option+Shift+Cmd+N' },
{ command: 'synchronize', accelerator: 'F11' },
{ command: 'textBold', accelerator: 'Shift+F5' },
Expand All @@ -187,14 +185,14 @@ describe('services_KeymapService', () => {
{ command: 'focusElementNoteList', accelerator: 'Shift+Cmd+S' /* Default of focusElementSideBar */ },
];

expect(() => keymapService.setKeymap(customKeymap_Darwin)).not.toThrow();
customKeymap_Darwin.forEach(({ command, accelerator }) => {
expect(() => keymapService.overrideKeymap(customKeymapItems_Darwin)).not.toThrow();
customKeymapItems_Darwin.forEach(({ command, accelerator }) => {
// Also check if keymap is updated or not
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
});

keymapService.initialize('win32');
const customKeymap_Win32 = [
const customKeymapItems_Win32 = [
{ command: 'newNote', accelerator: 'Ctrl+Alt+Shift+N' },
{ command: 'synchronize', accelerator: 'F11' },
{ command: 'textBold', accelerator: 'Shift+F5' },
Expand All @@ -208,8 +206,8 @@ describe('services_KeymapService', () => {
{ command: 'focusElementNoteList', accelerator: 'Ctrl+Shift+S' /* Default of focusElementSideBar */ },
];

expect(() => keymapService.setKeymap(customKeymap_Win32)).not.toThrow();
customKeymap_Win32.forEach(({ command, accelerator }) => {
expect(() => keymapService.overrideKeymap(customKeymapItems_Win32)).not.toThrow();
customKeymapItems_Win32.forEach(({ command, accelerator }) => {
// Also check if keymap is updated or not
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
});
Expand Down Expand Up @@ -240,30 +238,30 @@ describe('services_KeymapService', () => {
];

for (let i = 0; i < customKeymaps.length; i++) {
const customKeymap = customKeymaps[i];
expect(() => keymapService.setKeymap(customKeymap)).toThrow();
const customKeymapItems = customKeymaps[i];
expect(() => keymapService.overrideKeymap(customKeymapItems)).toThrow();
}
});

it('should throw when the provided Accelerators are invalid', () => {
// Only one test case is provided since KeymapService.validateAccelerator() is already tested
const customKeymap = [
const customKeymapItems = [
{ command: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
{ command: 'print', accelerator: 'Alt+P' },
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+J+O+P+L+I+N' },
];

expect(() => keymapService.setKeymap(customKeymap)).toThrow();
expect(() => keymapService.overrideKeymap(customKeymapItems)).toThrow();
});

it('should throw when the provided commands are invalid', () => {
const customKeymap = [
const customKeymapItems = [
{ command: 'totallyInvalidCommand', accelerator: 'Ctrl+Shift+G' },
{ command: 'print', accelerator: 'Alt+P' },
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+J' },
];

expect(() => keymapService.setKeymap(customKeymap)).toThrow();
expect(() => keymapService.overrideKeymap(customKeymapItems)).toThrow();
});

it('should throw when duplicate accelerators are provided', () => {
Expand All @@ -281,14 +279,8 @@ describe('services_KeymapService', () => {
];

for (let i = 0; i < customKeymaps_Darwin.length; i++) {
const customKeymap = customKeymaps_Darwin[i];
const defaultAccelerators = customKeymap.map(({ command }) => keymapService.getAccelerator(command));

expect(() => keymapService.setKeymap(customKeymap)).toThrow();
// All items should be reset to default values
for (let j = 0; j < customKeymap.length; j++) {
expect(keymapService.getAccelerator(customKeymap[j].command)).toEqual(defaultAccelerators[j]);
}
const customKeymapItems = customKeymaps_Darwin[i];
expect(() => keymapService.overrideKeymap(customKeymapItems)).toThrow();
}

const customKeymaps_Linux = [
Expand All @@ -305,14 +297,8 @@ describe('services_KeymapService', () => {
];

for (let i = 0; i < customKeymaps_Linux.length; i++) {
const customKeymap = customKeymaps_Linux[i];
const defaultAccelerators = customKeymap.map(({ command }) => keymapService.getAccelerator(command));

expect(() => keymapService.setKeymap(customKeymap)).toThrow();

for (let j = 0; j < customKeymap.length; j++) {
expect(keymapService.getAccelerator(customKeymap[j].command)).toEqual(defaultAccelerators[j]);
}
const customKeymapItems = customKeymaps_Linux[i];
expect(() => keymapService.overrideKeymap(customKeymapItems)).toThrow();
}
});
});
Expand Down
14 changes: 8 additions & 6 deletions ElectronClient/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const PluginManager = require('lib/services/PluginManager');
const RevisionService = require('lib/services/RevisionService');
const MigrationService = require('lib/services/MigrationService');
const CommandService = require('lib/services/CommandService').default;
const KeymapService = require('lib/services/KeymapService.js').default;
const KeymapService = require('lib/services/KeymapService').default;
const TemplateUtils = require('lib/TemplateUtils');
const CssUtils = require('lib/CssUtils');
const resourceEditWatcherReducer = require('lib/services/ResourceEditWatcher/reducer').default;
Expand Down Expand Up @@ -110,6 +110,8 @@ class Application extends BaseApplication {

this.commandService_commandsEnabledStateChange = this.commandService_commandsEnabledStateChange.bind(this);
CommandService.instance().on('commandsEnabledStateChange', this.commandService_commandsEnabledStateChange);

KeymapService.instance().on('keymapChange', this.refreshMenu.bind(this));
}

commandService_commandsEnabledStateChange() {
Expand Down Expand Up @@ -569,7 +571,7 @@ class Application extends BaseApplication {
const toolsItemsWindowsLinux = toolsItemsFirst.concat([{
label: _('Options'),
visible: !shim.isMac(),
accelerator: shim.isMac() ? null : keymapService.getAccelerator('config'),
accelerator: !shim.isMac() && keymapService.getAccelerator('config'),
click: () => {
this.dispatch({
type: 'NAV_GO',
Expand Down Expand Up @@ -631,7 +633,7 @@ class Application extends BaseApplication {
}, {
label: _('Preferences...'),
visible: shim.isMac() ? true : false,
accelerator: shim.isMac() ? keymapService.getAccelerator('config') : null,
accelerator: shim.isMac() && keymapService.getAccelerator('config'),
click: () => {
this.dispatch({
type: 'NAV_GO',
Expand Down Expand Up @@ -680,7 +682,7 @@ class Application extends BaseApplication {
}, {
label: _('Hide %s', 'Joplin'),
platforms: ['darwin'],
accelerator: shim.isMac() ? keymapService.getAccelerator('hideApp') : null,
accelerator: shim.isMac() && keymapService.getAccelerator('hideApp'),
click: () => { bridge().electronApp().hide(); },
}, {
type: 'separator',
Expand All @@ -700,7 +702,7 @@ class Application extends BaseApplication {
newNotebookItem, {
label: _('Close Window'),
platforms: ['darwin'],
accelerator: shim.isMac() ? keymapService.getAccelerator('closeWindow') : null,
accelerator: shim.isMac() && keymapService.getAccelerator('closeWindow'),
selector: 'performClose:',
}, {
type: 'separator',
Expand Down Expand Up @@ -1087,7 +1089,7 @@ class Application extends BaseApplication {
const keymapService = KeymapService.instance();

try {
await KeymapService.instance().loadKeymap(`${dir}/keymap-desktop.json`);
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
} catch (err) {
bridge().showErrorMessageBox(err.message);
}
Expand Down
2 changes: 2 additions & 0 deletions ElectronClient/gui/ConfigScreen.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const shared = require('lib/components/shared/config-shared.js');
const ConfigMenuBar = require('./ConfigMenuBar.min.js');
const { EncryptionConfigScreen } = require('./EncryptionConfigScreen.min');
const { ClipperConfigScreen } = require('./ClipperConfigScreen.min');
const { KeymapConfigScreen } = require('./KeymapConfig/KeymapConfigScreen');

class ConfigScreenComponent extends React.Component {
constructor() {
Expand Down Expand Up @@ -68,6 +69,7 @@ class ConfigScreenComponent extends React.Component {
screenFromName(screenName) {
if (screenName === 'encryption') return <EncryptionConfigScreen theme={this.props.theme}/>;
if (screenName === 'server') return <ClipperConfigScreen theme={this.props.theme}/>;
if (screenName === 'keymap') return <KeymapConfigScreen themeId={this.props.theme}/>;

throw new Error(`Invalid screen name: ${screenName}`);
}
Expand Down
Loading