johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
import SwiftUI
/// A commit in a list: its subject, the start of the rest, and who made it.
struct CommitRow: View {
let repo: RepoRef
let commit: Commit
var body: some View {
VStack(alignment: .leading, spacing: Space.s1_5) {
HStack(alignment: .firstTextBaseline, spacing: Space.s2_5) {
Text(commit.subject).font(.body.weight(.semibold)).lineLimit(1)
Spacer(minLength: 0)
Text(When.short(commit.when)).font(.subheadline).foregroundStyle(.secondary).fixedSize()
}
if !commit.details.isEmpty {
Text(commit.details.replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression))
.font(.footnote.monospaced())
.foregroundStyle(.secondary)
.lineLimit(1)
}
CommitPeople(commit: commit, whole: false)
}
.background {
// Behind the row, so that the list draws no chevron for it.
NavigationLink(value: Route.commit(repo, sha: commit.sha)) { EmptyView() }.opacity(0)
}
}
}
/// Their faces, and who wrote it. A list stops there: a queued pull lands as
/// the squash its runner commits, so saying who committed each would name that
/// runner on every row. The commit's own page says, when `whole`.
private struct CommitPeople: View {
let commit: Commit
let whole: Bool
private func names(_ people: [CommitAuthor]) -> Text {
var out = Text("")
for (i, person) in people.enumerated() {
if i > 0 {
let joiner = i == people.count - 1 ? " and " : ", "
out = Text("\(out)\(Text(joiner).foregroundStyle(.secondary))")
}
out = Text("\(out)\(Text(person.label).fontWeight(.semibold))")
}
return out
}
var body: some View {
let authors = commit.authors
HStack(spacing: Space.s2) {
HStack(spacing: -6) {
ForEach(Array(authors.prefix(2).enumerated()), id: \.offset) { _, person in
Avatar(url: person.actor?.avatar, name: person.label, did: person.actor?.did, face: person.face, size: Size.Avatar.small)
}
}
if whole, let committer = commit.otherCommitter {
Text("\(names(authors))\(Text(" authored and ").foregroundStyle(.secondary))\(names([committer]))\(Text(" committed").foregroundStyle(.secondary))")
} else {
Text("\(names(authors))\(Text(" committed").foregroundStyle(.secondary))")
}
}
.font(.subheadline)
}
}
struct CommitView: View {
let repo: RepoRef
let sha: String
private enum Tab: Hashable { case changes, details }
@State private var tab = Tab.changes
var body: some View {
Loading(load: { [repo, sha] in
let answer: CommitAnswer = try await Client.shared.query("getCommit", ["repo": repo.uri, "sha": sha])
return answer
}) { answer in
switch tab {
case .changes: CommitChanges(repo: repo, answer: answer)
case .details: CommitDetails(repo: repo, commit: answer.commit)
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
Picker("Show", selection: $tab) {
Text("Changes").tag(Tab.changes)
Text("Details").tag(Tab.details)
}
.pickerStyle(.segmented)
.fixedSize()
}
if let url = URL(string: "https://knotless.dev/\(repo.handle)/\(repo.name)/commit/\(sha)") {
ToolbarItem(placement: .topBarTrailing) { ShareLink(item: url) }
}
}
}
}
/// What it says, then what it changed, file by file.
private struct CommitChanges: View {
let repo: RepoRef
let answer: CommitAnswer
private var commit: Commit { answer.commit }
var body: some View {
ScrollViewReader { reader in
ScrollView {
VStack(alignment: .leading, spacing: Space.s3) {
HStack(alignment: .firstTextBaseline, spacing: Space.s2_5) {
CommitPeople(commit: commit, whole: true)
Spacer(minLength: 0)
Text(When.short(commit.when)).font(.subheadline).foregroundStyle(.secondary).fixedSize()
}
Text(commit.subject).font(.headline)
if !commit.details.isEmpty { MarkdownView(source: commit.details, place: MarkdownPlace(repo: repo)) }
Text(summary).font(.footnote).foregroundStyle(.secondary)
}
.padding(Space.s4)
.frame(maxWidth: .infinity, alignment: .leading)
LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) {
ForEach(answer.files) { file in
FileSection(repo: repo, file: file).id(file.id)
}
}
.padding(.bottom, Size.floatingClearance)
}
.overlay(alignment: .bottomTrailing) {
if answer.files.count > 1 {
Menu {
ForEach(answer.files) { file in
Button(file.path) { withAnimation { reader.scrollTo(file.id, anchor: .top) } }
}
} label: {
SwiftUI.Label { Text("Jump to file") } icon: { Oct(name: "file-moved") }.labelStyle(TightLabel())
}
.floating()
.padding(Space.s4)
}
}
}
}
private var summary: String {
let files = answer.files.count == 1 ? "1 file changed" : "\(answer.files.count) files changed"
let added = answer.files.reduce(0) { $0 + $1.added }
let removed = answer.files.reduce(0) { $0 + $1.removed }
return "\(files), +\(added) -\(removed)"
}
}
/// Its name, who made it, what it follows and the pull that brought it in.
private struct CommitDetails: View {
let repo: RepoRef
let commit: Commit
@State private var copied = false
var body: some View {
List {
Group {
Section("Commit") {
HStack(spacing: Space.s2_5) {
Text(commit.sha).font(.body.monospaced()).lineLimit(1).truncationMode(.tail)
Spacer(minLength: 0)
Button {
UIPasteboard.general.string = commit.sha
copied = true
} label: {
Oct(name: copied ? "check" : "copy", size: Icon.xl).foregroundStyle(copied ? Color.success : Color.accent)
}
.buttonStyle(.borderless)
.accessibilityLabel(copied ? "Copied" : "Copy")
}
}
Section("Contributors") {
ForEach(Array(contributors.enumerated()), id: \.offset) { _, one in
if let handle = one.person.actor?.handle {
NavigationLink(value: Route.profile(handle)) { person(one.person, one.role) }
} else {
person(one.person, one.role)
}
}
}
if let parents = commit.parents, !parents.isEmpty {
Section(parents.count == 1 ? "Parent" : "Parents") {
ForEach(parents, id: \.sha) { parent in
NavigationLink(value: Route.commit(repo, sha: parent.sha)) {
HStack(spacing: Space.s2_5) {
Oct(name: "git-commit", size: Icon.xl).foregroundStyle(.secondary)
Text(parent.sha).font(.body.monospaced()).lineLimit(1).truncationMode(.tail)
}
}
}
}
}
if let pull = commit.pull {
Section("Pull") {
NavigationLink(value: Route.thread(repo, uri: pull.uri)) {
HStack(spacing: Space.s2_5) {
Oct(name: "git-pull-request", size: Icon.xl).foregroundStyle(.secondary)
Text(pull.title ?? "This commit's pull").lineLimit(2)
}
}
}
}
}
.listRowBackground(Color.card)
}
}
private var contributors: [(person: CommitAuthor, role: String)] {
var out = (commit.author.map { [(person: $0, role: "Author")] } ?? [])
out += (commit.coauthors ?? []).map { (person: $0, role: "Co-author") }
if let committer = commit.otherCommitter { out.append((person: committer, role: "Committer")) }
return out
}
private func person(_ person: CommitAuthor, _ role: String) -> some View {
HStack(spacing: Space.s3) {
Avatar(url: person.actor?.avatar, name: person.label, did: person.actor?.did, face: person.face, size: Size.Avatar.medium)
Text(person.label).lineLimit(1)
Spacer(minLength: 8)
Text(role).foregroundStyle(.secondary)
}
}
}