ChWebsiteApp/CHDataManagement/Views/Pages/LocalizedPageContentView.swift
2025-02-07 14:08:51 +01:00

173 lines
4.8 KiB
Swift

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
var content: Content
@ObservedObject
var page: Page
let language: ContentLanguage
@State
private var pageContent: String = ""
@State
private var generationResults: PageGenerationResults?
@State
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 {
Image(systemSymbol: saveState.symbol)
.foregroundStyle(saveState.color)
if let generationResults {
PageContentResultsView(results: generationResults)
}
let linkingPosts = content.posts.filter { $0.linkedPage == page }
TextWithPopup(
symbol: .ipadAndArrowForward,
title: "Post linking to page",
text: "\(linkingPosts.count) linking posts",
items: linkingPosts.map { $0.title(in: language) })
}.foregroundStyle(.secondary)
InsertableItemsView()
HighlightedTextEditor(
text: $pageContent,
highlightRules: .markdown)
.onChange(of: pageContent) {
didChangeContent()
}
}
.onAppear(perform: loadContent)
.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"
return
}
guard let content = page.pageContent(in: language) else {
print("Failed to load page content")
pageContent = "Failed to load"
return
}
guard content != "" else {
pageContent = "New file"
return
}
pageContent = content
checkContent()
}
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 {
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
}
saveState = .isSaved
checkContent()
}
private func checkContent() {
guard !self.content.isGeneratingWebsite else {
return
}
self.content.check(content: pageContent, of: page, for: language) {
self.generationResults = $0
}
}
}