feat: refactor ActivityTimeline and TaskCommentsSection to utilize useNewItemKeys hook
- Replaced manual tracking of new item keys in ActivityTimeline and TaskCommentsSection with a new custom hook, useNewItemKeys, for improved code clarity and reusability. - Simplified state management related to new items in both components, enhancing maintainability and reducing complexity. - Updated related logic to ensure consistent handling of newly visible items during pagination and resets.
This commit is contained in:
parent
355fe237a6
commit
821c3019be
3 changed files with 69 additions and 65 deletions
|
|
@ -7,6 +7,7 @@ import { AnimatedHeightReveal } from './AnimatedHeightReveal';
|
|||
import { ActivityItem, isNoiseMessage } from './ActivityItem';
|
||||
import { findNewestMessageIndex, resolveTimelineCollapseState } from './collapseState';
|
||||
import { groupTimelineItems, isLeadThought, LeadThoughtsGroupRow } from './LeadThoughtsGroup';
|
||||
import { useNewItemKeys } from './useNewItemKeys';
|
||||
|
||||
import type { TimelineItem } from './LeadThoughtsGroup';
|
||||
import type { ActivityCollapseState } from './collapseState';
|
||||
|
|
@ -145,11 +146,6 @@ export const ActivityTimeline = ({
|
|||
}: ActivityTimelineProps): React.JSX.Element => {
|
||||
const [visibleCount, setVisibleCount] = useState(MESSAGES_PAGE_SIZE);
|
||||
|
||||
// --- New-message animation tracking ---
|
||||
const knownKeysRef = useRef<Set<string>>(new Set<string>());
|
||||
const isInitializedRef = useRef(false);
|
||||
const prevVisibleCountRef = useRef(visibleCount);
|
||||
|
||||
const colorMap = members ? buildMemberColorMap(members) : new Map<string, string>();
|
||||
const memberInfo = new Map<string, { role?: string; color?: string }>();
|
||||
if (members) {
|
||||
|
|
@ -243,32 +239,11 @@ export const ActivityTimeline = ({
|
|||
return timelineItems.map(getItemKey);
|
||||
}, [timelineItems]);
|
||||
|
||||
const isPaginationExpansion =
|
||||
isInitializedRef.current && visibleCount > prevVisibleCountRef.current;
|
||||
|
||||
const newItemKeys = useMemo(() => {
|
||||
if (!isInitializedRef.current || isPaginationExpansion) {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
const newKeys = new Set<string>();
|
||||
for (const key of timelineItemKeys) {
|
||||
if (!knownKeysRef.current.has(key)) {
|
||||
newKeys.add(key);
|
||||
}
|
||||
}
|
||||
return newKeys;
|
||||
}, [isPaginationExpansion, timelineItemKeys]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInitializedRef.current) {
|
||||
isInitializedRef.current = true;
|
||||
}
|
||||
for (const key of timelineItemKeys) {
|
||||
knownKeysRef.current.add(key);
|
||||
}
|
||||
prevVisibleCountRef.current = visibleCount;
|
||||
}, [timelineItemKeys, visibleCount]);
|
||||
const newItemKeys = useNewItemKeys({
|
||||
itemKeys: timelineItemKeys,
|
||||
paginationKey: visibleCount,
|
||||
resetKey: teamName,
|
||||
});
|
||||
|
||||
const handleShowMore = (): void => {
|
||||
setVisibleCount((prev) => prev + MESSAGES_PAGE_SIZE);
|
||||
|
|
|
|||
56
src/renderer/components/team/activity/useNewItemKeys.ts
Normal file
56
src/renderer/components/team/activity/useNewItemKeys.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
interface UseNewItemKeysOptions {
|
||||
itemKeys: string[];
|
||||
paginationKey?: number;
|
||||
resetKey?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks which currently visible items are newly mounted since the last committed render.
|
||||
* Pagination expansions are treated as non-animated so "Show more" does not replay enter motion.
|
||||
*/
|
||||
export function useNewItemKeys({
|
||||
itemKeys,
|
||||
paginationKey = 0,
|
||||
resetKey,
|
||||
}: UseNewItemKeysOptions): Set<string> {
|
||||
const knownKeysRef = useRef<Set<string>>(new Set());
|
||||
const isInitializedRef = useRef(false);
|
||||
const prevPaginationKeyRef = useRef(paginationKey);
|
||||
|
||||
useEffect(() => {
|
||||
knownKeysRef.current = new Set();
|
||||
isInitializedRef.current = false;
|
||||
prevPaginationKeyRef.current = paginationKey;
|
||||
}, [resetKey]);
|
||||
|
||||
const isPaginationExpansion =
|
||||
isInitializedRef.current && paginationKey > prevPaginationKeyRef.current;
|
||||
|
||||
const newItemKeys = useMemo(() => {
|
||||
if (!isInitializedRef.current || isPaginationExpansion) {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
const next = new Set<string>();
|
||||
for (const key of itemKeys) {
|
||||
if (!knownKeysRef.current.has(key)) {
|
||||
next.add(key);
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}, [isPaginationExpansion, itemKeys]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInitializedRef.current) {
|
||||
isInitializedRef.current = true;
|
||||
}
|
||||
for (const key of itemKeys) {
|
||||
knownKeysRef.current.add(key);
|
||||
}
|
||||
prevPaginationKeyRef.current = paginationKey;
|
||||
}, [itemKeys, paginationKey]);
|
||||
|
||||
return newItemKeys;
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { CopyButton } from '@renderer/components/common/CopyButton';
|
||||
import { MarkdownViewer } from '@renderer/components/chat/viewers/MarkdownViewer';
|
||||
import { AnimatedHeightReveal } from '@renderer/components/team/activity/AnimatedHeightReveal';
|
||||
import { ReplyQuoteBlock } from '@renderer/components/team/activity/ReplyQuoteBlock';
|
||||
import { useNewItemKeys } from '@renderer/components/team/activity/useNewItemKeys';
|
||||
import { ImageLightbox } from '@renderer/components/team/attachments/ImageLightbox';
|
||||
import { MemberBadge } from '@renderer/components/team/MemberBadge';
|
||||
import { ExpandableContent } from '@renderer/components/ui/ExpandableContent';
|
||||
|
|
@ -90,9 +91,6 @@ export const TaskCommentsSection = ({
|
|||
const [replyTo, setReplyTo] = useState<{ author: string; text: string } | null>(null);
|
||||
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COMMENTS);
|
||||
const [previewImageUrl, setPreviewImageUrl] = useState<string | null>(null);
|
||||
const knownCommentIdsRef = useRef<Set<string>>(new Set());
|
||||
const isInitializedRef = useRef(false);
|
||||
const prevVisibleCountRef = useRef(INITIAL_VISIBLE_COMMENTS);
|
||||
|
||||
// Reset local UI state when team/task changes.
|
||||
useEffect(() => {
|
||||
|
|
@ -100,9 +98,6 @@ export const TaskCommentsSection = ({
|
|||
setVisibleCount(INITIAL_VISIBLE_COMMENTS);
|
||||
setReplyTo(null);
|
||||
setPreviewImageUrl(null);
|
||||
knownCommentIdsRef.current = new Set();
|
||||
isInitializedRef.current = false;
|
||||
prevVisibleCountRef.current = INITIAL_VISIBLE_COMMENTS;
|
||||
}, [teamName, taskId]);
|
||||
|
||||
const draft = useDraftPersistence({ key: `taskComment:${teamName}:${taskId}` });
|
||||
|
|
@ -130,33 +125,11 @@ export const TaskCommentsSection = ({
|
|||
() => visibleComments.map((comment) => comment.id),
|
||||
[visibleComments]
|
||||
);
|
||||
|
||||
const isPaginationExpansion =
|
||||
isInitializedRef.current && visibleCount > prevVisibleCountRef.current;
|
||||
|
||||
const newCommentIds = useMemo(() => {
|
||||
if (!isInitializedRef.current || isPaginationExpansion) {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
const next = new Set<string>();
|
||||
for (const id of visibleCommentIds) {
|
||||
if (!knownCommentIdsRef.current.has(id)) {
|
||||
next.add(id);
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}, [isPaginationExpansion, visibleCommentIds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInitializedRef.current) {
|
||||
isInitializedRef.current = true;
|
||||
}
|
||||
for (const id of visibleCommentIds) {
|
||||
knownCommentIdsRef.current.add(id);
|
||||
}
|
||||
prevVisibleCountRef.current = visibleCount;
|
||||
}, [visibleCommentIds, visibleCount]);
|
||||
const newCommentIds = useNewItemKeys({
|
||||
itemKeys: visibleCommentIds,
|
||||
paginationKey: visibleCount,
|
||||
resetKey: `${teamName}:${taskId}`,
|
||||
});
|
||||
|
||||
const mentionSuggestions = useMemo<MentionSuggestion[]>(
|
||||
() =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue