import SwiftUI import SFSafeSymbols /** **Content** - Podcast: Fix audio player, preview image - Article Cap Mosaic: -> GIF feature - iPhone Backgrounds: Add page, html **UI** - Image search: Add view to see all images and filter - Pages: Show linking posts - Page Content: Show all results of `PageGenerationResults` - Files: Show usages of file - Images: Show list of generated versions **Features** - Files: Optional Property `customFilePath` for external files to place them in another location - Files: Property `version` and `sourceUrl` to track asset files - Posts: Generate separate pages for posts to link to - Settings: Introduce `Authors` (`name`, `image`, `description`) - Page: Property `author` - Video: Specify versions **Generation** - ImageSet: Specify image aspect ratio (width, height) to prevent page jumps - 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 - Mock content: Clean and improve */ @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 @State private var selectedTab: MainViewTab = .posts @State private var selectedPost: Post? @State private var selectedPage: Page? @State private var selectedTag: Tag? @State private var selectedFile: FileResource? @State private var selectedSection: SettingsSection = .folders @State private var showAddSheet = false @State private var showInitialSetupSheet = false @ViewBuilder var sidebar: some View { switch selectedTab { case .posts: PostListView(selectedPost: $selectedPost) case .pages: PageListView(selectedPage: $selectedPage) case .tags: TagListView(selectedTag: $selectedTag) case .files: FileListView(selectedFile: $selectedFile) case .generation: SettingsListView(selectedSection: $selectedSection) } } @ViewBuilder var viewContent: some View { switch selectedTab { case .posts: SelectedContentView(selected: $selectedPost) case .pages: SelectedContentView(selected: $selectedPage) case .tags: SelectedContentView(selected: $selectedTag) case .files: SelectedContentView(selected: $selectedFile) case .generation: GenerationContentView(selected: $selectedSection) } } @ViewBuilder var detail: some View { switch selectedTab { case .posts: SelectedDetailView(selected: $selectedPost) case .pages: SelectedDetailView(selected: $selectedPage) case .tags: SelectedDetailView(selected: $selectedTag) case .files: SelectedDetailView(selected: $selectedFile) case .generation: GenerationDetailView(section: selectedSection) } } @ViewBuilder var addItemSheet: some View { switch selectedTab { case .posts: AddPostView(selected: $selectedPost) case .pages: AddPageView(selected: $selectedPage) case .tags: AddTagView(selected: $selectedTag) case .files: AddFileView(selectedFile: $selectedFile) case .generation: Text("Not implemented") } } var body: some Scene { WindowGroup { NavigationSplitView { sidebar .toolbar { ToolbarItem(placement: .navigation) { Picker("", selection: $selectedTab) { 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(!selectedTab.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) { if content.storage.contentScope != nil { Button(action: save) { Text("Save") } } else { Button(action: showInitialSheet) { Text("Setup") } .background(RoundedRectangle(cornerRadius: 8).fill(Color.red)) } } } .navigationTitle("") .environment(\.language, language) .environmentObject(content) .onAppear(perform: loadContent) .onReceive(Timer.publish(every: 60.0, on: .main, in: .common).autoconnect()) { _ in save() } .sheet(isPresented: $showAddSheet) { addItemSheet .environment(\.language, language) .environmentObject(content) } .sheet(isPresented: $showInitialSetupSheet) { InitialSetupView() .environment(\.language, language) .environmentObject(content) } } } private func save() { do { try content.saveToDisk() } catch { print("Failed to save content: \(error.localizedDescription)") } } private func loadContent() { guard content.storage.contentScope != nil else { showInitialSheet() return } do { try content.loadFromDisk() } catch { print("Failed to load content: \(error.localizedDescription)") } } private func showInitialSheet() { DispatchQueue.main.async { selectedSection = .folders selectedTab = .generation showInitialSetupSheet = true } } }