Improved activity editor re-renders (#4149)
* Refactor task count * Fixed show page rerender * Less rerenders and way better title and body UX * Finished breaking down activity editor subscriptions * Removed console.log * Last console.log * Fixed bugs and cleaned
This commit is contained in:
@ -1,11 +1,8 @@
|
||||
import { useEffect } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useActivities } from '@/activities/hooks/useActivities';
|
||||
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
|
||||
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
|
||||
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectState';
|
||||
import { timelineActivitiesNetworkingState } from '@/activities/timeline/states/timelineActivitiesNetworkingState';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
|
||||
import {
|
||||
@ -15,7 +12,6 @@ import {
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
} from '@/ui/layout/animated-placeholder/components/EmptyPlaceholderStyled';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { TimelineItemsContainer } from './TimelineItemsContainer';
|
||||
|
||||
@ -36,21 +32,10 @@ export const Timeline = ({
|
||||
}: {
|
||||
targetableObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
const { activities, initialized, noActivities } = useActivities({
|
||||
targetableObjects: [targetableObject],
|
||||
activitiesFilters: {},
|
||||
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
|
||||
skip: !isDefined(targetableObject),
|
||||
});
|
||||
|
||||
const setTimelineTargetableObject = useSetRecoilState(
|
||||
objectShowPageTargetableObjectState,
|
||||
const { initialized, noActivities } = useRecoilValue(
|
||||
timelineActivitiesNetworkingState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTimelineTargetableObject(targetableObject);
|
||||
}, [targetableObject, setTimelineTargetableObject]);
|
||||
|
||||
const showEmptyState = noActivities;
|
||||
|
||||
const showLoadingState = !initialized;
|
||||
@ -79,7 +64,7 @@ export const Timeline = ({
|
||||
|
||||
return (
|
||||
<StyledMainContainer>
|
||||
<TimelineItemsContainer activities={activities} />
|
||||
<TimelineItemsContainer />
|
||||
</StyledMainContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,7 +4,7 @@ import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { timelineActivityWithoutTargetsFamilyState } from '@/activities/timeline/states/timelineActivityFirstLevelFamilySelector';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { IconCheckbox, IconNotes } from '@/ui/display/icon';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
@ -136,28 +136,42 @@ const StyledTimelineItemContainer = styled.div<{ isGap?: boolean }>`
|
||||
`;
|
||||
|
||||
type TimelineActivityProps = {
|
||||
activity: Activity;
|
||||
isLastActivity?: boolean;
|
||||
activityId: string;
|
||||
};
|
||||
|
||||
export const TimelineActivity = ({
|
||||
activity,
|
||||
isLastActivity,
|
||||
activityId,
|
||||
}: TimelineActivityProps) => {
|
||||
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(activity.createdAt);
|
||||
const exactCreatedAt = beautifyExactDateTime(activity.createdAt);
|
||||
const activityForTimeline = useRecoilValue(
|
||||
timelineActivityWithoutTargetsFamilyState(activityId),
|
||||
);
|
||||
|
||||
const beautifiedCreatedAt = activityForTimeline
|
||||
? beautifyPastDateRelativeToNow(activityForTimeline.createdAt)
|
||||
: '';
|
||||
const exactCreatedAt = activityForTimeline
|
||||
? beautifyExactDateTime(activityForTimeline.createdAt)
|
||||
: '';
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
||||
const theme = useTheme();
|
||||
|
||||
const activityFromStore = useRecoilValue(recordStoreFamilyState(activity.id));
|
||||
const activityFromStore = useRecoilValue(
|
||||
recordStoreFamilyState(activityForTimeline?.id ?? ''),
|
||||
);
|
||||
|
||||
if (!activityForTimeline) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledTimelineItemContainer>
|
||||
<StyledAvatarContainer>
|
||||
<Avatar
|
||||
avatarUrl={activity.author?.avatarUrl}
|
||||
placeholder={activity.author?.name.firstName ?? ''}
|
||||
avatarUrl={activityForTimeline.author?.avatarUrl}
|
||||
placeholder={activityForTimeline.author?.name.firstName ?? ''}
|
||||
size="sm"
|
||||
type="rounded"
|
||||
/>
|
||||
@ -166,23 +180,26 @@ export const TimelineActivity = ({
|
||||
<StyledItemTitleContainer>
|
||||
<StyledItemAuthorText>
|
||||
<span>
|
||||
{activity.author?.name.firstName}{' '}
|
||||
{activity.author?.name.lastName}
|
||||
{activityForTimeline.author?.name.firstName}{' '}
|
||||
{activityForTimeline.author?.name.lastName}
|
||||
</span>
|
||||
created a {activity.type.toLowerCase()}
|
||||
created a {activityForTimeline.type.toLowerCase()}
|
||||
</StyledItemAuthorText>
|
||||
<StyledItemTitle>
|
||||
<StyledIconContainer>
|
||||
{activity.type === 'Note' && (
|
||||
{activityForTimeline.type === 'Note' && (
|
||||
<IconNotes size={theme.icon.size.sm} />
|
||||
)}
|
||||
{activity.type === 'Task' && (
|
||||
{activityForTimeline.type === 'Task' && (
|
||||
<IconCheckbox size={theme.icon.size.sm} />
|
||||
)}
|
||||
</StyledIconContainer>
|
||||
{(activity.type === 'Note' || activity.type === 'Task') && (
|
||||
{(activityForTimeline.type === 'Note' ||
|
||||
activityForTimeline.type === 'Task') && (
|
||||
<StyledActivityTitle
|
||||
onClick={() => openActivityRightDrawer(activity)}
|
||||
onClick={() =>
|
||||
openActivityRightDrawer(activityForTimeline.id)
|
||||
}
|
||||
>
|
||||
“
|
||||
<StyledActivityLink
|
||||
@ -195,11 +212,11 @@ export const TimelineActivity = ({
|
||||
)}
|
||||
</StyledItemTitle>
|
||||
</StyledItemTitleContainer>
|
||||
<StyledItemTitleDate id={`id-${activity.id}`}>
|
||||
<StyledItemTitleDate id={`id-${activityForTimeline.id}`}>
|
||||
{beautifiedCreatedAt}
|
||||
</StyledItemTitleDate>
|
||||
<StyledTooltip
|
||||
anchorSelect={`#id-${activity.id}`}
|
||||
anchorSelect={`#id-${activityForTimeline.id}`}
|
||||
content={exactCreatedAt}
|
||||
clickable
|
||||
noArrow
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
|
||||
import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
|
||||
import { groupActivitiesByMonth } from '../utils/groupActivitiesByMonth';
|
||||
@ -23,14 +24,12 @@ const StyledTimelineContainer = styled.div`
|
||||
|
||||
const StyledScrollWrapper = styled(ScrollWrapper)``;
|
||||
|
||||
export type TimelineItemsContainerProps = {
|
||||
activities: ActivityForDrawer[];
|
||||
};
|
||||
export const TimelineItemsContainer = () => {
|
||||
const timelineActivitiesForGroup = useRecoilValue(
|
||||
timelineActivitiesForGroupState,
|
||||
);
|
||||
|
||||
export const TimelineItemsContainer = ({
|
||||
activities,
|
||||
}: TimelineItemsContainerProps) => {
|
||||
const groupedActivities = groupActivitiesByMonth(activities);
|
||||
const groupedActivities = groupActivitiesByMonth(timelineActivitiesForGroup);
|
||||
|
||||
return (
|
||||
<StyledScrollWrapper>
|
||||
|
||||
@ -0,0 +1,131 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useActivities } from '@/activities/hooks/useActivities';
|
||||
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY';
|
||||
import { objectShowPageTargetableObjectState } from '@/activities/timeline/states/objectShowPageTargetableObjectIdState';
|
||||
import { timelineActivitiesFammilyState } from '@/activities/timeline/states/timelineActivitiesFamilyState';
|
||||
import { timelineActivitiesForGroupState } from '@/activities/timeline/states/timelineActivitiesForGroupState';
|
||||
import { timelineActivitiesNetworkingState } from '@/activities/timeline/states/timelineActivitiesNetworkingState';
|
||||
import { timelineActivityWithoutTargetsFamilyState } from '@/activities/timeline/states/timelineActivityFirstLevelFamilySelector';
|
||||
import { Activity } from '@/activities/types/Activity';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { sortObjectRecordByDateField } from '@/object-record/utils/sortObjectRecordByDateField';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const TimelineQueryEffect = ({
|
||||
targetableObject,
|
||||
}: {
|
||||
targetableObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
const setTimelineTargetableObject = useSetRecoilState(
|
||||
objectShowPageTargetableObjectState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTimelineTargetableObject(targetableObject);
|
||||
}, [targetableObject, setTimelineTargetableObject]);
|
||||
|
||||
const { activities, initialized, noActivities } = useActivities({
|
||||
targetableObjects: [targetableObject],
|
||||
activitiesFilters: {},
|
||||
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
|
||||
skip: !isDefined(targetableObject),
|
||||
});
|
||||
|
||||
const [timelineActivitiesNetworking, setTimelineActivitiesNetworking] =
|
||||
useRecoilState(timelineActivitiesNetworkingState);
|
||||
|
||||
const [timelineActivitiesForGroup, setTimelineActivitiesForGroup] =
|
||||
useRecoilState(timelineActivitiesForGroupState);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefined(targetableObject)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activitiesForGroup = activities
|
||||
.map((activity) => ({
|
||||
id: activity.id,
|
||||
createdAt: activity.createdAt,
|
||||
}))
|
||||
.toSorted(sortObjectRecordByDateField('createdAt', 'DescNullsLast'));
|
||||
|
||||
const timelineActivitiesForGroupSorted =
|
||||
timelineActivitiesForGroup.toSorted(
|
||||
sortObjectRecordByDateField('createdAt', 'DescNullsLast'),
|
||||
);
|
||||
|
||||
if (!isDeeplyEqual(activitiesForGroup, timelineActivitiesForGroupSorted)) {
|
||||
setTimelineActivitiesForGroup(activitiesForGroup);
|
||||
}
|
||||
|
||||
if (
|
||||
!isDeeplyEqual(timelineActivitiesNetworking.initialized, initialized) ||
|
||||
!isDeeplyEqual(timelineActivitiesNetworking.noActivities, noActivities)
|
||||
) {
|
||||
setTimelineActivitiesNetworking({
|
||||
initialized,
|
||||
noActivities,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
activities,
|
||||
initialized,
|
||||
noActivities,
|
||||
setTimelineActivitiesNetworking,
|
||||
targetableObject,
|
||||
timelineActivitiesNetworking,
|
||||
timelineActivitiesForGroup,
|
||||
setTimelineActivitiesForGroup,
|
||||
]);
|
||||
|
||||
const updateTimelineActivities = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(newActivities: Activity[]) => {
|
||||
for (const newActivity of newActivities) {
|
||||
const currentActivity = snapshot
|
||||
.getLoadable(timelineActivitiesFammilyState(newActivity.id))
|
||||
.getValue();
|
||||
|
||||
if (!isDeeplyEqual(newActivity, currentActivity)) {
|
||||
set(timelineActivitiesFammilyState(newActivity.id), newActivity);
|
||||
}
|
||||
|
||||
const currentActivityWithoutTarget = snapshot
|
||||
.getLoadable(
|
||||
timelineActivityWithoutTargetsFamilyState(newActivity.id),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
const newActivityWithoutTarget = {
|
||||
id: newActivity.id,
|
||||
title: newActivity.title,
|
||||
createdAt: newActivity.createdAt,
|
||||
author: newActivity.author,
|
||||
type: newActivity.type,
|
||||
};
|
||||
|
||||
if (
|
||||
!isDeeplyEqual(
|
||||
newActivityWithoutTarget,
|
||||
currentActivityWithoutTarget,
|
||||
)
|
||||
) {
|
||||
set(
|
||||
timelineActivityWithoutTargetsFamilyState(newActivity.id),
|
||||
newActivityWithoutTarget,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateTimelineActivities(activities);
|
||||
}, [activities, updateTimelineActivities]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -68,7 +68,7 @@ export const TimelineActivityGroup = ({
|
||||
{group.items.map((activity, index) => (
|
||||
<TimelineActivity
|
||||
key={activity.id}
|
||||
activity={activity}
|
||||
activityId={activity.id}
|
||||
isLastActivity={index === group.items.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user