External files, improve page generation
This commit is contained in:
@ -34,6 +34,7 @@ struct AddFileView: View {
|
||||
HStack {
|
||||
Button("Cancel", role: .cancel) { dismiss() }
|
||||
Button("Select more files", action: openFilePanel)
|
||||
Button("Add placeholder", action: addPlaceholderFile)
|
||||
Button("Add selected", action: importSelectedFiles)
|
||||
.disabled(filesToAdd.isEmpty)
|
||||
}
|
||||
@ -68,6 +69,11 @@ struct AddFileView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func addPlaceholderFile() {
|
||||
let newFile = FileToAdd(content: content, externalFile: "placeholder")
|
||||
filesToAdd.append(newFile)
|
||||
}
|
||||
|
||||
private func delete(file: FileToAdd) {
|
||||
guard let index = filesToAdd.firstIndex(of: file) else {
|
||||
return
|
||||
@ -85,16 +91,19 @@ struct AddFileView: View {
|
||||
print("Skipping existing file \(file.uniqueId)")
|
||||
continue
|
||||
}
|
||||
do {
|
||||
try content.storage.copyFile(at: file.url, fileId: file.uniqueId)
|
||||
} catch {
|
||||
print("Failed to import file '\(file.uniqueId)' at \(file.url.path()): \(error)")
|
||||
return
|
||||
if let url = file.url {
|
||||
do {
|
||||
try content.storage.copyFile(at: url, fileId: file.uniqueId)
|
||||
} catch {
|
||||
print("Failed to import file '\(file.uniqueId)' at \(url.path()): \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let resource = FileResource(
|
||||
content: content,
|
||||
id: file.uniqueId,
|
||||
isExternallyStored: file.url == nil,
|
||||
en: "", de: "")
|
||||
// TODO: Insert at correct index?
|
||||
content.files.insert(resource, at: 0)
|
||||
|
@ -13,44 +13,56 @@ struct FileContentView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
switch file.type {
|
||||
case .image:
|
||||
file.imageToDisplay
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
case .model:
|
||||
if file.isExternallyStored {
|
||||
VStack {
|
||||
Image(systemSymbol: .cubeTransparent)
|
||||
Image(systemSymbol: .squareDashed)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: iconSize)
|
||||
Text("No preview available")
|
||||
Text("External file")
|
||||
.font(.title)
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
case .text, .code:
|
||||
TextFileContentView(file: file)
|
||||
.id(file.id)
|
||||
case .video:
|
||||
VStack {
|
||||
Image(systemSymbol: .film)
|
||||
} else {
|
||||
switch file.type {
|
||||
case .image:
|
||||
file.imageToDisplay
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: iconSize)
|
||||
Text("No preview available")
|
||||
.font(.title)
|
||||
case .model:
|
||||
VStack {
|
||||
Image(systemSymbol: .cubeTransparent)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: iconSize)
|
||||
Text("No preview available")
|
||||
.font(.title)
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
case .text, .code:
|
||||
TextFileContentView(file: file)
|
||||
.id(file.id)
|
||||
case .video:
|
||||
VStack {
|
||||
Image(systemSymbol: .film)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: iconSize)
|
||||
Text("No preview available")
|
||||
.font(.title)
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
case .other:
|
||||
VStack {
|
||||
Image(systemSymbol: .docQuestionmark)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: iconSize)
|
||||
Text("No preview available")
|
||||
.font(.title)
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
case .other:
|
||||
VStack {
|
||||
Image(systemSymbol: .docQuestionmark)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: iconSize)
|
||||
Text("No preview available")
|
||||
.font(.title)
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
|
@ -2,9 +2,12 @@ import Foundation
|
||||
|
||||
final class FileToAdd: ObservableObject {
|
||||
|
||||
let id: Int
|
||||
|
||||
unowned let content: Content
|
||||
|
||||
let url: URL
|
||||
// The external path to the file, or nil if the file is just a placeholder
|
||||
let url: URL?
|
||||
|
||||
@Published
|
||||
var uniqueId: String
|
||||
@ -13,11 +16,19 @@ final class FileToAdd: ObservableObject {
|
||||
var isSelected: Bool = true
|
||||
|
||||
init(content: Content, url: URL) {
|
||||
self.id = .random()
|
||||
self.content = content
|
||||
self.url = url
|
||||
self.uniqueId = url.lastPathComponent
|
||||
}
|
||||
|
||||
init(content: Content, externalFile: String) {
|
||||
self.id = .random()
|
||||
self.content = content
|
||||
self.url = nil
|
||||
self.uniqueId = externalFile
|
||||
}
|
||||
|
||||
var idAlreadyExists: Bool {
|
||||
content.files.contains { $0.id == uniqueId }
|
||||
}
|
||||
@ -25,9 +36,6 @@ final class FileToAdd: ObservableObject {
|
||||
|
||||
extension FileToAdd: Identifiable {
|
||||
|
||||
var id: URL {
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
extension FileToAdd: Equatable {
|
||||
|
@ -30,7 +30,7 @@ struct FileToAddView: View {
|
||||
.frame(maxWidth: 200)
|
||||
|
||||
}
|
||||
Text(file.url.path())
|
||||
Text(file.url?.path() ?? "Placeholder file")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ImageContentView: View {
|
||||
|
||||
@ObservedObject
|
||||
var image: FileResource
|
||||
|
||||
var body: some View {
|
||||
image.imageToDisplay
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
}
|
||||
}
|
||||
|
||||
extension ImageContentView: MainContentView {
|
||||
|
||||
init(item: FileResource) {
|
||||
self.image = item
|
||||
}
|
||||
|
||||
static let itemDescription = "an image"
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ImageContentView(image: .init(resourceImage: "image1", type: .jpg))
|
||||
}
|
@ -73,9 +73,11 @@ struct AddPageView: View {
|
||||
createdDate: .now,
|
||||
startDate: .now,
|
||||
endDate: nil,
|
||||
german: .init(urlString: "seite",
|
||||
german: .init(content: content,
|
||||
urlString: "seite",
|
||||
title: "Ein Titel"),
|
||||
english: .init(urlString: "page",
|
||||
english: .init(content: content,
|
||||
urlString: "page",
|
||||
title: "A Title"),
|
||||
tags: [])
|
||||
content.pages.insert(page, at: 0)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
import HighlightedTextEditor
|
||||
|
||||
struct LocalizedPageContentView: View {
|
||||
@ -11,17 +12,21 @@ struct LocalizedPageContentView: View {
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@State
|
||||
private var isGeneratingWebsite = false
|
||||
|
||||
@State
|
||||
private var loadedPageContentLanguage: ContentLanguage?
|
||||
|
||||
@State
|
||||
private var pageContent: String = ""
|
||||
|
||||
@State
|
||||
private var didLoadContent = false
|
||||
private var pageContentUsedForGeneration: String = ""
|
||||
|
||||
@State
|
||||
private var generationResults = PageGenerationResults()
|
||||
|
||||
|
||||
init(pageId: String, page: LocalizedPage) {
|
||||
self.pageId = pageId
|
||||
@ -41,8 +46,12 @@ struct LocalizedPageContentView: View {
|
||||
Button(action: saveContent) {
|
||||
Text("Save")
|
||||
}
|
||||
Button(action: checkContent) {
|
||||
Text("Check")
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
PageContentResultsView(results: generationResults)
|
||||
HighlightedTextEditor(
|
||||
text: $pageContent,
|
||||
highlightRules: .markdown)
|
||||
@ -53,32 +62,50 @@ struct LocalizedPageContentView: View {
|
||||
}
|
||||
|
||||
private func loadContent() {
|
||||
let language = language
|
||||
do {
|
||||
let content = try content.storage.pageContent(for: pageId, language: language)
|
||||
let content = try page.content.storage.pageContent(for: pageId, language: language)
|
||||
|
||||
guard content != "" else {
|
||||
pageContent = "New file"
|
||||
didLoadContent = false
|
||||
loadedPageContentLanguage = nil
|
||||
return
|
||||
}
|
||||
pageContent = content
|
||||
didLoadContent = true
|
||||
|
||||
loadedPageContentLanguage = language
|
||||
checkContent()
|
||||
} catch {
|
||||
print("Failed to load page content: \(error)")
|
||||
pageContent = "Failed to load"
|
||||
loadedPageContentLanguage = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func saveContent() {
|
||||
guard didLoadContent else {
|
||||
guard let loadedPageContentLanguage else {
|
||||
return
|
||||
}
|
||||
do {
|
||||
try content.storage.save(pageContent: pageContent, for: pageId, language: language)
|
||||
try page.content.storage.save(pageContent: pageContent, for: pageId, language: loadedPageContentLanguage)
|
||||
} catch {
|
||||
print("Failed to save content: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func checkContent() {
|
||||
let content = self.pageContent
|
||||
guard content != pageContentUsedForGeneration else {
|
||||
return
|
||||
}
|
||||
isGeneratingWebsite = true
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
let generator = PageContentParser(content: page.content, language: language)
|
||||
_ = generator.generatePage(from: content)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.generationResults = generator.results
|
||||
isGeneratingWebsite = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,22 +4,47 @@ import SFSafeSymbols
|
||||
struct LocalizedPageDetailView: View {
|
||||
|
||||
@ObservedObject
|
||||
private var item: LocalizedPage
|
||||
private var page: LocalizedPage
|
||||
|
||||
init(page: LocalizedPage, showImagePicker: Bool = false) {
|
||||
self.item = page
|
||||
self.page = page
|
||||
self.showImagePicker = showImagePicker
|
||||
self.newUrlString = page.urlString
|
||||
}
|
||||
|
||||
@State
|
||||
private var showImagePicker = false
|
||||
|
||||
@State
|
||||
private var newUrlString: String
|
||||
|
||||
private let allowedCharactersInPostId = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted
|
||||
|
||||
private var idExists: Bool {
|
||||
page.content.pages.contains {
|
||||
$0.german.urlString == newUrlString
|
||||
|| $0.english.urlString == newUrlString
|
||||
}
|
||||
}
|
||||
|
||||
private var containsInvalidCharacters: Bool {
|
||||
newUrlString.rangeOfCharacter(from: allowedCharactersInPostId) != nil
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
TextField("", text: $newUrlString)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
Button("Update", action: setNewId)
|
||||
.disabled(newUrlString.isEmpty || containsInvalidCharacters || idExists)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
Text("Link Preview Title")
|
||||
.font(.headline)
|
||||
OptionalTextField("", text: $item.linkPreviewTitle,
|
||||
prompt: item.title)
|
||||
OptionalTextField("", text: $page.linkPreviewTitle,
|
||||
prompt: page.title)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.bottom)
|
||||
|
||||
@ -35,13 +60,13 @@ struct LocalizedPageDetailView: View {
|
||||
IconButton(symbol: .trashCircleFill,
|
||||
size: 22,
|
||||
color: .red) {
|
||||
item.linkPreviewImage = nil
|
||||
}.disabled(item.linkPreviewImage == nil)
|
||||
page.linkPreviewImage = nil
|
||||
}.disabled(page.linkPreviewImage == nil)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
.buttonStyle(.plain)
|
||||
if let image = item.linkPreviewImage {
|
||||
if let image = page.linkPreviewImage {
|
||||
image.imageToDisplay
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
@ -52,16 +77,20 @@ struct LocalizedPageDetailView: View {
|
||||
Text("Link Preview Description")
|
||||
.font(.headline)
|
||||
.padding(.top)
|
||||
OptionalDescriptionField(text: $item.linkPreviewDescription)
|
||||
OptionalDescriptionField(text: $page.linkPreviewDescription)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.sheet(isPresented: $showImagePicker) {
|
||||
ImagePickerView(showImagePicker: $showImagePicker) { image in
|
||||
item.linkPreviewImage = image
|
||||
page.linkPreviewImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setNewId() {
|
||||
page.urlString = newUrlString
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
118
CHDataManagement/Views/Pages/PageContentResultsView.swift
Normal file
118
CHDataManagement/Views/Pages/PageContentResultsView.swift
Normal file
@ -0,0 +1,118 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
private struct ListPopup: View {
|
||||
|
||||
@Environment(\.dismiss)
|
||||
var dismiss
|
||||
|
||||
let items: [String]
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
ForEach(items, id: \.self) { page in
|
||||
Text(page)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: min(CGFloat(items.count) * 31, 500))
|
||||
Button("Dismiss") { dismiss() }
|
||||
}
|
||||
.padding(.vertical)
|
||||
.onTapGesture {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TextWithPopup: View {
|
||||
|
||||
let symbol: SFSymbol
|
||||
|
||||
let text: LocalizedStringKey
|
||||
|
||||
let items: [String]
|
||||
|
||||
@State
|
||||
private var isHovering = false
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(systemSymbol: symbol)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 16, height: 16)
|
||||
Text(text)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
if items.count > 0 {
|
||||
isHovering.toggle()
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $isHovering) {
|
||||
ListPopup(items: items)
|
||||
.onTapGesture {
|
||||
isHovering.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PageContentResultsView: View {
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@ObservedObject
|
||||
var results: PageGenerationResults
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
TextWithPopup(
|
||||
symbol: .photoOnRectangleAngled,
|
||||
text: "\(results.files.count + results.missingFiles.count) images and files",
|
||||
items: results.files.sorted().map { $0.id })
|
||||
.foregroundStyle(.secondary)
|
||||
TextWithPopup(
|
||||
symbol: .docBadgePlus,
|
||||
text: "\(results.linkedPages.count + results.missingPages.count) page links",
|
||||
items: results.linkedPages.sorted().map { $0.localized(in: language).title })
|
||||
.foregroundStyle(.secondary)
|
||||
if !results.missingPages.isEmpty {
|
||||
TextWithPopup(
|
||||
symbol: .exclamationmarkTriangleFill,
|
||||
text: "\(results.missingPages.count) missing pages",
|
||||
items: results.missingPages.sorted())
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
if !results.missingFiles.isEmpty {
|
||||
TextWithPopup(
|
||||
symbol: .exclamationmarkTriangleFill,
|
||||
text: "\(results.missingFiles.count) missing files",
|
||||
items: results.missingFiles.sorted())
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
if !results.warnings.isEmpty {
|
||||
TextWithPopup(
|
||||
symbol: .exclamationmarkTriangleFill,
|
||||
text: "\(results.warnings.count) errors",
|
||||
items: results.warnings.sorted())
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
if !results.invalidCommandArguments.isEmpty {
|
||||
TextWithPopup(
|
||||
symbol: .exclamationmarkTriangleFill,
|
||||
text: "\(results.invalidCommandArguments.count) errors",
|
||||
items: results.invalidCommandArguments.map {
|
||||
"\($0.command.rawValue): \($0.arguments.joined(separator: ";"))"
|
||||
})
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
PageContentResultsView(results: .init())
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct PageDetailView: View {
|
||||
|
||||
@ -17,6 +18,9 @@ struct PageDetailView: View {
|
||||
@State
|
||||
private var newId: String
|
||||
|
||||
@State
|
||||
private var didGenerateWebsite: Bool?
|
||||
|
||||
init(page: Page) {
|
||||
self.page = page
|
||||
self.newId = page.id
|
||||
@ -35,10 +39,21 @@ struct PageDetailView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Button(action: generate) {
|
||||
Text("Generate")
|
||||
HStack {
|
||||
Button(action: generate) {
|
||||
Text("Generate")
|
||||
}
|
||||
.disabled(isGeneratingWebsite)
|
||||
if let didGenerateWebsite {
|
||||
if didGenerateWebsite {
|
||||
Image(systemSymbol: .checkmarkCircleFill)
|
||||
.foregroundStyle(.green)
|
||||
} else {
|
||||
Image(systemSymbol: .xmarkCircleFill)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(isGeneratingWebsite)
|
||||
HStack {
|
||||
TextField("", text: $newId)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
@ -86,6 +101,7 @@ struct PageDetailView: View {
|
||||
}
|
||||
|
||||
LocalizedPageDetailView(page: page.localized(in: language))
|
||||
.id(page.id + language.rawValue)
|
||||
|
||||
}
|
||||
.padding()
|
||||
@ -106,11 +122,13 @@ struct PageDetailView: View {
|
||||
isGeneratingWebsite = true
|
||||
print("Generating page")
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let generator = WebsiteGenerator(
|
||||
content: content,
|
||||
language: language)
|
||||
if !generator.generate(page: page) {
|
||||
print("Generation failed")
|
||||
for language in ContentLanguage.allCases {
|
||||
let generator = LocalizedWebsiteGenerator(
|
||||
content: content,
|
||||
language: language)
|
||||
if !generator.generate(page: page) {
|
||||
print("Generation failed")
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
isGeneratingWebsite = false
|
||||
|
@ -1,6 +1,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct GenerationSettingsView: View {
|
||||
struct GenerationContentView: View {
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
@ -37,7 +37,7 @@ struct GenerationSettingsView: View {
|
||||
Text(generatorText)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ struct GenerationSettingsView: View {
|
||||
}
|
||||
isGeneratingWebsite = true
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let generator = WebsiteGenerator(
|
||||
let generator = LocalizedWebsiteGenerator(
|
||||
content: content,
|
||||
language: language)
|
||||
_ = generator.generateWebsite { text in
|
||||
@ -71,7 +71,7 @@ struct GenerationSettingsView: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
GenerationSettingsView()
|
||||
GenerationContentView()
|
||||
.environmentObject(Content.mock)
|
||||
.padding()
|
||||
}
|
30
CHDataManagement/Views/Settings/GenerationDetailView.swift
Normal file
30
CHDataManagement/Views/Settings/GenerationDetailView.swift
Normal file
@ -0,0 +1,30 @@
|
||||
import SwiftUI
|
||||
|
||||
struct GenerationDetailView: View {
|
||||
|
||||
let section: SettingsSection
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch section {
|
||||
//case .generation:
|
||||
// GenerationSettingsView()
|
||||
case .folders:
|
||||
FolderSettingsView()
|
||||
case .navigationBar:
|
||||
NavigationBarSettingsView()
|
||||
case .postFeed:
|
||||
PostFeedSettingsView()
|
||||
case .pages:
|
||||
PageSettingsView()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.padding()
|
||||
.navigationTitle("")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
GenerationDetailView(section: .folders)
|
||||
}
|
@ -26,7 +26,7 @@ struct NavigationBarSettingsView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Notification Bar Settings")
|
||||
Text("Navigation Bar")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
Text("Customize the navigation bar for all pages at the top of the website")
|
||||
@ -37,7 +37,6 @@ struct NavigationBarSettingsView: View {
|
||||
.font(.headline)
|
||||
TextField("", text: $content.settings.navigationBar.iconPath)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.frame(maxWidth: 300)
|
||||
Text("Specify the path to the icon file with regard to the final website folder.")
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.bottom, 30)
|
||||
|
@ -21,16 +21,22 @@ struct PageSettingsView: View {
|
||||
.font(.headline)
|
||||
IntegerField("", number: $content.settings.pages.contentWidth)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.frame(maxWidth: 400)
|
||||
Text("The maximum width of the content in pages (in pixels)")
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("Image Width")
|
||||
.font(.headline)
|
||||
IntegerField("", number: $content.settings.pages.largeImageWidth)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
Text("The maximum width of images that are diplayed fullscreen")
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("Page URL Prefix")
|
||||
.font(.headline)
|
||||
TextField("", text: $content.settings.pages.pageUrlPrefix)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.frame(maxWidth: 400)
|
||||
Text("The URL prefix used for the links to pages")
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.bottom)
|
||||
|
@ -1,49 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SectionedSettingsView: View {
|
||||
|
||||
@State
|
||||
private var selectedSection: SettingsSection? = .generation
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
SettingsSidebar(selectedSection: $selectedSection)
|
||||
.frame(minWidth: 200, idealWidth: 200, maxWidth: 200)
|
||||
} detail: {
|
||||
GenerationDetailView(section: selectedSection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct GenerationDetailView: View {
|
||||
|
||||
let section: SettingsSection?
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch section {
|
||||
case .generation:
|
||||
GenerationSettingsView()
|
||||
case .folders:
|
||||
FolderSettingsView()
|
||||
case .navigationBar:
|
||||
NavigationBarSettingsView()
|
||||
case .postFeed:
|
||||
PostFeedSettingsView()
|
||||
case .pages:
|
||||
PageSettingsView()
|
||||
case .none:
|
||||
Text("Select a setting from the sidebar")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.padding()
|
||||
.navigationTitle("")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SectionedSettingsView()
|
||||
}
|
13
CHDataManagement/Views/Settings/SettingsListView.swift
Normal file
13
CHDataManagement/Views/Settings/SettingsListView.swift
Normal file
@ -0,0 +1,13 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsListView: View {
|
||||
|
||||
@Binding
|
||||
var selectedSection: SettingsSection
|
||||
|
||||
var body: some View {
|
||||
List(SettingsSection.allCases, selection: $selectedSection) { item in
|
||||
Label(item.rawValue, systemSymbol: item.icon).tag(item)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ import SFSafeSymbols
|
||||
|
||||
enum SettingsSection: String {
|
||||
|
||||
case generation = "Generation"
|
||||
//case generation = "Generation"
|
||||
|
||||
case folders = "Folders"
|
||||
|
||||
@ -18,7 +18,7 @@ extension SettingsSection {
|
||||
|
||||
var icon: SFSymbol {
|
||||
switch self {
|
||||
case .generation: return .arrowTriangle2Circlepath
|
||||
//case .generation: return .arrowTriangle2Circlepath
|
||||
case .folders: return .folder
|
||||
case .navigationBar: return .menubarRectangle
|
||||
case .postFeed: return .rectangleGrid1x2
|
||||
|
@ -1,15 +0,0 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct SettingsSidebar: View {
|
||||
|
||||
@Binding var selectedSection: SettingsSection?
|
||||
|
||||
var body: some View {
|
||||
List(SettingsSection.allCases, selection: $selectedSection) { item in
|
||||
Label(item.rawValue, systemSymbol: item.icon)
|
||||
.tag(item)
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
}
|
||||
}
|
@ -19,50 +19,71 @@ struct LocalizedTagDetailView: View {
|
||||
VStack(alignment: .leading) {
|
||||
Toggle("Appears in overviews", isOn: $tagIsVisible)
|
||||
.toggleStyle(.switch)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.headline)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("Name")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.headline)
|
||||
TextField("", text: $tag.name)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("URL String")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.headline)
|
||||
TextField("", text: $tag.urlComponent)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("Original url")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.headline)
|
||||
Text(tag.originalUrl ?? "-")
|
||||
.padding(.top, 1)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("Subtitle")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.headline)
|
||||
OptionalTextField("", text: $tag.subtitle)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("Description")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
OptionalTextField("", text: $tag.description)
|
||||
Text("Link Preview Description")
|
||||
.font(.headline)
|
||||
.padding(.top)
|
||||
OptionalDescriptionField(text: $tag.description)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("Thumbnail")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
Button(action: { showImagePicker = true }) {
|
||||
Text(tag.thumbnail?.id ?? "Select")
|
||||
HStack {
|
||||
Text("Link Preview Image")
|
||||
.font(.headline)
|
||||
IconButton(symbol: .squareAndPencilCircleFill,
|
||||
size: 22,
|
||||
color: .blue) {
|
||||
showImagePicker = true
|
||||
}
|
||||
|
||||
IconButton(symbol: .trashCircleFill,
|
||||
size: 22,
|
||||
color: .red) {
|
||||
tag.linkPreviewImage = nil
|
||||
}.disabled(tag.linkPreviewImage == nil)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
.buttonStyle(.plain)
|
||||
.foregroundStyle(.blue)
|
||||
if let image = tag.linkPreviewImage {
|
||||
image.imageToDisplay
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(maxWidth: 400, maxHeight: 300)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.sheet(isPresented: $showImagePicker) {
|
||||
ImagePickerView(showImagePicker: $showImagePicker) { image in
|
||||
tag.thumbnail = image
|
||||
tag.linkPreviewImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user