From 42b6c2da0e614d9d4d79728f7e5ff23200bdd94e Mon Sep 17 00:00:00 2001 From: mrFq1 <1xxbx0il0@mozmail.com> Date: Sun, 4 Jun 2023 14:53:01 +0800 Subject: [PATCH] refactor: log tableview --- .../Views/ClashApiDatasStorage.swift | 16 +- .../ContentTabs/Logs/LogsTableView.swift | 158 ++++++++++++++++++ .../Views/ContentTabs/Logs/LogsView.swift | 32 +--- 3 files changed, 170 insertions(+), 36 deletions(-) create mode 100644 ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsTableView.swift diff --git a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ClashApiDatasStorage.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ClashApiDatasStorage.swift index 6577606..addab5a 100644 --- a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ClashApiDatasStorage.swift +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ClashApiDatasStorage.swift @@ -7,6 +7,7 @@ import Cocoa import SwiftUI import CocoaLumberjackSwift +import DifferenceKit class ClashApiDatasStorage: NSObject, ObservableObject { @@ -125,14 +126,17 @@ class ClashOverviewData: ObservableObject, Identifiable { class ClashLogStorage: ObservableObject { @Published var logs = [ClashLog]() - class ClashLog: NSObject, ObservableObject, Identifiable { + class ClashLog: NSObject, ObservableObject, Identifiable, Differentiable { let id: String + var differenceIdentifier: String { + return id + } let date: Date let level: ClashLogLevel @objc let log: String - let levelColor: Color + let levelColor: NSColor @objc let levelString: String init(level: String, log: String) { @@ -144,13 +148,13 @@ class ClashLogStorage: ObservableObject { self.levelString = level switch self.level { case .info: - levelColor = .blue + levelColor = .systemBlue case .warning: - levelColor = .yellow + levelColor = .systemYellow case .error: - levelColor = .red + levelColor = .systemRed case .debug: - levelColor = .green + levelColor = .systemGreen default: levelColor = .white } diff --git a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsTableView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsTableView.swift new file mode 100644 index 0000000..b7ea20c --- /dev/null +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsTableView.swift @@ -0,0 +1,158 @@ +// +// LogsTableView.swift +// +// +// + +import Cocoa +import SwiftUI +import DifferenceKit + +struct LogsTableView: 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 + } + + } +} diff --git a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsView.swift index 775c16b..5928e32 100644 --- a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsView.swift +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsView.swift @@ -13,37 +13,9 @@ struct LogsView: View { @State var searchString: String = "" @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 { - Table(logs) { - TableColumn("Date") { - 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) + Group { + LogsTableView(data: logStorage.logs.reversed(), filterString: searchString) } .searchable(text: $searchString) .onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) {