Compare commits

...

5 Commits

Author SHA1 Message Date
Ihor Romaniuk
72bce9e652 fix: save scroll position on exit from video xblock fullscreen mode (#981)
* fix: save scroll position on exit from video xblock fullscreen mode

* fix: change initial scroll top value

* fix: update tests
2023-06-21 13:45:34 -04:00
Eugene Dyudyunov
66482ab23a fix: first section celebration
Fix the first section celebration modal showing logic.

On Nutmeg+ it's shown only after the page reload or after going directly
to the second section from the course home. Going through the course
with the Next/Previous buttons has no effect (which worked on Maple).

Notes:
- the weekly goal has the same showing logic, but I assume that is
correct behavior so no changes are added for it in this commit.
- showing a celebration modal for the first section completion when
going directly to the first unit of the second section seems to be a bug
(reproduces on Maple too)
2023-02-14 15:40:50 -05:00
Ihor Romaniuk
03e0f41692 fix: fix alignment in the streak celebration modal (#974)
* fix: fix alignment in the streak celebration modal

* chore: update package-log
2022-12-05 11:54:48 -05:00
Diana Catalina Olarte
8e1904a235 fix: show site name instead of edX
(cherry picked from commit cafb881a61)
2022-05-18 15:52:10 +01:00
Michael Terry
88485a0f77 fix: add back es-check & fsevents for now to fix build
A previous commit (7f37575) dropped es-check, which dropped
fsevents, which caused our build system (which is still using
npm@6) to fail with an error like `Unsupported platform for
fsevents` when trying to install fsevents through a dependency
(e.g. when installing npm aliases).

I am reintroducing all the package-lock changes from that commit
to get back fsevents in a state where that error does not occur.

I think a longer-term fix would be to instead upgrade our build
system to node16 / npm6. But this is an easy fix for now to unblock
the builds.
2022-04-14 10:16:14 -04:00
17 changed files with 1692 additions and 7199 deletions

8776
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -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>

View File

@@ -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]);

View File

@@ -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();

View 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();
});
});

View File

@@ -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'));

View File

@@ -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' };

View File

@@ -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>
);

View File

@@ -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": "Lets take a quick tour of edX so you can get the most out of your course.",
"tours.newUserModal.body": "Lets 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.",

View File

@@ -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.",

View File

@@ -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.",

View File

@@ -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": "Lets take a quick tour of edX so you can get the most out of your course.",
"tours.newUserModal.body": "Lets 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.",

View File

@@ -13,7 +13,7 @@ const messages = defineMessages({
},
newUserModalBody: {
id: 'tours.newUserModal.body',
defaultMessage: 'Lets take a quick tour of edX so you can get the most out of your course.',
defaultMessage: 'Lets take a quick tour of {siteName} so you can get the most out of your course.',
},
newUserModalTitleWelcome: {
id: 'tours.newUserModal.title.welcome',

View File

@@ -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>
);
}

View File

@@ -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>

View File

@@ -26,6 +26,10 @@
.modal-body {
padding-top: .5rem;
font-size: 1.2rem;
.pgn__modal-body-content {
text-align: center;
}
}
.modal-footer {