floq 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/changelog.js +26 -9
- package/dist/cli.js +8 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +8 -0
- package/dist/index.js +0 -0
- package/dist/ui/App.js +7 -3
- package/dist/ui/components/GtdDQ.js +10 -2
- package/dist/ui/components/GtdMario.js +10 -2
- package/dist/ui/components/KanbanBoard.js +7 -3
- package/dist/ui/components/KanbanDQ.js +7 -2
- package/dist/ui/components/KanbanMario.js +7 -2
- package/package.json +1 -1
- package/dist/ui/components/DQLayout.d.ts +0 -36
- package/dist/ui/components/DQLayout.js +0 -53
- package/dist/ui/components/DQTaskList.d.ts +0 -53
- package/dist/ui/components/DQTaskList.js +0 -48
- package/dist/ui/components/DQWindow.d.ts +0 -19
- package/dist/ui/components/DQWindow.js +0 -33
- package/dist/ui/components/FullScreenBox.d.ts +0 -8
- package/dist/ui/components/FullScreenBox.js +0 -10
package/dist/changelog.js
CHANGED
|
@@ -1,27 +1,44 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { dirname, join } from 'path';
|
|
4
|
+
import { getLocale } from './config.js';
|
|
4
5
|
function findChangelogPath() {
|
|
5
6
|
// Get the directory of the current module
|
|
6
7
|
const currentFile = fileURLToPath(import.meta.url);
|
|
7
8
|
const currentDir = dirname(currentFile);
|
|
8
|
-
|
|
9
|
+
const locale = getLocale();
|
|
10
|
+
const filename = locale === 'ja' ? 'CHANGELOG.ja.md' : 'CHANGELOG.md';
|
|
11
|
+
const fallbackFilename = 'CHANGELOG.md';
|
|
12
|
+
// Try to find localized CHANGELOG relative to the package root
|
|
9
13
|
// From dist/ or src/, go up one level
|
|
10
|
-
const
|
|
11
|
-
join(currentDir, '..'
|
|
12
|
-
join(currentDir, '..', '..'
|
|
13
|
-
|
|
14
|
+
const basePaths = [
|
|
15
|
+
join(currentDir, '..'),
|
|
16
|
+
join(currentDir, '..', '..'),
|
|
17
|
+
process.cwd(),
|
|
14
18
|
];
|
|
15
|
-
|
|
19
|
+
// First, try to find the localized version
|
|
20
|
+
for (const basePath of basePaths) {
|
|
21
|
+
const localizedPath = join(basePath, filename);
|
|
16
22
|
try {
|
|
17
|
-
readFileSync(
|
|
18
|
-
return
|
|
23
|
+
readFileSync(localizedPath, 'utf-8');
|
|
24
|
+
return localizedPath;
|
|
19
25
|
}
|
|
20
26
|
catch {
|
|
21
27
|
// continue to next path
|
|
22
28
|
}
|
|
23
29
|
}
|
|
24
|
-
|
|
30
|
+
// Fall back to default CHANGELOG.md
|
|
31
|
+
for (const basePath of basePaths) {
|
|
32
|
+
const fallbackPath = join(basePath, fallbackFilename);
|
|
33
|
+
try {
|
|
34
|
+
readFileSync(fallbackPath, 'utf-8');
|
|
35
|
+
return fallbackPath;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// continue to next path
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return join(basePaths[0], fallbackFilename); // Return first path as fallback
|
|
25
42
|
}
|
|
26
43
|
export function parseChangelog() {
|
|
27
44
|
const changelogPath = findChangelogPath();
|
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,14 @@ program
|
|
|
20
20
|
// Default command - launch TUI
|
|
21
21
|
program
|
|
22
22
|
.action(() => {
|
|
23
|
-
|
|
23
|
+
// Enter alternate screen buffer (like btop/vim)
|
|
24
|
+
process.stdout.write('\x1b[?1049h');
|
|
25
|
+
process.stdout.write('\x1b[H'); // Move cursor to top-left
|
|
26
|
+
const { waitUntilExit } = render(React.createElement(App));
|
|
27
|
+
waitUntilExit().then(() => {
|
|
28
|
+
// Leave alternate screen buffer
|
|
29
|
+
process.stdout.write('\x1b[?1049l');
|
|
30
|
+
});
|
|
24
31
|
});
|
|
25
32
|
// Add task
|
|
26
33
|
program
|
package/dist/config.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface Config {
|
|
|
14
14
|
turso?: TursoConfig;
|
|
15
15
|
contexts?: string[];
|
|
16
16
|
splashDuration?: number;
|
|
17
|
+
contextFilter?: string | null;
|
|
17
18
|
}
|
|
18
19
|
export declare function loadConfig(): Config;
|
|
19
20
|
export declare function saveConfig(updates: Partial<Config>): void;
|
|
@@ -33,3 +34,5 @@ export declare function addContext(context: string): boolean;
|
|
|
33
34
|
export declare function removeContext(context: string): boolean;
|
|
34
35
|
export declare function getSplashDuration(): number;
|
|
35
36
|
export declare function setSplashDuration(duration: number): void;
|
|
37
|
+
export declare function getContextFilter(): string | null;
|
|
38
|
+
export declare function setContextFilter(contextFilter: string | null): void;
|
package/dist/config.js
CHANGED
|
@@ -151,3 +151,11 @@ export function setSplashDuration(duration) {
|
|
|
151
151
|
// Allow -1 (wait for key), 0 (disabled), or positive values
|
|
152
152
|
saveConfig({ splashDuration: duration >= 0 ? duration : -1 });
|
|
153
153
|
}
|
|
154
|
+
export function getContextFilter() {
|
|
155
|
+
const config = loadConfig();
|
|
156
|
+
// undefined means not set (default to null = all)
|
|
157
|
+
return config.contextFilter === undefined ? null : config.contextFilter;
|
|
158
|
+
}
|
|
159
|
+
export function setContextFilter(contextFilter) {
|
|
160
|
+
saveConfig({ contextFilter });
|
|
161
|
+
}
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/ui/App.js
CHANGED
|
@@ -17,7 +17,7 @@ import { LanguageSelector } from './LanguageSelector.js';
|
|
|
17
17
|
import { getDb, schema } from '../db/index.js';
|
|
18
18
|
import { t, fmt } from '../i18n/index.js';
|
|
19
19
|
import { ThemeProvider, useTheme, getTheme } from './theme/index.js';
|
|
20
|
-
import { getThemeName, getViewMode, setThemeName, setViewMode, setLocale, isTursoEnabled, getContexts, addContext, getSplashDuration } from '../config.js';
|
|
20
|
+
import { getThemeName, getViewMode, setThemeName, setViewMode, setLocale, isTursoEnabled, getContexts, addContext, getSplashDuration, getContextFilter, setContextFilter as saveContextFilter } from '../config.js';
|
|
21
21
|
import { KanbanBoard } from './components/KanbanBoard.js';
|
|
22
22
|
import { KanbanDQ } from './components/KanbanDQ.js';
|
|
23
23
|
import { KanbanMario } from './components/KanbanMario.js';
|
|
@@ -101,8 +101,12 @@ function AppContent({ onOpenSettings }) {
|
|
|
101
101
|
const [searchQuery, setSearchQuery] = useState('');
|
|
102
102
|
const [searchResults, setSearchResults] = useState([]);
|
|
103
103
|
const [searchResultIndex, setSearchResultIndex] = useState(0);
|
|
104
|
-
// Context filter state
|
|
105
|
-
const [contextFilter,
|
|
104
|
+
// Context filter state - load from config for persistence across sessions/terminals
|
|
105
|
+
const [contextFilter, setContextFilterState] = useState(() => getContextFilter());
|
|
106
|
+
const setContextFilter = useCallback((value) => {
|
|
107
|
+
setContextFilterState(value);
|
|
108
|
+
saveContextFilter(value);
|
|
109
|
+
}, []);
|
|
106
110
|
const [contextSelectIndex, setContextSelectIndex] = useState(0);
|
|
107
111
|
const [availableContexts, setAvailableContexts] = useState([]);
|
|
108
112
|
const i18n = t();
|
|
@@ -7,7 +7,7 @@ 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 } from '../../config.js';
|
|
10
|
+
import { isTursoEnabled, getContexts, addContext, getLocale, getContextFilter, setContextFilter as saveContextFilter } from '../../config.js';
|
|
11
11
|
import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from '../history/index.js';
|
|
12
12
|
import { SearchBar } from './SearchBar.js';
|
|
13
13
|
import { SearchResults } from './SearchResults.js';
|
|
@@ -139,7 +139,12 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
139
139
|
const [taskToWaiting, setTaskToWaiting] = useState(null);
|
|
140
140
|
const [taskToDelete, setTaskToDelete] = useState(null);
|
|
141
141
|
const [projectProgress, setProjectProgress] = useState({});
|
|
142
|
-
|
|
142
|
+
// Context filter state - load from config for persistence across sessions/terminals
|
|
143
|
+
const [contextFilter, setContextFilterState] = useState(() => getContextFilter());
|
|
144
|
+
const setContextFilter = useCallback((value) => {
|
|
145
|
+
setContextFilterState(value);
|
|
146
|
+
saveContextFilter(value);
|
|
147
|
+
}, []);
|
|
143
148
|
const [contextSelectIndex, setContextSelectIndex] = useState(0);
|
|
144
149
|
const [availableContexts, setAvailableContexts] = useState([]);
|
|
145
150
|
const [searchQuery, setSearchQuery] = useState('');
|
|
@@ -894,6 +899,9 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
894
899
|
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] }))] }), _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) => {
|
|
895
900
|
const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
|
|
896
901
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
|
|
902
|
+
}) })] })) : 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) => {
|
|
903
|
+
const label = ctx === 'clear' ? (i18n.tui.context?.none || 'Clear context') : ctx === 'new' ? (i18n.tui.context?.addNew || 'New context...') : `@${ctx}`;
|
|
904
|
+
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
|
|
897
905
|
}) })] })) : 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) => {
|
|
898
906
|
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
899
907
|
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));
|
|
@@ -7,7 +7,7 @@ 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 } from '../../config.js';
|
|
10
|
+
import { isTursoEnabled, getContexts, addContext, getContextFilter, setContextFilter as saveContextFilter } from '../../config.js';
|
|
11
11
|
import { VERSION } from '../../version.js';
|
|
12
12
|
import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from '../history/index.js';
|
|
13
13
|
import { SearchBar } from './SearchBar.js';
|
|
@@ -81,7 +81,12 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
81
81
|
const [taskToWaiting, setTaskToWaiting] = useState(null);
|
|
82
82
|
const [taskToDelete, setTaskToDelete] = useState(null);
|
|
83
83
|
const [projectProgress, setProjectProgress] = useState({});
|
|
84
|
-
|
|
84
|
+
// Context filter state - load from config for persistence across sessions/terminals
|
|
85
|
+
const [contextFilter, setContextFilterState] = useState(() => getContextFilter());
|
|
86
|
+
const setContextFilter = useCallback((value) => {
|
|
87
|
+
setContextFilterState(value);
|
|
88
|
+
saveContextFilter(value);
|
|
89
|
+
}, []);
|
|
85
90
|
const [contextSelectIndex, setContextSelectIndex] = useState(0);
|
|
86
91
|
const [availableContexts, setAvailableContexts] = useState([]);
|
|
87
92
|
const [searchQuery, setSearchQuery] = useState('');
|
|
@@ -798,6 +803,9 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
798
803
|
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(Text, { color: theme.colors.secondary, children: "TIME " }), _jsx(Text, { color: theme.colors.text, children: new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' }) })] })] }), 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) => {
|
|
799
804
|
const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
|
|
800
805
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '🍄 ' : ' ', label] }, ctx));
|
|
806
|
+
}) })] })) : 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) => {
|
|
807
|
+
const label = ctx === 'clear' ? (i18n.tui.context?.none || 'Clear context') : ctx === 'new' ? (i18n.tui.context?.addNew || 'New context...') : `@${ctx}`;
|
|
808
|
+
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '🍄 ' : ' ', label] }, ctx));
|
|
801
809
|
}) })] })) : 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) => {
|
|
802
810
|
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
803
811
|
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));
|
|
@@ -12,7 +12,7 @@ import { SearchResults } from './SearchResults.js';
|
|
|
12
12
|
import { getDb, schema } from '../../db/index.js';
|
|
13
13
|
import { t, fmt } from '../../i18n/index.js';
|
|
14
14
|
import { useTheme } from '../theme/index.js';
|
|
15
|
-
import { isTursoEnabled, getContexts, addContext } from '../../config.js';
|
|
15
|
+
import { isTursoEnabled, getContexts, addContext, getContextFilter, setContextFilter as saveContextFilter } from '../../config.js';
|
|
16
16
|
import { VERSION } from '../../version.js';
|
|
17
17
|
import { useHistory, CreateTaskCommand, MoveTaskCommand, LinkTaskCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from '../history/index.js';
|
|
18
18
|
const COLUMNS = ['todo', 'doing', 'done'];
|
|
@@ -43,8 +43,12 @@ export function KanbanBoard({ onSwitchToGtd, onOpenSettings }) {
|
|
|
43
43
|
const [searchQuery, setSearchQuery] = useState('');
|
|
44
44
|
const [searchResults, setSearchResults] = useState([]);
|
|
45
45
|
const [searchResultIndex, setSearchResultIndex] = useState(0);
|
|
46
|
-
// Context filter state
|
|
47
|
-
const [contextFilter,
|
|
46
|
+
// Context filter state - load from config for persistence across sessions/terminals
|
|
47
|
+
const [contextFilter, setContextFilterState] = useState(() => getContextFilter());
|
|
48
|
+
const setContextFilter = useCallback((value) => {
|
|
49
|
+
setContextFilterState(value);
|
|
50
|
+
saveContextFilter(value);
|
|
51
|
+
}, []);
|
|
48
52
|
const [contextSelectIndex, setContextSelectIndex] = useState(0);
|
|
49
53
|
const [availableContexts, setAvailableContexts] = useState([]);
|
|
50
54
|
const i18n = t();
|
|
@@ -7,7 +7,7 @@ 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 } from '../../config.js';
|
|
10
|
+
import { isTursoEnabled, getContexts, addContext, getLocale, getContextFilter, setContextFilter as saveContextFilter } from '../../config.js';
|
|
11
11
|
import { useHistory, CreateTaskCommand, MoveTaskCommand, } from '../history/index.js';
|
|
12
12
|
import { SearchBar } from './SearchBar.js';
|
|
13
13
|
import { SearchResults } from './SearchResults.js';
|
|
@@ -113,7 +113,12 @@ export function KanbanDQ({ onOpenSettings }) {
|
|
|
113
113
|
const [selectedTask, setSelectedTask] = useState(null);
|
|
114
114
|
const [taskComments, setTaskComments] = useState([]);
|
|
115
115
|
const [selectedCommentIndex, setSelectedCommentIndex] = useState(0);
|
|
116
|
-
|
|
116
|
+
// Context filter state - load from config for persistence across sessions/terminals
|
|
117
|
+
const [contextFilter, setContextFilterState] = useState(() => getContextFilter());
|
|
118
|
+
const setContextFilter = useCallback((value) => {
|
|
119
|
+
setContextFilterState(value);
|
|
120
|
+
saveContextFilter(value);
|
|
121
|
+
}, []);
|
|
117
122
|
const [contextSelectIndex, setContextSelectIndex] = useState(0);
|
|
118
123
|
const [availableContexts, setAvailableContexts] = useState([]);
|
|
119
124
|
const [searchQuery, setSearchQuery] = useState('');
|
|
@@ -7,7 +7,7 @@ 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 } from '../../config.js';
|
|
10
|
+
import { isTursoEnabled, getContexts, addContext, getContextFilter, setContextFilter as saveContextFilter } from '../../config.js';
|
|
11
11
|
import { VERSION } from '../../version.js';
|
|
12
12
|
import { useHistory, CreateTaskCommand, MoveTaskCommand, } from '../history/index.js';
|
|
13
13
|
import { SearchBar } from './SearchBar.js';
|
|
@@ -35,7 +35,12 @@ export function KanbanMario({ onOpenSettings }) {
|
|
|
35
35
|
const [selectedTask, setSelectedTask] = useState(null);
|
|
36
36
|
const [taskComments, setTaskComments] = useState([]);
|
|
37
37
|
const [selectedCommentIndex, setSelectedCommentIndex] = useState(0);
|
|
38
|
-
|
|
38
|
+
// Context filter state - load from config for persistence across sessions/terminals
|
|
39
|
+
const [contextFilter, setContextFilterState] = useState(() => getContextFilter());
|
|
40
|
+
const setContextFilter = useCallback((value) => {
|
|
41
|
+
setContextFilterState(value);
|
|
42
|
+
saveContextFilter(value);
|
|
43
|
+
}, []);
|
|
39
44
|
const [contextSelectIndex, setContextSelectIndex] = useState(0);
|
|
40
45
|
const [availableContexts, setAvailableContexts] = useState([]);
|
|
41
46
|
const [searchQuery, setSearchQuery] = useState('');
|
package/package.json
CHANGED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
interface DQLayoutProps {
|
|
3
|
-
title: string;
|
|
4
|
-
subtitle?: string;
|
|
5
|
-
menuTitle: string;
|
|
6
|
-
menuItems: Array<{
|
|
7
|
-
label: string;
|
|
8
|
-
count?: number;
|
|
9
|
-
isActive: boolean;
|
|
10
|
-
}>;
|
|
11
|
-
onMenuSelect?: (index: number) => void;
|
|
12
|
-
contentTitle: string;
|
|
13
|
-
children: React.ReactNode;
|
|
14
|
-
statusTitle?: string;
|
|
15
|
-
statusItems?: Array<{
|
|
16
|
-
label: string;
|
|
17
|
-
value: string;
|
|
18
|
-
}>;
|
|
19
|
-
footer?: string;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Dragon Quest style multi-window layout
|
|
23
|
-
*
|
|
24
|
-
* ╭────────────────────────────────────────────╮
|
|
25
|
-
* │ FLOQ - GTD Manager │
|
|
26
|
-
* ╰────────────────────────────────────────────╯
|
|
27
|
-
* ╭─ コマンド ─╮ ╭─ Inbox ──────────────────────╮
|
|
28
|
-
* │ ▶ Inbox │ │ ▶ タスク1 │
|
|
29
|
-
* │ 次 │ │ タスク2 │
|
|
30
|
-
* │ 待ち │ │ タスク3 │
|
|
31
|
-
* │ いつか │ ╰────────────────────────────────╯
|
|
32
|
-
* │ 完了 │
|
|
33
|
-
* ╰───────────╯
|
|
34
|
-
*/
|
|
35
|
-
export declare function DQLayout({ title, subtitle, menuTitle, menuItems, contentTitle, children, statusTitle, statusItems, footer, }: DQLayoutProps): React.ReactElement;
|
|
36
|
-
export {};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { useTheme } from '../theme/index.js';
|
|
4
|
-
/**
|
|
5
|
-
* Dragon Quest style multi-window layout
|
|
6
|
-
*
|
|
7
|
-
* ╭────────────────────────────────────────────╮
|
|
8
|
-
* │ FLOQ - GTD Manager │
|
|
9
|
-
* ╰────────────────────────────────────────────╯
|
|
10
|
-
* ╭─ コマンド ─╮ ╭─ Inbox ──────────────────────╮
|
|
11
|
-
* │ ▶ Inbox │ │ ▶ タスク1 │
|
|
12
|
-
* │ 次 │ │ タスク2 │
|
|
13
|
-
* │ 待ち │ │ タスク3 │
|
|
14
|
-
* │ いつか │ ╰────────────────────────────────╯
|
|
15
|
-
* │ 完了 │
|
|
16
|
-
* ╰───────────╯
|
|
17
|
-
*/
|
|
18
|
-
export function DQLayout({ title, subtitle, menuTitle, menuItems, contentTitle, children, statusTitle, statusItems, footer, }) {
|
|
19
|
-
const theme = useTheme();
|
|
20
|
-
const borderColor = theme.colors.border;
|
|
21
|
-
const activeColor = theme.colors.borderActive;
|
|
22
|
-
// Render a window with title on border
|
|
23
|
-
const renderWindow = (windowTitle, content, options = {}) => {
|
|
24
|
-
const { width, minHeight = 3, active = false, flexGrow } = options;
|
|
25
|
-
const color = active ? activeColor : borderColor;
|
|
26
|
-
const titleDisplay = ` ${windowTitle} `;
|
|
27
|
-
const minW = width || 20;
|
|
28
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, minHeight: minHeight, flexGrow: flexGrow, children: [_jsxs(Box, { children: [_jsx(Text, { color: color, children: "\u256D\u2500\u2500" }), _jsx(Text, { color: theme.colors.text, bold: true, children: titleDisplay }), _jsxs(Text, { color: color, children: ['─'.repeat(Math.max(minW - titleDisplay.length - 6, 2)), "\u256E"] })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [_jsx(Text, { color: color, children: "\u2502 " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: content }), _jsx(Text, { color: color, children: " \u2502" })] }) }), _jsx(Box, { children: _jsxs(Text, { color: color, children: ["\u2570", '─'.repeat(Math.max(minW - 2, 2)), "\u256F"] }) })] }));
|
|
29
|
-
};
|
|
30
|
-
// Render header window
|
|
31
|
-
const renderHeader = () => {
|
|
32
|
-
const headerWidth = 50;
|
|
33
|
-
const padding = Math.floor((headerWidth - title.length - 4) / 2);
|
|
34
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: borderColor, children: ["\u256D", '─'.repeat(headerWidth - 2), "\u256E"] }) }), _jsxs(Box, { children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsxs(Text, { color: theme.colors.primary, bold: true, children: [' '.repeat(padding), title, ' '.repeat(padding)] }), _jsx(Text, { color: borderColor, children: "\u2502" })] }), subtitle && (_jsxs(Box, { children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsxs(Text, { color: theme.colors.textMuted, children: [' '.repeat(Math.floor((headerWidth - subtitle.length - 4) / 2)), subtitle] }), _jsx(Text, { color: borderColor, children: "\u2502" })] })), _jsx(Box, { children: _jsxs(Text, { color: borderColor, children: ["\u2570", '─'.repeat(headerWidth - 2), "\u256F"] }) })] }));
|
|
35
|
-
};
|
|
36
|
-
// Render menu items
|
|
37
|
-
const renderMenuContent = () => (_jsx(_Fragment, { children: menuItems.map((item, index) => (_jsx(Box, { children: _jsxs(Text, { color: item.isActive ? theme.colors.textSelected : theme.colors.text, bold: item.isActive, children: [item.isActive ? theme.style.selectedPrefix : theme.style.unselectedPrefix, item.label, item.count !== undefined && ` (${item.count})`] }) }, index))) }));
|
|
38
|
-
// Render status window
|
|
39
|
-
const renderStatus = () => {
|
|
40
|
-
if (!statusTitle || !statusItems)
|
|
41
|
-
return null;
|
|
42
|
-
return (_jsx(Box, { marginTop: 1, children: renderWindow(statusTitle, _jsx(_Fragment, { children: statusItems.map((item, index) => (_jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.textMuted, children: [item.label, ": "] }), _jsx(Text, { color: theme.colors.accent, children: item.value })] }, index))) }), { width: 20, minHeight: statusItems.length + 2 }) }));
|
|
43
|
-
};
|
|
44
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [renderHeader(), _jsxs(Box, { flexDirection: "row", children: [_jsxs(Box, { flexDirection: "column", marginRight: 1, children: [renderWindow(menuTitle, renderMenuContent(), {
|
|
45
|
-
width: 18,
|
|
46
|
-
minHeight: menuItems.length + 2,
|
|
47
|
-
active: true,
|
|
48
|
-
}), renderStatus()] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: renderWindow(contentTitle, children, {
|
|
49
|
-
minHeight: 12,
|
|
50
|
-
active: true,
|
|
51
|
-
flexGrow: 1,
|
|
52
|
-
}) })] }), footer && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: footer }) }))] }));
|
|
53
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { Task } from '../../db/schema.js';
|
|
3
|
-
import type { ProjectProgress } from './TaskItem.js';
|
|
4
|
-
interface DQTaskListProps {
|
|
5
|
-
tasks: Task[];
|
|
6
|
-
selectedIndex: number;
|
|
7
|
-
emptyMessage: string;
|
|
8
|
-
showProject?: boolean;
|
|
9
|
-
getParentProject?: (parentId: string | null) => Task | undefined;
|
|
10
|
-
projectProgress?: Record<string, ProjectProgress>;
|
|
11
|
-
isProjectTab?: boolean;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Dragon Quest style task list inside a window
|
|
15
|
-
*/
|
|
16
|
-
export declare function DQTaskList({ tasks, selectedIndex, emptyMessage, showProject, getParentProject, projectProgress, isProjectTab, }: DQTaskListProps): React.ReactElement;
|
|
17
|
-
interface DQWindowFrameProps {
|
|
18
|
-
title: string;
|
|
19
|
-
children: React.ReactNode;
|
|
20
|
-
width?: number | string;
|
|
21
|
-
minHeight?: number;
|
|
22
|
-
active?: boolean;
|
|
23
|
-
flexGrow?: number;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Dragon Quest style window frame with title on border
|
|
27
|
-
*/
|
|
28
|
-
export declare function DQWindowFrame({ title, children, width, minHeight, active, flexGrow, }: DQWindowFrameProps): React.ReactElement;
|
|
29
|
-
interface DQMenuProps {
|
|
30
|
-
title: string;
|
|
31
|
-
items: Array<{
|
|
32
|
-
key: string;
|
|
33
|
-
label: string;
|
|
34
|
-
count: number;
|
|
35
|
-
isActive: boolean;
|
|
36
|
-
}>;
|
|
37
|
-
width?: number;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Dragon Quest style menu (left side panel)
|
|
41
|
-
*/
|
|
42
|
-
export declare function DQMenu({ title, items, width }: DQMenuProps): React.ReactElement;
|
|
43
|
-
interface DQHeaderProps {
|
|
44
|
-
title: string;
|
|
45
|
-
version: string;
|
|
46
|
-
dbMode: string;
|
|
47
|
-
contextFilter?: string | null;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Dragon Quest style header window
|
|
51
|
-
*/
|
|
52
|
-
export declare function DQHeader({ title, version, dbMode, contextFilter }: DQHeaderProps): React.ReactElement;
|
|
53
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { useTheme } from '../theme/index.js';
|
|
4
|
-
/**
|
|
5
|
-
* Dragon Quest style task list inside a window
|
|
6
|
-
*/
|
|
7
|
-
export function DQTaskList({ tasks, selectedIndex, emptyMessage, showProject = false, getParentProject, projectProgress, isProjectTab = false, }) {
|
|
8
|
-
const theme = useTheme();
|
|
9
|
-
if (tasks.length === 0) {
|
|
10
|
-
return (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: emptyMessage }));
|
|
11
|
-
}
|
|
12
|
-
return (_jsx(_Fragment, { children: tasks.map((task, index) => {
|
|
13
|
-
const isSelected = index === selectedIndex;
|
|
14
|
-
const parentProject = showProject && getParentProject ? getParentProject(task.parentId) : undefined;
|
|
15
|
-
const progress = isProjectTab && projectProgress ? projectProgress[task.id] : undefined;
|
|
16
|
-
return (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix, task.title, task.waitingFor && (_jsxs(Text, { color: theme.colors.statusWaiting, children: [" [", task.waitingFor, "]"] })), task.context && (_jsxs(Text, { color: theme.colors.accent, children: [" @", task.context] })), parentProject && (_jsxs(Text, { color: theme.colors.textMuted, children: [" \u2190 ", parentProject.title] })), progress && (_jsxs(Text, { color: theme.colors.textMuted, children: [" [", progress.completed, "/", progress.total, "]"] }))] }) }, task.id));
|
|
17
|
-
}) }));
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Dragon Quest style window frame with title on border
|
|
21
|
-
*/
|
|
22
|
-
export function DQWindowFrame({ title, children, width, minHeight = 10, active = false, flexGrow, }) {
|
|
23
|
-
const theme = useTheme();
|
|
24
|
-
const borderColor = active ? theme.colors.borderActive : theme.colors.border;
|
|
25
|
-
const titleText = ` ${title} `;
|
|
26
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, minHeight: minHeight, flexGrow: flexGrow, children: [_jsxs(Text, { color: borderColor, children: ["\u256D\u2500\u2500", _jsx(Text, { color: theme.colors.text, bold: true, children: titleText }), _jsx(Text, { color: borderColor, children: '─'.repeat(30) })] }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [_jsx(Text, { color: borderColor, children: "\u2502 " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: children }), _jsx(Text, { color: borderColor, children: " \u2502" })] }), _jsxs(Text, { color: borderColor, children: ["\u2570", '─'.repeat(titleText.length + 32), "\u256F"] })] }));
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Dragon Quest style menu (left side panel)
|
|
30
|
-
*/
|
|
31
|
-
export function DQMenu({ title, items, width = 16 }) {
|
|
32
|
-
const theme = useTheme();
|
|
33
|
-
const borderColor = theme.colors.border;
|
|
34
|
-
const titleText = ` ${title} `;
|
|
35
|
-
const innerWidth = width - 4;
|
|
36
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsxs(Text, { color: borderColor, children: ["\u256D\u2500", _jsx(Text, { color: theme.colors.text, bold: true, children: titleText }), _jsxs(Text, { color: borderColor, children: ['─'.repeat(Math.max(innerWidth - titleText.length, 0)), "\u256E"] })] }), items.map((item) => (_jsxs(Box, { children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsxs(Text, { color: item.isActive ? theme.colors.textSelected : theme.colors.text, bold: item.isActive, children: [item.isActive ? theme.style.selectedPrefix : theme.style.unselectedPrefix, item.label] }), _jsxs(Text, { color: theme.colors.textMuted, children: ["(", item.count, ")"] }), _jsx(Text, { color: borderColor, children: "\u2502" })] }, item.key))), _jsxs(Text, { color: borderColor, children: ["\u2570", '─'.repeat(innerWidth + 2), "\u256F"] })] }));
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Dragon Quest style header window
|
|
40
|
-
*/
|
|
41
|
-
export function DQHeader({ title, version, dbMode, contextFilter }) {
|
|
42
|
-
const theme = useTheme();
|
|
43
|
-
const borderColor = theme.colors.border;
|
|
44
|
-
const headerText = `${title} ver.${version}`;
|
|
45
|
-
const width = 50;
|
|
46
|
-
const padding = Math.floor((width - headerText.length - 2) / 2);
|
|
47
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: borderColor, children: ["\u256D", '─'.repeat(width - 2), "\u256E"] }), _jsxs(Box, { children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsxs(Text, { color: theme.colors.primary, bold: true, children: [' '.repeat(padding), headerText, ' '.repeat(padding)] }), _jsx(Text, { color: borderColor, children: "\u2502" })] }), _jsxs(Box, { children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsxs(Text, { children: [' '.repeat(Math.floor((width - 20) / 2)), _jsxs(Text, { color: theme.colors.textMuted, children: ["[", dbMode, "]"] }), contextFilter !== null && contextFilter !== undefined && (_jsxs(Text, { color: theme.colors.accent, children: [" @", contextFilter === '' ? 'none' : contextFilter] }))] }), _jsx(Text, { color: borderColor, children: "\u2502" })] }), _jsxs(Text, { color: borderColor, children: ["\u2570", '─'.repeat(width - 2), "\u256F"] })] }));
|
|
48
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
interface DQWindowProps {
|
|
3
|
-
title?: string;
|
|
4
|
-
children: React.ReactNode;
|
|
5
|
-
width?: number | string;
|
|
6
|
-
height?: number | string;
|
|
7
|
-
minHeight?: number;
|
|
8
|
-
minWidth?: number;
|
|
9
|
-
active?: boolean;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Dragon Quest style window with title on the border
|
|
13
|
-
*
|
|
14
|
-
* ╭─ Title ──────────╮
|
|
15
|
-
* │ Content here │
|
|
16
|
-
* ╰──────────────────╯
|
|
17
|
-
*/
|
|
18
|
-
export declare function DQWindow({ title, children, width, height, minHeight, minWidth, active, }: DQWindowProps): React.ReactElement;
|
|
19
|
-
export {};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { useTheme } from '../theme/index.js';
|
|
4
|
-
/**
|
|
5
|
-
* Dragon Quest style window with title on the border
|
|
6
|
-
*
|
|
7
|
-
* ╭─ Title ──────────╮
|
|
8
|
-
* │ Content here │
|
|
9
|
-
* ╰──────────────────╯
|
|
10
|
-
*/
|
|
11
|
-
export function DQWindow({ title, children, width, height, minHeight, minWidth, active = false, }) {
|
|
12
|
-
const theme = useTheme();
|
|
13
|
-
const borderColor = active ? theme.colors.borderActive : theme.colors.border;
|
|
14
|
-
// Build the top border with title embedded
|
|
15
|
-
const renderTopBorder = (contentWidth) => {
|
|
16
|
-
if (!title) {
|
|
17
|
-
// No title - just return standard corner
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
const titleText = ` ${title} `;
|
|
21
|
-
const titleLen = titleText.length;
|
|
22
|
-
// Calculate dashes needed (minimum 2 on each side of title)
|
|
23
|
-
const availableWidth = Math.max(contentWidth - titleLen - 4, 0);
|
|
24
|
-
const leftDashes = 2;
|
|
25
|
-
const rightDashes = Math.max(availableWidth - leftDashes, 2);
|
|
26
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: borderColor, children: ["\u256D", '─'.repeat(leftDashes)] }), _jsx(Text, { color: theme.colors.text, bold: true, children: titleText }), _jsxs(Text, { color: borderColor, children: ['─'.repeat(rightDashes), "\u256E"] })] }));
|
|
27
|
-
};
|
|
28
|
-
const renderBottomBorder = (contentWidth) => {
|
|
29
|
-
return (_jsx(Box, { children: _jsxs(Text, { color: borderColor, children: ["\u2570", '─'.repeat(contentWidth), "\u256F"] }) }));
|
|
30
|
-
};
|
|
31
|
-
// For DQ style, we render custom borders
|
|
32
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, minHeight: minHeight, minWidth: minWidth, children: [title && renderTopBorder(typeof minWidth === 'number' ? minWidth - 2 : 20), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsx(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: children }), _jsx(Text, { color: borderColor, children: "\u2502" })] }), renderBottomBorder(typeof minWidth === 'number' ? minWidth - 2 : 20)] }));
|
|
33
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
interface FullScreenBoxProps {
|
|
3
|
-
children: React.ReactNode;
|
|
4
|
-
backgroundColor?: string;
|
|
5
|
-
padding?: number;
|
|
6
|
-
}
|
|
7
|
-
export declare function FullScreenBox({ children, backgroundColor, padding }: FullScreenBoxProps): React.ReactElement;
|
|
8
|
-
export {};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, useStdout } from 'ink';
|
|
3
|
-
export function FullScreenBox({ children, backgroundColor, padding = 1 }) {
|
|
4
|
-
const { stdout } = useStdout();
|
|
5
|
-
const width = stdout?.columns || 80;
|
|
6
|
-
const height = stdout?.rows || 24;
|
|
7
|
-
// 背景色で画面全体を埋めるために、各行に空白を追加
|
|
8
|
-
const fillLine = backgroundColor ? ' '.repeat(width) : '';
|
|
9
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, minHeight: height, padding: padding, ...(backgroundColor ? { backgroundColor } : {}), children: [children, backgroundColor && (_jsx(Box, { flexGrow: 1, width: width, children: _jsx(Box, { backgroundColor: backgroundColor, width: width }) }))] }));
|
|
10
|
-
}
|