floq 0.4.0 → 0.6.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/README.ja.md +16 -3
- package/README.md +16 -3
- package/dist/cli.js +12 -1
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +51 -1
- package/dist/commands/list.js +17 -5
- package/dist/config.d.ts +3 -0
- package/dist/config.js +13 -0
- package/dist/i18n/en.d.ts +13 -0
- package/dist/i18n/en.js +7 -0
- package/dist/i18n/ja.js +7 -0
- package/dist/ui/App.js +39 -20
- package/dist/ui/SplashScreen.d.ts +2 -1
- package/dist/ui/SplashScreen.js +109 -10
- package/dist/ui/components/DQLayout.d.ts +36 -0
- package/dist/ui/components/DQLayout.js +53 -0
- package/dist/ui/components/DQTaskList.d.ts +53 -0
- package/dist/ui/components/DQTaskList.js +48 -0
- package/dist/ui/components/DQWindow.d.ts +19 -0
- package/dist/ui/components/DQWindow.js +33 -0
- package/dist/ui/components/GtdDQ.d.ts +7 -0
- package/dist/ui/components/GtdDQ.js +773 -0
- package/dist/ui/components/HelpModal.js +136 -102
- package/dist/ui/components/KanbanBoard.js +10 -6
- package/dist/ui/components/KanbanColumn.js +53 -1
- package/dist/ui/components/KanbanDQ.d.ts +7 -0
- package/dist/ui/components/KanbanDQ.js +470 -0
- package/dist/ui/components/SearchResults.d.ts +2 -1
- package/dist/ui/components/SearchResults.js +22 -2
- package/dist/ui/components/TitledBox.d.ts +11 -0
- package/dist/ui/components/TitledBox.js +66 -0
- package/dist/ui/theme/themes.d.ts +1 -0
- package/dist/ui/theme/themes.js +43 -1
- package/dist/ui/theme/types.d.ts +3 -1
- package/package.json +1 -1
package/dist/ui/SplashScreen.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
4
|
import { useTheme } from './theme/index.js';
|
|
5
5
|
import { VERSION } from '../version.js';
|
|
6
|
+
import { t } from '../i18n/index.js';
|
|
6
7
|
const LOGO_MODERN = `
|
|
7
8
|
███████╗██╗ ██████╗ ██████╗
|
|
8
9
|
██╔════╝██║ ██╔═══██╗██╔═══██╗
|
|
@@ -20,14 +21,88 @@ const LOGO_DOS = `
|
|
|
20
21
|
║ ██ ███████ ██████ ██████ ║
|
|
21
22
|
╚═══════════════════════════════════╝
|
|
22
23
|
`;
|
|
24
|
+
// Dragon Quest style FLOQ logo with wings and sword
|
|
25
|
+
const FLOQ_LOGO = [
|
|
26
|
+
' /\\',
|
|
27
|
+
' __ / \\ __',
|
|
28
|
+
' / \\ / || \\ / \\',
|
|
29
|
+
' / /\\ \\ / || \\ / /\\ \\',
|
|
30
|
+
' / / \\ \\ / || \\ / / \\ \\',
|
|
31
|
+
' / / \\ \\_/ || \\_/ / \\ \\',
|
|
32
|
+
' /_/ \\__/ || \\__/ \\_\\',
|
|
33
|
+
' ||',
|
|
34
|
+
' =================[##]=================',
|
|
35
|
+
'',
|
|
36
|
+
' ######## ## ###### ######',
|
|
37
|
+
' ## ## ## ## ## ##',
|
|
38
|
+
' ###### ## ## ## ## ##',
|
|
39
|
+
' ## ## ## ## ## # ##',
|
|
40
|
+
' ## ######## ###### #### ##',
|
|
41
|
+
'',
|
|
42
|
+
' =====================================',
|
|
43
|
+
' ~ Flow Your Tasks ~',
|
|
44
|
+
];
|
|
45
|
+
// Dragon Quest style border characters
|
|
46
|
+
const DQ_BORDER = {
|
|
47
|
+
topLeft: '╭',
|
|
48
|
+
topRight: '╮',
|
|
49
|
+
bottomLeft: '╰',
|
|
50
|
+
bottomRight: '╯',
|
|
51
|
+
horizontal: '─',
|
|
52
|
+
vertical: '│',
|
|
53
|
+
};
|
|
23
54
|
const TAGLINE = 'Flow your tasks, clear your mind';
|
|
24
|
-
|
|
55
|
+
// Dragon Quest famous quotes for splash screen
|
|
56
|
+
const DQ_QUOTES_JA = [
|
|
57
|
+
'へんじがない。ただのしかばねのようだ。',
|
|
58
|
+
'おきのどくですが ぼうけんのしょは きえてしまいました。',
|
|
59
|
+
'レベルがあがった!',
|
|
60
|
+
'しかし まわりこまれてしまった!',
|
|
61
|
+
'たたかいに やぶれた...',
|
|
62
|
+
'どうぐがいっぱいで もてません。',
|
|
63
|
+
'やくそうを つかった!',
|
|
64
|
+
'かいしんのいちげき!',
|
|
65
|
+
'しかし なにも おこらなかった',
|
|
66
|
+
'そのほうこうには すすめません',
|
|
67
|
+
];
|
|
68
|
+
const DQ_QUOTES_EN = [
|
|
69
|
+
'No response. It seems to be just a corpse.',
|
|
70
|
+
'Unfortunately, your adventure log has been erased.',
|
|
71
|
+
'Level up!',
|
|
72
|
+
'But you were surrounded!',
|
|
73
|
+
'You have been defeated...',
|
|
74
|
+
'Your inventory is full.',
|
|
75
|
+
'Used an herb!',
|
|
76
|
+
'Critical hit!',
|
|
77
|
+
'But nothing happened.',
|
|
78
|
+
'You cannot go that way.',
|
|
79
|
+
];
|
|
80
|
+
export function SplashScreen({ onComplete, duration = 1500, viewMode = 'gtd' }) {
|
|
25
81
|
const [frame, setFrame] = useState(0);
|
|
26
82
|
const [showTagline, setShowTagline] = useState(false);
|
|
83
|
+
const [blinkVisible, setBlinkVisible] = useState(true);
|
|
27
84
|
const theme = useTheme();
|
|
85
|
+
const i18n = t();
|
|
86
|
+
const isDqStyle = theme.uiStyle === 'titled-box';
|
|
28
87
|
const isDosStyle = theme.name !== 'modern';
|
|
29
88
|
const logo = isDosStyle ? LOGO_DOS : LOGO_MODERN;
|
|
30
89
|
const [filled, empty] = theme.style.loadingChars;
|
|
90
|
+
// Pick a random quote (stable across re-renders)
|
|
91
|
+
const [randomQuote] = useState(() => {
|
|
92
|
+
const isJapanese = i18n.splash?.welcome === 'ようこそ!';
|
|
93
|
+
const quotes = isJapanese ? DQ_QUOTES_JA : DQ_QUOTES_EN;
|
|
94
|
+
return quotes[Math.floor(Math.random() * quotes.length)];
|
|
95
|
+
});
|
|
96
|
+
// Adventure subtitle based on view mode
|
|
97
|
+
const adventureSubtitle = viewMode === 'kanban'
|
|
98
|
+
? (i18n.splash?.subtitleKanban || 'Kanbanの冒険がはじまる')
|
|
99
|
+
: (i18n.splash?.subtitle || 'GTDの冒険がはじまる');
|
|
100
|
+
// Wait for key press mode (duration = -1)
|
|
101
|
+
const waitForKeyPress = duration < 0;
|
|
102
|
+
// Handle key press to skip splash
|
|
103
|
+
useInput(() => {
|
|
104
|
+
onComplete();
|
|
105
|
+
});
|
|
31
106
|
useEffect(() => {
|
|
32
107
|
// Animate logo appearance
|
|
33
108
|
const frameInterval = setInterval(() => {
|
|
@@ -43,19 +118,43 @@ export function SplashScreen({ onComplete, duration = 1500 }) {
|
|
|
43
118
|
const taglineTimer = setTimeout(() => {
|
|
44
119
|
setShowTagline(true);
|
|
45
120
|
}, 600);
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
121
|
+
// Blink effect for DQ style or wait-for-key mode
|
|
122
|
+
let blinkInterval = null;
|
|
123
|
+
if (isDqStyle || waitForKeyPress) {
|
|
124
|
+
blinkInterval = setInterval(() => {
|
|
125
|
+
setBlinkVisible((prev) => !prev);
|
|
126
|
+
}, 500);
|
|
127
|
+
}
|
|
128
|
+
// Complete splash screen (only if not waiting for key press)
|
|
129
|
+
let completeTimer = null;
|
|
130
|
+
if (!waitForKeyPress) {
|
|
131
|
+
completeTimer = setTimeout(() => {
|
|
132
|
+
onComplete();
|
|
133
|
+
}, duration);
|
|
134
|
+
}
|
|
50
135
|
return () => {
|
|
51
136
|
clearInterval(frameInterval);
|
|
52
137
|
clearTimeout(taglineTimer);
|
|
53
|
-
|
|
138
|
+
if (completeTimer)
|
|
139
|
+
clearTimeout(completeTimer);
|
|
140
|
+
if (blinkInterval)
|
|
141
|
+
clearInterval(blinkInterval);
|
|
54
142
|
};
|
|
55
|
-
}, [onComplete, duration]);
|
|
143
|
+
}, [onComplete, duration, isDqStyle, waitForKeyPress]);
|
|
144
|
+
// Dragon Quest style splash
|
|
145
|
+
if (isDqStyle) {
|
|
146
|
+
const boxWidth = 40;
|
|
147
|
+
const innerWidth = boxWidth - 2;
|
|
148
|
+
const title = 'FLOQ';
|
|
149
|
+
const leftDashes = 3;
|
|
150
|
+
const rightDashes = innerWidth - leftDashes - title.length - 2;
|
|
151
|
+
const shadowColor = theme.colors.muted;
|
|
152
|
+
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 2, children: [_jsx(Box, { flexDirection: "column", alignItems: "center", marginBottom: 1, children: FLOQ_LOGO.map((line, index) => (_jsx(Text, { color: index === 4 ? theme.colors.text : theme.colors.accent, bold: index === 4, children: line }, index))) }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.border, children: DQ_BORDER.topLeft }), _jsxs(Text, { color: theme.colors.border, children: [DQ_BORDER.horizontal.repeat(leftDashes), " "] }), _jsx(Text, { color: theme.colors.accent, bold: true, children: title }), _jsxs(Text, { color: theme.colors.border, children: [" ", DQ_BORDER.horizontal.repeat(rightDashes)] }), _jsx(Text, { color: theme.colors.border, children: DQ_BORDER.topRight }), _jsx(Text, { children: " " })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Box, { width: innerWidth, justifyContent: "center", children: _jsx(Text, { color: theme.colors.text, children: " " }) }), _jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Text, { color: shadowColor, children: "\u2591" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Box, { width: innerWidth, justifyContent: "center", children: _jsx(Text, { color: theme.colors.text, bold: true, children: adventureSubtitle }) }), _jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Text, { color: shadowColor, children: "\u2591" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Box, { width: innerWidth, justifyContent: "center", children: _jsx(Text, { color: theme.colors.text, children: " " }) }), _jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Text, { color: shadowColor, children: "\u2591" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Box, { width: innerWidth, justifyContent: "center", children: _jsx(Text, { color: theme.colors.textMuted, children: randomQuote }) }), _jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Text, { color: shadowColor, children: "\u2591" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Box, { width: innerWidth, justifyContent: "center", children: _jsx(Text, { color: theme.colors.text, children: " " }) }), _jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Text, { color: shadowColor, children: "\u2591" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Box, { width: innerWidth, justifyContent: "center", children: _jsx(Text, { color: theme.colors.textMuted, children: blinkVisible ? (i18n.splash?.pressKey || '▼ キーを押してください') : ' ' }) }), _jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Text, { color: shadowColor, children: "\u2591" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Box, { width: innerWidth, justifyContent: "center", children: _jsx(Text, { color: theme.colors.text, children: " " }) }), _jsx(Text, { color: theme.colors.border, children: DQ_BORDER.vertical }), _jsx(Text, { color: shadowColor, children: "\u2591" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.border, children: DQ_BORDER.bottomLeft }), _jsx(Text, { color: theme.colors.border, children: DQ_BORDER.horizontal.repeat(innerWidth) }), _jsx(Text, { color: theme.colors.border, children: DQ_BORDER.bottomRight }), _jsx(Text, { color: shadowColor, children: "\u2591" })] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: shadowColor, children: '░'.repeat(boxWidth) })] })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.colors.textMuted, children: ["VER ", VERSION] }) })] }));
|
|
153
|
+
}
|
|
154
|
+
// Standard splash (modern / DOS style)
|
|
56
155
|
const logoLines = logo.split('\n');
|
|
57
156
|
const visibleLines = Math.min(Math.floor(frame * logoLines.length / 10), logoLines.length);
|
|
58
|
-
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 2, children: [_jsx(Box, { flexDirection: "column", alignItems: "center", children: logoLines.slice(0, visibleLines).map((line, index) => (_jsx(Text, { color: theme.colors.secondary, bold: true, children: line }, index))) }), showTagline && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, italic: !isDosStyle, children: isDosStyle ? `[ ${TAGLINE.toUpperCase()} ]` : TAGLINE }) })), _jsx(Box, { marginTop: 2, children: _jsx(Text, { color: theme.colors.primary, children: frame < 10
|
|
157
|
+
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 2, children: [_jsx(Box, { flexDirection: "column", alignItems: "center", children: logoLines.slice(0, visibleLines).map((line, index) => (_jsx(Text, { color: theme.colors.secondary, bold: true, children: line }, index))) }), showTagline && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, italic: !isDosStyle, children: isDosStyle ? `[ ${TAGLINE.toUpperCase()} ]` : TAGLINE }) })), _jsx(Box, { marginTop: 2, children: waitForKeyPress ? (_jsx(Text, { color: theme.colors.textMuted, children: blinkVisible ? (i18n.splash?.pressKey || '▼ Press any key') : ' ' })) : (_jsx(Text, { color: theme.colors.primary, children: frame < 10
|
|
59
158
|
? filled.repeat(frame) + empty.repeat(10 - frame)
|
|
60
|
-
: filled.repeat(10) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: isDosStyle ? theme.colors.textMuted : theme.colors.muted, children: isDosStyle ? `VER ${VERSION}` : `v${VERSION}` }) })] }));
|
|
159
|
+
: filled.repeat(10) })) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: isDosStyle ? theme.colors.textMuted : theme.colors.muted, children: isDosStyle ? `VER ${VERSION}` : `v${VERSION}` }) })] }));
|
|
61
160
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
type SettingsMode = 'none' | 'theme-select' | 'mode-select' | 'lang-select';
|
|
3
|
+
interface GtdDQProps {
|
|
4
|
+
onOpenSettings?: (mode: SettingsMode) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function GtdDQ({ onOpenSettings }: GtdDQProps): React.ReactElement;
|
|
7
|
+
export {};
|