Christoph Hagen 1d4b3c266c Prepare for selection of items
Update MainView.swift
2025-01-18 12:02:54 +01:00

265 lines
8.5 KiB
Swift

import SwiftUI
import SFSafeSymbols
/**
**Content**
- iPhone Backgrounds: Add page, html
- CV: Update PDF
**UI**
- Image search: Add view to see all images and filter
- Page Content: Show all results of `PageGenerationResults`
- Files: Show usages of file
- Buttons to insert special commands (images, page links, ...)
- Post: Create page from information of post
**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
- Mock content: Clean and improve
- Investigate issue with spaces in content file names
- Check assignment of blog posts to tags
*/
@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 showLoadErrorSheet = false
@State
private var loadErrors: [String] = []
@ViewBuilder
var sidebar: some View {
switch selection.tab {
case .posts:
PostListView(selectedPost: $selection.post)
case .pages:
PageListView(selectedPage: $selection.page)
case .tags:
TagListView(selectedTag: $selection.tag)
case .files:
FileListView(selectedFile: $selection.file)
case .generation:
SettingsListView(selectedSection: $selection.section)
}
}
@ViewBuilder
var viewContent: some View {
switch selection.tab {
case .posts:
SelectedContentView<PostContentView>(selected: $selection.post)
case .pages:
SelectedContentView<PageContentView>(selected: $selection.page)
case .tags:
SelectedContentView<TagContentView>(selected: $selection.tag)
case .files:
SelectedContentView<FileContentView>(selected: $selection.file)
case .generation:
GenerationContentView(selected: $selection.section)
}
}
@ViewBuilder
var detail: some View {
switch selection.tab {
case .posts:
SelectedDetailView<PostDetailView>(selected: $selection.post)
case .pages:
SelectedDetailView<PageDetailView>(selected: $selection.page)
case .tags:
SelectedDetailView<TagDetailView>(selected: $selection.tag)
case .files:
SelectedDetailView<FileDetailView>(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) {
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)
.environmentObject(selection)
.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)
.environmentObject(selection)
}
.sheet(isPresented: $showInitialSetupSheet) {
InitialSetupView()
.environment(\.language, language)
.environmentObject(content)
.environmentObject(selection)
}
.sheet(isPresented: $showLoadErrorSheet) {
VStack {
Text("Failed to load database")
.font(.headline)
List(loadErrors, id: \.self) { error in
HStack {
Text(error)
Spacer()
}
}
.frame(minHeight: 200)
Button("Dismiss", action: { showLoadErrorSheet = false })
.padding()
}
.padding()
}
}
}
private func save() {
guard content.saveToDisk() else {
print("Failed to save content")
#warning("Show error message")
return
}
}
private func loadContent() {
guard content.storage.contentScope != nil else {
showInitialSheet()
return
}
content.loadFromDisk { errors in
prepareAfterLoad()
guard !errors.isEmpty else {
return
}
self.loadErrors = errors
self.showLoadErrorSheet = 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
}
}
}