Unified detail views, model
This commit is contained in:
@ -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 {
|
||||
|
@ -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("")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user