epiq 0.0.3 → 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 (54) hide show
  1. package/dist/app.js +4 -5
  2. package/dist/board/components/Board.js +2 -2
  3. package/dist/board/components/CommandLine.js +11 -4
  4. package/dist/board/components/ContextBar.js +12 -3
  5. package/dist/board/components/Help.js +4 -4
  6. package/dist/board/components/Swimlane.d.ts +1 -0
  7. package/dist/board/components/Swimlane.js +3 -4
  8. package/dist/board/components/Swimlanes.js +8 -11
  9. package/dist/board/components/TicketListItem.js +3 -3
  10. package/dist/board/components/TicketUI.js +1 -1
  11. package/dist/board/hints/hints.d.ts +4 -4
  12. package/dist/board/hints/hints.js +7 -15
  13. package/dist/board/mock/board.js +500 -523
  14. package/dist/cli.d.ts +1 -1
  15. package/dist/cli.js +20 -61
  16. package/dist/debug-logger.d.ts +1 -1
  17. package/dist/debug-logger.js +5 -4
  18. package/dist/init-project.d.ts +1 -0
  19. package/dist/init-project.js +1 -0
  20. package/dist/navigation/actions/add-item/add-item-actions.js +21 -11
  21. package/dist/navigation/actions/board-action-map.js +3 -3
  22. package/dist/navigation/actions/default/default-action-utils.d.ts +8 -7
  23. package/dist/navigation/actions/default/default-action-utils.js +67 -17
  24. package/dist/navigation/actions/default/default-actions.js +30 -44
  25. package/dist/navigation/actions/default/navigation-action-utils.d.ts +16 -0
  26. package/dist/navigation/actions/default/navigation-action-utils.js +109 -0
  27. package/dist/navigation/actions/input/command-line-input.js +4 -4
  28. package/dist/navigation/actions/input/input-actions.js +13 -12
  29. package/dist/navigation/actions/move/move-actions-utils.d.ts +4 -5
  30. package/dist/navigation/actions/move/move-actions-utils.js +44 -31
  31. package/dist/navigation/actions/move/move-actions.js +17 -11
  32. package/dist/navigation/command-line/command-line-sequence-actions.d.ts +1 -1
  33. package/dist/navigation/command-line/command-line-sequence-actions.js +24 -11
  34. package/dist/navigation/command-line/command-line-sequence-intent.d.ts +3 -2
  35. package/dist/navigation/command-line/command-line-sequence-intent.js +13 -10
  36. package/dist/navigation/command-line/commands.d.ts +2 -0
  37. package/dist/navigation/command-line/commands.js +58 -0
  38. package/dist/navigation/keypress-listener.d.ts +1 -0
  39. package/dist/navigation/keypress-listener.js +33 -0
  40. package/dist/navigation/model/action-map.model.d.ts +4 -4
  41. package/dist/navigation/model/navigation-ctx.model.d.ts +1 -16
  42. package/dist/navigation/model/navigation-ctx.model.js +0 -1
  43. package/dist/navigation/model/navigation-tree.model.d.ts +2 -1
  44. package/dist/navigation/navigation.d.ts +3 -10
  45. package/dist/navigation/navigation.js +73 -79
  46. package/dist/navigation/state/command-line.state.d.ts +5 -0
  47. package/dist/navigation/state/command-line.state.js +21 -4
  48. package/dist/navigation/state/state.d.ts +17 -9
  49. package/dist/navigation/state/state.js +34 -35
  50. package/dist/navigation/utils/get-command-line-intent.d.ts +2 -2
  51. package/dist/navigation/utils/get-command-line-intent.js +7 -7
  52. package/dist/navigation/utils/key-intent.d.ts +5 -5
  53. package/dist/navigation/utils/key-intent.js +68 -74
  54. package/package.json +2 -2
@@ -1,5 +1,4 @@
1
- import { NavigateCtx } from '../../model/navigation-ctx.model.js';
2
- export declare const moveChildToNextParent: (ctx: NavigateCtx) => void;
3
- export declare const moveChildToPreviousParent: (ctx: NavigateCtx) => void;
4
- export declare const moveChildPreviousWithinParent: (ctx: NavigateCtx) => void;
5
- export declare const moveChildNextWithinParent: (ctx: NavigateCtx) => void;
1
+ export declare const moveChildToNextParent: () => void;
2
+ export declare const moveChildToPreviousParent: () => void;
3
+ export declare const moveChildPreviousWithinParent: () => void;
4
+ export declare const moveChildNextWithinParent: () => void;
@@ -1,3 +1,5 @@
1
+ import { appState } from '../../state/state.js';
2
+ import { navigator } from '../default/navigation-action-utils.js';
1
3
  function moveItemInArray({ array, from, to, }) {
2
4
  if (from < 0 || from >= array.length || to < 0 || to >= array.length)
3
5
  return;
@@ -5,49 +7,60 @@ function moveItemInArray({ array, from, to, }) {
5
7
  if (item)
6
8
  array.splice(to, 0, item);
7
9
  }
8
- function moveNodeToSiblingContainer(ctx, direction) {
9
- const parent = ctx.breadCrumb.at(-1);
10
- const grandParent = ctx.breadCrumb.at(-2);
11
- if (!parent || !grandParent)
10
+ function moveNodeToSiblingContainer(direction) {
11
+ const currentNode = appState.breadCrumb.at(-1);
12
+ const parentNode = appState.breadCrumb.at(-2);
13
+ if (!currentNode || !parentNode)
12
14
  return;
13
- if (!Array.isArray(grandParent.children))
15
+ if (!Array.isArray(parentNode.children))
14
16
  return;
15
- const parentIndex = grandParent.children.findIndex(x => x.id === parent.id);
16
- const targetIndex = parentIndex + direction;
17
- if (parentIndex < 0 ||
18
- targetIndex < 0 ||
19
- targetIndex >= grandParent.children.length)
17
+ const currentNodeIndex = parentNode.children.findIndex(x => x.id === currentNode.id);
18
+ const targetNodeIndex = currentNodeIndex + direction;
19
+ if (currentNodeIndex < 0 ||
20
+ targetNodeIndex < 0 ||
21
+ targetNodeIndex >= parentNode.children.length)
20
22
  return;
21
- const sibling = grandParent.children[targetIndex];
22
- if (!sibling)
23
+ const siblingNode = parentNode.children[targetNodeIndex];
24
+ if (!siblingNode)
23
25
  return;
24
- if (!Array.isArray(parent.children))
26
+ if (!Array.isArray(currentNode.children))
25
27
  return;
26
- if (!Array.isArray(sibling.children))
27
- sibling.children = []; // normalize empty container
28
- const currentIndex = ctx.getSelectedIndex();
29
- if (currentIndex < 0 || currentIndex >= parent.children.length)
28
+ if (!Array.isArray(siblingNode.children))
29
+ siblingNode.children = []; // normalize empty container
30
+ const currentSelectionIndex = appState.selectedIndex;
31
+ if (currentSelectionIndex < 0 ||
32
+ currentSelectionIndex >= currentNode.children.length)
30
33
  return;
31
- const [node] = parent.children.splice(currentIndex, 1);
32
- if (!node)
34
+ const [moveNode] = currentNode.children.splice(currentSelectionIndex, 1);
35
+ if (!moveNode)
33
36
  return;
34
- sibling.children.push(node);
35
- const newBreadCrumb = [...ctx.breadCrumb.slice(0, -1), sibling];
36
- ctx.reInvokeNavigate(sibling.children.length - 1, newBreadCrumb);
37
+ siblingNode.children.push(moveNode);
38
+ navigator.navigate({
39
+ selectedIndex: siblingNode.children.length - 1,
40
+ currentNode: siblingNode,
41
+ });
37
42
  }
38
- export const moveChildToNextParent = (ctx) => moveNodeToSiblingContainer(ctx, 1);
39
- export const moveChildToPreviousParent = (ctx) => moveNodeToSiblingContainer(ctx, -1);
40
- function moveChildWithinParent(ctx, direction) {
41
- const from = ctx._selectionIndex;
43
+ export const moveChildToNextParent = () => {
44
+ moveNodeToSiblingContainer(1);
45
+ };
46
+ export const moveChildToPreviousParent = () => {
47
+ moveNodeToSiblingContainer(-1);
48
+ };
49
+ function moveChildWithinParent(direction) {
50
+ const from = appState.selectedIndex;
42
51
  const to = from + direction;
43
- if (to < 0 || to >= ctx.navigationNode.children.length)
52
+ if (to < 0 || to >= appState.currentNode.children.length)
44
53
  return;
45
54
  moveItemInArray({
46
- array: ctx.navigationNode.children,
55
+ array: appState.currentNode.children,
47
56
  from,
48
57
  to,
49
58
  });
50
- ctx.updateSelection(to);
59
+ navigator.navigate({ selectedIndex: to });
51
60
  }
52
- export const moveChildPreviousWithinParent = (ctx) => moveChildWithinParent(ctx, -1);
53
- export const moveChildNextWithinParent = (ctx) => moveChildWithinParent(ctx, 1);
61
+ export const moveChildPreviousWithinParent = () => {
62
+ moveChildWithinParent(-1);
63
+ };
64
+ export const moveChildNextWithinParent = () => {
65
+ moveChildWithinParent(1);
66
+ };
@@ -1,10 +1,20 @@
1
1
  import { Mode } from '../../model/action-map.model.js';
2
2
  import { patchState } from '../../state/state.js';
3
- import { KeyIntent } from '../../utils/key-intent.js';
3
+ import { Intent } from '../../utils/key-intent.js';
4
4
  import { moveChildNextWithinParent, moveChildPreviousWithinParent, moveChildToNextParent, moveChildToPreviousParent, } from './move-actions-utils.js';
5
5
  export const toggleMoveMode = [
6
6
  {
7
- intent: KeyIntent.ToggleMove,
7
+ intent: Intent.Exit,
8
+ mode: Mode.MOVE,
9
+ description: '[Y] Exit yank mode',
10
+ action: () => {
11
+ patchState({
12
+ mode: Mode.DEFAULT,
13
+ });
14
+ },
15
+ },
16
+ {
17
+ intent: Intent.InitMove,
8
18
  mode: Mode.DEFAULT,
9
19
  description: '[Y] Toggle move/yank mode',
10
20
  action: () => {
@@ -14,7 +24,7 @@ export const toggleMoveMode = [
14
24
  },
15
25
  },
16
26
  {
17
- intent: KeyIntent.ToggleMove,
27
+ intent: Intent.InitMove, // Change name to toggle move?
18
28
  mode: Mode.MOVE,
19
29
  action: () => {
20
30
  patchState({
@@ -25,28 +35,24 @@ export const toggleMoveMode = [
25
35
  ];
26
36
  export const moveWithinParent = [
27
37
  {
28
- mode: Mode.MOVE,
29
- description: '[Y, ARROW KEYS] Move item',
30
- },
31
- {
32
- intent: KeyIntent.MovePreviousItem,
38
+ intent: Intent.MovePreviousItem,
33
39
  mode: Mode.MOVE,
34
40
  action: moveChildPreviousWithinParent,
35
41
  },
36
42
  {
37
- intent: KeyIntent.MoveNextItem,
43
+ intent: Intent.MoveNextItem,
38
44
  mode: Mode.MOVE,
39
45
  action: moveChildNextWithinParent,
40
46
  },
41
47
  ];
42
48
  export const moveAcrossParents = [
43
49
  {
44
- intent: KeyIntent.MoveToNextContainer,
50
+ intent: Intent.MoveToNextContainer,
45
51
  mode: Mode.MOVE,
46
52
  action: moveChildToNextParent,
47
53
  },
48
54
  {
49
- intent: KeyIntent.MoveToPreviousContainer,
55
+ intent: Intent.MoveToPreviousContainer,
50
56
  mode: Mode.MOVE,
51
57
  action: moveChildToPreviousParent,
52
58
  },
@@ -1,2 +1,2 @@
1
1
  import { CommandLineActionEntry } from '../model/action-map.model.js';
2
- export declare const commandLineSequenceActions: CommandLineActionEntry[];
2
+ export declare const commands: CommandLineActionEntry[];
@@ -1,19 +1,16 @@
1
1
  import { addSwimlaneAction, addTicketAction, } from '../actions/add-item/add-item-actions.js';
2
2
  import { Mode } from '../model/action-map.model.js';
3
- import { patchState } from '../state/state.js';
4
- import { CommandLineSequenceIntent } from './command-line-sequence-intent.js';
5
- export const commandLineSequenceActions = [
3
+ import { getCommandLineArgumentValue } from '../state/command-line.state.js';
4
+ import { patchState, updateState } from '../state/state.js';
5
+ import { CmdIntent } from './command-line-sequence-intent.js';
6
+ export const commands = [
6
7
  {
7
- intent: CommandLineSequenceIntent.ViewHelp,
8
+ intent: CmdIntent.ViewHelp,
8
9
  mode: Mode.COMMAND_LINE,
9
- action: () => {
10
- patchState({
11
- mode: Mode.HELP,
12
- });
13
- },
10
+ action: () => patchState({ mode: Mode.HELP }),
14
11
  },
15
12
  {
16
- intent: CommandLineSequenceIntent.AddSwimlane,
13
+ intent: CmdIntent.AddSwimlane,
17
14
  mode: Mode.COMMAND_LINE,
18
15
  action: (...args) => {
19
16
  addSwimlaneAction(...args);
@@ -21,11 +18,27 @@ export const commandLineSequenceActions = [
21
18
  },
22
19
  },
23
20
  {
24
- intent: CommandLineSequenceIntent.AddTicket,
21
+ intent: CmdIntent.AddTicket,
25
22
  mode: Mode.COMMAND_LINE,
26
23
  action: (...args) => {
27
24
  addTicketAction(...args);
28
25
  patchState({ mode: Mode.DEFAULT });
29
26
  },
30
27
  },
28
+ {
29
+ intent: CmdIntent.Rename,
30
+ mode: Mode.COMMAND_LINE,
31
+ action: () => {
32
+ updateState(s => ({
33
+ ...s,
34
+ mode: Mode.DEFAULT,
35
+ currentNode: s.currentNode
36
+ ? {
37
+ ...s.currentNode,
38
+ name: getCommandLineArgumentValue(),
39
+ }
40
+ : null,
41
+ }));
42
+ },
43
+ },
31
44
  ];
@@ -1,7 +1,8 @@
1
- export declare const CommandLineSequenceIntent: {
1
+ export declare const CmdIntent: {
2
2
  readonly None: "none";
3
3
  readonly AddSwimlane: "add-swimlane";
4
4
  readonly AddTicket: "add-ticket";
5
5
  readonly ViewHelp: "view-help";
6
+ readonly Rename: "rename";
6
7
  };
7
- export declare const getCommandLineIntent: (command: string) => (typeof CommandLineSequenceIntent)[keyof typeof CommandLineSequenceIntent];
8
+ export declare const getCommandIntent: (command: string) => (typeof CmdIntent)[keyof typeof CmdIntent];
@@ -1,29 +1,32 @@
1
1
  import { BoardItemTypes } from '../../board/model/board.model.js';
2
- import { navigationState } from '../state/state.js';
3
- export const CommandLineSequenceIntent = {
2
+ import { appState } from '../state/state.js';
3
+ export const CmdIntent = {
4
4
  None: 'none',
5
5
  AddSwimlane: 'add-swimlane',
6
6
  AddTicket: 'add-ticket',
7
7
  ViewHelp: 'view-help',
8
+ Rename: 'rename',
8
9
  };
9
- export const getCommandLineIntent = (command) => {
10
- const actionContext = navigationState?.currentNode?.actionContext;
10
+ export const getCommandIntent = (command) => {
11
+ const actionContext = appState?.currentNode?.actionContext;
11
12
  if (!actionContext)
12
- return CommandLineSequenceIntent.None;
13
+ return CmdIntent.None;
13
14
  switch (command) {
14
15
  case 'help':
15
16
  case 'he':
16
- return CommandLineSequenceIntent.ViewHelp;
17
+ return CmdIntent.ViewHelp;
18
+ case 'rename':
19
+ return CmdIntent.Rename;
17
20
  case 'add':
18
21
  switch (actionContext) {
19
22
  case BoardItemTypes.SWIMLANE:
20
- return CommandLineSequenceIntent.AddSwimlane;
23
+ return CmdIntent.AddSwimlane;
21
24
  case BoardItemTypes.TICKET:
22
- return CommandLineSequenceIntent.AddTicket;
25
+ return CmdIntent.AddTicket;
23
26
  default:
24
- return CommandLineSequenceIntent.None;
27
+ return CmdIntent.None;
25
28
  }
26
29
  default:
27
- return CommandLineSequenceIntent.None;
30
+ return CmdIntent.None;
28
31
  }
29
32
  };
@@ -0,0 +1,2 @@
1
+ import { CommandLineActionEntry } from '../model/action-map.model.js';
2
+ export declare const commands: CommandLineActionEntry[];
@@ -0,0 +1,58 @@
1
+ import { addSwimlaneAction, addTicketAction, } from '../actions/add-item/add-item-actions.js';
2
+ import { Mode } from '../model/action-map.model.js';
3
+ import { getCommandLineArgumentValue } from '../state/command-line.state.js';
4
+ import { appState, patchState, updateState } from '../state/state.js';
5
+ import { CmdIntent } from './command-line-sequence-intent.js';
6
+ export const commands = [
7
+ {
8
+ intent: CmdIntent.ViewHelp,
9
+ mode: Mode.COMMAND_LINE,
10
+ action: () => patchState({ mode: Mode.HELP }),
11
+ },
12
+ {
13
+ intent: CmdIntent.AddSwimlane,
14
+ mode: Mode.COMMAND_LINE,
15
+ action: (...args) => {
16
+ addSwimlaneAction(...args);
17
+ patchState({ mode: Mode.DEFAULT });
18
+ },
19
+ },
20
+ {
21
+ intent: CmdIntent.AddTicket,
22
+ mode: Mode.COMMAND_LINE,
23
+ action: (...args) => {
24
+ addTicketAction(...args);
25
+ patchState({ mode: Mode.DEFAULT });
26
+ },
27
+ },
28
+ {
29
+ intent: CmdIntent.Rename,
30
+ mode: Mode.COMMAND_LINE,
31
+ action: () => {
32
+ updateState(s => {
33
+ // Update board on file
34
+ const newBoard = appState.rootNode;
35
+ const currentId = s.currentNode?.id;
36
+ if (!currentId)
37
+ return s;
38
+ const findItemInBoard = (item, id) => {
39
+ if (item.id === id)
40
+ return item;
41
+ return (item.children.find((child) => findItemInBoard(child, id)) ||
42
+ undefined);
43
+ };
44
+ const newName = getCommandLineArgumentValue();
45
+ const itemInBoard = findItemInBoard(newBoard, currentId);
46
+ if (!itemInBoard)
47
+ return s;
48
+ itemInBoard.name = newName;
49
+ return {
50
+ ...s,
51
+ mode: Mode.DEFAULT,
52
+ rootNode: newBoard,
53
+ currentNode: itemInBoard,
54
+ };
55
+ });
56
+ },
57
+ },
58
+ ];
@@ -0,0 +1 @@
1
+ export declare function initListeners(): void;
@@ -0,0 +1,33 @@
1
+ import readline from 'readline';
2
+ import { appState } from './state/state.js';
3
+ import { getKeyIntent } from './utils/key-intent.js';
4
+ import { navigator } from './actions/default/navigation-action-utils.js';
5
+ let currentKeypressListener;
6
+ const getKeyPressListener = () => {
7
+ return async function onKeyPress(_, key) {
8
+ if (key.ctrl && key.name === 'c')
9
+ return navigator.exit();
10
+ const filteredActions = appState.availableActions.filter(a => a.mode === appState.mode);
11
+ const actionMeta = filteredActions.find(({ intent, mode }) => getKeyIntent(key, mode) === intent);
12
+ if (!actionMeta?.action)
13
+ return;
14
+ try {
15
+ const res = actionMeta.action(navigator, actionMeta, key);
16
+ if (res instanceof Promise)
17
+ await res;
18
+ }
19
+ catch (err) {
20
+ console.error(err);
21
+ }
22
+ };
23
+ };
24
+ export function initListeners() {
25
+ if (currentKeypressListener) {
26
+ process.stdin.removeListener('keypress', currentKeypressListener);
27
+ }
28
+ currentKeypressListener = getKeyPressListener();
29
+ readline.emitKeypressEvents(process.stdin);
30
+ if (process.stdin.isTTY)
31
+ process.stdin.setRawMode(true);
32
+ process.stdin.on('keypress', currentKeypressListener);
33
+ }
@@ -1,4 +1,4 @@
1
- import { NavigateCtx } from './navigation-ctx.model.js';
1
+ import { Navigator } from '../actions/default/navigation-action-utils.js';
2
2
  import readline from 'readline';
3
3
  export declare const Mode: {
4
4
  readonly DEFAULT: "default";
@@ -9,9 +9,9 @@ export declare const Mode: {
9
9
  export type ModeUnion = (typeof Mode)[keyof typeof Mode];
10
10
  export type ActionEntry = {
11
11
  intent?: string;
12
- mode: ModeUnion | ModeUnion[];
12
+ mode: ModeUnion;
13
13
  description?: `[${string}] ${string}`;
14
- action?: (...args: [NavigateCtx, ActionEntry, readline.Key]) => void | Promise<void>;
14
+ action?: (...args: [Navigator, ActionEntry, readline.Key]) => void | Promise<void>;
15
15
  };
16
16
  export type ActionMap<T extends Record<string, any[]>> = {
17
17
  [K in keyof T]: ActionEntry[];
@@ -21,6 +21,6 @@ type CommandLineInput = {
21
21
  command: string;
22
22
  };
23
23
  export type CommandLineActionEntry = Omit<ActionEntry, 'action'> & {
24
- action?: (...args: [NavigateCtx, CommandLineActionEntry, CommandLineInput]) => void | Promise<void>;
24
+ action?: (...args: [Navigator, CommandLineActionEntry, CommandLineInput]) => void | Promise<void>;
25
25
  };
26
26
  export {};
@@ -1,16 +1 @@
1
- import { NavigationTree } from './navigation-tree.model.js';
2
- export interface NavigateCtx {
3
- _selectionIndex: number;
4
- breadCrumb: Array<NavigationTree<NavigationTree>>;
5
- navigationNode: NavigationTree<NavigationTree>;
6
- children: NavigationTree[];
7
- getSelectedIndex(): number;
8
- updateSelection(index: number): void;
9
- selectNone(): void;
10
- reInvokeNavigate(index: number, breadCrumb: NavigationTree<NavigationTree>[]): void;
11
- render(): void;
12
- confirm(selected: NavigationTree<NavigationTree>): void;
13
- exit(): void;
14
- enterChildNode(node: NavigationTree<NavigationTree>): void;
15
- enterParentNode(): void;
16
- }
1
+ export {};
@@ -1,2 +1 @@
1
- // navigation-context.ts
2
1
  export {};
@@ -1,3 +1,4 @@
1
+ import { BoardItemTypes } from '../../board/model/board.model.js';
1
2
  export type NavigationTree<TMeta = Record<string, unknown>> = {
2
3
  id: string;
3
4
  isSelected: boolean;
@@ -5,6 +6,6 @@ export type NavigationTree<TMeta = Record<string, unknown>> = {
5
6
  name: string;
6
7
  description?: string;
7
8
  children: NavigationTree<unknown>[];
8
- actionContext: string;
9
+ actionContext: (typeof BoardItemTypes)[keyof typeof BoardItemTypes];
9
10
  enableChildNavigationAcrossContainers?: boolean;
10
11
  } & TMeta;
@@ -1,10 +1,3 @@
1
- import { NavigationTree } from './model/navigation-tree.model.js';
2
- export declare function navigate<T extends NavigationTree>({ index, breadCrumb, callbacks, }: {
3
- index: number;
4
- breadCrumb: Array<NavigationTree<NavigationTree>>;
5
- callbacks: Partial<{
6
- render: () => void;
7
- onSelectChange: (s: T['children'][number] | undefined, breadCrumb: T['children']) => void;
8
- onConfirm: (s: T['children'][number]) => void;
9
- }>;
10
- }): void;
1
+ import { NavigateUtils } from './model/navigation-ctx.model.js';
2
+ export declare const navigationUtils: NavigateUtils;
3
+ export declare function initListeners(): void;
@@ -1,88 +1,82 @@
1
1
  import readline from 'readline';
2
- import { navigationState } from './state/state.js';
2
+ import { appState, patchState, updateState } from './state/state.js';
3
3
  import { getKeyIntent } from './utils/key-intent.js';
4
- export function navigate({ index = 0, breadCrumb, callbacks, }) {
5
- const ctx = {
6
- _selectionIndex: 0,
7
- breadCrumb: breadCrumb,
8
- render() {
9
- callbacks.render?.();
10
- },
11
- get navigationNode() {
12
- return this.breadCrumb?.at(-1) ?? breadCrumb[0];
13
- },
14
- get children() {
15
- return this.navigationNode.children ?? [];
16
- },
17
- selectNone() {
18
- ctx.updateSelection(-1);
19
- },
20
- getSelectedIndex() {
21
- return this._selectionIndex;
22
- },
23
- updateSelection(idx) {
24
- this._selectionIndex = idx;
25
- const children = this.navigationNode.children ?? [];
26
- children.forEach((c, i) => (c.isSelected = i === idx));
27
- if (idx < 0 || idx >= children.length) {
28
- callbacks.onSelectChange?.(undefined, breadCrumb);
29
- return;
30
- }
31
- callbacks.onSelectChange?.(children[idx], breadCrumb);
32
- },
33
- reInvokeNavigate(idx, crumb) {
34
- cleanup();
35
- navigate({ index: idx, breadCrumb: [...crumb], callbacks });
36
- },
37
- confirm(sel) {
38
- return callbacks.onConfirm?.(sel);
39
- },
40
- exit() {
41
- cleanup();
42
- process.exit(0);
43
- },
44
- enterChildNode(childNode) {
45
- this.updateSelection(-1); // Clear all on this level
46
- this.reInvokeNavigate(0, [...breadCrumb, childNode]);
47
- },
48
- enterParentNode() {
49
- ctx.updateSelection(-1); // Clear all on this level
50
- if (breadCrumb.length < 2)
51
- return; // Need at least grandparent + parent
52
- const ancestors = breadCrumb;
53
- const parent = ancestors[ancestors.length - 1];
54
- const grandParent = ancestors[ancestors.length - 2];
55
- if (!parent || !grandParent)
56
- return;
57
- const parentIndex = grandParent?.children.findIndex(x => parent.id === x.id);
58
- this.reInvokeNavigate(parentIndex, breadCrumb.slice(0, -1)); // Go to parent level
59
- },
60
- };
61
- ctx.updateSelection(index);
62
- async function onKeyPress(_, key) {
63
- if (key.ctrl && key.name === 'c')
64
- return ctx.exit();
65
- const filteredActions = navigationState.availableActions.filter(x => x.mode === navigationState.mode);
66
- const actionMeta = filteredActions.find(actionMetaItem => {
67
- const intent = getKeyIntent(key, ctx, navigationState.mode);
68
- return intent === actionMetaItem.intent;
4
+ let currentKeypressListener;
5
+ export const navigationUtils = {
6
+ updateSelection: (idx) => {
7
+ updateState(oldState => {
8
+ const currentNode = oldState.breadCrumb.at(-1) ?? oldState.breadCrumb[0];
9
+ if (!currentNode)
10
+ return oldState;
11
+ currentNode.children?.forEach((child, i) => (child.isSelected = i === idx));
12
+ return {
13
+ ...oldState,
14
+ selectedIndex: idx,
15
+ currentNode: currentNode, // keep currentNode in sync
16
+ breadCrumb: [...oldState.breadCrumb], // keep reference fresh if you rely on it
17
+ };
18
+ });
19
+ },
20
+ selectNone() {
21
+ this.updateSelection(-1);
22
+ },
23
+ confirm(sel) {
24
+ console.log(sel);
25
+ },
26
+ exit() {
27
+ process.exit(0);
28
+ },
29
+ enterChildNode(childNode) {
30
+ patchState({
31
+ breadCrumb: [...appState.breadCrumb, childNode],
32
+ currentNode: childNode,
33
+ selectedIndex: 0,
34
+ });
35
+ this.updateSelection(0);
36
+ },
37
+ enterParentNode() {
38
+ const crumb = appState.breadCrumb;
39
+ if (crumb.length < 2)
40
+ return;
41
+ const newCrumb = crumb.slice(0, -1);
42
+ const parentLevelNode = newCrumb.at(-1);
43
+ // find old parent index inside grandparent
44
+ const grandParent = newCrumb.at(-2);
45
+ const parentIndex = grandParent?.children?.findIndex(x => x.id === parentLevelNode.id) ?? 0;
46
+ patchState({
47
+ breadCrumb: newCrumb,
48
+ currentNode: parentLevelNode,
49
+ selectedIndex: parentIndex,
69
50
  });
70
- if (actionMeta?.action) {
71
- try {
72
- const res = actionMeta.action(ctx, actionMeta, key);
73
- if (res instanceof Promise)
74
- await res;
75
- }
76
- catch (err) {
77
- console.error(err);
78
- }
51
+ this.updateSelection(parentIndex);
52
+ },
53
+ };
54
+ const getKeyPressListener = () => {
55
+ navigationUtils.updateSelection(appState.selectedIndex ?? 0);
56
+ return async function onKeyPress(_, key) {
57
+ if (key.ctrl && key.name === 'c')
58
+ return navigationUtils.exit();
59
+ const filteredActions = appState.availableActions.filter(a => a.mode === appState.mode);
60
+ const actionMeta = filteredActions.find(({ intent, mode }) => getKeyIntent(key, mode) === intent);
61
+ if (!actionMeta?.action)
62
+ return;
63
+ try {
64
+ const res = actionMeta.action(navigationUtils, actionMeta, key);
65
+ if (res instanceof Promise)
66
+ await res;
67
+ }
68
+ catch (err) {
69
+ console.error(err);
79
70
  }
80
- ctx.render();
71
+ };
72
+ };
73
+ export function initListeners() {
74
+ if (currentKeypressListener) {
75
+ process.stdin.removeListener('keypress', currentKeypressListener);
81
76
  }
82
- const cleanup = () => process.stdin.removeListener('keypress', onKeyPress);
83
- callbacks.render?.();
77
+ currentKeypressListener = getKeyPressListener();
84
78
  readline.emitKeypressEvents(process.stdin);
85
79
  if (process.stdin.isTTY)
86
80
  process.stdin.setRawMode(true);
87
- process.stdin.on('keypress', onKeyPress);
81
+ process.stdin.on('keypress', currentKeypressListener);
88
82
  }
@@ -1,9 +1,12 @@
1
+ export declare const commandDelimiter = " ";
1
2
  export type CommandLineState = {
2
3
  value: string;
3
4
  commandHistory: string[];
4
5
  commandHistoryIndex: number;
5
6
  };
6
7
  export declare let commandLineState: CommandLineState;
8
+ type Listener = () => void;
9
+ export declare const subscribeCommandLineState: (listener: Listener) => () => boolean;
7
10
  export declare const updateCommandLineState: (cb: (old: CommandLineState) => CommandLineState) => void;
8
11
  export declare const updateCommandLineInput: (cb: (previous: string) => string) => void;
9
12
  export declare const updateCommandHistory: () => void;
@@ -11,3 +14,5 @@ export declare const getPrevCommand: () => void;
11
14
  export declare const getNextCommand: () => void;
12
15
  export declare const clearCommandLine: () => void;
13
16
  export declare const getCommandLineInput: () => string;
17
+ export declare const getCommandLineArgumentValue: () => string;
18
+ export {};