perf(renderer): memoize global task rows
This commit is contained in:
parent
0b0eb45784
commit
e5f2c262e8
1 changed files with 174 additions and 84 deletions
|
|
@ -190,6 +190,100 @@ function buildTaskTeamSummary(task: GlobalTask): TeamSummary {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TaskRowAction = (teamName: string, taskId: string) => void;
|
||||||
|
type TaskRowDeleteAction = (teamName: string, taskId: string) => void | Promise<void>;
|
||||||
|
type TaskDisplaySubjectResolver = (task: GlobalTask) => string | undefined;
|
||||||
|
|
||||||
|
interface GlobalTaskRowProps {
|
||||||
|
task: GlobalTask;
|
||||||
|
isPinned: boolean;
|
||||||
|
isArchived: boolean;
|
||||||
|
isNew: boolean;
|
||||||
|
teamOffline: boolean;
|
||||||
|
renamingKey: string | null;
|
||||||
|
hideTeamName?: boolean;
|
||||||
|
hideProjectName?: boolean;
|
||||||
|
showTeamName?: boolean;
|
||||||
|
onTogglePin: TaskRowAction;
|
||||||
|
onToggleArchive: TaskRowAction;
|
||||||
|
onMarkUnread: TaskRowAction;
|
||||||
|
onRename: TaskRowAction;
|
||||||
|
onDelete: TaskRowDeleteAction;
|
||||||
|
onRenameComplete: (teamName: string, taskId: string, newSubject: string) => void;
|
||||||
|
onRenameCancel: () => void;
|
||||||
|
getDisplaySubject: TaskDisplaySubjectResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GlobalTaskRow = memo(function GlobalTaskRow({
|
||||||
|
task,
|
||||||
|
isPinned,
|
||||||
|
isArchived,
|
||||||
|
isNew,
|
||||||
|
teamOffline,
|
||||||
|
renamingKey,
|
||||||
|
hideTeamName,
|
||||||
|
hideProjectName,
|
||||||
|
showTeamName,
|
||||||
|
onTogglePin,
|
||||||
|
onToggleArchive,
|
||||||
|
onMarkUnread,
|
||||||
|
onRename,
|
||||||
|
onDelete,
|
||||||
|
onRenameComplete,
|
||||||
|
onRenameCancel,
|
||||||
|
getDisplaySubject,
|
||||||
|
}: GlobalTaskRowProps): React.JSX.Element {
|
||||||
|
const taskRenamingKey = `${task.teamName}:${task.id}`;
|
||||||
|
const effectiveRenamingKey = renamingKey === taskRenamingKey ? renamingKey : null;
|
||||||
|
|
||||||
|
const handleTogglePin = useCallback(() => {
|
||||||
|
onTogglePin(task.teamName, task.id);
|
||||||
|
}, [onTogglePin, task.id, task.teamName]);
|
||||||
|
|
||||||
|
const handleToggleArchive = useCallback(() => {
|
||||||
|
onToggleArchive(task.teamName, task.id);
|
||||||
|
}, [onToggleArchive, task.id, task.teamName]);
|
||||||
|
|
||||||
|
const handleMarkUnread = useCallback(() => {
|
||||||
|
onMarkUnread(task.teamName, task.id);
|
||||||
|
}, [onMarkUnread, task.id, task.teamName]);
|
||||||
|
|
||||||
|
const handleRename = useCallback(() => {
|
||||||
|
onRename(task.teamName, task.id);
|
||||||
|
}, [onRename, task.id, task.teamName]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
void onDelete(task.teamName, task.id);
|
||||||
|
}, [onDelete, task.id, task.teamName]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TaskContextMenu
|
||||||
|
task={task}
|
||||||
|
isPinned={isPinned}
|
||||||
|
isArchived={isArchived}
|
||||||
|
onTogglePin={handleTogglePin}
|
||||||
|
onToggleArchive={handleToggleArchive}
|
||||||
|
onMarkUnread={handleMarkUnread}
|
||||||
|
onRename={handleRename}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
>
|
||||||
|
<AnimatedHeightReveal animate={isNew}>
|
||||||
|
<SidebarTaskItem
|
||||||
|
task={task}
|
||||||
|
hideTeamName={hideTeamName}
|
||||||
|
hideProjectName={hideProjectName}
|
||||||
|
showTeamName={showTeamName}
|
||||||
|
teamOffline={teamOffline}
|
||||||
|
renamingKey={effectiveRenamingKey}
|
||||||
|
onRenameComplete={onRenameComplete}
|
||||||
|
onRenameCancel={onRenameCancel}
|
||||||
|
getDisplaySubject={getDisplaySubject}
|
||||||
|
/>
|
||||||
|
</AnimatedHeightReveal>
|
||||||
|
</TaskContextMenu>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export const GlobalTaskList = memo(function GlobalTaskList({
|
export const GlobalTaskList = memo(function GlobalTaskList({
|
||||||
hideHeader = false,
|
hideHeader = false,
|
||||||
filters: externalFilters,
|
filters: externalFilters,
|
||||||
|
|
@ -425,6 +519,30 @@ export const GlobalTaskList = memo(function GlobalTaskList({
|
||||||
markTaskUnread(teamName, taskId);
|
markTaskUnread(teamName, taskId);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleToggleTaskPin = useCallback(
|
||||||
|
(teamName: string, taskId: string): void => {
|
||||||
|
taskLocalState.togglePin(teamName, taskId);
|
||||||
|
},
|
||||||
|
[taskLocalState]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleToggleTaskArchive = useCallback(
|
||||||
|
(teamName: string, taskId: string): void => {
|
||||||
|
taskLocalState.toggleArchive(teamName, taskId);
|
||||||
|
},
|
||||||
|
[taskLocalState]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleStartTaskRename = useCallback((teamName: string, taskId: string): void => {
|
||||||
|
setRenamingTaskKey(`${teamName}:${taskId}`);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getTaskDisplaySubject = useCallback(
|
||||||
|
(task: GlobalTask): string | undefined =>
|
||||||
|
taskLocalState.getRenamedSubject(task.teamName, task.id),
|
||||||
|
[taskLocalState]
|
||||||
|
);
|
||||||
|
|
||||||
const handleDeleteTask = useCallback(
|
const handleDeleteTask = useCallback(
|
||||||
async (teamName: string, taskId: string): Promise<void> => {
|
async (teamName: string, taskId: string): Promise<void> => {
|
||||||
const confirmed = await confirm({
|
const confirmed = await confirm({
|
||||||
|
|
@ -713,29 +831,24 @@ export const GlobalTaskList = memo(function GlobalTaskList({
|
||||||
<span className="text-[11px] text-text-muted">{t('tasksPanel.pinned')}</span>
|
<span className="text-[11px] text-text-muted">{t('tasksPanel.pinned')}</span>
|
||||||
</div>
|
</div>
|
||||||
{sortTasksByFreshness(pinnedTasks).map((task) => (
|
{sortTasksByFreshness(pinnedTasks).map((task) => (
|
||||||
<TaskContextMenu
|
<GlobalTaskRow
|
||||||
key={`pinned-${task.teamName}-${task.id}`}
|
key={`pinned-${task.teamName}-${task.id}`}
|
||||||
task={task}
|
task={task}
|
||||||
isPinned={true}
|
isPinned={true}
|
||||||
isArchived={false}
|
isArchived={false}
|
||||||
onTogglePin={() => taskLocalState.togglePin(task.teamName, task.id)}
|
isNew={isNewTask(task)}
|
||||||
onToggleArchive={() => taskLocalState.toggleArchive(task.teamName, task.id)}
|
showTeamName
|
||||||
onMarkUnread={() => handleMarkTaskUnread(task.teamName, task.id)}
|
teamOffline={offlineTeamNames.has(task.teamName)}
|
||||||
onRename={() => setRenamingTaskKey(`${task.teamName}:${task.id}`)}
|
renamingKey={renamingTaskKey}
|
||||||
onDelete={() => handleDeleteTask(task.teamName, task.id)}
|
onTogglePin={handleToggleTaskPin}
|
||||||
>
|
onToggleArchive={handleToggleTaskArchive}
|
||||||
<AnimatedHeightReveal animate={isNewTask(task)}>
|
onMarkUnread={handleMarkTaskUnread}
|
||||||
<SidebarTaskItem
|
onRename={handleStartTaskRename}
|
||||||
task={task}
|
onDelete={handleDeleteTask}
|
||||||
showTeamName
|
onRenameComplete={handleRenameComplete}
|
||||||
teamOffline={offlineTeamNames.has(task.teamName)}
|
onRenameCancel={handleRenameCancel}
|
||||||
renamingKey={renamingTaskKey}
|
getDisplaySubject={getTaskDisplaySubject}
|
||||||
onRenameComplete={handleRenameComplete}
|
/>
|
||||||
onRenameCancel={handleRenameCancel}
|
|
||||||
getDisplaySubject={(t) => taskLocalState.getRenamedSubject(t.teamName, t.id)}
|
|
||||||
/>
|
|
||||||
</AnimatedHeightReveal>
|
|
||||||
</TaskContextMenu>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -821,29 +934,24 @@ export const GlobalTaskList = memo(function GlobalTaskList({
|
||||||
|
|
||||||
{groupingMode === 'none' &&
|
{groupingMode === 'none' &&
|
||||||
sortedFlat.map((task) => (
|
sortedFlat.map((task) => (
|
||||||
<TaskContextMenu
|
<GlobalTaskRow
|
||||||
key={`${task.teamName}-${task.id}`}
|
key={`${task.teamName}-${task.id}`}
|
||||||
task={task}
|
task={task}
|
||||||
isPinned={taskLocalState.isPinned(task.teamName, task.id)}
|
isPinned={taskLocalState.isPinned(task.teamName, task.id)}
|
||||||
isArchived={taskLocalState.isArchived(task.teamName, task.id)}
|
isArchived={taskLocalState.isArchived(task.teamName, task.id)}
|
||||||
onTogglePin={() => taskLocalState.togglePin(task.teamName, task.id)}
|
isNew={isNewTask(task)}
|
||||||
onToggleArchive={() => taskLocalState.toggleArchive(task.teamName, task.id)}
|
showTeamName
|
||||||
onMarkUnread={() => handleMarkTaskUnread(task.teamName, task.id)}
|
teamOffline={offlineTeamNames.has(task.teamName)}
|
||||||
onRename={() => setRenamingTaskKey(`${task.teamName}:${task.id}`)}
|
renamingKey={renamingTaskKey}
|
||||||
onDelete={() => handleDeleteTask(task.teamName, task.id)}
|
onTogglePin={handleToggleTaskPin}
|
||||||
>
|
onToggleArchive={handleToggleTaskArchive}
|
||||||
<AnimatedHeightReveal animate={isNewTask(task)}>
|
onMarkUnread={handleMarkTaskUnread}
|
||||||
<SidebarTaskItem
|
onRename={handleStartTaskRename}
|
||||||
task={task}
|
onDelete={handleDeleteTask}
|
||||||
showTeamName
|
onRenameComplete={handleRenameComplete}
|
||||||
teamOffline={offlineTeamNames.has(task.teamName)}
|
onRenameCancel={handleRenameCancel}
|
||||||
renamingKey={renamingTaskKey}
|
getDisplaySubject={getTaskDisplaySubject}
|
||||||
onRenameComplete={handleRenameComplete}
|
/>
|
||||||
onRenameCancel={handleRenameCancel}
|
|
||||||
getDisplaySubject={(t) => taskLocalState.getRenamedSubject(t.teamName, t.id)}
|
|
||||||
/>
|
|
||||||
</AnimatedHeightReveal>
|
|
||||||
</TaskContextMenu>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{groupingMode === 'project' &&
|
{groupingMode === 'project' &&
|
||||||
|
|
@ -907,33 +1015,24 @@ export const GlobalTaskList = memo(function GlobalTaskList({
|
||||||
{t('tasksPanel.teamLabel', { team: task.teamDisplayName })}
|
{t('tasksPanel.teamLabel', { team: task.teamDisplayName })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<TaskContextMenu
|
<GlobalTaskRow
|
||||||
task={task}
|
task={task}
|
||||||
isPinned={taskLocalState.isPinned(task.teamName, task.id)}
|
isPinned={taskLocalState.isPinned(task.teamName, task.id)}
|
||||||
isArchived={taskLocalState.isArchived(task.teamName, task.id)}
|
isArchived={taskLocalState.isArchived(task.teamName, task.id)}
|
||||||
onTogglePin={() => taskLocalState.togglePin(task.teamName, task.id)}
|
isNew={isNewTask(task)}
|
||||||
onToggleArchive={() =>
|
hideTeamName
|
||||||
taskLocalState.toggleArchive(task.teamName, task.id)
|
hideProjectName
|
||||||
}
|
teamOffline={offlineTeamNames.has(task.teamName)}
|
||||||
onMarkUnread={() => handleMarkTaskUnread(task.teamName, task.id)}
|
renamingKey={renamingTaskKey}
|
||||||
onRename={() => setRenamingTaskKey(`${task.teamName}:${task.id}`)}
|
onTogglePin={handleToggleTaskPin}
|
||||||
onDelete={() => handleDeleteTask(task.teamName, task.id)}
|
onToggleArchive={handleToggleTaskArchive}
|
||||||
>
|
onMarkUnread={handleMarkTaskUnread}
|
||||||
<AnimatedHeightReveal animate={isNewTask(task)}>
|
onRename={handleStartTaskRename}
|
||||||
<SidebarTaskItem
|
onDelete={handleDeleteTask}
|
||||||
task={task}
|
onRenameComplete={handleRenameComplete}
|
||||||
hideTeamName
|
onRenameCancel={handleRenameCancel}
|
||||||
hideProjectName
|
getDisplaySubject={getTaskDisplaySubject}
|
||||||
teamOffline={offlineTeamNames.has(task.teamName)}
|
/>
|
||||||
renamingKey={renamingTaskKey}
|
|
||||||
onRenameComplete={handleRenameComplete}
|
|
||||||
onRenameCancel={handleRenameCancel}
|
|
||||||
getDisplaySubject={(t) =>
|
|
||||||
taskLocalState.getRenamedSubject(t.teamName, t.id)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</AnimatedHeightReveal>
|
|
||||||
</TaskContextMenu>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -1016,31 +1115,22 @@ export const GlobalTaskList = memo(function GlobalTaskList({
|
||||||
{t('tasksPanel.teamLabel', { team: task.teamDisplayName })}
|
{t('tasksPanel.teamLabel', { team: task.teamDisplayName })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<TaskContextMenu
|
<GlobalTaskRow
|
||||||
task={task}
|
task={task}
|
||||||
isPinned={taskLocalState.isPinned(task.teamName, task.id)}
|
isPinned={taskLocalState.isPinned(task.teamName, task.id)}
|
||||||
isArchived={taskLocalState.isArchived(task.teamName, task.id)}
|
isArchived={taskLocalState.isArchived(task.teamName, task.id)}
|
||||||
onTogglePin={() => taskLocalState.togglePin(task.teamName, task.id)}
|
isNew={isNewTask(task)}
|
||||||
onToggleArchive={() =>
|
teamOffline={offlineTeamNames.has(task.teamName)}
|
||||||
taskLocalState.toggleArchive(task.teamName, task.id)
|
renamingKey={renamingTaskKey}
|
||||||
}
|
onTogglePin={handleToggleTaskPin}
|
||||||
onMarkUnread={() => handleMarkTaskUnread(task.teamName, task.id)}
|
onToggleArchive={handleToggleTaskArchive}
|
||||||
onRename={() => setRenamingTaskKey(`${task.teamName}:${task.id}`)}
|
onMarkUnread={handleMarkTaskUnread}
|
||||||
onDelete={() => handleDeleteTask(task.teamName, task.id)}
|
onRename={handleStartTaskRename}
|
||||||
>
|
onDelete={handleDeleteTask}
|
||||||
<AnimatedHeightReveal animate={isNewTask(task)}>
|
onRenameComplete={handleRenameComplete}
|
||||||
<SidebarTaskItem
|
onRenameCancel={handleRenameCancel}
|
||||||
task={task}
|
getDisplaySubject={getTaskDisplaySubject}
|
||||||
teamOffline={offlineTeamNames.has(task.teamName)}
|
/>
|
||||||
renamingKey={renamingTaskKey}
|
|
||||||
onRenameComplete={handleRenameComplete}
|
|
||||||
onRenameCancel={handleRenameCancel}
|
|
||||||
getDisplaySubject={(t) =>
|
|
||||||
taskLocalState.getRenamedSubject(t.teamName, t.id)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</AnimatedHeightReveal>
|
|
||||||
</TaskContextMenu>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue