Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

fix: LEAP-248: Fix app crashing by hotkeys #1653

Merged
merged 6 commits into from
Jan 5, 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
32 changes: 23 additions & 9 deletions src/core/Hotkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ keymaster.filter = function(event) {
const ALIASES = {
'plus': '=', // "ctrl plus" is actually a "ctrl =" because shift is not used
'minus': '-',
// Here is a magic trick. Keymaster doesn't work with comma correctly (it breaks down everything upon unbinding), but the key code for comma it expects is 188
// And the magic is that '¼' has the same keycode. So we are going to trick keymaster to handle this in the right way.
',': '¼',
};

export const Hotkey = (
Expand Down Expand Up @@ -144,17 +147,27 @@ export const Hotkey = (
});
};

const getKeys = (key: string) => {
const tokenRegex = /((?:\w+\+)*(?:[^,]+|,)),?/g;

return [...key.replace(/\s/,'').matchAll(tokenRegex)].map(match => match[1]);
};

const unbind = () => {
for (const scope of [DEFAULT_SCOPE, INPUT_SCOPE]) {
for (const key of Object.keys(_hotkeys_map)) {
if (isFF(FF_LSDV_1148)) {
removeKeyHandlerRef(scope, key);
keymaster.unbind(key, scope);
rebindKeyHandlers(scope, key);
} else {
keymaster.unbind(key, scope);
const keys = getKeys(key);

for (const key of keys) {
if (isFF(FF_LSDV_1148)) {
removeKeyHandlerRef(scope, key);
keymaster.unbind(key, scope);
rebindKeyHandlers(scope, key);
} else {
keymaster.unbind(key, scope);
}
delete _hotkeys_desc[key];
}
delete _hotkeys_desc[key];
}
}

Expand All @@ -165,8 +178,9 @@ export const Hotkey = (

return {
applyAliases(key: string) {
return key
.split(',')
const keys = getKeys(key);

return keys
.map(k => k.split('+').map(k => ALIASES[k.trim()] ?? k).join('+'))
.join(',');
},
Expand Down
6 changes: 6 additions & 0 deletions tests/functional/data/core/hotkeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const createConfigWithHotkey = (hotkey: string) => `<View>
<Image name="img" value="$image"/>
<RectangleLabels name="tag" toName="img">
<Label value="Label" hotkey="${hotkey}" />
</RectangleLabels>
</View>`;
68 changes: 68 additions & 0 deletions tests/functional/specs/core/hotkeys.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Labels, LabelStudio } from '@heartexlabs/ls-test/helpers/LSF';
import {
createConfigWithHotkey
} from '../../data/core/hotkeys';
import { Generator } from '@heartexlabs/ls-test/helpers/common/Generator';

describe('Hotkeys', () => {
const hotkeysToCheck = [
['L', 'r'],
['ctrl+L', 'ctrl+r'],
[','],
[',',',',','], // it won't work with odd number of commas 'cause 2 calls of selecting label cancel each other
['a',','],
[',','b'],
['a',',','b'],
['ctrl+,','ctrl+.'],
['ctrl+.','ctrl+,'],
];

for (const hotkeys of hotkeysToCheck) {
const hotkeyString = hotkeys.join(',');

it(`should be able to use hotkey ${hotkeyString}`, () => {
Generator.generateImageUrl({ width: 800, height: 50 })
.then(imageUrl => {
LabelStudio.params()
.config(createConfigWithHotkey(hotkeyString))
.data({ image: imageUrl })
.withResult([])
.init();
});

LabelStudio.waitForObjectsReady();
Labels.label.should('have.length', 1);

cy.log('Check that hotkeys work');
for (const hotkey of hotkeys) {
const hotkeyInput = hotkey.includes('+') ? `{${hotkey}}` : `${hotkey}`;

// try to use hotkey
cy.get('body').type(hotkeyInput);
Labels.selectedLabel.contains('Label');
// deselect
cy.get('body').type('{esc}');
}

cy.log('Reload LS');
cy.window().then(win => {
win.LabelStudio.destroyAll();
new win.LabelStudio('label-studio', win.LSF_CONFIG);
});

LabelStudio.waitForObjectsReady();
Labels.label.should('have.length', 1);

cy.log('Check that hotkeys still work');
for (const hotkey of hotkeys) {
const hotkeyInput = hotkey.includes('+') ? `{${hotkey}}` : `${hotkey}`;

// try to use hotkey
cy.get('body').type(hotkeyInput);
Labels.selectedLabel.contains('Label');
// deselect
cy.get('body').type('{esc}');
}
});
}
});
Loading