@kaban-board/tui 0.3.1 → 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 -310
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -124,9 +124,10 @@ async function refreshBoard(state) {
|
|
|
124
124
|
alignItems: "center"
|
|
125
125
|
});
|
|
126
126
|
const modeIndicator = state.archiveViewMode ? " [ARCHIVE]" : "";
|
|
127
|
+
const projectPath = ` (${state.projectRoot})`;
|
|
127
128
|
const headerText = new TextRenderable2(renderer, {
|
|
128
129
|
id: "header-text",
|
|
129
|
-
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)}`
|
|
130
131
|
});
|
|
131
132
|
header.add(headerText);
|
|
132
133
|
mainContainer.add(header);
|
|
@@ -307,6 +308,44 @@ function createButtonRow(renderer, id, buttons) {
|
|
|
307
308
|
return state;
|
|
308
309
|
}
|
|
309
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
|
+
|
|
310
349
|
// src/components/overlay.ts
|
|
311
350
|
import { BoxRenderable as BoxRenderable4 } from "@opentui/core";
|
|
312
351
|
function createModalOverlay(renderer, options) {
|
|
@@ -385,8 +424,8 @@ function showAddTaskModal(state, onTaskCreated) {
|
|
|
385
424
|
return;
|
|
386
425
|
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
387
426
|
id: "add-task-dialog",
|
|
388
|
-
width:
|
|
389
|
-
height:
|
|
427
|
+
width: MODAL_WIDTHS.medium,
|
|
428
|
+
height: MODAL_HEIGHTS.medium
|
|
390
429
|
});
|
|
391
430
|
const titleRow = new BoxRenderable5(renderer, {
|
|
392
431
|
id: "title-row",
|
|
@@ -425,11 +464,15 @@ function showAddTaskModal(state, onTaskCreated) {
|
|
|
425
464
|
const spacer2 = new BoxRenderable5(renderer, { id: "dialog-spacer2", width: "100%", height: 1 });
|
|
426
465
|
const doCreate = async () => {
|
|
427
466
|
const taskTitle = input.value.trim();
|
|
428
|
-
if (taskTitle) {
|
|
429
|
-
|
|
467
|
+
if (!taskTitle) {
|
|
468
|
+
closeModal(state);
|
|
469
|
+
return;
|
|
430
470
|
}
|
|
471
|
+
const result = await withErrorHandling(state, () => state.taskService.addTask({ title: taskTitle, columnId: column.id }), "Failed to create task");
|
|
431
472
|
closeModal(state);
|
|
432
|
-
|
|
473
|
+
if (result) {
|
|
474
|
+
await onTaskCreated();
|
|
475
|
+
}
|
|
433
476
|
};
|
|
434
477
|
const doCancel = () => {
|
|
435
478
|
closeModal(state);
|
|
@@ -455,9 +498,6 @@ function showAddTaskModal(state, onTaskCreated) {
|
|
|
455
498
|
state.activeModal = "addTask";
|
|
456
499
|
input.on(InputRenderableEvents.ENTER, doCreate);
|
|
457
500
|
}
|
|
458
|
-
// src/components/modals/archive-task.ts
|
|
459
|
-
import { BoxRenderable as BoxRenderable6, TextRenderable as TextRenderable5 } from "@opentui/core";
|
|
460
|
-
|
|
461
501
|
// src/lib/types.ts
|
|
462
502
|
function getSelectedTaskId(state) {
|
|
463
503
|
const column = state.columns[state.currentColumnIndex];
|
|
@@ -471,9 +511,97 @@ function getSelectedTaskId(state) {
|
|
|
471
511
|
return typeof value === "string" ? value : null;
|
|
472
512
|
}
|
|
473
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
|
+
|
|
474
603
|
// src/components/modals/archive-task.ts
|
|
475
604
|
async function showArchiveTaskModal(state, onArchived) {
|
|
476
|
-
const { renderer } = state;
|
|
477
605
|
const taskId = getSelectedTaskId(state);
|
|
478
606
|
if (!taskId) {
|
|
479
607
|
return;
|
|
@@ -486,81 +614,160 @@ async function showArchiveTaskModal(state, onArchived) {
|
|
|
486
614
|
return;
|
|
487
615
|
}
|
|
488
616
|
state.selectedTask = task;
|
|
489
|
-
|
|
617
|
+
showConfirmationModal(state, {
|
|
490
618
|
id: "archive-task-dialog",
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
+
}
|
|
494
632
|
});
|
|
495
|
-
|
|
496
|
-
|
|
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",
|
|
497
687
|
width: "100%",
|
|
498
688
|
height: 1,
|
|
499
|
-
|
|
689
|
+
flexDirection: "row"
|
|
500
690
|
});
|
|
501
|
-
const
|
|
502
|
-
id: "
|
|
503
|
-
content: "
|
|
504
|
-
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
|
|
505
695
|
});
|
|
506
|
-
titleRow.add(
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
id: "archive-task-row",
|
|
696
|
+
titleRow.add(taskTitle);
|
|
697
|
+
const spacer = new BoxRenderable7(renderer, {
|
|
698
|
+
id: "history-spacer",
|
|
510
699
|
width: "100%",
|
|
511
700
|
height: 1
|
|
512
701
|
});
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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",
|
|
521
744
|
width: "100%",
|
|
522
745
|
height: 1
|
|
523
746
|
});
|
|
524
|
-
const
|
|
525
|
-
id: "
|
|
526
|
-
content: "Task will be moved to archive.",
|
|
527
|
-
fg: COLORS.textMuted
|
|
528
|
-
});
|
|
529
|
-
infoRow.add(info);
|
|
530
|
-
const spacer2 = new BoxRenderable6(renderer, { id: "archive-spacer2", width: "100%", height: 2 });
|
|
531
|
-
const hintRow = new BoxRenderable6(renderer, {
|
|
532
|
-
id: "archive-hint-row",
|
|
747
|
+
const hintRow = new BoxRenderable7(renderer, {
|
|
748
|
+
id: "history-hint-row",
|
|
533
749
|
width: "100%",
|
|
534
750
|
height: 1,
|
|
535
751
|
justifyContent: "center"
|
|
536
752
|
});
|
|
537
|
-
const
|
|
538
|
-
id: "
|
|
539
|
-
content: "[
|
|
540
|
-
fg: COLORS.
|
|
753
|
+
const hintText = new TextRenderable6(renderer, {
|
|
754
|
+
id: "history-hint-text",
|
|
755
|
+
content: "[Esc] close",
|
|
756
|
+
fg: COLORS.textDim
|
|
541
757
|
});
|
|
542
|
-
hintRow.add(
|
|
543
|
-
dialog.add(
|
|
544
|
-
dialog.add(spacer1);
|
|
545
|
-
dialog.add(taskRow);
|
|
546
|
-
dialog.add(infoRow);
|
|
547
|
-
dialog.add(spacer2);
|
|
758
|
+
hintRow.add(hintText);
|
|
759
|
+
dialog.add(footerSpacer);
|
|
548
760
|
dialog.add(hintRow);
|
|
549
761
|
renderer.root.add(overlay);
|
|
550
762
|
state.modalOverlay = overlay;
|
|
551
|
-
state.activeModal = "
|
|
552
|
-
state.onModalConfirm = async () => {
|
|
553
|
-
await state.taskService.archiveTasks({ taskIds: [taskId] });
|
|
554
|
-
closeModal(state);
|
|
555
|
-
await onArchived();
|
|
556
|
-
};
|
|
763
|
+
state.activeModal = "taskHistory";
|
|
557
764
|
}
|
|
558
765
|
// src/components/modals/assign-task.ts
|
|
559
766
|
import {
|
|
560
|
-
BoxRenderable as
|
|
767
|
+
BoxRenderable as BoxRenderable8,
|
|
561
768
|
InputRenderable as InputRenderable2,
|
|
562
769
|
InputRenderableEvents as InputRenderableEvents2,
|
|
563
|
-
TextRenderable as
|
|
770
|
+
TextRenderable as TextRenderable7
|
|
564
771
|
} from "@opentui/core";
|
|
565
772
|
async function showAssignTaskModal(state, onAssigned) {
|
|
566
773
|
const { renderer } = state;
|
|
@@ -574,39 +781,39 @@ async function showAssignTaskModal(state, onAssigned) {
|
|
|
574
781
|
}
|
|
575
782
|
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
576
783
|
id: "assign-task-dialog",
|
|
577
|
-
width:
|
|
784
|
+
width: MODAL_WIDTHS.confirmation,
|
|
578
785
|
height: 12
|
|
579
786
|
});
|
|
580
|
-
const titleRow = new
|
|
787
|
+
const titleRow = new BoxRenderable8(renderer, {
|
|
581
788
|
id: "assign-title-row",
|
|
582
789
|
width: "100%",
|
|
583
790
|
height: 1
|
|
584
791
|
});
|
|
585
|
-
const title = new
|
|
792
|
+
const title = new TextRenderable7(renderer, {
|
|
586
793
|
id: "assign-title",
|
|
587
794
|
content: "Assign Task",
|
|
588
795
|
fg: COLORS.accent
|
|
589
796
|
});
|
|
590
797
|
titleRow.add(title);
|
|
591
|
-
const taskRow = new
|
|
798
|
+
const taskRow = new BoxRenderable8(renderer, {
|
|
592
799
|
id: "assign-task-row",
|
|
593
800
|
width: "100%",
|
|
594
801
|
height: 1
|
|
595
802
|
});
|
|
596
|
-
const taskText = new
|
|
803
|
+
const taskText = new TextRenderable7(renderer, {
|
|
597
804
|
id: "assign-task-text",
|
|
598
|
-
content: task.title.slice(0,
|
|
805
|
+
content: task.title.slice(0, TRUNCATION.taskTitle),
|
|
599
806
|
fg: COLORS.textMuted
|
|
600
807
|
});
|
|
601
808
|
taskRow.add(taskText);
|
|
602
|
-
const spacer1 = new
|
|
603
|
-
const labelRow = new
|
|
809
|
+
const spacer1 = new BoxRenderable8(renderer, { id: "assign-spacer1", width: "100%", height: 1 });
|
|
810
|
+
const labelRow = new BoxRenderable8(renderer, {
|
|
604
811
|
id: "assign-label-row",
|
|
605
812
|
width: "100%",
|
|
606
813
|
height: 1
|
|
607
814
|
});
|
|
608
815
|
const currentAssignee = task.assignedTo ?? "(unassigned)";
|
|
609
|
-
const label = new
|
|
816
|
+
const label = new TextRenderable7(renderer, {
|
|
610
817
|
id: "assign-label",
|
|
611
818
|
content: `Current: ${currentAssignee}`,
|
|
612
819
|
fg: COLORS.text
|
|
@@ -623,14 +830,14 @@ async function showAssignTaskModal(state, onAssigned) {
|
|
|
623
830
|
focusedBackgroundColor: COLORS.inputBg,
|
|
624
831
|
cursorColor: COLORS.cursor
|
|
625
832
|
});
|
|
626
|
-
const spacer2 = new
|
|
833
|
+
const spacer2 = new BoxRenderable8(renderer, { id: "assign-spacer2", width: "100%", height: 1 });
|
|
627
834
|
const doAssign = async () => {
|
|
628
835
|
const assignee = input.value.trim();
|
|
629
|
-
await state.taskService.updateTask(taskId, {
|
|
630
|
-
assignedTo: assignee || null
|
|
631
|
-
});
|
|
836
|
+
const result = await withErrorHandling(state, () => state.taskService.updateTask(taskId, { assignedTo: assignee || null }), "Failed to assign task");
|
|
632
837
|
closeModal(state);
|
|
633
|
-
|
|
838
|
+
if (result) {
|
|
839
|
+
await onAssigned();
|
|
840
|
+
}
|
|
634
841
|
};
|
|
635
842
|
const doCancel = () => {
|
|
636
843
|
closeModal(state);
|
|
@@ -658,9 +865,7 @@ async function showAssignTaskModal(state, onAssigned) {
|
|
|
658
865
|
input.on(InputRenderableEvents2.ENTER, doAssign);
|
|
659
866
|
}
|
|
660
867
|
// src/components/modals/delete-task.ts
|
|
661
|
-
import { BoxRenderable as BoxRenderable8, TextRenderable as TextRenderable7 } from "@opentui/core";
|
|
662
868
|
async function showDeleteTaskModal(state, onDeleted) {
|
|
663
|
-
const { renderer } = state;
|
|
664
869
|
const taskId = getSelectedTaskId(state);
|
|
665
870
|
if (!taskId) {
|
|
666
871
|
return;
|
|
@@ -670,168 +875,166 @@ async function showDeleteTaskModal(state, onDeleted) {
|
|
|
670
875
|
return;
|
|
671
876
|
}
|
|
672
877
|
state.selectedTask = task;
|
|
673
|
-
|
|
878
|
+
showConfirmationModal(state, {
|
|
674
879
|
id: "delete-task-dialog",
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
content: "Delete Task?",
|
|
688
|
-
fg: COLORS.danger
|
|
689
|
-
});
|
|
690
|
-
titleRow.add(title);
|
|
691
|
-
const spacer1 = new BoxRenderable8(renderer, { id: "delete-spacer1", width: "100%", height: 1 });
|
|
692
|
-
const taskRow = new BoxRenderable8(renderer, {
|
|
693
|
-
id: "delete-task-row",
|
|
694
|
-
width: "100%",
|
|
695
|
-
height: 1
|
|
696
|
-
});
|
|
697
|
-
const taskText = new TextRenderable7(renderer, {
|
|
698
|
-
id: "delete-task-text",
|
|
699
|
-
content: task.title.slice(0, 40),
|
|
700
|
-
fg: COLORS.text
|
|
701
|
-
});
|
|
702
|
-
taskRow.add(taskText);
|
|
703
|
-
const warningRow = new BoxRenderable8(renderer, {
|
|
704
|
-
id: "delete-warning-row",
|
|
705
|
-
width: "100%",
|
|
706
|
-
height: 1
|
|
707
|
-
});
|
|
708
|
-
const warning = new TextRenderable7(renderer, {
|
|
709
|
-
id: "delete-warning",
|
|
710
|
-
content: "This action cannot be undone.",
|
|
711
|
-
fg: COLORS.warning
|
|
712
|
-
});
|
|
713
|
-
warningRow.add(warning);
|
|
714
|
-
const spacer2 = new BoxRenderable8(renderer, { id: "delete-spacer2", width: "100%", height: 2 });
|
|
715
|
-
const hintRow = new BoxRenderable8(renderer, {
|
|
716
|
-
id: "delete-hint-row",
|
|
717
|
-
width: "100%",
|
|
718
|
-
height: 1,
|
|
719
|
-
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
|
+
}
|
|
720
892
|
});
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
+
}
|
|
725
918
|
});
|
|
726
|
-
hintRow.add(hint);
|
|
727
|
-
dialog.add(titleRow);
|
|
728
|
-
dialog.add(spacer1);
|
|
729
|
-
dialog.add(taskRow);
|
|
730
|
-
dialog.add(warningRow);
|
|
731
|
-
dialog.add(spacer2);
|
|
732
|
-
dialog.add(hintRow);
|
|
733
|
-
renderer.root.add(overlay);
|
|
734
|
-
state.modalOverlay = overlay;
|
|
735
|
-
state.activeModal = "deleteTask";
|
|
736
|
-
state.onModalConfirm = async () => {
|
|
737
|
-
await state.taskService.deleteTask(taskId);
|
|
738
|
-
closeModal(state);
|
|
739
|
-
await onDeleted();
|
|
740
|
-
};
|
|
741
919
|
}
|
|
742
920
|
// src/components/modals/restore-task.ts
|
|
743
|
-
import { BoxRenderable as BoxRenderable9, TextRenderable as TextRenderable8 } from "@opentui/core";
|
|
744
921
|
async function showRestoreTaskModal(state, onRestored) {
|
|
745
|
-
const { renderer } = state;
|
|
746
922
|
const taskId = getSelectedTaskId(state);
|
|
747
923
|
if (!taskId) {
|
|
748
924
|
return;
|
|
749
925
|
}
|
|
750
926
|
const task = await state.taskService.getTask(taskId);
|
|
751
|
-
if (!task) {
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
if (!task.archived) {
|
|
927
|
+
if (!task || !task.archived) {
|
|
755
928
|
return;
|
|
756
929
|
}
|
|
757
930
|
state.selectedTask = task;
|
|
758
|
-
|
|
931
|
+
showConfirmationModal(state, {
|
|
759
932
|
id: "restore-task-dialog",
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
|
763
961
|
});
|
|
764
962
|
const titleRow = new BoxRenderable9(renderer, {
|
|
765
|
-
id: "
|
|
963
|
+
id: "search-title-row",
|
|
766
964
|
width: "100%",
|
|
767
|
-
height: 1
|
|
768
|
-
justifyContent: "center"
|
|
965
|
+
height: 1
|
|
769
966
|
});
|
|
770
967
|
const title = new TextRenderable8(renderer, {
|
|
771
|
-
id: "
|
|
772
|
-
content: "
|
|
773
|
-
fg: COLORS.
|
|
968
|
+
id: "search-title",
|
|
969
|
+
content: " Search Archive ",
|
|
970
|
+
fg: COLORS.accent
|
|
774
971
|
});
|
|
775
972
|
titleRow.add(title);
|
|
776
|
-
const spacer1 = new BoxRenderable9(renderer, { id: "
|
|
777
|
-
const
|
|
778
|
-
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",
|
|
779
976
|
width: "100%",
|
|
780
977
|
height: 1
|
|
781
978
|
});
|
|
782
|
-
const
|
|
783
|
-
id: "
|
|
784
|
-
content:
|
|
979
|
+
const label = new TextRenderable8(renderer, {
|
|
980
|
+
id: "search-label",
|
|
981
|
+
content: "Search query:",
|
|
785
982
|
fg: COLORS.text
|
|
786
983
|
});
|
|
787
|
-
|
|
788
|
-
const
|
|
789
|
-
id: "
|
|
790
|
-
width:
|
|
791
|
-
height: 1
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
|
797
995
|
});
|
|
798
|
-
|
|
799
|
-
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 });
|
|
800
997
|
const hintRow = new BoxRenderable9(renderer, {
|
|
801
|
-
id: "
|
|
998
|
+
id: "search-hint-row",
|
|
802
999
|
width: "100%",
|
|
803
|
-
height: 1
|
|
804
|
-
justifyContent: "center"
|
|
1000
|
+
height: 1
|
|
805
1001
|
});
|
|
806
1002
|
const hint = new TextRenderable8(renderer, {
|
|
807
|
-
id: "
|
|
808
|
-
content: "[
|
|
1003
|
+
id: "search-hint",
|
|
1004
|
+
content: "[Enter] Search [Esc] Cancel",
|
|
809
1005
|
fg: COLORS.textMuted
|
|
810
1006
|
});
|
|
811
1007
|
hintRow.add(hint);
|
|
812
1008
|
dialog.add(titleRow);
|
|
813
1009
|
dialog.add(spacer1);
|
|
814
|
-
dialog.add(
|
|
815
|
-
dialog.add(
|
|
1010
|
+
dialog.add(labelRow);
|
|
1011
|
+
dialog.add(input);
|
|
816
1012
|
dialog.add(spacer2);
|
|
817
1013
|
dialog.add(hintRow);
|
|
818
1014
|
renderer.root.add(overlay);
|
|
819
1015
|
state.modalOverlay = overlay;
|
|
820
|
-
state.activeModal = "
|
|
821
|
-
state.
|
|
822
|
-
|
|
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");
|
|
823
1024
|
closeModal(state);
|
|
824
|
-
|
|
825
|
-
|
|
1025
|
+
if (result) {
|
|
1026
|
+
await onResults(result.tasks);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
826
1029
|
}
|
|
827
1030
|
// src/components/modals/edit-task.ts
|
|
828
1031
|
import {
|
|
829
1032
|
BoxRenderable as BoxRenderable10,
|
|
830
|
-
InputRenderable as
|
|
831
|
-
InputRenderableEvents as
|
|
1033
|
+
InputRenderable as InputRenderable4,
|
|
1034
|
+
InputRenderableEvents as InputRenderableEvents4,
|
|
832
1035
|
TextRenderable as TextRenderable9
|
|
833
1036
|
} from "@opentui/core";
|
|
834
|
-
var
|
|
1037
|
+
var DIALOG_WIDTH2 = MODAL_WIDTHS.large;
|
|
835
1038
|
var DESC_INPUT_HEIGHT = 6;
|
|
836
1039
|
async function showEditTaskModal(state, callbacks) {
|
|
837
1040
|
const { renderer } = state;
|
|
@@ -847,7 +1050,7 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
847
1050
|
const dialogHeight = 18;
|
|
848
1051
|
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
849
1052
|
id: "edit-task-dialog",
|
|
850
|
-
width:
|
|
1053
|
+
width: DIALOG_WIDTH2,
|
|
851
1054
|
height: dialogHeight
|
|
852
1055
|
});
|
|
853
1056
|
const headerRow = new BoxRenderable10(renderer, {
|
|
@@ -857,7 +1060,7 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
857
1060
|
});
|
|
858
1061
|
const headerText = new TextRenderable9(renderer, {
|
|
859
1062
|
id: "edit-header-text",
|
|
860
|
-
content: `Edit Task: ${truncate(task.title,
|
|
1063
|
+
content: `Edit Task: ${truncate(task.title, DIALOG_WIDTH2 - 20)}`,
|
|
861
1064
|
fg: COLORS.accent
|
|
862
1065
|
});
|
|
863
1066
|
headerRow.add(headerText);
|
|
@@ -877,9 +1080,9 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
877
1080
|
fg: COLORS.textMuted
|
|
878
1081
|
});
|
|
879
1082
|
titleLabelRow.add(titleLabel);
|
|
880
|
-
const titleInput = new
|
|
1083
|
+
const titleInput = new InputRenderable4(renderer, {
|
|
881
1084
|
id: "edit-title-input",
|
|
882
|
-
width:
|
|
1085
|
+
width: DIALOG_WIDTH2 - 6,
|
|
883
1086
|
height: 1,
|
|
884
1087
|
placeholder: "Task title...",
|
|
885
1088
|
textColor: COLORS.text,
|
|
@@ -905,9 +1108,9 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
905
1108
|
fg: COLORS.textMuted
|
|
906
1109
|
});
|
|
907
1110
|
descLabelRow.add(descLabel);
|
|
908
|
-
const descInput = new
|
|
1111
|
+
const descInput = new InputRenderable4(renderer, {
|
|
909
1112
|
id: "edit-desc-input",
|
|
910
|
-
width:
|
|
1113
|
+
width: DIALOG_WIDTH2 - 6,
|
|
911
1114
|
height: DESC_INPUT_HEIGHT,
|
|
912
1115
|
placeholder: "Task description (optional)...",
|
|
913
1116
|
textColor: COLORS.text,
|
|
@@ -930,10 +1133,13 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
930
1133
|
}
|
|
931
1134
|
const hasChanges = newTitle !== task.title || newDescription !== (task.description ?? "");
|
|
932
1135
|
if (hasChanges) {
|
|
933
|
-
await state.taskService.updateTask(task.id, {
|
|
1136
|
+
const result = await withErrorHandling(state, () => state.taskService.updateTask(task.id, {
|
|
934
1137
|
title: newTitle,
|
|
935
1138
|
description: newDescription || undefined
|
|
936
|
-
});
|
|
1139
|
+
}), "Failed to save task");
|
|
1140
|
+
if (!result) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
937
1143
|
}
|
|
938
1144
|
closeModal(state);
|
|
939
1145
|
state.editTaskState = null;
|
|
@@ -979,7 +1185,7 @@ async function showEditTaskModal(state, callbacks) {
|
|
|
979
1185
|
state.taskInput = titleInput;
|
|
980
1186
|
state.buttonRow = buttonRow;
|
|
981
1187
|
state.activeModal = "editTask";
|
|
982
|
-
titleInput.on(
|
|
1188
|
+
titleInput.on(InputRenderableEvents4.ENTER, doSave);
|
|
983
1189
|
state.editTaskRuntime = {
|
|
984
1190
|
titleInput,
|
|
985
1191
|
descInput,
|
|
@@ -1012,6 +1218,30 @@ function focusNextEditField(state) {
|
|
|
1012
1218
|
break;
|
|
1013
1219
|
}
|
|
1014
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
|
+
}
|
|
1015
1245
|
function cancelEditTask(state) {
|
|
1016
1246
|
if (!state.editTaskRuntime)
|
|
1017
1247
|
return;
|
|
@@ -1027,8 +1257,12 @@ var SHORTCUTS = [
|
|
|
1027
1257
|
["m", "Move task (change status)"],
|
|
1028
1258
|
["u", "Assign user to task"],
|
|
1029
1259
|
["d", "Delete task"],
|
|
1260
|
+
["C", "Complete task (move to done)"],
|
|
1261
|
+
["H", "View task history"],
|
|
1030
1262
|
["x", "Archive task"],
|
|
1031
1263
|
["r", "Restore task (archive view)"],
|
|
1264
|
+
["/", "Search archive (archive view)"],
|
|
1265
|
+
["P", "Purge archive (archive view)"],
|
|
1032
1266
|
["Tab", "Toggle archive view"],
|
|
1033
1267
|
["?", "Show/hide help"],
|
|
1034
1268
|
["q", "Quit"]
|
|
@@ -1037,8 +1271,8 @@ function showHelpModal(state) {
|
|
|
1037
1271
|
const { renderer } = state;
|
|
1038
1272
|
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
1039
1273
|
id: "help-dialog",
|
|
1040
|
-
width:
|
|
1041
|
-
height:
|
|
1274
|
+
width: MODAL_WIDTHS.confirmation,
|
|
1275
|
+
height: MODAL_HEIGHTS.large,
|
|
1042
1276
|
padding: 2
|
|
1043
1277
|
});
|
|
1044
1278
|
const titleRow = new BoxRenderable11(renderer, {
|
|
@@ -1144,7 +1378,7 @@ async function showMoveTaskModal(state, onMoved) {
|
|
|
1144
1378
|
});
|
|
1145
1379
|
const taskText = new TextRenderable11(renderer, {
|
|
1146
1380
|
id: "move-task-text",
|
|
1147
|
-
content: task.title.slice(0,
|
|
1381
|
+
content: task.title.slice(0, TRUNCATION.taskTitleShort),
|
|
1148
1382
|
fg: COLORS.textMuted
|
|
1149
1383
|
});
|
|
1150
1384
|
taskRow.add(taskText);
|
|
@@ -1200,20 +1434,54 @@ async function showMoveTaskModal(state, onMoved) {
|
|
|
1200
1434
|
state.activeModal = "moveTask";
|
|
1201
1435
|
columnSelect.on(SelectRenderableEvents.ITEM_SELECTED, async () => {
|
|
1202
1436
|
const selected = columnSelect.getSelectedOption();
|
|
1203
|
-
if (selected?.value) {
|
|
1204
|
-
|
|
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);
|
|
1205
1446
|
}
|
|
1206
|
-
closeModal(state);
|
|
1207
|
-
await onMoved();
|
|
1208
1447
|
});
|
|
1209
1448
|
}
|
|
1210
1449
|
// src/components/modals/onboarding.ts
|
|
1211
1450
|
import {
|
|
1212
1451
|
BoxRenderable as BoxRenderable13,
|
|
1213
|
-
InputRenderable as
|
|
1214
|
-
InputRenderableEvents as
|
|
1452
|
+
InputRenderable as InputRenderable5,
|
|
1453
|
+
InputRenderableEvents as InputRenderableEvents5,
|
|
1215
1454
|
TextRenderable as TextRenderable12
|
|
1216
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
|
|
1217
1485
|
async function showOnboarding(renderer) {
|
|
1218
1486
|
return new Promise((resolvePromise) => {
|
|
1219
1487
|
const container = new BoxRenderable13(renderer, {
|
|
@@ -1273,7 +1541,7 @@ async function showOnboarding(renderer) {
|
|
|
1273
1541
|
fg: COLORS.text
|
|
1274
1542
|
});
|
|
1275
1543
|
labelRow.add(label);
|
|
1276
|
-
const input = new
|
|
1544
|
+
const input = new InputRenderable5(renderer, {
|
|
1277
1545
|
id: "board-name-input",
|
|
1278
1546
|
width: 44,
|
|
1279
1547
|
height: 1,
|
|
@@ -1285,7 +1553,7 @@ async function showOnboarding(renderer) {
|
|
|
1285
1553
|
cursorColor: COLORS.cursor
|
|
1286
1554
|
});
|
|
1287
1555
|
const spacer2 = new BoxRenderable13(renderer, { id: "spacer2", width: "100%", height: 1 });
|
|
1288
|
-
const keyEmitter = renderer
|
|
1556
|
+
const keyEmitter = getKeyInput(renderer);
|
|
1289
1557
|
const doCreate = () => {
|
|
1290
1558
|
keyEmitter.off("keypress", keyHandler);
|
|
1291
1559
|
const boardName = input.value.trim() || "Kaban Board";
|
|
@@ -1311,7 +1579,7 @@ async function showOnboarding(renderer) {
|
|
|
1311
1579
|
container.add(card);
|
|
1312
1580
|
renderer.root.add(container);
|
|
1313
1581
|
input.focus();
|
|
1314
|
-
input.on(
|
|
1582
|
+
input.on(InputRenderableEvents5.ENTER, doCreate);
|
|
1315
1583
|
const keyBindings = {
|
|
1316
1584
|
tab: () => {
|
|
1317
1585
|
input.blur();
|
|
@@ -1340,50 +1608,22 @@ async function showOnboarding(renderer) {
|
|
|
1340
1608
|
});
|
|
1341
1609
|
}
|
|
1342
1610
|
// src/components/modals/quit.ts
|
|
1343
|
-
import { BoxRenderable as BoxRenderable14, TextRenderable as TextRenderable13 } from "@opentui/core";
|
|
1344
1611
|
function showQuitModal(state) {
|
|
1345
|
-
|
|
1346
|
-
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
1612
|
+
showConfirmationModal(state, {
|
|
1347
1613
|
id: "quit-dialog",
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
});
|
|
1358
|
-
const title = new TextRenderable13(renderer, {
|
|
1359
|
-
id: "quit-title",
|
|
1360
|
-
content: "Quit Kaban?",
|
|
1361
|
-
fg: COLORS.danger
|
|
1362
|
-
});
|
|
1363
|
-
titleRow.add(title);
|
|
1364
|
-
const spacer = new BoxRenderable14(renderer, { id: "quit-spacer", width: "100%", height: 2 });
|
|
1365
|
-
const hintRow = new BoxRenderable14(renderer, {
|
|
1366
|
-
id: "quit-hint-row",
|
|
1367
|
-
width: "100%",
|
|
1368
|
-
height: 1,
|
|
1369
|
-
justifyContent: "center"
|
|
1370
|
-
});
|
|
1371
|
-
const hint = new TextRenderable13(renderer, {
|
|
1372
|
-
id: "quit-hint",
|
|
1373
|
-
content: "[y] Yes [n/Esc] No",
|
|
1374
|
-
fg: COLORS.textMuted
|
|
1375
|
-
});
|
|
1376
|
-
hintRow.add(hint);
|
|
1377
|
-
dialog.add(titleRow);
|
|
1378
|
-
dialog.add(spacer);
|
|
1379
|
-
dialog.add(hintRow);
|
|
1380
|
-
renderer.root.add(overlay);
|
|
1381
|
-
state.modalOverlay = overlay;
|
|
1382
|
-
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 () => {});
|
|
1383
1623
|
}
|
|
1384
1624
|
// src/components/modals/view-task.ts
|
|
1385
|
-
import { BoxRenderable as
|
|
1386
|
-
var
|
|
1625
|
+
import { BoxRenderable as BoxRenderable14, TextRenderable as TextRenderable13 } from "@opentui/core";
|
|
1626
|
+
var DIALOG_WIDTH3 = MODAL_WIDTHS.large;
|
|
1387
1627
|
var DESC_VISIBLE_LINES = 4;
|
|
1388
1628
|
var LABEL_WIDTH = 12;
|
|
1389
1629
|
function formatDate(date) {
|
|
@@ -1425,51 +1665,51 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1425
1665
|
const dialogHeight = 24;
|
|
1426
1666
|
const { overlay, dialog } = createModalOverlay(renderer, {
|
|
1427
1667
|
id: "view-task-dialog",
|
|
1428
|
-
width:
|
|
1668
|
+
width: DIALOG_WIDTH3,
|
|
1429
1669
|
height: dialogHeight
|
|
1430
1670
|
});
|
|
1431
1671
|
const headerDivider = createSectionDivider(renderer, {
|
|
1432
1672
|
label: "Task Details",
|
|
1433
|
-
width:
|
|
1673
|
+
width: DIALOG_WIDTH3 - 4,
|
|
1434
1674
|
id: "view-header"
|
|
1435
1675
|
});
|
|
1436
|
-
const spacerHeader = new
|
|
1676
|
+
const spacerHeader = new BoxRenderable14(renderer, {
|
|
1437
1677
|
id: "view-spacer-header",
|
|
1438
1678
|
width: "100%",
|
|
1439
1679
|
height: 1
|
|
1440
1680
|
});
|
|
1441
|
-
const titleRow = new
|
|
1681
|
+
const titleRow = new BoxRenderable14(renderer, {
|
|
1442
1682
|
id: "view-title-row",
|
|
1443
1683
|
width: "100%",
|
|
1444
1684
|
height: 1,
|
|
1445
1685
|
flexDirection: "row",
|
|
1446
1686
|
justifyContent: "space-between"
|
|
1447
1687
|
});
|
|
1448
|
-
const taskTitle = new
|
|
1688
|
+
const taskTitle = new TextRenderable13(renderer, {
|
|
1449
1689
|
id: "view-task-title",
|
|
1450
|
-
content: truncate(task.title,
|
|
1690
|
+
content: truncate(task.title, DIALOG_WIDTH3 - 14),
|
|
1451
1691
|
fg: COLORS.text
|
|
1452
1692
|
});
|
|
1453
|
-
const editHint = new
|
|
1693
|
+
const editHint = new TextRenderable13(renderer, {
|
|
1454
1694
|
id: "view-edit-hint",
|
|
1455
1695
|
content: "[e]dit",
|
|
1456
1696
|
fg: COLORS.textDim
|
|
1457
1697
|
});
|
|
1458
1698
|
titleRow.add(taskTitle);
|
|
1459
1699
|
titleRow.add(editHint);
|
|
1460
|
-
const idRow = new
|
|
1700
|
+
const idRow = new BoxRenderable14(renderer, {
|
|
1461
1701
|
id: "view-id-row",
|
|
1462
1702
|
width: "100%",
|
|
1463
1703
|
height: 1,
|
|
1464
1704
|
flexDirection: "row",
|
|
1465
1705
|
justifyContent: "space-between"
|
|
1466
1706
|
});
|
|
1467
|
-
const idValue = new
|
|
1707
|
+
const idValue = new TextRenderable13(renderer, {
|
|
1468
1708
|
id: "view-id-value",
|
|
1469
|
-
content: truncateMiddle(task.id,
|
|
1709
|
+
content: truncateMiddle(task.id, DIALOG_WIDTH3 - 14),
|
|
1470
1710
|
fg: COLORS.textDim
|
|
1471
1711
|
});
|
|
1472
|
-
const copyHint = new
|
|
1712
|
+
const copyHint = new TextRenderable13(renderer, {
|
|
1473
1713
|
id: "view-copy-hint",
|
|
1474
1714
|
content: "[c]opy",
|
|
1475
1715
|
fg: COLORS.textDim
|
|
@@ -1478,26 +1718,26 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1478
1718
|
idRow.add(copyHint);
|
|
1479
1719
|
const statusDivider = createSectionDivider(renderer, {
|
|
1480
1720
|
label: "Status",
|
|
1481
|
-
width:
|
|
1721
|
+
width: DIALOG_WIDTH3 - 4,
|
|
1482
1722
|
id: "view-status"
|
|
1483
1723
|
});
|
|
1484
|
-
const columnRow = new
|
|
1724
|
+
const columnRow = new BoxRenderable14(renderer, {
|
|
1485
1725
|
id: "view-column-row",
|
|
1486
1726
|
width: "100%",
|
|
1487
1727
|
height: 1,
|
|
1488
1728
|
flexDirection: "row"
|
|
1489
1729
|
});
|
|
1490
|
-
const columnLabel = new
|
|
1730
|
+
const columnLabel = new TextRenderable13(renderer, {
|
|
1491
1731
|
id: "view-column-label",
|
|
1492
1732
|
content: padLabel("Column"),
|
|
1493
1733
|
fg: COLORS.textMuted
|
|
1494
1734
|
});
|
|
1495
|
-
const columnBullet = new
|
|
1735
|
+
const columnBullet = new TextRenderable13(renderer, {
|
|
1496
1736
|
id: "view-column-bullet",
|
|
1497
1737
|
content: "\u25CF ",
|
|
1498
1738
|
fg: statusColor
|
|
1499
1739
|
});
|
|
1500
|
-
const columnValue = new
|
|
1740
|
+
const columnValue = new TextRenderable13(renderer, {
|
|
1501
1741
|
id: "view-column-value",
|
|
1502
1742
|
content: columnName,
|
|
1503
1743
|
fg: COLORS.text
|
|
@@ -1505,54 +1745,54 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1505
1745
|
columnRow.add(columnLabel);
|
|
1506
1746
|
columnRow.add(columnBullet);
|
|
1507
1747
|
columnRow.add(columnValue);
|
|
1508
|
-
const assigneeRow = new
|
|
1748
|
+
const assigneeRow = new BoxRenderable14(renderer, {
|
|
1509
1749
|
id: "view-assignee-row",
|
|
1510
1750
|
width: "100%",
|
|
1511
1751
|
height: 1,
|
|
1512
1752
|
flexDirection: "row"
|
|
1513
1753
|
});
|
|
1514
|
-
const assigneeLabel = new
|
|
1754
|
+
const assigneeLabel = new TextRenderable13(renderer, {
|
|
1515
1755
|
id: "view-assignee-label",
|
|
1516
1756
|
content: padLabel("Assignee"),
|
|
1517
1757
|
fg: COLORS.textMuted
|
|
1518
1758
|
});
|
|
1519
|
-
const assigneeValue = new
|
|
1759
|
+
const assigneeValue = new TextRenderable13(renderer, {
|
|
1520
1760
|
id: "view-assignee-value",
|
|
1521
1761
|
content: task.assignedTo ?? "\u2014 unassigned",
|
|
1522
1762
|
fg: task.assignedTo ? COLORS.success : COLORS.textDim
|
|
1523
1763
|
});
|
|
1524
1764
|
assigneeRow.add(assigneeLabel);
|
|
1525
1765
|
assigneeRow.add(assigneeValue);
|
|
1526
|
-
const creatorRow = new
|
|
1766
|
+
const creatorRow = new BoxRenderable14(renderer, {
|
|
1527
1767
|
id: "view-creator-row",
|
|
1528
1768
|
width: "100%",
|
|
1529
1769
|
height: 1,
|
|
1530
1770
|
flexDirection: "row"
|
|
1531
1771
|
});
|
|
1532
|
-
const creatorLabel = new
|
|
1772
|
+
const creatorLabel = new TextRenderable13(renderer, {
|
|
1533
1773
|
id: "view-creator-label",
|
|
1534
1774
|
content: padLabel("Creator"),
|
|
1535
1775
|
fg: COLORS.textMuted
|
|
1536
1776
|
});
|
|
1537
|
-
const creatorValue = new
|
|
1777
|
+
const creatorValue = new TextRenderable13(renderer, {
|
|
1538
1778
|
id: "view-creator-value",
|
|
1539
1779
|
content: task.createdBy,
|
|
1540
1780
|
fg: COLORS.text
|
|
1541
1781
|
});
|
|
1542
1782
|
creatorRow.add(creatorLabel);
|
|
1543
1783
|
creatorRow.add(creatorValue);
|
|
1544
|
-
const labelsRow = new
|
|
1784
|
+
const labelsRow = new BoxRenderable14(renderer, {
|
|
1545
1785
|
id: "view-labels-row",
|
|
1546
1786
|
width: "100%",
|
|
1547
1787
|
height: 1,
|
|
1548
1788
|
flexDirection: "row"
|
|
1549
1789
|
});
|
|
1550
|
-
const labelsLabel = new
|
|
1790
|
+
const labelsLabel = new TextRenderable13(renderer, {
|
|
1551
1791
|
id: "view-labels-label",
|
|
1552
1792
|
content: padLabel("Labels"),
|
|
1553
1793
|
fg: COLORS.textMuted
|
|
1554
1794
|
});
|
|
1555
|
-
const labelsValue = new
|
|
1795
|
+
const labelsValue = new TextRenderable13(renderer, {
|
|
1556
1796
|
id: "view-labels-value",
|
|
1557
1797
|
content: "\u2014 none",
|
|
1558
1798
|
fg: COLORS.textDim
|
|
@@ -1561,52 +1801,52 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1561
1801
|
labelsRow.add(labelsValue);
|
|
1562
1802
|
const timelineDivider = createSectionDivider(renderer, {
|
|
1563
1803
|
label: "Timeline",
|
|
1564
|
-
width:
|
|
1804
|
+
width: DIALOG_WIDTH3 - 4,
|
|
1565
1805
|
id: "view-timeline"
|
|
1566
1806
|
});
|
|
1567
|
-
const createdRow = new
|
|
1807
|
+
const createdRow = new BoxRenderable14(renderer, {
|
|
1568
1808
|
id: "view-created-row",
|
|
1569
1809
|
width: "100%",
|
|
1570
1810
|
height: 1,
|
|
1571
1811
|
flexDirection: "row"
|
|
1572
1812
|
});
|
|
1573
|
-
const createdLabel = new
|
|
1813
|
+
const createdLabel = new TextRenderable13(renderer, {
|
|
1574
1814
|
id: "view-created-label",
|
|
1575
1815
|
content: padLabel("Created"),
|
|
1576
1816
|
fg: COLORS.textMuted
|
|
1577
1817
|
});
|
|
1578
|
-
const createdValue = new
|
|
1818
|
+
const createdValue = new TextRenderable13(renderer, {
|
|
1579
1819
|
id: "view-created-value",
|
|
1580
1820
|
content: formatDate(task.createdAt),
|
|
1581
1821
|
fg: COLORS.textDim
|
|
1582
1822
|
});
|
|
1583
1823
|
createdRow.add(createdLabel);
|
|
1584
1824
|
createdRow.add(createdValue);
|
|
1585
|
-
const updatedRow = new
|
|
1825
|
+
const updatedRow = new BoxRenderable14(renderer, {
|
|
1586
1826
|
id: "view-updated-row",
|
|
1587
1827
|
width: "100%",
|
|
1588
1828
|
height: 1,
|
|
1589
1829
|
flexDirection: "row",
|
|
1590
1830
|
justifyContent: "space-between"
|
|
1591
1831
|
});
|
|
1592
|
-
const updatedLeft = new
|
|
1832
|
+
const updatedLeft = new BoxRenderable14(renderer, {
|
|
1593
1833
|
id: "view-updated-left",
|
|
1594
1834
|
height: 1,
|
|
1595
1835
|
flexDirection: "row"
|
|
1596
1836
|
});
|
|
1597
|
-
const updatedLabel = new
|
|
1837
|
+
const updatedLabel = new TextRenderable13(renderer, {
|
|
1598
1838
|
id: "view-updated-label",
|
|
1599
1839
|
content: padLabel("Updated"),
|
|
1600
1840
|
fg: COLORS.textMuted
|
|
1601
1841
|
});
|
|
1602
|
-
const updatedValue = new
|
|
1842
|
+
const updatedValue = new TextRenderable13(renderer, {
|
|
1603
1843
|
id: "view-updated-value",
|
|
1604
1844
|
content: formatDate(task.updatedAt),
|
|
1605
1845
|
fg: COLORS.textDim
|
|
1606
1846
|
});
|
|
1607
1847
|
updatedLeft.add(updatedLabel);
|
|
1608
1848
|
updatedLeft.add(updatedValue);
|
|
1609
|
-
const relativeTime = new
|
|
1849
|
+
const relativeTime = new TextRenderable13(renderer, {
|
|
1610
1850
|
id: "view-relative-time",
|
|
1611
1851
|
content: `(${formatRelativeTime(task.updatedAt)})`,
|
|
1612
1852
|
fg: COLORS.textDim
|
|
@@ -1615,10 +1855,10 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1615
1855
|
updatedRow.add(relativeTime);
|
|
1616
1856
|
const descDivider = createSectionDivider(renderer, {
|
|
1617
1857
|
label: "Description",
|
|
1618
|
-
width:
|
|
1858
|
+
width: DIALOG_WIDTH3 - 4,
|
|
1619
1859
|
id: "view-desc"
|
|
1620
1860
|
});
|
|
1621
|
-
const descContainer = new
|
|
1861
|
+
const descContainer = new BoxRenderable14(renderer, {
|
|
1622
1862
|
id: "view-desc-container",
|
|
1623
1863
|
width: "100%",
|
|
1624
1864
|
height: DESC_VISIBLE_LINES,
|
|
@@ -1626,7 +1866,7 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1626
1866
|
});
|
|
1627
1867
|
const descLineRenderables = [];
|
|
1628
1868
|
for (let i = 0;i < DESC_VISIBLE_LINES; i++) {
|
|
1629
|
-
const line = new
|
|
1869
|
+
const line = new TextRenderable13(renderer, {
|
|
1630
1870
|
id: `view-desc-line-${i}`,
|
|
1631
1871
|
content: " ",
|
|
1632
1872
|
fg: COLORS.text
|
|
@@ -1649,64 +1889,64 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1649
1889
|
for (let i = 0;i < DESC_VISIBLE_LINES; i++) {
|
|
1650
1890
|
const lineContent = visibleLines[i] ?? "";
|
|
1651
1891
|
const isLastLine = i === DESC_VISIBLE_LINES - 1;
|
|
1652
|
-
let displayContent = truncate(lineContent,
|
|
1892
|
+
let displayContent = truncate(lineContent, DIALOG_WIDTH3 - 12);
|
|
1653
1893
|
if (isLastLine && hasMore) {
|
|
1654
1894
|
const remaining = totalDescLines - scrollOffset - DESC_VISIBLE_LINES;
|
|
1655
|
-
displayContent = `${truncate(lineContent,
|
|
1895
|
+
displayContent = `${truncate(lineContent, DIALOG_WIDTH3 - 18)} \u25BC ${remaining}+`;
|
|
1656
1896
|
}
|
|
1657
1897
|
if (i === 0 && hasLess) {
|
|
1658
|
-
displayContent = `\u25B2 ${scrollOffset}+ ${truncate(lineContent,
|
|
1898
|
+
displayContent = `\u25B2 ${scrollOffset}+ ${truncate(lineContent, DIALOG_WIDTH3 - 18)}`;
|
|
1659
1899
|
}
|
|
1660
1900
|
descLineRenderables[i].content = displayContent || " ";
|
|
1661
1901
|
descLineRenderables[i].fg = i === 0 && hasLess || isLastLine && hasMore ? COLORS.textDim : COLORS.text;
|
|
1662
1902
|
}
|
|
1663
1903
|
}
|
|
1664
1904
|
updateDescriptionContent(0);
|
|
1665
|
-
const footerDivider = new
|
|
1905
|
+
const footerDivider = new BoxRenderable14(renderer, {
|
|
1666
1906
|
id: "view-footer-divider",
|
|
1667
1907
|
width: "100%",
|
|
1668
1908
|
height: 1
|
|
1669
1909
|
});
|
|
1670
|
-
const footerLine = new
|
|
1910
|
+
const footerLine = new TextRenderable13(renderer, {
|
|
1671
1911
|
id: "view-footer-line",
|
|
1672
|
-
content: "\u2500".repeat(
|
|
1912
|
+
content: "\u2500".repeat(DIALOG_WIDTH3 - 4),
|
|
1673
1913
|
fg: COLORS.border
|
|
1674
1914
|
});
|
|
1675
1915
|
footerDivider.add(footerLine);
|
|
1676
|
-
const actionsRow = new
|
|
1916
|
+
const actionsRow = new BoxRenderable14(renderer, {
|
|
1677
1917
|
id: "view-actions-row",
|
|
1678
1918
|
width: "100%",
|
|
1679
1919
|
height: 1,
|
|
1680
1920
|
flexDirection: "row",
|
|
1681
1921
|
justifyContent: "space-between"
|
|
1682
1922
|
});
|
|
1683
|
-
const actionsLeft = new
|
|
1923
|
+
const actionsLeft = new BoxRenderable14(renderer, {
|
|
1684
1924
|
id: "view-actions-left",
|
|
1685
1925
|
height: 1,
|
|
1686
1926
|
flexDirection: "row",
|
|
1687
1927
|
gap: 2
|
|
1688
1928
|
});
|
|
1689
|
-
const moveAction = new
|
|
1929
|
+
const moveAction = new TextRenderable13(renderer, {
|
|
1690
1930
|
id: "view-action-move",
|
|
1691
1931
|
content: "[m] Move",
|
|
1692
1932
|
fg: COLORS.textMuted
|
|
1693
1933
|
});
|
|
1694
|
-
const assignAction = new
|
|
1934
|
+
const assignAction = new TextRenderable13(renderer, {
|
|
1695
1935
|
id: "view-action-assign",
|
|
1696
1936
|
content: "[u] Assign",
|
|
1697
1937
|
fg: COLORS.textMuted
|
|
1698
1938
|
});
|
|
1699
|
-
const editAction = new
|
|
1939
|
+
const editAction = new TextRenderable13(renderer, {
|
|
1700
1940
|
id: "view-action-edit",
|
|
1701
1941
|
content: "[e] Edit",
|
|
1702
1942
|
fg: COLORS.textMuted
|
|
1703
1943
|
});
|
|
1704
|
-
const deleteAction = new
|
|
1944
|
+
const deleteAction = new TextRenderable13(renderer, {
|
|
1705
1945
|
id: "view-action-delete",
|
|
1706
1946
|
content: "[d] Delete",
|
|
1707
1947
|
fg: COLORS.danger
|
|
1708
1948
|
});
|
|
1709
|
-
const archiveRestoreAction = new
|
|
1949
|
+
const archiveRestoreAction = new TextRenderable13(renderer, {
|
|
1710
1950
|
id: "view-action-archive-restore",
|
|
1711
1951
|
content: task.archived ? "[r] Restore" : "[x] Archive",
|
|
1712
1952
|
fg: task.archived ? COLORS.success : COLORS.warning
|
|
@@ -1716,7 +1956,7 @@ async function showViewTaskModal(state, actions, taskIdOverride) {
|
|
|
1716
1956
|
actionsLeft.add(editAction);
|
|
1717
1957
|
actionsLeft.add(archiveRestoreAction);
|
|
1718
1958
|
actionsLeft.add(deleteAction);
|
|
1719
|
-
const escAction = new
|
|
1959
|
+
const escAction = new TextRenderable13(renderer, {
|
|
1720
1960
|
id: "view-action-esc",
|
|
1721
1961
|
content: "[Esc]",
|
|
1722
1962
|
fg: COLORS.textDim
|
|
@@ -1803,7 +2043,7 @@ async function copyTaskId(state) {
|
|
|
1803
2043
|
copyHint.fg = COLORS.danger;
|
|
1804
2044
|
state.viewTaskRuntime.copyTimeoutId = setTimeout(() => {
|
|
1805
2045
|
if (state.viewTaskRuntime) {
|
|
1806
|
-
idValue.content = truncateMiddle(taskId,
|
|
2046
|
+
idValue.content = truncateMiddle(taskId, DIALOG_WIDTH3 - 14);
|
|
1807
2047
|
idValue.fg = COLORS.textDim;
|
|
1808
2048
|
copyHint.content = "[c]opy";
|
|
1809
2049
|
copyHint.fg = COLORS.textDim;
|
|
@@ -1981,9 +2221,43 @@ var modalBindings = {
|
|
|
1981
2221
|
return openArchiveModal(state);
|
|
1982
2222
|
}
|
|
1983
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
|
+
},
|
|
1984
2252
|
tab: toggleArchiveView,
|
|
1985
2253
|
return: openViewModal,
|
|
1986
|
-
"?": showHelpModal
|
|
2254
|
+
"?": showHelpModal,
|
|
2255
|
+
H: (state) => {
|
|
2256
|
+
const taskId = getSelectedTaskId(state);
|
|
2257
|
+
if (taskId) {
|
|
2258
|
+
return showTaskHistoryModal(state);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
1987
2261
|
},
|
|
1988
2262
|
addTask: {
|
|
1989
2263
|
escape: closeModal,
|
|
@@ -2061,6 +2335,7 @@ var modalBindings = {
|
|
|
2061
2335
|
editTask: {
|
|
2062
2336
|
escape: cancelEditTask,
|
|
2063
2337
|
tab: focusNextEditField,
|
|
2338
|
+
"shift+tab": focusPrevEditField,
|
|
2064
2339
|
left: buttonSelectPrev,
|
|
2065
2340
|
right: buttonSelectNext,
|
|
2066
2341
|
return: editTaskSave
|
|
@@ -2072,11 +2347,24 @@ var modalBindings = {
|
|
|
2072
2347
|
y: quit,
|
|
2073
2348
|
n: closeModal,
|
|
2074
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
|
|
2075
2362
|
}
|
|
2076
2363
|
};
|
|
2077
2364
|
function handleKeypress(state, key) {
|
|
2078
2365
|
const bindings = modalBindings[state.activeModal];
|
|
2079
|
-
const
|
|
2366
|
+
const shiftKey = key.shift ? `shift+${key.name}` : undefined;
|
|
2367
|
+
const handler = (shiftKey ? bindings[shiftKey] : undefined) ?? bindings[key.name] ?? bindings[WILDCARD];
|
|
2080
2368
|
return handler?.(state);
|
|
2081
2369
|
}
|
|
2082
2370
|
|
|
@@ -2153,9 +2441,11 @@ async function main() {
|
|
|
2153
2441
|
}
|
|
2154
2442
|
const state = {
|
|
2155
2443
|
renderer,
|
|
2444
|
+
db,
|
|
2156
2445
|
taskService,
|
|
2157
2446
|
boardService,
|
|
2158
2447
|
boardName: board.name,
|
|
2448
|
+
projectRoot,
|
|
2159
2449
|
columns: [],
|
|
2160
2450
|
columnPanels: [],
|
|
2161
2451
|
taskSelects: new Map,
|
|
@@ -2175,13 +2465,12 @@ async function main() {
|
|
|
2175
2465
|
};
|
|
2176
2466
|
await refreshBoard(state);
|
|
2177
2467
|
let lastDataVersion = null;
|
|
2178
|
-
const client = db
|
|
2468
|
+
const client = getDbClient(db);
|
|
2179
2469
|
const checkForChanges = async () => {
|
|
2180
2470
|
if (state.activeModal !== "none")
|
|
2181
2471
|
return;
|
|
2182
2472
|
try {
|
|
2183
|
-
const
|
|
2184
|
-
const currentVersion = result.rows[0]?.[0];
|
|
2473
|
+
const currentVersion = await getDataVersion(client);
|
|
2185
2474
|
if (lastDataVersion !== null && currentVersion !== lastDataVersion) {
|
|
2186
2475
|
await refreshBoard(state);
|
|
2187
2476
|
}
|
|
@@ -2193,8 +2482,7 @@ async function main() {
|
|
|
2193
2482
|
process.on("exit", cleanup);
|
|
2194
2483
|
process.on("SIGINT", cleanup);
|
|
2195
2484
|
process.on("SIGTERM", cleanup);
|
|
2196
|
-
|
|
2197
|
-
keyEmitter.on("keypress", (key) => {
|
|
2485
|
+
getKeyInput(renderer).on("keypress", (key) => {
|
|
2198
2486
|
handleKeypress(state, key);
|
|
2199
2487
|
});
|
|
2200
2488
|
}
|