From cf17b513d050852caf6381b55ee002566a1503c5 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Sun, 23 Feb 2025 20:34:02 +0100 Subject: [PATCH] Combine initial and storage view --- CHDataManagement.xcodeproj/project.pbxproj | 8 +- CHDataManagement/Main/MainView.swift | 31 +---- CHDataManagement/Main/StorageErrorView.swift | 33 ----- CHDataManagement/Main/StorageStatusView.swift | 121 ++++++++++++++++++ .../Settings/Paths/PathSettingsView.swift | 2 +- 5 files changed, 132 insertions(+), 63 deletions(-) delete mode 100644 CHDataManagement/Main/StorageErrorView.swift create mode 100644 CHDataManagement/Main/StorageStatusView.swift diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index 221b930..e463885 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -56,7 +56,7 @@ E2521DFC2D5020BE00C56662 /* PostContentGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2521DFB2D501DAE00C56662 /* PostContentGenerator.swift */; }; E2521E002D50BB6E00C56662 /* ItemLinkResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2521DFF2D50BB6E00C56662 /* ItemLinkResults.swift */; }; E2521E022D51776300C56662 /* StorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2521E012D51776000C56662 /* StorageError.swift */; }; - E2521E042D51796000C56662 /* StorageErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2521E032D51795B00C56662 /* StorageErrorView.swift */; }; + E2521E042D51796000C56662 /* StorageStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2521E032D51795B00C56662 /* StorageStatusView.swift */; }; E2581DED2C75202400F1F079 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2581DEC2C75202400F1F079 /* Tag.swift */; }; E25DA5092CFD964E00AEF16D /* TagContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5082CFD964E00AEF16D /* TagContentView.swift */; }; E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA50A2CFD988100AEF16D /* PageTagAssignmentView.swift */; }; @@ -331,7 +331,7 @@ E2521DFB2D501DAE00C56662 /* PostContentGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostContentGenerator.swift; sourceTree = ""; }; E2521DFF2D50BB6E00C56662 /* ItemLinkResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemLinkResults.swift; sourceTree = ""; }; E2521E012D51776000C56662 /* StorageError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageError.swift; sourceTree = ""; }; - E2521E032D51795B00C56662 /* StorageErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageErrorView.swift; sourceTree = ""; }; + E2521E032D51795B00C56662 /* StorageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageStatusView.swift; sourceTree = ""; }; E2581DEC2C75202400F1F079 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = ""; }; E25A0B882CE4021400F33674 /* LocalizedPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPage.swift; sourceTree = ""; }; E25DA5082CFD964E00AEF16D /* TagContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagContentView.swift; sourceTree = ""; }; @@ -719,7 +719,7 @@ E29D31372D043EB80051B7F4 /* Main */ = { isa = PBXGroup; children = ( - E2521E032D51795B00C56662 /* StorageErrorView.swift */, + E2521E032D51795B00C56662 /* StorageStatusView.swift */, E2FD1D332D3BA2DE00B48627 /* SelectedContent.swift */, E229904B2D10BE59009F8D77 /* InitialSetupView.swift */, E29D31422D0488950051B7F4 /* MainContentView.swift */, @@ -1290,7 +1290,7 @@ E29D317D2D086AB00051B7F4 /* Int+Random.swift in Sources */, E2BF1BC62D6B16FF003089F1 /* HeadlineLink.swift in Sources */, E25DA56F2D00F9A100AEF16D /* PostFeedSettingsView.swift in Sources */, - E2521E042D51796000C56662 /* StorageErrorView.swift in Sources */, + E2521E042D51796000C56662 /* StorageStatusView.swift in Sources */, E29D313B2D04464A0051B7F4 /* LocalizedTagDetailView.swift in Sources */, E2FE0F552D2BCFC4002963B7 /* ContentBlock.swift in Sources */, E2A37D212CEA94EC0000979F /* Sequence+Sorted.swift in Sources */, diff --git a/CHDataManagement/Main/MainView.swift b/CHDataManagement/Main/MainView.swift index e3d148d..b3b4d11 100644 --- a/CHDataManagement/Main/MainView.swift +++ b/CHDataManagement/Main/MainView.swift @@ -33,7 +33,7 @@ struct MainView: App { private var showInitialSetupSheet = false @State - private var showStorageErrorSheet = false + private var showStorageStatusSheet = false @State private var showSettingsSheet = false @@ -176,7 +176,7 @@ struct MainView: App { } } ToolbarItem { - Button(action: saveButtonPressed) { + Button(action: { showStorageStatusSheet = true }) { Image(systemSymbol: content.saveState.symbol) .foregroundStyle(content.saveState.color) } @@ -199,8 +199,8 @@ struct MainView: App { .environmentObject(content) .environmentObject(selection) } - .sheet(isPresented: $showStorageErrorSheet) { - StorageErrorView(isPresented: $showStorageErrorSheet) + .sheet(isPresented: $showStorageStatusSheet) { + StorageStatusView(isPresented: $showStorageStatusSheet) .environmentObject(content) } .sheet(isPresented: $showSettingsSheet) { @@ -230,28 +230,15 @@ struct MainView: App { } } - 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() + showStorageStatusSheet = true return } content.loadFromDisk { prepareAfterLoad() if !content.storageErrors.isEmpty { - self.showStorageErrorSheet = true + self.showStorageStatusSheet = true } } } @@ -270,11 +257,5 @@ struct MainView: App { selection.file = content.files.first } } - - private func showInitialSheet() { - DispatchQueue.main.async { - showInitialSetupSheet = true - } - } } diff --git a/CHDataManagement/Main/StorageErrorView.swift b/CHDataManagement/Main/StorageErrorView.swift deleted file mode 100644 index 9f53b4d..0000000 --- a/CHDataManagement/Main/StorageErrorView.swift +++ /dev/null @@ -1,33 +0,0 @@ -import SwiftUI - -struct StorageErrorView: View { - - @EnvironmentObject - private var content: Content - - @Binding - var isPresented: Bool - - var body: some View { - VStack { - Text("Failed to load database") - .font(.headline) - List(content.storageErrors) { error in - VStack { - Text(error.message) - Text(error.date.formatted()) - .font(.footnote) - } - } - .frame(minHeight: 300) - if content.saveState == .savingPausedDueToLoadErrors { - Button("Allow saving", action: { content.resumeSavingAfterLoadingErrors() }) - .padding() - Text("Saving has been disabled to prevent data corruption due to loading errors. Enable saving to save the partially loaded data.") - } - Button("Dismiss", action: { isPresented = false }) - .padding() - } - .padding() - } -} diff --git a/CHDataManagement/Main/StorageStatusView.swift b/CHDataManagement/Main/StorageStatusView.swift new file mode 100644 index 0000000..ff0e08c --- /dev/null +++ b/CHDataManagement/Main/StorageStatusView.swift @@ -0,0 +1,121 @@ +import SwiftUI + +struct StorageStatusView: View { + + @EnvironmentObject + private var content: Content + + @Binding + var isPresented: Bool + + var title: String { + switch content.saveState { + case .storageNotInitialized: + "No Database Loaded" + case .savingPausedDueToLoadErrors: + "Database corrupted" + case .isSaved: + "Database is healthy" + case .needsSave: + "Database will be saved soon" + case .failedToSave: + "Database errors" + case .isSaving: + "Database is being saved" + } + } + + var subtitle: String { + switch content.saveState { + case .storageNotInitialized: + "To start editing the content of a website, create a new database or load an existing one. Open a folder with an existing database, or choose an empty folder to create a new project." + case .savingPausedDueToLoadErrors: + "The errors loading the database are listed below. Saving has been disabled to prevent data corruption. Enable saving to save the partially loaded data." + case .isSaved: + "All data is saved to disk" + case .needsSave: + "Wait for a few seconds until the remaining changes are saved." + case .failedToSave: + "The errors while saving the database are listed below." + case .isSaving: + "Changes are being written to disk" + } + } + + var buttonText: String { + switch content.saveState { + case .storageNotInitialized: + "Choose folder" + case .savingPausedDueToLoadErrors: + "Save partial data" + case .isSaved, .isSaving, .needsSave: + "Save database" + case .failedToSave: + "Attempt save again" + } + } + + var body: some View { + VStack { + Text(title) + .font(.title) + .padding() + Text(subtitle) + .multilineTextAlignment(.center) + + List(content.storageErrors) { error in + VStack { + Text(error.message) + Text(error.date.formatted()) + .font(.footnote) + } + } + .frame(minHeight: 300) + Button(buttonText, action: saveButtonPressed) + .padding() + .disabled(content.saveState == .isSaving) + Button("Dismiss", action: { isPresented = false }) + .padding() + } + .padding() + } + + func saveButtonPressed() { + switch content.saveState { + case .storageNotInitialized: + selectContentPath() + case .savingPausedDueToLoadErrors: + content.resumeSavingAfterLoadingErrors() + case .isSaved, .needsSave, .failedToSave: + content.saveUnconditionally() + case .isSaving: + break + } + } + + private func selectContentPath() { + 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 the database folder" + + let response = panel.runModal() + guard response == .OK else { + content.storageErrors.append(.init(message: "Failed to select a folder: \(response)")) + return + } + guard let url = panel.url else { + content.storageErrors.append(.init(message: "No url found for selected content folder")) + return + } + + guard content.storage.save(contentPath: url) else { + content.storageErrors.append(.init(message: "Failed to set content path")) + return + } + content.loadFromDisk { } + } +} diff --git a/CHDataManagement/Views/Settings/Paths/PathSettingsView.swift b/CHDataManagement/Views/Settings/Paths/PathSettingsView.swift index 139aab3..5f22e47 100644 --- a/CHDataManagement/Views/Settings/Paths/PathSettingsView.swift +++ b/CHDataManagement/Views/Settings/Paths/PathSettingsView.swift @@ -68,7 +68,7 @@ struct PathSettingsView: View { } .padding() .sheet(isPresented: $showLoadErrorSheet) { - StorageErrorView(isPresented: $showLoadErrorSheet) + StorageStatusView(isPresented: $showLoadErrorSheet) } } }