Files
Pulse/Sources/PulseUI/Features/Main/MainView-macos.swift
2022-08-19 20:47:29 -04:00

209 lines
7.7 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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