import SwiftUI import SFSafeSymbols struct PageDetailView: View { @Environment(\.language) private var language @EnvironmentObject private var content: Content @ObservedObject private var page: Page @State private var isGeneratingWebsite = false @State private var newId: String @State private var didGenerateWebsite: Bool? init(page: Page) { self.page = page self.newId = page.id } private let allowedCharactersInPostId = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted private var idExists: Bool { page.content.pages.contains { $0.id == newId } } private var containsInvalidCharacters: Bool { newId.rangeOfCharacter(from: allowedCharactersInPostId) != nil } var body: some View { ScrollView { VStack(alignment: .leading) { HStack { Button(action: generate) { Text("Generate") } .disabled(isGeneratingWebsite) if let didGenerateWebsite { if didGenerateWebsite { Image(systemSymbol: .checkmarkCircleFill) .foregroundStyle(.green) } else { Image(systemSymbol: .xmarkCircleFill) .foregroundStyle(.red) } } } HStack { TextField("", text: $newId) .textFieldStyle(.roundedBorder) Button("Update", action: setNewId) .disabled(newId.isEmpty || containsInvalidCharacters || idExists) } .padding(.bottom) Text("External url") .font(.headline) OptionalTextField("", text: $page.externalLink, prompt: "External url") .textFieldStyle(.roundedBorder) .padding(.bottom) HStack { Text("Draft") .font(.headline) Spacer() Toggle("", isOn: $page.isDraft) .toggleStyle(.switch) } .padding(.bottom) HStack { Text("Start") .font(.headline) Spacer() DatePicker("", selection: $page.startDate, displayedComponents: .date) .datePickerStyle(.compact) .padding(.bottom) } HStack(alignment: .firstTextBaseline) { Text("Has end date") .font(.headline) Spacer() Toggle("", isOn: $page.hasEndDate) .toggleStyle(.switch) .padding(.bottom) } if page.hasEndDate { HStack(alignment: .firstTextBaseline) { Text("End date") .font(.headline) Spacer() DatePicker("", selection: $page.endDate, displayedComponents: .date) .datePickerStyle(.compact) .padding(.bottom) } } LocalizedPageDetailView(page: page.localized(in: language)) .id(page.id + language.rawValue) } .padding() } } private func generate() { guard content.settings.paths.outputDirectoryPath != "" else { print("Invalid output path") return } let url = content.settings.outputDirectory guard FileManager.default.fileExists(atPath: url.path) else { print("Missing output folder") return } isGeneratingWebsite = true DispatchQueue.global(qos: .userInitiated).async { var success = true for language in ContentLanguage.allCases { let generator = LocalizedWebsiteGenerator( content: content, language: language) if !generator.generate(page: page) { print("Generation failed") success = false } } DispatchQueue.main.async { isGeneratingWebsite = false didGenerateWebsite = success } } } private func setNewId() { guard page.update(id: newId) else { newId = page.id return } page.id = newId } } extension PageDetailView: MainContentView { init(item: Page) { self.init(page: item) } static let itemDescription = "a page" } #Preview { PageDetailView(page: .empty) }