effective-progress 0.8.1 → 0.10.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/README.md +2 -0
- package/dist/index.d.mts +4 -5
- package/dist/index.mjs +845 -863
- package/package.json +7 -2
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Brand, Cause, Clock, Context,
|
|
1
|
+
import { Brand, Cause, Clock, Context, Effect, Exit, FiberRef, Layer, Option, Schema } from "effect";
|
|
2
2
|
import { dual } from "effect/Function";
|
|
3
3
|
import { Box, Text, render } from "ink";
|
|
4
|
-
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
5
4
|
import stringWidth from "fast-string-width";
|
|
6
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import cliSpinners from "cli-spinners";
|
|
7
|
+
import { createContext, memo, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
8
|
+
import { VirtualList } from "ink-virtual-list";
|
|
7
9
|
|
|
8
10
|
//#region src/types.ts
|
|
9
11
|
const TaskIdSchema = Schema.Number.pipe(Schema.brand("TaskId"));
|
|
@@ -16,7 +18,7 @@ const TaskUnitsSchema = Schema.Struct({
|
|
|
16
18
|
processed: Schema.Number,
|
|
17
19
|
total: Schema.optional(Schema.Number)
|
|
18
20
|
});
|
|
19
|
-
|
|
21
|
+
const TaskSnapshotSchema = Schema.Struct({
|
|
20
22
|
id: TaskIdSchema,
|
|
21
23
|
parentId: Schema.NullOr(TaskIdSchema),
|
|
22
24
|
description: Schema.String,
|
|
@@ -26,7 +28,8 @@ var TaskSnapshot = class extends Schema.TaggedClass()("TaskSnapshot", {
|
|
|
26
28
|
units: TaskUnitsSchema,
|
|
27
29
|
startedAt: Schema.Number,
|
|
28
30
|
completedAt: Schema.NullOr(Schema.Number)
|
|
29
|
-
})
|
|
31
|
+
});
|
|
32
|
+
const TaskSnapshot = (snapshot) => snapshot;
|
|
30
33
|
var Task = class extends Context.Tag("stromseng.dev/effective-progress/Task")() {};
|
|
31
34
|
var TaskAddedEvent = class extends Schema.TaggedClass()("TaskAdded", {
|
|
32
35
|
taskId: TaskIdSchema,
|
|
@@ -58,60 +61,7 @@ const ProgressTaskEventSchema = Schema.Union(TaskAddedEvent, TaskUpdatedEvent, T
|
|
|
58
61
|
const decodeProgressTaskEvent = Schema.decodeUnknownSync(ProgressTaskEventSchema);
|
|
59
62
|
|
|
60
63
|
//#endregion
|
|
61
|
-
//#region src/
|
|
62
|
-
const orderedVisibleTasks = (store) => store.renderOrder.flatMap((row) => {
|
|
63
|
-
const snapshot = store.tasks.get(row.id);
|
|
64
|
-
if (!snapshot || snapshot.transient && snapshot.status !== "running") return [];
|
|
65
|
-
return [{
|
|
66
|
-
snapshot,
|
|
67
|
-
depth: row.depth
|
|
68
|
-
}];
|
|
69
|
-
});
|
|
70
|
-
const computeTreeInfo = (ordered) => {
|
|
71
|
-
const hasNextSiblingByIndex = Array.from({ length: ordered.length }, () => false);
|
|
72
|
-
for (let i = 0; i < ordered.length; i++) {
|
|
73
|
-
const depth = ordered[i].depth;
|
|
74
|
-
for (let j = i + 1; j < ordered.length; j++) {
|
|
75
|
-
const candidateDepth = ordered[j].depth;
|
|
76
|
-
if (candidateDepth < depth) break;
|
|
77
|
-
if (candidateDepth === depth) {
|
|
78
|
-
hasNextSiblingByIndex[i] = true;
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const ancestorStateByDepth = [];
|
|
84
|
-
return ordered.map((entry, index) => {
|
|
85
|
-
const depth = entry.depth;
|
|
86
|
-
ancestorStateByDepth.length = depth;
|
|
87
|
-
const hasChildren = index + 1 < ordered.length && ordered[index + 1] !== void 0 && ordered[index + 1].depth > depth;
|
|
88
|
-
const tree = {
|
|
89
|
-
depth,
|
|
90
|
-
hasNextSibling: hasNextSiblingByIndex[index] ?? false,
|
|
91
|
-
hasChildren,
|
|
92
|
-
ancestorHasNextSibling: [...ancestorStateByDepth]
|
|
93
|
-
};
|
|
94
|
-
ancestorStateByDepth[depth] = hasNextSiblingByIndex[index] ?? false;
|
|
95
|
-
return {
|
|
96
|
-
...entry,
|
|
97
|
-
tree
|
|
98
|
-
};
|
|
99
|
-
});
|
|
100
|
-
};
|
|
101
|
-
const toRenderSnapshot = (store) => {
|
|
102
|
-
const visibleTasks = orderedVisibleTasks(store);
|
|
103
|
-
const hasRunningTasks = visibleTasks.some((entry) => entry.snapshot.status === "running");
|
|
104
|
-
return {
|
|
105
|
-
rows: computeTreeInfo(visibleTasks).map((entry) => ({
|
|
106
|
-
task: entry.snapshot,
|
|
107
|
-
tree: entry.tree
|
|
108
|
-
})),
|
|
109
|
-
hasRunningTasks
|
|
110
|
-
};
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
//#endregion
|
|
114
|
-
//#region src/ink-renderer/store.ts
|
|
64
|
+
//#region src/renderer/store.ts
|
|
115
65
|
const hasExplicitTotal = (options) => Object.prototype.hasOwnProperty.call(options, "total");
|
|
116
66
|
const sanitizeTotalOnAdd = (total) => {
|
|
117
67
|
if (total === void 0) return;
|
|
@@ -142,7 +92,7 @@ const updatedSnapshot = (snapshot, options) => {
|
|
|
142
92
|
failed: options.failed ?? currentUnits.failed,
|
|
143
93
|
total: hasExplicitTotal(options) ? sanitizeTotalOnUpdate(options.total) : currentUnits.total
|
|
144
94
|
});
|
|
145
|
-
return
|
|
95
|
+
return TaskSnapshot({
|
|
146
96
|
id: snapshot.id,
|
|
147
97
|
parentId: snapshot.parentId,
|
|
148
98
|
description: options.description ?? snapshot.description,
|
|
@@ -154,7 +104,7 @@ const updatedSnapshot = (snapshot, options) => {
|
|
|
154
104
|
completedAt: snapshot.completedAt
|
|
155
105
|
});
|
|
156
106
|
};
|
|
157
|
-
const withTransient = (snapshot, transient) =>
|
|
107
|
+
const withTransient = (snapshot, transient) => TaskSnapshot({
|
|
158
108
|
id: snapshot.id,
|
|
159
109
|
parentId: snapshot.parentId,
|
|
160
110
|
description: snapshot.description,
|
|
@@ -193,14 +143,26 @@ const removeFromRenderOrder = (renderOrder, taskId) => {
|
|
|
193
143
|
next.splice(idx, end - idx);
|
|
194
144
|
return next;
|
|
195
145
|
};
|
|
196
|
-
const
|
|
146
|
+
const subtreeTaskIds = (renderOrder, taskId) => {
|
|
147
|
+
const idx = renderOrder.findIndex((row) => row.id === taskId);
|
|
148
|
+
if (idx === -1) return [];
|
|
149
|
+
const taskDepth = renderOrder[idx].depth;
|
|
150
|
+
let end = idx + 1;
|
|
151
|
+
while (end < renderOrder.length && renderOrder[end].depth > taskDepth) end++;
|
|
152
|
+
return renderOrder.slice(idx, end).map((row) => row.id);
|
|
153
|
+
};
|
|
154
|
+
const SNAPSHOT_PUBLISH_INTERVAL_MILLIS = 100;
|
|
197
155
|
const makeProgressRenderStore = () => {
|
|
198
156
|
let nextTaskId = 0;
|
|
199
157
|
let state = {
|
|
200
158
|
tasks: /* @__PURE__ */ new Map(),
|
|
201
159
|
renderOrder: []
|
|
202
160
|
};
|
|
203
|
-
let
|
|
161
|
+
let pendingEvents = [];
|
|
162
|
+
let publishedPublication = {
|
|
163
|
+
snapshot: state,
|
|
164
|
+
events: []
|
|
165
|
+
};
|
|
204
166
|
let hasPendingPublish = false;
|
|
205
167
|
let lastPublishAt = 0;
|
|
206
168
|
let publishTimeout;
|
|
@@ -209,10 +171,15 @@ const makeProgressRenderStore = () => {
|
|
|
209
171
|
for (const listener of listeners) listener();
|
|
210
172
|
};
|
|
211
173
|
const publishNow = () => {
|
|
174
|
+
const nextPublication = {
|
|
175
|
+
snapshot: state,
|
|
176
|
+
events: [...pendingEvents]
|
|
177
|
+
};
|
|
212
178
|
hasPendingPublish = false;
|
|
213
|
-
|
|
214
|
-
publishedSnapshot = toRenderSnapshot(state);
|
|
179
|
+
publishedPublication = nextPublication;
|
|
215
180
|
notifyListeners();
|
|
181
|
+
pendingEvents = [];
|
|
182
|
+
lastPublishAt = Date.now();
|
|
216
183
|
};
|
|
217
184
|
const clearScheduledPublish = () => {
|
|
218
185
|
if (publishTimeout === void 0) return;
|
|
@@ -235,9 +202,10 @@ const makeProgressRenderStore = () => {
|
|
|
235
202
|
publishNow();
|
|
236
203
|
}, waitMillis);
|
|
237
204
|
};
|
|
238
|
-
const publish = (
|
|
239
|
-
if (
|
|
240
|
-
state =
|
|
205
|
+
const publish = (update) => {
|
|
206
|
+
if (update.state === state) return;
|
|
207
|
+
state = update.state;
|
|
208
|
+
if (update.events.length > 0) pendingEvents.push(...update.events);
|
|
241
209
|
hasPendingPublish = true;
|
|
242
210
|
schedulePublish();
|
|
243
211
|
};
|
|
@@ -245,7 +213,7 @@ const makeProgressRenderStore = () => {
|
|
|
245
213
|
publish(transform(state));
|
|
246
214
|
};
|
|
247
215
|
return {
|
|
248
|
-
getSnapshot: () =>
|
|
216
|
+
getSnapshot: () => publishedPublication,
|
|
249
217
|
subscribe: (listener) => {
|
|
250
218
|
listeners.add(listener);
|
|
251
219
|
return () => {
|
|
@@ -268,7 +236,7 @@ const makeProgressRenderStore = () => {
|
|
|
268
236
|
const now = yield* Clock.currentTimeMillis;
|
|
269
237
|
const parentId = options.parentId ?? null;
|
|
270
238
|
const countDisplay = options.countDisplay ?? parentSnapshot?.countDisplay ?? "detailed";
|
|
271
|
-
const task =
|
|
239
|
+
const task = TaskSnapshot({
|
|
272
240
|
id: taskId,
|
|
273
241
|
parentId,
|
|
274
242
|
description: options.description,
|
|
@@ -289,45 +257,70 @@ const makeProgressRenderStore = () => {
|
|
|
289
257
|
depth
|
|
290
258
|
});
|
|
291
259
|
return {
|
|
292
|
-
|
|
293
|
-
|
|
260
|
+
state: {
|
|
261
|
+
tasks: nextTasks,
|
|
262
|
+
renderOrder: nextRenderOrder
|
|
263
|
+
},
|
|
264
|
+
events: [new TaskAddedEvent({
|
|
265
|
+
taskId,
|
|
266
|
+
parentId,
|
|
267
|
+
description: task.description,
|
|
268
|
+
total: task.units.total,
|
|
269
|
+
transient: task.transient,
|
|
270
|
+
countDisplay: task.countDisplay
|
|
271
|
+
})]
|
|
294
272
|
};
|
|
295
273
|
});
|
|
296
274
|
return taskId;
|
|
297
275
|
}),
|
|
298
|
-
updateTask: (taskId, options) => Effect.
|
|
299
|
-
const currentTask = state.tasks.get(taskId);
|
|
300
|
-
if (!currentTask) return;
|
|
301
|
-
const nextTask = updatedSnapshot(currentTask, options);
|
|
276
|
+
updateTask: (taskId, options) => Effect.sync(() => {
|
|
302
277
|
updateState((current) => {
|
|
303
|
-
|
|
278
|
+
const currentTask = current.tasks.get(taskId);
|
|
279
|
+
if (!currentTask) return {
|
|
280
|
+
state: current,
|
|
281
|
+
events: []
|
|
282
|
+
};
|
|
283
|
+
const nextTask = updatedSnapshot(currentTask, options);
|
|
304
284
|
const nextTasks = new Map(current.tasks);
|
|
305
285
|
nextTasks.set(taskId, nextTask);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
286
|
+
const events = [new TaskUpdatedEvent({
|
|
287
|
+
taskId,
|
|
288
|
+
description: options.description ?? void 0,
|
|
289
|
+
succeeded: options.succeeded ?? void 0,
|
|
290
|
+
failed: options.failed ?? void 0,
|
|
291
|
+
processed: options.succeeded !== void 0 || options.failed !== void 0 ? nextTask.units.processed : void 0,
|
|
292
|
+
total: hasExplicitTotal(options) ? nextTask.units.total : void 0,
|
|
293
|
+
transient: options.transient ?? void 0,
|
|
294
|
+
countDisplay: options.countDisplay ?? void 0
|
|
295
|
+
})];
|
|
296
|
+
if (options.transient !== void 0) for (const candidateId of subtreeTaskIds(current.renderOrder, taskId).slice(1)) {
|
|
297
|
+
const candidate = current.tasks.get(candidateId);
|
|
298
|
+
if (!candidate) continue;
|
|
299
|
+
const nextCandidate = withTransient(candidate, nextTask.transient);
|
|
300
|
+
nextTasks.set(candidateId, nextCandidate);
|
|
301
|
+
events.push(new TaskUpdatedEvent({
|
|
302
|
+
taskId: candidateId,
|
|
303
|
+
transient: nextCandidate.transient
|
|
304
|
+
}));
|
|
318
305
|
}
|
|
319
306
|
return {
|
|
320
|
-
|
|
321
|
-
|
|
307
|
+
state: {
|
|
308
|
+
tasks: nextTasks,
|
|
309
|
+
renderOrder: current.renderOrder
|
|
310
|
+
},
|
|
311
|
+
events
|
|
322
312
|
};
|
|
323
313
|
});
|
|
324
314
|
}),
|
|
325
315
|
incrementSucceeded: (taskId, amount = 1) => Effect.sync(() => {
|
|
326
316
|
updateState((current) => {
|
|
327
317
|
const currentTask = current.tasks.get(taskId);
|
|
328
|
-
if (!currentTask) return
|
|
318
|
+
if (!currentTask) return {
|
|
319
|
+
state: current,
|
|
320
|
+
events: []
|
|
321
|
+
};
|
|
329
322
|
const nextTasks = new Map(current.tasks);
|
|
330
|
-
nextTasks.set(taskId,
|
|
323
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
331
324
|
id: currentTask.id,
|
|
332
325
|
parentId: currentTask.parentId,
|
|
333
326
|
description: currentTask.description,
|
|
@@ -343,17 +336,27 @@ const makeProgressRenderStore = () => {
|
|
|
343
336
|
completedAt: currentTask.completedAt
|
|
344
337
|
}));
|
|
345
338
|
return {
|
|
346
|
-
|
|
347
|
-
|
|
339
|
+
state: {
|
|
340
|
+
tasks: nextTasks,
|
|
341
|
+
renderOrder: current.renderOrder
|
|
342
|
+
},
|
|
343
|
+
events: [new TaskAdvancedEvent({
|
|
344
|
+
taskId,
|
|
345
|
+
amount,
|
|
346
|
+
kind: "succeeded"
|
|
347
|
+
})]
|
|
348
348
|
};
|
|
349
349
|
});
|
|
350
350
|
}),
|
|
351
351
|
incrementFailed: (taskId, amount = 1) => Effect.sync(() => {
|
|
352
352
|
updateState((current) => {
|
|
353
353
|
const currentTask = current.tasks.get(taskId);
|
|
354
|
-
if (!currentTask) return
|
|
354
|
+
if (!currentTask) return {
|
|
355
|
+
state: current,
|
|
356
|
+
events: []
|
|
357
|
+
};
|
|
355
358
|
const nextTasks = new Map(current.tasks);
|
|
356
|
-
nextTasks.set(taskId,
|
|
359
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
357
360
|
id: currentTask.id,
|
|
358
361
|
parentId: currentTask.parentId,
|
|
359
362
|
description: currentTask.description,
|
|
@@ -369,8 +372,15 @@ const makeProgressRenderStore = () => {
|
|
|
369
372
|
completedAt: currentTask.completedAt
|
|
370
373
|
}));
|
|
371
374
|
return {
|
|
372
|
-
|
|
373
|
-
|
|
375
|
+
state: {
|
|
376
|
+
tasks: nextTasks,
|
|
377
|
+
renderOrder: current.renderOrder
|
|
378
|
+
},
|
|
379
|
+
events: [new TaskAdvancedEvent({
|
|
380
|
+
taskId,
|
|
381
|
+
amount,
|
|
382
|
+
kind: "failed"
|
|
383
|
+
})]
|
|
374
384
|
};
|
|
375
385
|
});
|
|
376
386
|
}),
|
|
@@ -378,16 +388,23 @@ const makeProgressRenderStore = () => {
|
|
|
378
388
|
const now = yield* Clock.currentTimeMillis;
|
|
379
389
|
updateState((current) => {
|
|
380
390
|
const currentTask = current.tasks.get(taskId);
|
|
381
|
-
if (!currentTask) return
|
|
391
|
+
if (!currentTask) return {
|
|
392
|
+
state: current,
|
|
393
|
+
events: []
|
|
394
|
+
};
|
|
382
395
|
const nextTasks = new Map(current.tasks);
|
|
383
396
|
if (currentTask.transient) {
|
|
384
|
-
|
|
397
|
+
const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
|
|
398
|
+
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
385
399
|
return {
|
|
386
|
-
|
|
387
|
-
|
|
400
|
+
state: {
|
|
401
|
+
tasks: nextTasks,
|
|
402
|
+
renderOrder: removeFromRenderOrder(current.renderOrder, taskId)
|
|
403
|
+
},
|
|
404
|
+
events: [new TaskCompletedEvent({ taskId }), ...removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
|
|
388
405
|
};
|
|
389
406
|
}
|
|
390
|
-
nextTasks.set(taskId,
|
|
407
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
391
408
|
id: currentTask.id,
|
|
392
409
|
parentId: currentTask.parentId,
|
|
393
410
|
description: currentTask.description,
|
|
@@ -407,8 +424,11 @@ const makeProgressRenderStore = () => {
|
|
|
407
424
|
completedAt: now
|
|
408
425
|
}));
|
|
409
426
|
return {
|
|
410
|
-
|
|
411
|
-
|
|
427
|
+
state: {
|
|
428
|
+
tasks: nextTasks,
|
|
429
|
+
renderOrder: current.renderOrder
|
|
430
|
+
},
|
|
431
|
+
events: [new TaskCompletedEvent({ taskId })]
|
|
412
432
|
};
|
|
413
433
|
});
|
|
414
434
|
}),
|
|
@@ -416,16 +436,23 @@ const makeProgressRenderStore = () => {
|
|
|
416
436
|
const now = yield* Clock.currentTimeMillis;
|
|
417
437
|
updateState((current) => {
|
|
418
438
|
const currentTask = current.tasks.get(taskId);
|
|
419
|
-
if (!currentTask) return
|
|
439
|
+
if (!currentTask) return {
|
|
440
|
+
state: current,
|
|
441
|
+
events: []
|
|
442
|
+
};
|
|
420
443
|
const nextTasks = new Map(current.tasks);
|
|
421
444
|
if (currentTask.transient) {
|
|
422
|
-
|
|
445
|
+
const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
|
|
446
|
+
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
423
447
|
return {
|
|
424
|
-
|
|
425
|
-
|
|
448
|
+
state: {
|
|
449
|
+
tasks: nextTasks,
|
|
450
|
+
renderOrder: removeFromRenderOrder(current.renderOrder, taskId)
|
|
451
|
+
},
|
|
452
|
+
events: [new TaskFailedEvent({ taskId }), ...removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
|
|
426
453
|
};
|
|
427
454
|
}
|
|
428
|
-
nextTasks.set(taskId,
|
|
455
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
429
456
|
id: currentTask.id,
|
|
430
457
|
parentId: currentTask.parentId,
|
|
431
458
|
description: currentTask.description,
|
|
@@ -437,8 +464,11 @@ const makeProgressRenderStore = () => {
|
|
|
437
464
|
completedAt: now
|
|
438
465
|
}));
|
|
439
466
|
return {
|
|
440
|
-
|
|
441
|
-
|
|
467
|
+
state: {
|
|
468
|
+
tasks: nextTasks,
|
|
469
|
+
renderOrder: current.renderOrder
|
|
470
|
+
},
|
|
471
|
+
events: [new TaskFailedEvent({ taskId })]
|
|
442
472
|
};
|
|
443
473
|
});
|
|
444
474
|
}),
|
|
@@ -448,46 +478,11 @@ const makeProgressRenderStore = () => {
|
|
|
448
478
|
};
|
|
449
479
|
|
|
450
480
|
//#endregion
|
|
451
|
-
//#region src/
|
|
452
|
-
const
|
|
453
|
-
const tasks = /* @__PURE__ */ new Map();
|
|
454
|
-
const trees = /* @__PURE__ */ new Map();
|
|
455
|
-
for (const row of rows) {
|
|
456
|
-
tasks.set(row.task.id, row.task);
|
|
457
|
-
trees.set(row.task.id, row.tree);
|
|
458
|
-
}
|
|
459
|
-
return {
|
|
460
|
-
taskIds: rows.map((row) => row.task.id),
|
|
461
|
-
now,
|
|
462
|
-
tick,
|
|
463
|
-
stickyWidths,
|
|
464
|
-
getTask: (taskId) => {
|
|
465
|
-
const task = tasks.get(taskId);
|
|
466
|
-
if (task === void 0) throw new Error(`Unknown task id: ${taskId}`);
|
|
467
|
-
return task;
|
|
468
|
-
},
|
|
469
|
-
getTree: (taskId) => {
|
|
470
|
-
const tree = trees.get(taskId);
|
|
471
|
-
if (tree === void 0) throw new Error(`Unknown task tree: ${taskId}`);
|
|
472
|
-
return tree;
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
};
|
|
481
|
+
//#region src/renderer/shared/determinate.ts
|
|
482
|
+
const isDeterminate$2 = (task) => task.units.total !== void 0;
|
|
476
483
|
|
|
477
484
|
//#endregion
|
|
478
|
-
//#region src/
|
|
479
|
-
const SPINNER_FRAMES = [
|
|
480
|
-
"⠋",
|
|
481
|
-
"⠙",
|
|
482
|
-
"⠹",
|
|
483
|
-
"⠸",
|
|
484
|
-
"⠼",
|
|
485
|
-
"⠴",
|
|
486
|
-
"⠦",
|
|
487
|
-
"⠧",
|
|
488
|
-
"⠇",
|
|
489
|
-
"⠏"
|
|
490
|
-
];
|
|
485
|
+
//#region src/renderer/shared/format.ts
|
|
491
486
|
const isDeterminate$1 = (task) => task.units.total !== void 0;
|
|
492
487
|
const showsUnknownTotalCounts = (task) => task.units.total === void 0 && task.units.processed > 0;
|
|
493
488
|
const formatDurationSeconds = (seconds) => {
|
|
@@ -513,37 +508,6 @@ const formatEta = (task, now) => {
|
|
|
513
508
|
const elapsedMillis = Math.max(1, now - task.startedAt);
|
|
514
509
|
return formatDurationSeconds(Math.max(0, Math.floor(elapsedMillis / processed * remaining)) / 1e3);
|
|
515
510
|
};
|
|
516
|
-
const getTaskIndicator = (task, tick) => {
|
|
517
|
-
if (task.status === "running") return {
|
|
518
|
-
symbol: SPINNER_FRAMES[tick % SPINNER_FRAMES.length] ?? SPINNER_FRAMES[0],
|
|
519
|
-
color: "yellow"
|
|
520
|
-
};
|
|
521
|
-
if (task.status === "failed") return {
|
|
522
|
-
symbol: "✗",
|
|
523
|
-
color: "red"
|
|
524
|
-
};
|
|
525
|
-
if (!isDeterminate$1(task)) return {
|
|
526
|
-
symbol: "✓",
|
|
527
|
-
color: "green"
|
|
528
|
-
};
|
|
529
|
-
const { succeeded, failed, processed, total } = task.units;
|
|
530
|
-
if (failed === 0 && processed === total) return {
|
|
531
|
-
symbol: "✓",
|
|
532
|
-
color: "green"
|
|
533
|
-
};
|
|
534
|
-
if (failed > 0 && succeeded > 0) return {
|
|
535
|
-
symbol: "~",
|
|
536
|
-
color: "yellow"
|
|
537
|
-
};
|
|
538
|
-
if (failed > 0 && succeeded === 0) return {
|
|
539
|
-
symbol: "✗",
|
|
540
|
-
color: "red"
|
|
541
|
-
};
|
|
542
|
-
return {
|
|
543
|
-
symbol: "✓",
|
|
544
|
-
color: "green"
|
|
545
|
-
};
|
|
546
|
-
};
|
|
547
511
|
const formatDeterminateAmountParts = (task) => {
|
|
548
512
|
if (!isDeterminate$1(task)) return;
|
|
549
513
|
const totalText = `${task.units.total}`;
|
|
@@ -556,16 +520,6 @@ const formatDeterminateAmountParts = (task) => {
|
|
|
556
520
|
total: totalText
|
|
557
521
|
};
|
|
558
522
|
};
|
|
559
|
-
const getDeterminateProcessedColor = (task) => {
|
|
560
|
-
if (!isDeterminate$1(task)) return "whiteBright";
|
|
561
|
-
const { succeeded, failed, processed, total } = task.units;
|
|
562
|
-
if (task.status === "failed" && processed < total) return "red";
|
|
563
|
-
if (processed >= total && failed === 0) return "green";
|
|
564
|
-
if (processed >= total && failed > 0 && succeeded === 0) return "red";
|
|
565
|
-
if (succeeded > 0 && failed > 0) return "yellow";
|
|
566
|
-
if (failed > 0 && succeeded === 0) return "red";
|
|
567
|
-
return "whiteBright";
|
|
568
|
-
};
|
|
569
523
|
const formatAmount = (task, _tick) => {
|
|
570
524
|
if (isDeterminate$1(task)) {
|
|
571
525
|
const parts = formatDeterminateAmountParts(task);
|
|
@@ -582,780 +536,808 @@ const formatAmount = (task, _tick) => {
|
|
|
582
536
|
};
|
|
583
537
|
|
|
584
538
|
//#endregion
|
|
585
|
-
//#region src/
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
539
|
+
//#region src/renderer/shared/text-width.ts
|
|
540
|
+
const WIDTH_CACHE_LIMIT = 8192;
|
|
541
|
+
const widthCache = /* @__PURE__ */ new Map();
|
|
542
|
+
const textWidth = (text) => {
|
|
543
|
+
const cached = widthCache.get(text);
|
|
544
|
+
if (cached !== void 0) return cached;
|
|
545
|
+
const width = stringWidth(text);
|
|
546
|
+
if (widthCache.size >= WIDTH_CACHE_LIMIT) widthCache.clear();
|
|
547
|
+
widthCache.set(text, width);
|
|
548
|
+
return width;
|
|
591
549
|
};
|
|
592
550
|
|
|
593
551
|
//#endregion
|
|
594
|
-
//#region src/
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
552
|
+
//#region src/renderer/columns/amount-column.tsx
|
|
553
|
+
const hasUnknownTotalCounts = (task) => task.units.total === void 0 && task.units.processed > 0;
|
|
554
|
+
const hasCountedAmount = (task) => isDeterminate$2(task) || hasUnknownTotalCounts(task);
|
|
555
|
+
const totalTextFor = (task) => task.units.total === void 0 ? "?" : `${task.units.total}`;
|
|
556
|
+
const emptyAmountLayout = {
|
|
557
|
+
hasDetailedRows: false,
|
|
558
|
+
countWidth: 0,
|
|
559
|
+
processedWidth: 0,
|
|
560
|
+
totalWidth: 0,
|
|
561
|
+
preferredWidth: 0
|
|
562
|
+
};
|
|
563
|
+
const measureAmountLayout = (rows) => {
|
|
564
|
+
const countedTasks = rows.flatMap((row) => hasCountedAmount(row.task) ? [row.task] : []);
|
|
565
|
+
const hasDetailedRows = countedTasks.some((task) => task.countDisplay === "detailed");
|
|
566
|
+
if (countedTasks.length === 0) {
|
|
567
|
+
const preferredWidth = rows.reduce((max, row) => Math.max(max, textWidth(formatAmount(row.task, 0))), 0);
|
|
568
|
+
return {
|
|
569
|
+
...emptyAmountLayout,
|
|
570
|
+
preferredWidth
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
const processedWidth = countedTasks.reduce((max, task) => Math.max(max, `${task.units.processed}`.length), 1);
|
|
574
|
+
const totalWidth = countedTasks.reduce((max, task) => Math.max(max, totalTextFor(task).length), 1);
|
|
575
|
+
const countWidth = hasDetailedRows ? countedTasks.reduce((max, task) => Math.max(max, processedWidth, totalWidth, `${task.units.succeeded}`.length, `${task.units.failed}`.length), 1) : 0;
|
|
576
|
+
const countedWidth = (hasDetailedRows ? countWidth + 1 + countWidth + 1 : 0) + processedWidth + 1 + totalWidth;
|
|
598
577
|
return {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
578
|
+
hasDetailedRows,
|
|
579
|
+
countWidth,
|
|
580
|
+
processedWidth,
|
|
581
|
+
totalWidth,
|
|
582
|
+
preferredWidth: rows.reduce((max, row) => {
|
|
583
|
+
if (hasCountedAmount(row.task)) return Math.max(max, countedWidth);
|
|
584
|
+
return Math.max(max, textWidth(formatAmount(row.task, 0)));
|
|
585
|
+
}, countedWidth)
|
|
602
586
|
};
|
|
603
587
|
};
|
|
604
|
-
const
|
|
605
|
-
|
|
588
|
+
const renderAmount = (task, layout) => {
|
|
589
|
+
if (!hasCountedAmount(task)) return formatAmount(task, 0);
|
|
590
|
+
const processed = `${task.units.processed}`.padStart(layout.processedWidth, " ");
|
|
591
|
+
const total = totalTextFor(task).padStart(layout.totalWidth, " ");
|
|
592
|
+
if (!layout.hasDetailedRows) return `${processed}/${total}`;
|
|
593
|
+
if (task.countDisplay !== "detailed") return `${" ".repeat(layout.countWidth)} ${" ".repeat(layout.countWidth)} ${processed}/${total}`;
|
|
594
|
+
const succeeded = `${task.units.succeeded}`.padStart(layout.countWidth, " ");
|
|
595
|
+
const failed = `${task.units.failed}`.padStart(layout.countWidth, " ");
|
|
596
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
597
|
+
/* @__PURE__ */ jsx(Text, {
|
|
598
|
+
color: "green",
|
|
599
|
+
children: succeeded
|
|
600
|
+
}),
|
|
601
|
+
/* @__PURE__ */ jsx(Text, { children: ` ` }),
|
|
602
|
+
/* @__PURE__ */ jsx(Text, {
|
|
603
|
+
color: "red",
|
|
604
|
+
children: failed
|
|
605
|
+
}),
|
|
606
|
+
/* @__PURE__ */ jsx(Text, { children: ` ${processed}/${total}` })
|
|
607
|
+
] });
|
|
608
|
+
};
|
|
609
|
+
const defaultAmountColumnConfig = {
|
|
610
|
+
minWidth: 4,
|
|
611
|
+
sticky: true
|
|
606
612
|
};
|
|
607
|
-
const
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
+
const createAmountColumn = (config) => {
|
|
614
|
+
const resolvedConfig = {
|
|
615
|
+
...defaultAmountColumnConfig,
|
|
616
|
+
...config
|
|
617
|
+
};
|
|
618
|
+
let amountLayout = emptyAmountLayout;
|
|
613
619
|
return {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
620
|
+
Component: ({ row }) => /* @__PURE__ */ jsx(Text, {
|
|
621
|
+
wrap: "truncate-end",
|
|
622
|
+
children: renderAmount(row.task, amountLayout)
|
|
623
|
+
}),
|
|
624
|
+
measure: ({ rows }) => {
|
|
625
|
+
amountLayout = measureAmountLayout(rows);
|
|
626
|
+
return {
|
|
627
|
+
minWidth: amountLayout.preferredWidth > 0 ? Math.min(resolvedConfig.minWidth, amountLayout.preferredWidth) : 0,
|
|
628
|
+
preferredWidth: amountLayout.preferredWidth,
|
|
629
|
+
maxWidth: amountLayout.preferredWidth
|
|
630
|
+
};
|
|
621
631
|
},
|
|
622
|
-
|
|
632
|
+
noWrap: false,
|
|
633
|
+
sticky: resolvedConfig.sticky
|
|
623
634
|
};
|
|
624
635
|
};
|
|
625
636
|
|
|
626
637
|
//#endregion
|
|
627
|
-
//#region src/
|
|
628
|
-
const
|
|
629
|
-
const
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
638
|
+
//#region src/renderer/columns/bar-column.tsx
|
|
639
|
+
const DEFAULT_BAR_WIDTH = 30;
|
|
640
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
641
|
+
const renderProgressBar = (task, width) => {
|
|
642
|
+
if (!isDeterminate$2(task)) return /* @__PURE__ */ jsx(Text, { children: ` `.repeat(Math.max(0, width)) });
|
|
643
|
+
const displayTotal = Math.max(task.units.total, task.units.succeeded + task.units.failed);
|
|
644
|
+
const succeededEnd = displayTotal === 0 ? width : Math.round(task.units.succeeded / displayTotal * width);
|
|
645
|
+
const failedEnd = displayTotal === 0 ? width : Math.round((task.units.succeeded + task.units.failed) / displayTotal * width);
|
|
646
|
+
const succeededLength = clamp(succeededEnd, 0, width);
|
|
647
|
+
const failedLength = clamp(failedEnd, succeededLength, width) - succeededLength;
|
|
648
|
+
const remainingLength = Math.max(0, width - succeededLength - failedLength);
|
|
649
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
650
|
+
wrap: "truncate-end",
|
|
651
|
+
children: [
|
|
652
|
+
/* @__PURE__ */ jsx(Text, {
|
|
653
|
+
color: "green",
|
|
654
|
+
children: "━".repeat(succeededLength)
|
|
655
|
+
}),
|
|
656
|
+
/* @__PURE__ */ jsx(Text, {
|
|
657
|
+
color: "red",
|
|
658
|
+
children: "━".repeat(failedLength)
|
|
659
|
+
}),
|
|
660
|
+
/* @__PURE__ */ jsx(Text, {
|
|
661
|
+
color: "gray",
|
|
662
|
+
children: "─".repeat(remainingLength)
|
|
663
|
+
})
|
|
664
|
+
]
|
|
665
|
+
});
|
|
637
666
|
};
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const MIN_COMPACT_DESCRIPTION_WIDTH = 3;
|
|
643
|
-
const MIN_SPINNER_WIDTH = 1;
|
|
644
|
-
const MIN_TREE_DESCRIPTION_TEXT_WIDTH = 6;
|
|
645
|
-
const DESCRIPTION_TREE_STICKY_KEY = Symbol("description.tree");
|
|
646
|
-
const DESCRIPTION_PLAIN_STICKY_KEY = Symbol("description.plain");
|
|
647
|
-
const DESCRIPTION_COMPACT_STICKY_KEY = Symbol("description.compact");
|
|
648
|
-
const treeAncestorPrefix = (tree) => tree.ancestorHasNextSibling.slice(1).map((hasNext) => hasNext ? "│ " : " ").join("");
|
|
649
|
-
const renderTreePrefix = (tree) => {
|
|
650
|
-
if (tree.depth <= 0) return "";
|
|
651
|
-
return `${treeAncestorPrefix(tree)}${tree.hasNextSibling ? "├─ " : "└─ "}`;
|
|
667
|
+
const defaultBarColumnConfig = {
|
|
668
|
+
minWidth: 4,
|
|
669
|
+
barWidth: DEFAULT_BAR_WIDTH,
|
|
670
|
+
sticky: true
|
|
652
671
|
};
|
|
653
|
-
const
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
preferred,
|
|
672
|
-
max: preferred
|
|
672
|
+
const createBarColumn = (config) => {
|
|
673
|
+
const resolvedConfig = {
|
|
674
|
+
...defaultBarColumnConfig,
|
|
675
|
+
...config
|
|
676
|
+
};
|
|
677
|
+
return {
|
|
678
|
+
Component: ({ row, width }) => /* @__PURE__ */ jsx(Text, {
|
|
679
|
+
wrap: "truncate-end",
|
|
680
|
+
children: renderProgressBar(row.task, width)
|
|
681
|
+
}),
|
|
682
|
+
measure: ({ rows }) => {
|
|
683
|
+
const hasDeterminateRows = rows.some((row) => row.derived.isDeterminate);
|
|
684
|
+
const preferredWidth = hasDeterminateRows ? resolvedConfig.barWidth : 0;
|
|
685
|
+
return {
|
|
686
|
+
minWidth: hasDeterminateRows ? Math.min(resolvedConfig.minWidth, preferredWidth) : 0,
|
|
687
|
+
preferredWidth,
|
|
688
|
+
maxWidth: preferredWidth
|
|
689
|
+
};
|
|
673
690
|
},
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const tree = frame.getTree(taskId);
|
|
678
|
-
const treePrefix = showTree ? renderTreePrefix(tree) : "";
|
|
679
|
-
const indicator = getTaskIndicator(task, frame.tick);
|
|
680
|
-
if (variant === "spinner") return /* @__PURE__ */ jsx(Text, {
|
|
681
|
-
color: indicator.color,
|
|
682
|
-
children: indicator.symbol
|
|
683
|
-
});
|
|
684
|
-
return /* @__PURE__ */ jsxs(Text, {
|
|
685
|
-
wrap: "truncate-end",
|
|
686
|
-
children: [
|
|
687
|
-
treePrefix,
|
|
688
|
-
/* @__PURE__ */ jsx(Text, {
|
|
689
|
-
color: indicator.color,
|
|
690
|
-
children: indicator.symbol
|
|
691
|
-
}),
|
|
692
|
-
` ${task.description}`
|
|
693
|
-
]
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
});
|
|
691
|
+
noWrap: false,
|
|
692
|
+
sticky: resolvedConfig.sticky
|
|
693
|
+
};
|
|
697
694
|
};
|
|
698
|
-
const createDescriptionRootColumn = (variant) => createColumnDefinition({
|
|
699
|
-
_tag: "description",
|
|
700
|
-
variant
|
|
701
|
-
}, (frame, config) => DescriptionColumn(frame, config));
|
|
702
|
-
const DescriptionTreeRootColumn = createDescriptionRootColumn("tree");
|
|
703
|
-
const DescriptionPlainRootColumn = createDescriptionRootColumn("plain");
|
|
704
|
-
const DescriptionCompactRootColumn = createDescriptionRootColumn("compact");
|
|
705
|
-
const DescriptionSpinnerRootColumn = createDescriptionRootColumn("spinner");
|
|
706
695
|
|
|
707
696
|
//#endregion
|
|
708
|
-
//#region src/
|
|
709
|
-
const
|
|
710
|
-
const
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
697
|
+
//#region src/renderer/hooks/use-spinner-clock.ts
|
|
698
|
+
const normalizeIntervalMillis = (intervalMillis) => Math.max(1, intervalMillis);
|
|
699
|
+
const getSpinnerTickAtTime = (baseTick, startedAt, now, intervalMillis) => {
|
|
700
|
+
const elapsedMillis = Math.max(0, now - startedAt);
|
|
701
|
+
return baseTick + Math.floor(elapsedMillis / normalizeIntervalMillis(intervalMillis));
|
|
702
|
+
};
|
|
703
|
+
const useSpinnerClock = (active, intervalMillis) => {
|
|
704
|
+
const [tick, setTick] = useState(0);
|
|
705
|
+
const tickRef = useRef(tick);
|
|
706
|
+
useEffect(() => {
|
|
707
|
+
tickRef.current = tick;
|
|
708
|
+
}, [tick]);
|
|
709
|
+
useEffect(() => {
|
|
710
|
+
if (!active) return;
|
|
711
|
+
const baseTick = tickRef.current;
|
|
712
|
+
const startedAt = Date.now();
|
|
713
|
+
const updateTick = () => {
|
|
714
|
+
setTick(getSpinnerTickAtTime(baseTick, startedAt, Date.now(), intervalMillis));
|
|
715
|
+
};
|
|
716
|
+
updateTick();
|
|
717
|
+
const interval = setInterval(() => {
|
|
718
|
+
updateTick();
|
|
719
|
+
}, normalizeIntervalMillis(intervalMillis));
|
|
720
|
+
return () => {
|
|
721
|
+
clearInterval(interval);
|
|
722
|
+
};
|
|
723
|
+
}, [active, intervalMillis]);
|
|
724
|
+
return tick;
|
|
734
725
|
};
|
|
735
|
-
const ElapsedRootColumn = createColumnDefinition({ _tag: "elapsed" }, (frame) => ElapsedColumn(frame));
|
|
736
726
|
|
|
737
727
|
//#endregion
|
|
738
|
-
//#region src/
|
|
739
|
-
const
|
|
728
|
+
//#region src/renderer/context/spinner-context.tsx
|
|
729
|
+
const DEFAULT_SPINNER_INTERVAL_MILLIS = cliSpinners.dots.interval;
|
|
730
|
+
const SpinnerContext = createContext(0);
|
|
731
|
+
const SpinnerProvider = ({ active, children, intervalMillis = DEFAULT_SPINNER_INTERVAL_MILLIS, tickOverride }) => {
|
|
732
|
+
const liveTick = useSpinnerClock(active, intervalMillis);
|
|
733
|
+
const tick = tickOverride ?? liveTick;
|
|
734
|
+
return /* @__PURE__ */ jsx(SpinnerContext.Provider, {
|
|
735
|
+
value: tick,
|
|
736
|
+
children
|
|
737
|
+
});
|
|
738
|
+
};
|
|
739
|
+
const useSpinnerTick = () => useContext(SpinnerContext);
|
|
740
740
|
|
|
741
741
|
//#endregion
|
|
742
|
-
//#region src/
|
|
743
|
-
const
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
return eta.length > 0 ? eta : "--";
|
|
742
|
+
//#region src/renderer/columns/description-column.tsx
|
|
743
|
+
const MIN_TREE_DESCRIPTION_TEXT_WIDTH = 6;
|
|
744
|
+
const defaultDescriptionColumnConfig = {
|
|
745
|
+
minWidth: 1,
|
|
746
|
+
spinnerType: "dots",
|
|
747
|
+
sticky: true
|
|
749
748
|
};
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
if (width >= textWidth(prefixed)) return prefixed;
|
|
755
|
-
if (width >= textWidth(duration)) return duration;
|
|
756
|
-
return primaryUnit(duration);
|
|
749
|
+
const isDeterminate = (task) => task.units.total !== void 0;
|
|
750
|
+
const getSpinnerFrame = (tick, spinnerType) => {
|
|
751
|
+
const frames = cliSpinners[spinnerType].frames;
|
|
752
|
+
return frames[(tick % frames.length + frames.length) % frames.length] ?? frames[0] ?? "";
|
|
757
753
|
};
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
754
|
+
const getTaskIndicator = (task, tick, spinnerType = defaultDescriptionColumnConfig.spinnerType) => {
|
|
755
|
+
if (task.status === "running") return {
|
|
756
|
+
symbol: getSpinnerFrame(tick, spinnerType),
|
|
757
|
+
color: "yellow"
|
|
758
|
+
};
|
|
759
|
+
if (task.status === "failed") return {
|
|
760
|
+
symbol: "✗",
|
|
761
|
+
color: "red"
|
|
762
|
+
};
|
|
763
|
+
if (!isDeterminate(task)) return {
|
|
764
|
+
symbol: "✓",
|
|
765
|
+
color: "green"
|
|
766
|
+
};
|
|
767
|
+
const { succeeded, failed, processed, total } = task.units;
|
|
768
|
+
if (failed === 0 && processed === total) return {
|
|
769
|
+
symbol: "✓",
|
|
770
|
+
color: "green"
|
|
771
|
+
};
|
|
772
|
+
if (failed > 0 && succeeded > 0) return {
|
|
773
|
+
symbol: "~",
|
|
774
|
+
color: "yellow"
|
|
775
|
+
};
|
|
776
|
+
if (failed > 0 && succeeded === 0) return {
|
|
777
|
+
symbol: "✗",
|
|
778
|
+
color: "red"
|
|
779
|
+
};
|
|
773
780
|
return {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
durationWidth: Math.max(2, durationWidth),
|
|
777
|
-
primaryUnitWidth: Math.max(2, primaryUnitWidth)
|
|
781
|
+
symbol: "✓",
|
|
782
|
+
color: "green"
|
|
778
783
|
};
|
|
779
784
|
};
|
|
780
|
-
const
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
stickyKey: ETA_STICKY_KEY,
|
|
786
|
-
measure: {
|
|
787
|
-
min: metrics.primaryUnitWidth,
|
|
788
|
-
preferred: Math.max(metrics.prefixedWidth, RESERVED_ETA_WIDTH_UP_TO_ONE_HOUR),
|
|
789
|
-
max: Math.max(metrics.prefixedWidth, RESERVED_ETA_WIDTH_UP_TO_ONE_HOUR)
|
|
790
|
-
},
|
|
791
|
-
render: (taskId, width) => /* @__PURE__ */ jsx(Text, {
|
|
792
|
-
wrap: "truncate-end",
|
|
793
|
-
color: "gray",
|
|
794
|
-
children: renderEtaText(frame.getTask(taskId), frame.now, width)
|
|
795
|
-
})
|
|
785
|
+
const TaskIndicatorGlyph = ({ task, spinnerType = defaultDescriptionColumnConfig.spinnerType }) => {
|
|
786
|
+
const indicator = getTaskIndicator(task, useSpinnerTick(), spinnerType);
|
|
787
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
788
|
+
color: indicator.color,
|
|
789
|
+
children: indicator.symbol
|
|
796
790
|
});
|
|
797
791
|
};
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
const
|
|
806
|
-
const
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
792
|
+
const createDescriptionColumn = (config) => {
|
|
793
|
+
const resolvedConfig = {
|
|
794
|
+
...defaultDescriptionColumnConfig,
|
|
795
|
+
...config,
|
|
796
|
+
spinnerType: config?.spinnerType ?? defaultDescriptionColumnConfig.spinnerType
|
|
797
|
+
};
|
|
798
|
+
let minTreeWidth = MIN_TREE_DESCRIPTION_TEXT_WIDTH + 2;
|
|
799
|
+
const Component = ({ row, width }) => {
|
|
800
|
+
const showTree = width >= minTreeWidth;
|
|
801
|
+
const treePrefix = showTree ? row.derived.treePrefix : "";
|
|
802
|
+
if (!showTree && width <= 1) return /* @__PURE__ */ jsx(TaskIndicatorGlyph, {
|
|
803
|
+
task: row.task,
|
|
804
|
+
spinnerType: resolvedConfig.spinnerType
|
|
805
|
+
});
|
|
806
|
+
if (!showTree && width === 2) return /* @__PURE__ */ jsxs(Text, {
|
|
807
|
+
wrap: "truncate-end",
|
|
808
|
+
children: [/* @__PURE__ */ jsx(TaskIndicatorGlyph, {
|
|
809
|
+
task: row.task,
|
|
810
|
+
spinnerType: resolvedConfig.spinnerType
|
|
811
|
+
}), "…"]
|
|
812
|
+
});
|
|
813
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
814
|
+
wrap: "truncate-end",
|
|
815
|
+
children: [
|
|
816
|
+
treePrefix,
|
|
817
|
+
/* @__PURE__ */ jsx(TaskIndicatorGlyph, {
|
|
818
|
+
task: row.task,
|
|
819
|
+
spinnerType: resolvedConfig.spinnerType
|
|
820
|
+
}),
|
|
821
|
+
` ${row.task.description}`
|
|
822
|
+
]
|
|
823
|
+
});
|
|
824
|
+
};
|
|
828
825
|
return {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
826
|
+
Component,
|
|
827
|
+
measure: ({ rows }) => {
|
|
828
|
+
const hasNestedRows = rows.some((row) => row.tree.depth > 0);
|
|
829
|
+
minTreeWidth = rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + MIN_TREE_DESCRIPTION_TEXT_WIDTH), MIN_TREE_DESCRIPTION_TEXT_WIDTH + 2);
|
|
830
|
+
return {
|
|
831
|
+
minWidth: resolvedConfig.minWidth,
|
|
832
|
+
preferredWidth: Math.max(rows.reduce((max, row) => Math.max(max, row.derived.treePrefixedDescriptionWidth + 2), resolvedConfig.minWidth), hasNestedRows ? minTreeWidth : resolvedConfig.minWidth),
|
|
833
|
+
maxWidth: void 0
|
|
834
|
+
};
|
|
835
|
+
},
|
|
836
|
+
noWrap: false,
|
|
837
|
+
sticky: resolvedConfig.sticky,
|
|
838
|
+
stickyMaxWidth: resolvedConfig.stickyMaxWidth
|
|
835
839
|
};
|
|
836
840
|
};
|
|
837
|
-
const processedAmountWidth = (metrics) => metrics.countDigits + 1 + metrics.totalWidth;
|
|
838
|
-
const detailedAmountWidth = (metrics) => metrics.countDigits + 1 + metrics.totalWidth + (metrics.hasDetailed ? metrics.countDigits + 1 + metrics.countDigits + 1 : 0);
|
|
839
|
-
const percentText = (task) => {
|
|
840
|
-
if (!isDeterminate(task)) return formatAmount(task, 0);
|
|
841
|
-
if (task.units.total === 0) return "100%";
|
|
842
|
-
const displayTotal = Math.max(task.units.total, task.units.processed);
|
|
843
|
-
return `${Math.max(0, Math.min(100, Math.round(task.units.processed / displayTotal * 100)))}%`;
|
|
844
|
-
};
|
|
845
841
|
|
|
846
842
|
//#endregion
|
|
847
|
-
//#region src/
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
843
|
+
//#region src/renderer/hooks/use-now-clock.ts
|
|
844
|
+
const useNowClock = (active, intervalMillis) => {
|
|
845
|
+
const [now, setNow] = useState(() => Date.now());
|
|
846
|
+
useEffect(() => {
|
|
847
|
+
if (!active) return;
|
|
848
|
+
setNow(Date.now());
|
|
849
|
+
const interval = setInterval(() => {
|
|
850
|
+
setNow(Date.now());
|
|
851
|
+
}, intervalMillis);
|
|
852
|
+
return () => {
|
|
853
|
+
clearInterval(interval);
|
|
854
|
+
};
|
|
855
|
+
}, [active, intervalMillis]);
|
|
856
|
+
return now;
|
|
854
857
|
};
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
858
|
+
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region src/renderer/context/now-context.tsx
|
|
861
|
+
const NOW_INTERVAL_MILLIS = 1e3;
|
|
862
|
+
const NowContext = createContext(Date.now());
|
|
863
|
+
const NowProvider = ({ active, children, nowOverride }) => {
|
|
864
|
+
const liveNow = useNowClock(active, NOW_INTERVAL_MILLIS);
|
|
865
|
+
const now = nowOverride ?? liveNow;
|
|
866
|
+
return /* @__PURE__ */ jsx(NowContext.Provider, {
|
|
867
|
+
value: now,
|
|
868
|
+
children
|
|
860
869
|
});
|
|
861
870
|
};
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
871
|
+
const useNow = () => useContext(NowContext);
|
|
872
|
+
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/renderer/columns/elapsed-column.tsx
|
|
875
|
+
const defaultElapsedColumnConfig = {
|
|
876
|
+
minWidth: 2,
|
|
877
|
+
justify: "right",
|
|
878
|
+
sticky: true
|
|
869
879
|
};
|
|
870
|
-
const
|
|
871
|
-
|
|
880
|
+
const ElapsedText = ({ task, width }) => {
|
|
881
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
872
882
|
wrap: "truncate-end",
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
if (layout.kind === "processed") return /* @__PURE__ */ jsxs(Box, {
|
|
876
|
-
flexDirection: "row",
|
|
877
|
-
width,
|
|
878
|
-
children: [
|
|
879
|
-
processedCount(task, layout.processedWidth),
|
|
880
|
-
/* @__PURE__ */ jsx(Text, { children: "/" }),
|
|
881
|
-
totalCount(task, layout.totalWidth)
|
|
882
|
-
]
|
|
883
|
-
});
|
|
884
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
885
|
-
flexDirection: "row",
|
|
886
|
-
width,
|
|
887
|
-
children: [
|
|
888
|
-
layout.succeededWidth > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [succeededCount(task, layout.succeededWidth), /* @__PURE__ */ jsx(Text, { children: ` ` })] }) : null,
|
|
889
|
-
layout.failedWidth > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [failedCount(task, layout.failedWidth), /* @__PURE__ */ jsx(Text, { children: ` ` })] }) : null,
|
|
890
|
-
processedCount(task, layout.processedWidth),
|
|
891
|
-
/* @__PURE__ */ jsx(Text, { children: "/" }),
|
|
892
|
-
totalCount(task, layout.totalWidth)
|
|
893
|
-
]
|
|
883
|
+
color: "gray",
|
|
884
|
+
children: formatElapsed(task, useNow()).slice(0, Math.max(0, width))
|
|
894
885
|
});
|
|
895
886
|
};
|
|
896
|
-
const
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
const preferredWidth = metrics.hasStructuredCounts ? metrics.hasDetailed ? detailedWidth : processedWidth : Math.max(1, metrics.simpleTextWidth);
|
|
901
|
-
const minWidth = metrics.hasStructuredCounts ? processedWidth : Math.max(1, metrics.simpleTextWidth);
|
|
902
|
-
const summary = {
|
|
903
|
-
hasDetailed: metrics.hasDetailed,
|
|
904
|
-
countDigits: metrics.countDigits,
|
|
905
|
-
totalWidth: metrics.totalWidth,
|
|
906
|
-
detailedWidth,
|
|
907
|
-
processedWidth,
|
|
908
|
-
preferredWidth,
|
|
909
|
-
minWidth,
|
|
910
|
-
simpleTextWidth: Math.max(1, metrics.simpleTextWidth)
|
|
887
|
+
const createElapsedColumn = (config) => {
|
|
888
|
+
const resolvedConfig = {
|
|
889
|
+
...defaultElapsedColumnConfig,
|
|
890
|
+
...config
|
|
911
891
|
};
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
min: summary.minWidth,
|
|
916
|
-
preferred: summary.preferredWidth,
|
|
917
|
-
max: summary.preferredWidth
|
|
918
|
-
},
|
|
919
|
-
stickyKey: config.stickyWidth === true ? config.key : void 0,
|
|
920
|
-
render: (taskId, width) => {
|
|
921
|
-
const task = frame.getTask(taskId);
|
|
922
|
-
if (!shouldShowCountAmount(task)) return /* @__PURE__ */ jsx(Text, {
|
|
923
|
-
wrap: "truncate-end",
|
|
924
|
-
children: formatAmount(task, frame.tick)
|
|
925
|
-
});
|
|
926
|
-
if (summary.hasDetailed && width >= summary.detailedWidth) return structuredAmount(task, frame.tick, width, {
|
|
927
|
-
kind: "detailed",
|
|
928
|
-
succeededWidth: summary.countDigits,
|
|
929
|
-
failedWidth: summary.countDigits,
|
|
930
|
-
processedWidth: summary.countDigits,
|
|
931
|
-
totalWidth: summary.totalWidth
|
|
932
|
-
});
|
|
933
|
-
return structuredAmount(task, frame.tick, width, {
|
|
934
|
-
kind: "processed",
|
|
935
|
-
processedWidth: summary.countDigits,
|
|
936
|
-
totalWidth: summary.totalWidth
|
|
937
|
-
});
|
|
938
|
-
}
|
|
892
|
+
const Component = ({ row, width }) => /* @__PURE__ */ jsx(ElapsedText, {
|
|
893
|
+
task: row.task,
|
|
894
|
+
width
|
|
939
895
|
});
|
|
896
|
+
return {
|
|
897
|
+
Component,
|
|
898
|
+
measure: ({ rows, now }) => {
|
|
899
|
+
const width = rows.reduce((max, row) => Math.max(max, formatElapsed(row.task, now).length), resolvedConfig.minWidth);
|
|
900
|
+
return {
|
|
901
|
+
minWidth: resolvedConfig.minWidth,
|
|
902
|
+
preferredWidth: width,
|
|
903
|
+
maxWidth: width
|
|
904
|
+
};
|
|
905
|
+
},
|
|
906
|
+
getLayoutDependency: ({ rows, now }) => rows.reduce((max, row) => Math.max(max, formatElapsed(row.task, now).length), 0),
|
|
907
|
+
justify: resolvedConfig.justify,
|
|
908
|
+
noWrap: true,
|
|
909
|
+
sticky: resolvedConfig.sticky
|
|
910
|
+
};
|
|
940
911
|
};
|
|
941
912
|
|
|
942
913
|
//#endregion
|
|
943
|
-
//#region src/
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
914
|
+
//#region src/renderer/columns/eta-column.tsx
|
|
915
|
+
const primaryUnit = (duration) => duration.split(" ")[0] ?? duration;
|
|
916
|
+
const cropTextToWidth = (text, width) => {
|
|
917
|
+
if (width <= 0) return "";
|
|
918
|
+
return text.slice(0, width);
|
|
919
|
+
};
|
|
920
|
+
const etaDurationText = (row, now) => {
|
|
921
|
+
const eta = formatEta(row.task, now);
|
|
922
|
+
return eta === "" ? void 0 : eta;
|
|
923
|
+
};
|
|
924
|
+
const renderEtaText = (row, now, width) => {
|
|
925
|
+
const duration = etaDurationText(row, now);
|
|
926
|
+
if (duration === void 0) return {
|
|
927
|
+
mode: "compact",
|
|
928
|
+
text: ""
|
|
929
|
+
};
|
|
930
|
+
const prefixed = `ETA: ${duration}`;
|
|
931
|
+
if (width >= prefixed.length) return {
|
|
932
|
+
mode: "prefixed",
|
|
933
|
+
text: prefixed
|
|
934
|
+
};
|
|
935
|
+
if (width >= duration.length) return {
|
|
936
|
+
mode: "duration",
|
|
937
|
+
text: duration
|
|
949
938
|
};
|
|
950
|
-
const displayTotal = Math.max(total, succeeded + failed);
|
|
951
|
-
const succeededEnd = Math.round(succeeded / displayTotal * width);
|
|
952
|
-
const failedEnd = Math.round((succeeded + failed) / displayTotal * width);
|
|
953
|
-
const succeededLength = Math.max(0, Math.min(width, succeededEnd));
|
|
954
|
-
const failedLength = Math.max(0, Math.min(width, failedEnd) - succeededLength);
|
|
955
939
|
return {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
remaining: Math.max(0, width - succeededLength - failedLength)
|
|
940
|
+
mode: "compact",
|
|
941
|
+
text: cropTextToWidth(primaryUnit(duration), width)
|
|
959
942
|
};
|
|
960
943
|
};
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
min: 4,
|
|
966
|
-
preferred: DEFAULT_BAR_WIDTH,
|
|
967
|
-
max: config.fullWidth ? void 0 : DEFAULT_BAR_WIDTH
|
|
968
|
-
},
|
|
969
|
-
stickyKey: config.stickyWidth === true ? config.key : void 0,
|
|
970
|
-
render: (taskId, width) => {
|
|
971
|
-
const task = frame.getTask(taskId);
|
|
972
|
-
if (!isDeterminate(task)) return /* @__PURE__ */ jsx(Text, { children: blank(width) });
|
|
973
|
-
const lengths = segmentLengths(Math.max(1, Math.floor(width)), task.units.total, task.units.succeeded, task.units.failed);
|
|
974
|
-
return /* @__PURE__ */ jsxs(Text, {
|
|
975
|
-
wrap: "truncate-end",
|
|
976
|
-
children: [
|
|
977
|
-
/* @__PURE__ */ jsx(Text, {
|
|
978
|
-
color: "green",
|
|
979
|
-
children: "━".repeat(lengths.succeeded)
|
|
980
|
-
}),
|
|
981
|
-
/* @__PURE__ */ jsx(Text, {
|
|
982
|
-
color: "red",
|
|
983
|
-
children: "━".repeat(lengths.failed)
|
|
984
|
-
}),
|
|
985
|
-
/* @__PURE__ */ jsx(Text, {
|
|
986
|
-
color: "gray",
|
|
987
|
-
children: "─".repeat(lengths.remaining)
|
|
988
|
-
})
|
|
989
|
-
]
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
});
|
|
944
|
+
const defaultEtaColumnConfig = {
|
|
945
|
+
minWidth: 3,
|
|
946
|
+
justify: "right",
|
|
947
|
+
sticky: true
|
|
993
948
|
};
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
preferred: 4,
|
|
1001
|
-
max: 4
|
|
1002
|
-
},
|
|
1003
|
-
render: (taskId, _width) => {
|
|
1004
|
-
const task = frame.getTask(taskId);
|
|
1005
|
-
const determinateColor = isDeterminate(task) ? getDeterminateProcessedColor(task) : void 0;
|
|
1006
|
-
return /* @__PURE__ */ jsx(Text, {
|
|
949
|
+
const EtaText = ({ row, width, justify }) => {
|
|
950
|
+
const rendered = renderEtaText(row, useNow(), width);
|
|
951
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
952
|
+
width,
|
|
953
|
+
justifyContent: rendered.mode === "prefixed" ? justify === "right" ? "flex-end" : "flex-start" : "flex-start",
|
|
954
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1007
955
|
wrap: "truncate-end",
|
|
1008
|
-
color:
|
|
1009
|
-
children:
|
|
1010
|
-
})
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
|
-
//#endregion
|
|
1015
|
-
//#region src/ink-renderer/columns/progress-metrics-column.tsx
|
|
1016
|
-
const PROGRESS_BAR_STICKY_KEY = Symbol("progress.bar");
|
|
1017
|
-
const PROGRESS_AMOUNT_STICKY_KEY = Symbol("progress.amount");
|
|
1018
|
-
const createProgressColumnModel = (frame, mode) => {
|
|
1019
|
-
const metrics = computeProgressMetrics(frame);
|
|
1020
|
-
const percent = PercentColumn(frame);
|
|
1021
|
-
if (mode === "percent" && metrics.hasDeterminate) return {
|
|
1022
|
-
metrics,
|
|
1023
|
-
percent
|
|
1024
|
-
};
|
|
1025
|
-
return {
|
|
1026
|
-
metrics,
|
|
1027
|
-
percent,
|
|
1028
|
-
amount: AmountColumn(frame, {
|
|
1029
|
-
key: PROGRESS_AMOUNT_STICKY_KEY,
|
|
1030
|
-
metrics,
|
|
1031
|
-
stickyWidth: true
|
|
1032
|
-
}),
|
|
1033
|
-
bar: metrics.hasDeterminate ? BarColumn(frame, {
|
|
1034
|
-
key: PROGRESS_BAR_STICKY_KEY,
|
|
1035
|
-
fullWidth: false,
|
|
1036
|
-
stickyWidth: true
|
|
1037
|
-
}) : void 0
|
|
1038
|
-
};
|
|
956
|
+
color: "gray",
|
|
957
|
+
children: rendered.text
|
|
958
|
+
})
|
|
959
|
+
});
|
|
1039
960
|
};
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
if (width < bar.measure.min + 1 + model.amount.measure.min || width < PERCENT_FALLBACK_WIDTH) return { kind: "percent" };
|
|
1045
|
-
const available = Math.max(0, width - 1);
|
|
1046
|
-
const amountWidth = Math.min(model.amount.measure.preferred, Math.max(model.amount.measure.min, available - bar.measure.min));
|
|
1047
|
-
return {
|
|
1048
|
-
kind: "bar-amount",
|
|
1049
|
-
bar,
|
|
1050
|
-
barWidth: Math.max(bar.measure.min, available - amountWidth),
|
|
1051
|
-
amountWidth
|
|
961
|
+
const createEtaColumn = (config) => {
|
|
962
|
+
const resolvedConfig = {
|
|
963
|
+
...defaultEtaColumnConfig,
|
|
964
|
+
...config
|
|
1052
965
|
};
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
if (mode === "percent" && model.metrics.hasDeterminate) return model.percent;
|
|
1059
|
-
const amount = model.amount;
|
|
1060
|
-
if (amount === void 0) return;
|
|
1061
|
-
const preferred = model.metrics.hasDeterminate && model.bar !== void 0 ? model.bar.measure.preferred + 1 + amount.measure.preferred : amount.measure.preferred;
|
|
1062
|
-
const fullMin = model.metrics.hasDeterminate && model.bar !== void 0 ? model.bar.measure.min + 1 + amount.measure.min : amount.measure.min;
|
|
1063
|
-
const max = !model.metrics.hasDeterminate || model.bar === void 0 || model.bar.measure.max === void 0 ? amount.measure.max : model.bar.measure.max + 1 + amount.measure.preferred;
|
|
966
|
+
const Component = ({ row, width }) => /* @__PURE__ */ jsx(EtaText, {
|
|
967
|
+
row,
|
|
968
|
+
width,
|
|
969
|
+
justify: resolvedConfig.justify
|
|
970
|
+
});
|
|
1064
971
|
return {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
972
|
+
Component,
|
|
973
|
+
measure: ({ rows, now }) => {
|
|
974
|
+
const width = rows.reduce((max, row) => {
|
|
975
|
+
const duration = etaDurationText(row, now);
|
|
976
|
+
if (duration === void 0) return max;
|
|
977
|
+
return Math.max(max, `ETA: ${duration}`.length);
|
|
978
|
+
}, resolvedConfig.minWidth);
|
|
979
|
+
return {
|
|
980
|
+
minWidth: resolvedConfig.minWidth,
|
|
981
|
+
preferredWidth: width,
|
|
982
|
+
maxWidth: width
|
|
983
|
+
};
|
|
1073
984
|
},
|
|
1074
|
-
|
|
1075
|
-
const
|
|
1076
|
-
if (
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
/* @__PURE__ */ jsx(Box, {
|
|
1083
|
-
width: layout.barWidth,
|
|
1084
|
-
children: layout.bar.render(taskId, layout.barWidth)
|
|
1085
|
-
}),
|
|
1086
|
-
/* @__PURE__ */ jsx(Box, { marginRight: 1 }),
|
|
1087
|
-
/* @__PURE__ */ jsx(Box, {
|
|
1088
|
-
width: layout.amountWidth,
|
|
1089
|
-
children: amount.render(taskId, layout.amountWidth)
|
|
1090
|
-
})
|
|
1091
|
-
]
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
985
|
+
getLayoutDependency: ({ rows, now }) => rows.reduce((max, row) => {
|
|
986
|
+
const duration = etaDurationText(row, now);
|
|
987
|
+
if (duration === void 0) return max;
|
|
988
|
+
return Math.max(max, `ETA: ${duration}`.length);
|
|
989
|
+
}, 0),
|
|
990
|
+
justify: resolvedConfig.justify,
|
|
991
|
+
noWrap: true,
|
|
992
|
+
sticky: resolvedConfig.sticky
|
|
1094
993
|
};
|
|
1095
994
|
};
|
|
1096
|
-
const createProgressRootColumn = (mode) => createColumnDefinition({
|
|
1097
|
-
_tag: "progress",
|
|
1098
|
-
mode
|
|
1099
|
-
}, (frame, config) => ProgressMetricsColumn(frame, config));
|
|
1100
|
-
const ProgressRootColumn = createProgressRootColumn("full");
|
|
1101
|
-
const ProgressPercentRootColumn = createProgressRootColumn("percent");
|
|
1102
995
|
|
|
1103
996
|
//#endregion
|
|
1104
|
-
//#region src/
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
[
|
|
1112
|
-
DescriptionTreeRootColumn,
|
|
1113
|
-
ProgressRootColumn,
|
|
1114
|
-
ElapsedRootColumn,
|
|
1115
|
-
EtaRootColumn
|
|
1116
|
-
],
|
|
1117
|
-
[
|
|
1118
|
-
DescriptionPlainRootColumn,
|
|
1119
|
-
ProgressRootColumn,
|
|
1120
|
-
ElapsedRootColumn,
|
|
1121
|
-
EtaRootColumn
|
|
1122
|
-
],
|
|
1123
|
-
[
|
|
1124
|
-
DescriptionPlainRootColumn,
|
|
1125
|
-
ProgressPercentRootColumn,
|
|
1126
|
-
ElapsedRootColumn,
|
|
1127
|
-
EtaRootColumn
|
|
1128
|
-
],
|
|
1129
|
-
[
|
|
1130
|
-
DescriptionPlainRootColumn,
|
|
1131
|
-
ProgressPercentRootColumn,
|
|
1132
|
-
ElapsedRootColumn
|
|
1133
|
-
],
|
|
1134
|
-
[DescriptionPlainRootColumn, ProgressPercentRootColumn],
|
|
1135
|
-
[DescriptionCompactRootColumn, ProgressPercentRootColumn],
|
|
1136
|
-
[DescriptionCompactRootColumn],
|
|
1137
|
-
[DescriptionSpinnerRootColumn]
|
|
997
|
+
//#region src/renderer/default-columns.tsx
|
|
998
|
+
const defaultRendererv2Columns = [
|
|
999
|
+
createDescriptionColumn(),
|
|
1000
|
+
createBarColumn(),
|
|
1001
|
+
createAmountColumn(),
|
|
1002
|
+
createElapsedColumn(),
|
|
1003
|
+
createEtaColumn()
|
|
1138
1004
|
];
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1005
|
+
|
|
1006
|
+
//#endregion
|
|
1007
|
+
//#region src/renderer/width-allocator.ts
|
|
1008
|
+
const RENDERER_COLUMN_GAP = 1;
|
|
1009
|
+
const clampWidth = (width, minWidth, maxWidth) => maxWidth === void 0 ? Math.max(minWidth, width) : Math.max(minWidth, Math.min(maxWidth, width));
|
|
1010
|
+
const normalizeMeasurement = (definition, context) => {
|
|
1011
|
+
if (definition.fixedWidth !== void 0) {
|
|
1012
|
+
const width = Math.max(0, definition.fixedWidth);
|
|
1013
|
+
return {
|
|
1014
|
+
definition,
|
|
1015
|
+
minWidth: width,
|
|
1016
|
+
maxWidth: width,
|
|
1017
|
+
preferredWidth: width,
|
|
1018
|
+
sticky: definition.sticky === true,
|
|
1019
|
+
stickyLimit: width
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
const measured = definition.measure(context);
|
|
1023
|
+
const minWidth = Math.max(0, measured.minWidth);
|
|
1024
|
+
const maxWidth = measured.maxWidth === void 0 ? void 0 : Math.max(minWidth, measured.maxWidth);
|
|
1025
|
+
const preferredWidth = clampWidth(measured.preferredWidth, minWidth, maxWidth);
|
|
1026
|
+
const stickyLimit = definition.sticky !== true ? void 0 : definition.stickyMaxWidth !== void 0 ? Math.max(minWidth, definition.stickyMaxWidth) : maxWidth;
|
|
1027
|
+
return {
|
|
1145
1028
|
definition,
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
preferredWidth
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
}]];
|
|
1152
|
-
}));
|
|
1153
|
-
const candidateRootLayouts = (columnsById) => {
|
|
1154
|
-
const hasProgress = columnsById.has(ProgressRootColumn.id);
|
|
1155
|
-
const hasPercentProgress = columnsById.has(ProgressPercentRootColumn.id);
|
|
1156
|
-
const hasEta = columnsById.has(EtaRootColumn.id);
|
|
1157
|
-
return [
|
|
1158
|
-
...hasProgress && hasEta ? [[
|
|
1159
|
-
DescriptionTreeRootColumn,
|
|
1160
|
-
ProgressRootColumn,
|
|
1161
|
-
ElapsedRootColumn,
|
|
1162
|
-
EtaRootColumn
|
|
1163
|
-
], [
|
|
1164
|
-
DescriptionPlainRootColumn,
|
|
1165
|
-
ProgressRootColumn,
|
|
1166
|
-
ElapsedRootColumn,
|
|
1167
|
-
EtaRootColumn
|
|
1168
|
-
]] : [],
|
|
1169
|
-
...hasProgress && !hasEta ? [[
|
|
1170
|
-
DescriptionTreeRootColumn,
|
|
1171
|
-
ProgressRootColumn,
|
|
1172
|
-
ElapsedRootColumn
|
|
1173
|
-
], [
|
|
1174
|
-
DescriptionPlainRootColumn,
|
|
1175
|
-
ProgressRootColumn,
|
|
1176
|
-
ElapsedRootColumn
|
|
1177
|
-
]] : [],
|
|
1178
|
-
...hasPercentProgress && hasEta ? [[
|
|
1179
|
-
DescriptionPlainRootColumn,
|
|
1180
|
-
ProgressPercentRootColumn,
|
|
1181
|
-
ElapsedRootColumn,
|
|
1182
|
-
EtaRootColumn
|
|
1183
|
-
]] : [],
|
|
1184
|
-
...hasPercentProgress ? [
|
|
1185
|
-
[
|
|
1186
|
-
DescriptionPlainRootColumn,
|
|
1187
|
-
ProgressPercentRootColumn,
|
|
1188
|
-
ElapsedRootColumn
|
|
1189
|
-
],
|
|
1190
|
-
[DescriptionPlainRootColumn, ProgressPercentRootColumn],
|
|
1191
|
-
[DescriptionCompactRootColumn, ProgressPercentRootColumn]
|
|
1192
|
-
] : [],
|
|
1193
|
-
...!hasProgress && !hasPercentProgress ? [
|
|
1194
|
-
[DescriptionTreeRootColumn, ElapsedRootColumn],
|
|
1195
|
-
[DescriptionPlainRootColumn, ElapsedRootColumn],
|
|
1196
|
-
[DescriptionPlainRootColumn]
|
|
1197
|
-
] : [],
|
|
1198
|
-
[DescriptionCompactRootColumn],
|
|
1199
|
-
[DescriptionSpinnerRootColumn]
|
|
1200
|
-
];
|
|
1201
|
-
};
|
|
1202
|
-
const resolveRootLayouts = (columnsById) => {
|
|
1203
|
-
const resolveLayout = (definitions) => {
|
|
1204
|
-
const columns = [];
|
|
1205
|
-
for (const definition of definitions) {
|
|
1206
|
-
const column = columnsById.get(definition.id);
|
|
1207
|
-
if (column === void 0) return;
|
|
1208
|
-
columns.push(column);
|
|
1209
|
-
}
|
|
1210
|
-
return columns;
|
|
1029
|
+
minWidth,
|
|
1030
|
+
maxWidth,
|
|
1031
|
+
preferredWidth,
|
|
1032
|
+
sticky: definition.sticky === true,
|
|
1033
|
+
stickyLimit
|
|
1211
1034
|
};
|
|
1212
|
-
return candidateRootLayouts(columnsById).map(resolveLayout).filter((columns) => columns !== void 0);
|
|
1213
1035
|
};
|
|
1214
|
-
const
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
if (
|
|
1218
|
-
return
|
|
1036
|
+
const visibleIndices = (widths) => widths.flatMap((width, index) => width > 0 ? [index] : []);
|
|
1037
|
+
const totalVisibleWidth = (widths) => {
|
|
1038
|
+
const indices = visibleIndices(widths);
|
|
1039
|
+
if (indices.length === 0) return 0;
|
|
1040
|
+
return indices.reduce((sum, index, visibleIndex) => {
|
|
1041
|
+
const padding = visibleIndex < indices.length - 1 ? RENDERER_COLUMN_GAP : 0;
|
|
1042
|
+
return sum + widths[index] + padding;
|
|
1043
|
+
}, 0);
|
|
1219
1044
|
};
|
|
1220
|
-
const preferredWidthsForSet = (columns) => columns.map((column) => Math.max(column.measure.min, column.preferredWidth));
|
|
1221
1045
|
const nextDistinctWidth = (entries, widest) => entries.find((entry) => entry.width < widest)?.width;
|
|
1222
|
-
const
|
|
1223
|
-
|
|
1224
|
-
while (overflow > 0) {
|
|
1046
|
+
const shrinkWidestFirst = (widths, columns, targetWidth, canShrink) => {
|
|
1047
|
+
while (totalVisibleWidth(widths) > targetWidth) {
|
|
1225
1048
|
const shrinkable = widths.map((width, index) => ({
|
|
1226
|
-
width,
|
|
1227
1049
|
index,
|
|
1228
|
-
|
|
1229
|
-
|
|
1050
|
+
width,
|
|
1051
|
+
minWidth: columns[index].minWidth
|
|
1052
|
+
})).filter(({ index, width, minWidth }) => width > minWidth && canShrink(index)).sort((left, right) => right.width - left.width || right.index - left.index);
|
|
1230
1053
|
if (shrinkable.length === 0) break;
|
|
1054
|
+
const overflow = totalVisibleWidth(widths) - targetWidth;
|
|
1231
1055
|
const widest = shrinkable[0].width;
|
|
1232
|
-
const cohort = shrinkable.filter((
|
|
1233
|
-
const
|
|
1234
|
-
const maxUniformDrop = widest - Math.max(nextWidth ?? 0, ...cohort.map(({ minimum }) => minimum));
|
|
1056
|
+
const cohort = shrinkable.filter((entry) => entry.width === widest);
|
|
1057
|
+
const maxUniformDrop = widest - Math.max(nextDistinctWidth(shrinkable, widest) ?? 0, ...cohort.map((entry) => entry.minWidth));
|
|
1235
1058
|
const uniformDrop = Math.min(maxUniformDrop, Math.floor(overflow / cohort.length));
|
|
1236
1059
|
if (uniformDrop > 0) {
|
|
1237
|
-
for (const { index } of cohort) widths[index] = widths[index] - uniformDrop;
|
|
1238
|
-
overflow -= uniformDrop * cohort.length;
|
|
1060
|
+
for (const { index } of cohort) widths[index] = Math.max(columns[index].minWidth, widths[index] - uniformDrop);
|
|
1239
1061
|
continue;
|
|
1240
1062
|
}
|
|
1241
1063
|
let changed = false;
|
|
1242
|
-
for (const { index,
|
|
1243
|
-
if (
|
|
1244
|
-
if (widths[index] <=
|
|
1064
|
+
for (const { index, minWidth } of cohort) {
|
|
1065
|
+
if (totalVisibleWidth(widths) <= targetWidth) break;
|
|
1066
|
+
if (widths[index] <= minWidth) continue;
|
|
1245
1067
|
widths[index] = widths[index] - 1;
|
|
1246
|
-
overflow -= 1;
|
|
1247
1068
|
changed = true;
|
|
1248
1069
|
}
|
|
1249
1070
|
if (!changed) break;
|
|
1250
1071
|
}
|
|
1251
1072
|
return widths;
|
|
1252
1073
|
};
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
return
|
|
1074
|
+
const forceFitFromRight = (widths, columns, targetWidth, canShrink) => {
|
|
1075
|
+
while (totalVisibleWidth(widths) > targetWidth) {
|
|
1076
|
+
const rightmostVisibleIndex = widths.findLastIndex((width, index) => width > 0 && canShrink(index));
|
|
1077
|
+
if (rightmostVisibleIndex < 0) break;
|
|
1078
|
+
widths[rightmostVisibleIndex] = widths[rightmostVisibleIndex] - 1;
|
|
1079
|
+
}
|
|
1080
|
+
return widths;
|
|
1081
|
+
};
|
|
1082
|
+
const applyStickyGrowth = (widths, columns, targetWidth, previousStickyWidths) => {
|
|
1083
|
+
const stickyTargets = columns.map((column, index) => {
|
|
1084
|
+
if (!column.sticky) return;
|
|
1085
|
+
return clampWidth(Math.max(previousStickyWidths.get(index) ?? 0, column.preferredWidth), column.minWidth, column.stickyLimit);
|
|
1086
|
+
});
|
|
1087
|
+
if (targetWidth === void 0) stickyTargets.forEach((target, index) => {
|
|
1088
|
+
if (target !== void 0) widths[index] = Math.max(widths[index], target);
|
|
1089
|
+
});
|
|
1090
|
+
else {
|
|
1091
|
+
let remaining = targetWidth - totalVisibleWidth(widths);
|
|
1092
|
+
if (remaining > 0) stickyTargets.forEach((target, index) => {
|
|
1093
|
+
if (target === void 0 || remaining <= 0) return;
|
|
1094
|
+
const deficit = Math.max(0, target - widths[index]);
|
|
1095
|
+
const growth = Math.min(deficit, remaining);
|
|
1096
|
+
widths[index] = widths[index] + growth;
|
|
1097
|
+
remaining -= growth;
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
const nextStickyWidths = /* @__PURE__ */ new Map();
|
|
1101
|
+
stickyTargets.forEach((target, index) => {
|
|
1102
|
+
if (target === void 0) return;
|
|
1103
|
+
nextStickyWidths.set(index, clampWidth(Math.max(target, widths[index]), columns[index].minWidth, columns[index].stickyLimit));
|
|
1104
|
+
});
|
|
1105
|
+
return {
|
|
1106
|
+
widths,
|
|
1107
|
+
nextStickyWidths
|
|
1108
|
+
};
|
|
1260
1109
|
};
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1110
|
+
const planColumnLayout = (definitions, rows, now, terminalColumns, previousStickyWidths) => {
|
|
1111
|
+
const measureContext = {
|
|
1112
|
+
rows,
|
|
1113
|
+
now
|
|
1114
|
+
};
|
|
1115
|
+
const columns = definitions.map((definition) => normalizeMeasurement(definition, measureContext));
|
|
1116
|
+
let widths = columns.map((column) => column.preferredWidth);
|
|
1117
|
+
if (terminalColumns !== void 0) {
|
|
1118
|
+
widths = shrinkWidestFirst([...widths], columns, terminalColumns, (index) => columns[index].definition.fixedWidth === void 0);
|
|
1119
|
+
if (totalVisibleWidth(widths) > terminalColumns) widths = forceFitFromRight([...widths], columns, terminalColumns, (index) => columns[index].definition.fixedWidth === void 0);
|
|
1120
|
+
}
|
|
1121
|
+
const stickyResult = applyStickyGrowth(widths, columns, terminalColumns, previousStickyWidths);
|
|
1122
|
+
return {
|
|
1123
|
+
columns: stickyResult.widths.map((width, index) => ({
|
|
1124
|
+
definition: columns[index].definition,
|
|
1125
|
+
width
|
|
1126
|
+
})).filter(({ width }) => width > 0),
|
|
1127
|
+
nextStickyWidths: stickyResult.nextStickyWidths
|
|
1128
|
+
};
|
|
1264
1129
|
};
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
return { render: () => /* @__PURE__ */ jsx(Box, {
|
|
1277
|
-
flexDirection: "row",
|
|
1278
|
-
minWidth: rowWidth,
|
|
1279
|
-
children: columns.map((column, index) => /* @__PURE__ */ jsx(Box, {
|
|
1280
|
-
flexDirection: "column",
|
|
1130
|
+
|
|
1131
|
+
//#endregion
|
|
1132
|
+
//#region src/renderer/public-api.tsx
|
|
1133
|
+
const ProgressRow = memo(({ row, rowIndex, terminalColumns, columns }) => /* @__PURE__ */ jsx(Box, {
|
|
1134
|
+
flexDirection: "row",
|
|
1135
|
+
width: terminalColumns,
|
|
1136
|
+
columnGap: RENDERER_COLUMN_GAP,
|
|
1137
|
+
children: columns.map((column, columnIndex) => {
|
|
1138
|
+
const Component = column.definition.Component;
|
|
1139
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
1140
|
+
height: 1,
|
|
1281
1141
|
width: column.width,
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1142
|
+
minWidth: column.width,
|
|
1143
|
+
flexBasis: column.width,
|
|
1144
|
+
flexGrow: 0,
|
|
1145
|
+
flexShrink: 0,
|
|
1146
|
+
justifyContent: column.definition.justify === "right" ? "flex-end" : "flex-start",
|
|
1147
|
+
children: /* @__PURE__ */ jsx(Component, {
|
|
1148
|
+
row,
|
|
1149
|
+
rowIndex,
|
|
1150
|
+
width: column.width
|
|
1151
|
+
})
|
|
1152
|
+
}, columnIndex);
|
|
1153
|
+
})
|
|
1154
|
+
}), (previousProps, nextProps) => previousProps.row === nextProps.row && previousProps.rowIndex === nextProps.rowIndex && previousProps.terminalColumns === nextProps.terminalColumns && previousProps.columns === nextProps.columns);
|
|
1155
|
+
const layoutDependencyKeyFor = (columns, context) => columns.map((column, index) => `${index}:${column.getLayoutDependency?.(context) ?? "static"}`).join("|");
|
|
1156
|
+
const CreateProgressRenderer = (columns) => {
|
|
1157
|
+
return ({ rows, terminalColumns, terminalRows }) => {
|
|
1158
|
+
const stickyWidthsRef = useRef(/* @__PURE__ */ new Map());
|
|
1159
|
+
const now = useNow();
|
|
1160
|
+
const layout = useMemo(() => rows.length === 0 ? {
|
|
1161
|
+
columns: [],
|
|
1162
|
+
nextStickyWidths: /* @__PURE__ */ new Map()
|
|
1163
|
+
} : planColumnLayout(columns, rows, now, terminalColumns, stickyWidthsRef.current), [
|
|
1164
|
+
columns,
|
|
1165
|
+
layoutDependencyKeyFor(columns, {
|
|
1166
|
+
rows,
|
|
1167
|
+
now
|
|
1168
|
+
}),
|
|
1169
|
+
rows,
|
|
1170
|
+
terminalColumns
|
|
1171
|
+
]);
|
|
1172
|
+
useEffect(() => {
|
|
1173
|
+
stickyWidthsRef.current = rows.length === 0 ? /* @__PURE__ */ new Map() : layout.nextStickyWidths;
|
|
1174
|
+
}, [layout.nextStickyWidths, rows.length]);
|
|
1175
|
+
if (layout.columns.length === 0) return null;
|
|
1176
|
+
return /* @__PURE__ */ jsx(VirtualList, {
|
|
1177
|
+
items: rows,
|
|
1178
|
+
keyExtractor: (row) => `${row.task.id}`,
|
|
1179
|
+
selectedIndex: Math.max(0, rows.length - 1),
|
|
1180
|
+
height: terminalRows ?? "auto",
|
|
1181
|
+
itemHeight: 1,
|
|
1182
|
+
showOverflowIndicators: true,
|
|
1183
|
+
renderItem: ({ item: row, index: rowIndex }) => /* @__PURE__ */ jsx(ProgressRow, {
|
|
1184
|
+
row,
|
|
1185
|
+
rowIndex,
|
|
1186
|
+
terminalColumns,
|
|
1187
|
+
columns: layout.columns
|
|
1188
|
+
})
|
|
1189
|
+
});
|
|
1190
|
+
};
|
|
1290
1191
|
};
|
|
1291
1192
|
|
|
1292
1193
|
//#endregion
|
|
1293
|
-
//#region src/
|
|
1294
|
-
const
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1194
|
+
//#region src/renderer/store/render-snapshot.ts
|
|
1195
|
+
const orderedVisibleTasks = (store) => store.renderOrder.flatMap((row) => {
|
|
1196
|
+
const snapshot = store.tasks.get(row.id);
|
|
1197
|
+
if (!snapshot || snapshot.transient && snapshot.status !== "running") return [];
|
|
1198
|
+
return [{
|
|
1199
|
+
snapshot,
|
|
1200
|
+
depth: row.depth
|
|
1201
|
+
}];
|
|
1202
|
+
});
|
|
1203
|
+
const treeAncestorPrefix = (ancestorHasNextSibling) => ancestorHasNextSibling.slice(1).map((hasNextSibling) => hasNextSibling ? "│ " : " ").join("");
|
|
1204
|
+
const renderTreePrefix = (tree) => {
|
|
1205
|
+
if (tree.depth <= 0) return "";
|
|
1206
|
+
return `${treeAncestorPrefix(tree.ancestorHasNextSibling)}${tree.hasNextSibling ? "├─ " : "└─ "}`;
|
|
1207
|
+
};
|
|
1208
|
+
const arraysEqual = (left, right) => {
|
|
1209
|
+
if (left.length !== right.length) return false;
|
|
1210
|
+
for (let i = 0; i < left.length; i++) if (left[i] !== right[i]) return false;
|
|
1211
|
+
return true;
|
|
1212
|
+
};
|
|
1213
|
+
const sameTreePrefixInputs = (left, right) => left.depth === right.depth && left.hasNextSibling === right.hasNextSibling && arraysEqual(left.ancestorHasNextSibling, right.ancestorHasNextSibling);
|
|
1214
|
+
const sameTree = (left, right) => sameTreePrefixInputs(left, right) && left.hasChildren === right.hasChildren;
|
|
1215
|
+
const deriveRow = (task, tree, previousRow) => {
|
|
1216
|
+
if (previousRow !== void 0 && previousRow.task === task && sameTree(previousRow.tree, tree)) return previousRow.derived;
|
|
1217
|
+
const treePrefix = previousRow !== void 0 && sameTreePrefixInputs(previousRow.tree, tree) ? previousRow.derived.treePrefix : renderTreePrefix(tree);
|
|
1218
|
+
const treePrefixWidth = previousRow !== void 0 && sameTreePrefixInputs(previousRow.tree, tree) ? previousRow.derived.treePrefixWidth : treePrefix.length;
|
|
1219
|
+
const descriptionWidth = previousRow !== void 0 && previousRow.task.description === task.description ? previousRow.derived.descriptionWidth : textWidth(task.description);
|
|
1220
|
+
const isDeterminate = previousRow !== void 0 && previousRow.task.units.total === task.units.total ? previousRow.derived.isDeterminate : task.units.total !== void 0;
|
|
1221
|
+
const hasRenderableProgress = previousRow !== void 0 && previousRow.task.units.total === task.units.total && previousRow.task.units.processed === task.units.processed ? previousRow.derived.hasRenderableProgress : task.units.total !== void 0 || task.units.processed > 0;
|
|
1222
|
+
return {
|
|
1223
|
+
treePrefix,
|
|
1224
|
+
treePrefixWidth,
|
|
1225
|
+
descriptionWidth,
|
|
1226
|
+
treePrefixedDescriptionWidth: treePrefixWidth + descriptionWidth,
|
|
1227
|
+
hasRenderableProgress,
|
|
1228
|
+
isDeterminate
|
|
1229
|
+
};
|
|
1230
|
+
};
|
|
1231
|
+
const computeTreeInfo = (ordered, previousRows) => {
|
|
1232
|
+
const hasNextSiblingByIndex = Array.from({ length: ordered.length }, () => false);
|
|
1233
|
+
const seenByDepth = [];
|
|
1234
|
+
for (let i = ordered.length - 1; i >= 0; i--) {
|
|
1235
|
+
const depth = ordered[i].depth;
|
|
1236
|
+
hasNextSiblingByIndex[i] = seenByDepth[depth] ?? false;
|
|
1237
|
+
seenByDepth[depth] = true;
|
|
1238
|
+
seenByDepth.length = depth + 1;
|
|
1239
|
+
}
|
|
1240
|
+
const ancestorStateByDepth = [];
|
|
1241
|
+
const previousRowsByTaskId = new Map(previousRows.map((row) => [row.task.id, row]));
|
|
1242
|
+
return ordered.map((entry, index) => {
|
|
1243
|
+
const depth = entry.depth;
|
|
1244
|
+
ancestorStateByDepth.length = depth;
|
|
1245
|
+
const hasChildren = index + 1 < ordered.length && ordered[index + 1] !== void 0 && ordered[index + 1].depth > depth;
|
|
1246
|
+
const tree = {
|
|
1247
|
+
depth,
|
|
1248
|
+
hasNextSibling: hasNextSiblingByIndex[index] ?? false,
|
|
1249
|
+
hasChildren,
|
|
1250
|
+
ancestorHasNextSibling: [...ancestorStateByDepth]
|
|
1304
1251
|
};
|
|
1305
|
-
|
|
1306
|
-
|
|
1252
|
+
const previousRow = previousRowsByTaskId.get(entry.snapshot.id);
|
|
1253
|
+
ancestorStateByDepth[depth] = hasNextSiblingByIndex[index] ?? false;
|
|
1254
|
+
if (previousRow !== void 0 && previousRow.task === entry.snapshot && sameTree(previousRow.tree, tree)) return previousRow;
|
|
1255
|
+
const derived = deriveRow(entry.snapshot, tree, previousRow);
|
|
1256
|
+
return {
|
|
1257
|
+
task: entry.snapshot,
|
|
1258
|
+
tree: previousRow !== void 0 && sameTree(previousRow.tree, tree) ? previousRow.tree : tree,
|
|
1259
|
+
derived
|
|
1260
|
+
};
|
|
1261
|
+
});
|
|
1262
|
+
};
|
|
1263
|
+
const toRenderSnapshot = (store, previousSnapshot) => {
|
|
1264
|
+
const visibleTasks = orderedVisibleTasks(store);
|
|
1265
|
+
const hasRunningTasks = visibleTasks.some((entry) => entry.snapshot.status === "running");
|
|
1266
|
+
return {
|
|
1267
|
+
rows: computeTreeInfo(visibleTasks, previousSnapshot?.rows ?? []),
|
|
1268
|
+
hasRunningTasks
|
|
1269
|
+
};
|
|
1307
1270
|
};
|
|
1308
1271
|
|
|
1309
1272
|
//#endregion
|
|
1310
|
-
//#region src/
|
|
1311
|
-
const
|
|
1312
|
-
const
|
|
1273
|
+
//#region src/renderer/store/use-progress-render-view.ts
|
|
1274
|
+
const useRenderSnapshot = (storeSnapshot) => {
|
|
1275
|
+
const previousSnapshotRef = useRef(void 0);
|
|
1276
|
+
const renderSnapshot = useMemo(() => toRenderSnapshot(storeSnapshot, previousSnapshotRef.current), [storeSnapshot]);
|
|
1313
1277
|
useEffect(() => {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1278
|
+
previousSnapshotRef.current = renderSnapshot;
|
|
1279
|
+
}, [renderSnapshot]);
|
|
1280
|
+
return renderSnapshot;
|
|
1281
|
+
};
|
|
1282
|
+
const useProgressRenderView = (store) => {
|
|
1283
|
+
const publication = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
1284
|
+
const renderSnapshot = useRenderSnapshot(publication.snapshot);
|
|
1285
|
+
return {
|
|
1286
|
+
publication,
|
|
1287
|
+
renderSnapshot,
|
|
1288
|
+
hasRunningTasks: renderSnapshot.rows.some((row) => row.task.status === "running")
|
|
1289
|
+
};
|
|
1323
1290
|
};
|
|
1324
1291
|
|
|
1325
1292
|
//#endregion
|
|
1326
|
-
//#region src/
|
|
1327
|
-
const
|
|
1328
|
-
const
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1293
|
+
//#region src/renderer/renderer-service.tsx
|
|
1294
|
+
const MAX_FPS = 24;
|
|
1295
|
+
const CreateProgressRoot = (columns) => {
|
|
1296
|
+
const ProgressRenderer = CreateProgressRenderer(columns);
|
|
1297
|
+
return ({ store, getTerminalColumns, getTerminalRows }) => {
|
|
1298
|
+
const { renderSnapshot, hasRunningTasks } = useProgressRenderView(store);
|
|
1299
|
+
return /* @__PURE__ */ jsx(SpinnerProvider, {
|
|
1300
|
+
active: hasRunningTasks,
|
|
1301
|
+
children: /* @__PURE__ */ jsx(NowProvider, {
|
|
1302
|
+
active: hasRunningTasks,
|
|
1303
|
+
children: /* @__PURE__ */ jsx(ProgressRenderer, {
|
|
1304
|
+
rows: renderSnapshot.rows,
|
|
1305
|
+
terminalColumns: getTerminalColumns(),
|
|
1306
|
+
terminalRows: getTerminalRows()
|
|
1307
|
+
})
|
|
1308
|
+
})
|
|
1309
|
+
});
|
|
1310
|
+
};
|
|
1311
|
+
};
|
|
1312
|
+
const makeRendererv2InkRendererService = (columns) => {
|
|
1313
|
+
const ProgressRoot = CreateProgressRoot(columns);
|
|
1314
|
+
return { run: (store, stdio, isTTY) => {
|
|
1315
|
+
const proot = /* @__PURE__ */ jsx(ProgressRoot, {
|
|
1316
|
+
store,
|
|
1317
|
+
getTerminalColumns: () => isTTY ? stdio.stderr.columns : void 0,
|
|
1318
|
+
getTerminalRows: () => isTTY ? stdio.stderr.rows : void 0
|
|
1319
|
+
});
|
|
1320
|
+
return Effect.sync(() => render(proot, {
|
|
1321
|
+
stdout: stdio.stdout,
|
|
1322
|
+
stderr: stdio.stderr,
|
|
1323
|
+
patchConsole: true,
|
|
1324
|
+
exitOnCtrlC: false,
|
|
1325
|
+
debug: false,
|
|
1326
|
+
maxFps: MAX_FPS
|
|
1327
|
+
})).pipe(Effect.flatMap((instance) => Effect.never.pipe(Effect.ensuring(Effect.gen(function* () {
|
|
1328
|
+
store.flush();
|
|
1329
|
+
instance.rerender(proot);
|
|
1330
|
+
yield* Effect.sync(() => {
|
|
1331
|
+
instance.unmount();
|
|
1332
|
+
});
|
|
1333
|
+
})))));
|
|
1334
|
+
} };
|
|
1335
1335
|
};
|
|
1336
1336
|
|
|
1337
1337
|
//#endregion
|
|
1338
1338
|
//#region src/services/ink-renderer.tsx
|
|
1339
|
-
const MAX_FPS = 12;
|
|
1340
|
-
const makeDefaultInkRenderer = () => ({ run: (store, stdio, isTTY) => Effect.sync(() => render(/* @__PURE__ */ jsx(ProgressRoot, {
|
|
1341
|
-
store,
|
|
1342
|
-
getTerminalColumns: () => isTTY ? stdio.stderr.columns : void 0
|
|
1343
|
-
}), {
|
|
1344
|
-
stdout: stdio.stdout,
|
|
1345
|
-
stderr: stdio.stderr,
|
|
1346
|
-
patchConsole: true,
|
|
1347
|
-
exitOnCtrlC: false,
|
|
1348
|
-
debug: false,
|
|
1349
|
-
maxFps: MAX_FPS
|
|
1350
|
-
})).pipe(Effect.flatMap((instance) => Effect.never.pipe(Effect.ensuring(Effect.gen(function* () {
|
|
1351
|
-
store.flush();
|
|
1352
|
-
yield* Effect.sleep("0 millis");
|
|
1353
|
-
yield* Effect.sync(() => {
|
|
1354
|
-
instance.unmount();
|
|
1355
|
-
});
|
|
1356
|
-
}))))) });
|
|
1357
1339
|
var InkRenderer = class InkRenderer extends Context.Tag("stromseng.dev/effective-progress/InkRenderer")() {
|
|
1358
|
-
static Default = Layer.succeed(InkRenderer, InkRenderer.of(
|
|
1340
|
+
static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeRendererv2InkRendererService(defaultRendererv2Columns)));
|
|
1359
1341
|
};
|
|
1360
1342
|
|
|
1361
1343
|
//#endregion
|
|
@@ -1533,4 +1515,4 @@ const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(fun
|
|
|
1533
1515
|
})));
|
|
1534
1516
|
|
|
1535
1517
|
//#endregion
|
|
1536
|
-
export { Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskRemovedEvent, TaskSnapshot, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|
|
1518
|
+
export { Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskRemovedEvent, TaskSnapshot, TaskSnapshotSchema, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|