@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.
- package/CHANGELOG.md +11 -0
- package/cli/af-apply.js +1 -1
- package/cli/af-deploy.js +1 -1
- package/cli/editor/TransactionsEditor.js +134 -158
- package/cli/editor/TransactionsPreviewDialog.js +175 -104
- package/cli/editor/tableFormatting.js +9 -80
- package/cli/editor/useCommand.js +148 -0
- package/cli/editor/useScrollableText.js +28 -0
- package/cli/editor/useTransactionEdits.js +142 -0
- package/cli/editor/useTransactionText.js +113 -0
- package/cli/editor/useTransactions.js +53 -0
- package/cli/editor/useTransactionsSelection.js +41 -0
- package/cli/editor/useTransactionsTableLayout.js +54 -0
- package/cli/editor/{useTransactionsTableViewport.js → useVirtualScrolling.js} +62 -24
- package/cli/editor/useVirtualText.js +18 -0
- package/package.json +2 -2
- package/src/af-apply.ts +1 -1
- package/src/af-deploy.ts +1 -1
- package/src/editor/TransactionsEditor.tsx +155 -180
- package/src/editor/TransactionsPreviewDialog.tsx +196 -146
- package/src/editor/tableFormatting.ts +8 -94
- package/src/editor/useCommand.ts +191 -0
- package/src/editor/useScrollableText.ts +48 -0
- package/src/editor/useTransactionEdits.ts +183 -0
- package/src/editor/useTransactionText.ts +153 -0
- package/src/editor/useTransactions.ts +75 -0
- package/src/editor/useTransactionsSelection.ts +54 -0
- package/src/editor/useTransactionsTableLayout.ts +84 -0
- package/src/editor/{useTransactionsTableViewport.ts → useVirtualScrolling.ts} +87 -38
- package/src/editor/useVirtualText.ts +32 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/cli/editor/useTransactionsEditorData.js +0 -206
- package/cli/editor/useTransactionsEditorInput.js +0 -109
- package/src/editor/useTransactionsEditorData.ts +0 -245
- package/src/editor/useTransactionsEditorInput.ts +0 -147
|
@@ -1,163 +1,226 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { Box, Text
|
|
3
|
-
import {
|
|
1
|
+
import { type TransactionStatusCode, transactionStatusLabels } from "@olenbetong/appframe-updater";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { type ReactNode, useMemo } from "react";
|
|
4
4
|
|
|
5
|
+
import type { Server } from "../lib/Server.js";
|
|
5
6
|
import { FullScreenBox } from "./FullScreenBox.js";
|
|
7
|
+
import { STATUS_DIRTY_INDICATOR } from "./tableFormatting.js";
|
|
8
|
+
import { useCommand } from "./useCommand.js";
|
|
6
9
|
import { useScreenSize } from "./useScreenSize.js";
|
|
10
|
+
import { useScrollableText } from "./useScrollableText.js";
|
|
11
|
+
import { useTransactionEdits } from "./useTransactionEdits.js";
|
|
12
|
+
import { useTransactions } from "./useTransactions.js";
|
|
13
|
+
import { useTransactionText } from "./useTransactionText.js";
|
|
14
|
+
import { useVirtualText } from "./useVirtualText.js";
|
|
7
15
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
let lines = value.split(/\r?\n/);
|
|
11
|
-
return lines.length > 0 ? lines : [""];
|
|
12
|
-
}
|
|
16
|
+
const PREVIEW_SAVE_SHORTCUTS = ["ctrl+s", "meta+s"];
|
|
17
|
+
const NO_TRANSACTION_FILTER = "1 = 0";
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (serialized) {
|
|
18
|
-
return serialized.split("\n");
|
|
19
|
-
}
|
|
20
|
-
} catch {}
|
|
21
|
-
}
|
|
19
|
+
function escapePrimKey(primKey: string) {
|
|
20
|
+
return primKey.replace(/'/g, "''");
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
function createPrimKeyFilter(primKey: string | null) {
|
|
24
|
+
if (!primKey) {
|
|
25
|
+
return NO_TRANSACTION_FILTER;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
return
|
|
28
|
+
return `PrimKey = '${escapePrimKey(primKey)}'`;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export type TransactionsPreviewDialogProps = {
|
|
31
|
-
|
|
32
|
+
server: Server;
|
|
33
|
+
namespace: string | number;
|
|
32
34
|
frameWidth: number;
|
|
35
|
+
focusedPrimKey: string | null;
|
|
36
|
+
focusNext: () => void;
|
|
37
|
+
focusPrevious: () => void;
|
|
33
38
|
};
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
40
|
+
function Shortcut({ keyboard, command }: { keyboard: string; command: string }) {
|
|
41
|
+
return (
|
|
42
|
+
<Text>
|
|
43
|
+
[{keyboard}] {command}
|
|
44
|
+
</Text>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
export function TransactionsPreviewDialog({
|
|
49
|
+
server,
|
|
50
|
+
namespace,
|
|
51
|
+
frameWidth,
|
|
52
|
+
focusedPrimKey,
|
|
53
|
+
focusNext,
|
|
54
|
+
focusPrevious,
|
|
55
|
+
}: TransactionsPreviewDialogProps) {
|
|
56
|
+
let transactionFilter = useMemo(() => createPrimKeyFilter(focusedPrimKey), [focusedPrimKey]);
|
|
57
|
+
let { loading, error, refresh, normalizedTransactions, transactionMap } = useTransactions({
|
|
58
|
+
server,
|
|
59
|
+
namespace,
|
|
60
|
+
filter: transactionFilter,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let selectedPrimKeys = useMemo(
|
|
64
|
+
() => (focusedPrimKey ? new Set([focusedPrimKey]) : new Set<string>()),
|
|
65
|
+
[focusedPrimKey],
|
|
66
|
+
);
|
|
47
67
|
|
|
48
|
-
|
|
68
|
+
let { pendingStatuses, cycleStatus, save } = useTransactionEdits({
|
|
69
|
+
normalizedTransactions,
|
|
70
|
+
transactionRecordMap: transactionMap,
|
|
71
|
+
selectedPrimKeys,
|
|
72
|
+
server,
|
|
73
|
+
refresh,
|
|
74
|
+
});
|
|
49
75
|
|
|
50
|
-
|
|
51
|
-
let
|
|
52
|
-
() =>
|
|
53
|
-
Object.entries(record)
|
|
54
|
-
.filter(([key]) => key !== "PrimKey")
|
|
55
|
-
.map(([key, value]) => ({ key, lines: formatPreviewValue(value) })),
|
|
56
|
-
[record],
|
|
57
|
-
);
|
|
76
|
+
let record = focusedPrimKey ? transactionMap.get(focusedPrimKey) : undefined;
|
|
77
|
+
let pendingStatus = focusedPrimKey ? pendingStatuses[focusedPrimKey] : undefined;
|
|
58
78
|
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
79
|
+
let statusPreviewValue = useMemo(() => {
|
|
80
|
+
if (!record) {
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let originalStatus = record.Status;
|
|
85
|
+
let numericOriginal =
|
|
86
|
+
typeof originalStatus === "number"
|
|
87
|
+
? originalStatus
|
|
88
|
+
: typeof originalStatus === "string"
|
|
89
|
+
? Number(originalStatus)
|
|
90
|
+
: null;
|
|
91
|
+
|
|
92
|
+
let baseText: string;
|
|
93
|
+
if (pendingStatus !== undefined) {
|
|
94
|
+
baseText = String(pendingStatus);
|
|
95
|
+
} else if (originalStatus === null) {
|
|
96
|
+
baseText = "null";
|
|
97
|
+
} else if (originalStatus === undefined) {
|
|
98
|
+
baseText = "";
|
|
99
|
+
} else {
|
|
100
|
+
baseText = String(originalStatus);
|
|
101
|
+
}
|
|
63
102
|
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
103
|
+
let labelSource: number | null = null;
|
|
104
|
+
if (pendingStatus !== undefined) {
|
|
105
|
+
labelSource = pendingStatus;
|
|
106
|
+
} else if (numericOriginal !== null && Number.isFinite(numericOriginal)) {
|
|
107
|
+
labelSource = numericOriginal;
|
|
67
108
|
}
|
|
68
109
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
type: "line",
|
|
74
|
-
key: `${entry.key}-${lineIndex}`,
|
|
75
|
-
entryKey: entry.key,
|
|
76
|
-
line,
|
|
77
|
-
lineIndex,
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
if (entryIndex < previewEntries.length - 1) {
|
|
82
|
-
items.push({ type: "spacer", key: `${entry.key}-spacer` });
|
|
110
|
+
if (labelSource !== null) {
|
|
111
|
+
let label = transactionStatusLabels[labelSource as TransactionStatusCode];
|
|
112
|
+
if (label) {
|
|
113
|
+
baseText = baseText.length > 0 ? `${baseText} (${label})` : `(${label})`;
|
|
83
114
|
}
|
|
84
|
-
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let isDirty = false;
|
|
118
|
+
if (pendingStatus !== undefined) {
|
|
119
|
+
isDirty = numericOriginal === null || !Number.isFinite(numericOriginal) || pendingStatus !== numericOriginal;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (isDirty) {
|
|
123
|
+
baseText = `${baseText}${STATUS_DIRTY_INDICATOR}`;
|
|
124
|
+
}
|
|
85
125
|
|
|
86
|
-
return
|
|
87
|
-
}, [
|
|
126
|
+
return baseText;
|
|
127
|
+
}, [pendingStatus, record]);
|
|
88
128
|
|
|
89
|
-
let
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
129
|
+
let { text: transactionText, dialogWidth } = useTransactionText({
|
|
130
|
+
transaction: record,
|
|
131
|
+
statusPreviewValue,
|
|
132
|
+
frameWidth,
|
|
133
|
+
});
|
|
93
134
|
|
|
94
135
|
let { height: screenHeight } = useScreenSize();
|
|
95
136
|
|
|
96
|
-
let headerLineCount = 3 + (record
|
|
137
|
+
let headerLineCount = 3 + (record?.Name ? 1 : 0);
|
|
97
138
|
let marginLines = 1; // Box marginTop between header and content
|
|
98
|
-
let
|
|
99
|
-
let contentLineCount =
|
|
100
|
-
|
|
101
|
-
let
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
139
|
+
let baseNonContentLines = headerLineCount + marginLines;
|
|
140
|
+
let contentLineCount = transactionText.length > 0 ? transactionText.length : 1;
|
|
141
|
+
|
|
142
|
+
let computeLayout = (extraNonContentLines: number) => {
|
|
143
|
+
let nonContentLines = baseNonContentLines + extraNonContentLines;
|
|
144
|
+
let naturalDialogHeight = nonContentLines + contentLineCount;
|
|
145
|
+
let maxDialogHeight = Math.max(screenHeight - 4, nonContentLines + 1);
|
|
146
|
+
maxDialogHeight = Math.min(maxDialogHeight, Math.max(screenHeight - 2, 1));
|
|
147
|
+
let dialogHeightValue = Math.min(naturalDialogHeight, maxDialogHeight);
|
|
148
|
+
let contentHeightValue = Math.max(dialogHeightValue - nonContentLines, 1);
|
|
149
|
+
return { dialogHeight: dialogHeightValue, contentHeight: contentHeightValue, nonContentLines };
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
let { dialogHeight, contentHeight } = computeLayout(0);
|
|
153
|
+
let showScrollSummary = record && transactionText.length > contentHeight;
|
|
154
|
+
if (showScrollSummary) {
|
|
155
|
+
({ dialogHeight, contentHeight } = computeLayout(2));
|
|
156
|
+
}
|
|
107
157
|
|
|
108
158
|
let previewSignature = useMemo(
|
|
109
|
-
() =>
|
|
110
|
-
[
|
|
159
|
+
() => `${focusedPrimKey ?? ""}|${transactionText.join("|")}`,
|
|
160
|
+
[focusedPrimKey, transactionText],
|
|
111
161
|
);
|
|
112
162
|
|
|
113
|
-
let
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
setScrollOffset((offset) => Math.max(offset - 1, 0));
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (key.downArrow) {
|
|
137
|
-
setScrollOffset((offset) => Math.min(offset + 1, maxScrollOffset));
|
|
138
|
-
return;
|
|
163
|
+
let { scrollOffset } = useScrollableText({
|
|
164
|
+
textLength: transactionText.length,
|
|
165
|
+
viewportHeight: contentHeight,
|
|
166
|
+
signature: previewSignature,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
let { visibleText, totalLines, offset } = useVirtualText({
|
|
170
|
+
text: transactionText,
|
|
171
|
+
viewportHeight: contentHeight,
|
|
172
|
+
scrollOffset,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
useCommand("left", focusPrevious, true);
|
|
176
|
+
useCommand("right", focusNext, true);
|
|
177
|
+
useCommand(
|
|
178
|
+
PREVIEW_SAVE_SHORTCUTS,
|
|
179
|
+
() => {
|
|
180
|
+
if (focusedPrimKey) {
|
|
181
|
+
save({ primKey: focusedPrimKey });
|
|
139
182
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
return;
|
|
183
|
+
},
|
|
184
|
+
focusedPrimKey !== null,
|
|
185
|
+
);
|
|
186
|
+
useCommand(
|
|
187
|
+
"s",
|
|
188
|
+
() => {
|
|
189
|
+
if (focusedPrimKey) {
|
|
190
|
+
cycleStatus(focusedPrimKey, { exclusive: true });
|
|
149
191
|
}
|
|
150
192
|
},
|
|
151
|
-
|
|
193
|
+
focusedPrimKey !== null,
|
|
152
194
|
);
|
|
153
195
|
|
|
154
|
-
let visibleItems = previewItems.slice(scrollOffset, scrollOffset + contentHeight);
|
|
155
|
-
|
|
156
196
|
let summaryText = useMemo(() => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
197
|
+
if (totalLines === 0) {
|
|
198
|
+
return "";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let start = offset + 1;
|
|
202
|
+
let end = Math.min(offset + contentHeight, totalLines);
|
|
203
|
+
return `Showing ${start}–${end} of ${totalLines}`;
|
|
204
|
+
}, [contentHeight, offset, totalLines]);
|
|
205
|
+
|
|
206
|
+
let bodyContent: ReactNode;
|
|
207
|
+
if (!focusedPrimKey) {
|
|
208
|
+
bodyContent = <Text color="gray">No transaction selected.</Text>;
|
|
209
|
+
} else if (error) {
|
|
210
|
+
bodyContent = <Text color="red">{error.message}</Text>;
|
|
211
|
+
} else if (!record) {
|
|
212
|
+
bodyContent = loading ? (
|
|
213
|
+
<Text color="gray">Loading transaction…</Text>
|
|
214
|
+
) : (
|
|
215
|
+
<Text color="yellow">Transaction not found.</Text>
|
|
216
|
+
);
|
|
217
|
+
} else if (transactionText.length === 0) {
|
|
218
|
+
bodyContent = <Text color="gray">No data available.</Text>;
|
|
219
|
+
} else {
|
|
220
|
+
bodyContent = visibleText.map((line, index) => (
|
|
221
|
+
<Text key={`${offset + index}`}>{line.length > 0 ? line : " "}</Text>
|
|
222
|
+
));
|
|
223
|
+
}
|
|
161
224
|
|
|
162
225
|
return (
|
|
163
226
|
<FullScreenBox position="absolute" justifyContent="center" alignItems="center">
|
|
@@ -171,32 +234,19 @@ export function TransactionsPreviewDialog({ record, frameWidth }: TransactionsPr
|
|
|
171
234
|
backgroundColor="black"
|
|
172
235
|
>
|
|
173
236
|
<Text bold>Transaction preview</Text>
|
|
174
|
-
{record
|
|
175
|
-
<Text color="gray">
|
|
237
|
+
{record?.Name ? <Text>{record.Name}</Text> : null}
|
|
238
|
+
<Text color="gray">
|
|
239
|
+
<Shortcut keyboard="esc" command="Close preview" /> · <Shortcut keyboard="←/→" command="Change focus" /> ·{" "}
|
|
240
|
+
<Shortcut keyboard="↑/↓" command="Scroll" /> · <Shortcut keyboard="PgUp/PgDn" command="Scroll faster" /> ·{" "}
|
|
241
|
+
<Shortcut keyboard="s" command="Cycle status" /> · <Shortcut keyboard="Ctrl+S" command="Save" />
|
|
242
|
+
</Text>
|
|
176
243
|
<Box marginTop={1} flexDirection="column" height={contentHeight}>
|
|
177
|
-
{
|
|
178
|
-
<Text color="gray">No data available.</Text>
|
|
179
|
-
) : (
|
|
180
|
-
visibleItems.map((item) =>
|
|
181
|
-
item.type === "spacer" ? (
|
|
182
|
-
<Text key={item.key}> </Text>
|
|
183
|
-
) : (
|
|
184
|
-
<Box key={item.key} flexDirection="row" alignItems="flex-start">
|
|
185
|
-
<Box width={keyColumnWidth} marginRight={1} flexShrink={0}>
|
|
186
|
-
<Text bold={item.lineIndex === 0} wrap="truncate-end">
|
|
187
|
-
{item.lineIndex === 0 ? item.entryKey.padEnd(keyColumnWidth) : ""}
|
|
188
|
-
</Text>
|
|
189
|
-
</Box>
|
|
190
|
-
<Box flexGrow={1} flexShrink={1}>
|
|
191
|
-
<Text wrap="wrap">{item.line}</Text>
|
|
192
|
-
</Box>
|
|
193
|
-
</Box>
|
|
194
|
-
),
|
|
195
|
-
)
|
|
196
|
-
)}
|
|
244
|
+
{bodyContent}
|
|
197
245
|
</Box>
|
|
198
|
-
{
|
|
199
|
-
<
|
|
246
|
+
{showScrollSummary ? (
|
|
247
|
+
<Box marginTop={1} flexDirection="column">
|
|
248
|
+
<Text color="gray">{summaryText}</Text>
|
|
249
|
+
</Box>
|
|
200
250
|
) : null}
|
|
201
251
|
</Box>
|
|
202
252
|
</FullScreenBox>
|
|
@@ -14,8 +14,6 @@ const STATUS_COLUMN_MIN_WIDTH = (() => {
|
|
|
14
14
|
return Math.max("Status".length, longest + STATUS_DIRTY_INDICATOR.length);
|
|
15
15
|
})();
|
|
16
16
|
|
|
17
|
-
export const ERROR_COLUMN_MAX_LINES = 5;
|
|
18
|
-
|
|
19
17
|
export interface TableColumn<Row> {
|
|
20
18
|
key: keyof Row;
|
|
21
19
|
header: string;
|
|
@@ -23,8 +21,6 @@ export interface TableColumn<Row> {
|
|
|
23
21
|
max: number;
|
|
24
22
|
weight: number;
|
|
25
23
|
shrinkPriority?: number;
|
|
26
|
-
wrap?: boolean;
|
|
27
|
-
maxWrapLines?: number;
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
export interface EditorTransactionRow {
|
|
@@ -56,65 +52,6 @@ export function truncateCellValue(value: string, width: number) {
|
|
|
56
52
|
return `${value.slice(0, width - 1)}…`;
|
|
57
53
|
}
|
|
58
54
|
|
|
59
|
-
export function wrapCellValue(value: string, width: number, maxLines: number) {
|
|
60
|
-
if (width <= 0 || maxLines <= 0) {
|
|
61
|
-
return [];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let sanitized = sanitizeCellValue(value);
|
|
65
|
-
if (sanitized.length === 0) {
|
|
66
|
-
return ["".padEnd(width, " ")];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let remaining = sanitized;
|
|
70
|
-
let lines: string[] = [];
|
|
71
|
-
|
|
72
|
-
while (remaining.length > 0 && lines.length < maxLines) {
|
|
73
|
-
if (remaining.length <= width) {
|
|
74
|
-
lines.push(remaining.padEnd(width, " "));
|
|
75
|
-
remaining = "";
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let sliceEnd = Math.min(width, remaining.length);
|
|
80
|
-
let breakIndex = remaining.lastIndexOf(" ", sliceEnd);
|
|
81
|
-
if (breakIndex <= 0) {
|
|
82
|
-
breakIndex = sliceEnd;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
let chunk = remaining.slice(0, breakIndex).trimEnd();
|
|
86
|
-
if (chunk.length === 0) {
|
|
87
|
-
chunk = remaining.slice(0, width);
|
|
88
|
-
breakIndex = chunk.length;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
lines.push(chunk.padEnd(width, " "));
|
|
92
|
-
remaining = remaining.slice(breakIndex).trimStart();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (remaining.length > 0) {
|
|
96
|
-
let ellipsisLine: string;
|
|
97
|
-
if (lines.length === 0) {
|
|
98
|
-
let truncated = remaining.slice(0, Math.max(width - 1, 0));
|
|
99
|
-
ellipsisLine = `${truncated}${width > 0 ? "…" : ""}`;
|
|
100
|
-
} else {
|
|
101
|
-
let last = lines.pop() ?? "";
|
|
102
|
-
let trimmed = last.trimEnd();
|
|
103
|
-
if (trimmed.length >= width) {
|
|
104
|
-
trimmed = trimmed.slice(0, Math.max(width - 1, 0));
|
|
105
|
-
}
|
|
106
|
-
ellipsisLine = `${trimmed}${width > 0 ? "…" : ""}`;
|
|
107
|
-
}
|
|
108
|
-
lines.push(ellipsisLine.padEnd(width, " "));
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (lines.length === 0) {
|
|
112
|
-
lines.push("".padEnd(width, " "));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return lines;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
55
|
export function calculateColumnWidths<Row extends Record<string, unknown>>({
|
|
119
56
|
columns,
|
|
120
57
|
rows,
|
|
@@ -218,39 +155,18 @@ export function formatRowLines<Row extends Record<string, unknown>>({
|
|
|
218
155
|
widths: number[];
|
|
219
156
|
separator?: string;
|
|
220
157
|
}) {
|
|
221
|
-
let
|
|
158
|
+
let parts = columns.map((column, index) => {
|
|
222
159
|
let width = widths[index];
|
|
223
|
-
if (column.wrap) {
|
|
224
|
-
let maxLines = column.maxWrapLines ?? 1;
|
|
225
|
-
return wrapCellValue(String(row[column.key] ?? ""), width, maxLines);
|
|
226
|
-
}
|
|
227
160
|
let value = sanitizeCellValue(row[column.key]);
|
|
228
|
-
return
|
|
161
|
+
return truncateCellValue(value, width);
|
|
229
162
|
});
|
|
230
163
|
|
|
231
|
-
let
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
for (let lineIndex = 0; lineIndex < height; lineIndex++) {
|
|
235
|
-
let parts = columnLines.map((column, index) => {
|
|
236
|
-
let width = widths[index];
|
|
237
|
-
let value = column[lineIndex] ?? "";
|
|
238
|
-
if (value.length < width) {
|
|
239
|
-
return value.padEnd(width, " ");
|
|
240
|
-
}
|
|
241
|
-
if (value.length > width) {
|
|
242
|
-
return truncateCellValue(value, width);
|
|
243
|
-
}
|
|
244
|
-
return value;
|
|
245
|
-
});
|
|
246
|
-
lines.push(parts.join(separator));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (lines.length === 0) {
|
|
250
|
-
lines.push(columns.map((_, index) => "".padEnd(widths[index], " ")).join(separator));
|
|
164
|
+
let line = parts.join(separator);
|
|
165
|
+
if (line.length === 0) {
|
|
166
|
+
return [columns.map((_, index) => "".padEnd(widths[index], " ")).join(separator)];
|
|
251
167
|
}
|
|
252
168
|
|
|
253
|
-
return
|
|
169
|
+
return [line];
|
|
254
170
|
}
|
|
255
171
|
|
|
256
172
|
export function addViewportEllipsis(line: string) {
|
|
@@ -308,8 +224,8 @@ export function createStatusLine(message: string, stats: string, lineWidth: numb
|
|
|
308
224
|
export function createTransactionColumns(showError: boolean): TableColumn<EditorTransactionRow>[] {
|
|
309
225
|
let columns: TableColumn<EditorTransactionRow>[] = [
|
|
310
226
|
{ key: "Namespace", header: "Namespace", min: 10, max: 24, weight: 0, shrinkPriority: 3 },
|
|
311
|
-
{ key: "Name", header: "Name", min:
|
|
312
|
-
{ key: "CreatedBy", header: "CreatedBy", min:
|
|
227
|
+
{ key: "Name", header: "Name", min: 32, max: 40, weight: 1, shrinkPriority: 1 },
|
|
228
|
+
{ key: "CreatedBy", header: "CreatedBy", min: 13, max: 20, weight: 0, shrinkPriority: 4 },
|
|
313
229
|
{
|
|
314
230
|
key: "Status",
|
|
315
231
|
header: "Status",
|
|
@@ -328,8 +244,6 @@ export function createTransactionColumns(showError: boolean): TableColumn<Editor
|
|
|
328
244
|
max: 120,
|
|
329
245
|
weight: 2,
|
|
330
246
|
shrinkPriority: 0,
|
|
331
|
-
wrap: true,
|
|
332
|
-
maxWrapLines: ERROR_COLUMN_MAX_LINES,
|
|
333
247
|
});
|
|
334
248
|
}
|
|
335
249
|
|