mirror of
https://github.com/yJason/ClashX-Dashboard.git
synced 2026-02-10 04:42:27 +08:00
172 lines
4.0 KiB
Swift
172 lines
4.0 KiB
Swift
//
|
|
// LogsTableView.swift
|
|
//
|
|
//
|
|
//
|
|
|
|
import Cocoa
|
|
import SwiftUI
|
|
|
|
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("LogsTableView." + $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,
|
|
var data = data as? [ClashLogStorage.ClashLog] else {
|
|
return
|
|
}
|
|
data = updateSorts(data, tableView: tableView)
|
|
context.coordinator.updateLogs(data, for: tableView)
|
|
}
|
|
|
|
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 updateLogs(_ logs: [ClashLogStorage.ClashLog], for tableView: NSTableView) {
|
|
|
|
let changes = logs.difference(from: self.logs) {
|
|
$0.id == $1.id
|
|
}
|
|
|
|
guard let partialChanges = self.logs.applying(changes) else { return }
|
|
|
|
self.logs = partialChanges
|
|
|
|
let indicesToReload = IndexSet(zip(partialChanges, logs).enumerated().compactMap { index, pair -> Int? in
|
|
(pair.0.id == pair.1.id && pair.0 != pair.1) ? index : nil
|
|
})
|
|
|
|
tableView.reloadData(changes, indexs: indicesToReload)
|
|
}
|
|
|
|
|
|
func numberOfRows(in tableView: NSTableView) -> Int {
|
|
logs.count
|
|
}
|
|
|
|
|
|
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
|
|
|
guard let identifier = tableColumn?.identifier,
|
|
let cellView = tableView.makeCellView(with: identifier.rawValue, owner: self),
|
|
let s = identifier.rawValue.split(separator: ".").last,
|
|
let tc = TableColumn(rawValue: String(s)),
|
|
row >= 0,
|
|
row < logs.count,
|
|
let tf = cellView.textField
|
|
else { return nil }
|
|
|
|
let log = logs[row]
|
|
|
|
tf.isEditable = false
|
|
tf.isSelectable = false
|
|
|
|
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
|
|
tf.isSelectable = true
|
|
}
|
|
|
|
return cellView
|
|
}
|
|
}
|
|
}
|