feat: [AXIMST-19, 20, 22] Course unit - Modal windows for course unit page components (#118)

* feat: added modal windows for course unit page components

* refactor: code refactoring

* refactor: added translations

* refactor: refactoring after review

* refactor: after review
This commit is contained in:
Peter Kulko
2024-01-22 11:03:28 +02:00
committed by Adolfo R. Brandes
parent 2b11df9eb5
commit 6aff1c1168
30 changed files with 855 additions and 69 deletions

View File

@@ -36,7 +36,7 @@ const CourseUnit = ({ courseId }) => {
headerNavigationsActions,
handleTitleEdit,
handleInternetConnectionFailed,
handleCreateNewCourseXblock,
handleCreateNewCourseXBlock,
} = useCourseUnit({ courseId, blockId });
document.title = getPageHeadTitle('', unitTitle);
@@ -80,7 +80,7 @@ const CourseUnit = ({ courseId }) => {
courseId={courseId}
sequenceId={sequenceId}
unitId={blockId}
handleCreateNewCourseXblock={handleCreateNewCourseXblock}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
/>
<Layout
lg={[{ span: 9 }, { span: 3 }]}
@@ -92,7 +92,7 @@ const CourseUnit = ({ courseId }) => {
<Layout.Element>
<AddComponent
blockId={blockId}
handleCreateNewCourseXblock={handleCreateNewCourseXblock}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
/>
</Layout.Element>
<Layout.Element />

View File

@@ -2,30 +2,58 @@ import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Button } from '@openedx/paragon';
import { useToggle } from '@openedx/paragon';
import { getCourseSectionVertical } from '../data/selectors';
import { COMPONENT_ICON_TYPES } from '../constants';
import ComponentIcon from './ComponentIcon';
import ComponentModalView from './add-component-modals/ComponentModalView';
import AddComponentButton from './add-component-btn';
import messages from './messages';
const AddComponent = ({ blockId, handleCreateNewCourseXblock }) => {
const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
const navigate = useNavigate();
const intl = useIntl();
const [isOpenAdvanced, openAdvanced, closeAdvanced] = useToggle(false);
const [isOpenHtml, openHtml, closeHtml] = useToggle(false);
const [isOpenOpenAssessment, openOpenAssessment, closeOpenAssessment] = useToggle(false);
const { componentTemplates } = useSelector(getCourseSectionVertical);
const handleCreateNewXblock = (type) => () => {
const handleCreateNewXBlock = (type, moduleName) => {
switch (type) {
case COMPONENT_ICON_TYPES.discussion:
case COMPONENT_ICON_TYPES.dragAndDrop:
handleCreateNewCourseXblock({ type, parentLocator: blockId });
handleCreateNewCourseXBlock({ type, parentLocator: blockId });
break;
case COMPONENT_ICON_TYPES.problem:
case COMPONENT_ICON_TYPES.video:
handleCreateNewCourseXblock({ type, parentLocator: blockId }, ({ courseKey, locator }) => {
handleCreateNewCourseXBlock({ type, parentLocator: blockId }, ({ courseKey, locator }) => {
navigate(`/course/${courseKey}/editor/${type}/${locator}`);
});
break;
// TODO: The library functional will be a bit different of current legacy (CMS)
// behaviour and this ticket is on hold (blocked by other development team).
case COMPONENT_ICON_TYPES.library:
handleCreateNewCourseXBlock({ type, category: 'library_content', parentLocator: blockId });
break;
case COMPONENT_ICON_TYPES.advanced:
handleCreateNewCourseXBlock({
type: moduleName, category: moduleName, parentLocator: blockId,
});
break;
case COMPONENT_ICON_TYPES.openassessment:
handleCreateNewCourseXBlock({
boilerplate: moduleName, category: type, parentLocator: blockId,
});
break;
case COMPONENT_ICON_TYPES.html:
handleCreateNewCourseXBlock({
type,
boilerplate: moduleName,
parentLocator: blockId,
}, ({ courseKey, locator }) => {
navigate(`/course/${courseKey}/editor/html/${locator}`);
});
break;
default:
}
};
@@ -38,19 +66,53 @@ const AddComponent = ({ blockId, handleCreateNewCourseXblock }) => {
<div className="py-4">
<h5 className="h3 mb-4 text-center">{intl.formatMessage(messages.title)}</h5>
<ul className="new-component-type list-unstyled m-0 d-flex flex-wrap justify-content-center">
{Object.keys(componentTemplates).map((component) => (
<li key={componentTemplates[component].type}>
<Button
variant="outline-primary"
className="add-component-button flex-column rounded-sm"
onClick={handleCreateNewXblock(componentTemplates[component].type)}
>
<ComponentIcon type={componentTemplates[component].type} />
<span className="sr-only">{intl.formatMessage(messages.buttonText)}</span>
<span className="small mt-2">{componentTemplates[component].displayName}</span>
</Button>
</li>
))}
{componentTemplates.map((component) => {
const { type, displayName } = component;
let modalParams;
switch (type) {
case COMPONENT_ICON_TYPES.advanced:
modalParams = {
open: openAdvanced,
close: closeAdvanced,
isOpen: isOpenAdvanced,
};
break;
case COMPONENT_ICON_TYPES.html:
modalParams = {
open: openHtml,
close: closeHtml,
isOpen: isOpenHtml,
};
break;
case COMPONENT_ICON_TYPES.openassessment:
modalParams = {
open: openOpenAssessment,
close: closeOpenAssessment,
isOpen: isOpenOpenAssessment,
};
break;
default:
return (
<li key={type}>
<AddComponentButton
onClick={() => handleCreateNewXBlock(type)}
displayName={displayName}
type={type}
/>
</li>
);
}
return (
<ComponentModalView
key={type}
component={component}
handleCreateNewXBlock={handleCreateNewXBlock}
modalParams={modalParams}
/>
);
})}
</ul>
</div>
);
@@ -58,7 +120,7 @@ const AddComponent = ({ blockId, handleCreateNewCourseXblock }) => {
AddComponent.propTypes = {
blockId: PropTypes.string.isRequired,
handleCreateNewCourseXblock: PropTypes.func.isRequired,
handleCreateNewCourseXBlock: PropTypes.func.isRequired,
};
export default AddComponent;

View File

@@ -10,3 +10,7 @@
height: 6.875rem;
}
}
.add-component-modal-radio .pgn__form-radio-input {
min-width: 1.25rem;
}

View File

@@ -1,5 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import { render } from '@testing-library/react';
import {
render, waitFor, within,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { initializeMockApp } from '@edx/frontend-platform';
@@ -12,20 +14,21 @@ import { executeThunk } from '../../utils';
import { fetchCourseSectionVerticalData } from '../data/thunk';
import { getCourseSectionVerticalApiUrl } from '../data/api';
import { courseSectionVerticalMock } from '../__mocks__';
import { COMPONENT_ICON_TYPES } from '../constants';
import AddComponent from './AddComponent';
import messages from './messages';
let store;
let axiosMock;
const blockId = '123';
const handleCreateNewCourseXblockMock = jest.fn();
const handleCreateNewCourseXBlockMock = jest.fn();
const renderComponent = (props) => render(
<AppProvider store={store}>
<IntlProvider locale="en">
<AddComponent
blockId={blockId}
handleCreateNewCourseXblock={handleCreateNewCourseXblockMock}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlockMock}
{...props}
/>
</IntlProvider>
@@ -100,7 +103,7 @@ describe('<AddComponent />', () => {
});
userEvent.click(customComponentButton);
expect(handleCreateNewCourseXblockMock).not.toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).not.toHaveBeenCalled();
});
it('calls handleCreateNewCourseXblock with correct parameters when Discussion xblock create button is clicked', () => {
@@ -111,8 +114,8 @@ describe('<AddComponent />', () => {
});
userEvent.click(discussionButton);
expect(handleCreateNewCourseXblockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXblockMock).toHaveBeenCalledWith({
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
parentLocator: '123',
type: 'discussion',
});
@@ -126,14 +129,14 @@ describe('<AddComponent />', () => {
});
userEvent.click(discussionButton);
expect(handleCreateNewCourseXblockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXblockMock).toHaveBeenCalledWith({
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
parentLocator: '123',
type: 'drag-and-drop-v2',
});
});
it('calls handleCreateNewCourseXblock with correct parameters when Problem xblock create button is clicked', () => {
it('calls handleCreateNewCourseXBlock with correct parameters when Problem xblock create button is clicked', () => {
const { getByRole } = renderComponent();
const discussionButton = getByRole('button', {
@@ -141,14 +144,14 @@ describe('<AddComponent />', () => {
});
userEvent.click(discussionButton);
expect(handleCreateNewCourseXblockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXblockMock).toHaveBeenCalledWith({
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
parentLocator: '123',
type: 'problem',
}, expect.any(Function));
});
it('calls handleCreateNewCourseXblock with correct parameters when Video xblock create button is clicked', () => {
it('calls handleCreateNewCourseXBlock with correct parameters when Video xblock create button is clicked', () => {
const { getByRole } = renderComponent();
const discussionButton = getByRole('button', {
@@ -156,10 +159,187 @@ describe('<AddComponent />', () => {
});
userEvent.click(discussionButton);
expect(handleCreateNewCourseXblockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXblockMock).toHaveBeenCalledWith({
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
parentLocator: '123',
type: 'video',
}, expect.any(Function));
});
it('creates new "Library" xblock on click', () => {
const { getByRole } = renderComponent();
const discussionButton = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Library Content`, 'i'),
});
userEvent.click(discussionButton);
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
parentLocator: '123',
category: 'library_content',
type: 'library',
});
});
it('verifies modal behavior on button click', async () => {
const { getByRole, queryByRole } = renderComponent();
const advancedBtn = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Advanced`, 'i'),
});
userEvent.click(advancedBtn);
const modalContainer = getByRole('dialog');
expect(within(modalContainer).getByRole('button', { name: messages.modalContainerCancelBtnText.defaultMessage })).toBeInTheDocument();
expect(within(modalContainer).getByRole('button', { name: messages.modalBtnText.defaultMessage })).toBeInTheDocument();
userEvent.click(within(modalContainer).getByRole('button', { name: messages.modalContainerCancelBtnText.defaultMessage }));
expect(queryByRole('button', { name: messages.modalContainerCancelBtnText.defaultMessage })).toBeNull();
expect(queryByRole('button', { name: messages.modalBtnText.defaultMessage })).toBeNull();
});
it('verifies "Advanced" component selection in modal', async () => {
const { getByRole, getByText } = renderComponent();
const advancedBtn = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Advanced`, 'i'),
});
const componentTemplates = courseSectionVerticalMock.component_templates;
userEvent.click(advancedBtn);
const modalContainer = getByRole('dialog');
await waitFor(() => {
expect(getByText(/Add advanced component/i)).toBeInTheDocument();
componentTemplates.forEach((componentTemplate) => {
if (componentTemplate.type === COMPONENT_ICON_TYPES.advanced) {
componentTemplate.templates.forEach((template) => {
expect(within(modalContainer).getByRole('radio', { name: template.display_name })).toBeInTheDocument();
});
}
});
});
});
it('verifies "Text" component selection in modal', async () => {
const { getByRole, getByText } = renderComponent();
const textBtn = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Text`, 'i'),
});
const componentTemplates = courseSectionVerticalMock.component_templates;
userEvent.click(textBtn);
const modalContainer = getByRole('dialog');
await waitFor(() => {
expect(getByText(/Add text component/i)).toBeInTheDocument();
componentTemplates.forEach((componentTemplate) => {
if (componentTemplate.type === COMPONENT_ICON_TYPES.html) {
componentTemplate.templates.forEach((template) => {
expect(within(modalContainer).getByRole('radio', { name: template.display_name })).toBeInTheDocument();
});
}
});
});
});
it('verifies "Open Response" component selection in modal', async () => {
const { getByRole, getByText } = renderComponent();
const openResponseBtn = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Open Response`, 'i'),
});
const componentTemplates = courseSectionVerticalMock.component_templates;
userEvent.click(openResponseBtn);
const modalContainer = getByRole('dialog');
await waitFor(() => {
expect(getByText(/Add open response component/i)).toBeInTheDocument();
componentTemplates.forEach((componentTemplate) => {
if (componentTemplate.type === COMPONENT_ICON_TYPES.openassessment) {
componentTemplate.templates.forEach((template) => {
expect(within(modalContainer).getByRole('radio', { name: template.display_name })).toBeInTheDocument();
});
}
});
});
});
it('verifies "Advanced" component creation and submission in modal', () => {
const { getByRole } = renderComponent();
const advancedButton = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Advanced`, 'i'),
});
userEvent.click(advancedButton);
const modalContainer = getByRole('dialog');
const radioInput = within(modalContainer).getByRole('radio', { name: 'Annotation' });
const sendBtn = within(modalContainer).getByRole('button', { name: messages.modalBtnText.defaultMessage });
expect(sendBtn).toBeDisabled();
userEvent.click(radioInput);
expect(sendBtn).not.toBeDisabled();
userEvent.click(sendBtn);
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
parentLocator: '123',
type: 'annotatable',
category: 'annotatable',
});
});
it('verifies "Text" component creation and submission in modal', () => {
const { getByRole } = renderComponent();
const advancedButton = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Text`, 'i'),
});
userEvent.click(advancedButton);
const modalContainer = getByRole('dialog');
const radioInput = within(modalContainer).getByRole('radio', { name: 'Text' });
const sendBtn = within(modalContainer).getByRole('button', { name: messages.modalBtnText.defaultMessage });
expect(sendBtn).toBeDisabled();
userEvent.click(radioInput);
expect(sendBtn).not.toBeDisabled();
userEvent.click(sendBtn);
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
parentLocator: '123',
type: 'html',
boilerplate: 'html',
}, expect.any(Function));
});
it('verifies "Open Response" component creation and submission in modal', () => {
const { getByRole } = renderComponent();
const advancedButton = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Open Response`, 'i'),
});
userEvent.click(advancedButton);
const modalContainer = getByRole('dialog');
const radioInput = within(modalContainer).getByRole('radio', { name: 'Peer Assessment Only' });
const sendBtn = within(modalContainer).getByRole('button', { name: messages.modalBtnText.defaultMessage });
expect(sendBtn).toBeDisabled();
userEvent.click(radioInput);
expect(sendBtn).not.toBeDisabled();
userEvent.click(sendBtn);
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
parentLocator: '123',
category: 'openassessment',
boilerplate: 'peer-assessment',
});
});
});

View File

@@ -2,16 +2,16 @@ import PropTypes from 'prop-types';
import { Icon } from '@openedx/paragon';
import { EditNote as EditNoteIcon } from '@openedx/paragon/icons';
import { COMPONENT_TYPE_ICON_MAP, COMPONENT_ICON_TYPES } from '../constants';
import { COMPONENT_ICON_TYPES, COMPONENT_TYPE_ICON_MAP } from '../../constants';
const ComponentIcon = ({ type }) => {
const AddComponentIcon = ({ type }) => {
const icon = COMPONENT_TYPE_ICON_MAP[type] || EditNoteIcon;
return <Icon src={icon} screenReaderText={type} />;
};
ComponentIcon.propTypes = {
AddComponentIcon.propTypes = {
type: PropTypes.oneOf(Object.values(COMPONENT_ICON_TYPES)).isRequired,
};
export default ComponentIcon;
export default AddComponentIcon;

View File

@@ -0,0 +1,30 @@
import PropTypes from 'prop-types';
import { Button } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from '../messages';
import AddComponentIcon from './AddComponentIcon';
const AddComponentButton = ({ type, displayName, onClick }) => {
const intl = useIntl();
return (
<Button
variant="outline-primary"
className="add-component-button flex-column rounded-sm"
onClick={onClick}
>
<AddComponentIcon type={type} />
<span className="sr-only">{intl.formatMessage(messages.buttonText)}</span>
<span className="small mt-2">{displayName}</span>
</Button>
);
};
AddComponentButton.propTypes = {
type: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};
export default AddComponentButton;

View File

@@ -0,0 +1,93 @@
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import { Form } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import { updateQueryPendingStatus } from '../../data/slice';
import AddComponentButton from '../add-component-btn';
import messages from '../messages';
import ModalContainer from './ModalContainer';
const ComponentModalView = ({
component,
modalParams,
handleCreateNewXBlock,
}) => {
const intl = useIntl();
const dispatch = useDispatch();
const [moduleTitle, setModuleTitle] = useState('');
const { open, close, isOpen } = modalParams;
const { type, displayName, templates } = component;
const handleSubmit = () => {
handleCreateNewXBlock(type, moduleTitle);
dispatch(updateQueryPendingStatus(true));
setModuleTitle('');
};
return (
<>
<li>
<AddComponentButton
onClick={open}
type={type}
displayName={displayName}
/>
</li>
<ModalContainer
isOpen={isOpen}
close={close}
title={intl.formatMessage(messages.modalContainerTitle, { componentTitle: displayName.toLowerCase() })}
btnText={intl.formatMessage(messages.modalBtnText)}
onSubmit={handleSubmit}
resetDisabled={() => setModuleTitle('')}
hasValue={!moduleTitle.length}
>
<Form.Group>
<Form.RadioSet
name={displayName}
onChange={(e) => setModuleTitle(e.target.value)}
>
{templates.map((componentTemplate) => {
const value = componentTemplate.boilerplateName || componentTemplate.category;
return (
<Form.Radio
key={componentTemplate.displayName}
className="add-component-modal-radio mb-2.5"
value={value}
>
{componentTemplate.displayName}
</Form.Radio>
);
})}
</Form.RadioSet>
</Form.Group>
</ModalContainer>
</>
);
};
ComponentModalView.propTypes = {
modalParams: PropTypes.shape({
open: PropTypes.func,
close: PropTypes.func,
isOpen: PropTypes.bool,
}).isRequired,
handleCreateNewXBlock: PropTypes.func.isRequired,
component: PropTypes.shape({
displayName: PropTypes.string.isRequired,
category: PropTypes.string,
type: PropTypes.string.isRequired,
templates: PropTypes.arrayOf(
PropTypes.shape({
boilerplateName: PropTypes.string,
category: PropTypes.string,
displayName: PropTypes.string.isRequired,
}),
),
}).isRequired,
};
export default ComponentModalView;

View File

@@ -0,0 +1,61 @@
import PropTypes from 'prop-types';
import { ActionRow, Button, StandardModal } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from '../messages';
const ModalContainer = ({
title, isOpen, close, children, btnText, size, onSubmit, hasValue, resetDisabled,
}) => {
const intl = useIntl();
const handleSubmit = () => {
onSubmit();
close();
};
const handleClose = () => {
resetDisabled();
close();
};
return (
<StandardModal
title={title}
isOpen={isOpen}
onClose={handleClose}
size={size}
footerNode={(
<ActionRow>
<ActionRow.Spacer />
<Button variant="tertiary" onClick={handleClose}>
{intl.formatMessage(messages.modalContainerCancelBtnText)}
</Button>
<Button onClick={handleSubmit} disabled={hasValue}>
{btnText}
</Button>
</ActionRow>
)}
>
{children}
</StandardModal>
);
};
ModalContainer.propTypes = {
title: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
children: PropTypes.node.isRequired,
btnText: PropTypes.string.isRequired,
size: PropTypes.string,
onSubmit: PropTypes.func.isRequired,
hasValue: PropTypes.bool.isRequired,
resetDisabled: PropTypes.func.isRequired,
};
ModalContainer.defaultProps = {
size: 'md',
};
export default ModalContainer;

View File

@@ -9,6 +9,18 @@ const messages = defineMessages({
id: 'course-authoring.course-unit.add.component.button.text',
defaultMessage: 'Add Component:',
},
modalBtnText: {
id: 'course-authoring.course-unit.modal.button.text',
defaultMessage: 'Select',
},
modalContainerTitle: {
id: 'course-authoring.course-unit.modal.container.title',
defaultMessage: 'Add {componentTitle} component',
},
modalContainerCancelBtnText: {
id: 'course-authoring.course-unit.modal.container.cancel.button.text',
defaultMessage: 'Cancel',
},
});
export default messages;

View File

@@ -13,7 +13,7 @@ const Sequence = ({
courseId,
sequenceId,
unitId,
handleCreateNewCourseXblock,
handleCreateNewCourseXBlock,
}) => {
const intl = useIntl();
const { IN_PROGRESS, FAILED, SUCCESSFUL } = RequestStatus;
@@ -27,7 +27,7 @@ const Sequence = ({
sequenceId={sequenceId}
unitId={unitId}
courseId={courseId}
handleCreateNewCourseXblock={handleCreateNewCourseXblock}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
/>
</div>
</div>
@@ -60,7 +60,7 @@ Sequence.propTypes = {
unitId: PropTypes.string,
courseId: PropTypes.string.isRequired,
sequenceId: PropTypes.string,
handleCreateNewCourseXblock: PropTypes.func.isRequired,
handleCreateNewCourseXBlock: PropTypes.func.isRequired,
};
Sequence.defaultProps = {

View File

@@ -23,7 +23,7 @@ const SequenceNavigation = ({
unitId,
sequenceId,
className,
handleCreateNewCourseXblock,
handleCreateNewCourseXBlock,
}) => {
const sequenceStatus = useSelector(getSequenceStatus);
const {
@@ -43,7 +43,7 @@ const SequenceNavigation = ({
<SequenceNavigationTabs
unitIds={sequence.unitIds || []}
unitId={unitId}
handleCreateNewCourseXblock={handleCreateNewCourseXblock}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
/>
);
};
@@ -107,7 +107,7 @@ SequenceNavigation.propTypes = {
unitId: PropTypes.string,
className: PropTypes.string,
sequenceId: PropTypes.string,
handleCreateNewCourseXblock: PropTypes.func.isRequired,
handleCreateNewCourseXBlock: PropTypes.func.isRequired,
};
SequenceNavigation.defaultProps = {

View File

@@ -12,7 +12,7 @@ import { useIndexOfLastVisibleChild } from '../hooks';
import SequenceNavigationDropdown from './SequenceNavigationDropdown';
import UnitButton from './UnitButton';
const SequenceNavigationTabs = ({ unitIds, unitId, handleCreateNewCourseXblock }) => {
const SequenceNavigationTabs = ({ unitIds, unitId, handleCreateNewCourseXBlock }) => {
const intl = useIntl();
const dispatch = useDispatch();
const navigate = useNavigate();
@@ -28,7 +28,7 @@ const SequenceNavigationTabs = ({ unitIds, unitId, handleCreateNewCourseXblock }
const handleAddNewSequenceUnit = () => {
dispatch(updateQueryPendingStatus(true));
handleCreateNewCourseXblock({ parentLocator: sequenceId, category: 'vertical', displayName: 'Unit' }, ({ courseKey, locator }) => {
handleCreateNewCourseXBlock({ parentLocator: sequenceId, category: 'vertical', displayName: 'Unit' }, ({ courseKey, locator }) => {
navigate(`/course/${courseKey}/container/${locator}/${sequenceId}`, courseId);
dispatch(changeEditTitleFormOpen(true));
});
@@ -72,7 +72,7 @@ const SequenceNavigationTabs = ({ unitIds, unitId, handleCreateNewCourseXblock }
SequenceNavigationTabs.propTypes = {
unitId: PropTypes.string.isRequired,
unitIds: PropTypes.arrayOf(PropTypes.string).isRequired,
handleCreateNewCourseXblock: PropTypes.func.isRequired,
handleCreateNewCourseXBlock: PropTypes.func.isRequired,
};
export default SequenceNavigationTabs;

View File

@@ -14,13 +14,14 @@ const getStudioBaseUrl = () => getConfig().STUDIO_BASE_URL;
const getLmsBaseUrl = () => getConfig().LMS_BASE_URL;
export const getCourseUnitApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/container/${itemId}`;
export const postXBlockBaseApiUrl = () => `${getStudioBaseUrl()}/xblock/`;
export const getXBlockBaseApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/${itemId}`;
export const getCourseSectionVerticalApiUrl = (itemId) => `${getStudioBaseUrl()}/api/contentstore/v1/container_handler/${itemId}`;
export const getLearningSequencesOutlineApiUrl = (courseId) => `${getLmsBaseUrl()}/api/learning_sequences/v1/course_outline/${courseId}`;
export const getCourseMetadataApiUrl = (courseId) => `${getLmsBaseUrl()}/api/courseware/course/${courseId}`;
export const getCourseHomeCourseMetadataApiUrl = (courseId) => `${getLmsBaseUrl()}/api/course_home/course_metadata/${courseId}`;
export const postXBlockBaseApiUrl = () => `${getStudioBaseUrl()}/xblock/`;
/**
* Get course unit.
* @param {string} unitId
@@ -101,15 +102,27 @@ export async function getCourseHomeCourseMetadata(courseId, rootSlug) {
return normalizeCourseHomeCourseMetadata(data, rootSlug);
}
/**
* Creates a new course XBlock.
* @param {Object} options - The options for creating the XBlock.
* @param {string} options.type - The type of the XBlock.
* @param {string} [options.category] - The category of the XBlock. Defaults to the type if not provided.
* @param {string} options.parentLocator - The parent locator of the XBlock.
* @param {string} [options.displayName] - The display name for the XBlock.
* @param {string} [options.boilerplate] - The boilerplate for the XBlock.
* @returns {Promise<Object>} A Promise that resolves to the created XBlock data.
*/
export async function createCourseXblock({
type, category, parentLocator, displayName,
type, category, parentLocator, displayName, boilerplate,
}) {
const body = {
type,
boilerplate,
category: category || type,
parent_locator: parentLocator,
display_name: displayName,
};
const { data } = await getAuthenticatedHttpClient()
.post(postXBlockBaseApiUrl(), body);

View File

@@ -8,6 +8,7 @@ export const getSavingStatus = (state) => state.courseUnit.savingStatus;
export const getLoadingStatus = (state) => state.courseUnit.loadingStatus;
export const getSequenceStatus = (state) => state.courseUnit.sequenceStatus;
export const getCourseSectionVertical = (state) => state.courseUnit.courseSectionVertical;
export const getCourseUnitComponentTemplates = (state) => state.courseUnit.courseSectionVertical.componentTemplates;
export const getCourseSectionVerticalLoadingStatus = (state) => state
.courseUnit.loadingStatus.courseSectionVerticalLoadingStatus;
export const getCourseStatus = state => state.courseUnit.courseStatus;

View File

@@ -66,7 +66,7 @@ export const useCourseUnit = ({ courseId, blockId }) => {
}
};
const handleCreateNewCourseXblock = (body, callback) => (
const handleCreateNewCourseXBlock = (body, callback) => (
dispatch(createNewCourseXblock(body, callback))
);
@@ -101,6 +101,6 @@ export const useCourseUnit = ({ courseId, blockId }) => {
headerNavigationsActions,
handleTitleEdit,
handleTitleEditSubmit,
handleCreateNewCourseXblock,
handleCreateNewCourseXBlock,
};
};

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -14,5 +14,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}

View File

@@ -991,5 +991,27 @@
"course-authoring.course-unit.sequence.no.content": "There is no content here.",
"course-authoring.course-unit.sequence.navigation.menu": "{current} of {total}",
"course-authoring.course-unit.add.component.title": "Add a new component",
"course-authoring.course-unit.add.component.button.text": "Add Component:"
"course-authoring.course-unit.add.component.button.text": "Add Component:",
"course-authoring.certificates.heading.title": "Certificates",
"course-authoring.certificates.heading.subtitle": "Settings",
"course-authoring.certificates.heading.action.button.preview": "Preview certificate",
"course-authoring.certificates.heading.action.button.deactivate": "Deactivate",
"course-authoring.certificates.nocertificate.text": "You haven't added any certificates to this course yet.",
"course-authoring.certificates.setup.certificate.button": "Add your first certificate",
"course-authoring.certificates.without.modes.text": "This course does not use a mode that offers certificates.",
"course-authoring.certificates.sidebar.about.title": "Working with Certificates",
"course-authoring.certificates.sidebar.about.description-1": "Specify a course title to use on the certificate if the course's official title is too long to be displayed well.",
"course-authoring.certificates.sidebar.about.description-2": "For verified certificates, specify between one and four signatories and upload the associated images. To edit or delete a certificate before it is activated, hover over the top right corner of the form and select {strongText} or the delete icon.",
"course-authoring.certificates.sidebar.about.description-2.strong": "Edit",
"course-authoring.certificates.sidebar.about.description-3": "To view a sample certificate, choose a course mode and select {strongText}.",
"course-authoring.certificates.sidebar.about.description-3.strong": "Preview Certificate",
"course-authoring.certificates.sidebar.about2.title": "Issuing Certificates to Learners",
"course-authoring.certificates.sidebar.about2.description-1": "To begin issuing course certificates, a course team member with either the Staff or Admin role selects {strongText}. Only course team members with these roles can edit or delete an activated certificate.",
"course-authoring.certificates.sidebar.about2.description-1.strong": "Activate",
"course-authoring.certificates.sidebar.about2.description-2": "{strongText} delete certificates after a course has started; learners who have already earned certificates will no longer be able to access them.",
"course-authoring.certificates.sidebar.about2.description-2.strong": "Do not",
"course-authoring.certificates.sidebar.learnmore.button": "Learn more about certificates",
"course-authoring.course-unit.modal.button.text": "Select",
"course-authoring.course-unit.modal.container.title": "Add {componentTitle} component",
"course-authoring.course-unit.modal.container.cancel.button.text": "Cancel"
}