effective-progress 0.9.0 → 0.11.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 +118 -11
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/index.d.mts +166 -18
- package/dist/index.mjs +824 -926
- package/package.json +9 -2
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
|
|
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 { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
-
import stringWidth from "fast-string-width";
|
|
4
|
+
import { Box, Text, render, useBoxMetrics } from "ink";
|
|
5
|
+
import { createContext, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import cliSpinners from "cli-spinners";
|
|
8
|
+
import stringWidth from "fast-string-width";
|
|
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,
|
|
@@ -25,8 +27,10 @@ var TaskSnapshot = class extends Schema.TaggedClass()("TaskSnapshot", {
|
|
|
25
27
|
transient: Schema.Boolean,
|
|
26
28
|
units: TaskUnitsSchema,
|
|
27
29
|
startedAt: Schema.Number,
|
|
28
|
-
completedAt: Schema.NullOr(Schema.Number)
|
|
29
|
-
|
|
30
|
+
completedAt: Schema.NullOr(Schema.Number),
|
|
31
|
+
metadata: Schema.Unknown
|
|
32
|
+
});
|
|
33
|
+
const TaskSnapshot = (snapshot) => snapshot;
|
|
30
34
|
var Task = class extends Context.Tag("stromseng.dev/effective-progress/Task")() {};
|
|
31
35
|
var TaskAddedEvent = class extends Schema.TaggedClass()("TaskAdded", {
|
|
32
36
|
taskId: TaskIdSchema,
|
|
@@ -58,60 +62,7 @@ const ProgressTaskEventSchema = Schema.Union(TaskAddedEvent, TaskUpdatedEvent, T
|
|
|
58
62
|
const decodeProgressTaskEvent = Schema.decodeUnknownSync(ProgressTaskEventSchema);
|
|
59
63
|
|
|
60
64
|
//#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
|
|
65
|
+
//#region src/renderer/store.ts
|
|
115
66
|
const hasExplicitTotal = (options) => Object.prototype.hasOwnProperty.call(options, "total");
|
|
116
67
|
const sanitizeTotalOnAdd = (total) => {
|
|
117
68
|
if (total === void 0) return;
|
|
@@ -142,7 +93,7 @@ const updatedSnapshot = (snapshot, options) => {
|
|
|
142
93
|
failed: options.failed ?? currentUnits.failed,
|
|
143
94
|
total: hasExplicitTotal(options) ? sanitizeTotalOnUpdate(options.total) : currentUnits.total
|
|
144
95
|
});
|
|
145
|
-
return
|
|
96
|
+
return TaskSnapshot({
|
|
146
97
|
id: snapshot.id,
|
|
147
98
|
parentId: snapshot.parentId,
|
|
148
99
|
description: options.description ?? snapshot.description,
|
|
@@ -151,10 +102,11 @@ const updatedSnapshot = (snapshot, options) => {
|
|
|
151
102
|
transient: options.transient ?? snapshot.transient,
|
|
152
103
|
units,
|
|
153
104
|
startedAt: snapshot.startedAt,
|
|
154
|
-
completedAt: snapshot.completedAt
|
|
105
|
+
completedAt: snapshot.completedAt,
|
|
106
|
+
metadata: snapshot.metadata
|
|
155
107
|
});
|
|
156
108
|
};
|
|
157
|
-
const withTransient = (snapshot, transient) =>
|
|
109
|
+
const withTransient = (snapshot, transient) => TaskSnapshot({
|
|
158
110
|
id: snapshot.id,
|
|
159
111
|
parentId: snapshot.parentId,
|
|
160
112
|
description: snapshot.description,
|
|
@@ -163,7 +115,8 @@ const withTransient = (snapshot, transient) => new TaskSnapshot({
|
|
|
163
115
|
transient,
|
|
164
116
|
units: snapshot.units,
|
|
165
117
|
startedAt: snapshot.startedAt,
|
|
166
|
-
completedAt: snapshot.completedAt
|
|
118
|
+
completedAt: snapshot.completedAt,
|
|
119
|
+
metadata: snapshot.metadata
|
|
167
120
|
});
|
|
168
121
|
const findInsertionIndex = (renderOrder, parentId) => {
|
|
169
122
|
if (parentId === null) return {
|
|
@@ -193,14 +146,27 @@ const removeFromRenderOrder = (renderOrder, taskId) => {
|
|
|
193
146
|
next.splice(idx, end - idx);
|
|
194
147
|
return next;
|
|
195
148
|
};
|
|
196
|
-
const
|
|
149
|
+
const subtreeTaskIds = (renderOrder, taskId) => {
|
|
150
|
+
const idx = renderOrder.findIndex((row) => row.id === taskId);
|
|
151
|
+
if (idx === -1) return [];
|
|
152
|
+
const taskDepth = renderOrder[idx].depth;
|
|
153
|
+
let end = idx + 1;
|
|
154
|
+
while (end < renderOrder.length && renderOrder[end].depth > taskDepth) end++;
|
|
155
|
+
return renderOrder.slice(idx, end).map((row) => row.id);
|
|
156
|
+
};
|
|
157
|
+
const SNAPSHOT_PUBLISH_INTERVAL_MILLIS = 100;
|
|
197
158
|
const makeProgressRenderStore = () => {
|
|
198
159
|
let nextTaskId = 0;
|
|
199
160
|
let state = {
|
|
200
161
|
tasks: /* @__PURE__ */ new Map(),
|
|
201
|
-
renderOrder: []
|
|
162
|
+
renderOrder: [],
|
|
163
|
+
columns: /* @__PURE__ */ new Map()
|
|
164
|
+
};
|
|
165
|
+
let pendingEvents = [];
|
|
166
|
+
let publishedPublication = {
|
|
167
|
+
snapshot: state,
|
|
168
|
+
events: []
|
|
202
169
|
};
|
|
203
|
-
let publishedSnapshot = toRenderSnapshot(state);
|
|
204
170
|
let hasPendingPublish = false;
|
|
205
171
|
let lastPublishAt = 0;
|
|
206
172
|
let publishTimeout;
|
|
@@ -209,10 +175,15 @@ const makeProgressRenderStore = () => {
|
|
|
209
175
|
for (const listener of listeners) listener();
|
|
210
176
|
};
|
|
211
177
|
const publishNow = () => {
|
|
178
|
+
const nextPublication = {
|
|
179
|
+
snapshot: state,
|
|
180
|
+
events: [...pendingEvents]
|
|
181
|
+
};
|
|
212
182
|
hasPendingPublish = false;
|
|
213
|
-
|
|
214
|
-
publishedSnapshot = toRenderSnapshot(state);
|
|
183
|
+
publishedPublication = nextPublication;
|
|
215
184
|
notifyListeners();
|
|
185
|
+
pendingEvents = [];
|
|
186
|
+
lastPublishAt = Date.now();
|
|
216
187
|
};
|
|
217
188
|
const clearScheduledPublish = () => {
|
|
218
189
|
if (publishTimeout === void 0) return;
|
|
@@ -235,9 +206,10 @@ const makeProgressRenderStore = () => {
|
|
|
235
206
|
publishNow();
|
|
236
207
|
}, waitMillis);
|
|
237
208
|
};
|
|
238
|
-
const publish = (
|
|
239
|
-
if (
|
|
240
|
-
state =
|
|
209
|
+
const publish = (update) => {
|
|
210
|
+
if (update.state === state) return;
|
|
211
|
+
state = update.state;
|
|
212
|
+
if (update.events.length > 0) pendingEvents.push(...update.events);
|
|
241
213
|
hasPendingPublish = true;
|
|
242
214
|
schedulePublish();
|
|
243
215
|
};
|
|
@@ -245,7 +217,7 @@ const makeProgressRenderStore = () => {
|
|
|
245
217
|
publish(transform(state));
|
|
246
218
|
};
|
|
247
219
|
return {
|
|
248
|
-
getSnapshot: () =>
|
|
220
|
+
getSnapshot: () => publishedPublication,
|
|
249
221
|
subscribe: (listener) => {
|
|
250
222
|
listeners.add(listener);
|
|
251
223
|
return () => {
|
|
@@ -268,7 +240,7 @@ const makeProgressRenderStore = () => {
|
|
|
268
240
|
const now = yield* Clock.currentTimeMillis;
|
|
269
241
|
const parentId = options.parentId ?? null;
|
|
270
242
|
const countDisplay = options.countDisplay ?? parentSnapshot?.countDisplay ?? "detailed";
|
|
271
|
-
const task =
|
|
243
|
+
const task = TaskSnapshot({
|
|
272
244
|
id: taskId,
|
|
273
245
|
parentId,
|
|
274
246
|
description: options.description,
|
|
@@ -277,7 +249,8 @@ const makeProgressRenderStore = () => {
|
|
|
277
249
|
transient: (parentSnapshot?.transient ?? false) || (options.transient ?? false),
|
|
278
250
|
units,
|
|
279
251
|
startedAt: now,
|
|
280
|
-
completedAt: null
|
|
252
|
+
completedAt: null,
|
|
253
|
+
metadata: options.metadata
|
|
281
254
|
});
|
|
282
255
|
updateState((current) => {
|
|
283
256
|
const nextTasks = new Map(current.tasks);
|
|
@@ -289,45 +262,72 @@ const makeProgressRenderStore = () => {
|
|
|
289
262
|
depth
|
|
290
263
|
});
|
|
291
264
|
return {
|
|
292
|
-
|
|
293
|
-
|
|
265
|
+
state: {
|
|
266
|
+
tasks: nextTasks,
|
|
267
|
+
renderOrder: nextRenderOrder,
|
|
268
|
+
columns: options.columns ? new Map(current.columns).set(taskId, options.columns) : current.columns
|
|
269
|
+
},
|
|
270
|
+
events: [new TaskAddedEvent({
|
|
271
|
+
taskId,
|
|
272
|
+
parentId,
|
|
273
|
+
description: task.description,
|
|
274
|
+
total: task.units.total,
|
|
275
|
+
transient: task.transient,
|
|
276
|
+
countDisplay: task.countDisplay
|
|
277
|
+
})]
|
|
294
278
|
};
|
|
295
279
|
});
|
|
296
280
|
return taskId;
|
|
297
281
|
}),
|
|
298
|
-
updateTask: (taskId, options) => Effect.
|
|
299
|
-
const currentTask = state.tasks.get(taskId);
|
|
300
|
-
if (!currentTask) return;
|
|
301
|
-
const nextTask = updatedSnapshot(currentTask, options);
|
|
282
|
+
updateTask: (taskId, options) => Effect.sync(() => {
|
|
302
283
|
updateState((current) => {
|
|
303
|
-
|
|
284
|
+
const currentTask = current.tasks.get(taskId);
|
|
285
|
+
if (!currentTask) return {
|
|
286
|
+
state: current,
|
|
287
|
+
events: []
|
|
288
|
+
};
|
|
289
|
+
const nextTask = updatedSnapshot(currentTask, options);
|
|
304
290
|
const nextTasks = new Map(current.tasks);
|
|
305
291
|
nextTasks.set(taskId, nextTask);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
292
|
+
const events = [new TaskUpdatedEvent({
|
|
293
|
+
taskId,
|
|
294
|
+
description: options.description ?? void 0,
|
|
295
|
+
succeeded: options.succeeded ?? void 0,
|
|
296
|
+
failed: options.failed ?? void 0,
|
|
297
|
+
processed: options.succeeded !== void 0 || options.failed !== void 0 ? nextTask.units.processed : void 0,
|
|
298
|
+
total: hasExplicitTotal(options) ? nextTask.units.total : void 0,
|
|
299
|
+
transient: options.transient ?? void 0,
|
|
300
|
+
countDisplay: options.countDisplay ?? void 0
|
|
301
|
+
})];
|
|
302
|
+
if (options.transient !== void 0) for (const candidateId of subtreeTaskIds(current.renderOrder, taskId).slice(1)) {
|
|
303
|
+
const candidate = current.tasks.get(candidateId);
|
|
304
|
+
if (!candidate) continue;
|
|
305
|
+
const nextCandidate = withTransient(candidate, nextTask.transient);
|
|
306
|
+
nextTasks.set(candidateId, nextCandidate);
|
|
307
|
+
events.push(new TaskUpdatedEvent({
|
|
308
|
+
taskId: candidateId,
|
|
309
|
+
transient: nextCandidate.transient
|
|
310
|
+
}));
|
|
318
311
|
}
|
|
319
312
|
return {
|
|
320
|
-
|
|
321
|
-
|
|
313
|
+
state: {
|
|
314
|
+
tasks: nextTasks,
|
|
315
|
+
renderOrder: current.renderOrder,
|
|
316
|
+
columns: current.columns
|
|
317
|
+
},
|
|
318
|
+
events
|
|
322
319
|
};
|
|
323
320
|
});
|
|
324
321
|
}),
|
|
325
322
|
incrementSucceeded: (taskId, amount = 1) => Effect.sync(() => {
|
|
326
323
|
updateState((current) => {
|
|
327
324
|
const currentTask = current.tasks.get(taskId);
|
|
328
|
-
if (!currentTask) return
|
|
325
|
+
if (!currentTask) return {
|
|
326
|
+
state: current,
|
|
327
|
+
events: []
|
|
328
|
+
};
|
|
329
329
|
const nextTasks = new Map(current.tasks);
|
|
330
|
-
nextTasks.set(taskId,
|
|
330
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
331
331
|
id: currentTask.id,
|
|
332
332
|
parentId: currentTask.parentId,
|
|
333
333
|
description: currentTask.description,
|
|
@@ -340,20 +340,32 @@ const makeProgressRenderStore = () => {
|
|
|
340
340
|
total: currentTask.units.total
|
|
341
341
|
}),
|
|
342
342
|
startedAt: currentTask.startedAt,
|
|
343
|
-
completedAt: currentTask.completedAt
|
|
343
|
+
completedAt: currentTask.completedAt,
|
|
344
|
+
metadata: currentTask.metadata
|
|
344
345
|
}));
|
|
345
346
|
return {
|
|
346
|
-
|
|
347
|
-
|
|
347
|
+
state: {
|
|
348
|
+
tasks: nextTasks,
|
|
349
|
+
renderOrder: current.renderOrder,
|
|
350
|
+
columns: current.columns
|
|
351
|
+
},
|
|
352
|
+
events: [new TaskAdvancedEvent({
|
|
353
|
+
taskId,
|
|
354
|
+
amount,
|
|
355
|
+
kind: "succeeded"
|
|
356
|
+
})]
|
|
348
357
|
};
|
|
349
358
|
});
|
|
350
359
|
}),
|
|
351
360
|
incrementFailed: (taskId, amount = 1) => Effect.sync(() => {
|
|
352
361
|
updateState((current) => {
|
|
353
362
|
const currentTask = current.tasks.get(taskId);
|
|
354
|
-
if (!currentTask) return
|
|
363
|
+
if (!currentTask) return {
|
|
364
|
+
state: current,
|
|
365
|
+
events: []
|
|
366
|
+
};
|
|
355
367
|
const nextTasks = new Map(current.tasks);
|
|
356
|
-
nextTasks.set(taskId,
|
|
368
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
357
369
|
id: currentTask.id,
|
|
358
370
|
parentId: currentTask.parentId,
|
|
359
371
|
description: currentTask.description,
|
|
@@ -366,11 +378,20 @@ const makeProgressRenderStore = () => {
|
|
|
366
378
|
total: currentTask.units.total
|
|
367
379
|
}),
|
|
368
380
|
startedAt: currentTask.startedAt,
|
|
369
|
-
completedAt: currentTask.completedAt
|
|
381
|
+
completedAt: currentTask.completedAt,
|
|
382
|
+
metadata: currentTask.metadata
|
|
370
383
|
}));
|
|
371
384
|
return {
|
|
372
|
-
|
|
373
|
-
|
|
385
|
+
state: {
|
|
386
|
+
tasks: nextTasks,
|
|
387
|
+
renderOrder: current.renderOrder,
|
|
388
|
+
columns: current.columns
|
|
389
|
+
},
|
|
390
|
+
events: [new TaskAdvancedEvent({
|
|
391
|
+
taskId,
|
|
392
|
+
amount,
|
|
393
|
+
kind: "failed"
|
|
394
|
+
})]
|
|
374
395
|
};
|
|
375
396
|
});
|
|
376
397
|
}),
|
|
@@ -378,16 +399,30 @@ const makeProgressRenderStore = () => {
|
|
|
378
399
|
const now = yield* Clock.currentTimeMillis;
|
|
379
400
|
updateState((current) => {
|
|
380
401
|
const currentTask = current.tasks.get(taskId);
|
|
381
|
-
if (!currentTask) return
|
|
402
|
+
if (!currentTask) return {
|
|
403
|
+
state: current,
|
|
404
|
+
events: []
|
|
405
|
+
};
|
|
406
|
+
if (currentTask.status !== "running") return {
|
|
407
|
+
state: current,
|
|
408
|
+
events: []
|
|
409
|
+
};
|
|
382
410
|
const nextTasks = new Map(current.tasks);
|
|
383
411
|
if (currentTask.transient) {
|
|
384
|
-
|
|
412
|
+
const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
|
|
413
|
+
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
414
|
+
const nextColumns = new Map(current.columns);
|
|
415
|
+
for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
|
|
385
416
|
return {
|
|
386
|
-
|
|
387
|
-
|
|
417
|
+
state: {
|
|
418
|
+
tasks: nextTasks,
|
|
419
|
+
renderOrder: removeFromRenderOrder(current.renderOrder, taskId),
|
|
420
|
+
columns: nextColumns
|
|
421
|
+
},
|
|
422
|
+
events: [new TaskCompletedEvent({ taskId }), ...removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
|
|
388
423
|
};
|
|
389
424
|
}
|
|
390
|
-
nextTasks.set(taskId,
|
|
425
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
391
426
|
id: currentTask.id,
|
|
392
427
|
parentId: currentTask.parentId,
|
|
393
428
|
description: currentTask.description,
|
|
@@ -404,11 +439,16 @@ const makeProgressRenderStore = () => {
|
|
|
404
439
|
total: currentTask.units.processed
|
|
405
440
|
}) : currentTask.units,
|
|
406
441
|
startedAt: currentTask.startedAt,
|
|
407
|
-
completedAt: now
|
|
442
|
+
completedAt: now,
|
|
443
|
+
metadata: currentTask.metadata
|
|
408
444
|
}));
|
|
409
445
|
return {
|
|
410
|
-
|
|
411
|
-
|
|
446
|
+
state: {
|
|
447
|
+
tasks: nextTasks,
|
|
448
|
+
renderOrder: current.renderOrder,
|
|
449
|
+
columns: current.columns
|
|
450
|
+
},
|
|
451
|
+
events: [new TaskCompletedEvent({ taskId })]
|
|
412
452
|
};
|
|
413
453
|
});
|
|
414
454
|
}),
|
|
@@ -416,16 +456,30 @@ const makeProgressRenderStore = () => {
|
|
|
416
456
|
const now = yield* Clock.currentTimeMillis;
|
|
417
457
|
updateState((current) => {
|
|
418
458
|
const currentTask = current.tasks.get(taskId);
|
|
419
|
-
if (!currentTask) return
|
|
459
|
+
if (!currentTask) return {
|
|
460
|
+
state: current,
|
|
461
|
+
events: []
|
|
462
|
+
};
|
|
463
|
+
if (currentTask.status !== "running") return {
|
|
464
|
+
state: current,
|
|
465
|
+
events: []
|
|
466
|
+
};
|
|
420
467
|
const nextTasks = new Map(current.tasks);
|
|
421
468
|
if (currentTask.transient) {
|
|
422
|
-
|
|
469
|
+
const removedTaskIds = subtreeTaskIds(current.renderOrder, taskId);
|
|
470
|
+
for (const removedTaskId of removedTaskIds) nextTasks.delete(removedTaskId);
|
|
471
|
+
const nextColumns = new Map(current.columns);
|
|
472
|
+
for (const removedTaskId of removedTaskIds) nextColumns.delete(removedTaskId);
|
|
423
473
|
return {
|
|
424
|
-
|
|
425
|
-
|
|
474
|
+
state: {
|
|
475
|
+
tasks: nextTasks,
|
|
476
|
+
renderOrder: removeFromRenderOrder(current.renderOrder, taskId),
|
|
477
|
+
columns: nextColumns
|
|
478
|
+
},
|
|
479
|
+
events: [new TaskFailedEvent({ taskId }), ...removedTaskIds.map((removedTaskId) => new TaskRemovedEvent({ taskId: removedTaskId }))]
|
|
426
480
|
};
|
|
427
481
|
}
|
|
428
|
-
nextTasks.set(taskId,
|
|
482
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
429
483
|
id: currentTask.id,
|
|
430
484
|
parentId: currentTask.parentId,
|
|
431
485
|
description: currentTask.description,
|
|
@@ -434,60 +488,139 @@ const makeProgressRenderStore = () => {
|
|
|
434
488
|
transient: currentTask.transient,
|
|
435
489
|
units: currentTask.units,
|
|
436
490
|
startedAt: currentTask.startedAt,
|
|
437
|
-
completedAt: now
|
|
491
|
+
completedAt: now,
|
|
492
|
+
metadata: currentTask.metadata
|
|
438
493
|
}));
|
|
439
494
|
return {
|
|
440
|
-
|
|
441
|
-
|
|
495
|
+
state: {
|
|
496
|
+
tasks: nextTasks,
|
|
497
|
+
renderOrder: current.renderOrder,
|
|
498
|
+
columns: current.columns
|
|
499
|
+
},
|
|
500
|
+
events: [new TaskFailedEvent({ taskId })]
|
|
442
501
|
};
|
|
443
502
|
});
|
|
444
503
|
}),
|
|
445
504
|
getTask: (taskId) => Effect.sync(() => Option.fromNullable(state.tasks.get(taskId))),
|
|
446
|
-
listTasks: Effect.sync(() => Array.from(state.tasks.values()))
|
|
505
|
+
listTasks: Effect.sync(() => Array.from(state.tasks.values())),
|
|
506
|
+
setMetadata: (taskId, metadata) => Effect.sync(() => {
|
|
507
|
+
updateState((current) => {
|
|
508
|
+
const currentTask = current.tasks.get(taskId);
|
|
509
|
+
if (!currentTask) return {
|
|
510
|
+
state: current,
|
|
511
|
+
events: []
|
|
512
|
+
};
|
|
513
|
+
const nextTasks = new Map(current.tasks);
|
|
514
|
+
nextTasks.set(taskId, TaskSnapshot({
|
|
515
|
+
id: currentTask.id,
|
|
516
|
+
parentId: currentTask.parentId,
|
|
517
|
+
description: currentTask.description,
|
|
518
|
+
status: currentTask.status,
|
|
519
|
+
countDisplay: currentTask.countDisplay,
|
|
520
|
+
transient: currentTask.transient,
|
|
521
|
+
units: currentTask.units,
|
|
522
|
+
startedAt: currentTask.startedAt,
|
|
523
|
+
completedAt: currentTask.completedAt,
|
|
524
|
+
metadata
|
|
525
|
+
}));
|
|
526
|
+
return {
|
|
527
|
+
state: {
|
|
528
|
+
tasks: nextTasks,
|
|
529
|
+
renderOrder: current.renderOrder,
|
|
530
|
+
columns: current.columns
|
|
531
|
+
},
|
|
532
|
+
events: []
|
|
533
|
+
};
|
|
534
|
+
});
|
|
535
|
+
}),
|
|
536
|
+
getMetadata: (taskId) => Effect.sync(() => {
|
|
537
|
+
return state.tasks.get(taskId)?.metadata;
|
|
538
|
+
})
|
|
447
539
|
};
|
|
448
540
|
};
|
|
449
541
|
|
|
450
542
|
//#endregion
|
|
451
|
-
//#region src/
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
};
|
|
543
|
+
//#region src/renderer/hooks/use-now-clock.ts
|
|
544
|
+
const useNowClock = (active, intervalMillis) => {
|
|
545
|
+
const [now, setNow] = useState(() => Date.now());
|
|
546
|
+
useEffect(() => {
|
|
547
|
+
if (!active) return;
|
|
548
|
+
setNow(Date.now());
|
|
549
|
+
const interval = setInterval(() => {
|
|
550
|
+
setNow(Date.now());
|
|
551
|
+
}, intervalMillis);
|
|
552
|
+
return () => {
|
|
553
|
+
clearInterval(interval);
|
|
554
|
+
};
|
|
555
|
+
}, [active, intervalMillis]);
|
|
556
|
+
return now;
|
|
475
557
|
};
|
|
476
558
|
|
|
477
559
|
//#endregion
|
|
478
|
-
//#region src/
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
560
|
+
//#region src/renderer/context/now-context.tsx
|
|
561
|
+
const NOW_INTERVAL_MILLIS = 1e3;
|
|
562
|
+
const NowContext = createContext(Date.now());
|
|
563
|
+
const NowProvider = ({ active, children, nowOverride }) => {
|
|
564
|
+
const liveNow = useNowClock(active, NOW_INTERVAL_MILLIS);
|
|
565
|
+
const now = nowOverride ?? liveNow;
|
|
566
|
+
return /* @__PURE__ */ jsx(NowContext.Provider, {
|
|
567
|
+
value: now,
|
|
568
|
+
children
|
|
569
|
+
});
|
|
570
|
+
};
|
|
571
|
+
const useNow = () => useContext(NowContext);
|
|
572
|
+
|
|
573
|
+
//#endregion
|
|
574
|
+
//#region src/renderer/hooks/use-spinner-clock.ts
|
|
575
|
+
const normalizeIntervalMillis = (intervalMillis) => Math.max(1, intervalMillis);
|
|
576
|
+
const getSpinnerTickAtTime = (baseTick, startedAt, now, intervalMillis) => {
|
|
577
|
+
const elapsedMillis = Math.max(0, now - startedAt);
|
|
578
|
+
return baseTick + Math.floor(elapsedMillis / normalizeIntervalMillis(intervalMillis));
|
|
579
|
+
};
|
|
580
|
+
const useSpinnerClock = (active, intervalMillis) => {
|
|
581
|
+
const [tick, setTick] = useState(0);
|
|
582
|
+
const tickRef = useRef(tick);
|
|
583
|
+
useEffect(() => {
|
|
584
|
+
tickRef.current = tick;
|
|
585
|
+
}, [tick]);
|
|
586
|
+
useEffect(() => {
|
|
587
|
+
if (!active) return;
|
|
588
|
+
const baseTick = tickRef.current;
|
|
589
|
+
const startedAt = Date.now();
|
|
590
|
+
const updateTick = () => {
|
|
591
|
+
setTick(getSpinnerTickAtTime(baseTick, startedAt, Date.now(), intervalMillis));
|
|
592
|
+
};
|
|
593
|
+
updateTick();
|
|
594
|
+
const interval = setInterval(() => {
|
|
595
|
+
updateTick();
|
|
596
|
+
}, normalizeIntervalMillis(intervalMillis));
|
|
597
|
+
return () => {
|
|
598
|
+
clearInterval(interval);
|
|
599
|
+
};
|
|
600
|
+
}, [active, intervalMillis]);
|
|
601
|
+
return tick;
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/renderer/context/spinner-context.tsx
|
|
606
|
+
const DEFAULT_SPINNER_INTERVAL_MILLIS = cliSpinners.dots.interval;
|
|
607
|
+
const SpinnerContext = createContext(0);
|
|
608
|
+
const SpinnerProvider = ({ active, children, intervalMillis = DEFAULT_SPINNER_INTERVAL_MILLIS, tickOverride }) => {
|
|
609
|
+
const liveTick = useSpinnerClock(active, intervalMillis);
|
|
610
|
+
const tick = tickOverride ?? liveTick;
|
|
611
|
+
return /* @__PURE__ */ jsx(SpinnerContext.Provider, {
|
|
612
|
+
value: tick,
|
|
613
|
+
children
|
|
614
|
+
});
|
|
615
|
+
};
|
|
616
|
+
const useSpinnerTick = () => useContext(SpinnerContext);
|
|
617
|
+
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/renderer/shared/determinate.ts
|
|
620
|
+
const isDeterminate$2 = (task) => task.units.total !== void 0;
|
|
621
|
+
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/renderer/shared/format.ts
|
|
491
624
|
const isDeterminate$1 = (task) => task.units.total !== void 0;
|
|
492
625
|
const showsUnknownTotalCounts = (task) => task.units.total === void 0 && task.units.processed > 0;
|
|
493
626
|
const formatDurationSeconds = (seconds) => {
|
|
@@ -513,37 +646,6 @@ const formatEta = (task, now) => {
|
|
|
513
646
|
const elapsedMillis = Math.max(1, now - task.startedAt);
|
|
514
647
|
return formatDurationSeconds(Math.max(0, Math.floor(elapsedMillis / processed * remaining)) / 1e3);
|
|
515
648
|
};
|
|
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
649
|
const formatDeterminateAmountParts = (task) => {
|
|
548
650
|
if (!isDeterminate$1(task)) return;
|
|
549
651
|
const totalText = `${task.units.total}`;
|
|
@@ -556,16 +658,6 @@ const formatDeterminateAmountParts = (task) => {
|
|
|
556
658
|
total: totalText
|
|
557
659
|
};
|
|
558
660
|
};
|
|
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
661
|
const formatAmount = (task, _tick) => {
|
|
570
662
|
if (isDeterminate$1(task)) {
|
|
571
663
|
const parts = formatDeterminateAmountParts(task);
|
|
@@ -582,50 +674,8 @@ const formatAmount = (task, _tick) => {
|
|
|
582
674
|
};
|
|
583
675
|
|
|
584
676
|
//#endregion
|
|
585
|
-
//#region src/
|
|
586
|
-
const
|
|
587
|
-
return {
|
|
588
|
-
id: Hash.hash(Data.struct(config)).toString(36),
|
|
589
|
-
build: (frame) => create(frame, config)
|
|
590
|
-
};
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
//#endregion
|
|
594
|
-
//#region src/ink-renderer/columns/sticky-width.ts
|
|
595
|
-
const applyStickyWidth = ({ key, measure, stickyWidths }) => {
|
|
596
|
-
const preferred = Math.max(measure.preferred, stickyWidths.get(key) ?? 0);
|
|
597
|
-
const max = measure.max === void 0 ? void 0 : Math.max(measure.max, preferred);
|
|
598
|
-
return {
|
|
599
|
-
...measure,
|
|
600
|
-
preferred,
|
|
601
|
-
max
|
|
602
|
-
};
|
|
603
|
-
};
|
|
604
|
-
const commitStickyWidth = ({ key, measure, stickyWidths }) => {
|
|
605
|
-
stickyWidths.set(key, measure.preferred);
|
|
606
|
-
};
|
|
607
|
-
const createStickyColumn = ({ frame, measure: baseMeasure, render, stickyKey }) => {
|
|
608
|
-
const measure = stickyKey === void 0 ? baseMeasure : applyStickyWidth({
|
|
609
|
-
key: stickyKey,
|
|
610
|
-
measure: baseMeasure,
|
|
611
|
-
stickyWidths: frame.stickyWidths
|
|
612
|
-
});
|
|
613
|
-
return {
|
|
614
|
-
measure,
|
|
615
|
-
commitStickyWidth: stickyKey === void 0 ? void 0 : () => {
|
|
616
|
-
commitStickyWidth({
|
|
617
|
-
key: stickyKey,
|
|
618
|
-
measure,
|
|
619
|
-
stickyWidths: frame.stickyWidths
|
|
620
|
-
});
|
|
621
|
-
},
|
|
622
|
-
render
|
|
623
|
-
};
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
//#endregion
|
|
627
|
-
//#region src/ink-renderer/columns/text-width.ts
|
|
628
|
-
const WIDTH_CACHE_LIMIT = 4096;
|
|
677
|
+
//#region src/renderer/shared/text-width.ts
|
|
678
|
+
const WIDTH_CACHE_LIMIT = 8192;
|
|
629
679
|
const widthCache = /* @__PURE__ */ new Map();
|
|
630
680
|
const textWidth = (text) => {
|
|
631
681
|
const cached = widthCache.get(text);
|
|
@@ -637,725 +687,526 @@ const textWidth = (text) => {
|
|
|
637
687
|
};
|
|
638
688
|
|
|
639
689
|
//#endregion
|
|
640
|
-
//#region src/
|
|
641
|
-
const
|
|
642
|
-
const
|
|
643
|
-
const
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if (tree.depth <= 0) return "";
|
|
651
|
-
return `${treeAncestorPrefix(tree)}${tree.hasNextSibling ? "├─ " : "└─ "}`;
|
|
690
|
+
//#region src/renderer/columns/amount-column.tsx
|
|
691
|
+
const hasUnknownTotalCounts = (task) => task.units.total === void 0 && task.units.processed > 0;
|
|
692
|
+
const hasCountedAmount = (task) => isDeterminate$2(task) || hasUnknownTotalCounts(task);
|
|
693
|
+
const totalTextFor = (task) => task.units.total === void 0 ? "?" : `${task.units.total}`;
|
|
694
|
+
const emptyAmountLayout = {
|
|
695
|
+
hasDetailedRows: false,
|
|
696
|
+
countWidth: 0,
|
|
697
|
+
processedWidth: 0,
|
|
698
|
+
totalWidth: 0,
|
|
699
|
+
preferredWidth: 0
|
|
652
700
|
};
|
|
653
|
-
const
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
const
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
return
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
});
|
|
701
|
+
const measureAmountLayout = (rows) => {
|
|
702
|
+
const countedTasks = rows.flatMap((row) => hasCountedAmount(row.task) ? [row.task] : []);
|
|
703
|
+
const hasDetailedRows = countedTasks.some((task) => task.countDisplay === "detailed");
|
|
704
|
+
if (countedTasks.length === 0) {
|
|
705
|
+
const preferredWidth = rows.reduce((max, row) => Math.max(max, textWidth(formatAmount(row.task, 0))), 0);
|
|
706
|
+
return {
|
|
707
|
+
...emptyAmountLayout,
|
|
708
|
+
preferredWidth
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
const processedWidth = countedTasks.reduce((max, task) => Math.max(max, `${task.units.processed}`.length), 1);
|
|
712
|
+
const totalWidth = countedTasks.reduce((max, task) => Math.max(max, totalTextFor(task).length), 1);
|
|
713
|
+
const countWidth = hasDetailedRows ? countedTasks.reduce((max, task) => Math.max(max, processedWidth, totalWidth, `${task.units.succeeded}`.length, `${task.units.failed}`.length), 1) : 0;
|
|
714
|
+
const countedWidth = (hasDetailedRows ? countWidth + 1 + countWidth + 1 : 0) + processedWidth + 1 + totalWidth;
|
|
715
|
+
return {
|
|
716
|
+
hasDetailedRows,
|
|
717
|
+
countWidth,
|
|
718
|
+
processedWidth,
|
|
719
|
+
totalWidth,
|
|
720
|
+
preferredWidth: rows.reduce((max, row) => {
|
|
721
|
+
if (hasCountedAmount(row.task)) return Math.max(max, countedWidth);
|
|
722
|
+
return Math.max(max, textWidth(formatAmount(row.task, 0)));
|
|
723
|
+
}, countedWidth)
|
|
724
|
+
};
|
|
697
725
|
};
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
const
|
|
726
|
+
const renderAmount = (task, layout) => {
|
|
727
|
+
if (!hasCountedAmount(task)) return formatAmount(task, 0);
|
|
728
|
+
const processed = `${task.units.processed}`.padStart(layout.processedWidth, " ");
|
|
729
|
+
const total = totalTextFor(task).padStart(layout.totalWidth, " ");
|
|
730
|
+
if (!layout.hasDetailedRows) return `${processed}/${total}`;
|
|
731
|
+
if (task.countDisplay !== "detailed") return `${" ".repeat(layout.countWidth)} ${" ".repeat(layout.countWidth)} ${processed}/${total}`;
|
|
732
|
+
const succeeded = `${task.units.succeeded}`.padStart(layout.countWidth, " ");
|
|
733
|
+
const failed = `${task.units.failed}`.padStart(layout.countWidth, " ");
|
|
734
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
735
|
+
/* @__PURE__ */ jsx(Text, {
|
|
736
|
+
color: "green",
|
|
737
|
+
children: succeeded
|
|
738
|
+
}),
|
|
739
|
+
/* @__PURE__ */ jsx(Text, { children: ` ` }),
|
|
740
|
+
/* @__PURE__ */ jsx(Text, {
|
|
741
|
+
color: "red",
|
|
742
|
+
children: failed
|
|
743
|
+
}),
|
|
744
|
+
/* @__PURE__ */ jsx(Text, { children: ` ${processed}/${total}` })
|
|
745
|
+
] });
|
|
746
|
+
};
|
|
747
|
+
const AmountCell = ({ task, layout }) => /* @__PURE__ */ jsx(Text, {
|
|
748
|
+
wrap: "truncate-end",
|
|
749
|
+
children: renderAmount(task, layout)
|
|
750
|
+
});
|
|
706
751
|
|
|
707
752
|
//#endregion
|
|
708
|
-
//#region src/
|
|
709
|
-
const
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
return
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
})
|
|
732
|
-
|
|
753
|
+
//#region src/renderer/columns/bar-column.tsx
|
|
754
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
755
|
+
const prepareBar = (rows) => {
|
|
756
|
+
return { hasDeterminateRows: rows.some((row) => row.derived.isDeterminate) };
|
|
757
|
+
};
|
|
758
|
+
const renderProgressBar = (task, width) => {
|
|
759
|
+
if (!isDeterminate$2(task)) return /* @__PURE__ */ jsx(Text, { children: ` `.repeat(Math.max(0, width)) });
|
|
760
|
+
const displayTotal = Math.max(task.units.total, task.units.succeeded + task.units.failed);
|
|
761
|
+
const succeededEnd = displayTotal === 0 ? width : Math.round(task.units.succeeded / displayTotal * width);
|
|
762
|
+
const failedEnd = displayTotal === 0 ? width : Math.round((task.units.succeeded + task.units.failed) / displayTotal * width);
|
|
763
|
+
const succeededLength = clamp(succeededEnd, 0, width);
|
|
764
|
+
const failedLength = clamp(failedEnd, succeededLength, width) - succeededLength;
|
|
765
|
+
const remainingLength = Math.max(0, width - succeededLength - failedLength);
|
|
766
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
767
|
+
wrap: "truncate-end",
|
|
768
|
+
children: [
|
|
769
|
+
/* @__PURE__ */ jsx(Text, {
|
|
770
|
+
color: "green",
|
|
771
|
+
children: "━".repeat(succeededLength)
|
|
772
|
+
}),
|
|
773
|
+
/* @__PURE__ */ jsx(Text, {
|
|
774
|
+
color: "red",
|
|
775
|
+
children: "━".repeat(failedLength)
|
|
776
|
+
}),
|
|
777
|
+
/* @__PURE__ */ jsx(Text, {
|
|
778
|
+
color: "gray",
|
|
779
|
+
children: "─".repeat(remainingLength)
|
|
780
|
+
})
|
|
781
|
+
]
|
|
733
782
|
});
|
|
734
783
|
};
|
|
735
|
-
const
|
|
784
|
+
const BarCell = ({ task, width }) => /* @__PURE__ */ jsx(Text, {
|
|
785
|
+
wrap: "truncate-end",
|
|
786
|
+
children: renderProgressBar(task, width ?? 0)
|
|
787
|
+
});
|
|
736
788
|
|
|
737
789
|
//#endregion
|
|
738
|
-
//#region src/
|
|
790
|
+
//#region src/renderer/columns/description-column.tsx
|
|
791
|
+
const MIN_TREE_DESCRIPTION_TEXT_WIDTH = 6;
|
|
792
|
+
const DEFAULT_SPINNER_TYPE = "dots";
|
|
739
793
|
const isDeterminate = (task) => task.units.total !== void 0;
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
const primaryUnit = (duration) => duration.split(" ")[0] ?? duration;
|
|
744
|
-
const ETA_STICKY_KEY = Symbol("eta");
|
|
745
|
-
const etaDurationText = (task, now) => {
|
|
746
|
-
if (task.status !== "running" || !isDeterminate(task)) return;
|
|
747
|
-
const eta = formatEta(task, now);
|
|
748
|
-
return eta.length > 0 ? eta : "--";
|
|
749
|
-
};
|
|
750
|
-
const renderEtaText = (task, now, width) => {
|
|
751
|
-
const duration = etaDurationText(task, now);
|
|
752
|
-
if (duration === void 0) return "";
|
|
753
|
-
const prefixed = `ETA: ${duration}`;
|
|
754
|
-
if (width >= textWidth(prefixed)) return prefixed;
|
|
755
|
-
if (width >= textWidth(duration)) return duration;
|
|
756
|
-
return primaryUnit(duration);
|
|
794
|
+
const getSpinnerFrame = (tick, spinnerType) => {
|
|
795
|
+
const frames = cliSpinners[spinnerType].frames;
|
|
796
|
+
return frames[(tick % frames.length + frames.length) % frames.length] ?? frames[0] ?? "";
|
|
757
797
|
};
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
798
|
+
const getTaskIndicator = (task, tick, spinnerType = DEFAULT_SPINNER_TYPE) => {
|
|
799
|
+
if (task.status === "running") return {
|
|
800
|
+
symbol: getSpinnerFrame(tick, spinnerType),
|
|
801
|
+
color: "yellow"
|
|
802
|
+
};
|
|
803
|
+
if (task.status === "failed") return {
|
|
804
|
+
symbol: "✗",
|
|
805
|
+
color: "red"
|
|
806
|
+
};
|
|
807
|
+
if (!isDeterminate(task)) return {
|
|
808
|
+
symbol: "✓",
|
|
809
|
+
color: "green"
|
|
810
|
+
};
|
|
811
|
+
const { succeeded, failed, processed, total } = task.units;
|
|
812
|
+
if (failed === 0 && processed === total) return {
|
|
813
|
+
symbol: "✓",
|
|
814
|
+
color: "green"
|
|
815
|
+
};
|
|
816
|
+
if (failed > 0 && succeeded > 0) return {
|
|
817
|
+
symbol: "~",
|
|
818
|
+
color: "yellow"
|
|
819
|
+
};
|
|
820
|
+
if (failed > 0 && succeeded === 0) return {
|
|
821
|
+
symbol: "✗",
|
|
822
|
+
color: "red"
|
|
778
823
|
};
|
|
779
|
-
};
|
|
780
|
-
const EtaColumn = (frame) => {
|
|
781
|
-
const metrics = computeEtaMetrics(frame);
|
|
782
|
-
if (!metrics.hasEta) return;
|
|
783
|
-
return createStickyColumn({
|
|
784
|
-
frame,
|
|
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
|
-
})
|
|
796
|
-
});
|
|
797
|
-
};
|
|
798
|
-
const EtaRootColumn = createColumnDefinition({ _tag: "eta" }, (frame) => EtaColumn(frame));
|
|
799
|
-
|
|
800
|
-
//#endregion
|
|
801
|
-
//#region src/ink-renderer/columns/progress/shared.ts
|
|
802
|
-
const DEFAULT_BAR_WIDTH = 30;
|
|
803
|
-
const PERCENT_FALLBACK_WIDTH = 10;
|
|
804
|
-
const padLeft = (value, width) => value.padStart(Math.max(0, width), " ");
|
|
805
|
-
const padRight = (value, width) => value.padEnd(Math.max(0, width), " ");
|
|
806
|
-
const blank = (width) => " ".repeat(Math.max(0, width));
|
|
807
|
-
const shouldShowCountAmount = (task) => task.units.total !== void 0 || task.units.processed > 0;
|
|
808
|
-
const shouldShowDetailedCounts = (task) => shouldShowCountAmount(task) && task.countDisplay === "detailed";
|
|
809
|
-
const computeProgressMetrics = (frame) => {
|
|
810
|
-
let hasStructuredCounts = false;
|
|
811
|
-
let hasDetailed = false;
|
|
812
|
-
let hasDeterminate = false;
|
|
813
|
-
let countDigits = 0;
|
|
814
|
-
let totalWidth = 0;
|
|
815
|
-
let simpleTextWidth = 0;
|
|
816
|
-
for (const taskId of frame.taskIds) {
|
|
817
|
-
const task = frame.getTask(taskId);
|
|
818
|
-
hasDeterminate ||= isDeterminate(task);
|
|
819
|
-
if (shouldShowCountAmount(task)) {
|
|
820
|
-
hasStructuredCounts = true;
|
|
821
|
-
countDigits = Math.max(countDigits, textWidth(`${task.units.succeeded}`), textWidth(`${task.units.failed}`), textWidth(`${task.units.processed}`));
|
|
822
|
-
totalWidth = Math.max(totalWidth, textWidth(isDeterminate(task) ? `${task.units.total}` : "?"));
|
|
823
|
-
if (task.countDisplay === "detailed") hasDetailed = true;
|
|
824
|
-
continue;
|
|
825
|
-
}
|
|
826
|
-
simpleTextWidth = Math.max(simpleTextWidth, textWidth(formatAmount(task, frame.tick)));
|
|
827
|
-
}
|
|
828
824
|
return {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
hasDeterminate,
|
|
832
|
-
countDigits: Math.max(1, countDigits),
|
|
833
|
-
totalWidth: Math.max(1, totalWidth),
|
|
834
|
-
simpleTextWidth
|
|
825
|
+
symbol: "✓",
|
|
826
|
+
color: "green"
|
|
835
827
|
};
|
|
836
828
|
};
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
const
|
|
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
|
-
|
|
846
|
-
//#endregion
|
|
847
|
-
//#region src/ink-renderer/columns/progress/amount-column.tsx
|
|
848
|
-
const succeededCount = (task, width) => {
|
|
849
|
-
if (width <= 0 || !shouldShowCountAmount(task) || !shouldShowDetailedCounts(task)) return /* @__PURE__ */ jsx(Text, { children: blank(width) });
|
|
829
|
+
const prepareDescription = (rows) => ({ minTreeWidth: rows.reduce((max, row) => Math.max(max, row.derived.treePrefixWidth + 2 + MIN_TREE_DESCRIPTION_TEXT_WIDTH), MIN_TREE_DESCRIPTION_TEXT_WIDTH + 2) });
|
|
830
|
+
const TaskIndicatorGlyph = ({ task, tick, spinnerType = DEFAULT_SPINNER_TYPE }) => {
|
|
831
|
+
const indicator = getTaskIndicator(task, tick, spinnerType);
|
|
850
832
|
return /* @__PURE__ */ jsx(Text, {
|
|
851
|
-
color:
|
|
852
|
-
children:
|
|
833
|
+
color: indicator.color,
|
|
834
|
+
children: indicator.symbol
|
|
853
835
|
});
|
|
854
836
|
};
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
837
|
+
const DescriptionCell = ({ cell, width, minTreeWidth, spinnerTick }) => {
|
|
838
|
+
const showTree = width === void 0 || width >= minTreeWidth;
|
|
839
|
+
const treePrefix = showTree ? cell.derived.treePrefix : "";
|
|
840
|
+
if (!showTree && width !== void 0 && width <= 1) return /* @__PURE__ */ jsx(TaskIndicatorGlyph, {
|
|
841
|
+
task: cell.task,
|
|
842
|
+
tick: spinnerTick
|
|
860
843
|
});
|
|
861
|
-
|
|
862
|
-
const processedCount = (task, width) => {
|
|
863
|
-
if (width <= 0 || !shouldShowCountAmount(task)) return /* @__PURE__ */ jsx(Text, { children: blank(width) });
|
|
864
|
-
return /* @__PURE__ */ jsx(Text, { children: padLeft(`${task.units.processed}`, width) });
|
|
865
|
-
};
|
|
866
|
-
const totalCount = (task, width) => {
|
|
867
|
-
if (width <= 0 || !shouldShowCountAmount(task)) return /* @__PURE__ */ jsx(Text, { children: blank(width) });
|
|
868
|
-
return /* @__PURE__ */ jsx(Text, { children: padRight(isDeterminate(task) ? `${task.units.total}` : "?", width) });
|
|
869
|
-
};
|
|
870
|
-
const structuredAmount = (task, tick, width, layout) => {
|
|
871
|
-
if (!shouldShowCountAmount(task)) return /* @__PURE__ */ jsx(Text, {
|
|
844
|
+
if (!showTree && width !== void 0 && width === 2) return /* @__PURE__ */ jsxs(Text, {
|
|
872
845
|
wrap: "truncate-end",
|
|
873
|
-
children:
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
width,
|
|
878
|
-
children: [
|
|
879
|
-
processedCount(task, layout.processedWidth),
|
|
880
|
-
/* @__PURE__ */ jsx(Text, { children: "/" }),
|
|
881
|
-
totalCount(task, layout.totalWidth)
|
|
882
|
-
]
|
|
846
|
+
children: [/* @__PURE__ */ jsx(TaskIndicatorGlyph, {
|
|
847
|
+
task: cell.task,
|
|
848
|
+
tick: spinnerTick
|
|
849
|
+
}), "…"]
|
|
883
850
|
});
|
|
884
|
-
return /* @__PURE__ */ jsxs(
|
|
885
|
-
|
|
886
|
-
width,
|
|
851
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
852
|
+
wrap: "truncate-end",
|
|
887
853
|
children: [
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
854
|
+
treePrefix,
|
|
855
|
+
/* @__PURE__ */ jsx(TaskIndicatorGlyph, {
|
|
856
|
+
task: cell.task,
|
|
857
|
+
tick: spinnerTick
|
|
858
|
+
}),
|
|
859
|
+
` ${cell.task.description}`
|
|
893
860
|
]
|
|
894
861
|
});
|
|
895
862
|
};
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
countDigits: metrics.countDigits,
|
|
905
|
-
totalWidth: metrics.totalWidth,
|
|
906
|
-
detailedWidth,
|
|
907
|
-
processedWidth,
|
|
908
|
-
preferredWidth,
|
|
909
|
-
minWidth,
|
|
910
|
-
simpleTextWidth: Math.max(1, metrics.simpleTextWidth)
|
|
911
|
-
};
|
|
912
|
-
return createStickyColumn({
|
|
913
|
-
frame,
|
|
914
|
-
measure: {
|
|
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
|
-
}
|
|
863
|
+
|
|
864
|
+
//#endregion
|
|
865
|
+
//#region src/renderer/columns/elapsed-column.tsx
|
|
866
|
+
const ElapsedCell = ({ task, now }) => {
|
|
867
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
868
|
+
wrap: "truncate-end",
|
|
869
|
+
color: "gray",
|
|
870
|
+
children: formatElapsed(task, now)
|
|
939
871
|
});
|
|
940
872
|
};
|
|
941
873
|
|
|
942
874
|
//#endregion
|
|
943
|
-
//#region src/
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
return {
|
|
956
|
-
succeeded: succeededLength,
|
|
957
|
-
failed: failedLength,
|
|
958
|
-
remaining: Math.max(0, width - succeededLength - failedLength)
|
|
959
|
-
};
|
|
960
|
-
};
|
|
961
|
-
const BarColumn = (frame, config) => {
|
|
962
|
-
return createStickyColumn({
|
|
963
|
-
frame,
|
|
964
|
-
measure: {
|
|
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
|
-
}
|
|
875
|
+
//#region src/renderer/columns/eta-column.tsx
|
|
876
|
+
const EtaCell = ({ task, now }) => {
|
|
877
|
+
const eta = formatEta(task, now);
|
|
878
|
+
if (eta === "") return null;
|
|
879
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
880
|
+
wrap: "truncate-end",
|
|
881
|
+
color: "gray",
|
|
882
|
+
children: `ETA: ${eta}`
|
|
992
883
|
});
|
|
993
884
|
};
|
|
994
885
|
|
|
995
886
|
//#endregion
|
|
996
|
-
//#region src/
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
887
|
+
//#region src/columns.tsx
|
|
888
|
+
var columns_exports = /* @__PURE__ */ __exportAll({
|
|
889
|
+
amount: () => amount,
|
|
890
|
+
bar: () => bar,
|
|
891
|
+
defaults: () => defaults,
|
|
892
|
+
description: () => description,
|
|
893
|
+
elapsed: () => elapsed,
|
|
894
|
+
eta: () => eta,
|
|
895
|
+
resolveColumnSizeValue: () => resolveColumnSizeValue,
|
|
896
|
+
spacer: () => spacer
|
|
897
|
+
});
|
|
898
|
+
const spacer = ({ flexGrow, flexShrink, flexBasis, minWidth } = {}) => ({
|
|
899
|
+
render: () => null,
|
|
900
|
+
flexGrow,
|
|
901
|
+
flexShrink,
|
|
902
|
+
flexBasis,
|
|
903
|
+
minWidth
|
|
904
|
+
});
|
|
905
|
+
const description = () => ({
|
|
906
|
+
prepare: prepareDescription,
|
|
907
|
+
flexGrow: 1,
|
|
908
|
+
flexShrink: 1,
|
|
909
|
+
minWidth: 1,
|
|
910
|
+
render: (cell, ctx) => /* @__PURE__ */ jsx(DescriptionCell, {
|
|
911
|
+
cell,
|
|
912
|
+
width: ctx.width,
|
|
913
|
+
minTreeWidth: ctx.prepared.minTreeWidth,
|
|
914
|
+
spinnerTick: ctx.spinnerTick
|
|
915
|
+
})
|
|
916
|
+
});
|
|
917
|
+
const bar = () => ({
|
|
918
|
+
prepare: prepareBar,
|
|
919
|
+
flexShrink: (prepared) => prepared.hasDeterminateRows ? 1 : 0,
|
|
920
|
+
flexBasis: (prepared) => prepared.hasDeterminateRows ? 30 : 0,
|
|
921
|
+
minWidth: (prepared) => prepared.hasDeterminateRows ? 4 : 0,
|
|
922
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(BarCell, {
|
|
923
|
+
task,
|
|
924
|
+
width: ctx.width
|
|
925
|
+
})
|
|
926
|
+
});
|
|
927
|
+
const amount = () => ({
|
|
928
|
+
prepare: measureAmountLayout,
|
|
929
|
+
align: "right",
|
|
930
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(AmountCell, {
|
|
931
|
+
task,
|
|
932
|
+
layout: ctx.prepared
|
|
933
|
+
})
|
|
934
|
+
});
|
|
935
|
+
const elapsed = () => ({
|
|
936
|
+
align: "right",
|
|
937
|
+
flexShrink: 0,
|
|
938
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(ElapsedCell, {
|
|
939
|
+
task,
|
|
940
|
+
now: ctx.now
|
|
941
|
+
})
|
|
1012
942
|
});
|
|
943
|
+
const eta = () => ({
|
|
944
|
+
align: "right",
|
|
945
|
+
flexShrink: 0,
|
|
946
|
+
render: ({ task }, ctx) => /* @__PURE__ */ jsx(EtaCell, {
|
|
947
|
+
task,
|
|
948
|
+
now: ctx.now
|
|
949
|
+
})
|
|
950
|
+
});
|
|
951
|
+
const defaults = () => [
|
|
952
|
+
description(),
|
|
953
|
+
bar(),
|
|
954
|
+
amount(),
|
|
955
|
+
elapsed(),
|
|
956
|
+
eta()
|
|
957
|
+
];
|
|
958
|
+
const resolveColumnSizeValue = (value, prepared) => {
|
|
959
|
+
if (typeof value === "function") return value(prepared);
|
|
960
|
+
return value;
|
|
961
|
+
};
|
|
1013
962
|
|
|
1014
963
|
//#endregion
|
|
1015
|
-
//#region src/
|
|
1016
|
-
const
|
|
1017
|
-
const
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
};
|
|
964
|
+
//#region src/renderer/column-resolver.ts
|
|
965
|
+
const NO_PREPARE = Symbol("no-prepare");
|
|
966
|
+
const getColumnsForRow = (row, columns) => columns.get(row.task.id) ?? defaults();
|
|
967
|
+
const toCellInfo = (row) => row;
|
|
968
|
+
const maxDefined = (values) => values.reduce((maxValue, value) => value === void 0 ? maxValue : Math.max(maxValue ?? Number.NEGATIVE_INFINITY, value), void 0);
|
|
969
|
+
const resolvePreparedGroups = (defs, cellInfos) => {
|
|
970
|
+
const groupedRows = /* @__PURE__ */ new Map();
|
|
971
|
+
defs.forEach((def, rowIndex) => {
|
|
972
|
+
if (!def?.prepare) return;
|
|
973
|
+
const key = def.prepare;
|
|
974
|
+
const rows = groupedRows.get(key);
|
|
975
|
+
const cell = cellInfos[rowIndex];
|
|
976
|
+
if (!cell) return;
|
|
977
|
+
if (rows) rows.push(cell);
|
|
978
|
+
else groupedRows.set(key, [cell]);
|
|
979
|
+
});
|
|
980
|
+
const groups = [{
|
|
981
|
+
key: NO_PREPARE,
|
|
982
|
+
prepared: void 0
|
|
983
|
+
}];
|
|
984
|
+
for (const [key, rows] of groupedRows) groups.push({
|
|
985
|
+
key,
|
|
986
|
+
prepared: key(rows)
|
|
987
|
+
});
|
|
988
|
+
return groups;
|
|
1039
989
|
};
|
|
1040
|
-
const
|
|
1041
|
-
if (!
|
|
1042
|
-
|
|
1043
|
-
if (bar === void 0) return { kind: "percent" };
|
|
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
|
|
1052
|
-
};
|
|
990
|
+
const getPreparedFor = (def, groups) => {
|
|
991
|
+
if (!def?.prepare) return;
|
|
992
|
+
return groups.find((group) => group.key === def.prepare)?.prepared;
|
|
1053
993
|
};
|
|
1054
|
-
const
|
|
1055
|
-
const
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
const layout = layoutForWidth(width, model);
|
|
1076
|
-
if (layout.kind === "percent") return model.percent.render(taskId, width);
|
|
1077
|
-
if (layout.kind === "amount-only") return amount.render(taskId, width);
|
|
1078
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
1079
|
-
flexDirection: "row",
|
|
1080
|
-
width,
|
|
1081
|
-
children: [
|
|
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
|
-
}
|
|
1094
|
-
};
|
|
994
|
+
const resolveColumns = (rows, columns) => {
|
|
995
|
+
const columnsByRow = rows.map((row) => getColumnsForRow(row, columns));
|
|
996
|
+
const cellInfos = rows.map(toCellInfo);
|
|
997
|
+
const maxColumnCount = columnsByRow.reduce((max, defs) => Math.max(max, defs.length), 0);
|
|
998
|
+
return Array.from({ length: maxColumnCount }, (_, index) => {
|
|
999
|
+
const defsAtIndex = columnsByRow.map((defs) => defs[index]);
|
|
1000
|
+
const preparedGroups = resolvePreparedGroups(defsAtIndex, cellInfos);
|
|
1001
|
+
const entries = defsAtIndex.map((column) => column === void 0 ? void 0 : {
|
|
1002
|
+
column,
|
|
1003
|
+
prepared: getPreparedFor(column, preparedGroups)
|
|
1004
|
+
});
|
|
1005
|
+
return {
|
|
1006
|
+
index,
|
|
1007
|
+
rows,
|
|
1008
|
+
entries,
|
|
1009
|
+
flexGrow: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.flexGrow, entry.prepared))),
|
|
1010
|
+
flexShrink: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.flexShrink, entry.prepared))),
|
|
1011
|
+
flexBasis: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.flexBasis, entry.prepared))),
|
|
1012
|
+
minWidth: maxDefined(entries.map((entry) => entry === void 0 ? void 0 : resolveColumnSizeValue(entry.column.minWidth, entry.prepared)))
|
|
1013
|
+
};
|
|
1014
|
+
});
|
|
1095
1015
|
};
|
|
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
1016
|
|
|
1103
1017
|
//#endregion
|
|
1104
|
-
//#region src/
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
return
|
|
1109
|
-
};
|
|
1110
|
-
const ROOT_LAYOUTS = [
|
|
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]
|
|
1138
|
-
];
|
|
1139
|
-
const ROOT_COLUMNS = [...new Map(ROOT_LAYOUTS.flat().map((column) => [column.id, column])).values()];
|
|
1140
|
-
const measureColumns = (frame) => new Map(ROOT_COLUMNS.flatMap((definition) => {
|
|
1141
|
-
const column = definition.build(frame);
|
|
1142
|
-
if (column === void 0) return [];
|
|
1143
|
-
const measure = column.measure;
|
|
1144
|
-
return [[definition.id, {
|
|
1145
|
-
definition,
|
|
1146
|
-
id: definition.id,
|
|
1147
|
-
measure,
|
|
1148
|
-
preferredWidth: measure.preferred,
|
|
1149
|
-
commitStickyWidth: column.commitStickyWidth,
|
|
1150
|
-
render: column.render
|
|
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;
|
|
1211
|
-
};
|
|
1212
|
-
return candidateRootLayouts(columnsById).map(resolveLayout).filter((columns) => columns !== void 0);
|
|
1213
|
-
};
|
|
1214
|
-
const minimumWidthForSet = (columns) => visibleWidth(columns.map((column) => column.measure.min), ROOT_GAP);
|
|
1215
|
-
const selectColumnSet = (columnSets, terminalColumns) => {
|
|
1216
|
-
if (columnSets.length === 0) return [];
|
|
1217
|
-
if (terminalColumns === void 0) return columnSets[0] ?? [];
|
|
1218
|
-
return columnSets.find((columns) => minimumWidthForSet(columns) <= terminalColumns) ?? columnSets.at(-1) ?? [];
|
|
1219
|
-
};
|
|
1220
|
-
const preferredWidthsForSet = (columns) => columns.map((column) => Math.max(column.measure.min, column.preferredWidth));
|
|
1221
|
-
const nextDistinctWidth = (entries, widest) => entries.find((entry) => entry.width < widest)?.width;
|
|
1222
|
-
const reduceOverflowRichStyle = (widths, minimums, targetWidth) => {
|
|
1223
|
-
let overflow = visibleWidth(widths, ROOT_GAP) - targetWidth;
|
|
1224
|
-
while (overflow > 0) {
|
|
1225
|
-
const shrinkable = widths.map((width, index) => ({
|
|
1226
|
-
width,
|
|
1227
|
-
index,
|
|
1228
|
-
minimum: minimums[index] ?? width
|
|
1229
|
-
})).filter(({ width, minimum }) => width > minimum).sort((left, right) => right.width - left.width || left.index - right.index);
|
|
1230
|
-
if (shrinkable.length === 0) break;
|
|
1231
|
-
const widest = shrinkable[0].width;
|
|
1232
|
-
const cohort = shrinkable.filter(({ width }) => width === widest);
|
|
1233
|
-
const nextWidth = nextDistinctWidth(shrinkable, widest);
|
|
1234
|
-
const maxUniformDrop = widest - Math.max(nextWidth ?? 0, ...cohort.map(({ minimum }) => minimum));
|
|
1235
|
-
const uniformDrop = Math.min(maxUniformDrop, Math.floor(overflow / cohort.length));
|
|
1236
|
-
if (uniformDrop > 0) {
|
|
1237
|
-
for (const { index } of cohort) widths[index] = widths[index] - uniformDrop;
|
|
1238
|
-
overflow -= uniformDrop * cohort.length;
|
|
1239
|
-
continue;
|
|
1240
|
-
}
|
|
1241
|
-
let changed = false;
|
|
1242
|
-
for (const { index, minimum } of cohort) {
|
|
1243
|
-
if (overflow <= 0) break;
|
|
1244
|
-
if (widths[index] <= minimum) continue;
|
|
1245
|
-
widths[index] = widths[index] - 1;
|
|
1246
|
-
overflow -= 1;
|
|
1247
|
-
changed = true;
|
|
1248
|
-
}
|
|
1249
|
-
if (!changed) break;
|
|
1250
|
-
}
|
|
1251
|
-
return widths;
|
|
1018
|
+
//#region src/renderer/public-api.tsx
|
|
1019
|
+
const justifyContentForAlign = (align) => {
|
|
1020
|
+
if (align === "right") return "flex-end";
|
|
1021
|
+
if (align === "center") return "center";
|
|
1022
|
+
return "flex-start";
|
|
1252
1023
|
};
|
|
1253
|
-
const
|
|
1254
|
-
if (
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
return reduceOverflowRichStyle(widths, minimums, targetWidth);
|
|
1024
|
+
const renderNode = (node) => {
|
|
1025
|
+
if (typeof node === "string" || typeof node === "number") return /* @__PURE__ */ jsx(Text, {
|
|
1026
|
+
wrap: "truncate-end",
|
|
1027
|
+
children: node
|
|
1028
|
+
});
|
|
1029
|
+
return node;
|
|
1260
1030
|
};
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1031
|
+
const ColumnPosition = ({ position }) => {
|
|
1032
|
+
const ref = useRef(null);
|
|
1033
|
+
const { width, hasMeasured } = useBoxMetrics(ref);
|
|
1034
|
+
const now = useNow();
|
|
1035
|
+
const spinnerTick = useSpinnerTick();
|
|
1036
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
1037
|
+
ref,
|
|
1038
|
+
flexDirection: "column",
|
|
1039
|
+
flexGrow: position.flexGrow,
|
|
1040
|
+
flexShrink: position.flexShrink ?? 0,
|
|
1041
|
+
flexBasis: position.flexBasis,
|
|
1042
|
+
minWidth: position.minWidth,
|
|
1043
|
+
children: position.rows.map((row, rowIndex) => {
|
|
1044
|
+
const entry = position.entries[rowIndex];
|
|
1045
|
+
const column = entry?.column;
|
|
1046
|
+
const cell = row;
|
|
1047
|
+
const output = column?.render(cell, {
|
|
1048
|
+
width: hasMeasured ? width : void 0,
|
|
1049
|
+
now,
|
|
1050
|
+
spinnerTick,
|
|
1051
|
+
prepared: entry?.prepared
|
|
1052
|
+
}) ?? null;
|
|
1053
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
1054
|
+
height: 1,
|
|
1055
|
+
justifyContent: justifyContentForAlign(column?.align),
|
|
1056
|
+
children: renderNode(output)
|
|
1057
|
+
}, row.task.id);
|
|
1058
|
+
})
|
|
1059
|
+
});
|
|
1264
1060
|
};
|
|
1265
|
-
const
|
|
1266
|
-
if (rows.length === 0) return
|
|
1267
|
-
|
|
1268
|
-
for (const column of selectedColumns) column.commitStickyWidth?.();
|
|
1269
|
-
const columns = widthForSelectedSet(selectedColumns, terminalColumns === void 0 ? visibleWidth(selectedColumns.map((column) => column.preferredWidth), ROOT_GAP) : terminalColumns).map((width, index) => ({
|
|
1270
|
-
id: selectedColumns[index].id,
|
|
1271
|
-
width,
|
|
1272
|
-
render: selectedColumns[index].render
|
|
1273
|
-
})).filter((column) => column.width > 0);
|
|
1274
|
-
const taskIds = rows.map((row) => row.task.id);
|
|
1275
|
-
const rowWidth = visibleWidth(columns.map((column) => column.width), ROOT_GAP);
|
|
1276
|
-
return { render: () => /* @__PURE__ */ jsx(Box, {
|
|
1061
|
+
const ProgressRenderer = ({ rows, columns }) => {
|
|
1062
|
+
if (rows.length === 0) return null;
|
|
1063
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
1277
1064
|
flexDirection: "row",
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
marginRight: index < columns.length - 1 ? ROOT_GAP : 0,
|
|
1283
|
-
children: taskIds.map((taskId) => /* @__PURE__ */ jsx(Box, {
|
|
1284
|
-
width: column.width,
|
|
1285
|
-
height: 1,
|
|
1286
|
-
children: column.render(taskId, column.width)
|
|
1287
|
-
}, taskId))
|
|
1288
|
-
}, column.id))
|
|
1289
|
-
}) };
|
|
1065
|
+
columnGap: 1,
|
|
1066
|
+
overflow: "hidden",
|
|
1067
|
+
children: resolveColumns(rows, columns).map((position) => /* @__PURE__ */ jsx(ColumnPosition, { position }, position.index))
|
|
1068
|
+
});
|
|
1290
1069
|
};
|
|
1291
1070
|
|
|
1292
1071
|
//#endregion
|
|
1293
|
-
//#region src/
|
|
1294
|
-
const
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1072
|
+
//#region src/renderer/store/render-snapshot.ts
|
|
1073
|
+
const orderedVisibleTasks = (store) => store.renderOrder.flatMap((row) => {
|
|
1074
|
+
const snapshot = store.tasks.get(row.id);
|
|
1075
|
+
if (!snapshot || snapshot.transient && snapshot.status !== "running") return [];
|
|
1076
|
+
return [{
|
|
1077
|
+
snapshot,
|
|
1078
|
+
depth: row.depth
|
|
1079
|
+
}];
|
|
1080
|
+
});
|
|
1081
|
+
const treeAncestorPrefix = (ancestorHasNextSibling) => ancestorHasNextSibling.slice(1).map((hasNextSibling) => hasNextSibling ? "│ " : " ").join("");
|
|
1082
|
+
const renderTreePrefix = (tree) => {
|
|
1083
|
+
if (tree.depth <= 0) return "";
|
|
1084
|
+
return `${treeAncestorPrefix(tree.ancestorHasNextSibling)}${tree.hasNextSibling ? "├─ " : "└─ "}`;
|
|
1085
|
+
};
|
|
1086
|
+
const arraysEqual = (left, right) => {
|
|
1087
|
+
if (left.length !== right.length) return false;
|
|
1088
|
+
for (let i = 0; i < left.length; i++) if (left[i] !== right[i]) return false;
|
|
1089
|
+
return true;
|
|
1090
|
+
};
|
|
1091
|
+
const sameTreePrefixInputs = (left, right) => left.depth === right.depth && left.hasNextSibling === right.hasNextSibling && arraysEqual(left.ancestorHasNextSibling, right.ancestorHasNextSibling);
|
|
1092
|
+
const sameTree = (left, right) => sameTreePrefixInputs(left, right) && left.hasChildren === right.hasChildren;
|
|
1093
|
+
const deriveRow = (task, tree, previousRow) => {
|
|
1094
|
+
if (previousRow !== void 0 && previousRow.task === task && sameTree(previousRow.tree, tree)) return previousRow.derived;
|
|
1095
|
+
const treePrefix = previousRow !== void 0 && sameTreePrefixInputs(previousRow.tree, tree) ? previousRow.derived.treePrefix : renderTreePrefix(tree);
|
|
1096
|
+
const treePrefixWidth = previousRow !== void 0 && sameTreePrefixInputs(previousRow.tree, tree) ? previousRow.derived.treePrefixWidth : treePrefix.length;
|
|
1097
|
+
const descriptionWidth = previousRow !== void 0 && previousRow.task.description === task.description ? previousRow.derived.descriptionWidth : textWidth(task.description);
|
|
1098
|
+
const isDeterminate = previousRow !== void 0 && previousRow.task.units.total === task.units.total ? previousRow.derived.isDeterminate : task.units.total !== void 0;
|
|
1099
|
+
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;
|
|
1100
|
+
return {
|
|
1101
|
+
treePrefix,
|
|
1102
|
+
treePrefixWidth,
|
|
1103
|
+
descriptionWidth,
|
|
1104
|
+
treePrefixedDescriptionWidth: treePrefixWidth + descriptionWidth,
|
|
1105
|
+
hasRenderableProgress,
|
|
1106
|
+
isDeterminate
|
|
1107
|
+
};
|
|
1108
|
+
};
|
|
1109
|
+
const computeTreeInfo = (ordered, previousRows) => {
|
|
1110
|
+
const hasNextSiblingByIndex = Array.from({ length: ordered.length }, () => false);
|
|
1111
|
+
const seenByDepth = [];
|
|
1112
|
+
for (let i = ordered.length - 1; i >= 0; i--) {
|
|
1113
|
+
const depth = ordered[i].depth;
|
|
1114
|
+
hasNextSiblingByIndex[i] = seenByDepth[depth] ?? false;
|
|
1115
|
+
seenByDepth[depth] = true;
|
|
1116
|
+
seenByDepth.length = depth + 1;
|
|
1117
|
+
}
|
|
1118
|
+
const ancestorStateByDepth = [];
|
|
1119
|
+
const previousRowsByTaskId = new Map(previousRows.map((row) => [row.task.id, row]));
|
|
1120
|
+
return ordered.map((entry, index) => {
|
|
1121
|
+
const depth = entry.depth;
|
|
1122
|
+
ancestorStateByDepth.length = depth;
|
|
1123
|
+
const hasChildren = index + 1 < ordered.length && ordered[index + 1] !== void 0 && ordered[index + 1].depth > depth;
|
|
1124
|
+
const tree = {
|
|
1125
|
+
depth,
|
|
1126
|
+
hasNextSibling: hasNextSiblingByIndex[index] ?? false,
|
|
1127
|
+
hasChildren,
|
|
1128
|
+
ancestorHasNextSibling: [...ancestorStateByDepth]
|
|
1304
1129
|
};
|
|
1305
|
-
|
|
1306
|
-
|
|
1130
|
+
const previousRow = previousRowsByTaskId.get(entry.snapshot.id);
|
|
1131
|
+
ancestorStateByDepth[depth] = hasNextSiblingByIndex[index] ?? false;
|
|
1132
|
+
if (previousRow !== void 0 && previousRow.task === entry.snapshot && sameTree(previousRow.tree, tree)) return previousRow;
|
|
1133
|
+
const derived = deriveRow(entry.snapshot, tree, previousRow);
|
|
1134
|
+
return {
|
|
1135
|
+
task: entry.snapshot,
|
|
1136
|
+
tree: previousRow !== void 0 && sameTree(previousRow.tree, tree) ? previousRow.tree : tree,
|
|
1137
|
+
derived
|
|
1138
|
+
};
|
|
1139
|
+
});
|
|
1140
|
+
};
|
|
1141
|
+
const toRenderSnapshot = (store, previousSnapshot) => {
|
|
1142
|
+
const visibleTasks = orderedVisibleTasks(store);
|
|
1143
|
+
const hasRunningTasks = visibleTasks.some((entry) => entry.snapshot.status === "running");
|
|
1144
|
+
return {
|
|
1145
|
+
rows: computeTreeInfo(visibleTasks, previousSnapshot?.rows ?? []),
|
|
1146
|
+
hasRunningTasks
|
|
1147
|
+
};
|
|
1307
1148
|
};
|
|
1308
1149
|
|
|
1309
1150
|
//#endregion
|
|
1310
|
-
//#region src/
|
|
1311
|
-
const
|
|
1312
|
-
const
|
|
1151
|
+
//#region src/renderer/store/use-progress-render-view.ts
|
|
1152
|
+
const useRenderSnapshot = (storeSnapshot) => {
|
|
1153
|
+
const previousSnapshotRef = useRef(void 0);
|
|
1154
|
+
const renderSnapshot = useMemo(() => toRenderSnapshot(storeSnapshot, previousSnapshotRef.current), [storeSnapshot]);
|
|
1313
1155
|
useEffect(() => {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1156
|
+
previousSnapshotRef.current = renderSnapshot;
|
|
1157
|
+
}, [renderSnapshot]);
|
|
1158
|
+
return renderSnapshot;
|
|
1159
|
+
};
|
|
1160
|
+
const useProgressRenderView = (store) => {
|
|
1161
|
+
const publication = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
1162
|
+
const renderSnapshot = useRenderSnapshot(publication.snapshot);
|
|
1163
|
+
return {
|
|
1164
|
+
publication,
|
|
1165
|
+
renderSnapshot,
|
|
1166
|
+
hasRunningTasks: renderSnapshot.rows.some((row) => row.task.status === "running")
|
|
1167
|
+
};
|
|
1323
1168
|
};
|
|
1324
1169
|
|
|
1325
1170
|
//#endregion
|
|
1326
|
-
//#region src/
|
|
1327
|
-
const
|
|
1328
|
-
const
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1171
|
+
//#region src/renderer/renderer-service.tsx
|
|
1172
|
+
const MAX_FPS = 24;
|
|
1173
|
+
const ProgressRoot = ({ store }) => {
|
|
1174
|
+
const { renderSnapshot, hasRunningTasks, publication } = useProgressRenderView(store);
|
|
1175
|
+
return /* @__PURE__ */ jsx(SpinnerProvider, {
|
|
1176
|
+
active: hasRunningTasks,
|
|
1177
|
+
children: /* @__PURE__ */ jsx(NowProvider, {
|
|
1178
|
+
active: hasRunningTasks,
|
|
1179
|
+
children: /* @__PURE__ */ jsx(ProgressRenderer, {
|
|
1180
|
+
rows: renderSnapshot.rows,
|
|
1181
|
+
columns: publication.snapshot.columns
|
|
1182
|
+
})
|
|
1183
|
+
})
|
|
1184
|
+
});
|
|
1185
|
+
};
|
|
1186
|
+
const makeRendererv2InkRendererService = () => {
|
|
1187
|
+
return { run: (store, stdio) => {
|
|
1188
|
+
const proot = /* @__PURE__ */ jsx(ProgressRoot, { store });
|
|
1189
|
+
return Effect.sync(() => render(proot, {
|
|
1190
|
+
stdout: stdio.stdout,
|
|
1191
|
+
stderr: stdio.stderr,
|
|
1192
|
+
patchConsole: true,
|
|
1193
|
+
exitOnCtrlC: false,
|
|
1194
|
+
debug: false,
|
|
1195
|
+
maxFps: MAX_FPS
|
|
1196
|
+
})).pipe(Effect.flatMap((instance) => Effect.never.pipe(Effect.ensuring(Effect.gen(function* () {
|
|
1197
|
+
store.flush();
|
|
1198
|
+
instance.rerender(proot);
|
|
1199
|
+
yield* Effect.sync(() => {
|
|
1200
|
+
instance.unmount();
|
|
1201
|
+
});
|
|
1202
|
+
})))));
|
|
1203
|
+
} };
|
|
1335
1204
|
};
|
|
1336
1205
|
|
|
1337
1206
|
//#endregion
|
|
1338
1207
|
//#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
1208
|
var InkRenderer = class InkRenderer extends Context.Tag("stromseng.dev/effective-progress/InkRenderer")() {
|
|
1358
|
-
static Default = Layer.succeed(InkRenderer, InkRenderer.of(
|
|
1209
|
+
static Default = Layer.succeed(InkRenderer, InkRenderer.of(makeRendererv2InkRendererService()));
|
|
1359
1210
|
};
|
|
1360
1211
|
|
|
1361
1212
|
//#endregion
|
|
@@ -1370,16 +1221,16 @@ var ProgressStdio = class ProgressStdio extends Context.Tag("stromseng.dev/effec
|
|
|
1370
1221
|
|
|
1371
1222
|
//#endregion
|
|
1372
1223
|
//#region src/services/progress.ts
|
|
1224
|
+
/** Builds the scoped implementation used by `ProgressService.task(...)` without auto-providing services. */
|
|
1373
1225
|
const makeProgressService = Effect.gen(function* () {
|
|
1374
1226
|
const stdio = yield* ProgressStdio;
|
|
1375
1227
|
const inkRenderer = yield* InkRenderer;
|
|
1376
1228
|
const outerConsole = yield* Effect.console;
|
|
1377
|
-
const isTTY = Boolean(stdio.stderr.isTTY);
|
|
1378
1229
|
const store = makeProgressRenderStore();
|
|
1379
1230
|
const currentParentRef = yield* FiberRef.make(Option.none());
|
|
1380
1231
|
const scope = yield* Effect.scope;
|
|
1381
1232
|
const log = (...args) => args.length === 0 ? Effect.void : outerConsole.log(...args);
|
|
1382
|
-
yield* Effect.forkIn(inkRenderer.run(store, stdio
|
|
1233
|
+
yield* Effect.forkIn(inkRenderer.run(store, stdio), scope);
|
|
1383
1234
|
yield* Effect.sleep("0 millis");
|
|
1384
1235
|
const addTask = (options) => Effect.gen(function* () {
|
|
1385
1236
|
const resolvedParentId = options.parentId === void 0 ? yield* FiberRef.get(currentParentRef) : Option.some(options.parentId);
|
|
@@ -1395,7 +1246,27 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
1395
1246
|
const failTask = store.failTask;
|
|
1396
1247
|
const getTask = store.getTask;
|
|
1397
1248
|
const listTasks = store.listTasks;
|
|
1398
|
-
const
|
|
1249
|
+
const setMetadata = store.setMetadata;
|
|
1250
|
+
const getMetadata = store.getMetadata;
|
|
1251
|
+
const makeTaskHandle = (taskId) => ({
|
|
1252
|
+
id: taskId,
|
|
1253
|
+
getMetadata: getMetadata(taskId),
|
|
1254
|
+
setMetadata: (metadata) => setMetadata(taskId, metadata),
|
|
1255
|
+
updateMetadata: (f) => Effect.flatMap(getMetadata(taskId), (current) => setMetadata(taskId, f(current))),
|
|
1256
|
+
incrementSucceeded: (amount) => incrementSucceeded(taskId, amount),
|
|
1257
|
+
incrementFailed: (amount) => incrementFailed(taskId, amount),
|
|
1258
|
+
update: (options) => updateTask(taskId, options),
|
|
1259
|
+
complete: completeTask(taskId),
|
|
1260
|
+
fail: failTask(taskId),
|
|
1261
|
+
getSnapshot: getTask(taskId).pipe(Effect.map(Option.getOrThrow))
|
|
1262
|
+
});
|
|
1263
|
+
const autoFinalizeIfRunning = (taskId, exit) => Effect.gen(function* () {
|
|
1264
|
+
const task = yield* getTask(taskId);
|
|
1265
|
+
if (Option.isNone(task) || task.value.status !== "running") return;
|
|
1266
|
+
if (Exit.isSuccess(exit)) yield* completeTask(taskId);
|
|
1267
|
+
else yield* failTask(taskId);
|
|
1268
|
+
});
|
|
1269
|
+
const scopedTask = dual(2, (effect, options) => Effect.gen(function* () {
|
|
1399
1270
|
const inheritedParentId = yield* FiberRef.get(currentParentRef);
|
|
1400
1271
|
const resolvedParentId = options.parentId === void 0 ? inheritedParentId : Option.some(options.parentId);
|
|
1401
1272
|
const taskId = yield* addTask({
|
|
@@ -1415,17 +1286,29 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
1415
1286
|
log,
|
|
1416
1287
|
getTask,
|
|
1417
1288
|
listTasks,
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1289
|
+
setMetadata,
|
|
1290
|
+
getMetadata,
|
|
1291
|
+
task: dual(2, (effectOrCallback, options) => {
|
|
1292
|
+
if (typeof effectOrCallback === "function") return scopedTask(Effect.gen(function* () {
|
|
1293
|
+
const taskId = yield* Task;
|
|
1294
|
+
const handle = makeTaskHandle(taskId);
|
|
1295
|
+
const exit = yield* Effect.exit(effectOrCallback(handle));
|
|
1296
|
+
yield* autoFinalizeIfRunning(taskId, exit);
|
|
1297
|
+
return yield* Exit.match(exit, {
|
|
1298
|
+
onFailure: Effect.failCause,
|
|
1299
|
+
onSuccess: Effect.succeed
|
|
1300
|
+
});
|
|
1301
|
+
}), options);
|
|
1302
|
+
return scopedTask(Effect.gen(function* () {
|
|
1303
|
+
const taskId = yield* Task;
|
|
1304
|
+
const exit = yield* Effect.exit(effectOrCallback);
|
|
1305
|
+
yield* autoFinalizeIfRunning(taskId, exit);
|
|
1306
|
+
return yield* Exit.match(exit, {
|
|
1307
|
+
onFailure: Effect.failCause,
|
|
1308
|
+
onSuccess: Effect.succeed
|
|
1309
|
+
});
|
|
1310
|
+
}), options);
|
|
1311
|
+
})
|
|
1429
1312
|
};
|
|
1430
1313
|
return Progress.of(service);
|
|
1431
1314
|
});
|
|
@@ -1457,9 +1340,17 @@ const provideProgress = (effect) => Effect.gen(function* () {
|
|
|
1457
1340
|
if (Option.isSome(existing)) return yield* Effect.provideService(effect, Progress, existing.value);
|
|
1458
1341
|
return yield* Effect.scoped(effect.pipe(Effect.provide(Progress.Default)));
|
|
1459
1342
|
});
|
|
1460
|
-
|
|
1343
|
+
/**
|
|
1344
|
+
* Runs an effect inside a task, creating and providing a `Progress` service automatically when one
|
|
1345
|
+
* is not already present in the environment.
|
|
1346
|
+
*
|
|
1347
|
+
* The effect form tracks success and failure from the effect exit. The callback form exposes a
|
|
1348
|
+
* typed `TaskHandle` for task-local updates, typed metadata, and explicit completion or failure,
|
|
1349
|
+
* and otherwise auto-finalizes from the callback exit if the task is still `running`.
|
|
1350
|
+
*/
|
|
1351
|
+
const task = dual(2, (effectOrCallback, options) => {
|
|
1461
1352
|
return provideProgress(Effect.gen(function* () {
|
|
1462
|
-
return yield* (yield* Progress).
|
|
1353
|
+
return yield* (yield* Progress).task(effectOrCallback, options);
|
|
1463
1354
|
}));
|
|
1464
1355
|
});
|
|
1465
1356
|
const wrapEffects = (effects, tap) => Array.isArray(effects) ? effects.map(tap) : Object.fromEntries(Object.entries(effects).map(([k, effect]) => [k, tap(effect)]));
|
|
@@ -1482,10 +1373,14 @@ const isTaskFullyProcessed = (progress, taskId) => Effect.gen(function* () {
|
|
|
1482
1373
|
const { processed, total } = taskOption.value.units;
|
|
1483
1374
|
return total !== void 0 && processed >= total;
|
|
1484
1375
|
});
|
|
1376
|
+
/**
|
|
1377
|
+
* Runs multiple effects under a single parent task and keeps the task counters in sync with the
|
|
1378
|
+
* child effect outcomes.
|
|
1379
|
+
*/
|
|
1485
1380
|
const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* () {
|
|
1486
1381
|
const progress = yield* Progress;
|
|
1487
|
-
return yield* progress.
|
|
1488
|
-
const taskId =
|
|
1382
|
+
return yield* progress.task((handle) => Effect.gen(function* () {
|
|
1383
|
+
const taskId = handle.id;
|
|
1489
1384
|
const exit = yield* Effect.exit(Effect.all(wrapEffects(effects, (effect) => wrapTrackedEffect(progress, taskId, effect)), {
|
|
1490
1385
|
concurrency: options.concurrency,
|
|
1491
1386
|
batching: options.batching,
|
|
@@ -1508,10 +1403,13 @@ const all = dual(2, (effects, options) => provideProgress(Effect.gen(function* (
|
|
|
1508
1403
|
countDisplay: allCountDisplay(options.mode)
|
|
1509
1404
|
});
|
|
1510
1405
|
})));
|
|
1406
|
+
/**
|
|
1407
|
+
* Runs `Effect.forEach` under a single parent task and advances the task counters as items finish.
|
|
1408
|
+
*/
|
|
1511
1409
|
const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(function* () {
|
|
1512
1410
|
const progress = yield* Progress;
|
|
1513
|
-
return yield* progress.
|
|
1514
|
-
const taskId =
|
|
1411
|
+
return yield* progress.task((handle) => Effect.gen(function* () {
|
|
1412
|
+
const taskId = handle.id;
|
|
1515
1413
|
const exit = yield* Effect.exit(Effect.forEach(iterable, (item, index) => wrapTrackedEffect(progress, taskId, f(item, index)), {
|
|
1516
1414
|
concurrency: options.concurrency,
|
|
1517
1415
|
batching: options.batching,
|
|
@@ -1533,4 +1431,4 @@ const forEach = dual(3, (iterable, f, options) => provideProgress(Effect.gen(fun
|
|
|
1533
1431
|
})));
|
|
1534
1432
|
|
|
1535
1433
|
//#endregion
|
|
1536
|
-
export { Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskRemovedEvent, TaskSnapshot, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|
|
1434
|
+
export { columns_exports as Columns, Progress, ProgressStdio, ProgressTaskEventSchema, Task, TaskAddedEvent, TaskAdvancedEvent, TaskCompletedEvent, TaskCountDisplaySchema, TaskFailedEvent, TaskId, TaskRemovedEvent, TaskSnapshot, TaskSnapshotSchema, TaskStatusSchema, TaskUnitsSchema, TaskUpdatedEvent, all, decodeProgressTaskEvent, forEach, task };
|