effective-progress 0.10.0 → 0.11.1
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 +120 -11
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/index.d.mts +179 -14
- package/dist/index.mjs +586 -608
- package/package.json +2 -3
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
|
|
1
2
|
import { Brand, Cause, Clock, Context, Effect, Exit, FiberRef, Layer, Option, Schema } from "effect";
|
|
2
3
|
import { dual } from "effect/Function";
|
|
3
|
-
import { Box, Text, render } from "ink";
|
|
4
|
-
import
|
|
4
|
+
import { Box, Text, render, useBoxMetrics } from "ink";
|
|
5
|
+
import { createContext, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
import cliSpinners from "cli-spinners";
|
|
7
|
-
import
|
|
8
|
-
import { VirtualList } from "ink-virtual-list";
|
|
8
|
+
import stringWidth from "fast-string-width";
|
|
9
9
|
|
|
10
10
|
//#region src/types.ts
|
|
11
11
|
const TaskIdSchema = Schema.Number.pipe(Schema.brand("TaskId"));
|
|
@@ -18,6 +18,10 @@ const TaskUnitsSchema = Schema.Struct({
|
|
|
18
18
|
processed: Schema.Number,
|
|
19
19
|
total: Schema.optional(Schema.Number)
|
|
20
20
|
});
|
|
21
|
+
const TaskProgressSampleSchema = Schema.Struct({
|
|
22
|
+
timestamp: Schema.Number,
|
|
23
|
+
processed: Schema.Number
|
|
24
|
+
});
|
|
21
25
|
const TaskSnapshotSchema = Schema.Struct({
|
|
22
26
|
id: TaskIdSchema,
|
|
23
27
|
parentId: Schema.NullOr(TaskIdSchema),
|
|
@@ -27,7 +31,9 @@ const TaskSnapshotSchema = Schema.Struct({
|
|
|
27
31
|
transient: Schema.Boolean,
|
|
28
32
|
units: TaskUnitsSchema,
|
|
29
33
|
startedAt: Schema.Number,
|
|
30
|
-
completedAt: Schema.NullOr(Schema.Number)
|
|
34
|
+
completedAt: Schema.NullOr(Schema.Number),
|
|
35
|
+
progressSamples: Schema.Array(TaskProgressSampleSchema),
|
|
36
|
+
metadata: Schema.Unknown
|
|
31
37
|
});
|
|
32
38
|
const TaskSnapshot = (snapshot) => snapshot;
|
|
33
39
|
var Task = class extends Context.Tag("stromseng.dev/effective-progress/Task")() {};
|
|
@@ -62,6 +68,8 @@ const decodeProgressTaskEvent = Schema.decodeUnknownSync(ProgressTaskEventSchema
|
|
|
62
68
|
|
|
63
69
|
//#endregion
|
|
64
70
|
//#region src/renderer/store.ts
|
|
71
|
+
const ETA_SAMPLE_WINDOW_MILLIS = 3e4;
|
|
72
|
+
const ETA_SAMPLE_MAX_LENGTH = 1e3;
|
|
65
73
|
const hasExplicitTotal = (options) => Object.prototype.hasOwnProperty.call(options, "total");
|
|
66
74
|
const sanitizeTotalOnAdd = (total) => {
|
|
67
75
|
if (total === void 0) return;
|
|
@@ -85,7 +93,25 @@ const normalizeUnits = (counts) => {
|
|
|
85
93
|
total: counts.total
|
|
86
94
|
};
|
|
87
95
|
};
|
|
88
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Returns a new progress sample deque with the latest processed count appended.
|
|
98
|
+
*
|
|
99
|
+
* Samples are retained for the ETA rolling window and capped by count so very chatty tasks do not
|
|
100
|
+
* grow memory without bound.
|
|
101
|
+
*/
|
|
102
|
+
const appendProgressSample = (samples, now, processed) => {
|
|
103
|
+
const previousSamples = samples ?? [];
|
|
104
|
+
if (previousSamples.at(-1)?.processed === processed) return previousSamples;
|
|
105
|
+
const windowStart = now - ETA_SAMPLE_WINDOW_MILLIS;
|
|
106
|
+
const nextSamples = [...previousSamples, {
|
|
107
|
+
timestamp: now,
|
|
108
|
+
processed
|
|
109
|
+
}];
|
|
110
|
+
while (nextSamples.length > 2 && (nextSamples.length > ETA_SAMPLE_MAX_LENGTH || nextSamples[1].timestamp < windowStart)) nextSamples.shift();
|
|
111
|
+
return nextSamples;
|
|
112
|
+
};
|
|
113
|
+
/** Applies mutable task fields and records a progress sample when the processed count changes. */
|
|
114
|
+
const updatedSnapshot = (snapshot, options, now) => {
|
|
89
115
|
const currentUnits = snapshot.units;
|
|
90
116
|
const units = options.succeeded === void 0 && options.failed === void 0 && options.total === void 0 && !hasExplicitTotal(options) ? currentUnits : normalizeUnits({
|
|
91
117
|
succeeded: options.succeeded ?? currentUnits.succeeded,
|
|
@@ -93,28 +119,14 @@ const updatedSnapshot = (snapshot, options) => {
|
|
|
93
119
|
total: hasExplicitTotal(options) ? sanitizeTotalOnUpdate(options.total) : currentUnits.total
|
|
94
120
|
});
|
|
95
121
|
return TaskSnapshot({
|
|
96
|
-
|
|
97
|
-
parentId: snapshot.parentId,
|
|
122
|
+
...snapshot,
|
|
98
123
|
description: options.description ?? snapshot.description,
|
|
99
|
-
status: snapshot.status,
|
|
100
124
|
countDisplay: options.countDisplay ?? snapshot.countDisplay,
|
|
101
125
|
transient: options.transient ?? snapshot.transient,
|
|
102
126
|
units,
|
|
103
|
-
|
|
104
|
-
completedAt: snapshot.completedAt
|
|
127
|
+
progressSamples: appendProgressSample(snapshot.progressSamples, now, units.processed)
|
|
105
128
|
});
|
|
106
129
|
};
|
|
107
|
-
const withTransient = (snapshot, transient) => TaskSnapshot({
|
|
108
|
-
id: snapshot.id,
|
|
109
|
-
parentId: snapshot.parentId,
|
|
110
|
-
description: snapshot.description,
|
|
111
|
-
status: snapshot.status,
|
|
112
|
-
countDisplay: snapshot.countDisplay,
|
|
113
|
-
transient,
|
|
114
|
-
units: snapshot.units,
|
|
115
|
-
startedAt: snapshot.startedAt,
|
|
116
|
-
completedAt: snapshot.completedAt
|
|
117
|
-
});
|
|
118
130
|
const findInsertionIndex = (renderOrder, parentId) => {
|
|
119
131
|
if (parentId === null) return {
|
|
120
132
|
index: renderOrder.length,
|
|
@@ -156,7 +168,8 @@ const makeProgressRenderStore = () => {
|
|
|
156
168
|
let nextTaskId = 0;
|
|
157
169
|
let state = {
|
|
158
170
|
tasks: /* @__PURE__ */ new Map(),
|
|
159
|
-
renderOrder: []
|
|
171
|
+
renderOrder: [],
|
|
172
|
+
columns: /* @__PURE__ */ new Map()
|
|
160
173
|
};
|
|
161
174
|
let pendingEvents = [];
|
|
162
175
|
let publishedPublication = {
|
|
@@ -245,7 +258,12 @@ const makeProgressRenderStore = () => {
|
|
|
245
258
|
transient: (parentSnapshot?.transient ?? false) || (options.transient ?? false),
|
|
246
259
|
units,
|
|
247
260
|
startedAt: now,
|
|
248
|
-
completedAt: null
|
|
261
|
+
completedAt: null,
|
|
262
|
+
progressSamples: [{
|
|
263
|
+
timestamp: now,
|
|
264
|
+
processed: units.processed
|
|
265
|
+
}],
|
|
266
|
+
metadata: options.metadata
|
|
249
267
|
});
|
|
250
268
|
updateState((current) => {
|
|
251
269
|
const nextTasks = new Map(current.tasks);
|
|
@@ -259,7 +277,8 @@ const makeProgressRenderStore = () => {
|
|
|
259
277
|
return {
|
|
260
278
|
state: {
|
|
261
279
|
tasks: nextTasks,
|
|
262
|
-
renderOrder: nextRenderOrder
|
|
280
|
+
renderOrder: nextRenderOrder,
|
|
281
|
+
columns: options.columns ? new Map(current.columns).set(taskId, options.columns) : current.columns
|
|
263
282
|
},
|
|
264
283
|
events: [new TaskAddedEvent({
|
|
265
284
|
taskId,
|
|
@@ -273,14 +292,15 @@ const makeProgressRenderStore = () => {
|
|
|
273
292
|
});
|
|
274
293
|
return taskId;
|
|
275
294
|
}),
|
|
276
|
-
updateTask: (taskId, options) => Effect.
|
|
295
|
+
updateTask: (taskId, options) => Effect.gen(function* () {
|
|
296
|
+
const now = yield* Clock.currentTimeMillis;
|
|
277
297
|
updateState((current) => {
|
|
278
298
|
const currentTask = current.tasks.get(taskId);
|
|
279
299
|
if (!currentTask) return {
|
|
280
300
|
state: current,
|
|
281
301
|
events: []
|
|
282
302
|
};
|
|
283
|
-
const nextTask = updatedSnapshot(currentTask, options);
|
|
303
|
+
const nextTask = updatedSnapshot(currentTask, options, now);
|
|
284
304
|
const nextTasks = new Map(current.tasks);
|
|
285
305
|
nextTasks.set(taskId, nextTask);
|
|
286
306
|
const events = [new TaskUpdatedEvent({
|
|
@@ -296,7 +316,10 @@ const makeProgressRenderStore = () => {
|
|
|
296
316
|
if (options.transient !== void 0) for (const candidateId of subtreeTaskIds(current.renderOrder, taskId).slice(1)) {
|
|
297
317
|
const candidate = current.tasks.get(candidateId);
|
|
298
318
|
if (!candidate) continue;
|
|
299
|
-
const nextCandidate =
|
|
319
|
+
const nextCandidate = TaskSnapshot({
|
|
320
|
+
...candidate,
|
|
321
|
+
transient: nextTask.transient
|
|
322
|
+
});
|
|
300
323
|
nextTasks.set(candidateId, nextCandidate);
|
|
301
324
|
events.push(new TaskUpdatedEvent({
|
|
302
325
|
taskId: candidateId,
|
|
@@ -306,39 +329,37 @@ const makeProgressRenderStore = () => {
|
|
|
306
329
|
return {
|
|
307
330
|
state: {
|
|
308
331
|
tasks: nextTasks,
|
|
309
|
-
renderOrder: current.renderOrder
|
|
332
|
+
renderOrder: current.renderOrder,
|
|
333
|
+
columns: current.columns
|
|
310
334
|
},
|
|
311
335
|
events
|
|
312
336
|
};
|
|
313
337
|
});
|
|
314
338
|
}),
|
|
315
|
-
incrementSucceeded: (taskId, amount = 1) => Effect.
|
|
339
|
+
incrementSucceeded: (taskId, amount = 1) => Effect.gen(function* () {
|
|
340
|
+
const now = yield* Clock.currentTimeMillis;
|
|
316
341
|
updateState((current) => {
|
|
317
342
|
const currentTask = current.tasks.get(taskId);
|
|
318
343
|
if (!currentTask) return {
|
|
319
344
|
state: current,
|
|
320
345
|
events: []
|
|
321
346
|
};
|
|
347
|
+
const units = normalizeUnits({
|
|
348
|
+
succeeded: currentTask.units.succeeded + amount,
|
|
349
|
+
failed: currentTask.units.failed,
|
|
350
|
+
total: currentTask.units.total
|
|
351
|
+
});
|
|
322
352
|
const nextTasks = new Map(current.tasks);
|
|
323
353
|
nextTasks.set(taskId, TaskSnapshot({
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
status: currentTask.status,
|
|
328
|
-
countDisplay: currentTask.countDisplay,
|
|
329
|
-
transient: currentTask.transient,
|
|
330
|
-
units: normalizeUnits({
|
|
331
|
-
succeeded: currentTask.units.succeeded + amount,
|
|
332
|
-
failed: currentTask.units.failed,
|
|
333
|
-
total: currentTask.units.total
|
|
334
|
-
}),
|
|
335
|
-
startedAt: currentTask.startedAt,
|
|
336
|
-
completedAt: currentTask.completedAt
|
|
354
|
+
...currentTask,
|
|
355
|
+
units,
|
|
356
|
+
progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
|
|
337
357
|
}));
|
|
338
358
|
return {
|
|
339
359
|
state: {
|
|
340
360
|
tasks: nextTasks,
|
|
341
|
-
renderOrder: current.renderOrder
|
|
361
|
+
renderOrder: current.renderOrder,
|
|
362
|
+
columns: current.columns
|
|
342
363
|
},
|
|
343
364
|
events: [new TaskAdvancedEvent({
|
|
344
365
|
taskId,
|
|
@@ -348,33 +369,30 @@ const makeProgressRenderStore = () => {
|
|
|
348
369
|
};
|
|
349
370
|
});
|
|
350
371
|
}),
|
|
351
|
-
incrementFailed: (taskId, amount = 1) => Effect.
|
|
372
|
+
incrementFailed: (taskId, amount = 1) => Effect.gen(function* () {
|
|
373
|
+
const now = yield* Clock.currentTimeMillis;
|
|
352
374
|
updateState((current) => {
|
|
353
375
|
const currentTask = current.tasks.get(taskId);
|
|
354
376
|
if (!currentTask) return {
|
|
355
377
|
state: current,
|
|
356
378
|
events: []
|
|
357
379
|
};
|
|
380
|
+
const units = normalizeUnits({
|
|
381
|
+
succeeded: currentTask.units.succeeded,
|
|
382
|
+
failed: currentTask.units.failed + amount,
|
|
383
|
+
total: currentTask.units.total
|
|
384
|
+
});
|
|
358
385
|
const nextTasks = new Map(current.tasks);
|
|
359
386
|
nextTasks.set(taskId, TaskSnapshot({
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
status: currentTask.status,
|
|
364
|
-
countDisplay: currentTask.countDisplay,
|
|
365
|
-
transient: currentTask.transient,
|
|
366
|
-
units: normalizeUnits({
|
|
367
|
-
succeeded: currentTask.units.succeeded,
|
|
368
|
-
failed: currentTask.units.failed + amount,
|
|
369
|
-
total: currentTask.units.total
|
|
370
|
-
}),
|
|
371
|
-
startedAt: currentTask.startedAt,
|
|
372
|
-
completedAt: currentTask.completedAt
|
|
387
|
+
...currentTask,
|
|
388
|
+
units,
|
|
389
|
+
progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
|
|
373
390
|
}));
|
|
374
391
|
return {
|
|
375
392
|
state: {
|
|
376
393
|
tasks: nextTasks,
|
|
377
|
-
renderOrder: current.renderOrder
|
|
394
|
+
renderOrder: current.renderOrder,
|
|
395
|
+
columns: current.columns
|
|
378
396
|
},
|
|
379
397
|
events: [new TaskAdvancedEvent({
|
|
380
398
|
taskId,
|
|
@@ -392,41 +410,46 @@ const makeProgressRenderStore = () => {
|
|
|
392
410
|
state: current,
|
|
393
411
|
events: []
|
|
394
412
|
};
|
|
413
|
+
if (currentTask.status !== "running") return {
|
|
414
|
+
state: current,
|
|
415
|
+
events: []
|
|
416
|
+
};
|
|
395
417
|
const nextTasks = new Map(current.tasks);
|
|
396
418
|
if (currentTask.transient) {
|
|
397
419
|
const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
|
|
398
420
|
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
421
|
+
const nextColumns = new Map(current.columns);
|
|
422
|
+
for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
|
|
399
423
|
return {
|
|
400
424
|
state: {
|
|
401
425
|
tasks: nextTasks,
|
|
402
|
-
renderOrder: removeFromRenderOrder(current.renderOrder, taskId)
|
|
426
|
+
renderOrder: removeFromRenderOrder(current.renderOrder, taskId),
|
|
427
|
+
columns: nextColumns
|
|
403
428
|
},
|
|
404
429
|
events: [new TaskCompletedEvent({ taskId }), ...removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
|
|
405
430
|
};
|
|
406
431
|
}
|
|
432
|
+
const units = currentTask.units.total !== void 0 ? currentTask.units.processed < currentTask.units.total ? normalizeUnits({
|
|
433
|
+
succeeded: currentTask.units.succeeded + (currentTask.units.total - currentTask.units.processed),
|
|
434
|
+
failed: currentTask.units.failed,
|
|
435
|
+
total: currentTask.units.total
|
|
436
|
+
}) : currentTask.units : currentTask.units.processed > 0 ? normalizeUnits({
|
|
437
|
+
succeeded: currentTask.units.succeeded,
|
|
438
|
+
failed: currentTask.units.failed,
|
|
439
|
+
total: currentTask.units.processed
|
|
440
|
+
}) : currentTask.units;
|
|
407
441
|
nextTasks.set(taskId, TaskSnapshot({
|
|
408
|
-
|
|
409
|
-
parentId: currentTask.parentId,
|
|
410
|
-
description: currentTask.description,
|
|
442
|
+
...currentTask,
|
|
411
443
|
status: "done",
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
succeeded: currentTask.units.succeeded + (currentTask.units.total - currentTask.units.processed),
|
|
416
|
-
failed: currentTask.units.failed,
|
|
417
|
-
total: currentTask.units.total
|
|
418
|
-
}) : currentTask.units : currentTask.units.processed > 0 ? normalizeUnits({
|
|
419
|
-
succeeded: currentTask.units.succeeded,
|
|
420
|
-
failed: currentTask.units.failed,
|
|
421
|
-
total: currentTask.units.processed
|
|
422
|
-
}) : currentTask.units,
|
|
423
|
-
startedAt: currentTask.startedAt,
|
|
424
|
-
completedAt: now
|
|
444
|
+
units,
|
|
445
|
+
completedAt: now,
|
|
446
|
+
progressSamples: appendProgressSample(currentTask.progressSamples, now, units.processed)
|
|
425
447
|
}));
|
|
426
448
|
return {
|
|
427
449
|
state: {
|
|
428
450
|
tasks: nextTasks,
|
|
429
|
-
renderOrder: current.renderOrder
|
|
451
|
+
renderOrder: current.renderOrder,
|
|
452
|
+
columns: current.columns
|
|
430
453
|
},
|
|
431
454
|
events: [new TaskCompletedEvent({ taskId })]
|
|
432
455
|
};
|
|
@@ -440,43 +463,146 @@ const makeProgressRenderStore = () => {
|
|
|
440
463
|
state: current,
|
|
441
464
|
events: []
|
|
442
465
|
};
|
|
466
|
+
if (currentTask.status !== "running") return {
|
|
467
|
+
state: current,
|
|
468
|
+
events: []
|
|
469
|
+
};
|
|
443
470
|
const nextTasks = new Map(current.tasks);
|
|
444
471
|
if (currentTask.transient) {
|
|
445
472
|
const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
|
|
446
473
|
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
474
|
+
const nextColumns = new Map(current.columns);
|
|
475
|
+
for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
|
|
447
476
|
return {
|
|
448
477
|
state: {
|
|
449
478
|
tasks: nextTasks,
|
|
450
|
-
renderOrder: removeFromRenderOrder(current.renderOrder, taskId)
|
|
479
|
+
renderOrder: removeFromRenderOrder(current.renderOrder, taskId),
|
|
480
|
+
columns: nextColumns
|
|
451
481
|
},
|
|
452
482
|
events: [new TaskFailedEvent({ taskId }), ...removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
|
|
453
483
|
};
|
|
454
484
|
}
|
|
455
485
|
nextTasks.set(taskId, TaskSnapshot({
|
|
456
|
-
|
|
457
|
-
parentId: currentTask.parentId,
|
|
458
|
-
description: currentTask.description,
|
|
486
|
+
...currentTask,
|
|
459
487
|
status: "failed",
|
|
460
|
-
countDisplay: currentTask.countDisplay,
|
|
461
|
-
transient: currentTask.transient,
|
|
462
|
-
units: currentTask.units,
|
|
463
|
-
startedAt: currentTask.startedAt,
|
|
464
488
|
completedAt: now
|
|
465
489
|
}));
|
|
466
490
|
return {
|
|
467
491
|
state: {
|
|
468
492
|
tasks: nextTasks,
|
|
469
|
-
renderOrder: current.renderOrder
|
|
493
|
+
renderOrder: current.renderOrder,
|
|
494
|
+
columns: current.columns
|
|
470
495
|
},
|
|
471
496
|
events: [new TaskFailedEvent({ taskId })]
|
|
472
497
|
};
|
|
473
498
|
});
|
|
474
499
|
}),
|
|
475
500
|
getTask: (taskId) => Effect.sync(() => Option.fromNullable(state.tasks.get(taskId))),
|
|
476
|
-
listTasks: Effect.sync(() => Array.from(state.tasks.values()))
|
|
501
|
+
listTasks: Effect.sync(() => Array.from(state.tasks.values())),
|
|
502
|
+
setMetadata: (taskId, metadata) => Effect.sync(() => {
|
|
503
|
+
updateState((current) => {
|
|
504
|
+
const currentTask = current.tasks.get(taskId);
|
|
505
|
+
if (!currentTask) return {
|
|
506
|
+
state: current,
|
|
507
|
+
events: []
|
|
508
|
+
};
|
|
509
|
+
const nextTasks = new Map(current.tasks);
|
|
510
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
511
|
+
...currentTask,
|
|
512
|
+
metadata
|
|
513
|
+
}));
|
|
514
|
+
return {
|
|
515
|
+
state: {
|
|
516
|
+
tasks: nextTasks,
|
|
517
|
+
renderOrder: current.renderOrder,
|
|
518
|
+
columns: current.columns
|
|
519
|
+
},
|
|
520
|
+
events: []
|
|
521
|
+
};
|
|
522
|
+
});
|
|
523
|
+
}),
|
|
524
|
+
getMetadata: (taskId) => Effect.sync(() => {
|
|
525
|
+
return state.tasks.get(taskId)?.metadata;
|
|
526
|
+
})
|
|
477
527
|
};
|
|
478
528
|
};
|
|
479
529
|
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/renderer/hooks/use-now-clock.ts
|
|
532
|
+
const useNowClock = (active, intervalMillis) => {
|
|
533
|
+
const [now, setNow] = useState(() => Date.now());
|
|
534
|
+
useEffect(() => {
|
|
535
|
+
if (!active) return;
|
|
536
|
+
setNow(Date.now());
|
|
537
|
+
const interval = setInterval(() => {
|
|
538
|
+
setNow(Date.now());
|
|
539
|
+
}, intervalMillis);
|
|
540
|
+
return () => {
|
|
541
|
+
clearInterval(interval);
|
|
542
|
+
};
|
|
543
|
+
}, [active, intervalMillis]);
|
|
544
|
+
return now;
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/renderer/context/now-context.tsx
|
|
549
|
+
const NOW_INTERVAL_MILLIS = 1e3;
|
|
550
|
+
const NowContext = createContext(Date.now());
|
|
551
|
+
const NowProvider = ({ active, children, nowOverride }) => {
|
|
552
|
+
const liveNow = useNowClock(active, NOW_INTERVAL_MILLIS);
|
|
553
|
+
const now = nowOverride ?? liveNow;
|
|
554
|
+
return /* @__PURE__ */ jsx(NowContext.Provider, {
|
|
555
|
+
value: now,
|
|
556
|
+
children
|
|
557
|
+
});
|
|
558
|
+
};
|
|
559
|
+
const useNow = () => useContext(NowContext);
|
|
560
|
+
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/renderer/hooks/use-spinner-clock.ts
|
|
563
|
+
const normalizeIntervalMillis = (intervalMillis) => Math.max(1, intervalMillis);
|
|
564
|
+
const getSpinnerTickAtTime = (baseTick, startedAt, now, intervalMillis) => {
|
|
565
|
+
const elapsedMillis = Math.max(0, now - startedAt);
|
|
566
|
+
return baseTick + Math.floor(elapsedMillis / normalizeIntervalMillis(intervalMillis));
|
|
567
|
+
};
|
|
568
|
+
const useSpinnerClock = (active, intervalMillis) => {
|
|
569
|
+
const [tick, setTick] = useState(0);
|
|
570
|
+
const tickRef = useRef(tick);
|
|
571
|
+
useEffect(() => {
|
|
572
|
+
tickRef.current = tick;
|
|
573
|
+
}, [tick]);
|
|
574
|
+
useEffect(() => {
|
|
575
|
+
if (!active) return;
|
|
576
|
+
const baseTick = tickRef.current;
|
|
577
|
+
const startedAt = Date.now();
|
|
578
|
+
const updateTick = () => {
|
|
579
|
+
setTick(getSpinnerTickAtTime(baseTick, startedAt, Date.now(), intervalMillis));
|
|
580
|
+
};
|
|
581
|
+
updateTick();
|
|
582
|
+
const interval = setInterval(() => {
|
|
583
|
+
updateTick();
|
|
584
|
+
}, normalizeIntervalMillis(intervalMillis));
|
|
585
|
+
return () => {
|
|
586
|
+
clearInterval(interval);
|
|
587
|
+
};
|
|
588
|
+
}, [active, intervalMillis]);
|
|
589
|
+
return tick;
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/renderer/context/spinner-context.tsx
|
|
594
|
+
const DEFAULT_SPINNER_INTERVAL_MILLIS = cliSpinners.dots.interval;
|
|
595
|
+
const SpinnerContext = createContext(0);
|
|
596
|
+
const SpinnerProvider = ({ active, children, intervalMillis = DEFAULT_SPINNER_INTERVAL_MILLIS, tickOverride }) => {
|
|
597
|
+
const liveTick = useSpinnerClock(active, intervalMillis);
|
|
598
|
+
const tick = tickOverride ?? liveTick;
|
|
599
|
+
return /* @__PURE__ */ jsx(SpinnerContext.Provider, {
|
|
600
|
+
value: tick,
|
|
601
|
+
children
|
|
602
|
+
});
|
|
603
|
+
};
|
|
604
|
+
const useSpinnerTick = () => useContext(SpinnerContext);
|
|
605
|
+
|
|
480
606
|
//#endregion
|
|
481
607
|
//#region src/renderer/shared/determinate.ts
|
|
482
608
|
const isDeterminate$2 = (task) => task.units.total !== void 0;
|
|
@@ -497,17 +623,58 @@ const formatDurationSeconds = (seconds) => {
|
|
|
497
623
|
const mins = Math.floor(value % 3600 / 60);
|
|
498
624
|
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
499
625
|
};
|
|
626
|
+
const formatClockDurationSeconds = (seconds) => {
|
|
627
|
+
const value = Math.max(0, Math.floor(seconds));
|
|
628
|
+
const hours = Math.floor(value / 3600);
|
|
629
|
+
const mins = Math.floor(value % 3600 / 60);
|
|
630
|
+
const secs = value % 60;
|
|
631
|
+
const clock = `${`${mins}`.padStart(2, "0")}:${`${secs}`.padStart(2, "0")}`;
|
|
632
|
+
return hours > 0 ? `${`${hours}`.padStart(2, "0")}:${clock}` : clock;
|
|
633
|
+
};
|
|
634
|
+
/**
|
|
635
|
+
* Estimates remaining time from the task's retained progress sample deque.
|
|
636
|
+
*
|
|
637
|
+
* Returns undefined until there are at least two samples with positive processed and time deltas.
|
|
638
|
+
*/
|
|
639
|
+
const getSmoothedEtaMillis = (task) => {
|
|
640
|
+
const { processed, total } = task.units;
|
|
641
|
+
const remaining = total - processed;
|
|
642
|
+
if (processed <= 0 || remaining <= 0) return;
|
|
643
|
+
const samples = task.progressSamples;
|
|
644
|
+
const lastSample = samples.at(-1);
|
|
645
|
+
if (lastSample === void 0) return;
|
|
646
|
+
const firstSample = samples[0];
|
|
647
|
+
if (firstSample === void 0 || firstSample === lastSample) return;
|
|
648
|
+
const deltaProcessed = lastSample.processed - firstSample.processed;
|
|
649
|
+
const deltaMillis = lastSample.timestamp - firstSample.timestamp;
|
|
650
|
+
if (deltaProcessed <= 0 || deltaMillis <= 0) return;
|
|
651
|
+
return Math.max(0, Math.floor(remaining * deltaMillis / deltaProcessed));
|
|
652
|
+
};
|
|
500
653
|
const formatElapsed = (task, now) => {
|
|
501
654
|
return formatDurationSeconds(Math.max(0, (task.completedAt ?? now) - task.startedAt) / 1e3);
|
|
502
655
|
};
|
|
656
|
+
const formatElapsedClock = (task, now) => {
|
|
657
|
+
return formatClockDurationSeconds(Math.max(0, (task.completedAt ?? now) - task.startedAt) / 1e3);
|
|
658
|
+
};
|
|
503
659
|
const formatEta = (task, now) => {
|
|
504
660
|
if (task.status !== "running" || !isDeterminate$1(task)) return "";
|
|
505
661
|
const { processed, total } = task.units;
|
|
506
662
|
const remaining = total - processed;
|
|
507
663
|
if (processed <= 0 || remaining <= 0) return "";
|
|
508
|
-
const
|
|
509
|
-
|
|
664
|
+
const etaMillis = getSmoothedEtaMillis(task);
|
|
665
|
+
if (etaMillis === void 0) return "";
|
|
666
|
+
return formatClockDurationSeconds(etaMillis / 1e3);
|
|
667
|
+
};
|
|
668
|
+
const formatEtaClock = (task, now) => {
|
|
669
|
+
if (task.status !== "running" || !isDeterminate$1(task)) return;
|
|
670
|
+
const { processed, total } = task.units;
|
|
671
|
+
const remaining = total - processed;
|
|
672
|
+
if (processed <= 0 || remaining <= 0) return;
|
|
673
|
+
const etaMillis = getSmoothedEtaMillis(task);
|
|
674
|
+
if (etaMillis === void 0) return;
|
|
675
|
+
return formatClockDurationSeconds(etaMillis / 1e3);
|
|
510
676
|
};
|
|
677
|
+
const formatElapsedEta = (task, now) => `${formatElapsedClock(task, now)}<${formatEtaClock(task, now) ?? "00:00"}`;
|
|
511
678
|
const formatDeterminateAmountParts = (task) => {
|
|
512
679
|
if (!isDeterminate$1(task)) return;
|
|
513
680
|
const totalText = `${task.units.total}`;
|
|
@@ -606,38 +773,17 @@ const renderAmount = (task, layout) => {
|
|
|
606
773
|
/* @__PURE__ */ jsx(Text, { children: ` ${processed}/${total}` })
|
|
607
774
|
] });
|
|
608
775
|
};
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
};
|
|
613
|
-
const createAmountColumn = (config) => {
|
|
614
|
-
const resolvedConfig = {
|
|
615
|
-
...defaultAmountColumnConfig,
|
|
616
|
-
...config
|
|
617
|
-
};
|
|
618
|
-
let amountLayout = emptyAmountLayout;
|
|
619
|
-
return {
|
|
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
|
-
};
|
|
631
|
-
},
|
|
632
|
-
noWrap: false,
|
|
633
|
-
sticky: resolvedConfig.sticky
|
|
634
|
-
};
|
|
635
|
-
};
|
|
776
|
+
const AmountCell = ({ task, layout }) => /* @__PURE__ */ jsx(Text, {
|
|
777
|
+
wrap: "truncate-end",
|
|
778
|
+
children: renderAmount(task, layout)
|
|
779
|
+
});
|
|
636
780
|
|
|
637
781
|
//#endregion
|
|
638
782
|
//#region src/renderer/columns/bar-column.tsx
|
|
639
|
-
const DEFAULT_BAR_WIDTH = 30;
|
|
640
783
|
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
784
|
+
const prepareBar = (rows) => {
|
|
785
|
+
return { hasDeterminateRows: rows.some((row) => row.derived.isDeterminate) };
|
|
786
|
+
};
|
|
641
787
|
const renderProgressBar = (task, width) => {
|
|
642
788
|
if (!isDeterminate$2(task)) return /* @__PURE__ */ jsx(Text, { children: ` `.repeat(Math.max(0, width)) });
|
|
643
789
|
const displayTotal = Math.max(task.units.total, task.units.succeeded + task.units.failed);
|
|
@@ -664,94 +810,21 @@ const renderProgressBar = (task, width) => {
|
|
|
664
810
|
]
|
|
665
811
|
});
|
|
666
812
|
};
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
};
|
|
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
|
-
};
|
|
690
|
-
},
|
|
691
|
-
noWrap: false,
|
|
692
|
-
sticky: resolvedConfig.sticky
|
|
693
|
-
};
|
|
694
|
-
};
|
|
695
|
-
|
|
696
|
-
//#endregion
|
|
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;
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
//#endregion
|
|
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);
|
|
813
|
+
const BarCell = ({ task, width }) => /* @__PURE__ */ jsx(Text, {
|
|
814
|
+
wrap: "truncate-end",
|
|
815
|
+
children: renderProgressBar(task, width ?? 0)
|
|
816
|
+
});
|
|
740
817
|
|
|
741
818
|
//#endregion
|
|
742
819
|
//#region src/renderer/columns/description-column.tsx
|
|
743
820
|
const MIN_TREE_DESCRIPTION_TEXT_WIDTH = 6;
|
|
744
|
-
const
|
|
745
|
-
minWidth: 1,
|
|
746
|
-
spinnerType: "dots",
|
|
747
|
-
sticky: true
|
|
748
|
-
};
|
|
821
|
+
const DEFAULT_SPINNER_TYPE = "dots";
|
|
749
822
|
const isDeterminate = (task) => task.units.total !== void 0;
|
|
750
823
|
const getSpinnerFrame = (tick, spinnerType) => {
|
|
751
824
|
const frames = cliSpinners[spinnerType].frames;
|
|
752
825
|
return frames[(tick % frames.length + frames.length) % frames.length] ?? frames[0] ?? "";
|
|
753
826
|
};
|
|
754
|
-
const getTaskIndicator = (task, tick, spinnerType =
|
|
827
|
+
const getTaskIndicator = (task, tick, spinnerType = DEFAULT_SPINNER_TYPE) => {
|
|
755
828
|
if (task.status === "running") return {
|
|
756
829
|
symbol: getSpinnerFrame(tick, spinnerType),
|
|
757
830
|
color: "yellow"
|
|
@@ -782,412 +855,279 @@ const getTaskIndicator = (task, tick, spinnerType = defaultDescriptionColumnConf
|
|
|
782
855
|
color: "green"
|
|
783
856
|
};
|
|
784
857
|
};
|
|
785
|
-
const
|
|
786
|
-
|
|
858
|
+
const prepareDescription = (rows) => ({
|
|
859
|
+
minTreeWidth: rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + MIN_TREE_DESCRIPTION_TEXT_WIDTH), MIN_TREE_DESCRIPTION_TEXT_WIDTH + 2),
|
|
860
|
+
preferredWidth: rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + row.derived.descriptionWidth), 2)
|
|
861
|
+
});
|
|
862
|
+
const TaskIndicatorGlyph = ({ task, tick, spinnerType = DEFAULT_SPINNER_TYPE }) => {
|
|
863
|
+
const indicator = getTaskIndicator(task, tick, spinnerType);
|
|
787
864
|
return /* @__PURE__ */ jsx(Text, {
|
|
788
865
|
color: indicator.color,
|
|
789
866
|
children: indicator.symbol
|
|
790
867
|
});
|
|
791
868
|
};
|
|
792
|
-
const
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
/* @__PURE__ */ jsx(TaskIndicatorGlyph, {
|
|
818
|
-
task: row.task,
|
|
819
|
-
spinnerType: resolvedConfig.spinnerType
|
|
820
|
-
}),
|
|
821
|
-
` ${row.task.description}`
|
|
822
|
-
]
|
|
823
|
-
});
|
|
824
|
-
};
|
|
825
|
-
return {
|
|
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
|
|
839
|
-
};
|
|
840
|
-
};
|
|
841
|
-
|
|
842
|
-
//#endregion
|
|
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;
|
|
869
|
+
const DescriptionCell = ({ cell, width, minTreeWidth, spinnerTick }) => {
|
|
870
|
+
const showTree = width === void 0 || width >= minTreeWidth;
|
|
871
|
+
const treePrefix = showTree ? cell.derived.treePrefix : "";
|
|
872
|
+
if (!showTree && width !== void 0 && width <= 1) return /* @__PURE__ */ jsx(TaskIndicatorGlyph, {
|
|
873
|
+
task: cell.task,
|
|
874
|
+
tick: spinnerTick
|
|
875
|
+
});
|
|
876
|
+
if (!showTree && width !== void 0 && width === 2) return /* @__PURE__ */ jsxs(Text, {
|
|
877
|
+
wrap: "truncate-end",
|
|
878
|
+
children: [/* @__PURE__ */ jsx(TaskIndicatorGlyph, {
|
|
879
|
+
task: cell.task,
|
|
880
|
+
tick: spinnerTick
|
|
881
|
+
}), "…"]
|
|
882
|
+
});
|
|
883
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
884
|
+
wrap: "truncate-end",
|
|
885
|
+
children: [
|
|
886
|
+
treePrefix,
|
|
887
|
+
/* @__PURE__ */ jsx(TaskIndicatorGlyph, {
|
|
888
|
+
task: cell.task,
|
|
889
|
+
tick: spinnerTick
|
|
890
|
+
}),
|
|
891
|
+
` ${cell.task.description}`
|
|
892
|
+
]
|
|
893
|
+
});
|
|
857
894
|
};
|
|
858
895
|
|
|
859
896
|
//#endregion
|
|
860
|
-
//#region src/renderer/
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
return /* @__PURE__ */ jsx(NowContext.Provider, {
|
|
867
|
-
value: now,
|
|
868
|
-
children
|
|
897
|
+
//#region src/renderer/columns/elapsed-eta-column.tsx
|
|
898
|
+
const ElapsedEtaCell = ({ task, now }) => {
|
|
899
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
900
|
+
wrap: "truncate-end",
|
|
901
|
+
color: "gray",
|
|
902
|
+
children: formatElapsedEta(task, now)
|
|
869
903
|
});
|
|
870
904
|
};
|
|
871
|
-
const useNow = () => useContext(NowContext);
|
|
872
905
|
|
|
873
906
|
//#endregion
|
|
874
907
|
//#region src/renderer/columns/elapsed-column.tsx
|
|
875
|
-
const
|
|
876
|
-
minWidth: 2,
|
|
877
|
-
justify: "right",
|
|
878
|
-
sticky: true
|
|
879
|
-
};
|
|
880
|
-
const ElapsedText = ({ task, width }) => {
|
|
908
|
+
const ElapsedCell = ({ task, now }) => {
|
|
881
909
|
return /* @__PURE__ */ jsx(Text, {
|
|
882
910
|
wrap: "truncate-end",
|
|
883
911
|
color: "gray",
|
|
884
|
-
children: formatElapsed(task,
|
|
912
|
+
children: formatElapsed(task, now)
|
|
885
913
|
});
|
|
886
914
|
};
|
|
887
|
-
const createElapsedColumn = (config) => {
|
|
888
|
-
const resolvedConfig = {
|
|
889
|
-
...defaultElapsedColumnConfig,
|
|
890
|
-
...config
|
|
891
|
-
};
|
|
892
|
-
const Component = ({ row, width }) => /* @__PURE__ */ jsx(ElapsedText, {
|
|
893
|
-
task: row.task,
|
|
894
|
-
width
|
|
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
|
-
};
|
|
911
|
-
};
|
|
912
915
|
|
|
913
916
|
//#endregion
|
|
914
917
|
//#region src/renderer/columns/eta-column.tsx
|
|
915
|
-
const
|
|
916
|
-
const
|
|
917
|
-
if (
|
|
918
|
-
return
|
|
918
|
+
const EtaCell = ({ task, now }) => {
|
|
919
|
+
const eta = formatEta(task, now);
|
|
920
|
+
if (eta === "") return null;
|
|
921
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
922
|
+
wrap: "truncate-end",
|
|
923
|
+
color: "gray",
|
|
924
|
+
children: `ETA: ${eta}`
|
|
925
|
+
});
|
|
919
926
|
};
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
927
|
+
|
|
928
|
+
//#endregion
|
|
929
|
+
//#region src/columns.tsx
|
|
930
|
+
var columns_exports = /* @__PURE__ */ __exportAll({
|
|
931
|
+
amount: () => amount,
|
|
932
|
+
bar: () => bar,
|
|
933
|
+
defaults: () => defaults,
|
|
934
|
+
description: () => description,
|
|
935
|
+
elapsed: () => elapsed,
|
|
936
|
+
elapsedEta: () => elapsedEta,
|
|
937
|
+
eta: () => eta,
|
|
938
|
+
resolveColumnSizeValue: () => resolveColumnSizeValue,
|
|
939
|
+
spacer: () => spacer
|
|
940
|
+
});
|
|
941
|
+
const DEFAULT_BAR_SIZE = 30;
|
|
942
|
+
const resolveBarSize = (size) => {
|
|
943
|
+
if (size === "fullwidth") return size;
|
|
944
|
+
if (typeof size !== "number" || !Number.isFinite(size)) return DEFAULT_BAR_SIZE;
|
|
945
|
+
return Math.max(1, Math.floor(size));
|
|
923
946
|
};
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
947
|
+
const spacer = ({ flexGrow, flexShrink, flexBasis, minWidth } = {}) => ({
|
|
948
|
+
render: () => null,
|
|
949
|
+
flexGrow,
|
|
950
|
+
flexShrink,
|
|
951
|
+
flexBasis,
|
|
952
|
+
minWidth
|
|
953
|
+
});
|
|
954
|
+
const description = () => ({
|
|
955
|
+
prepare: prepareDescription,
|
|
956
|
+
flexShrink: 1,
|
|
957
|
+
flexBasis: (prepared) => prepared.preferredWidth,
|
|
958
|
+
minWidth: 1,
|
|
959
|
+
render: (cell, ctx) => /* @__PURE__ */ jsx(DescriptionCell, {
|
|
960
|
+
cell,
|
|
961
|
+
width: ctx.width,
|
|
962
|
+
minTreeWidth: ctx.prepared.minTreeWidth,
|
|
963
|
+
spinnerTick: ctx.spinnerTick
|
|
964
|
+
})
|
|
965
|
+
});
|
|
966
|
+
const bar = ({ size } = {}) => {
|
|
967
|
+
const resolvedSize = resolveBarSize(size);
|
|
939
968
|
return {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
};
|
|
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, {
|
|
955
|
-
wrap: "truncate-end",
|
|
956
|
-
color: "gray",
|
|
957
|
-
children: rendered.text
|
|
969
|
+
prepare: prepareBar,
|
|
970
|
+
flexGrow: (prepared) => prepared.hasDeterminateRows && resolvedSize === "fullwidth" ? 1 : 0,
|
|
971
|
+
flexShrink: (prepared) => prepared.hasDeterminateRows ? 1 : 0,
|
|
972
|
+
flexBasis: (prepared) => prepared.hasDeterminateRows ? resolvedSize === "fullwidth" ? DEFAULT_BAR_SIZE : resolvedSize : 0,
|
|
973
|
+
minWidth: (prepared) => prepared.hasDeterminateRows ? resolvedSize === "fullwidth" ? 4 : resolvedSize : 0,
|
|
974
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(BarCell, {
|
|
975
|
+
task,
|
|
976
|
+
width: ctx.width
|
|
958
977
|
})
|
|
959
|
-
});
|
|
960
|
-
};
|
|
961
|
-
const createEtaColumn = (config) => {
|
|
962
|
-
const resolvedConfig = {
|
|
963
|
-
...defaultEtaColumnConfig,
|
|
964
|
-
...config
|
|
965
|
-
};
|
|
966
|
-
const Component = ({ row, width }) => /* @__PURE__ */ jsx(EtaText, {
|
|
967
|
-
row,
|
|
968
|
-
width,
|
|
969
|
-
justify: resolvedConfig.justify
|
|
970
|
-
});
|
|
971
|
-
return {
|
|
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
|
-
};
|
|
984
|
-
},
|
|
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
|
|
993
978
|
};
|
|
994
979
|
};
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
980
|
+
const amount = () => ({
|
|
981
|
+
prepare: measureAmountLayout,
|
|
982
|
+
align: "right",
|
|
983
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(AmountCell, {
|
|
984
|
+
task,
|
|
985
|
+
layout: ctx.prepared
|
|
986
|
+
})
|
|
987
|
+
});
|
|
988
|
+
const elapsed = () => ({
|
|
989
|
+
align: "right",
|
|
990
|
+
flexShrink: 0,
|
|
991
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(ElapsedCell, {
|
|
992
|
+
task,
|
|
993
|
+
now: ctx.now
|
|
994
|
+
})
|
|
995
|
+
});
|
|
996
|
+
const elapsedEta = () => ({
|
|
997
|
+
align: "right",
|
|
998
|
+
flexShrink: 0,
|
|
999
|
+
minWidth: 11,
|
|
1000
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(ElapsedEtaCell, {
|
|
1001
|
+
task,
|
|
1002
|
+
now: ctx.now
|
|
1003
|
+
})
|
|
1004
|
+
});
|
|
1005
|
+
const eta = () => ({
|
|
1006
|
+
align: "right",
|
|
1007
|
+
flexShrink: 0,
|
|
1008
|
+
minWidth: 8,
|
|
1009
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(EtaCell, {
|
|
1010
|
+
task,
|
|
1011
|
+
now: ctx.now
|
|
1012
|
+
})
|
|
1013
|
+
});
|
|
1014
|
+
const defaults = () => [
|
|
1015
|
+
description(),
|
|
1016
|
+
bar(),
|
|
1017
|
+
amount(),
|
|
1018
|
+
elapsedEta()
|
|
1004
1019
|
];
|
|
1020
|
+
const resolveColumnSizeValue = (value, prepared) => {
|
|
1021
|
+
if (typeof value === "function") return value(prepared);
|
|
1022
|
+
return value;
|
|
1023
|
+
};
|
|
1005
1024
|
|
|
1006
1025
|
//#endregion
|
|
1007
|
-
//#region src/renderer/
|
|
1008
|
-
const
|
|
1009
|
-
const
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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 {
|
|
1028
|
-
definition,
|
|
1029
|
-
minWidth,
|
|
1030
|
-
maxWidth,
|
|
1031
|
-
preferredWidth,
|
|
1032
|
-
sticky: definition.sticky === true,
|
|
1033
|
-
stickyLimit
|
|
1034
|
-
};
|
|
1035
|
-
};
|
|
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);
|
|
1044
|
-
};
|
|
1045
|
-
const nextDistinctWidth = (entries, widest) => entries.find((entry) => entry.width < widest)?.width;
|
|
1046
|
-
const shrinkWidestFirst = (widths, columns, targetWidth, canShrink) => {
|
|
1047
|
-
while (totalVisibleWidth(widths) > targetWidth) {
|
|
1048
|
-
const shrinkable = widths.map((width, index) => ({
|
|
1049
|
-
index,
|
|
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);
|
|
1053
|
-
if (shrinkable.length === 0) break;
|
|
1054
|
-
const overflow = totalVisibleWidth(widths) - targetWidth;
|
|
1055
|
-
const widest = shrinkable[0].width;
|
|
1056
|
-
const cohort = shrinkable.filter((entry) => entry.width === widest);
|
|
1057
|
-
const maxUniformDrop = widest - Math.max(nextDistinctWidth(shrinkable, widest) ?? 0, ...cohort.map((entry) => entry.minWidth));
|
|
1058
|
-
const uniformDrop = Math.min(maxUniformDrop, Math.floor(overflow / cohort.length));
|
|
1059
|
-
if (uniformDrop > 0) {
|
|
1060
|
-
for (const { index } of cohort) widths[index] = Math.max(columns[index].minWidth, widths[index] - uniformDrop);
|
|
1061
|
-
continue;
|
|
1062
|
-
}
|
|
1063
|
-
let changed = false;
|
|
1064
|
-
for (const { index, minWidth } of cohort) {
|
|
1065
|
-
if (totalVisibleWidth(widths) <= targetWidth) break;
|
|
1066
|
-
if (widths[index] <= minWidth) continue;
|
|
1067
|
-
widths[index] = widths[index] - 1;
|
|
1068
|
-
changed = true;
|
|
1069
|
-
}
|
|
1070
|
-
if (!changed) break;
|
|
1071
|
-
}
|
|
1072
|
-
return widths;
|
|
1073
|
-
};
|
|
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);
|
|
1026
|
+
//#region src/renderer/column-resolver.ts
|
|
1027
|
+
const NO_PREPARE = Symbol("no-prepare");
|
|
1028
|
+
const getColumnsForRow = (row, columns) => columns.get(row.task.id) ?? defaults();
|
|
1029
|
+
const toCellInfo = (row) => row;
|
|
1030
|
+
const maxDefined = (values) => values.reduce((maxValue, value) => value === void 0 ? maxValue : Math.max(maxValue ?? Number.NEGATIVE_INFINITY, value), void 0);
|
|
1031
|
+
const resolvePreparedGroups = (defs, cellInfos) => {
|
|
1032
|
+
const groupedRows = /* @__PURE__ */ new Map();
|
|
1033
|
+
defs.forEach((def, rowIndex) => {
|
|
1034
|
+
if (!def?.prepare) return;
|
|
1035
|
+
const key = def.prepare;
|
|
1036
|
+
const rows = groupedRows.get(key);
|
|
1037
|
+
const cell = cellInfos[rowIndex];
|
|
1038
|
+
if (!cell) return;
|
|
1039
|
+
if (rows) rows.push(cell);
|
|
1040
|
+
else groupedRows.set(key, [cell]);
|
|
1086
1041
|
});
|
|
1087
|
-
|
|
1088
|
-
|
|
1042
|
+
const groups = [{
|
|
1043
|
+
key: NO_PREPARE,
|
|
1044
|
+
prepared: void 0
|
|
1045
|
+
}];
|
|
1046
|
+
for (const [key, rows] of groupedRows) groups.push({
|
|
1047
|
+
key,
|
|
1048
|
+
prepared: key(rows)
|
|
1089
1049
|
});
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1050
|
+
return groups;
|
|
1051
|
+
};
|
|
1052
|
+
const getPreparedFor = (def, groups) => {
|
|
1053
|
+
if (!def?.prepare) return;
|
|
1054
|
+
return groups.find((group) => group.key === def.prepare)?.prepared;
|
|
1055
|
+
};
|
|
1056
|
+
const resolveColumns = (rows, columns) => {
|
|
1057
|
+
const columnsByRow = rows.map((row) => getColumnsForRow(row, columns));
|
|
1058
|
+
const cellInfos = rows.map(toCellInfo);
|
|
1059
|
+
const maxColumnCount = columnsByRow.reduce((max, defs) => Math.max(max, defs.length), 0);
|
|
1060
|
+
return Array.from({ length: maxColumnCount }, (_, index) => {
|
|
1061
|
+
const defsAtIndex = columnsByRow.map((defs) => defs[index]);
|
|
1062
|
+
const preparedGroups = resolvePreparedGroups(defsAtIndex, cellInfos);
|
|
1063
|
+
const entries = defsAtIndex.map((column) => column === void 0 ? void 0 : {
|
|
1064
|
+
column,
|
|
1065
|
+
prepared: getPreparedFor(column, preparedGroups)
|
|
1098
1066
|
});
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1067
|
+
return {
|
|
1068
|
+
index,
|
|
1069
|
+
rows,
|
|
1070
|
+
entries,
|
|
1071
|
+
flexGrow: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.flexGrow, entry.prepared))),
|
|
1072
|
+
flexShrink: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.flexShrink, entry.prepared))),
|
|
1073
|
+
flexBasis: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.flexBasis, entry.prepared))),
|
|
1074
|
+
minWidth: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.minWidth, entry.prepared)))
|
|
1075
|
+
};
|
|
1104
1076
|
});
|
|
1105
|
-
return {
|
|
1106
|
-
widths,
|
|
1107
|
-
nextStickyWidths
|
|
1108
|
-
};
|
|
1109
|
-
};
|
|
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
|
-
};
|
|
1129
1077
|
};
|
|
1130
1078
|
|
|
1131
1079
|
//#endregion
|
|
1132
1080
|
//#region src/renderer/public-api.tsx
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
renderItem: ({ item: row, index: rowIndex }) => /* @__PURE__ */ jsx(ProgressRow, {
|
|
1184
|
-
row,
|
|
1185
|
-
rowIndex,
|
|
1186
|
-
terminalColumns,
|
|
1187
|
-
columns: layout.columns
|
|
1188
|
-
})
|
|
1189
|
-
});
|
|
1190
|
-
};
|
|
1081
|
+
const justifyContentForAlign = (align) => {
|
|
1082
|
+
if (align === "right") return "flex-end";
|
|
1083
|
+
if (align === "center") return "center";
|
|
1084
|
+
return "flex-start";
|
|
1085
|
+
};
|
|
1086
|
+
const renderNode = (node) => {
|
|
1087
|
+
if (typeof node === "string" || typeof node === "number") return /* @__PURE__ */ jsx(Text, {
|
|
1088
|
+
wrap: "truncate-end",
|
|
1089
|
+
children: node
|
|
1090
|
+
});
|
|
1091
|
+
return node;
|
|
1092
|
+
};
|
|
1093
|
+
const ColumnPosition = ({ position }) => {
|
|
1094
|
+
const ref = useRef(null);
|
|
1095
|
+
const { width, hasMeasured } = useBoxMetrics(ref);
|
|
1096
|
+
const now = useNow();
|
|
1097
|
+
const spinnerTick = useSpinnerTick();
|
|
1098
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
1099
|
+
ref,
|
|
1100
|
+
flexDirection: "column",
|
|
1101
|
+
flexGrow: position.flexGrow,
|
|
1102
|
+
flexShrink: position.flexShrink ?? 0,
|
|
1103
|
+
flexBasis: position.flexBasis,
|
|
1104
|
+
minWidth: position.minWidth,
|
|
1105
|
+
children: position.rows.map((row, rowIndex) => {
|
|
1106
|
+
const entry = position.entries[rowIndex];
|
|
1107
|
+
const column = entry?.column;
|
|
1108
|
+
const cell = row;
|
|
1109
|
+
const output = column?.render(cell, {
|
|
1110
|
+
width: hasMeasured ? width : void 0,
|
|
1111
|
+
now,
|
|
1112
|
+
spinnerTick,
|
|
1113
|
+
prepared: entry?.prepared
|
|
1114
|
+
}) ?? null;
|
|
1115
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
1116
|
+
height: 1,
|
|
1117
|
+
justifyContent: justifyContentForAlign(column?.align),
|
|
1118
|
+
children: renderNode(output)
|
|
1119
|
+
}, row.task.id);
|
|
1120
|
+
})
|
|
1121
|
+
});
|
|
1122
|
+
};
|
|
1123
|
+
const ProgressRenderer = ({ rows, columns }) => {
|
|
1124
|
+
if (rows.length === 0) return null;
|
|
1125
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
1126
|
+
flexDirection: "row",
|
|
1127
|
+
columnGap: 1,
|
|
1128
|
+
overflow: "hidden",
|
|
1129
|
+
children: resolveColumns(rows, columns).map((position) => /* @__PURE__ */ jsx(ColumnPosition, { position }, position.index))
|
|
1130
|
+
});
|
|
1191
1131
|
};
|
|
1192
1132
|
|
|
1193
1133
|
//#endregion
|
|
@@ -1292,31 +1232,22 @@ const useProgressRenderView = (store) => {
|
|
|
1292
1232
|
//#endregion
|
|
1293
1233
|
//#region src/renderer/renderer-service.tsx
|
|
1294
1234
|
const MAX_FPS = 24;
|
|
1295
|
-
const
|
|
1296
|
-
const
|
|
1297
|
-
return
|
|
1298
|
-
|
|
1299
|
-
|
|
1235
|
+
const ProgressRoot = ({ store }) => {
|
|
1236
|
+
const { renderSnapshot, hasRunningTasks, publication } = useProgressRenderView(store);
|
|
1237
|
+
return /* @__PURE__ */ jsx(SpinnerProvider, {
|
|
1238
|
+
active: hasRunningTasks,
|
|
1239
|
+
children: /* @__PURE__ */ jsx(NowProvider, {
|
|
1300
1240
|
active: hasRunningTasks,
|
|
1301
|
-
children: /* @__PURE__ */ jsx(
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
rows: renderSnapshot.rows,
|
|
1305
|
-
terminalColumns: getTerminalColumns(),
|
|
1306
|
-
terminalRows: getTerminalRows()
|
|
1307
|
-
})
|
|
1241
|
+
children: /* @__PURE__ */ jsx(ProgressRenderer, {
|
|
1242
|
+
rows: renderSnapshot.rows,
|
|
1243
|
+
columns: publication.snapshot.columns
|
|
1308
1244
|
})
|
|
1309
|
-
})
|
|
1310
|
-
};
|
|
1245
|
+
})
|
|
1246
|
+
});
|
|
1311
1247
|
};
|
|
1312
|
-
const makeRendererv2InkRendererService = (
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
-
});
|
|
1248
|
+
const makeRendererv2InkRendererService = () => {
|
|
1249
|
+
return { run: (store, stdio) => {
|
|
1250
|
+
const proot = /* @__PURE__ */ jsx(ProgressRoot, { store });
|
|
1320
1251
|
return Effect.sync(() => render(proot, {
|
|
1321
1252
|
stdout: stdio.stdout,
|
|
1322
1253
|
stderr: stdio.stderr,
|
|
@@ -1337,7 +1268,7 @@ const makeRendererv2InkRendererService = (columns) => {
|
|
|
1337
1268
|
//#endregion
|
|
1338
1269
|
//#region src/services/ink-renderer.tsx
|
|
1339
1270
|
var InkRenderer = class InkRenderer extends Context.Tag("stromseng.dev/effective-progress/InkRenderer")() {
|
|
1340
|
-
static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeRendererv2InkRendererService(
|
|
1271
|
+
static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeRendererv2InkRendererService()));
|
|
1341
1272
|
};
|
|
1342
1273
|
|
|
1343
1274
|
//#endregion
|
|
@@ -1352,16 +1283,16 @@ var ProgressStdio = class ProgressStdio extends Context.Tag("stromseng.dev/effec
|
|
|
1352
1283
|
|
|
1353
1284
|
//#endregion
|
|
1354
1285
|
//#region src/services/progress.ts
|
|
1286
|
+
/** Builds the scoped implementation used by `ProgressService.task(...)` without auto-providing services. */
|
|
1355
1287
|
const makeProgressService = Effect.gen(function* () {
|
|
1356
1288
|
const stdio = yield* ProgressStdio;
|
|
1357
1289
|
const inkRenderer = yield* InkRenderer;
|
|
1358
1290
|
const outerConsole = yield* Effect.console;
|
|
1359
|
-
const isTTY = Boolean(stdio.stderr.isTTY);
|
|
1360
1291
|
const store = makeProgressRenderStore();
|
|
1361
1292
|
const currentParentRef = yield* FiberRef.make(Option.none());
|
|
1362
1293
|
const scope = yield* Effect.scope;
|
|
1363
1294
|
const log = (...args) => args.length === 0 ? Effect.void : outerConsole.log(...args);
|
|
1364
|
-
yield* Effect.forkIn(inkRenderer.run(store, stdio
|
|
1295
|
+
yield* Effect.forkIn(inkRenderer.run(store, stdio), scope);
|
|
1365
1296
|
yield* Effect.sleep("0 millis");
|
|
1366
1297
|
const addTask = (options) => Effect.gen(function* () {
|
|
1367
1298
|
const resolvedParentId = options.parentId === void 0 ? yield* FiberRef.get(currentParentRef) : Option.some(options.parentId);
|
|
@@ -1377,7 +1308,27 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
1377
1308
|
const failTask = store.failTask;
|
|
1378
1309
|
const getTask = store.getTask;
|
|
1379
1310
|
const listTasks = store.listTasks;
|
|
1380
|
-
const
|
|
1311
|
+
const setMetadata = store.setMetadata;
|
|
1312
|
+
const getMetadata = store.getMetadata;
|
|
1313
|
+
const makeTaskHandle = (taskId) => ({
|
|
1314
|
+
id: taskId,
|
|
1315
|
+
getMetadata: getMetadata(taskId),
|
|
1316
|
+
setMetadata: (metadata) => setMetadata(taskId, metadata),
|
|
1317
|
+
updateMetadata: (f) => Effect.flatMap(getMetadata(taskId), (current) => setMetadata(taskId, f(current))),
|
|
1318
|
+
incrementSucceeded: (amount) => incrementSucceeded(taskId, amount),
|
|
1319
|
+
incrementFailed: (amount) => incrementFailed(taskId, amount),
|
|
1320
|
+
update: (options) => updateTask(taskId, options),
|
|
1321
|
+
complete: completeTask(taskId),
|
|
1322
|
+
fail: failTask(taskId),
|
|
1323
|
+
getSnapshot: getTask(taskId).pipe(Effect.map(Option.getOrThrow))
|
|
1324
|
+
});
|
|
1325
|
+
const autoFinalizeIfRunning = (taskId, exit) => Effect.gen(function* () {
|
|
1326
|
+
const task = yield* getTask(taskId);
|
|
1327
|
+
if (Option.isNone(task) || task.value.status !== "running") return;
|
|
1328
|
+
if (Exit.isSuccess(exit)) yield* completeTask(taskId);
|
|
1329
|
+
else yield* failTask(taskId);
|
|
1330
|
+
});
|
|
1331
|
+
const scopedTask = dual(2, (effect, options) => Effect.gen(function* () {
|
|
1381
1332
|
const inheritedParentId = yield* FiberRef.get(currentParentRef);
|
|
1382
1333
|
const resolvedParentId = options.parentId === void 0 ? inheritedParentId : Option.some(options.parentId);
|
|
1383
1334
|
const taskId = yield* addTask({
|
|
@@ -1397,17 +1348,29 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
1397
1348
|
log,
|
|
1398
1349
|
getTask,
|
|
1399
1350
|
listTasks,
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1351
|
+
setMetadata,
|
|
1352
|
+
getMetadata,
|
|
1353
|
+
task: dual(2, (effectOrCallback, options) => {
|
|
1354
|
+
if (typeof effectOrCallback === "function") return scopedTask(Effect.gen(function* () {
|
|
1355
|
+
const taskId = yield* Task;
|
|
1356
|
+
const handle = makeTaskHandle(taskId);
|
|
1357
|
+
const exit = yield* Effect.exit(effectOrCallback(handle));
|
|
1358
|
+
yield* autoFinalizeIfRunning(taskId, exit);
|
|
1359
|
+
return yield* Exit.match(exit, {
|
|
1360
|
+
onFailure: Effect.failCause,
|
|
1361
|
+
onSuccess: Effect.succeed
|
|
1362
|
+
});
|
|
1363
|
+
}), options);
|
|
1364
|
+
return scopedTask(Effect.gen(function* () {
|
|
1365
|
+
const taskId = yield* Task;
|
|
1366
|
+
const exit = yield* Effect.exit(effectOrCallback);
|
|
1367
|
+
yield* autoFinalizeIfRunning(taskId, exit);
|
|
1368
|
+
return yield* Exit.match(exit, {
|
|
1369
|
+
onFailure: Effect.failCause,
|
|
1370
|
+
onSuccess: Effect.succeed
|
|
1371
|
+
});
|
|
1372
|
+
}), options);
|
|
1373
|
+
})
|
|
1411
1374
|
};
|
|
1412
1375
|
return Progress.of(service);
|
|
1413
1376
|
});
|
|
@@ -1439,9 +1402,17 @@ const provideProgress = (effect) => Effect.gen(function* () {
|
|
|
1439
1402
|
if (Option.isSome(existing)) return yield* Effect.provideService(effect, Progress, existing.value);
|
|
1440
1403
|
return yield* Effect.scoped(effect.pipe(Effect.provide(Progress.Default)));
|
|
1441
1404
|
});
|
|
1442
|
-
|
|
1405
|
+
/**
|
|
1406
|
+
* Runs an effect inside a task, creating and providing a `Progress` service automatically when one
|
|
1407
|
+
* is not already present in the environment.
|
|
1408
|
+
*
|
|
1409
|
+
* The effect form tracks success and failure from the effect exit. The callback form exposes a
|
|
1410
|
+
* typed `TaskHandle` for task-local updates, typed metadata, and explicit completion or failure,
|
|
1411
|
+
* and otherwise auto-finalizes from the callback exit if the task is still `running`.
|
|
1412
|
+
*/
|
|
1413
|
+
const task = dual(2, (effectOrCallback, options) => {
|
|
1443
1414
|
return provideProgress(Effect.gen(function* () {
|
|
1444
|
-
return yield* (yield* Progress).
|
|
1415
|
+
return yield* (yield* Progress).task(effectOrCallback, options);
|
|
1445
1416
|
}));
|
|
1446
1417
|
});
|
|
1447
1418
|
const wrapEffects = (effects, tap) => Array.isArray(effects) ? effects.map(tap) : Object.fromEntries(Object.entries(effects).map(([k, effect]) => [k, tap(effect)]));
|
|
@@ -1464,10 +1435,14 @@ const isTaskFullyProcessed = (progress, taskId) => Effect.gen(function* () {
|
|
|
1464
1435
|
const { processed, total } = taskOption.value.units;
|
|
1465
1436
|
return total !== void 0 && processed >= total;
|
|
1466
1437
|
});
|
|
1438
|
+
/**
|
|
1439
|
+
* Runs multiple effects under a single parent task and keeps the task counters in sync with the
|
|
1440
|
+
* child effect outcomes.
|
|
1441
|
+
*/
|
|
1467
1442
|
const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* () {
|
|
1468
1443
|
const progress = yield* Progress;
|
|
1469
|
-
return yield* progress.
|
|
1470
|
-
const taskId =
|
|
1444
|
+
return yield* progress.task((handle) => Effect.gen(function* () {
|
|
1445
|
+
const taskId = handle.id;
|
|
1471
1446
|
const exit = yield* Effect.exit(Effect.all(wrapEffects(effects, (effect) => wrapTrackedEffect(progress, taskId, effect)), {
|
|
1472
1447
|
concurrency: options.concurrency,
|
|
1473
1448
|
batching: options.batching,
|
|
@@ -1490,10 +1465,13 @@ const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* (
|
|
|
1490
1465
|
countDisplay: allCountDisplay(options.mode)
|
|
1491
1466
|
});
|
|
1492
1467
|
})));
|
|
1468
|
+
/**
|
|
1469
|
+
* Runs `Effect.forEach` under a single parent task and advances the task counters as items finish.
|
|
1470
|
+
*/
|
|
1493
1471
|
const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(function* () {
|
|
1494
1472
|
const progress = yield* Progress;
|
|
1495
|
-
return yield* progress.
|
|
1496
|
-
const taskId =
|
|
1473
|
+
return yield* progress.task((handle) => Effect.gen(function* () {
|
|
1474
|
+
const taskId = handle.id;
|
|
1497
1475
|
const exit = yield* Effect.exit(Effect.forEach(iterable, (item, index) => wrapTrackedEffect(progress, taskId, f(item, index)), {
|
|
1498
1476
|
concurrency: options.concurrency,
|
|
1499
1477
|
batching: options.batching,
|
|
@@ -1515,4 +1493,4 @@ const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(fun
|
|
|
1515
1493
|
})));
|
|
1516
1494
|
|
|
1517
1495
|
//#endregion
|
|
1518
|
-
export { Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskRemovedEvent, TaskSnapshot, TaskSnapshotSchema, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|
|
1496
|
+
export { columns_exports as Columns, Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskProgressSampleSchema, TaskRemovedEvent, TaskSnapshot, TaskSnapshotSchema, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|