122 lines
3.9 KiB
Swift
122 lines
3.9 KiB
Swift
|
|
import SwiftUI
|
||
|
|
import SaplingCore
|
||
|
|
|
||
|
|
public struct SaplingInspectorView: View {
|
||
|
|
private let project: Project?
|
||
|
|
private let document: MarkdownDocument?
|
||
|
|
private let statuses: [GitFileStatus]
|
||
|
|
|
||
|
|
public init(
|
||
|
|
project: Project?,
|
||
|
|
document: MarkdownDocument?,
|
||
|
|
statuses: [GitFileStatus]
|
||
|
|
) {
|
||
|
|
self.project = project
|
||
|
|
self.document = document
|
||
|
|
self.statuses = statuses
|
||
|
|
}
|
||
|
|
|
||
|
|
public var body: some View {
|
||
|
|
List {
|
||
|
|
projectSection
|
||
|
|
gitStatusSection
|
||
|
|
outlineSection
|
||
|
|
}
|
||
|
|
.listStyle(.inset)
|
||
|
|
.navigationTitle("Inspector")
|
||
|
|
}
|
||
|
|
|
||
|
|
@ViewBuilder
|
||
|
|
private var projectSection: some View {
|
||
|
|
Section("Project") {
|
||
|
|
if let project {
|
||
|
|
LabeledContent("Name", value: project.name)
|
||
|
|
LabeledContent("Branch", value: project.gitRepository.currentBranch?.name ?? "Unknown")
|
||
|
|
LabeledContent("Remotes", value: "\(project.remotes.count)")
|
||
|
|
LabeledContent("LFS", value: project.usesGitLFS ? "Enabled" : "Disabled")
|
||
|
|
LabeledContent("Subprojects", value: "\(project.subprojects.count)")
|
||
|
|
} else {
|
||
|
|
Text("No project selected")
|
||
|
|
.foregroundStyle(.secondary)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private var gitStatusSection: some View {
|
||
|
|
Section("Git Status") {
|
||
|
|
if statuses.isEmpty {
|
||
|
|
Label("Clean", systemImage: "checkmark.circle")
|
||
|
|
.foregroundStyle(.green)
|
||
|
|
} else {
|
||
|
|
ForEach(statuses) { status in
|
||
|
|
HStack {
|
||
|
|
Image(systemName: iconName(for: status.state))
|
||
|
|
.foregroundStyle(color(for: status.state))
|
||
|
|
Text(status.path)
|
||
|
|
.lineLimit(1)
|
||
|
|
Spacer()
|
||
|
|
Text(status.state.rawValue)
|
||
|
|
.font(.caption)
|
||
|
|
.foregroundStyle(.secondary)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private var outlineSection: some View {
|
||
|
|
Section("Outline") {
|
||
|
|
if let document {
|
||
|
|
let headings = outlineHeadings(in: document.content)
|
||
|
|
if headings.isEmpty {
|
||
|
|
Text("No headings")
|
||
|
|
.foregroundStyle(.secondary)
|
||
|
|
} else {
|
||
|
|
ForEach(headings, id: \.self) { heading in
|
||
|
|
Text(heading)
|
||
|
|
.lineLimit(1)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
Text("No document selected")
|
||
|
|
.foregroundStyle(.secondary)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private func outlineHeadings(in markdown: String) -> [String] {
|
||
|
|
markdown
|
||
|
|
.split(separator: "\n")
|
||
|
|
.map(String.init)
|
||
|
|
.compactMap { line in
|
||
|
|
let trimmed = line.trimmingCharacters(in: .whitespaces)
|
||
|
|
guard trimmed.hasPrefix("#") else { return nil }
|
||
|
|
let hashes = trimmed.prefix { $0 == "#" }.count
|
||
|
|
guard hashes <= 6, trimmed.dropFirst(hashes).first == " " else { return nil }
|
||
|
|
return String(trimmed.dropFirst(hashes + 1))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private func iconName(for state: GitFileState) -> String {
|
||
|
|
switch state {
|
||
|
|
case .untracked: "questionmark.circle"
|
||
|
|
case .added: "plus.circle"
|
||
|
|
case .modified: "pencil.circle"
|
||
|
|
case .deleted: "minus.circle"
|
||
|
|
case .renamed: "arrow.triangle.2.circlepath"
|
||
|
|
case .conflicted: "exclamationmark.triangle"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private func color(for state: GitFileState) -> Color {
|
||
|
|
switch state {
|
||
|
|
case .untracked: .secondary
|
||
|
|
case .added: .green
|
||
|
|
case .modified: .orange
|
||
|
|
case .deleted: .red
|
||
|
|
case .renamed: .blue
|
||
|
|
case .conflicted: .red
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|