diff --git a/e2e/tests/sync/audio-paragraphs.test.js b/e2e/tests/sync/audio-paragraphs.test.js index eb5f6b90d..ef4e10d23 100644 --- a/e2e/tests/sync/audio-paragraphs.test.js +++ b/e2e/tests/sync/audio-paragraphs.test.js @@ -54,16 +54,16 @@ const data = { url: 'https://htx-misc.s3.amazonaws.com/opensource/label-studio/examples/audio/barradeen-emotional.mp3', text: [ { - 'end': 2, + 'end': 3, 'text': 'Dont you hate that?', - 'start': 0, + 'start': 1, 'author': 'Mia Wallace', }, { 'text': 'Hate what?', - 'start': 2, + 'start': 3, 'author': 'Vincent Vega:', - 'duration': 2, + 'duration': 1, }, { '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 assert.equal(startingAudioTime, startingParagraphAudioTime); assert.equal(startingParagraphAudioTime, 0); - I.click('[aria-label="play-circle"]'); + I.click('[aria-label="play"]'); I.wait(1); - I.click('[aria-label="pause-circle"]'); + I.click('[aria-label="play"]'); I.wait(1); const [{ currentTime: seekAudioTime }, { currentTime: seekParagraphAudioTime }] = await AtAudioView.getCurrentAudio(); @@ -209,29 +209,29 @@ FFlagMatrix(['fflag_feat_front_lsdv_e_278_contextual_scrolling_short'], function AtAudioView.clickPauseButton(); // Plays the first paragraph segment when the audio interface is played - I.seeElement('[data-testid="phrase:0"] [aria-label="pause-circle"]'); - I.seeElement('[data-testid="phrase:1"] [aria-label="play-circle"]'); - I.seeElement('[data-testid="phrase:2"] [aria-label="play-circle"]'); - I.seeElement('[data-testid="phrase:3"] [aria-label="play-circle"]'); - I.seeElement('[data-testid="phrase:4"] [aria-label="play-circle"]'); + I.seeElement('[data-testid="phrase:0"] [aria-label="pause"]'); + I.seeElement('[data-testid="phrase:1"] [aria-label="play"]'); + I.seeElement('[data-testid="phrase:2"] [aria-label="play"]'); + I.seeElement('[data-testid="phrase:3"] [aria-label="play"]'); + I.seeElement('[data-testid="phrase:4"] [aria-label="play"]'); I.wait(2); // Plays the second paragraph segment when the audio progresses to the second paragraph segment - I.seeElement('[data-testid="phrase:1"] [aria-label="pause-circle"]'); - I.seeElement('[data-testid="phrase:0"] [aria-label="play-circle"]'); - I.seeElement('[data-testid="phrase:2"] [aria-label="play-circle"]'); - I.seeElement('[data-testid="phrase:3"] [aria-label="play-circle"]'); - I.seeElement('[data-testid="phrase:4"] [aria-label="play-circle"]'); + I.seeElement('[data-testid="phrase:1"] [aria-label="pause"]'); + I.seeElement('[data-testid="phrase:0"] [aria-label="play"]'); + I.seeElement('[data-testid="phrase:2"] [aria-label="play"]'); + I.seeElement('[data-testid="phrase:3"] [aria-label="play"]'); + I.seeElement('[data-testid="phrase:4"] [aria-label="play"]'); I.wait(2); // Plays the third paragraph segment when the audio progresses to the third paragraph segment - I.seeElement('[data-testid="phrase:2"] [aria-label="pause-circle"]'); - I.seeElement('[data-testid="phrase:0"] [aria-label="play-circle"]'); - I.seeElement('[data-testid="phrase:1"] [aria-label="play-circle"]'); - I.seeElement('[data-testid="phrase:3"] [aria-label="play-circle"]'); - I.seeElement('[data-testid="phrase:4"] [aria-label="play-circle"]'); + I.seeElement('[data-testid="phrase:2"] [aria-label="pause"]'); + I.seeElement('[data-testid="phrase:0"] [aria-label="play"]'); + I.seeElement('[data-testid="phrase:1"] [aria-label="play"]'); + I.seeElement('[data-testid="phrase:3"] [aria-label="play"]'); + I.seeElement('[data-testid="phrase:4"] [aria-label="play"]'); }); 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 AtAudioView.clickAtBeginning(); + I.wait(1); + AtAudioView.clickPauseButton(); const scrollPosition = await I.executeScript(function(selector) { diff --git a/e2e/tests/sync/audio-video-paragraphs.test.js b/e2e/tests/sync/audio-video-paragraphs.test.js index 40bb39c4c..400c8a0a1 100644 --- a/e2e/tests/sync/audio-video-paragraphs.test.js +++ b/e2e/tests/sync/audio-video-paragraphs.test.js @@ -121,7 +121,7 @@ FFlagMatrix([ assert.equal(startingParagraphAudioTime, 0); } - I.click('[aria-label="play-circle"]'); + I.click('[aria-label="play"]'); I.wait(1); { I.say('Audio, Video, and Paragraph Audio are playing'); @@ -134,7 +134,7 @@ FFlagMatrix([ assert.equal(paragraphAudioPaused, false); } - I.click('[aria-label="pause-circle"]'); + I.click('[aria-label="pause"]'); I.wait(1); { I.say('Audio, Video and Paragraph Audio are played to the same time and are now paused'); @@ -307,9 +307,9 @@ FFlagMatrix([ assert.equal(startingAudioTime, startingVideoTime); } - I.click('[aria-label="play-circle"]'); + I.click('[aria-label="play"]'); I.wait(1); - I.click('[aria-label="pause-circle"]'); + I.click('[aria-label="pause"]'); I.wait(1); { I.say('Seek playback from paragraph. Audio, video and paragraph audio are played to the same time and are now paused'); diff --git a/src/assets/icons/index.tsx b/src/assets/icons/index.tsx index 3c2b533aa..7b363ab5e 100644 --- a/src/assets/icons/index.tsx +++ b/src/assets/icons/index.tsx @@ -32,6 +32,8 @@ export { ReactComponent as IconFast } from './fast.svg'; export { ReactComponent as IconDuplicate } from './duplicate.svg'; export { ReactComponent as IconEllipsis } from './ellipsis.svg'; export { ReactComponent as IconWarning } from './warning.svg'; +export { ReactComponent as IconPlay } from './play.svg'; +export { ReactComponent as IconPause } from './pause.svg'; export { ReactComponent as IconHelp } from './help.svg'; export { ReactComponent as IconCheck } from './check.svg'; diff --git a/src/assets/icons/pause.svg b/src/assets/icons/pause.svg new file mode 100644 index 000000000..50c2605a5 --- /dev/null +++ b/src/assets/icons/pause.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/play.svg b/src/assets/icons/play.svg new file mode 100644 index 000000000..bda9a7a41 --- /dev/null +++ b/src/assets/icons/play.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/tags/object/Paragraphs/Paragraphs.module.scss b/src/tags/object/Paragraphs/Paragraphs.module.scss index 3f84f5f21..b8925429c 100644 --- a/src/tags/object/Paragraphs/Paragraphs.module.scss +++ b/src/tags/object/Paragraphs/Paragraphs.module.scss @@ -38,7 +38,7 @@ $border-thin: 1px solid rgba(137, 128, 152, 0.16); .dialoguename { font-weight: bold; - background: white !important; + background: white; border-radius: 5px; padding: 5px; margin-right: 10px; @@ -63,7 +63,8 @@ $border-thin: 1px solid rgba(137, 128, 152, 0.16); .scroll_container { position: relative; - overflow: auto; + overflow-y: auto; + overflow-x: hidden; counter-reset: phrase; border: $border-thin; padding: 8px; @@ -166,6 +167,19 @@ $border-thin: 1px solid rgba(137, 128, 152, 0.16); z-index: 1; } +.playNewUi { + user-select: none; + cursor: pointer; + position: absolute; + left: 5px; + margin-top: -0.3em; + font-size: inherit; + + &:hover, &:active, &:focus { + background: none; + } +} + .play { user-select: none; position: absolute; @@ -183,3 +197,64 @@ $border-thin: 1px solid rgba(137, 128, 152, 0.16); fill: #1890ff; } } + +.newUI { + transition: all .1s ease-out; + border-radius: 4px; + display: flex; + flex-wrap: wrap; + width: calc(100% - 36px); + + &.collapsed { + background-color: var(--highlight-color); + border: 1px solid var(--highlight-color); + } + + &:not(.collapsed) { + background-color: var(--background-color); + border-left: 4px solid var(--highlight-color); + border-top: 1px solid rgba(137, 128, 152, 0.16); + border-bottom: 1px solid rgba(137, 128, 152, 0.16); + border-right: 1px solid rgba(137, 128, 152, 0.16); + padding: 8px 12px 8px 12px; + } + + .dialoguename { + transition: all .1s ease-out; + background: none; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.15px; + padding: 0 + } + + .dialoguetext { + transition: all .1s ease-out; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + letter-spacing: 0.25px; + color: #1F1F1F; + width: 100%; + margin-top: 8px; + } + + .titleWrapper { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + + .time { + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 16px; + letter-spacing: 0.5px; + color: #898098; + } + } +} diff --git a/src/tags/object/Paragraphs/Phrases.js b/src/tags/object/Paragraphs/Phrases.js index 1e9e4d949..bd9e1a8f7 100644 --- a/src/tags/object/Paragraphs/Phrases.js +++ b/src/tags/object/Paragraphs/Phrases.js @@ -3,6 +3,22 @@ import { getRoot } from 'mobx-state-tree'; import { Button } from 'antd'; import { PauseCircleOutlined, PlayCircleOutlined } from '@ant-design/icons'; import styles from './Paragraphs.module.scss'; +import { FF_LSDV_E_278, isFF } from '../../../utils/feature-flags'; +import { IconPause, IconPlay } from '../../../assets/icons'; + +const formatTime = (seconds) => { + if (isNaN(seconds)) return ''; + + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const remainingSeconds = Math.round(seconds % 60); + + const formattedHours = String(hours).padStart(2, '0'); + const formattedMinutes = String(minutes).padStart(2, '0'); + const formattedSeconds = String(remainingSeconds).padStart(2, '0'); + + return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`; +}; export const Phrases = observer(({ item, playingId, activeRef }) => { const cls = item.layoutClasses; @@ -11,26 +27,48 @@ export const Phrases = observer(({ item, playingId, activeRef }) => { if (!item._value) return null; const val = item._value.map((v, idx) => { - const style = item.layoutStyles(v); + const isActive = playingId === idx; + const isPlaying = isActive && item.playing; + const style = (isFF(FF_LSDV_E_278) && !isActive) ? item.layoutStyles(v).inactive: item.layoutStyles(v); const classNames = [cls.phrase]; const isContentVisible = item.isVisibleForAuthorFilter(v); - const isPlaying = playingId === idx && item.playing; + + const withFormattedTime = (item) => { + const startTime = formatTime(item._value[idx]?.start); + const endTime = formatTime(!item._value[idx]?.end ? item._value[idx]?.start + item._value[idx]?.duration : item._value[idx]?.end); + + return `${startTime} - ${endTime}`; + }; if (withAudio) classNames.push(styles.withAudio); if (!isContentVisible) classNames.push(styles.collapsed); if (getRoot(item).settings.showLineNumbers) classNames.push(styles.numbered); return ( -
+
{isContentVisible && withAudio && !isNaN(v.start) && (
); diff --git a/src/tags/object/Paragraphs/model.js b/src/tags/object/Paragraphs/model.js index 982a392d6..9291fc582 100644 --- a/src/tags/object/Paragraphs/model.js +++ b/src/tags/object/Paragraphs/model.js @@ -97,10 +97,28 @@ const Model = types layoutStyles(data) { if (self.layout === 'dialogue') { const seed = data[self.namekey]; + const color = ColorScheme.make_color({ seed })[0]; - return { - phrase: { backgroundColor: Utils.Colors.convertToRGBA(ColorScheme.make_color({ seed })[0], 0.25) }, - }; + if (isFF(FF_LSDV_E_278)) { + return { + phrase: { + '--highlight-color': color, + '--background-color': '#FFF', + }, + name: { color }, + inactive: { + phrase: { + '--highlight-color': Utils.Colors.convertToRGBA(color, 0.4), + '--background-color': '#FAFAFA', + }, + name: { color: Utils.Colors.convertToRGBA(color, 0.9) }, + }, + }; + } else { + return { + phrase: { backgroundColor: Utils.Colors.convertToRGBA(color, 0.25) }, + }; + } } return {}; @@ -186,6 +204,9 @@ const PlayableAndSyncable = types.model() return { start, end }; }); }, + get regionsValues() { + return Object.values(self.regionsStartEnd); + }, })) .actions(self => ({ /** @@ -236,6 +257,8 @@ const PlayableAndSyncable = types.model() } else { self.play(self.playingId); } + } else if (isFF(FF_LSDV_E_278)) { + self.trackPlayingId(); } }, @@ -313,7 +336,7 @@ const PlayableAndSyncable = types.model() return; } - const regions = Object.values(self.regionsStartEnd); + const regions = self.regionsValues; self.playingId = regions.findIndex(({ start, end }) => { return currentTime >= start && currentTime <= end;