mirror of
https://github.com/kean/Pulse.git
synced 2026-05-30 21:07:33 +00:00
209 lines
7.7 KiB
Swift
209 lines
7.7 KiB
Swift
// The MIT License (MIT)
|
||
//
|
||
// Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
|
||
|
||
import SwiftUI
|
||
import CoreData
|
||
import Pulse
|
||
import Combine
|
||
|
||
#if os(macOS)
|
||
|
||
public struct MainView: View {
|
||
@StateObject private var viewModel: MainViewModel
|
||
@State private var isShowingSettings = false
|
||
@State private var isShowingShareSheet = false
|
||
@State private var shareItems: ShareItems?
|
||
|
||
public init(store: LoggerStore = .shared) {
|
||
_viewModel = StateObject(wrappedValue: MainViewModel(store: store))
|
||
}
|
||
|
||
public var body: some View {
|
||
ConsoleContainerViewPro(viewModel: viewModel, details: viewModel.details, toolbar: viewModel.toolbar)
|
||
.navigationTitle("Console")
|
||
.toolbar {
|
||
ToolbarItemGroup(placement: .navigation) {
|
||
Button(action: { isShowingSettings = true }) {
|
||
Image(systemName: "gearshape")
|
||
}
|
||
Button(action: { isShowingShareSheet = true }) {
|
||
Image(systemName: "square.and.arrow.up")
|
||
}
|
||
.popover(isPresented: $isShowingShareSheet, arrowEdge: .top) {
|
||
ShareStoreView(store: viewModel.store, isPresented: $isShowingShareSheet) { item in
|
||
isShowingShareSheet = false
|
||
DispatchQueue.main.async {
|
||
shareItems = item
|
||
}
|
||
}
|
||
}
|
||
.popover(item: $shareItems) { item in
|
||
ShareView(item)
|
||
.fixedSize()
|
||
}
|
||
}
|
||
ToolbarItemGroup(placement: .automatic) {
|
||
Button(action: {
|
||
// TODO: Refactor
|
||
viewModel.toolbar.isSearchBarActive = true
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
|
||
viewModel.searchBar.onFind.send()
|
||
}
|
||
}) {
|
||
Image(systemName: "magnifyingglass")
|
||
}.keyboardShortcut("f")
|
||
ConsoleToolbarToggleOnlyErrorsButton(viewModel: viewModel.toolbar)
|
||
.keyboardShortcut("e", modifiers: [.command, .shift])
|
||
ConsoleToolbarModePickerButton(viewModel: viewModel.mode)
|
||
.keyboardShortcut("n", modifiers: [.command, .shift])
|
||
FilterPopoverToolbarButton(viewModel: viewModel, mode: viewModel.mode)
|
||
.keyboardShortcut("f", modifiers: [.command, .option])
|
||
ConsoleToolbarToggleVerticalView(viewModel: viewModel.toolbar)
|
||
}
|
||
}
|
||
.sheet(isPresented: $isShowingSettings) {
|
||
SettingsView(viewModel: .init(store: viewModel.store))
|
||
}
|
||
.onDisappear { viewModel.freeMemory() }
|
||
}
|
||
}
|
||
|
||
private struct ConsoleContainerViewPro: View {
|
||
var viewModel: MainViewModel
|
||
@ObservedObject var details: ConsoleDetailsRouterViewModel
|
||
@ObservedObject var toolbar: ConsoleToolbarViewModel
|
||
|
||
var body: some View {
|
||
NotSplitView(
|
||
MainPanelView(viewModel: viewModel, mode: viewModel.mode)
|
||
.frame(minWidth: 320, idealWidth: 320, maxWidth: .infinity, minHeight: 120, idealHeight: 480, maxHeight: .infinity, alignment: .center),
|
||
ConsoleMessageDetailsRouter(viewModel: details)
|
||
.frame(minWidth: 430, idealWidth: 500, maxWidth: .infinity, minHeight: 320, idealHeight: 480, maxHeight: .infinity, alignment: .center),
|
||
isPanelTwoCollaped: viewModel.details.viewModel == nil,
|
||
isVertical: toolbar.isVertical
|
||
)
|
||
}
|
||
}
|
||
|
||
private struct MainPanelView: View {
|
||
var viewModel: MainViewModel
|
||
@ObservedObject var mode: ConsoleModePickerViewModel
|
||
|
||
var body: some View {
|
||
VStack(spacing: 0) {
|
||
content
|
||
ConsoleToolbarSearchBar(viewModel: viewModel)
|
||
}
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var content: some View {
|
||
if mode.isNetworkOnly {
|
||
ConsoleTableView(viewModel: viewModel.network.table, onSelected: {
|
||
viewModel.details.select($0)
|
||
})
|
||
.onAppear(perform: viewModel.network.onAppear)
|
||
.onDisappear(perform: viewModel.network.onDisappear)
|
||
.background(NavigationTitleUpdater(title: "Requests", viewModel: viewModel.network.table))
|
||
} else {
|
||
ConsoleTableView(viewModel: viewModel.console.table, onSelected: {
|
||
viewModel.details.select($0)
|
||
})
|
||
.onAppear(perform: viewModel.console.onAppear)
|
||
.onDisappear(perform: viewModel.console.onDisappear)
|
||
.background(NavigationTitleUpdater(title: "Messages", viewModel: viewModel.console.table))
|
||
}
|
||
}
|
||
}
|
||
|
||
private struct ConsoleToolbarSearchBar: View {
|
||
let viewModel: MainViewModel
|
||
@ObservedObject var toolbar: ConsoleToolbarViewModel
|
||
@ObservedObject var searchBar: ConsoleSearchBarViewModel
|
||
|
||
init(viewModel: MainViewModel) {
|
||
self.viewModel = viewModel
|
||
self.toolbar = viewModel.toolbar
|
||
self.searchBar = viewModel.searchBar
|
||
}
|
||
|
||
var body: some View {
|
||
if toolbar.isSearchBarActive {
|
||
VStack(spacing: 0) {
|
||
Divider()
|
||
HStack {
|
||
SearchBar(title: "Filter", text: $searchBar.text, onFind: searchBar.onFind, onEditingChanged: { isEditing in
|
||
toolbar.isSearchBarActive = isEditing
|
||
}, onReturn: { })
|
||
.frame(maxWidth: toolbar.isSearchBarActive ? 320 : 200)
|
||
Spacer()
|
||
}.padding(6)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private struct FilterPopoverToolbarButton: View {
|
||
let viewModel: MainViewModel
|
||
@ObservedObject var mode: ConsoleModePickerViewModel
|
||
@State private var isFilterPresented = false
|
||
|
||
var body: some View {
|
||
Button(action: { isFilterPresented.toggle() }, label: {
|
||
Image(systemName: isFilterPresented ? "line.horizontal.3.decrease.circle.fill" : "line.horizontal.3.decrease.circle")
|
||
.foregroundColor(isFilterPresented ? .accentColor : .secondary)
|
||
})
|
||
.help("Toggle Filters Panel (⌥⌘F)")
|
||
.popover(isPresented: $isFilterPresented, arrowEdge: .top) {
|
||
filters.frame(width: Filters.preferredWidth).padding(.bottom, 16)
|
||
}
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var filters: some View {
|
||
if mode.isNetworkOnly {
|
||
NetworkFiltersView(viewModel: viewModel.network.searchCriteria)
|
||
} else {
|
||
ConsoleFiltersView(viewModel: viewModel.console.searchCriteria)
|
||
}
|
||
}
|
||
}
|
||
|
||
private struct ConsoleToolbarModePickerButton: View {
|
||
@ObservedObject var viewModel: ConsoleModePickerViewModel
|
||
|
||
var body: some View {
|
||
Button(action: { viewModel.isNetworkOnly.toggle() }) {
|
||
Image(systemName: viewModel.isNetworkOnly ? "network" : "network")
|
||
.foregroundColor(viewModel.isNetworkOnly ? Color.accentColor : Color.secondary)
|
||
}.help("Automatically Scroll to Recent Messages (⇧⌘N)")
|
||
}
|
||
}
|
||
|
||
private struct NavigationTitleUpdater: View {
|
||
let title: String
|
||
@ObservedObject var viewModel: ConsoleTableViewModel
|
||
|
||
var body: some View {
|
||
EmptyView().navigationSubtitle(message)
|
||
}
|
||
|
||
private var message: String {
|
||
let count = viewModel.entities.count
|
||
let title = count % 10 == 1 ? String(title.dropLast()) : title
|
||
return "\(count) \(title)"
|
||
}
|
||
}
|
||
|
||
#if DEBUG
|
||
struct MainView_Previews: PreviewProvider {
|
||
static var previews: some View {
|
||
NavigationView {
|
||
MainView(store: .mock)
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
#endif
|