loop-task 1.5.0 → 1.5.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.md +52 -9
- package/dist/board/App.js +54 -18
- package/dist/board/components/CreateForm.js +45 -78
- package/dist/board/components/CreateProjectModal.js +13 -20
- package/dist/board/components/EditProjectModal.js +13 -20
- package/dist/board/components/ProjectsPage.js +46 -36
- package/dist/board/components/SearchSelect.js +55 -23
- package/dist/board/components/TaskBrowser.js +4 -2
- package/dist/board/components/TaskForm.js +26 -47
- package/dist/board/focus-context.js +98 -0
- package/dist/board/hooks/useBoardKeybindings.js +5 -47
- package/dist/board/hooks/useTabNav.js +60 -0
- package/dist/board/hooks/useTaskKeybindings.js +3 -30
- package/dist/i18n/en.json +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -290,29 +290,28 @@ gh issue edit {{number}} --add-label "refining" --remove-label "to refine"
|
|
|
290
290
|
|
|
291
291
|
interpolated: `gh issue edit 123 --add-label "refining" --remove-label "to refine"`
|
|
292
292
|
|
|
293
|
-
**Task 3** (chain, onSuccess): Rewrite with AI
|
|
293
|
+
**Task 3** (chain, onSuccess): Rewrite with AI (edits the issue directly)
|
|
294
294
|
|
|
295
295
|
```bash
|
|
296
|
-
opencode run "Rewrite this GitHub issue as a detailed user story using project context and
|
|
296
|
+
opencode run "Rewrite this GitHub issue as a detailed user story using project context. Update the issue title and body directly using gh issue edit. Issue number: {{number}} Original title: {{title}} Original body: {{body}}" --model "opencode/big-pickle"
|
|
297
297
|
```
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
context: `{ number: 123, title: "As a user, I want to log in securely", body: "## Acceptance Criteria\n- ..." }`
|
|
299
|
+
interpolated: `opencode run "Rewrite this GitHub issue as a detailed user story using project context. Update the issue title and body directly using gh issue edit. Issue number: 123 Original title: Fix login Original body: It doesn't work" --model "opencode/big-pickle"`
|
|
301
300
|
|
|
302
|
-
**Task 4** (chain, onSuccess):
|
|
301
|
+
**Task 4** (chain, onSuccess): Relabel as ready to implement
|
|
303
302
|
|
|
304
303
|
```bash
|
|
305
|
-
gh issue edit {{number}} --
|
|
304
|
+
gh issue edit {{number}} --remove-label "refining" --add-label "to implement"
|
|
306
305
|
```
|
|
307
306
|
|
|
308
|
-
interpolated: `gh issue edit 123 --
|
|
307
|
+
interpolated: `gh issue edit 123 --remove-label "refining" --add-label "to implement"`
|
|
309
308
|
|
|
310
309
|
### How it works
|
|
311
310
|
|
|
312
311
|
1. Task 1 queries the issue and emits a JSON object with `number`, `title`, and `body` via `--jq`. The primary task cannot use `{{key}}` interpolation because the chain context is empty when it runs.
|
|
313
312
|
2. Task 2 receives `{{number}}` interpolated from task 1's context. It relabels the issue from "to refine" to "refining" - no re-query needed.
|
|
314
|
-
3. Task 3 runs opencode, which finds the issue by the "refining" label and rewrites it
|
|
315
|
-
4. Task 4 receives `{{number}}` (still 123 from task 1)
|
|
313
|
+
3. Task 3 runs opencode, which finds the issue by the "refining" label and rewrites it in place using `gh issue edit`. The AI agent edits the issue directly - no need to parse its stdout as JSON.
|
|
314
|
+
4. Task 4 receives `{{number}}` (still 123 from task 1) and relabels the issue as "to implement" - no re-query needed.
|
|
316
315
|
|
|
317
316
|
### Wrapping values with --jq
|
|
318
317
|
|
|
@@ -324,6 +323,50 @@ gh issue list --label "to refine" --json number,title --jq '{number: .[0].number
|
|
|
324
323
|
|
|
325
324
|
This stores `{ "number": 123, "title": "Fix login" }` in context instead of overwriting `output`.
|
|
326
325
|
|
|
326
|
+
### Example: Issue Implementation Chain
|
|
327
|
+
|
|
328
|
+
A four-task chain that finds an issue to implement, marks it in-progress, runs an AI agent to implement it, then closes it - all without re-querying:
|
|
329
|
+
|
|
330
|
+
**Task 1** (primary): Find an issue to implement (or exit if one is already in progress)
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
gh issue list --label "implementing" --limit 1 --json number --jq 'length == 0' | grep -q true && gh issue list --label "to implement" --limit 1 --json number,title,body --jq '{number: .[0].number, title: .[0].title, body: .[0].body}'
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
stdout: `{"number":456,"title":"Add dark mode toggle","body":"Users want a dark theme"}`
|
|
337
|
+
context: `{ number: 456, title: "Add dark mode toggle", body: "Users want a dark theme" }`
|
|
338
|
+
|
|
339
|
+
If an issue with the "implementing" label already exists, `length == 0` returns `false`, `grep -q true` fails, and the `&&` short-circuits - the chain does not fire. The loop waits for the next iteration.
|
|
340
|
+
|
|
341
|
+
**Task 2** (chain, onSuccess): Mark as in-progress
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
gh issue edit {{number}} --add-label "implementing" --remove-label "to implement"
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
interpolated: `gh issue edit 456 --add-label "implementing" --remove-label "to implement"`
|
|
348
|
+
|
|
349
|
+
**Task 3** (chain, onSuccess): Implement with AI agent
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
git fetch origin && git checkout main && git reset --hard origin/main && opencode run "Implement this GitHub issue using /ob-autopilot and return only JSON with fields title and body after implementation is completed, merged to main, pushed to origin and the issue has been referenced in GitHub. Issue title: {{title}} Issue body: {{body}}" --model "opencode/big-pickle"
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
interpolated: `git fetch origin && git checkout main && git reset --hard origin/main && opencode run "Implement this GitHub issue using /ob-autopilot ... Issue title: Add dark mode toggle Issue body: Users want a dark theme" --model "opencode/big-pickle"`
|
|
356
|
+
|
|
357
|
+
stdout: `{"title":"Add dark mode toggle","body":"Implemented dark mode toggle with CSS variables..."}`
|
|
358
|
+
context: `{ number: 456, title: "Add dark mode toggle", body: "Implemented dark mode toggle..." }`
|
|
359
|
+
|
|
360
|
+
**Task 4** (chain, onSuccess): Verify sync and close the issue
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
git push && git fetch origin && [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ] && gh issue edit {{number}} --remove-label "implementing" && gh issue close {{number}}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
interpolated: `git push && git fetch origin && [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ] && gh issue edit 456 --remove-label "implementing" && gh issue close 456`
|
|
367
|
+
|
|
368
|
+
The `git rev-parse` check ensures local and remote are in sync before closing - if the push failed or remote is ahead, the command fails and the issue stays open.
|
|
369
|
+
|
|
327
370
|
## Development
|
|
328
371
|
|
|
329
372
|
Requires [Bun](https://bun.sh) >= 1.2 for package management and the board, and [Node.js](https://nodejs.org) >= 20 for the CLI and daemon.
|
package/dist/board/App.js
CHANGED
|
@@ -12,7 +12,7 @@ import { FilterBar } from "./components/FilterBar.js";
|
|
|
12
12
|
import { Navigator } from "./components/Navigator.js";
|
|
13
13
|
import { Inspector } from "./components/Inspector.js";
|
|
14
14
|
import { RunHistory } from "./components/RunHistory.js";
|
|
15
|
-
import { ActionButtons } from "./components/ActionButtons.js";
|
|
15
|
+
import { ActionButtons, getActionKeys } from "./components/ActionButtons.js";
|
|
16
16
|
import { HelpModal } from "./components/HelpModal.js";
|
|
17
17
|
import { ContextHelpModal } from "./components/ContextHelpModal.js";
|
|
18
18
|
import { Footer } from "./components/Footer.js";
|
|
@@ -28,6 +28,7 @@ import { fetchRunLog, deleteLoop, pauseLoop, resumeLoop, stopLoop, playLoop, tri
|
|
|
28
28
|
import { useBreakpoint } from "./hooks/useBreakpoint.js";
|
|
29
29
|
import { useRouter } from "./router.js";
|
|
30
30
|
import { POLL_MS } from "../config/constants.js";
|
|
31
|
+
import { useTabNav } from "./hooks/useTabNav.js";
|
|
31
32
|
const VIEW_TO_MODE = {
|
|
32
33
|
create: "create",
|
|
33
34
|
"task-create": "task",
|
|
@@ -68,14 +69,12 @@ export function App(props) {
|
|
|
68
69
|
const [pendingTaskSelection, setPendingTaskSelection] = useState(null);
|
|
69
70
|
const [selectedRunIndex, setSelectedRunIndex] = useState(0);
|
|
70
71
|
const [selectedAction, setSelectedAction] = useState(0);
|
|
71
|
-
const [focusedPanel, setFocusedPanel] = useState("loops");
|
|
72
72
|
const [logModalRun, setLogModalRun] = useState(null);
|
|
73
73
|
const [logModalLines, setLogModalLines] = useState([]);
|
|
74
74
|
const [logModalLoading, setLogModalLoading] = useState(false);
|
|
75
75
|
const [tasks, setTasks] = useState([]);
|
|
76
76
|
const [taskSelectedIndex, setTaskSelectedIndex] = useState(0);
|
|
77
77
|
const [taskSelectedAction, setTaskSelectedAction] = useState(0);
|
|
78
|
-
const [taskFocusedPanel, setTaskFocusedPanel] = useState("tasks");
|
|
79
78
|
const [taskSearchActive, setTaskSearchActive] = useState(false);
|
|
80
79
|
const [taskQuery, setTaskQuery] = useState("");
|
|
81
80
|
const [projects, setProjects] = useState([]);
|
|
@@ -282,6 +281,30 @@ export function App(props) {
|
|
|
282
281
|
pop();
|
|
283
282
|
pushToast("success", updated ? t("board.toastTaskUpdated", { id }) : t("board.toastTaskCreated", { id }));
|
|
284
283
|
};
|
|
284
|
+
const FORM_VIEWS = ["create", "task-create", "task-edit"];
|
|
285
|
+
const isFormView = FORM_VIEWS.includes(view);
|
|
286
|
+
const actionKeys = selected ? getActionKeys(selected.status) : [];
|
|
287
|
+
const actionItems = actionKeys.map((k) => `action-${k}`);
|
|
288
|
+
const boardItems = ["search", "project-filter", "status", "sort", "header-1", "header-2", "header-3", "navigator", "run-history", ...actionItems];
|
|
289
|
+
const taskItems = ["task-search", "header-1", "header-2", "header-3", "task-list", ...(["select", "edit", "delete"].map((k) => `task-action-${k}`))];
|
|
290
|
+
const projectItems = ["header-1", "header-2", "header-3"];
|
|
291
|
+
const navItems = isFormView ? [] : view === "task-list" ? taskItems : view === "projects" ? projectItems : boardItems;
|
|
292
|
+
const { focusedItem: navFocused, setFocusIndex: setNavIndex, enabled: navEnabled } = useTabNav(navItems);
|
|
293
|
+
const HEADER_MAP = { "header-1": "header-tasks", "header-2": "header-projects", "header-3": "header-new" };
|
|
294
|
+
const isHeaderFocused = navFocused === "header-1" || navFocused === "header-2" || navFocused === "header-3";
|
|
295
|
+
const derivedFocusedPanel = isHeaderFocused ? HEADER_MAP[navFocused] : navFocused ?? "loops";
|
|
296
|
+
const actionIdx = navFocused?.startsWith("action-") ? actionKeys.indexOf(navFocused.slice("action-".length)) : -1;
|
|
297
|
+
const derivedSelectedAction = actionIdx >= 0 ? actionIdx : selectedAction;
|
|
298
|
+
const taskActionIdx = navFocused?.startsWith("task-action-") ? ["select", "edit", "delete"].indexOf(navFocused.slice("task-action-".length)) : -1;
|
|
299
|
+
const derivedTaskAction = taskActionIdx >= 0 ? taskActionIdx : taskSelectedAction;
|
|
300
|
+
const taskPanelMap = {
|
|
301
|
+
"task-search": "search",
|
|
302
|
+
"task-list": "tasks",
|
|
303
|
+
"task-action-select": "actions",
|
|
304
|
+
"task-action-edit": "actions",
|
|
305
|
+
"task-action-delete": "actions",
|
|
306
|
+
};
|
|
307
|
+
const derivedTaskPanel = (navFocused ? taskPanelMap[navFocused] : undefined) ?? "tasks";
|
|
285
308
|
useBoardKeybindings({
|
|
286
309
|
confirm,
|
|
287
310
|
confirmChoice,
|
|
@@ -309,9 +332,13 @@ export function App(props) {
|
|
|
309
332
|
selectedRunIndex,
|
|
310
333
|
setSelectedRunIndex,
|
|
311
334
|
selectedRunCount: selected?.runHistory?.length ?? 0,
|
|
312
|
-
focusedPanel,
|
|
313
|
-
setFocusedPanel
|
|
314
|
-
|
|
335
|
+
focusedPanel: derivedFocusedPanel,
|
|
336
|
+
setFocusedPanel: ((p) => {
|
|
337
|
+
const navIdx = navItems.indexOf(p);
|
|
338
|
+
if (navIdx >= 0)
|
|
339
|
+
setNavIndex(navIdx);
|
|
340
|
+
}),
|
|
341
|
+
selectedAction: derivedSelectedAction,
|
|
315
342
|
setSelectedAction,
|
|
316
343
|
onAction: handleAction,
|
|
317
344
|
onOpenRunLog: handleOpenRunLog,
|
|
@@ -329,10 +356,23 @@ export function App(props) {
|
|
|
329
356
|
tasks: filteredTasks,
|
|
330
357
|
taskSelectedIndex,
|
|
331
358
|
setTaskSelectedIndex,
|
|
332
|
-
taskSelectedAction,
|
|
333
|
-
setTaskSelectedAction
|
|
334
|
-
|
|
335
|
-
|
|
359
|
+
taskSelectedAction: derivedTaskAction,
|
|
360
|
+
setTaskSelectedAction: ((updater) => {
|
|
361
|
+
const nextIdx = typeof updater === "function" ? updater(derivedTaskAction) : updater;
|
|
362
|
+
const taskActions = ["select", "edit", "delete"];
|
|
363
|
+
const actionKey = taskActions[nextIdx];
|
|
364
|
+
if (actionKey) {
|
|
365
|
+
const navIdx = navItems.indexOf(`task-action-${actionKey}`);
|
|
366
|
+
if (navIdx >= 0)
|
|
367
|
+
setNavIndex(navIdx);
|
|
368
|
+
}
|
|
369
|
+
}),
|
|
370
|
+
taskFocusedPanel: derivedTaskPanel,
|
|
371
|
+
setTaskFocusedPanel: ((p) => {
|
|
372
|
+
const navIdx = navItems.indexOf(p === "search" ? "task-search" : p === "actions" ? "task-actions" : "task-list");
|
|
373
|
+
if (navIdx >= 0)
|
|
374
|
+
setNavIndex(navIdx);
|
|
375
|
+
}),
|
|
336
376
|
taskSearchActive,
|
|
337
377
|
setTaskSearchActive,
|
|
338
378
|
taskQuery,
|
|
@@ -340,10 +380,8 @@ export function App(props) {
|
|
|
340
380
|
onTaskAction: handleTaskAction,
|
|
341
381
|
onCancel: cancelTaskList,
|
|
342
382
|
onCreateTask: handleCreateTask,
|
|
343
|
-
onEnterHeader: (direction) => {
|
|
344
|
-
setFocusedPanel(direction === "right" ? "header-tasks" : "header-new");
|
|
345
|
-
},
|
|
346
383
|
onToggleContextHelp: () => setContextHelpOpen((v) => !v),
|
|
384
|
+
headerFocused: isHeaderFocused,
|
|
347
385
|
selectable: stack.includes("create") || stack.includes("task-edit"),
|
|
348
386
|
});
|
|
349
387
|
const counts = {
|
|
@@ -354,16 +392,14 @@ export function App(props) {
|
|
|
354
392
|
idle: loops.filter((l) => l.status === "idle").length,
|
|
355
393
|
};
|
|
356
394
|
const mode = resolveMode(confirm, searchActive || taskSearchActive, helpOpen, view);
|
|
357
|
-
return (_jsxs("box", { style: { flexDirection: "column", width: "100%", height: "100%", backgroundColor: "#0b0b0b" }, children: [_jsx(Header, { daemonStatus: daemonStatus, counts: counts, view: view, focusedPanel:
|
|
395
|
+
return (_jsxs("box", { style: { flexDirection: "column", width: "100%", height: "100%", backgroundColor: "#0b0b0b" }, children: [_jsx(Header, { daemonStatus: daemonStatus, counts: counts, view: view, focusedPanel: derivedFocusedPanel, onViewLoops: () => replace("board"), onViewTasks: () => { void refreshTasks(); push("task-list"); }, onViewProjects: () => push("projects"), onAddLoop: () => { setEditTarget(null); push("create"); }, onAddTask: () => { setEditTask(null); push("task-create"); }, onAddProject: () => { if (view === "projects") {
|
|
358
396
|
createProjectTriggerRef.current?.();
|
|
359
397
|
}
|
|
360
398
|
else {
|
|
361
399
|
push("projects");
|
|
362
|
-
} } }), view === "board" ? (_jsx(FilterBar, { filters: filters, sort: sort, searchActive: searchActive, focusedPanel:
|
|
363
|
-
setFocusedPanel(direction === "right" ? "header-tasks" : "header-new");
|
|
364
|
-
} })) : (_jsxs("box", { style: { flexDirection: breakpoint === "narrow" ? "column" : "row", flexGrow: 1, backgroundColor: "#0b0b0b" }, children: [_jsx(Navigator, { visible: visible, total: loops.length, selectedIndex: clampedIndex, filters: filters, sort: sort, breakpoint: breakpoint, focused: focusedPanel === "loops", projects: projects, onSelect: (index) => { setSelectedIndex(index); setFocusedPanel("loops"); }, onActivate: (index) => { setSelectedIndex(index); const loop = visible[index]; if (loop) {
|
|
400
|
+
} } }), view === "board" ? (_jsx(FilterBar, { filters: filters, sort: sort, searchActive: searchActive, focusedPanel: derivedFocusedPanel, onStatusCycle: () => setFilters((prev) => ({ ...prev, status: cycleStatusFilter(prev.status) })), onSortCycle: () => setSort(cycleSortMode(sort)), onSelectProject: () => setProjectsModalOpen(true), currentProjectName: currentProjectId === "all" ? t("project.showAll") : (projects.find(p => p.id === currentProjectId)?.name ?? "Default"), onQueryChange: (value) => setFilters((prev) => ({ ...prev, query: value })), onSearchActivate: () => setSearchActive(true), onSearchDismiss: () => { setSearchActive(false); } })) : view === "task-list" ? (_jsx(TaskFilterBar, { query: taskQuery, searchActive: taskSearchActive, focusedPanel: derivedTaskPanel, onQueryChange: setTaskQuery, onSearchActivate: () => setTaskSearchActive(true), onSearchDismiss: () => { setTaskSearchActive(false); } })) : null, _jsx("box", { style: { flexGrow: 1, backgroundColor: "#0b0b0b" }, children: view === "create" ? (_jsx(CreateView, { mode: editTarget && !cloneMode ? "edit" : "create", editId: editTarget && !cloneMode ? editTarget.id : null, initial: createInitialValues(editTarget, currentProjectId), selectedTaskId: pendingTaskSelection?.id ?? null, selectedTaskName: pendingTaskSelection?.name ?? null, projects: projects, currentProjectId: currentProjectId, onCancel: cancelCreate, onDone: onCreateDone, onChooseTask: handleChooseTask })) : TASK_FORM_VIEWS.has(view) ? (_jsx(TaskForm, { mode: view === "task-edit" ? "edit" : "create", editTask: editTask, onCancel: cancelTask, onDone: onTaskDone, onCopy: () => pushToast("success", t("board.toastCopied")) })) : view === "task-list" ? (_jsxs("box", { style: { flexDirection: breakpoint === "narrow" ? "column" : "row", flexGrow: 1, backgroundColor: "#0b0b0b" }, children: [_jsx(TaskNavigator, { visible: filteredTasks, total: tasks.length, selectedIndex: taskClampedIndex, focused: derivedTaskPanel === "tasks", query: taskQuery, onSelect: (index) => { setTaskSelectedIndex(index); }, onActivate: (index) => { setTaskSelectedIndex(index); setEditTask(filteredTasks[index] ?? null); push("task-edit"); } }), _jsxs("box", { style: { flexDirection: "column", flexGrow: 1, backgroundColor: "#0b0b0b", overflow: "hidden" }, children: [_jsx(TaskInspector, { task: selectedTask, onCopy: () => pushToast("success", t("board.toastCopied")) }, `ti-${selectedTask?.id}`), _jsx(TaskActionButtons, { task: selectedTask, focused: taskActionIdx >= 0, selectedAction: derivedTaskAction, selectable: stack.includes("create") || stack.includes("task-edit"), onAction: handleTaskAction }, `tab-${selectedTask?.id}`)] })] })) : view === "projects" ? (_jsx(ProjectsPage, { projects: projects, loops: loops, headerFocused: isHeaderFocused, onClose: () => pop(), onRefresh: refreshProjects, onOpenCreate: (trigger) => { createProjectTriggerRef.current = trigger; }, onEnterHeader: () => { } })) : (_jsxs("box", { style: { flexDirection: breakpoint === "narrow" ? "column" : "row", flexGrow: 1, backgroundColor: "#0b0b0b" }, children: [_jsx(Navigator, { visible: visible, total: loops.length, selectedIndex: clampedIndex, filters: filters, sort: sort, breakpoint: breakpoint, focused: derivedFocusedPanel === "loops" || derivedFocusedPanel === "navigator", projects: projects, onSelect: (index) => setSelectedIndex(index), onActivate: (index) => { setSelectedIndex(index); const loop = visible[index]; if (loop) {
|
|
365
401
|
setEditTarget(loop);
|
|
366
402
|
push("create");
|
|
367
|
-
} } }), _jsxs("box", { style: { flexDirection: "column", flexGrow: 1, backgroundColor: "#0b0b0b", overflow: "hidden" }, children: [_jsx(Inspector, { loop: selected }), _jsx(RunHistory, { loop: selected, selectedRunIndex: selectedRunIndex, focused:
|
|
403
|
+
} } }), _jsxs("box", { style: { flexDirection: "column", flexGrow: 1, backgroundColor: "#0b0b0b", overflow: "hidden" }, children: [_jsx(Inspector, { loop: selected }), _jsx(RunHistory, { loop: selected, selectedRunIndex: selectedRunIndex, focused: derivedFocusedPanel === "runs" || derivedFocusedPanel === "run-history", onSelectRun: (index) => setSelectedRunIndex(index), onOpenRun: handleOpenRunLog }), _jsx(ActionButtons, { loop: selected, focused: actionIdx >= 0, selectedAction: derivedSelectedAction, onAction: handleAction })] })] })) }, viewKey(view, editTarget, editTask)), _jsx(Footer, { mode: mode }), confirm ? (_jsx(ConfirmModal, { message: confirm.message, choice: confirmChoice, onYes: () => { const action = confirm.action; setConfirm(null); void action(); }, onNo: () => setConfirm(null) })) : null, helpOpen ? _jsx(HelpModal, { view: view }) : null, contextHelpOpen ? _jsx(ContextHelpModal, { onClose: () => setContextHelpOpen(false) }) : null, logModalRun ? (_jsx(LogModal, { loopId: selectedId, run: logModalRun, logLines: logModalLines, loading: logModalLoading, onClose: () => setLogModalRun(null) })) : null, projectsModalOpen ? (_jsx(ProjectsModal, { projects: projects, loops: loops, currentProjectId: currentProjectId, onSelect: (id) => { setCurrentProjectId(id); setProjectsModalOpen(false); }, onClose: () => setProjectsModalOpen(false) })) : null, _jsx(ToastStack, { toasts: toasts })] }));
|
|
368
404
|
}
|
|
369
405
|
const TASK_FORM_VIEWS = new Set(["task-create", "task-edit"]);
|
|
@@ -8,6 +8,7 @@ import { commandLine } from "../format.js";
|
|
|
8
8
|
import { createLoop, updateLoop, listTasks } from "../daemon.js";
|
|
9
9
|
import { useHoverState } from "../hooks/useHoverState.js";
|
|
10
10
|
import { useInputShortcuts } from "../hooks/useInputShortcuts.js";
|
|
11
|
+
import { useTabNav } from "../hooks/useTabNav.js";
|
|
11
12
|
import { SearchSelect } from "./SearchSelect.js";
|
|
12
13
|
import { HOVER_BG } from "../../config/constants.js";
|
|
13
14
|
export const TASK_MODE_INLINE = "inline";
|
|
@@ -45,25 +46,10 @@ export function CreateView(props) {
|
|
|
45
46
|
project: props.initial.project ?? props.currentProjectId ?? "default",
|
|
46
47
|
});
|
|
47
48
|
const valuesRef = useRef(values);
|
|
48
|
-
const [focusIndex, setFocusIndex] = useState(0);
|
|
49
49
|
const [error, setError] = useState("");
|
|
50
50
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
51
51
|
const [selectedTaskName, setSelectedTaskName] = useState(props.selectedTaskName ?? null);
|
|
52
52
|
const inputRef = useRef(null);
|
|
53
|
-
useInputShortcuts(() => {
|
|
54
|
-
if (focusIndex >= saveIndex)
|
|
55
|
-
return null;
|
|
56
|
-
return inputRef.current;
|
|
57
|
-
});
|
|
58
|
-
useState(() => {
|
|
59
|
-
if (values.taskId && !selectedTaskName) {
|
|
60
|
-
void listTasks().then((tasks) => {
|
|
61
|
-
const found = tasks.find((t) => t.id === values.taskId);
|
|
62
|
-
if (found)
|
|
63
|
-
setSelectedTaskName(found.name);
|
|
64
|
-
}).catch(() => { });
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
53
|
const { width: termWidth } = useTerminalDimensions();
|
|
68
54
|
const btnWidth = Math.max(10, Math.min(14, Math.floor(termWidth / 6)));
|
|
69
55
|
const isInline = values.taskMode === TASK_MODE_INLINE;
|
|
@@ -77,9 +63,25 @@ export function CreateView(props) {
|
|
|
77
63
|
return !isInline;
|
|
78
64
|
return true;
|
|
79
65
|
});
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
66
|
+
const navItems = [...filteredFields, "save", "cancel"];
|
|
67
|
+
const { setFocusIndex, focusedItem, isFocused } = useTabNav(navItems);
|
|
68
|
+
const focusedItemRef = useRef(focusedItem);
|
|
69
|
+
focusedItemRef.current = focusedItem;
|
|
70
|
+
useInputShortcuts(() => {
|
|
71
|
+
const fi = focusedItemRef.current;
|
|
72
|
+
if (fi === "save" || fi === "cancel" || fi === "project")
|
|
73
|
+
return null;
|
|
74
|
+
return inputRef.current;
|
|
75
|
+
});
|
|
76
|
+
useState(() => {
|
|
77
|
+
if (values.taskId && !selectedTaskName) {
|
|
78
|
+
void listTasks().then((tasks) => {
|
|
79
|
+
const found = tasks.find((t) => t.id === values.taskId);
|
|
80
|
+
if (found)
|
|
81
|
+
setSelectedTaskName(found.name);
|
|
82
|
+
}).catch(() => { });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
83
85
|
function updateValues(next) {
|
|
84
86
|
valuesRef.current = next;
|
|
85
87
|
setValues(next);
|
|
@@ -91,60 +93,33 @@ export function CreateView(props) {
|
|
|
91
93
|
setSelectedTaskName(props.selectedTaskName ?? props.selectedTaskId);
|
|
92
94
|
}
|
|
93
95
|
useKeyboard((key) => {
|
|
94
|
-
if (key.name
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return 0;
|
|
101
|
-
return next;
|
|
102
|
-
});
|
|
96
|
+
if (key.name !== "return" && key.name !== "enter" && key.name !== " ")
|
|
97
|
+
return;
|
|
98
|
+
const fi = focusedItemRef.current;
|
|
99
|
+
if (fi === "taskMode") {
|
|
100
|
+
const next = valuesRef.current.taskMode === TASK_MODE_INLINE ? TASK_MODE_EXISTING : TASK_MODE_INLINE;
|
|
101
|
+
updateValues({ ...valuesRef.current, taskMode: next });
|
|
103
102
|
key.preventDefault();
|
|
104
103
|
return;
|
|
105
104
|
}
|
|
106
|
-
if (
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
return;
|
|
110
|
-
setFocusIndex((i) => {
|
|
111
|
-
const next = key.name === "up" ? i - 1 : i + 1;
|
|
112
|
-
if (next < 0)
|
|
113
|
-
return cancelIndex;
|
|
114
|
-
if (next > cancelIndex)
|
|
115
|
-
return 0;
|
|
116
|
-
return next;
|
|
117
|
-
});
|
|
105
|
+
if (fi === "runNow") {
|
|
106
|
+
const next = valuesRef.current.runNow === "y" ? "n" : "y";
|
|
107
|
+
updateValues({ ...valuesRef.current, runNow: next });
|
|
118
108
|
key.preventDefault();
|
|
119
109
|
return;
|
|
120
110
|
}
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (focusIndex === chooseTaskIdx && !isInline) {
|
|
136
|
-
props.onChooseTask();
|
|
137
|
-
key.preventDefault();
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (focusIndex === saveIndex) {
|
|
141
|
-
void submit(valuesRef.current);
|
|
142
|
-
key.preventDefault();
|
|
143
|
-
}
|
|
144
|
-
else if (focusIndex === cancelIndex) {
|
|
145
|
-
props.onCancel();
|
|
146
|
-
key.preventDefault();
|
|
147
|
-
}
|
|
111
|
+
if (fi === "taskId" && !isInline) {
|
|
112
|
+
props.onChooseTask();
|
|
113
|
+
key.preventDefault();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (fi === "save") {
|
|
117
|
+
void submit(valuesRef.current);
|
|
118
|
+
key.preventDefault();
|
|
119
|
+
}
|
|
120
|
+
else if (fi === "cancel") {
|
|
121
|
+
props.onCancel();
|
|
122
|
+
key.preventDefault();
|
|
148
123
|
}
|
|
149
124
|
});
|
|
150
125
|
async function submit(current) {
|
|
@@ -270,8 +245,8 @@ export function CreateView(props) {
|
|
|
270
245
|
return (_jsxs("box", { title: title, border: true, style: { flexDirection: "column", flexGrow: 1, padding: 1, backgroundColor: "#0b0b0b" }, children: [_jsxs("box", { style: { flexDirection: "column", marginBottom: 1 }, children: [_jsx("text", { fg: "#9ca3af", children: t("board.exampleHeading") }), _jsx("text", { fg: "#d1d5db", children: t("board.exampleFull") })] }), _jsx("box", { style: { flexDirection: "column", flexGrow: 1 }, children: Array.from({ length: Math.ceil(filteredFields.length / 2) }, (_, row) => {
|
|
271
246
|
const leftField = filteredFields[row * 2];
|
|
272
247
|
const rightField = row * 2 + 1 < filteredFields.length ? filteredFields[row * 2 + 1] : null;
|
|
273
|
-
return (_jsxs("box", { style: { flexDirection: "row", marginBottom: 1 }, children: [_jsx(FormRow, { field: leftField, index: row * 2,
|
|
274
|
-
}) }), _jsxs("box", { style: { flexDirection: "row", height: 3, marginBottom: 1, backgroundColor: "#0b0b0b" }, children: [_jsx(HoverButton, { label: isSubmitting ? t("board.saving") : props.mode === "edit" ? t("board.save") : t("board.create"), onMouseDown: () => void submit(valuesRef.current), selected:
|
|
248
|
+
return (_jsxs("box", { style: { flexDirection: "row", marginBottom: 1 }, children: [_jsx(FormRow, { field: leftField, index: row * 2, isFocused: isFocused(leftField), values: values, valuesRef: valuesRef, updateValues: updateValues, setFocusIndex: setFocusIndex, submit: submit, labels: labels, hints: hints, examples: examples, taskModeOptions: taskModeOptions, runNowOptions: runNowOptions, projectOptions: projectOptions, selectedTaskName: selectedTaskName, onChooseTask: props.onChooseTask, inputRef: inputRef, style: { width: "50%", paddingRight: 1 } }), rightField ? (_jsx(FormRow, { field: rightField, index: row * 2 + 1, isFocused: isFocused(rightField), values: values, valuesRef: valuesRef, updateValues: updateValues, setFocusIndex: setFocusIndex, submit: submit, labels: labels, hints: hints, examples: examples, taskModeOptions: taskModeOptions, runNowOptions: runNowOptions, projectOptions: projectOptions, selectedTaskName: selectedTaskName, onChooseTask: props.onChooseTask, inputRef: inputRef, style: { width: "50%" } })) : (_jsx("box", { style: { width: "50%" } }))] }, row));
|
|
249
|
+
}) }), _jsxs("box", { style: { flexDirection: "row", height: 3, marginBottom: 1, backgroundColor: "#0b0b0b" }, children: [_jsx(HoverButton, { label: isSubmitting ? t("board.saving") : props.mode === "edit" ? t("board.save") : t("board.create"), onMouseDown: () => void submit(valuesRef.current), selected: isFocused("save"), width: btnWidth, marginRight: 1 }), _jsx(HoverButton, { label: t("board.cancel"), onMouseDown: props.onCancel, selected: isFocused("cancel"), width: btnWidth })] }), _jsx("text", { fg: "#9ca3af", children: t("board.formNav") }), error ? _jsx("text", { fg: "#f87171", children: error }) : null] }));
|
|
275
250
|
}
|
|
276
251
|
function HoverButton(props) {
|
|
277
252
|
const { isHovered, hoverProps } = useHoverState();
|
|
@@ -280,9 +255,8 @@ function HoverButton(props) {
|
|
|
280
255
|
return (_jsx("box", { border: true, onMouseDown: props.onMouseDown, borderColor: borderColor, style: { width: props.width, justifyContent: "center", alignItems: "center", marginRight: props.marginRight, backgroundColor: bg }, ...hoverProps, children: _jsx("text", { fg: "#e5e7eb", children: _jsx("strong", { children: props.label }) }) }));
|
|
281
256
|
}
|
|
282
257
|
function FormRow(props) {
|
|
283
|
-
const { field, index,
|
|
258
|
+
const { field, index, isFocused, values, valuesRef, updateValues, setFocusIndex, submit, labels, hints, examples, taskModeOptions, runNowOptions, projectOptions, selectedTaskName, onChooseTask, inputRef, style } = props;
|
|
284
259
|
const { isHovered, hoverProps } = useHoverState();
|
|
285
|
-
const isFocused = focusIndex === index;
|
|
286
260
|
const isToggleField = field === "taskMode" || field === "runNow";
|
|
287
261
|
const isTaskButton = field === "taskId";
|
|
288
262
|
const isProjectField = field === "project";
|
|
@@ -293,12 +267,5 @@ function FormRow(props) {
|
|
|
293
267
|
return (_jsx("box", { onMouseDown: () => updateValues({ ...valuesRef.current, [field]: opt.value }), style: { backgroundColor: isActive ? "#1e3a8a" : undefined, paddingLeft: 1, paddingRight: 1, marginRight: 1 }, ...hoverProps, children: _jsx("text", { fg: isActive ? "#ffffff" : isHovered ? "#e5e7eb" : "#9ca3af", children: _jsx("strong", { children: opt.name }) }) }, opt.value));
|
|
294
268
|
}) })) : isTaskButton ? (_jsx("box", { border: true, borderColor: isFocused ? "#38bdf8" : undefined, style: { height: 3, flexDirection: "row", backgroundColor: "#0b0b0b", alignItems: "center", paddingLeft: 1 }, onMouseDown: () => { setFocusIndex(index); onChooseTask(); }, children: _jsx("text", { fg: values.taskId ? "#4ade80" : "#9ca3af", children: values.taskId
|
|
295
269
|
? t("board.selectedTask", { name: selectedTaskName ?? values.taskId })
|
|
296
|
-
: t("board.chooseTask") }) })) : isProjectField ? (_jsx(SearchSelect, { options: projectOptions, value: values.project, onChange: (v) => updateValues({ ...valuesRef.current, project: v }), focused: isFocused })) : (_jsx("box", { border: true, borderColor: isFocused ? "#38bdf8" : undefined, style: { height: 3, backgroundColor: "#0b0b0b" }, children: _jsx("input", { ref: inputRef, focused: isFocused, value: values[field], placeholder: examples[field] ? t("board.placeholderExample", { example: examples[field] }) : t("board.placeholderBlank"), onInput: (value) => updateValues({ ...valuesRef.current, [field]: value }), onSubmit: () => {
|
|
297
|
-
if (index < fields.length - 1) {
|
|
298
|
-
setFocusIndex(index + 1);
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
void submit(valuesRef.current);
|
|
302
|
-
}
|
|
303
|
-
} }) }))] }));
|
|
270
|
+
: t("board.chooseTask") }) })) : isProjectField ? (_jsx(SearchSelect, { options: projectOptions, value: values.project, onChange: (v) => updateValues({ ...valuesRef.current, project: v }), focused: isFocused })) : (_jsx("box", { border: true, borderColor: isFocused ? "#38bdf8" : undefined, style: { height: 3, backgroundColor: "#0b0b0b" }, children: _jsx("input", { ref: inputRef, focused: isFocused, value: values[field], placeholder: examples[field] ? t("board.placeholderExample", { example: examples[field] }) : t("board.placeholderBlank"), onInput: (value) => updateValues({ ...valuesRef.current, [field]: value }), onSubmit: () => { void submit(valuesRef.current); } }) }))] }));
|
|
304
271
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
2
|
+
import { useState, useRef } from "react";
|
|
3
|
+
import { useTabNav } from "../hooks/useTabNav.js";
|
|
3
4
|
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
4
5
|
import { t } from "../../i18n/index.js";
|
|
5
6
|
import { createProject } from "../daemon.js";
|
|
@@ -12,36 +13,28 @@ export function CreateProjectModal(props) {
|
|
|
12
13
|
const [selectedColorKey, setSelectedColorKey] = useState(defaultColorKey);
|
|
13
14
|
const [error, setError] = useState("");
|
|
14
15
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
15
|
-
const
|
|
16
|
+
const { focusedItem: focusField, setFocusIndex } = useTabNav(["name", "color", "save", "cancel"]);
|
|
17
|
+
const focusFieldRef = useRef(focusField);
|
|
18
|
+
focusFieldRef.current = focusField;
|
|
16
19
|
useKeyboard((key) => {
|
|
17
|
-
if (key.name === "tab") {
|
|
18
|
-
const order = ["name", "color", "save", "cancel"];
|
|
19
|
-
const idx = order.indexOf(focusField);
|
|
20
|
-
const next = key.shift ? order[(idx - 1 + order.length) % order.length] : order[(idx + 1) % order.length];
|
|
21
|
-
setFocusField(next ?? "name");
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
20
|
if (key.name === "escape") {
|
|
25
21
|
onCancel();
|
|
26
22
|
return;
|
|
27
23
|
}
|
|
28
24
|
if (key.name === "return" || key.name === "enter") {
|
|
29
|
-
if (
|
|
25
|
+
if (focusFieldRef.current === "cancel") {
|
|
30
26
|
onCancel();
|
|
31
27
|
return;
|
|
32
28
|
}
|
|
33
29
|
void submit();
|
|
34
30
|
return;
|
|
35
31
|
}
|
|
36
|
-
if (
|
|
37
|
-
if (key.name === "left") {
|
|
38
|
-
const idx = PROJECT_COLOR_KEYS.indexOf(selectedColorKey);
|
|
39
|
-
setSelectedColorKey(PROJECT_COLOR_KEYS[(idx - 1 + PROJECT_COLOR_KEYS.length) % PROJECT_COLOR_KEYS.length] ?? selectedColorKey);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (key.name === "right") {
|
|
32
|
+
if (focusFieldRef.current === "color") {
|
|
33
|
+
if (key.name === "left" || key.name === "right") {
|
|
43
34
|
const idx = PROJECT_COLOR_KEYS.indexOf(selectedColorKey);
|
|
44
|
-
|
|
35
|
+
const dir = key.name === "left" ? -1 : 1;
|
|
36
|
+
setSelectedColorKey(PROJECT_COLOR_KEYS[(idx + dir + PROJECT_COLOR_KEYS.length) % PROJECT_COLOR_KEYS.length] ?? selectedColorKey);
|
|
37
|
+
key.preventDefault();
|
|
45
38
|
return;
|
|
46
39
|
}
|
|
47
40
|
}
|
|
@@ -81,9 +74,9 @@ export function CreateProjectModal(props) {
|
|
|
81
74
|
padding: 1,
|
|
82
75
|
minWidth: Math.min(44, width - 4),
|
|
83
76
|
backgroundColor: "#111827",
|
|
84
|
-
}, children: [_jsx("text", { fg: focusField === "name" ? "#38bdf8" : "#e5e7eb", children: t("project.labelName") }), _jsx("text", { fg: "#6b7280", children: t("project.hintName") }), _jsx("box", { border: true, borderColor: focusField === "name" ? "#38bdf8" : undefined, style: { height: 3, backgroundColor: "#0b0b0b", marginBottom: 1 }, children: _jsx("input", { focused: focusField === "name", value: name, placeholder: "My Project", onInput: (value) => setName(value)
|
|
77
|
+
}, children: [_jsx("text", { fg: focusField === "name" ? "#38bdf8" : "#e5e7eb", children: t("project.labelName") }), _jsx("text", { fg: "#6b7280", children: t("project.hintName") }), _jsx("box", { border: true, borderColor: focusField === "name" ? "#38bdf8" : undefined, style: { height: 3, backgroundColor: "#0b0b0b", marginBottom: 1 }, children: _jsx("input", { focused: focusField === "name", value: name, placeholder: "My Project", onInput: (value) => setName(value) }) }), _jsx("text", { fg: focusField === "color" ? "#38bdf8" : "#e5e7eb", children: t("project.labelColor") }), _jsx("text", { fg: "#6b7280", children: t("project.hintColor") }), _jsx("box", { border: true, borderColor: focusField === "color" ? "#38bdf8" : undefined, style: { height: 3, flexDirection: "row", backgroundColor: "#0b0b0b", alignItems: "center", marginBottom: 1 }, children: PROJECT_COLOR_KEYS.map((colorKey) => {
|
|
85
78
|
const isActive = selectedColorKey === colorKey;
|
|
86
|
-
return (_jsx("box", { onMouseDown: () => {
|
|
79
|
+
return (_jsx("box", { onMouseDown: () => { setFocusIndex(1); setSelectedColorKey(colorKey); }, style: {
|
|
87
80
|
backgroundColor: isActive ? "#1e3a8a" : undefined,
|
|
88
81
|
paddingLeft: 1,
|
|
89
82
|
paddingRight: 1,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
2
|
+
import { useState, useRef } from "react";
|
|
3
|
+
import { useTabNav } from "../hooks/useTabNav.js";
|
|
3
4
|
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
4
5
|
import { t } from "../../i18n/index.js";
|
|
5
6
|
import { updateProject } from "../daemon.js";
|
|
@@ -14,36 +15,28 @@ export function EditProjectModal(props) {
|
|
|
14
15
|
});
|
|
15
16
|
const [error, setError] = useState("");
|
|
16
17
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
17
|
-
const
|
|
18
|
+
const { focusedItem: focusField, setFocusIndex } = useTabNav(["name", "color", "save", "cancel"]);
|
|
19
|
+
const focusFieldRef = useRef(focusField);
|
|
20
|
+
focusFieldRef.current = focusField;
|
|
18
21
|
useKeyboard((key) => {
|
|
19
|
-
if (key.name === "tab") {
|
|
20
|
-
const order = ["name", "color", "save", "cancel"];
|
|
21
|
-
const idx = order.indexOf(focusField);
|
|
22
|
-
const next = key.shift ? order[(idx - 1 + order.length) % order.length] : order[(idx + 1) % order.length];
|
|
23
|
-
setFocusField(next ?? "name");
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
22
|
if (key.name === "escape") {
|
|
27
23
|
onCancel();
|
|
28
24
|
return;
|
|
29
25
|
}
|
|
30
26
|
if (key.name === "return" || key.name === "enter") {
|
|
31
|
-
if (
|
|
27
|
+
if (focusFieldRef.current === "cancel") {
|
|
32
28
|
onCancel();
|
|
33
29
|
return;
|
|
34
30
|
}
|
|
35
31
|
void submit();
|
|
36
32
|
return;
|
|
37
33
|
}
|
|
38
|
-
if (
|
|
39
|
-
if (key.name === "left") {
|
|
40
|
-
const idx = PROJECT_COLOR_KEYS.indexOf(selectedColorKey);
|
|
41
|
-
setSelectedColorKey(PROJECT_COLOR_KEYS[(idx - 1 + PROJECT_COLOR_KEYS.length) % PROJECT_COLOR_KEYS.length] ?? selectedColorKey);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (key.name === "right") {
|
|
34
|
+
if (focusFieldRef.current === "color") {
|
|
35
|
+
if (key.name === "left" || key.name === "right") {
|
|
45
36
|
const idx = PROJECT_COLOR_KEYS.indexOf(selectedColorKey);
|
|
46
|
-
|
|
37
|
+
const dir = key.name === "left" ? -1 : 1;
|
|
38
|
+
setSelectedColorKey(PROJECT_COLOR_KEYS[(idx + dir + PROJECT_COLOR_KEYS.length) % PROJECT_COLOR_KEYS.length] ?? selectedColorKey);
|
|
39
|
+
key.preventDefault();
|
|
47
40
|
return;
|
|
48
41
|
}
|
|
49
42
|
}
|
|
@@ -83,9 +76,9 @@ export function EditProjectModal(props) {
|
|
|
83
76
|
padding: 1,
|
|
84
77
|
minWidth: Math.min(44, width - 4),
|
|
85
78
|
backgroundColor: "#111827",
|
|
86
|
-
}, children: [_jsx("text", { fg: focusField === "name" ? "#38bdf8" : "#e5e7eb", children: t("project.labelName") }), _jsx("text", { fg: "#6b7280", children: t("project.hintName") }), _jsx("box", { border: true, borderColor: focusField === "name" ? "#38bdf8" : undefined, style: { height: 3, backgroundColor: "#0b0b0b", marginBottom: 1 }, children: _jsx("input", { focused: focusField === "name", value: name, placeholder: project.name, onInput: (value) => setName(value)
|
|
79
|
+
}, children: [_jsx("text", { fg: focusField === "name" ? "#38bdf8" : "#e5e7eb", children: t("project.labelName") }), _jsx("text", { fg: "#6b7280", children: t("project.hintName") }), _jsx("box", { border: true, borderColor: focusField === "name" ? "#38bdf8" : undefined, style: { height: 3, backgroundColor: "#0b0b0b", marginBottom: 1 }, children: _jsx("input", { focused: focusField === "name", value: name, placeholder: project.name, onInput: (value) => setName(value) }) }), _jsx("text", { fg: focusField === "color" ? "#38bdf8" : "#e5e7eb", children: t("project.labelColor") }), _jsx("text", { fg: "#6b7280", children: t("project.hintColor") }), _jsx("box", { border: true, borderColor: focusField === "color" ? "#38bdf8" : undefined, style: { height: 3, flexDirection: "row", backgroundColor: "#0b0b0b", alignItems: "center", marginBottom: 1 }, children: PROJECT_COLOR_KEYS.map((colorKey) => {
|
|
87
80
|
const isActive = selectedColorKey === colorKey;
|
|
88
|
-
return (_jsx("box", { onMouseDown: () => {
|
|
81
|
+
return (_jsx("box", { onMouseDown: () => { setFocusIndex(1); setSelectedColorKey(colorKey); }, style: {
|
|
89
82
|
backgroundColor: isActive ? "#1e3a8a" : undefined,
|
|
90
83
|
paddingLeft: 1,
|
|
91
84
|
paddingRight: 1,
|