Improve tag and images view, save website settings
This commit is contained in:
@ -5,7 +5,8 @@ struct ImageDetailsView: View {
|
||||
@Environment(\.language)
|
||||
var language
|
||||
|
||||
let image: ImageResource
|
||||
@ObservedObject
|
||||
var image: ImageResource
|
||||
|
||||
@State
|
||||
private var newId: String
|
||||
|
17
CHDataManagement/Views/Images/ImagesContentView.swift
Normal file
17
CHDataManagement/Views/Images/ImagesContentView.swift
Normal file
@ -0,0 +1,17 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ImagesContentView: View {
|
||||
|
||||
@ObservedObject
|
||||
var image: ImageResource
|
||||
|
||||
var body: some View {
|
||||
image.imageToDisplay
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ImagesContentView(image: .init(resourceName: "image1"))
|
||||
}
|
@ -19,38 +19,26 @@ struct ImagesView: View {
|
||||
private var showImageDetails = false
|
||||
|
||||
var body: some View {
|
||||
FlexibleColumnView(items: $content.images) { image, width in
|
||||
let isSelected = selectedImage == image
|
||||
let borderColor: Color = isSelected ? .accentColor : .clear
|
||||
return image.imageToDisplay
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.border(borderColor, width: 5)
|
||||
.frame(width: width)
|
||||
.onTapGesture { didTap(image: image) }
|
||||
}
|
||||
.inspector(isPresented: $showImageDetails) {
|
||||
NavigationSplitView {
|
||||
List(content.images, selection: $selectedImage) { image in
|
||||
Text(image.id)
|
||||
.tag(image)
|
||||
}
|
||||
} content: {
|
||||
if let selectedImage {
|
||||
ImagesContentView(image: selectedImage)
|
||||
.layoutPriority(1)
|
||||
} else {
|
||||
Text("Select an image in the sidebar")
|
||||
}
|
||||
} detail: {
|
||||
if let selectedImage {
|
||||
ImageDetailsView(image: selectedImage)
|
||||
.frame(maxWidth: 350)
|
||||
} else {
|
||||
Text("Select an image to show its details")
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button(action: { showImageDetails.toggle() }) {
|
||||
Label("Details", systemSymbol: .infoCircle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func didTap(image: ImageResource) {
|
||||
if selectedImage == image {
|
||||
selectedImage = nil
|
||||
} else {
|
||||
selectedImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
25
CHDataManagement/Views/Settings/LocalizedSettingsView.swift
Normal file
25
CHDataManagement/Views/Settings/LocalizedSettingsView.swift
Normal file
@ -0,0 +1,25 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LocalizedSettingsView: View {
|
||||
|
||||
@ObservedObject
|
||||
var settings: LocalizedWebsiteData
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Title")
|
||||
.font(.headline)
|
||||
TextField("", text: $settings.title)
|
||||
Text("Description")
|
||||
.font(.headline)
|
||||
TextField("", text: $settings.description)
|
||||
Text("Icon description")
|
||||
.font(.headline)
|
||||
TextField("", text: $settings.iconDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
LocalizedSettingsView(settings: .english)
|
||||
}
|
@ -20,31 +20,65 @@ struct SettingsView: View {
|
||||
@State
|
||||
private var showFileImporter = false
|
||||
|
||||
@State
|
||||
private var showTagPicker = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Content Folder")
|
||||
.font(.headline)
|
||||
TextField("Content Folder", text: $contentPath)
|
||||
Button(action: selectContentFolder) {
|
||||
Text("Select folder")
|
||||
}
|
||||
Text("Output Folder")
|
||||
.font(.headline)
|
||||
TextField("Output Folder", text: $outputPath)
|
||||
Button(action: selectOutputFolder) {
|
||||
Text("Select folder")
|
||||
}
|
||||
Text("Feed")
|
||||
.font(.headline)
|
||||
Button(action: generateFeed) {
|
||||
Text("Generate")
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Content Folder")
|
||||
.font(.headline)
|
||||
TextField("Content Folder", text: $contentPath)
|
||||
Button(action: selectContentFolder) {
|
||||
Text("Select folder")
|
||||
}
|
||||
Text("Output Folder")
|
||||
.font(.headline)
|
||||
TextField("Output Folder", text: $outputPath)
|
||||
Button(action: selectOutputFolder) {
|
||||
Text("Select folder")
|
||||
}
|
||||
Text("Navigation Bar Items")
|
||||
.font(.headline)
|
||||
FlowHStack {
|
||||
ForEach(content.websiteData.navigationTags, id: \.id) { tag in
|
||||
TagView(tag: .init(
|
||||
en: tag.english.name,
|
||||
de: tag.german.name)
|
||||
)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
Button(action: { showTagPicker = true }) {
|
||||
Image(systemSymbol: .squareAndPencilCircleFill)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(height: 22)
|
||||
.foregroundColor(Color.gray)
|
||||
.background(Circle()
|
||||
.fill(Color.white)
|
||||
.padding(1))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
LocalizedSettingsView(settings: content.websiteData.localized(in: language))
|
||||
Text("Feed")
|
||||
.font(.headline)
|
||||
Button(action: generateFeed) {
|
||||
Text("Generate")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
.fileImporter(
|
||||
isPresented: $showFileImporter,
|
||||
allowedContentTypes: [.folder],
|
||||
onCompletion: didSelectContentFolder)
|
||||
.sheet(isPresented: $showTagPicker) {
|
||||
TagSelectionView(
|
||||
presented: $showTagPicker,
|
||||
selected: $content.websiteData.navigationTags,
|
||||
tags: $content.tags)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Folder selection
|
||||
@ -140,4 +174,5 @@ struct SettingsView: View {
|
||||
|
||||
#Preview {
|
||||
SettingsView()
|
||||
.environmentObject(Content.mock)
|
||||
}
|
||||
|
68
CHDataManagement/Views/Tags/PageTagAssignmentView.swift
Normal file
68
CHDataManagement/Views/Tags/PageTagAssignmentView.swift
Normal file
@ -0,0 +1,68 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
private struct PageSelectionView: View {
|
||||
|
||||
@ObservedObject
|
||||
var tag: Tag
|
||||
|
||||
@ObservedObject
|
||||
var page: Page
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
let isSelected = page.tags.contains(tag)
|
||||
Image(systemSymbol: isSelected ? .checkmarkCircleFill : .circle)
|
||||
.foregroundStyle(Color.blue)
|
||||
Text(page.localized(in: language).title)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
toggleTagAssignment()
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleTagAssignment() {
|
||||
guard let index = page.tags.firstIndex(of: tag) else {
|
||||
page.tags.append(tag)
|
||||
return
|
||||
}
|
||||
page.tags.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
struct PageTagAssignmentView: View {
|
||||
|
||||
@ObservedObject
|
||||
var tag: Tag
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss: DismissAction
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
ForEach(content.pages) { page in
|
||||
PageSelectionView(tag: tag, page: page)
|
||||
}
|
||||
}.frame(minHeight: 400)
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
}.padding(.bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
PageTagAssignmentView(tag: .hiking)
|
||||
.environmentObject(Content.mock)
|
||||
}
|
68
CHDataManagement/Views/Tags/PostTagAssignmentView.swift
Normal file
68
CHDataManagement/Views/Tags/PostTagAssignmentView.swift
Normal file
@ -0,0 +1,68 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
private struct PostSelectionView: View {
|
||||
|
||||
@ObservedObject
|
||||
var tag: Tag
|
||||
|
||||
@ObservedObject
|
||||
var post: Post
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
let isSelected = post.tags.contains(tag)
|
||||
Image(systemSymbol: isSelected ? .checkmarkCircleFill : .circle)
|
||||
.foregroundStyle(Color.blue)
|
||||
Text(post.localized(in: language).title)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
toggleTagAssignment()
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleTagAssignment() {
|
||||
guard let index = post.tags.firstIndex(of: tag) else {
|
||||
post.tags.append(tag)
|
||||
return
|
||||
}
|
||||
post.tags.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
struct PostTagAssignmentView: View {
|
||||
|
||||
@ObservedObject
|
||||
var tag: Tag
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss: DismissAction
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
ForEach(content.posts) { post in
|
||||
PostSelectionView(tag: tag, post: post)
|
||||
}
|
||||
}.frame(minHeight: 400)
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
}.padding(.bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
PostTagAssignmentView(tag: .hiking)
|
||||
.environmentObject(Content.mock)
|
||||
}
|
63
CHDataManagement/Views/Tags/TagContentView.swift
Normal file
63
CHDataManagement/Views/Tags/TagContentView.swift
Normal file
@ -0,0 +1,63 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TagContentView: View {
|
||||
|
||||
@ObservedObject
|
||||
var tag: Tag
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@State
|
||||
private var showPageSelection = false
|
||||
|
||||
@State
|
||||
private var showPostSelection = false
|
||||
|
||||
var selectedPages: [Page] {
|
||||
content.pages.filter { $0.tags.contains(tag) }
|
||||
}
|
||||
|
||||
var selectedPosts: [Post] {
|
||||
content.posts.filter { $0.tags.contains(tag) }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section("Pages") {
|
||||
ForEach(selectedPages) { page in
|
||||
Text(page.localized(in: language).title)
|
||||
}
|
||||
Button(action: { showPageSelection = true }) {
|
||||
Text("Select pages")
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.foregroundStyle(Color.blue)
|
||||
}
|
||||
Section("Posts") {
|
||||
ForEach(selectedPosts) { post in
|
||||
Text(post.localized(in: language).title)
|
||||
}
|
||||
Button(action: { showPostSelection = true }) {
|
||||
Text("Select posts")
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.foregroundStyle(Color.blue)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showPageSelection) {
|
||||
PageTagAssignmentView(tag: tag)
|
||||
}
|
||||
.sheet(isPresented: $showPostSelection) {
|
||||
PostTagAssignmentView(tag: tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
TagContentView(tag: .hiking)
|
||||
.environmentObject(Content.mock)
|
||||
}
|
@ -15,49 +15,51 @@ struct TagDetailView: View {
|
||||
private var showImagePicker = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Toggle("Appears in overviews", isOn: $tagIsVisible)
|
||||
.toggleStyle(.switch)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Toggle("Appears in overviews", isOn: $tagIsVisible)
|
||||
.toggleStyle(.switch)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Text("Name")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
TextField("", text: $tag.name)
|
||||
Text("Name")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
TextField("", text: $tag.name)
|
||||
|
||||
Text("URL String")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
TextField("", text: $tag.urlComponent)
|
||||
Text("URL String")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
TextField("", text: $tag.urlComponent)
|
||||
|
||||
Text("Original url")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
Text(tag.originalUrl ?? "-")
|
||||
.padding(.top, 1)
|
||||
.padding(.bottom)
|
||||
Text("Original url")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
Text(tag.originalUrl ?? "-")
|
||||
.padding(.top, 1)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("Subtitle")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
OptionalTextField("", text: $tag.subtitle)
|
||||
Text("Subtitle")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
OptionalTextField("", text: $tag.subtitle)
|
||||
|
||||
Text("Description")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
OptionalTextField("", text: $tag.description)
|
||||
Text("Description")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
OptionalTextField("", text: $tag.description)
|
||||
|
||||
Text("Thumbnail")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
Button(action: { showImagePicker = true }) {
|
||||
Text(tag.thumbnail?.id ?? "Select")
|
||||
Text("Thumbnail")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
Button(action: { showImagePicker = true }) {
|
||||
Text(tag.thumbnail?.id ?? "Select")
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.foregroundStyle(.blue)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.foregroundStyle(.blue)
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
.sheet(isPresented: $showImagePicker) {
|
||||
ImagePickerView(showImagePicker: $showImagePicker) { image in
|
||||
tag.thumbnail = image
|
||||
|
@ -26,6 +26,7 @@ struct TagsListView: View {
|
||||
@State
|
||||
var selectedTag: Tag?
|
||||
|
||||
#warning("TODO: Resort tag list when name changes")
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
List(content.tags, selection: $selectedTag) { tag in
|
||||
@ -33,20 +34,43 @@ struct TagsListView: View {
|
||||
.tag(tag)
|
||||
|
||||
}
|
||||
} detail: {
|
||||
} content: {
|
||||
if let selectedTag {
|
||||
SelectedTagView(tag: selectedTag)
|
||||
TagContentView(tag: selectedTag)
|
||||
.layoutPriority(1)
|
||||
} else {
|
||||
Text("Select a tag to show the details")
|
||||
.font(.largeTitle)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
} detail: {
|
||||
if let selectedTag {
|
||||
SelectedTagView(tag: selectedTag)
|
||||
.frame(maxWidth: 350)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if selectedTag == nil {
|
||||
selectedTag = content.tags.first
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button(action: addNewTag) {
|
||||
Label("New tag", systemSymbol: .plus)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addNewTag() {
|
||||
let newTag = Tag(isVisible: true,
|
||||
german: .init(urlComponent: "tag", name: "Neuer Tag"),
|
||||
english: .init(urlComponent: "tag-en", name: "New Tag"))
|
||||
// Add to top of the list, and resort when changing the name
|
||||
content.tags.insert(newTag, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user