Move settings + generation to sheets

This commit is contained in:
Christoph Hagen
2025-02-05 15:40:09 +01:00
parent 5abe6e1a9f
commit 156bbf77d1
32 changed files with 402 additions and 852 deletions

View File

@ -52,15 +52,9 @@ struct FileListView: View {
.textFieldStyle(.roundedBorder)
.padding(.horizontal, 8)
List(filteredFiles) { file in
HStack {
SelectableListItem(selected: selectedFile == file) {
Text(file.id)
Spacer()
}
.listRowBackground(RoundedRectangle(cornerRadius: 5)
.fill(selectedFile == file ? Color.blue : Color.clear)
.padding(.horizontal, 10)
)
.contentShape(Rectangle())
.onTapGesture {
selectedFile = file
}

View File

@ -0,0 +1,115 @@
import SwiftUI
struct GenerationContentView: View {
@Environment(\.language)
private var language
@EnvironmentObject
private var content: Content
@Environment(\.dismiss)
private var dismiss
var body: 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))")
}
}
}
.frame(minHeight: 400)
HorizontalCenter {
Button(action: { dismiss() }) {
Text("Close")
}
}
}.padding()
}
}
#Preview {
GenerationContentView()
.environmentObject(Content.mock)
.padding()
}

View File

@ -0,0 +1,26 @@
import SwiftUI
struct SelectableListItem<Content>: View where Content: View {
let content: Content
let selected: Bool
public init(selected: Bool, @ViewBuilder content: () -> Content) {
self.selected = selected
self.content = content()
}
var body: some View {
HStack {
content
Spacer()
}
.foregroundStyle(selected ? Color.white : Color.primary)
.listRowBackground(RoundedRectangle(cornerRadius: 5)
.fill(selected ? Color.blue : Color.clear)
.padding(.horizontal, 10)
)
.contentShape(Rectangle())
}
}

View File

@ -38,6 +38,7 @@ struct LocalizedPageContentView: View {
}.disabled(content.isGeneratingWebsite)
if content.isGeneratingWebsite {
ProgressView()
.scaleEffect(0.6)
.frame(height: 15)
}
Spacer()

View File

@ -31,30 +31,12 @@ struct PageDetailView: View {
}
}
#warning("Show info on page generation")
var body: some View {
ScrollView {
VStack(alignment: .leading) {
DetailTitle(
title: "Page",
text: "A page contains longer content")
HStack(alignment: .firstTextBaseline) {
Button(action: generate) {
Text("Generate")
}
.disabled(content.isGeneratingWebsite)
// switch didGenerateWebsite {
// case .none:
// Image(systemSymbol: .questionmarkCircleFill)
// .foregroundStyle(.gray)
// case .some(true):
// Image(systemSymbol: .checkmarkCircleFill)
// .foregroundStyle(.green)
// case .some(false):
// Image(systemSymbol: .xmarkCircleFill)
// .foregroundStyle(.red)
// }
}
IdPropertyView(
id: $page.id,
footer: "The page id is used to link to it internally.",
@ -127,12 +109,6 @@ struct PageDetailView: View {
MultiFileSelectionView(selectedFiles: $page.requiredFiles, insertSorted: true)
}
}
private func generate() {
DispatchQueue.global(qos: .userInitiated).async {
content.generatePage(page)
}
}
}
extension PageDetailView: MainContentView {

View File

@ -64,15 +64,12 @@ struct PageListView: View {
.textFieldStyle(.roundedBorder)
.padding(.horizontal, 8)
List(filteredPages) { page in
PageListItem(page: page)
.listRowBackground(RoundedRectangle(cornerRadius: 5)
.fill(selection.page == page ? Color.blue : Color.clear)
.padding(.horizontal, 10)
)
.contentShape(Rectangle())
.onTapGesture {
selection.page = page
}
SelectableListItem(selected: selection.page == page) {
PageListItem(page: page)
}
.onTapGesture {
selection.page = page
}
}
}
.onAppear {

View File

@ -73,15 +73,12 @@ struct PostListView: View {
.textFieldStyle(.roundedBorder)
.padding(.horizontal, 8)
List(filteredAndSortedPosts) { post in
PostListItem(post: post)
.listRowBackground(RoundedRectangle(cornerRadius: 5)
.fill(selection.post == post ? Color.blue : Color.clear)
.padding(.horizontal, 10)
)
.contentShape(Rectangle())
.onTapGesture {
selection.post = post
}
SelectableListItem(selected: selection.post == post) {
PostListItem(post: post)
}
.onTapGesture {
selection.post = post
}
}
}.onAppear {
if selection.post == nil, let first = content.posts.first {

View File

@ -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")
}
}

View File

@ -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")
}
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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,

View File

@ -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()
}

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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)
}

View File

@ -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
}
}
}
}

View File

@ -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

View 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)
}
}

View File

@ -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,

View File

@ -33,15 +33,12 @@ struct TagListView: View {
.textFieldStyle(.roundedBorder)
.padding(.horizontal, 8)
List(filteredAndSortedTags) { tag in
TagListItem(tag: tag.localized(in: language))
.listRowBackground(RoundedRectangle(cornerRadius: 5)
.fill(selection.tag == tag ? Color.blue : Color.clear)
.padding(.horizontal, 10)
)
.contentShape(Rectangle())
.onTapGesture {
selection.tag = tag
}
SelectableListItem(selected: selection.tag == tag) {
TagListItem(tag: tag.localized(in: language))
}
.onTapGesture {
selection.tag = tag
}
}
}.onAppear {
if selection.tag == nil, let first = content.tags.first {
@ -57,9 +54,6 @@ private struct TagListItem: View {
var tag: LocalizedTag
var body: some View {
HStack {
Text(tag.title)
Spacer()
}
Text(tag.title)
}
}