Compact run stage cards
This commit is contained in:
@@ -54,6 +54,15 @@ private func stageLabel(_ s: RunStage) -> String {
|
|||||||
return "waiting to start"
|
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 {
|
private func stateColor(_ state: String) -> Color {
|
||||||
switch state {
|
switch state {
|
||||||
case "running": Palette.accent
|
case "running": Palette.accent
|
||||||
@@ -195,20 +204,37 @@ private struct StageCard: View {
|
|||||||
let stage: RunStage
|
let stage: RunStage
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let only = stage.attempts.count == 1 ? stage.attempts[0] : nil
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
||||||
|
if let only {
|
||||||
|
Text(taskLink(only.taskId, stage.name, bold: true))
|
||||||
|
} else {
|
||||||
Text(stage.name).font(Fonts.font(13, weight: 700))
|
Text(stage.name).font(Fonts.font(13, weight: 700))
|
||||||
|
}
|
||||||
Text(stageLabel(stage))
|
Text(stageLabel(stage))
|
||||||
.font(Fonts.font(11))
|
.font(Fonts.font(11))
|
||||||
.foregroundStyle(stateColor(stage.state.rawValue))
|
.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: ", "))")
|
}
|
||||||
|
if let only, only.title.caseInsensitiveCompare(stage.name) != .orderedSame {
|
||||||
|
Text("task “\(only.title)”")
|
||||||
.font(Fonts.font(11))
|
.font(Fonts.font(11))
|
||||||
.foregroundStyle(Palette.muted)
|
.foregroundStyle(Palette.muted)
|
||||||
if !stage.attempts.isEmpty {
|
}
|
||||||
|
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) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
ForEach(Array(stage.attempts.enumerated()), id: \.offset) { i, a in
|
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)
|
.padding(.top, 8)
|
||||||
@@ -227,17 +253,17 @@ private struct AttemptRow: View {
|
|||||||
let stage: RunStage
|
let stage: RunStage
|
||||||
let attempt: StageAttempt
|
let attempt: StageAttempt
|
||||||
let index: Int
|
let index: Int
|
||||||
|
let solo: Bool
|
||||||
@Environment(Store.self) private var store
|
@Environment(Store.self) private var store
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let a = attempt
|
let a = attempt
|
||||||
let fed = run.stages.flatMap { s in s.attempts.filter { d in d.inputs.contains { $0.taskId == a.taskId } }.map(\.taskId) }
|
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) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
if !solo {
|
||||||
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
||||||
if stage.attempts.count > 1 {
|
|
||||||
Text("#\(index + 1)").foregroundStyle(Palette.muted)
|
Text("#\(index + 1)").foregroundStyle(Palette.muted)
|
||||||
}
|
Text(taskLink(a.taskId, a.title, bold: a.taskId == store.selectedTaskId))
|
||||||
Text(link(a.taskId, a.title, bold: a.taskId == store.selectedTaskId))
|
|
||||||
Text(attemptLabel(a))
|
Text(attemptLabel(a))
|
||||||
.font(Fonts.font(11))
|
.font(Fonts.font(11))
|
||||||
.foregroundStyle(stateColor(a.state.rawValue))
|
.foregroundStyle(stateColor(a.state.rawValue))
|
||||||
@@ -245,6 +271,7 @@ private struct AttemptRow: View {
|
|||||||
Text("discarding…").font(Fonts.font(11)).foregroundStyle(Palette.muted)
|
Text("discarding…").font(Fonts.font(11)).foregroundStyle(Palette.muted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if let from = a.forkedFrom {
|
if let from = a.forkedFrom {
|
||||||
line([("fork of ", nil), (attemptName(from), from)])
|
line([("fork of ", nil), (attemptName(from), from)])
|
||||||
}
|
}
|
||||||
@@ -273,11 +300,13 @@ private struct AttemptRow: View {
|
|||||||
.padding(.top, 6)
|
.padding(.top, 6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 8)
|
.padding(.top, solo ? 0 : 8)
|
||||||
.overlay(alignment: .top) {
|
.overlay(alignment: .top) {
|
||||||
|
if !solo {
|
||||||
Rectangle().fill(Palette.border).frame(height: 1)
|
Rectangle().fill(Palette.border).frame(height: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func joined(_ ids: [String]) -> [(String, String?)] {
|
private func joined(_ ids: [String]) -> [(String, String?)] {
|
||||||
var out: [(String, String?)] = []
|
var out: [(String, String?)] = []
|
||||||
@@ -299,19 +328,11 @@ private struct AttemptRow: View {
|
|||||||
return store.findTask(taskId)?.title ?? taskId
|
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 {
|
private func line(_ parts: [(String, String?)]) -> some View {
|
||||||
var out = AttributedString()
|
var out = AttributedString()
|
||||||
for (text, taskId) in parts {
|
for (text, taskId) in parts {
|
||||||
if let taskId {
|
if let taskId {
|
||||||
var a = link(taskId, text)
|
var a = taskLink(taskId, text)
|
||||||
a.font = Fonts.font(11)
|
a.font = Fonts.font(11)
|
||||||
out += a
|
out += a
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user