fix: transcript tab layout (#686)

* fix: transcript tab layout

* fix: console warnings for missing config values
This commit is contained in:
Kristin Aoki
2023-11-17 09:48:13 -05:00
committed by GitHub
parent af0124d4e6
commit eb3ee3a6b2
7 changed files with 131 additions and 82 deletions

View File

@@ -7,6 +7,7 @@ import {
} from '@edx/frontend-platform/i18n';
import {
ModalDialog,
Stack,
Truncate,
} from '@edx/paragon';
@@ -30,6 +31,7 @@ const InfoModal = ({
size="lg"
hasCloseButton
data-testid="file-info-modal"
style={{ minHeight: '799px' }}
>
<ModalDialog.Header>
<ModalDialog.Title>
@@ -44,25 +46,29 @@ const InfoModal = ({
<hr />
<div className="row flex-nowrap m-0 mt-4">
<div className="col-7 mr-3">
<FileThumbnail
thumbnail={file?.thumbnail}
externalUrl={file?.externalUrl}
displayName={file?.displayName}
wrapperType={file?.wrapperType}
id={file?.id}
status={file?.status}
thumbnailPreview={thumbnailPreview}
imageSize={{ width: '503px', height: '281px' }}
/>
<Stack gap={5}>
<FileThumbnail
thumbnail={file?.thumbnail}
externalUrl={file?.externalUrl}
displayName={file?.displayName}
wrapperType={file?.wrapperType}
id={file?.id}
status={file?.status}
thumbnailPreview={thumbnailPreview}
imageSize={{ width: '503px', height: '281px' }}
/>
<div>
<div className="row m-0 font-weight-bold">
<FormattedMessage {...messages.usageTitle} />
</div>
<UsageMetricsMessages {...{ usageLocations: file?.usageLocations, usagePathStatus, error }} />
</div>
</Stack>
</div>
<div className="col-5">
{sidebar(file)}
</div>
</div>
<div className="row m-0 pt-3 font-weight-bold">
<FormattedMessage {...messages.usageTitle} />
</div>
<UsageMetricsMessages {...{ usageLocations: file?.usageLocations, usagePathStatus, error }} />
</ModalDialog.Body>
</ModalDialog>
);

View File

@@ -72,6 +72,26 @@ export const getLanguages = (availableLanguages) => {
return languages;
};
export const getSortedTranscripts = (languages, transcripts) => {
const transcriptDisplayNames = [];
transcripts.forEach(transcript => {
const displayName = languages[transcript];
transcriptDisplayNames.push(displayName);
});
const sortedTranscripts = transcriptDisplayNames.sort();
const sortedTranscriptCodes = [];
sortedTranscripts.forEach(transcript => {
Object.entries(languages).forEach(([key, value]) => {
if (value === transcript) {
sortedTranscriptCodes.push(key);
}
});
});
return sortedTranscriptCodes;
};
export const getSupportedFormats = (supportedFileFormats) => {
if (isEmpty(supportedFileFormats)) {
return null;

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { isEmpty } from 'lodash';
@@ -6,7 +6,7 @@ import { ErrorAlert } from '@edx/frontend-lib-content-components';
import { Button, Stack } from '@edx/paragon';
import { Add } from '@edx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getLanguages } from '../data/utils';
import { getLanguages, getSortedTranscripts } from '../data/utils';
import Transcript from './transcript-item';
import {
deleteVideoTranscript,
@@ -23,6 +23,7 @@ const TranscriptTab = ({
intl,
}) => {
const dispatch = useDispatch();
const divRef = useRef(null);
const { transcriptStatus, errors } = useSelector(state => state.videos);
const {
transcriptAvailableLanguages,
@@ -35,13 +36,22 @@ const TranscriptTab = ({
} = videoTranscriptSettings;
const { transcripts, id, displayName } = video;
const languages = getLanguages(transcriptAvailableLanguages);
let sortedTranscripts = getSortedTranscripts(languages, transcripts);
const [previousSelection, setPreviousSelection] = useState(sortedTranscripts);
const [previousSelection, setPreviousSelection] = useState(transcripts);
useEffect(() => {
dispatch(resetErrors({ errorType: 'transcript' }));
setPreviousSelection(transcripts);
sortedTranscripts = getSortedTranscripts(languages, transcripts);
setPreviousSelection(sortedTranscripts);
}, [transcripts]);
const handleAddEmptyTranscript = () => {
setPreviousSelection(['', ...previousSelection]);
if (divRef?.current?.scrollTo) {
divRef.current.scrollTo({ top: 0, behavior: 'smooth' });
}
};
const handleTranscript = (data, actionType) => {
const {
language,
@@ -52,7 +62,8 @@ const TranscriptTab = ({
switch (actionType) {
case 'delete':
if (isEmpty(language)) {
const updatedSelection = previousSelection.filter(selection => selection !== '');
const updatedSelection = previousSelection;
updatedSelection.shift();
setPreviousSelection(updatedSelection);
} else {
dispatch(deleteVideoTranscript({
@@ -87,38 +98,42 @@ const TranscriptTab = ({
};
return (
<Stack gap={3} className="mt-3">
<ErrorAlert
hideHeading={false}
isError={transcriptStatus === RequestStatus.FAILED && !isEmpty(errors.transcript)}
>
<ul className="p-0">
{errors.transcript.map(message => (
<li key={`transcript-error-${message}`} style={{ listStyle: 'none' }}>
{intl.formatMessage(messages.errorAlertMessage, { message })}
</li>
))}
</ul>
</ErrorAlert>
{previousSelection.map(transcript => (
<Transcript
{...{
languages,
transcript,
previousSelection,
handleTranscript,
}}
/>
))}
<Button
variant="link"
iconBefore={Add}
size="sm"
className="text-primary-500 justify-content-start pl-0"
onClick={() => setPreviousSelection([...previousSelection, ''])}
>
{intl.formatMessage(messages.uploadButtonLabel)}
</Button>
<Stack gap={3}>
<div ref={divRef} style={{ overflowY: 'scroll', height: '310px' }} className="px-1 py-2">
<ErrorAlert
hideHeading={false}
isError={transcriptStatus === RequestStatus.FAILED && !isEmpty(errors.transcript)}
>
<ul className="p-0">
{errors.transcript.map(message => (
<li key={`transcript-error-${message}`} style={{ listStyle: 'none' }}>
{intl.formatMessage(messages.errorAlertMessage, { message })}
</li>
))}
</ul>
</ErrorAlert>
{previousSelection.map(transcript => (
<Transcript
{...{
languages,
transcript,
previousSelection,
handleTranscript,
}}
/>
))}
</div>
<div className="border-top border-light-400">
<Button
variant="link"
iconBefore={Add}
size="sm"
className="text-primary-500 justify-content-start pl-0 pt-3"
onClick={handleAddEmptyTranscript}
>
{intl.formatMessage(messages.uploadButtonLabel)}
</Button>
</div>
</Stack>
);
};

View File

@@ -281,13 +281,11 @@ describe('TranscriptTab', () => {
});
const englishOption = screen.getByText('English');
const arabicOption = screen.getAllByRole('button', { name: 'Arabic' })[0];
await act(async () => {
expect(arabicOption).toHaveClass('disabled');
fireEvent.click(englishOption);
});
const menuButton = screen.getByTestId('fr-transcript-menu');
const menuButton = screen.getByTestId('ar-transcript-menu');
await waitFor(() => {
fireEvent.click(menuButton);
});
@@ -302,8 +300,6 @@ describe('TranscriptTab', () => {
await act(async () => {
const addFileInput = screen.getAllByLabelText('file-input')[0];
expect(addFileInput).toBeInTheDocument();
userEvent.upload(addFileInput, file);
});
const addStatus = store.getState().videos.transcriptStatus;
@@ -312,7 +308,7 @@ describe('TranscriptTab', () => {
const updatedTranscripts = store.getState().models.videos[defaultProps.id].transcripts;
expect(updatedTranscripts).toEqual(['ar', 'en']);
expect(updatedTranscripts).toEqual(['fr', 'en']);
});
it('should show error message', async () => {
@@ -320,8 +316,6 @@ describe('TranscriptTab', () => {
await act(async () => {
const addFileInput = screen.getAllByLabelText('file-input')[0];
expect(addFileInput).toBeInTheDocument();
userEvent.upload(addFileInput, file);
});
@@ -329,7 +323,7 @@ describe('TranscriptTab', () => {
expect(addStatus).toEqual(RequestStatus.FAILED);
expect(screen.getAllByText('Failed to replace fr with en.')[0]).toBeVisible();
expect(screen.getAllByText('Failed to replace ar with en.')[0]).toBeVisible();
});
});
});

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Dropdown, Icon } from '@edx/paragon';
import { Check } from '@edx/paragon/icons';
import { Check, ExpandMore } from '@edx/paragon/icons';
import { isEmpty } from 'lodash';
const LanguageSelect = ({
@@ -15,7 +15,8 @@ const LanguageSelect = ({
return (
<Dropdown>
<Dropdown.Toggle
variant="teritary"
variant="tertiary"
size="sm"
className="border border-gray-700 justify-content-between"
style={{ minWidth: '100%' }}
id={`language-select-dropdown-${currentSelection}`}
@@ -23,28 +24,33 @@ const LanguageSelect = ({
>
{currentSelection}
</Dropdown.Toggle>
<Dropdown.Menu className="m-0" style={{ height: '300px', overflowY: 'scroll' }}>
{Object.entries(options).map(([valueKey, text]) => {
if (valueKey === value) {
<Dropdown.Menu className="m-0">
<div style={{ height: '230px', overflowY: 'scroll' }}>
{Object.entries(options).map(([valueKey, text]) => {
if (valueKey === value) {
return (
<Dropdown.Item className="small" key={`${valueKey}-item`}>
<Icon size="inline" src={Check} className="m-n2" /><span className="pl-3">{text}</span>
</Dropdown.Item>
);
}
if (!previousSelection.includes(valueKey)) {
return (
<Dropdown.Item className="small" onClick={() => handleSelect(valueKey)} key={`${valueKey}-item`}>
<span className="pl-3">{text}</span>
</Dropdown.Item>
);
}
return (
<Dropdown.Item key={`${valueKey}-item`}>
<Icon size="inline" src={Check} className="m-n2" /><span className="pl-3">{text}</span>
</Dropdown.Item>
);
}
if (!previousSelection.includes(valueKey)) {
return (
<Dropdown.Item onClick={() => handleSelect(valueKey)} key={`${valueKey}-item`}>
<Dropdown.Item disabled className="small" key={`${valueKey}-item`}>
<span className="pl-3">{text}</span>
</Dropdown.Item>
);
}
return (
<Dropdown.Item disabled key={`${valueKey}-item`}>
<span className="pl-3">{text}</span>
</Dropdown.Item>
);
})}
})}
</div>
<div className="row justify-content-center">
<Icon src={ExpandMore} size="xs" />
</div>
</Dropdown.Menu>
</Dropdown>
);

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
Card,
@@ -27,6 +27,10 @@ const Transcript = ({
const [newLanguage, setNewLanguage] = useState(transcript);
const language = transcript;
useEffect(() => {
setNewLanguage(transcript);
}, [transcript]);
const input = useFileInput({
onAddFile: (file) => {
handleTranscript({
@@ -49,7 +53,7 @@ const Transcript = ({
return (
<>
{isConfirmationOpen ? (
<Card className="mb-2">
<Card className="my-2">
<Card.Header title={(<FormattedMessage {...messages.deleteConfirmationHeader} />)} />
<Card.Body>
<Card.Section>

View File

@@ -89,6 +89,10 @@ initialize({
BBB_LEARN_MORE_URL: process.env.BBB_LEARN_MORE_URL || '',
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL || null,
STUDIO_SHORT_NAME: process.env.STUDIO_SHORT_NAME || null,
TERMS_OF_SERVICE_URL: process.env.TERMS_OF_SERVICE_URL || null,
PRIVACY_POLICY_URL: process.env.PRIVACY_POLICY_URL || null,
SHOW_ACCESSIBILITY_PAGE: process.env.SHOW_ACCESSIBILITY_PAGE || false,
NOTIFICATION_FEEDBACK_URL: process.env.NOTIFICATION_FEEDBACK_URL || null,
}, 'CourseAuthoringConfig');
},
},