Skip to content

Commit

Permalink
fix: scroll to element only when required
Browse files Browse the repository at this point in the history
  • Loading branch information
navinkarkera committed Dec 13, 2023
1 parent a7c1ae0 commit 213cd0f
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 37 deletions.
18 changes: 2 additions & 16 deletions src/course-outline/CourseOutline.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
React, useState, useCallback, useEffect, useRef,
React, useState, useCallback, useEffect,
} from 'react';
import update from 'immutability-helper';
import { DndProvider } from 'react-dnd';
Expand Down Expand Up @@ -40,10 +40,8 @@ import ConfigureModal from './configure-modal/ConfigureModal';
import DeleteModal from './delete-modal/DeleteModal';
import { useCourseOutline } from './hooks';
import messages from './messages';
import { scrollToElement } from './utils';

const CourseOutline = ({ courseId }) => {
const listRef = useRef(null);
const intl = useIntl();

const {
Expand Down Expand Up @@ -98,17 +96,14 @@ const CourseOutline = ({ courseId }) => {
title: processingNotificationTitle,
} = useSelector(getProcessingNotification);

const moveSection = useCallback((dragIndex, hoverIndex, dragElement) => {
const moveSection = useCallback((dragIndex, hoverIndex) => {
setSections((prevSections) => {
const newList = update(prevSections, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, prevSections[dragIndex]],
],
});
// set listRef to element that was dragged to make sure scrolling
// uses the correct element while calculating visibility.
listRef.current = dragElement;
return newList;
});
}, []);
Expand All @@ -123,13 +118,6 @@ const CourseOutline = ({ courseId }) => {
setSections(sectionsList);
}, [sectionsList]);

useEffect(() => {
// scrollToElement called in another useEffect to make sure sections are rendered first.
if (listRef.current) {
scrollToElement(listRef.current);
}
}, [sections]);

if (isLoading) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
Expand Down Expand Up @@ -210,7 +198,6 @@ const CourseOutline = ({ courseId }) => {
onNewSubsectionSubmit={handleNewSubsectionSubmit}
moveSection={moveSection}
finalizeSectionOrder={finalizeSectionOrder}
ref={listRef}
>
{section.childInfo.children.map((subsection) => (
<SubsectionCard
Expand All @@ -222,7 +209,6 @@ const CourseOutline = ({ courseId }) => {
onOpenDeleteModal={openDeleteModal}
onEditSubmit={handleEditSubmit}
onDuplicateSubmit={handleDuplicateSubsectionSubmit}
ref={listRef}
/>
))}
</SectionCard>
Expand Down
9 changes: 7 additions & 2 deletions src/course-outline/data/thunk.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,13 @@ export function fetchCourseReindexQuery(courseId, reindexLink) {
};
}

export function fetchCourseSectionQuery(sectionId) {
export function fetchCourseSectionQuery(sectionId, shouldScroll = false) {
return async (dispatch) => {
dispatch(updateFetchSectionLoadingStatus({ status: RequestStatus.IN_PROGRESS }));

try {
const data = await getCourseItem(sectionId);
data.shouldScroll = shouldScroll;
dispatch(updateSectionList(data));
dispatch(updateFetchSectionLoadingStatus({ status: RequestStatus.SUCCESSFUL }));
} catch (error) {
Expand Down Expand Up @@ -307,6 +308,8 @@ export function duplicateSectionQuery(sectionId, courseBlockId) {
courseBlockId,
async (locator) => {
const duplicatedItem = await getCourseItem(locator);
// Page should scroll to newly duplicated item.
duplicatedItem.shouldScroll = true;
dispatch(duplicateSection({ id: sectionId, duplicatedItem }));
},
));
Expand All @@ -318,7 +321,7 @@ export function duplicateSubsectionQuery(subsectionId, sectionId) {
dispatch(duplicateCourseItemQuery(
subsectionId,
sectionId,
async () => dispatch(fetchCourseSectionQuery(sectionId)),
async () => dispatch(fetchCourseSectionQuery(sectionId, true)),
));
};
}
Expand All @@ -344,6 +347,8 @@ function addNewCourseItemQuery(parentLocator, category, displayName, addItemFn)
).then(async (result) => {
if (result) {
const data = await getCourseItem(result.locator);
// Page should scroll to newly created item.
data.shouldScroll = true;
dispatch(addItemFn(data));
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
dispatch(hideProcessingNotification());
Expand Down
33 changes: 20 additions & 13 deletions src/course-outline/section-card/SectionCard.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {
forwardRef, useEffect, useState, useRef,
useEffect, useState, useRef,
} from 'react';
import { useDrag, useDrop } from 'react-dnd';
import PropTypes from 'prop-types';
Expand All @@ -11,11 +11,11 @@ import { Add as IconAdd } from '@edx/paragon/icons';
import { setCurrentItem, setCurrentSection } from '../data/slice';
import { RequestStatus } from '../../data/constants';
import CardHeader from '../card-header/CardHeader';
import { getItemStatus } from '../utils';
import { getItemStatus, scrollToElement } from '../utils';
import messages from './messages';
import ItemTypes from './itemTypes';

const SectionCard = forwardRef(({
const SectionCard = ({
section,
index,
children,
Expand All @@ -30,7 +30,8 @@ const SectionCard = forwardRef(({
onNewSubsectionSubmit,
moveSection,
finalizeSectionOrder,
}, lastItemRef) => {
}) => {
const currentRef = useRef(null);
const intl = useIntl();
const dispatch = useDispatch();
const [isExpanded, setIsExpanded] = useState(isSectionsExpanded);
Expand All @@ -40,6 +41,13 @@ const SectionCard = forwardRef(({
setIsExpanded(isSectionsExpanded);
}, [isSectionsExpanded]);

useEffect(() => {
// if this items has been newly added, scroll to it.
if (currentRef.current && section.shouldScroll) {
scrollToElement(currentRef.current);
}
}, []);

const {
id,
displayName,
Expand All @@ -52,8 +60,6 @@ const SectionCard = forwardRef(({
highlights,
} = section;

const moveRef = useRef(null);

const [{ handlerId }, drop] = useDrop({
accept: ItemTypes.SECTION,
collect(monitor) {
Expand All @@ -62,7 +68,7 @@ const SectionCard = forwardRef(({
};
},
hover(item, monitor) {
if (!moveRef.current) {
if (!currentRef.current) {
return;
}
const dragIndex = item.index;
Expand All @@ -72,7 +78,7 @@ const SectionCard = forwardRef(({
return;
}
// Determine rectangle on screen
const hoverBoundingRect = moveRef.current?.getBoundingClientRect();
const hoverBoundingRect = currentRef.current?.getBoundingClientRect();
// Get vertical middle
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
// Determine mouse position
Expand All @@ -91,7 +97,7 @@ const SectionCard = forwardRef(({
return;
}
// Time to actually perform the action
moveSection(dragIndex, hoverIndex, moveRef.current);
moveSection(dragIndex, hoverIndex);
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
Expand All @@ -117,7 +123,7 @@ const SectionCard = forwardRef(({
},
});
const opacity = isDragging ? 0 : 1;
drag(drop(moveRef));
drag(drop(currentRef));

const sectionStatus = getItemStatus({
published,
Expand Down Expand Up @@ -166,9 +172,9 @@ const SectionCard = forwardRef(({
data-testid="section-card"
data-handler-id={handlerId}
style={{ opacity }}
ref={moveRef}
ref={currentRef}
>
<div ref={lastItemRef}>
<div>
<CardHeader
sectionId={id}
title={displayName}
Expand Down Expand Up @@ -222,7 +228,7 @@ const SectionCard = forwardRef(({
</div>
</div>
);
});
};

SectionCard.defaultProps = {
children: null,
Expand All @@ -239,6 +245,7 @@ SectionCard.propTypes = {
visibilityState: PropTypes.string.isRequired,
staffOnlyMessage: PropTypes.bool.isRequired,
highlights: PropTypes.arrayOf(PropTypes.string).isRequired,
shouldScroll: PropTypes.bool,
}).isRequired,
index: PropTypes.number.isRequired,
children: PropTypes.node,
Expand Down
24 changes: 18 additions & 6 deletions src/course-outline/subsection-card/SubsectionCard.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { forwardRef, useEffect, useState } from 'react';
import { useEffect, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n';
Expand All @@ -8,10 +8,10 @@ import { Add as IconAdd } from '@edx/paragon/icons';
import { setCurrentItem, setCurrentSection, setCurrentSubsection } from '../data/slice';
import { RequestStatus } from '../../data/constants';
import CardHeader from '../card-header/CardHeader';
import { getItemStatus } from '../utils';
import { getItemStatus, scrollToElement } from '../utils';
import messages from './messages';

const SubsectionCard = forwardRef(({
const SubsectionCard = ({
section,
subsection,
children,
Expand All @@ -20,7 +20,8 @@ const SubsectionCard = forwardRef(({
savingStatus,
onOpenDeleteModal,
onDuplicateSubmit,
}, lastItemRef) => {
}) => {
const currentRef = useRef(null);
const intl = useIntl();
const dispatch = useDispatch();
const [isExpanded, setIsExpanded] = useState(false);
Expand Down Expand Up @@ -64,14 +65,23 @@ const SubsectionCard = forwardRef(({
closeForm();
};

useEffect(() => {
// if this items has been newly added, scroll to it.
// we need to check section.shouldScroll as whole section is fetched when a
// subsection is duplicated under it.
if (currentRef.current && (section.shouldScroll || subsection.shouldScroll)) {
scrollToElement(currentRef.current);
}
}, []);

useEffect(() => {
if (savingStatus === RequestStatus.SUCCESSFUL) {
closeForm();
}
}, [savingStatus]);

return (
<div className="subsection-card" data-testid="subsection-card" ref={lastItemRef}>
<div className="subsection-card" data-testid="subsection-card" ref={currentRef}>
<CardHeader
title={displayName}
status={subsectionStatus}
Expand Down Expand Up @@ -107,7 +117,7 @@ const SubsectionCard = forwardRef(({
)}
</div>
);
});
};

SubsectionCard.defaultProps = {
children: null,
Expand All @@ -123,6 +133,7 @@ SubsectionCard.propTypes = {
visibleToStaffOnly: PropTypes.bool,
visibilityState: PropTypes.string.isRequired,
staffOnlyMessage: PropTypes.bool.isRequired,
shouldScroll: PropTypes.bool,
}).isRequired,
subsection: PropTypes.shape({
id: PropTypes.string.isRequired,
Expand All @@ -133,6 +144,7 @@ SubsectionCard.propTypes = {
visibleToStaffOnly: PropTypes.bool,
visibilityState: PropTypes.string.isRequired,
staffOnlyMessage: PropTypes.bool.isRequired,
shouldScroll: PropTypes.bool,
}).isRequired,
children: PropTypes.node,
onOpenPublishModal: PropTypes.func.isRequired,
Expand Down

0 comments on commit 213cd0f

Please sign in to comment.