External files, improve page generation

This commit is contained in:
Christoph Hagen
2024-12-10 15:21:28 +01:00
parent 8183bc4903
commit efc9234917
50 changed files with 1069 additions and 424 deletions

View File

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

View File

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

View File

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

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

View File

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