feat(editor): implement document loading and saving
This commit is contained in:
parent
93d095feeb
commit
1e15778364
1 changed files with 94 additions and 7 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
import SaplingCore
|
||||
import SaplingWorkspace
|
||||
import SaplingGit
|
||||
|
|
@ -40,6 +41,7 @@ private final class SaplingAppModel: ObservableObject {
|
|||
@Published var editorViewModel: HybridMarkdownEditorViewModel?
|
||||
@Published var gitStatuses: [GitFileStatus] = []
|
||||
@Published var configuration: SaplingConfiguration
|
||||
@Published var editorErrorMessage: String?
|
||||
|
||||
private let gitProvider: any GitProvider
|
||||
private let configurationStore: any ConfigurationStore
|
||||
|
|
@ -71,11 +73,40 @@ private final class SaplingAppModel: ObservableObject {
|
|||
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."
|
||||
))
|
||||
openDocument(at: file.url)
|
||||
}
|
||||
}
|
||||
|
||||
func openDocument(at url: URL) {
|
||||
let didStartAccessing = url.startAccessingSecurityScopedResource()
|
||||
defer {
|
||||
if didStartAccessing {
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
let document = try HybridMarkdownEditorViewModel.loadDocument(at: url)
|
||||
setSelectedDocument(document)
|
||||
editorErrorMessage = nil
|
||||
logger.info("Opened document: \(document.title)", category: .editor)
|
||||
} catch {
|
||||
editorErrorMessage = "Unable to open \(url.lastPathComponent): \(error.localizedDescription)"
|
||||
logger.error("Failed to open document: \(error)", category: .editor)
|
||||
}
|
||||
}
|
||||
|
||||
func saveSelectedDocument() {
|
||||
guard let editorViewModel else { return }
|
||||
|
||||
do {
|
||||
try editorViewModel.save()
|
||||
selectedDocument = editorViewModel.document
|
||||
editorErrorMessage = nil
|
||||
logger.info("Saved document: \(editorViewModel.document.title)", category: .editor)
|
||||
} catch {
|
||||
editorErrorMessage = "Unable to save \(editorViewModel.document.title): \(error.localizedDescription)"
|
||||
logger.error("Failed to save document: \(error)", category: .editor)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,6 +154,7 @@ private final class SaplingAppModel: ObservableObject {
|
|||
|
||||
private struct MainWindow: View {
|
||||
@ObservedObject var model: SaplingAppModel
|
||||
@State private var isImportingMarkdown = false
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
|
|
@ -138,18 +170,73 @@ private struct MainWindow: View {
|
|||
viewModel: editorViewModel,
|
||||
renderer: MarkdownRenderer()
|
||||
)
|
||||
.navigationTitle(editorViewModel.document.title)
|
||||
.navigationTitle(navigationTitle(for: editorViewModel))
|
||||
} else {
|
||||
EmptyEditorPlaceholder()
|
||||
}
|
||||
} detail: {
|
||||
SaplingInspectorView(
|
||||
project: model.selectedProject,
|
||||
document: model.selectedDocument,
|
||||
document: model.editorViewModel?.document ?? model.selectedDocument,
|
||||
statuses: model.gitStatuses
|
||||
)
|
||||
.frame(minWidth: 260)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItemGroup {
|
||||
Button {
|
||||
isImportingMarkdown = true
|
||||
} label: {
|
||||
Label("Open Markdown", systemImage: "doc.badge.plus")
|
||||
}
|
||||
.help("Open Markdown")
|
||||
|
||||
Button {
|
||||
model.saveSelectedDocument()
|
||||
} label: {
|
||||
Label("Save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.help("Save")
|
||||
.disabled(model.editorViewModel?.hasUnsavedChanges != true)
|
||||
.keyboardShortcut("s", modifiers: .command)
|
||||
}
|
||||
}
|
||||
.fileImporter(
|
||||
isPresented: $isImportingMarkdown,
|
||||
allowedContentTypes: [.saplingMarkdown, .plainText],
|
||||
allowsMultipleSelection: false
|
||||
) { result in
|
||||
switch result {
|
||||
case .success(let urls):
|
||||
guard let url = urls.first else { return }
|
||||
model.openDocument(at: url)
|
||||
case .failure(let error):
|
||||
model.editorErrorMessage = "Unable to open document: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
.alert(
|
||||
"Editor Error",
|
||||
isPresented: Binding(
|
||||
get: { model.editorErrorMessage != nil },
|
||||
set: { if !$0 { model.editorErrorMessage = nil } }
|
||||
)
|
||||
) {
|
||||
Button("OK", role: .cancel) {
|
||||
model.editorErrorMessage = nil
|
||||
}
|
||||
} message: {
|
||||
Text(model.editorErrorMessage ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
private func navigationTitle(for viewModel: HybridMarkdownEditorViewModel) -> String {
|
||||
viewModel.hasUnsavedChanges ? "\(viewModel.document.title) *" : viewModel.document.title
|
||||
}
|
||||
}
|
||||
|
||||
private extension UTType {
|
||||
static var saplingMarkdown: UTType {
|
||||
UTType(filenameExtension: "md") ?? .plainText
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue