effective-progress 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -40
- package/package.json +1 -1
- package/src/api.ts +20 -7
- package/src/index.ts +2 -1
- package/src/renderer.ts +1155 -214
- package/src/runtime.ts +106 -32
- package/src/terminal.ts +12 -10
- package/src/theme.ts +87 -0
- package/src/types.ts +12 -11
- package/src/colors.ts +0 -156
package/src/runtime.ts
CHANGED
|
@@ -4,8 +4,9 @@ import { mergeWith } from "es-toolkit/object";
|
|
|
4
4
|
import { formatWithOptions } from "node:util";
|
|
5
5
|
import type { PartialDeep } from "type-fest";
|
|
6
6
|
import { makeProgressConsole } from "./console";
|
|
7
|
-
import {
|
|
7
|
+
import { BuildStage, ColorStage, FrameRenderer, ShrinkStage } from "./renderer";
|
|
8
8
|
import { ProgressTerminal } from "./terminal";
|
|
9
|
+
import { Theme, type ThemeService } from "./theme";
|
|
9
10
|
import type {
|
|
10
11
|
AddTaskOptions,
|
|
11
12
|
ProgressService,
|
|
@@ -87,6 +88,19 @@ const updatedSnapshot = (snapshot: TaskSnapshot, options: UpdateTaskOptions): Ta
|
|
|
87
88
|
});
|
|
88
89
|
};
|
|
89
90
|
|
|
91
|
+
const withTransient = (snapshot: TaskSnapshot, transient: boolean): TaskSnapshot =>
|
|
92
|
+
new TaskSnapshot({
|
|
93
|
+
id: snapshot.id,
|
|
94
|
+
parentId: snapshot.parentId,
|
|
95
|
+
description: snapshot.description,
|
|
96
|
+
status: snapshot.status,
|
|
97
|
+
transient,
|
|
98
|
+
units: snapshot.units,
|
|
99
|
+
config: snapshot.config,
|
|
100
|
+
startedAt: snapshot.startedAt,
|
|
101
|
+
completedAt: snapshot.completedAt,
|
|
102
|
+
});
|
|
103
|
+
|
|
90
104
|
const findInsertionIndex = (
|
|
91
105
|
renderOrder: ReadonlyArray<RenderRow>,
|
|
92
106
|
parentId: TaskId | null,
|
|
@@ -119,7 +133,6 @@ const removeFromRenderOrder = (
|
|
|
119
133
|
const makeProgressService = Effect.gen(function* () {
|
|
120
134
|
const rendererConfigOption = yield* Effect.serviceOption(RendererConfig);
|
|
121
135
|
const progressBarConfigOption = yield* Effect.serviceOption(ProgressBarConfig);
|
|
122
|
-
|
|
123
136
|
const rendererConfig = decodeRendererConfigSync(
|
|
124
137
|
mergeConfig(
|
|
125
138
|
defaultRendererConfig,
|
|
@@ -133,6 +146,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
133
146
|
),
|
|
134
147
|
);
|
|
135
148
|
const terminal = yield* ProgressTerminal;
|
|
149
|
+
const frameRenderer = yield* FrameRenderer;
|
|
136
150
|
const isTTY = yield* terminal.isTTY;
|
|
137
151
|
const maxRetainedLogLines = Math.max(0, Math.floor(rendererConfig.maxLogLines ?? 0));
|
|
138
152
|
|
|
@@ -140,6 +154,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
140
154
|
const storeRef = yield* Ref.make<TaskStore>({
|
|
141
155
|
tasks: new Map<TaskId, TaskSnapshot>(),
|
|
142
156
|
renderOrder: [],
|
|
157
|
+
themes: new Map<TaskId, ThemeService>(),
|
|
143
158
|
});
|
|
144
159
|
const logsRef = yield* Ref.make<ReadonlyArray<string>>([]);
|
|
145
160
|
const pendingLogsRef = yield* Ref.make<ReadonlyArray<string>>([]);
|
|
@@ -148,7 +163,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
148
163
|
const scope = yield* Effect.scope;
|
|
149
164
|
|
|
150
165
|
yield* Effect.forkIn(
|
|
151
|
-
|
|
166
|
+
frameRenderer.run({
|
|
152
167
|
storeRef,
|
|
153
168
|
logsRef,
|
|
154
169
|
pendingLogsRef,
|
|
@@ -157,7 +172,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
157
172
|
isTTY,
|
|
158
173
|
rendererConfig,
|
|
159
174
|
maxRetainedLogLines,
|
|
160
|
-
),
|
|
175
|
+
}),
|
|
161
176
|
scope,
|
|
162
177
|
);
|
|
163
178
|
|
|
@@ -169,6 +184,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
169
184
|
options.parentId === undefined
|
|
170
185
|
? yield* FiberRef.get(currentParentRef)
|
|
171
186
|
: Option.some(options.parentId);
|
|
187
|
+
const themeOption = yield* Effect.serviceOption(Theme);
|
|
172
188
|
const taskId = TaskId(yield* Ref.updateAndGet(nextTaskIdRef, (id) => id + 1));
|
|
173
189
|
const units =
|
|
174
190
|
options.total === undefined || options.total <= 0
|
|
@@ -190,7 +206,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
190
206
|
parentId: parentIdValue,
|
|
191
207
|
description: options.description,
|
|
192
208
|
status: "running",
|
|
193
|
-
transient: options.transient ?? false,
|
|
209
|
+
transient: parentSnapshot?.transient ?? options.transient ?? false,
|
|
194
210
|
units,
|
|
195
211
|
config: resolvedProgressBarConfig,
|
|
196
212
|
startedAt: now,
|
|
@@ -203,7 +219,11 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
203
219
|
const { index, depth } = findInsertionIndex(s.renderOrder, parentIdValue);
|
|
204
220
|
const nextOrder = [...s.renderOrder];
|
|
205
221
|
nextOrder.splice(index, 0, { id: taskId, depth });
|
|
206
|
-
|
|
222
|
+
const nextThemes = new Map(s.themes);
|
|
223
|
+
if (Option.isSome(themeOption)) {
|
|
224
|
+
nextThemes.set(taskId, themeOption.value);
|
|
225
|
+
}
|
|
226
|
+
return { tasks: nextTasks, renderOrder: nextOrder, themes: nextThemes };
|
|
207
227
|
});
|
|
208
228
|
yield* markDirty;
|
|
209
229
|
|
|
@@ -215,8 +235,32 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
215
235
|
const snapshot = store.tasks.get(taskId);
|
|
216
236
|
if (!snapshot) return store;
|
|
217
237
|
const nextTasks = new Map(store.tasks);
|
|
218
|
-
|
|
219
|
-
|
|
238
|
+
const nextSnapshot = updatedSnapshot(snapshot, options);
|
|
239
|
+
nextTasks.set(taskId, nextSnapshot);
|
|
240
|
+
|
|
241
|
+
if (options.transient !== undefined) {
|
|
242
|
+
for (const [candidateId, candidate] of store.tasks.entries()) {
|
|
243
|
+
if (candidateId === taskId) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let parentId = candidate.parentId;
|
|
248
|
+
let isDescendant = false;
|
|
249
|
+
while (parentId !== null) {
|
|
250
|
+
if (parentId === taskId) {
|
|
251
|
+
isDescendant = true;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
parentId = store.tasks.get(parentId)?.parentId ?? null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (isDescendant) {
|
|
258
|
+
nextTasks.set(candidateId, withTransient(candidate, nextSnapshot.transient));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder, themes: store.themes };
|
|
220
264
|
}).pipe(Effect.zipRight(markDirty));
|
|
221
265
|
|
|
222
266
|
const advanceTask = (taskId: TaskId, amount = 1) =>
|
|
@@ -250,7 +294,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
250
294
|
}),
|
|
251
295
|
);
|
|
252
296
|
|
|
253
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
297
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder, themes: store.themes };
|
|
254
298
|
}).pipe(Effect.zipRight(markDirty));
|
|
255
299
|
|
|
256
300
|
const completeTask = (taskId: TaskId) =>
|
|
@@ -263,7 +307,13 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
263
307
|
const nextTasks = new Map(store.tasks);
|
|
264
308
|
if (snapshot.transient) {
|
|
265
309
|
nextTasks.delete(taskId);
|
|
266
|
-
|
|
310
|
+
const nextThemes = new Map(store.themes);
|
|
311
|
+
nextThemes.delete(taskId);
|
|
312
|
+
return {
|
|
313
|
+
tasks: nextTasks,
|
|
314
|
+
renderOrder: removeFromRenderOrder(store.renderOrder, taskId),
|
|
315
|
+
themes: nextThemes,
|
|
316
|
+
};
|
|
267
317
|
}
|
|
268
318
|
|
|
269
319
|
nextTasks.set(
|
|
@@ -286,7 +336,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
286
336
|
completedAt: now,
|
|
287
337
|
}),
|
|
288
338
|
);
|
|
289
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
339
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder, themes: store.themes };
|
|
290
340
|
});
|
|
291
341
|
yield* markDirty;
|
|
292
342
|
});
|
|
@@ -301,7 +351,13 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
301
351
|
const nextTasks = new Map(store.tasks);
|
|
302
352
|
if (snapshot.transient) {
|
|
303
353
|
nextTasks.delete(taskId);
|
|
304
|
-
|
|
354
|
+
const nextThemes = new Map(store.themes);
|
|
355
|
+
nextThemes.delete(taskId);
|
|
356
|
+
return {
|
|
357
|
+
tasks: nextTasks,
|
|
358
|
+
renderOrder: removeFromRenderOrder(store.renderOrder, taskId),
|
|
359
|
+
themes: nextThemes,
|
|
360
|
+
};
|
|
305
361
|
}
|
|
306
362
|
|
|
307
363
|
nextTasks.set(
|
|
@@ -318,7 +374,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
318
374
|
completedAt: now,
|
|
319
375
|
}),
|
|
320
376
|
);
|
|
321
|
-
return { tasks: nextTasks, renderOrder: store.renderOrder };
|
|
377
|
+
return { tasks: nextTasks, renderOrder: store.renderOrder, themes: store.themes };
|
|
322
378
|
});
|
|
323
379
|
yield* markDirty;
|
|
324
380
|
});
|
|
@@ -371,7 +427,7 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
371
427
|
const taskId = yield* addTask({
|
|
372
428
|
...options,
|
|
373
429
|
parentId: Option.isSome(resolvedParentId) ? resolvedParentId.value : undefined,
|
|
374
|
-
transient: options.transient
|
|
430
|
+
transient: options.transient,
|
|
375
431
|
});
|
|
376
432
|
|
|
377
433
|
return yield* Effect.locally(
|
|
@@ -424,22 +480,40 @@ const makeProgressService = Effect.gen(function* () {
|
|
|
424
480
|
return Progress.of(service);
|
|
425
481
|
});
|
|
426
482
|
|
|
427
|
-
export class Progress extends Context.Tag("stromseng.dev/Progress")<
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
483
|
+
export class Progress extends Context.Tag("stromseng.dev/effective-progress/Progress")<
|
|
484
|
+
Progress,
|
|
485
|
+
ProgressService
|
|
486
|
+
>() {
|
|
487
|
+
static readonly Default = Layer.unwrapEffect(
|
|
488
|
+
Effect.gen(function* () {
|
|
489
|
+
const themeOption = yield* Effect.serviceOption(Theme);
|
|
490
|
+
const terminalOption = yield* Effect.serviceOption(ProgressTerminal);
|
|
491
|
+
const buildStageOption = yield* Effect.serviceOption(BuildStage);
|
|
492
|
+
const shrinkStageOption = yield* Effect.serviceOption(ShrinkStage);
|
|
493
|
+
const colorStageOption = yield* Effect.serviceOption(ColorStage);
|
|
494
|
+
const frameRendererOption = yield* Effect.serviceOption(FrameRenderer);
|
|
495
|
+
let layer: Layer.Layer<Progress, never, any> = Layer.scoped(Progress, makeProgressService);
|
|
496
|
+
|
|
497
|
+
if (Option.isNone(frameRendererOption)) {
|
|
498
|
+
layer = layer.pipe(Layer.provide(FrameRenderer.Default));
|
|
499
|
+
}
|
|
500
|
+
if (Option.isNone(colorStageOption)) {
|
|
501
|
+
layer = layer.pipe(Layer.provide(ColorStage.Default));
|
|
502
|
+
}
|
|
503
|
+
if (Option.isNone(shrinkStageOption)) {
|
|
504
|
+
layer = layer.pipe(Layer.provide(ShrinkStage.Default));
|
|
505
|
+
}
|
|
506
|
+
if (Option.isNone(buildStageOption)) {
|
|
507
|
+
layer = layer.pipe(Layer.provide(BuildStage.Default));
|
|
508
|
+
}
|
|
509
|
+
if (Option.isNone(themeOption)) {
|
|
510
|
+
layer = layer.pipe(Layer.provide(Theme.Default));
|
|
511
|
+
}
|
|
512
|
+
if (Option.isNone(terminalOption)) {
|
|
513
|
+
layer = layer.pipe(Layer.provide(ProgressTerminal.Default));
|
|
514
|
+
}
|
|
442
515
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
516
|
+
return layer as Layer.Layer<Progress, never, never>;
|
|
517
|
+
}),
|
|
518
|
+
);
|
|
519
|
+
}
|
package/src/terminal.ts
CHANGED
|
@@ -42,18 +42,20 @@ const withRawInputCapture: ProgressTerminalService["withRawInputCapture"] = (eff
|
|
|
42
42
|
);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
const defaultTerminalService: ProgressTerminalService = {
|
|
46
|
+
isTTY: Effect.sync(() => Boolean(process.stderr.isTTY)),
|
|
47
|
+
stderrRows: Effect.sync(() => process.stderr.rows),
|
|
48
|
+
stderrColumns: Effect.sync(() => process.stderr.columns),
|
|
49
|
+
writeStderr: (text) =>
|
|
50
|
+
Effect.sync(() => {
|
|
51
|
+
process.stderr.write(text);
|
|
52
|
+
}),
|
|
53
|
+
withRawInputCapture,
|
|
54
|
+
};
|
|
55
|
+
|
|
45
56
|
export class ProgressTerminal extends Context.Tag("stromseng.dev/ProgressTerminal")<
|
|
46
57
|
ProgressTerminal,
|
|
47
58
|
ProgressTerminalService
|
|
48
59
|
>() {
|
|
49
|
-
static readonly Default = Layer.succeed(ProgressTerminal,
|
|
50
|
-
isTTY: Effect.sync(() => Boolean(process.stderr.isTTY)),
|
|
51
|
-
stderrRows: Effect.sync(() => process.stderr.rows),
|
|
52
|
-
stderrColumns: Effect.sync(() => process.stderr.columns),
|
|
53
|
-
writeStderr: (text) =>
|
|
54
|
-
Effect.sync(() => {
|
|
55
|
-
process.stderr.write(text);
|
|
56
|
-
}),
|
|
57
|
-
withRawInputCapture,
|
|
58
|
-
} satisfies ProgressTerminalService);
|
|
60
|
+
static readonly Default = Layer.succeed(ProgressTerminal, defaultTerminalService);
|
|
59
61
|
}
|
package/src/theme.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import chalk, { type ChalkInstance } from "chalk";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
|
|
4
|
+
export type ThemeRole =
|
|
5
|
+
| "plain"
|
|
6
|
+
| "barFill"
|
|
7
|
+
| "barEmpty"
|
|
8
|
+
| "barBracket"
|
|
9
|
+
| "spinner"
|
|
10
|
+
| "statusDone"
|
|
11
|
+
| "statusFailed"
|
|
12
|
+
| "text"
|
|
13
|
+
| "units"
|
|
14
|
+
| "eta"
|
|
15
|
+
| "elapsed"
|
|
16
|
+
| "treeConnector";
|
|
17
|
+
|
|
18
|
+
export type ThemeStyle = (text: string) => string;
|
|
19
|
+
|
|
20
|
+
export interface ThemeService {
|
|
21
|
+
readonly styles: Readonly<Record<ThemeRole, ThemeStyle>>;
|
|
22
|
+
readonly depthPalette?: (depth: number, role: ThemeRole) => ThemeStyle | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class Theme extends Context.Tag("stromseng.dev/effective-progress/Theme")<
|
|
26
|
+
Theme,
|
|
27
|
+
ThemeService
|
|
28
|
+
>() {
|
|
29
|
+
static readonly Default = Layer.succeed(
|
|
30
|
+
Theme,
|
|
31
|
+
Theme.of({
|
|
32
|
+
styles: {
|
|
33
|
+
plain: (text) => text,
|
|
34
|
+
barFill: chalk.blue,
|
|
35
|
+
barEmpty: chalk.white.dim,
|
|
36
|
+
barBracket: chalk.white.dim,
|
|
37
|
+
spinner: chalk.yellow,
|
|
38
|
+
statusDone: chalk.green,
|
|
39
|
+
statusFailed: chalk.red,
|
|
40
|
+
text: (text) => text,
|
|
41
|
+
units: chalk.whiteBright,
|
|
42
|
+
eta: chalk.gray,
|
|
43
|
+
elapsed: chalk.gray,
|
|
44
|
+
treeConnector: chalk.gray,
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
static readonly Rainbow = Layer.unwrapEffect(
|
|
50
|
+
Effect.gen(function* () {
|
|
51
|
+
let colorNumber = 17;
|
|
52
|
+
yield* Effect.fork(
|
|
53
|
+
Effect.gen(function* () {
|
|
54
|
+
while (true) {
|
|
55
|
+
colorNumber = (colorNumber + 1) % 256;
|
|
56
|
+
yield* Effect.sleep("200 millis");
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const dynamic =
|
|
62
|
+
(instance: ChalkInstance): ThemeStyle =>
|
|
63
|
+
(text) =>
|
|
64
|
+
instance.ansi256(colorNumber)(text);
|
|
65
|
+
|
|
66
|
+
return Layer.succeed(
|
|
67
|
+
Theme,
|
|
68
|
+
Theme.of({
|
|
69
|
+
styles: {
|
|
70
|
+
plain: (text) => text,
|
|
71
|
+
barFill: dynamic(chalk),
|
|
72
|
+
barEmpty: dynamic(chalk.dim),
|
|
73
|
+
barBracket: dynamic(chalk),
|
|
74
|
+
spinner: dynamic(chalk),
|
|
75
|
+
statusDone: dynamic(chalk.bold),
|
|
76
|
+
statusFailed: dynamic(chalk.bold),
|
|
77
|
+
text: dynamic(chalk),
|
|
78
|
+
units: dynamic(chalk),
|
|
79
|
+
eta: dynamic(chalk),
|
|
80
|
+
elapsed: dynamic(chalk),
|
|
81
|
+
treeConnector: dynamic(chalk),
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Brand, Context, Effect, Option, Schema } from "effect";
|
|
2
2
|
import type { PartialDeep } from "type-fest";
|
|
3
|
-
import {
|
|
3
|
+
import type { ThemeService } from "./theme";
|
|
4
4
|
|
|
5
5
|
export const RendererConfigSchema = Schema.Struct({
|
|
6
6
|
disableUserInput: Schema.Boolean,
|
|
7
7
|
renderIntervalMillis: Schema.Number,
|
|
8
8
|
maxLogLines: Schema.optional(Schema.Number),
|
|
9
9
|
nonTtyUpdateStep: Schema.Number,
|
|
10
|
+
determinateTaskLayout: Schema.Literal("single-line", "two-lines"),
|
|
11
|
+
maxTaskWidth: Schema.optional(Schema.Number),
|
|
10
12
|
});
|
|
11
13
|
export type RendererConfigShape = typeof RendererConfigSchema.Type;
|
|
12
14
|
export const decodeRendererConfigSync = Schema.decodeUnknownSync(RendererConfigSchema);
|
|
@@ -18,7 +20,6 @@ export const ProgressBarConfigSchema = Schema.Struct({
|
|
|
18
20
|
emptyChar: Schema.String,
|
|
19
21
|
leftBracket: Schema.String,
|
|
20
22
|
rightBracket: Schema.String,
|
|
21
|
-
colors: ProgressBarColorsSchema,
|
|
22
23
|
});
|
|
23
24
|
export type ProgressBarConfigShape = typeof ProgressBarConfigSchema.Type;
|
|
24
25
|
export const decodeProgressBarConfigSync = Schema.decodeUnknownSync(ProgressBarConfigSchema);
|
|
@@ -28,27 +29,26 @@ export const defaultRendererConfig: RendererConfigShape = {
|
|
|
28
29
|
renderIntervalMillis: 100, // 10 FPS
|
|
29
30
|
maxLogLines: 0,
|
|
30
31
|
nonTtyUpdateStep: 5,
|
|
32
|
+
determinateTaskLayout: "single-line",
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
export const defaultProgressBarConfig: ProgressBarConfigShape = {
|
|
34
|
-
spinnerFrames: ["
|
|
35
|
-
barWidth:
|
|
36
|
+
spinnerFrames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
37
|
+
barWidth: 40,
|
|
36
38
|
fillChar: "━",
|
|
37
39
|
emptyChar: "─",
|
|
38
40
|
leftBracket: "",
|
|
39
41
|
rightBracket: "",
|
|
40
|
-
colors: defaultProgressBarColors,
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
export class RendererConfig extends Context.Tag("stromseng.dev/RendererConfig")<
|
|
44
|
+
export class RendererConfig extends Context.Tag("stromseng.dev/effective-progress/RendererConfig")<
|
|
44
45
|
RendererConfig,
|
|
45
46
|
PartialDeep<RendererConfigShape>
|
|
46
47
|
>() {}
|
|
47
48
|
|
|
48
|
-
export class ProgressBarConfig extends Context.Tag(
|
|
49
|
-
ProgressBarConfig,
|
|
50
|
-
|
|
51
|
-
>() {}
|
|
49
|
+
export class ProgressBarConfig extends Context.Tag(
|
|
50
|
+
"stromseng.dev/effective-progress/ProgressBarConfig",
|
|
51
|
+
)<ProgressBarConfig, PartialDeep<ProgressBarConfigShape>>() {}
|
|
52
52
|
|
|
53
53
|
const TaskIdSchema = Schema.Number.pipe(Schema.brand("TaskId"));
|
|
54
54
|
|
|
@@ -115,6 +115,7 @@ export interface RenderRow {
|
|
|
115
115
|
export interface TaskStore {
|
|
116
116
|
readonly tasks: Map<TaskId, TaskSnapshot>;
|
|
117
117
|
readonly renderOrder: ReadonlyArray<RenderRow>;
|
|
118
|
+
readonly themes: Map<TaskId, ThemeService>;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
export interface ProgressService {
|
|
@@ -146,7 +147,7 @@ export interface ProgressService {
|
|
|
146
147
|
};
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
export class Task extends Context.Tag("stromseng.dev/Task")<Task, TaskId>() {}
|
|
150
|
+
export class Task extends Context.Tag("stromseng.dev/effective-progress/Task")<Task, TaskId>() {}
|
|
150
151
|
|
|
151
152
|
export class TaskAddedEvent extends Schema.TaggedClass<TaskAddedEvent>()("TaskAdded", {
|
|
152
153
|
taskId: TaskIdSchema,
|
package/src/colors.ts
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import type { ChalkInstance } from "chalk";
|
|
3
|
-
import { Schema } from "effect";
|
|
4
|
-
|
|
5
|
-
export const StyleModifierSchema = Schema.Literal(
|
|
6
|
-
"bold",
|
|
7
|
-
"dim",
|
|
8
|
-
"italic",
|
|
9
|
-
"underline",
|
|
10
|
-
"inverse",
|
|
11
|
-
"hidden",
|
|
12
|
-
"strikethrough",
|
|
13
|
-
);
|
|
14
|
-
export type StyleModifier = typeof StyleModifierSchema.Type;
|
|
15
|
-
|
|
16
|
-
export const NamedColorSchema = Schema.Literal(
|
|
17
|
-
"black",
|
|
18
|
-
"red",
|
|
19
|
-
"green",
|
|
20
|
-
"yellow",
|
|
21
|
-
"blue",
|
|
22
|
-
"magenta",
|
|
23
|
-
"cyan",
|
|
24
|
-
"white",
|
|
25
|
-
"blackBright",
|
|
26
|
-
"redBright",
|
|
27
|
-
"greenBright",
|
|
28
|
-
"yellowBright",
|
|
29
|
-
"blueBright",
|
|
30
|
-
"magentaBright",
|
|
31
|
-
"cyanBright",
|
|
32
|
-
"whiteBright",
|
|
33
|
-
);
|
|
34
|
-
export type NamedColor = typeof NamedColorSchema.Type;
|
|
35
|
-
|
|
36
|
-
const HexColorSchema = Schema.String.pipe(Schema.pattern(/^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/));
|
|
37
|
-
const ColorChannelSchema = Schema.Number.pipe(
|
|
38
|
-
Schema.int(),
|
|
39
|
-
Schema.greaterThanOrEqualTo(0),
|
|
40
|
-
Schema.lessThanOrEqualTo(255),
|
|
41
|
-
);
|
|
42
|
-
const Ansi256Schema = Schema.Number.pipe(
|
|
43
|
-
Schema.int(),
|
|
44
|
-
Schema.greaterThanOrEqualTo(0),
|
|
45
|
-
Schema.lessThanOrEqualTo(255),
|
|
46
|
-
);
|
|
47
|
-
const ModifiersSchema = Schema.optional(Schema.Array(StyleModifierSchema));
|
|
48
|
-
|
|
49
|
-
export const NamedColorStyleSchema = Schema.Struct({
|
|
50
|
-
kind: Schema.Literal("named"),
|
|
51
|
-
value: NamedColorSchema,
|
|
52
|
-
modifiers: ModifiersSchema,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
export const HexColorStyleSchema = Schema.Struct({
|
|
56
|
-
kind: Schema.Literal("hex"),
|
|
57
|
-
value: HexColorSchema,
|
|
58
|
-
modifiers: ModifiersSchema,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
export const RgbColorStyleSchema = Schema.Struct({
|
|
62
|
-
kind: Schema.Literal("rgb"),
|
|
63
|
-
value: Schema.Struct({
|
|
64
|
-
r: ColorChannelSchema,
|
|
65
|
-
g: ColorChannelSchema,
|
|
66
|
-
b: ColorChannelSchema,
|
|
67
|
-
}),
|
|
68
|
-
modifiers: ModifiersSchema,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
export const Ansi256ColorStyleSchema = Schema.Struct({
|
|
72
|
-
kind: Schema.Literal("ansi256"),
|
|
73
|
-
value: Ansi256Schema,
|
|
74
|
-
modifiers: ModifiersSchema,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
export const ColorStyleSchema = Schema.Union(
|
|
78
|
-
NamedColorStyleSchema,
|
|
79
|
-
HexColorStyleSchema,
|
|
80
|
-
RgbColorStyleSchema,
|
|
81
|
-
Ansi256ColorStyleSchema,
|
|
82
|
-
);
|
|
83
|
-
export type ColorStyle = typeof ColorStyleSchema.Type;
|
|
84
|
-
|
|
85
|
-
export const ProgressBarColorsSchema = Schema.Struct({
|
|
86
|
-
fill: ColorStyleSchema,
|
|
87
|
-
empty: ColorStyleSchema,
|
|
88
|
-
brackets: ColorStyleSchema,
|
|
89
|
-
percent: ColorStyleSchema,
|
|
90
|
-
spinner: ColorStyleSchema,
|
|
91
|
-
done: ColorStyleSchema,
|
|
92
|
-
failed: ColorStyleSchema,
|
|
93
|
-
});
|
|
94
|
-
export type ProgressBarColors = typeof ProgressBarColorsSchema.Type;
|
|
95
|
-
|
|
96
|
-
export const defaultProgressBarColors: ProgressBarColors = {
|
|
97
|
-
fill: { kind: "named", value: "cyan" },
|
|
98
|
-
empty: { kind: "named", value: "white", modifiers: ["dim"] },
|
|
99
|
-
brackets: { kind: "named", value: "white", modifiers: ["dim"] },
|
|
100
|
-
percent: { kind: "named", value: "white", modifiers: ["bold"] },
|
|
101
|
-
spinner: { kind: "named", value: "yellow" },
|
|
102
|
-
done: { kind: "named", value: "green" },
|
|
103
|
-
failed: { kind: "named", value: "red" },
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const applyModifier = (instance: ChalkInstance, modifier: StyleModifier): ChalkInstance =>
|
|
107
|
-
instance[modifier];
|
|
108
|
-
|
|
109
|
-
const applyNamedColor = (instance: ChalkInstance, color: NamedColor): ChalkInstance =>
|
|
110
|
-
instance[color];
|
|
111
|
-
|
|
112
|
-
const resolveBaseStyle = (style: ColorStyle): ChalkInstance => {
|
|
113
|
-
switch (style.kind) {
|
|
114
|
-
case "named":
|
|
115
|
-
return applyNamedColor(chalk, style.value);
|
|
116
|
-
case "hex":
|
|
117
|
-
return chalk.hex(style.value);
|
|
118
|
-
case "rgb":
|
|
119
|
-
return chalk.rgb(style.value.r, style.value.g, style.value.b);
|
|
120
|
-
case "ansi256":
|
|
121
|
-
return chalk.ansi256(style.value);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const applyModifiers = (instance: ChalkInstance, modifiers: ReadonlyArray<StyleModifier>) => {
|
|
126
|
-
let styled = instance;
|
|
127
|
-
for (const modifier of modifiers) {
|
|
128
|
-
styled = applyModifier(styled, modifier);
|
|
129
|
-
}
|
|
130
|
-
return styled;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
export const applyColorStyle = (style: ColorStyle, text: string): string => {
|
|
134
|
-
const styled = applyModifiers(resolveBaseStyle(style), style.modifiers ?? []);
|
|
135
|
-
return styled(text);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
export interface CompiledProgressBarColors {
|
|
139
|
-
readonly fill: (text: string) => string;
|
|
140
|
-
readonly empty: (text: string) => string;
|
|
141
|
-
readonly brackets: (text: string) => string;
|
|
142
|
-
readonly percent: (text: string) => string;
|
|
143
|
-
readonly spinner: (text: string) => string;
|
|
144
|
-
readonly done: (text: string) => string;
|
|
145
|
-
readonly failed: (text: string) => string;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export const compileProgressBarColors = (colors: ProgressBarColors): CompiledProgressBarColors => ({
|
|
149
|
-
fill: (text) => applyColorStyle(colors.fill, text),
|
|
150
|
-
empty: (text) => applyColorStyle(colors.empty, text),
|
|
151
|
-
brackets: (text) => applyColorStyle(colors.brackets, text),
|
|
152
|
-
percent: (text) => applyColorStyle(colors.percent, text),
|
|
153
|
-
spinner: (text) => applyColorStyle(colors.spinner, text),
|
|
154
|
-
done: (text) => applyColorStyle(colors.done, text),
|
|
155
|
-
failed: (text) => applyColorStyle(colors.failed, text),
|
|
156
|
-
});
|