@kaban-board/tui 0.3.0 → 0.3.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/dist/index.js +598 -326
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,21 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
-
var __defProp = Object.defineProperty;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
-
for (let key of __getOwnPropNames(mod))
|
|
12
|
-
if (!__hasOwnProp.call(to, key))
|
|
13
|
-
__defProp(to, key, {
|
|
14
|
-
get: () => mod[key],
|
|
15
|
-
enumerable: true
|
|
16
|
-
});
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
3
|
var __require = import.meta.require;
|
|
20
4
|
|
|
21
5
|
// src/index.ts
|
|
@@ -140,9 +124,10 @@ async function refreshBoard(state) {
|
|
|
140
124
|
alignItems: "center"
|
|
141
125
|
});
|
|
142
126
|
const modeIndicator = state.archiveViewMode ? " [ARCHIVE]" : "";
|
|
127
|
+
const projectPath = ` (${state.projectRoot})`;
|
|
143
128
|
const headerText = new TextRenderable2(renderer, {
|
|
144
129
|
id: "header-text",
|
|
145
|
-
content: t`${fg(COLORS.warning)(LOGO)} ${fg(COLORS.accent)(state.boardName)}${fg(COLORS.textMuted)(modeIndicator)}`
|
|
130
|
+
content: t`${fg(COLORS.warning)(LOGO)} ${fg(COLORS.accent)(state.boardName)}${fg(COLORS.textMuted)(modeIndicator)}${fg(COLORS.textDim)(projectPath)}`
|
|
146
131
|
});
|
|
147
132
|
header.add(headerText);
|
|
148
133
|
mainContainer.add(header);
|
|
@@ -323,6 +308,44 @@ function createButtonRow(renderer, id, buttons) {
|
|
|
323
308
|
return state;
|
|
324
309
|
}
|
|
325
310
|
|
|
311
|
+
// src/lib/constants.ts
|
|
312
|
+
var MODAL_WIDTHS = {
|
|
313
|
+
small: 32,
|
|
314
|
+
confirmation: 45,
|
|
315
|
+
medium: 52,
|
|
316
|
+
large: 60,
|
|
317
|
+
history: 65
|
|
318
|
+
};
|
|
319
|
+
var MODAL_HEIGHTS = {
|
|
320
|
+
small: 8,
|
|
321
|
+
confirmation: 10,
|
|
322
|
+
medium: 11,
|
|
323
|
+
large: 24
|
|
324
|
+
};
|
|
325
|
+
var TRUNCATION = {
|
|
326
|
+
taskTitle: 40,
|
|
327
|
+
taskTitleShort: 30,
|
|
328
|
+
taskId: 8
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// src/lib/error.ts
|
|
332
|
+
async function withErrorHandling(state, operation, context) {
|
|
333
|
+
try {
|
|
334
|
+
return await operation();
|
|
335
|
+
} catch (error) {
|
|
336
|
+
try {
|
|
337
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
338
|
+
showErrorToast(state, `${context}: ${message}`);
|
|
339
|
+
} catch {
|
|
340
|
+
console.error(`[CRITICAL] Error handler failed: ${context}`);
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function showErrorToast(_state, message) {
|
|
346
|
+
console.error(`[TUI Error] ${message}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
326
349
|
// src/components/overlay.ts
|
|
327
350
|
import { BoxRenderable as BoxRenderable4 } from "@opentui/core";
|
|
328
351
|
function createModalOverlay(renderer, options) {
|
|
@@ -401,8 +424,8 @@ function showAddTaskModal(state, onTaskCreated) {
|
|
|
401
424
|
return;
|
|
402
425
|
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
403
426
|
id: "add-task-dialog",
|
|
404
|
-
width:
|
|
405
|
-
height:
|
|
427
|
+
width: MODAL_WIDTHS.medium,
|
|
428
|
+
height: MODAL_HEIGHTS.medium
|
|
406
429
|
});
|
|
407
430
|
const titleRow = new BoxRenderable5(renderer, {
|
|
408
431
|
id: "title-row",
|
|
@@ -441,11 +464,15 @@ function showAddTaskModal(state, onTaskCreated) {
|
|
|
441
464
|
const spacer2 = new BoxRenderable5(renderer, { id: "dialog-spacer2", width: "100%", height: 1 });
|
|
442
465
|
const doCreate = async () => {
|
|
443
466
|
const taskTitle = input.value.trim();
|
|
444
|
-
if (taskTitle) {
|
|
445
|
-
|
|
467
|
+
if (!taskTitle) {
|
|
468
|
+
closeModal(state);
|
|
469
|
+
return;
|
|
446
470
|
}
|
|
471
|
+
const result = await withErrorHandling(state, () => state.taskService.addTask({ title: taskTitle, columnId: column.id }), "Failed to create task");
|
|
447
472
|
closeModal(state);
|
|
448
|
-
|
|
473
|
+
if (result) {
|
|
474
|
+
await onTaskCreated();
|
|
475
|
+
}
|
|
449
476
|
};
|
|
450
477
|
const doCancel = () => {
|
|
451
478
|
closeModal(state);
|
|
@@ -471,9 +498,6 @@ function showAddTaskModal(state, onTaskCreated) {
|
|
|
471
498
|
state.activeModal = "addTask";
|
|
472
499
|
input.on(InputRenderableEvents.ENTER, doCreate);
|
|
473
500
|
}
|
|
474
|
-
// src/components/modals/archive-task.ts
|
|
475
|
-
import { BoxRenderable as BoxRenderable6, TextRenderable as TextRenderable5 } from "@opentui/core";
|
|
476
|
-
|
|
477
501
|
// src/lib/types.ts
|
|
478
502
|
function getSelectedTaskId(state) {
|
|
479
503
|
const column = state.columns[state.currentColumnIndex];
|
|
@@ -487,9 +511,97 @@ function getSelectedTaskId(state) {
|
|
|
487
511
|
return typeof value === "string" ? value : null;
|
|
488
512
|
}
|
|
489
513
|
|
|
514
|
+
// src/components/modals/factories/confirmation.ts
|
|
515
|
+
import { BoxRenderable as BoxRenderable6, TextRenderable as TextRenderable5 } from "@opentui/core";
|
|
516
|
+
function showConfirmationModal(state, options, onConfirm) {
|
|
517
|
+
const { renderer } = state;
|
|
518
|
+
const width = options.width ?? MODAL_WIDTHS.confirmation;
|
|
519
|
+
const height = options.height ?? MODAL_HEIGHTS.confirmation;
|
|
520
|
+
const messageColor = options.messageColor ?? COLORS.text;
|
|
521
|
+
const warningColor = options.warningColor ?? COLORS.warning;
|
|
522
|
+
const confirmHint = options.confirmHint ?? "[y] Yes [n/Esc] No";
|
|
523
|
+
const borderColor = options.borderColor ?? COLORS.accent;
|
|
524
|
+
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
525
|
+
id: options.id,
|
|
526
|
+
width,
|
|
527
|
+
height,
|
|
528
|
+
borderColor
|
|
529
|
+
});
|
|
530
|
+
const titleRow = new BoxRenderable6(renderer, {
|
|
531
|
+
id: `${options.id}-title-row`,
|
|
532
|
+
width: "100%",
|
|
533
|
+
height: 1,
|
|
534
|
+
justifyContent: "center"
|
|
535
|
+
});
|
|
536
|
+
const title = new TextRenderable5(renderer, {
|
|
537
|
+
id: `${options.id}-title`,
|
|
538
|
+
content: options.title,
|
|
539
|
+
fg: options.titleColor
|
|
540
|
+
});
|
|
541
|
+
titleRow.add(title);
|
|
542
|
+
const spacer1 = new BoxRenderable6(renderer, {
|
|
543
|
+
id: `${options.id}-spacer1`,
|
|
544
|
+
width: "100%",
|
|
545
|
+
height: 1
|
|
546
|
+
});
|
|
547
|
+
const messageRow = new BoxRenderable6(renderer, {
|
|
548
|
+
id: `${options.id}-message-row`,
|
|
549
|
+
width: "100%",
|
|
550
|
+
height: 1
|
|
551
|
+
});
|
|
552
|
+
const message = new TextRenderable5(renderer, {
|
|
553
|
+
id: `${options.id}-message`,
|
|
554
|
+
content: options.message,
|
|
555
|
+
fg: messageColor
|
|
556
|
+
});
|
|
557
|
+
messageRow.add(message);
|
|
558
|
+
dialog.add(titleRow);
|
|
559
|
+
dialog.add(spacer1);
|
|
560
|
+
dialog.add(messageRow);
|
|
561
|
+
if (options.warning) {
|
|
562
|
+
const warningRow = new BoxRenderable6(renderer, {
|
|
563
|
+
id: `${options.id}-warning-row`,
|
|
564
|
+
width: "100%",
|
|
565
|
+
height: 1
|
|
566
|
+
});
|
|
567
|
+
const warning = new TextRenderable5(renderer, {
|
|
568
|
+
id: `${options.id}-warning`,
|
|
569
|
+
content: options.warning,
|
|
570
|
+
fg: warningColor
|
|
571
|
+
});
|
|
572
|
+
warningRow.add(warning);
|
|
573
|
+
dialog.add(warningRow);
|
|
574
|
+
}
|
|
575
|
+
const spacer2 = new BoxRenderable6(renderer, {
|
|
576
|
+
id: `${options.id}-spacer2`,
|
|
577
|
+
width: "100%",
|
|
578
|
+
height: options.warning ? 1 : 2
|
|
579
|
+
});
|
|
580
|
+
dialog.add(spacer2);
|
|
581
|
+
const hintRow = new BoxRenderable6(renderer, {
|
|
582
|
+
id: `${options.id}-hint-row`,
|
|
583
|
+
width: "100%",
|
|
584
|
+
height: 1,
|
|
585
|
+
justifyContent: "center"
|
|
586
|
+
});
|
|
587
|
+
const hint = new TextRenderable5(renderer, {
|
|
588
|
+
id: `${options.id}-hint`,
|
|
589
|
+
content: confirmHint,
|
|
590
|
+
fg: COLORS.textMuted
|
|
591
|
+
});
|
|
592
|
+
hintRow.add(hint);
|
|
593
|
+
dialog.add(hintRow);
|
|
594
|
+
renderer.root.add(overlay);
|
|
595
|
+
state.modalOverlay = overlay;
|
|
596
|
+
state.activeModal = options.modalType;
|
|
597
|
+
state.onModalConfirm = async () => {
|
|
598
|
+
await onConfirm();
|
|
599
|
+
closeModal(state);
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
490
603
|
// src/components/modals/archive-task.ts
|
|
491
604
|
async function showArchiveTaskModal(state, onArchived) {
|
|
492
|
-
const { renderer } = state;
|
|
493
605
|
const taskId = getSelectedTaskId(state);
|
|
494
606
|
if (!taskId) {
|
|
495
607
|
return;
|
|
@@ -502,81 +614,160 @@ async function showArchiveTaskModal(state, onArchived) {
|
|
|
502
614
|
return;
|
|
503
615
|
}
|
|
504
616
|
state.selectedTask = task;
|
|
505
|
-
|
|
617
|
+
showConfirmationModal(state, {
|
|
506
618
|
id: "archive-task-dialog",
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
619
|
+
modalType: "archiveTask",
|
|
620
|
+
title: "Archive Task?",
|
|
621
|
+
titleColor: COLORS.warning,
|
|
622
|
+
message: task.title.slice(0, 40),
|
|
623
|
+
warning: "Task will be moved to archive.",
|
|
624
|
+
warningColor: COLORS.textMuted,
|
|
625
|
+
borderColor: COLORS.warning,
|
|
626
|
+
confirmHint: "[y] Archive [n/Esc] Cancel"
|
|
627
|
+
}, async () => {
|
|
628
|
+
const result = await withErrorHandling(state, () => state.taskService.archiveTasks({ taskIds: [taskId] }), "Failed to archive task");
|
|
629
|
+
if (result) {
|
|
630
|
+
await onArchived();
|
|
631
|
+
}
|
|
510
632
|
});
|
|
511
|
-
|
|
512
|
-
|
|
633
|
+
}
|
|
634
|
+
// src/components/modals/history.ts
|
|
635
|
+
import { AuditService } from "@kaban-board/core/bun";
|
|
636
|
+
import { BoxRenderable as BoxRenderable7, TextRenderable as TextRenderable6 } from "@opentui/core";
|
|
637
|
+
var DIALOG_WIDTH = MODAL_WIDTHS.history;
|
|
638
|
+
var MAX_ENTRIES = 15;
|
|
639
|
+
function formatTimestamp(date) {
|
|
640
|
+
return date.toLocaleString("en-US", {
|
|
641
|
+
month: "short",
|
|
642
|
+
day: "numeric",
|
|
643
|
+
hour: "2-digit",
|
|
644
|
+
minute: "2-digit"
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
function formatEntry(entry) {
|
|
648
|
+
const time = formatTimestamp(entry.timestamp);
|
|
649
|
+
const actor = entry.actor ? `@${entry.actor}` : "";
|
|
650
|
+
if (entry.eventType === "CREATE") {
|
|
651
|
+
return `${time} CREATED ${actor}`;
|
|
652
|
+
}
|
|
653
|
+
if (entry.eventType === "DELETE") {
|
|
654
|
+
return `${time} DELETED ${actor}`;
|
|
655
|
+
}
|
|
656
|
+
const field = entry.fieldName ?? "?";
|
|
657
|
+
const oldVal = entry.oldValue === null ? "null" : truncate(entry.oldValue, 15);
|
|
658
|
+
const newVal = entry.newValue === null ? "null" : truncate(entry.newValue, 15);
|
|
659
|
+
return `${time} ${field}: ${oldVal} -> ${newVal} ${actor}`;
|
|
660
|
+
}
|
|
661
|
+
async function showTaskHistoryModal(state) {
|
|
662
|
+
const { renderer, db, taskService } = state;
|
|
663
|
+
const taskId = getSelectedTaskId(state);
|
|
664
|
+
if (!taskId)
|
|
665
|
+
return;
|
|
666
|
+
const task = await taskService.getTask(taskId);
|
|
667
|
+
if (!task)
|
|
668
|
+
return;
|
|
669
|
+
blurCurrentColumnSelect(state);
|
|
670
|
+
const auditService = new AuditService(db);
|
|
671
|
+
const entries = await withErrorHandling(state, () => auditService.getTaskHistory(task.id, MAX_ENTRIES), "Failed to load task history");
|
|
672
|
+
if (!entries)
|
|
673
|
+
return;
|
|
674
|
+
const dialogHeight = Math.min(entries.length + 8, 20);
|
|
675
|
+
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
676
|
+
id: "history-dialog",
|
|
677
|
+
width: DIALOG_WIDTH,
|
|
678
|
+
height: dialogHeight
|
|
679
|
+
});
|
|
680
|
+
const headerDivider = createSectionDivider(renderer, {
|
|
681
|
+
label: "Task History",
|
|
682
|
+
width: DIALOG_WIDTH - 4,
|
|
683
|
+
id: "history-header"
|
|
684
|
+
});
|
|
685
|
+
const titleRow = new BoxRenderable7(renderer, {
|
|
686
|
+
id: "history-title-row",
|
|
513
687
|
width: "100%",
|
|
514
688
|
height: 1,
|
|
515
|
-
|
|
689
|
+
flexDirection: "row"
|
|
516
690
|
});
|
|
517
|
-
const
|
|
518
|
-
id: "
|
|
519
|
-
content: "
|
|
520
|
-
fg: COLORS.
|
|
691
|
+
const taskTitle = new TextRenderable6(renderer, {
|
|
692
|
+
id: "history-task-title",
|
|
693
|
+
content: `[${task.id.slice(0, TRUNCATION.taskId)}] "${truncate(task.title, TRUNCATION.taskTitle)}"`,
|
|
694
|
+
fg: COLORS.accent
|
|
521
695
|
});
|
|
522
|
-
titleRow.add(
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
id: "archive-task-row",
|
|
696
|
+
titleRow.add(taskTitle);
|
|
697
|
+
const spacer = new BoxRenderable7(renderer, {
|
|
698
|
+
id: "history-spacer",
|
|
526
699
|
width: "100%",
|
|
527
700
|
height: 1
|
|
528
701
|
});
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
702
|
+
dialog.add(headerDivider);
|
|
703
|
+
dialog.add(titleRow);
|
|
704
|
+
dialog.add(spacer);
|
|
705
|
+
if (entries.length === 0) {
|
|
706
|
+
const emptyRow = new BoxRenderable7(renderer, {
|
|
707
|
+
id: "history-empty",
|
|
708
|
+
width: "100%",
|
|
709
|
+
height: 1
|
|
710
|
+
});
|
|
711
|
+
const emptyText = new TextRenderable6(renderer, {
|
|
712
|
+
id: "history-empty-text",
|
|
713
|
+
content: " No history found",
|
|
714
|
+
fg: COLORS.textMuted
|
|
715
|
+
});
|
|
716
|
+
emptyRow.add(emptyText);
|
|
717
|
+
dialog.add(emptyRow);
|
|
718
|
+
} else {
|
|
719
|
+
for (let i = 0;i < entries.length; i++) {
|
|
720
|
+
const entry = entries[i];
|
|
721
|
+
const row = new BoxRenderable7(renderer, {
|
|
722
|
+
id: `history-entry-${i}`,
|
|
723
|
+
width: "100%",
|
|
724
|
+
height: 1
|
|
725
|
+
});
|
|
726
|
+
let color = COLORS.textMuted;
|
|
727
|
+
if (entry.eventType === "CREATE")
|
|
728
|
+
color = COLORS.success;
|
|
729
|
+
else if (entry.eventType === "DELETE")
|
|
730
|
+
color = COLORS.danger;
|
|
731
|
+
else
|
|
732
|
+
color = COLORS.text;
|
|
733
|
+
const text = new TextRenderable6(renderer, {
|
|
734
|
+
id: `history-entry-text-${i}`,
|
|
735
|
+
content: ` ${formatEntry(entry)}`,
|
|
736
|
+
fg: color
|
|
737
|
+
});
|
|
738
|
+
row.add(text);
|
|
739
|
+
dialog.add(row);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const footerSpacer = new BoxRenderable7(renderer, {
|
|
743
|
+
id: "history-footer-spacer",
|
|
537
744
|
width: "100%",
|
|
538
745
|
height: 1
|
|
539
746
|
});
|
|
540
|
-
const
|
|
541
|
-
id: "
|
|
542
|
-
content: "Task will be moved to archive.",
|
|
543
|
-
fg: COLORS.textMuted
|
|
544
|
-
});
|
|
545
|
-
infoRow.add(info);
|
|
546
|
-
const spacer2 = new BoxRenderable6(renderer, { id: "archive-spacer2", width: "100%", height: 2 });
|
|
547
|
-
const hintRow = new BoxRenderable6(renderer, {
|
|
548
|
-
id: "archive-hint-row",
|
|
747
|
+
const hintRow = new BoxRenderable7(renderer, {
|
|
748
|
+
id: "history-hint-row",
|
|
549
749
|
width: "100%",
|
|
550
750
|
height: 1,
|
|
551
751
|
justifyContent: "center"
|
|
552
752
|
});
|
|
553
|
-
const
|
|
554
|
-
id: "
|
|
555
|
-
content: "[
|
|
556
|
-
fg: COLORS.
|
|
753
|
+
const hintText = new TextRenderable6(renderer, {
|
|
754
|
+
id: "history-hint-text",
|
|
755
|
+
content: "[Esc] close",
|
|
756
|
+
fg: COLORS.textDim
|
|
557
757
|
});
|
|
558
|
-
hintRow.add(
|
|
559
|
-
dialog.add(
|
|
560
|
-
dialog.add(spacer1);
|
|
561
|
-
dialog.add(taskRow);
|
|
562
|
-
dialog.add(infoRow);
|
|
563
|
-
dialog.add(spacer2);
|
|
758
|
+
hintRow.add(hintText);
|
|
759
|
+
dialog.add(footerSpacer);
|
|
564
760
|
dialog.add(hintRow);
|
|
565
761
|
renderer.root.add(overlay);
|
|
566
762
|
state.modalOverlay = overlay;
|
|
567
|
-
state.activeModal = "
|
|
568
|
-
state.onModalConfirm = async () => {
|
|
569
|
-
await state.taskService.archiveTasks({ taskIds: [taskId] });
|
|
570
|
-
closeModal(state);
|
|
571
|
-
await onArchived();
|
|
572
|
-
};
|
|
763
|
+
state.activeModal = "taskHistory";
|
|
573
764
|
}
|
|
574
765
|
// src/components/modals/assign-task.ts
|
|
575
766
|
import {
|
|
576
|
-
BoxRenderable as
|
|
767
|
+
BoxRenderable as BoxRenderable8,
|
|
577
768
|
InputRenderable as InputRenderable2,
|
|
578
769
|
InputRenderableEvents as InputRenderableEvents2,
|
|
579
|
-
TextRenderable as
|
|
770
|
+
TextRenderable as TextRenderable7
|
|
580
771
|
} from "@opentui/core";
|
|
581
772
|
async function showAssignTaskModal(state, onAssigned) {
|
|
582
773
|
const { renderer } = state;
|
|
@@ -590,39 +781,39 @@ async function showAssignTaskModal(state, onAssigned) {
|
|
|
590
781
|
}
|
|
591
782
|
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
592
783
|
id: "assign-task-dialog",
|
|
593
|
-
width:
|
|
784
|
+
width: MODAL_WIDTHS.confirmation,
|
|
594
785
|
height: 12
|
|
595
786
|
});
|
|
596
|
-
const titleRow = new
|
|
787
|
+
const titleRow = new BoxRenderable8(renderer, {
|
|
597
788
|
id: "assign-title-row",
|
|
598
789
|
width: "100%",
|
|
599
790
|
height: 1
|
|
600
791
|
});
|
|
601
|
-
const title = new
|
|
792
|
+
const title = new TextRenderable7(renderer, {
|
|
602
793
|
id: "assign-title",
|
|
603
794
|
content: "Assign Task",
|
|
604
795
|
fg: COLORS.accent
|
|
605
796
|
});
|
|
606
797
|
titleRow.add(title);
|
|
607
|
-
const taskRow = new
|
|
798
|
+
const taskRow = new BoxRenderable8(renderer, {
|
|
608
799
|
id: "assign-task-row",
|
|
609
800
|
width: "100%",
|
|
610
801
|
height: 1
|
|
611
802
|
});
|
|
612
|
-
const taskText = new
|
|
803
|
+
const taskText = new TextRenderable7(renderer, {
|
|
613
804
|
id: "assign-task-text",
|
|
614
|
-
content: task.title.slice(0,
|
|
805
|
+
content: task.title.slice(0, TRUNCATION.taskTitle),
|
|
615
806
|
fg: COLORS.textMuted
|
|
616
807
|
});
|
|
617
808
|
taskRow.add(taskText);
|
|
618
|
-
const spacer1 = new
|
|
619
|
-
const labelRow = new
|
|
809
|
+
const spacer1 = new BoxRenderable8(renderer, { id: "assign-spacer1", width: "100%", height: 1 });
|
|
810
|
+
const labelRow = new BoxRenderable8(renderer, {
|
|
620
811
|
id: "assign-label-row",
|
|
621
812
|
width: "100%",
|
|
622
813
|
height: 1
|
|
623
814
|
});
|
|
624
815
|
const currentAssignee = task.assignedTo ?? "(unassigned)";
|
|
625
|
-
const label = new
|
|
816
|
+
const label = new TextRenderable7(renderer, {
|
|
626
817
|
id: "assign-label",
|
|
627
818
|
content: `Current: ${currentAssignee}`,
|
|
628
819
|
fg: COLORS.text
|
|
@@ -639,14 +830,14 @@ async function showAssignTaskModal(state, onAssigned) {
|
|
|
639
830
|
focusedBackgroundColor: COLORS.inputBg,
|
|
640
831
|
cursorColor: COLORS.cursor
|
|
641
832
|
});
|
|
642
|
-
const spacer2 = new
|
|
833
|
+
const spacer2 = new BoxRenderable8(renderer, { id: "assign-spacer2", width: "100%", height: 1 });
|
|
643
834
|
const doAssign = async () => {
|
|
644
835
|
const assignee = input.value.trim();
|
|
645
|
-
await state.taskService.updateTask(taskId, {
|
|
646
|
-
assignedTo: assignee || null
|
|
647
|
-
});
|
|
836
|
+
const result = await withErrorHandling(state, () => state.taskService.updateTask(taskId, { assignedTo: assignee || null }), "Failed to assign task");
|
|
648
837
|
closeModal(state);
|
|
649
|
-
|
|
838
|
+
if (result) {
|
|
839
|
+
await onAssigned();
|
|
840
|
+
}
|
|
650
841
|
};
|
|
651
842
|
const doCancel = () => {
|
|
652
843
|
closeModal(state);
|
|
@@ -674,9 +865,7 @@ async function showAssignTaskModal(state, onAssigned) {
|
|
|
674
865
|
input.on(InputRenderableEvents2.ENTER, doAssign);
|
|
675
866
|
}
|
|
676
867
|
// src/components/modals/delete-task.ts
|
|
677
|
-
import { BoxRenderable as BoxRenderable8, TextRenderable as TextRenderable7 } from "@opentui/core";
|
|
678
868
|
async function showDeleteTaskModal(state, onDeleted) {
|
|
679
|
-
const { renderer } = state;
|
|
680
869
|
const taskId = getSelectedTaskId(state);
|
|
681
870
|
if (!taskId) {
|
|
682
871
|
return;
|
|
@@ -686,168 +875,166 @@ async function showDeleteTaskModal(state, onDeleted) {
|
|
|
686
875
|
return;
|
|
687
876
|
}
|
|
688
877
|
state.selectedTask = task;
|
|
689
|
-
|
|
878
|
+
showConfirmationModal(state, {
|
|
690
879
|
id: "delete-task-dialog",
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
content: "Delete Task?",
|
|
704
|
-
fg: COLORS.danger
|
|
705
|
-
});
|
|
706
|
-
titleRow.add(title);
|
|
707
|
-
const spacer1 = new BoxRenderable8(renderer, { id: "delete-spacer1", width: "100%", height: 1 });
|
|
708
|
-
const taskRow = new BoxRenderable8(renderer, {
|
|
709
|
-
id: "delete-task-row",
|
|
710
|
-
width: "100%",
|
|
711
|
-
height: 1
|
|
712
|
-
});
|
|
713
|
-
const taskText = new TextRenderable7(renderer, {
|
|
714
|
-
id: "delete-task-text",
|
|
715
|
-
content: task.title.slice(0, 40),
|
|
716
|
-
fg: COLORS.text
|
|
717
|
-
});
|
|
718
|
-
taskRow.add(taskText);
|
|
719
|
-
const warningRow = new BoxRenderable8(renderer, {
|
|
720
|
-
id: "delete-warning-row",
|
|
721
|
-
width: "100%",
|
|
722
|
-
height: 1
|
|
723
|
-
});
|
|
724
|
-
const warning = new TextRenderable7(renderer, {
|
|
725
|
-
id: "delete-warning",
|
|
726
|
-
content: "This action cannot be undone.",
|
|
727
|
-
fg: COLORS.warning
|
|
728
|
-
});
|
|
729
|
-
warningRow.add(warning);
|
|
730
|
-
const spacer2 = new BoxRenderable8(renderer, { id: "delete-spacer2", width: "100%", height: 2 });
|
|
731
|
-
const hintRow = new BoxRenderable8(renderer, {
|
|
732
|
-
id: "delete-hint-row",
|
|
733
|
-
width: "100%",
|
|
734
|
-
height: 1,
|
|
735
|
-
justifyContent: "center"
|
|
880
|
+
modalType: "deleteTask",
|
|
881
|
+
title: "Delete Task?",
|
|
882
|
+
titleColor: COLORS.danger,
|
|
883
|
+
message: task.title.slice(0, 40),
|
|
884
|
+
warning: "This action cannot be undone.",
|
|
885
|
+
borderColor: COLORS.danger,
|
|
886
|
+
confirmHint: "[y] Delete [n/Esc] Cancel"
|
|
887
|
+
}, async () => {
|
|
888
|
+
const result = await withErrorHandling(state, () => state.taskService.deleteTask(taskId), "Failed to delete task");
|
|
889
|
+
if (result !== null) {
|
|
890
|
+
await onDeleted();
|
|
891
|
+
}
|
|
736
892
|
});
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
893
|
+
}
|
|
894
|
+
// src/components/modals/purge-archive.ts
|
|
895
|
+
async function showPurgeArchiveModal(state, onPurged) {
|
|
896
|
+
const stats = await state.taskService.getArchiveStats();
|
|
897
|
+
if (stats.totalArchived === 0) {
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
const taskCount = stats.totalArchived;
|
|
901
|
+
const taskWord = taskCount === 1 ? "task" : "tasks";
|
|
902
|
+
showConfirmationModal(state, {
|
|
903
|
+
id: "purge-archive-dialog",
|
|
904
|
+
modalType: "purgeArchive",
|
|
905
|
+
title: "Purge Archive?",
|
|
906
|
+
titleColor: COLORS.danger,
|
|
907
|
+
message: `${taskCount} archived ${taskWord} will be deleted`,
|
|
908
|
+
warning: "This will permanently delete ALL archived tasks. Cannot be undone.",
|
|
909
|
+
borderColor: COLORS.danger,
|
|
910
|
+
width: 50,
|
|
911
|
+
height: 12,
|
|
912
|
+
confirmHint: "[y] Purge All [n/Esc] Cancel"
|
|
913
|
+
}, async () => {
|
|
914
|
+
const result = await withErrorHandling(state, () => state.taskService.purgeArchive(), "Failed to purge archive");
|
|
915
|
+
if (result) {
|
|
916
|
+
await onPurged();
|
|
917
|
+
}
|
|
741
918
|
});
|
|
742
|
-
hintRow.add(hint);
|
|
743
|
-
dialog.add(titleRow);
|
|
744
|
-
dialog.add(spacer1);
|
|
745
|
-
dialog.add(taskRow);
|
|
746
|
-
dialog.add(warningRow);
|
|
747
|
-
dialog.add(spacer2);
|
|
748
|
-
dialog.add(hintRow);
|
|
749
|
-
renderer.root.add(overlay);
|
|
750
|
-
state.modalOverlay = overlay;
|
|
751
|
-
state.activeModal = "deleteTask";
|
|
752
|
-
state.onModalConfirm = async () => {
|
|
753
|
-
await state.taskService.deleteTask(taskId);
|
|
754
|
-
closeModal(state);
|
|
755
|
-
await onDeleted();
|
|
756
|
-
};
|
|
757
919
|
}
|
|
758
920
|
// src/components/modals/restore-task.ts
|
|
759
|
-
import { BoxRenderable as BoxRenderable9, TextRenderable as TextRenderable8 } from "@opentui/core";
|
|
760
921
|
async function showRestoreTaskModal(state, onRestored) {
|
|
761
|
-
const { renderer } = state;
|
|
762
922
|
const taskId = getSelectedTaskId(state);
|
|
763
923
|
if (!taskId) {
|
|
764
924
|
return;
|
|
765
925
|
}
|
|
766
926
|
const task = await state.taskService.getTask(taskId);
|
|
767
|
-
if (!task) {
|
|
768
|
-
return;
|
|
769
|
-
}
|
|
770
|
-
if (!task.archived) {
|
|
927
|
+
if (!task || !task.archived) {
|
|
771
928
|
return;
|
|
772
929
|
}
|
|
773
930
|
state.selectedTask = task;
|
|
774
|
-
|
|
931
|
+
showConfirmationModal(state, {
|
|
775
932
|
id: "restore-task-dialog",
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
933
|
+
modalType: "restoreTask",
|
|
934
|
+
title: "Restore Task?",
|
|
935
|
+
titleColor: COLORS.success,
|
|
936
|
+
message: task.title.slice(0, 40),
|
|
937
|
+
warning: "Task will be restored to its original column.",
|
|
938
|
+
warningColor: COLORS.textMuted,
|
|
939
|
+
borderColor: COLORS.success,
|
|
940
|
+
confirmHint: "[y] Restore [n/Esc] Cancel"
|
|
941
|
+
}, async () => {
|
|
942
|
+
const result = await withErrorHandling(state, () => state.taskService.restoreTask(taskId), "Failed to restore task");
|
|
943
|
+
if (result) {
|
|
944
|
+
await onRestored();
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
// src/components/modals/search-archive.ts
|
|
949
|
+
import {
|
|
950
|
+
BoxRenderable as BoxRenderable9,
|
|
951
|
+
InputRenderable as InputRenderable3,
|
|
952
|
+
InputRenderableEvents as InputRenderableEvents3,
|
|
953
|
+
TextRenderable as TextRenderable8
|
|
954
|
+
} from "@opentui/core";
|
|
955
|
+
function showSearchArchiveModal(state, onResults) {
|
|
956
|
+
const { renderer } = state;
|
|
957
|
+
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
958
|
+
id: "search-archive-dialog",
|
|
959
|
+
width: MODAL_WIDTHS.medium,
|
|
960
|
+
height: 9
|
|
779
961
|
});
|
|
780
962
|
const titleRow = new BoxRenderable9(renderer, {
|
|
781
|
-
id: "
|
|
963
|
+
id: "search-title-row",
|
|
782
964
|
width: "100%",
|
|
783
|
-
height: 1
|
|
784
|
-
justifyContent: "center"
|
|
965
|
+
height: 1
|
|
785
966
|
});
|
|
786
967
|
const title = new TextRenderable8(renderer, {
|
|
787
|
-
id: "
|
|
788
|
-
content: "
|
|
789
|
-
fg: COLORS.
|
|
968
|
+
id: "search-title",
|
|
969
|
+
content: " Search Archive ",
|
|
970
|
+
fg: COLORS.accent
|
|
790
971
|
});
|
|
791
972
|
titleRow.add(title);
|
|
792
|
-
const spacer1 = new BoxRenderable9(renderer, { id: "
|
|
793
|
-
const
|
|
794
|
-
id: "
|
|
973
|
+
const spacer1 = new BoxRenderable9(renderer, { id: "search-spacer1", width: "100%", height: 1 });
|
|
974
|
+
const labelRow = new BoxRenderable9(renderer, {
|
|
975
|
+
id: "search-label-row",
|
|
795
976
|
width: "100%",
|
|
796
977
|
height: 1
|
|
797
978
|
});
|
|
798
|
-
const
|
|
799
|
-
id: "
|
|
800
|
-
content:
|
|
979
|
+
const label = new TextRenderable8(renderer, {
|
|
980
|
+
id: "search-label",
|
|
981
|
+
content: "Search query:",
|
|
801
982
|
fg: COLORS.text
|
|
802
983
|
});
|
|
803
|
-
|
|
804
|
-
const
|
|
805
|
-
id: "
|
|
806
|
-
width:
|
|
807
|
-
height: 1
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
984
|
+
labelRow.add(label);
|
|
985
|
+
const input = new InputRenderable3(renderer, {
|
|
986
|
+
id: "search-input",
|
|
987
|
+
width: 46,
|
|
988
|
+
height: 1,
|
|
989
|
+
placeholder: "Enter search query...",
|
|
990
|
+
textColor: COLORS.text,
|
|
991
|
+
placeholderColor: COLORS.textDim,
|
|
992
|
+
backgroundColor: COLORS.inputBg,
|
|
993
|
+
focusedBackgroundColor: COLORS.inputBg,
|
|
994
|
+
cursorColor: COLORS.cursor
|
|
813
995
|
});
|
|
814
|
-
|
|
815
|
-
const spacer2 = new BoxRenderable9(renderer, { id: "restore-spacer2", width: "100%", height: 2 });
|
|
996
|
+
const spacer2 = new BoxRenderable9(renderer, { id: "search-spacer2", width: "100%", height: 2 });
|
|
816
997
|
const hintRow = new BoxRenderable9(renderer, {
|
|
817
|
-
id: "
|
|
998
|
+
id: "search-hint-row",
|
|
818
999
|
width: "100%",
|
|
819
|
-
height: 1
|
|
820
|
-
justifyContent: "center"
|
|
1000
|
+
height: 1
|
|
821
1001
|
});
|
|
822
1002
|
const hint = new TextRenderable8(renderer, {
|
|
823
|
-
id: "
|
|
824
|
-
content: "[
|
|
1003
|
+
id: "search-hint",
|
|
1004
|
+
content: "[Enter] Search [Esc] Cancel",
|
|
825
1005
|
fg: COLORS.textMuted
|
|
826
1006
|
});
|
|
827
1007
|
hintRow.add(hint);
|
|
828
1008
|
dialog.add(titleRow);
|
|
829
1009
|
dialog.add(spacer1);
|
|
830
|
-
dialog.add(
|
|
831
|
-
dialog.add(
|
|
1010
|
+
dialog.add(labelRow);
|
|
1011
|
+
dialog.add(input);
|
|
832
1012
|
dialog.add(spacer2);
|
|
833
1013
|
dialog.add(hintRow);
|
|
834
1014
|
renderer.root.add(overlay);
|
|
835
1015
|
state.modalOverlay = overlay;
|
|
836
|
-
state.activeModal = "
|
|
837
|
-
state.
|
|
838
|
-
|
|
1016
|
+
state.activeModal = "searchArchive";
|
|
1017
|
+
state.taskInput = input;
|
|
1018
|
+
setImmediate(() => input.focus());
|
|
1019
|
+
input.on(InputRenderableEvents3.ENTER, async () => {
|
|
1020
|
+
const query = input.value.trim();
|
|
1021
|
+
if (!query)
|
|
1022
|
+
return;
|
|
1023
|
+
const result = await withErrorHandling(state, () => state.taskService.searchArchive(query, { limit: 50 }), "Failed to search archive");
|
|
839
1024
|
closeModal(state);
|
|
840
|
-
|
|
841
|
-
|
|
1025
|
+
if (result) {
|
|
1026
|
+
await onResults(result.tasks);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
842
1029
|
}
|
|
843
1030
|
// src/components/modals/edit-task.ts
|
|
844
1031
|
import {
|
|
845
1032
|
BoxRenderable as BoxRenderable10,
|
|
846
|
-
InputRenderable as
|
|
847
|
-
InputRenderableEvents as
|
|
1033
|
+
InputRenderable as InputRenderable4,
|
|
1034
|
+
InputRenderableEvents as InputRenderableEvents4,
|
|
848
1035
|
TextRenderable as TextRenderable9
|
|
849
1036
|
} from "@opentui/core";
|
|
850
|
-
var
|
|
1037
|
+
var DIALOG_WIDTH2 = MODAL_WIDTHS.large;
|
|
851
1038
|
var DESC_INPUT_HEIGHT = 6;
|
|
852
1039
|
async function showEditTaskModal(state, callbacks) {
|
|
853
1040
|
const { renderer } = state;
|
|
@@ -863,7 +1050,7 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
863
1050
|
const dialogHeight = 18;
|
|
864
1051
|
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
865
1052
|
id: "edit-task-dialog",
|
|
866
|
-
width:
|
|
1053
|
+
width: DIALOG_WIDTH2,
|
|
867
1054
|
height: dialogHeight
|
|
868
1055
|
});
|
|
869
1056
|
const headerRow = new BoxRenderable10(renderer, {
|
|
@@ -873,7 +1060,7 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
873
1060
|
});
|
|
874
1061
|
const headerText = new TextRenderable9(renderer, {
|
|
875
1062
|
id: "edit-header-text",
|
|
876
|
-
content: `Edit Task: ${truncate(task.title,
|
|
1063
|
+
content: `Edit Task: ${truncate(task.title, DIALOG_WIDTH2 - 20)}`,
|
|
877
1064
|
fg: COLORS.accent
|
|
878
1065
|
});
|
|
879
1066
|
headerRow.add(headerText);
|
|
@@ -893,9 +1080,9 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
893
1080
|
fg: COLORS.textMuted
|
|
894
1081
|
});
|
|
895
1082
|
titleLabelRow.add(titleLabel);
|
|
896
|
-
const titleInput = new
|
|
1083
|
+
const titleInput = new InputRenderable4(renderer, {
|
|
897
1084
|
id: "edit-title-input",
|
|
898
|
-
width:
|
|
1085
|
+
width: DIALOG_WIDTH2 - 6,
|
|
899
1086
|
height: 1,
|
|
900
1087
|
placeholder: "Task title...",
|
|
901
1088
|
textColor: COLORS.text,
|
|
@@ -921,9 +1108,9 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
921
1108
|
fg: COLORS.textMuted
|
|
922
1109
|
});
|
|
923
1110
|
descLabelRow.add(descLabel);
|
|
924
|
-
const descInput = new
|
|
1111
|
+
const descInput = new InputRenderable4(renderer, {
|
|
925
1112
|
id: "edit-desc-input",
|
|
926
|
-
width:
|
|
1113
|
+
width: DIALOG_WIDTH2 - 6,
|
|
927
1114
|
height: DESC_INPUT_HEIGHT,
|
|
928
1115
|
placeholder: "Task description (optional)...",
|
|
929
1116
|
textColor: COLORS.text,
|
|
@@ -946,10 +1133,13 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
946
1133
|
}
|
|
947
1134
|
const hasChanges = newTitle !== task.title || newDescription !== (task.description ?? "");
|
|
948
1135
|
if (hasChanges) {
|
|
949
|
-
await state.taskService.updateTask(task.id, {
|
|
1136
|
+
const result = await withErrorHandling(state, () => state.taskService.updateTask(task.id, {
|
|
950
1137
|
title: newTitle,
|
|
951
1138
|
description: newDescription || undefined
|
|
952
|
-
});
|
|
1139
|
+
}), "Failed to save task");
|
|
1140
|
+
if (!result) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
953
1143
|
}
|
|
954
1144
|
closeModal(state);
|
|
955
1145
|
state.editTaskState = null;
|
|
@@ -995,7 +1185,7 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
995
1185
|
state.taskInput = titleInput;
|
|
996
1186
|
state.buttonRow = buttonRow;
|
|
997
1187
|
state.activeModal = "editTask";
|
|
998
|
-
titleInput.on(
|
|
1188
|
+
titleInput.on(InputRenderableEvents4.ENTER, doSave);
|
|
999
1189
|
state.editTaskRuntime = {
|
|
1000
1190
|
titleInput,
|
|
1001
1191
|
descInput,
|
|
@@ -1028,6 +1218,30 @@ function focusNextEditField(state) {
|
|
|
1028
1218
|
break;
|
|
1029
1219
|
}
|
|
1030
1220
|
}
|
|
1221
|
+
function focusPrevEditField(state) {
|
|
1222
|
+
if (!state.editTaskRuntime || !state.editTaskState)
|
|
1223
|
+
return;
|
|
1224
|
+
const { titleInput, descInput } = state.editTaskRuntime;
|
|
1225
|
+
const { buttonRow } = state;
|
|
1226
|
+
switch (state.editTaskState.focusedField) {
|
|
1227
|
+
case "title":
|
|
1228
|
+
titleInput.blur();
|
|
1229
|
+
buttonRow?.setFocused(true);
|
|
1230
|
+
state.editTaskState.focusedField = "buttons";
|
|
1231
|
+
break;
|
|
1232
|
+
case "description":
|
|
1233
|
+
descInput.blur();
|
|
1234
|
+
titleInput.focus();
|
|
1235
|
+
buttonRow?.setFocused(false);
|
|
1236
|
+
state.editTaskState.focusedField = "title";
|
|
1237
|
+
break;
|
|
1238
|
+
case "buttons":
|
|
1239
|
+
buttonRow?.setFocused(false);
|
|
1240
|
+
descInput.focus();
|
|
1241
|
+
state.editTaskState.focusedField = "description";
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1031
1245
|
function cancelEditTask(state) {
|
|
1032
1246
|
if (!state.editTaskRuntime)
|
|
1033
1247
|
return;
|
|
@@ -1043,8 +1257,12 @@ var SHORTCUTS = [
|
|
|
1043
1257
|
["m", "Move task (change status)"],
|
|
1044
1258
|
["u", "Assign user to task"],
|
|
1045
1259
|
["d", "Delete task"],
|
|
1260
|
+
["C", "Complete task (move to done)"],
|
|
1261
|
+
["H", "View task history"],
|
|
1046
1262
|
["x", "Archive task"],
|
|
1047
1263
|
["r", "Restore task (archive view)"],
|
|
1264
|
+
["/", "Search archive (archive view)"],
|
|
1265
|
+
["P", "Purge archive (archive view)"],
|
|
1048
1266
|
["Tab", "Toggle archive view"],
|
|
1049
1267
|
["?", "Show/hide help"],
|
|
1050
1268
|
["q", "Quit"]
|
|
@@ -1053,8 +1271,8 @@ function showHelpModal(state) {
|
|
|
1053
1271
|
const { renderer } = state;
|
|
1054
1272
|
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
1055
1273
|
id: "help-dialog",
|
|
1056
|
-
width:
|
|
1057
|
-
height:
|
|
1274
|
+
width: MODAL_WIDTHS.confirmation,
|
|
1275
|
+
height: MODAL_HEIGHTS.large,
|
|
1058
1276
|
padding: 2
|
|
1059
1277
|
});
|
|
1060
1278
|
const titleRow = new BoxRenderable11(renderer, {
|
|
@@ -1160,7 +1378,7 @@ async function showMoveTaskModal(state, onMoved) {
|
|
|
1160
1378
|
});
|
|
1161
1379
|
const taskText = new TextRenderable11(renderer, {
|
|
1162
1380
|
id: "move-task-text",
|
|
1163
|
-
content: task.title.slice(0,
|
|
1381
|
+
content: task.title.slice(0, TRUNCATION.taskTitleShort),
|
|
1164
1382
|
fg: COLORS.textMuted
|
|
1165
1383
|
});
|
|
1166
1384
|
taskRow.add(taskText);
|
|
@@ -1216,20 +1434,54 @@ async function showMoveTaskModal(state, onMoved) {
|
|
|
1216
1434
|
state.activeModal = "moveTask";
|
|
1217
1435
|
columnSelect.on(SelectRenderableEvents.ITEM_SELECTED, async () => {
|
|
1218
1436
|
const selected = columnSelect.getSelectedOption();
|
|
1219
|
-
if (selected?.value) {
|
|
1220
|
-
|
|
1437
|
+
if (selected?.value && typeof selected.value === "string") {
|
|
1438
|
+
const columnId = selected.value;
|
|
1439
|
+
const result = await withErrorHandling(state, () => state.taskService.moveTask(taskId, columnId), "Failed to move task");
|
|
1440
|
+
closeModal(state);
|
|
1441
|
+
if (result) {
|
|
1442
|
+
await onMoved();
|
|
1443
|
+
}
|
|
1444
|
+
} else {
|
|
1445
|
+
closeModal(state);
|
|
1221
1446
|
}
|
|
1222
|
-
closeModal(state);
|
|
1223
|
-
await onMoved();
|
|
1224
1447
|
});
|
|
1225
1448
|
}
|
|
1226
1449
|
// src/components/modals/onboarding.ts
|
|
1227
1450
|
import {
|
|
1228
1451
|
BoxRenderable as BoxRenderable13,
|
|
1229
|
-
InputRenderable as
|
|
1230
|
-
InputRenderableEvents as
|
|
1452
|
+
InputRenderable as InputRenderable5,
|
|
1453
|
+
InputRenderableEvents as InputRenderableEvents5,
|
|
1231
1454
|
TextRenderable as TextRenderable12
|
|
1232
1455
|
} from "@opentui/core";
|
|
1456
|
+
|
|
1457
|
+
// src/lib/db-client.ts
|
|
1458
|
+
function getDbClient(db) {
|
|
1459
|
+
const internal = db;
|
|
1460
|
+
if (!internal.$client) {
|
|
1461
|
+
throw new Error("Unable to access database client");
|
|
1462
|
+
}
|
|
1463
|
+
const client = internal.$client;
|
|
1464
|
+
if (typeof client.query === "function" || typeof client.execute === "function") {
|
|
1465
|
+
return client;
|
|
1466
|
+
}
|
|
1467
|
+
throw new Error("Unknown database client type");
|
|
1468
|
+
}
|
|
1469
|
+
function isBunSqlite(client) {
|
|
1470
|
+
return typeof client.query === "function";
|
|
1471
|
+
}
|
|
1472
|
+
async function getDataVersion(client) {
|
|
1473
|
+
if (isBunSqlite(client)) {
|
|
1474
|
+
const row = client.query("PRAGMA data_version").get();
|
|
1475
|
+
return row?.data_version ?? 0;
|
|
1476
|
+
}
|
|
1477
|
+
const result = await client.execute("PRAGMA data_version");
|
|
1478
|
+
return result.rows[0]?.[0];
|
|
1479
|
+
}
|
|
1480
|
+
function getKeyInput(renderer) {
|
|
1481
|
+
return renderer.keyInput;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// src/components/modals/onboarding.ts
|
|
1233
1485
|
async function showOnboarding(renderer) {
|
|
1234
1486
|
return new Promise((resolvePromise) => {
|
|
1235
1487
|
const container = new BoxRenderable13(renderer, {
|
|
@@ -1289,7 +1541,7 @@ async function showOnboarding(renderer) {
|
|
|
1289
1541
|
fg: COLORS.text
|
|
1290
1542
|
});
|
|
1291
1543
|
labelRow.add(label);
|
|
1292
|
-
const input = new
|
|
1544
|
+
const input = new InputRenderable5(renderer, {
|
|
1293
1545
|
id: "board-name-input",
|
|
1294
1546
|
width: 44,
|
|
1295
1547
|
height: 1,
|
|
@@ -1301,7 +1553,7 @@ async function showOnboarding(renderer) {
|
|
|
1301
1553
|
cursorColor: COLORS.cursor
|
|
1302
1554
|
});
|
|
1303
1555
|
const spacer2 = new BoxRenderable13(renderer, { id: "spacer2", width: "100%", height: 1 });
|
|
1304
|
-
const keyEmitter = renderer
|
|
1556
|
+
const keyEmitter = getKeyInput(renderer);
|
|
1305
1557
|
const doCreate = () => {
|
|
1306
1558
|
keyEmitter.off("keypress", keyHandler);
|
|
1307
1559
|
const boardName = input.value.trim() || "Kaban Board";
|
|
@@ -1327,7 +1579,7 @@ async function showOnboarding(renderer) {
|
|
|
1327
1579
|
container.add(card);
|
|
1328
1580
|
renderer.root.add(container);
|
|
1329
1581
|
input.focus();
|
|
1330
|
-
input.on(
|
|
1582
|
+
input.on(InputRenderableEvents5.ENTER, doCreate);
|
|
1331
1583
|
const keyBindings = {
|
|
1332
1584
|
tab: () => {
|
|
1333
1585
|
input.blur();
|
|
@@ -1356,50 +1608,22 @@ async function showOnboarding(renderer) {
|
|
|
1356
1608
|
});
|
|
1357
1609
|
}
|
|
1358
1610
|
// src/components/modals/quit.ts
|
|
1359
|
-
import { BoxRenderable as BoxRenderable14, TextRenderable as TextRenderable13 } from "@opentui/core";
|
|
1360
1611
|
function showQuitModal(state) {
|
|
1361
|
-
|
|
1362
|
-
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
1612
|
+
showConfirmationModal(state, {
|
|
1363
1613
|
id: "quit-dialog",
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
});
|
|
1374
|
-
const title = new TextRenderable13(renderer, {
|
|
1375
|
-
id: "quit-title",
|
|
1376
|
-
content: "Quit Kaban?",
|
|
1377
|
-
fg: COLORS.danger
|
|
1378
|
-
});
|
|
1379
|
-
titleRow.add(title);
|
|
1380
|
-
const spacer = new BoxRenderable14(renderer, { id: "quit-spacer", width: "100%", height: 2 });
|
|
1381
|
-
const hintRow = new BoxRenderable14(renderer, {
|
|
1382
|
-
id: "quit-hint-row",
|
|
1383
|
-
width: "100%",
|
|
1384
|
-
height: 1,
|
|
1385
|
-
justifyContent: "center"
|
|
1386
|
-
});
|
|
1387
|
-
const hint = new TextRenderable13(renderer, {
|
|
1388
|
-
id: "quit-hint",
|
|
1389
|
-
content: "[y] Yes [n/Esc] No",
|
|
1390
|
-
fg: COLORS.textMuted
|
|
1391
|
-
});
|
|
1392
|
-
hintRow.add(hint);
|
|
1393
|
-
dialog.add(titleRow);
|
|
1394
|
-
dialog.add(spacer);
|
|
1395
|
-
dialog.add(hintRow);
|
|
1396
|
-
renderer.root.add(overlay);
|
|
1397
|
-
state.modalOverlay = overlay;
|
|
1398
|
-
state.activeModal = "quit";
|
|
1614
|
+
modalType: "quit",
|
|
1615
|
+
title: "Quit Kaban?",
|
|
1616
|
+
titleColor: COLORS.danger,
|
|
1617
|
+
message: "",
|
|
1618
|
+
borderColor: COLORS.danger,
|
|
1619
|
+
width: MODAL_WIDTHS.small,
|
|
1620
|
+
height: MODAL_HEIGHTS.small,
|
|
1621
|
+
confirmHint: "[y] Yes [n/Esc] No"
|
|
1622
|
+
}, async () => {});
|
|
1399
1623
|
}
|
|
1400
1624
|
// src/components/modals/view-task.ts
|
|
1401
|
-
import { BoxRenderable as
|
|
1402
|
-
var
|
|
1625
|
+
import { BoxRenderable as BoxRenderable14, TextRenderable as TextRenderable13 } from "@opentui/core";
|
|
1626
|
+
var DIALOG_WIDTH3 = MODAL_WIDTHS.large;
|
|
1403
1627
|
var DESC_VISIBLE_LINES = 4;
|
|
1404
1628
|
var LABEL_WIDTH = 12;
|
|
1405
1629
|
function formatDate(date) {
|
|
@@ -1441,51 +1665,51 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1441
1665
|
const dialogHeight = 24;
|
|
1442
1666
|
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
1443
1667
|
id: "view-task-dialog",
|
|
1444
|
-
width:
|
|
1668
|
+
width: DIALOG_WIDTH3,
|
|
1445
1669
|
height: dialogHeight
|
|
1446
1670
|
});
|
|
1447
1671
|
const headerDivider = createSectionDivider(renderer, {
|
|
1448
1672
|
label: "Task Details",
|
|
1449
|
-
width:
|
|
1673
|
+
width: DIALOG_WIDTH3 - 4,
|
|
1450
1674
|
id: "view-header"
|
|
1451
1675
|
});
|
|
1452
|
-
const spacerHeader = new
|
|
1676
|
+
const spacerHeader = new BoxRenderable14(renderer, {
|
|
1453
1677
|
id: "view-spacer-header",
|
|
1454
1678
|
width: "100%",
|
|
1455
1679
|
height: 1
|
|
1456
1680
|
});
|
|
1457
|
-
const titleRow = new
|
|
1681
|
+
const titleRow = new BoxRenderable14(renderer, {
|
|
1458
1682
|
id: "view-title-row",
|
|
1459
1683
|
width: "100%",
|
|
1460
1684
|
height: 1,
|
|
1461
1685
|
flexDirection: "row",
|
|
1462
1686
|
justifyContent: "space-between"
|
|
1463
1687
|
});
|
|
1464
|
-
const taskTitle = new
|
|
1688
|
+
const taskTitle = new TextRenderable13(renderer, {
|
|
1465
1689
|
id: "view-task-title",
|
|
1466
|
-
content: truncate(task.title,
|
|
1690
|
+
content: truncate(task.title, DIALOG_WIDTH3 - 14),
|
|
1467
1691
|
fg: COLORS.text
|
|
1468
1692
|
});
|
|
1469
|
-
const editHint = new
|
|
1693
|
+
const editHint = new TextRenderable13(renderer, {
|
|
1470
1694
|
id: "view-edit-hint",
|
|
1471
1695
|
content: "[e]dit",
|
|
1472
1696
|
fg: COLORS.textDim
|
|
1473
1697
|
});
|
|
1474
1698
|
titleRow.add(taskTitle);
|
|
1475
1699
|
titleRow.add(editHint);
|
|
1476
|
-
const idRow = new
|
|
1700
|
+
const idRow = new BoxRenderable14(renderer, {
|
|
1477
1701
|
id: "view-id-row",
|
|
1478
1702
|
width: "100%",
|
|
1479
1703
|
height: 1,
|
|
1480
1704
|
flexDirection: "row",
|
|
1481
1705
|
justifyContent: "space-between"
|
|
1482
1706
|
});
|
|
1483
|
-
const idValue = new
|
|
1707
|
+
const idValue = new TextRenderable13(renderer, {
|
|
1484
1708
|
id: "view-id-value",
|
|
1485
|
-
content: truncateMiddle(task.id,
|
|
1709
|
+
content: truncateMiddle(task.id, DIALOG_WIDTH3 - 14),
|
|
1486
1710
|
fg: COLORS.textDim
|
|
1487
1711
|
});
|
|
1488
|
-
const copyHint = new
|
|
1712
|
+
const copyHint = new TextRenderable13(renderer, {
|
|
1489
1713
|
id: "view-copy-hint",
|
|
1490
1714
|
content: "[c]opy",
|
|
1491
1715
|
fg: COLORS.textDim
|
|
@@ -1494,26 +1718,26 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1494
1718
|
idRow.add(copyHint);
|
|
1495
1719
|
const statusDivider = createSectionDivider(renderer, {
|
|
1496
1720
|
label: "Status",
|
|
1497
|
-
width:
|
|
1721
|
+
width: DIALOG_WIDTH3 - 4,
|
|
1498
1722
|
id: "view-status"
|
|
1499
1723
|
});
|
|
1500
|
-
const columnRow = new
|
|
1724
|
+
const columnRow = new BoxRenderable14(renderer, {
|
|
1501
1725
|
id: "view-column-row",
|
|
1502
1726
|
width: "100%",
|
|
1503
1727
|
height: 1,
|
|
1504
1728
|
flexDirection: "row"
|
|
1505
1729
|
});
|
|
1506
|
-
const columnLabel = new
|
|
1730
|
+
const columnLabel = new TextRenderable13(renderer, {
|
|
1507
1731
|
id: "view-column-label",
|
|
1508
1732
|
content: padLabel("Column"),
|
|
1509
1733
|
fg: COLORS.textMuted
|
|
1510
1734
|
});
|
|
1511
|
-
const columnBullet = new
|
|
1735
|
+
const columnBullet = new TextRenderable13(renderer, {
|
|
1512
1736
|
id: "view-column-bullet",
|
|
1513
1737
|
content: "\u25CF ",
|
|
1514
1738
|
fg: statusColor
|
|
1515
1739
|
});
|
|
1516
|
-
const columnValue = new
|
|
1740
|
+
const columnValue = new TextRenderable13(renderer, {
|
|
1517
1741
|
id: "view-column-value",
|
|
1518
1742
|
content: columnName,
|
|
1519
1743
|
fg: COLORS.text
|
|
@@ -1521,54 +1745,54 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1521
1745
|
columnRow.add(columnLabel);
|
|
1522
1746
|
columnRow.add(columnBullet);
|
|
1523
1747
|
columnRow.add(columnValue);
|
|
1524
|
-
const assigneeRow = new
|
|
1748
|
+
const assigneeRow = new BoxRenderable14(renderer, {
|
|
1525
1749
|
id: "view-assignee-row",
|
|
1526
1750
|
width: "100%",
|
|
1527
1751
|
height: 1,
|
|
1528
1752
|
flexDirection: "row"
|
|
1529
1753
|
});
|
|
1530
|
-
const assigneeLabel = new
|
|
1754
|
+
const assigneeLabel = new TextRenderable13(renderer, {
|
|
1531
1755
|
id: "view-assignee-label",
|
|
1532
1756
|
content: padLabel("Assignee"),
|
|
1533
1757
|
fg: COLORS.textMuted
|
|
1534
1758
|
});
|
|
1535
|
-
const assigneeValue = new
|
|
1759
|
+
const assigneeValue = new TextRenderable13(renderer, {
|
|
1536
1760
|
id: "view-assignee-value",
|
|
1537
1761
|
content: task.assignedTo ?? "\u2014 unassigned",
|
|
1538
1762
|
fg: task.assignedTo ? COLORS.success : COLORS.textDim
|
|
1539
1763
|
});
|
|
1540
1764
|
assigneeRow.add(assigneeLabel);
|
|
1541
1765
|
assigneeRow.add(assigneeValue);
|
|
1542
|
-
const creatorRow = new
|
|
1766
|
+
const creatorRow = new BoxRenderable14(renderer, {
|
|
1543
1767
|
id: "view-creator-row",
|
|
1544
1768
|
width: "100%",
|
|
1545
1769
|
height: 1,
|
|
1546
1770
|
flexDirection: "row"
|
|
1547
1771
|
});
|
|
1548
|
-
const creatorLabel = new
|
|
1772
|
+
const creatorLabel = new TextRenderable13(renderer, {
|
|
1549
1773
|
id: "view-creator-label",
|
|
1550
1774
|
content: padLabel("Creator"),
|
|
1551
1775
|
fg: COLORS.textMuted
|
|
1552
1776
|
});
|
|
1553
|
-
const creatorValue = new
|
|
1777
|
+
const creatorValue = new TextRenderable13(renderer, {
|
|
1554
1778
|
id: "view-creator-value",
|
|
1555
1779
|
content: task.createdBy,
|
|
1556
1780
|
fg: COLORS.text
|
|
1557
1781
|
});
|
|
1558
1782
|
creatorRow.add(creatorLabel);
|
|
1559
1783
|
creatorRow.add(creatorValue);
|
|
1560
|
-
const labelsRow = new
|
|
1784
|
+
const labelsRow = new BoxRenderable14(renderer, {
|
|
1561
1785
|
id: "view-labels-row",
|
|
1562
1786
|
width: "100%",
|
|
1563
1787
|
height: 1,
|
|
1564
1788
|
flexDirection: "row"
|
|
1565
1789
|
});
|
|
1566
|
-
const labelsLabel = new
|
|
1790
|
+
const labelsLabel = new TextRenderable13(renderer, {
|
|
1567
1791
|
id: "view-labels-label",
|
|
1568
1792
|
content: padLabel("Labels"),
|
|
1569
1793
|
fg: COLORS.textMuted
|
|
1570
1794
|
});
|
|
1571
|
-
const labelsValue = new
|
|
1795
|
+
const labelsValue = new TextRenderable13(renderer, {
|
|
1572
1796
|
id: "view-labels-value",
|
|
1573
1797
|
content: "\u2014 none",
|
|
1574
1798
|
fg: COLORS.textDim
|
|
@@ -1577,52 +1801,52 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1577
1801
|
labelsRow.add(labelsValue);
|
|
1578
1802
|
const timelineDivider = createSectionDivider(renderer, {
|
|
1579
1803
|
label: "Timeline",
|
|
1580
|
-
width:
|
|
1804
|
+
width: DIALOG_WIDTH3 - 4,
|
|
1581
1805
|
id: "view-timeline"
|
|
1582
1806
|
});
|
|
1583
|
-
const createdRow = new
|
|
1807
|
+
const createdRow = new BoxRenderable14(renderer, {
|
|
1584
1808
|
id: "view-created-row",
|
|
1585
1809
|
width: "100%",
|
|
1586
1810
|
height: 1,
|
|
1587
1811
|
flexDirection: "row"
|
|
1588
1812
|
});
|
|
1589
|
-
const createdLabel = new
|
|
1813
|
+
const createdLabel = new TextRenderable13(renderer, {
|
|
1590
1814
|
id: "view-created-label",
|
|
1591
1815
|
content: padLabel("Created"),
|
|
1592
1816
|
fg: COLORS.textMuted
|
|
1593
1817
|
});
|
|
1594
|
-
const createdValue = new
|
|
1818
|
+
const createdValue = new TextRenderable13(renderer, {
|
|
1595
1819
|
id: "view-created-value",
|
|
1596
1820
|
content: formatDate(task.createdAt),
|
|
1597
1821
|
fg: COLORS.textDim
|
|
1598
1822
|
});
|
|
1599
1823
|
createdRow.add(createdLabel);
|
|
1600
1824
|
createdRow.add(createdValue);
|
|
1601
|
-
const updatedRow = new
|
|
1825
|
+
const updatedRow = new BoxRenderable14(renderer, {
|
|
1602
1826
|
id: "view-updated-row",
|
|
1603
1827
|
width: "100%",
|
|
1604
1828
|
height: 1,
|
|
1605
1829
|
flexDirection: "row",
|
|
1606
1830
|
justifyContent: "space-between"
|
|
1607
1831
|
});
|
|
1608
|
-
const updatedLeft = new
|
|
1832
|
+
const updatedLeft = new BoxRenderable14(renderer, {
|
|
1609
1833
|
id: "view-updated-left",
|
|
1610
1834
|
height: 1,
|
|
1611
1835
|
flexDirection: "row"
|
|
1612
1836
|
});
|
|
1613
|
-
const updatedLabel = new
|
|
1837
|
+
const updatedLabel = new TextRenderable13(renderer, {
|
|
1614
1838
|
id: "view-updated-label",
|
|
1615
1839
|
content: padLabel("Updated"),
|
|
1616
1840
|
fg: COLORS.textMuted
|
|
1617
1841
|
});
|
|
1618
|
-
const updatedValue = new
|
|
1842
|
+
const updatedValue = new TextRenderable13(renderer, {
|
|
1619
1843
|
id: "view-updated-value",
|
|
1620
1844
|
content: formatDate(task.updatedAt),
|
|
1621
1845
|
fg: COLORS.textDim
|
|
1622
1846
|
});
|
|
1623
1847
|
updatedLeft.add(updatedLabel);
|
|
1624
1848
|
updatedLeft.add(updatedValue);
|
|
1625
|
-
const relativeTime = new
|
|
1849
|
+
const relativeTime = new TextRenderable13(renderer, {
|
|
1626
1850
|
id: "view-relative-time",
|
|
1627
1851
|
content: `(${formatRelativeTime(task.updatedAt)})`,
|
|
1628
1852
|
fg: COLORS.textDim
|
|
@@ -1631,10 +1855,10 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1631
1855
|
updatedRow.add(relativeTime);
|
|
1632
1856
|
const descDivider = createSectionDivider(renderer, {
|
|
1633
1857
|
label: "Description",
|
|
1634
|
-
width:
|
|
1858
|
+
width: DIALOG_WIDTH3 - 4,
|
|
1635
1859
|
id: "view-desc"
|
|
1636
1860
|
});
|
|
1637
|
-
const descContainer = new
|
|
1861
|
+
const descContainer = new BoxRenderable14(renderer, {
|
|
1638
1862
|
id: "view-desc-container",
|
|
1639
1863
|
width: "100%",
|
|
1640
1864
|
height: DESC_VISIBLE_LINES,
|
|
@@ -1642,7 +1866,7 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1642
1866
|
});
|
|
1643
1867
|
const descLineRenderables = [];
|
|
1644
1868
|
for (let i = 0;i < DESC_VISIBLE_LINES; i++) {
|
|
1645
|
-
const line = new
|
|
1869
|
+
const line = new TextRenderable13(renderer, {
|
|
1646
1870
|
id: `view-desc-line-${i}`,
|
|
1647
1871
|
content: " ",
|
|
1648
1872
|
fg: COLORS.text
|
|
@@ -1665,64 +1889,64 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1665
1889
|
for (let i = 0;i < DESC_VISIBLE_LINES; i++) {
|
|
1666
1890
|
const lineContent = visibleLines[i] ?? "";
|
|
1667
1891
|
const isLastLine = i === DESC_VISIBLE_LINES - 1;
|
|
1668
|
-
let displayContent = truncate(lineContent,
|
|
1892
|
+
let displayContent = truncate(lineContent, DIALOG_WIDTH3 - 12);
|
|
1669
1893
|
if (isLastLine && hasMore) {
|
|
1670
1894
|
const remaining = totalDescLines - scrollOffset - DESC_VISIBLE_LINES;
|
|
1671
|
-
displayContent = `${truncate(lineContent,
|
|
1895
|
+
displayContent = `${truncate(lineContent, DIALOG_WIDTH3 - 18)} \u25BC ${remaining}+`;
|
|
1672
1896
|
}
|
|
1673
1897
|
if (i === 0 && hasLess) {
|
|
1674
|
-
displayContent = `\u25B2 ${scrollOffset}+ ${truncate(lineContent,
|
|
1898
|
+
displayContent = `\u25B2 ${scrollOffset}+ ${truncate(lineContent, DIALOG_WIDTH3 - 18)}`;
|
|
1675
1899
|
}
|
|
1676
1900
|
descLineRenderables[i].content = displayContent || " ";
|
|
1677
1901
|
descLineRenderables[i].fg = i === 0 && hasLess || isLastLine && hasMore ? COLORS.textDim : COLORS.text;
|
|
1678
1902
|
}
|
|
1679
1903
|
}
|
|
1680
1904
|
updateDescriptionContent(0);
|
|
1681
|
-
const footerDivider = new
|
|
1905
|
+
const footerDivider = new BoxRenderable14(renderer, {
|
|
1682
1906
|
id: "view-footer-divider",
|
|
1683
1907
|
width: "100%",
|
|
1684
1908
|
height: 1
|
|
1685
1909
|
});
|
|
1686
|
-
const footerLine = new
|
|
1910
|
+
const footerLine = new TextRenderable13(renderer, {
|
|
1687
1911
|
id: "view-footer-line",
|
|
1688
|
-
content: "\u2500".repeat(
|
|
1912
|
+
content: "\u2500".repeat(DIALOG_WIDTH3 - 4),
|
|
1689
1913
|
fg: COLORS.border
|
|
1690
1914
|
});
|
|
1691
1915
|
footerDivider.add(footerLine);
|
|
1692
|
-
const actionsRow = new
|
|
1916
|
+
const actionsRow = new BoxRenderable14(renderer, {
|
|
1693
1917
|
id: "view-actions-row",
|
|
1694
1918
|
width: "100%",
|
|
1695
1919
|
height: 1,
|
|
1696
1920
|
flexDirection: "row",
|
|
1697
1921
|
justifyContent: "space-between"
|
|
1698
1922
|
});
|
|
1699
|
-
const actionsLeft = new
|
|
1923
|
+
const actionsLeft = new BoxRenderable14(renderer, {
|
|
1700
1924
|
id: "view-actions-left",
|
|
1701
1925
|
height: 1,
|
|
1702
1926
|
flexDirection: "row",
|
|
1703
1927
|
gap: 2
|
|
1704
1928
|
});
|
|
1705
|
-
const moveAction = new
|
|
1929
|
+
const moveAction = new TextRenderable13(renderer, {
|
|
1706
1930
|
id: "view-action-move",
|
|
1707
1931
|
content: "[m] Move",
|
|
1708
1932
|
fg: COLORS.textMuted
|
|
1709
1933
|
});
|
|
1710
|
-
const assignAction = new
|
|
1934
|
+
const assignAction = new TextRenderable13(renderer, {
|
|
1711
1935
|
id: "view-action-assign",
|
|
1712
1936
|
content: "[u] Assign",
|
|
1713
1937
|
fg: COLORS.textMuted
|
|
1714
1938
|
});
|
|
1715
|
-
const editAction = new
|
|
1939
|
+
const editAction = new TextRenderable13(renderer, {
|
|
1716
1940
|
id: "view-action-edit",
|
|
1717
1941
|
content: "[e] Edit",
|
|
1718
1942
|
fg: COLORS.textMuted
|
|
1719
1943
|
});
|
|
1720
|
-
const deleteAction = new
|
|
1944
|
+
const deleteAction = new TextRenderable13(renderer, {
|
|
1721
1945
|
id: "view-action-delete",
|
|
1722
1946
|
content: "[d] Delete",
|
|
1723
1947
|
fg: COLORS.danger
|
|
1724
1948
|
});
|
|
1725
|
-
const archiveRestoreAction = new
|
|
1949
|
+
const archiveRestoreAction = new TextRenderable13(renderer, {
|
|
1726
1950
|
id: "view-action-archive-restore",
|
|
1727
1951
|
content: task.archived ? "[r] Restore" : "[x] Archive",
|
|
1728
1952
|
fg: task.archived ? COLORS.success : COLORS.warning
|
|
@@ -1732,7 +1956,7 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1732
1956
|
actionsLeft.add(editAction);
|
|
1733
1957
|
actionsLeft.add(archiveRestoreAction);
|
|
1734
1958
|
actionsLeft.add(deleteAction);
|
|
1735
|
-
const escAction = new
|
|
1959
|
+
const escAction = new TextRenderable13(renderer, {
|
|
1736
1960
|
id: "view-action-esc",
|
|
1737
1961
|
content: "[Esc]",
|
|
1738
1962
|
fg: COLORS.textDim
|
|
@@ -1819,7 +2043,7 @@ async function copyTaskId(state) {
|
|
|
1819
2043
|
copyHint.fg = COLORS.danger;
|
|
1820
2044
|
state.viewTaskRuntime.copyTimeoutId = setTimeout(() => {
|
|
1821
2045
|
if (state.viewTaskRuntime) {
|
|
1822
|
-
idValue.content = truncateMiddle(taskId,
|
|
2046
|
+
idValue.content = truncateMiddle(taskId, DIALOG_WIDTH3 - 14);
|
|
1823
2047
|
idValue.fg = COLORS.textDim;
|
|
1824
2048
|
copyHint.content = "[c]opy";
|
|
1825
2049
|
copyHint.fg = COLORS.textDim;
|
|
@@ -1997,9 +2221,43 @@ var modalBindings = {
|
|
|
1997
2221
|
return openArchiveModal(state);
|
|
1998
2222
|
}
|
|
1999
2223
|
},
|
|
2224
|
+
C: async (state) => {
|
|
2225
|
+
if (state.archiveViewMode)
|
|
2226
|
+
return;
|
|
2227
|
+
const taskId = getSelectedTaskId(state);
|
|
2228
|
+
if (!taskId)
|
|
2229
|
+
return;
|
|
2230
|
+
const terminal = await state.boardService.getTerminalColumn();
|
|
2231
|
+
if (!terminal)
|
|
2232
|
+
return;
|
|
2233
|
+
const result = await withErrorHandling(state, () => state.taskService.moveTask(taskId, terminal.id), "Failed to complete task");
|
|
2234
|
+
if (result) {
|
|
2235
|
+
await refreshBoard(state);
|
|
2236
|
+
}
|
|
2237
|
+
},
|
|
2238
|
+
"/": (state) => {
|
|
2239
|
+
if (state.archiveViewMode) {
|
|
2240
|
+
return showSearchArchiveModal(state, async (_tasks) => {
|
|
2241
|
+
await refreshBoard(state);
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
},
|
|
2245
|
+
P: (state) => {
|
|
2246
|
+
if (state.archiveViewMode) {
|
|
2247
|
+
return showPurgeArchiveModal(state, async () => {
|
|
2248
|
+
await refreshBoard(state);
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
},
|
|
2000
2252
|
tab: toggleArchiveView,
|
|
2001
2253
|
return: openViewModal,
|
|
2002
|
-
"?": showHelpModal
|
|
2254
|
+
"?": showHelpModal,
|
|
2255
|
+
H: (state) => {
|
|
2256
|
+
const taskId = getSelectedTaskId(state);
|
|
2257
|
+
if (taskId) {
|
|
2258
|
+
return showTaskHistoryModal(state);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2003
2261
|
},
|
|
2004
2262
|
addTask: {
|
|
2005
2263
|
escape: closeModal,
|
|
@@ -2077,6 +2335,7 @@ var modalBindings = {
|
|
|
2077
2335
|
editTask: {
|
|
2078
2336
|
escape: cancelEditTask,
|
|
2079
2337
|
tab: focusNextEditField,
|
|
2338
|
+
"shift+tab": focusPrevEditField,
|
|
2080
2339
|
left: buttonSelectPrev,
|
|
2081
2340
|
right: buttonSelectNext,
|
|
2082
2341
|
return: editTaskSave
|
|
@@ -2088,11 +2347,24 @@ var modalBindings = {
|
|
|
2088
2347
|
y: quit,
|
|
2089
2348
|
n: closeModal,
|
|
2090
2349
|
escape: closeModal
|
|
2350
|
+
},
|
|
2351
|
+
searchArchive: {
|
|
2352
|
+
escape: closeModal
|
|
2353
|
+
},
|
|
2354
|
+
purgeArchive: {
|
|
2355
|
+
y: confirmModal,
|
|
2356
|
+
n: closeModal,
|
|
2357
|
+
escape: closeModal
|
|
2358
|
+
},
|
|
2359
|
+
taskHistory: {
|
|
2360
|
+
escape: closeModal,
|
|
2361
|
+
[WILDCARD]: closeModal
|
|
2091
2362
|
}
|
|
2092
2363
|
};
|
|
2093
2364
|
function handleKeypress(state, key) {
|
|
2094
2365
|
const bindings = modalBindings[state.activeModal];
|
|
2095
|
-
const
|
|
2366
|
+
const shiftKey = key.shift ? `shift+${key.name}` : undefined;
|
|
2367
|
+
const handler = (shiftKey ? bindings[shiftKey] : undefined) ?? bindings[key.name] ?? bindings[WILDCARD];
|
|
2096
2368
|
return handler?.(state);
|
|
2097
2369
|
}
|
|
2098
2370
|
|
|
@@ -2169,9 +2441,11 @@ async function main() {
|
|
|
2169
2441
|
}
|
|
2170
2442
|
const state = {
|
|
2171
2443
|
renderer,
|
|
2444
|
+
db,
|
|
2172
2445
|
taskService,
|
|
2173
2446
|
boardService,
|
|
2174
2447
|
boardName: board.name,
|
|
2448
|
+
projectRoot,
|
|
2175
2449
|
columns: [],
|
|
2176
2450
|
columnPanels: [],
|
|
2177
2451
|
taskSelects: new Map,
|
|
@@ -2191,13 +2465,12 @@ async function main() {
|
|
|
2191
2465
|
};
|
|
2192
2466
|
await refreshBoard(state);
|
|
2193
2467
|
let lastDataVersion = null;
|
|
2194
|
-
const client = db
|
|
2468
|
+
const client = getDbClient(db);
|
|
2195
2469
|
const checkForChanges = async () => {
|
|
2196
2470
|
if (state.activeModal !== "none")
|
|
2197
2471
|
return;
|
|
2198
2472
|
try {
|
|
2199
|
-
const
|
|
2200
|
-
const currentVersion = result.rows[0]?.[0];
|
|
2473
|
+
const currentVersion = await getDataVersion(client);
|
|
2201
2474
|
if (lastDataVersion !== null && currentVersion !== lastDataVersion) {
|
|
2202
2475
|
await refreshBoard(state);
|
|
2203
2476
|
}
|
|
@@ -2209,8 +2482,7 @@ async function main() {
|
|
|
2209
2482
|
process.on("exit", cleanup);
|
|
2210
2483
|
process.on("SIGINT", cleanup);
|
|
2211
2484
|
process.on("SIGTERM", cleanup);
|
|
2212
|
-
|
|
2213
|
-
keyEmitter.on("keypress", (key) => {
|
|
2485
|
+
getKeyInput(renderer).on("keypress", (key) => {
|
|
2214
2486
|
handleKeypress(state, key);
|
|
2215
2487
|
});
|
|
2216
2488
|
}
|