mirror of
https://github.com/yJason/ClashX-Dashboard.git
synced 2026-03-01 00:35:19 +08:00
misc: swift difference
This commit is contained in:
+35
-113
@@ -5,7 +5,6 @@
|
||||
//
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
import DifferenceKit
|
||||
|
||||
struct ConnectionsTableView<Item: Hashable>: NSViewRepresentable {
|
||||
|
||||
@@ -62,7 +61,7 @@ struct ConnectionsTableView<Item: Hashable>: NSViewRepresentable {
|
||||
|
||||
|
||||
TableColumn.allCases.forEach {
|
||||
let tableColumn = NSTableColumn(identifier: .init($0.rawValue))
|
||||
let tableColumn = NSTableColumn(identifier: .init("ConnectionsTableView." + $0.rawValue))
|
||||
tableColumn.title = $0.rawValue
|
||||
tableColumn.isEditable = false
|
||||
|
||||
@@ -139,22 +138,24 @@ struct ConnectionsTableView<Item: Hashable>: NSViewRepresentable {
|
||||
return
|
||||
}
|
||||
|
||||
let target = updateSorts(data.map(DBConnectionObject.init), tableView: tableView)
|
||||
var conns = data.map(DBConnectionObject.init)
|
||||
|
||||
let source = context.coordinator.conns
|
||||
let changeset = StagedChangeset(source: source, target: target)
|
||||
|
||||
|
||||
tableView.reload(using: changeset) { data in
|
||||
context.coordinator.conns = data
|
||||
}
|
||||
|
||||
conns = updateSorts(conns, tableView: tableView)
|
||||
context.coordinator.updateConns(conns, for: tableView)
|
||||
}
|
||||
|
||||
func updateSorts(_ objects: [DBConnectionObject],
|
||||
tableView: NSTableView) -> [DBConnectionObject] {
|
||||
var re = objects
|
||||
|
||||
var sortDescriptors = tableView.sortDescriptors
|
||||
var sortDescriptors = [NSSortDescriptor]()
|
||||
|
||||
if let sort = tableView.sortDescriptors.first {
|
||||
sortDescriptors.append(sort)
|
||||
}
|
||||
|
||||
sortDescriptors.append(.init(keyPath: \DBConnectionObject.id, ascending: true))
|
||||
re = re.sorted(descriptors: sortDescriptors)
|
||||
|
||||
@@ -188,6 +189,22 @@ struct ConnectionsTableView<Item: Hashable>: NSViewRepresentable {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func updateConns(_ conns: [DBConnectionObject], for tableView: NSTableView) {
|
||||
let changes = conns.difference(from: self.conns) {
|
||||
$0.id == $1.id
|
||||
}
|
||||
guard let partialChanges = self.conns.applying(changes) else {
|
||||
return
|
||||
}
|
||||
self.conns = conns
|
||||
|
||||
let indicesToReload = IndexSet(zip(partialChanges, conns).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 {
|
||||
conns.count
|
||||
@@ -196,14 +213,18 @@ struct ConnectionsTableView<Item: Hashable>: NSViewRepresentable {
|
||||
|
||||
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||
|
||||
guard let cellView = tableView.createCellView(with: "ConnsTableCellView"),
|
||||
let s = tableColumn?.identifier.rawValue.split(separator: ".").last,
|
||||
let tc = TableColumn(rawValue: String(s))
|
||||
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 < conns.count,
|
||||
let tf = cellView.textField
|
||||
else { return nil }
|
||||
|
||||
let conn = conns[row]
|
||||
|
||||
cellView.textField?.objectValue = {
|
||||
tf.objectValue = {
|
||||
switch tc {
|
||||
case .host:
|
||||
return conn.host
|
||||
@@ -247,102 +268,3 @@ struct ConnectionsTableView<Item: Hashable>: NSViewRepresentable {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension NSTableView {
|
||||
/// Applies multiple animated updates in stages using `StagedChangeset`.
|
||||
///
|
||||
/// - Note: There are combination of changes that crash when applied simultaneously in `performBatchUpdates`.
|
||||
/// Assumes that `StagedChangeset` has a minimum staged changesets to avoid it.
|
||||
/// The data of the data-source needs to be updated synchronously before `performBatchUpdates` in every stages.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - stagedChangeset: A staged set of changes.
|
||||
/// - interrupt: A closure that takes an changeset as its argument and returns `true` if the animated
|
||||
/// updates should be stopped and performed reloadData. Default is nil.
|
||||
/// - setData: A closure that takes the collection as a parameter.
|
||||
/// The collection should be set to data-source of NSTableView.
|
||||
|
||||
func reload<C>(
|
||||
using stagedChangeset: StagedChangeset<C>,
|
||||
interrupt: ((Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
if case .none = window, let data = stagedChangeset.last?.data {
|
||||
setData(data)
|
||||
return reloadData()
|
||||
}
|
||||
|
||||
for changeset in stagedChangeset {
|
||||
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
|
||||
setData(data)
|
||||
return reloadData()
|
||||
}
|
||||
|
||||
beginUpdates()
|
||||
setData(changeset.data)
|
||||
|
||||
if !changeset.elementDeleted.isEmpty {
|
||||
removeRows(at: IndexSet(changeset.elementDeleted.map { $0.element }))
|
||||
}
|
||||
|
||||
if !changeset.elementUpdated.isEmpty {
|
||||
reloadData(forRowIndexes: IndexSet(changeset.elementUpdated.map { $0.element }), columnIndexes: IndexSet(0..<tableColumns.count))
|
||||
}
|
||||
|
||||
if !changeset.elementInserted.isEmpty {
|
||||
insertRows(at: IndexSet(changeset.elementInserted.map { $0.element }))
|
||||
}
|
||||
endUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func createCellView(with identifier: String) -> NSTableCellView? {
|
||||
// https://stackoverflow.com/a/27624927
|
||||
|
||||
var cellView: NSTableCellView?
|
||||
if let spareView = makeView(withIdentifier: .init(identifier),
|
||||
owner: self) as? NSTableCellView {
|
||||
|
||||
// We can use an old cell - no need to do anything.
|
||||
cellView = spareView
|
||||
|
||||
} else {
|
||||
|
||||
// Create a text field for the cell
|
||||
let textField = NSTextField()
|
||||
textField.backgroundColor = NSColor.clear
|
||||
textField.translatesAutoresizingMaskIntoConstraints = false
|
||||
textField.isBordered = false
|
||||
textField.font = .systemFont(ofSize: 13)
|
||||
textField.lineBreakMode = .byTruncatingTail
|
||||
|
||||
// Create a cell
|
||||
let newCell = NSTableCellView()
|
||||
newCell.identifier = .init(identifier)
|
||||
newCell.addSubview(textField)
|
||||
newCell.textField = textField
|
||||
|
||||
// Constrain the text field within the cell
|
||||
newCell.addConstraints(
|
||||
NSLayoutConstraint.constraints(withVisualFormat: "H:|[textField]|",
|
||||
options: [],
|
||||
metrics: nil,
|
||||
views: ["textField" : textField]))
|
||||
|
||||
newCell.addConstraint(.init(item: textField, attribute: .centerY, relatedBy: .equal, toItem: newCell, attribute: .centerY, multiplier: 1, constant: 0))
|
||||
|
||||
|
||||
textField.bind(NSBindingName.value,
|
||||
to: newCell,
|
||||
withKeyPath: "objectValue",
|
||||
options: nil)
|
||||
|
||||
cellView = newCell
|
||||
}
|
||||
|
||||
return cellView
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user