epiq 0.0.2 → 0.0.4

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.
Files changed (148) hide show
  1. package/dist/app.js +10 -4
  2. package/dist/board/components/Board.js +4 -12
  3. package/dist/board/components/CommandLine.d.ts +2 -0
  4. package/dist/board/components/CommandLine.js +26 -0
  5. package/dist/{components/Help.d.ts → board/components/ContextBar.d.ts} +1 -1
  6. package/dist/board/components/ContextBar.js +17 -0
  7. package/dist/board/components/Help.js +19 -12
  8. package/dist/board/components/Swimlane.d.ts +1 -0
  9. package/dist/board/components/Swimlane.js +6 -4
  10. package/dist/board/components/Swimlanes.js +8 -11
  11. package/dist/board/components/TextInput.d.ts +6 -0
  12. package/dist/board/components/TextInput.js +9 -0
  13. package/dist/board/components/TicketListItem.js +6 -5
  14. package/dist/board/components/TicketUI.js +6 -7
  15. package/dist/board/hints/hints.d.ts +7 -0
  16. package/dist/board/hints/hints.js +21 -0
  17. package/dist/board/mock/board.js +73 -27
  18. package/dist/cli.d.ts +2 -1
  19. package/dist/cli.js +21 -39
  20. package/dist/debug-logger.d.ts +2 -0
  21. package/dist/debug-logger.js +16 -0
  22. package/dist/init-project.d.ts +1 -0
  23. package/dist/init-project.js +1 -0
  24. package/dist/navigation/actions/add/add-actions.d.ts +2 -0
  25. package/dist/navigation/actions/add/add-actions.js +25 -0
  26. package/dist/navigation/actions/add/add-item-actions.d.ts +2 -0
  27. package/dist/navigation/actions/add/add-item-actions.js +25 -0
  28. package/dist/navigation/actions/add-item/add-item-actions.d.ts +3 -0
  29. package/dist/navigation/actions/add-item/add-item-actions.js +45 -0
  30. package/dist/navigation/actions/board-action-map.d.ts +10 -0
  31. package/dist/navigation/actions/board-action-map.js +12 -0
  32. package/dist/navigation/actions/default/default-action-utils.d.ts +8 -0
  33. package/dist/navigation/actions/default/default-action-utils.js +101 -0
  34. package/dist/navigation/actions/default/default-actions.d.ts +2 -0
  35. package/dist/navigation/actions/default/default-actions.js +64 -0
  36. package/dist/navigation/actions/default/navigation-action-utils.d.ts +16 -0
  37. package/dist/navigation/actions/default/navigation-action-utils.js +109 -0
  38. package/dist/navigation/actions/input/command-line-input.d.ts +2 -0
  39. package/dist/navigation/actions/input/command-line-input.js +18 -0
  40. package/dist/navigation/actions/input/input-actions-utils.d.ts +8 -0
  41. package/dist/navigation/actions/input/input-actions-utils.js +47 -0
  42. package/dist/navigation/actions/input/input-actions.d.ts +2 -0
  43. package/dist/navigation/actions/input/input-actions.js +44 -0
  44. package/dist/navigation/actions/move/move-actions-routes.d.ts +4 -0
  45. package/dist/navigation/actions/move/move-actions-routes.js +55 -0
  46. package/dist/navigation/actions/move/move-actions-utils.d.ts +4 -0
  47. package/dist/navigation/actions/move/move-actions-utils.js +66 -0
  48. package/dist/navigation/actions/move/move-actions.d.ts +4 -0
  49. package/dist/navigation/actions/move/move-actions.js +59 -0
  50. package/dist/{lib/utils/default-action-utils.d.ts → navigation/actions/navigate/navigation-action-utils.d.ts} +1 -1
  51. package/dist/navigation/actions/navigate/navigation-actions.d.ts +2 -0
  52. package/dist/navigation/actions/navigate/navigation-actions.js +78 -0
  53. package/dist/navigation/command-line/command-line-actions.d.ts +2 -0
  54. package/dist/navigation/command-line/command-line-actions.js +32 -0
  55. package/dist/navigation/command-line/command-line-intent.d.ts +7 -0
  56. package/dist/navigation/command-line/command-line-intent.js +29 -0
  57. package/dist/navigation/command-line/command-line-sequence-actions.d.ts +2 -0
  58. package/dist/navigation/command-line/command-line-sequence-actions.js +44 -0
  59. package/dist/navigation/command-line/command-line-sequence-intent.d.ts +8 -0
  60. package/dist/navigation/command-line/command-line-sequence-intent.js +32 -0
  61. package/dist/navigation/command-line/commands.d.ts +2 -0
  62. package/dist/navigation/command-line/commands.js +58 -0
  63. package/dist/navigation/keypress-listener.d.ts +1 -0
  64. package/dist/navigation/keypress-listener.js +33 -0
  65. package/dist/navigation/model/action-map.model.d.ts +17 -5
  66. package/dist/navigation/model/action-map.model.js +2 -0
  67. package/dist/navigation/model/navigation-ctx.model.d.ts +1 -16
  68. package/dist/navigation/model/navigation-ctx.model.js +0 -1
  69. package/dist/navigation/model/navigation-tree.model.d.ts +3 -1
  70. package/dist/navigation/navigation.d.ts +3 -10
  71. package/dist/navigation/navigation.js +74 -66
  72. package/dist/navigation/state/command-line.state.d.ts +18 -0
  73. package/dist/navigation/state/command-line.state.js +60 -0
  74. package/dist/navigation/state/state.d.ts +18 -9
  75. package/dist/navigation/state/state.js +36 -8
  76. package/dist/navigation/utils/get-command-line-intent.d.ts +3 -0
  77. package/dist/navigation/utils/get-command-line-intent.js +17 -0
  78. package/dist/navigation/utils/getKeyIntent.d.ts +5 -0
  79. package/dist/navigation/utils/getKeyIntent.js +71 -0
  80. package/dist/navigation/utils/key-intent.d.ts +21 -12
  81. package/dist/navigation/utils/key-intent.js +104 -50
  82. package/package.json +3 -3
  83. package/dist/board/actions/board-action-map.d.ts +0 -11
  84. package/dist/board/actions/board-action-map.js +0 -11
  85. package/dist/board/actions/move-actions-routes.d.ts +0 -4
  86. package/dist/board/actions/move-actions-routes.js +0 -31
  87. package/dist/board/actions/move-actions.d.ts +0 -5
  88. package/dist/board/actions/move-actions.js +0 -49
  89. package/dist/board/board-action-map.d.ts +0 -9
  90. package/dist/board/board-action-map.js +0 -7
  91. package/dist/board/move-actions-routes.d.ts +0 -4
  92. package/dist/board/move-actions-routes.js +0 -31
  93. package/dist/board/move-actions.d.ts +0 -5
  94. package/dist/board/move-actions.js +0 -49
  95. package/dist/components/Board.d.ts +0 -6
  96. package/dist/components/Board.js +0 -15
  97. package/dist/components/Help.js +0 -21
  98. package/dist/components/Swimlane.d.ts +0 -8
  99. package/dist/components/Swimlane.js +0 -10
  100. package/dist/components/TicketListItem.d.ts +0 -6
  101. package/dist/components/TicketListItem.js +0 -12
  102. package/dist/lib/actions/board-action-map.d.ts +0 -9
  103. package/dist/lib/actions/board-action-map.js +0 -7
  104. package/dist/lib/actions/move-actions-utils.d.ts +0 -5
  105. package/dist/lib/actions/move-actions-utils.js +0 -43
  106. package/dist/lib/actions/move-actions.d.ts +0 -4
  107. package/dist/lib/actions/move-actions.js +0 -31
  108. package/dist/lib/board/board-action-map.d.ts +0 -9
  109. package/dist/lib/board/board-action-map.js +0 -7
  110. package/dist/lib/board/move-actions-routes.d.ts +0 -4
  111. package/dist/lib/board/move-actions-routes.js +0 -31
  112. package/dist/lib/board/move-actions.d.ts +0 -5
  113. package/dist/lib/board/move-actions.js +0 -43
  114. package/dist/lib/board-utils.d.ts +0 -7
  115. package/dist/lib/board-utils.js +0 -33
  116. package/dist/lib/default-actions-routes.d.ts +0 -4
  117. package/dist/lib/default-actions-routes.js +0 -47
  118. package/dist/lib/default-actions.d.ts +0 -4
  119. package/dist/lib/default-actions.js +0 -47
  120. package/dist/lib/navigation-context.d.ts +0 -16
  121. package/dist/lib/navigation.d.ts +0 -10
  122. package/dist/lib/navigation.js +0 -74
  123. package/dist/lib/render-utils.d.ts +0 -10
  124. package/dist/lib/render-utils.js +0 -12
  125. package/dist/lib/state.d.ts +0 -8
  126. package/dist/lib/state.js +0 -8
  127. package/dist/lib/types/action-map.model.d.ts +0 -14
  128. package/dist/lib/types/action-map.model.js +0 -4
  129. package/dist/lib/types/board.model.d.ts +0 -21
  130. package/dist/lib/types/board.model.js +0 -5
  131. package/dist/lib/types/navigation.model.d.ts +0 -9
  132. package/dist/lib/utils/array-utils.d.ts +0 -5
  133. package/dist/lib/utils/array-utils.js +0 -7
  134. package/dist/lib/utils/default-action-utils.js +0 -50
  135. package/dist/lib/utils/default-actions.d.ts +0 -7
  136. package/dist/lib/utils/default-actions.js +0 -50
  137. package/dist/lib/utils/key-intent.d.ts +0 -16
  138. package/dist/lib/utils/key-intent.js +0 -65
  139. package/dist/lib/utils.d.ts +0 -10
  140. package/dist/lib/utils.js +0 -12
  141. package/dist/mock/board.d.ts +0 -2
  142. package/dist/mock/board.js +0 -139
  143. package/dist/navigation/actions/default-actions-routes.d.ts +0 -4
  144. package/dist/navigation/actions/default-actions-routes.js +0 -47
  145. package/dist/navigation/actions/default-actions.d.ts +0 -7
  146. /package/dist/navigation/actions/{default-actions.js → navigate/navigation-action-utils.js} +0 -0
  147. /package/dist/{lib/navigation-context.js → navigation/navigation-ctx.d.ts} +0 -0
  148. /package/dist/{lib/types/navigation.model.js → navigation/navigation-ctx.js} +0 -0
package/dist/cli.js CHANGED
@@ -1,57 +1,39 @@
1
1
  #!/usr/bin/env node
2
+ import './debug-logger.js';
2
3
  import { render } from 'ink';
3
4
  import meow from 'meow';
4
5
  import React from 'react';
5
6
  import App from './app.js';
6
- import { BoardActions } from './board/actions/board-action-map.js';
7
+ import { initListeners } from './navigation/keypress-listener.js';
8
+ import { appState, initAppState } from './navigation/state/state.js';
7
9
  import { board } from './board/mock/board.js';
8
- import { buildDefaultActions } from './navigation/actions/default-actions-routes.js';
9
- import { navigate } from './navigation/navigation.js';
10
- import { setState } from './navigation/state/state.js';
10
+ import { initProject } from './init-project.js';
11
11
  const cli = meow(`
12
- Usage
12
+ View board in directory:
13
13
  $ epiq
14
14
 
15
- Options
16
- --name Your name
17
-
18
- Examples
19
- $ epiq --name=Jane
20
- Hello, Jane
15
+ Init project in directory:
16
+ $ epiq --init "Project Name"
21
17
  `, {
22
18
  importMeta: import.meta,
23
19
  flags: {
24
- name: {
20
+ init: {
25
21
  type: 'string',
26
22
  },
27
23
  },
28
24
  });
29
25
  cli;
30
- export const main = () => {
31
- console.clear();
32
- navigate({
33
- index: 0,
34
- breadCrumb: [board],
35
- callbacks: {
36
- render: () => {
37
- render(React.createElement(App, { board: board }));
38
- },
39
- onSelectChange: (selected, breadCrumb) => {
40
- if (!selected)
41
- return;
42
- const type = selected
43
- .actionContext; // Fix so that we can infer this type
44
- setState(state => ({
45
- ...state,
46
- currentNode: selected,
47
- breadCrumb,
48
- availableActions: [...buildDefaultActions(), ...BoardActions[type]],
49
- }));
50
- },
51
- },
52
- });
26
+ export let renderBoard = () => {
27
+ render(React.createElement(App, { board: appState.rootNode }));
53
28
  };
54
- main();
55
- process.stdout.on('resize', () => {
56
- render(React.createElement(App, { board: board }));
57
- });
29
+ process.stdout.on('resize', () => renderBoard());
30
+ (() => {
31
+ debug(cli);
32
+ if (cli.flags.init) {
33
+ initProject();
34
+ }
35
+ else if (!Object.keys(cli.flags).length) {
36
+ initAppState(board);
37
+ initListeners();
38
+ }
39
+ })();
@@ -0,0 +1,2 @@
1
+ export declare function debug(...args: any[]): void;
2
+ export {};
@@ -0,0 +1,16 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import util from 'util';
4
+ const DEFAULT_PATH = path.resolve(process.cwd(), 'debug.log');
5
+ export function debug(...args) {
6
+ // const timestamp = new Date().toISOString();
7
+ const message = util.format(...args);
8
+ // const line = `[${timestamp}] ${message}\n`;
9
+ const line = `${message}\n`;
10
+ // Ensure parent directory exists
11
+ fs.mkdirSync(path.dirname(DEFAULT_PATH), { recursive: true });
12
+ // appendFileSync creates the file if it does not exist
13
+ fs.appendFileSync(DEFAULT_PATH, line, 'utf8');
14
+ }
15
+ // make it global
16
+ globalThis.debug = debug;
@@ -0,0 +1 @@
1
+ export declare const initProject: () => void;
@@ -0,0 +1 @@
1
+ export const initProject = () => console.log('initProject');
@@ -0,0 +1,2 @@
1
+ import { ActionEntry } from '../../model/action-map.model.js';
2
+ export declare const AddItemActions: ActionEntry[];
@@ -0,0 +1,25 @@
1
+ import { board } from '../../../board/mock/board.js';
2
+ import { triggerRender } from '../../../cli.js';
3
+ import { Mode } from '../../model/action-map.model.js';
4
+ import { KeyIntent } from '../../utils/key-intent.js';
5
+ export const AddItemActions = [
6
+ {
7
+ intent: KeyIntent.AddItem,
8
+ mode: Mode.DEFAULT,
9
+ description: '[A] Add item',
10
+ action: () => {
11
+ const newItem = {
12
+ id: `item-${Date.now()}`,
13
+ name: 'New Item',
14
+ actionContext: 'SWIMLANE',
15
+ children: [],
16
+ isSelected: false,
17
+ childrenRenderAxis: 'vertical',
18
+ enableChildNavigationAcrossContainers: true,
19
+ description: '',
20
+ };
21
+ board.children.push(newItem);
22
+ triggerRender();
23
+ },
24
+ },
25
+ ];
@@ -0,0 +1,2 @@
1
+ import { ActionEntry } from '../../model/action-map.model.js';
2
+ export declare const AddItemActions: ActionEntry[];
@@ -0,0 +1,25 @@
1
+ import { board } from '../../../board/mock/board.js';
2
+ import { triggerRender } from '../../../cli.js';
3
+ import { Mode } from '../../model/action-map.model.js';
4
+ import { KeyIntent } from '../../utils/key-intent.js';
5
+ export const AddItemActions = [
6
+ {
7
+ intent: KeyIntent.AddItem,
8
+ mode: Mode.DEFAULT,
9
+ description: '[A] Add item',
10
+ action: () => {
11
+ const newItem = {
12
+ id: `item-${Date.now()}`,
13
+ name: 'New Item',
14
+ actionContext: 'SWIMLANE',
15
+ children: [],
16
+ isSelected: false,
17
+ childrenRenderAxis: 'vertical',
18
+ enableChildNavigationAcrossContainers: true,
19
+ description: '',
20
+ };
21
+ board.children.push(newItem);
22
+ triggerRender();
23
+ },
24
+ },
25
+ ];
@@ -0,0 +1,3 @@
1
+ import { CommandLineActionEntry } from '../../model/action-map.model.js';
2
+ export declare const addSwimlaneAction: NonNullable<CommandLineActionEntry['action']>;
3
+ export declare const addTicketAction: NonNullable<CommandLineActionEntry['action']>;
@@ -0,0 +1,45 @@
1
+ import { BoardItemTypes, } from '../../../board/model/board.model.js';
2
+ import { appState } from '../../state/state.js';
3
+ import { navigator } from '../default/navigation-action-utils.js';
4
+ export const addSwimlaneAction = async (_ctx, _cmd, { value }) => {
5
+ const newItem = {
6
+ id: `${Date.now()}`,
7
+ name: value || 'New lane',
8
+ description: '...',
9
+ actionContext: BoardItemTypes.SWIMLANE,
10
+ isSelected: false,
11
+ childrenRenderAxis: 'vertical',
12
+ enableChildNavigationAcrossContainers: true,
13
+ children: [],
14
+ };
15
+ const parent = appState.currentNode;
16
+ parent.children ?? (parent.children = []);
17
+ const newItemIndex = parent.children.length;
18
+ parent.children.push(newItem);
19
+ navigator.navigate({ selectedIndex: newItemIndex });
20
+ };
21
+ export const addTicketAction = async (_ctx, _cmd, { value }) => {
22
+ const newItem = {
23
+ id: `${Date.now()}`,
24
+ name: value || 'New issue',
25
+ actionContext: BoardItemTypes.TICKET_LIST_ITEM,
26
+ isSelected: false,
27
+ childrenRenderAxis: 'vertical',
28
+ children: [
29
+ {
30
+ isSelected: false,
31
+ id: `${Date.now()}`,
32
+ name: 'Description',
33
+ description: '...add description',
34
+ actionContext: BoardItemTypes.TICKET,
35
+ childrenRenderAxis: 'vertical',
36
+ children: [],
37
+ },
38
+ ],
39
+ };
40
+ const parent = appState.currentNode;
41
+ parent.children ?? (parent.children = []);
42
+ const newItemIndex = parent.children.length;
43
+ parent.children.push(newItem);
44
+ navigator.navigate({ selectedIndex: newItemIndex });
45
+ };
@@ -0,0 +1,10 @@
1
+ import { BoardItemTypes } from '../../board/model/board.model.js';
2
+ import { ActionEntry, ActionMap } from '../model/action-map.model.js';
3
+ type BoardActionMap = ActionMap<{
4
+ [BoardItemTypes.BOARD]: ActionEntry[];
5
+ [BoardItemTypes.SWIMLANE]: ActionEntry[];
6
+ [BoardItemTypes.TICKET_LIST_ITEM]: ActionEntry[];
7
+ [BoardItemTypes.TICKET]: ActionEntry[];
8
+ }>;
9
+ export declare const ContextualActionMap: BoardActionMap;
10
+ export {};
@@ -0,0 +1,12 @@
1
+ import { BoardItemTypes } from '../../board/model/board.model.js';
2
+ import { moveAcrossParents, moveWithinParent, toggleMoveMode, } from './move/move-actions.js';
3
+ export const ContextualActionMap = {
4
+ [BoardItemTypes.BOARD]: [...toggleMoveMode, ...moveWithinParent],
5
+ [BoardItemTypes.SWIMLANE]: [
6
+ ...toggleMoveMode,
7
+ ...moveWithinParent,
8
+ ...moveAcrossParents,
9
+ ],
10
+ [BoardItemTypes.TICKET_LIST_ITEM]: [],
11
+ [BoardItemTypes.TICKET]: [],
12
+ };
@@ -0,0 +1,8 @@
1
+ import { NavigateUtils } from '../../model/navigation-ctx.model.js';
2
+ export declare const navigationUtils: NavigateUtils;
3
+ export declare const navigateToNextItem: () => void;
4
+ export declare const navigateToPreviousItem: () => void;
5
+ export declare const navigateToNextContainer: (ctx: NavigateUtils) => void;
6
+ export declare const navigateToPreviousContainer: (ctx: NavigateUtils) => void;
7
+ export declare const enterChildNode: (ctx: NavigateUtils) => Promise<void>;
8
+ export declare const exitToParentNode: (ctx: NavigateUtils) => void;
@@ -0,0 +1,101 @@
1
+ import { appState, patchState, updateState } from '../../state/state.js';
2
+ export const navigationUtils = {
3
+ updateSelection: (idx) => {
4
+ updateState(oldState => {
5
+ const currentNode = oldState.breadCrumb.at(-1) ?? oldState.breadCrumb[0];
6
+ if (!currentNode)
7
+ return oldState;
8
+ currentNode.children?.forEach((child, i) => (child.isSelected = i === idx));
9
+ return {
10
+ ...oldState,
11
+ selectedIndex: idx,
12
+ currentNode: currentNode, // keep currentNode in sync
13
+ breadCrumb: [...oldState.breadCrumb], // keep reference fresh if you rely on it
14
+ };
15
+ });
16
+ },
17
+ selectNone() {
18
+ this.updateSelection(-1);
19
+ },
20
+ confirm(sel) {
21
+ console.log(sel);
22
+ },
23
+ exit() {
24
+ process.exit(0);
25
+ },
26
+ enterChildNode(childNode) {
27
+ patchState({
28
+ breadCrumb: [...appState.breadCrumb, childNode],
29
+ currentNode: childNode,
30
+ selectedIndex: 0,
31
+ });
32
+ this.updateSelection(0);
33
+ },
34
+ enterParentNode() {
35
+ const crumb = appState.breadCrumb;
36
+ if (crumb.length < 2)
37
+ return;
38
+ const newCrumb = crumb.slice(0, -1);
39
+ const parentLevelNode = newCrumb.at(-1);
40
+ // find old parent index inside grandparent
41
+ const grandParent = newCrumb.at(-2);
42
+ const parentIndex = grandParent?.children?.findIndex(x => x.id === parentLevelNode.id) ?? 0;
43
+ patchState({
44
+ breadCrumb: newCrumb,
45
+ currentNode: parentLevelNode,
46
+ selectedIndex: parentIndex,
47
+ });
48
+ this.updateSelection(parentIndex);
49
+ },
50
+ };
51
+ const navigateByOffset = (offset) => {
52
+ const len = appState.currentNode.children.length;
53
+ if (len === 0)
54
+ return;
55
+ const newIndex = (appState.selectedIndex + offset + len) % len;
56
+ navigationUtils.updateSelection(newIndex);
57
+ };
58
+ export const navigateToNextItem = () => navigateByOffset(1);
59
+ export const navigateToPreviousItem = () => navigateByOffset(-1);
60
+ const navigateToSiblingContainer = (ctx, direction) => {
61
+ if (!appState.currentNode.enableChildNavigationAcrossContainers)
62
+ return;
63
+ const parent = appState.breadCrumb.at(-1);
64
+ const grandParent = appState.breadCrumb.at(-2);
65
+ if (!parent || !grandParent)
66
+ return;
67
+ const parentIndex = grandParent.children.findIndex(x => x.id === parent.id);
68
+ const sliced = direction === -1
69
+ ? grandParent.children.toSpliced(parentIndex).toReversed()
70
+ : grandParent.children.slice(parentIndex + 1);
71
+ const nextSibling = sliced.find(x => x.children.length);
72
+ if (!nextSibling?.children?.length)
73
+ return;
74
+ const prevIndex = appState.selectedIndex;
75
+ const boundedIndex = Math.min(prevIndex, nextSibling.children.length - 1);
76
+ ctx.selectNone();
77
+ patchState({
78
+ selectedIndex: boundedIndex,
79
+ breadCrumb: [...appState.breadCrumb.slice(0, -1), nextSibling],
80
+ });
81
+ };
82
+ export const navigateToNextContainer = (ctx) => navigateToSiblingContainer(ctx, 1);
83
+ export const navigateToPreviousContainer = (ctx) => navigateToSiblingContainer(ctx, -1);
84
+ export const enterChildNode = async (ctx) => {
85
+ const currentNode = appState.breadCrumb.at(-1);
86
+ const focusNode = currentNode?.children[appState.selectedIndex];
87
+ if (!currentNode || !focusNode)
88
+ return;
89
+ if (!focusNode?.children?.length) {
90
+ await ctx.enterChildNode(focusNode);
91
+ return;
92
+ }
93
+ else {
94
+ ctx.enterChildNode(focusNode);
95
+ }
96
+ };
97
+ export const exitToParentNode = (ctx) => {
98
+ if (appState.breadCrumb.length >= 2) {
99
+ ctx.enterParentNode();
100
+ }
101
+ };
@@ -0,0 +1,2 @@
1
+ import { ActionEntry } from '../../model/action-map.model.js';
2
+ export declare const DefaultActions: ActionEntry[];
@@ -0,0 +1,64 @@
1
+ import { CmdIntent } from '../../command-line/command-line-sequence-intent.js';
2
+ import { Mode } from '../../model/action-map.model.js';
3
+ import { updateCommandLineInput } from '../../state/command-line.state.js';
4
+ import { appState, patchState } from '../../state/state.js';
5
+ import { Intent } from '../../utils/key-intent.js';
6
+ import { navigator } from './navigation-action-utils.js';
7
+ export const DefaultActions = [
8
+ {
9
+ intent: Intent.ToggleCommandLine,
10
+ mode: Mode.DEFAULT,
11
+ description: '[:] Toggle command line',
12
+ action: () => patchState({ mode: Mode.COMMAND_LINE }),
13
+ },
14
+ {
15
+ intent: Intent.ToggleCommandLine,
16
+ mode: Mode.COMMAND_LINE,
17
+ description: '[ESC] Toggle command line',
18
+ action: () => patchState({ mode: Mode.DEFAULT }),
19
+ },
20
+ {
21
+ intent: Intent.Confirm,
22
+ mode: Mode.DEFAULT,
23
+ description: '[ENTER] Confirm/Enter context',
24
+ action: navigator.enterChildNode,
25
+ },
26
+ {
27
+ intent: Intent.Exit,
28
+ mode: Mode.DEFAULT,
29
+ description: '[ESC/Q] Exit application',
30
+ action: navigator.enterParentNode,
31
+ },
32
+ {
33
+ mode: Mode.DEFAULT,
34
+ description: '[ARROWS/HJKL] Navigate',
35
+ },
36
+ {
37
+ intent: Intent.NavPreviousItem,
38
+ mode: Mode.DEFAULT,
39
+ action: navigator.navigateToPreviousItem,
40
+ },
41
+ {
42
+ intent: Intent.NavNextItem,
43
+ mode: Mode.DEFAULT,
44
+ action: navigator.navigateToNextItem,
45
+ },
46
+ {
47
+ intent: Intent.NavToPreviousContainer,
48
+ mode: Mode.DEFAULT,
49
+ action: navigator.navigateToPreviousContainer,
50
+ },
51
+ {
52
+ intent: Intent.NavToNextContainer,
53
+ mode: Mode.DEFAULT,
54
+ action: navigator.navigateToNextContainer,
55
+ },
56
+ {
57
+ intent: Intent.Edit,
58
+ mode: Mode.DEFAULT,
59
+ action: () => {
60
+ patchState({ mode: Mode.COMMAND_LINE });
61
+ updateCommandLineInput(() => `${CmdIntent.Rename} ${appState.currentNode?.name}`);
62
+ },
63
+ },
64
+ ];
@@ -0,0 +1,16 @@
1
+ import { NavigationTree } from '../../model/navigation-tree.model.js';
2
+ export interface Navigator {
3
+ navigate({ currentNode, selectedIndex, }: {
4
+ currentNode?: NavigationTree<NavigationTree>;
5
+ selectedIndex: number;
6
+ }): void;
7
+ confirm(selected: NavigationTree<NavigationTree>): void;
8
+ exit(): void;
9
+ enterChildNode(): void;
10
+ enterParentNode(): void;
11
+ navigateToNextItem: () => void;
12
+ navigateToPreviousItem: () => void;
13
+ navigateToNextContainer: () => void;
14
+ navigateToPreviousContainer: () => void;
15
+ }
16
+ export declare const navigator: Navigator;
@@ -0,0 +1,109 @@
1
+ import { appState, patchState } from '../../state/state.js';
2
+ export const navigator = {
3
+ confirm(sel) {
4
+ console.log(sel);
5
+ },
6
+ exit() {
7
+ process.exit(0);
8
+ },
9
+ enterChildNode() {
10
+ const currentNode = appState.currentNode;
11
+ const focusNode = currentNode?.children[appState.selectedIndex];
12
+ if (!currentNode || !focusNode)
13
+ return;
14
+ navigator.navigate({
15
+ currentNode: focusNode,
16
+ selectedIndex: 0,
17
+ });
18
+ },
19
+ enterParentNode() {
20
+ const breadCrumb = appState.breadCrumb;
21
+ if (breadCrumb.length < 2)
22
+ return;
23
+ const prevNode = breadCrumb.at(-1);
24
+ if (!prevNode)
25
+ return;
26
+ const newCurrentNode = breadCrumb.at(-2);
27
+ const newIndex = newCurrentNode?.children?.findIndex(x => x.id === prevNode.id) ?? 0;
28
+ navigator.navigate({
29
+ currentNode: newCurrentNode,
30
+ selectedIndex: newIndex,
31
+ });
32
+ },
33
+ navigateToNextItem: () => navigateByOffset(1),
34
+ navigateToPreviousItem: () => navigateByOffset(-1),
35
+ navigateToNextContainer: () => navigateToSiblingContainer(1),
36
+ navigateToPreviousContainer: () => navigateToSiblingContainer(-1),
37
+ navigate: ({ currentNode, selectedIndex }) => {
38
+ if (!currentNode) {
39
+ currentNode = appState.currentNode;
40
+ }
41
+ const findBreadCrumb = (node, targetId, path = []) => {
42
+ const nextPath = [...path, node];
43
+ if (node.id === targetId)
44
+ return nextPath;
45
+ for (const child of node.children ?? []) {
46
+ const found = findBreadCrumb(child, targetId, nextPath);
47
+ if (found.length)
48
+ return found;
49
+ }
50
+ return [];
51
+ };
52
+ const setIsSelected = (node, targetId, selectedIndex) => {
53
+ // Clear selection for all children at this node
54
+ for (const child of node.children ?? []) {
55
+ child.isSelected = false;
56
+ setIsSelected(child, targetId, selectedIndex);
57
+ }
58
+ // If this is the target container, select its child at selectedIndex
59
+ if (node.id === targetId) {
60
+ const children = node.children;
61
+ const idx = Math.max(0, Math.min(selectedIndex, children.length - 1));
62
+ if (children[idx]) {
63
+ children[idx].isSelected = true;
64
+ }
65
+ }
66
+ };
67
+ const root = appState.rootNode;
68
+ const breadCrumb = findBreadCrumb(root, currentNode.id);
69
+ setIsSelected(root, currentNode.id, selectedIndex);
70
+ patchState({
71
+ breadCrumb,
72
+ currentNode,
73
+ selectedIndex,
74
+ });
75
+ },
76
+ };
77
+ const navigateByOffset = (offset) => {
78
+ const len = appState.currentNode.children.length;
79
+ if (len === 0)
80
+ return;
81
+ const newIndex = (appState.selectedIndex + offset + len) % len;
82
+ navigator.navigate({ selectedIndex: newIndex });
83
+ };
84
+ const navigateToSiblingContainer = (direction) => {
85
+ if (!appState.currentNode.enableChildNavigationAcrossContainers)
86
+ return;
87
+ const currentNode = appState.breadCrumb.at(-1);
88
+ const parentNode = appState.breadCrumb.at(-2);
89
+ if (!currentNode || !parentNode)
90
+ return;
91
+ const siblings = parentNode.children ?? [];
92
+ const currentNodeIndex = siblings.findIndex(x => x.id === currentNode.id);
93
+ if (currentNodeIndex < 0)
94
+ return;
95
+ // Look for the next sibling "container" that actually has children
96
+ const candidates = direction === -1
97
+ ? siblings.slice(0, currentNodeIndex).toReversed()
98
+ : siblings.slice(currentNodeIndex + 1);
99
+ const nextSibling = candidates.find(x => (x.children?.length ?? 0) > 0);
100
+ if (!nextSibling?.children?.length)
101
+ return;
102
+ // Keep the same child index if possible, otherwise clamp to last child
103
+ const prevIndex = appState.selectedIndex;
104
+ const boundedIndex = Math.min(prevIndex, nextSibling.children.length - 1);
105
+ navigator.navigate({
106
+ currentNode: nextSibling,
107
+ selectedIndex: boundedIndex,
108
+ });
109
+ };
@@ -0,0 +1,2 @@
1
+ import { ActionEntry } from '../../model/action-map.model.js';
2
+ export declare const onConfirmCommandLineSequenceInput: (...args: Parameters<NonNullable<ActionEntry["action"]>>) => void;
@@ -0,0 +1,18 @@
1
+ import { getCommandIntent } from '../../command-line/command-line-sequence-intent.js';
2
+ import { commands } from '../../command-line/commands.js';
3
+ import { clearCommandLine, getCommandLineInput, updateCommandHistory, } from '../../state/command-line.state.js';
4
+ export const onConfirmCommandLineSequenceInput = (...args) => {
5
+ const commandSequence = getCommandLineInput();
6
+ const [ctx] = [...args];
7
+ const [firstItem, ...rest] = commandSequence.split(' ');
8
+ const command = (firstItem || '').trim();
9
+ const value = rest.join(' ').trim();
10
+ if (!command)
11
+ return;
12
+ const intent = getCommandIntent(command);
13
+ const actionMeta = commands.find(x => x.intent === intent);
14
+ actionMeta?.action?.(ctx, actionMeta, { command, value });
15
+ updateCommandHistory();
16
+ clearCommandLine();
17
+ return;
18
+ };
@@ -0,0 +1,8 @@
1
+ import { ActionEntry } from '../../model/action-map.model.js';
2
+ export declare const CommandLineIntent: {
3
+ readonly AddItem: "add-item";
4
+ readonly ViewHelp: "view-help";
5
+ };
6
+ export declare const getCommandLineIntent: (input: string) => "add-item" | "view-help" | undefined;
7
+ export declare const inputActions: ActionEntry[];
8
+ export declare const onConfirmCommandLineInput: (input: string) => void;
@@ -0,0 +1,47 @@
1
+ import { Mode } from '../../model/action-map.model.js';
2
+ import { patchState } from '../../state/state.js';
3
+ export const CommandLineIntent = {
4
+ AddItem: 'add-item',
5
+ ViewHelp: 'view-help',
6
+ };
7
+ export const getCommandLineIntent = (input) => {
8
+ switch (input) {
9
+ case 'help':
10
+ case 'h':
11
+ return CommandLineIntent.ViewHelp;
12
+ case 'add':
13
+ case 'a':
14
+ return CommandLineIntent.AddItem;
15
+ default:
16
+ return;
17
+ }
18
+ };
19
+ export const inputActions = [
20
+ {
21
+ intent: CommandLineIntent.ViewHelp,
22
+ mode: Mode.DEFAULT, // Make accept array
23
+ action: () => {
24
+ patchState({
25
+ mode: Mode.HELP,
26
+ commandLineInput: '',
27
+ });
28
+ },
29
+ },
30
+ ];
31
+ export const onConfirmCommandLineInput = (input) => {
32
+ switch (input.trim()) {
33
+ case 'help':
34
+ case 'h':
35
+ patchState({
36
+ mode: Mode.HELP,
37
+ commandLineInput: '',
38
+ });
39
+ return;
40
+ default:
41
+ patchState({
42
+ mode: Mode.DEFAULT,
43
+ commandLineInput: '',
44
+ });
45
+ return;
46
+ }
47
+ };
@@ -0,0 +1,2 @@
1
+ import { ActionEntry } from '../../model/action-map.model.js';
2
+ export declare const inputActions: ActionEntry[];