Revert "Perf: improved discussions MFE's components re-rendering and loading time (#485)" (#512)

This reverts commit 59b4366edd.
This commit is contained in:
Awais Ansari
2023-05-08 15:34:43 +05:00
committed by GitHub
parent 59b4366edd
commit 7b7c249abd
86 changed files with 1970 additions and 2486 deletions

View File

@@ -1,11 +1,9 @@
import React, {
useCallback, useMemo, useRef, useState,
} from 'react';
import React, { useCallback, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
import {
Button, Dropdown, Icon, IconButton, ModalPopup, useToggle,
@@ -15,22 +13,22 @@ import { MoreHoriz } from '@edx/paragon/icons';
import { ContentActions } from '../../data/constants';
import { selectBlackoutDate } from '../data/selectors';
import messages from '../messages';
import { commentShape } from '../post-comments/comments/comment/proptypes';
import { postShape } from '../posts/post/proptypes';
import { inBlackoutDateRange, useActions } from '../utils';
function ActionsDropdown({
actionHandlers,
contentType,
intl,
commentOrPost,
disabled,
dropDownIconSize,
actionHandlers,
iconSize,
id,
dropDownIconSize,
}) {
const buttonRef = useRef();
const intl = useIntl();
const [isOpen, open, close] = useToggle(false);
const [target, setTarget] = useState(null);
const blackoutDateRange = useSelector(selectBlackoutDate);
const actions = useActions(contentType, id);
const actions = useActions(commentOrPost);
const handleActions = useCallback((action) => {
const actionFunction = actionHandlers[action];
@@ -41,12 +39,11 @@ function ActionsDropdown({
}
}, [actionHandlers]);
const blackoutDateRange = useSelector(selectBlackoutDate);
// Find and remove edit action if in blackout date range.
useMemo(() => {
if (inBlackoutDateRange(blackoutDateRange)) {
actions.splice(actions.findIndex(action => action.id === 'edit'), 1);
}
}, [actions, blackoutDateRange]);
if (inBlackoutDateRange(blackoutDateRange)) {
actions.splice(actions.findIndex(action => action.id === 'edit'), 1);
}
const onClickButton = useCallback(() => {
setTarget(buttonRef.current);
@@ -83,7 +80,9 @@ function ActionsDropdown({
>
{actions.map(action => (
<React.Fragment key={action.id}>
{(action.action === ContentActions.DELETE) && <Dropdown.Divider />}
{(action.action === ContentActions.DELETE)
&& <Dropdown.Divider />}
<Dropdown.Item
as={Button}
variant="tertiary"
@@ -112,12 +111,12 @@ function ActionsDropdown({
}
ActionsDropdown.propTypes = {
id: PropTypes.string.isRequired,
intl: intlShape.isRequired,
commentOrPost: PropTypes.oneOfType([commentShape, postShape]).isRequired,
disabled: PropTypes.bool,
actionHandlers: PropTypes.objectOf(PropTypes.func).isRequired,
iconSize: PropTypes.string,
dropDownIconSize: PropTypes.bool,
contentType: PropTypes.oneOf(['POST', 'COMMENT']).isRequired,
};
ActionsDropdown.defaultProps = {
@@ -126,4 +125,4 @@ ActionsDropdown.defaultProps = {
dropDownIconSize: false,
};
export default ActionsDropdown;
export default injectIntl(ActionsDropdown);

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { useIntl } from '@edx/frontend-platform/i18n';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import { Report } from '@edx/paragon/icons';
@@ -12,31 +12,26 @@ import { AvatarOutlineAndLabelColors } from '../../data/constants';
import {
selectModerationSettings, selectUserHasModerationPrivileges, selectUserIsGroupTa, selectUserIsStaff,
} from '../data/selectors';
import { commentShape } from '../post-comments/comments/comment/proptypes';
import messages from '../post-comments/messages';
import { postShape } from '../posts/post/proptypes';
import AlertBar from './AlertBar';
const AlertBanner = ({
author,
abuseFlagged,
lastEdit,
closed,
closedBy,
closeReason,
editByLabel,
closedByLabel,
}) => {
const intl = useIntl();
function AlertBanner({
intl,
content,
}) {
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
const userIsGroupTa = useSelector(selectUserIsGroupTa);
const userIsGlobalStaff = useSelector(selectUserIsStaff);
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
const userIsContentAuthor = getAuthenticatedUser().username === author;
const canSeeReportedBanner = abuseFlagged;
const userIsContentAuthor = getAuthenticatedUser().username === content.author;
const canSeeReportedBanner = content?.abuseFlagged;
const canSeeLastEditOrClosedAlert = (userHasModerationPrivileges || userIsGroupTa
|| userIsGlobalStaff || userIsContentAuthor
);
const editByLabelColor = AvatarOutlineAndLabelColors[editByLabel];
const closedByLabelColor = AvatarOutlineAndLabelColors[closedByLabel];
const editByLabelColor = AvatarOutlineAndLabelColors[content.editByLabel];
const closedByLabelColor = AvatarOutlineAndLabelColors[content.closedByLabel];
return (
<>
@@ -47,52 +42,33 @@ const AlertBanner = ({
)}
{reasonCodesEnabled && canSeeLastEditOrClosedAlert && (
<>
{lastEdit?.reason && (
{content.lastEdit?.reason && (
<AlertBar
message={intl.formatMessage(messages.editedBy)}
author={lastEdit.editorUsername}
authorLabel={editByLabel}
message={messages.editedBy}
author={content.lastEdit.editorUsername}
authorLabel={content.editByLabel}
labelColor={editByLabelColor && `text-${editByLabelColor}`}
reason={lastEdit.reason}
reason={content.lastEdit.reason}
/>
)}
{closed && (
<AlertBar
message={intl.formatMessage(messages.closedBy)}
author={closedBy}
authorLabel={closedByLabel}
labelColor={closedByLabelColor && `text-${closedByLabelColor}`}
reason={closeReason}
/>
{content.closed && (
<AlertBar
message={messages.closedBy}
author={content.closedBy}
authorLabel={content.closedByLabel}
labelColor={closedByLabelColor && `text-${closedByLabelColor}`}
reason={content.closeReason}
/>
)}
</>
)}
</>
);
};
}
AlertBanner.propTypes = {
author: PropTypes.string.isRequired,
abuseFlagged: PropTypes.bool,
closed: PropTypes.bool,
closedBy: PropTypes.string,
closedByLabel: PropTypes.string,
closeReason: PropTypes.string,
editByLabel: PropTypes.string,
lastEdit: PropTypes.shape({
editorUsername: PropTypes.string,
reason: PropTypes.string,
}),
intl: intlShape.isRequired,
content: PropTypes.oneOfType([commentShape.isRequired, postShape.isRequired]).isRequired,
};
AlertBanner.defaultProps = {
abuseFlagged: false,
closed: undefined,
closedBy: undefined,
closedByLabel: undefined,
closeReason: undefined,
editByLabel: undefined,
lastEdit: {},
};
export default React.memo(AlertBanner);
export default injectIntl(AlertBanner);

View File

@@ -1,25 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import messages from '../post-comments/messages';
import AuthorLabel from './AuthorLabel';
const AlertBar = ({
function AlertBar({
intl,
message,
author,
authorLabel,
labelColor,
reason,
}) => {
const intl = useIntl();
}) {
return (
<Alert variant="info" className="px-3 shadow-none mb-1 py-10px bg-light-200">
<div className="d-flex align-items-center flex-wrap text-gray-700 font-style">
{message}
{intl.formatMessage(message)}
<span className="ml-1">
<AuthorLabel
author={author}
@@ -39,9 +38,10 @@ const AlertBar = ({
</div>
</Alert>
);
};
}
AlertBar.propTypes = {
intl: intlShape.isRequired,
message: PropTypes.string,
author: PropTypes.string,
authorLabel: PropTypes.string,
@@ -57,4 +57,4 @@ AlertBar.defaultProps = {
reason: '',
};
export default React.memo(AlertBar);
export default injectIntl(AlertBar);

View File

@@ -1,22 +1,23 @@
import React, { useContext, useMemo } from 'react';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { generatePath } from 'react-router';
import { Link } from 'react-router-dom';
import { Link, useLocation } from 'react-router-dom';
import * as timeago from 'timeago.js';
import { useIntl } from '@edx/frontend-platform/i18n';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Icon, OverlayTrigger, Tooltip } from '@edx/paragon';
import { Institution, School } from '@edx/paragon/icons';
import { Routes } from '../../data/constants';
import { useShowLearnersTab } from '../data/hooks';
import messages from '../messages';
import { discussionsPath } from '../utils';
import { DiscussionContext } from './context';
import timeLocale from './time-locale';
const AuthorLabel = ({
function AuthorLabel({
intl,
author,
authorLabel,
linkToProfile,
@@ -25,18 +26,17 @@ const AuthorLabel = ({
postCreatedAt,
authorToolTip,
postOrComment,
}) => {
timeago.register('time-locale', timeLocale);
const intl = useIntl();
}) {
const location = useLocation();
const { courseId } = useContext(DiscussionContext);
let icon = null;
let authorLabelMessage = null;
timeago.register('time-locale', timeLocale);
if (authorLabel === 'Staff') {
icon = Institution;
authorLabelMessage = intl.formatMessage(messages.authorLabelStaff);
}
if (authorLabel === 'Community TA') {
icon = School;
authorLabelMessage = intl.formatMessage(messages.authorLabelTA);
@@ -49,7 +49,7 @@ const AuthorLabel = ({
const showUserNameAsLink = useShowLearnersTab()
&& linkToProfile && author && author !== intl.formatMessage(messages.anonymous);
const authorName = useMemo(() => (
const authorName = (
<span
className={classNames('mr-1.5 font-size-14 font-style font-weight-500', {
'text-gray-700': isRetiredUser,
@@ -60,9 +60,8 @@ const AuthorLabel = ({
>
{isRetiredUser ? '[Deactivated]' : author}
</span>
), [author, authorLabelMessage, isRetiredUser]);
const labelContents = useMemo(() => (
);
const labelContents = (
<>
<OverlayTrigger
overlay={(
@@ -110,7 +109,7 @@ const AuthorLabel = ({
</span>
)}
</>
), [author, authorLabelMessage, authorToolTip, icon, isRetiredUser, postCreatedAt, showTextPrimary, alert]);
);
return showUserNameAsLink
? (
@@ -118,7 +117,7 @@ const AuthorLabel = ({
<Link
data-testid="learner-posts-link"
id="learner-posts-link"
to={generatePath(Routes.LEARNERS.POSTS, { learnerUsername: author, courseId })}
to={discussionsPath(Routes.LEARNERS.POSTS, { learnerUsername: author, courseId })(location)}
className="text-decoration-none"
style={{ width: 'fit-content' }}
>
@@ -128,9 +127,10 @@ const AuthorLabel = ({
</div>
)
: <div className={className}>{authorName}{labelContents}</div>;
};
}
AuthorLabel.propTypes = {
intl: intlShape.isRequired,
author: PropTypes.string.isRequired,
authorLabel: PropTypes.string,
linkToProfile: PropTypes.bool,
@@ -151,4 +151,4 @@ AuthorLabel.defaultProps = {
postOrComment: false,
};
export default React.memo(AuthorLabel);
export default injectIntl(AuthorLabel);

View File

@@ -1,12 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { ActionRow, Button, ModalDialog } from '@edx/paragon';
import messages from '../messages';
function Confirmation({
intl,
isOpen,
title,
description,
@@ -16,8 +17,6 @@ function Confirmation({
confirmButtonVariant,
confirmButtonText,
}) {
const intl = useIntl();
return (
<ModalDialog title={title} isOpen={isOpen} hasCloseButton={false} onClose={onClose} zIndex={5000}>
<ModalDialog.Header>
@@ -43,6 +42,7 @@ function Confirmation({
}
Confirmation.propTypes = {
intl: intlShape.isRequired,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
comfirmAction: PropTypes.func.isRequired,
@@ -59,4 +59,4 @@ Confirmation.defaultProps = {
confirmButtonText: '',
};
export default React.memo(Confirmation);
export default injectIntl(Confirmation);

View File

@@ -1,34 +1,30 @@
import React, { useContext } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import * as timeago from 'timeago.js';
import { useIntl } from '@edx/frontend-platform/i18n';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Alert, Icon } from '@edx/paragon';
import { CheckCircle, Verified } from '@edx/paragon/icons';
import { ThreadType } from '../../data/constants';
import { commentShape } from '../post-comments/comments/comment/proptypes';
import messages from '../post-comments/messages';
import { PostCommentsContext } from '../post-comments/postCommentsContext';
import AuthorLabel from './AuthorLabel';
import timeLocale from './time-locale';
function EndorsedAlertBanner({
endorsed,
endorsedAt,
endorsedBy,
endorsedByLabel,
intl,
content,
postType,
}) {
timeago.register('time-locale', timeLocale);
const intl = useIntl();
const { postType } = useContext(PostCommentsContext);
const isQuestion = postType === ThreadType.QUESTION;
const classes = isQuestion ? 'bg-success-500 text-white' : 'bg-dark-500 text-white';
const iconClass = isQuestion ? CheckCircle : Verified;
return (
endorsed && (
content.endorsed && (
<Alert
variant="plain"
className={`px-2.5 mb-0 py-8px align-items-center shadow-none ${classes}`}
@@ -49,11 +45,11 @@ function EndorsedAlertBanner({
</div>
<span className="d-flex align-items-center align-items-center flex-wrap" style={{ marginRight: '-1px' }}>
<AuthorLabel
author={endorsedBy}
authorLabel={endorsedByLabel}
author={content.endorsedBy}
authorLabel={content.endorsedByLabel}
linkToProfile
alert={endorsed}
postCreatedAt={endorsedAt}
alert={content.endorsed}
postCreatedAt={content.endorsedAt}
authorToolTip
postOrComment
/>
@@ -65,16 +61,13 @@ function EndorsedAlertBanner({
}
EndorsedAlertBanner.propTypes = {
endorsed: PropTypes.bool.isRequired,
endorsedAt: PropTypes.string,
endorsedBy: PropTypes.string,
endorsedByLabel: PropTypes.string,
intl: intlShape.isRequired,
content: PropTypes.oneOfType([commentShape.isRequired]).isRequired,
postType: PropTypes.string,
};
EndorsedAlertBanner.defaultProps = {
endorsedAt: null,
endorsedBy: null,
endorsedByLabel: null,
postType: null,
};
export default React.memo(EndorsedAlertBanner);
export default injectIntl(EndorsedAlertBanner);

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useIntl } from '@edx/frontend-platform/i18n';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Button, Icon, IconButton, OverlayTrigger, Tooltip,
} from '@edx/paragon';
@@ -12,32 +12,29 @@ import {
StarFilled, StarOutline, ThumbUpFilled, ThumbUpOutline,
} from '../../components/icons';
import { useUserCanAddThreadInBlackoutDate } from '../data/hooks';
import { PostCommentsContext } from '../post-comments/postCommentsContext';
import { commentShape } from '../post-comments/comments/comment/proptypes';
import { postShape } from '../posts/post/proptypes';
import ActionsDropdown from './ActionsDropdown';
import { DiscussionContext } from './context';
const HoverCard = ({
id,
contentType,
function HoverCard({
intl,
commentOrPost,
actionHandlers,
handleResponseCommentButton,
addResponseCommentButtonMessage,
onLike,
onFollow,
voted,
following,
isClosedPost,
endorseIcons,
}) => {
const intl = useIntl();
}) {
const { enableInContextSidebar } = useContext(DiscussionContext);
const { isClosed } = useContext(PostCommentsContext);
const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate();
return (
<div
className="flex-fill justify-content-end align-items-center hover-card mr-n4 position-absolute"
data-testid={`hover-card-${id}`}
id={`hover-card-${id}`}
data-testid={`hover-card-${commentOrPost.id}`}
id={`hover-card-${commentOrPost.id}`}
>
{userCanAddThreadInBlackoutDate && (
<div className="d-flex">
@@ -46,7 +43,7 @@ const HoverCard = ({
className={classNames('px-2.5 py-2 border-0 font-style text-gray-700 font-size-12',
{ 'w-100': enableInContextSidebar })}
onClick={() => handleResponseCommentButton()}
disabled={isClosed}
disabled={isClosedPost}
style={{ lineHeight: '20px' }}
>
{addResponseCommentButtonMessage}
@@ -79,7 +76,7 @@ const HoverCard = ({
)}
<div className="hover-button">
<IconButton
src={voted ? ThumbUpFilled : ThumbUpOutline}
src={commentOrPost.voted ? ThumbUpFilled : ThumbUpOutline}
iconAs={Icon}
size="sm"
alt="Like"
@@ -90,10 +87,10 @@ const HoverCard = ({
}}
/>
</div>
{following !== undefined && (
{commentOrPost.following !== undefined && (
<div className="hover-button">
<IconButton
src={following ? StarFilled : StarOutline}
src={commentOrPost.following ? StarFilled : StarOutline}
iconAs={Icon}
size="sm"
alt="Follow"
@@ -106,34 +103,27 @@ const HoverCard = ({
</div>
)}
<div className="hover-button ml-auto">
<ActionsDropdown
id={id}
contentType={contentType}
actionHandlers={actionHandlers}
dropDownIconSize
/>
<ActionsDropdown commentOrPost={commentOrPost} actionHandlers={actionHandlers} dropDownIconSize />
</div>
</div>
);
};
}
HoverCard.propTypes = {
id: PropTypes.string.isRequired,
contentType: PropTypes.string.isRequired,
intl: intlShape.isRequired,
commentOrPost: PropTypes.oneOfType([commentShape, postShape]).isRequired,
actionHandlers: PropTypes.objectOf(PropTypes.func).isRequired,
handleResponseCommentButton: PropTypes.func.isRequired,
addResponseCommentButtonMessage: PropTypes.string.isRequired,
onLike: PropTypes.func.isRequired,
voted: PropTypes.bool.isRequired,
endorseIcons: PropTypes.objectOf(PropTypes.any),
onFollow: PropTypes.func,
following: PropTypes.bool,
addResponseCommentButtonMessage: PropTypes.string.isRequired,
isClosedPost: PropTypes.bool.isRequired,
endorseIcons: PropTypes.objectOf(PropTypes.any),
};
HoverCard.defaultProps = {
onFollow: () => null,
endorseIcons: null,
following: undefined,
};
export default React.memo(HoverCard);
export default injectIntl(HoverCard);