Skip to content

Commit ba896a3

Browse files
authored
feat: Enable transcripts for video library [FC-0076] (#1596)
* Get updateTranscriptHandlerUrl() and call it when is ready. * Enable LanguageNamesWidget in a library. * Enable add transcripts for libraries. * Enable delete transcripts for libraries. * Enable replace transcripts for libraries. * Enable download transcripts for libraries. * Enable download transcripts from YouTube
1 parent 3e235d3 commit ba896a3

16 files changed

Lines changed: 458 additions & 62 deletions

File tree

src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/index.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { connect } from 'react-redux';
2+
import { connect, useDispatch, useSelector } from 'react-redux';
33
import PropTypes from 'prop-types';
44
import {
55
FormattedMessage,
@@ -17,7 +17,7 @@ import {
1717
} from '@openedx/paragon';
1818
import { Add, InfoOutline } from '@openedx/paragon/icons';
1919

20-
import { actions, selectors } from '../../../../../../data/redux';
20+
import { thunkActions, actions, selectors } from '../../../../../../data/redux';
2121
import messages from './messages';
2222

2323
import { RequestKeys } from '../../../../../../data/constants/requests';
@@ -97,6 +97,11 @@ const TranscriptWidget = ({
9797
const [showImportCard, setShowImportCard] = React.useState(true);
9898
const fullTextLanguages = module.hooks.transcriptLanguages(transcripts, intl);
9999
const hasTranscripts = module.hooks.hasTranscripts(transcripts);
100+
const isLibrary = useSelector(selectors.app.isLibrary);
101+
const dispatch = useDispatch();
102+
if (isLibrary) {
103+
dispatch(thunkActions.video.updateTranscriptHandlerUrl());
104+
}
100105

101106
return (
102107
<CollapsibleFormWidget

src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/index.test.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,14 @@ jest.mock('../../../../../../data/redux', () => ({
2828
thunkActions: {
2929
video: {
3030
deleteTranscript: jest.fn().mockName('thunkActions.video.deleteTranscript'),
31+
updateTranscriptHandlerUrl: jest.fn().mockName('thunkActions.video.updateTranscriptHandlerUrl'),
3132
},
3233
},
3334

3435
selectors: {
36+
app: {
37+
isLibrary: jest.fn(state => ({ isLibrary: state })),
38+
},
3539
video: {
3640
transcripts: jest.fn(state => ({ transcripts: state })),
3741
selectedVideoTranscriptUrls: jest.fn(state => ({ selectedVideoTranscriptUrls: state })),

src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoPreviewWidget/index.jsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export const VideoPreviewWidget = ({
1717
videoSource,
1818
transcripts,
1919
blockTitle,
20-
isLibrary,
2120
intl,
2221
}) => {
2322
const imgRef = React.useRef();
@@ -47,10 +46,7 @@ export const VideoPreviewWidget = ({
4746
/>
4847
<Stack gap={1} className="justify-content-center">
4948
<h4 className="text-primary mb-0">{blockTitle}</h4>
50-
{!isLibrary && (
51-
// Since content libraries v2 don't support static assets yet, we can't include transcripts.
52-
<LanguageNamesWidget transcripts={transcripts} />
53-
)}
49+
<LanguageNamesWidget transcripts={transcripts} />
5450
{videoType && (
5551
<Hyperlink
5652
className="text-primary x-small"
@@ -74,15 +70,13 @@ VideoPreviewWidget.propTypes = {
7470
thumbnail: PropTypes.string.isRequired,
7571
transcripts: PropTypes.arrayOf(PropTypes.string).isRequired,
7672
blockTitle: PropTypes.string.isRequired,
77-
isLibrary: PropTypes.bool.isRequired,
7873
};
7974

8075
export const mapStateToProps = (state) => ({
8176
transcripts: selectors.video.transcripts(state),
8277
videoSource: selectors.video.videoSource(state),
8378
thumbnail: selectors.video.thumbnail(state),
8479
blockTitle: selectors.app.blockTitle(state),
85-
isLibrary: selectors.app.isLibrary(state),
8680
});
8781

8882
export default injectIntl(connect(mapStateToProps)(VideoPreviewWidget));

src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoPreviewWidget/index.test.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe('VideoPreviewWidget', () => {
3030
expect(screen.queryByText('No transcripts added')).toBeInTheDocument();
3131
});
3232

33-
test('hides transcripts section in preview for libraries', () => {
33+
test('renders transcripts section in preview for libraries', () => {
3434
render(
3535
<VideoPreviewWidget
3636
videoSource="some-source"
@@ -41,7 +41,7 @@ describe('VideoPreviewWidget', () => {
4141
thumbnail=""
4242
/>,
4343
);
44-
expect(screen.queryByText('No transcripts added')).not.toBeInTheDocument();
44+
expect(screen.queryByText('No transcripts added')).toBeInTheDocument();
4545
});
4646
});
4747
});

src/editors/containers/VideoEditor/components/VideoSettingsModal/index.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ const VideoSettingsModal: React.FC<Props> = ({
4949
<SocialShareWidget />
5050
)}
5151
<ThumbnailWidget />
52-
{!isLibrary && ( // Since content libraries v2 don't support static assets yet, we can't include transcripts.
53-
<TranscriptWidget />
54-
)}
52+
<TranscriptWidget />
5553
<DurationWidget />
5654
<HandoutWidget />
5755
<LicenseWidget />

src/editors/data/constants/requests.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ export const RequestKeys = StrictDict({
2727
uploadAsset: 'uploadAsset',
2828
fetchAdvancedSettings: 'fetchAdvancedSettings',
2929
fetchVideoFeatures: 'fetchVideoFeatures',
30+
getHandlerUrl: 'getHandlerUrl',
3031
} as const);

src/editors/data/redux/thunkActions/requests.js

Lines changed: 111 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { RequestKeys } from '../../constants/requests';
44
import api, { loadImages } from '../../services/cms/api';
55
import { actions as requestsActions } from '../requests';
66
import { selectors as appSelectors } from '../app';
7+
import { selectors as videoSelectors } from '../video';
78

89
// This 'module' self-import hack enables mocking during tests.
910
// See src/editors/decisions/0005-internal-editor-testability-decisions.md. The whole approach to how hooks are tested
@@ -15,7 +16,7 @@ import { acceptedImgKeys } from '../../../sharedComponents/ImageUploadModal/Sele
1516

1617
// Similar to `import { actions, selectors } from '..';` but avoid circular imports:
1718
const actions = { requests: requestsActions };
18-
const selectors = { app: appSelectors };
19+
const selectors = { app: appSelectors, video: videoSelectors };
1920

2021
/**
2122
* Wrapper around a network request promise, that sends actions to the redux store to
@@ -239,16 +240,30 @@ export const importTranscript = ({ youTubeId, ...rest }) => (dispatch, getState)
239240
};
240241

241242
export const deleteTranscript = ({ language, videoId, ...rest }) => (dispatch, getState) => {
242-
dispatch(module.networkRequest({
243-
requestKey: RequestKeys.deleteTranscript,
244-
promise: api.deleteTranscript({
245-
blockId: selectors.app.blockId(getState()),
246-
language,
247-
videoId,
248-
studioEndpointUrl: selectors.app.studioEndpointUrl(getState()),
249-
}),
250-
...rest,
251-
}));
243+
const state = getState();
244+
const isLibrary = selectors.app.isLibrary(state);
245+
if (isLibrary) {
246+
dispatch(module.networkRequest({
247+
requestKey: RequestKeys.deleteTranscript,
248+
promise: api.deleteTranscriptV2({
249+
language,
250+
videoId,
251+
handlerUrl: selectors.video.transcriptHandlerUrl(state),
252+
}),
253+
...rest,
254+
}));
255+
} else {
256+
dispatch(module.networkRequest({
257+
requestKey: RequestKeys.deleteTranscript,
258+
promise: api.deleteTranscript({
259+
blockId: selectors.app.blockId(state),
260+
language,
261+
videoId,
262+
studioEndpointUrl: selectors.app.studioEndpointUrl(state),
263+
}),
264+
...rest,
265+
}));
266+
}
252267
};
253268

254269
export const uploadTranscript = ({
@@ -257,17 +272,32 @@ export const uploadTranscript = ({
257272
language,
258273
...rest
259274
}) => (dispatch, getState) => {
260-
dispatch(module.networkRequest({
261-
requestKey: RequestKeys.uploadTranscript,
262-
promise: api.uploadTranscript({
263-
blockId: selectors.app.blockId(getState()),
264-
transcript,
265-
videoId,
266-
language,
267-
studioEndpointUrl: selectors.app.studioEndpointUrl(getState()),
268-
}),
269-
...rest,
270-
}));
275+
const state = getState();
276+
const isLibrary = selectors.app.isLibrary(state);
277+
if (isLibrary) {
278+
dispatch(module.networkRequest({
279+
requestKey: RequestKeys.uploadTranscript,
280+
promise: api.uploadTranscriptV2({
281+
handlerUrl: selectors.video.transcriptHandlerUrl(state),
282+
transcript,
283+
videoId,
284+
language,
285+
}),
286+
...rest,
287+
}));
288+
} else {
289+
dispatch(module.networkRequest({
290+
requestKey: RequestKeys.uploadTranscript,
291+
promise: api.uploadTranscript({
292+
blockId: selectors.app.blockId(state),
293+
transcript,
294+
videoId,
295+
language,
296+
studioEndpointUrl: selectors.app.studioEndpointUrl(state),
297+
}),
298+
...rest,
299+
}));
300+
}
271301
};
272302

273303
export const updateTranscriptLanguage = ({
@@ -277,28 +307,70 @@ export const updateTranscriptLanguage = ({
277307
videoId,
278308
...rest
279309
}) => (dispatch, getState) => {
280-
dispatch(module.networkRequest({
281-
requestKey: RequestKeys.updateTranscriptLanguage,
282-
promise: api.uploadTranscript({
283-
blockId: selectors.app.blockId(getState()),
284-
transcript: file,
285-
videoId,
286-
language: languageBeforeChange,
287-
newLanguage: newLanguageCode,
288-
studioEndpointUrl: selectors.app.studioEndpointUrl(getState()),
289-
}),
290-
...rest,
291-
}));
310+
const state = getState();
311+
const isLibrary = selectors.app.isLibrary(state);
312+
if (isLibrary) {
313+
dispatch(module.networkRequest({
314+
requestKey: RequestKeys.updateTranscriptLanguage,
315+
promise: api.uploadTranscriptV2({
316+
handlerUrl: selectors.video.transcriptHandlerUrl(state),
317+
transcript: file,
318+
videoId,
319+
language: languageBeforeChange,
320+
newLanguage: newLanguageCode,
321+
}),
322+
...rest,
323+
}));
324+
} else {
325+
dispatch(module.networkRequest({
326+
requestKey: RequestKeys.updateTranscriptLanguage,
327+
promise: api.uploadTranscript({
328+
blockId: selectors.app.blockId(state),
329+
transcript: file,
330+
videoId,
331+
language: languageBeforeChange,
332+
newLanguage: newLanguageCode,
333+
studioEndpointUrl: selectors.app.studioEndpointUrl(state),
334+
}),
335+
...rest,
336+
}));
337+
}
292338
};
293339

294340
export const getTranscriptFile = ({ language, videoId, ...rest }) => (dispatch, getState) => {
341+
const state = getState();
342+
const isLibrary = selectors.app.isLibrary(state);
343+
if (isLibrary) {
344+
dispatch(module.networkRequest({
345+
requestKey: RequestKeys.getTranscriptFile,
346+
promise: api.getTranscriptV2({
347+
handlerUrl: selectors.video.transcriptHandlerUrl(state),
348+
videoId,
349+
language,
350+
}),
351+
...rest,
352+
}));
353+
} else {
354+
dispatch(module.networkRequest({
355+
requestKey: RequestKeys.getTranscriptFile,
356+
promise: api.getTranscript({
357+
studioEndpointUrl: selectors.app.studioEndpointUrl(state),
358+
blockId: selectors.app.blockId(state),
359+
videoId,
360+
language,
361+
}),
362+
...rest,
363+
}));
364+
}
365+
};
366+
367+
export const getHandlerlUrl = ({ handlerName, ...rest }) => (dispatch, getState) => {
295368
dispatch(module.networkRequest({
296-
requestKey: RequestKeys.getTranscriptFile,
297-
promise: api.getTranscript({
369+
requestKey: RequestKeys.getHandlerUrl,
370+
promise: api.getHandlerUrl({
298371
studioEndpointUrl: selectors.app.studioEndpointUrl(getState()),
299372
blockId: selectors.app.blockId(getState()),
300-
videoId,
301-
language,
373+
handlerName,
302374
}),
303375
...rest,
304376
}));
@@ -368,4 +440,5 @@ export default StrictDict({
368440
fetchAdvancedSettings,
369441
fetchVideoFeatures,
370442
uploadVideo,
443+
getHandlerlUrl,
371444
});

0 commit comments

Comments
 (0)