floq 0.5.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 +14 -1
- package/README.md +14 -1
- package/dist/cli.js +12 -1
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +51 -1
- 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 +21 -15
- 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/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/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
|
@@ -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 {};
|