Add tag overview, improve assets

This commit is contained in:
Christoph Hagen
2024-12-15 21:20:12 +01:00
parent 8a3a0f1797
commit 1e67a99866
59 changed files with 1301 additions and 480 deletions

View File

@ -37,11 +37,11 @@ struct FileDetailView: View {
}
Text("German Description")
.font(.headline)
TextField("", text: $file.germanDescription)
TextField("", text: $file.german)
.textFieldStyle(.roundedBorder)
Text("English Description")
.font(.headline)
TextField("", text: $file.englishDescription)
TextField("", text: $file.english)
.textFieldStyle(.roundedBorder)
if file.type.isImage {
Text("Image size")

View File

@ -73,16 +73,30 @@ struct FileListView: View {
guard oldValue != newValue else {
return
}
if let selectedFile,
newValue.matches(selectedFile.type) {
let newFile = filteredFiles.first
guard let selectedFile else {
if let newFile {
DispatchQueue.main.async {
selectedFile = newFile
}
}
return
}
selectedFile = filteredFiles.first
if newValue.matches(selectedFile.type) {
return
}
DispatchQueue.main.async {
self.selectedFile = newFile
}
}
}
.onAppear {
if selectedFile == nil {
selectedFile = content.files.first
DispatchQueue.main.async {
selectedFile = content.files.first
}
}
}
}

View File

@ -10,17 +10,32 @@ struct FileSelectionView: View {
init(selectedFile: Binding<FileResource?>) {
self._selectedFile = selectedFile
self.newSelection = selectedFile.wrappedValue
}
@State
private var newSelection: FileResource?
var body: some View {
VStack {
FileListView(selectedFile: $selectedFile)
FileListView(selectedFile: $newSelection)
.frame(minHeight: 500, idealHeight: 600)
HStack {
Button("Cancel") {
selectedFile = nil
dismiss() }
Button("Select") { dismiss() }
DispatchQueue.main.async {
dismiss()
}
}
Button("Remove") {
DispatchQueue.main.async {
selectedFile = nil
dismiss()
}
}
Button("Select") {
selectedFile = newSelection
dismiss()
}
}
}
.padding()

View File

@ -0,0 +1,19 @@
import SwiftUI
struct DetailTitle: View {
let title: String
let text: String
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.largeTitle)
.bold()
Text(text)
.foregroundStyle(.secondary)
.padding(.bottom, 30)
}
}
}

View File

@ -0,0 +1,34 @@
import SwiftUI
struct FilePropertyView: View {
let title: String
let description: String
@Binding
var selectedFile: FileResource?
@State
private var showFileSelectionSheet = false
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.headline)
HStack {
Text(selectedFile?.id ?? "No file selected")
Spacer()
Button("Select") {
showFileSelectionSheet = true
}
}
Text(description)
.foregroundStyle(.secondary)
.padding(.bottom)
}
.sheet(isPresented: $showFileSelectionSheet) {
FileSelectionView(selectedFile: $selectedFile)
}
}
}

View File

@ -0,0 +1,23 @@
import SwiftUI
struct IntegerPropertyView: View {
@Binding
var value: Int
let title: String
let footer: String
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.headline)
IntegerField("", number: $value)
.textFieldStyle(.roundedBorder)
Text(footer)
.foregroundStyle(.secondary)
.padding(.bottom)
}
}
}

View File

@ -0,0 +1,111 @@
import SwiftUI
import SFSafeSymbols
struct ItemSelectionView: View {
@Binding
var isPresented: Bool
@Binding
var selectedItems: [Item]
@Environment(\.language)
private var language
@EnvironmentObject
private var content: Content
init(isPresented: Binding<Bool>, selectedItems: Binding<[Item]>) {
self._isPresented = isPresented
self._selectedItems = selectedItems
}
var body: some View {
VStack {
List {
Section("Selected") {
ForEach(selectedItems) { item in
HStack {
Image(systemSymbol: .minusCircleFill)
.foregroundStyle(.red)
.onTapGesture {
deselect(item: item)
}
Text(item.title(in: language))
Spacer()
}
}
.onMove(perform: moveItem)
}
if let tagOverview = content.tagOverview {
Section("Special Pages") {
HStack {
Image(systemSymbol: .plusCircleFill)
.foregroundStyle(.green)
Text("Tags Overview")
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
if !selectedItems.contains(where: { $0 is TagOverviewPage }) {
selectedItems.append(tagOverview)
}
}
}
}
Section("Tags") {
ForEach(content.tags) { item in
HStack {
Image(systemSymbol: .plusCircleFill)
.foregroundStyle(.green)
Text(item.title(in: language))
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
select(item: item)
}
}
}
Section("Pages") {
ForEach(content.pages) { item in
HStack {
Image(systemSymbol: .plusCircleFill)
.foregroundStyle(.green)
Text(item.title(in: language))
}
.contentShape(Rectangle())
.onTapGesture {
select(item: item)
}
}
}
}
Button("Dismiss", action: dismiss)
}
.frame(minHeight: 500)
.padding()
}
private func moveItem(from source: IndexSet, to destination: Int) {
selectedItems.move(fromOffsets: source, toOffset: destination)
}
private func deselect(item: Item) {
guard let index = selectedItems.firstIndex(of: item) else {
return
}
selectedItems.remove(at: index)
}
private func select(item: Item) {
if selectedItems.contains(item) {
return
}
selectedItems.append(item)
}
private func dismiss() {
isPresented = false
}
}

View File

@ -210,7 +210,7 @@ struct PageIssueView: View {
}
private func createPage(pageId: String) {
guard content.isValidIdForTagOrTagOrPost(pageId) else {
guard content.isValidIdForTagOrPageOrPost(pageId) else {
show(error: "Invalid page id, can't create page")
return
}
@ -245,7 +245,7 @@ struct PageIssueView: View {
}
private func createTag(tagId: String) {
guard content.isValidIdForTagOrTagOrPost(tagId) else {
guard content.isValidIdForTagOrPageOrPost(tagId) else {
show(error: "Invalid tag id, can't create tag")
return
}

View File

@ -23,7 +23,7 @@ struct GenerationContentView: View {
var body: some View {
switch selectedSection {
case .folders, .navigationBar, .postFeed:
case .folders, .navigationBar, .postFeed, .tagOverview:
generationView
case .pages:
PageSettingsContentView()

View File

@ -17,6 +17,8 @@ struct GenerationDetailView: View {
PostFeedSettingsView()
case .pages:
PageSettingsDetailView()
case .tagOverview:
TagOverviewDetailView()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)

View File

@ -1,4 +1,5 @@
import SwiftUI
import SFSafeSymbols
struct NavigationBarSettingsView: View {
@ -9,7 +10,7 @@ struct NavigationBarSettingsView: View {
private var content: Content
@State
private var showTagPicker = false
private var showItemPicker = false
var body: some View {
ScrollView {
@ -21,14 +22,10 @@ struct NavigationBarSettingsView: View {
.foregroundStyle(.secondary)
.padding(.bottom, 30)
Text("Visible Tags")
.font(.headline)
FlowHStack {
ForEach(content.settings.navigationTags) { tag in
TagView(text: tag.localized(in: language).name)
.foregroundStyle(.white)
}
Button(action: { showTagPicker = true }) {
HStack {
Text("Links")
.font(.headline)
Button(action: { showItemPicker = true }) {
Image(systemSymbol: .squareAndPencilCircleFill)
.resizable()
.aspectRatio(1, contentMode: .fit)
@ -40,15 +37,19 @@ struct NavigationBarSettingsView: View {
}
.buttonStyle(.plain)
}
ForEach(content.settings.navigationItems) { tag in
TagView(text: tag.title(in: language))
.foregroundStyle(.white)
}
Text("Select the tags to show in the navigation bar. The number should be even.")
.foregroundStyle(.secondary)
}
}
.sheet(isPresented: $showTagPicker) {
TagSelectionView(
presented: $showTagPicker,
selected: $content.settings.navigationTags,
tags: $content.tags)
.sheet(isPresented: $showItemPicker) {
ItemSelectionView(
isPresented: $showItemPicker,
selectedItems: $content.settings.navigationItems)
}
}
}

View File

@ -11,51 +11,49 @@ struct PageSettingsDetailView: View {
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text("Page Settings")
.font(.largeTitle)
.bold()
Text("Change the way pages are displayed")
.padding(.bottom, 30)
DetailTitle(
title: "Page Settings",
text: "Change the way pages are displayed")
Text("Content Width")
.font(.headline)
IntegerField("", number: $content.settings.pages.contentWidth)
.textFieldStyle(.roundedBorder)
Text("The maximum width of the content in pages (in pixels)")
.foregroundStyle(.secondary)
.padding(.bottom)
IntegerPropertyView(
value: $content.settings.pages.contentWidth,
title: "Content Width",
footer: "The maximum width of the content in pages (in pixels)")
Text("Fullscreen 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)
IntegerPropertyView(
value: $content.settings.pages.largeImageWidth,
title: "Fullscreen Image Width",
footer: "The maximum width of images that are diplayed fullscreen")
Text("Page Link Image Width")
.font(.headline)
IntegerField("", number: $content.settings.pages.pageLinkImageSize)
.textFieldStyle(.roundedBorder)
Text("The maximum width of images diplayed as thumbnails on page links")
.foregroundStyle(.secondary)
.padding(.bottom)
IntegerPropertyView(
value: $content.settings.pages.pageLinkImageSize,
title: "Page Link Image Width",
footer: "The maximum width of images diplayed as thumbnails on page links")
Text("Page URL Prefix")
.font(.headline)
TextField("", text: $content.settings.pages.pageUrlPrefix)
.textFieldStyle(.roundedBorder)
Text("The URL prefix used for the links to pages")
.foregroundStyle(.secondary)
.padding(.bottom)
FilePropertyView(
title: "Default CSS File",
description: "The CSS file containing the styling of all pages",
selectedFile: $content.settings.pages.defaultCssFile)
Text("Javascript Files Path")
.font(.headline)
TextField("", text: $content.settings.pages.javascriptFilesPath)
.textFieldStyle(.roundedBorder)
Text("The path to the javascript files in the output folder")
.foregroundStyle(.secondary)
.padding(.bottom)
FilePropertyView(
title: "Code Highlighting File",
description: "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",
selectedFile: $content.settings.pages.audioPlayerCssFile)
FilePropertyView(
title: "Audio Player JavaScript File",
description: "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",
selectedFile: $content.settings.pages.modelViewerJsFile)
}
}
}

View File

@ -85,6 +85,14 @@ struct PathSettingsView: View {
Text("The path in the output folder where the generated videos are stored")
.foregroundStyle(.secondary)
.padding(.bottom)
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)
}
}
}

View File

@ -11,32 +11,36 @@ struct PostFeedSettingsView: View {
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text("Post Feed Settings")
.font(.largeTitle)
.bold()
Text("Change the way the posts are displayed")
.foregroundStyle(.secondary)
.padding(.bottom, 30)
DetailTitle(title: "Post Feed Settings",
text: "Change the way the posts are displayed")
Text("Content Width")
.font(.headline)
IntegerField("", number: $content.settings.posts.contentWidth)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 400)
Text("The maximum width of the content the post feed (in pixels)")
.foregroundStyle(.secondary)
.padding(.bottom)
IntegerPropertyView(
value: $content.settings.posts.contentWidth,
title: "Content Width",
footer: "The maximum width of the content the post feed (in pixels)")
Text("Posts Per Page")
.font(.headline)
IntegerField("", number: $content.settings.posts.postsPerPage)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 400)
Text("The maximum number of posts displayed on a single page")
.foregroundStyle(.secondary)
.padding(.bottom)
IntegerPropertyView(
value: $content.settings.posts.postsPerPage,
title: "Posts Per Page",
footer: "The maximum number of posts displayed on a single page")
LocalizedPostFeedSettingsView(settings: content.settings.localized(in: language))
FilePropertyView(
title: "Default CSS File",
description: "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",
selectedFile: $content.settings.posts.swiperCssFile)
FilePropertyView(
title: "Swiper JavaScript File",
description: "The JavaScript file to load the image gallery code in post feeds",
selectedFile: $content.settings.posts.swiperJsFile)
LocalizedPostFeedSettingsView(
settings: content.settings.localized(in: language))
}
}
}

View File

@ -12,17 +12,19 @@ enum SettingsSection: String {
case pages = "Pages"
case tagOverview = "Tag Overview"
}
extension SettingsSection {
var icon: SFSymbol {
switch self {
//case .generation: return .arrowTriangle2Circlepath
case .folders: return .folder
case .navigationBar: return .menubarRectangle
case .postFeed: return .rectangleGrid1x2
case .pages: return .docRichtext
case .tagOverview: return .tag
}
}
}

View File

@ -0,0 +1,130 @@
import SwiftUI
struct TagOverviewDetailView: View {
@Environment(\.language)
private var language
@EnvironmentObject
private var content: Content
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)
if let page = content.tagOverview?.localized(in: language) {
TagOverviewDetails(page: page)
} else {
Button("Create", action: createTagOverviewPage)
}
}
}
}
private func createTagOverviewPage() {
content.tagOverview = TagOverviewPage(
content: content,
german: .init(title: "Alle Tags", urlString: "alle"),
english: .init(title: "All tags", urlString: "all"))
}
}
private struct TagOverviewDetails: View {
@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)
HStack {
Text("Page URL String")
.font(.headline)
TextField("", text: $newUrlString)
.textFieldStyle(.roundedBorder)
Button("Update", action: setNewId)
.disabled(!newUrlCanBeUpdated)
}
.padding(.bottom)
Text("Link Preview Title")
.font(.headline)
OptionalTextField("", text: $page.linkPreviewTitle,
prompt: page.title)
.textFieldStyle(.roundedBorder)
.padding(.bottom)
HStack {
Text("Link Preview Image")
.font(.headline)
IconButton(symbol: .squareAndPencilCircleFill,
size: 22,
color: .blue) {
showImagePicker = true
}
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)
}
.sheet(isPresented: $showImagePicker) {
ImagePickerView(showImagePicker: $showImagePicker) { image in
page.linkPreviewImage = image
}
}
}
private func setNewId() {
page.urlString = newUrlString
}
}

View File

@ -26,6 +26,7 @@ struct AddTagView: View {
private func addNewTag() {
let newTag = Tag(
content: content,
id: "tag",
isVisible: true,
german: .init(urlComponent: "tag", name: "Neuer Tag"),
english: .init(urlComponent: "tag-en", name: "New Tag"))