Compare commits
5 Commits
master
...
open-relea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72bce9e652 | ||
|
|
66482ab23a | ||
|
|
03e0f41692 | ||
|
|
8e1904a235 | ||
|
|
88485a0f77 |
8776
package-lock.json
generated
8776
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -70,6 +70,7 @@
|
||||
"@testing-library/user-event": "13.5.0",
|
||||
"axios-mock-adapter": "1.20.0",
|
||||
"codecov": "3.8.3",
|
||||
"es-check": "6.2.1",
|
||||
"husky": "7.0.4",
|
||||
"jest": "27.5.1",
|
||||
"rosie": "2.1.0"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -46,10 +46,8 @@ function Course({
|
||||
|
||||
// Below the tabs, above the breadcrumbs alerts (appearing in the order listed here)
|
||||
const dispatch = useDispatch();
|
||||
const celebrateFirstSection = celebrations && celebrations.firstSection;
|
||||
const [firstSectionCelebrationOpen, setFirstSectionCelebrationOpen] = useState(shouldCelebrateOnSectionLoad(
|
||||
courseId, sequenceId, celebrateFirstSection, dispatch, celebrations,
|
||||
));
|
||||
|
||||
const [firstSectionCelebrationOpen, setFirstSectionCelebrationOpen] = useState(false);
|
||||
// If streakLengthToCelebrate is populated, that modal takes precedence. Wait til the next load to display
|
||||
// the weekly goal celebration modal.
|
||||
const [weeklyGoalCelebrationOpen, setWeeklyGoalCelebrationOpen] = useState(
|
||||
@@ -74,6 +72,17 @@ function Course({
|
||||
/** [MM-P2P] Experiment */
|
||||
const MMP2P = initCoursewareMMP2P(courseId, sequenceId, unitId);
|
||||
|
||||
useEffect(() => {
|
||||
const celebrateFirstSection = celebrations && celebrations.firstSection;
|
||||
setFirstSectionCelebrationOpen(shouldCelebrateOnSectionLoad(
|
||||
courseId,
|
||||
sequenceId,
|
||||
celebrateFirstSection,
|
||||
dispatch,
|
||||
celebrations,
|
||||
));
|
||||
}, [sequenceId]);
|
||||
|
||||
return (
|
||||
<SidebarProvider courseId={courseId} unitId={unitId}>
|
||||
<Helmet>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
useWindowSize,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import ClapsMobile from './assets/claps_280x201.gif';
|
||||
import ClapsTablet from './assets/claps_456x328.gif';
|
||||
import messages from './messages';
|
||||
@@ -19,12 +20,13 @@ import { useModel } from '../../../generic/model-store';
|
||||
function CelebrationModal({
|
||||
courseId, intl, isOpen, onClose, ...rest
|
||||
}) {
|
||||
const { org } = useModel('courseHomeMeta', courseId);
|
||||
const { org, celebrations } = useModel('courseHomeMeta', courseId);
|
||||
const dispatch = useDispatch();
|
||||
const wideScreen = useWindowSize().width >= breakpoints.small.minWidth;
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
recordFirstSectionCelebration(org, courseId);
|
||||
recordFirstSectionCelebration(org, courseId, celebrations, dispatch);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
|
||||
@@ -15,9 +15,20 @@ function handleNextSectionCelebration(sequenceId, nextSequenceId) {
|
||||
});
|
||||
}
|
||||
|
||||
function recordFirstSectionCelebration(org, courseId) {
|
||||
function recordFirstSectionCelebration(org, courseId, celebrations, dispatch) {
|
||||
// Tell the LMS
|
||||
postCelebrationComplete(courseId, { first_section: false });
|
||||
// Update our local copy of course data from LMS
|
||||
dispatch(updateModel({
|
||||
modelType: 'courseHomeMeta',
|
||||
model: {
|
||||
id: courseId,
|
||||
celebrations: {
|
||||
...celebrations,
|
||||
firstSection: false,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Tell our analytics
|
||||
const { administrator } = getAuthenticatedUser();
|
||||
|
||||
15
src/courseware/course/celebration/utils.test.jsx
Normal file
15
src/courseware/course/celebration/utils.test.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { recordFirstSectionCelebration } from './utils';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics');
|
||||
jest.mock('./data/api');
|
||||
jest.mock('@edx/frontend-platform/auth', () => ({
|
||||
getAuthenticatedUser: jest.fn(() => ({ administrator: 'admin' })),
|
||||
}));
|
||||
|
||||
describe('recordFirstSectionCelebration', () => {
|
||||
it('updates the local copy of the course data from the LMS', async () => {
|
||||
const dispatchMock = jest.fn();
|
||||
recordFirstSectionCelebration('org', 'courseId', 'celebration', dispatchMock);
|
||||
expect(dispatchMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -96,6 +96,7 @@ function Unit({
|
||||
const [showError, setShowError] = useState(false);
|
||||
const [modalOptions, setModalOptions] = useState({ open: false });
|
||||
const [shouldDisplayHonorCode, setShouldDisplayHonorCode] = useState(false);
|
||||
const [windowTopOffset, setWindowTopOffset] = useState(null);
|
||||
|
||||
const unit = useModel('units', id);
|
||||
const course = useModel('coursewareMeta', courseId);
|
||||
@@ -123,6 +124,13 @@ function Unit({
|
||||
} = data;
|
||||
if (type === 'plugin.resize') {
|
||||
setIframeHeight(payload.height);
|
||||
|
||||
// We observe exit from the video xblock full screen mode
|
||||
// and do page scroll to the previously saved scroll position
|
||||
if (windowTopOffset !== null) {
|
||||
window.scrollTo(0, Number(windowTopOffset));
|
||||
}
|
||||
|
||||
if (!hasLoaded && iframeHeight === 0 && payload.height > 0) {
|
||||
setHasLoaded(true);
|
||||
if (onLoaded) {
|
||||
@@ -132,12 +140,16 @@ function Unit({
|
||||
} else if (type === 'plugin.modal') {
|
||||
payload.open = true;
|
||||
setModalOptions(payload);
|
||||
} else if (type === 'plugin.videoFullScreen') {
|
||||
// We listen for this message from LMS to know when we need to
|
||||
// save or reset scroll position on toggle video xblock full screen mode.
|
||||
setWindowTopOffset(payload.open ? window.scrollY : null);
|
||||
} else if (data.offset) {
|
||||
// We listen for this message from LMS to know when the page needs to
|
||||
// be scrolled to another location on the page.
|
||||
window.scrollTo(0, data.offset + document.getElementById('unit-iframe').offsetTop);
|
||||
}
|
||||
}, [id, setIframeHeight, hasLoaded, iframeHeight, setHasLoaded, onLoaded]);
|
||||
}, [id, setIframeHeight, hasLoaded, iframeHeight, setHasLoaded, onLoaded, setWindowTopOffset, windowTopOffset]);
|
||||
useEventListener('message', receiveMessage);
|
||||
useEffect(() => {
|
||||
sendUrlHashToFrame(document.getElementById('unit-iframe'));
|
||||
|
||||
@@ -131,6 +131,21 @@ describe('Unit', () => {
|
||||
expect(window.scrollY === testMessageWithOffset.offset);
|
||||
});
|
||||
|
||||
it('scrolls page on MessagaeEvent when receiving videoFullScreen state', async () => {
|
||||
// Set message to constain video full screen data.
|
||||
const defaultTopOffset = 800;
|
||||
const testMessageWithOtherHeight = { ...messageEvent, payload: { height: 500 } };
|
||||
const testMessageWithFullscreenState = (isOpen) => ({ type: 'plugin.videoFullScreen', payload: { open: isOpen } });
|
||||
render(<Unit {...mockData} />);
|
||||
Object.defineProperty(window, 'scrollY', { value: defaultTopOffset, writable: true });
|
||||
window.postMessage(testMessageWithFullscreenState(true), '*');
|
||||
window.postMessage(testMessageWithFullscreenState(false), '*');
|
||||
window.postMessage(testMessageWithOtherHeight, '*');
|
||||
|
||||
await expect(waitFor(() => expect(window.scrollTo()).toHaveBeenCalledTimes(1)));
|
||||
expect(window.scrollY === defaultTopOffset);
|
||||
});
|
||||
|
||||
it('ignores MessageEvent with unhandled type', async () => {
|
||||
// Clone message and set different type.
|
||||
const testMessageWithUnhandledType = { ...messageEvent, type: 'wrong type' };
|
||||
|
||||
@@ -98,9 +98,9 @@ export function SupportMissionBullet() {
|
||||
<CheckmarkBullet />
|
||||
<FormattedMessage
|
||||
id="learning.generic.upsell.supportMissionBullet"
|
||||
defaultMessage="Support our {missionInBoldText} at edX"
|
||||
defaultMessage="Support our {missionInBoldText} at {siteName}"
|
||||
description="Bullet encouraging user to support edX's goals."
|
||||
values={{ missionInBoldText }}
|
||||
values={{ missionInBoldText, siteName: getConfig().SITE_NAME }}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"learning.generic.upsell.fullAccessBullet.fullAccess": "Full access",
|
||||
"learning.generic.upsell.fullAccessBullet": "{fullAccessInBoldText} to course content and materials, even after the course ends",
|
||||
"learning.generic.upsell.supportMissionBullet.mission": "mission",
|
||||
"learning.generic.upsell.supportMissionBullet": "ادعم {missionInBoldText} في edX",
|
||||
"learning.generic.upsell.supportMissionBullet": "ادعم {missionInBoldText} في {siteName}",
|
||||
"masquerade-widget.userName.error.generic": "حدث خطأ؛ يرجى المحاولة مرة أخرى.",
|
||||
"masquerade-widget.userName.input.placeholder": "اسم المستخدم أو البريد الإلكتروني",
|
||||
"masquerade-widget.userName.input.label": "عرف كهذا المستخدم",
|
||||
@@ -409,7 +409,7 @@
|
||||
"tours.button.okay": "تمام",
|
||||
"tours.button.beginTour": "ابدأ الجولة",
|
||||
"tours.button.launchTour": "انطلاق الجولة",
|
||||
"tours.newUserModal.body": "Let’s take a quick tour of edX so you can get the most out of your course.",
|
||||
"tours.newUserModal.body": "Let’s take a quick tour of {siteName} so you can get the most out of your course.",
|
||||
"tours.newUserModal.title.welcome": "مرحبًا بك في",
|
||||
"tours.button.skipForNow": "تخطي في الوقت الراهن",
|
||||
"tours.datesCheckpoint.body": "Important dates can help you stay on track.",
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"learning.generic.upsell.fullAccessBullet.fullAccess": "Acceso completo",
|
||||
"learning.generic.upsell.fullAccessBullet": "{fullAccessInBoldText} al contenido y los materiales del curso, incluso después de que finalice el curso",
|
||||
"learning.generic.upsell.supportMissionBullet.mission": "misión",
|
||||
"learning.generic.upsell.supportMissionBullet": "Apoya nuestra {missionInBoldText} en edX",
|
||||
"learning.generic.upsell.supportMissionBullet": "Apoya nuestra {missionInBoldText} en {siteName}",
|
||||
"masquerade-widget.userName.error.generic": "Se ha producido un error. Inténtalo de nuevo.",
|
||||
"masquerade-widget.userName.input.placeholder": "Nombre de usuario o correo electrónico",
|
||||
"masquerade-widget.userName.input.label": "Hazte pasar por este usuario",
|
||||
@@ -409,7 +409,7 @@
|
||||
"tours.button.okay": "Okey",
|
||||
"tours.button.beginTour": "Comenzar recorrido",
|
||||
"tours.button.launchTour": "gira de lanzamiento",
|
||||
"tours.newUserModal.body": "Hagamos un recorrido rápido por edX para que pueda aprovechar al máximo su curso.",
|
||||
"tours.newUserModal.body": "Hagamos un recorrido rápido por {siteName} para que pueda aprovechar al máximo su curso.",
|
||||
"tours.newUserModal.title.welcome": "Bienvenido a tu",
|
||||
"tours.button.skipForNow": "Saltar por ahora ",
|
||||
"tours.datesCheckpoint.body": "Las fechas importantes pueden ayudarlo a mantenerse encaminado.",
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"learning.generic.upsell.fullAccessBullet.fullAccess": "Accès complet",
|
||||
"learning.generic.upsell.fullAccessBullet": "{fullAccessInBoldText} au contenu et aux supports du cours, même après la fin du cours",
|
||||
"learning.generic.upsell.supportMissionBullet.mission": "mission",
|
||||
"learning.generic.upsell.supportMissionBullet": "Supportez notre {missionInBoldText} chez edX",
|
||||
"learning.generic.upsell.supportMissionBullet": "Supportez notre {missionInBoldText} chez {siteName}",
|
||||
"masquerade-widget.userName.error.generic": "Une erreur est survenue; veuillez réessayer.",
|
||||
"masquerade-widget.userName.input.placeholder": "Nom d'utilisateur ou courriel",
|
||||
"masquerade-widget.userName.input.label": "Se faire passer pour cet utilisateur",
|
||||
@@ -409,7 +409,7 @@
|
||||
"tours.button.okay": "Okay",
|
||||
"tours.button.beginTour": "Commencer la visite guidée",
|
||||
"tours.button.launchTour": "Lancer la visite guidée",
|
||||
"tours.newUserModal.body": "Faisons une visite guidée de edX afin de profiter au maximum de votre cours.",
|
||||
"tours.newUserModal.body": "Faisons une visite guidée de {siteName} afin de profiter au maximum de votre cours.",
|
||||
"tours.newUserModal.title.welcome": "Bienvenue à votre",
|
||||
"tours.button.skipForNow": "Ignorer pour l'instant",
|
||||
"tours.datesCheckpoint.body": "Dates importantes afin de vous maintenir sur la bonne voie.",
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"learning.generic.upsell.fullAccessBullet.fullAccess": "Full access",
|
||||
"learning.generic.upsell.fullAccessBullet": "{fullAccessInBoldText} to course content and materials, even after the course ends",
|
||||
"learning.generic.upsell.supportMissionBullet.mission": "mission",
|
||||
"learning.generic.upsell.supportMissionBullet": "Support our {missionInBoldText} at edX",
|
||||
"learning.generic.upsell.supportMissionBullet": "Support our {missionInBoldText} at {siteName}",
|
||||
"masquerade-widget.userName.error.generic": "An error has occurred; please try again.",
|
||||
"masquerade-widget.userName.input.placeholder": "Username or email",
|
||||
"masquerade-widget.userName.input.label": "Masquerade as this user",
|
||||
@@ -409,7 +409,7 @@
|
||||
"tours.button.okay": "Okay",
|
||||
"tours.button.beginTour": "Begin tour",
|
||||
"tours.button.launchTour": "Launch tour",
|
||||
"tours.newUserModal.body": "Let’s take a quick tour of edX so you can get the most out of your course.",
|
||||
"tours.newUserModal.body": "Let’s take a quick tour of {siteName} so you can get the most out of your course.",
|
||||
"tours.newUserModal.title.welcome": "Welcome to your",
|
||||
"tours.button.skipForNow": "Skip for now",
|
||||
"tours.datesCheckpoint.body": "Important dates can help you stay on track.",
|
||||
|
||||
@@ -13,7 +13,7 @@ const messages = defineMessages({
|
||||
},
|
||||
newUserModalBody: {
|
||||
id: 'tours.newUserModal.body',
|
||||
defaultMessage: 'Let’s take a quick tour of edX so you can get the most out of your course.',
|
||||
defaultMessage: 'Let’s take a quick tour of {siteName} so you can get the most out of your course.',
|
||||
},
|
||||
newUserModalTitleWelcome: {
|
||||
id: 'tours.newUserModal.title.welcome',
|
||||
|
||||
@@ -58,7 +58,7 @@ function NewUserCourseHomeTourModal({
|
||||
)}
|
||||
onClose={onDismiss}
|
||||
>
|
||||
<p className="text-dark-900">{intl.formatMessage(messages.newUserModalBody)}</p>
|
||||
<p className="text-dark-900">{intl.formatMessage(messages.newUserModalBody, { siteName: getConfig().SITE_NAME })}</p>
|
||||
</MarketingModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -160,8 +160,8 @@ function StreakModal({
|
||||
</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body className="modal-body">
|
||||
<p>{intl.formatMessage(messages.streakBody)}</p>
|
||||
<p className="modal-image">
|
||||
<p className="text-center">{intl.formatMessage(messages.streakBody)}</p>
|
||||
<p className="modal-image text-center">
|
||||
{!wideScreen && <img src={StreakMobileImage} alt="" className="img-fluid" />}
|
||||
{wideScreen && <img src={StreakDesktopImage} alt="" className="img-fluid" />}
|
||||
</p>
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
.modal-body {
|
||||
padding-top: .5rem;
|
||||
font-size: 1.2rem;
|
||||
|
||||
.pgn__modal-body-content {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
|
||||
Reference in New Issue
Block a user