mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)
* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-2): 格式化 commands (79 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-5): 格式化 components其余 + hooks + tools (232 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md - README.md: 大幅重写,更详细版本历史和配置示例 - Run.ps1: 新增 Windows 启动脚本 - TODO.md: 新增包完成清单 - V6.md: 删除(架构重构规划已不适用) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复以前的问题 * fix: 修复 login 面板的问题 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,103 +1,117 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import figures from 'figures';
|
||||
import * as React from 'react';
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
||||
import { stringWidth } from '../ink/stringWidth.js';
|
||||
import { Box, Text } from '../ink.js';
|
||||
import { useAppState } from '../state/AppState.js';
|
||||
import { isInProcessTeammateTask } from '../tasks/InProcessTeammateTask/types.js';
|
||||
import { AGENT_COLOR_TO_THEME_COLOR, type AgentColorName } from '../tools/AgentTool/agentColorManager.js';
|
||||
import { isAgentSwarmsEnabled } from '../utils/agentSwarmsEnabled.js';
|
||||
import { count } from '../utils/array.js';
|
||||
import { summarizeRecentActivities } from '../utils/collapseReadSearch.js';
|
||||
import { truncateToWidth } from '../utils/format.js';
|
||||
import { isTodoV2Enabled, type Task } from '../utils/tasks.js';
|
||||
import type { Theme } from '../utils/theme.js';
|
||||
import ThemedText from './design-system/ThemedText.js';
|
||||
import figures from 'figures'
|
||||
import * as React from 'react'
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js'
|
||||
import { stringWidth } from '../ink/stringWidth.js'
|
||||
import { Box, Text } from '../ink.js'
|
||||
import { useAppState } from '../state/AppState.js'
|
||||
import { isInProcessTeammateTask } from '../tasks/InProcessTeammateTask/types.js'
|
||||
import {
|
||||
AGENT_COLOR_TO_THEME_COLOR,
|
||||
type AgentColorName,
|
||||
} from '../tools/AgentTool/agentColorManager.js'
|
||||
import { isAgentSwarmsEnabled } from '../utils/agentSwarmsEnabled.js'
|
||||
import { count } from '../utils/array.js'
|
||||
import { summarizeRecentActivities } from '../utils/collapseReadSearch.js'
|
||||
import { truncateToWidth } from '../utils/format.js'
|
||||
import { isTodoV2Enabled, type Task } from '../utils/tasks.js'
|
||||
import type { Theme } from '../utils/theme.js'
|
||||
import ThemedText from './design-system/ThemedText.js'
|
||||
|
||||
type Props = {
|
||||
tasks: Task[];
|
||||
isStandalone?: boolean;
|
||||
};
|
||||
const RECENT_COMPLETED_TTL_MS = 30_000;
|
||||
function byIdAsc(a: Task, b: Task): number {
|
||||
const aNum = parseInt(a.id, 10);
|
||||
const bNum = parseInt(b.id, 10);
|
||||
if (!isNaN(aNum) && !isNaN(bNum)) {
|
||||
return aNum - bNum;
|
||||
}
|
||||
return a.id.localeCompare(b.id);
|
||||
tasks: Task[]
|
||||
isStandalone?: boolean
|
||||
}
|
||||
|
||||
const RECENT_COMPLETED_TTL_MS = 30_000
|
||||
|
||||
function byIdAsc(a: Task, b: Task): number {
|
||||
const aNum = parseInt(a.id, 10)
|
||||
const bNum = parseInt(b.id, 10)
|
||||
if (!isNaN(aNum) && !isNaN(bNum)) {
|
||||
return aNum - bNum
|
||||
}
|
||||
return a.id.localeCompare(b.id)
|
||||
}
|
||||
|
||||
export function TaskListV2({
|
||||
tasks,
|
||||
isStandalone = false
|
||||
isStandalone = false,
|
||||
}: Props): React.ReactNode {
|
||||
const teamContext = useAppState(s => s.teamContext);
|
||||
const appStateTasks = useAppState(s_0 => s_0.tasks);
|
||||
const [, forceUpdate] = React.useState(0);
|
||||
const {
|
||||
rows,
|
||||
columns
|
||||
} = useTerminalSize();
|
||||
const teamContext = useAppState(s => s.teamContext)
|
||||
const appStateTasks = useAppState(s => s.tasks)
|
||||
const [, forceUpdate] = React.useState(0)
|
||||
const { rows, columns } = useTerminalSize()
|
||||
|
||||
// Track when each task was last observed transitioning to completed
|
||||
const completionTimestampsRef = React.useRef(new Map<string, number>());
|
||||
const previousCompletedIdsRef = React.useRef<Set<string> | null>(null);
|
||||
const completionTimestampsRef = React.useRef(new Map<string, number>())
|
||||
const previousCompletedIdsRef = React.useRef<Set<string> | null>(null)
|
||||
if (previousCompletedIdsRef.current === null) {
|
||||
previousCompletedIdsRef.current = new Set(tasks.filter(t => t.status === 'completed').map(t_0 => t_0.id));
|
||||
previousCompletedIdsRef.current = new Set(
|
||||
tasks.filter(t => t.status === 'completed').map(t => t.id),
|
||||
)
|
||||
}
|
||||
const maxDisplay = rows <= 10 ? 0 : Math.min(10, Math.max(3, rows - 14));
|
||||
const maxDisplay = rows <= 10 ? 0 : Math.min(10, Math.max(3, rows - 14))
|
||||
|
||||
// Update completion timestamps: reset when a task transitions to completed
|
||||
const currentCompletedIds = new Set(tasks.filter(t_1 => t_1.status === 'completed').map(t_2 => t_2.id));
|
||||
const now = Date.now();
|
||||
const currentCompletedIds = new Set(
|
||||
tasks.filter(t => t.status === 'completed').map(t => t.id),
|
||||
)
|
||||
const now = Date.now()
|
||||
for (const id of currentCompletedIds) {
|
||||
if (!previousCompletedIdsRef.current.has(id)) {
|
||||
completionTimestampsRef.current.set(id, now);
|
||||
completionTimestampsRef.current.set(id, now)
|
||||
}
|
||||
}
|
||||
for (const id_0 of completionTimestampsRef.current.keys()) {
|
||||
if (!currentCompletedIds.has(id_0)) {
|
||||
completionTimestampsRef.current.delete(id_0);
|
||||
for (const id of completionTimestampsRef.current.keys()) {
|
||||
if (!currentCompletedIds.has(id)) {
|
||||
completionTimestampsRef.current.delete(id)
|
||||
}
|
||||
}
|
||||
previousCompletedIdsRef.current = currentCompletedIds;
|
||||
previousCompletedIdsRef.current = currentCompletedIds
|
||||
|
||||
// Schedule re-render when the next recent completion expires.
|
||||
// Depend on `tasks` so the timer is only reset when the task list changes,
|
||||
// not on every render (which was causing unnecessary work).
|
||||
React.useEffect(() => {
|
||||
if (completionTimestampsRef.current.size === 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const currentNow = Date.now();
|
||||
let earliestExpiry = Infinity;
|
||||
const currentNow = Date.now()
|
||||
let earliestExpiry = Infinity
|
||||
for (const ts of completionTimestampsRef.current.values()) {
|
||||
const expiry = ts + RECENT_COMPLETED_TTL_MS;
|
||||
const expiry = ts + RECENT_COMPLETED_TTL_MS
|
||||
if (expiry > currentNow && expiry < earliestExpiry) {
|
||||
earliestExpiry = expiry;
|
||||
earliestExpiry = expiry
|
||||
}
|
||||
}
|
||||
if (earliestExpiry === Infinity) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const timer = setTimeout(forceUpdate_0 => forceUpdate_0((n: number) => n + 1), earliestExpiry - currentNow, forceUpdate);
|
||||
return () => clearTimeout(timer);
|
||||
}, [tasks]);
|
||||
const timer = setTimeout(
|
||||
forceUpdate => forceUpdate((n: number) => n + 1),
|
||||
earliestExpiry - currentNow,
|
||||
forceUpdate,
|
||||
)
|
||||
return () => clearTimeout(timer)
|
||||
}, [tasks])
|
||||
|
||||
if (!isTodoV2Enabled()) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
if (tasks.length === 0) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
// Build a map of teammate name -> theme color
|
||||
const teammateColors: Record<string, keyof Theme> = {};
|
||||
const teammateColors: Record<string, keyof Theme> = {}
|
||||
if (isAgentSwarmsEnabled() && teamContext?.teammates) {
|
||||
for (const teammate of Object.values(teamContext.teammates)) {
|
||||
if (teammate.color) {
|
||||
const themeColor = AGENT_COLOR_TO_THEME_COLOR[teammate.color as AgentColorName];
|
||||
const themeColor =
|
||||
AGENT_COLOR_TO_THEME_COLOR[teammate.color as AgentColorName]
|
||||
if (themeColor) {
|
||||
teammateColors[teammate.name] = themeColor;
|
||||
teammateColors[teammate.name] = themeColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,270 +122,240 @@ export function TaskListV2({
|
||||
// task owners match regardless of which format the model used.
|
||||
// Rolls up consecutive search/read tool uses into a compact summary.
|
||||
// Also track which teammates are still running (not shut down).
|
||||
const teammateActivity: Record<string, string> = {};
|
||||
const activeTeammates = new Set<string>();
|
||||
const teammateActivity: Record<string, string> = {}
|
||||
const activeTeammates = new Set<string>()
|
||||
if (isAgentSwarmsEnabled()) {
|
||||
for (const bgTask of Object.values(appStateTasks)) {
|
||||
if (isInProcessTeammateTask(bgTask) && bgTask.status === 'running') {
|
||||
activeTeammates.add(bgTask.identity.agentName);
|
||||
activeTeammates.add(bgTask.identity.agentId);
|
||||
const activities = bgTask.progress?.recentActivities;
|
||||
const desc = (activities && summarizeRecentActivities(activities)) ?? bgTask.progress?.lastActivity?.activityDescription;
|
||||
activeTeammates.add(bgTask.identity.agentName)
|
||||
activeTeammates.add(bgTask.identity.agentId)
|
||||
const activities = bgTask.progress?.recentActivities
|
||||
const desc =
|
||||
(activities && summarizeRecentActivities(activities)) ??
|
||||
bgTask.progress?.lastActivity?.activityDescription
|
||||
if (desc) {
|
||||
teammateActivity[bgTask.identity.agentName] = desc;
|
||||
teammateActivity[bgTask.identity.agentId] = desc;
|
||||
teammateActivity[bgTask.identity.agentName] = desc
|
||||
teammateActivity[bgTask.identity.agentId] = desc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get task counts for display
|
||||
const completedCount = count(tasks, t_3 => t_3.status === 'completed');
|
||||
const pendingCount = count(tasks, t_4 => t_4.status === 'pending');
|
||||
const inProgressCount = tasks.length - completedCount - pendingCount;
|
||||
const completedCount = count(tasks, t => t.status === 'completed')
|
||||
const pendingCount = count(tasks, t => t.status === 'pending')
|
||||
const inProgressCount = tasks.length - completedCount - pendingCount
|
||||
// Unresolved tasks (open or in_progress) block dependent tasks
|
||||
const unresolvedTaskIds = new Set(tasks.filter(t_5 => t_5.status !== 'completed').map(t_6 => t_6.id));
|
||||
const unresolvedTaskIds = new Set(
|
||||
tasks.filter(t => t.status !== 'completed').map(t => t.id),
|
||||
)
|
||||
|
||||
// Check if we need to truncate
|
||||
const needsTruncation = tasks.length > maxDisplay;
|
||||
let visibleTasks: Task[];
|
||||
let hiddenTasks: Task[];
|
||||
const needsTruncation = tasks.length > maxDisplay
|
||||
|
||||
let visibleTasks: Task[]
|
||||
let hiddenTasks: Task[]
|
||||
|
||||
if (needsTruncation) {
|
||||
// Prioritize: recently completed (within 30s), in-progress, pending, older completed
|
||||
const recentCompleted: Task[] = [];
|
||||
const olderCompleted: Task[] = [];
|
||||
for (const task of tasks.filter(t_7 => t_7.status === 'completed')) {
|
||||
const ts_0 = completionTimestampsRef.current.get(task.id);
|
||||
if (ts_0 && now - ts_0 < RECENT_COMPLETED_TTL_MS) {
|
||||
recentCompleted.push(task);
|
||||
const recentCompleted: Task[] = []
|
||||
const olderCompleted: Task[] = []
|
||||
for (const task of tasks.filter(t => t.status === 'completed')) {
|
||||
const ts = completionTimestampsRef.current.get(task.id)
|
||||
if (ts && now - ts < RECENT_COMPLETED_TTL_MS) {
|
||||
recentCompleted.push(task)
|
||||
} else {
|
||||
olderCompleted.push(task);
|
||||
olderCompleted.push(task)
|
||||
}
|
||||
}
|
||||
recentCompleted.sort(byIdAsc);
|
||||
olderCompleted.sort(byIdAsc);
|
||||
const inProgress = tasks.filter(t_8 => t_8.status === 'in_progress').sort(byIdAsc);
|
||||
const pending = tasks.filter(t_9 => t_9.status === 'pending').sort((a, b) => {
|
||||
const aBlocked = a.blockedBy.some(id_1 => unresolvedTaskIds.has(id_1));
|
||||
const bBlocked = b.blockedBy.some(id_2 => unresolvedTaskIds.has(id_2));
|
||||
if (aBlocked !== bBlocked) {
|
||||
return aBlocked ? 1 : -1;
|
||||
}
|
||||
return byIdAsc(a, b);
|
||||
});
|
||||
const prioritized = [...recentCompleted, ...inProgress, ...pending, ...olderCompleted];
|
||||
visibleTasks = prioritized.slice(0, maxDisplay);
|
||||
hiddenTasks = prioritized.slice(maxDisplay);
|
||||
recentCompleted.sort(byIdAsc)
|
||||
olderCompleted.sort(byIdAsc)
|
||||
const inProgress = tasks
|
||||
.filter(t => t.status === 'in_progress')
|
||||
.sort(byIdAsc)
|
||||
const pending = tasks
|
||||
.filter(t => t.status === 'pending')
|
||||
.sort((a, b) => {
|
||||
const aBlocked = a.blockedBy.some(id => unresolvedTaskIds.has(id))
|
||||
const bBlocked = b.blockedBy.some(id => unresolvedTaskIds.has(id))
|
||||
if (aBlocked !== bBlocked) {
|
||||
return aBlocked ? 1 : -1
|
||||
}
|
||||
return byIdAsc(a, b)
|
||||
})
|
||||
|
||||
const prioritized = [
|
||||
...recentCompleted,
|
||||
...inProgress,
|
||||
...pending,
|
||||
...olderCompleted,
|
||||
]
|
||||
visibleTasks = prioritized.slice(0, maxDisplay)
|
||||
hiddenTasks = prioritized.slice(maxDisplay)
|
||||
} else {
|
||||
// No truncation needed — sort by ID for stable ordering
|
||||
visibleTasks = [...tasks].sort(byIdAsc);
|
||||
hiddenTasks = [];
|
||||
visibleTasks = [...tasks].sort(byIdAsc)
|
||||
hiddenTasks = []
|
||||
}
|
||||
let hiddenSummary = '';
|
||||
|
||||
let hiddenSummary = ''
|
||||
if (hiddenTasks.length > 0) {
|
||||
const parts: string[] = [];
|
||||
const hiddenPending = count(hiddenTasks, t_10 => t_10.status === 'pending');
|
||||
const hiddenInProgress = count(hiddenTasks, t_11 => t_11.status === 'in_progress');
|
||||
const hiddenCompleted = count(hiddenTasks, t_12 => t_12.status === 'completed');
|
||||
const parts: string[] = []
|
||||
const hiddenPending = count(hiddenTasks, t => t.status === 'pending')
|
||||
const hiddenInProgress = count(hiddenTasks, t => t.status === 'in_progress')
|
||||
const hiddenCompleted = count(hiddenTasks, t => t.status === 'completed')
|
||||
if (hiddenInProgress > 0) {
|
||||
parts.push(`${hiddenInProgress} in progress`);
|
||||
parts.push(`${hiddenInProgress} in progress`)
|
||||
}
|
||||
if (hiddenPending > 0) {
|
||||
parts.push(`${hiddenPending} pending`);
|
||||
parts.push(`${hiddenPending} pending`)
|
||||
}
|
||||
if (hiddenCompleted > 0) {
|
||||
parts.push(`${hiddenCompleted} completed`);
|
||||
parts.push(`${hiddenCompleted} completed`)
|
||||
}
|
||||
hiddenSummary = ` … +${parts.join(', ')}`;
|
||||
hiddenSummary = ` … +${parts.join(', ')}`
|
||||
}
|
||||
const content = <>
|
||||
{visibleTasks.map(task_0 => <TaskItem key={task_0.id} task={task_0} ownerColor={task_0.owner ? teammateColors[task_0.owner] : undefined} openBlockers={task_0.blockedBy.filter(id_3 => unresolvedTaskIds.has(id_3))} activity={task_0.owner ? teammateActivity[task_0.owner] : undefined} ownerActive={task_0.owner ? activeTeammates.has(task_0.owner) : false} columns={columns} />)}
|
||||
|
||||
const content = (
|
||||
<>
|
||||
{visibleTasks.map(task => (
|
||||
<TaskItem
|
||||
key={task.id}
|
||||
task={task}
|
||||
ownerColor={task.owner ? teammateColors[task.owner] : undefined}
|
||||
openBlockers={task.blockedBy.filter(id => unresolvedTaskIds.has(id))}
|
||||
activity={task.owner ? teammateActivity[task.owner] : undefined}
|
||||
ownerActive={task.owner ? activeTeammates.has(task.owner) : false}
|
||||
columns={columns}
|
||||
/>
|
||||
))}
|
||||
{maxDisplay > 0 && hiddenSummary && <Text dimColor>{hiddenSummary}</Text>}
|
||||
</>;
|
||||
</>
|
||||
)
|
||||
|
||||
if (isStandalone) {
|
||||
return <Box flexDirection="column" marginTop={1} marginLeft={2}>
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1} marginLeft={2}>
|
||||
<Box>
|
||||
<Text dimColor>
|
||||
<Text bold>{tasks.length}</Text>
|
||||
{' tasks ('}
|
||||
<Text bold>{completedCount}</Text>
|
||||
{' done, '}
|
||||
{inProgressCount > 0 && <>
|
||||
{inProgressCount > 0 && (
|
||||
<>
|
||||
<Text bold>{inProgressCount}</Text>
|
||||
{' in progress, '}
|
||||
</>}
|
||||
</>
|
||||
)}
|
||||
<Text bold>{pendingCount}</Text>
|
||||
{' open)'}
|
||||
</Text>
|
||||
</Box>
|
||||
{content}
|
||||
</Box>;
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
return <Box flexDirection="column">{content}</Box>;
|
||||
|
||||
return <Box flexDirection="column">{content}</Box>
|
||||
}
|
||||
|
||||
type TaskItemProps = {
|
||||
task: Task;
|
||||
ownerColor?: keyof Theme;
|
||||
openBlockers: string[];
|
||||
activity?: string;
|
||||
ownerActive: boolean;
|
||||
columns: number;
|
||||
};
|
||||
task: Task
|
||||
ownerColor?: keyof Theme
|
||||
openBlockers: string[]
|
||||
activity?: string
|
||||
ownerActive: boolean
|
||||
columns: number
|
||||
}
|
||||
|
||||
function getTaskIcon(status: Task['status']): {
|
||||
icon: string;
|
||||
color: keyof Theme | undefined;
|
||||
icon: string
|
||||
color: keyof Theme | undefined
|
||||
} {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return {
|
||||
icon: figures.tick,
|
||||
color: 'success'
|
||||
};
|
||||
return { icon: figures.tick, color: 'success' }
|
||||
case 'in_progress':
|
||||
return {
|
||||
icon: figures.squareSmallFilled,
|
||||
color: 'claude'
|
||||
};
|
||||
return { icon: figures.squareSmallFilled, color: 'claude' }
|
||||
case 'pending':
|
||||
return {
|
||||
icon: figures.squareSmall,
|
||||
color: undefined
|
||||
};
|
||||
return { icon: figures.squareSmall, color: undefined }
|
||||
}
|
||||
}
|
||||
function TaskItem(t0) {
|
||||
const $ = _c(37);
|
||||
const {
|
||||
task,
|
||||
ownerColor,
|
||||
openBlockers,
|
||||
activity,
|
||||
ownerActive,
|
||||
columns
|
||||
} = t0;
|
||||
const isCompleted = task.status === "completed";
|
||||
const isInProgress = task.status === "in_progress";
|
||||
const isBlocked = openBlockers.length > 0;
|
||||
let t1;
|
||||
if ($[0] !== task.status) {
|
||||
t1 = getTaskIcon(task.status);
|
||||
$[0] = task.status;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const {
|
||||
icon,
|
||||
color
|
||||
} = t1;
|
||||
const showActivity = isInProgress && !isBlocked && activity;
|
||||
const showOwner = columns >= 60 && task.owner && ownerActive;
|
||||
let t2;
|
||||
if ($[2] !== showOwner || $[3] !== task.owner) {
|
||||
t2 = showOwner ? stringWidth(` (@${task.owner})`) : 0;
|
||||
$[2] = showOwner;
|
||||
$[3] = task.owner;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
const ownerWidth = t2;
|
||||
const maxSubjectWidth = Math.max(15, columns - 15 - ownerWidth);
|
||||
let t3;
|
||||
if ($[5] !== maxSubjectWidth || $[6] !== task.subject) {
|
||||
t3 = truncateToWidth(task.subject, maxSubjectWidth);
|
||||
$[5] = maxSubjectWidth;
|
||||
$[6] = task.subject;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
}
|
||||
const displaySubject = t3;
|
||||
const maxActivityWidth = Math.max(15, columns - 15);
|
||||
let t4;
|
||||
if ($[8] !== activity || $[9] !== maxActivityWidth) {
|
||||
t4 = activity ? truncateToWidth(activity, maxActivityWidth) : undefined;
|
||||
$[8] = activity;
|
||||
$[9] = maxActivityWidth;
|
||||
$[10] = t4;
|
||||
} else {
|
||||
t4 = $[10];
|
||||
}
|
||||
const displayActivity = t4;
|
||||
let t5;
|
||||
if ($[11] !== color || $[12] !== icon) {
|
||||
t5 = <Text color={color}>{icon} </Text>;
|
||||
$[11] = color;
|
||||
$[12] = icon;
|
||||
$[13] = t5;
|
||||
} else {
|
||||
t5 = $[13];
|
||||
}
|
||||
const t6 = isCompleted || isBlocked;
|
||||
let t7;
|
||||
if ($[14] !== displaySubject || $[15] !== isCompleted || $[16] !== isInProgress || $[17] !== t6) {
|
||||
t7 = <Text bold={isInProgress} strikethrough={isCompleted} dimColor={t6}>{displaySubject}</Text>;
|
||||
$[14] = displaySubject;
|
||||
$[15] = isCompleted;
|
||||
$[16] = isInProgress;
|
||||
$[17] = t6;
|
||||
$[18] = t7;
|
||||
} else {
|
||||
t7 = $[18];
|
||||
}
|
||||
let t8;
|
||||
if ($[19] !== ownerColor || $[20] !== showOwner || $[21] !== task.owner) {
|
||||
t8 = showOwner && <Text dimColor={true}>{" ("}{ownerColor ? <ThemedText color={ownerColor}>@{task.owner}</ThemedText> : `@${task.owner}`}{")"}</Text>;
|
||||
$[19] = ownerColor;
|
||||
$[20] = showOwner;
|
||||
$[21] = task.owner;
|
||||
$[22] = t8;
|
||||
} else {
|
||||
t8 = $[22];
|
||||
}
|
||||
let t9;
|
||||
if ($[23] !== isBlocked || $[24] !== openBlockers) {
|
||||
t9 = isBlocked && <Text dimColor={true}>{" "}{figures.pointerSmall} blocked by{" "}{[...openBlockers].sort(_temp).map(_temp2).join(", ")}</Text>;
|
||||
$[23] = isBlocked;
|
||||
$[24] = openBlockers;
|
||||
$[25] = t9;
|
||||
} else {
|
||||
t9 = $[25];
|
||||
}
|
||||
let t10;
|
||||
if ($[26] !== t5 || $[27] !== t7 || $[28] !== t8 || $[29] !== t9) {
|
||||
t10 = <Box>{t5}{t7}{t8}{t9}</Box>;
|
||||
$[26] = t5;
|
||||
$[27] = t7;
|
||||
$[28] = t8;
|
||||
$[29] = t9;
|
||||
$[30] = t10;
|
||||
} else {
|
||||
t10 = $[30];
|
||||
}
|
||||
let t11;
|
||||
if ($[31] !== displayActivity || $[32] !== showActivity) {
|
||||
t11 = showActivity && displayActivity && <Box><Text dimColor={true}>{" "}{displayActivity}{figures.ellipsis}</Text></Box>;
|
||||
$[31] = displayActivity;
|
||||
$[32] = showActivity;
|
||||
$[33] = t11;
|
||||
} else {
|
||||
t11 = $[33];
|
||||
}
|
||||
let t12;
|
||||
if ($[34] !== t10 || $[35] !== t11) {
|
||||
t12 = <Box flexDirection="column">{t10}{t11}</Box>;
|
||||
$[34] = t10;
|
||||
$[35] = t11;
|
||||
$[36] = t12;
|
||||
} else {
|
||||
t12 = $[36];
|
||||
}
|
||||
return t12;
|
||||
}
|
||||
function _temp2(id) {
|
||||
return `#${id}`;
|
||||
}
|
||||
function _temp(a, b) {
|
||||
return parseInt(a, 10) - parseInt(b, 10);
|
||||
|
||||
function TaskItem({
|
||||
task,
|
||||
ownerColor,
|
||||
openBlockers,
|
||||
activity,
|
||||
ownerActive,
|
||||
columns,
|
||||
}: TaskItemProps): React.ReactNode {
|
||||
const isCompleted = task.status === 'completed'
|
||||
const isInProgress = task.status === 'in_progress'
|
||||
const isBlocked = openBlockers.length > 0
|
||||
|
||||
const { icon, color } = getTaskIcon(task.status)
|
||||
|
||||
const showActivity = isInProgress && !isBlocked && activity
|
||||
|
||||
// Responsive layout: hide owner on narrow screens (<60 cols)
|
||||
// Truncate subject based on available space
|
||||
const showOwner = columns >= 60 && task.owner && ownerActive
|
||||
const ownerWidth = showOwner ? stringWidth(` (@${task.owner})`) : 0
|
||||
// Account for: icon(2) + indentation(~8 when nested under spinner) + owner + safety
|
||||
// Use columns - 15 as a conservative estimate for nested layouts
|
||||
const maxSubjectWidth = Math.max(15, columns - 15 - ownerWidth)
|
||||
const displaySubject = truncateToWidth(task.subject, maxSubjectWidth)
|
||||
|
||||
// Truncate activity for narrow screens
|
||||
const maxActivityWidth = Math.max(15, columns - 15)
|
||||
const displayActivity = activity
|
||||
? truncateToWidth(activity, maxActivityWidth)
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box>
|
||||
<Text color={color}>{icon} </Text>
|
||||
<Text
|
||||
bold={isInProgress}
|
||||
strikethrough={isCompleted}
|
||||
dimColor={isCompleted || isBlocked}
|
||||
>
|
||||
{displaySubject}
|
||||
</Text>
|
||||
{showOwner && (
|
||||
<Text dimColor>
|
||||
{' ('}
|
||||
{ownerColor ? (
|
||||
<ThemedText color={ownerColor}>@{task.owner}</ThemedText>
|
||||
) : (
|
||||
`@${task.owner}`
|
||||
)}
|
||||
{')'}
|
||||
</Text>
|
||||
)}
|
||||
{isBlocked && (
|
||||
<Text dimColor>
|
||||
{' '}
|
||||
{figures.pointerSmall} blocked by{' '}
|
||||
{[...openBlockers]
|
||||
.sort((a, b) => parseInt(a, 10) - parseInt(b, 10))
|
||||
.map(id => `#${id}`)
|
||||
.join(', ')}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
{showActivity && displayActivity && (
|
||||
<Box>
|
||||
<Text dimColor>
|
||||
{' '}
|
||||
{displayActivity}
|
||||
{figures.ellipsis}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user