From dff734337b6b16b1308077f58d0bb00e1bdf3a0b Mon Sep 17 00:00:00 2001 From: Kyrylo Kholodenko Date: Tue, 5 Sep 2023 10:19:58 +0300 Subject: [PATCH] feat: implement export page --- .env | 1 - .env.development | 1 - .env.test | 1 - package-lock.json | 1 + package.json | 1 + src/CourseAuthoringRoutes.jsx | 6 +- src/export-page/CourseExportPage.jsx | 127 ++++++++++++++++++ src/export-page/CourseExportPage.scss | 9 ++ src/export-page/CourseExportPage.test.jsx | 79 +++++++++++ src/export-page/__mocks__/exportPage.js | 3 + src/export-page/__mocks__/index.js | 2 + src/export-page/data/api.js | 19 +++ src/export-page/data/constants.js | 8 ++ src/export-page/data/selectors.js | 8 ++ src/export-page/data/slice.js | 63 +++++++++ src/export-page/data/thunks.js | 75 +++++++++++ .../export-footer/ExportFooter.jsx | 48 +++++++ .../export-footer/ExportFooter.scss | 14 ++ src/export-page/export-footer/messages.js | 58 ++++++++ .../export-modal-error/ExportModalError.jsx | 54 ++++++++ .../export-modal-error/messages.js | 34 +++++ .../export-sidebar/ExportSidebar.jsx | 49 +++++++ .../export-sidebar/ExportSidebar.scss | 4 + .../export-sidebar/ExportSidebar.test.jsx | 39 ++++++ src/export-page/export-sidebar/messages.js | 66 +++++++++ .../export-stepper/ExportStepper.jsx | 89 ++++++++++++ .../export-stepper/ExportStepper.scss | 3 + .../export-stepper/ExportStepper.test.jsx | 38 ++++++ src/export-page/export-stepper/messages.js | 46 +++++++ src/export-page/messages.js | 62 +++++++++ src/export-page/utils.js | 29 ++++ src/export-page/utils.test.js | 51 +++++++ .../course-stepper/CourseStepper.test.jsx | 96 +++++++++++++ src/generic/course-stepper/CouseStepper.scss | 68 ++++++++++ src/generic/course-stepper/index.jsx | 114 ++++++++++++++++ src/generic/modal-error/ModalError.jsx | 33 +++++ src/generic/styles.scss | 1 + src/i18n/messages/ar.json | 53 +++++++- src/i18n/messages/de.json | 53 +++++++- src/i18n/messages/de_DE.json | 53 +++++++- src/i18n/messages/es_419.json | 53 +++++++- src/i18n/messages/fr.json | 53 +++++++- src/i18n/messages/fr_CA.json | 53 +++++++- src/i18n/messages/hi.json | 53 +++++++- src/i18n/messages/it.json | 53 +++++++- src/i18n/messages/it_IT.json | 53 +++++++- src/i18n/messages/pt.json | 53 +++++++- src/i18n/messages/pt_PT.json | 53 +++++++- src/i18n/messages/ru.json | 53 +++++++- src/i18n/messages/uk.json | 53 +++++++- src/i18n/messages/zh_CN.json | 53 +++++++- src/index.scss | 1 + src/store.js | 2 + 53 files changed, 2124 insertions(+), 21 deletions(-) create mode 100644 src/export-page/CourseExportPage.jsx create mode 100644 src/export-page/CourseExportPage.scss create mode 100644 src/export-page/CourseExportPage.test.jsx create mode 100644 src/export-page/__mocks__/exportPage.js create mode 100644 src/export-page/__mocks__/index.js create mode 100644 src/export-page/data/api.js create mode 100644 src/export-page/data/constants.js create mode 100644 src/export-page/data/selectors.js create mode 100644 src/export-page/data/slice.js create mode 100644 src/export-page/data/thunks.js create mode 100644 src/export-page/export-footer/ExportFooter.jsx create mode 100644 src/export-page/export-footer/ExportFooter.scss create mode 100644 src/export-page/export-footer/messages.js create mode 100644 src/export-page/export-modal-error/ExportModalError.jsx create mode 100644 src/export-page/export-modal-error/messages.js create mode 100644 src/export-page/export-sidebar/ExportSidebar.jsx create mode 100644 src/export-page/export-sidebar/ExportSidebar.scss create mode 100644 src/export-page/export-sidebar/ExportSidebar.test.jsx create mode 100644 src/export-page/export-sidebar/messages.js create mode 100644 src/export-page/export-stepper/ExportStepper.jsx create mode 100644 src/export-page/export-stepper/ExportStepper.scss create mode 100644 src/export-page/export-stepper/ExportStepper.test.jsx create mode 100644 src/export-page/export-stepper/messages.js create mode 100644 src/export-page/messages.js create mode 100644 src/export-page/utils.js create mode 100644 src/export-page/utils.test.js create mode 100644 src/generic/course-stepper/CourseStepper.test.jsx create mode 100644 src/generic/course-stepper/CouseStepper.scss create mode 100644 src/generic/course-stepper/index.jsx create mode 100644 src/generic/modal-error/ModalError.jsx diff --git a/.env b/.env index 452df8210f..3df6415dca 100644 --- a/.env +++ b/.env @@ -36,7 +36,6 @@ ENABLE_NEW_VIDEO_UPLOAD_PAGE = false ENABLE_NEW_GRADING_PAGE = false ENABLE_NEW_COURSE_TEAM_PAGE = false ENABLE_NEW_IMPORT_PAGE = false -ENABLE_NEW_EXPORT_PAGE = false ENABLE_UNIT_PAGE = false ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = false BBB_LEARN_MORE_URL='' diff --git a/.env.development b/.env.development index 184972f459..b3ccd5f655 100644 --- a/.env.development +++ b/.env.development @@ -38,7 +38,6 @@ ENABLE_NEW_VIDEO_UPLOAD_PAGE = false ENABLE_NEW_GRADING_PAGE = false ENABLE_NEW_COURSE_TEAM_PAGE = false ENABLE_NEW_IMPORT_PAGE = false -ENABLE_NEW_EXPORT_PAGE = false ENABLE_UNIT_PAGE = false ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = false BBB_LEARN_MORE_URL='' diff --git a/.env.test b/.env.test index f97e6c8932..8118258480 100644 --- a/.env.test +++ b/.env.test @@ -34,7 +34,6 @@ ENABLE_NEW_VIDEO_UPLOAD_PAGE = true ENABLE_NEW_GRADING_PAGE = true ENABLE_NEW_COURSE_TEAM_PAGE = true ENABLE_NEW_IMPORT_PAGE = true -ENABLE_NEW_EXPORT_PAGE = true ENABLE_UNIT_PAGE = true ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = true BBB_LEARN_MORE_URL='' diff --git a/package-lock.json b/package-lock.json index db756f68b1..1b46d0f315 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "react-transition-group": "4.4.1", "redux": "4.0.5", "regenerator-runtime": "0.13.7", + "universal-cookie": "^4.0.4", "uuid": "^3.4.0", "yup": "0.31.1" }, diff --git a/package.json b/package.json index e0d4688fec..2e70bc737f 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "react-transition-group": "4.4.1", "redux": "4.0.5", "regenerator-runtime": "0.13.7", + "universal-cookie": "^4.0.4", "uuid": "^3.4.0", "yup": "0.31.1" }, diff --git a/src/CourseAuthoringRoutes.jsx b/src/CourseAuthoringRoutes.jsx index 1d886b0f86..3af7b0e74d 100644 --- a/src/CourseAuthoringRoutes.jsx +++ b/src/CourseAuthoringRoutes.jsx @@ -15,6 +15,7 @@ import ScheduleAndDetails from './schedule-and-details'; import { GradingSettings } from './grading-settings'; import CourseTeam from './course-team/CourseTeam'; import { CourseUpdates } from './course-updates'; +import CourseExportPage from './export-page/CourseExportPage'; /** * As of this writing, these routes are mounted at a path prefixed with the following: @@ -105,10 +106,7 @@ const CourseAuthoringRoutes = ({ courseId }) => { )} - {process.env.ENABLE_NEW_EXPORT_PAGE === 'true' - && ( - - )} + diff --git a/src/export-page/CourseExportPage.jsx b/src/export-page/CourseExportPage.jsx new file mode 100644 index 0000000000..9d3d9e698e --- /dev/null +++ b/src/export-page/CourseExportPage.jsx @@ -0,0 +1,127 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { + Container, Layout, Button, Card, +} from '@edx/paragon'; +import { ArrowCircleDown as ArrowCircleDownIcon } from '@edx/paragon/icons'; +import Cookies from 'universal-cookie'; +import { getConfig } from '@edx/frontend-platform'; +import { Helmet } from 'react-helmet'; + +import InternetConnectionAlert from '../generic/internet-connection-alert'; +import SubHeader from '../generic/sub-header/SubHeader'; +import { RequestStatus } from '../data/constants'; +import { useModel } from '../generic/model-store'; +import messages from './messages'; +import ExportSidebar from './export-sidebar/ExportSidebar'; +import { + getCurrentStage, getError, getExportTriggered, getLoadingStatus, getSavingStatus, +} from './data/selectors'; +import { startExportingCourse } from './data/thunks'; +import { EXPORT_STAGES, LAST_EXPORT_COOKIE_NAME } from './data/constants'; +import { updateExportTriggered, updateSavingStatus, updateSuccessDate } from './data/slice'; +import ExportModalError from './export-modal-error/ExportModalError'; +import ExportFooter from './export-footer/ExportFooter'; +import ExportStepper from './export-stepper/ExportStepper'; + +const CourseExportPage = ({ intl, courseId }) => { + const dispatch = useDispatch(); + const exportTriggered = useSelector(getExportTriggered); + const courseDetails = useModel('courseDetails', courseId); + const currentStage = useSelector(getCurrentStage); + const { msg: errorMessage } = useSelector(getError); + const loadingStatus = useSelector(getLoadingStatus); + const savingStatus = useSelector(getSavingStatus); + const cookies = new Cookies(); + const isShowExportButton = !exportTriggered || errorMessage || currentStage === EXPORT_STAGES.SUCCESS; + const anyRequestFailed = savingStatus === RequestStatus.FAILED || loadingStatus === RequestStatus.FAILED; + const anyRequestInProgress = savingStatus === RequestStatus.PENDING || loadingStatus === RequestStatus.IN_PROGRESS; + + useEffect(() => { + const cookieData = cookies.get(LAST_EXPORT_COOKIE_NAME); + if (cookieData) { + dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL })); + dispatch(updateExportTriggered(true)); + dispatch(updateSuccessDate(cookieData.date)); + } + }, []); + + return ( + <> + + + {intl.formatMessage(messages.pageTitle, { + headingTitle: intl.formatMessage(messages.headingTitle), + courseName: courseDetails?.name, + siteName: process.env.SITE_NAME, + })} + + + +
+ + +
+ +

{intl.formatMessage(messages.description1, { studioShortName: getConfig().STUDIO_SHORT_NAME })}

+

{intl.formatMessage(messages.description2)}

+ + + {isShowExportButton && ( + + + + )} + + {exportTriggered && } + +
+
+ + + +
+
+ +
+
+ null} + /> +
+ + ); +}; + +CourseExportPage.propTypes = { + intl: intlShape.isRequired, + courseId: PropTypes.string.isRequired, +}; + +CourseExportPage.defaultProps = {}; + +export default injectIntl(CourseExportPage); diff --git a/src/export-page/CourseExportPage.scss b/src/export-page/CourseExportPage.scss new file mode 100644 index 0000000000..a4db894a28 --- /dev/null +++ b/src/export-page/CourseExportPage.scss @@ -0,0 +1,9 @@ +@import "./export-stepper/ExportStepper"; +@import "./export-footer/ExportFooter"; +@import "./export-sidebar/ExportSidebar"; + +.export { + .help-sidebar { + margin-top: 7.188rem; + } +} diff --git a/src/export-page/CourseExportPage.test.jsx b/src/export-page/CourseExportPage.test.jsx new file mode 100644 index 0000000000..0d8a6c52c0 --- /dev/null +++ b/src/export-page/CourseExportPage.test.jsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; +import { AppProvider } from '@edx/frontend-platform/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import MockAdapter from 'axios-mock-adapter'; +import { Helmet } from 'react-helmet'; + +import initializeStore from '../store'; +import { exportPageMock } from './__mocks__'; +import messages from './messages'; +import CourseExportPage from './CourseExportPage'; +import { postExportCourseApiUrl } from './data/api'; + +let store; +let axiosMock; +const courseId = '123'; +const courseName = 'About Node JS'; + +jest.mock('../generic/model-store', () => ({ + useModel: jest.fn().mockReturnValue({ + name: courseName, + }), +})); + +const RootWrapper = () => ( + + + + + +); + +describe('', () => { + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + store = initializeStore(); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock + .onGet(postExportCourseApiUrl(courseId)) + .reply(200, exportPageMock); + }); + it('should render page title correctly', async () => { + render(); + await waitFor(() => { + const helmet = Helmet.peek(); + expect(helmet.title).toEqual( + `${messages.headingTitle.defaultMessage} | ${courseName} | ${process.env.SITE_NAME}`, + ); + }); + }); + it('should render without errors', async () => { + const { getByText } = render(); + await waitFor(() => { + expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument(); + const exportPageElement = getByText(messages.headingTitle.defaultMessage, { + selector: 'h2.sub-header-title', + }); + expect(exportPageElement).toBeInTheDocument(); + expect(getByText(messages.titleUnderButton.defaultMessage)).toBeInTheDocument(); + expect(getByText(messages.description2.defaultMessage)).toBeInTheDocument(); + expect(getByText(messages.buttonTitle.defaultMessage)).toBeInTheDocument(); + }); + }); + it('should start exporting on click', async () => { + const { getByText, container } = render(); + const button = container.querySelector('.btn-primary'); + fireEvent.click(button); + expect(getByText(/Preparing to start the export/i)).toBeInTheDocument(); + }); +}); diff --git a/src/export-page/__mocks__/exportPage.js b/src/export-page/__mocks__/exportPage.js new file mode 100644 index 0000000000..5380633d9c --- /dev/null +++ b/src/export-page/__mocks__/exportPage.js @@ -0,0 +1,3 @@ +module.exports = { + exportStatus: 1, +}; diff --git a/src/export-page/__mocks__/index.js b/src/export-page/__mocks__/index.js new file mode 100644 index 0000000000..ca6970871f --- /dev/null +++ b/src/export-page/__mocks__/index.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export +export { default as exportPageMock } from './exportPage'; diff --git a/src/export-page/data/api.js b/src/export-page/data/api.js new file mode 100644 index 0000000000..68d61c10ee --- /dev/null +++ b/src/export-page/data/api.js @@ -0,0 +1,19 @@ +/* eslint-disable import/prefer-default-export */ +import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; +export const postExportCourseApiUrl = (courseId) => new URL(`export/${courseId}`, getApiBaseUrl()).href; +export const getExportStatusApiUrl = (courseId) => new URL(`export_status/${courseId}`, getApiBaseUrl()).href; + +export async function startCourseExporting(courseId) { + const { data } = await getAuthenticatedHttpClient() + .post(postExportCourseApiUrl(courseId)); + return camelCaseObject(data); +} + +export async function getExportStatus(courseId) { + const { data } = await getAuthenticatedHttpClient() + .get(getExportStatusApiUrl(courseId)); + return camelCaseObject(data); +} diff --git a/src/export-page/data/constants.js b/src/export-page/data/constants.js new file mode 100644 index 0000000000..5824c61c7d --- /dev/null +++ b/src/export-page/data/constants.js @@ -0,0 +1,8 @@ +export const LAST_EXPORT_COOKIE_NAME = 'lastexport'; +export const EXPORT_STAGES = { + PREPARING: 0, + EXPORTING: 1, + COMPRESSING: 2, + SUCCESS: 3, +}; +export const SUCCESS_DATE_FORMAT = 'MM/DD/yyyy'; diff --git a/src/export-page/data/selectors.js b/src/export-page/data/selectors.js new file mode 100644 index 0000000000..2c6d0d737f --- /dev/null +++ b/src/export-page/data/selectors.js @@ -0,0 +1,8 @@ +export const getExportTriggered = (state) => state.courseExport.exportTriggered; +export const getCurrentStage = (state) => state.courseExport.currentStage; +export const getDownloadPath = (state) => state.courseExport.downloadPath; +export const getSuccessDate = (state) => state.courseExport.successDate; +export const getError = (state) => state.courseExport.error; +export const getIsErrorModalOpen = (state) => state.courseExport.isErrorModalOpen; +export const getLoadingStatus = (state) => state.courseExport.loadingStatus; +export const getSavingStatus = (state) => state.courseExport.savingStatus; diff --git a/src/export-page/data/slice.js b/src/export-page/data/slice.js new file mode 100644 index 0000000000..f431a69369 --- /dev/null +++ b/src/export-page/data/slice.js @@ -0,0 +1,63 @@ +/* eslint-disable no-param-reassign */ +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { + exportTriggered: false, + currentStage: 0, + error: { msg: null, unitUrl: null }, + downloadPath: null, + successDate: null, + isErrorModalOpen: false, + loadingStatus: '', + savingStatus: '', +}; + +const slice = createSlice({ + name: 'exportPage', + initialState, + reducers: { + updateExportTriggered: (state, { payload }) => { + state.exportTriggered = payload; + }, + updateCurrentStage: (state, { payload }) => { + if (payload >= state.currentStage) { + state.currentStage = payload; + } + }, + updateDownloadPath: (state, { payload }) => { + state.downloadPath = payload; + }, + updateSuccessDate: (state, { payload }) => { + state.successDate = payload; + }, + updateError: (state, { payload }) => { + state.error = payload; + }, + updateIsErrorModalOpen: (state, { payload }) => { + state.isErrorModalOpen = payload; + }, + reset: () => initialState, + updateLoadingStatus: (state, { payload }) => { + state.loadingStatus = payload.status; + }, + updateSavingStatus: (state, { payload }) => { + state.savingStatus = payload.status; + }, + }, +}); + +export const { + updateExportTriggered, + updateCurrentStage, + updateDownloadPath, + updateSuccessDate, + updateError, + updateIsErrorModalOpen, + reset, + updateLoadingStatus, + updateSavingStatus, +} = slice.actions; + +export const { + reducer, +} = slice; diff --git a/src/export-page/data/thunks.js b/src/export-page/data/thunks.js new file mode 100644 index 0000000000..ceded5cb8d --- /dev/null +++ b/src/export-page/data/thunks.js @@ -0,0 +1,75 @@ +import Cookies from 'universal-cookie'; +import moment from 'moment'; + +import { RequestStatus } from '../../data/constants'; +import { setExportCookie } from '../utils'; +import { EXPORT_STAGES, LAST_EXPORT_COOKIE_NAME } from './constants'; + +import { + startCourseExporting, + getExportStatus, +} from './api'; +import { + updateExportTriggered, + updateCurrentStage, + updateDownloadPath, + updateSuccessDate, + updateError, + updateIsErrorModalOpen, + reset, + updateLoadingStatus, + updateSavingStatus, +} from './slice'; + +export function startExportingCourse(courseId) { + return async (dispatch) => { + dispatch(updateSavingStatus({ status: RequestStatus.PENDING })); + try { + dispatch(reset()); + dispatch(updateExportTriggered(true)); + const exportData = await startCourseExporting(courseId); + dispatch(updateCurrentStage(exportData.exportStatus)); + setExportCookie(moment().valueOf(), exportData.exportStatus === EXPORT_STAGES.SUCCESS); + + dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL })); + return true; + } catch (error) { + dispatch(updateSavingStatus({ status: RequestStatus.FAILED })); + return false; + } + }; +} + +export function fetchExportStatus(courseId) { + return async (dispatch) => { + dispatch(updateLoadingStatus({ status: RequestStatus.IN_PROGRESS })); + try { + const { exportStatus, exportOutput, exportError } = await getExportStatus(courseId); + dispatch(updateCurrentStage(Math.abs(exportStatus))); + + if (exportOutput) { + dispatch(updateDownloadPath(exportOutput)); + dispatch(updateSuccessDate(moment().valueOf())); + } + + const cookies = new Cookies(); + const cookieData = cookies.get(LAST_EXPORT_COOKIE_NAME); + if (!cookieData?.completed) { + setExportCookie(moment().valueOf(), exportStatus === EXPORT_STAGES.SUCCESS); + } + + if (exportError) { + const errorMessage = exportError.rawErrorMsg || exportError; + const errorUnitUrl = exportError.editUnitUrl || null; + dispatch(updateError({ msg: errorMessage, unitUrl: errorUnitUrl })); + dispatch(updateIsErrorModalOpen(true)); + } + + dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL })); + return true; + } catch (error) { + dispatch(updateLoadingStatus({ status: RequestStatus.FAILED })); + return false; + } + }; +} diff --git a/src/export-page/export-footer/ExportFooter.jsx b/src/export-page/export-footer/ExportFooter.jsx new file mode 100644 index 0000000000..58b5d0e5b0 --- /dev/null +++ b/src/export-page/export-footer/ExportFooter.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { + injectIntl, + intlShape, +} from '@edx/frontend-platform/i18n'; +import { Layout } from '@edx/paragon'; + +import messages from './messages'; + +const ExportFooter = ({ intl }) => ( +
+ + +

{intl.formatMessage(messages.exportedDataTitle)}

+
    +
  • {intl.formatMessage(messages.exportedDataItem1)}
  • +
  • {intl.formatMessage(messages.exportedDataItem2)}
  • +
  • {intl.formatMessage(messages.exportedDataItem3)}
  • +
  • {intl.formatMessage(messages.exportedDataItem4)}
  • +
  • {intl.formatMessage(messages.exportedDataItem5)}
  • +
  • {intl.formatMessage(messages.exportedDataItem6)}
  • +
  • {intl.formatMessage(messages.exportedDataItem7)}
  • +
+
+ +

{intl.formatMessage(messages.notExportedDataTitle)}

+
    +
  • {intl.formatMessage(messages.notExportedDataItem1)}
  • +
  • {intl.formatMessage(messages.notExportedDataItem2)}
  • +
  • {intl.formatMessage(messages.notExportedDataItem3)}
  • +
  • {intl.formatMessage(messages.notExportedDataItem4)}
  • +
+
+
+
+); + +ExportFooter.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(ExportFooter); diff --git a/src/export-page/export-footer/ExportFooter.scss b/src/export-page/export-footer/ExportFooter.scss new file mode 100644 index 0000000000..33c04ac650 --- /dev/null +++ b/src/export-page/export-footer/ExportFooter.scss @@ -0,0 +1,14 @@ +.export-footer-list { + list-style: none; + padding-left: 0; + + li { + padding-bottom: .3125rem; + border-bottom: 1px solid #E5E5E5; + margin-bottom: .3125rem; + } + + li:last-child { + border-bottom: none; + } +} diff --git a/src/export-page/export-footer/messages.js b/src/export-page/export-footer/messages.js new file mode 100644 index 0000000000..a11d5b7ade --- /dev/null +++ b/src/export-page/export-footer/messages.js @@ -0,0 +1,58 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + exportedDataTitle: { + id: 'course-authoring.export.footer.exportedData.title', + defaultMessage: 'Data exported with your course:', + }, + exportedDataItem1: { + id: 'course-authoring.export.footer.exportedData.item.1', + defaultMessage: 'Values from Advanced settings, including MATLAB API keys and LTI passports', + }, + exportedDataItem2: { + id: 'course-authoring.export.footer.exportedData.item.2', + defaultMessage: 'Course content (all sections, sub-sections, and units)', + }, + exportedDataItem3: { + id: 'course-authoring.export.footer.exportedData.item.3', + defaultMessage: 'Course structure', + }, + exportedDataItem4: { + id: 'course-authoring.export.footer.exportedData.item.4', + defaultMessage: 'Individual problems', + }, + exportedDataItem5: { + id: 'course-authoring.export.footer.exportedData.item.5', + defaultMessage: 'Pages', + }, + exportedDataItem6: { + id: 'course-authoring.export.footer.exportedData.item.6', + defaultMessage: 'Course assets', + }, + exportedDataItem7: { + id: 'course-authoring.export.footer.exportedData.item.7', + defaultMessage: 'Course settings', + }, + notExportedDataTitle: { + id: 'course-authoring.export.footer.notExportedData.title', + defaultMessage: 'Data not exported with your course:', + }, + notExportedDataItem1: { + id: 'course-authoring.export.footer.notExportedData.item.1', + defaultMessage: 'User data', + }, + notExportedDataItem2: { + id: 'course-authoring.export.footer.notExportedData.item.2', + defaultMessage: 'Course team data', + }, + notExportedDataItem3: { + id: 'course-authoring.export.footer.notExportedData.item.3', + defaultMessage: 'Forum/discussion data', + }, + notExportedDataItem4: { + id: 'course-authoring.export.footer.notExportedData.item.4', + defaultMessage: 'Certificates', + }, +}); + +export default messages; diff --git a/src/export-page/export-modal-error/ExportModalError.jsx b/src/export-page/export-modal-error/ExportModalError.jsx new file mode 100644 index 0000000000..bff597db12 --- /dev/null +++ b/src/export-page/export-modal-error/ExportModalError.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useDispatch, useSelector } from 'react-redux'; +import { history } from '@edx/frontend-platform'; +import PropTypes from 'prop-types'; + +import ModalError from '../../generic/modal-error/ModalError'; +import { getError, getIsErrorModalOpen } from '../data/selectors'; +import { updateIsErrorModalOpen } from '../data/slice'; +import messages from './messages'; + +const ExportModalError = ({ + intl, + courseId, +}) => { + const dispatch = useDispatch(); + const isErrorModalOpen = useSelector(getIsErrorModalOpen); + const { msg: errorMessage, unitUrl: unitErrorUrl } = useSelector(getError); + + const handleUnitRedirect = () => { window.location.href = unitErrorUrl; }; + const handleRedirectCourseHome = () => history.push(`/course/${courseId}/outline`); + + return ( + dispatch(updateIsErrorModalOpen(false))} + handleAction={unitErrorUrl ? handleUnitRedirect : handleRedirectCourseHome} + /> + ); +}; + +ExportModalError.propTypes = { + intl: intlShape.isRequired, + courseId: PropTypes.string.isRequired, +}; + +ExportModalError.defaultProps = {}; + +export default injectIntl(ExportModalError); diff --git a/src/export-page/export-modal-error/messages.js b/src/export-page/export-modal-error/messages.js new file mode 100644 index 0000000000..b691db3ebc --- /dev/null +++ b/src/export-page/export-modal-error/messages.js @@ -0,0 +1,34 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + errorTitle: { + id: 'course-authoring.export.modal.error.title', + defaultMessage: 'There has been an error while exporting.', + }, + errorDescriptionNotUnit: { + id: 'course-authoring.export.modal.error.description.not.unit', + defaultMessage: 'Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}', + }, + errorDescriptionUnit: { + id: 'course-authoring.export.modal.error.description.unit', + defaultMessage: 'There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}', + }, + errorCancelButtonUnit: { + id: 'course-authoring.export.modal.error.button.cancel.unit', + defaultMessage: 'Return to export', + }, + errorCancelButtonNotUnit: { + id: 'course-authoring.export.modal.error.button.cancel.not.unit', + defaultMessage: 'Cancel', + }, + errorActionButtonNotUnit: { + id: 'course-authoring.export.modal.error.button.action.not.unit', + defaultMessage: 'Take me to the main course page', + }, + errorActionButtonUnit: { + id: 'course-authoring.export.modal.error.button.action.unit', + defaultMessage: 'Correct failed component', + }, +}); + +export default messages; diff --git a/src/export-page/export-sidebar/ExportSidebar.jsx b/src/export-page/export-sidebar/ExportSidebar.jsx new file mode 100644 index 0000000000..0d5f950901 --- /dev/null +++ b/src/export-page/export-sidebar/ExportSidebar.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { + injectIntl, + intlShape, +} from '@edx/frontend-platform/i18n'; +import PropTypes from 'prop-types'; +import { Button } from '@edx/paragon'; +import { getConfig } from '@edx/frontend-platform'; + +import HelpSidebar from '../../generic/help-sidebar'; +import { useHelpUrls } from '../../help-urls/hooks'; +import messages from './messages'; + +const ExportSidebar = ({ intl, courseId }) => { + const { exportCourse: exportLearnMoreUrl } = useHelpUrls(['exportCourse']); + return ( + +

{intl.formatMessage(messages.title1)}

+

{intl.formatMessage(messages.description1, { studioShortName: getConfig().STUDIO_SHORT_NAME })}

+
+

{intl.formatMessage(messages.exportedContent)}

+

{intl.formatMessage(messages.exportedContentHeading)}

+
    +
  • {intl.formatMessage(messages.content1)}
  • +
  • {intl.formatMessage(messages.content2)}
  • +
  • {intl.formatMessage(messages.content3)}
  • +
  • {intl.formatMessage(messages.content4)}
  • +
  • {intl.formatMessage(messages.content5)}
  • +
+

{intl.formatMessage(messages.notExportedContent)}

+
    +
  • {intl.formatMessage(messages.content6)}
  • +
  • {intl.formatMessage(messages.content7)}
  • +
+
+

{intl.formatMessage(messages.openDownloadFile)}

+

{intl.formatMessage(messages.openDownloadFileDescription)}

+
+ +
+ ); +}; + +ExportSidebar.propTypes = { + intl: intlShape.isRequired, + courseId: PropTypes.string.isRequired, +}; + +export default injectIntl(ExportSidebar); diff --git a/src/export-page/export-sidebar/ExportSidebar.scss b/src/export-page/export-sidebar/ExportSidebar.scss new file mode 100644 index 0000000000..2247058d4f --- /dev/null +++ b/src/export-page/export-sidebar/ExportSidebar.scss @@ -0,0 +1,4 @@ +.export-sidebar-list { + list-style: none; + padding-left: 0; +} diff --git a/src/export-page/export-sidebar/ExportSidebar.test.jsx b/src/export-page/export-sidebar/ExportSidebar.test.jsx new file mode 100644 index 0000000000..d770f4b156 --- /dev/null +++ b/src/export-page/export-sidebar/ExportSidebar.test.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { AppProvider } from '@edx/frontend-platform/react'; + +import initializeStore from '../../store'; +import messages from './messages'; +import ExportSidebar from './ExportSidebar'; + +const courseId = 'course-123'; +let store; + +const RootWrapper = () => ( + + + + + +); + +describe('', () => { + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + store = initializeStore(); + }); + it('render sidebar correctly', () => { + const { getByText } = render(); + expect(getByText(messages.title1.defaultMessage)).toBeInTheDocument(); + expect(getByText(messages.exportedContentHeading.defaultMessage)).toBeInTheDocument(); + }); +}); diff --git a/src/export-page/export-sidebar/messages.js b/src/export-page/export-sidebar/messages.js new file mode 100644 index 0000000000..0917cd866e --- /dev/null +++ b/src/export-page/export-sidebar/messages.js @@ -0,0 +1,66 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + title1: { + id: 'course-authoring.export.sidebar.title1', + defaultMessage: 'Why export a course?', + }, + description1: { + id: 'course-authoring.export.sidebar.description1', + defaultMessage: 'You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.', + }, + exportedContent: { + id: 'course-authoring.export.sidebar.exportedContent', + defaultMessage: 'What content is exported?', + }, + exportedContentHeading: { + id: 'course-authoring.export.sidebar.exportedContentHeading', + defaultMessage: 'The following content is exported.', + }, + content1: { + id: 'course-authoring.export.sidebar.content1', + defaultMessage: 'Course content and structure', + }, + content2: { + id: 'course-authoring.export.sidebar.content2', + defaultMessage: 'Course dates', + }, + content3: { + id: 'course-authoring.export.sidebar.content3', + defaultMessage: 'Grading policy', + }, + content4: { + id: 'course-authoring.export.sidebar.content4', + defaultMessage: 'Any group configurations', + }, + content5: { + id: 'course-authoring.export.sidebar.content5', + defaultMessage: 'Settings on the Advanced settings page, including MATLAB API keys and LTI passports', + }, + notExportedContent: { + id: 'course-authoring.export.sidebar.notExportedContent', + defaultMessage: 'The following content is not exported.', + }, + content6: { + id: 'course-authoring.export.sidebar.content6', + defaultMessage: 'Learner-specific content, such as learner grades and discussion forum data', + }, + content7: { + id: 'course-authoring.export.sidebar.content7', + defaultMessage: 'The course team', + }, + openDownloadFile: { + id: 'course-authoring.export.sidebar.openDownloadFile', + defaultMessage: 'Opening the downloaded file', + }, + openDownloadFileDescription: { + id: 'course-authoring.export.sidebar.openDownloadFileDescription', + defaultMessage: 'Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.', + }, + learnMoreButtonTitle: { + id: 'course-authoring.export.sidebar.learnMoreButtonTitle', + defaultMessage: 'Learn more about exporting a course', + }, +}); + +export default messages; diff --git a/src/export-page/export-stepper/ExportStepper.jsx b/src/export-page/export-stepper/ExportStepper.jsx new file mode 100644 index 0000000000..7a693d8b7e --- /dev/null +++ b/src/export-page/export-stepper/ExportStepper.jsx @@ -0,0 +1,89 @@ +import React, { useEffect } from 'react'; +import { + injectIntl, + intlShape, +} from '@edx/frontend-platform/i18n'; +import PropTypes from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; +import { getConfig } from '@edx/frontend-platform'; +import { Button } from '@edx/paragon'; + +import CourseStepper from '../../generic/course-stepper'; +import { + getCurrentStage, getDownloadPath, getError, getLoadingStatus, getSuccessDate, +} from '../data/selectors'; +import { fetchExportStatus } from '../data/thunks'; +import { EXPORT_STAGES } from '../data/constants'; +import { getFormattedSuccessDate } from '../utils'; +import { RequestStatus } from '../../data/constants'; +import messages from './messages'; + +const ExportStepper = ({ intl, courseId }) => { + const currentStage = useSelector(getCurrentStage); + const downloadPath = useSelector(getDownloadPath); + const successDate = useSelector(getSuccessDate); + const loadingStatus = useSelector(getLoadingStatus); + const { msg: errorMessage } = useSelector(getError); + const dispatch = useDispatch(); + const isStopFetching = currentStage === EXPORT_STAGES.SUCCESS + || loadingStatus === RequestStatus.FAILED + || errorMessage; + + useEffect(() => { + const id = setInterval(() => { + if (isStopFetching) { + clearInterval(id); + } else { + dispatch(fetchExportStatus(courseId)); + } + }, 3000); + return () => clearInterval(id); + }); + + let successTitle = intl.formatMessage(messages.stepperSuccessTitle); + const formattedSuccessDate = getFormattedSuccessDate(successDate); + if (formattedSuccessDate && currentStage === EXPORT_STAGES.SUCCESS) { + successTitle += formattedSuccessDate; + } + const steps = [ + { + title: intl.formatMessage(messages.stepperPreparingTitle), + description: intl.formatMessage(messages.stepperPreparingDescription), + key: EXPORT_STAGES.PREPARING, + }, { + title: intl.formatMessage(messages.stepperExportingTitle), + description: intl.formatMessage(messages.stepperExportingDescription), + key: EXPORT_STAGES.EXPORTING, + }, { + title: intl.formatMessage(messages.stepperCompressingTitle), + description: intl.formatMessage(messages.stepperCompressingDescription), + key: EXPORT_STAGES.COMPRESSING, + }, { + title: successTitle, + description: intl.formatMessage(messages.stepperSuccessDescription), + key: EXPORT_STAGES.SUCCESS, + }, + ]; + + return ( +
+

{intl.formatMessage(messages.stepperHeaderTitle)}

+
+ + {downloadPath && currentStage === EXPORT_STAGES.SUCCESS && } +
+ ); +}; + +ExportStepper.propTypes = { + intl: intlShape.isRequired, + courseId: PropTypes.string.isRequired, +}; + +export default injectIntl(ExportStepper); diff --git a/src/export-page/export-stepper/ExportStepper.scss b/src/export-page/export-stepper/ExportStepper.scss new file mode 100644 index 0000000000..ef2ab0f821 --- /dev/null +++ b/src/export-page/export-stepper/ExportStepper.scss @@ -0,0 +1,3 @@ +.pgn__stepper-header-step-list { + flex-direction: column; +} diff --git a/src/export-page/export-stepper/ExportStepper.test.jsx b/src/export-page/export-stepper/ExportStepper.test.jsx new file mode 100644 index 0000000000..c8ca1e4855 --- /dev/null +++ b/src/export-page/export-stepper/ExportStepper.test.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { AppProvider } from '@edx/frontend-platform/react'; + +import initializeStore from '../../store'; +import messages from './messages'; +import ExportStepper from './ExportStepper'; + +const courseId = 'course-123'; +let store; + +const RootWrapper = () => ( + + + + + +); + +describe('', () => { + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], + }, + }); + store = initializeStore(); + }); + it('render stepper correctly', () => { + const { getByText } = render(); + expect(getByText(messages.stepperHeaderTitle.defaultMessage)).toBeInTheDocument(); + }); +}); diff --git a/src/export-page/export-stepper/messages.js b/src/export-page/export-stepper/messages.js new file mode 100644 index 0000000000..e0f4adfa1b --- /dev/null +++ b/src/export-page/export-stepper/messages.js @@ -0,0 +1,46 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + stepperPreparingTitle: { + id: 'course-authoring.export.stepper.title.preparing', + defaultMessage: 'Preparing', + }, + stepperExportingTitle: { + id: 'course-authoring.export.stepper.title.exporting', + defaultMessage: 'Exporting', + }, + stepperCompressingTitle: { + id: 'course-authoring.export.stepper.title.compressing', + defaultMessage: 'Compressing', + }, + stepperSuccessTitle: { + id: 'course-authoring.export.stepper.title.success', + defaultMessage: 'Success', + }, + stepperPreparingDescription: { + id: 'course-authoring.export.stepper.description.preparing', + defaultMessage: 'Preparing to start the export', + }, + stepperExportingDescription: { + id: 'course-authoring.export.stepper.description.exporting', + defaultMessage: 'Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)', + }, + stepperCompressingDescription: { + id: 'course-authoring.export.stepper.description.compressing', + defaultMessage: 'Compressing the exported data and preparing it for download', + }, + stepperSuccessDescription: { + id: 'course-authoring.export.stepper.description.success', + defaultMessage: 'Your exported course can now be downloaded', + }, + downloadCourseButtonTitle: { + id: 'course-authoring.export.stepper.download.button.title', + defaultMessage: 'Download exported course', + }, + stepperHeaderTitle: { + id: 'course-authoring.export.stepper.header.title', + defaultMessage: 'Course export status', + }, +}); + +export default messages; diff --git a/src/export-page/messages.js b/src/export-page/messages.js new file mode 100644 index 0000000000..ea07ab9fd5 --- /dev/null +++ b/src/export-page/messages.js @@ -0,0 +1,62 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + pageTitle: { + id: 'course-authoring.export.page.title', + defaultMessage: '{headingTitle} | {courseName} | {siteName}', + }, + headingTitle: { + id: 'course-authoring.export.heading.title', + defaultMessage: 'Course export', + }, + headingSubtitle: { + id: 'course-authoring.export.heading.subtitle', + defaultMessage: 'Tools', + }, + description1: { + id: 'course-authoring.export.description1', + defaultMessage: 'You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you\'ve exported.', + }, + description2: { + id: 'course-authoring.export.description2', + defaultMessage: 'Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.', + }, + titleUnderButton: { + id: 'course-authoring.export.title-under-button', + defaultMessage: 'Export my course content', + }, + buttonTitle: { + id: 'course-authoring.export.button.title', + defaultMessage: 'Export course content', + }, + errorTitle: { + id: 'course-authoring.export.modal.error.title', + defaultMessage: 'There has been an error while exporting.', + }, + errorDescriptionNotUnit: { + id: 'course-authoring.export.modal.error.description.not.unit', + defaultMessage: 'Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}', + }, + errorDescriptionUnit: { + id: 'course-authoring.export.modal.error.description.unit', + defaultMessage: 'There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}', + }, + errorCancelButtonUnit: { + id: 'course-authoring.export.modal.error.button.cancel.unit', + defaultMessage: 'Return to Export', + }, + errorCancelButtonNotUnit: { + id: 'course-authoring.export.modal.error.button.cancel.not.unit', + defaultMessage: 'Cancel', + }, + errorActionButtonNotUnit: { + id: 'course-authoring.export.modal.error.button.action.not.unit', + defaultMessage: 'Take me to the main course page', + }, + errorActionButtonUnit: { + id: 'course-authoring.export.modal.error.button.action.unit', + defaultMessage: 'Correct failed component', + }, +}); + +export default messages; diff --git a/src/export-page/utils.js b/src/export-page/utils.js new file mode 100644 index 0000000000..9f24b743b7 --- /dev/null +++ b/src/export-page/utils.js @@ -0,0 +1,29 @@ +import Cookies from 'universal-cookie'; +import moment from 'moment'; + +import { TIME_FORMAT } from '../constants'; +import { LAST_EXPORT_COOKIE_NAME, SUCCESS_DATE_FORMAT } from './data/constants'; + +/** + * Sets an export-related cookie with the provided information. + * + * @param {Date} date - Date of export. + * @param {boolean} completed - Indicates if export was completed successfully. + * @returns {void} + */ +export const setExportCookie = (date, completed) => { + const cookies = new Cookies(); + cookies.set(LAST_EXPORT_COOKIE_NAME, { date, completed }, { path: window.location.pathname }); +}; + +/** + * Formats a Unix timestamp as a formatted success date string. + * + * @param {number} unixDate - Unix timestamp to be formatted. + * @returns {string|null} Formatted success date string, including date and time in UTC, or null if the input is falsy. + */ +export const getFormattedSuccessDate = (unixDate) => { + const formattedDate = moment(unixDate).utc().format(SUCCESS_DATE_FORMAT); + const formattedTime = moment(unixDate).utc().format(TIME_FORMAT); + return unixDate ? ` (${formattedDate} at ${formattedTime} UTC)` : null; +}; diff --git a/src/export-page/utils.test.js b/src/export-page/utils.test.js new file mode 100644 index 0000000000..276f76ce47 --- /dev/null +++ b/src/export-page/utils.test.js @@ -0,0 +1,51 @@ +import Cookies from 'universal-cookie'; +import moment from 'moment'; + +import { LAST_EXPORT_COOKIE_NAME } from './data/constants'; +import { setExportCookie, getFormattedSuccessDate } from './utils'; + +global.window = Object.create(window); +Object.defineProperty(window, 'location', { + value: { + pathname: '/some-path', + }, +}); + +describe('setExportCookie', () => { + it('should set the export cookie with the provided date and completed status', () => { + const cookiesSetMock = jest.spyOn(Cookies.prototype, 'set'); + const date = '2023-07-24'; + const completed = true; + setExportCookie(date, completed); + + expect(cookiesSetMock).toHaveBeenCalledWith( + LAST_EXPORT_COOKIE_NAME, + { date, completed }, + { path: '/some-path' }, + ); + + cookiesSetMock.mockRestore(); + }); +}); + +describe('getFormattedSuccessDate', () => { + it('should return formatted success date with valid input', () => { + const mockCurrentUtcDate = moment.utc('2023-07-24T12:34:56'); + const momentMock = jest.spyOn(moment, 'utc').mockReturnValue(mockCurrentUtcDate); + + const unixDate = 1679787000; + + const expectedFormattedDate = ' (01/20/1970 at 10:36 UTC)'; + + const result = getFormattedSuccessDate(unixDate); + expect(result).toBe(expectedFormattedDate); + + momentMock.mockRestore(); + }); + + it('should return null with null input', () => { + const unixDate = null; + const result = getFormattedSuccessDate(unixDate); + expect(result).toBeNull(); + }); +}); diff --git a/src/generic/course-stepper/CourseStepper.test.jsx b/src/generic/course-stepper/CourseStepper.test.jsx new file mode 100644 index 0000000000..0eba1f9211 --- /dev/null +++ b/src/generic/course-stepper/CourseStepper.test.jsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; + +import CourseStepper from '.'; + +const stepsMock = [ + { + title: 'Preparing', + description: 'Preparing to start the export', + }, + { + title: 'Exporting', + description: 'Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete', + }, + { + title: 'Compressing', + description: 'Compressing the exported data and preparing it for download', + }, + { + title: 'Success', + description: 'Your exported course can now be downloaded', + }, +]; + +const renderComponent = (props) => render( + + + , +); + +describe('', () => { + it('renders CourseStepper correctly', () => { + const { + getByText, getByTestId, getAllByTestId, queryByTestId, + } = renderComponent({ activeKey: 0 }); + + const steps = getAllByTestId('course-stepper__step'); + expect(steps.length).toBe(stepsMock.length); + + stepsMock.forEach((step) => { + expect(getByText(step.title)).toBeInTheDocument(); + expect(getByText(step.description)).toBeInTheDocument(); + expect(getByTestId(`${step.title}-icon`)).toBeInTheDocument(); + }); + + const percentElement = queryByTestId('course-stepper__step-percent'); + expect(percentElement).toBeNull(); + }); + + it('marks the active and done steps correctly', () => { + const activeKey = 1; + const { getAllByTestId } = renderComponent({ activeKey }); + + const steps = getAllByTestId('course-stepper__step'); + stepsMock.forEach((_, index) => { + const stepElement = steps[index]; + if (index === activeKey) { + expect(stepElement).toHaveClass('active'); + expect(stepElement).not.toHaveClass('done'); + } + if (index < activeKey) { + expect(stepElement).not.toHaveClass('active'); + expect(stepElement).toHaveClass('done'); + } + if (index > activeKey) { + expect(stepElement).not.toHaveClass('active'); + expect(stepElement).not.toHaveClass('done'); + } + }); + }); + + it('mark the error step correctly', () => { + const { getAllByTestId } = renderComponent({ activeKey: 1, hasError: true }); + + const errorStep = getAllByTestId('course-stepper__step')[1]; + expect(errorStep).toHaveClass('error'); + }); + + it('shows error message for error step', () => { + const errorMessage = 'Some error text'; + const { getAllByTestId } = renderComponent({ activeKey: 1, hasError: true, errorMessage }); + + const errorStep = getAllByTestId('course-stepper__step')[1]; + expect(errorStep).toHaveClass('error'); + }); + + it('shows percentage for active step', () => { + const percent = 50; + const { getByTestId } = renderComponent({ activeKey: 1, percent }); + + const percentElement = getByTestId('course-stepper__step-percent'); + expect(percentElement).toBeInTheDocument(); + expect(percentElement).toHaveTextContent(`${percent}%`); + }); +}); diff --git a/src/generic/course-stepper/CouseStepper.scss b/src/generic/course-stepper/CouseStepper.scss new file mode 100644 index 0000000000..868f1c6f89 --- /dev/null +++ b/src/generic/course-stepper/CouseStepper.scss @@ -0,0 +1,68 @@ +.course-stepper { + .course-stepper__step { + display: flex; + gap: $spacer; + padding: 1.25rem 0; + opacity: .5; + + &:not(:last-child) { + border-bottom: 1px solid $gray-200; + } + + .course-stepper__step-icon { + position: relative; + + & svg { + position: absolute; + top: .875rem; + left: 1.25rem; + } + } + + .course-stepper__step-info { + margin-left: 1.875rem; + } + + .course-stepper__step-title { + margin-bottom: .25rem; + } + + .course-stepper__step-percent { + margin: 0; + font-size: 1rem; + } + + .course-stepper__step-description { + margin: 0; + font-size: 1rem; + color: $gray-400; + } + } + + .course-stepper__step.active { + opacity: 1; + + & svg { + animation: rotate 2s infinite linear; + } + } + + .course-stepper__step.done { + opacity: 1; + + & svg, + .course-stepper__step-title { + color: $success-500; + } + } + + .course-stepper__step.error { + opacity: 1; + + .course-stepper__step-title, + .course-stepper__step-description, + & svg { + color: $danger-300; + } + } +} diff --git a/src/generic/course-stepper/index.jsx b/src/generic/course-stepper/index.jsx new file mode 100644 index 0000000000..01a4c4e220 --- /dev/null +++ b/src/generic/course-stepper/index.jsx @@ -0,0 +1,114 @@ +import React from 'react'; +import { injectIntl } from '@edx/frontend-platform/i18n'; +import { + Settings as SettingsIcon, + CheckBoxOutlineBlank as SuccessIcon, + LibraryAddCheck as SuccessDoneIcon, + Warning as ErrorIcon, +} from '@edx/paragon/icons'; +import { Icon } from '@edx/paragon'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +const CourseStepper = ({ + steps, + activeKey, + percent, + hasError, + errorMessage, +}) => { + const getStepperSettings = (index) => { + const lastStepIndex = steps.length - 1; + const isActiveStep = index === activeKey; + const isLastStep = index === lastStepIndex; + const isErrorStep = isActiveStep && hasError; + const isLastStepDone = isLastStep && isActiveStep; + + const getStepIcon = () => { + if (hasError && isActiveStep) { + return ErrorIcon; + } + if (isLastStep && !isActiveStep) { + return SuccessIcon; + } + if (isLastStepDone) { + return SuccessDoneIcon; + } + + return SettingsIcon; + }; + + return { + stepIcon: getStepIcon(index), + isPercentShow: Boolean(percent) && percent !== 100 && isActiveStep && !hasError, + isErrorMessageShow: isErrorStep && errorMessage, + isActiveClass: isActiveStep && !isLastStep && !hasError, + isDoneClass: index < activeKey || isLastStepDone, + isErrorClass: isErrorStep, + }; + }; + + return ( +
+ {steps.length ? steps.map(({ title, description }, index) => { + const { + stepIcon, + isPercentShow, + isErrorMessageShow, + isActiveClass, + isDoneClass, + isErrorClass, + } = getStepperSettings(index); + + return ( +
+
+ +
+
+

{title}

+ {isPercentShow && ( +

+ {percent}% +

+ )} +

+ {isErrorMessageShow ? errorMessage : description} +

+
+
+ ); + }) : null} +
+ ); +}; + +CourseStepper.defaultProps = { + percent: false, + hasError: false, + errorMessage: '', +}; + +CourseStepper.propTypes = { + steps: PropTypes.arrayOf(PropTypes.shape({ + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + })).isRequired, + activeKey: PropTypes.number.isRequired, + percent: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]), + errorMessage: PropTypes.string, + hasError: PropTypes.bool, +}; + +export default injectIntl(CourseStepper); diff --git a/src/generic/modal-error/ModalError.jsx b/src/generic/modal-error/ModalError.jsx new file mode 100644 index 0000000000..b28d3a634b --- /dev/null +++ b/src/generic/modal-error/ModalError.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { ActionRow, AlertModal, Button } from '@edx/paragon'; + +const ModalError = ({ + isOpen, title, message, handleCancel, handleAction, cancelButtonText, actionButtonText, +}) => ( + + + + + )} + > +

{message}

+
+); + +ModalError.propTypes = { + isOpen: PropTypes.bool.isRequired, + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + handleCancel: PropTypes.func.isRequired, + handleAction: PropTypes.func.isRequired, + cancelButtonText: PropTypes.string.isRequired, + actionButtonText: PropTypes.string.isRequired, +}; + +export default ModalError; diff --git a/src/generic/styles.scss b/src/generic/styles.scss index 4eceeddce2..a8cacf01d4 100644 --- a/src/generic/styles.scss +++ b/src/generic/styles.scss @@ -4,3 +4,4 @@ @import "./section-sub-header/SectionSubHeader"; @import "./processing-notification/ProccessingNotification"; @import "./WysiwygEditor"; +@import "./course-stepper/CouseStepper"; diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 2b817efded..a02146127c 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 77f0fb751a..2e8eebb614 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/de_DE.json b/src/i18n/messages/de_DE.json index bf1703b9bc..7262ad2cef 100644 --- a/src/i18n/messages/de_DE.json +++ b/src/i18n/messages/de_DE.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/es_419.json b/src/i18n/messages/es_419.json index 89b2cd2cfc..bda89368a5 100644 --- a/src/i18n/messages/es_419.json +++ b/src/i18n/messages/es_419.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 64a333ca3b..94fe2a22e5 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/fr_CA.json b/src/i18n/messages/fr_CA.json index 0106fb8fb1..eb651bdf8e 100644 --- a/src/i18n/messages/fr_CA.json +++ b/src/i18n/messages/fr_CA.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 77f0fb751a..2e8eebb614 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 77f0fb751a..2e8eebb614 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/it_IT.json b/src/i18n/messages/it_IT.json index fd53f7ac00..7de669feac 100644 --- a/src/i18n/messages/it_IT.json +++ b/src/i18n/messages/it_IT.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 77f0fb751a..2e8eebb614 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/pt_PT.json b/src/i18n/messages/pt_PT.json index ea886df5cf..f3326c1ffa 100644 --- a/src/i18n/messages/pt_PT.json +++ b/src/i18n/messages/pt_PT.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 1d3fa2d556..c3fc79d199 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/uk.json b/src/i18n/messages/uk.json index e8ab912b0e..795303baba 100644 --- a/src/i18n/messages/uk.json +++ b/src/i18n/messages/uk.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/i18n/messages/zh_CN.json b/src/i18n/messages/zh_CN.json index 77f0fb751a..2e8eebb614 100644 --- a/src/i18n/messages/zh_CN.json +++ b/src/i18n/messages/zh_CN.json @@ -734,5 +734,56 @@ "course-authoring.grading-settings.assignment.alert.warning.usage.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.warning.title": "Warning: The number of {type} assignments defined here does not match the current number of {type} assignments in the course:", "course-authoring.grading-settings.assignment.alert.success.title": "The number of {type} assignments in the course matches the number defined here.", - "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}" + "course-authoring.grading-settings.assignment.type-name.error.message-2": "For grading to work, you must change all {initialAssignmentName} subsections to {value}", + "course-authoring.export.footer.exportedData.title": "Data exported with your course:", + "course-authoring.export.footer.exportedData.item.1": "Values from Advanced settings, including MATLAB API keys and LTI passports", + "course-authoring.export.footer.exportedData.item.2": "Course content (all sections, sub-sections, and units)", + "course-authoring.export.footer.exportedData.item.3": "Course structure", + "course-authoring.export.footer.exportedData.item.4": "Individual problems", + "course-authoring.export.footer.exportedData.item.5": "Pages", + "course-authoring.export.footer.exportedData.item.6": "Course assets", + "course-authoring.export.footer.exportedData.item.7": "Course settings", + "course-authoring.export.footer.notExportedData.title": "Data not exported with your course:", + "course-authoring.export.footer.notExportedData.item.1": "User data", + "course-authoring.export.footer.notExportedData.item.2": "Course team data", + "course-authoring.export.footer.notExportedData.item.3": "Forum/discussion data", + "course-authoring.export.footer.notExportedData.item.4": "Certificates", + "course-authoring.export.modal.error.title": "There has been an error while exporting.", + "course-authoring.export.modal.error.description.not.unit": "Your course could not be exported to XML. There is not enough information to identify the failed component. Inspect your course to identify any problematic components and try again. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.description.unit": "There has been a failure to export to XML at least one component. It is recommended that you go to the edit page and repair the error before attempting another export. Please check that all components on the page are valid and do not display any error messages. The raw error message is: {errorMessage}", + "course-authoring.export.modal.error.button.cancel.unit": "Return to Export", + "course-authoring.export.modal.error.button.cancel.not.unit": "Cancel", + "course-authoring.export.modal.error.button.action.not.unit": "Take me to the main course page", + "course-authoring.export.modal.error.button.action.unit": "Correct failed component", + "course-authoring.export.sidebar.title1": "Why export a course?", + "course-authoring.export.sidebar.description1": "You may want to edit the XML in your course directly, outside of {studioShortName}. You may want to create a backup copy of your course. Or, you may want to create a copy of your course that you can later import into another course instance and customize.", + "course-authoring.export.sidebar.exportedContent": "What content is exported?", + "course-authoring.export.sidebar.exportedContentHeading": "The following content is exported.", + "course-authoring.export.sidebar.content1": "Course content and structure", + "course-authoring.export.sidebar.content2": "Course dates", + "course-authoring.export.sidebar.content3": "Grading policy", + "course-authoring.export.sidebar.content4": "Any group configurations", + "course-authoring.export.sidebar.content5": "Settings on the Advanced settings page, including MATLAB API keys and LTI passports", + "course-authoring.export.sidebar.notExportedContent": "The following content is not exported.", + "course-authoring.export.sidebar.content6": "Learner-specific content, such as learner grades and discussion forum data", + "course-authoring.export.sidebar.content7": "The course team", + "course-authoring.export.sidebar.openDownloadFile": "Opening the downloaded file", + "course-authoring.export.sidebar.openDownloadFileDescription": "Use an archive program to extract the data from the .tar.gz file. Extracted data includes the course.xml file, as well as subfolders that contain course content.", + "course-authoring.export.sidebar.learnMoreButtonTitle": "Learn more about exporting a course", + "course-authoring.export.stepper.title.preparing": "Preparing", + "course-authoring.export.stepper.title.exporting": "Exporting", + "course-authoring.export.stepper.title.compressing": "Compressing", + "course-authoring.export.stepper.title.success": "Success", + "course-authoring.export.stepper.description.preparing": "Preparing to start the export", + "course-authoring.export.stepper.description.exporting": "Creating the export data files (You can now leave this page safely, but avoid making drastic changes to content until this export is complete)", + "course-authoring.export.stepper.description.compressing": "Compressing the exported data and preparing it for download", + "course-authoring.export.stepper.description.success": "Your exported course can now be downloaded", + "course-authoring.export.stepper.download.button.title": "Download exported course", + "course-authoring.export.stepper.header.title": "Course import status", + "course-authoring.export.heading.title": "Course export", + "course-authoring.export.heading.subtitle": "Tools", + "course-authoring.export.description1": "You can export courses and edit them outside of {studioShortName}. The exported file is a .tar.gz file (that is, a .tar file compressed with GNU Zip) that contains the course structure and content. You can also re-import courses that you've exported.", + "course-authoring.export.description2": "Caution: When you export a course, information such as MATLAB API keys, LTI passports, annotation secret token strings, and annotation storage URLs are included in the exported data. If you share your exported files, you may also be sharing sensitive or license-specific information.", + "course-authoring.export.title-under-button": "Export my course content", + "course-authoring.export.button.title": "Export course content" } diff --git a/src/index.scss b/src/index.scss index 6af8bf24d9..69f7b94c19 100755 --- a/src/index.scss +++ b/src/index.scss @@ -17,3 +17,4 @@ @import "pages-and-resources/PagesAndResources"; @import "course-team/CourseTeam"; @import "course-updates/CourseUpdates"; +@import "export-page/CourseExportPage"; diff --git a/src/store.js b/src/store.js index 5b538134a5..38a373ade4 100644 --- a/src/store.js +++ b/src/store.js @@ -14,6 +14,7 @@ import { reducer as courseTeamReducer } from './course-team/data/slice'; import { reducer as CourseUpdatesReducer } from './course-updates/data/slice'; import { reducer as processingNotificationReducer } from './generic/processing-notification/data/slice'; import { reducer as helpUrlsReducer } from './help-urls/data/slice'; +import { reducer as courseExportReducer } from './export-page/data/slice'; export default function initializeStore(preloadedState = undefined) { return configureStore({ @@ -32,6 +33,7 @@ export default function initializeStore(preloadedState = undefined) { courseUpdates: CourseUpdatesReducer, processingNotification: processingNotificationReducer, helpUrls: helpUrlsReducer, + courseExport: courseExportReducer, }, preloadedState, });