Improve page indicators, adding items
This commit is contained in:
@ -8,15 +8,38 @@ struct FileToAddView: View {
|
||||
|
||||
let delete: (FileToAdd) -> Void
|
||||
|
||||
var symbol: SFSymbol {
|
||||
if file.idAlreadyExists {
|
||||
return .docOnDoc
|
||||
}
|
||||
if file.isSelected {
|
||||
return .checkmarkCircleFill
|
||||
}
|
||||
return .circle
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
if file.idAlreadyExists {
|
||||
return .red
|
||||
}
|
||||
if file.isSelected {
|
||||
return .blue
|
||||
}
|
||||
return .gray
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemSymbol: file.isSelected ? .checkmarkCircleFill : .circle)
|
||||
Image(systemSymbol: symbol)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundStyle(.blue)
|
||||
.foregroundStyle(color)
|
||||
.onTapGesture {
|
||||
file.isSelected.toggle()
|
||||
if !file.idAlreadyExists {
|
||||
file.isSelected.toggle()
|
||||
}
|
||||
}
|
||||
Image(systemSymbol: .trashCircleFill)
|
||||
.resizable()
|
||||
|
@ -1,15 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct DraftIndicator: View {
|
||||
|
||||
var body: some View {
|
||||
Text("Draft")
|
||||
.foregroundStyle(.white)
|
||||
.padding(.vertical, 2)
|
||||
.padding(.horizontal, 5)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 5, style: .circular)
|
||||
.foregroundStyle(Color.gray)
|
||||
)
|
||||
}
|
||||
}
|
33
CHDataManagement/Views/Generic/TextIndicator.swift
Normal file
33
CHDataManagement/Views/Generic/TextIndicator.swift
Normal file
@ -0,0 +1,33 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TextIndicator: View {
|
||||
|
||||
let text: LocalizedStringKey
|
||||
|
||||
let color: Color
|
||||
|
||||
let background: Color
|
||||
|
||||
init(text: String, color: Color = .white, background: Color = Color.gray) {
|
||||
self.text = .init(stringLiteral: text)
|
||||
self.background = background
|
||||
self.color = color
|
||||
}
|
||||
|
||||
init(text: LocalizedStringKey, color: Color = .white, background: Color = Color.gray) {
|
||||
self.text = text
|
||||
self.background = background
|
||||
self.color = color
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.foregroundStyle(color)
|
||||
.padding(.vertical, 2)
|
||||
.padding(.horizontal, 5)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 5, style: .circular)
|
||||
.foregroundStyle(background)
|
||||
)
|
||||
}
|
||||
}
|
@ -17,18 +17,16 @@ struct AddPageView: View {
|
||||
@State
|
||||
private var newPageId = ""
|
||||
|
||||
private let allowedCharactersInPageId = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted
|
||||
|
||||
init(selected: Binding<Page?>) {
|
||||
self._selectedPage = selected
|
||||
}
|
||||
|
||||
private var idExists: Bool {
|
||||
content.pages.contains { $0.id == newPageId }
|
||||
!content.isNewIdForPage(newPageId)
|
||||
}
|
||||
|
||||
private var containsInvalidCharacters: Bool {
|
||||
newPageId.rangeOfCharacter(from: allowedCharactersInPageId) != nil
|
||||
private var isInvalidId: Bool {
|
||||
!content.isValidIdForTagOrPageOrPost(newPageId)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -42,12 +40,12 @@ struct AddPageView: View {
|
||||
if newPageId.isEmpty {
|
||||
Text("Enter the id of the new page to create")
|
||||
.foregroundStyle(.secondary)
|
||||
} else if isInvalidId {
|
||||
Text("The id contains invalid characters")
|
||||
.foregroundStyle(Color.red)
|
||||
} else if idExists {
|
||||
Text("A page with the same id already exists")
|
||||
.foregroundStyle(Color.red)
|
||||
} else if containsInvalidCharacters {
|
||||
Text("The id contains invalid characters")
|
||||
.foregroundStyle(Color.red)
|
||||
} else {
|
||||
Text("Create a new page with the id")
|
||||
.foregroundStyle(.secondary)
|
||||
@ -59,7 +57,7 @@ struct AddPageView: View {
|
||||
Button(action: addNewPage) {
|
||||
Text("Create")
|
||||
}
|
||||
.disabled(newPageId.isEmpty || containsInvalidCharacters || idExists)
|
||||
.disabled(isInvalidId || idExists)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
@ -7,13 +7,11 @@ struct LocalizedPageContentView: View {
|
||||
@EnvironmentObject
|
||||
var content: Content
|
||||
|
||||
let pageId: String
|
||||
@ObservedObject
|
||||
var page: Page
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
@ObservedObject
|
||||
var page: LocalizedPage
|
||||
|
||||
@State
|
||||
private var pageContent: String = ""
|
||||
|
||||
@ -26,18 +24,8 @@ struct LocalizedPageContentView: View {
|
||||
@State
|
||||
private var didChangeContent = false
|
||||
|
||||
init(pageId: String, page: LocalizedPage, language: ContentLanguage) {
|
||||
self.pageId = pageId
|
||||
self.page = page
|
||||
self.language = language
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
TextField("", text: $page.title)
|
||||
.font(.title)
|
||||
.textFieldStyle(.plain)
|
||||
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Button(action: loadContent) {
|
||||
Text("Load")
|
||||
@ -70,14 +58,14 @@ struct LocalizedPageContentView: View {
|
||||
|
||||
private func loadContent() {
|
||||
let language = language
|
||||
guard page.content.storage.hasPageContent(for: pageId, language: language) else {
|
||||
guard page.localized(in: language).hasContent else {
|
||||
pageContent = "New file"
|
||||
DispatchQueue.main.async {
|
||||
didChangeContent = false
|
||||
}
|
||||
return
|
||||
}
|
||||
guard let content = page.content.storage.pageContent(for: pageId, language: language) else {
|
||||
guard let content = page.pageContent(in: language) else {
|
||||
print("Failed to load page content")
|
||||
pageContent = "Failed to load"
|
||||
DispatchQueue.main.async {
|
||||
@ -108,7 +96,7 @@ struct LocalizedPageContentView: View {
|
||||
guard didChangeContent else {
|
||||
return
|
||||
}
|
||||
guard page.content.storage.save(pageContent: pageContent, for: pageId, language: language) else {
|
||||
guard page.save(pageContent: pageContent, in: language) else {
|
||||
print("Failed to save content")
|
||||
return
|
||||
}
|
||||
@ -120,9 +108,6 @@ struct LocalizedPageContentView: View {
|
||||
guard content != pageContentUsedForGeneration else {
|
||||
return
|
||||
}
|
||||
guard let page = self.content.page(pageId) else {
|
||||
return
|
||||
}
|
||||
guard !self.content.isGeneratingWebsite else {
|
||||
return
|
||||
}
|
||||
|
@ -41,8 +41,10 @@ struct PageContentView: View {
|
||||
}.padding()
|
||||
} else {
|
||||
VStack(alignment: .leading) {
|
||||
PageTitleView(page: page.localized(in: language))
|
||||
.id(page.id + language.rawValue)
|
||||
TagDisplayView(tags: $page.tags)
|
||||
LocalizedPageContentView(pageId: page.id, page: page.localized(in: language), language: language)
|
||||
LocalizedPageContentView(page: page, language: language)
|
||||
.id(page.id + language.rawValue)
|
||||
}
|
||||
.padding()
|
||||
|
@ -1,5 +1,40 @@
|
||||
import SwiftUI
|
||||
|
||||
private struct PageListItem: View {
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@ObservedObject
|
||||
var page: Page
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
LocalizedPageListItem(page: page.localized(in: language))
|
||||
Spacer()
|
||||
if page.isExternalUrl {
|
||||
TextIndicator(text: "External")
|
||||
} else if page.isDraft {
|
||||
TextIndicator(text: "Draft", background: .yellow)
|
||||
} else {
|
||||
ForEach(page.missingContentLanguages, id: \.self) { language in
|
||||
TextIndicator(text: language.shortText, background: Color.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct LocalizedPageListItem: View {
|
||||
|
||||
@ObservedObject
|
||||
var page: LocalizedPage
|
||||
|
||||
var body: some View {
|
||||
Text(page.title)
|
||||
}
|
||||
}
|
||||
|
||||
struct PageListView: View {
|
||||
|
||||
@Environment(\.language)
|
||||
@ -31,13 +66,8 @@ struct PageListView: View {
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.horizontal, 8)
|
||||
List(filteredPages, selection: $selectedPage) { page in
|
||||
HStack {
|
||||
Text(page.title(in: language))
|
||||
Spacer()
|
||||
if page.isDraft {
|
||||
DraftIndicator()
|
||||
}
|
||||
}.tag(page)
|
||||
PageListItem(page: page)
|
||||
.tag(page)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
@ -17,18 +17,16 @@ struct AddPostView: View {
|
||||
@State
|
||||
private var newPostId = ""
|
||||
|
||||
private let allowedCharactersInPostId = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted
|
||||
|
||||
init(selected: Binding<Post?>) {
|
||||
self._selectedPost = selected
|
||||
}
|
||||
|
||||
private var idExists: Bool {
|
||||
content.posts.contains { $0.id == newPostId }
|
||||
!content.isNewIdForPost(newPostId)
|
||||
}
|
||||
|
||||
private var containsInvalidCharacters: Bool {
|
||||
newPostId.rangeOfCharacter(from: allowedCharactersInPostId) != nil
|
||||
private var isInvalidId: Bool {
|
||||
!content.isValidIdForTagOrPageOrPost(newPostId)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -45,7 +43,7 @@ struct AddPostView: View {
|
||||
} else if idExists {
|
||||
Text("A post with the same id already exists")
|
||||
.foregroundStyle(Color.red)
|
||||
} else if containsInvalidCharacters {
|
||||
} else if isInvalidId {
|
||||
Text("The id contains invalid characters")
|
||||
.foregroundStyle(Color.red)
|
||||
} else {
|
||||
@ -59,7 +57,7 @@ struct AddPostView: View {
|
||||
Button(action: addNewPost) {
|
||||
Text("Create")
|
||||
}
|
||||
.disabled(newPostId.isEmpty || containsInvalidCharacters || idExists)
|
||||
.disabled(isInvalidId || idExists)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
@ -35,7 +35,7 @@ struct PostListView: View {
|
||||
Text(post.title(in: language))
|
||||
Spacer()
|
||||
if post.isDraft {
|
||||
DraftIndicator()
|
||||
TextIndicator(text: "Draft")
|
||||
}
|
||||
}.tag(post)
|
||||
}
|
||||
|
@ -69,9 +69,6 @@ struct TagSelectionView: View {
|
||||
return
|
||||
}
|
||||
selected.remove(at: index)
|
||||
|
||||
let insertIndex = tags.firstIndex(where: { $0 > tag }) ?? tags.endIndex
|
||||
tags.insert(tag, at: insertIndex)
|
||||
}
|
||||
|
||||
private func select(tag: Tag) {
|
||||
|
@ -294,7 +294,7 @@ struct PageIssueView: View {
|
||||
}
|
||||
let modified = pageContent.replacingOccurrences(of: oldString, with: newString)
|
||||
|
||||
guard content.storage.save(pageContent: modified, for: page.id, language: language) else {
|
||||
guard content.storage.save(pageContent: modified, for: page.id, in: language) else {
|
||||
print("Replaced \(oldString) with \(newString) in page \(page.id) (\(language))")
|
||||
return
|
||||
}
|
||||
|
@ -14,24 +14,69 @@ struct AddTagView: View {
|
||||
@Binding
|
||||
var selectedTag: Tag?
|
||||
|
||||
@State
|
||||
private var newId = ""
|
||||
|
||||
init(selected: Binding<Tag?>) {
|
||||
self._selectedTag = selected
|
||||
}
|
||||
|
||||
private var idExists: Bool {
|
||||
!content.isNewIdForTag(newId)
|
||||
}
|
||||
|
||||
private var isInvalidId: Bool {
|
||||
!content.isValidIdForTagOrPageOrPost(newId)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Text("Creating tag...")
|
||||
.onAppear(perform: addNewTag)
|
||||
VStack {
|
||||
Text("New tag")
|
||||
.font(.headline)
|
||||
|
||||
TextField("", text: $newId)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.frame(maxWidth: 350)
|
||||
if newId.isEmpty {
|
||||
Text("Enter the id of the new tag to create")
|
||||
.foregroundStyle(.secondary)
|
||||
} else if isInvalidId {
|
||||
Text("The id contains invalid characters")
|
||||
.foregroundStyle(Color.red)
|
||||
} else if idExists {
|
||||
Text("A tag with the same id already exists")
|
||||
.foregroundStyle(Color.red)
|
||||
} else {
|
||||
Text("Create a new tag with the id")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
HStack {
|
||||
Button(role: .cancel, action: dismissSheet) {
|
||||
Text("Cancel")
|
||||
}
|
||||
Button(action: addNewTag) {
|
||||
Text("Create")
|
||||
}
|
||||
.disabled(isInvalidId || idExists)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
private func addNewTag() {
|
||||
let newTag = Tag(
|
||||
let tag = Tag(
|
||||
content: content,
|
||||
id: "tag",
|
||||
id: newId,
|
||||
isVisible: true,
|
||||
german: .init(content: content, urlComponent: "tag", name: "Neuer Tag"),
|
||||
english: .init(content: content, urlComponent: "tag-en", name: "New Tag"))
|
||||
german: .init(content: content, urlComponent: newId, name: newId),
|
||||
english: .init(content: content, urlComponent: "\(newId)-en", name: "\(newId)-en"))
|
||||
// Add to top of the list, and resort when changing the name
|
||||
content.tags.insert(newTag, at: 0)
|
||||
content.tags.insert(tag, at: 0)
|
||||
selectedTag = tag
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private func dismissSheet() {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
@ -25,13 +25,18 @@ struct TagListView: View {
|
||||
return content.tags.filter { $0.localized(in: language).name.contains(searchString) }
|
||||
}
|
||||
|
||||
private var filteredAndSortedTags: [Tag] {
|
||||
filteredTags.sorted { $0.title(in: language) }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
TextField("", text: $searchString, prompt: Text("Search"))
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.horizontal, 8)
|
||||
List(filteredTags, selection: $selectedTag) { tag in
|
||||
Text(tag.localized(in: language).title).tag(tag)
|
||||
List(filteredAndSortedTags, selection: $selectedTag) { tag in
|
||||
TagListItem(tag: tag.localized(in: language))
|
||||
.tag(tag)
|
||||
}
|
||||
}.onAppear {
|
||||
if selectedTag == nil {
|
||||
@ -40,3 +45,13 @@ struct TagListView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TagListItem: View {
|
||||
|
||||
@ObservedObject
|
||||
var tag: LocalizedTag
|
||||
|
||||
var body: some View {
|
||||
Text(tag.title)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user