import SwiftUI struct SettingsView: View { @Environment(\.language) var language @AppStorage("contentPath") var contentPath: String = "" @AppStorage("outputPath") var outputPath: String = "" @EnvironmentObject var content: Content @State private var isSelectingContentFolder = false @State private var showFileImporter = false @State private var showTagPicker = false var body: some View { ScrollView { VStack(alignment: .leading) { Text("Content Folder") .font(.headline) TextField("Content Folder", text: $contentPath) Button(action: selectContentFolder) { Text("Select folder") } Text("Output Folder") .font(.headline) TextField("Output Folder", text: $outputPath) Button(action: selectOutputFolder) { Text("Select folder") } Text("Navigation Bar Items") .font(.headline) FlowHStack { ForEach(content.websiteData.navigationTags, id: \.id) { tag in TagView(tag: .init( en: tag.english.name, de: tag.german.name) ) .foregroundStyle(.white) } Button(action: { showTagPicker = true }) { Image(systemSymbol: .squareAndPencilCircleFill) .resizable() .aspectRatio(1, contentMode: .fit) .frame(height: 22) .foregroundColor(Color.gray) .background(Circle() .fill(Color.white) .padding(1)) } .buttonStyle(.plain) } LocalizedSettingsView(settings: content.websiteData.localized(in: language)) Text("Feed") .font(.headline) Button(action: generateFeed) { Text("Generate") } } .padding() } .fileImporter( isPresented: $showFileImporter, allowedContentTypes: [.folder], onCompletion: didSelectContentFolder) .sheet(isPresented: $showTagPicker) { TagSelectionView( presented: $showTagPicker, selected: $content.websiteData.navigationTags, tags: $content.tags) } } // MARK: Folder selection private func selectContentFolder() { isSelectingContentFolder = true //showFileImporter = true guard let url = savePanelUsingOpenPanel(key: "contentPathBookmark") else { return } self.contentPath = url.path() } private func selectOutputFolder() { isSelectingContentFolder = false //showFileImporter = true guard let url = savePanelUsingOpenPanel(key: "outputPathBookmark") else { return } self.outputPath = url.path() } private func didSelectContentFolder(_ result: Result) { switch result { case .success(let url): didSelect(folder: url) case .failure(let error): print("Failed to select content folder: \(error)") } } private func didSelect(folder: URL) { let path = folder.absoluteString .replacingOccurrences(of: "file://", with: "") if isSelectingContentFolder { self.contentPath = path saveSecurityScopedBookmark(folder, key: "contentPathBookmark") } else { self.outputPath = path saveSecurityScopedBookmark(folder, key: "outputPathBookmark") } } // MARK: Feed private func generateFeed() { guard outputPath != "" else { print("Invalid output path") return } let url = URL(fileURLWithPath: outputPath) guard FileManager.default.fileExists(atPath: url.path) else { print("Missing output folder") return } content.generateFeed(for: language, bookmarkKey: "outputPathBookmark") } func savePanelUsingOpenPanel(key: String) -> URL? { let panel = NSOpenPanel() // Sets up so user can only select a single directory panel.canChooseFiles = false panel.canChooseDirectories = true panel.allowsMultipleSelection = false panel.showsHiddenFiles = false panel.title = "Select Save Directory" panel.prompt = "Select Save Directory" let response = panel.runModal() guard response == .OK else { return nil } guard let url = panel.url else { return nil } saveSecurityScopedBookmark(url, key: key) return url } func saveSecurityScopedBookmark(_ url: URL, key: String) { do { let bookmarkData = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) UserDefaults.standard.set(bookmarkData, forKey: key) print("Security-scoped bookmark saved.") } catch { print("Failed to create security-scoped bookmark: \(error)") } } } #Preview { SettingsView() .environmentObject(Content.mock) }