mirror of
https://github.com/yJason/ClashX-Dashboard.git
synced 2026-02-04 10:02:26 +08:00
refactor: log tableview
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CocoaLumberjackSwift
|
import CocoaLumberjackSwift
|
||||||
|
import DifferenceKit
|
||||||
|
|
||||||
class ClashApiDatasStorage: NSObject, ObservableObject {
|
class ClashApiDatasStorage: NSObject, ObservableObject {
|
||||||
|
|
||||||
@@ -125,14 +126,17 @@ class ClashOverviewData: ObservableObject, Identifiable {
|
|||||||
class ClashLogStorage: ObservableObject {
|
class ClashLogStorage: ObservableObject {
|
||||||
@Published var logs = [ClashLog]()
|
@Published var logs = [ClashLog]()
|
||||||
|
|
||||||
class ClashLog: NSObject, ObservableObject, Identifiable {
|
class ClashLog: NSObject, ObservableObject, Identifiable, Differentiable {
|
||||||
let id: String
|
let id: String
|
||||||
|
var differenceIdentifier: String {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
let date: Date
|
let date: Date
|
||||||
let level: ClashLogLevel
|
let level: ClashLogLevel
|
||||||
@objc let log: String
|
@objc let log: String
|
||||||
|
|
||||||
let levelColor: Color
|
let levelColor: NSColor
|
||||||
@objc let levelString: String
|
@objc let levelString: String
|
||||||
|
|
||||||
init(level: String, log: String) {
|
init(level: String, log: String) {
|
||||||
@@ -144,13 +148,13 @@ class ClashLogStorage: ObservableObject {
|
|||||||
self.levelString = level
|
self.levelString = level
|
||||||
switch self.level {
|
switch self.level {
|
||||||
case .info:
|
case .info:
|
||||||
levelColor = .blue
|
levelColor = .systemBlue
|
||||||
case .warning:
|
case .warning:
|
||||||
levelColor = .yellow
|
levelColor = .systemYellow
|
||||||
case .error:
|
case .error:
|
||||||
levelColor = .red
|
levelColor = .systemRed
|
||||||
case .debug:
|
case .debug:
|
||||||
levelColor = .green
|
levelColor = .systemGreen
|
||||||
default:
|
default:
|
||||||
levelColor = .white
|
levelColor = .white
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
//
|
||||||
|
// LogsTableView.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import SwiftUI
|
||||||
|
import DifferenceKit
|
||||||
|
|
||||||
|
struct LogsTableView<Item: Hashable>: NSViewRepresentable {
|
||||||
|
|
||||||
|
enum TableColumn: String, CaseIterable {
|
||||||
|
case date = "Date"
|
||||||
|
case level = "Level"
|
||||||
|
case log = "Log"
|
||||||
|
}
|
||||||
|
|
||||||
|
var data: [Item]
|
||||||
|
var filterString: String
|
||||||
|
|
||||||
|
class NonRespondingScrollView: NSScrollView {
|
||||||
|
override var acceptsFirstResponder: Bool { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonRespondingTableView: NSTableView {
|
||||||
|
override var acceptsFirstResponder: Bool { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeNSView(context: Context) -> NSScrollView {
|
||||||
|
|
||||||
|
let scrollView = NonRespondingScrollView()
|
||||||
|
scrollView.hasVerticalScroller = true
|
||||||
|
scrollView.hasHorizontalScroller = false
|
||||||
|
scrollView.autohidesScrollers = true
|
||||||
|
|
||||||
|
let tableView = NonRespondingTableView()
|
||||||
|
tableView.usesAlternatingRowBackgroundColors = true
|
||||||
|
|
||||||
|
tableView.delegate = context.coordinator
|
||||||
|
tableView.dataSource = context.coordinator
|
||||||
|
|
||||||
|
TableColumn.allCases.forEach {
|
||||||
|
let tableColumn = NSTableColumn(identifier: .init($0.rawValue))
|
||||||
|
tableColumn.title = $0.rawValue
|
||||||
|
tableColumn.isEditable = false
|
||||||
|
|
||||||
|
switch $0 {
|
||||||
|
case .date:
|
||||||
|
tableColumn.minWidth = 60
|
||||||
|
tableColumn.maxWidth = 140
|
||||||
|
tableColumn.width = 135
|
||||||
|
case .level:
|
||||||
|
tableColumn.minWidth = 40
|
||||||
|
tableColumn.maxWidth = 65
|
||||||
|
default:
|
||||||
|
tableColumn.minWidth = 120
|
||||||
|
tableColumn.maxWidth = .infinity
|
||||||
|
}
|
||||||
|
|
||||||
|
tableView.addTableColumn(tableColumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollView.documentView = tableView
|
||||||
|
|
||||||
|
return scrollView
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSView(_ nsView: NSScrollView, context: Context) {
|
||||||
|
context.coordinator.parent = self
|
||||||
|
guard let tableView = nsView.documentView as? NSTableView,
|
||||||
|
let data = data as? [ClashLogStorage.ClashLog] else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = updateSorts(data, tableView: tableView)
|
||||||
|
|
||||||
|
let source = context.coordinator.logs
|
||||||
|
let changeset = StagedChangeset(source: source, target: target)
|
||||||
|
|
||||||
|
|
||||||
|
tableView.reload(using: changeset) { data in
|
||||||
|
context.coordinator.logs = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSorts(_ objects: [ClashLogStorage.ClashLog],
|
||||||
|
tableView: NSTableView) -> [ClashLogStorage.ClashLog] {
|
||||||
|
var re = objects
|
||||||
|
|
||||||
|
let filterKeys = [
|
||||||
|
"levelString",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
|
re = re.filtered(filterString, for: filterKeys)
|
||||||
|
|
||||||
|
return re
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(parent: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
|
||||||
|
|
||||||
|
var parent: LogsTableView
|
||||||
|
var logs = [ClashLogStorage.ClashLog]()
|
||||||
|
|
||||||
|
let dateFormatter = {
|
||||||
|
let df = DateFormatter()
|
||||||
|
df.dateFormat = "MM/dd HH:mm:ss.SSS"
|
||||||
|
return df
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
init(parent: LogsTableView) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||||
|
logs.count
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||||
|
|
||||||
|
guard let cellView = tableView.createCellView(with: "LogsTableCellView"),
|
||||||
|
let s = tableColumn?.identifier.rawValue.split(separator: ".").last,
|
||||||
|
let tc = TableColumn(rawValue: String(s))
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
let log = logs[row]
|
||||||
|
let tf = cellView.textField
|
||||||
|
|
||||||
|
switch tc {
|
||||||
|
case .date:
|
||||||
|
tf?.lineBreakMode = .byTruncatingHead
|
||||||
|
tf?.textColor = .orange
|
||||||
|
tf?.stringValue = dateFormatter.string(from: log.date)
|
||||||
|
case .level:
|
||||||
|
tf?.lineBreakMode = .byTruncatingTail
|
||||||
|
tf?.textColor = log.levelColor
|
||||||
|
tf?.stringValue = log.levelString
|
||||||
|
case .log:
|
||||||
|
tf?.lineBreakMode = .byTruncatingTail
|
||||||
|
tf?.textColor = .labelColor
|
||||||
|
tf?.stringValue = log.log
|
||||||
|
}
|
||||||
|
|
||||||
|
return cellView
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,37 +13,9 @@ struct LogsView: View {
|
|||||||
@State var searchString: String = ""
|
@State var searchString: String = ""
|
||||||
@State var logLevel = ConfigManager.selectLoggingApiLevel
|
@State var logLevel = ConfigManager.selectLoggingApiLevel
|
||||||
|
|
||||||
var logs: [ClashLogStorage.ClashLog] {
|
|
||||||
let logs: [ClashLogStorage.ClashLog] = logStorage.logs.reversed()
|
|
||||||
if searchString.isEmpty {
|
|
||||||
return logs
|
|
||||||
} else {
|
|
||||||
return logs.filtered(searchString, for: ["log", "levelString"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Table(logs) {
|
Group {
|
||||||
TableColumn("Date") {
|
LogsTableView(data: logStorage.logs.reversed(), filterString: searchString)
|
||||||
Text($0.date.formatted(
|
|
||||||
Date.FormatStyle()
|
|
||||||
.year(.twoDigits)
|
|
||||||
.month(.twoDigits)
|
|
||||||
.day(.twoDigits)
|
|
||||||
.hour(.twoDigits(amPM: .omitted))
|
|
||||||
.minute(.twoDigits)
|
|
||||||
.second(.twoDigits)
|
|
||||||
))
|
|
||||||
.foregroundColor(.orange)
|
|
||||||
.truncationMode(.head)
|
|
||||||
}
|
|
||||||
.width(min: 60, max: 130)
|
|
||||||
TableColumn("Level") {
|
|
||||||
Text("[\($0.level.rawValue)]")
|
|
||||||
.foregroundColor($0.levelColor)
|
|
||||||
}
|
|
||||||
.width(min: 40, max: 65)
|
|
||||||
TableColumn("", value: \.log)
|
|
||||||
}
|
}
|
||||||
.searchable(text: $searchString)
|
.searchable(text: $searchString)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) {
|
.onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user