import SwiftUI import SFSafeSymbols /** **Content** - iPhone Backgrounds: Add page, html **UI** - Image search: Add view to see all images and filter - Page Content: Show all results of `PageGenerationResults` - Files: Show usages of file **Features** - Posts: Generate separate pages for posts to link to - Settings: Introduce `Authors` (`name`, `image`, `description`) - Page: Property `author` - Blocks: Convert more commands to blocks - Graphs, Map, GPX for hikes **Generation** - Consistency: Check output folder for unused files - Empty properties: Show warnings for empty link previews, etc. **Fixes** - Files: Id change: Check all page contents for links to the renamed file and replace occurences - Database: Show errors during loading - Investigate issue with spaces in content file names */ @main struct MainView: App { private let sidebarWidth: CGFloat = 250 private let detailWidth: CGFloat = 300 @StateObject private var content: Content = .init() @State private var language: ContentLanguage = .english @ObservedObject private var selection: SelectedContent = .init() @State private var showAddSheet = false @State private var showInitialSetupSheet = false @State private var showStorageErrorSheet = false @ViewBuilder var sidebar: some View { switch selection.tab { case .posts: PostListView() case .pages: PageListView() case .tags: TagListView() case .files: FileListView(selectedFile: $selection.file) case .generation: SettingsListView() } } @ViewBuilder var viewContent: some View { switch selection.tab { case .posts: SelectedContentView(selected: $selection.post) case .pages: SelectedContentView(selected: $selection.page) case .tags: SelectedContentView(selected: $selection.tag) case .files: SelectedContentView(selected: $selection.file) case .generation: GenerationContentView(selected: $selection.section) } } @ViewBuilder var detail: some View { switch selection.tab { case .posts: SelectedDetailView(selected: $selection.post) case .pages: SelectedDetailView(selected: $selection.page) case .tags: SelectedDetailView(selected: $selection.tag) case .files: SelectedDetailView(selected: $selection.file) case .generation: GenerationDetailView(section: selection.section) } } @ViewBuilder var addItemSheet: some View { switch selection.tab { case .posts: AddPostView(selected: $selection.post) case .pages: AddPageView(selected: $selection.page) case .tags: AddTagView(selected: $selection.tag) case .files: AddFileView(selectedFile: $selection.file) case .generation: Text("Not implemented") } } var body: some Scene { WindowGroup { NavigationSplitView { sidebar .toolbar { ToolbarItem(placement: .navigation) { Picker("", selection: $selection.tab) { Text("Posts").tag(MainViewTab.posts) Text("Pages").tag(MainViewTab.pages) Text("Tags").tag(MainViewTab.tags) Text("Files").tag(MainViewTab.files) Text("Generation").tag(MainViewTab.generation) }.pickerStyle(.segmented) } } .navigationSplitViewColumnWidth(min: sidebarWidth, ideal: sidebarWidth, max: sidebarWidth) .toolbar { ToolbarItem(placement: .primaryAction) { Button(action: { showAddSheet = true }) { Label("Add", systemSymbol: .plus) } .disabled(!selection.tab.canAddItems) } } } content: { viewContent } detail: { detail .navigationSplitViewColumnWidth(min: detailWidth, ideal: detailWidth, max: detailWidth) } .toolbar { ToolbarItem(placement: .primaryAction) { Picker("", selection: $language) { Text("English") .tag(ContentLanguage.english) Text("German") .tag(ContentLanguage.german) }.pickerStyle(.segmented) } ToolbarItem(placement: .primaryAction) { Button(action: saveButtonPressed) { Image(systemSymbol: content.saveState.symbol) .foregroundStyle(content.saveState.color) } } } .navigationTitle("") .environment(\.language, language) .environmentObject(content) .environmentObject(selection) .onAppear(perform: loadContent) .sheet(isPresented: $showAddSheet) { addItemSheet .environment(\.language, language) .environmentObject(content) .environmentObject(selection) } .sheet(isPresented: $showInitialSetupSheet) { InitialSetupView() .environment(\.language, language) .environmentObject(content) .environmentObject(selection) } .sheet(isPresented: $showStorageErrorSheet) { StorageErrorView(isPresented: $showStorageErrorSheet) .environmentObject(content) } } } private func saveButtonPressed() { switch content.saveState { case .storageNotInitialized: showInitialSheet() case .isSaved: content.saveUnconditionally() case .needsSave: content.saveUnconditionally() case .failedToSave: showStorageErrorSheet = true } } private func loadContent() { guard content.storage.contentScope != nil else { showInitialSheet() return } content.loadFromDisk { prepareAfterLoad() if !content.storageErrors.isEmpty { self.showStorageErrorSheet = true } } } private func prepareAfterLoad() { if selection.post == nil { selection.post = content.posts.first } if selection.page == nil { selection.page = content.pages.first } if selection.tag == nil { selection.tag = content.tags.first } if selection.file == nil { selection.file = content.files.first } } private func showInitialSheet() { DispatchQueue.main.async { selection.section = .folders selection.tab = .generation showInitialSetupSheet = true } } }