Move settings + generation to sheets
This commit is contained in:
@ -11,10 +11,6 @@ struct AudioSettingsDetailView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
DetailTitle(
|
||||
title: "Audio Player Settings",
|
||||
text: "Configure the files and settings for the audio player components")
|
||||
|
||||
IntegerPropertyView(
|
||||
title: "Playlist Cover Image Size",
|
||||
value: $audioPlayer.playlistCoverImageSize,
|
||||
@ -44,16 +40,3 @@ struct AudioSettingsDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalizedAudioSettingsDetailView: View {
|
||||
|
||||
@ObservedObject
|
||||
var settings: LocalizedAudioPlayerSettings
|
||||
|
||||
var body: some View {
|
||||
StringPropertyView(
|
||||
title: "Playlist Text",
|
||||
text: $settings.playlistText,
|
||||
footer: "The text on the audio player indicating the playlist")
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LocalizedAudioSettingsDetailView: View {
|
||||
|
||||
@ObservedObject
|
||||
var settings: LocalizedAudioPlayerSettings
|
||||
|
||||
var body: some View {
|
||||
StringPropertyView(
|
||||
title: "Playlist Text",
|
||||
text: $settings.playlistText,
|
||||
footer: "The text on the audio player indicating the playlist")
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
private struct FixSheet: View {
|
||||
|
||||
@Binding
|
||||
var isPresented: Bool
|
||||
|
||||
@Binding
|
||||
var message: String
|
||||
|
||||
@Binding
|
||||
var infoItems: [String]
|
||||
|
||||
let action: () -> Void
|
||||
|
||||
init(isPresented: Binding<Bool>, message: Binding<String>, infoItems: Binding<[String]>, action: @escaping () -> Void) {
|
||||
self._isPresented = isPresented
|
||||
self._message = message
|
||||
self._infoItems = infoItems
|
||||
self.action = action
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Fix issue")
|
||||
.font(.headline)
|
||||
Text(message)
|
||||
.font(.body)
|
||||
List {
|
||||
ForEach(infoItems, id: \.self) { item in
|
||||
Text(item)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Button("Fix", action: {
|
||||
isPresented = false
|
||||
action()
|
||||
})
|
||||
Button("Cancel", action: { isPresented = false })
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 200)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct PageSettingsContentView: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@StateObject
|
||||
var checker: PageIssueChecker = .init()
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Button("Check pages", action: { checker.check(pages: content.pages) })
|
||||
.disabled(checker.isCheckingPages)
|
||||
if checker.isCheckingPages {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.frame(height: 20)
|
||||
}
|
||||
}
|
||||
Text("\(checker.issues.count) Issues")
|
||||
.font(.headline)
|
||||
List(checker.issues.sorted()) { issue in
|
||||
HStack {
|
||||
PageIssueView(issue: issue)
|
||||
.id(issue.id)
|
||||
}
|
||||
.environmentObject(checker)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
|
||||
struct PageIssue {
|
||||
|
||||
let page: Page
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
let message: GenerationAnomaly
|
||||
|
||||
init(page: Page, language: ContentLanguage, message: GenerationAnomaly) {
|
||||
self.page = page
|
||||
self.language = language
|
||||
self.message = message
|
||||
|
||||
print("\(title) (\(language)): \(message)")
|
||||
}
|
||||
|
||||
var title: String {
|
||||
page.localized(in: language).title
|
||||
}
|
||||
}
|
||||
|
||||
extension PageIssue: Identifiable {
|
||||
|
||||
var id: String {
|
||||
page.id + "-" + language.rawValue + "-" + message.id
|
||||
}
|
||||
}
|
||||
|
||||
extension PageIssue: Equatable {
|
||||
|
||||
static func == (lhs: PageIssue, rhs: PageIssue) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
extension PageIssue: Hashable {
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
}
|
||||
|
||||
extension PageIssue: Comparable {
|
||||
|
||||
static func < (lhs: PageIssue, rhs: PageIssue) -> Bool {
|
||||
lhs.id < rhs.id
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
final class PageIssueChecker: ObservableObject {
|
||||
|
||||
@Published
|
||||
var isCheckingPages: Bool = false
|
||||
|
||||
@Published
|
||||
var issues: Set<PageIssue> = []
|
||||
|
||||
init() {
|
||||
|
||||
}
|
||||
|
||||
func check(pages: [Page], clearListBeforeStart: Bool = true) {
|
||||
guard !isCheckingPages else {
|
||||
return
|
||||
}
|
||||
isCheckingPages = true
|
||||
if clearListBeforeStart {
|
||||
issues = []
|
||||
}
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
for language in ContentLanguage.allCases {
|
||||
self.check(pages: pages, in: language)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.isCheckingPages = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func check(pages: [Page], in language: ContentLanguage) {
|
||||
for page in pages {
|
||||
analyze(page: page, in: language)
|
||||
}
|
||||
}
|
||||
|
||||
func check(page: Page, in language: ContentLanguage) {
|
||||
guard !isCheckingPages else {
|
||||
return
|
||||
}
|
||||
isCheckingPages = true
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.analyze(page: page, in: language)
|
||||
DispatchQueue.main.async {
|
||||
self.isCheckingPages = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func analyze(page: Page, in language: ContentLanguage) {
|
||||
let results = page.content.results.makeResults(for: page, in: language)
|
||||
let parser = PageContentParser(content: page.content, language: language, results: results)
|
||||
|
||||
let hasPreviousIssues = issues.contains { $0.page == page && $0.language == language }
|
||||
let pageIssues: [PageIssue]
|
||||
if let rawPageContent = page.content.storage.pageContent(for: page.id, language: language) {
|
||||
_ = parser.generatePage(from: rawPageContent)
|
||||
pageIssues = []
|
||||
} else {
|
||||
let message = GenerationAnomaly.failedToLoadContent
|
||||
let error = PageIssue(page: page, language: language, message: message)
|
||||
pageIssues = [error]
|
||||
}
|
||||
guard hasPreviousIssues || !pageIssues.isEmpty else {
|
||||
return
|
||||
}
|
||||
update(issues: pageIssues, for: page, in: language)
|
||||
}
|
||||
|
||||
private func update(issues: [PageIssue], for page: Page, in language: ContentLanguage) {
|
||||
let newIssues = self.issues
|
||||
.filter { $0.page != page || $0.language != language }
|
||||
.union(issues)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.issues = newIssues
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,319 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
private struct ButtonAction {
|
||||
|
||||
let name: String
|
||||
|
||||
let action: () -> Void
|
||||
}
|
||||
|
||||
private struct PopupSheet: View {
|
||||
|
||||
@Binding
|
||||
var isPresented: Bool
|
||||
|
||||
@Binding
|
||||
var title: String
|
||||
|
||||
@Binding
|
||||
var message: String
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
Text(message)
|
||||
Button("Dismiss") {
|
||||
message = ""
|
||||
isPresented = false
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
|
||||
private struct PageIssueGenericView: View {
|
||||
|
||||
let issue: PageIssue
|
||||
|
||||
let buttons: [ButtonAction]
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text(issue.message.description)
|
||||
Text("\(issue.title) (\(issue.language.rawValue.uppercased()))")
|
||||
.font(.caption)
|
||||
}
|
||||
Spacer()
|
||||
ForEach(buttons, id: \.name) { button in
|
||||
Button(button.name, action: button.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PageIssueView: View {
|
||||
|
||||
let issue: PageIssue
|
||||
|
||||
@EnvironmentObject
|
||||
private var checker: PageIssueChecker
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@State
|
||||
private var showPopupMessage = false
|
||||
|
||||
|
||||
@State
|
||||
private var popupTitle = "Error"
|
||||
|
||||
@State
|
||||
private var popupMessage = ""
|
||||
|
||||
@State
|
||||
private var showPagePicker = false
|
||||
|
||||
@State
|
||||
private var selectedPage: Page?
|
||||
|
||||
@State
|
||||
private var showFilePicker = false
|
||||
|
||||
@State
|
||||
private var selectedFile: FileResource?
|
||||
|
||||
private var buttons: [ButtonAction] {
|
||||
switch issue.message {
|
||||
case .warning:
|
||||
return [.init(name: "Retry", action: retryPageCheck)]
|
||||
case .failedToLoadContent:
|
||||
return [.init(name: "Retry", action: retryPageCheck)]
|
||||
case .failedToParseContent:
|
||||
return [.init(name: "Retry", action: retryPageCheck)]
|
||||
case .missingFile(let missing, _):
|
||||
return [
|
||||
.init(name: "Select file", action: { selectFile(missingFile: missing) }),
|
||||
.init(name: "Create external file", action: { createExternalFile(fileId: missing) })
|
||||
]
|
||||
case .missingPage(let missing, _):
|
||||
return [
|
||||
.init(name: "Select page", action: selectPage),
|
||||
.init(name: "Create page", action: { createPage(pageId: missing) })
|
||||
]
|
||||
case .missingTag(let missing, _):
|
||||
return [
|
||||
.init(name: "Select tag", action: { selectTag(missingPage: missing) }),
|
||||
.init(name: "Create tag", action: { createTag(tagId: missing) })
|
||||
]
|
||||
case .invalidCommand(_, let markdown):
|
||||
return [.init(name: "Replace text", action: { replaceCommand(originalText: markdown) })]
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
PageIssueGenericView(issue: issue, buttons: buttons)
|
||||
.sheet(isPresented: $showPopupMessage) {
|
||||
PopupSheet(isPresented: $showPopupMessage, title: $popupTitle, message: $popupMessage)
|
||||
}
|
||||
.sheet(isPresented: $showPagePicker) {
|
||||
if let page = selectedPage {
|
||||
didSelect(page: page)
|
||||
}
|
||||
} content: {
|
||||
PagePickerView(selectedPage: $selectedPage)
|
||||
}
|
||||
.sheet(isPresented: $showFilePicker) {
|
||||
if let file = selectedFile {
|
||||
didSelect(file: file)
|
||||
}
|
||||
} content: {
|
||||
FileSelectionView(selectedFile: $selectedFile)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func show(error: String) {
|
||||
DispatchQueue.main.async {
|
||||
self.popupTitle = "Error"
|
||||
self.popupMessage = error
|
||||
self.showPopupMessage = true
|
||||
}
|
||||
}
|
||||
|
||||
private func show(info: String) {
|
||||
DispatchQueue.main.async {
|
||||
self.popupTitle = "Info"
|
||||
self.popupMessage = info
|
||||
self.showPopupMessage = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func retryPageCheck() {
|
||||
DispatchQueue.main.async {
|
||||
checker.check(pages: content.pages, clearListBeforeStart: false)
|
||||
}
|
||||
}
|
||||
|
||||
private func selectFile(missingFile: String) {
|
||||
selectedFile = nil
|
||||
showFilePicker = true
|
||||
}
|
||||
|
||||
private func didSelect(file newFile: FileResource) {
|
||||
guard case .missingFile(let missingFile, let markdown) = issue.message else {
|
||||
show(error: "Inconsistency: Selected file, but issue is not a missing file")
|
||||
return
|
||||
}
|
||||
replace(missing: missingFile, with: newFile.id, in: markdown)
|
||||
retryPageCheck()
|
||||
DispatchQueue.main.async {
|
||||
selectedFile = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func createExternalFile(fileId: String) {
|
||||
guard content.isValidIdForFile(fileId) else {
|
||||
show(error: "Invalid file id, can't create external file")
|
||||
return
|
||||
}
|
||||
|
||||
let file = FileResource(
|
||||
content: content,
|
||||
id: fileId,
|
||||
isExternallyStored: true,
|
||||
english: "",
|
||||
german: "")
|
||||
content.add(file)
|
||||
|
||||
retryPageCheck()
|
||||
}
|
||||
|
||||
private func selectPage() {
|
||||
selectedPage = nil
|
||||
showPagePicker = true
|
||||
}
|
||||
|
||||
private func didSelect(page newPage: Page) {
|
||||
guard case .missingPage(let missingPage, let markdown) = issue.message else {
|
||||
show(error: "Inconsistency: Selected page, but issue is not a missing page")
|
||||
return
|
||||
}
|
||||
|
||||
replace(missing: missingPage, with: newPage.id, in: markdown)
|
||||
retryPageCheck()
|
||||
DispatchQueue.main.async {
|
||||
selectedPage = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func createPage(pageId: String) {
|
||||
guard content.isValidIdForTagOrPageOrPost(pageId) else {
|
||||
show(error: "Invalid page id, can't create page")
|
||||
return
|
||||
}
|
||||
|
||||
let deString = pageId + "-" + ContentLanguage.german.rawValue
|
||||
|
||||
let page = Page(
|
||||
content: content,
|
||||
id: pageId,
|
||||
externalLink: nil,
|
||||
isDraft: true,
|
||||
createdDate: .now,
|
||||
hideDate: false,
|
||||
startDate: .now,
|
||||
endDate: nil,
|
||||
german: .init(content: content,
|
||||
urlString: deString,
|
||||
title: pageId),
|
||||
english: .init(content: content,
|
||||
urlString: pageId,
|
||||
title: pageId),
|
||||
tags: [],
|
||||
requiredFiles: [])
|
||||
content.pages.insert(page, at: 0)
|
||||
|
||||
retryPageCheck()
|
||||
}
|
||||
|
||||
private func selectTag(missingPage: String) {
|
||||
// TODO: Show sheet to select a tag
|
||||
// TODO: Replace tag id in page content with new tag id
|
||||
|
||||
retryPageCheck()
|
||||
}
|
||||
|
||||
private func createTag(tagId: String) {
|
||||
guard content.isValidIdForTagOrPageOrPost(tagId) else {
|
||||
show(error: "Invalid tag id, can't create tag")
|
||||
return
|
||||
}
|
||||
|
||||
let tag = Tag(content: content, id: tagId)
|
||||
content.tags.append(tag)
|
||||
|
||||
retryPageCheck()
|
||||
}
|
||||
|
||||
private func replaceCommand(originalText: String) {
|
||||
// TODO: Show sheet with text input
|
||||
// TODO: Replace original text in page content with new text
|
||||
|
||||
retryPageCheck()
|
||||
}
|
||||
|
||||
// MARK: Page Content manipulation
|
||||
|
||||
private func replace(missing: String, with newText: String, in markdown: String) {
|
||||
|
||||
let newString = markdown.replacingOccurrences(of: missing, with: newText)
|
||||
guard newString != markdown else {
|
||||
show(error: "No change in content detected trying to perform replacement")
|
||||
return
|
||||
}
|
||||
|
||||
let occurrences = findOccurrences(of: markdown, in: issue.page, language: issue.language)
|
||||
guard !occurrences.isEmpty else {
|
||||
show(error: "No occurrences of '\(markdown)' found in the page")
|
||||
return
|
||||
}
|
||||
replace(markdown, with: newString, in: issue.page, language: issue.language)
|
||||
|
||||
show(info: "Replaced \(occurrences.count) occurrences of '\(missing)' with '\(newText)'")
|
||||
|
||||
retryPageCheck()
|
||||
}
|
||||
|
||||
private func replace(_ oldString: String, with newString: String, in page: Page, language: ContentLanguage) {
|
||||
guard let pageContent = content.storage.pageContent(for: page.id, language: language) else {
|
||||
print("Failed to replace in page \(page.id) (\(language)), no content")
|
||||
return
|
||||
}
|
||||
let modified = pageContent.replacingOccurrences(of: oldString, with: newString)
|
||||
|
||||
guard content.storage.save(pageContent: modified, for: page.id, in: language) else {
|
||||
print("Replaced \(oldString) with \(newString) in page \(page.id) (\(language))")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func findOccurrences(of searchString: String, in page: Page, language: ContentLanguage) -> [String] {
|
||||
guard let parts = content.storage.pageContent(for: page.id, language: language)?
|
||||
.components(separatedBy: searchString) else {
|
||||
print("Failed to get page content to find occurrences, no content")
|
||||
return []
|
||||
}
|
||||
|
||||
var occurrences: [String] = []
|
||||
for index in parts.indices.dropLast() {
|
||||
let start = parts[index].suffix(10)
|
||||
let end = parts[index+1].prefix(10)
|
||||
let full = "...\(start)\(searchString)\(end)...".replacingOccurrences(of: "\n", with: "\\n")
|
||||
occurrences.append(full)
|
||||
}
|
||||
return occurrences
|
||||
}
|
||||
}
|
@ -8,9 +8,6 @@ struct GeneralSettingsDetailView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
DetailTitle(title: "General",
|
||||
text: "General settings for the webpage")
|
||||
|
||||
StringPropertyView(
|
||||
title: "Website URL",
|
||||
text: $generalSettings.url,
|
@ -1,123 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct GenerationContentView: View {
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@Binding
|
||||
private var selectedSection: SettingsSection
|
||||
|
||||
init(selected: Binding<SettingsSection>) {
|
||||
self._selectedSection = selected
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
switch selectedSection {
|
||||
case .pages:
|
||||
PageSettingsContentView()
|
||||
default:
|
||||
generationView
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var generationView: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Website Generation")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
Text("Regenerate the website and monitor the output")
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.bottom, 30)
|
||||
|
||||
HStack {
|
||||
Button {
|
||||
if content.isGeneratingWebsite {
|
||||
content.endCurrentGeneration()
|
||||
} else {
|
||||
content.generateWebsiteInAllLanguages()
|
||||
}
|
||||
} label: {
|
||||
Text(content.isGeneratingWebsite ? "Cancel" : "Generate")
|
||||
}
|
||||
.disabled(content.isGeneratingWebsite != content.shouldGenerateWebsite)
|
||||
if content.isGeneratingWebsite {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.frame(height: 25)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Text(content.generationStatus)
|
||||
.padding(.vertical, 5)
|
||||
HStack(spacing: 8) {
|
||||
Text("\(content.results.imagesToGenerate.count) images")
|
||||
Text("\(content.results.externalLinks.count) external links")
|
||||
Text("\(content.results.resultCount) items processed")
|
||||
Text("\(content.results.requiredFiles.count) files")
|
||||
}
|
||||
List {
|
||||
Section("Inaccessible files (\(content.results.inaccessibleFiles.count))") {
|
||||
ForEach(content.results.inaccessibleFiles.sorted()) { file in
|
||||
Text(file.id)
|
||||
}
|
||||
}
|
||||
Section("Unparsable files (\(content.results.unparsableFiles.count))") {
|
||||
ForEach(content.results.unparsableFiles.sorted()) { file in
|
||||
Text(file.id)
|
||||
}
|
||||
}
|
||||
Section("Missing files (\(content.results.missingFiles.count))") {
|
||||
ForEach(content.results.missingFiles.sorted(), id: \.self) { file in
|
||||
Text(file)
|
||||
}
|
||||
}
|
||||
Section("Missing tags (\(content.results.missingTags.count))") {
|
||||
ForEach(content.results.missingTags.sorted(), id: \.self) { tag in
|
||||
Text(tag)
|
||||
}
|
||||
}
|
||||
Section("Missing pages (\(content.results.missingPages.count))") {
|
||||
ForEach(content.results.missingPages.sorted(), id: \.self) { page in
|
||||
Text(page)
|
||||
}
|
||||
}
|
||||
Section("Invalid commands (\(content.results.invalidCommands.count))") {
|
||||
ForEach(content.results.invalidCommands.sorted(), id: \.self) { markdown in
|
||||
Text(markdown)
|
||||
}
|
||||
}
|
||||
Section("Invalid blocks (\(content.results.invalidBlocks.count))") {
|
||||
ForEach(content.results.invalidBlocks.sorted(), id: \.self) { markdown in
|
||||
Text(markdown)
|
||||
}
|
||||
}
|
||||
Section("Warnings (\(content.results.warnings.count))") {
|
||||
ForEach(content.results.warnings.sorted(), id: \.self) { warning in
|
||||
Text(warning)
|
||||
}
|
||||
}
|
||||
Section("Unsaved output files (\(content.results.unsavedOutputFiles.count))") {
|
||||
ForEach(content.results.unsavedOutputFiles.sorted(), id: \.self) { file in
|
||||
Text(file)
|
||||
}
|
||||
}
|
||||
Section("Empty pages (\(content.results.emptyPages.count))") {
|
||||
ForEach(content.results.emptyPages.sorted()) { id in
|
||||
Text("\(id.pageId) (\(id.language))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
GenerationContentView(selected: .constant(.folders))
|
||||
.environmentObject(Content.mock)
|
||||
.padding()
|
||||
}
|
@ -15,10 +15,6 @@ struct NavigationBarSettingsView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
DetailTitle(
|
||||
title: "Navigation Bar",
|
||||
text: "Customize the navigation bar for all pages at the top of the website")
|
||||
|
||||
HStack {
|
||||
Text("Links")
|
||||
.font(.headline)
|
@ -11,10 +11,6 @@ struct PageSettingsDetailView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
DetailTitle(
|
||||
title: "Page Settings",
|
||||
text: "Change the way pages are displayed")
|
||||
|
||||
IntegerPropertyView(
|
||||
title: "Content Width",
|
||||
value: $pageSettings.contentWidth,
|
@ -17,10 +17,6 @@ struct PathSettingsView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
DetailTitle(
|
||||
title: "Folder Settings",
|
||||
text: "Select the folders for the app to work.")
|
||||
|
||||
FolderOnDiskPropertyView(
|
||||
title: "Content Folder",
|
||||
folder: $content.storage.contentScope,
|
@ -17,9 +17,6 @@ struct PostFeedSettingsView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
DetailTitle(title: "Post Feed Settings",
|
||||
text: "Change the way the posts are displayed")
|
||||
|
||||
IntegerPropertyView(
|
||||
title: "Content Width",
|
||||
value: $postSettings.contentWidth,
|
@ -1,6 +1,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct GenerationDetailView: View {
|
||||
struct SettingsContentView: View {
|
||||
|
||||
let section: SettingsSection
|
||||
|
||||
@ -11,7 +11,7 @@ struct GenerationDetailView: View {
|
||||
switch section {
|
||||
case .general:
|
||||
GeneralSettingsDetailView(generalSettings: content.settings.general)
|
||||
case .folders:
|
||||
case .paths:
|
||||
PathSettingsView()
|
||||
case .navigationBar:
|
||||
NavigationBarSettingsView()
|
||||
@ -28,5 +28,5 @@ struct GenerationDetailView: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
GenerationDetailView(section: .folders)
|
||||
SettingsContentView(section: .paths)
|
||||
}
|
@ -2,20 +2,18 @@ import SwiftUI
|
||||
|
||||
struct SettingsListView: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var selection: SelectedContent
|
||||
@Binding
|
||||
var section: SettingsSection
|
||||
|
||||
|
||||
var body: some View {
|
||||
List(SettingsSection.allCases) { section in
|
||||
Label(section.rawValue, systemSymbol: section.icon)
|
||||
.listRowBackground(RoundedRectangle(cornerRadius: 5)
|
||||
.fill(selection.section == section ? Color.blue : Color.clear)
|
||||
.padding(.horizontal, 10)
|
||||
)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
selection.section = section
|
||||
}
|
||||
SelectableListItem(selected: self.section == section) {
|
||||
Label(section.rawValue, systemSymbol: section.icon)
|
||||
}
|
||||
.onTapGesture {
|
||||
self.section = section
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ enum SettingsSection: String {
|
||||
|
||||
case general = "General"
|
||||
|
||||
case folders = "Folders"
|
||||
case paths = "Paths"
|
||||
|
||||
case navigationBar = "Navigation Bar"
|
||||
|
||||
@ -23,7 +23,7 @@ extension SettingsSection {
|
||||
var icon: SFSymbol {
|
||||
switch self {
|
||||
case .general: return .noteText
|
||||
case .folders: return .folder
|
||||
case .paths: return .folder
|
||||
case .navigationBar: return .menubarArrowUpRectangle
|
||||
case .postFeed: return .rectangleGrid1x2
|
||||
case .pages: return .docRichtext
|
||||
|
70
CHDataManagement/Views/Settings/SettingsSheet.swift
Normal file
70
CHDataManagement/Views/Settings/SettingsSheet.swift
Normal file
@ -0,0 +1,70 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsSheet: View {
|
||||
|
||||
private let sidebarWidth: CGFloat = 250
|
||||
|
||||
private let contentWidth: CGFloat = 300
|
||||
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss
|
||||
|
||||
@Binding
|
||||
var language: ContentLanguage
|
||||
|
||||
@State
|
||||
var section: SettingsSection = .general
|
||||
|
||||
private var title: String {
|
||||
switch section {
|
||||
case .general: "General Settings"
|
||||
case .paths: "Folder Settings"
|
||||
case .navigationBar: "Navigation Bar Settings"
|
||||
case .postFeed: "Post Feed Settings"
|
||||
case .pages: "Pages Settings"
|
||||
case .tagOverview: "Tag Overview Settings"
|
||||
case .audioPlayer: "Audio Player Settings"
|
||||
}
|
||||
}
|
||||
|
||||
private var text: String {
|
||||
switch section {
|
||||
case .general: "General settings for the webpage"
|
||||
case .paths: "Select the folders for the app to work."
|
||||
case .navigationBar: "Customize the navigation bar for all pages at the top of the website"
|
||||
case .postFeed: "Change the way the posts are displayed"
|
||||
case .pages: "Change the way pages are displayed"
|
||||
case .tagOverview: "Configure the page showing all tags"
|
||||
case .audioPlayer: "Configure the files and settings for the audio player components"
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .top) {
|
||||
DetailTitle(
|
||||
title: title,
|
||||
text: text)
|
||||
Spacer()
|
||||
Picker("", selection: $language) {
|
||||
Text("English")
|
||||
.tag(ContentLanguage.english)
|
||||
Text("German")
|
||||
.tag(ContentLanguage.german)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
Button(action: { dismiss() }) {
|
||||
Text("Close")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color(NSColor.windowBackgroundColor))
|
||||
NavigationSplitView {
|
||||
SettingsListView(section: $section)
|
||||
.navigationSplitViewColumnWidth(min: sidebarWidth, ideal: sidebarWidth, max: sidebarWidth)
|
||||
} detail: {
|
||||
SettingsContentView(section: section)
|
||||
}
|
||||
}.frame(width: 550, height: 600)
|
||||
}
|
||||
}
|
@ -15,10 +15,6 @@ struct TagOverviewDetailView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
DetailTitle(
|
||||
title: "Tag Overview",
|
||||
text: "Configure the page showing all tags")
|
||||
|
||||
if let tag = content.tagOverview?.localized(in: language) {
|
||||
LocalizedTagDetailView(
|
||||
tag: tag,
|
Reference in New Issue
Block a user