import Foundation import SwiftUI import Combine final class Content: ObservableObject { @Published var didLoadContent = false @ObservedObject var storage: Storage @Published var settings: Settings @Published var posts: [Post] @Published var pages: [Page] @Published var tags: [Tag] @Published var files: [FileResource] @Published var tagOverview: Tag? @Published var results: GenerationResults @Published var generationStatus: String = "Ready to generate" @Published private(set) var isGeneratingWebsite = false @Published private(set) var shouldGenerateWebsite = false let imageGenerator: ImageGenerator init(settings: Settings, posts: [Post], pages: [Page], tags: [Tag], files: [FileResource], tagOverview: Tag?) { self.settings = settings self.posts = posts self.pages = pages self.tags = tags self.files = files self.tagOverview = tagOverview self.results = .init() let storage = Storage() self.storage = storage self.imageGenerator = ImageGenerator( storage: storage, settings: settings) } init() { let settings = Settings.default self.settings = settings self.posts = [] self.pages = [] self.tags = [] self.files = [] self.tagOverview = nil self.results = .init() let storage = Storage() self.storage = storage self.imageGenerator = ImageGenerator( storage: storage, settings: settings) } private func clear() { self.settings = .default self.posts = [] self.pages = [] self.tags = [] self.files = [] self.tagOverview = nil self.results = .init() } var images: [FileResource] { files.filter { $0.type.isImage } } func set(isGenerating: Bool) { self.isGeneratingWebsite = isGenerating } func set(shouldGenerate: Bool) { self.shouldGenerateWebsite = shouldGenerate } func add(_ file: FileResource) { // TODO: Insert at correct index? files.insert(file, at: 0) } func add(_ page: Page) { // TODO: Insert at correct index? pages.insert(page, at: 0) } func update(contentPath: URL, callback: @escaping ([String]) -> ()) { guard storage.save(contentPath: contentPath) else { return } loadFromDisk(callback: callback) } func remove(_ file: FileResource) { files.remove(file) for post in posts { post.remove(file) } for page in pages { page.remove(file) } for tag in tags { tag.remove(file) } settings.remove(file) } func file(withOutputPath: String) -> FileResource? { files.first { $0.absoluteUrl == withOutputPath } } private let errorPrinter = ErrorPrinter() func loadFromDisk(callback: @escaping (_ errors: [String]) -> ()) { defer { storage.contentScope?.delegate = errorPrinter } DispatchQueue.global().async { let loader = ModelLoader(content: self, storage: self.storage) let result = loader.load() guard result.errors.isEmpty else { DispatchQueue.main.async { self.didLoadContent = false callback(result.errors.sorted()) } return } DispatchQueue.main.async { self.files = result.files self.posts = result.posts self.pages = result.pages self.tags = result.tags self.settings = result.settings self.tagOverview = result.tagOverview self.didLoadContent = true callback([]) } } } }