diff --git a/mac/Sources/Byre/Workflow.swift b/mac/Sources/Byre/Workflow.swift index 2886b94..c744a2e 100644 --- a/mac/Sources/Byre/Workflow.swift +++ b/mac/Sources/Byre/Workflow.swift @@ -54,6 +54,15 @@ private func stageLabel(_ s: RunStage) -> String { return "waiting to start" } +@MainActor +private func taskLink(_ taskId: String, _ label: String, bold: Bool = false) -> AttributedString { + var a = AttributedString(label) + a.link = AppLink.task(taskId) + a.foregroundColor = Palette.accent + a.font = Fonts.font(13, weight: bold ? 700 : 400) + return a +} + private func stateColor(_ state: String) -> Color { switch state { case "running": Palette.accent @@ -195,20 +204,37 @@ private struct StageCard: View { let stage: RunStage var body: some View { + let only = stage.attempts.count == 1 ? stage.attempts[0] : nil VStack(alignment: .leading, spacing: 2) { HStack(alignment: .firstTextBaseline, spacing: 10) { - Text(stage.name).font(Fonts.font(13, weight: 700)) + if let only { + Text(taskLink(only.taskId, stage.name, bold: true)) + } else { + Text(stage.name).font(Fonts.font(13, weight: 700)) + } Text(stageLabel(stage)) .font(Fonts.font(11)) .foregroundStyle(stateColor(stage.state.rawValue)) + if only?.discarding == true { + Text("discarding…").font(Fonts.font(11)).foregroundStyle(Palette.muted) + } } - Text(stage.after.isEmpty ? "starts from the brief" : "after \(stage.after.map { "“\($0)”" }.joined(separator: ", "))") - .font(Fonts.font(11)) - .foregroundStyle(Palette.muted) - if !stage.attempts.isEmpty { + if let only, only.title.caseInsensitiveCompare(stage.name) != .orderedSame { + Text("task “\(only.title)”") + .font(Fonts.font(11)) + .foregroundStyle(Palette.muted) + } + if !stage.after.isEmpty { + Text("after \(stage.after.map { "“\($0)”" }.joined(separator: ", "))") + .font(Fonts.font(11)) + .foregroundStyle(Palette.muted) + } + if let only { + AttemptRow(run: run, stage: stage, attempt: only, index: 0, solo: true) + } else if !stage.attempts.isEmpty { VStack(alignment: .leading, spacing: 8) { ForEach(Array(stage.attempts.enumerated()), id: \.offset) { i, a in - AttemptRow(run: run, stage: stage, attempt: a, index: i) + AttemptRow(run: run, stage: stage, attempt: a, index: i, solo: false) } } .padding(.top, 8) @@ -227,22 +253,23 @@ private struct AttemptRow: View { let stage: RunStage let attempt: StageAttempt let index: Int + let solo: Bool @Environment(Store.self) private var store var body: some View { let a = attempt let fed = run.stages.flatMap { s in s.attempts.filter { d in d.inputs.contains { $0.taskId == a.taskId } }.map(\.taskId) } VStack(alignment: .leading, spacing: 2) { - HStack(alignment: .firstTextBaseline, spacing: 10) { - if stage.attempts.count > 1 { + if !solo { + HStack(alignment: .firstTextBaseline, spacing: 10) { Text("#\(index + 1)").foregroundStyle(Palette.muted) - } - Text(link(a.taskId, a.title, bold: a.taskId == store.selectedTaskId)) - Text(attemptLabel(a)) - .font(Fonts.font(11)) - .foregroundStyle(stateColor(a.state.rawValue)) - if a.discarding { - Text("discarding…").font(Fonts.font(11)).foregroundStyle(Palette.muted) + Text(taskLink(a.taskId, a.title, bold: a.taskId == store.selectedTaskId)) + Text(attemptLabel(a)) + .font(Fonts.font(11)) + .foregroundStyle(stateColor(a.state.rawValue)) + if a.discarding { + Text("discarding…").font(Fonts.font(11)).foregroundStyle(Palette.muted) + } } } if let from = a.forkedFrom { @@ -273,9 +300,11 @@ private struct AttemptRow: View { .padding(.top, 6) } } - .padding(.top, 8) + .padding(.top, solo ? 0 : 8) .overlay(alignment: .top) { - Rectangle().fill(Palette.border).frame(height: 1) + if !solo { + Rectangle().fill(Palette.border).frame(height: 1) + } } } @@ -299,19 +328,11 @@ private struct AttemptRow: View { return store.findTask(taskId)?.title ?? taskId } - private func link(_ taskId: String, _ label: String, bold: Bool = false) -> AttributedString { - var a = AttributedString(label) - a.link = AppLink.task(taskId) - a.foregroundColor = Palette.accent - a.font = Fonts.font(13, weight: bold ? 700 : 400) - return a - } - private func line(_ parts: [(String, String?)]) -> some View { var out = AttributedString() for (text, taskId) in parts { if let taskId { - var a = link(taskId, text) + var a = taskLink(taskId, text) a.font = Fonts.font(11) out += a } else {