Unified detail views, model

This commit is contained in:
Christoph Hagen
2024-12-16 09:54:21 +01:00
parent 1e67a99866
commit 31d1ecb8bd
57 changed files with 853 additions and 954 deletions

View File

@ -120,9 +120,7 @@ struct PageIssueView: View {
didSelect(page: page)
}
} content: {
PagePickerView(
showPagePicker: $showPagePicker,
selectedPage: $selectedPage)
PagePickerView(selectedPage: $selectedPage)
}
.sheet(isPresented: $showFilePicker) {
if let file = selectedFile {

View File

@ -5,25 +5,18 @@ struct GenerationDetailView: View {
let section: SettingsSection
var body: some View {
Group {
switch section {
//case .generation:
// GenerationSettingsView()
case .folders:
PathSettingsView()
case .navigationBar:
NavigationBarSettingsView()
case .postFeed:
PostFeedSettingsView()
case .pages:
PageSettingsDetailView()
case .tagOverview:
TagOverviewDetailView()
}
switch section {
case .folders:
PathSettingsView()
case .navigationBar:
NavigationBarSettingsView()
case .postFeed:
PostFeedSettingsView()
case .pages:
PageSettingsDetailView()
case .tagOverview:
TagOverviewDetailView()
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding()
.navigationTitle("")
}
}

View File

@ -15,12 +15,9 @@ struct NavigationBarSettingsView: View {
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text("Navigation Bar")
.font(.largeTitle)
.bold()
Text("Customize the navigation bar for all pages at the top of the website")
.foregroundStyle(.secondary)
.padding(.bottom, 30)
DetailTitle(
title: "Navigation Bar",
text: "Customize the navigation bar for all pages at the top of the website")
HStack {
Text("Links")

View File

@ -16,43 +16,43 @@ struct PageSettingsDetailView: View {
text: "Change the way pages are displayed")
IntegerPropertyView(
value: $content.settings.pages.contentWidth,
title: "Content Width",
value: $content.settings.pages.contentWidth,
footer: "The maximum width of the content in pages (in pixels)")
IntegerPropertyView(
value: $content.settings.pages.largeImageWidth,
title: "Fullscreen Image Width",
value: $content.settings.pages.largeImageWidth,
footer: "The maximum width of images that are diplayed fullscreen")
IntegerPropertyView(
value: $content.settings.pages.pageLinkImageSize,
title: "Page Link Image Width",
value: $content.settings.pages.pageLinkImageSize,
footer: "The maximum width of images diplayed as thumbnails on page links")
FilePropertyView(
title: "Default CSS File",
description: "The CSS file containing the styling of all pages",
footer: "The CSS file containing the styling of all pages",
selectedFile: $content.settings.pages.defaultCssFile)
FilePropertyView(
title: "Code Highlighting File",
description: "The JavaScript file to provide syntax highlighting of code blocks",
footer: "The JavaScript file to provide syntax highlighting of code blocks",
selectedFile: $content.settings.pages.codeHighlightingJsFile)
FilePropertyView(
title: "Audio Player CSS File",
description: "The CSS file to provide the style for the audio player",
footer: "The CSS file to provide the style for the audio player",
selectedFile: $content.settings.pages.audioPlayerCssFile)
FilePropertyView(
title: "Audio Player JavaScript File",
description: "The CSS file to provide the functionality for the audio player",
footer: "The CSS file to provide the functionality for the audio player",
selectedFile: $content.settings.pages.audioPlayerJsFile)
FilePropertyView(
title: "3D Model Viewer File",
description: "The JavaScript file to provide the functionality for the 3D model viewer",
footer: "The JavaScript file to provide the functionality for the 3D model viewer",
selectedFile: $content.settings.pages.modelViewerJsFile)
}
}

View File

@ -11,131 +11,66 @@ struct PathSettingsView: View {
@EnvironmentObject
private var content: Content
@State
private var folderSelection: SecurityScopeBookmark = .contentPath
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text("Folder Settings")
.font(.largeTitle)
.bold()
Text("Select the folders for the app to work.")
.foregroundStyle(.secondary)
.padding(.bottom, 30)
DetailTitle(
title: "Folder Settings",
text: "Select the folders for the app to work.")
Text("Content Folder")
.font(.headline)
.padding(.bottom, 1)
Text(contentPath)
Button(action: selectContentFolder) {
Text("Select folder")
}
Text("The folder where the raw content of the website is stored")
.foregroundStyle(.secondary)
.padding(.bottom)
FolderOnDiskPropertyView(
title: "Content Folder",
folder: $contentPath,
footer: "The folder where the raw content of the website is stored") { url in
guard content.storage.save(folderUrl: url, in: .contentPath) else {
return
}
contentPath = url.path()
}
Text("Output Folder")
.font(.headline)
.padding(.bottom, 1)
Text(content.settings.paths.outputDirectoryPath)
Button(action: selectOutputFolder) {
Text("Select folder")
}
Text("The folder where the generated website is stored")
.foregroundStyle(.secondary)
.padding(.bottom)
FolderOnDiskPropertyView(
title: "Output Folder",
folder: $content.settings.paths.outputDirectoryPath,
footer: "The folder where the generated website is stored") { url in
guard content.storage.save(folderUrl: url, in: .outputPath) else {
return
}
content.settings.paths.outputDirectoryPath = url.path()
}
Text("Pages output folder")
.font(.headline)
TextField("", text: $content.settings.paths.pagesOutputFolderPath)
.textFieldStyle(.roundedBorder)
Text("The path in the output folder where the generated pages are stored")
.foregroundStyle(.secondary)
.padding(.bottom)
StringPropertyView(
title: "Pages output folder",
text: $content.settings.paths.pagesOutputFolderPath,
footer: "The path in the output folder where the generated pages are stored")
Text("Tags output folder")
.font(.headline)
TextField("", text: $content.settings.paths.tagsOutputFolderPath)
.textFieldStyle(.roundedBorder)
Text("The path in the output folder where the generated tag pages are stored")
.foregroundStyle(.secondary)
.padding(.bottom)
StringPropertyView(
title: "Tags output folder",
text: $content.settings.paths.tagsOutputFolderPath,
footer: "The path in the output folder where the generated tag pages are stored")
Text("Files output folder")
.font(.headline)
TextField("", text: $content.settings.paths.filesOutputFolderPath)
.textFieldStyle(.roundedBorder)
Text("The path in the output folder where the copied files are stored")
.foregroundStyle(.secondary)
.padding(.bottom)
StringPropertyView(
title: "Files output folder",
text: $content.settings.paths.filesOutputFolderPath,
footer: "The path in the output folder where the copied files are stored")
Text("Images output folder")
.font(.headline)
TextField("", text: $content.settings.paths.imagesOutputFolderPath)
.textFieldStyle(.roundedBorder)
Text("The path in the output folder where the generated images are stored")
.foregroundStyle(.secondary)
.padding(.bottom)
StringPropertyView(
title: "Images output folder",
text: $content.settings.paths.imagesOutputFolderPath,
footer: "The path in the output folder where the generated images are stored")
Text("Videos output folder")
.font(.headline)
TextField("", text: $content.settings.paths.videosOutputFolderPath)
.textFieldStyle(.roundedBorder)
Text("The path in the output folder where the generated videos are stored")
.foregroundStyle(.secondary)
.padding(.bottom)
StringPropertyView(
title: "Videos output folder",
text: $content.settings.paths.videosOutputFolderPath,
footer: "The path in the output folder where the generated videos are stored")
Text("Assets output folder")
.font(.headline)
TextField("", text: $content.settings.paths.assetsOutputFolderPath)
.textFieldStyle(.roundedBorder)
Text("The path in the output folder where assets are stored")
.foregroundStyle(.secondary)
.padding(.bottom)
StringPropertyView(
title: "Assets output folder",
text: $content.settings.paths.assetsOutputFolderPath,
footer: "The path in the output folder where assets are stored")
}
.padding()
}
}
// MARK: Folder selection
private func selectContentFolder() {
folderSelection = .contentPath
guard let url = savePanelUsingOpenPanel() else {
return
}
self.contentPath = url.path()
}
private func selectOutputFolder() {
folderSelection = .outputPath
guard let url = savePanelUsingOpenPanel() else {
return
}
content.settings.paths.outputDirectoryPath = url.path()
}
private func savePanelUsingOpenPanel() -> URL? {
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 Save Directory"
panel.prompt = "Select Save Directory"
let response = panel.runModal()
guard response == .OK else {
return nil
}
guard let url = panel.url else {
return nil
}
content.storage.save(folderUrl: url, in: folderSelection)
return url
}
}
#Preview {

View File

@ -15,28 +15,28 @@ struct PostFeedSettingsView: View {
text: "Change the way the posts are displayed")
IntegerPropertyView(
value: $content.settings.posts.contentWidth,
title: "Content Width",
value: $content.settings.posts.contentWidth,
footer: "The maximum width of the content the post feed (in pixels)")
IntegerPropertyView(
value: $content.settings.posts.postsPerPage,
title: "Posts Per Page",
value: $content.settings.posts.postsPerPage,
footer: "The maximum number of posts displayed on a single page")
FilePropertyView(
title: "Default CSS File",
description: "The CSS file containing the styling of all post pages",
footer: "The CSS file containing the styling of all post pages",
selectedFile: $content.settings.posts.defaultCssFile)
FilePropertyView(
title: "Swiper CSS File",
description: "The CSS file containing the styling of image galleries in post feeds",
footer: "The CSS file containing the styling of image galleries in post feeds",
selectedFile: $content.settings.posts.swiperCssFile)
FilePropertyView(
title: "Swiper JavaScript File",
description: "The JavaScript file to load the image gallery code in post feeds",
footer: "The JavaScript file to load the image gallery code in post feeds",
selectedFile: $content.settings.posts.swiperJsFile)
LocalizedPostFeedSettingsView(

View File

@ -11,12 +11,9 @@ struct TagOverviewDetailView: View {
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text("Tag Overview")
.font(.largeTitle)
.bold()
Text("Configure the page showing all tags")
.foregroundStyle(.secondary)
.padding(.bottom, 30)
DetailTitle(
title: "Tag Overview",
text: "Configure the page showing all tags")
if let page = content.tagOverview?.localized(in: language) {
TagOverviewDetails(page: page)
@ -30,101 +27,48 @@ struct TagOverviewDetailView: View {
private func createTagOverviewPage() {
content.tagOverview = TagOverviewPage(
content: content,
german: .init(title: "Alle Tags", urlString: "alle"),
english: .init(title: "All tags", urlString: "all"))
german: .init(content: content, title: "Alle Tags", urlString: "alle"),
english: .init(content: content, title: "All tags", urlString: "all"))
}
}
private struct TagOverviewDetails: View {
@EnvironmentObject
private var content: Content
@ObservedObject
var page: LocalizedTagOverviewPage
@EnvironmentObject
var content: Content
@State
private var showImagePicker = false
@State
private var newUrlString: String = ""
init(page: LocalizedTagOverviewPage) {
self.page = page
}
private var newUrlCanBeUpdated: Bool {
guard !newUrlString.isEmpty else { return false }
guard content.isValidIdForTagOrPageOrPost(newUrlString) else { return false }
return !content.containsTag(withUrlComponent: newUrlString)
}
var body: some View {
VStack(alignment: .leading) {
Text("Title")
.font(.headline)
TextField("", text: $page.title)
.textFieldStyle(.roundedBorder)
StringPropertyView(
title: "Title",
text: $page.title,
footer: "The title of the overview page")
HStack {
Text("Page URL String")
.font(.headline)
TextField("", text: $newUrlString)
.textFieldStyle(.roundedBorder)
Button("Update", action: setNewId)
.disabled(!newUrlCanBeUpdated)
}
.padding(.bottom)
IdPropertyView(
id: $page.urlComponent,
title: "Page URL String",
footer: "The url component to use for the link to the page",
validation: page.isValid,
update: { page.urlComponent = $0 })
Text("Link Preview Title")
.font(.headline)
OptionalTextField("", text: $page.linkPreviewTitle,
prompt: page.title)
.textFieldStyle(.roundedBorder)
.padding(.bottom)
OptionalStringPropertyView(
title: "Preview Title",
text: $page.linkPreviewTitle,
prompt: page.title,
footer: "The title to use for the page when linking to it")
HStack {
Text("Link Preview Image")
.font(.headline)
IconButton(symbol: .squareAndPencilCircleFill,
size: 22,
color: .blue) {
showImagePicker = true
}
OptionalImagePropertyView(
title: "Preview Image",
selectedImage: $page.linkPreviewImage,
footer: "The image to show for previews of this page")
IconButton(symbol: .trashCircleFill,
size: 22,
color: .red) {
page.linkPreviewImage = nil
}.disabled(page.linkPreviewImage == nil)
}
.buttonStyle(.plain)
if let image = page.linkPreviewImage {
image.imageToDisplay
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 400, maxHeight: 300)
.cornerRadius(8)
Text(image.id)
.font(.headline)
}
Text("Link Preview Description")
.font(.headline)
.padding(.top)
OptionalDescriptionField(text: $page.linkPreviewDescription)
.textFieldStyle(.roundedBorder)
.padding(.bottom)
OptionalTextFieldPropertyView(
title: "Preview Description",
text: $page.linkPreviewDescription,
footer: "The description to show in previews of the page")
}
.sheet(isPresented: $showImagePicker) {
ImagePickerView(showImagePicker: $showImagePicker) { image in
page.linkPreviewImage = image
}
}
}
private func setNewId() {
page.urlString = newUrlString
}
}