-
-
Notifications
You must be signed in to change notification settings - Fork 5k
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
Conversation
There was a problem hiding this 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.
const initialKeymap = commandNames.map(commandName => { | ||
return { | ||
label: CommandService.instance().label(commandName), | ||
accelerator: getAccelerator(commandName), | ||
command: commandName, | ||
isEditing: false, | ||
}; | ||
}); |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
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! |
…ectronAccelerator functions in KeymapService itself
e052cd4
to
66c5375
Compare
There was a problem hiding this 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.
There was a problem hiding this 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.
|
||
// Some commands aren't actually registered in CommandService | ||
// These hardcoded labels are used for those commands instead | ||
const commandLabels: CommandLabels = { |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
toggleEditing: () => void, | ||
} | ||
|
||
export const ShortcutRecorder = ({ setAccelerator, resetAccelerator, toggleEditing, theme }: ShortcutRecorderProps) => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
@anjulalk, any update on your pull request? If there's something tricky about some of my comments, please let me know. |
I'm still working on some issues. I'll be able to push the changes within the next 24 hours. Apologies for the delay. |
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_; | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
09281ad
to
989b3d2
Compare
There was a problem hiding this 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 |
There was a problem hiding this comment.
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?
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_; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
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));
There was a problem hiding this comment.
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.
I've added the label for the search input. Previously I thought you meant the |
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. |
There was a problem hiding this 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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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? IMHO, I think it'd look better without the label. But the current layout is fine too. |
Thanks for the update @anjulalk.
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. |
8ade9a2
to
b73ad39
Compare
b73ad39
to
88f5300
Compare
I've updated the pull request. Please review when you have the time. Thanks! |
That's great, thanks @anjulalk! We'll make the feature part of the next release. |
Contains the implementation of Keyboard shortcut editor GUI. This PR includes,
keymap-desktop.json
file in the profile directory