From 5db74f5fe9c0bf5626bacb63786729f6c455e6f1 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 1 Nov 2023 19:38:44 +0800 Subject: [PATCH] Labels editor view and offline sync of label changes using CreateLabelInput instead of IDs --- .../Share/Views/EditLabelsSheet.swift | 104 +++++++----------- .../App/Views/Home/HomeFeedViewModel.swift | 10 +- .../App/Views/Labels/ApplyLabelsView.swift | 19 ++-- .../App/Views/Labels/LabelsViewModel.swift | 2 +- .../UpdateArticleLabelsPublisher.swift | 24 ++-- 5 files changed, 71 insertions(+), 88 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/EditLabelsSheet.swift b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/EditLabelsSheet.swift index 5c5615ecd..3435aec05 100644 --- a/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/EditLabelsSheet.swift +++ b/apple/OmnivoreKit/Sources/App/AppExtensions/Share/Views/EditLabelsSheet.swift @@ -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() diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index d1ba61cbc..5c33f409e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -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) } diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift index 9b6129bd6..592159255 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift @@ -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 { diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift index 11b652f16..3578e362f 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift @@ -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) { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift index 41ad8b18f..8536e9d97 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift @@ -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