From ba74c544326ae012fafdf864cb51008e7490edae Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 10 Oct 2024 06:42:04 -0400 Subject: [PATCH] Update --- .../LoggerStore/LoggerStore+Entities.swift | 1 + .../Pulse/LoggerStore/LoggerStore+Model.swift | 1 + Sources/Pulse/LoggerStore/LoggerStore.swift | 14 +++- .../Console/Views/ConsoleHelperViews.swift | 9 +++ .../Console/Views/ConsoleMessageCell.swift | 6 +- .../Console/Views/ConsoleTaskCell.swift | 6 +- .../Views/ConsoleDomainsSelectionView.swift | 8 ++- .../Views/ConsoleLabelsSelectionView.swift | 8 ++- .../ConsoleSearchListSelectionView.swift | 39 ++--------- .../Views/ConsoleSessionsPickerView.swift | 4 ++ .../Services/ConsoleSearchOccurrence.swift | 38 +++++++++-- .../Services/ConsoleSearchOperation.swift | 4 +- .../ConsoleSearchResultsSectionView.swift | 43 ++---------- .../Features/Sessions/SessionListView.swift | 65 ++++++------------- .../Features/Sessions/SessionPickerView.swift | 4 +- .../Settings/SettingsView-macos.swift | 10 ++- .../PulseUI/Helpers/StringSearchOptions.swift | 5 +- .../PulseUI/Helpers/TextRendererJSON.swift | 7 +- Sources/PulseUI/Views/ContextMenus.swift | 17 ----- Sources/PulseUI/Views/PinView.swift | 33 ---------- Sources/PulseUI/Views/SearchBar-macos.swift | 65 ------------------- 21 files changed, 120 insertions(+), 267 deletions(-) delete mode 100644 Sources/PulseUI/Views/PinView.swift delete mode 100644 Sources/PulseUI/Views/SearchBar-macos.swift diff --git a/Sources/Pulse/LoggerStore/LoggerStore+Entities.swift b/Sources/Pulse/LoggerStore/LoggerStore+Entities.swift index 64c8e140a..b1bfb8446 100644 --- a/Sources/Pulse/LoggerStore/LoggerStore+Entities.swift +++ b/Sources/Pulse/LoggerStore/LoggerStore+Entities.swift @@ -30,6 +30,7 @@ public final class LoggerMessageEntity: NSManagedObject { public final class NetworkTaskEntity: NSManagedObject { // Primary @NSManaged public var createdAt: Date + @NSManaged public var isPinned: Bool @NSManaged public var session: UUID @NSManaged public var taskId: UUID diff --git a/Sources/Pulse/LoggerStore/LoggerStore+Model.swift b/Sources/Pulse/LoggerStore/LoggerStore+Model.swift index 381f38968..a2a3528b1 100644 --- a/Sources/Pulse/LoggerStore/LoggerStore+Model.swift +++ b/Sources/Pulse/LoggerStore/LoggerStore+Model.swift @@ -45,6 +45,7 @@ extension LoggerStore { task.properties = [ Attribute("createdAt", .dateAttributeType), + Attribute("isPinned", .booleanAttributeType), Attribute("session", .UUIDAttributeType), Attribute("taskId", .UUIDAttributeType), Attribute("taskType", .integer16AttributeType), diff --git a/Sources/Pulse/LoggerStore/LoggerStore.swift b/Sources/Pulse/LoggerStore/LoggerStore.swift index 20b838487..7f9c627f5 100644 --- a/Sources/Pulse/LoggerStore/LoggerStore.swift +++ b/Sources/Pulse/LoggerStore/LoggerStore.swift @@ -808,6 +808,12 @@ extension LoggerStore { } } + package func clearSessions(withIDs sessionIDs: Set) { + perform { _ in + try? self._removeMessagesForSessions(withIDs: sessionIDs, isInverted: false) + } + } + private func _removeSessions(withIDs sessionIDs: Set, isInverted: Bool = false) throws { try deleteEntities(for: { let request = LoggerSessionEntity.fetchRequest() @@ -816,6 +822,10 @@ extension LoggerStore { return request }()) + try _removeMessagesForSessions(withIDs: sessionIDs, isInverted: isInverted) + } + + private func _removeMessagesForSessions(withIDs sessionIDs: Set, isInverted: Bool) throws { var predicate = NSPredicate(format: "session IN %@", sessionIDs) predicate = isInverted ? NSCompoundPredicate(notPredicateWithSubpredicate: predicate) : predicate try removeMessages(with: predicate) @@ -825,7 +835,9 @@ extension LoggerStore { /// Removes all of the previously recorded messages. public func removeAll() { - perform { _ in self._removeAll() } + perform { _ in + self._removeAll() + } } private func _removeAll() { diff --git a/Sources/PulseUI/Features/Console/Views/ConsoleHelperViews.swift b/Sources/PulseUI/Features/Console/Views/ConsoleHelperViews.swift index 68b84c0c9..3b8860c78 100644 --- a/Sources/PulseUI/Features/Console/Views/ConsoleHelperViews.swift +++ b/Sources/PulseUI/Features/Console/Views/ConsoleHelperViews.swift @@ -58,3 +58,12 @@ struct StatusIndicatorView: View { } } } + +struct BookmarkIconView: View { + var body: some View { + Image(systemName: "bookmark.fill") + .font(.footnote) + .foregroundColor(.pink) + .frame(width: 8, height: 8) + } +} diff --git a/Sources/PulseUI/Features/Console/Views/ConsoleMessageCell.swift b/Sources/PulseUI/Features/Console/Views/ConsoleMessageCell.swift index ecb8453ad..5cc712b4e 100644 --- a/Sources/PulseUI/Features/Console/Views/ConsoleMessageCell.swift +++ b/Sources/PulseUI/Features/Console/Views/ConsoleMessageCell.swift @@ -43,9 +43,9 @@ package struct ConsoleMessageCell: View { .foregroundColor(titleColor) Spacer() #if !os(watchOS) -#if canImport(RiftSupport) - PinView(message: message) -#endif + if message.isPinned { + BookmarkIconView() + } ConsoleTimestampView(date: message.createdAt) .padding(.trailing, 3) #endif diff --git a/Sources/PulseUI/Features/Console/Views/ConsoleTaskCell.swift b/Sources/PulseUI/Features/Console/Views/ConsoleTaskCell.swift index c448f84e2..5eea802a6 100644 --- a/Sources/PulseUI/Features/Console/Views/ConsoleTaskCell.swift +++ b/Sources/PulseUI/Features/Console/Views/ConsoleTaskCell.swift @@ -57,9 +57,9 @@ package struct ConsoleTaskCell: View { info.higlighted(highlightedArea == .header) Spacer() -#if canImport(RiftSupport) - PinView(task: task) -#endif + if task.isPinned { + BookmarkIconView() + } ConsoleTimestampView(date: task.createdAt) .padding(.trailing, 3) } diff --git a/Sources/PulseUI/Features/Filters/Views/ConsoleDomainsSelectionView.swift b/Sources/PulseUI/Features/Filters/Views/ConsoleDomainsSelectionView.swift index b96a30197..20e7bc92c 100644 --- a/Sources/PulseUI/Features/Filters/Views/ConsoleDomainsSelectionView.swift +++ b/Sources/PulseUI/Features/Filters/Views/ConsoleDomainsSelectionView.swift @@ -6,6 +6,8 @@ import SwiftUI import Pulse import Combine +#if !os(macOS) + @available(iOS 16, visionOS 1, *) struct ConsoleDomainsSelectionView: View { @ObservedObject var viewModel: ConsoleFiltersViewModel @@ -23,8 +25,8 @@ struct ConsoleDomainsSelectionView: View { } } -private extension ConsoleFiltersViewModel { - func bindingForHosts(index: LoggerStoreIndex) -> Binding> { +extension ConsoleFiltersViewModel { + package func bindingForHosts(index: LoggerStoreIndex) -> Binding> { Binding(get: { if let focused = self.criteria.network.host.focused { return [focused] @@ -43,3 +45,5 @@ private extension ConsoleFiltersViewModel { }) } } + +#endif diff --git a/Sources/PulseUI/Features/Filters/Views/ConsoleLabelsSelectionView.swift b/Sources/PulseUI/Features/Filters/Views/ConsoleLabelsSelectionView.swift index b340007a3..0b8c3b26b 100644 --- a/Sources/PulseUI/Features/Filters/Views/ConsoleLabelsSelectionView.swift +++ b/Sources/PulseUI/Features/Filters/Views/ConsoleLabelsSelectionView.swift @@ -6,6 +6,8 @@ import SwiftUI import Pulse import Combine +#if !os(macOS) + @available(iOS 16, visionOS 1, *) struct ConsoleLabelsSelectionView: View { @ObservedObject var viewModel: ConsoleFiltersViewModel @@ -23,8 +25,10 @@ struct ConsoleLabelsSelectionView: View { } } -private extension ConsoleFiltersViewModel { - func bindingForSelectedLabels(index: LoggerStoreIndex) -> Binding> { +#endif + +extension ConsoleFiltersViewModel { + package func bindingForSelectedLabels(index: LoggerStoreIndex) -> Binding> { Binding(get: { if let focused = self.criteria.messages.labels.focused { return [focused] diff --git a/Sources/PulseUI/Features/Filters/Views/ConsoleSearchListSelectionView.swift b/Sources/PulseUI/Features/Filters/Views/ConsoleSearchListSelectionView.swift index b3f985e8a..4ec33cba4 100644 --- a/Sources/PulseUI/Features/Filters/Views/ConsoleSearchListSelectionView.swift +++ b/Sources/PulseUI/Features/Filters/Views/ConsoleSearchListSelectionView.swift @@ -2,6 +2,8 @@ // // Copyright (c) 2020-2024 Alexander Grebenyuk (github.com/kean). +#if !os(macOS) + import SwiftUI import Pulse @@ -14,7 +16,7 @@ package struct ConsoleSearchListSelectionView String @ViewBuilder package let label: (Data.Element) -> Label -#if os(iOS) || os(macOS) || os(visionOS) +#if os(iOS) || os(visionOS) package var limit = 6 #else package var limit = 3 @@ -42,34 +44,6 @@ package struct ConsoleSearchListSelectionView limit { - HStack { - if !isExpanded { - Button(action: { isExpanded = true }) { - Text("Show All ") + Text("(\(items.count))").foregroundColor(.secondary) - } - } else { - Button("Show Less") { isExpanded = false } - } - } - } - } - } -#else @State private var isExpandedListPresented = false @State private var isSearching = false @@ -116,7 +90,6 @@ package struct ConsoleSearchListSelectionView>) -> AnyView = { +#if os(macOS) + AnyView(EmptyView()) // Has to be injected +#else AnyView(SessionPickerView(selection: $0)) +#endif } #endif diff --git a/Sources/PulseUI/Features/Search/Services/ConsoleSearchOccurrence.swift b/Sources/PulseUI/Features/Search/Services/ConsoleSearchOccurrence.swift index 222024dfd..6850e222f 100644 --- a/Sources/PulseUI/Features/Search/Services/ConsoleSearchOccurrence.swift +++ b/Sources/PulseUI/Features/Search/Services/ConsoleSearchOccurrence.swift @@ -9,6 +9,10 @@ import Pulse import CoreData import Combine +#if os(macOS) +import AppKit +#endif + @available(iOS 16, visionOS 1, *) package final class ConsoleSearchOccurrence: Identifiable, Equatable, Hashable { package let id = ConsoleSearchOccurrenceId() @@ -40,9 +44,33 @@ private let previewAttibutes = TextHelper().attributes(role: .body2, style: .mon @available(iOS 16, visionOS 1, *) extension ConsoleSearchOccurrence { - static func makePreview(for match: ConsoleSearchMatch, attributes customAttributes: [NSAttributedString.Key: Any] = [:]) -> AttributedString { + package static func makePreview(for match: ConsoleSearchMatch, attributes customAttributes: [NSAttributedString.Key: Any] = [:]) -> AttributedString { + let segments = makePreviewSegments(for: match) + + var attributes = AttributeContainer(customAttributes) +#if os(macOS) + attributes.foregroundColor = .secondary + attributes.font = .callout +#endif - let prefixStartIndex = match.line.index(match.range.lowerBound, offsetBy: -50, limitedBy: match.line.startIndex) ?? match.line.startIndex + var middle = AttributedString(segments[1], attributes: attributes) +#if os(macOS) + middle.foregroundColor = .primary + middle.font = Font.callout.weight(.semibold) +#else + middle.foregroundColor = .orange +#endif + return AttributedString(segments[0], attributes: attributes) + middle + AttributedString(segments[2], attributes: attributes) + } + + package static func makePreviewSegments(for match: ConsoleSearchMatch) -> [Substring] { + let prefixOffset: Int +#if os(macOS) + prefixOffset = -20 +#else + prefixOffset = -50 +#endif + let prefixStartIndex = match.line.index(match.range.lowerBound, offsetBy: prefixOffset, limitedBy: match.line.startIndex) ?? match.line.startIndex let prefixRange = prefixStartIndex.. package let term: ConsoleSearchTerm - package static let limit = 1000 + package static let limit = 6 package init(line: String, lineNumber: Int, range: Range, term: ConsoleSearchTerm) { self.line = line diff --git a/Sources/PulseUI/Features/Search/Views/ConsoleSearchResultsSectionView.swift b/Sources/PulseUI/Features/Search/Views/ConsoleSearchResultsSectionView.swift index 25cf74e21..8769201c1 100644 --- a/Sources/PulseUI/Features/Search/Views/ConsoleSearchResultsSectionView.swift +++ b/Sources/PulseUI/Features/Search/Views/ConsoleSearchResultsSectionView.swift @@ -114,6 +114,10 @@ struct ConsoleSearchResultDetailsView: View { } } +#endif + +#if os(iOS) || os(visionOS) || os(macOS) + @available(iOS 16, visionOS 1, *) package struct PlainListGroupSeparator: View { package init() {} @@ -127,10 +131,6 @@ package struct PlainListGroupSeparator: View { } } -#endif - -#if os(iOS) || os(visionOS) || os(macOS) - @available(iOS 16, macOS 13, visionOS 1, *) package struct PlainListSectionHeader: View { package var title: String? @@ -171,39 +171,4 @@ extension PlainListSectionHeader where Content == Text { } } -@available(iOS 16, visionOS 1, *) -package struct PlainListSeeAllView: View { - let count: Int - - package init(count: Int) { - self.count = count - } - - package var body: some View { - (Text("Show All").foregroundColor(.accentColor) + - Text(" (\(count))")) - .font(.subheadline) - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, alignment: .leading) - } -} - -@available(iOS 16, visionOS 1, *) -package struct PlainListSectionHeaderSeparator: View { - let title: String - - package init(title: String) { - self.title = title - } - - package var body: some View { - HStack(alignment: .bottom, spacing: 0) { - Text(title) - .foregroundColor(.secondary) - .font(.subheadline.weight(.medium)) - Spacer() - } - } -} - #endif diff --git a/Sources/PulseUI/Features/Sessions/SessionListView.swift b/Sources/PulseUI/Features/Sessions/SessionListView.swift index 8a88332df..e496b4629 100644 --- a/Sources/PulseUI/Features/Sessions/SessionListView.swift +++ b/Sources/PulseUI/Features/Sessions/SessionListView.swift @@ -8,7 +8,7 @@ import SwiftUI import CoreData import Combine -#if os(iOS) || os(macOS) || os(visionOS) +#if os(iOS) || os(visionOS) @available(iOS 16, macOS 13, visionOS 1, *) struct SessionListView: View { @@ -21,9 +21,7 @@ struct SessionListView: View { @State private var filterTerm = "" @State private var groupedSessions: [(Date, [LoggerSessionEntity])] = [] -#if os(iOS) || os(visionOS) @Environment(\.editMode) private var editMode -#endif @Environment(\.store) private var store var body: some View { @@ -42,30 +40,6 @@ struct SessionListView: View { @ViewBuilder private var content: some View { -#if os(macOS) - VStack(spacing: 0) { - list - - Divider() - SearchBar(title: "Filter", imageName: "line.3.horizontal.decrease.circle", text: $filterTerm) - .help("Show sessions with matching name") - .padding(8) - } -#else - list -#endif - } - - private func refreshGroups() { - let calendar = Calendar.current - let groups = Dictionary(grouping: sessions) { - let components = calendar.dateComponents([.day, .year, .month], from: $0.createdAt) - return calendar.date(from: components) ?? $0.createdAt - } - self.groupedSessions = Array(groups.sorted(by: { $0.key > $1.key })) - } - - private var list: some View { List(selection: $selection) { if !filterTerm.isEmpty { ForEach(getFilteredSessions(), id: \.id, content: makeCell) @@ -77,26 +51,26 @@ struct SessionListView: View { } } } -#if os(iOS) || os(visionOS) .listStyle(.plain) .searchable(text: $filterTerm) -#else - .listStyle(.sidebar) -#endif + } + + private func refreshGroups() { + let calendar = Calendar.current + let groups = Dictionary(grouping: sessions) { + let components = calendar.dateComponents([.day, .year, .month], from: $0.createdAt) + return calendar.date(from: components) ?? $0.createdAt + } + self.groupedSessions = Array(groups.sorted(by: { $0.key > $1.key })) } private func makeHeader(for startDate: Date, sessions: [LoggerSessionEntity]) -> some View { HStack { -#if os(macOS) - PlainListSectionHeaderSeparator(title: sectionTitleFormatter.string(from: startDate) + " (\(sessions.count))") -#else (Text(sectionTitleFormatter.string(from: startDate)) + Text(" (\(sessions.count))").foregroundColor(.secondary.opacity(0.5))) .font(.headline) .padding(.vertical, 6) -#endif -#if os(iOS) || os(visionOS) if editMode?.wrappedValue.isEditing ?? false { Spacer() @@ -110,7 +84,6 @@ struct SessionListView: View { } }.font(.subheadline) } -#endif } } @@ -141,15 +114,6 @@ struct SessionListView: View { } } -package struct SelectedSessionsIDs: Hashable, Identifiable { - package var id: SelectedSessionsIDs { self } - package let ids: Set - - package init(ids: Set) { - self.ids = ids - } -} - private let sectionTitleFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .long @@ -160,6 +124,15 @@ private let sectionTitleFormatter: DateFormatter = { #endif +package struct SelectedSessionsIDs: Hashable, Identifiable { + package var id: SelectedSessionsIDs { self } + package let ids: Set + + package init(ids: Set) { + self.ids = ids + } +} + @available(macOS 13, *) package struct ConsoleSessionCell: View { let session: LoggerSessionEntity diff --git a/Sources/PulseUI/Features/Sessions/SessionPickerView.swift b/Sources/PulseUI/Features/Sessions/SessionPickerView.swift index 4453f4d2d..6bb252c6d 100644 --- a/Sources/PulseUI/Features/Sessions/SessionPickerView.swift +++ b/Sources/PulseUI/Features/Sessions/SessionPickerView.swift @@ -8,7 +8,7 @@ import SwiftUI import CoreData import Combine -#if os(iOS) || os(macOS) || os(visionOS) +#if os(iOS) || os(visionOS) @available(iOS 16, macOS 13, visionOS 1, *) struct SessionPickerView: View { @@ -16,10 +16,8 @@ struct SessionPickerView: View { var body: some View { SessionListView(selection: $selection, sharedSessions: .constant(nil)) -#if os(iOS) || os(visionOS) .environment(\.editMode, .constant(.active)) .inlineNavigationTitle("Sessions") -#endif } } diff --git a/Sources/PulseUI/Features/Settings/SettingsView-macos.swift b/Sources/PulseUI/Features/Settings/SettingsView-macos.swift index e5eed0d24..1a9b556c7 100644 --- a/Sources/PulseUI/Features/Settings/SettingsView-macos.swift +++ b/Sources/PulseUI/Features/Settings/SettingsView-macos.swift @@ -25,13 +25,11 @@ struct SettingsView: View { .foregroundColor(.secondary) } } - Section { + Section("Store") { // TODO: load this info async -// if #available(macOS 13, *), let info = try? store.info() { -// LoggerStoreSizeChart(info: info, sizeLimit: store.configuration.sizeLimit) -// } - } header: { - PlainListSectionHeaderSeparator(title: "Store") + // if #available(macOS 13, *), let info = try? store.info() { + // LoggerStoreSizeChart(info: info, sizeLimit: store.configuration.sizeLimit) + // } } Section { diff --git a/Sources/PulseUI/Helpers/StringSearchOptions.swift b/Sources/PulseUI/Helpers/StringSearchOptions.swift index 7511e709a..22b51c9ed 100644 --- a/Sources/PulseUI/Helpers/StringSearchOptions.swift +++ b/Sources/PulseUI/Helpers/StringSearchOptions.swift @@ -84,12 +84,13 @@ extension String { } extension String { - package func ranges(of target: String, options: StringSearchOptions) -> [Range] { + package func ranges(of target: String, options: StringSearchOptions, limit: Int = Int.max) -> [Range] { var startIndex = target.startIndex var ranges = [Range]() let target = options.kind == .wildcard ? makeRegexForWildcard(target, rule: options.rule) : target let options = String.CompareOptions(options) - while startIndex < endIndex, + while ranges.count < limit, + startIndex < endIndex, let range = range(of: target, options: options, range: startIndex..) { - self.title = title - self.imageName = imageName - self._text = text - } - - package var body: some View { - HStack(spacing: 6) { - Image(systemName: imageName) - .foregroundColor(.secondary) - .padding(.leading, 6) - TextField(title, text: $text) - .textFieldStyle(.plain) - .frame(height: 22) - if !text.isEmpty { - Button(action: { text = "" }) { - Image(systemName: "xmark.circle.fill") - .font(.system(size: 11)) - .foregroundColor(.secondary) - .frame(width: 20, height: 20) - } - .buttonStyle(.plain) - } - } - .cornerRadius(4) - .overlay( - RoundedRectangle(cornerRadius: 4) - .stroke(.separator, lineWidth: 1) - ) - } -} - -#if DEBUG -struct Previews_SearchBar_macos_Previews: PreviewProvider { - static var previews: some View { - SearchBarDemo().padding() - } -} - -private struct SearchBarDemo: View { - @State var value = "" - - var body: some View { - SearchBar(title: "Search", text: $value) - } -} -#endif - -#endif