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

Conversation

anjulalk
Copy link
Contributor

@anjulalk anjulalk commented Jul 18, 2020

Contains the implementation of Keyboard shortcut editor GUI. This PR includes,

  • Integration with KeymapService and CommandService
  • Display a list of available commands along with their keyboard shortcuts
  • Allow editing a keyboard shortcut, or reset it to the default shortcut
  • Synchronize changes with the keymap-desktop.json file in the profile directory
  • Search for keyboard shortcuts by their command name or label

@anjulalk anjulalk marked this pull request as draft July 18, 2020 18:53
@tessus tessus added gsoc-2020 desktop All desktop platforms labels Jul 18, 2020
Copy link
Owner

@laurent22 laurent22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anjulalk, I've left a few comments, although I realise it's a WIP so maybe you were already planning to do some of what I wrote.

One thing I would note is that you should check existing .tsx components in the codebase - how they are organised, what are the naming conventions, etc. Large effects should be moved to custom hooks in separate file, although you don't have any such hooks yet.

Comment on lines 35 to 42
const initialKeymap = commandNames.map(commandName => {
return {
label: CommandService.instance().label(commandName),
accelerator: getAccelerator(commandName),
command: commandName,
isEditing: false,
};
});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be loaded inside the component, using useEffect for example. In general, only constants or functions can be defined outside the component. Any state-related variables should be defined inside the component (or as hooks).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've passed a state initialization function to useState(). It'll only be initialized once per lifetime.

ElectronClient/gui/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ReactNativeClient/lib/KeymapUtils.ts Outdated Show resolved Hide resolved
ReactNativeClient/lib/KeymapUtils.ts Outdated Show resolved Hide resolved
ReactNativeClient/lib/KeymapUtils.ts Outdated Show resolved Hide resolved
ReactNativeClient/lib/services/CommandService.ts Outdated Show resolved Hide resolved
@anjulalk
Copy link
Contributor Author

Thanks a lot for the early feedback. It'll certainly help me move in the right direction. I believe I addressed all the issues you've mentioned.

I'll continue to refine the code from examples as I progress. Thanks!

Copy link
Owner

@laurent22 laurent22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @anjulalk, I've left a few more comments. One thing that's not present yet is the logic to save keymaps. I think it would be best to get this in relatively soon so that we can get a full picture of how it's going to work.

ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfig/ShortcutRecorder.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfig/styles/index.ts Outdated Show resolved Hide resolved
@anjulalk anjulalk changed the base branch from master to dev August 14, 2020 22:53
Copy link
Owner

@laurent22 laurent22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update @anjulalk. Overall the design of your component is pretty good and I like how you use array functions like map, reduce and filter to make the intent of your code clear. I’ve left a few comments, which are a bit short as I’m on a tablet, so if something’s unclear please let me know.

ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved

// Some commands aren't actually registered in CommandService
// These hardcoded labels are used for those commands instead
const commandLabels: CommandLabels = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the labels get updated when the user changes the language ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrapped this logic in a function. Now it's working even after changing the language.

ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ReactNativeClient/lib/services/KeymapService.ts Outdated Show resolved Hide resolved
ReactNativeClient/lib/services/KeymapService.ts Outdated Show resolved Hide resolved
toggleEditing: () => void,
}

export const ShortcutRecorder = ({ setAccelerator, resetAccelerator, toggleEditing, theme }: ShortcutRecorderProps) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The child component logic should be decoupled from that of the parent. ShortcutRecorder should only take three event handlers: onSave, onCancel, onReset then on the parent component you handle the business logic to set keymap.

In fact I don’t see anything in this component to cancel input but that should be present

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the logic to use handlers instead. Thanks for pointing that out.

ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
@laurent22
Copy link
Owner

@anjulalk, any update on your pull request? If there's something tricky about some of my comments, please let me know.

@anjulalk
Copy link
Contributor Author

I'm still working on some issues. I'll be able to push the changes within the next 24 hours. Apologies for the delay.

ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
Comment on lines 91 to 108
export class KeymapError extends Error {
private details_: KeymapErrorDetails;

constructor(message: string, details: KeymapErrorDetails = {}) {
super(message);
this.details_ = {
missingAccelerator: false,
missingCommand: false,
invalidAccelerator: false,
invalidCommand: false,
duplicateAccelerators: false,
...details,
};
}

get details() {
return this.details_;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you extend JoplinError and simply set the code to some string? I think the details object with potentially multiple error causes seem unnecessarily complex. The keymap shortcut should be validated when the user inputs it anyway so we shouldn't have so much complexity going on. Check what they type, throw back an error with a message and that's almost all we need.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. In that case I'll add some error handling to the ShortcutRecorder component. I'll update it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay to keep just an array of invalid Keymap items, so that we can show the little status icon next to the shortcut?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What could cause the keymap to have multiple invalid items? If an item is invalid it shouldn't be possible to save it anyway, so there could only be one invalid item (the currently active, but unsaved, one), or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you're right. We should prevent the user from causing the error by validating the shortcut before the user sets it.
I'm working on it at the moment. I'll push an update in the next 24 hours.

@anjulalk anjulalk marked this pull request as ready for review August 27, 2020 21:10
@anjulalk anjulalk changed the title Desktop: Keyboard shortcut editor (WIP) Desktop: Keyboard shortcut editor Aug 27, 2020
Copy link
Owner

@laurent22 laurent22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anjulalk, thanks for the recent changes. I found just two remaining issues and I think it will be ready.


<div style={styles.container}>
<div style={styles.topActions}>
<input
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the Search label hasn't been added yet?

Comment on lines 82 to 95
export class KeymapError extends Error {
// Translated error message for the GUI
private altMessage_: string;

constructor(message: string, altMessage: string) {
super(message);
this.altMessage_ = altMessage;
}

get altMessage() {
return this.altMessage_;
}
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the altMessage property - error should only have one message. In fact, as far as I can see there's no actual use for the KeymapError object anymore so please remove KeymapError and use a regular Error object.

To throw the error, please use this:

new Error(_('Accelerator "%s" is already used for command "%s".', item.accelerator, originalItem.command));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for mentioning the example. I agree that it's unneccessary. My initial idea was give a friendly error message in the GUI.
I'll replace them with one message.

@anjulalk
Copy link
Contributor Author

I've added the label for the search input. Previously I thought you meant the aria-label property, so I added it. Sorry about that.

@anjulalk
Copy link
Contributor Author

anjulalk commented Aug 30, 2020

I've pushed an update with Import/Export keymap functionality. Please review when you've got the time. I tested every feature and everything seems to be working as expected.

I've also cleaned up some unused code, and refactored most of the code. It should be much clear and easy to follow now.

Copy link
Owner

@laurent22 laurent22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update @anjulalk, I've tried the editor and it's all looking good. I've only left two comments for the import/export feature.

Also please could you add the following tooltips?

  • When a shortcut is invalid (for example for a duplicate shortcut), you display a small warning icon to the right, which is good, but please could you also add a tooltip on this icon with the complete error message (which would be from the error object that you throw)?

  • Please could you add a tooltip over the shortcut recorder input field? It should be "Press the shortcut, then press ENTER. Or BACKSPACE to clear the shortcut".

if (filePath) {
try {
// KeymapService is already synchronized with the in-state keymap
await exportCustomKeymap(filePath);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please call await keymapService.saveCustomKeymap(filePath); directly from here. You're importing KeymapService into this component anyway so the additional layer of indirection is unnecessary. Also it's best to keep React hooks small. So if you really wanted to use a hook here, it would be best to create a new one like "useImportExport" and put the import/export feature there, but it won't be necessary to do it now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, it's unneccessary. I'll remove it and call the method directly.

ElectronClient/gui/KeymapConfig/KeymapConfigScreen.tsx Outdated Show resolved Hide resolved
@anjulalk
Copy link
Contributor Author

anjulalk commented Sep 1, 2020

I made the requested changes base on your feedback. Please have a look.

After adding the label, it looks a bit odd because the label is "Search" and the search box also says "Search...". Can we change the search box placeholder to something like "Type in here to search...", or something like that?

image

IMHO, I think it'd look better without the label. But the current layout is fine too.

@laurent22
Copy link
Owner

Thanks for the update @anjulalk.

After adding the label, it looks a bit odd because the label is "Search" and the search box also says "Search...".

Hmm, now that you say it, it indeed doesn't look right. Could you remove it actually and make it like it was before? Sorry about that!

Also you have some conflicts on the ignore files. Once these small changes are done I think it's all good, and we can merge.

@anjulalk anjulalk force-pushed the keyboard-shortcut-editor branch 2 times, most recently from 8ade9a2 to b73ad39 Compare September 5, 2020 19:07
@anjulalk
Copy link
Contributor Author

anjulalk commented Sep 5, 2020

I've updated the pull request. Please review when you have the time. Thanks!

@laurent22
Copy link
Owner

That's great, thanks @anjulalk! We'll make the feature part of the next release.

@laurent22 laurent22 merged commit a8296e2 into laurent22:dev Sep 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
desktop All desktop platforms
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants