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

Commit 624dfac

Browse files
juliosgarbibmartel
andauthored
feat: LSDV-5255: Highlight the active paragraph text segment (#1500)
* feat: LSDV-5255: Highlight the active paragraph text segment * put wrapper behind FF * fix the paragraphs container overflow to not have horizontal scrollbars * fix paragraph active indicator to work regardless of playback state, fix NaN formatting of time ranges. * put playback aria-label directly on the paragraph button always * fix collapsed state of filtered paragraph segments * round the the seconds * fix audio paragraphs tests * fix e2e tests * add wait in the test to scroll to beginning --------- Co-authored-by: bmartel <brandonmartel@gmail.com>
1 parent 083ca0a commit 624dfac

File tree

8 files changed

+184
-37
lines changed

8 files changed

+184
-37
lines changed

e2e/tests/sync/audio-paragraphs.test.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,16 @@ const data = {
5454
url: 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/audio/barradeen-emotional.mp3',
5555
text: [
5656
{
57-
'end': 2,
57+
'end': 3,
5858
'text': 'Dont you hate that?',
59-
'start': 0,
59+
'start': 1,
6060
'author': 'Mia Wallace',
6161
},
6262
{
6363
'text': 'Hate what?',
64-
'start': 2,
64+
'start': 3,
6565
'author': 'Vincent Vega:',
66-
'duration': 2,
66+
'duration': 1,
6767
},
6868
{
6969
'text': 'Uncomfortable silences. Why do we feel its necessary to yak about bullshit in order to be comfortable?',
@@ -172,10 +172,10 @@ FFlagMatrix(['fflag_feat_front_lsdv_e_278_contextual_scrolling_short'], function
172172
assert.equal(startingAudioTime, startingParagraphAudioTime);
173173
assert.equal(startingParagraphAudioTime, 0);
174174

175-
I.click('[aria-label="play-circle"]');
175+
I.click('[aria-label="play"]');
176176
I.wait(1);
177177

178-
I.click('[aria-label="pause-circle"]');
178+
I.click('[aria-label="play"]');
179179
I.wait(1);
180180

181181
const [{ currentTime: seekAudioTime }, { currentTime: seekParagraphAudioTime }] = await AtAudioView.getCurrentAudio();
@@ -209,29 +209,29 @@ FFlagMatrix(['fflag_feat_front_lsdv_e_278_contextual_scrolling_short'], function
209209
AtAudioView.clickPauseButton();
210210

211211
// Plays the first paragraph segment when the audio interface is played
212-
I.seeElement('[data-testid="phrase:0"] [aria-label="pause-circle"]');
213-
I.seeElement('[data-testid="phrase:1"] [aria-label="play-circle"]');
214-
I.seeElement('[data-testid="phrase:2"] [aria-label="play-circle"]');
215-
I.seeElement('[data-testid="phrase:3"] [aria-label="play-circle"]');
216-
I.seeElement('[data-testid="phrase:4"] [aria-label="play-circle"]');
212+
I.seeElement('[data-testid="phrase:0"] [aria-label="pause"]');
213+
I.seeElement('[data-testid="phrase:1"] [aria-label="play"]');
214+
I.seeElement('[data-testid="phrase:2"] [aria-label="play"]');
215+
I.seeElement('[data-testid="phrase:3"] [aria-label="play"]');
216+
I.seeElement('[data-testid="phrase:4"] [aria-label="play"]');
217217

218218
I.wait(2);
219219

220220
// Plays the second paragraph segment when the audio progresses to the second paragraph segment
221-
I.seeElement('[data-testid="phrase:1"] [aria-label="pause-circle"]');
222-
I.seeElement('[data-testid="phrase:0"] [aria-label="play-circle"]');
223-
I.seeElement('[data-testid="phrase:2"] [aria-label="play-circle"]');
224-
I.seeElement('[data-testid="phrase:3"] [aria-label="play-circle"]');
225-
I.seeElement('[data-testid="phrase:4"] [aria-label="play-circle"]');
221+
I.seeElement('[data-testid="phrase:1"] [aria-label="pause"]');
222+
I.seeElement('[data-testid="phrase:0"] [aria-label="play"]');
223+
I.seeElement('[data-testid="phrase:2"] [aria-label="play"]');
224+
I.seeElement('[data-testid="phrase:3"] [aria-label="play"]');
225+
I.seeElement('[data-testid="phrase:4"] [aria-label="play"]');
226226

227227
I.wait(2);
228228

229229
// Plays the third paragraph segment when the audio progresses to the third paragraph segment
230-
I.seeElement('[data-testid="phrase:2"] [aria-label="pause-circle"]');
231-
I.seeElement('[data-testid="phrase:0"] [aria-label="play-circle"]');
232-
I.seeElement('[data-testid="phrase:1"] [aria-label="play-circle"]');
233-
I.seeElement('[data-testid="phrase:3"] [aria-label="play-circle"]');
234-
I.seeElement('[data-testid="phrase:4"] [aria-label="play-circle"]');
230+
I.seeElement('[data-testid="phrase:2"] [aria-label="pause"]');
231+
I.seeElement('[data-testid="phrase:0"] [aria-label="play"]');
232+
I.seeElement('[data-testid="phrase:1"] [aria-label="play"]');
233+
I.seeElement('[data-testid="phrase:3"] [aria-label="play"]');
234+
I.seeElement('[data-testid="phrase:4"] [aria-label="play"]');
235235
});
236236

237237
FFlagScenario('Check if paragraph is scrolling automatically following the audio', async function({ I, LabelStudio, AtAudioView }) {
@@ -298,6 +298,8 @@ FFlagMatrix(['fflag_feat_front_lsdv_e_278_contextual_scrolling_short'], function
298298

299299
AtAudioView.clickAtBeginning();
300300

301+
I.wait(1);
302+
301303
AtAudioView.clickPauseButton();
302304

303305
const scrollPosition = await I.executeScript(function(selector) {

e2e/tests/sync/audio-video-paragraphs.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ FFlagMatrix([
121121
assert.equal(startingParagraphAudioTime, 0);
122122
}
123123

124-
I.click('[aria-label="play-circle"]');
124+
I.click('[aria-label="play"]');
125125
I.wait(1);
126126
{
127127
I.say('Audio, Video, and Paragraph Audio are playing');
@@ -134,7 +134,7 @@ FFlagMatrix([
134134
assert.equal(paragraphAudioPaused, false);
135135
}
136136

137-
I.click('[aria-label="pause-circle"]');
137+
I.click('[aria-label="pause"]');
138138
I.wait(1);
139139
{
140140
I.say('Audio, Video and Paragraph Audio are played to the same time and are now paused');
@@ -307,9 +307,9 @@ FFlagMatrix([
307307
assert.equal(startingAudioTime, startingVideoTime);
308308
}
309309

310-
I.click('[aria-label="play-circle"]');
310+
I.click('[aria-label="play"]');
311311
I.wait(1);
312-
I.click('[aria-label="pause-circle"]');
312+
I.click('[aria-label="pause"]');
313313
I.wait(1);
314314
{
315315
I.say('Seek playback from paragraph. Audio, video and paragraph audio are played to the same time and are now paused');

src/assets/icons/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export { ReactComponent as IconFast } from './fast.svg';
3232
export { ReactComponent as IconDuplicate } from './duplicate.svg';
3333
export { ReactComponent as IconEllipsis } from './ellipsis.svg';
3434
export { ReactComponent as IconWarning } from './warning.svg';
35+
export { ReactComponent as IconPlay } from './play.svg';
36+
export { ReactComponent as IconPause } from './pause.svg';
3537
export { ReactComponent as IconHelp } from './help.svg';
3638

3739
export { ReactComponent as IconCheck } from './check.svg';

src/assets/icons/pause.svg

Lines changed: 4 additions & 0 deletions
Loading

src/assets/icons/play.svg

Lines changed: 3 additions & 0 deletions
Loading

src/tags/object/Paragraphs/Paragraphs.module.scss

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ $border-thin: 1px solid rgba(137, 128, 152, 0.16);
3838

3939
.dialoguename {
4040
font-weight: bold;
41-
background: white !important;
41+
background: white;
4242
border-radius: 5px;
4343
padding: 5px;
4444
margin-right: 10px;
@@ -63,7 +63,8 @@ $border-thin: 1px solid rgba(137, 128, 152, 0.16);
6363

6464
.scroll_container {
6565
position: relative;
66-
overflow: auto;
66+
overflow-y: auto;
67+
overflow-x: hidden;
6768
counter-reset: phrase;
6869
border: $border-thin;
6970
padding: 8px;
@@ -166,6 +167,19 @@ $border-thin: 1px solid rgba(137, 128, 152, 0.16);
166167
z-index: 1;
167168
}
168169

170+
.playNewUi {
171+
user-select: none;
172+
cursor: pointer;
173+
position: absolute;
174+
left: 5px;
175+
margin-top: -0.3em;
176+
font-size: inherit;
177+
178+
&:hover, &:active, &:focus {
179+
background: none;
180+
}
181+
}
182+
169183
.play {
170184
user-select: none;
171185
position: absolute;
@@ -183,3 +197,64 @@ $border-thin: 1px solid rgba(137, 128, 152, 0.16);
183197
fill: #1890ff;
184198
}
185199
}
200+
201+
.newUI {
202+
transition: all .1s ease-out;
203+
border-radius: 4px;
204+
display: flex;
205+
flex-wrap: wrap;
206+
width: calc(100% - 36px);
207+
208+
&.collapsed {
209+
background-color: var(--highlight-color);
210+
border: 1px solid var(--highlight-color);
211+
}
212+
213+
&:not(.collapsed) {
214+
background-color: var(--background-color);
215+
border-left: 4px solid var(--highlight-color);
216+
border-top: 1px solid rgba(137, 128, 152, 0.16);
217+
border-bottom: 1px solid rgba(137, 128, 152, 0.16);
218+
border-right: 1px solid rgba(137, 128, 152, 0.16);
219+
padding: 8px 12px 8px 12px;
220+
}
221+
222+
.dialoguename {
223+
transition: all .1s ease-out;
224+
background: none;
225+
font-size: 16px;
226+
font-style: normal;
227+
font-weight: 500;
228+
line-height: 24px;
229+
letter-spacing: 0.15px;
230+
padding: 0
231+
}
232+
233+
.dialoguetext {
234+
transition: all .1s ease-out;
235+
font-size: 14px;
236+
font-style: normal;
237+
font-weight: 400;
238+
line-height: 20px;
239+
letter-spacing: 0.25px;
240+
color: #1F1F1F;
241+
width: 100%;
242+
margin-top: 8px;
243+
}
244+
245+
.titleWrapper {
246+
display: flex;
247+
align-items: center;
248+
justify-content: space-between;
249+
width: 100%;
250+
251+
.time {
252+
font-size: 12px;
253+
font-style: normal;
254+
font-weight: 500;
255+
line-height: 16px;
256+
letter-spacing: 0.5px;
257+
color: #898098;
258+
}
259+
}
260+
}

src/tags/object/Paragraphs/Phrases.js

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@ import { getRoot } from 'mobx-state-tree';
33
import { Button } from 'antd';
44
import { PauseCircleOutlined, PlayCircleOutlined } from '@ant-design/icons';
55
import styles from './Paragraphs.module.scss';
6+
import { FF_LSDV_E_278, isFF } from '../../../utils/feature-flags';
7+
import { IconPause, IconPlay } from '../../../assets/icons';
8+
9+
const formatTime = (seconds) => {
10+
if (isNaN(seconds)) return '';
11+
12+
const hours = Math.floor(seconds / 3600);
13+
const minutes = Math.floor((seconds % 3600) / 60);
14+
const remainingSeconds = Math.round(seconds % 60);
15+
16+
const formattedHours = String(hours).padStart(2, '0');
17+
const formattedMinutes = String(minutes).padStart(2, '0');
18+
const formattedSeconds = String(remainingSeconds).padStart(2, '0');
19+
20+
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
21+
};
622

723
export const Phrases = observer(({ item, playingId, activeRef }) => {
824
const cls = item.layoutClasses;
@@ -11,26 +27,48 @@ export const Phrases = observer(({ item, playingId, activeRef }) => {
1127
if (!item._value) return null;
1228

1329
const val = item._value.map((v, idx) => {
14-
const style = item.layoutStyles(v);
30+
const isActive = playingId === idx;
31+
const isPlaying = isActive && item.playing;
32+
const style = (isFF(FF_LSDV_E_278) && !isActive) ? item.layoutStyles(v).inactive: item.layoutStyles(v);
1533
const classNames = [cls.phrase];
1634
const isContentVisible = item.isVisibleForAuthorFilter(v);
17-
const isPlaying = playingId === idx && item.playing;
35+
36+
const withFormattedTime = (item) => {
37+
const startTime = formatTime(item._value[idx]?.start);
38+
const endTime = formatTime(!item._value[idx]?.end ? item._value[idx]?.start + item._value[idx]?.duration : item._value[idx]?.end);
39+
40+
return `${startTime} - ${endTime}`;
41+
};
1842

1943
if (withAudio) classNames.push(styles.withAudio);
2044
if (!isContentVisible) classNames.push(styles.collapsed);
2145
if (getRoot(item).settings.showLineNumbers) classNames.push(styles.numbered);
2246

2347
return (
24-
<div key={`${item.name}-${idx}`} ref={isPlaying ? activeRef : null} data-testid={`phrase:${idx}`} className={classNames.join(' ')} style={style.phrase}>
48+
<div key={`${item.name}-${idx}`} ref={isActive ? activeRef : null} data-testid={`phrase:${idx}`} className={`${classNames.join(' ')} ${isFF(FF_LSDV_E_278) && styles.newUI}`} style={style.phrase}>
2549
{isContentVisible && withAudio && !isNaN(v.start) && (
2650
<Button
2751
type="text"
28-
className={styles.play}
29-
icon={isPlaying ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
52+
className={isFF(FF_LSDV_E_278) ? styles.playNewUi : styles.play}
53+
aria-label={isPlaying ? 'pause' : 'play'}
54+
icon={isPlaying ?
55+
isFF(FF_LSDV_E_278) ?
56+
<IconPause /> : <PauseCircleOutlined /> :
57+
isFF(FF_LSDV_E_278) ?
58+
<IconPlay /> : <PlayCircleOutlined />
59+
}
3060
onClick={() => item.play(idx)}
3161
/>
3262
)}
33-
<span className={cls.name} data-skip-node="true">{v[item.namekey]}</span>
63+
{isFF(FF_LSDV_E_278) ? (
64+
<span className={styles.titleWrapper} data-skip-node="true">
65+
<span className={cls.name} style={style.name}>{v[item.namekey]}</span>
66+
<span className={styles.time}>{withFormattedTime(item)}</span>
67+
</span>
68+
) : (
69+
<span className={cls.name} data-skip-node="true" style={style.name}>{v[item.namekey]}</span>
70+
)}
71+
3472
<span className={cls.text}>{v[item.textkey]}</span>
3573
</div>
3674
);

src/tags/object/Paragraphs/model.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,28 @@ const Model = types
9797
layoutStyles(data) {
9898
if (self.layout === 'dialogue') {
9999
const seed = data[self.namekey];
100+
const color = ColorScheme.make_color({ seed })[0];
100101

101-
return {
102-
phrase: { backgroundColor: Utils.Colors.convertToRGBA(ColorScheme.make_color({ seed })[0], 0.25) },
103-
};
102+
if (isFF(FF_LSDV_E_278)) {
103+
return {
104+
phrase: {
105+
'--highlight-color': color,
106+
'--background-color': '#FFF',
107+
},
108+
name: { color },
109+
inactive: {
110+
phrase: {
111+
'--highlight-color': Utils.Colors.convertToRGBA(color, 0.4),
112+
'--background-color': '#FAFAFA',
113+
},
114+
name: { color: Utils.Colors.convertToRGBA(color, 0.9) },
115+
},
116+
};
117+
} else {
118+
return {
119+
phrase: { backgroundColor: Utils.Colors.convertToRGBA(color, 0.25) },
120+
};
121+
}
104122
}
105123

106124
return {};
@@ -186,6 +204,9 @@ const PlayableAndSyncable = types.model()
186204
return { start, end };
187205
});
188206
},
207+
get regionsValues() {
208+
return Object.values(self.regionsStartEnd);
209+
},
189210
}))
190211
.actions(self => ({
191212
/**
@@ -236,6 +257,8 @@ const PlayableAndSyncable = types.model()
236257
} else {
237258
self.play(self.playingId);
238259
}
260+
} else if (isFF(FF_LSDV_E_278)) {
261+
self.trackPlayingId();
239262
}
240263
},
241264

@@ -313,7 +336,7 @@ const PlayableAndSyncable = types.model()
313336
return;
314337
}
315338

316-
const regions = Object.values(self.regionsStartEnd);
339+
const regions = self.regionsValues;
317340

318341
self.playingId = regions.findIndex(({ start, end }) => {
319342
return currentTime >= start && currentTime <= end;

0 commit comments

Comments
 (0)