@mak-98/dirvi-cli 0.4.0 → 0.5.0

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 (92) hide show
  1. package/dist/application/effect-to-action.d.ts +5 -7
  2. package/dist/application/effect-to-action.d.ts.map +1 -1
  3. package/dist/application/effect-to-action.js +81 -86
  4. package/dist/application/reducer-action.d.ts +23 -23
  5. package/dist/application/reducer-action.d.ts.map +1 -1
  6. package/dist/application/reducer.d.ts +4 -3
  7. package/dist/application/reducer.d.ts.map +1 -1
  8. package/dist/application/reducer.js +67 -42
  9. package/dist/domain/app-api.d.ts +47 -0
  10. package/dist/domain/app-api.d.ts.map +1 -0
  11. package/dist/domain/app-api.js +21 -0
  12. package/dist/domain/event-message.d.ts +12 -15
  13. package/dist/domain/event-message.d.ts.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +40 -42
  16. package/dist/infrastructure/get-dir-entries.d.ts +3 -0
  17. package/dist/infrastructure/get-dir-entries.d.ts.map +1 -0
  18. package/dist/infrastructure/get-dir-entries.js +33 -0
  19. package/dist/infrastructure/get-dir-lazy-entries.d.ts +3 -0
  20. package/dist/infrastructure/get-dir-lazy-entries.d.ts.map +1 -0
  21. package/dist/infrastructure/get-dir-lazy-entries.js +33 -0
  22. package/dist/infrastructure/get-unix-abs-path.d.ts +3 -0
  23. package/dist/infrastructure/get-unix-abs-path.d.ts.map +1 -0
  24. package/dist/infrastructure/get-unix-abs-path.js +14 -0
  25. package/dist/infrastructure/ink-input-to-user-input.d.ts +2 -5
  26. package/dist/infrastructure/ink-input-to-user-input.js +42 -42
  27. package/dist/infrastructure/load-posix-app-api.d.ts +4 -0
  28. package/dist/infrastructure/load-posix-app-api.d.ts.map +1 -0
  29. package/dist/infrastructure/load-posix-app-api.js +82 -0
  30. package/dist/ui/App.d.ts +16 -15
  31. package/dist/ui/App.d.ts.map +1 -1
  32. package/dist/ui/App.js +127 -128
  33. package/dist/ui/AppShell.d.ts +6 -16
  34. package/dist/ui/AppShell.d.ts.map +1 -1
  35. package/dist/ui/AppShell.js +22 -13
  36. package/dist/ui/LoadingApp.d.ts +11 -0
  37. package/dist/ui/LoadingApp.d.ts.map +1 -0
  38. package/dist/ui/LoadingApp.js +67 -0
  39. package/dist/ui/components/StatusBar.d.ts +3 -5
  40. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  41. package/dist/ui/components/StatusBar.js +3 -28
  42. package/dist/ui/components/ViewRowComponent.d.ts +4 -6
  43. package/dist/ui/components/ViewRowComponent.js +10 -30
  44. package/dist/ui/hooks/useView.d.ts +4 -7
  45. package/dist/ui/hooks/useView.d.ts.map +1 -1
  46. package/dist/ui/hooks/useView.js +20 -29
  47. package/dist/ui/view.d.ts +13 -17
  48. package/dist/ui/view.d.ts.map +1 -1
  49. package/dist/ui/view.js +84 -64
  50. package/package.json +2 -2
  51. package/dist/application/action.d.ts +0 -26
  52. package/dist/application/action.d.ts.map +0 -1
  53. package/dist/application/action.js +0 -1
  54. package/dist/application/dir-buffer-helpers.d.ts +0 -6
  55. package/dist/application/dir-buffer-helpers.d.ts.map +0 -1
  56. package/dist/application/dir-buffer-helpers.js +0 -21
  57. package/dist/application/display-rows.d.ts +0 -11
  58. package/dist/application/display-rows.d.ts.map +0 -1
  59. package/dist/application/display-rows.js +0 -63
  60. package/dist/application/fold-helpers.d.ts +0 -24
  61. package/dist/application/fold-helpers.d.ts.map +0 -1
  62. package/dist/application/fold-helpers.js +0 -47
  63. package/dist/application/intent-to-effect.d.ts +0 -59
  64. package/dist/application/intent-to-effect.d.ts.map +0 -1
  65. package/dist/application/intent-to-effect.js +0 -131
  66. package/dist/application/user-input-to-intent.d.ts +0 -39
  67. package/dist/application/user-input-to-intent.d.ts.map +0 -1
  68. package/dist/application/user-input-to-intent.js +0 -110
  69. package/dist/domain/print-message.d.ts +0 -11
  70. package/dist/domain/print-message.d.ts.map +0 -1
  71. package/dist/domain/print-message.js +0 -1
  72. package/dist/infrastructure/input.d.ts +0 -12
  73. package/dist/infrastructure/input.d.ts.map +0 -1
  74. package/dist/infrastructure/input.js +0 -81
  75. package/dist/infrastructure/load-initial-props.d.ts +0 -3
  76. package/dist/infrastructure/load-initial-props.d.ts.map +0 -1
  77. package/dist/infrastructure/load-initial-props.js +0 -36
  78. package/dist/infrastructure/user-input.d.ts +0 -32
  79. package/dist/infrastructure/user-input.d.ts.map +0 -1
  80. package/dist/infrastructure/user-input.js +0 -45
  81. package/dist/ui/App2.d.ts +0 -16
  82. package/dist/ui/App2.d.ts.map +0 -1
  83. package/dist/ui/App2.js +0 -107
  84. package/dist/ui/components/DirEntries.d.ts +0 -11
  85. package/dist/ui/components/DirEntries.d.ts.map +0 -1
  86. package/dist/ui/components/DirEntries.js +0 -64
  87. package/dist/ui/components/UnixEntry.d.ts +0 -9
  88. package/dist/ui/components/UnixEntry.d.ts.map +0 -1
  89. package/dist/ui/components/UnixEntry.js +0 -19
  90. package/dist/ui/components/UnixEntryComponent.d.ts +0 -9
  91. package/dist/ui/components/UnixEntryComponent.d.ts.map +0 -1
  92. package/dist/ui/components/UnixEntryComponent.js +0 -19
@@ -0,0 +1,3 @@
1
+ import type { UnixAbsolutePath, PosixNode } from 'dirvi-lib';
2
+ export declare function getDirLazyEntries(address: UnixAbsolutePath): Promise<PosixNode[]>;
3
+ //# sourceMappingURL=get-dir-lazy-entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-dir-lazy-entries.d.ts","sourceRoot":"","sources":["../../src/infrastructure/get-dir-lazy-entries.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG7D,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,SAAS,EAAE,CAAC,CAyCtB"}
@@ -0,0 +1,33 @@
1
+ import { readdir, readlink } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { UnixPathSchema, PosixNameSchema } from 'dirvi-lib';
4
+ export async function getDirLazyEntries(address) {
5
+ const directoryEntries = await readdir(address, {
6
+ withFileTypes: true,
7
+ });
8
+ return Promise.all(directoryEntries.map(async (directoryEntry) => {
9
+ const name = PosixNameSchema.parse(directoryEntry.name);
10
+ if (directoryEntry.isSymbolicLink()) {
11
+ const target = UnixPathSchema.parse(await readlink(join(address, directoryEntry.name)));
12
+ return {
13
+ kind: 'symlink',
14
+ name,
15
+ target,
16
+ };
17
+ }
18
+ if (directoryEntry.isDirectory()) {
19
+ return {
20
+ kind: 'directory',
21
+ name,
22
+ branches: null, // Directory not expanded.
23
+ };
24
+ }
25
+ if (directoryEntry.isFile()) {
26
+ return {
27
+ kind: 'file',
28
+ name,
29
+ };
30
+ }
31
+ throw new Error(`Unsupported filesystem entry "${join(address, directoryEntry.name)}"`);
32
+ }));
33
+ }
@@ -0,0 +1,3 @@
1
+ import type { UnixAbsolutePath } from 'dirvi-lib';
2
+ export declare function getUnixAbsPath(directory: string): UnixAbsolutePath;
3
+ //# sourceMappingURL=get-unix-abs-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-unix-abs-path.d.ts","sourceRoot":"","sources":["../../src/infrastructure/get-unix-abs-path.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAgBlE"}
@@ -0,0 +1,14 @@
1
+ import { resolve } from 'node:path';
2
+ import { UnixAbsolutePathSchema } from 'dirvi-lib';
3
+ export function getUnixAbsPath(directory) {
4
+ const absolutePath = resolve(directory);
5
+ const result = UnixAbsolutePathSchema.safeParse(absolutePath);
6
+ if (!result.success) {
7
+ const details = result.error.issues
8
+ .map((issue) => issue.message)
9
+ .join('; ');
10
+ throw new Error(`Bad path for directory "${directory}" ` +
11
+ `(resolved to "${absolutePath}"): ${details}`);
12
+ }
13
+ return result.data;
14
+ }
@@ -1,7 +1,4 @@
1
1
  import type { Key } from 'ink';
2
2
  import { UserInput } from 'dirvi-lib';
3
- export declare function inkInputToUserInput(
4
- input: string,
5
- key: Key,
6
- ): UserInput | undefined;
7
- //# sourceMappingURL=ink-input-to-user-input.d.ts.map
3
+ export declare function inkInputToUserInput(input: string, key: Key): UserInput | undefined;
4
+ //# sourceMappingURL=ink-input-to-user-input.d.ts.map
@@ -1,45 +1,45 @@
1
1
  // Interprets raw user input to typed UserInput.
2
2
  export function inkInputToUserInput(input, key) {
3
- if (key.escape) {
4
- return {
5
- userInputType: 'esc',
6
- };
7
- }
8
- if (key.return) {
9
- return {
10
- userInputType: 'enter',
11
- };
12
- }
13
- if (key.backspace) {
14
- return {
15
- userInputType: 'backspace',
16
- };
17
- }
18
- if (key.rightArrow) {
19
- return {
20
- userInputType: 'rightArrow',
21
- };
22
- }
23
- if (key.leftArrow) {
24
- return {
25
- userInputType: 'leftArrow',
26
- };
27
- }
28
- if (key.upArrow) {
29
- return {
30
- userInputType: 'upArrow',
31
- };
32
- }
33
- if (key.downArrow) {
34
- return {
35
- userInputType: 'downArrow',
36
- };
37
- }
38
- if (input !== '') {
39
- return {
40
- userInputType: 'character',
41
- string: input,
42
- };
43
- }
44
- return undefined;
3
+ if (key.escape) {
4
+ return {
5
+ userInputType: 'esc',
6
+ };
7
+ }
8
+ if (key.return) {
9
+ return {
10
+ userInputType: 'enter',
11
+ };
12
+ }
13
+ if (key.backspace) {
14
+ return {
15
+ userInputType: 'backspace',
16
+ };
17
+ }
18
+ if (key.rightArrow) {
19
+ return {
20
+ userInputType: 'rightArrow',
21
+ };
22
+ }
23
+ if (key.leftArrow) {
24
+ return {
25
+ userInputType: 'leftArrow',
26
+ };
27
+ }
28
+ if (key.upArrow) {
29
+ return {
30
+ userInputType: 'upArrow',
31
+ };
32
+ }
33
+ if (key.downArrow) {
34
+ return {
35
+ userInputType: 'downArrow',
36
+ };
37
+ }
38
+ if (input !== '') {
39
+ return {
40
+ userInputType: 'character',
41
+ string: input,
42
+ };
43
+ }
44
+ return undefined;
45
45
  }
@@ -0,0 +1,4 @@
1
+ import { PosixName, PosixNode } from 'dirvi-lib';
2
+ import { AppApi } from '../domain/app-api.js';
3
+ export declare function loadPosixAppApi(directory?: string): AppApi<PosixName, PosixNode>;
4
+ //# sourceMappingURL=load-posix-app-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-posix-app-api.d.ts","sourceRoot":"","sources":["../../src/infrastructure/load-posix-app-api.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,MAAM,EAAiB,MAAM,sBAAsB,CAAC;AAI7D,wBAAgB,eAAe,CAC7B,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAkB9B"}
@@ -0,0 +1,82 @@
1
+ import { join } from 'node:path';
2
+ import { watch } from 'node:fs';
3
+ import { createAppApis } from '../domain/app-api.js';
4
+ import { getUnixAbsPath } from './get-unix-abs-path.js';
5
+ import { getDirEntries } from './get-dir-entries.js';
6
+ export function loadPosixAppApi(directory) {
7
+ const unixAbsPath = getUnixAbsPath(directory ?? process.cwd());
8
+ const apis = createAppApis();
9
+ const subscribeToResync = createFsResyncSubscription(join(unixAbsPath));
10
+ return {
11
+ name: `Posix(${unixAbsPath})`,
12
+ emptyForestMessage: 'The directory is empty.',
13
+ loadBranches: (path) => {
14
+ const address = join(unixAbsPath, ...path);
15
+ return getDirEntries(address);
16
+ },
17
+ subscribeToResync,
18
+ ...apis,
19
+ };
20
+ }
21
+ // `recursive: true` is supported by macOS and Windows, but
22
+ // recursive watching is not supported on all Node/POSIX
23
+ // platforms: Linux unsupported.
24
+ function createFsResyncSubscription(directory) {
25
+ const listeners = new Set();
26
+ let watcher;
27
+ let notificationTimer;
28
+ function notifyListeners() {
29
+ for (const listener of listeners) {
30
+ listener();
31
+ }
32
+ }
33
+ function scheduleNotification() {
34
+ // fs.watch can emit several events for one filesystem operation.
35
+ // Debounce them into a single resync request.
36
+ if (notificationTimer !== undefined) {
37
+ clearTimeout(notificationTimer);
38
+ }
39
+ notificationTimer = setTimeout(() => {
40
+ notificationTimer = undefined;
41
+ if (listeners.size > 0) {
42
+ notifyListeners();
43
+ }
44
+ }, 100);
45
+ }
46
+ function startWatcher() {
47
+ if (watcher !== undefined) {
48
+ return;
49
+ }
50
+ watcher = watch(directory, {
51
+ recursive: true,
52
+ }, (_eventType, _filename) => {
53
+ scheduleNotification();
54
+ });
55
+ watcher.on('error', () => {
56
+ // The next loadBranches call will report the relevant error.
57
+ });
58
+ }
59
+ function stopWatcher() {
60
+ if (notificationTimer !== undefined) {
61
+ clearTimeout(notificationTimer);
62
+ notificationTimer = undefined;
63
+ }
64
+ watcher?.close();
65
+ watcher = undefined;
66
+ }
67
+ return (listener) => {
68
+ listeners.add(listener);
69
+ startWatcher();
70
+ let subscribed = true;
71
+ return () => {
72
+ if (!subscribed) {
73
+ return;
74
+ }
75
+ subscribed = false;
76
+ listeners.delete(listener);
77
+ if (listeners.size === 0) {
78
+ stopWatcher();
79
+ }
80
+ };
81
+ };
82
+ }
package/dist/ui/App.d.ts CHANGED
@@ -1,16 +1,17 @@
1
- import { State } from 'dirvi-lib';
2
- import type { UnixAbsolutePath } from 'dirvi-lib';
3
- import { EventMessage } from '../domain/event-message.js';
4
- export type AppProps = {
5
- cwdAddress: UnixAbsolutePath;
6
- initialState: State;
7
- print?: (message: EventMessage) => void;
8
- onError?: (error: Error) => void;
1
+ import { IntentToEffect, SerializableKey, State, TreeNode } from 'dirvi-lib';
2
+ import type { EventMessage } from '../domain/event-message.js';
3
+ import { EffectToAction } from '../application/effect-to-action.js';
4
+ import { AppApi } from '../domain/app-api.js';
5
+ import { Reducer } from '../application/reducer.js';
6
+ type AppProps<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>> = {
7
+ appApi: AppApi<Id, BufferNode>;
8
+ initialState: State<Id, BufferNode>;
9
+ reducer: Reducer<Id, BufferNode>;
10
+ intentToEffect: IntentToEffect<Id, BufferNode>;
11
+ effectToAction: EffectToAction<Id, BufferNode>;
12
+ print?: (message: EventMessage<Id, BufferNode>) => void;
13
+ onError?: (error: Error) => void;
9
14
  };
10
- export declare function App({
11
- cwdAddress,
12
- initialState,
13
- print,
14
- onError,
15
- }: AppProps): import('react').JSX.Element | null;
16
- //# sourceMappingURL=App.d.ts.map
15
+ export declare function App<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>({ appApi, initialState, reducer, intentToEffect, effectToAction, print, onError, }: AppProps<Id, BufferNode>): import("react").JSX.Element | null;
16
+ export {};
17
+ //# sourceMappingURL=App.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/ui/App.tsx"],"names":[],"mappings":"AAIA,OAAO,EAML,KAAK,EAEN,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAIlD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAM1D,MAAM,MAAM,QAAQ,GAAG;IACrB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC;IACpB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAgB,GAAG,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,QAAQ,sCA2IzE"}
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/ui/App.tsx"],"names":[],"mappings":"AAGA,OAAO,EAGL,cAAc,EAAE,eAAe,EAC/B,KAAK,EACL,QAAQ,EAET,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAGpE,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAGpD,KAAK,QAAQ,CACX,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,IACzC;IACF,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IAC/B,YAAY,EAAE,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IACpC,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IAC/C,cAAc,EAAE,cAAc,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IAC/C,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,EAAE,UAAU,CAAC,KAAK,IAAI,CAAC;IACxD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAgB,GAAG,CACjB,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,EAC3C,EACA,MAAM,EACN,YAAY,EACZ,OAAO,EACP,cAAc,EACd,cAAc,EACd,KAAK,EACL,OAAO,GACR,EAAE,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,sCAoL1B"}
package/dist/ui/App.js CHANGED
@@ -1,139 +1,138 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';
2
- import { join } from 'node:path';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
2
  import { Box, Text, useApp, useInput, useWindowSize } from 'ink';
4
- import { useEffect, useMemo, useReducer, useState } from 'react';
5
- import {
6
- getDirLazyEntries,
7
- intentToEffect,
8
- NavigationNode,
9
- userInputToIntent,
10
- } from 'dirvi-lib';
11
- import { useView } from './hooks/useView.js';
12
- import { reducer } from '../application/reducer.js';
3
+ import { useEffect, useMemo, useReducer, useRef, useState } from 'react';
4
+ import { userInputToIntent, } from 'dirvi-lib';
13
5
  import { ViewRowComponent } from './components/ViewRowComponent.js';
14
- import { effectToAction } from '../application/effect-to-action.js';
15
6
  import { StatusBar } from './components/StatusBar.js';
16
7
  import { inkInputToUserInput } from '../infrastructure/ink-input-to-user-input.js';
17
- export function App({ cwdAddress, initialState, print, onError }) {
18
- const [state, dispatch] = useReducer(reducer, initialState);
19
- const navigation = useMemo(
20
- () => NavigationNode.from(state.buffer, state.folds),
21
- [state.buffer, state.folds],
22
- );
23
- const [inputState, setInputState] = useState({
24
- inputMode: 'normal',
25
- normalBuffer: '',
26
- });
27
- const { rows: terminalRows } = useWindowSize();
28
- const view = useView(navigation, state, terminalRows);
29
- const [exitStatus, setExitStatus] = useState();
30
- // Print view on changes
31
- useEffect(() => {
32
- print?.({ type: 'view', view: state });
33
- const visibleFilesPaths = NavigationNode.visibleFilesPaths(navigation).map(
34
- (path) => join(...path),
35
- );
36
- print?.({
37
- type: 'displayed-files-paths',
38
- paths: visibleFilesPaths,
8
+ import { useView } from './hooks/useView.js';
9
+ export function App({ appApi, initialState, reducer, intentToEffect, effectToAction, print, onError, }) {
10
+ const [state, dispatch] = useReducer(reducer, initialState);
11
+ // Give `subscribeToResync` callback a way to see current state:
12
+ const stateRef = useRef(state);
13
+ stateRef.current = state;
14
+ const navigation = useMemo(() => appApi.navNodeApi.from(state.buffer, state.foldNode), [appApi.navNodeApi, state.buffer, state.foldNode]);
15
+ const [inputState, setInputState] = useState({
16
+ inputMode: 'normal',
17
+ normalBuffer: '',
39
18
  });
40
- }, [state, print, navigation, cwdAddress]);
41
- function executeEffect(effect) {
42
- if (effect === undefined) {
43
- return;
44
- }
45
- switch (effect.effectType) {
46
- case 'dispatchEffectAction':
47
- const action = effectToAction(effect.action, navigation, state);
48
- if (action === undefined) {
49
- return;
50
- }
51
- dispatch(action);
52
- setInputState({
53
- inputMode: 'normal',
54
- normalBuffer: '',
19
+ const { rows: terminalRows } = useWindowSize();
20
+ const view = useView(navigation, state, appApi.cursorApi, terminalRows);
21
+ const [exitStatus, setExitStatus] = useState();
22
+ // Print on changes: view, paths of visible leaves
23
+ useEffect(() => {
24
+ print?.({ type: 'view', view: state });
25
+ const visibleLeavesPaths = appApi.navNodeApi.visibleLeavesPaths(navigation);
26
+ print?.({
27
+ type: 'displayed-leaves-paths',
28
+ paths: visibleLeavesPaths,
55
29
  });
56
- return;
57
- case 'setInputState':
58
- console.log('setInputState', effect.inputState);
59
- setInputState(effect.inputState);
60
- return;
61
- case 'loadDir': {
62
- const address = join(cwdAddress, ...effect.path);
63
- void getDirLazyEntries(address)
64
- .then((entries) => {
65
- dispatch({
66
- kind: 'updateDir',
67
- path: effect.path,
68
- entries,
30
+ }, [appApi.navNodeApi, state, print, navigation]);
31
+ // Subscribe to directory watcher, do not
32
+ // resubscribe on every state change.
33
+ useEffect(() => {
34
+ let active = true;
35
+ const unsubscribe = appApi.subscribeToResync(() => {
36
+ const oldState = stateRef.current;
37
+ void appApi.stateApi
38
+ .resync(oldState, appApi.loadBranches)
39
+ .then((newState) => {
40
+ if (!active) {
41
+ return;
42
+ }
43
+ dispatch({
44
+ kind: 'setState',
45
+ oldState: oldState,
46
+ newState: newState,
47
+ });
48
+ })
49
+ .catch((error) => {
50
+ const appError = error instanceof Error ? error : new Error(String(error));
51
+ onError?.(appError);
69
52
  });
70
- })
71
- .catch((error) => {
72
- const appError =
73
- error instanceof Error ? error : new Error(String(error));
74
- onError?.(appError);
75
- setExitStatus(`Unable to open directory: ${appError.message}`);
76
- });
77
- return;
78
- }
79
- case 'printFile':
80
- print?.({
81
- type: 'file',
82
- path: effect.path,
83
53
  });
84
- return;
85
- case 'quit':
86
- setExitStatus(effect.exitMessage);
87
- return;
88
- }
89
- }
90
- // --- Input Hook ---
91
- useInput((input, key) => {
92
- const userInput = inkInputToUserInput(input, key);
93
- if (userInput === undefined) {
94
- return;
95
- }
96
- const intent = userInputToIntent(userInput, inputState);
97
- if (intent === undefined) {
98
- return;
99
- }
100
- const effectResult = intentToEffect(intent, state);
101
- if (effectResult === undefined) {
102
- return;
103
- }
104
- executeEffect(effectResult);
105
- });
106
- // --- Exit Logic ---
107
- const { exit } = useApp();
108
- useEffect(() => {
109
- if (exitStatus === undefined) {
110
- return;
54
+ return () => {
55
+ active = false;
56
+ unsubscribe();
57
+ };
58
+ }, [appApi, onError]);
59
+ function executeEffect(effect) {
60
+ if (effect === undefined) {
61
+ return;
62
+ }
63
+ switch (effect.effectType) {
64
+ case 'dispatchEffectAction':
65
+ const action = effectToAction(effect.action, navigation, state);
66
+ if (action === undefined) {
67
+ return;
68
+ }
69
+ dispatch(action);
70
+ setInputState({
71
+ inputMode: 'normal',
72
+ normalBuffer: '',
73
+ });
74
+ return;
75
+ case 'setInputState':
76
+ setInputState(effect.inputState);
77
+ return;
78
+ case 'loadBranchEntries': {
79
+ void appApi
80
+ .loadBranches(effect.path)
81
+ .then((entries) => {
82
+ dispatch({
83
+ kind: 'updateBranch',
84
+ path: effect.path,
85
+ entries,
86
+ });
87
+ })
88
+ .catch((error) => {
89
+ const appError = error instanceof Error ? error : new Error(String(error));
90
+ onError?.(appError);
91
+ setExitStatus(`Unable to open directory: ${appError.message}`);
92
+ });
93
+ return;
94
+ }
95
+ case 'emitPath':
96
+ print?.({
97
+ type: 'file',
98
+ path: effect.path,
99
+ });
100
+ return;
101
+ case 'quit':
102
+ setExitStatus(effect.exitMessage);
103
+ return;
104
+ }
111
105
  }
112
- if (exitStatus !== '') {
113
- onError?.(new Error(exitStatus));
106
+ // --- Input Hook ---
107
+ useInput((input, key) => {
108
+ const userInput = inkInputToUserInput(input, key);
109
+ if (userInput === undefined) {
110
+ return;
111
+ }
112
+ const intent = userInputToIntent(userInput, inputState);
113
+ if (intent === undefined) {
114
+ return;
115
+ }
116
+ const effectResult = intentToEffect(intent, state);
117
+ if (effectResult === undefined) {
118
+ return;
119
+ }
120
+ executeEffect(effectResult);
121
+ });
122
+ // --- Exit Logic ---
123
+ const { exit } = useApp();
124
+ useEffect(() => {
125
+ if (exitStatus === undefined) {
126
+ return;
127
+ }
128
+ if (exitStatus !== '') {
129
+ onError?.(new Error(exitStatus));
130
+ }
131
+ exit();
132
+ }, [exitStatus, exit, onError]);
133
+ if (exitStatus !== undefined) {
134
+ return null;
114
135
  }
115
- exit();
116
- }, [exitStatus, exit, onError]);
117
- if (exitStatus !== undefined) {
118
- return null;
119
- }
120
- // --- JSX ---
121
- return _jsxs(Box, {
122
- flexDirection: 'column',
123
- height: terminalRows,
124
- children: [
125
- _jsx(Box, {
126
- flexDirection: 'column',
127
- flexGrow: 1,
128
- flexShrink: 1,
129
- children:
130
- view.rows.length === 0
131
- ? _jsx(Text, { dimColor: true, children: 'Directory is empty.' })
132
- : view.rows.map((row) =>
133
- _jsx(ViewRowComponent, { row: row }, row.id),
134
- ),
135
- }),
136
- _jsx(StatusBar, { inputState: inputState }),
137
- ],
138
- });
136
+ // --- JSX ---
137
+ return (_jsxs(Box, { flexDirection: "column", height: terminalRows, children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: view.rows.length === 0 ? (_jsx(Text, { dimColor: true, children: "Empty." })) : (view.rows.map((row) => _jsx(ViewRowComponent, { row: row }, row.id))) }), _jsx(StatusBar, { inputState: inputState })] }));
139
138
  }
@@ -1,18 +1,8 @@
1
- import { UnixAbsolutePath, State } from 'dirvi-lib';
2
- import { EventMessage } from '../domain/event-message.js';
3
- export type EmptyView = {
4
- cursor: undefined;
5
- };
1
+ import { SerializableKey, TreeNode } from 'dirvi-lib';
6
2
  export type ShellAppProps = {
7
- cwdAddress: UnixAbsolutePath;
8
- initialState: State | EmptyView;
9
- print?: (message: EventMessage) => void;
10
- onError?: (error: Error) => void;
3
+ directory?: string;
4
+ print?: (message: string) => void;
5
+ onError?: (error: Error) => void;
11
6
  };
12
- export declare function AppShell({
13
- cwdAddress,
14
- initialState,
15
- print,
16
- onError,
17
- }: ShellAppProps): import('react').JSX.Element;
18
- //# sourceMappingURL=AppShell.d.ts.map
7
+ export declare function AppShell<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>({ directory, print, onError }: ShellAppProps): import("react").JSX.Element;
8
+ //# sourceMappingURL=AppShell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AppShell.d.ts","sourceRoot":"","sources":["../../src/ui/AppShell.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAI1D,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,YAAY,EAAE,KAAK,GAAG,SAAS,CAAC;IAChC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EACvB,UAAU,EACV,YAAY,EACZ,KAAK,EACL,OAAO,GACR,EAAE,aAAa,+BAaf"}
1
+ {"version":3,"file":"AppShell.d.ts","sourceRoot":"","sources":["../../src/ui/AppShell.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAwB,eAAe,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAI5E,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAIF,wBAAgB,QAAQ,CACtB,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,EAC3C,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,aAAa,+BAyB7C"}
@@ -1,14 +1,23 @@
1
- import { jsx as _jsx } from 'react/jsx-runtime';
2
- import { Text } from 'ink';
3
- import { App } from './App.js';
4
- export function AppShell({ cwdAddress, initialState, print, onError }) {
5
- if (initialState.cursor === undefined) {
6
- return _jsx(Text, { dimColor: true, children: 'Directory is empty.' });
7
- }
8
- return _jsx(App, {
9
- cwdAddress: cwdAddress,
10
- initialState: initialState,
11
- print: print,
12
- onError: onError,
13
- });
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { loadPosixAppApi } from '../infrastructure/load-posix-app-api.js';
3
+ import { LoadingApp } from './LoadingApp.js';
4
+ import { join } from 'node:path';
5
+ import { useMemo } from 'react';
6
+ // App Shell is for choosing the active app, then loading the api,
7
+ // and showing the app.
8
+ export function AppShell({ directory, print, onError }) {
9
+ // Api owns directory watcher and subscriptions
10
+ const posixAppApi = useMemo(() => loadPosixAppApi(directory), [directory]);
11
+ const emitEventMsg = print
12
+ ? (message) => {
13
+ const externalMessage = message.type === 'displayed-leaves-paths'
14
+ ? {
15
+ ...message,
16
+ paths: message.paths.map((path) => join(...path)),
17
+ }
18
+ : message;
19
+ print(`${JSON.stringify(externalMessage)}\n`);
20
+ }
21
+ : undefined;
22
+ return (_jsx(LoadingApp, { appApi: posixAppApi, ...(emitEventMsg === undefined ? {} : { print: emitEventMsg }), ...(onError === undefined ? {} : { onError }) }));
14
23
  }
@@ -0,0 +1,11 @@
1
+ import { SerializableKey, TreeNode } from 'dirvi-lib';
2
+ import type { EventMessage } from '../domain/event-message.js';
3
+ import { AppApi } from '../domain/app-api.js';
4
+ type LoadingAppProps<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>> = {
5
+ appApi: AppApi<Id, BufferNode>;
6
+ print?: (message: EventMessage<Id, BufferNode>) => void;
7
+ onError?: (error: Error) => void;
8
+ };
9
+ export declare function LoadingApp<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>({ appApi, print, onError }: LoadingAppProps<Id, BufferNode>): import("react").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=LoadingApp.d.ts.map