epoch-tui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +3 -0
- package/dist/App.js +38 -0
- package/dist/components/calendar/CalendarPane.d.ts +2 -0
- package/dist/components/calendar/CalendarPane.js +91 -0
- package/dist/components/calendar/DayCell.d.ts +7 -0
- package/dist/components/calendar/DayCell.js +26 -0
- package/dist/components/calendar/MonthView.d.ts +7 -0
- package/dist/components/calendar/MonthView.js +10 -0
- package/dist/components/common/BorderedBox.d.ts +18 -0
- package/dist/components/common/BorderedBox.js +29 -0
- package/dist/components/common/ClearTimelineDialog.d.ts +2 -0
- package/dist/components/common/ClearTimelineDialog.js +33 -0
- package/dist/components/common/FullscreenBackground.d.ts +11 -0
- package/dist/components/common/FullscreenBackground.js +12 -0
- package/dist/components/common/HelpDialog.d.ts +2 -0
- package/dist/components/common/HelpDialog.js +40 -0
- package/dist/components/common/Modal.d.ts +10 -0
- package/dist/components/common/Modal.js +15 -0
- package/dist/components/common/Separator.d.ts +8 -0
- package/dist/components/common/Separator.js +10 -0
- package/dist/components/common/ThemeDialog.d.ts +2 -0
- package/dist/components/common/ThemeDialog.js +117 -0
- package/dist/components/common/ThemedScreen.d.ts +22 -0
- package/dist/components/common/ThemedScreen.js +36 -0
- package/dist/components/common/ThemedText.d.ts +11 -0
- package/dist/components/common/ThemedText.js +20 -0
- package/dist/components/layout/Pane.d.ts +12 -0
- package/dist/components/layout/Pane.js +10 -0
- package/dist/components/layout/ThreeColumnLayout.d.ts +13 -0
- package/dist/components/layout/ThreeColumnLayout.js +22 -0
- package/dist/components/overview/OverviewScreen.d.ts +2 -0
- package/dist/components/overview/OverviewScreen.js +138 -0
- package/dist/components/tasks/TaskHeader.d.ts +7 -0
- package/dist/components/tasks/TaskHeader.js +8 -0
- package/dist/components/tasks/TaskItem.d.ts +10 -0
- package/dist/components/tasks/TaskItem.js +25 -0
- package/dist/components/tasks/TaskList.d.ts +11 -0
- package/dist/components/tasks/TaskList.js +11 -0
- package/dist/components/tasks/TasksPane.d.ts +2 -0
- package/dist/components/tasks/TasksPane.js +410 -0
- package/dist/components/timeline/TimelineEntry.d.ts +9 -0
- package/dist/components/timeline/TimelineEntry.js +26 -0
- package/dist/components/timeline/TimelinePane.d.ts +2 -0
- package/dist/components/timeline/TimelinePane.js +78 -0
- package/dist/contexts/AppContext.d.ts +47 -0
- package/dist/contexts/AppContext.js +104 -0
- package/dist/contexts/StorageContext.d.ts +15 -0
- package/dist/contexts/StorageContext.js +83 -0
- package/dist/contexts/ThemeContext.d.ts +15 -0
- package/dist/contexts/ThemeContext.js +44 -0
- package/dist/hooks/useKeyboardNav.d.ts +1 -0
- package/dist/hooks/useKeyboardNav.js +89 -0
- package/dist/hooks/useTerminalSize.d.ts +9 -0
- package/dist/hooks/useTerminalSize.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/services/calendarService.d.ts +18 -0
- package/dist/services/calendarService.js +57 -0
- package/dist/services/storage.d.ts +14 -0
- package/dist/services/storage.js +130 -0
- package/dist/services/taskService.d.ts +17 -0
- package/dist/services/taskService.js +106 -0
- package/dist/services/timelineService.d.ts +25 -0
- package/dist/services/timelineService.js +78 -0
- package/dist/themes/amazon.d.ts +2 -0
- package/dist/themes/amazon.js +37 -0
- package/dist/themes/amazonLight.d.ts +2 -0
- package/dist/themes/amazonLight.js +38 -0
- package/dist/themes/apple.d.ts +2 -0
- package/dist/themes/apple.js +38 -0
- package/dist/themes/appleLight.d.ts +2 -0
- package/dist/themes/appleLight.js +38 -0
- package/dist/themes/atomOneDark.d.ts +2 -0
- package/dist/themes/atomOneDark.js +37 -0
- package/dist/themes/atomOneLight.d.ts +2 -0
- package/dist/themes/atomOneLight.js +38 -0
- package/dist/themes/batman.d.ts +2 -0
- package/dist/themes/batman.js +37 -0
- package/dist/themes/catppuccin.d.ts +2 -0
- package/dist/themes/catppuccin.js +37 -0
- package/dist/themes/catppuccinLatte.d.ts +2 -0
- package/dist/themes/catppuccinLatte.js +38 -0
- package/dist/themes/claude.d.ts +2 -0
- package/dist/themes/claude.js +48 -0
- package/dist/themes/claudeCode.d.ts +2 -0
- package/dist/themes/claudeCode.js +47 -0
- package/dist/themes/cursor.d.ts +2 -0
- package/dist/themes/cursor.js +38 -0
- package/dist/themes/cursorLight.d.ts +2 -0
- package/dist/themes/cursorLight.js +38 -0
- package/dist/themes/dark.d.ts +2 -0
- package/dist/themes/dark.js +38 -0
- package/dist/themes/githubDark.d.ts +2 -0
- package/dist/themes/githubDark.js +37 -0
- package/dist/themes/githubLight.d.ts +2 -0
- package/dist/themes/githubLight.js +38 -0
- package/dist/themes/index.d.ts +9 -0
- package/dist/themes/index.js +83 -0
- package/dist/themes/instagram.d.ts +2 -0
- package/dist/themes/instagram.js +37 -0
- package/dist/themes/instagramLight.d.ts +2 -0
- package/dist/themes/instagramLight.js +38 -0
- package/dist/themes/intellij.d.ts +2 -0
- package/dist/themes/intellij.js +37 -0
- package/dist/themes/intellijLight.d.ts +2 -0
- package/dist/themes/intellijLight.js +38 -0
- package/dist/themes/light.d.ts +2 -0
- package/dist/themes/light.js +38 -0
- package/dist/themes/nord.d.ts +2 -0
- package/dist/themes/nord.js +37 -0
- package/dist/themes/nordLight.d.ts +2 -0
- package/dist/themes/nordLight.js +38 -0
- package/dist/themes/postman.d.ts +2 -0
- package/dist/themes/postman.js +37 -0
- package/dist/themes/postmanLight.d.ts +2 -0
- package/dist/themes/postmanLight.js +38 -0
- package/dist/themes/spiderman.d.ts +2 -0
- package/dist/themes/spiderman.js +37 -0
- package/dist/themes/terminal.d.ts +2 -0
- package/dist/themes/terminal.js +37 -0
- package/dist/themes/ubuntu.d.ts +2 -0
- package/dist/themes/ubuntu.js +37 -0
- package/dist/themes/ubuntuLight.d.ts +2 -0
- package/dist/themes/ubuntuLight.js +38 -0
- package/dist/themes/x.d.ts +2 -0
- package/dist/themes/x.js +38 -0
- package/dist/themes/xLight.d.ts +2 -0
- package/dist/themes/xLight.js +38 -0
- package/dist/types/calendar.d.ts +19 -0
- package/dist/types/calendar.js +1 -0
- package/dist/types/storage.d.ts +21 -0
- package/dist/types/storage.js +1 -0
- package/dist/types/task.d.ts +21 -0
- package/dist/types/task.js +1 -0
- package/dist/types/theme.d.ts +38 -0
- package/dist/types/theme.js +1 -0
- package/dist/types/timeline.d.ts +23 -0
- package/dist/types/timeline.js +9 -0
- package/dist/utils/date.d.ts +7 -0
- package/dist/utils/date.js +37 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.js +39 -0
- package/dist/utils/tree.d.ts +11 -0
- package/dist/utils/tree.js +64 -0
- package/dist/utils/validation.d.ts +7 -0
- package/dist/utils/validation.js +35 -0
- package/package.json +44 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "ink";
|
|
3
|
+
import { Text } from "../common/ThemedText";
|
|
4
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
5
|
+
export const Pane = ({ children, title, isFocused = false, width, height, center = false, }) => {
|
|
6
|
+
const { theme } = useTheme();
|
|
7
|
+
// Allow height to be controlled by content or parent, don't force full screen
|
|
8
|
+
const paneHeight = height;
|
|
9
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: paneHeight, paddingRight: 1, paddingX: 2, alignItems: center ? "center" : "flex-start", children: [title && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { backgroundColor: isFocused ? theme.colors.focusIndicator : undefined, color: isFocused ? theme.colors.background : theme.colors.taskHeader, bold: true, children: isFocused ? ` ${title.toUpperCase()} ` : title }) })), _jsx(Box, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", alignItems: center ? "center" : "flex-start", children: children })] }));
|
|
10
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
interface ThreeColumnLayoutProps {
|
|
4
|
+
leftPane: ReactNode;
|
|
5
|
+
centerPane: ReactNode;
|
|
6
|
+
rightPane: ReactNode;
|
|
7
|
+
leftWidth?: number | string;
|
|
8
|
+
rightWidth?: number | string;
|
|
9
|
+
height?: number;
|
|
10
|
+
activePane?: "calendar" | "tasks" | "timeline";
|
|
11
|
+
}
|
|
12
|
+
export declare const ThreeColumnLayout: React.FC<ThreeColumnLayoutProps>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
4
|
+
// Vertical separator component that fills height with proper background
|
|
5
|
+
const VerticalSeparator = ({ color, backgroundColor, height, isFocused }) => {
|
|
6
|
+
// Create a column of characters - use heavy vertical line if focused
|
|
7
|
+
const char = isFocused ? "┃" : "│";
|
|
8
|
+
const lines = height ? Array(height).fill(char) : [char];
|
|
9
|
+
return (_jsx(Box, { flexDirection: "column", backgroundColor: backgroundColor, children: lines.map((c, i) => (_jsx(Text, { color: color, backgroundColor: backgroundColor, children: c }, i))) }));
|
|
10
|
+
};
|
|
11
|
+
export const ThreeColumnLayout = ({ leftPane, centerPane, rightPane, leftWidth = "15%", rightWidth = "25%", height, activePane, }) => {
|
|
12
|
+
const { theme } = useTheme();
|
|
13
|
+
const focusedColor = theme.colors.focusIndicator;
|
|
14
|
+
const normalColor = theme.colors.border;
|
|
15
|
+
// Apply background color for non-terminal themes
|
|
16
|
+
const bgColor = theme.name !== "terminal" ? theme.colors.background : undefined;
|
|
17
|
+
return (_jsxs(Box, { flexDirection: "row", width: "100%", height: height, backgroundColor: bgColor, children: [_jsx(Box, { width: leftWidth, flexShrink: 0, flexDirection: "column", backgroundColor: bgColor, children: leftPane }), _jsx(VerticalSeparator, { isFocused: activePane === "calendar" || activePane === "tasks", color: activePane === "calendar" || activePane === "tasks"
|
|
18
|
+
? focusedColor
|
|
19
|
+
: normalColor, backgroundColor: bgColor, height: height }), _jsx(Box, { flexGrow: 1, flexShrink: 1, flexDirection: "column", backgroundColor: bgColor, children: centerPane }), _jsx(VerticalSeparator, { isFocused: activePane === "tasks" || activePane === "timeline", color: activePane === "tasks" || activePane === "timeline"
|
|
20
|
+
? focusedColor
|
|
21
|
+
: normalColor, backgroundColor: bgColor, height: height }), _jsx(Box, { width: rightWidth, flexShrink: 0.3, flexDirection: "column", backgroundColor: bgColor, children: rightPane })] }));
|
|
22
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useMemo } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
5
|
+
import { useApp } from "../../contexts/AppContext";
|
|
6
|
+
import { getDateString, formatDate } from "../../utils/date";
|
|
7
|
+
import { startOfMonth, endOfMonth, eachDayOfInterval } from "date-fns";
|
|
8
|
+
import { useTerminalSize } from "../../hooks/useTerminalSize";
|
|
9
|
+
export const OverviewScreen = () => {
|
|
10
|
+
const { theme } = useTheme();
|
|
11
|
+
const { tasks, overviewMonth, setOverviewMonth, setShowOverview } = useApp();
|
|
12
|
+
const [scrollOffset, setScrollOffset] = React.useState(0);
|
|
13
|
+
const { height: terminalHeight } = useTerminalSize();
|
|
14
|
+
// Calculate visible rows based on terminal height to avoid wasting space
|
|
15
|
+
// Header (~3) + Footer (~2) + Padding (~2) + Indicators (~2) = ~9 lines fixed
|
|
16
|
+
// Minimum row height is 3 lines (Date + "No tasks" + margin)
|
|
17
|
+
const visibleRows = useMemo(() => {
|
|
18
|
+
return Math.max(2, Math.floor((terminalHeight - 9) / 3));
|
|
19
|
+
}, [terminalHeight]);
|
|
20
|
+
const monthDates = useMemo(() => {
|
|
21
|
+
const monthStart = startOfMonth(new Date(overviewMonth.year, overviewMonth.month, 1));
|
|
22
|
+
const monthEnd = endOfMonth(monthStart);
|
|
23
|
+
return eachDayOfInterval({ start: monthStart, end: monthEnd });
|
|
24
|
+
}, [overviewMonth]);
|
|
25
|
+
// Get tasks grouped by date
|
|
26
|
+
const tasksByDate = useMemo(() => {
|
|
27
|
+
const grouped = {};
|
|
28
|
+
monthDates.forEach((date) => {
|
|
29
|
+
const dateStr = getDateString(date);
|
|
30
|
+
grouped[dateStr] = tasks[dateStr] || [];
|
|
31
|
+
});
|
|
32
|
+
return grouped;
|
|
33
|
+
}, [monthDates, tasks]);
|
|
34
|
+
// Month navigation
|
|
35
|
+
const handlePrevMonth = () => {
|
|
36
|
+
const newMonth = overviewMonth.month - 1;
|
|
37
|
+
if (newMonth < 0) {
|
|
38
|
+
setOverviewMonth({ year: overviewMonth.year - 1, month: 11 });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
setOverviewMonth({ ...overviewMonth, month: newMonth });
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const handleNextMonth = () => {
|
|
45
|
+
const newMonth = overviewMonth.month + 1;
|
|
46
|
+
if (newMonth > 11) {
|
|
47
|
+
setOverviewMonth({ year: overviewMonth.year + 1, month: 0 });
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
setOverviewMonth({ ...overviewMonth, month: newMonth });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
// Reset scroll when month changes
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
setScrollOffset(0);
|
|
56
|
+
}, [overviewMonth]);
|
|
57
|
+
useInput((input, key) => {
|
|
58
|
+
if (key.escape) {
|
|
59
|
+
setShowOverview(false);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (input === "n" || key.rightArrow) {
|
|
63
|
+
handleNextMonth();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (input === "p" || key.leftArrow) {
|
|
67
|
+
handlePrevMonth();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (input === "j" || key.downArrow) {
|
|
71
|
+
setScrollOffset((prev) => Math.min(prev + 1, Math.max(0, rows - visibleRows)));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (input === "k" || key.upArrow) {
|
|
75
|
+
setScrollOffset((prev) => Math.max(prev - 1, 0));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
const monthName = formatDate(new Date(overviewMonth.year, overviewMonth.month, 1), "MMMM yyyy");
|
|
80
|
+
// Calculate grid layout - 4 columns
|
|
81
|
+
const columns = 4;
|
|
82
|
+
const rows = Math.ceil(monthDates.length / columns);
|
|
83
|
+
const visibleRowData = Array.from({ length: rows }).slice(scrollOffset, scrollOffset + visibleRows);
|
|
84
|
+
const canScrollUp = scrollOffset > 0;
|
|
85
|
+
const canScrollDown = scrollOffset + visibleRows < rows;
|
|
86
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", height: "100%", children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.focusIndicator, children: "Overview" }), _jsx(Text, { color: theme.colors.foreground, children: monthName })] }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [canScrollUp && (_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsx(Text, { color: theme.colors.keyboardHint, dimColor: true, children: "-- more above --" }) })), visibleRowData.map((_, index) => {
|
|
87
|
+
const rowIndex = scrollOffset + index;
|
|
88
|
+
return (_jsx(Box, { flexDirection: "row", marginBottom: 1, children: Array.from({ length: columns }).map((_, colIndex) => {
|
|
89
|
+
const dateIndex = rowIndex * columns + colIndex;
|
|
90
|
+
const date = monthDates[dateIndex];
|
|
91
|
+
if (!date) {
|
|
92
|
+
return (_jsx(Box, { flexDirection: "column", flexGrow: 1, flexBasis: 0, marginRight: colIndex === columns - 1 ? 0 : 2 }, `empty-${colIndex}`));
|
|
93
|
+
}
|
|
94
|
+
const dateStr = getDateString(date);
|
|
95
|
+
const dayTasks = tasksByDate[dateStr] || [];
|
|
96
|
+
// Flatten tasks with depth info for indentation
|
|
97
|
+
const flatTasksWithDepth = [];
|
|
98
|
+
const traverse = (taskList, depth) => {
|
|
99
|
+
for (const task of taskList) {
|
|
100
|
+
flatTasksWithDepth.push({ task, depth });
|
|
101
|
+
traverse(task.children, depth + 1);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
traverse(dayTasks, 0);
|
|
105
|
+
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, flexBasis: 0, marginRight: colIndex === columns - 1 ? 0 : 2, children: [_jsx(Text, { bold: true, color: theme.colors.calendarSelected, children: formatDate(date, "do MMM") }), _jsxs(Box, { flexDirection: "column", children: [flatTasksWithDepth.length === 0 ? (_jsx(Text, { dimColor: true, color: theme.colors.keyboardHint, children: "No tasks" })) : (flatTasksWithDepth
|
|
106
|
+
.slice(0, 10)
|
|
107
|
+
.map(({ task, depth }) => (_jsx(TaskItem, { task: task, theme: theme, depth: depth }, task.id)))), flatTasksWithDepth.length > 10 && (_jsxs(Text, { dimColor: true, color: theme.colors.keyboardHint, children: ["+", flatTasksWithDepth.length - 10, " more..."] }))] })] }, dateStr));
|
|
108
|
+
}) }, rowIndex));
|
|
109
|
+
}), canScrollDown && (_jsx(Box, { justifyContent: "center", marginTop: 1, children: _jsx(Text, { color: theme.colors.keyboardHint, dimColor: true, children: "-- more below --" }) }))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.keyboardHint, dimColor: true, children: "n/p or \u2190/\u2192: month | j/k or \u2193/\u2191: scroll | Esc: close | Shift+;: toggle" }) })] }));
|
|
110
|
+
};
|
|
111
|
+
const TaskItem = ({ task, theme, depth }) => {
|
|
112
|
+
const checkbox = getCheckbox(task.state);
|
|
113
|
+
const color = getStateColor(task.state, theme);
|
|
114
|
+
// Note: We don't recurse here anymore because the parent component (OverviewScreen)
|
|
115
|
+
// already uses flattenTasks() to get a flat list of all tasks including children.
|
|
116
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: color, children: [checkbox, " "] }), depth > 0 && _jsx(Text, { children: " ".repeat(depth) }), _jsx(Text, { color: color, strikethrough: task.state === "completed", dimColor: task.state === "delayed", children: task.title.length > 35 ? task.title.slice(0, 32) + "..." : task.title })] }));
|
|
117
|
+
};
|
|
118
|
+
function getCheckbox(state) {
|
|
119
|
+
switch (state) {
|
|
120
|
+
case "completed":
|
|
121
|
+
return "[✓]";
|
|
122
|
+
case "delegated":
|
|
123
|
+
return "[→]";
|
|
124
|
+
case "delayed":
|
|
125
|
+
return "[‖]";
|
|
126
|
+
default:
|
|
127
|
+
return "[ ]";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function getStateColor(state, theme) {
|
|
131
|
+
const colors = {
|
|
132
|
+
todo: theme.colors.taskStateTodo,
|
|
133
|
+
completed: theme.colors.taskStateCompleted,
|
|
134
|
+
delegated: theme.colors.taskStateDelegated,
|
|
135
|
+
delayed: theme.colors.taskStateDelayed,
|
|
136
|
+
};
|
|
137
|
+
return colors[state] || theme.colors.foreground;
|
|
138
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
4
|
+
import { formatDate } from "../../utils/date";
|
|
5
|
+
export const TaskHeader = ({ selectedDate, completionPercentage, }) => {
|
|
6
|
+
const { theme } = useTheme();
|
|
7
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: theme.colors.taskHeader, children: formatDate(selectedDate, "EEEE, MMMM d, yyyy") }), _jsxs(Text, { color: theme.colors.taskHeader, children: [completionPercentage, "% completed"] })] }));
|
|
8
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
4
|
+
export const TaskItem = ({ task, depth, isSelected, isExpanded, }) => {
|
|
5
|
+
const { theme } = useTheme();
|
|
6
|
+
const stateColors = {
|
|
7
|
+
todo: theme.colors.taskStateTodo,
|
|
8
|
+
completed: theme.colors.taskStateCompleted,
|
|
9
|
+
delegated: theme.colors.taskStateDelegated,
|
|
10
|
+
delayed: theme.colors.taskStateDelayed,
|
|
11
|
+
};
|
|
12
|
+
const checkbox = task.state === "completed"
|
|
13
|
+
? "☑"
|
|
14
|
+
: task.state === "delegated"
|
|
15
|
+
? "↦"
|
|
16
|
+
: task.state === "delayed"
|
|
17
|
+
? "⏸"
|
|
18
|
+
: "☐";
|
|
19
|
+
const expandIcon = task.children.length > 0 ? (isExpanded ? "▼ " : "▶ ") : " ";
|
|
20
|
+
const indent = " ".repeat(depth);
|
|
21
|
+
const selector = isSelected ? ">" : " ";
|
|
22
|
+
const textColor = stateColors[task.state] || theme.colors.foreground;
|
|
23
|
+
const strikethrough = task.state === "completed";
|
|
24
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? theme.colors.focusIndicator : theme.colors.foreground, children: selector }), _jsx(Text, { children: " " }), _jsx(Text, { color: textColor, children: checkbox }), _jsx(Text, { children: " " }), _jsx(Text, { children: expandIcon }), _jsx(Text, { children: indent }), _jsx(Text, { color: textColor, strikethrough: strikethrough, dimColor: task.state === "delayed", children: task.title }), task.startTime && !task.endTime && (_jsx(Text, { color: theme.colors.timelineEventStarted, children: " \u25B6" }))] }));
|
|
25
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Task } from "../../types/task";
|
|
3
|
+
interface TaskListProps {
|
|
4
|
+
tasks: Task[];
|
|
5
|
+
depth?: number;
|
|
6
|
+
selectedId?: string;
|
|
7
|
+
expandedIds?: Set<string>;
|
|
8
|
+
onToggleExpand?: (id: string) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const TaskList: React.FC<TaskListProps>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "ink";
|
|
3
|
+
import { TaskItem } from "./TaskItem";
|
|
4
|
+
export const TaskList = ({ tasks, depth = 0, selectedId, expandedIds = new Set(), onToggleExpand, }) => {
|
|
5
|
+
return (_jsx(Box, { flexDirection: "column", children: tasks.map((task) => {
|
|
6
|
+
const isSelected = task.id === selectedId;
|
|
7
|
+
const isExpanded = expandedIds.has(task.id);
|
|
8
|
+
const hasChildren = task.children.length > 0;
|
|
9
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TaskItem, { task: task, depth: depth, isSelected: isSelected, isExpanded: isExpanded }), hasChildren && isExpanded && (_jsx(TaskList, { tasks: task.children, depth: depth + 1, selectedId: selectedId, expandedIds: expandedIds, onToggleExpand: onToggleExpand }))] }, task.id));
|
|
10
|
+
}) }));
|
|
11
|
+
};
|