@olenbetong/appframe-cli 4.3.1 → 4.4.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 (35) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/cli/af-apply.js +1 -1
  3. package/cli/af-deploy.js +1 -1
  4. package/cli/editor/TransactionsEditor.js +134 -158
  5. package/cli/editor/TransactionsPreviewDialog.js +175 -104
  6. package/cli/editor/tableFormatting.js +9 -80
  7. package/cli/editor/useCommand.js +148 -0
  8. package/cli/editor/useScrollableText.js +28 -0
  9. package/cli/editor/useTransactionEdits.js +142 -0
  10. package/cli/editor/useTransactionText.js +113 -0
  11. package/cli/editor/useTransactions.js +53 -0
  12. package/cli/editor/useTransactionsSelection.js +41 -0
  13. package/cli/editor/useTransactionsTableLayout.js +54 -0
  14. package/cli/editor/{useTransactionsTableViewport.js → useVirtualScrolling.js} +62 -24
  15. package/cli/editor/useVirtualText.js +18 -0
  16. package/package.json +2 -2
  17. package/src/af-apply.ts +1 -1
  18. package/src/af-deploy.ts +1 -1
  19. package/src/editor/TransactionsEditor.tsx +155 -180
  20. package/src/editor/TransactionsPreviewDialog.tsx +196 -146
  21. package/src/editor/tableFormatting.ts +8 -94
  22. package/src/editor/useCommand.ts +191 -0
  23. package/src/editor/useScrollableText.ts +48 -0
  24. package/src/editor/useTransactionEdits.ts +183 -0
  25. package/src/editor/useTransactionText.ts +153 -0
  26. package/src/editor/useTransactions.ts +75 -0
  27. package/src/editor/useTransactionsSelection.ts +54 -0
  28. package/src/editor/useTransactionsTableLayout.ts +84 -0
  29. package/src/editor/{useTransactionsTableViewport.ts → useVirtualScrolling.ts} +87 -38
  30. package/src/editor/useVirtualText.ts +32 -0
  31. package/tsconfig.build.tsbuildinfo +1 -1
  32. package/cli/editor/useTransactionsEditorData.js +0 -206
  33. package/cli/editor/useTransactionsEditorInput.js +0 -109
  34. package/src/editor/useTransactionsEditorData.ts +0 -245
  35. package/src/editor/useTransactionsEditorInput.ts +0 -147
@@ -1,206 +0,0 @@
1
- "use strict";
2
- import {
3
- transactionStatuses
4
- } from "@olenbetong/appframe-updater";
5
- import { useCallback, useEffect, useMemo, useState } from "react";
6
- const STATUS_CYCLE = Object.freeze(
7
- Object.values(transactionStatuses).sort((a, b) => a - b)
8
- );
9
- export function useTransactionsEditorData({ server, namespace, onActionMessage }) {
10
- let [transactions, setTransactions] = useState([]);
11
- let [loading, setLoading] = useState(false);
12
- let [error, setError] = useState();
13
- let [selectedPrimKeys, setSelectedPrimKeys] = useState(/* @__PURE__ */ new Set());
14
- let [pendingStatuses, setPendingStatuses] = useState({});
15
- let [saving, setSaving] = useState(false);
16
- let [lastRefreshOptions, setLastRefreshOptions] = useState({
17
- preserveViewport: false,
18
- anchorPrimKey: null
19
- });
20
- let refresh = useCallback(
21
- async (options = {}) => {
22
- let { preserveViewport = false, anchorPrimKey = null } = options;
23
- setLastRefreshOptions({ preserveViewport, anchorPrimKey });
24
- setLoading(true);
25
- setError(void 0);
26
- try {
27
- let data = await server.getTransactions("edit", namespace);
28
- setTransactions(data);
29
- } catch (caught) {
30
- setError(caught);
31
- } finally {
32
- setLoading(false);
33
- }
34
- },
35
- [namespace, server]
36
- );
37
- let normalizedTransactions = useMemo(
38
- () => transactions.map((transaction) => ({
39
- ID: transaction.ID,
40
- Namespace: transaction.Namespace,
41
- Name: transaction.Name,
42
- CreatedBy: transaction.CreatedBy ?? transaction.CreatedByName ?? transaction.LocalCreatedBy,
43
- Status: transaction.Status,
44
- LastError: transaction.LastError ?? void 0,
45
- PrimKey: transaction.PrimKey
46
- })),
47
- [transactions]
48
- );
49
- let normalizedTransactionMap = useMemo(
50
- () => new Map(normalizedTransactions.map((transaction) => [transaction.PrimKey, transaction])),
51
- [normalizedTransactions]
52
- );
53
- let displayTransactions = useMemo(
54
- () => normalizedTransactions.map((transaction) => ({
55
- ...transaction,
56
- Status: pendingStatuses[transaction.PrimKey] ?? transaction.Status
57
- })),
58
- [normalizedTransactions, pendingStatuses]
59
- );
60
- let transactionRecordByPrimKey = useMemo(
61
- () => new Map(transactions.map((transaction) => [transaction.PrimKey, transaction])),
62
- [transactions]
63
- );
64
- let modifiedPrimKeys = useMemo(() => {
65
- let modified = /* @__PURE__ */ new Set();
66
- for (let transaction of normalizedTransactions) {
67
- let override = pendingStatuses[transaction.PrimKey];
68
- if (override !== void 0 && override !== transaction.Status) {
69
- modified.add(transaction.PrimKey);
70
- }
71
- }
72
- return modified;
73
- }, [normalizedTransactions, pendingStatuses]);
74
- let availablePrimKeys = useMemo(
75
- () => new Set(transactions.map((transaction) => transaction.PrimKey)),
76
- [transactions]
77
- );
78
- useEffect(() => {
79
- setSelectedPrimKeys((current) => {
80
- if (current.size === 0) {
81
- return current;
82
- }
83
- let next = /* @__PURE__ */ new Set();
84
- let changed = false;
85
- for (let key of current) {
86
- if (availablePrimKeys.has(key)) {
87
- next.add(key);
88
- } else {
89
- changed = true;
90
- }
91
- }
92
- return changed ? next : current;
93
- });
94
- setPendingStatuses((current) => {
95
- let changed = false;
96
- let next = {};
97
- for (let [key, value] of Object.entries(current)) {
98
- if (availablePrimKeys.has(key)) {
99
- next[key] = value;
100
- } else {
101
- changed = true;
102
- }
103
- }
104
- return changed ? next : current;
105
- });
106
- }, [availablePrimKeys]);
107
- let cycleStatus = useCallback(
108
- (focusedPrimKey) => {
109
- let targets = selectedPrimKeys.size > 0 ? Array.from(selectedPrimKeys) : [];
110
- if (targets.length === 0 && focusedPrimKey) {
111
- targets = [focusedPrimKey];
112
- }
113
- if (targets.length === 0) {
114
- return;
115
- }
116
- setPendingStatuses((current) => {
117
- let next = { ...current };
118
- let changed = false;
119
- for (let primKey of targets) {
120
- let transaction = normalizedTransactionMap.get(primKey);
121
- if (!transaction) {
122
- continue;
123
- }
124
- let originalStatus = transaction.Status;
125
- let currentStatus = current[primKey] ?? originalStatus;
126
- let cycleIndex = STATUS_CYCLE.indexOf(currentStatus);
127
- let nextStatus = STATUS_CYCLE[(cycleIndex + 1) % STATUS_CYCLE.length];
128
- if (nextStatus === originalStatus) {
129
- if (primKey in next) {
130
- delete next[primKey];
131
- changed = true;
132
- }
133
- } else if (next[primKey] !== nextStatus) {
134
- next[primKey] = nextStatus;
135
- changed = true;
136
- }
137
- }
138
- return changed ? next : current;
139
- });
140
- },
141
- [normalizedTransactionMap, selectedPrimKeys]
142
- );
143
- let handleSave = useCallback(async () => {
144
- if (saving) {
145
- return;
146
- }
147
- let updates = Object.entries(pendingStatuses).map(([primKey, status]) => {
148
- let record = transactionRecordByPrimKey.get(primKey);
149
- if (!record) {
150
- return null;
151
- }
152
- if (status === record.Status) {
153
- return null;
154
- }
155
- return { record, status };
156
- }).filter(Boolean);
157
- if (updates.length === 0) {
158
- onActionMessage?.("No changes to save.", "gray");
159
- return;
160
- }
161
- setSaving(true);
162
- onActionMessage?.(`Saving ${updates.length} update${updates.length === 1 ? "" : "s"}\u2026`, "yellow");
163
- try {
164
- await Promise.all(
165
- updates.map(({ record, status }) => {
166
- let payload = record.PrimKey ? { PrimKey: record.PrimKey, Status: status } : { ID: record.ID, Status: status };
167
- return server.dsTransactions.update(payload);
168
- })
169
- );
170
- onActionMessage?.(`Saved ${updates.length} update${updates.length === 1 ? "" : "s"}.`, "green");
171
- setPendingStatuses((current) => {
172
- let next = { ...current };
173
- for (let { record } of updates) {
174
- delete next[record.PrimKey];
175
- }
176
- return next;
177
- });
178
- let anchor = updates[0]?.record.PrimKey ?? null;
179
- await refresh({ preserveViewport: true, anchorPrimKey: anchor });
180
- } catch (caught) {
181
- let message = caught instanceof Error ? caught.message : String(caught);
182
- onActionMessage?.(`Failed to save changes: ${message}`, "red");
183
- } finally {
184
- setSaving(false);
185
- }
186
- }, [pendingStatuses, refresh, saving, server, transactionRecordByPrimKey, onActionMessage]);
187
- return {
188
- transactions,
189
- loading,
190
- error,
191
- refresh,
192
- normalizedTransactions,
193
- displayTransactions,
194
- transactionRecordByPrimKey,
195
- modifiedPrimKeys,
196
- availablePrimKeys,
197
- selectedPrimKeys,
198
- setSelectedPrimKeys,
199
- pendingStatuses,
200
- setPendingStatuses,
201
- cycleStatus,
202
- handleSave,
203
- saving,
204
- lastRefreshOptions
205
- };
206
- }
@@ -1,109 +0,0 @@
1
- "use strict";
2
- import { useInput } from "ink";
3
- export function useTransactionsEditorInput({
4
- setCursor,
5
- setSelectedPrimKeys,
6
- totalRowCount,
7
- pageStep,
8
- normalizedTransactions,
9
- cursor,
10
- cycleStatus,
11
- handleSave,
12
- previewOpen,
13
- handlePreview,
14
- onPreviewClose,
15
- refresh,
16
- exit
17
- }) {
18
- useInput((input, key) => {
19
- if (previewOpen) {
20
- if (key.escape) {
21
- onPreviewClose?.();
22
- }
23
- if (key.leftArrow) {
24
- setCursor((current) => totalRowCount === 0 ? 0 : Math.max(current - 1, 0));
25
- }
26
- if (key.rightArrow) {
27
- setCursor((current) => {
28
- if (totalRowCount === 0) {
29
- return 0;
30
- }
31
- return Math.min(current + 1, totalRowCount - 1);
32
- });
33
- }
34
- return;
35
- }
36
- if (key.upArrow) {
37
- setCursor((current) => totalRowCount === 0 ? 0 : Math.max(current - 1, 0));
38
- return;
39
- }
40
- if (key.downArrow) {
41
- setCursor((current) => {
42
- if (totalRowCount === 0) {
43
- return 0;
44
- }
45
- return Math.min(current + 1, totalRowCount - 1);
46
- });
47
- return;
48
- }
49
- if (key.pageUp) {
50
- setCursor((current) => Math.max(current - pageStep, 0));
51
- return;
52
- }
53
- if (key.pageDown) {
54
- setCursor((current) => {
55
- if (totalRowCount === 0) {
56
- return 0;
57
- }
58
- return Math.min(current + pageStep, totalRowCount - 1);
59
- });
60
- return;
61
- }
62
- if (input) {
63
- if (input === "\x1B[H" || input === "\x1B[1~" || input === "\x1BOH") {
64
- setCursor(0);
65
- return;
66
- }
67
- if (input === "\x1B[F" || input === "\x1B[4~" || input === "\x1BOF") {
68
- setCursor(Math.max(totalRowCount - 1, 0));
69
- return;
70
- }
71
- if ((key.ctrl || key.meta) && input.toLowerCase() === "s") {
72
- void handleSave();
73
- return;
74
- }
75
- if ((key.ctrl || key.meta) && input.toLowerCase() === "x") {
76
- exit();
77
- return;
78
- }
79
- if (input === " " && !key.ctrl && !key.meta) {
80
- let focused = normalizedTransactions[cursor];
81
- if (focused) {
82
- setSelectedPrimKeys((current) => {
83
- let next = new Set(current);
84
- if (next.has(focused.PrimKey)) {
85
- next.delete(focused.PrimKey);
86
- } else {
87
- next.add(focused.PrimKey);
88
- }
89
- return next;
90
- });
91
- }
92
- return;
93
- }
94
- if (input.toLowerCase() === "s" && !key.ctrl && !key.meta) {
95
- cycleStatus();
96
- return;
97
- }
98
- if (input.toLowerCase() === "r") {
99
- let anchor = normalizedTransactions[cursor]?.PrimKey ?? null;
100
- refresh({ preserveViewport: true, anchorPrimKey: anchor });
101
- return;
102
- }
103
- if (input.toLowerCase() === "p" && !key.ctrl && !key.meta) {
104
- handlePreview();
105
- return;
106
- }
107
- }
108
- });
109
- }
@@ -1,245 +0,0 @@
1
- import {
2
- type Transaction,
3
- type TransactionStatusCode,
4
- type TransactionsRecord,
5
- transactionStatuses,
6
- } from "@olenbetong/appframe-updater";
7
- import { useCallback, useEffect, useMemo, useState } from "react";
8
-
9
- import type { Server } from "../lib/Server.js";
10
-
11
- export type TransactionsEditorRefreshOptions = {
12
- preserveViewport?: boolean;
13
- anchorPrimKey?: string | null;
14
- };
15
-
16
- const STATUS_CYCLE = Object.freeze(
17
- (Object.values(transactionStatuses) as TransactionStatusCode[]).sort((a, b) => a - b),
18
- );
19
-
20
- type UseTransactionsEditorDataOptions = {
21
- server: Server;
22
- namespace: string | number;
23
- showErrorColumn?: boolean;
24
- onActionMessage?: (message: string | null, color: "gray" | "red" | "yellow" | "green") => void;
25
- };
26
-
27
- export function useTransactionsEditorData({ server, namespace, onActionMessage }: UseTransactionsEditorDataOptions) {
28
- let [transactions, setTransactions] = useState<TransactionsRecord[]>([]);
29
- let [loading, setLoading] = useState(false);
30
- let [error, setError] = useState<Error | undefined>();
31
- let [selectedPrimKeys, setSelectedPrimKeys] = useState<Set<string>>(new Set());
32
- let [pendingStatuses, setPendingStatuses] = useState<Record<string, TransactionStatusCode>>({});
33
-
34
- let [saving, setSaving] = useState(false);
35
- let [lastRefreshOptions, setLastRefreshOptions] = useState<TransactionsEditorRefreshOptions>({
36
- preserveViewport: false,
37
- anchorPrimKey: null,
38
- });
39
-
40
- let refresh = useCallback(
41
- async (options: TransactionsEditorRefreshOptions = {}) => {
42
- let { preserveViewport = false, anchorPrimKey = null } = options;
43
- setLastRefreshOptions({ preserveViewport, anchorPrimKey });
44
- setLoading(true);
45
- setError(undefined);
46
- try {
47
- let data = (await server.getTransactions("edit", namespace)) as TransactionsRecord[];
48
- setTransactions(data);
49
- } catch (caught) {
50
- setError(caught as Error);
51
- } finally {
52
- setLoading(false);
53
- }
54
- },
55
- [namespace, server],
56
- );
57
-
58
- let normalizedTransactions = useMemo<Transaction[]>(
59
- () =>
60
- transactions.map((transaction) => ({
61
- ID: transaction.ID,
62
- Namespace: transaction.Namespace,
63
- Name: transaction.Name,
64
- CreatedBy: transaction.CreatedBy ?? transaction.CreatedByName ?? transaction.LocalCreatedBy,
65
- Status: transaction.Status,
66
- LastError: transaction.LastError ?? undefined,
67
- PrimKey: transaction.PrimKey,
68
- })),
69
- [transactions],
70
- );
71
-
72
- let normalizedTransactionMap = useMemo(
73
- () => new Map(normalizedTransactions.map((transaction) => [transaction.PrimKey, transaction])),
74
- [normalizedTransactions],
75
- );
76
-
77
- let displayTransactions = useMemo(
78
- () =>
79
- normalizedTransactions.map((transaction) => ({
80
- ...transaction,
81
- Status: pendingStatuses[transaction.PrimKey] ?? transaction.Status,
82
- })),
83
- [normalizedTransactions, pendingStatuses],
84
- );
85
-
86
- let transactionRecordByPrimKey = useMemo(
87
- () => new Map(transactions.map((transaction) => [transaction.PrimKey, transaction])),
88
- [transactions],
89
- );
90
-
91
- let modifiedPrimKeys = useMemo(() => {
92
- let modified = new Set<string>();
93
- for (let transaction of normalizedTransactions) {
94
- let override = pendingStatuses[transaction.PrimKey];
95
- if (override !== undefined && override !== transaction.Status) {
96
- modified.add(transaction.PrimKey);
97
- }
98
- }
99
- return modified;
100
- }, [normalizedTransactions, pendingStatuses]);
101
-
102
- let availablePrimKeys = useMemo(
103
- () => new Set(transactions.map((transaction) => transaction.PrimKey)),
104
- [transactions],
105
- );
106
-
107
- useEffect(() => {
108
- setSelectedPrimKeys((current) => {
109
- if (current.size === 0) {
110
- return current;
111
- }
112
- let next = new Set<string>();
113
- let changed = false;
114
- for (let key of current) {
115
- if (availablePrimKeys.has(key)) {
116
- next.add(key);
117
- } else {
118
- changed = true;
119
- }
120
- }
121
- return changed ? next : current;
122
- });
123
- setPendingStatuses((current) => {
124
- let changed = false;
125
- let next: Record<string, TransactionStatusCode> = {};
126
- for (let [key, value] of Object.entries(current)) {
127
- if (availablePrimKeys.has(key)) {
128
- next[key] = value;
129
- } else {
130
- changed = true;
131
- }
132
- }
133
- return changed ? next : current;
134
- });
135
- }, [availablePrimKeys]);
136
-
137
- let cycleStatus = useCallback(
138
- (focusedPrimKey: string | null) => {
139
- let targets = selectedPrimKeys.size > 0 ? Array.from(selectedPrimKeys) : [];
140
- if (targets.length === 0 && focusedPrimKey) {
141
- targets = [focusedPrimKey];
142
- }
143
- if (targets.length === 0) {
144
- return;
145
- }
146
-
147
- setPendingStatuses((current) => {
148
- let next = { ...current };
149
- let changed = false;
150
- for (let primKey of targets) {
151
- let transaction = normalizedTransactionMap.get(primKey);
152
- if (!transaction) {
153
- continue;
154
- }
155
- let originalStatus = transaction.Status;
156
- let currentStatus = current[primKey] ?? originalStatus;
157
- let cycleIndex = STATUS_CYCLE.indexOf(currentStatus);
158
- let nextStatus = STATUS_CYCLE[(cycleIndex + 1) % STATUS_CYCLE.length];
159
- if (nextStatus === originalStatus) {
160
- if (primKey in next) {
161
- delete next[primKey];
162
- changed = true;
163
- }
164
- } else if (next[primKey] !== nextStatus) {
165
- next[primKey] = nextStatus;
166
- changed = true;
167
- }
168
- }
169
- return changed ? next : current;
170
- });
171
- },
172
- [normalizedTransactionMap, selectedPrimKeys],
173
- );
174
-
175
- let handleSave = useCallback(async () => {
176
- if (saving) {
177
- return;
178
- }
179
- let updates = Object.entries(pendingStatuses)
180
- .map(([primKey, status]) => {
181
- let record = transactionRecordByPrimKey.get(primKey);
182
- if (!record) {
183
- return null;
184
- }
185
- if (status === record.Status) {
186
- return null;
187
- }
188
- return { record, status };
189
- })
190
- .filter(Boolean) as Array<{ record: TransactionsRecord; status: number }>;
191
-
192
- if (updates.length === 0) {
193
- onActionMessage?.("No changes to save.", "gray");
194
- return;
195
- }
196
-
197
- setSaving(true);
198
- onActionMessage?.(`Saving ${updates.length} update${updates.length === 1 ? "" : "s"}…`, "yellow");
199
- try {
200
- await Promise.all(
201
- updates.map(({ record, status }) => {
202
- let payload = record.PrimKey
203
- ? { PrimKey: record.PrimKey, Status: status }
204
- : { ID: record.ID, Status: status };
205
- return server.dsTransactions.update(payload);
206
- }),
207
- );
208
- onActionMessage?.(`Saved ${updates.length} update${updates.length === 1 ? "" : "s"}.`, "green");
209
- setPendingStatuses((current) => {
210
- let next = { ...current };
211
- for (let { record } of updates) {
212
- delete next[record.PrimKey];
213
- }
214
- return next;
215
- });
216
- let anchor = updates[0]?.record.PrimKey ?? null;
217
- await refresh({ preserveViewport: true, anchorPrimKey: anchor });
218
- } catch (caught) {
219
- let message = caught instanceof Error ? caught.message : String(caught);
220
- onActionMessage?.(`Failed to save changes: ${message}`, "red");
221
- } finally {
222
- setSaving(false);
223
- }
224
- }, [pendingStatuses, refresh, saving, server, transactionRecordByPrimKey, onActionMessage]);
225
-
226
- return {
227
- transactions,
228
- loading,
229
- error,
230
- refresh,
231
- normalizedTransactions,
232
- displayTransactions,
233
- transactionRecordByPrimKey,
234
- modifiedPrimKeys,
235
- availablePrimKeys,
236
- selectedPrimKeys,
237
- setSelectedPrimKeys,
238
- pendingStatuses,
239
- setPendingStatuses,
240
- cycleStatus,
241
- handleSave,
242
- saving,
243
- lastRefreshOptions,
244
- };
245
- }
@@ -1,147 +0,0 @@
1
- import type { Transaction } from "@olenbetong/appframe-updater";
2
- import { useInput } from "ink";
3
- import type { Dispatch, SetStateAction } from "react";
4
-
5
- import type { TransactionsEditorRefreshOptions } from "./useTransactionsEditorData.js";
6
-
7
- type SetNumberState = Dispatch<SetStateAction<number>>;
8
- type SetSelectedPrimKeysState = Dispatch<SetStateAction<Set<string>>>;
9
-
10
- type SaveHandler = () => void | Promise<void>;
11
- type PreviewHandler = () => void | Promise<void>;
12
- type RefreshHandler = (options?: TransactionsEditorRefreshOptions) => void | Promise<void>;
13
-
14
- type TransactionsEditorInputOptions = {
15
- setCursor: SetNumberState;
16
- setSelectedPrimKeys: SetSelectedPrimKeysState;
17
- totalRowCount: number;
18
- pageStep: number;
19
- normalizedTransactions: Transaction[];
20
- cursor: number;
21
- cycleStatus: () => void;
22
- handleSave: SaveHandler;
23
- previewOpen: boolean;
24
- handlePreview: PreviewHandler;
25
- onPreviewClose?: () => void;
26
- refresh: RefreshHandler;
27
- exit: () => void;
28
- };
29
-
30
- export function useTransactionsEditorInput({
31
- setCursor,
32
- setSelectedPrimKeys,
33
- totalRowCount,
34
- pageStep,
35
- normalizedTransactions,
36
- cursor,
37
- cycleStatus,
38
- handleSave,
39
- previewOpen,
40
- handlePreview,
41
- onPreviewClose,
42
- refresh,
43
- exit,
44
- }: TransactionsEditorInputOptions) {
45
- useInput((input, key) => {
46
- if (previewOpen) {
47
- if (key.escape) {
48
- onPreviewClose?.();
49
- }
50
-
51
- if (key.leftArrow) {
52
- setCursor((current) => (totalRowCount === 0 ? 0 : Math.max(current - 1, 0)));
53
- }
54
-
55
- if (key.rightArrow) {
56
- setCursor((current) => {
57
- if (totalRowCount === 0) {
58
- return 0;
59
- }
60
- return Math.min(current + 1, totalRowCount - 1);
61
- });
62
- }
63
- return;
64
- }
65
-
66
- if (key.upArrow) {
67
- setCursor((current) => (totalRowCount === 0 ? 0 : Math.max(current - 1, 0)));
68
- return;
69
- }
70
- if (key.downArrow) {
71
- setCursor((current) => {
72
- if (totalRowCount === 0) {
73
- return 0;
74
- }
75
- return Math.min(current + 1, totalRowCount - 1);
76
- });
77
- return;
78
- }
79
- if (key.pageUp) {
80
- setCursor((current) => Math.max(current - pageStep, 0));
81
- return;
82
- }
83
- if (key.pageDown) {
84
- setCursor((current) => {
85
- if (totalRowCount === 0) {
86
- return 0;
87
- }
88
- return Math.min(current + pageStep, totalRowCount - 1);
89
- });
90
- return;
91
- }
92
-
93
- if (input) {
94
- if (input === "\u001b[H" || input === "\u001b[1~" || input === "\u001bOH") {
95
- setCursor(0);
96
- return;
97
- }
98
-
99
- if (input === "\u001b[F" || input === "\u001b[4~" || input === "\u001bOF") {
100
- setCursor(Math.max(totalRowCount - 1, 0));
101
- return;
102
- }
103
-
104
- if ((key.ctrl || key.meta) && input.toLowerCase() === "s") {
105
- void handleSave();
106
- return;
107
- }
108
-
109
- if ((key.ctrl || key.meta) && input.toLowerCase() === "x") {
110
- exit();
111
- return;
112
- }
113
-
114
- if (input === " " && !key.ctrl && !key.meta) {
115
- let focused = normalizedTransactions[cursor];
116
- if (focused) {
117
- setSelectedPrimKeys((current) => {
118
- let next = new Set(current);
119
- if (next.has(focused.PrimKey)) {
120
- next.delete(focused.PrimKey);
121
- } else {
122
- next.add(focused.PrimKey);
123
- }
124
- return next;
125
- });
126
- }
127
- return;
128
- }
129
-
130
- if (input.toLowerCase() === "s" && !key.ctrl && !key.meta) {
131
- cycleStatus();
132
- return;
133
- }
134
-
135
- if (input.toLowerCase() === "r") {
136
- let anchor = normalizedTransactions[cursor]?.PrimKey ?? null;
137
- refresh({ preserveViewport: true, anchorPrimKey: anchor });
138
- return;
139
- }
140
-
141
- if (input.toLowerCase() === "p" && !key.ctrl && !key.meta) {
142
- handlePreview();
143
- return;
144
- }
145
- }
146
- });
147
- }