Save and check page content automatically

This commit is contained in:
Christoph Hagen 2025-02-05 21:46:46 +01:00
parent 41171c31db
commit 7ebc9d8404
3 changed files with 105 additions and 48 deletions

View File

@ -155,6 +155,14 @@ final class Page: Item, DateItem, LocalizedItem {
content.storage.pageContent(for: id, language: language)
}
func removeContent(in language: ContentLanguage) -> Bool {
guard content.storage.remove(pageContent: id, in: language) else {
return false
}
localized(in: language).hasContent = false
return true
}
func save(pageContent: String, in language: ContentLanguage) -> Bool {
guard content.storage.save(pageContent: pageContent, for: id, in: language) else {
return false

View File

@ -77,6 +77,12 @@ final class Storage: ObservableObject {
return contentScope.write(pageContent, to: path)
}
func remove(pageContent pageId: String, in language: ContentLanguage) -> Bool {
guard let contentScope else { return false }
let path = pageContentPath(page: pageId, language: language)
return contentScope.deleteFile(at: path)
}
func save(page: Page.Data, for pageId: String) -> Bool {
guard let contentScope else { return false }
let path = pageMetadataPath(page: pageId)

View File

@ -2,6 +2,37 @@ import SwiftUI
import SFSafeSymbols
import HighlightedTextEditor
enum PageContentSaveStatus {
case isSaved
case failedToSave
case needsSave
case notLoaded
var symbol: SFSymbol {
switch self {
case .notLoaded:
return .questionmarkSquareDashed
case .isSaved:
return .checkmarkCircleFill
case .needsSave:
return .hourglassCircleFill
case .failedToSave:
return .exclamationmarkTriangleFill
}
}
var color: Color {
switch self {
case .isSaved, .notLoaded:
return .green
case .needsSave:
return .yellow
case .failedToSave:
return .red
}
}
}
struct LocalizedPageContentView: View {
@EnvironmentObject
@ -15,36 +46,23 @@ struct LocalizedPageContentView: View {
@State
private var pageContent: String = ""
@State
private var pageContentUsedForGeneration: String = ""
@State
private var generationResults: PageGenerationResults?
@State
private var didChangeContent = false
private var saveState: PageContentSaveStatus = .notLoaded
@State
private var lastSave: Date = .now
@State
private var lastModification: Date = .now
var body: some View {
VStack(alignment: .leading) {
HStack(alignment: .firstTextBaseline) {
Button(action: loadContent) {
Text("Load")
}
Button(action: saveContent) {
Text("Save")
}
Button(action: checkContent) {
Text("Check")
}.disabled(content.isGeneratingWebsite)
if content.isGeneratingWebsite {
ProgressView()
.scaleEffect(0.6)
.frame(height: 15)
}
Spacer()
}
HStack {
Image(systemSymbol: saveState.symbol)
.foregroundStyle(saveState.color)
if let generationResults {
PageContentResultsView(results: generationResults)
}
@ -59,69 +77,94 @@ struct LocalizedPageContentView: View {
text: $pageContent,
highlightRules: .markdown)
.onChange(of: pageContent) {
didChangeContent = true
didChangeContent()
}
}
.onAppear(perform: loadContent)
.onDisappear(perform: saveContent)
.onDisappear { saveContentIfNeeded(isFinalSave: true) }
}
private func didChangeContent() {
if saveState == .notLoaded {
// Content was changed due to loading
saveState = .isSaved
return
}
saveState = .needsSave
// Wait a few seconds for a save, to allow additional changes
// Reduces the number of saves
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10)) {
self.saveContentIfNeeded()
}
}
private func loadContent() {
let language = language
guard page.localized(in: language).hasContent else {
pageContent = "New file"
DispatchQueue.main.async {
didChangeContent = false
}
return
}
guard let content = page.pageContent(in: language) else {
print("Failed to load page content")
pageContent = "Failed to load"
DispatchQueue.main.async {
didChangeContent = false
}
return
}
guard content != "" else {
pageContent = "New file"
DispatchQueue.main.async {
didChangeContent = false
}
return
}
pageContent = content
checkContent()
DispatchQueue.main.async {
didChangeContent = false
}
}
private func saveContent() {
private func saveContentIfNeeded(isFinalSave: Bool = false) {
switch saveState {
case .isSaved, .notLoaded:
return
default:
break
}
if !isFinalSave, Date.now.timeIntervalSince(lastModification) < 10 {
// Additional modification made
// Wait for next scheduled invocation of saveIfNeeded()
// if the overall unsaved time is not too long
if Date.now.timeIntervalSince(lastSave) < 60 {
//print("Waiting while modifying")
return
}
print("Saving content after 30 seconds of modifications")
}
saveUnconditionally()
}
private func saveUnconditionally() {
guard pageContent != "New file", pageContent != "" else {
// TODO: Delete file?
return
}
guard didChangeContent else {
guard page.removeContent(in: language) else {
print("Failed to remove empty content from disk")
saveState = .failedToSave
return
}
saveState = .notLoaded
generationResults = nil
return
}
guard page.save(pageContent: pageContent, in: language) else {
print("Failed to save content")
saveState = .failedToSave
return
}
didChangeContent = false
saveState = .isSaved
checkContent()
}
private func checkContent() {
let content = self.pageContent
guard content != pageContentUsedForGeneration else {
return
}
guard !self.content.isGeneratingWebsite else {
return
}
self.content.check(content: content, of: page, for: language) {
self.content.check(content: pageContent, of: page, for: language) {
self.generationResults = $0
}
}