105 lines
2.4 KiB
Dart
105 lines
2.4 KiB
Dart
import 'dart:async';
|
|
|
|
enum StepState { pending, active, done, failed, skipped }
|
|
|
|
class DeployStep {
|
|
final String key;
|
|
final String label;
|
|
StepState state;
|
|
double? progress; // 0..1 within this step, when meaningful
|
|
|
|
DeployStep(
|
|
this.key,
|
|
this.label, {
|
|
this.state = StepState.pending,
|
|
this.progress,
|
|
});
|
|
}
|
|
|
|
/// Tracks an ordered list of steps for a build or deploy operation and streams
|
|
/// updates so the UI can render a live stepper with per-step progress.
|
|
class StepTracker {
|
|
final _controller = StreamController<List<DeployStep>>.broadcast();
|
|
Stream<List<DeployStep>> get stream => _controller.stream;
|
|
final List<DeployStep> steps = [];
|
|
|
|
void reset(List<(String, String)> defs) {
|
|
steps
|
|
..clear()
|
|
..addAll(defs.map((d) => DeployStep(d.$1, d.$2)));
|
|
_emit();
|
|
}
|
|
|
|
DeployStep? _find(String key) {
|
|
for (final s in steps) {
|
|
if (s.key == key) return s;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void active(String key) {
|
|
final s = _find(key);
|
|
if (s == null) return;
|
|
// mark earlier active/pending steps done as we move forward
|
|
for (final step in steps) {
|
|
if (step == s) break;
|
|
if (step.state == StepState.active) step.state = StepState.done;
|
|
}
|
|
s.state = StepState.active;
|
|
s.progress = null;
|
|
_emit();
|
|
}
|
|
|
|
void progress(String key, double? p) {
|
|
final s = _find(key);
|
|
if (s == null) return;
|
|
s.progress = p;
|
|
_emit();
|
|
}
|
|
|
|
void done(String key) => _set(key, StepState.done);
|
|
void fail(String key) => _set(key, StepState.failed);
|
|
void skip(String key) => _set(key, StepState.skipped);
|
|
|
|
void _set(String key, StepState state) {
|
|
final s = _find(key);
|
|
if (s == null) return;
|
|
s.state = state;
|
|
s.progress = null;
|
|
_emit();
|
|
}
|
|
|
|
/// Marks the currently-active step done (used before moving on).
|
|
void completeActive() {
|
|
for (final s in steps) {
|
|
if (s.state == StepState.active) s.state = StepState.done;
|
|
}
|
|
_emit();
|
|
}
|
|
|
|
/// Any still-pending/active steps become failed (on an error).
|
|
void failRemaining() {
|
|
for (final s in steps) {
|
|
if (s.state == StepState.active) {
|
|
s.state = StepState.failed;
|
|
} else if (s.state == StepState.pending) {
|
|
s.state = StepState.skipped;
|
|
}
|
|
}
|
|
_emit();
|
|
}
|
|
|
|
void clear() {
|
|
steps.clear();
|
|
_emit();
|
|
}
|
|
|
|
void _emit() {
|
|
if (!_controller.isClosed) {
|
|
_controller.add(List.unmodifiable(steps));
|
|
}
|
|
}
|
|
|
|
void dispose() => _controller.close();
|
|
}
|