2025-02-18 22:08:36 +01:00

281 lines
9.0 KiB
Swift

import SwiftUI
import SFSafeSymbols
@main
struct MainView: App {
private let sidebarWidth: CGFloat = 250
private let detailWidth: CGFloat = 300
@StateObject
private var server: WebServer = .init(port: 8000)
@StateObject
private var content: Content = .init()
@StateObject
private var upload: RemotePush = .init()
@StateObject
private var notifications: NotificationSender = .init()
@State
private var language: ContentLanguage = .english
@StateObject
private var selection: SelectedContent = .init()
@State
private var showAddSheet = false
@State
private var showInitialSetupSheet = false
@State
private var showStorageErrorSheet = false
@State
private var showSettingsSheet = false
@State
private var showGenerationSheet = false
@State
private var showPreviewSheet = false
@State
private var showUploadSheet = false
@State
private var showNotificationsSheet = false
@ViewBuilder
var sidebar: some View {
switch selection.tab {
case .posts: PostListView()
case .pages: PageListView()
case .tags: TagListView()
case .files: FileListView(selectedFile: $selection.file)
}
}
@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)
}
}
@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)
}
}
@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)
}
}
var body: some Scene {
WindowGroup {
NavigationSplitView {
sidebar
.navigationSplitViewColumnWidth(min: sidebarWidth, ideal: sidebarWidth, max: sidebarWidth)
.toolbar {
ToolbarItem(placement: .automatic) {
Button(action: { showAddSheet = true }) {
Label("Add", systemSymbol: .plus)
}
}
}
} content: {
viewContent
.toolbar {
ToolbarItem(placement: .navigation) {
HStack {
Picker("", selection: $selection.tab) {
Text("Posts").tag(MainViewTab.posts)
Text("Pages").tag(MainViewTab.pages)
Text("Tags").tag(MainViewTab.tags)
Text("Files").tag(MainViewTab.files)
}.pickerStyle(.segmented)
}.frame(minWidth: 400)
}
}
} detail: {
detail
.navigationSplitViewColumnWidth(min: detailWidth, ideal: detailWidth, max: detailWidth)
}
.toolbar {
ToolbarItem {
Picker("", selection: $language) {
Text("English")
.tag(ContentLanguage.english)
Text("German")
.tag(ContentLanguage.german)
}
.pickerStyle(.segmented)
.frame(minWidth: 200)
}
ToolbarItem {
Button(action: { showSettingsSheet = true }) {
Image(systemSymbol: .gearshape)
}
}
ToolbarItem {
Button(action: { showGenerationSheet = true }) {
if content.isGeneratingWebsite {
ProgressView()
.scaleEffect(0.6)
.frame(width: 20, height: 20, alignment: .center)
} else {
Image(systemSymbol: .globe)
.frame(width: 20, height: 20, alignment: .center)
}
}
}
ToolbarItem {
Button(action: { showPreviewSheet = true }) {
Image(systemSymbol: .eye)
}
}
ToolbarItem {
Button(action: { showUploadSheet = true }) {
Image(systemSymbol: .squareAndArrowUp)
}
}
ToolbarItem {
Button(action: { showNotificationsSheet = true }) {
Image(systemSymbol: .bell)
}
}
ToolbarItem {
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)
}
.sheet(isPresented: $showSettingsSheet) {
SettingsSheet(language: $language)
.environmentObject(content)
.presentedWindowStyle(.titleBar)
}
.sheet(isPresented: $showGenerationSheet) {
GenerationContentView()
.environmentObject(content)
}
.sheet(isPresented: $showPreviewSheet) {
WebsitePreviewSheet()
.environmentObject(content)
.environmentObject(server)
}
.sheet(isPresented: $showUploadSheet) {
UploadSheet()
.environmentObject(content)
.environmentObject(upload)
}
.sheet(isPresented: $showNotificationsSheet) {
NotificationSheet()
.environmentObject(content)
.environmentObject(notifications)
}
}
}
private func saveButtonPressed() {
switch content.saveState {
case .storageNotInitialized:
showInitialSheet()
case .isSaved, .needsSave:
content.saveUnconditionally()
case .isSaving:
break
case .failedToSave, .savingPausedDueToLoadErrors:
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 {
showInitialSetupSheet = true
}
}
}