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
|
@@ -7,11 +7,12 @@ 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 } from '../../config.js';
|
|
11
|
-
import { useHistory, CreateTaskCommand, MoveTaskCommand, } from '../history/index.js';
|
|
10
|
+
import { isTursoEnabled, getContexts, addContext, getLocale, getContextFilter, setContextFilter as saveContextFilter, getFocusFilter, setFocusFilter } from '../../config.js';
|
|
11
|
+
import { useHistory, CreateTaskCommand, MoveTaskCommand, 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';
|
|
16
17
|
import { Clock } from './Clock.js';
|
|
17
18
|
import { CalendarEvents } from './CalendarEvents.js';
|
|
@@ -127,9 +128,23 @@ export function KanbanDQ({ onOpenSettings }) {
|
|
|
127
128
|
const [searchQuery, setSearchQuery] = useState('');
|
|
128
129
|
const [searchResults, setSearchResults] = useState([]);
|
|
129
130
|
const [searchResultIndex, setSearchResultIndex] = useState(0);
|
|
131
|
+
// Focus filter state
|
|
132
|
+
const [focusFilter, setFocusFilterState] = useState(getFocusFilter());
|
|
133
|
+
const [effortSelectIndex, setEffortSelectIndex] = useState(0);
|
|
130
134
|
const terminalWidth = stdout?.columns || 80;
|
|
131
135
|
const leftPaneWidth = 20;
|
|
132
136
|
const rightPaneWidth = terminalWidth - leftPaneWidth - 6;
|
|
137
|
+
const EFFORT_OPTIONS = [
|
|
138
|
+
{ value: 'small', label: i18n.tui.effort?.small || 'Small' },
|
|
139
|
+
{ value: 'medium', label: i18n.tui.effort?.medium || 'Medium' },
|
|
140
|
+
{ value: 'large', label: i18n.tui.effort?.large || 'Large' },
|
|
141
|
+
{ value: null, label: i18n.tui.effort?.clear || 'Clear' },
|
|
142
|
+
];
|
|
143
|
+
const getCurrentTask = () => {
|
|
144
|
+
if (currentTasks.length === 0)
|
|
145
|
+
return undefined;
|
|
146
|
+
return currentTasks[selectedTaskIndex];
|
|
147
|
+
};
|
|
133
148
|
const loadTasks = useCallback(async () => {
|
|
134
149
|
const db = getDb();
|
|
135
150
|
const filterByContext = (taskList) => {
|
|
@@ -139,6 +154,11 @@ export function KanbanDQ({ onOpenSettings }) {
|
|
|
139
154
|
return taskList.filter(t => !t.context);
|
|
140
155
|
return taskList.filter(t => t.context === contextFilter);
|
|
141
156
|
};
|
|
157
|
+
const filterByFocus = (taskList) => {
|
|
158
|
+
if (!focusFilter)
|
|
159
|
+
return taskList;
|
|
160
|
+
return taskList.filter(t => t.isFocused);
|
|
161
|
+
};
|
|
142
162
|
let todoTasks = await db
|
|
143
163
|
.select()
|
|
144
164
|
.from(schema.tasks)
|
|
@@ -154,12 +174,12 @@ export function KanbanDQ({ onOpenSettings }) {
|
|
|
154
174
|
.from(schema.tasks)
|
|
155
175
|
.where(and(eq(schema.tasks.status, 'done'), eq(schema.tasks.isProject, false), gte(schema.tasks.updatedAt, oneWeekAgo)));
|
|
156
176
|
setTasks({
|
|
157
|
-
todo: filterByContext(todoTasks),
|
|
158
|
-
doing: filterByContext(doingTasks),
|
|
159
|
-
done: filterByContext(doneTasks),
|
|
177
|
+
todo: filterByFocus(filterByContext(todoTasks)),
|
|
178
|
+
doing: filterByFocus(filterByContext(doingTasks)),
|
|
179
|
+
done: filterByFocus(filterByContext(doneTasks)),
|
|
160
180
|
});
|
|
161
181
|
setAvailableContexts(getContexts());
|
|
162
|
-
}, [contextFilter]);
|
|
182
|
+
}, [contextFilter, focusFilter]);
|
|
163
183
|
const loadTaskComments = useCallback(async (taskId) => {
|
|
164
184
|
const db = getDb();
|
|
165
185
|
const comments = await db
|
|
@@ -275,6 +295,48 @@ export function KanbanDQ({ onOpenSettings }) {
|
|
|
275
295
|
setMessage(fmt(i18n.tui.completed, { title: task.title }));
|
|
276
296
|
await loadTasks();
|
|
277
297
|
}, [i18n.tui.completed, loadTasks, history]);
|
|
298
|
+
const toggleTaskFocus = async () => {
|
|
299
|
+
const task = getCurrentTask();
|
|
300
|
+
if (!task)
|
|
301
|
+
return;
|
|
302
|
+
const newFocused = !task.isFocused;
|
|
303
|
+
const description = newFocused
|
|
304
|
+
? fmt(i18n.tui.focus?.taskFocused || 'Focused: "{title}"', { title: task.title })
|
|
305
|
+
: fmt(i18n.tui.focus?.taskUnfocused || 'Unfocused: "{title}"', { title: task.title });
|
|
306
|
+
const command = new SetFocusCommand({
|
|
307
|
+
taskId: task.id,
|
|
308
|
+
fromFocused: task.isFocused,
|
|
309
|
+
toFocused: newFocused,
|
|
310
|
+
description,
|
|
311
|
+
});
|
|
312
|
+
await history.execute(command);
|
|
313
|
+
setMessage(description);
|
|
314
|
+
await loadTasks();
|
|
315
|
+
};
|
|
316
|
+
const toggleFocusFilter = () => {
|
|
317
|
+
const newValue = !focusFilter;
|
|
318
|
+
setFocusFilterState(newValue);
|
|
319
|
+
setFocusFilter(newValue);
|
|
320
|
+
setMessage(newValue ? (i18n.tui.focus?.filterOn || 'Focus filter ON') : (i18n.tui.focus?.filterOff || 'Focus filter OFF'));
|
|
321
|
+
};
|
|
322
|
+
const setTaskEffort = async (effort) => {
|
|
323
|
+
const task = getCurrentTask();
|
|
324
|
+
if (!task)
|
|
325
|
+
return;
|
|
326
|
+
const description = effort
|
|
327
|
+
? fmt(i18n.tui.effort?.effortSet || 'Set effort {effort} for "{title}"', { effort: i18n.tui.effort?.[effort] || effort, title: task.title })
|
|
328
|
+
: fmt(i18n.tui.effort?.effortCleared || 'Cleared effort for "{title}"', { title: task.title });
|
|
329
|
+
const command = new SetEffortCommand({
|
|
330
|
+
taskId: task.id,
|
|
331
|
+
fromEffort: task.effort,
|
|
332
|
+
toEffort: effort,
|
|
333
|
+
description,
|
|
334
|
+
});
|
|
335
|
+
await history.execute(command);
|
|
336
|
+
setMessage(description);
|
|
337
|
+
setMode('normal');
|
|
338
|
+
await loadTasks();
|
|
339
|
+
};
|
|
278
340
|
const handleInputSubmit = async (value) => {
|
|
279
341
|
// Handle search mode submit
|
|
280
342
|
if (mode === 'search') {
|
|
@@ -311,8 +373,8 @@ export function KanbanDQ({ onOpenSettings }) {
|
|
|
311
373
|
}
|
|
312
374
|
};
|
|
313
375
|
useInput((input, key) => {
|
|
314
|
-
// Handle help mode - let
|
|
315
|
-
if (mode === 'help') {
|
|
376
|
+
// Handle help/insights mode - let modals handle their own input
|
|
377
|
+
if (mode === 'help' || mode === 'insights') {
|
|
316
378
|
return;
|
|
317
379
|
}
|
|
318
380
|
if (mode === 'add' || mode === 'add-comment' || mode === 'add-context') {
|
|
@@ -375,6 +437,25 @@ export function KanbanDQ({ onOpenSettings }) {
|
|
|
375
437
|
}
|
|
376
438
|
return;
|
|
377
439
|
}
|
|
440
|
+
if (mode === 'set-effort') {
|
|
441
|
+
if (key.escape) {
|
|
442
|
+
setMode('normal');
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (input === 'j' || key.downArrow) {
|
|
446
|
+
setEffortSelectIndex(prev => Math.min(prev + 1, EFFORT_OPTIONS.length - 1));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (input === 'k' || key.upArrow) {
|
|
450
|
+
setEffortSelectIndex(prev => Math.max(prev - 1, 0));
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (key.return) {
|
|
454
|
+
setTaskEffort(EFFORT_OPTIONS[effortSelectIndex].value);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
378
459
|
if (message)
|
|
379
460
|
setMessage(null);
|
|
380
461
|
// Quit
|
|
@@ -387,6 +468,11 @@ export function KanbanDQ({ onOpenSettings }) {
|
|
|
387
468
|
setMode('help');
|
|
388
469
|
return;
|
|
389
470
|
}
|
|
471
|
+
// Insights
|
|
472
|
+
if (input === 'I') {
|
|
473
|
+
setMode('insights');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
390
476
|
// Calendar
|
|
391
477
|
if (input === 'C') {
|
|
392
478
|
setMode('calendar');
|
|
@@ -488,6 +574,24 @@ export function KanbanDQ({ onOpenSettings }) {
|
|
|
488
574
|
});
|
|
489
575
|
return;
|
|
490
576
|
}
|
|
577
|
+
// Toggle task focus
|
|
578
|
+
if (input === 'g') {
|
|
579
|
+
toggleTaskFocus();
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
// Toggle focus filter
|
|
583
|
+
if (input === 'G') {
|
|
584
|
+
toggleFocusFilter();
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
// Set effort
|
|
588
|
+
if (input === 'E') {
|
|
589
|
+
if (getCurrentTask()) {
|
|
590
|
+
setEffortSelectIndex(0);
|
|
591
|
+
setMode('set-effort');
|
|
592
|
+
}
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
491
595
|
}
|
|
492
596
|
// Refresh
|
|
493
597
|
if (input === 'r' && !key.ctrl) {
|
|
@@ -501,20 +605,25 @@ export function KanbanDQ({ onOpenSettings }) {
|
|
|
501
605
|
if (mode === 'help') {
|
|
502
606
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal'), isKanban: true }) }));
|
|
503
607
|
}
|
|
608
|
+
// Insights modal overlay
|
|
609
|
+
if (mode === 'insights') {
|
|
610
|
+
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(InsightsModal, { onClose: () => setMode('normal') }) }));
|
|
611
|
+
}
|
|
504
612
|
// Calendar modal overlay
|
|
505
613
|
if (mode === 'calendar') {
|
|
506
614
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(CalendarModal, { onClose: () => setMode('normal') }) }));
|
|
507
615
|
}
|
|
508
|
-
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.todo.length + tasks.doing.length }), _jsxs(Text, { color: theme.colors.textMuted, children: ["/", tasks.todo.length + tasks.doing.length + tasks.done.length] }), _jsx(Text, { color: theme.colors.text, children: " MP " }), _jsx(Text, { bold: true, color: theme.colors.statusWaiting, children: tasks.doing.length }), _jsx(Text, { color: theme.colors.accent, children: " [KANBAN]" }), _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" })] })] }), 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) => {
|
|
616
|
+
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.todo.length + tasks.doing.length }), _jsxs(Text, { color: theme.colors.textMuted, children: ["/", tasks.todo.length + tasks.doing.length + tasks.done.length] }), _jsx(Text, { color: theme.colors.text, children: " MP " }), _jsx(Text, { bold: true, color: theme.colors.statusWaiting, children: tasks.doing.length }), _jsx(Text, { color: theme.colors.accent, children: " [KANBAN]" }), _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] })), focusFilter && (_jsx(Text, { color: theme.colors.accent, children: " [\u2605FOCUS]" }))] }), _jsxs(Box, { children: [_jsx(CalendarEvents, { compact: true, showLabel: true, withSeparator: true }), _jsx(Clock, {}), _jsx(Text, { color: theme.colors.textMuted, children: " ?=help q=quit" })] })] }), 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) => {
|
|
509
617
|
const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
|
|
510
618
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
|
|
511
|
-
}) })] })) : (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { marginRight: 2, children: _jsx(TitledBoxInline, { title: 'カテゴリ', width: leftPaneWidth, minHeight: 5, isActive: paneFocus === 'category', children: CATEGORIES.map((cat) => {
|
|
619
|
+
}) })] })) : mode === 'set-effort' ? (_jsx(TitledBoxInline, { title: i18n.tui.effort?.set || 'Set Effort', width: 30, 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.value || 'clear'))) })) : (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { marginRight: 2, children: _jsx(TitledBoxInline, { title: 'カテゴリ', width: leftPaneWidth, minHeight: 5, isActive: paneFocus === 'category', children: CATEGORIES.map((cat) => {
|
|
512
620
|
const isSelected = cat === selectedCategory;
|
|
513
621
|
const count = tasks[cat].length;
|
|
514
622
|
return (_jsxs(Text, { color: isSelected && paneFocus === 'category' ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? '▶ ' : ' ', getCategoryLabel(cat), " (", count, ")"] }, cat));
|
|
515
623
|
}) }) }), _jsx(Box, { flexGrow: 1, children: _jsx(TitledBoxInline, { title: getCategoryLabel(selectedCategory), width: rightPaneWidth, minHeight: 10, isActive: paneFocus === 'tasks', children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
|
|
516
624
|
const isSelected = paneFocus === 'tasks' && index === selectedTaskIndex;
|
|
517
|
-
|
|
625
|
+
const effortBadge = task.effort === 'small' ? '[S]' : task.effort === 'medium' ? '[M]' : task.effort === 'large' ? '[L]' : '';
|
|
626
|
+
return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? '▶ ' : ' ', task.isFocused ? '★ ' : '', task.title, effortBadge && _jsxs(Text, { color: theme.colors.secondary, children: [" ", effortBadge] }), task.context && _jsxs(Text, { color: theme.colors.muted, children: [" @", task.context] })] }, task.id));
|
|
518
627
|
})) }) })] })), mode === 'add' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder })] })), mode === 'search' && (_jsx(SearchBar, { value: searchQuery, onChange: handleSearchChange, onSubmit: handleInputSubmit })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery, viewMode: "kanban" })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: paneFocus === 'category'
|
|
519
628
|
? i18n.tui.kanbanFooter.category
|
|
520
629
|
: i18n.tui.kanbanFooter.tasks }) })] }));
|
|
@@ -7,12 +7,13 @@ 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 } from '../../config.js';
|
|
10
|
+
import { isTursoEnabled, getContexts, addContext, getContextFilter, setContextFilter as saveContextFilter, getFocusFilter, setFocusFilter } from '../../config.js';
|
|
11
11
|
import { VERSION } from '../../version.js';
|
|
12
|
-
import { useHistory, CreateTaskCommand, MoveTaskCommand, } from '../history/index.js';
|
|
12
|
+
import { useHistory, CreateTaskCommand, MoveTaskCommand, 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';
|
|
17
18
|
import { MarioBoxInline } from './MarioBox.js';
|
|
18
19
|
import { Clock } from './Clock.js';
|
|
@@ -49,6 +50,19 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
49
50
|
const [searchQuery, setSearchQuery] = useState('');
|
|
50
51
|
const [searchResults, setSearchResults] = useState([]);
|
|
51
52
|
const [searchResultIndex, setSearchResultIndex] = useState(0);
|
|
53
|
+
const [focusFilter, setFocusFilterState] = useState(getFocusFilter());
|
|
54
|
+
const [effortSelectIndex, setEffortSelectIndex] = useState(0);
|
|
55
|
+
const EFFORT_OPTIONS = [
|
|
56
|
+
{ value: 'small', label: i18n.tui.effort?.small || 'Small' },
|
|
57
|
+
{ value: 'medium', label: i18n.tui.effort?.medium || 'Medium' },
|
|
58
|
+
{ value: 'large', label: i18n.tui.effort?.large || 'Large' },
|
|
59
|
+
{ value: null, label: i18n.tui.effort?.clear || 'Clear' },
|
|
60
|
+
];
|
|
61
|
+
const EFFORT_LABELS = {
|
|
62
|
+
small: 'S',
|
|
63
|
+
medium: 'M',
|
|
64
|
+
large: 'L',
|
|
65
|
+
};
|
|
52
66
|
const terminalWidth = stdout?.columns || 80;
|
|
53
67
|
const leftPaneWidth = 20;
|
|
54
68
|
const rightPaneWidth = terminalWidth - leftPaneWidth - 6;
|
|
@@ -75,13 +89,18 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
75
89
|
.select()
|
|
76
90
|
.from(schema.tasks)
|
|
77
91
|
.where(and(eq(schema.tasks.status, 'done'), eq(schema.tasks.isProject, false), gte(schema.tasks.updatedAt, oneWeekAgo)));
|
|
92
|
+
const filterByFocus = (taskList) => {
|
|
93
|
+
if (!focusFilter)
|
|
94
|
+
return taskList;
|
|
95
|
+
return taskList.filter(t => t.isFocused);
|
|
96
|
+
};
|
|
78
97
|
setTasks({
|
|
79
|
-
todo: filterByContext(todoTasks),
|
|
80
|
-
doing: filterByContext(doingTasks),
|
|
81
|
-
done: filterByContext(doneTasks),
|
|
98
|
+
todo: filterByFocus(filterByContext(todoTasks)),
|
|
99
|
+
doing: filterByFocus(filterByContext(doingTasks)),
|
|
100
|
+
done: filterByFocus(filterByContext(doneTasks)),
|
|
82
101
|
});
|
|
83
102
|
setAvailableContexts(getContexts());
|
|
84
|
-
}, [contextFilter]);
|
|
103
|
+
}, [contextFilter, focusFilter]);
|
|
85
104
|
const loadTaskComments = useCallback(async (taskId) => {
|
|
86
105
|
const db = getDb();
|
|
87
106
|
const comments = await db
|
|
@@ -138,6 +157,53 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
138
157
|
loadTasks();
|
|
139
158
|
}, [loadTasks]);
|
|
140
159
|
const currentTasks = tasks[selectedCategory];
|
|
160
|
+
const getCurrentTask = () => {
|
|
161
|
+
if (currentTasks.length === 0)
|
|
162
|
+
return undefined;
|
|
163
|
+
return currentTasks[selectedTaskIndex];
|
|
164
|
+
};
|
|
165
|
+
const toggleTaskFocus = async () => {
|
|
166
|
+
const task = getCurrentTask();
|
|
167
|
+
if (!task)
|
|
168
|
+
return;
|
|
169
|
+
const newFocused = !task.isFocused;
|
|
170
|
+
const description = newFocused
|
|
171
|
+
? fmt(i18n.tui.focus?.taskFocused || 'Focused: "{title}"', { title: task.title })
|
|
172
|
+
: fmt(i18n.tui.focus?.taskUnfocused || 'Unfocused: "{title}"', { title: task.title });
|
|
173
|
+
const command = new SetFocusCommand({
|
|
174
|
+
taskId: task.id,
|
|
175
|
+
fromFocused: task.isFocused,
|
|
176
|
+
toFocused: newFocused,
|
|
177
|
+
description,
|
|
178
|
+
});
|
|
179
|
+
await history.execute(command);
|
|
180
|
+
setMessage(description);
|
|
181
|
+
await loadTasks();
|
|
182
|
+
};
|
|
183
|
+
const toggleFocusFilter = () => {
|
|
184
|
+
const newValue = !focusFilter;
|
|
185
|
+
setFocusFilterState(newValue);
|
|
186
|
+
setFocusFilter(newValue);
|
|
187
|
+
setMessage(newValue ? (i18n.tui.focus?.filterOn || 'Focus filter ON') : (i18n.tui.focus?.filterOff || 'Focus filter OFF'));
|
|
188
|
+
};
|
|
189
|
+
const setTaskEffort = async (effort) => {
|
|
190
|
+
const task = getCurrentTask();
|
|
191
|
+
if (!task)
|
|
192
|
+
return;
|
|
193
|
+
const description = effort
|
|
194
|
+
? fmt(i18n.tui.effort?.effortSet || 'Set effort {effort} for "{title}"', { effort: i18n.tui.effort?.[effort] || effort, title: task.title })
|
|
195
|
+
: fmt(i18n.tui.effort?.effortCleared || 'Cleared effort for "{title}"', { title: task.title });
|
|
196
|
+
const command = new SetEffortCommand({
|
|
197
|
+
taskId: task.id,
|
|
198
|
+
fromEffort: task.effort,
|
|
199
|
+
toEffort: effort,
|
|
200
|
+
description,
|
|
201
|
+
});
|
|
202
|
+
await history.execute(command);
|
|
203
|
+
setMessage(description);
|
|
204
|
+
setMode('normal');
|
|
205
|
+
await loadTasks();
|
|
206
|
+
};
|
|
141
207
|
const getCategoryLabel = (cat) => {
|
|
142
208
|
return i18n.kanban[cat];
|
|
143
209
|
};
|
|
@@ -233,8 +299,8 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
233
299
|
}
|
|
234
300
|
};
|
|
235
301
|
useInput((input, key) => {
|
|
236
|
-
// Handle help mode - let
|
|
237
|
-
if (mode === 'help') {
|
|
302
|
+
// Handle help/insights mode - let modals handle their own input
|
|
303
|
+
if (mode === 'help' || mode === 'insights') {
|
|
238
304
|
return;
|
|
239
305
|
}
|
|
240
306
|
if (mode === 'add' || mode === 'add-comment' || mode === 'add-context') {
|
|
@@ -297,6 +363,26 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
297
363
|
}
|
|
298
364
|
return;
|
|
299
365
|
}
|
|
366
|
+
// Handle set-effort mode
|
|
367
|
+
if (mode === 'set-effort') {
|
|
368
|
+
if (key.escape) {
|
|
369
|
+
setMode('normal');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (input === 'j' || key.downArrow) {
|
|
373
|
+
setEffortSelectIndex(prev => Math.min(prev + 1, EFFORT_OPTIONS.length - 1));
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (input === 'k' || key.upArrow) {
|
|
377
|
+
setEffortSelectIndex(prev => Math.max(prev - 1, 0));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (key.return) {
|
|
381
|
+
setTaskEffort(EFFORT_OPTIONS[effortSelectIndex].value);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
300
386
|
if (message)
|
|
301
387
|
setMessage(null);
|
|
302
388
|
// Quit
|
|
@@ -309,6 +395,11 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
309
395
|
setMode('help');
|
|
310
396
|
return;
|
|
311
397
|
}
|
|
398
|
+
// Insights
|
|
399
|
+
if (input === 'I') {
|
|
400
|
+
setMode('insights');
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
312
403
|
// Calendar
|
|
313
404
|
if (input === 'C') {
|
|
314
405
|
setMode('calendar');
|
|
@@ -397,6 +488,24 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
397
488
|
});
|
|
398
489
|
return;
|
|
399
490
|
}
|
|
491
|
+
// Toggle focus
|
|
492
|
+
if (input === 'g') {
|
|
493
|
+
toggleTaskFocus();
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
// Toggle focus filter
|
|
497
|
+
if (input === 'G') {
|
|
498
|
+
toggleFocusFilter();
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
// Set effort
|
|
502
|
+
if (input === 'E') {
|
|
503
|
+
if (getCurrentTask()) {
|
|
504
|
+
setEffortSelectIndex(0);
|
|
505
|
+
setMode('set-effort');
|
|
506
|
+
}
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
400
509
|
// Undo
|
|
401
510
|
if (input === 'u') {
|
|
402
511
|
history.undo().then((didUndo) => {
|
|
@@ -423,11 +532,15 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
423
532
|
if (mode === 'help') {
|
|
424
533
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal'), isKanban: true }) }));
|
|
425
534
|
}
|
|
535
|
+
// Insights modal overlay
|
|
536
|
+
if (mode === 'insights') {
|
|
537
|
+
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(InsightsModal, { onClose: () => setMode('normal') }) }));
|
|
538
|
+
}
|
|
426
539
|
// Calendar modal overlay
|
|
427
540
|
if (mode === 'calendar') {
|
|
428
541
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(CalendarModal, { onClose: () => setMode('normal') }) }));
|
|
429
542
|
}
|
|
430
|
-
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.todo.length + tasks.doing.length).toString().padStart(2, '0') }), _jsxs(Text, { color: theme.colors.textMuted, children: [" FLOQ v", VERSION] }), _jsx(Text, { color: theme.colors.accent, children: " [KANBAN]" }), _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, {})] })] }), 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) => {
|
|
543
|
+
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.todo.length + tasks.doing.length).toString().padStart(2, '0') }), _jsxs(Text, { color: theme.colors.textMuted, children: [" FLOQ v", VERSION] }), _jsx(Text, { color: theme.colors.accent, children: " [KANBAN]" }), _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, {})] })] }), 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) => {
|
|
431
544
|
const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
|
|
432
545
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '🍄 ' : ' ', label] }, ctx));
|
|
433
546
|
}) })] })) : (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { marginRight: 2, children: _jsx(MarioBoxInline, { title: 'STAGE', width: leftPaneWidth, minHeight: 5, isActive: paneFocus === 'category', children: CATEGORIES.map((cat) => {
|
|
@@ -436,8 +549,9 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
436
549
|
return (_jsxs(Text, { color: isSelected && paneFocus === 'category' ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? '🍄 ' : ' ', getCategoryLabel(cat), " (", count, ")"] }, cat));
|
|
437
550
|
}) }) }), _jsx(Box, { flexGrow: 1, children: _jsx(MarioBoxInline, { title: getCategoryLabel(selectedCategory), width: rightPaneWidth, minHeight: 10, isActive: paneFocus === 'tasks', children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
|
|
438
551
|
const isSelected = paneFocus === 'tasks' && index === selectedTaskIndex;
|
|
439
|
-
|
|
440
|
-
|
|
552
|
+
const shortId = task.id.slice(0, 4);
|
|
553
|
+
return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? '🍄 ' : ' ', task.isFocused ? '★ ' : '', task.effort ? `[${EFFORT_LABELS[task.effort]}] ` : '', "[", shortId, "] ", task.title, task.context && _jsxs(Text, { color: theme.colors.muted, children: [" @", task.context] })] }, task.id));
|
|
554
|
+
})) }) })] })), mode === 'add' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder })] })), mode === 'search' && (_jsx(SearchBar, { value: searchQuery, onChange: handleSearchChange, onSubmit: handleInputSubmit })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery, viewMode: "kanban" })), mode === 'set-effort' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(MarioBoxInline, { title: i18n.tui.effort?.set || 'Set effort', width: 30, minHeight: EFFORT_OPTIONS.length + 2, 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))) }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.effort?.setHelp || 'j/k: select, Enter: confirm, Esc: cancel' })] })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: paneFocus === 'category'
|
|
441
555
|
? i18n.tui.kanbanFooter.category
|
|
442
556
|
: i18n.tui.kanbanFooter.tasks }) })] }));
|
|
443
557
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { t } from '../../i18n/index.js';
|
|
4
4
|
import { useTheme } from '../theme/index.js';
|
|
5
5
|
import { ProgressBar } from './ProgressBar.js';
|
|
6
|
+
const EFFORT_LABELS = {
|
|
7
|
+
small: 'S',
|
|
8
|
+
medium: 'M',
|
|
9
|
+
large: 'L',
|
|
10
|
+
};
|
|
6
11
|
export function TaskItem({ task, isSelected, projectName, progress }) {
|
|
7
12
|
const shortId = task.id.slice(0, 8);
|
|
8
13
|
const i18n = t();
|
|
9
14
|
const theme = useTheme();
|
|
10
|
-
return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix, "[", shortId, "] ", task.title, task.context && (_jsxs(Text, { color: theme.colors.accent, children: [" @", task.context] })), projectName && (_jsxs(Text, { color: theme.colors.statusSomeday, children: [" [", projectName, "]"] })), progress && progress.total > 0 && (_jsx(ProgressBar, { completed: progress.completed, total: progress.total })), task.waitingFor && (_jsxs(Text, { color: theme.colors.statusWaiting, children: [" (", i18n.status.waiting.toLowerCase(), ": ", task.waitingFor, ")"] })), task.dueDate && (_jsxs(Text, { color: theme.colors.accent, children: [" (due: ", task.dueDate.toLocaleDateString(), ")"] }))] }) }));
|
|
15
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix, task.isFocused && _jsx(Text, { color: theme.colors.accent, children: "\u2605 " }), "[", shortId, "] ", task.effort && _jsxs(Text, { color: theme.colors.secondary, children: ["[", EFFORT_LABELS[task.effort], "] "] }), task.title, task.context && (_jsxs(Text, { color: theme.colors.accent, children: [" @", task.context] })), projectName && (_jsxs(Text, { color: theme.colors.statusSomeday, children: [" [", projectName, "]"] })), progress && progress.total > 0 && (_jsx(ProgressBar, { completed: progress.completed, total: progress.total })), task.waitingFor && (_jsxs(Text, { color: theme.colors.statusWaiting, children: [" (", i18n.status.waiting.toLowerCase(), ": ", task.waitingFor, ")"] })), task.dueDate && (_jsxs(Text, { color: theme.colors.accent, children: [" (due: ", task.dueDate.toLocaleDateString(), ")"] }))] }) }));
|
|
11
16
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { UndoableCommand } from '../types.js';
|
|
2
|
+
interface SetEffortParams {
|
|
3
|
+
taskId: string;
|
|
4
|
+
fromEffort: string | null;
|
|
5
|
+
toEffort: string | null;
|
|
6
|
+
description: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Command to set/change a task's effort size
|
|
10
|
+
*/
|
|
11
|
+
export declare class SetEffortCommand implements UndoableCommand {
|
|
12
|
+
readonly description: string;
|
|
13
|
+
private readonly taskId;
|
|
14
|
+
private readonly fromEffort;
|
|
15
|
+
private readonly toEffort;
|
|
16
|
+
constructor(params: SetEffortParams);
|
|
17
|
+
execute(): Promise<void>;
|
|
18
|
+
undo(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
2
|
+
import { getDb, schema } from '../../../db/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Command to set/change a task's effort size
|
|
5
|
+
*/
|
|
6
|
+
export class SetEffortCommand {
|
|
7
|
+
description;
|
|
8
|
+
taskId;
|
|
9
|
+
fromEffort;
|
|
10
|
+
toEffort;
|
|
11
|
+
constructor(params) {
|
|
12
|
+
this.taskId = params.taskId;
|
|
13
|
+
this.fromEffort = params.fromEffort;
|
|
14
|
+
this.toEffort = params.toEffort;
|
|
15
|
+
this.description = params.description;
|
|
16
|
+
}
|
|
17
|
+
async execute() {
|
|
18
|
+
const db = getDb();
|
|
19
|
+
await db
|
|
20
|
+
.update(schema.tasks)
|
|
21
|
+
.set({
|
|
22
|
+
effort: this.toEffort,
|
|
23
|
+
updatedAt: new Date(),
|
|
24
|
+
})
|
|
25
|
+
.where(eq(schema.tasks.id, this.taskId));
|
|
26
|
+
}
|
|
27
|
+
async undo() {
|
|
28
|
+
const db = getDb();
|
|
29
|
+
await db
|
|
30
|
+
.update(schema.tasks)
|
|
31
|
+
.set({
|
|
32
|
+
effort: this.fromEffort,
|
|
33
|
+
updatedAt: new Date(),
|
|
34
|
+
})
|
|
35
|
+
.where(eq(schema.tasks.id, this.taskId));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { UndoableCommand } from '../types.js';
|
|
2
|
+
interface SetFocusParams {
|
|
3
|
+
taskId: string;
|
|
4
|
+
fromFocused: boolean;
|
|
5
|
+
toFocused: boolean;
|
|
6
|
+
description: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Command to toggle a task's focus state
|
|
10
|
+
*/
|
|
11
|
+
export declare class SetFocusCommand implements UndoableCommand {
|
|
12
|
+
readonly description: string;
|
|
13
|
+
private readonly taskId;
|
|
14
|
+
private readonly fromFocused;
|
|
15
|
+
private readonly toFocused;
|
|
16
|
+
constructor(params: SetFocusParams);
|
|
17
|
+
execute(): Promise<void>;
|
|
18
|
+
undo(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
2
|
+
import { getDb, schema } from '../../../db/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Command to toggle a task's focus state
|
|
5
|
+
*/
|
|
6
|
+
export class SetFocusCommand {
|
|
7
|
+
description;
|
|
8
|
+
taskId;
|
|
9
|
+
fromFocused;
|
|
10
|
+
toFocused;
|
|
11
|
+
constructor(params) {
|
|
12
|
+
this.taskId = params.taskId;
|
|
13
|
+
this.fromFocused = params.fromFocused;
|
|
14
|
+
this.toFocused = params.toFocused;
|
|
15
|
+
this.description = params.description;
|
|
16
|
+
}
|
|
17
|
+
async execute() {
|
|
18
|
+
const db = getDb();
|
|
19
|
+
await db
|
|
20
|
+
.update(schema.tasks)
|
|
21
|
+
.set({
|
|
22
|
+
isFocused: this.toFocused,
|
|
23
|
+
updatedAt: new Date(),
|
|
24
|
+
})
|
|
25
|
+
.where(eq(schema.tasks.id, this.taskId));
|
|
26
|
+
}
|
|
27
|
+
async undo() {
|
|
28
|
+
const db = getDb();
|
|
29
|
+
await db
|
|
30
|
+
.update(schema.tasks)
|
|
31
|
+
.set({
|
|
32
|
+
isFocused: this.fromFocused,
|
|
33
|
+
updatedAt: new Date(),
|
|
34
|
+
})
|
|
35
|
+
.where(eq(schema.tasks.id, this.taskId));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -6,3 +6,5 @@ export { ConvertToProjectCommand } from './ConvertToProjectCommand.js';
|
|
|
6
6
|
export { CreateCommentCommand } from './CreateCommentCommand.js';
|
|
7
7
|
export { DeleteCommentCommand } from './DeleteCommentCommand.js';
|
|
8
8
|
export { SetContextCommand } from './SetContextCommand.js';
|
|
9
|
+
export { SetFocusCommand } from './SetFocusCommand.js';
|
|
10
|
+
export { SetEffortCommand } from './SetEffortCommand.js';
|
|
@@ -6,3 +6,5 @@ export { ConvertToProjectCommand } from './ConvertToProjectCommand.js';
|
|
|
6
6
|
export { CreateCommentCommand } from './CreateCommentCommand.js';
|
|
7
7
|
export { DeleteCommentCommand } from './DeleteCommentCommand.js';
|
|
8
8
|
export { SetContextCommand } from './SetContextCommand.js';
|
|
9
|
+
export { SetFocusCommand } from './SetFocusCommand.js';
|
|
10
|
+
export { SetEffortCommand } from './SetEffortCommand.js';
|
|
@@ -3,4 +3,4 @@ export { MAX_HISTORY_SIZE } from './types.js';
|
|
|
3
3
|
export { HistoryManager, getHistoryManager } from './HistoryManager.js';
|
|
4
4
|
export { HistoryProvider, useHistoryContext } from './HistoryContext.js';
|
|
5
5
|
export { useHistory } from './useHistory.js';
|
|
6
|
-
export { CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from './commands/index.js';
|
|
6
|
+
export { CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, SetFocusCommand, SetEffortCommand, } from './commands/index.js';
|
package/dist/ui/history/index.js
CHANGED
|
@@ -5,4 +5,4 @@ export { HistoryManager, getHistoryManager } from './HistoryManager.js';
|
|
|
5
5
|
export { HistoryProvider, useHistoryContext } from './HistoryContext.js';
|
|
6
6
|
export { useHistory } from './useHistory.js';
|
|
7
7
|
// Commands
|
|
8
|
-
export { CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from './commands/index.js';
|
|
8
|
+
export { CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, SetFocusCommand, SetEffortCommand, } from './commands/index.js';
|