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

Commit 271beab

Browse files
authored
fix: DEV-3730: Make shortcut buttons work without focusing textarea (#1127)
1 parent c3ed720 commit 271beab

File tree

4 files changed

+163
-7
lines changed

4 files changed

+163
-7
lines changed

e2e/fragments/AtOutliner.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* global inject, locate */
2+
const { I } = inject();
3+
4+
module.exports = {
5+
_rootSelector: '.lsf-outliner',
6+
_regionListSelector: '.lsf-outliner-tree',
7+
_regionListItemSelector: '.lsf-tree__node',
8+
_regionListItemIndex: '.lsf-outliner-item__index',
9+
locateOutliner() {
10+
return locate(this._rootSelector);
11+
},
12+
locate(locator) {
13+
return locator ? locate(locator).inside(this.locateOutliner()) : this.locateOutliner();
14+
},
15+
locateRegionList() {
16+
return this.locate(this._regionListSelector);
17+
},
18+
locateRegionItemList() {
19+
return locate(this._regionListItemSelector).inside(this.locateRegionList());
20+
},
21+
locateRegionItemIndex(idx) {
22+
return locate(this._regionListItemIndex).withText(`${idx}`).inside(this.locateRegionItemList());
23+
},
24+
seeRegions(count) {
25+
count && I.seeElement(this.locateRegionItemList().at(count));
26+
I.dontSeeElement(this.locateRegionItemList().at(count + 1));
27+
},
28+
clickRegion(idx) {
29+
I.click(this.locateRegionItemIndex(idx));
30+
},
31+
};

e2e/tests/shortcuts.test.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,102 @@ Data(configParams).Scenario('Should work with existent regions.', async ({ I, La
200200
AtSidebar.see('-A + B!');
201201
});
202202

203+
{
204+
const paramsTable = new DataTable(['isInline', 'isPerRegion']);
205+
206+
for (const isPerRegion of [true, false]) {
207+
for (const isInline of [true, false]) {
208+
paramsTable.add([isInline, isPerRegion]);
209+
}
210+
}
211+
212+
const createConfig = ({ rows = '1' }) => {
213+
return `<View>
214+
<Text name="text" value="$text" />
215+
<TextArea name="comment" toName="text" editable="true" rows="${rows}">
216+
<Shortcut value="Shortcut" hotkey="Alt+V" />
217+
</TextArea>
218+
</View>
219+
`;
220+
};
221+
const createPerRegionConfig = ({ rows = '1' }) => {
222+
return `<View>
223+
<Labels name="label" toName="text">
224+
<Label value="region" />
225+
</Labels>
226+
<Text name="text" value="$text" />
227+
<TextArea name="comment" toName="text" editable="true" rows="${rows}">
228+
<Shortcut value="Shortcut" hotkey="Alt+V" />
229+
</TextArea>
230+
</View>
231+
`;
232+
};
233+
234+
Data(paramsTable).Scenario('Initial focus', async ({ I, LabelStudio, AtOutliner, current }) => {
235+
const { isInline, isPerRegion } = current;
236+
const config = (isPerRegion ? createPerRegionConfig : createConfig)({
237+
rows: isInline ? '1' : '3',
238+
});
239+
240+
I.amOnPage('/');
241+
LabelStudio.setFeatureFlags({
242+
ff_front_dev_1564_dev_1565_shortcuts_focus_and_cursor_010222_short: true,
243+
ff_front_dev_1566_shortcuts_in_results_010222_short: true,
244+
ff_front_1170_outliner_030222_short: true,
245+
fflag_fix_front_dev_3730_shortcuts_initial_input_22122022_short: true,
246+
});
247+
248+
LabelStudio.init({
249+
config,
250+
data: { text: 'A simple text' },
251+
annotations: [{
252+
id: 'Test',
253+
result: isPerRegion ? [
254+
{
255+
'value': {
256+
'start': 9,
257+
'end': 13,
258+
'text': 'text',
259+
'labels': [
260+
'region',
261+
],
262+
},
263+
'id': 'Yk0XNP_zRJ',
264+
'from_name': 'label',
265+
'to_name': 'text',
266+
'type': 'labels',
267+
'origin': 'manual',
268+
},
269+
] : [],
270+
}],
271+
});
272+
273+
if (isPerRegion) {
274+
I.say('Select region');
275+
AtOutliner.clickRegion(1);
276+
}
277+
I.say('Try to use shortcut at the start');
278+
I.click(locate('.ant-tag').withText('Shortcut'));
279+
// eslint-disable-next-line
280+
// pause();
281+
282+
I.waitForValue('[name="comment"]', 'Shortcut');
283+
284+
I.say('Commit text');
285+
I.pressKey(['Shift', 'Enter']);
286+
287+
I.say('Check that shortcuts still work in the edit mode');
288+
I.click('[aria-label="edit"]');
289+
I.click(locate('.ant-tag').withText('Shortcut'));
290+
I.waitForValue('[name^="comment:"]', 'ShortcutShortcut');
291+
292+
I.say('Commit changes');
293+
I.pressKey(['Shift', 'Enter']);
294+
295+
I.say('Check that shortcuts work with textarea again');
296+
I.click(locate('.ant-tag').withText('Shortcut'));
297+
I.waitForValue('[name="comment"]', 'Shortcut');
298+
299+
});
300+
}
301+

src/tags/control/TextArea/TextArea.js

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react';
1+
import React, { createRef, forwardRef, useCallback, useEffect, useMemo, useRef } from 'react';
22
import { Button, Form, Input } from 'antd';
33
import { observer } from 'mobx-react';
44
import { destroy, isAlive, types } from 'mobx-state-tree';
@@ -18,7 +18,7 @@ import styles from '../../../components/HtxTextBox/HtxTextBox.module.scss';
1818
import { Block, Elem } from '../../../utils/bem';
1919
import './TextArea.styl';
2020
import { IconTrash } from '../../../assets/icons';
21-
import { FF_DEV_1564_DEV_1565, isFF } from '../../../utils/feature-flags';
21+
import { FF_DEV_1564_DEV_1565, FF_DEV_3730, isFF } from '../../../utils/feature-flags';
2222

2323
const { TextArea } = Input;
2424

@@ -82,6 +82,7 @@ const Model = types.model({
8282
}).volatile(() => {
8383
return {
8484
focusable: true,
85+
textareaRef: createRef(),
8586
};
8687
}).views(self => ({
8788
get isEditable() {
@@ -137,6 +138,14 @@ const Model = types.model({
137138
let lastActiveElement = null;
138139
let lastActiveElementModel = null;
139140

141+
const isAvailableElement = (element, elementModel) => {
142+
if (!element || !elementModel || !isAlive(elementModel)) return false;
143+
// Not available if active element is disappeared
144+
if (self === elementModel && !self.showSubmit) return false;
145+
if (!element.parentElement) return false;
146+
return true;
147+
};
148+
140149
return {
141150
getSerializableValue() {
142151
const texts = self.regions.map(s => s._value);
@@ -232,11 +241,21 @@ const Model = types.model({
232241

233242
onShortcut(value) {
234243
if (isFF(FF_DEV_1564_DEV_1565)) {
235-
if (!lastActiveElement || !lastActiveElementModel || !isAlive(lastActiveElementModel)) return;
236-
// Do nothing if active element is disappeared
237-
if (self === lastActiveElementModel && !self.showSubmit) return;
238-
if (!lastActiveElement.parentElement) return;
239-
244+
if (!isAvailableElement(lastActiveElement, lastActiveElementModel)) {
245+
if (isFF(FF_DEV_3730)) {
246+
// Try to use main textarea element
247+
const textareaElement = self.textareaRef.current?.input || self.textareaRef.current?.resizableTextArea?.textArea;
248+
249+
if (isAvailableElement(textareaElement, self)) {
250+
lastActiveElement = textareaElement;
251+
lastActiveElementModel = self;
252+
} else {
253+
return;
254+
}
255+
} else {
256+
return;
257+
}
258+
}
240259
lastActiveElement.setRangeText(value, lastActiveElement.selectionStart, lastActiveElement.selectionEnd, 'end');
241260
lastActiveElementModel.setValue(lastActiveElement.value);
242261
} else {
@@ -313,6 +332,7 @@ const HtxTextArea = observer(({ item }) => {
313332
item.setValue(value);
314333
},
315334
onFocus,
335+
ref: item.textareaRef,
316336
};
317337

318338
if (rows > 1) {

src/utils/feature-flags.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ export const FF_DEV_3617 = 'fflag_fix_front_dev_3617_taxonomy_memory_leaks_fix';
130130
*/
131131
export const FF_DEV_1284 = 'fflag_fix_front_dev_1284_auto_detect_undo_281022_short';
132132

133+
/**
134+
* Allow shourtcuts button to work with visible main textarea when there is no focus
135+
* @link https://app.launchdarkly.com/default/production/features/fflag_fix_front_dev_3730_shortcuts_initial_input_22122022_short
136+
*/
137+
export const FF_DEV_3730 = 'fflag_fix_front_dev_3730_shortcuts_initial_input_22122022_short';
138+
133139
function getFeatureFlags() {
134140
return {
135141
...(window.APP_SETTINGS?.feature_flags ?? {}),

0 commit comments

Comments
 (0)