mirror of
https://github.com/kean/Pulse.git
synced 2026-05-30 21:07:33 +00:00
200 lines
6.0 KiB
Swift
200 lines
6.0 KiB
Swift
// The MIT License (MIT)
|
|
//
|
|
// Copyright (c) 2020-2024 Alexander Grebenyuk (github.com/kean).
|
|
|
|
#if os(iOS) || os(macOS) || os(visionOS)
|
|
|
|
import SwiftUI
|
|
import Pulse
|
|
import CoreData
|
|
import Combine
|
|
|
|
@available(iOS 15, visionOS 1.0, macOS 13, *)
|
|
struct ConsoleSearchResultView: View {
|
|
let viewModel: ConsoleSearchResultViewModel
|
|
var limit: Int = 4
|
|
var isSeparatorNeeded = false
|
|
|
|
@EnvironmentObject private var environment: ConsoleEnvironment
|
|
|
|
var body: some View {
|
|
ConsoleEntityCell(entity: viewModel.entity)
|
|
let occurrences = Array(viewModel.occurrences).filter { $0.scope.isDisplayedInResults }
|
|
ForEach(occurrences.prefix(limit)) { item in
|
|
NavigationLink(destination: ConsoleSearchResultView.makeDestination(for: item, entity: viewModel.entity).injecting(environment)) {
|
|
makeCell(for: item)
|
|
}
|
|
}
|
|
if occurrences.count > limit {
|
|
let total = occurrences.count > ConsoleSearchMatch.limit ? "\(ConsoleSearchMatch.limit)+" : "\(occurrences.count)"
|
|
#if os(macOS)
|
|
Text("Total Results: \(total)")
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.padding(.leading, 16)
|
|
.padding(.bottom, 8)
|
|
#else
|
|
NavigationLink(destination: ConsoleSearchResultDetailsView(viewModel: viewModel).injecting(environment)) {
|
|
Text("Total Results: ")
|
|
.font(ConsoleConstants.fontBody) +
|
|
Text(total)
|
|
.font(ConsoleConstants.fontBody)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
#endif
|
|
}
|
|
#if os(iOS) || os(visionOS)
|
|
if isSeparatorNeeded {
|
|
PlainListGroupSeparator()
|
|
}
|
|
#endif
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func makeCell(for occurrence: ConsoleSearchOccurrence) -> some View {
|
|
let contents = VStack(alignment: .leading, spacing: 4) {
|
|
Text(occurrence.preview)
|
|
.lineLimit(3)
|
|
Text(occurrence.scope.title + " (\(occurrence.line):\(occurrence.range.lowerBound + 1))")
|
|
.font(ConsoleConstants.fontInfo)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
#if os(macOS)
|
|
.listRowSeparator(.visible)
|
|
.padding(.leading, 16)
|
|
#endif
|
|
if #unavailable(iOS 16) {
|
|
contents.padding(.vertical, 4)
|
|
} else {
|
|
#if os(macOS)
|
|
contents.padding(.vertical, 4)
|
|
#else
|
|
contents
|
|
#endif
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
static func makeDestination(for occurrence: ConsoleSearchOccurrence, entity: NSManagedObject) -> some View {
|
|
_makeDestination(for: occurrence, entity: entity)
|
|
.environment(\.textViewSearchContext, occurrence.searchContext)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private static func _makeDestination(for occurrence: ConsoleSearchOccurrence, entity: NSManagedObject) -> some View {
|
|
switch LoggerEntity(entity) {
|
|
case .message(let message):
|
|
ConsoleMessageDetailsView(message: message)
|
|
case .task(let task):
|
|
_makeDestination(for: occurrence, task: task)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private static func _makeDestination(for occurrence: ConsoleSearchOccurrence, task: NetworkTaskEntity) -> some View {
|
|
switch occurrence.scope {
|
|
case .originalRequestHeaders, .currentRequestHeaders, .responseHeaders:
|
|
EmptyView() // Reserved
|
|
case .requestBody:
|
|
NetworkInspectorRequestBodyView(viewModel: .init(task: task))
|
|
case .responseBody:
|
|
NetworkInspectorResponseBodyView(viewModel: .init(task: task))
|
|
case .url, .message, .metadata:
|
|
EmptyView()
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(iOS 15, visionOS 1.0, macOS 13, *)
|
|
struct ConsoleSearchResultDetailsView: View {
|
|
let viewModel: ConsoleSearchResultViewModel
|
|
|
|
var body: some View {
|
|
List {
|
|
ConsoleSearchResultView(viewModel: viewModel, limit: Int.max)
|
|
}
|
|
.listStyle(.plain)
|
|
.environment(\.defaultMinListRowHeight, 0)
|
|
.inlineNavigationTitle("Search Results")
|
|
}
|
|
}
|
|
|
|
#if os(iOS) || os(visionOS)
|
|
@available(iOS 15, visionOS 1.0, *)
|
|
struct PlainListGroupSeparator: View {
|
|
var body: some View {
|
|
Rectangle().foregroundColor(.clear) // DIY separator
|
|
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
|
.listRowBackground(Color.separator.opacity(0.2))
|
|
.listRowSeparator(.hidden)
|
|
.frame(height: 12)
|
|
}
|
|
}
|
|
#endif
|
|
|
|
@available(iOS 15, visionOS 1.0, *)
|
|
struct PlainListSectionHeader<Content: View>: View {
|
|
var title: String?
|
|
@ViewBuilder let content: () -> Content
|
|
|
|
var body: some View {
|
|
contents
|
|
.padding(.top, 8)
|
|
.listRowBackground(Color.separator.opacity(0.2))
|
|
#if os(iOS) || os(visionOS)
|
|
.listRowSeparator(.hidden)
|
|
#endif
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var contents: some View {
|
|
if let title = title {
|
|
HStack(alignment: .bottom, spacing: 0) {
|
|
Text(title)
|
|
.foregroundColor(.secondary)
|
|
.lineLimit(3)
|
|
.font(.subheadline.weight(.medium))
|
|
Spacer()
|
|
}
|
|
} else {
|
|
content()
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(iOS 15, visionOS 1.0, *)
|
|
extension PlainListSectionHeader where Content == Text {
|
|
init(title: String) {
|
|
self.init(title: title, content: { Text(title) })
|
|
}
|
|
}
|
|
|
|
@available(iOS 15, visionOS 1.0, *)
|
|
struct PlainListSeeAllView: View {
|
|
let count: Int
|
|
|
|
var body: some View {
|
|
(Text("Show All").foregroundColor(.accentColor) +
|
|
Text(" (\(count))"))
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
}
|
|
|
|
@available(iOS 15, visionOS 1.0, *)
|
|
struct PlainListSectionHeaderSeparator: View {
|
|
let title: String
|
|
|
|
var body: some View {
|
|
HStack(alignment: .bottom, spacing: 0) {
|
|
Text(title)
|
|
.foregroundColor(.secondary)
|
|
.font(.subheadline.weight(.medium))
|
|
Spacer()
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|