157 lines
4.3 KiB
Swift
157 lines
4.3 KiB
Swift
|
|
import SwiftUI
|
||
|
|
import SaplingCore
|
||
|
|
import SaplingWorkspace
|
||
|
|
import SaplingGit
|
||
|
|
import SaplingStorage
|
||
|
|
import SaplingEditor
|
||
|
|
import SaplingRenderer
|
||
|
|
import SaplingUI
|
||
|
|
|
||
|
|
@main
|
||
|
|
struct SaplingApplication: App {
|
||
|
|
@StateObject private var model = SaplingAppModel()
|
||
|
|
|
||
|
|
var body: some Scene {
|
||
|
|
WindowGroup {
|
||
|
|
MainWindow(model: model)
|
||
|
|
}
|
||
|
|
#if os(macOS)
|
||
|
|
.windowStyle(.titleBar)
|
||
|
|
.windowToolbarStyle(.unified)
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@MainActor
|
||
|
|
private final class SaplingAppModel: ObservableObject {
|
||
|
|
@Published var workspace: Workspace
|
||
|
|
@Published var selectedProject: Project?
|
||
|
|
@Published var selectedDocument: MarkdownDocument?
|
||
|
|
@Published var editorViewModel: HybridMarkdownEditorViewModel?
|
||
|
|
@Published var gitStatuses: [GitFileStatus] = []
|
||
|
|
|
||
|
|
private let gitProvider: any GitProvider
|
||
|
|
private let workspaceManager: any WorkspaceManaging
|
||
|
|
|
||
|
|
init() {
|
||
|
|
let gitProvider = MockGitProvider()
|
||
|
|
self.gitProvider = gitProvider
|
||
|
|
self.workspaceManager = LocalWorkspaceManager(
|
||
|
|
gitProvider: gitProvider,
|
||
|
|
metadataStore: InMemoryWorkspaceMetadataStore()
|
||
|
|
)
|
||
|
|
|
||
|
|
let workspace = workspaceManager.sampleWorkspace()
|
||
|
|
self.workspace = workspace
|
||
|
|
self.selectedProject = workspace.firstProject
|
||
|
|
setSelectedDocument(SaplingSampleData.document)
|
||
|
|
|
||
|
|
Task {
|
||
|
|
await refreshGitStatus()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func select(file: WorkspaceFile) {
|
||
|
|
guard file.kind == .markdown else { return }
|
||
|
|
|
||
|
|
if file.url == SaplingSampleData.document.url {
|
||
|
|
setSelectedDocument(SaplingSampleData.document)
|
||
|
|
} else {
|
||
|
|
setSelectedDocument(MarkdownDocument(
|
||
|
|
url: file.url,
|
||
|
|
title: file.name,
|
||
|
|
content: "# \(file.name)\n\nTODO: Load document contents from disk."
|
||
|
|
))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func select(project: Project) {
|
||
|
|
selectedProject = project
|
||
|
|
Task {
|
||
|
|
await refreshGitStatus()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private func refreshGitStatus() async {
|
||
|
|
guard let selectedProject else {
|
||
|
|
gitStatuses = []
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
do {
|
||
|
|
let repository = gitProvider.repository(at: selectedProject.repositoryURL)
|
||
|
|
gitStatuses = try await repository.status()
|
||
|
|
} catch {
|
||
|
|
gitStatuses = [
|
||
|
|
GitFileStatus(path: "Unable to load status: \(error)", state: .conflicted)
|
||
|
|
]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private func setSelectedDocument(_ document: MarkdownDocument) {
|
||
|
|
selectedDocument = document
|
||
|
|
editorViewModel = HybridMarkdownEditorViewModel(document: document)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private struct MainWindow: View {
|
||
|
|
@ObservedObject var model: SaplingAppModel
|
||
|
|
|
||
|
|
var body: some View {
|
||
|
|
NavigationSplitView {
|
||
|
|
WorkspaceTreeView(
|
||
|
|
workspace: model.workspace,
|
||
|
|
onSelectFile: model.select(file:),
|
||
|
|
onSelectProject: model.select(project:)
|
||
|
|
)
|
||
|
|
.frame(minWidth: 220)
|
||
|
|
} content: {
|
||
|
|
if let editorViewModel = model.editorViewModel {
|
||
|
|
HybridMarkdownEditor(
|
||
|
|
viewModel: editorViewModel,
|
||
|
|
renderer: MarkdownRenderer()
|
||
|
|
)
|
||
|
|
.navigationTitle(editorViewModel.document.title)
|
||
|
|
} else {
|
||
|
|
EmptyEditorPlaceholder()
|
||
|
|
}
|
||
|
|
} detail: {
|
||
|
|
SaplingInspectorView(
|
||
|
|
project: model.selectedProject,
|
||
|
|
document: model.selectedDocument,
|
||
|
|
statuses: model.gitStatuses
|
||
|
|
)
|
||
|
|
.frame(minWidth: 260)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private extension Workspace {
|
||
|
|
var firstProject: Project? {
|
||
|
|
for item in items {
|
||
|
|
if let project = item.firstProject {
|
||
|
|
return project
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private extension WorkspaceItem {
|
||
|
|
var firstProject: Project? {
|
||
|
|
switch self {
|
||
|
|
case .project(let project):
|
||
|
|
return project
|
||
|
|
case .folder(let folder):
|
||
|
|
for child in folder.children {
|
||
|
|
if let project = child.firstProject {
|
||
|
|
return project
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
case .file, .subproject:
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|