mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Labels editor view and offline sync of label changes using CreateLabelInput instead of IDs
This commit is contained in:
parent
e1076f9ac4
commit
5db74f5fe9
5 changed files with 71 additions and 88 deletions
|
|
@ -52,80 +52,50 @@ public struct EditLabelsSheet: View {
|
|||
}
|
||||
|
||||
var content: some View {
|
||||
VStack(spacing: 15) {
|
||||
VStack {
|
||||
LabelsEntryView(
|
||||
searchTerm: $labelsViewModel.labelSearchFilter,
|
||||
viewModel: labelsViewModel
|
||||
)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 20)
|
||||
|
||||
if labelsViewModel.labelSearchFilter.count >= 63 {
|
||||
Text("The maximum length of a label is 64 chars.").foregroundColor(Color.red).font(.footnote)
|
||||
}
|
||||
|
||||
List {
|
||||
Section {
|
||||
ForEach(labelsViewModel.labels.applySearchFilter(labelsViewModel.labelSearchFilter), id: \.self) { label in
|
||||
Button(
|
||||
action: {
|
||||
// if labelsViewModel.selectedLabels.contains(label) {
|
||||
// if let idx = viewModel.selectedLabels.firstIndex(of: label) {
|
||||
// viewModel.selectedLabels.remove(at: idx)
|
||||
// }
|
||||
// } else {
|
||||
// viewModel.labelSearchFilter = ZWSP
|
||||
// viewModel.selectedLabels.append(label)
|
||||
// }
|
||||
},
|
||||
label: {
|
||||
HStack {
|
||||
TextChip(feedItemLabel: label).allowsHitTesting(false)
|
||||
Spacer()
|
||||
if isSelected(label) {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
ForEach(labelsViewModel.labels.applySearchFilter(labelsViewModel.labelSearchFilter), id: \.self) { label in
|
||||
Button(
|
||||
action: {
|
||||
if let idx = labelsViewModel.selectedLabels.firstIndex(of: label) {
|
||||
labelsViewModel.selectedLabels.remove(at: idx)
|
||||
} else {
|
||||
labelsViewModel.labelSearchFilter = ZWSP
|
||||
labelsViewModel.selectedLabels.append(label)
|
||||
}
|
||||
)
|
||||
.padding(.vertical, 5)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
#if os(macOS)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
#endif
|
||||
}
|
||||
// createLabelButton
|
||||
},
|
||||
label: {
|
||||
HStack {
|
||||
TextChip(feedItemLabel: label).allowsHitTesting(false)
|
||||
Spacer()
|
||||
if isSelected(label) {
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
)
|
||||
.padding(.vertical, 5)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
#if os(macOS)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
|
||||
Spacer()
|
||||
|
||||
// // swiftlint:disable line_length
|
||||
// ScrollView {
|
||||
// LabelsMasonaryView(
|
||||
// labels: labelsViewModel.labels.applySearchFilter(labelsViewModel.labelSearchFilter),
|
||||
// selectedLabels: labelsViewModel.selectedLabels.applySearchFilter(labelsViewModel.labelSearchFilter),
|
||||
// onLabelTap: onLabelTap
|
||||
// )
|
||||
//
|
||||
// Button(
|
||||
// action: { labelsViewModel.showCreateLabelModal = true },
|
||||
// label: {
|
||||
// HStack {
|
||||
// let trimmedLabelName = labelsViewModel.labelSearchFilter.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
// Image(systemName: "tag").foregroundColor(.blue)
|
||||
// Text(
|
||||
// labelsViewModel.labelSearchFilter.count > 0 ?
|
||||
// "Create: \"\(trimmedLabelName)\" label" :
|
||||
// LocalText.createLabelMessage
|
||||
// ).foregroundColor(.blue)
|
||||
// .font(Font.system(size: 14))
|
||||
// Spacer()
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// .buttonStyle(PlainButtonStyle())
|
||||
// .padding(10)
|
||||
// }
|
||||
.listStyle(.plain)
|
||||
.background(Color.extensionBackground)
|
||||
}
|
||||
.background(Color.clear)
|
||||
.padding(20)
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
|
|
@ -136,6 +106,12 @@ public struct EditLabelsSheet: View {
|
|||
.navigationTitle("Set Labels")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing: Button(action: {
|
||||
if let linkedItem = viewModel.linkedItem, let linkedItemId = linkedItem.id {
|
||||
labelsViewModel.saveItemLabelChanges(
|
||||
itemID: linkedItemId,
|
||||
dataService: viewModel.services.dataService
|
||||
)
|
||||
}
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("Done").bold()
|
||||
|
|
|
|||
|
|
@ -326,8 +326,8 @@ import Views
|
|||
|
||||
func addLabel(dataService: DataService, item: LinkedItem, label: String, color: String) {
|
||||
if let label = getOrCreateLabel(dataService: dataService, named: "Pinned", color: color) {
|
||||
let existingLabels = item.labels?.allObjects.compactMap { ($0 as? LinkedItemLabel)?.unwrappedID } ?? []
|
||||
dataService.updateItemLabels(itemID: item.unwrappedID, labelIDs: existingLabels + [label.unwrappedID])
|
||||
let existingLabels = item.labels?.allObjects.compactMap { $0 as? LinkedItemLabel } ?? []
|
||||
dataService.setItemLabels(itemID: item.unwrappedID, labels: InternalLinkedItemLabel.make(Set(existingLabels + [label]) as NSSet))
|
||||
|
||||
item.update(inContext: dataService.viewContext)
|
||||
updateFeatureFilter(context: dataService.viewContext, filter: FeaturedItemFilter(rawValue: featureFilter))
|
||||
|
|
@ -335,10 +335,10 @@ import Views
|
|||
}
|
||||
|
||||
func removeLabel(dataService: DataService, item: LinkedItem, named: String) {
|
||||
let labelIds = item.labels?
|
||||
let labels = item.labels?
|
||||
.filter { ($0 as? LinkedItemLabel)?.name != named }
|
||||
.compactMap { ($0 as? LinkedItemLabel)?.unwrappedID } ?? []
|
||||
dataService.updateItemLabels(itemID: item.unwrappedID, labelIDs: labelIds)
|
||||
.compactMap { $0 as? LinkedItemLabel } ?? []
|
||||
dataService.setItemLabels(itemID: item.unwrappedID, labels: InternalLinkedItemLabel.make(Set(labels) as NSSet))
|
||||
item.update(inContext: dataService.viewContext)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,14 +50,13 @@ struct ApplyLabelsView: View {
|
|||
|
||||
var innerBody: some View {
|
||||
VStack {
|
||||
if !viewModel.labels.isEmpty {
|
||||
LabelsEntryView(
|
||||
searchTerm: $viewModel.labelSearchFilter,
|
||||
viewModel: viewModel
|
||||
)
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
LabelsEntryView(
|
||||
searchTerm: $viewModel.labelSearchFilter,
|
||||
viewModel: viewModel
|
||||
)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 20)
|
||||
|
||||
if viewModel.labelSearchFilter.count >= 63 {
|
||||
Text("The maximum length of a label is 64 chars.").foregroundColor(Color.red).font(.footnote)
|
||||
}
|
||||
|
|
@ -96,11 +95,13 @@ struct ApplyLabelsView: View {
|
|||
createLabelButton
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
.listStyle(.plain)
|
||||
.background(Color.extensionBackground)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle(mode.navTitle)
|
||||
.background(Color.extensionBackground)
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ import SwiftUI
|
|||
}
|
||||
|
||||
func saveItemLabelChanges(itemID: String, dataService: DataService) {
|
||||
dataService.updateItemLabels(itemID: itemID, labelIDs: selectedLabels.map(\.unwrappedID))
|
||||
dataService.setItemLabels(itemID: itemID, labels: InternalLinkedItemLabel.make(Set(selectedLabels) as NSSet))
|
||||
}
|
||||
|
||||
func saveHighlightLabelChanges(highlightID: String, dataService: DataService) {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import Foundation
|
|||
import Models
|
||||
import SwiftGraphQL
|
||||
|
||||
extension DataService {
|
||||
public func updateItemLabels(itemID: String, labelIDs: [String]) {
|
||||
public extension DataService {
|
||||
func setItemLabels(itemID: String, labels: [InternalLinkedItemLabel]) {
|
||||
backgroundContext.perform { [weak self] in
|
||||
guard let self = self else { return }
|
||||
guard let linkedItem = LinkedItem.lookup(byID: itemID, inContext: self.backgroundContext) else { return }
|
||||
|
|
@ -13,21 +13,19 @@ extension DataService {
|
|||
linkedItem.removeFromLabels(existingLabels)
|
||||
}
|
||||
|
||||
for labelID in labelIDs {
|
||||
if let labelObject = LinkedItemLabel.lookup(byID: labelID, inContext: self.backgroundContext) {
|
||||
linkedItem.addToLabels(labelObject)
|
||||
}
|
||||
for label in labels {
|
||||
linkedItem.addToLabels(label.asManagedObject(inContext: self.backgroundContext))
|
||||
}
|
||||
|
||||
linkedItem.update(inContext: self.backgroundContext)
|
||||
try? self.backgroundContext.save()
|
||||
|
||||
// Send update to server
|
||||
self.syncLabelUpdates(itemID: itemID, labelIDs: labelIDs)
|
||||
self.syncLabelUpdates(itemID: itemID, labels: labels)
|
||||
}
|
||||
}
|
||||
|
||||
func syncLabelUpdates(itemID: String, labelIDs: [String]) {
|
||||
internal func syncLabelUpdates(itemID: String, labels: [InternalLinkedItemLabel]) {
|
||||
enum MutationResult {
|
||||
case saved(feedItem: [InternalLinkedItemLabel])
|
||||
case error(errorCode: Enums.SetLabelsErrorCode)
|
||||
|
|
@ -40,10 +38,18 @@ extension DataService {
|
|||
)
|
||||
}
|
||||
|
||||
let labelInputs = labels.compactMap { label in
|
||||
InputObjects.CreateLabelInput(
|
||||
color: OptionalArgument(label.color),
|
||||
description: OptionalArgument(label.labelDescription),
|
||||
name: label.name
|
||||
)
|
||||
}
|
||||
|
||||
let mutation = Selection.Mutation {
|
||||
try $0.setLabels(
|
||||
input: InputObjects.SetLabelsInput(
|
||||
labelIds: OptionalArgument(labelIDs),
|
||||
labels: OptionalArgument(labelInputs),
|
||||
pageId: itemID
|
||||
),
|
||||
selection: selection
|
||||
|
|
|
|||
Loading…
Reference in a new issue