floq 1.3.3 → 1.4.1
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/README.ja.md +8 -0
- package/README.md +8 -0
- package/dist/cli.js +10 -0
- package/dist/commands/insights.d.ts +1 -0
- package/dist/commands/insights.js +283 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +6 -0
- package/dist/db/index.js +24 -0
- package/dist/db/schema.d.ts +37 -0
- package/dist/db/schema.js +2 -0
- package/dist/i18n/en.d.ts +82 -0
- package/dist/i18n/en.js +57 -5
- package/dist/i18n/ja.js +57 -5
- package/dist/ui/App.js +110 -7
- package/dist/ui/components/GtdDQ.js +139 -12
- package/dist/ui/components/GtdMario.js +135 -11
- package/dist/ui/components/HelpModal.js +8 -0
- package/dist/ui/components/InsightsModal.d.ts +6 -0
- package/dist/ui/components/InsightsModal.js +272 -0
- package/dist/ui/components/KanbanBoard.js +119 -11
- package/dist/ui/components/KanbanColumn.js +7 -2
- package/dist/ui/components/KanbanDQ.js +120 -11
- package/dist/ui/components/KanbanMario.js +125 -11
- package/dist/ui/components/TaskItem.js +7 -2
- package/dist/ui/history/commands/SetEffortCommand.d.ts +20 -0
- package/dist/ui/history/commands/SetEffortCommand.js +37 -0
- package/dist/ui/history/commands/SetFocusCommand.d.ts +20 -0
- package/dist/ui/history/commands/SetFocusCommand.js +37 -0
- package/dist/ui/history/commands/index.d.ts +2 -0
- package/dist/ui/history/commands/index.js +2 -0
- package/dist/ui/history/index.d.ts +1 -1
- package/dist/ui/history/index.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { Box, Text, useInput, useApp, useStdout } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
@@ -7,12 +7,14 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
7
7
|
import { getDb, schema } from '../../db/index.js';
|
|
8
8
|
import { t, fmt } from '../../i18n/index.js';
|
|
9
9
|
import { useTheme } from '../theme/index.js';
|
|
10
|
-
import { isTursoEnabled, getContexts, addContext, getLocale, getContextFilter, setContextFilter as saveContextFilter, getPomodoroFocusMode, setPomodoroFocusMode } from '../../config.js';
|
|
11
|
-
import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from '../history/index.js';
|
|
10
|
+
import { isTursoEnabled, getContexts, addContext, getLocale, getContextFilter, setContextFilter as saveContextFilter, getPomodoroFocusMode, setPomodoroFocusMode, getFocusFilter, setFocusFilter } from '../../config.js';
|
|
11
|
+
import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, SetFocusCommand, SetEffortCommand, } from '../history/index.js';
|
|
12
12
|
import { SearchBar } from './SearchBar.js';
|
|
13
13
|
import { SearchResults } from './SearchResults.js';
|
|
14
14
|
import { HelpModal } from './HelpModal.js';
|
|
15
|
+
import { InsightsModal } from './InsightsModal.js';
|
|
15
16
|
import { CalendarModal } from './CalendarModal.js';
|
|
17
|
+
import { StatusBadge } from './StatusBadge.js';
|
|
16
18
|
import { Clock } from './Clock.js';
|
|
17
19
|
import { CalendarEvents } from './CalendarEvents.js';
|
|
18
20
|
import { PomodoroBattleUI, BattleMessage, getBattleMessage, BATTLE_COMMANDS } from './PomodoroBattleUI.js';
|
|
@@ -155,6 +157,9 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
155
157
|
const [searchQuery, setSearchQuery] = useState('');
|
|
156
158
|
const [searchResults, setSearchResults] = useState([]);
|
|
157
159
|
const [searchResultIndex, setSearchResultIndex] = useState(0);
|
|
160
|
+
// Focus filter state
|
|
161
|
+
const [focusFilterState, setFocusFilterState] = useState(getFocusFilter());
|
|
162
|
+
const [effortSelectIndex, setEffortSelectIndex] = useState(0);
|
|
158
163
|
// Pomodoro focus mode state
|
|
159
164
|
const [focusMode, setFocusModeState] = useState(() => getPomodoroFocusMode());
|
|
160
165
|
const toggleFocusMode = useCallback(() => {
|
|
@@ -181,6 +186,17 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
181
186
|
const rightPaneWidth = terminalWidth - leftPaneWidth - 6;
|
|
182
187
|
const currentTab = TABS[currentTabIndex];
|
|
183
188
|
const currentTasks = mode === 'project-detail' ? projectTasks : tasks[currentTab];
|
|
189
|
+
const EFFORT_OPTIONS = [
|
|
190
|
+
{ value: 'small', label: i18n.tui.effort?.small || 'Small' },
|
|
191
|
+
{ value: 'medium', label: i18n.tui.effort?.medium || 'Medium' },
|
|
192
|
+
{ value: 'large', label: i18n.tui.effort?.large || 'Large' },
|
|
193
|
+
{ value: null, label: i18n.tui.effort?.clear || 'Clear' },
|
|
194
|
+
];
|
|
195
|
+
const getCurrentTask = () => {
|
|
196
|
+
if (currentTasks.length === 0)
|
|
197
|
+
return undefined;
|
|
198
|
+
return currentTasks[selectedTaskIndex];
|
|
199
|
+
};
|
|
184
200
|
const loadTasks = useCallback(async () => {
|
|
185
201
|
const db = getDb();
|
|
186
202
|
const newTasks = {
|
|
@@ -214,6 +230,9 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
214
230
|
allTasks = allTasks.filter(t => t.context === contextFilter);
|
|
215
231
|
}
|
|
216
232
|
}
|
|
233
|
+
if (focusFilterState) {
|
|
234
|
+
allTasks = allTasks.filter(t => t.isFocused);
|
|
235
|
+
}
|
|
217
236
|
newTasks[status] = allTasks;
|
|
218
237
|
}
|
|
219
238
|
newTasks.projects = await db
|
|
@@ -233,7 +252,7 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
233
252
|
setProjectProgress(progress);
|
|
234
253
|
setTasks(newTasks);
|
|
235
254
|
setAvailableContexts(getContexts());
|
|
236
|
-
}, [contextFilter]);
|
|
255
|
+
}, [contextFilter, focusFilterState]);
|
|
237
256
|
const loadProjectTasks = useCallback(async (projectId) => {
|
|
238
257
|
const db = getDb();
|
|
239
258
|
const children = await db
|
|
@@ -430,6 +449,48 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
430
449
|
setMessage(description);
|
|
431
450
|
await loadTasks();
|
|
432
451
|
}, [i18n.tui.context, loadTasks, history]);
|
|
452
|
+
const toggleTaskFocus = async () => {
|
|
453
|
+
const task = getCurrentTask();
|
|
454
|
+
if (!task)
|
|
455
|
+
return;
|
|
456
|
+
const newFocused = !task.isFocused;
|
|
457
|
+
const description = newFocused
|
|
458
|
+
? fmt(i18n.tui.focus?.taskFocused || 'Focused: "{title}"', { title: task.title })
|
|
459
|
+
: fmt(i18n.tui.focus?.taskUnfocused || 'Unfocused: "{title}"', { title: task.title });
|
|
460
|
+
const command = new SetFocusCommand({
|
|
461
|
+
taskId: task.id,
|
|
462
|
+
fromFocused: task.isFocused,
|
|
463
|
+
toFocused: newFocused,
|
|
464
|
+
description,
|
|
465
|
+
});
|
|
466
|
+
await history.execute(command);
|
|
467
|
+
setMessage(description);
|
|
468
|
+
await loadTasks();
|
|
469
|
+
};
|
|
470
|
+
const toggleFocusFilter = () => {
|
|
471
|
+
const newValue = !focusFilterState;
|
|
472
|
+
setFocusFilterState(newValue);
|
|
473
|
+
setFocusFilter(newValue);
|
|
474
|
+
setMessage(newValue ? (i18n.tui.focus?.filterOn || 'Focus filter ON') : (i18n.tui.focus?.filterOff || 'Focus filter OFF'));
|
|
475
|
+
};
|
|
476
|
+
const setTaskEffort = async (effort) => {
|
|
477
|
+
const task = getCurrentTask();
|
|
478
|
+
if (!task)
|
|
479
|
+
return;
|
|
480
|
+
const description = effort
|
|
481
|
+
? fmt(i18n.tui.effort?.effortSet || 'Set effort {effort} for "{title}"', { effort: i18n.tui.effort?.[effort] || effort, title: task.title })
|
|
482
|
+
: fmt(i18n.tui.effort?.effortCleared || 'Cleared effort for "{title}"', { title: task.title });
|
|
483
|
+
const command = new SetEffortCommand({
|
|
484
|
+
taskId: task.id,
|
|
485
|
+
fromEffort: task.effort,
|
|
486
|
+
toEffort: effort,
|
|
487
|
+
description,
|
|
488
|
+
});
|
|
489
|
+
await history.execute(command);
|
|
490
|
+
setMessage(description);
|
|
491
|
+
setMode('normal');
|
|
492
|
+
await loadTasks();
|
|
493
|
+
};
|
|
433
494
|
const handleInputSubmit = async (value) => {
|
|
434
495
|
// Handle search mode submit
|
|
435
496
|
if (mode === 'search') {
|
|
@@ -494,8 +555,8 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
494
555
|
}
|
|
495
556
|
};
|
|
496
557
|
useInput((input, key) => {
|
|
497
|
-
// Handle help mode - let
|
|
498
|
-
if (mode === 'help') {
|
|
558
|
+
// Handle help/insights mode - let modals handle their own input
|
|
559
|
+
if (mode === 'help' || mode === 'insights') {
|
|
499
560
|
return;
|
|
500
561
|
}
|
|
501
562
|
// Battle command navigation (j/k) - when timer is running and in focus mode
|
|
@@ -585,10 +646,20 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
585
646
|
// Handle task-detail mode
|
|
586
647
|
if (mode === 'task-detail') {
|
|
587
648
|
if (key.escape || input === 'b' || input === 'h' || key.leftArrow) {
|
|
649
|
+
// If came from project-detail, go back to project-detail
|
|
650
|
+
if (selectedProject) {
|
|
651
|
+
setMode('project-detail');
|
|
652
|
+
const taskIndex = projectTasks.findIndex(t => t.id === selectedTask?.id);
|
|
653
|
+
if (taskIndex >= 0) {
|
|
654
|
+
setSelectedTaskIndex(taskIndex);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
setMode('normal');
|
|
659
|
+
}
|
|
588
660
|
setSelectedTask(null);
|
|
589
661
|
setTaskComments([]);
|
|
590
662
|
setSelectedCommentIndex(0);
|
|
591
|
-
setMode('normal');
|
|
592
663
|
return;
|
|
593
664
|
}
|
|
594
665
|
// Navigate comments
|
|
@@ -730,6 +801,26 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
730
801
|
}
|
|
731
802
|
return;
|
|
732
803
|
}
|
|
804
|
+
// Handle set-effort mode
|
|
805
|
+
if (mode === 'set-effort') {
|
|
806
|
+
if (key.escape) {
|
|
807
|
+
setMode('normal');
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
if (input === 'j' || key.downArrow) {
|
|
811
|
+
setEffortSelectIndex(prev => Math.min(prev + 1, EFFORT_OPTIONS.length - 1));
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (input === 'k' || key.upArrow) {
|
|
815
|
+
setEffortSelectIndex(prev => Math.max(prev - 1, 0));
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (key.return) {
|
|
819
|
+
setTaskEffort(EFFORT_OPTIONS[effortSelectIndex].value);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
733
824
|
// Handle select-project mode
|
|
734
825
|
if (mode === 'select-project') {
|
|
735
826
|
if (key.escape) {
|
|
@@ -788,6 +879,14 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
788
879
|
});
|
|
789
880
|
return;
|
|
790
881
|
}
|
|
882
|
+
// Enter to view task details within project
|
|
883
|
+
if (key.return && projectTasks.length > 0) {
|
|
884
|
+
const task = projectTasks[selectedTaskIndex];
|
|
885
|
+
setSelectedTask(task);
|
|
886
|
+
loadTaskComments(task.id);
|
|
887
|
+
setMode('task-detail');
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
791
890
|
return;
|
|
792
891
|
}
|
|
793
892
|
if (message)
|
|
@@ -801,6 +900,10 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
801
900
|
setMode('help');
|
|
802
901
|
return;
|
|
803
902
|
}
|
|
903
|
+
if (input === 'I') {
|
|
904
|
+
setMode('insights');
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
804
907
|
if (input === 'C') {
|
|
805
908
|
setMode('calendar');
|
|
806
909
|
return;
|
|
@@ -922,7 +1025,7 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
922
1025
|
return;
|
|
923
1026
|
}
|
|
924
1027
|
// Move to inbox
|
|
925
|
-
if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects'
|
|
1028
|
+
if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects') {
|
|
926
1029
|
moveTaskToStatus(task, 'inbox').then(() => {
|
|
927
1030
|
if (selectedTaskIndex >= currentTasks.length - 1) {
|
|
928
1031
|
setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
|
|
@@ -958,6 +1061,24 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
958
1061
|
setMode('set-context');
|
|
959
1062
|
return;
|
|
960
1063
|
}
|
|
1064
|
+
// Toggle focus on task
|
|
1065
|
+
if (input === 'g') {
|
|
1066
|
+
toggleTaskFocus();
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
// Toggle focus filter
|
|
1070
|
+
if (input === 'G') {
|
|
1071
|
+
toggleFocusFilter();
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
// Set effort
|
|
1075
|
+
if (input === 'E') {
|
|
1076
|
+
if (getCurrentTask()) {
|
|
1077
|
+
setEffortSelectIndex(0);
|
|
1078
|
+
setMode('set-effort');
|
|
1079
|
+
}
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
961
1082
|
}
|
|
962
1083
|
// Undo
|
|
963
1084
|
if (input === 'u') {
|
|
@@ -1035,6 +1156,10 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1035
1156
|
if (mode === 'help') {
|
|
1036
1157
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal') }) }));
|
|
1037
1158
|
}
|
|
1159
|
+
// Insights modal overlay
|
|
1160
|
+
if (mode === 'insights') {
|
|
1161
|
+
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(InsightsModal, { onClose: () => setMode('normal') }) }));
|
|
1162
|
+
}
|
|
1038
1163
|
// Calendar modal overlay
|
|
1039
1164
|
if (mode === 'calendar') {
|
|
1040
1165
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(CalendarModal, { onClose: () => setMode('normal') }) }));
|
|
@@ -1045,13 +1170,13 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1045
1170
|
const battleWidth = terminalWidth - 4;
|
|
1046
1171
|
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(PomodoroBattleUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, jobClass: jobClass, level: playerLevel, totalCompleted: tasks.done.length, width: battleWidth, selectedCommand: selectedCommand }), message && (_jsx(BattleMessage, { message: message, isNew: true })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.accent, children: "\u2328\uFE0F " }), _jsxs(Text, { color: theme.colors.textMuted, children: ["a=", i18n.tui.keyBar.add, " f=focus off"] })] }) })] }));
|
|
1047
1172
|
}
|
|
1048
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: jobClass }), _jsx(Text, { color: theme.colors.text, children: " Lv." }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: Math.floor(tasks.done.length / 5) + 1 }), _jsx(Text, { color: theme.colors.text, children: " HP " }), _jsx(Text, { bold: true, color: theme.colors.statusNext, children: tasks.inbox.length + tasks.next.length }), _jsxs(Text, { color: theme.colors.textMuted, children: ["/", tasks.inbox.length + tasks.next.length + tasks.waiting.length + tasks.someday.length] }), _jsx(Text, { color: theme.colors.text, children: " MP " }), _jsx(Text, { bold: true, color: theme.colors.statusWaiting, children: tasks.projects.length }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: tursoEnabled ? ' [TURSO]' : '' }), contextFilter !== null && (_jsxs(Text, { color: theme.colors.accent, children: [' ', "@", contextFilter === '' ? 'none' : contextFilter] }))] }), _jsxs(Box, { children: [_jsx(CalendarEvents, { compact: true, showLabel: true, withSeparator: true }), _jsx(Clock, {}), _jsx(Text, { color: theme.colors.textMuted, children: " ?=help q=quit" })] })] }), pomodoro.isRunning && pomodoro.state && (_jsx(Box, { marginBottom: 1, children: _jsx(PomodoroBattleUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, jobClass: jobClass, level: Math.floor(tasks.done.length / 5) + 1, totalCompleted: tasks.done.length, width: terminalWidth - 4, compact: true }) })), mode === 'context-filter' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "Filter by context" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
|
|
1173
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: jobClass }), _jsx(Text, { color: theme.colors.text, children: " Lv." }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: Math.floor(tasks.done.length / 5) + 1 }), _jsx(Text, { color: theme.colors.text, children: " HP " }), _jsx(Text, { bold: true, color: theme.colors.statusNext, children: tasks.inbox.length + tasks.next.length }), _jsxs(Text, { color: theme.colors.textMuted, children: ["/", tasks.inbox.length + tasks.next.length + tasks.waiting.length + tasks.someday.length] }), _jsx(Text, { color: theme.colors.text, children: " MP " }), _jsx(Text, { bold: true, color: theme.colors.statusWaiting, children: tasks.projects.length }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: tursoEnabled ? ' [TURSO]' : '' }), contextFilter !== null && (_jsxs(Text, { color: theme.colors.accent, children: [' ', "@", contextFilter === '' ? 'none' : contextFilter] })), focusFilterState && (_jsxs(Text, { color: theme.colors.accent, children: [" ", i18n.tui.focus?.focused || '★ Focused'] }))] }), _jsxs(Box, { children: [_jsx(CalendarEvents, { compact: true, showLabel: true, withSeparator: true }), _jsx(Clock, {}), _jsx(Text, { color: theme.colors.textMuted, children: " ?=help q=quit" })] })] }), pomodoro.isRunning && pomodoro.state && (_jsx(Box, { marginBottom: 1, children: _jsx(PomodoroBattleUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, jobClass: jobClass, level: Math.floor(tasks.done.length / 5) + 1, totalCompleted: tasks.done.length, width: terminalWidth - 4, compact: true }) })), mode === 'context-filter' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "Filter by context" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
|
|
1049
1174
|
const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
|
|
1050
1175
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
|
|
1051
1176
|
}) })] })) : mode === 'set-context' && currentTasks.length > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.setContext || 'Set context for', ": ", currentTasks[selectedTaskIndex]?.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['clear', ...availableContexts, 'new'].map((ctx, index) => {
|
|
1052
1177
|
const label = ctx === 'clear' ? (i18n.tui.context?.none || 'Clear context') : ctx === 'new' ? (i18n.tui.context?.addNew || 'New context...') : `@${ctx}`;
|
|
1053
1178
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
|
|
1054
|
-
}) })] })) : mode === 'select-project' && taskToLink ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? '▶ ' : ' ', project.title] }, project.id))) })] })) : mode === 'confirm-delete' && taskToDelete ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: theme.colors.accent, bold: true, children: fmt(i18n.tui.deleteConfirm || 'Delete "{title}"? (y/n)', { title: taskToDelete.title }) }) })) : (mode === 'task-detail' || mode === 'add-comment') && selectedTask ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(TitledBoxInline, { title: i18n.tui.taskDetailTitle || 'Task Details', width: terminalWidth - 4, minHeight: 4, isActive: true, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus || 'Status', ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(TitledBoxInline, { title: `${i18n.tui.comments || 'Comments'} (${taskComments.length})`, width: terminalWidth - 4, minHeight: 5, isActive: mode === 'task-detail', children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
|
|
1179
|
+
}) })] })) : mode === 'set-effort' && getCurrentTask() ? (_jsx(Box, { flexDirection: "column", children: _jsx(TitledBoxInline, { title: i18n.tui.effort?.set || 'Set Effort', width: Math.min(40, terminalWidth - 4), minHeight: EFFORT_OPTIONS.length, isActive: true, children: EFFORT_OPTIONS.map((option, index) => (_jsxs(Text, { color: index === effortSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === effortSelectIndex, children: [index === effortSelectIndex ? '▶ ' : ' ', option.label] }, option.label))) }) })) : mode === 'select-project' && taskToLink ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? '▶ ' : ' ', project.title] }, project.id))) })] })) : mode === 'confirm-delete' && taskToDelete ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: theme.colors.accent, bold: true, children: fmt(i18n.tui.deleteConfirm || 'Delete "{title}"? (y/n)', { title: taskToDelete.title }) }) })) : (mode === 'task-detail' || mode === 'add-comment') && selectedTask ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(TitledBoxInline, { title: i18n.tui.taskDetailTitle || 'Task Details', width: terminalWidth - 4, minHeight: 4, isActive: true, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus || 'Status', ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.label || 'Effort', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.effort ? (i18n.tui.effort?.[selectedTask.effort] || selectedTask.effort) : '-' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.focus?.label || 'Focus', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.isFocused ? '★' : '-' })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(TitledBoxInline, { title: `${i18n.tui.comments || 'Comments'} (${taskComments.length})`, width: terminalWidth - 4, minHeight: 5, isActive: mode === 'task-detail', children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
|
|
1055
1180
|
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
1056
1181
|
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.textMuted, children: [isSelected ? '▶ ' : ' ', "[", comment.createdAt.toLocaleString(), "]"] }), _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [' ', comment.content] })] }, comment.id));
|
|
1057
1182
|
})) }) })] })) : (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { marginRight: 2, children: _jsx(TitledBoxInline, { title: mode === 'project-detail' && selectedProject ? selectedProject.title : 'GTD', width: leftPaneWidth, minHeight: 8, isActive: paneFocus === 'tabs' && mode !== 'project-detail', children: mode === 'project-detail' ? (_jsx(Text, { color: theme.colors.textMuted, children: "\u2190 Esc/b: back" })) : (TABS.map((tab, index) => {
|
|
@@ -1062,6 +1187,8 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1062
1187
|
const isSelected = (paneFocus === 'tasks' || mode === 'project-detail') && index === selectedTaskIndex;
|
|
1063
1188
|
const parentProject = getParentProject(task.parentId);
|
|
1064
1189
|
const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
|
|
1190
|
+
const focusPrefix = task.isFocused ? '★ ' : '';
|
|
1191
|
+
const effortBadge = task.effort ? `[${{ 'small': 'S', 'medium': 'M', 'large': 'L' }[task.effort]}] ` : '';
|
|
1065
1192
|
// Calculate available width for title
|
|
1066
1193
|
const prefix = isSelected ? '▶ ' : ' ';
|
|
1067
1194
|
const suffix = [
|
|
@@ -1070,9 +1197,9 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1070
1197
|
parentProject ? ` [${parentProject.title}]` : '',
|
|
1071
1198
|
progress ? ` [${progress.completed}/${progress.total}]` : '',
|
|
1072
1199
|
].join('');
|
|
1073
|
-
const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(suffix);
|
|
1200
|
+
const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(focusPrefix) - getDisplayWidth(effortBadge) - getDisplayWidth(suffix);
|
|
1074
1201
|
const displayTitle = truncateString(task.title, availableWidth);
|
|
1075
|
-
return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, displayTitle, task.waitingFor && _jsxs(Text, { color: theme.colors.muted, children: [" (", task.waitingFor, ")"] }), task.context && _jsxs(Text, { color: theme.colors.muted, children: [" @", task.context] }), parentProject && _jsxs(Text, { color: theme.colors.muted, children: [" [", parentProject.title, "]"] }), progress && _jsxs(Text, { color: theme.colors.muted, children: [" [", progress.completed, "/", progress.total, "]"] })] }, task.id));
|
|
1202
|
+
return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, mode === 'project-detail' && _jsxs(_Fragment, { children: [_jsx(StatusBadge, { status: task.status }), " "] }), focusPrefix, effortBadge, displayTitle, task.waitingFor && _jsxs(Text, { color: theme.colors.muted, children: [" (", task.waitingFor, ")"] }), task.context && _jsxs(Text, { color: theme.colors.muted, children: [" @", task.context] }), parentProject && _jsxs(Text, { color: theme.colors.muted, children: [" [", parentProject.title, "]"] }), progress && _jsxs(Text, { color: theme.colors.muted, children: [" [", progress.completed, "/", progress.total, "]"] })] }, task.id));
|
|
1076
1203
|
})) }) })] })), (mode === 'add' || mode === 'add-to-project') && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: mode === 'add-to-project' && selectedProject
|
|
1077
1204
|
? `${i18n.tui.newTask}[${selectedProject.title}] `
|
|
1078
1205
|
: i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder })] })), mode === 'move-to-waiting' && taskToWaiting && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.waitingFor }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" })] })), mode === 'add-context' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "New context: " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "Enter context name..." })] })), mode === 'add-comment' && selectedTask && (_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.addComment || 'Add comment', ": "] }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "Enter comment..." })] })), mode === 'search' && (_jsx(SearchBar, { value: searchQuery, onChange: handleSearchChange, onSubmit: handleInputSubmit })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.accent, children: "\u2328\uFE0F " }), _jsx(Text, { color: theme.colors.textMuted, children: mode === 'task-detail'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { Box, Text, useInput, useApp, useStdout } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
@@ -7,13 +7,15 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
7
7
|
import { getDb, schema } from '../../db/index.js';
|
|
8
8
|
import { t, fmt } from '../../i18n/index.js';
|
|
9
9
|
import { useTheme } from '../theme/index.js';
|
|
10
|
-
import { isTursoEnabled, getContexts, addContext, getContextFilter, setContextFilter as saveContextFilter, getPomodoroFocusMode, setPomodoroFocusMode } from '../../config.js';
|
|
10
|
+
import { isTursoEnabled, getContexts, addContext, getContextFilter, setContextFilter as saveContextFilter, getPomodoroFocusMode, setPomodoroFocusMode, getFocusFilter, setFocusFilter } from '../../config.js';
|
|
11
11
|
import { VERSION } from '../../version.js';
|
|
12
|
-
import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from '../history/index.js';
|
|
12
|
+
import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, SetFocusCommand, SetEffortCommand, } from '../history/index.js';
|
|
13
13
|
import { SearchBar } from './SearchBar.js';
|
|
14
14
|
import { SearchResults } from './SearchResults.js';
|
|
15
15
|
import { HelpModal } from './HelpModal.js';
|
|
16
|
+
import { InsightsModal } from './InsightsModal.js';
|
|
16
17
|
import { CalendarModal } from './CalendarModal.js';
|
|
18
|
+
import { StatusBadge } from './StatusBadge.js';
|
|
17
19
|
import { MarioBoxInline } from './MarioBox.js';
|
|
18
20
|
import { Clock } from './Clock.js';
|
|
19
21
|
import { CalendarEvents } from './CalendarEvents.js';
|
|
@@ -97,6 +99,8 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
97
99
|
const [searchQuery, setSearchQuery] = useState('');
|
|
98
100
|
const [searchResults, setSearchResults] = useState([]);
|
|
99
101
|
const [searchResultIndex, setSearchResultIndex] = useState(0);
|
|
102
|
+
const [focusFilter, setFocusFilterState] = useState(getFocusFilter());
|
|
103
|
+
const [effortSelectIndex, setEffortSelectIndex] = useState(0);
|
|
100
104
|
// Focus mode state (can be toggled during pomodoro)
|
|
101
105
|
const [focusMode, setFocusModeState] = useState(() => getPomodoroFocusMode());
|
|
102
106
|
const toggleFocusMode = useCallback(() => {
|
|
@@ -132,6 +136,17 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
132
136
|
const rightPaneWidth = terminalWidth - leftPaneWidth - 6;
|
|
133
137
|
const currentTab = TABS[currentTabIndex];
|
|
134
138
|
const currentTasks = mode === 'project-detail' ? projectTasks : tasks[currentTab];
|
|
139
|
+
const getCurrentTask = () => {
|
|
140
|
+
if (currentTasks.length === 0)
|
|
141
|
+
return undefined;
|
|
142
|
+
return currentTasks[selectedTaskIndex];
|
|
143
|
+
};
|
|
144
|
+
const EFFORT_OPTIONS = [
|
|
145
|
+
{ value: 'small', label: i18n.tui.effort?.small || 'Small' },
|
|
146
|
+
{ value: 'medium', label: i18n.tui.effort?.medium || 'Medium' },
|
|
147
|
+
{ value: 'large', label: i18n.tui.effort?.large || 'Large' },
|
|
148
|
+
{ value: null, label: i18n.tui.effort?.clear || 'Clear' },
|
|
149
|
+
];
|
|
135
150
|
const loadTasks = useCallback(async () => {
|
|
136
151
|
const db = getDb();
|
|
137
152
|
const newTasks = {
|
|
@@ -165,6 +180,9 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
165
180
|
allTasks = allTasks.filter(t => t.context === contextFilter);
|
|
166
181
|
}
|
|
167
182
|
}
|
|
183
|
+
if (focusFilter) {
|
|
184
|
+
allTasks = allTasks.filter(t => t.isFocused);
|
|
185
|
+
}
|
|
168
186
|
newTasks[status] = allTasks;
|
|
169
187
|
}
|
|
170
188
|
newTasks.projects = await db
|
|
@@ -184,7 +202,7 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
184
202
|
setProjectProgress(progress);
|
|
185
203
|
setTasks(newTasks);
|
|
186
204
|
setAvailableContexts(getContexts());
|
|
187
|
-
}, [contextFilter]);
|
|
205
|
+
}, [contextFilter, focusFilter]);
|
|
188
206
|
const loadProjectTasks = useCallback(async (projectId) => {
|
|
189
207
|
const db = getDb();
|
|
190
208
|
const children = await db
|
|
@@ -377,6 +395,48 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
377
395
|
setMessage(description);
|
|
378
396
|
await loadTasks();
|
|
379
397
|
}, [i18n.tui.context, loadTasks, history]);
|
|
398
|
+
const toggleTaskFocus = async () => {
|
|
399
|
+
const task = getCurrentTask();
|
|
400
|
+
if (!task)
|
|
401
|
+
return;
|
|
402
|
+
const newFocused = !task.isFocused;
|
|
403
|
+
const description = newFocused
|
|
404
|
+
? fmt(i18n.tui.focus?.taskFocused || 'Focused: "{title}"', { title: task.title })
|
|
405
|
+
: fmt(i18n.tui.focus?.taskUnfocused || 'Unfocused: "{title}"', { title: task.title });
|
|
406
|
+
const command = new SetFocusCommand({
|
|
407
|
+
taskId: task.id,
|
|
408
|
+
fromFocused: task.isFocused,
|
|
409
|
+
toFocused: newFocused,
|
|
410
|
+
description,
|
|
411
|
+
});
|
|
412
|
+
await history.execute(command);
|
|
413
|
+
setMessage(description);
|
|
414
|
+
await loadTasks();
|
|
415
|
+
};
|
|
416
|
+
const toggleFocusFilter = () => {
|
|
417
|
+
const newValue = !focusFilter;
|
|
418
|
+
setFocusFilterState(newValue);
|
|
419
|
+
setFocusFilter(newValue);
|
|
420
|
+
setMessage(newValue ? (i18n.tui.focus?.filterOn || 'Focus filter ON') : (i18n.tui.focus?.filterOff || 'Focus filter OFF'));
|
|
421
|
+
};
|
|
422
|
+
const setTaskEffort = async (effort) => {
|
|
423
|
+
const task = getCurrentTask();
|
|
424
|
+
if (!task)
|
|
425
|
+
return;
|
|
426
|
+
const description = effort
|
|
427
|
+
? fmt(i18n.tui.effort?.effortSet || 'Set effort {effort} for "{title}"', { effort: i18n.tui.effort?.[effort] || effort, title: task.title })
|
|
428
|
+
: fmt(i18n.tui.effort?.effortCleared || 'Cleared effort for "{title}"', { title: task.title });
|
|
429
|
+
const command = new SetEffortCommand({
|
|
430
|
+
taskId: task.id,
|
|
431
|
+
fromEffort: task.effort,
|
|
432
|
+
toEffort: effort,
|
|
433
|
+
description,
|
|
434
|
+
});
|
|
435
|
+
await history.execute(command);
|
|
436
|
+
setMessage(description);
|
|
437
|
+
setMode('normal');
|
|
438
|
+
await loadTasks();
|
|
439
|
+
};
|
|
380
440
|
const handleInputSubmit = async (value) => {
|
|
381
441
|
if (mode === 'search') {
|
|
382
442
|
if (searchResults.length > 0) {
|
|
@@ -439,7 +499,7 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
439
499
|
}
|
|
440
500
|
};
|
|
441
501
|
useInput((input, key) => {
|
|
442
|
-
if (mode === 'help') {
|
|
502
|
+
if (mode === 'help' || mode === 'insights') {
|
|
443
503
|
return;
|
|
444
504
|
}
|
|
445
505
|
// Focus mode key handling - simplified controls
|
|
@@ -509,10 +569,20 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
509
569
|
}
|
|
510
570
|
if (mode === 'task-detail') {
|
|
511
571
|
if (key.escape || input === 'b' || input === 'h' || key.leftArrow) {
|
|
572
|
+
// If came from project-detail, go back to project-detail
|
|
573
|
+
if (selectedProject) {
|
|
574
|
+
setMode('project-detail');
|
|
575
|
+
const taskIndex = projectTasks.findIndex(t => t.id === selectedTask?.id);
|
|
576
|
+
if (taskIndex >= 0) {
|
|
577
|
+
setSelectedTaskIndex(taskIndex);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
setMode('normal');
|
|
582
|
+
}
|
|
512
583
|
setSelectedTask(null);
|
|
513
584
|
setTaskComments([]);
|
|
514
585
|
setSelectedCommentIndex(0);
|
|
515
|
-
setMode('normal');
|
|
516
586
|
return;
|
|
517
587
|
}
|
|
518
588
|
if (key.upArrow || input === 'k') {
|
|
@@ -644,6 +714,25 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
644
714
|
}
|
|
645
715
|
return;
|
|
646
716
|
}
|
|
717
|
+
if (mode === 'set-effort') {
|
|
718
|
+
if (key.escape) {
|
|
719
|
+
setMode('normal');
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (input === 'j' || key.downArrow) {
|
|
723
|
+
setEffortSelectIndex(prev => Math.min(prev + 1, EFFORT_OPTIONS.length - 1));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (input === 'k' || key.upArrow) {
|
|
727
|
+
setEffortSelectIndex(prev => Math.max(prev - 1, 0));
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (key.return) {
|
|
731
|
+
setTaskEffort(EFFORT_OPTIONS[effortSelectIndex].value);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
647
736
|
if (mode === 'select-project') {
|
|
648
737
|
if (key.escape) {
|
|
649
738
|
const wasFromTaskDetail = selectedTask !== null;
|
|
@@ -700,6 +789,14 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
700
789
|
});
|
|
701
790
|
return;
|
|
702
791
|
}
|
|
792
|
+
// Enter to view task details within project
|
|
793
|
+
if (key.return && projectTasks.length > 0) {
|
|
794
|
+
const task = projectTasks[selectedTaskIndex];
|
|
795
|
+
setSelectedTask(task);
|
|
796
|
+
loadTaskComments(task.id);
|
|
797
|
+
setMode('task-detail');
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
703
800
|
return;
|
|
704
801
|
}
|
|
705
802
|
if (message)
|
|
@@ -712,6 +809,10 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
712
809
|
setMode('help');
|
|
713
810
|
return;
|
|
714
811
|
}
|
|
812
|
+
if (input === 'I') {
|
|
813
|
+
setMode('insights');
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
715
816
|
if (input === 'C') {
|
|
716
817
|
setMode('calendar');
|
|
717
818
|
return;
|
|
@@ -824,7 +925,7 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
824
925
|
setMode('move-to-waiting');
|
|
825
926
|
return;
|
|
826
927
|
}
|
|
827
|
-
if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects'
|
|
928
|
+
if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects') {
|
|
828
929
|
moveTaskToStatus(task, 'inbox').then(() => {
|
|
829
930
|
if (selectedTaskIndex >= currentTasks.length - 1) {
|
|
830
931
|
setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
|
|
@@ -857,6 +958,24 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
857
958
|
setMode('set-context');
|
|
858
959
|
return;
|
|
859
960
|
}
|
|
961
|
+
// Toggle focus on task
|
|
962
|
+
if (input === 'g') {
|
|
963
|
+
toggleTaskFocus();
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
// Toggle focus filter
|
|
967
|
+
if (input === 'G') {
|
|
968
|
+
toggleFocusFilter();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
// Set effort
|
|
972
|
+
if (input === 'E') {
|
|
973
|
+
if (getCurrentTask()) {
|
|
974
|
+
setEffortSelectIndex(0);
|
|
975
|
+
setMode('set-effort');
|
|
976
|
+
}
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
860
979
|
}
|
|
861
980
|
if (input === 'u') {
|
|
862
981
|
history.undo().then((didUndo) => {
|
|
@@ -928,6 +1047,9 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
928
1047
|
if (mode === 'help') {
|
|
929
1048
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal') }) }));
|
|
930
1049
|
}
|
|
1050
|
+
if (mode === 'insights') {
|
|
1051
|
+
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(InsightsModal, { onClose: () => setMode('normal') }) }));
|
|
1052
|
+
}
|
|
931
1053
|
if (mode === 'calendar') {
|
|
932
1054
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(CalendarModal, { onClose: () => setMode('normal') }) }));
|
|
933
1055
|
}
|
|
@@ -942,13 +1064,13 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
942
1064
|
const marioWidth = terminalWidth - 4;
|
|
943
1065
|
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(PomodoroMarioUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, score: score, coins: coins, lives: lives, world: world, width: marioWidth, animationFrame: animationFrame }), message && (_jsx(MarioMessage, { message: message, isNew: true })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.accent, children: "\u2328\uFE0F " }), _jsxs(Text, { color: theme.colors.textMuted, children: ["a=", i18n.tui.keyBar.add, " Space=", pomodoro.isPaused ? 'resume' : 'pause', " S=skip X=stop f=focus off"] })] }) })] }));
|
|
944
1066
|
}
|
|
945
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "WORLD " }), _jsxs(Text, { color: theme.colors.primary, bold: true, children: [(new Date().getMonth() + 1), "-", new Date().getDate()] }), _jsx(Text, { color: theme.colors.text, children: " " }), _jsx(Text, { color: theme.colors.secondary, children: "x" }), _jsx(Text, { color: theme.colors.text, children: (tasks.inbox.length + tasks.next.length + tasks.waiting.length + tasks.someday.length + tasks.projects.length).toString().padStart(2, '0') }), _jsxs(Text, { color: theme.colors.textMuted, children: [" FLOQ v", VERSION] }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: tursoEnabled ? ' [TURSO]' : ' [LOCAL]' }), contextFilter !== null && (_jsxs(Text, { color: theme.colors.accent, children: [' ', "@", contextFilter === '' ? 'none' : contextFilter] }))] }), _jsxs(Box, { children: [_jsx(CalendarEvents, { compact: true, showLabel: true, withSeparator: true }), _jsx(Clock, {})] })] }), pomodoro.isRunning && pomodoro.state && (_jsx(Box, { marginBottom: 1, children: _jsx(PomodoroMarioUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, score: tasks.done.length * 100, coins: pomodoro.state.completedCount, lives: Math.floor(tasks.done.length / 10) + 3, world: `${new Date().getMonth() + 1}-${new Date().getDate()}`, width: terminalWidth - 4, compact: true }) })), mode === 'context-filter' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "Filter by context" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
|
|
1067
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "WORLD " }), _jsxs(Text, { color: theme.colors.primary, bold: true, children: [(new Date().getMonth() + 1), "-", new Date().getDate()] }), _jsx(Text, { color: theme.colors.text, children: " " }), _jsx(Text, { color: theme.colors.secondary, children: "x" }), _jsx(Text, { color: theme.colors.text, children: (tasks.inbox.length + tasks.next.length + tasks.waiting.length + tasks.someday.length + tasks.projects.length).toString().padStart(2, '0') }), _jsxs(Text, { color: theme.colors.textMuted, children: [" FLOQ v", VERSION] }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: tursoEnabled ? ' [TURSO]' : ' [LOCAL]' }), contextFilter !== null && (_jsxs(Text, { color: theme.colors.accent, children: [' ', "@", contextFilter === '' ? 'none' : contextFilter] })), focusFilter && (_jsxs(Text, { color: theme.colors.accent, children: [" ", i18n.tui.focus?.focused || '★ Focused'] }))] }), _jsxs(Box, { children: [_jsx(CalendarEvents, { compact: true, showLabel: true, withSeparator: true }), _jsx(Clock, {})] })] }), pomodoro.isRunning && pomodoro.state && (_jsx(Box, { marginBottom: 1, children: _jsx(PomodoroMarioUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, score: tasks.done.length * 100, coins: pomodoro.state.completedCount, lives: Math.floor(tasks.done.length / 10) + 3, world: `${new Date().getMonth() + 1}-${new Date().getDate()}`, width: terminalWidth - 4, compact: true }) })), mode === 'context-filter' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "Filter by context" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
|
|
946
1068
|
const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
|
|
947
1069
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '🍄 ' : ' ', label] }, ctx));
|
|
948
1070
|
}) })] })) : mode === 'set-context' && currentTasks.length > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.setContext || 'Set context for', ": ", currentTasks[selectedTaskIndex]?.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['clear', ...availableContexts, 'new'].map((ctx, index) => {
|
|
949
1071
|
const label = ctx === 'clear' ? (i18n.tui.context?.none || 'Clear context') : ctx === 'new' ? (i18n.tui.context?.addNew || 'New context...') : `@${ctx}`;
|
|
950
1072
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '🍄 ' : ' ', label] }, ctx));
|
|
951
|
-
}) })] })) : mode === 'select-project' && taskToLink ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? '🍄 ' : ' ', project.title] }, project.id))) })] })) : mode === 'confirm-delete' && taskToDelete ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: theme.colors.accent, bold: true, children: fmt(i18n.tui.deleteConfirm || 'Delete "{title}"? (y/n)', { title: taskToDelete.title }) }) })) : (mode === 'task-detail' || mode === 'add-comment') && selectedTask ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(MarioBoxInline, { title: i18n.tui.taskDetailTitle || 'Task Details', width: terminalWidth - 4, minHeight: 4, isActive: true, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus || 'Status', ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(MarioBoxInline, { title: `${i18n.tui.comments || 'Comments'} (${taskComments.length})`, width: terminalWidth - 4, minHeight: 5, isActive: mode === 'task-detail', children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
|
|
1073
|
+
}) })] })) : mode === 'set-effort' && currentTasks.length > 0 ? (_jsx(Box, { flexDirection: "column", children: _jsxs(MarioBoxInline, { title: i18n.tui.effort?.set || 'Set Effort', width: terminalWidth - 4, minHeight: 4, isActive: true, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.set || 'Set effort for', ": ", currentTasks[selectedTaskIndex]?.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: EFFORT_OPTIONS.map((opt, index) => (_jsxs(Text, { color: index === effortSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === effortSelectIndex, children: [index === effortSelectIndex ? '🍄 ' : ' ', opt.label] }, opt.value || 'clear'))) })] }) })) : mode === 'select-project' && taskToLink ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? '🍄 ' : ' ', project.title] }, project.id))) })] })) : mode === 'confirm-delete' && taskToDelete ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: theme.colors.accent, bold: true, children: fmt(i18n.tui.deleteConfirm || 'Delete "{title}"? (y/n)', { title: taskToDelete.title }) }) })) : (mode === 'task-detail' || mode === 'add-comment') && selectedTask ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(MarioBoxInline, { title: i18n.tui.taskDetailTitle || 'Task Details', width: terminalWidth - 4, minHeight: 4, isActive: true, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus || 'Status', ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.label || 'Effort', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.effort ? (i18n.tui.effort?.[selectedTask.effort] || selectedTask.effort) : '-' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.focus?.label || 'Focus', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.isFocused ? '★' : '-' })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(MarioBoxInline, { title: `${i18n.tui.comments || 'Comments'} (${taskComments.length})`, width: terminalWidth - 4, minHeight: 5, isActive: mode === 'task-detail', children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
|
|
952
1074
|
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
953
1075
|
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.textMuted, children: [isSelected ? '🍄 ' : ' ', "[", comment.createdAt.toLocaleString(), "]"] }), _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [' ', comment.content] })] }, comment.id));
|
|
954
1076
|
})) }) })] })) : (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { marginRight: 2, children: _jsx(MarioBoxInline, { title: mode === 'project-detail' && selectedProject ? selectedProject.title : 'STAGE', width: leftPaneWidth, minHeight: 8, isActive: paneFocus === 'tabs' && mode !== 'project-detail', children: mode === 'project-detail' ? (_jsx(Text, { color: theme.colors.textMuted, children: "\u2190 Esc/b: back" })) : (TABS.map((tab, index) => {
|
|
@@ -959,6 +1081,8 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
959
1081
|
const isSelected = (paneFocus === 'tasks' || mode === 'project-detail') && index === selectedTaskIndex;
|
|
960
1082
|
const parentProject = getParentProject(task.parentId);
|
|
961
1083
|
const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
|
|
1084
|
+
const focusPrefix = task.isFocused ? '★ ' : '';
|
|
1085
|
+
const effortBadge = task.effort ? `[${{ 'small': 'S', 'medium': 'M', 'large': 'L' }[task.effort]}] ` : '';
|
|
962
1086
|
const prefix = isSelected ? '🍄 ' : ' ';
|
|
963
1087
|
const suffix = [
|
|
964
1088
|
task.waitingFor ? ` (${task.waitingFor})` : '',
|
|
@@ -966,9 +1090,9 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
966
1090
|
parentProject ? ` [${parentProject.title}]` : '',
|
|
967
1091
|
progress ? ` [${progress.completed}/${progress.total}]` : '',
|
|
968
1092
|
].join('');
|
|
969
|
-
const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(suffix);
|
|
1093
|
+
const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(`${focusPrefix}${effortBadge}`) - getDisplayWidth(suffix);
|
|
970
1094
|
const displayTitle = truncateString(task.title, availableWidth);
|
|
971
|
-
return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, displayTitle, task.waitingFor && _jsxs(Text, { color: theme.colors.muted, children: [" (", task.waitingFor, ")"] }), task.context && _jsxs(Text, { color: theme.colors.muted, children: [" @", task.context] }), parentProject && _jsxs(Text, { color: theme.colors.muted, children: [" [", parentProject.title, "]"] }), progress && _jsxs(Text, { color: theme.colors.muted, children: [" [", progress.completed, "/", progress.total, "]"] })] }, task.id));
|
|
1095
|
+
return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, mode === 'project-detail' && _jsxs(_Fragment, { children: [_jsx(StatusBadge, { status: task.status }), " "] }), focusPrefix, effortBadge, displayTitle, task.waitingFor && _jsxs(Text, { color: theme.colors.muted, children: [" (", task.waitingFor, ")"] }), task.context && _jsxs(Text, { color: theme.colors.muted, children: [" @", task.context] }), parentProject && _jsxs(Text, { color: theme.colors.muted, children: [" [", parentProject.title, "]"] }), progress && _jsxs(Text, { color: theme.colors.muted, children: [" [", progress.completed, "/", progress.total, "]"] })] }, task.id));
|
|
972
1096
|
})) }) })] })), (mode === 'add' || mode === 'add-to-project') && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: mode === 'add-to-project' && selectedProject
|
|
973
1097
|
? `${i18n.tui.newTask}[${selectedProject.title}] `
|
|
974
1098
|
: i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder })] })), mode === 'move-to-waiting' && taskToWaiting && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.waitingFor }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" })] })), mode === 'add-context' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "New context: " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "Enter context name..." })] })), mode === 'add-comment' && selectedTask && (_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.addComment || 'Add comment', ": "] }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "Enter comment..." })] })), mode === 'search' && (_jsx(SearchBar, { value: searchQuery, onChange: handleSearchChange, onSubmit: handleInputSubmit })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.accent, children: "\u2328\uFE0F " }), _jsx(Text, { color: theme.colors.textMuted, children: mode === 'task-detail'
|