floq 0.2.0 → 0.2.2
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 +11 -1
- package/README.md +11 -1
- package/dist/i18n/en.d.ts +57 -0
- package/dist/i18n/en.js +30 -1
- package/dist/i18n/ja.js +30 -1
- package/dist/ui/App.js +96 -20
- package/dist/ui/LanguageSelector.d.ts +8 -0
- package/dist/ui/LanguageSelector.js +37 -0
- package/dist/ui/ModeSelector.d.ts +2 -1
- package/dist/ui/ModeSelector.js +6 -2
- package/dist/ui/ThemeSelector.d.ts +2 -1
- package/dist/ui/ThemeSelector.js +6 -2
- package/dist/ui/components/HelpModal.js +43 -6
- package/dist/ui/components/KanbanBoard.d.ts +3 -1
- package/dist/ui/components/KanbanBoard.js +20 -15
- package/dist/ui/theme/themes.d.ts +10 -0
- package/dist/ui/theme/themes.js +410 -0
- package/dist/ui/theme/types.d.ts +1 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -228,7 +228,7 @@ floq config turso --disable
|
|
|
228
228
|
|
|
229
229
|
## テーマ
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
26種類のテーマが利用可能。`floq config theme` でインタラクティブに選択(j/kで移動)。
|
|
232
232
|
|
|
233
233
|
| テーマ | 説明 |
|
|
234
234
|
|--------|------|
|
|
@@ -248,6 +248,16 @@ floq config turso --disable
|
|
|
248
248
|
| `synthwave` | ネオン80sスタイル |
|
|
249
249
|
| `paper` | 紙とインク風ライト |
|
|
250
250
|
| `coffee` | 暖かみのある茶系 |
|
|
251
|
+
| `nord` | 北欧風ブルーグレー |
|
|
252
|
+
| `dracula` | ダーク&ビビッドカラー |
|
|
253
|
+
| `monokai` | エディタ定番の鮮やかな配色 |
|
|
254
|
+
| `gruvbox` | レトロな暖色系ダーク |
|
|
255
|
+
| `tokyo-night` | 東京の夜景イメージ |
|
|
256
|
+
| `catppuccin` | パステル系モダン |
|
|
257
|
+
| `ocean` | 深海ブルー |
|
|
258
|
+
| `sakura` | 桜ピンク |
|
|
259
|
+
| `msx` | MSXコンピュータ(TMS9918) |
|
|
260
|
+
| `pc-98` | NEC PC-9801風 |
|
|
251
261
|
|
|
252
262
|
> **注意**: 背景色はターミナルの設定に依存します。
|
|
253
263
|
|
package/README.md
CHANGED
|
@@ -228,7 +228,7 @@ floq config turso --disable
|
|
|
228
228
|
|
|
229
229
|
## Themes
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
26 themes available. Use `floq config theme` for interactive selection (j/k to navigate).
|
|
232
232
|
|
|
233
233
|
| Theme | Description |
|
|
234
234
|
|-------|-------------|
|
|
@@ -248,6 +248,16 @@ floq config turso --disable
|
|
|
248
248
|
| `synthwave` | Neon 80s aesthetic |
|
|
249
249
|
| `paper` | Light minimal theme |
|
|
250
250
|
| `coffee` | Warm brown tones |
|
|
251
|
+
| `nord` | Arctic, north-bluish palette |
|
|
252
|
+
| `dracula` | Dark theme with vibrant colors |
|
|
253
|
+
| `monokai` | Classic editor vivid colors |
|
|
254
|
+
| `gruvbox` | Retro groove warm tones |
|
|
255
|
+
| `tokyo-night` | Tokyo night lights inspired |
|
|
256
|
+
| `catppuccin` | Soothing pastel theme |
|
|
257
|
+
| `ocean` | Deep sea blue theme |
|
|
258
|
+
| `sakura` | Cherry blossom pink |
|
|
259
|
+
| `msx` | MSX computer (TMS9918) |
|
|
260
|
+
| `pc-98` | NEC PC-9801 style |
|
|
251
261
|
|
|
252
262
|
> **Note**: Background colors depend on your terminal settings.
|
|
253
263
|
|
package/dist/i18n/en.d.ts
CHANGED
|
@@ -107,6 +107,7 @@ export declare const en: {
|
|
|
107
107
|
title: string;
|
|
108
108
|
whatsNewTab: string;
|
|
109
109
|
keybindingsTab: string;
|
|
110
|
+
infoTab: string;
|
|
110
111
|
tabHint: string;
|
|
111
112
|
navigation: string;
|
|
112
113
|
tabSwitch: string;
|
|
@@ -128,6 +129,10 @@ export declare const en: {
|
|
|
128
129
|
taskDetail: string;
|
|
129
130
|
addComment: string;
|
|
130
131
|
searchTasks: string;
|
|
132
|
+
settings: string;
|
|
133
|
+
changeTheme: string;
|
|
134
|
+
changeViewMode: string;
|
|
135
|
+
changeLanguage: string;
|
|
131
136
|
other: string;
|
|
132
137
|
showHelp: string;
|
|
133
138
|
quit: string;
|
|
@@ -155,6 +160,10 @@ export declare const en: {
|
|
|
155
160
|
moveRight: string;
|
|
156
161
|
moveLeft: string;
|
|
157
162
|
searchTasks: string;
|
|
163
|
+
settings: string;
|
|
164
|
+
changeTheme: string;
|
|
165
|
+
changeViewMode: string;
|
|
166
|
+
changeLanguage: string;
|
|
158
167
|
other: string;
|
|
159
168
|
showHelp: string;
|
|
160
169
|
quit: string;
|
|
@@ -168,6 +177,10 @@ export declare const en: {
|
|
|
168
177
|
commentDeleted: string;
|
|
169
178
|
taskDetailTitle: string;
|
|
170
179
|
taskDetailFooter: string;
|
|
180
|
+
taskDetailStatus: string;
|
|
181
|
+
deleteConfirm: string;
|
|
182
|
+
deleted: string;
|
|
183
|
+
deleteCancelled: string;
|
|
171
184
|
comments: string;
|
|
172
185
|
projectTasks: string;
|
|
173
186
|
search: {
|
|
@@ -178,6 +191,21 @@ export declare const en: {
|
|
|
178
191
|
resultsTitle: string;
|
|
179
192
|
searchTasks: string;
|
|
180
193
|
};
|
|
194
|
+
info: {
|
|
195
|
+
settings: string;
|
|
196
|
+
database: string;
|
|
197
|
+
paths: string;
|
|
198
|
+
theme: string;
|
|
199
|
+
language: string;
|
|
200
|
+
viewMode: string;
|
|
201
|
+
dbType: string;
|
|
202
|
+
dbPath: string;
|
|
203
|
+
tursoUrl: string;
|
|
204
|
+
configFile: string;
|
|
205
|
+
dataDir: string;
|
|
206
|
+
local: string;
|
|
207
|
+
turso: string;
|
|
208
|
+
};
|
|
181
209
|
};
|
|
182
210
|
setup: {
|
|
183
211
|
welcome: {
|
|
@@ -232,6 +260,7 @@ export type HelpTranslations = {
|
|
|
232
260
|
title: string;
|
|
233
261
|
whatsNewTab: string;
|
|
234
262
|
keybindingsTab: string;
|
|
263
|
+
infoTab: string;
|
|
235
264
|
tabHint: string;
|
|
236
265
|
navigation: string;
|
|
237
266
|
tabSwitch: string;
|
|
@@ -253,6 +282,10 @@ export type HelpTranslations = {
|
|
|
253
282
|
taskDetail: string;
|
|
254
283
|
addComment: string;
|
|
255
284
|
searchTasks: string;
|
|
285
|
+
settings: string;
|
|
286
|
+
changeTheme: string;
|
|
287
|
+
changeViewMode: string;
|
|
288
|
+
changeLanguage: string;
|
|
256
289
|
other: string;
|
|
257
290
|
showHelp: string;
|
|
258
291
|
quit: string;
|
|
@@ -280,6 +313,10 @@ export type KanbanHelpTranslations = {
|
|
|
280
313
|
moveRight: string;
|
|
281
314
|
moveLeft: string;
|
|
282
315
|
searchTasks: string;
|
|
316
|
+
settings: string;
|
|
317
|
+
changeTheme: string;
|
|
318
|
+
changeViewMode: string;
|
|
319
|
+
changeLanguage: string;
|
|
283
320
|
other: string;
|
|
284
321
|
showHelp: string;
|
|
285
322
|
quit: string;
|
|
@@ -312,6 +349,21 @@ export type SearchTranslations = {
|
|
|
312
349
|
resultsTitle: string;
|
|
313
350
|
searchTasks: string;
|
|
314
351
|
};
|
|
352
|
+
export type InfoTranslations = {
|
|
353
|
+
settings: string;
|
|
354
|
+
database: string;
|
|
355
|
+
paths: string;
|
|
356
|
+
theme: string;
|
|
357
|
+
language: string;
|
|
358
|
+
viewMode: string;
|
|
359
|
+
dbType: string;
|
|
360
|
+
dbPath: string;
|
|
361
|
+
tursoUrl: string;
|
|
362
|
+
configFile: string;
|
|
363
|
+
dataDir: string;
|
|
364
|
+
local: string;
|
|
365
|
+
turso: string;
|
|
366
|
+
};
|
|
315
367
|
export type TuiTranslations = {
|
|
316
368
|
title: string;
|
|
317
369
|
helpHint: string;
|
|
@@ -342,6 +394,7 @@ export type TuiTranslations = {
|
|
|
342
394
|
whatsNew: WhatsNewTranslations;
|
|
343
395
|
kanbanHelp: KanbanHelpTranslations;
|
|
344
396
|
search: SearchTranslations;
|
|
397
|
+
info: InfoTranslations;
|
|
345
398
|
addComment: string;
|
|
346
399
|
noComments: string;
|
|
347
400
|
commentHint: string;
|
|
@@ -350,6 +403,10 @@ export type TuiTranslations = {
|
|
|
350
403
|
commentDeleted: string;
|
|
351
404
|
taskDetailTitle: string;
|
|
352
405
|
taskDetailFooter: string;
|
|
406
|
+
taskDetailStatus: string;
|
|
407
|
+
deleteConfirm: string;
|
|
408
|
+
deleted: string;
|
|
409
|
+
deleteCancelled: string;
|
|
353
410
|
comments: string;
|
|
354
411
|
projectTasks: string;
|
|
355
412
|
};
|
package/dist/i18n/en.js
CHANGED
|
@@ -81,7 +81,7 @@ export const en = {
|
|
|
81
81
|
movedToWaiting: 'Moved "{title}" to Waiting (for {person})',
|
|
82
82
|
waitingFor: 'Waiting for: ',
|
|
83
83
|
refreshed: 'Refreshed',
|
|
84
|
-
footer: 'a=add d=done n=next s=someday w=waiting i=inbox p=project P=link',
|
|
84
|
+
footer: 'a=add d=done D=delete n=next s=someday w=waiting i=inbox p=project P=link',
|
|
85
85
|
noTasks: 'No tasks',
|
|
86
86
|
// Tab labels
|
|
87
87
|
tabInbox: 'Inbox',
|
|
@@ -116,6 +116,7 @@ export const en = {
|
|
|
116
116
|
title: 'Keyboard Shortcuts',
|
|
117
117
|
whatsNewTab: "What's New",
|
|
118
118
|
keybindingsTab: 'Keybindings',
|
|
119
|
+
infoTab: 'Info',
|
|
119
120
|
tabHint: 'Tab: switch view',
|
|
120
121
|
navigation: 'Navigation',
|
|
121
122
|
tabSwitch: 'Switch tab (5=Projects, 6=Done)',
|
|
@@ -137,6 +138,10 @@ export const en = {
|
|
|
137
138
|
taskDetail: 'View task details',
|
|
138
139
|
addComment: 'Add comment',
|
|
139
140
|
searchTasks: 'Search tasks',
|
|
141
|
+
settings: 'Settings',
|
|
142
|
+
changeTheme: 'Change theme',
|
|
143
|
+
changeViewMode: 'Change view mode',
|
|
144
|
+
changeLanguage: 'Change language',
|
|
140
145
|
other: 'Other',
|
|
141
146
|
showHelp: 'Show this help',
|
|
142
147
|
quit: 'Quit',
|
|
@@ -166,6 +171,10 @@ export const en = {
|
|
|
166
171
|
moveRight: 'Move task to next column',
|
|
167
172
|
moveLeft: 'Move task to previous column',
|
|
168
173
|
searchTasks: 'Search tasks',
|
|
174
|
+
settings: 'Settings',
|
|
175
|
+
changeTheme: 'Change theme',
|
|
176
|
+
changeViewMode: 'Change view mode',
|
|
177
|
+
changeLanguage: 'Change language',
|
|
169
178
|
other: 'Other',
|
|
170
179
|
showHelp: 'Show this help',
|
|
171
180
|
quit: 'Quit',
|
|
@@ -179,6 +188,10 @@ export const en = {
|
|
|
179
188
|
commentDeleted: 'Comment deleted',
|
|
180
189
|
taskDetailTitle: 'Task Details',
|
|
181
190
|
taskDetailFooter: 'j/k=select i=comment d=delete P=link b/Esc=back',
|
|
191
|
+
taskDetailStatus: 'Status',
|
|
192
|
+
deleteConfirm: 'Delete "{title}"? (y/n)',
|
|
193
|
+
deleted: 'Deleted: "{title}"',
|
|
194
|
+
deleteCancelled: 'Delete cancelled',
|
|
182
195
|
comments: 'Comments',
|
|
183
196
|
projectTasks: 'Tasks',
|
|
184
197
|
// Search
|
|
@@ -190,6 +203,22 @@ export const en = {
|
|
|
190
203
|
resultsTitle: 'Search Results',
|
|
191
204
|
searchTasks: 'Search tasks',
|
|
192
205
|
},
|
|
206
|
+
// Info tab
|
|
207
|
+
info: {
|
|
208
|
+
settings: 'Settings',
|
|
209
|
+
database: 'Database',
|
|
210
|
+
paths: 'Paths',
|
|
211
|
+
theme: 'Theme',
|
|
212
|
+
language: 'Language',
|
|
213
|
+
viewMode: 'View Mode',
|
|
214
|
+
dbType: 'Type',
|
|
215
|
+
dbPath: 'Path',
|
|
216
|
+
tursoUrl: 'Turso URL',
|
|
217
|
+
configFile: 'Config File',
|
|
218
|
+
dataDir: 'Data Directory',
|
|
219
|
+
local: 'local',
|
|
220
|
+
turso: 'turso',
|
|
221
|
+
},
|
|
193
222
|
},
|
|
194
223
|
// Setup wizard
|
|
195
224
|
setup: {
|
package/dist/i18n/ja.js
CHANGED
|
@@ -81,7 +81,7 @@ export const ja = {
|
|
|
81
81
|
movedToWaiting: '「{title}」を連絡待ち({person})に移動しました',
|
|
82
82
|
waitingFor: '待機相手: ',
|
|
83
83
|
refreshed: '更新しました',
|
|
84
|
-
footer: 'a=追加 d=完了 n=次 s=いつか w=待ち i=Inbox p=プロジェクト化 P=紐づけ',
|
|
84
|
+
footer: 'a=追加 d=完了 D=削除 n=次 s=いつか w=待ち i=Inbox p=プロジェクト化 P=紐づけ',
|
|
85
85
|
noTasks: 'タスクなし',
|
|
86
86
|
// Tab labels
|
|
87
87
|
tabInbox: 'Inbox',
|
|
@@ -116,6 +116,7 @@ export const ja = {
|
|
|
116
116
|
title: 'キーボードショートカット',
|
|
117
117
|
whatsNewTab: '更新履歴',
|
|
118
118
|
keybindingsTab: 'キー操作',
|
|
119
|
+
infoTab: '情報',
|
|
119
120
|
tabHint: 'Tab: 表示切替',
|
|
120
121
|
navigation: 'ナビゲーション',
|
|
121
122
|
tabSwitch: 'タブ切替 (5=プロジェクト, 6=完了)',
|
|
@@ -137,6 +138,10 @@ export const ja = {
|
|
|
137
138
|
taskDetail: 'タスク詳細を表示',
|
|
138
139
|
addComment: 'コメント追加',
|
|
139
140
|
searchTasks: 'タスク検索',
|
|
141
|
+
settings: '設定',
|
|
142
|
+
changeTheme: 'テーマ変更',
|
|
143
|
+
changeViewMode: '表示モード変更',
|
|
144
|
+
changeLanguage: '言語変更',
|
|
140
145
|
other: 'その他',
|
|
141
146
|
showHelp: 'このヘルプを表示',
|
|
142
147
|
quit: '終了',
|
|
@@ -166,6 +171,10 @@ export const ja = {
|
|
|
166
171
|
moveRight: '次のカラムへ移動',
|
|
167
172
|
moveLeft: '前のカラムへ戻す',
|
|
168
173
|
searchTasks: 'タスク検索',
|
|
174
|
+
settings: '設定',
|
|
175
|
+
changeTheme: 'テーマ変更',
|
|
176
|
+
changeViewMode: '表示モード変更',
|
|
177
|
+
changeLanguage: '言語変更',
|
|
169
178
|
other: 'その他',
|
|
170
179
|
showHelp: 'このヘルプを表示',
|
|
171
180
|
quit: '終了',
|
|
@@ -179,6 +188,10 @@ export const ja = {
|
|
|
179
188
|
commentDeleted: 'コメントを削除しました',
|
|
180
189
|
taskDetailTitle: 'タスク詳細',
|
|
181
190
|
taskDetailFooter: 'j/k=選択 i=コメント d=削除 P=紐づけ b/Esc=戻る',
|
|
191
|
+
taskDetailStatus: 'ステータス',
|
|
192
|
+
deleteConfirm: '「{title}」を削除しますか? (y/n)',
|
|
193
|
+
deleted: '削除しました: 「{title}」',
|
|
194
|
+
deleteCancelled: '削除をキャンセルしました',
|
|
182
195
|
comments: 'コメント',
|
|
183
196
|
projectTasks: 'タスク一覧',
|
|
184
197
|
// Search
|
|
@@ -190,6 +203,22 @@ export const ja = {
|
|
|
190
203
|
resultsTitle: '検索結果',
|
|
191
204
|
searchTasks: 'タスク検索',
|
|
192
205
|
},
|
|
206
|
+
// Info tab
|
|
207
|
+
info: {
|
|
208
|
+
settings: '設定',
|
|
209
|
+
database: 'データベース',
|
|
210
|
+
paths: 'パス',
|
|
211
|
+
theme: 'テーマ',
|
|
212
|
+
language: '言語',
|
|
213
|
+
viewMode: '表示モード',
|
|
214
|
+
dbType: '種類',
|
|
215
|
+
dbPath: 'パス',
|
|
216
|
+
tursoUrl: 'Turso URL',
|
|
217
|
+
configFile: '設定ファイル',
|
|
218
|
+
dataDir: 'データディレクトリ',
|
|
219
|
+
local: 'ローカル',
|
|
220
|
+
turso: 'turso',
|
|
221
|
+
},
|
|
193
222
|
},
|
|
194
223
|
// Setup wizard
|
|
195
224
|
setup: {
|
package/dist/ui/App.js
CHANGED
|
@@ -10,19 +10,52 @@ import { FunctionKeyBar } from './components/FunctionKeyBar.js';
|
|
|
10
10
|
import { SearchBar } from './components/SearchBar.js';
|
|
11
11
|
import { SearchResults } from './components/SearchResults.js';
|
|
12
12
|
import { SplashScreen } from './SplashScreen.js';
|
|
13
|
+
import { ThemeSelector } from './ThemeSelector.js';
|
|
14
|
+
import { ModeSelector } from './ModeSelector.js';
|
|
15
|
+
import { LanguageSelector } from './LanguageSelector.js';
|
|
13
16
|
import { getDb, schema } from '../db/index.js';
|
|
14
17
|
import { t, fmt } from '../i18n/index.js';
|
|
15
18
|
import { ThemeProvider, useTheme } from './theme/index.js';
|
|
16
|
-
import { getThemeName, getViewMode,
|
|
19
|
+
import { getThemeName, getViewMode, setThemeName, setViewMode, setLocale, isTursoEnabled } from '../config.js';
|
|
17
20
|
import { KanbanBoard } from './components/KanbanBoard.js';
|
|
18
21
|
import { VERSION } from '../version.js';
|
|
19
22
|
const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects', 'done'];
|
|
20
23
|
export function App() {
|
|
21
|
-
const themeName = getThemeName
|
|
22
|
-
const viewMode = getViewMode
|
|
23
|
-
|
|
24
|
+
const [themeName, setThemeNameState] = useState(getThemeName);
|
|
25
|
+
const [viewMode, setViewModeState] = useState(getViewMode);
|
|
26
|
+
const [settingsMode, setSettingsMode] = useState('none');
|
|
27
|
+
const [, forceUpdate] = useState({});
|
|
28
|
+
const handleThemeSelect = (theme) => {
|
|
29
|
+
setThemeName(theme);
|
|
30
|
+
setThemeNameState(theme);
|
|
31
|
+
setSettingsMode('none');
|
|
32
|
+
};
|
|
33
|
+
const handleModeSelect = (mode) => {
|
|
34
|
+
setViewMode(mode);
|
|
35
|
+
setViewModeState(mode);
|
|
36
|
+
setSettingsMode('none');
|
|
37
|
+
};
|
|
38
|
+
const handleLocaleSelect = (locale) => {
|
|
39
|
+
setLocale(locale);
|
|
40
|
+
setSettingsMode('none');
|
|
41
|
+
forceUpdate({});
|
|
42
|
+
};
|
|
43
|
+
const handleSettingsCancel = () => {
|
|
44
|
+
setSettingsMode('none');
|
|
45
|
+
};
|
|
46
|
+
// Settings selector screens
|
|
47
|
+
if (settingsMode === 'theme-select') {
|
|
48
|
+
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(ThemeSelector, { onSelect: handleThemeSelect, onCancel: handleSettingsCancel }) }));
|
|
49
|
+
}
|
|
50
|
+
if (settingsMode === 'mode-select') {
|
|
51
|
+
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(ModeSelector, { onSelect: handleModeSelect, onCancel: handleSettingsCancel }) }));
|
|
52
|
+
}
|
|
53
|
+
if (settingsMode === 'lang-select') {
|
|
54
|
+
return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(LanguageSelector, { onSelect: handleLocaleSelect, onCancel: handleSettingsCancel }) }));
|
|
55
|
+
}
|
|
56
|
+
return (_jsx(ThemeProvider, { themeName: themeName, children: viewMode === 'kanban' ? (_jsx(KanbanBoard, { onOpenSettings: setSettingsMode })) : (_jsx(AppContent, { onOpenSettings: setSettingsMode })) }));
|
|
24
57
|
}
|
|
25
|
-
function AppContent() {
|
|
58
|
+
function AppContent({ onOpenSettings }) {
|
|
26
59
|
const theme = useTheme();
|
|
27
60
|
const { exit } = useApp();
|
|
28
61
|
const [mode, setMode] = useState('splash');
|
|
@@ -46,6 +79,7 @@ function AppContent() {
|
|
|
46
79
|
const [taskComments, setTaskComments] = useState([]);
|
|
47
80
|
const [selectedCommentIndex, setSelectedCommentIndex] = useState(0);
|
|
48
81
|
const [taskToWaiting, setTaskToWaiting] = useState(null);
|
|
82
|
+
const [taskToDelete, setTaskToDelete] = useState(null);
|
|
49
83
|
const [projectProgress, setProjectProgress] = useState({});
|
|
50
84
|
// Search state
|
|
51
85
|
const [searchQuery, setSearchQuery] = useState('');
|
|
@@ -267,6 +301,15 @@ function AppContent() {
|
|
|
267
301
|
setMessage(fmt(i18n.tui.madeProject || 'Made project: {title}', { title: task.title }));
|
|
268
302
|
await loadTasks();
|
|
269
303
|
}, [i18n.tui.madeProject, loadTasks]);
|
|
304
|
+
const deleteTask = useCallback(async (task) => {
|
|
305
|
+
const db = getDb();
|
|
306
|
+
// Delete comments first
|
|
307
|
+
await db.delete(schema.comments).where(eq(schema.comments.taskId, task.id));
|
|
308
|
+
// Delete the task
|
|
309
|
+
await db.delete(schema.tasks).where(eq(schema.tasks.id, task.id));
|
|
310
|
+
setMessage(fmt(i18n.tui.deleted || 'Deleted: "{title}"', { title: task.title }));
|
|
311
|
+
await loadTasks();
|
|
312
|
+
}, [i18n.tui.deleted, loadTasks]);
|
|
270
313
|
const getTabLabel = (tab) => {
|
|
271
314
|
switch (tab) {
|
|
272
315
|
case 'inbox':
|
|
@@ -314,6 +357,27 @@ function AppContent() {
|
|
|
314
357
|
// Let TextInput handle other keys
|
|
315
358
|
return;
|
|
316
359
|
}
|
|
360
|
+
// Handle confirm-delete mode
|
|
361
|
+
if (mode === 'confirm-delete' && taskToDelete) {
|
|
362
|
+
if (input === 'y' || input === 'Y') {
|
|
363
|
+
deleteTask(taskToDelete).then(() => {
|
|
364
|
+
if (selectedTaskIndex >= currentTasks.length - 1) {
|
|
365
|
+
setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
setTaskToDelete(null);
|
|
369
|
+
setMode('normal');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (input === 'n' || input === 'N' || key.escape) {
|
|
373
|
+
setMessage(i18n.tui.deleteCancelled || 'Delete cancelled');
|
|
374
|
+
setTaskToDelete(null);
|
|
375
|
+
setMode('normal');
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
// Ignore other keys in confirm mode
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
317
381
|
// Handle add mode
|
|
318
382
|
if (mode === 'add' || mode === 'add-to-project' || mode === 'add-comment' || mode === 'move-to-waiting') {
|
|
319
383
|
if (key.escape) {
|
|
@@ -520,6 +584,21 @@ function AppContent() {
|
|
|
520
584
|
setSearchResultIndex(0);
|
|
521
585
|
return;
|
|
522
586
|
}
|
|
587
|
+
// Settings: Theme selector
|
|
588
|
+
if (input === 'T') {
|
|
589
|
+
onOpenSettings('theme-select');
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
// Settings: Mode selector
|
|
593
|
+
if (input === 'V') {
|
|
594
|
+
onOpenSettings('mode-select');
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
// Settings: Language selector
|
|
598
|
+
if (input === 'L') {
|
|
599
|
+
onOpenSettings('lang-select');
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
523
602
|
// Quit
|
|
524
603
|
if (input === 'q' || (key.ctrl && input === 'c')) {
|
|
525
604
|
exit();
|
|
@@ -626,6 +705,13 @@ function AppContent() {
|
|
|
626
705
|
});
|
|
627
706
|
return;
|
|
628
707
|
}
|
|
708
|
+
// Delete task (D key - with confirmation)
|
|
709
|
+
if (input === 'D' && currentTasks.length > 0 && currentTab !== 'projects') {
|
|
710
|
+
const task = currentTasks[selectedTaskIndex];
|
|
711
|
+
setTaskToDelete(task);
|
|
712
|
+
setMode('confirm-delete');
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
629
715
|
// Move to next actions
|
|
630
716
|
if (input === 'n' && currentTasks.length > 0 && currentTab !== 'next' && currentTab !== 'projects' && currentTab !== 'done') {
|
|
631
717
|
const task = currentTasks[selectedTaskIndex];
|
|
@@ -685,24 +771,14 @@ function AppContent() {
|
|
|
685
771
|
};
|
|
686
772
|
// Turso 接続情報を取得
|
|
687
773
|
const tursoEnabled = isTursoEnabled();
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
try {
|
|
692
|
-
return new URL(config.url).host;
|
|
693
|
-
}
|
|
694
|
-
catch {
|
|
695
|
-
return config.url;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
return '';
|
|
699
|
-
})() : '';
|
|
700
|
-
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.primary, children: formatTitle(i18n.tui.title) }), _jsx(Text, { color: theme.colors.textMuted, children: theme.name === 'modern' ? ` v${VERSION}` : ` VER ${VERSION}` }), tursoEnabled && (_jsxs(Text, { color: theme.colors.accent, children: [theme.name === 'modern' ? ' ☁️ ' : ' [SYNC] ', tursoHost] })), !tursoEnabled && (_jsx(Text, { color: theme.colors.textMuted, children: theme.name === 'modern' ? ' 💾 local' : ' [LOCAL]' }))] }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.helpHint })] }), _jsx(Box, { marginBottom: 1, children: TABS.map((tab, index) => {
|
|
774
|
+
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.primary, children: formatTitle(i18n.tui.title) }), _jsx(Text, { color: theme.colors.textMuted, children: theme.name === 'modern' ? ` v${VERSION}` : ` VER ${VERSION}` }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: theme.name === 'modern'
|
|
775
|
+
? (tursoEnabled ? ' ☁️ turso' : ' 💾 local')
|
|
776
|
+
: (tursoEnabled ? ' [DB]TURSO' : ' [DB]local') })] }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.helpHint })] }), _jsx(Box, { marginBottom: 1, children: TABS.map((tab, index) => {
|
|
701
777
|
const isActive = index === currentListIndex && mode !== 'project-detail';
|
|
702
778
|
const count = tasks[tab].length;
|
|
703
779
|
const label = `${index + 1}:${getTabLabel(tab)}(${count})`;
|
|
704
780
|
return (_jsx(Box, { marginRight: 1, children: _jsx(Text, { color: isActive ? theme.colors.textSelected : theme.colors.textMuted, bold: isActive, inverse: isActive && theme.style.tabActiveInverse, children: formatTabLabel(` ${label} `, isActive) }) }, tab));
|
|
705
|
-
}) }), mode === 'project-detail' && selectedProject && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📁 ' : '>> ', selectedProject.title] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ")"] })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.projectTasks || 'Tasks', " (", projectTasks.length, ")"] }) })] })), (mode === 'task-detail' || mode === 'add-comment') && selectedTask && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📋 ' : '>> ', i18n.tui.taskDetailTitle || 'Task Details'] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ", ", i18n.tui.commentHint || 'i: add comment', ")"] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, marginBottom: 1, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Text, { color: theme.colors.
|
|
781
|
+
}) }), mode === 'project-detail' && selectedProject && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📁 ' : '>> ', selectedProject.title] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ")"] })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.projectTasks || 'Tasks', " (", projectTasks.length, ")"] }) })] })), (mode === 'task-detail' || mode === 'add-comment') && selectedTask && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📋 ' : '>> ', i18n.tui.taskDetailTitle || 'Task Details'] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ", ", i18n.tui.commentHint || 'i: add comment', ")"] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, marginBottom: 1, 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, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus, ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.comments || 'Comments', " (", taskComments.length, ")"] }) }), _jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 5, children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
|
|
706
782
|
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
707
783
|
return (_jsxs(Box, { flexDirection: "row", marginBottom: 1, children: [_jsx(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.textMuted, children: isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.textMuted, children: ["[", comment.createdAt.toLocaleString(), "]"] }), _jsx(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: comment.content })] })] }, comment.id));
|
|
708
784
|
})) }), mode === 'add-comment' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.addComment || 'New comment: ' }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] }))] })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery })), mode !== 'task-detail' && mode !== 'add-comment' && mode !== 'search' && (_jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 10, children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
|
|
@@ -711,7 +787,7 @@ function AppContent() {
|
|
|
711
787
|
return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress }, task.id));
|
|
712
788
|
})) })), (mode === 'add' || mode === 'add-to-project') && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: mode === 'add-to-project' && selectedProject
|
|
713
789
|
? `${i18n.tui.newTask}[${selectedProject.title}] `
|
|
714
|
-
: i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'move-to-waiting' && taskToWaiting && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.waitingFor }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'select-project' && taskToLink && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project for', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: theme.borders.list, borderColor: theme.colors.borderActive, paddingX: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? theme.style.selectedPrefix : theme.style.unselectedPrefix, project.title] }, project.id))) }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.selectProjectHelp || 'j/k: select, Enter: confirm, Esc: cancel' })] })), mode === 'search' && (_jsx(SearchBar, { value: searchQuery, onChange: handleSearchChange, onSubmit: handleInputSubmit })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsx(Box, { marginTop: 1, children: (mode === 'task-detail' || mode === 'add-comment') ? (theme.style.showFunctionKeys ? (_jsx(FunctionKeyBar, { keys: [
|
|
790
|
+
: i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'move-to-waiting' && taskToWaiting && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.waitingFor }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'select-project' && taskToLink && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project for', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: theme.borders.list, borderColor: theme.colors.borderActive, paddingX: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? theme.style.selectedPrefix : theme.style.unselectedPrefix, project.title] }, project.id))) }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.selectProjectHelp || 'j/k: select, Enter: confirm, Esc: cancel' })] })), mode === 'search' && (_jsx(SearchBar, { value: searchQuery, onChange: handleSearchChange, onSubmit: handleInputSubmit })), mode === 'confirm-delete' && taskToDelete && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.accent, bold: true, children: fmt(i18n.tui.deleteConfirm || 'Delete "{title}"? (y/n)', { title: taskToDelete.title }) }) })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsx(Box, { marginTop: 1, children: (mode === 'task-detail' || mode === 'add-comment') ? (theme.style.showFunctionKeys ? (_jsx(FunctionKeyBar, { keys: [
|
|
715
791
|
{ key: 'i', label: i18n.tui.keyBar.comment },
|
|
716
792
|
{ key: 'd', label: i18n.tui.keyBar.delete },
|
|
717
793
|
{ key: 'P', label: i18n.tui.keyBar.project },
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Locale } from '../config.js';
|
|
3
|
+
interface LanguageSelectorProps {
|
|
4
|
+
onSelect: (locale: Locale) => void;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function LanguageSelector({ onSelect, onCancel }: LanguageSelectorProps): React.ReactElement;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { getLocale } from '../config.js';
|
|
5
|
+
const VALID_LOCALES = ['en', 'ja'];
|
|
6
|
+
const localeDisplayNames = {
|
|
7
|
+
en: 'English',
|
|
8
|
+
ja: '日本語 (Japanese)',
|
|
9
|
+
};
|
|
10
|
+
export function LanguageSelector({ onSelect, onCancel }) {
|
|
11
|
+
const currentLocale = getLocale();
|
|
12
|
+
const initialIndex = VALID_LOCALES.indexOf(currentLocale);
|
|
13
|
+
const [selectedIndex, setSelectedIndex] = useState(initialIndex >= 0 ? initialIndex : 0);
|
|
14
|
+
useInput((input, key) => {
|
|
15
|
+
if (key.escape) {
|
|
16
|
+
onCancel();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// j or down arrow: move down
|
|
20
|
+
if (input === 'j' || key.downArrow) {
|
|
21
|
+
setSelectedIndex((prev) => (prev < VALID_LOCALES.length - 1 ? prev + 1 : 0));
|
|
22
|
+
}
|
|
23
|
+
// k or up arrow: move up
|
|
24
|
+
if (input === 'k' || key.upArrow) {
|
|
25
|
+
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : VALID_LOCALES.length - 1));
|
|
26
|
+
}
|
|
27
|
+
// Enter: select
|
|
28
|
+
if (key.return) {
|
|
29
|
+
onSelect(VALID_LOCALES[selectedIndex]);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Select language / \u8A00\u8A9E\u3092\u9078\u629E:" }), _jsx(Text, { dimColor: true, children: "j/k: select, Enter: confirm, Esc: cancel" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: VALID_LOCALES.map((locale, index) => {
|
|
33
|
+
const isSelected = index === selectedIndex;
|
|
34
|
+
const isCurrent = locale === currentLocale;
|
|
35
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? 'cyan' : undefined, children: [isSelected ? '› ' : ' ', localeDisplayNames[locale], isCurrent ? ' (current)' : ''] }) }, locale));
|
|
36
|
+
}) })] }));
|
|
37
|
+
}
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { type ViewMode } from '../config.js';
|
|
3
3
|
interface ModeSelectorProps {
|
|
4
4
|
onSelect: (mode: ViewMode) => void;
|
|
5
|
+
onCancel?: () => void;
|
|
5
6
|
}
|
|
6
|
-
export declare function ModeSelector({ onSelect }: ModeSelectorProps): React.ReactElement;
|
|
7
|
+
export declare function ModeSelector({ onSelect, onCancel }: ModeSelectorProps): React.ReactElement;
|
|
7
8
|
export {};
|
package/dist/ui/ModeSelector.js
CHANGED
|
@@ -11,11 +11,15 @@ const modeDescriptions = {
|
|
|
11
11
|
gtd: 'Classic GTD workflow with Inbox, Next, Waiting, Someday lists',
|
|
12
12
|
kanban: '3-column kanban board view',
|
|
13
13
|
};
|
|
14
|
-
export function ModeSelector({ onSelect }) {
|
|
14
|
+
export function ModeSelector({ onSelect, onCancel }) {
|
|
15
15
|
const currentMode = getViewMode();
|
|
16
16
|
const initialIndex = VALID_VIEW_MODES.indexOf(currentMode);
|
|
17
17
|
const [selectedIndex, setSelectedIndex] = useState(initialIndex >= 0 ? initialIndex : 0);
|
|
18
18
|
useInput((input, key) => {
|
|
19
|
+
if (key.escape && onCancel) {
|
|
20
|
+
onCancel();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
19
23
|
// j or down arrow: move down
|
|
20
24
|
if (input === 'j' || key.downArrow) {
|
|
21
25
|
setSelectedIndex((prev) => (prev < VALID_VIEW_MODES.length - 1 ? prev + 1 : 0));
|
|
@@ -29,7 +33,7 @@ export function ModeSelector({ onSelect }) {
|
|
|
29
33
|
onSelect(VALID_VIEW_MODES[selectedIndex]);
|
|
30
34
|
}
|
|
31
35
|
});
|
|
32
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Select a view mode:" }), _jsx(Text, { dimColor: true, children: "j/k: select, Enter: confirm" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: VALID_VIEW_MODES.map((mode, index) => {
|
|
36
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Select a view mode:" }), _jsx(Text, { dimColor: true, children: "j/k: select, Enter: confirm, Esc: cancel" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: VALID_VIEW_MODES.map((mode, index) => {
|
|
33
37
|
const isSelected = index === selectedIndex;
|
|
34
38
|
const isCurrent = mode === currentMode;
|
|
35
39
|
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: isSelected ? 'cyan' : undefined, children: [isSelected ? '› ' : ' ', modeDisplayNames[mode], isCurrent ? ' (current)' : ''] }), _jsxs(Text, { dimColor: true, children: [' ', modeDescriptions[mode]] })] }, mode));
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import type { ThemeName } from './theme/types.js';
|
|
3
3
|
interface ThemeSelectorProps {
|
|
4
4
|
onSelect: (theme: ThemeName) => void;
|
|
5
|
+
onCancel?: () => void;
|
|
5
6
|
}
|
|
6
|
-
export declare function ThemeSelector({ onSelect }: ThemeSelectorProps): React.ReactElement;
|
|
7
|
+
export declare function ThemeSelector({ onSelect, onCancel }: ThemeSelectorProps): React.ReactElement;
|
|
7
8
|
export {};
|
package/dist/ui/ThemeSelector.js
CHANGED
|
@@ -3,11 +3,15 @@ import { useState } from 'react';
|
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
4
|
import { themes, VALID_THEMES } from './theme/themes.js';
|
|
5
5
|
import { getThemeName } from '../config.js';
|
|
6
|
-
export function ThemeSelector({ onSelect }) {
|
|
6
|
+
export function ThemeSelector({ onSelect, onCancel }) {
|
|
7
7
|
const currentTheme = getThemeName();
|
|
8
8
|
const initialIndex = VALID_THEMES.indexOf(currentTheme);
|
|
9
9
|
const [selectedIndex, setSelectedIndex] = useState(initialIndex >= 0 ? initialIndex : 0);
|
|
10
10
|
useInput((input, key) => {
|
|
11
|
+
if (key.escape && onCancel) {
|
|
12
|
+
onCancel();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
11
15
|
// j or down arrow: move down
|
|
12
16
|
if (input === 'j' || key.downArrow) {
|
|
13
17
|
setSelectedIndex((prev) => (prev < VALID_THEMES.length - 1 ? prev + 1 : 0));
|
|
@@ -21,7 +25,7 @@ export function ThemeSelector({ onSelect }) {
|
|
|
21
25
|
onSelect(VALID_THEMES[selectedIndex]);
|
|
22
26
|
}
|
|
23
27
|
});
|
|
24
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Select a theme:" }), _jsx(Text, { dimColor: true, children: "j/k: select, Enter: confirm" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: VALID_THEMES.map((themeName, index) => {
|
|
28
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Select a theme:" }), _jsx(Text, { dimColor: true, children: "j/k: select, Enter: confirm, Esc: cancel" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: VALID_THEMES.map((themeName, index) => {
|
|
25
29
|
const theme = themes[themeName];
|
|
26
30
|
const isSelected = index === selectedIndex;
|
|
27
31
|
const isCurrent = themeName === currentTheme;
|