Merge branch 'spm' into dev

This commit is contained in:
mrFq1
2023-06-05 22:50:52 +08:00
64 changed files with 1555 additions and 707 deletions

9
ClashX Dashboard Kit/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@@ -0,0 +1,47 @@
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "ClashX Dashboard Kit",
platforms: [
.macOS(.v12),
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "ClashX Dashboard Kit",
targets: ["ClashX Dashboard Kit"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.0.0"),
.package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.0"),
.package(url: "https://github.com/daltoniam/Starscream.git", exact: "3.1.1"),
.package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", from: "3.0.0"),
.package(url: "https://github.com/ra1028/DifferenceKit.git", from: "1.0.0"),
.package(url: "https://github.com/dagronf/DSFSparkline.git", from: "4.0.0"),
.package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.2.3"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "ClashX Dashboard Kit",
dependencies: [
"Alamofire",
"CocoaLumberjack",
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
"DifferenceKit",
"DSFSparkline",
.product(name: "Introspect", package: "SwiftUI-Introspect"),
"Starscream",
"SwiftyJSON",
]),
.testTarget(
name: "ClashX Dashboard KitTests",
dependencies: ["ClashX Dashboard Kit"]),
]
)

View File

@@ -0,0 +1,3 @@
# ClashX Dashboard Kit
A description of this package.

View File

@@ -0,0 +1,13 @@
import SwiftUI
@available(macOS 12.0, *)
public struct DashboardView: View {
public init() {
}
public var body: some View {
ContentView()
}
}

View File

@@ -0,0 +1,283 @@
//
// DashboardViewContoller.swift
// ClashX
//
// Created by yicheng on 2018/8/28.
// Copyright © 2018 west2online. All rights reserved.
//
import Cocoa
import SwiftUI
public class DashboardWindowController: NSWindowController {
public var onWindowClose: (() -> Void)?
public static func create() -> DashboardWindowController {
let win = NSWindow()
win.center()
let wc = DashboardWindowController(window: win)
wc.contentViewController = DashboardViewContoller()
return wc
}
public override func showWindow(_ sender: Any?) {
super.showWindow(sender)
NSApp.activate(ignoringOtherApps: true)
window?.makeKeyAndOrderFront(self)
window?.delegate = self
}
public func set(_ apiURL: String, secret: String? = nil) {
ConfigManager.shared.isRunning = true
ConfigManager.shared.overrideApiURL = .init(string: apiURL)
ConfigManager.shared.overrideSecret = secret
}
public func reload() {
NotificationCenter.default.post(name: .reloadDashboard, object: nil)
}
}
extension DashboardWindowController: NSWindowDelegate {
public func windowWillClose(_ notification: Notification) {
NSApp.setActivationPolicy(.accessory)
onWindowClose?()
if let contentVC = contentViewController as? DashboardViewContoller, let win = window {
if !win.styleMask.contains(.fullScreen) {
contentVC.lastSize = win.frame.size
}
}
}
}
class DashboardViewContoller: NSViewController {
let contentView = NSHostingView(rootView: DashboardView())
let minSize = NSSize(width: 920, height: 580)
var lastSize: CGSize? {
set {
if let size = newValue {
UserDefaults.standard.set(NSStringFromSize(size), forKey: "ClashWebViewContoller.lastSize")
}
}
get {
if let str = UserDefaults.standard.value(forKey: "ClashWebViewContoller.lastSize") as? String {
return NSSizeFromString(str) as CGSize
}
return nil
}
}
let effectView = NSVisualEffectView()
private let levels = [
ClashLogLevel.silent,
.error,
.warning,
.info,
.debug
]
private var sidebarItemObserver: NSObjectProtocol?
func createWindowController() -> NSWindowController {
let sb = NSStoryboard(name: "Main", bundle: Bundle.main)
let vc = sb.instantiateController(withIdentifier: "DashboardViewContoller") as! DashboardViewContoller
let wc = NSWindowController(window: NSWindow())
wc.contentViewController = vc
return wc
}
override func loadView() {
view = contentView
}
override func viewDidLoad() {
super.viewDidLoad()
sidebarItemObserver = NotificationCenter.default.addObserver(forName: .sidebarItemChanged, object: nil, queue: .main) {
guard let item = $0.userInfo?["item"] as? SidebarItem else { return }
var items = [NSToolbarItem.Identifier]()
items.append(.toggleSidebar)
switch item {
case .overview, .config:
break
case .proxies, .providers:
items.append(.hideNamesItem)
items.append(.searchItem)
case .rules:
items.append(.searchItem)
case .conns:
items.append(.stopConnsItem)
items.append(.searchItem)
case .logs:
items.append(.logLevelItem)
items.append(.searchItem)
}
self.reinitToolbar(items)
}
}
public override func viewWillAppear() {
super.viewWillAppear()
view.window?.styleMask.insert(.fullSizeContentView)
view.window?.isOpaque = false
view.window?.styleMask.insert(.closable)
view.window?.styleMask.insert(.resizable)
view.window?.styleMask.insert(.miniaturizable)
let toolbar = NSToolbar(identifier: .init("DashboardToolbar"))
toolbar.displayMode = .iconOnly
toolbar.delegate = self
view.window?.toolbar = toolbar
view.window?.title = "Dashboard"
reinitToolbar([])
view.window?.minSize = minSize
if let lastSize = lastSize, lastSize != .zero {
view.window?.setContentSize(lastSize)
}
view.window?.center()
if NSApp.activationPolicy() == .accessory {
NSApp.setActivationPolicy(.regular)
}
}
func reinitToolbar(_ items: [NSToolbarItem.Identifier]) {
guard let toolbar = view.window?.toolbar else { return }
toolbar.items.enumerated().reversed().forEach {
toolbar.removeItem(at: $0.offset)
}
items.reversed().forEach {
toolbar.insertItem(withItemIdentifier: $0, at: 0)
}
}
deinit {
if let sidebarItemObserver {
NotificationCenter.default.removeObserver(sidebarItemObserver)
}
NSApp.setActivationPolicy(.accessory)
}
}
extension NSToolbarItem.Identifier {
static let hideNamesItem = NSToolbarItem.Identifier("HideNamesItem")
static let stopConnsItem = NSToolbarItem.Identifier("StopConnsItem")
static let logLevelItem = NSToolbarItem.Identifier("LogLevelItem")
static let searchItem = NSToolbarItem.Identifier("SearchItem")
}
extension DashboardViewContoller: NSSearchFieldDelegate {
func controlTextDidChange(_ obj: Notification) {
guard let obj = obj.object as? NSSearchField else { return }
NotificationCenter.default.post(name: .toolbarSearchString, object: nil, userInfo: ["String": obj.stringValue])
}
@IBAction func stopConns(_ sender: NSToolbarItem) {
NotificationCenter.default.post(name: .stopConns, object: nil)
}
@IBAction func hideNames(_ sender: NSToolbarItem) {
switch sender.tag {
case 0:
sender.tag = 1
sender.image = NSImage(systemSymbolName: "eyeglasses", accessibilityDescription: nil)
case 1:
sender.tag = 0
sender.image = NSImage(systemSymbolName: "wand.and.stars", accessibilityDescription: nil)
default:
break
}
NotificationCenter.default.post(name: .hideNames, object: nil, userInfo: ["hide": sender.tag == 1])
}
@objc func setLogLevel(_ sender: NSToolbarItemGroup) {
guard sender.selectedIndex < levels.count, sender.selectedIndex >= 0 else { return }
let level = levels[sender.selectedIndex]
NotificationCenter.default.post(name: .logLevelChanged, object: nil, userInfo: ["level": level])
}
}
extension DashboardViewContoller: NSToolbarDelegate, NSToolbarItemValidation {
func validateToolbarItem(_ item: NSToolbarItem) -> Bool {
return true
}
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
switch itemIdentifier {
case .searchItem:
let item = NSSearchToolbarItem(itemIdentifier: .searchItem)
item.resignsFirstResponderWithCancel = true
item.searchField.delegate = self
item.toolTip = "Search"
return item
case .toggleSidebar:
return NSTrackingSeparatorToolbarItem(itemIdentifier: .toggleSidebar)
case .logLevelItem:
let titles = levels.map {
$0.rawValue.capitalized
}
let group = NSToolbarItemGroup(itemIdentifier: .logLevelItem, titles: titles, selectionMode: .selectOne, labels: titles, target: nil, action: #selector(setLogLevel(_:)))
group.selectionMode = .selectOne
group.controlRepresentation = .collapsed
group.selectedIndex = levels.firstIndex(of: ConfigManager.selectLoggingApiLevel) ?? 0
return group
case .hideNamesItem:
let item = NSToolbarItem(itemIdentifier: .hideNamesItem)
item.target = self
item.action = #selector(hideNames(_:))
item.isBordered = true
item.tag = 0
item.image = NSImage(systemSymbolName: "wand.and.stars", accessibilityDescription: nil)
return item
case .stopConnsItem:
let item = NSToolbarItem(itemIdentifier: .stopConnsItem)
item.target = self
item.action = #selector(stopConns(_:))
item.isBordered = true
item.image = NSImage(systemSymbolName: "stop.circle.fill", accessibilityDescription: nil)
return item
default:
break
}
return nil
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
[
.toggleSidebar,
.stopConnsItem,
.hideNamesItem,
.logLevelItem,
.searchItem
]
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
[
.toggleSidebar,
.stopConnsItem,
.hideNamesItem,
.logLevelItem,
.searchItem
]
}
}

View File

@@ -0,0 +1,17 @@
//
// NotificationNames.swift
//
//
//
import Foundation
extension NSNotification.Name {
static let reloadDashboard = NSNotification.Name("ReloadDashboard")
static let sidebarItemChanged = NSNotification.Name("SidebarItemChanged")
static let toolbarSearchString = NSNotification.Name("ToolbarSearchString")
static let stopConns = NSNotification.Name("StopConns")
static let hideNames = NSNotification.Name("HideNames")
static let logLevelChanged = NSNotification.Name("LogLevelChanged")
}

View File

@@ -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
}

View File

@@ -48,6 +48,7 @@ struct CollectionsTableView<Item: Hashable>: NSViewRepresentable {
let scrollView = NonRespondingScrollView()
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = true
scrollView.autohidesScrollers = true
let tableView = NonRespondingTableView()
tableView.usesAlternatingRowBackgroundColors = true
@@ -55,8 +56,6 @@ struct CollectionsTableView<Item: Hashable>: NSViewRepresentable {
tableView.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.register(.init(nibNamed: .init("CollectionTableCellView"), bundle: .main), forIdentifier: .init(rawValue: "CollectionTableCellView"))
TableColumn.allCases.forEach {
let tableColumn = NSTableColumn(identifier: .init($0.rawValue))
tableColumn.title = $0.rawValue
@@ -174,14 +173,15 @@ struct CollectionsTableView<Item: Hashable>: NSViewRepresentable {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let cell = tableView.makeView(withIdentifier: .init(rawValue: "CollectionTableCellView"), owner: nil) as? NSTableCellView,
guard let cellView = tableView.createCellView(with: "ConnsTableCellView"),
let s = tableColumn?.identifier.rawValue.split(separator: ".").last,
let tc = TableColumn(rawValue: String(s))
else { return nil }
let conn = conns[row]
cell.textField?.objectValue = {
cellView.textField?.objectValue = {
switch tc {
case .host:
return conn.host
@@ -208,7 +208,7 @@ struct CollectionsTableView<Item: Hashable>: NSViewRepresentable {
}
}()
return cell
return cellView
}
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
@@ -216,6 +216,7 @@ struct CollectionsTableView<Item: Hashable>: NSViewRepresentable {
tableView.reloadData()
}
}
}
@@ -268,4 +269,52 @@ extension NSTableView {
}
}
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
}
}

View File

@@ -18,16 +18,27 @@ struct ConnectionsView: View {
filterString: searchString)
.background(Color(nsColor: .textBackgroundColor))
.searchable(text: $searchString)
.onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) {
guard let string = $0.userInfo?["String"] as? String else { return }
searchString = string
}
.onReceive(NotificationCenter.default.publisher(for: .stopConns)) { _ in
stopConns()
}
.toolbar {
ToolbarItem {
Button {
ApiRequest.closeAllConnection()
stopConns()
} label: {
Image(systemName: "stop.circle.fill")
}
}
}
}
func stopConns() {
ApiRequest.closeAllConnection()
}
}
struct ConnectionsView_Previews: PreviewProvider {

View File

@@ -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
}
}
}

View File

@@ -0,0 +1,62 @@
//
// LogsView.swift
// ClashX Dashboard
//
//
import SwiftUI
struct LogsView: View {
@EnvironmentObject var logStorage: ClashLogStorage
@State var searchString: String = ""
@State var logLevel = ConfigManager.selectLoggingApiLevel
var body: some View {
Group {
LogsTableView(data: logStorage.logs.reversed(), filterString: searchString)
}
.searchable(text: $searchString)
.onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) {
guard let string = $0.userInfo?["String"] as? String else { return }
searchString = string
}
.onReceive(NotificationCenter.default.publisher(for: .logLevelChanged)) {
guard let level = $0.userInfo?["level"] as? ClashLogLevel else { return }
logLevelChanged(level)
}
.toolbar {
ToolbarItem {
Picker("", selection: $logLevel) {
ForEach([
ClashLogLevel.silent,
.error,
.warning,
.info,
.debug
], id: \.self) {
Text($0.rawValue.capitalized).tag($0)
}
}
.pickerStyle(.menu)
.onChange(of: logLevel) { newValue in
guard newValue != ConfigManager.selectLoggingApiLevel else { return }
logLevelChanged(newValue)
}
}
}
}
func logLevelChanged(_ level: ClashLogLevel) {
logStorage.logs.removeAll()
ConfigManager.selectLoggingApiLevel = level
ApiRequest.shared.resetLogStreamApi()
}
}
struct LogsView_Previews: PreviewProvider {
static var previews: some View {
LogsView()
}
}

View File

@@ -20,6 +20,14 @@ struct ProvidersView: View {
EmptyView()
}
.searchable(text: $searchString.string)
.onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) {
guard let string = $0.userInfo?["String"] as? String else { return }
searchString.string = string
}
.onReceive(NotificationCenter.default.publisher(for: .hideNames)) {
guard let hide = $0.userInfo?["hide"] as? Bool else { return }
hideProxyNames.hide = hide
}
.environmentObject(searchString)
.onAppear {
loadProviders()

View File

@@ -34,6 +34,14 @@ struct ProxiesView: View {
EmptyView()
}
.searchable(text: $searchString.string)
.onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) {
guard let string = $0.userInfo?["String"] as? String else { return }
searchString.string = string
}
.onReceive(NotificationCenter.default.publisher(for: .hideNames)) {
guard let hide = $0.userInfo?["hide"] as? Bool else { return }
hideProxyNames.hide = hide
}
.environmentObject(searchString)
.onAppear {
loadProxies()

View File

@@ -29,6 +29,10 @@ struct RulesView: View {
}
}
.searchable(text: $searchString)
.onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) {
guard let string = $0.userInfo?["String"] as? String else { return }
searchString = string
}
.onAppear {
ruleItems.removeAll()
ApiRequest.getRules {

View File

@@ -0,0 +1,18 @@
//
// SidebarItem.swift
// ClashX Dashboard
//
//
import Cocoa
import SwiftUI
enum SidebarItem: String {
case overview = "Overview"
case proxies = "Proxies"
case providers = "Providers"
case rules = "Rules"
case conns = "Conns"
case config = "Config"
case logs = "Logs"
}

View File

@@ -0,0 +1,83 @@
//
// SidebarListView.swift
// ClashX Dashboard
//
//
import SwiftUI
import Introspect
struct SidebarListView: View {
@Binding var selectionName: SidebarItem?
@State private var reloadID = UUID().uuidString
var body: some View {
List {
NavigationLink(destination: OverviewView(),
tag: SidebarItem.overview,
selection: $selectionName) {
Label(SidebarItem.overview.rawValue, systemImage: "chart.bar.xaxis")
}
NavigationLink(destination: ProxiesView(),
tag: SidebarItem.proxies,
selection: $selectionName) {
Label(SidebarItem.proxies.rawValue, systemImage: "globe.asia.australia")
}
NavigationLink(destination: ProvidersView(),
tag: SidebarItem.providers,
selection: $selectionName) {
Label(SidebarItem.providers.rawValue, systemImage: "link.icloud")
}
NavigationLink(destination: RulesView(),
tag: SidebarItem.rules,
selection: $selectionName) {
Label(SidebarItem.rules.rawValue, systemImage: "waveform.and.magnifyingglass")
}
NavigationLink(destination: ConnectionsView(),
tag: SidebarItem.conns,
selection: $selectionName) {
Label(SidebarItem.conns.rawValue, systemImage: "app.connected.to.app.below.fill")
}
NavigationLink(destination: ConfigView(),
tag: SidebarItem.config,
selection: $selectionName) {
Label(SidebarItem.config.rawValue, systemImage: "slider.horizontal.3")
}
NavigationLink(destination: LogsView(),
tag: SidebarItem.logs,
selection: $selectionName) {
Label(SidebarItem.logs.rawValue, systemImage: "wand.and.stars.inverse")
}
}
.introspectTableView {
if selectionName == nil {
selectionName = SidebarItem.overview
$0.allowsEmptySelection = false
if $0.selectedRow == -1 {
$0.selectRowIndexes(.init(integer: 0), byExtendingSelection: false)
}
}
}
.listStyle(.sidebar)
.id(reloadID)
.onReceive(NotificationCenter.default.publisher(for: .reloadDashboard)) { _ in
reloadID = UUID().uuidString
}
}
}
//struct SidebarListView_Previews: PreviewProvider {
// static var previews: some View {
// SidebarListView()
// }
//}

View File

@@ -13,10 +13,10 @@ struct SidebarView: View {
private let connsQueue = DispatchQueue(label: "thread-safe-connsQueue", attributes: .concurrent)
private let timer = Timer.publish(every: 1, on: .main, in: .default).autoconnect()
@State private var sidebarSelectionName: String? = "Overview"
@State private var sidebarSelectionName: SidebarItem?
var body: some View {
ScrollViewReader { scrollViewProxy in
Group {
SidebarListView(selectionName: $sidebarSelectionName)
}
.environmentObject(clashApiDatasStorage.overviewData)
@@ -35,9 +35,13 @@ struct SidebarView: View {
updateConnections()
}
.onChange(of: sidebarSelectionName) { newValue in
sidebarItemChanged(newValue)
}
.onReceive(timer, perform: { _ in
updateConnections()
})
}
func updateConnections() {
@@ -50,6 +54,12 @@ struct SidebarView: View {
}
}
}
func sidebarItemChanged(_ item: SidebarItem?) {
guard let item else { return }
NotificationCenter.default.post(name: .sidebarItemChanged, object: nil, userInfo: ["item": item])
}
}
//struct SidebarView_Previews: PreviewProvider {

View File

@@ -0,0 +1,11 @@
import XCTest
@testable import ClashX_Dashboard_Kit
final class ClashX_Dashboard_KitTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(ClashX_Dashboard_Kit().text, "Hello, World!")
}
}

View File

@@ -7,122 +7,23 @@
objects = {
/* Begin PBXBuildFile section */
010F693B29ED639A00BAAFB5 /* ClashServerAppStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010F693A29ED639A00BAAFB5 /* ClashServerAppStorage.swift */; };
01359A392A21D88B00A2B3FB /* SidebarListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01359A382A21D88B00A2B3FB /* SidebarListView.swift */; };
01505C4A2A147B84001ACC4F /* DBProviderStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01505C492A147B84001ACC4F /* DBProviderStorage.swift */; };
01505C4C2A14A1A3001ACC4F /* ProviderRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01505C4B2A14A1A3001ACC4F /* ProviderRowView.swift */; };
01505C4E2A14AAEB001ACC4F /* ProviderProxiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01505C4D2A14AAEB001ACC4F /* ProviderProxiesView.swift */; };
015278082A15F9FD00516236 /* ProxyProviderInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 015278072A15F9FD00516236 /* ProxyProviderInfoView.swift */; };
0155D39629F2342F00869830 /* TrafficGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0155D39529F2342F00869830 /* TrafficGraphView.swift */; };
0155D39829F23BDE00869830 /* OverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0155D39729F23BDE00869830 /* OverviewView.swift */; };
0172CB5129E5AE670072DDEF /* SwiftUIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172CB5029E5AE670072DDEF /* SwiftUIViewExtensions.swift */; };
0172F12F2A1FB06100EE2B6D /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F12C2A1FB06100EE2B6D /* DateFormatter+.swift */; };
0172F1312A1FB06100EE2B6D /* String+Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F12E2A1FB06100EE2B6D /* String+Encode.swift */; };
0172F1352A1FB0B900EE2B6D /* ConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1342A1FB0B900EE2B6D /* ConfigManager.swift */; };
0172F1372A1FB0CD00EE2B6D /* ApiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1362A1FB0CD00EE2B6D /* ApiRequest.swift */; };
0172F1392A1FB0E900EE2B6D /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1382A1FB0E900EE2B6D /* Logger.swift */; };
0172F1412A1FB10D00EE2B6D /* DBConnectionSnapShot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13B2A1FB10C00EE2B6D /* DBConnectionSnapShot.swift */; };
0172F1422A1FB10D00EE2B6D /* ClashRuleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13C2A1FB10D00EE2B6D /* ClashRuleProvider.swift */; };
0172F1432A1FB10D00EE2B6D /* ClashRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13D2A1FB10D00EE2B6D /* ClashRule.swift */; };
0172F1442A1FB10D00EE2B6D /* ClashConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13E2A1FB10D00EE2B6D /* ClashConfig.swift */; };
0172F1452A1FB10D00EE2B6D /* ClashProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13F2A1FB10D00EE2B6D /* ClashProvider.swift */; };
0172F1462A1FB10D00EE2B6D /* ClashProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1402A1FB10D00EE2B6D /* ClashProxy.swift */; };
0172F1482A1FB90200EE2B6D /* ClashConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1472A1FB90200EE2B6D /* ClashConnection.swift */; };
017753C029EF7FB2006999DB /* APIServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017753BF29EF7FB1006999DB /* APIServerItem.swift */; };
017DCADD29E83BFD00B9622A /* RuleProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DCADC29E83BFD00B9622A /* RuleProviderView.swift */; };
017F9AAA2A0DFEBD00B81497 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 017F9AA92A0DFEBD00B81497 /* Introspect */; };
017F9AAC2A0E0B2300B81497 /* ProxyGroupRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017F9AAB2A0E0B2300B81497 /* ProxyGroupRowView.swift */; };
018003B12A136DDB0070226E /* ProvidersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018003B02A136DDB0070226E /* ProvidersView.swift */; };
018A61BD29E9A2ED008608C0 /* APISettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018A61BC29E9A2ED008608C0 /* APISettingView.swift */; };
018AFEA52A1B5F7A0076E66B /* ProgressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018AFEA42A1B5F7A0076E66B /* ProgressButton.swift */; };
018C836C29E17505006366D3 /* ClashApiDatasStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018C836B29E17505006366D3 /* ClashApiDatasStorage.swift */; };
011BFE462A279D1C0027AD15 /* ClashX Dashboard Kit in Frameworks */ = {isa = PBXBuildFile; productRef = 011BFE452A279D1C0027AD15 /* ClashX Dashboard Kit */; };
0192315F29DD4DCF00539EDD /* ClashX_DashboardApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192315E29DD4DCF00539EDD /* ClashX_DashboardApp.swift */; };
0192316129DD4DCF00539EDD /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192316029DD4DCF00539EDD /* ContentView.swift */; };
0192316329DD4DD100539EDD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0192316229DD4DD100539EDD /* Assets.xcassets */; };
0192316629DD4DD100539EDD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0192316529DD4DD100539EDD /* Preview Assets.xcassets */; };
0192317129DD566000539EDD /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192317029DD566000539EDD /* SidebarView.swift */; };
0192317829DD5DA500539EDD /* ProxiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192317729DD5DA500539EDD /* ProxiesView.swift */; };
0192317A29DD5DB000539EDD /* RulesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192317929DD5DB000539EDD /* RulesView.swift */; };
0192317C29DD5DF200539EDD /* ConnectionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192317B29DD5DF200539EDD /* ConnectionsView.swift */; };
0192317E29DD5E0100539EDD /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192317D29DD5E0100539EDD /* ConfigView.swift */; };
0192318029DD5E0B00539EDD /* LogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192317F29DD5E0B00539EDD /* LogsView.swift */; };
0192318329DD70B400539EDD /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192318229DD70B400539EDD /* SidebarItem.swift */; };
0192318529DD7DCD00539EDD /* SidebarItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192318429DD7DCD00539EDD /* SidebarItemView.swift */; };
0192318729DD83FF00539EDD /* OverviewTopItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192318629DD83FF00539EDD /* OverviewTopItemView.swift */; };
0192B5BA29DE50F8002CDBF3 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 0192B5B929DE50F8002CDBF3 /* Alamofire */; };
0192B5BD29DE5113002CDBF3 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 0192B5BC29DE5113002CDBF3 /* SwiftyJSON */; };
0192B5C029DE5134002CDBF3 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 0192B5BF29DE5134002CDBF3 /* Starscream */; };
0192B5D429DE5190002CDBF3 /* CocoaLumberjackSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0192B5D329DE5190002CDBF3 /* CocoaLumberjackSwift */; };
019D6A8729F015DF00A6AC02 /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019D6A8629F015DF00A6AC02 /* ArrayExtensions.swift */; };
019D6A9629F194C600A6AC02 /* DSFSparkline in Frameworks */ = {isa = PBXBuildFile; productRef = 019D6A9529F194C600A6AC02 /* DSFSparkline */; };
01A351A229DD8F440054894E /* RuleItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A351A129DD8F440054894E /* RuleItemView.swift */; };
01A351A929DD9CB00054894E /* Connections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A351A829DD9CB00054894E /* Connections.swift */; };
01A3EF042A120103003038B5 /* DBProxyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A3EF032A120103003038B5 /* DBProxyStorage.swift */; };
01CD0A9229E93ABB00F4C17E /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 01CD0A9129E93ABB00F4C17E /* DifferenceKit */; };
01DCEFB12A150E8B00DBBDB3 /* RuleProvidersRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DCEFB02A150E8B00DBBDB3 /* RuleProvidersRowView.swift */; };
01DCEFB32A150FB300DBBDB3 /* ProxyProvidersRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DCEFB22A150FB300DBBDB3 /* ProxyProvidersRowView.swift */; };
01F5E3F42A1E53F4008F3DEB /* ConfigItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F5E3F32A1E53F4008F3DEB /* ConfigItemView.swift */; };
01F885CF29DFD8DF008241EB /* CollectionsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F885CE29DFD8DF008241EB /* CollectionsTableView.swift */; };
01F885D129E03F20008241EB /* CollectionTableCellView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 01F885D029E03F20008241EB /* CollectionTableCellView.xib */; };
01F885D329E04E21008241EB /* ProxyGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F885D229E04E21008241EB /* ProxyGroupView.swift */; };
01F885D529E053DE008241EB /* ProxyItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F885D429E053DE008241EB /* ProxyItemView.swift */; };
01A78B7F2A2B188F0058D1CA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01A78B7E2A2B188E0058D1CA /* Main.storyboard */; };
01FE884C2A29BB590084902C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FE884B2A29BB590084902C /* AppDelegate.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
010F693A29ED639A00BAAFB5 /* ClashServerAppStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashServerAppStorage.swift; sourceTree = "<group>"; };
01359A382A21D88B00A2B3FB /* SidebarListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListView.swift; sourceTree = "<group>"; };
01505C492A147B84001ACC4F /* DBProviderStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBProviderStorage.swift; sourceTree = "<group>"; };
01505C4B2A14A1A3001ACC4F /* ProviderRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderRowView.swift; sourceTree = "<group>"; };
01505C4D2A14AAEB001ACC4F /* ProviderProxiesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProxiesView.swift; sourceTree = "<group>"; };
015278072A15F9FD00516236 /* ProxyProviderInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyProviderInfoView.swift; sourceTree = "<group>"; };
0155D39529F2342F00869830 /* TrafficGraphView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficGraphView.swift; sourceTree = "<group>"; };
0155D39729F23BDE00869830 /* OverviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverviewView.swift; sourceTree = "<group>"; };
0172CB5029E5AE670072DDEF /* SwiftUIViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIViewExtensions.swift; sourceTree = "<group>"; };
0172F12C2A1FB06100EE2B6D /* DateFormatter+.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = "<group>"; };
0172F12E2A1FB06100EE2B6D /* String+Encode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Encode.swift"; sourceTree = "<group>"; };
0172F1342A1FB0B900EE2B6D /* ConfigManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigManager.swift; sourceTree = "<group>"; };
0172F1362A1FB0CD00EE2B6D /* ApiRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiRequest.swift; sourceTree = "<group>"; };
0172F1382A1FB0E900EE2B6D /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
0172F13B2A1FB10C00EE2B6D /* DBConnectionSnapShot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DBConnectionSnapShot.swift; sourceTree = "<group>"; };
0172F13C2A1FB10D00EE2B6D /* ClashRuleProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashRuleProvider.swift; sourceTree = "<group>"; };
0172F13D2A1FB10D00EE2B6D /* ClashRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashRule.swift; sourceTree = "<group>"; };
0172F13E2A1FB10D00EE2B6D /* ClashConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConfig.swift; sourceTree = "<group>"; };
0172F13F2A1FB10D00EE2B6D /* ClashProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashProvider.swift; sourceTree = "<group>"; };
0172F1402A1FB10D00EE2B6D /* ClashProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashProxy.swift; sourceTree = "<group>"; };
0172F1472A1FB90200EE2B6D /* ClashConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConnection.swift; sourceTree = "<group>"; };
017753BF29EF7FB1006999DB /* APIServerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIServerItem.swift; sourceTree = "<group>"; };
017DCADC29E83BFD00B9622A /* RuleProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleProviderView.swift; sourceTree = "<group>"; };
017F9AAB2A0E0B2300B81497 /* ProxyGroupRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupRowView.swift; sourceTree = "<group>"; };
018003B02A136DDB0070226E /* ProvidersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProvidersView.swift; sourceTree = "<group>"; };
018A61BC29E9A2ED008608C0 /* APISettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APISettingView.swift; sourceTree = "<group>"; };
018AFEA42A1B5F7A0076E66B /* ProgressButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButton.swift; sourceTree = "<group>"; };
018C836B29E17505006366D3 /* ClashApiDatasStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashApiDatasStorage.swift; sourceTree = "<group>"; };
011BFE402A2799550027AD15 /* ClashX Dashboard Kit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = "ClashX Dashboard Kit"; sourceTree = "<group>"; };
0192315B29DD4DCF00539EDD /* ClashX Dashboard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ClashX Dashboard.app"; sourceTree = BUILT_PRODUCTS_DIR; };
0192315E29DD4DCF00539EDD /* ClashX_DashboardApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashX_DashboardApp.swift; sourceTree = "<group>"; };
0192316029DD4DCF00539EDD /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
0192316229DD4DD100539EDD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0192316529DD4DD100539EDD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
0192316729DD4DD100539EDD /* ClashX_Dashboard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ClashX_Dashboard.entitlements; sourceTree = "<group>"; };
0192317029DD566000539EDD /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; };
0192317729DD5DA500539EDD /* ProxiesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxiesView.swift; sourceTree = "<group>"; };
0192317929DD5DB000539EDD /* RulesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RulesView.swift; sourceTree = "<group>"; };
0192317B29DD5DF200539EDD /* ConnectionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsView.swift; sourceTree = "<group>"; };
0192317D29DD5E0100539EDD /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; };
0192317F29DD5E0B00539EDD /* LogsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsView.swift; sourceTree = "<group>"; };
0192318229DD70B400539EDD /* SidebarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItem.swift; sourceTree = "<group>"; };
0192318429DD7DCD00539EDD /* SidebarItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItemView.swift; sourceTree = "<group>"; };
0192318629DD83FF00539EDD /* OverviewTopItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewTopItemView.swift; sourceTree = "<group>"; };
019D6A8629F015DF00A6AC02 /* ArrayExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtensions.swift; sourceTree = "<group>"; };
01A351A129DD8F440054894E /* RuleItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleItemView.swift; sourceTree = "<group>"; };
01A351A829DD9CB00054894E /* Connections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connections.swift; sourceTree = "<group>"; };
01A3EF032A120103003038B5 /* DBProxyStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBProxyStorage.swift; sourceTree = "<group>"; };
01DCEFB02A150E8B00DBBDB3 /* RuleProvidersRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleProvidersRowView.swift; sourceTree = "<group>"; };
01DCEFB22A150FB300DBBDB3 /* ProxyProvidersRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyProvidersRowView.swift; sourceTree = "<group>"; };
01F5E3F32A1E53F4008F3DEB /* ConfigItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigItemView.swift; sourceTree = "<group>"; };
01F885CE29DFD8DF008241EB /* CollectionsTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionsTableView.swift; sourceTree = "<group>"; };
01F885D029E03F20008241EB /* CollectionTableCellView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollectionTableCellView.xib; sourceTree = "<group>"; };
01F885D229E04E21008241EB /* ProxyGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupView.swift; sourceTree = "<group>"; };
01F885D429E053DE008241EB /* ProxyItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyItemView.swift; sourceTree = "<group>"; };
01A78B7E2A2B188E0058D1CA /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
01FE884B2A29BB590084902C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -130,109 +31,27 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0192B5BD29DE5113002CDBF3 /* SwiftyJSON in Frameworks */,
0192B5BA29DE50F8002CDBF3 /* Alamofire in Frameworks */,
019D6A9629F194C600A6AC02 /* DSFSparkline in Frameworks */,
0192B5D429DE5190002CDBF3 /* CocoaLumberjackSwift in Frameworks */,
017F9AAA2A0DFEBD00B81497 /* Introspect in Frameworks */,
01CD0A9229E93ABB00F4C17E /* DifferenceKit in Frameworks */,
0192B5C029DE5134002CDBF3 /* Starscream in Frameworks */,
011BFE462A279D1C0027AD15 /* ClashX Dashboard Kit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
010F693929ED638B00BAAFB5 /* APISetting */ = {
011BFE442A279D1C0027AD15 /* Frameworks */ = {
isa = PBXGroup;
children = (
018A61BC29E9A2ED008608C0 /* APISettingView.swift */,
018AFEA42A1B5F7A0076E66B /* ProgressButton.swift */,
017753BF29EF7FB1006999DB /* APIServerItem.swift */,
010F693A29ED639A00BAAFB5 /* ClashServerAppStorage.swift */,
);
path = APISetting;
sourceTree = "<group>";
};
0172F12B2A1FB05900EE2B6D /* Extensions */ = {
isa = PBXGroup;
children = (
0172F12C2A1FB06100EE2B6D /* DateFormatter+.swift */,
0172F12E2A1FB06100EE2B6D /* String+Encode.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
0172F1322A1FB0AA00EE2B6D /* General */ = {
isa = PBXGroup;
children = (
0172F1332A1FB0B000EE2B6D /* Managers */,
);
path = General;
sourceTree = "<group>";
};
0172F1332A1FB0B000EE2B6D /* Managers */ = {
isa = PBXGroup;
children = (
0172F1342A1FB0B900EE2B6D /* ConfigManager.swift */,
);
path = Managers;
sourceTree = "<group>";
};
0172F13A2A1FB10300EE2B6D /* Models */ = {
isa = PBXGroup;
children = (
0172F13E2A1FB10D00EE2B6D /* ClashConfig.swift */,
0172F1472A1FB90200EE2B6D /* ClashConnection.swift */,
0172F13F2A1FB10D00EE2B6D /* ClashProvider.swift */,
0172F1402A1FB10D00EE2B6D /* ClashProxy.swift */,
0172F13D2A1FB10D00EE2B6D /* ClashRule.swift */,
0172F13C2A1FB10D00EE2B6D /* ClashRuleProvider.swift */,
);
path = Models;
sourceTree = "<group>";
};
018003AF2A136D3E0070226E /* Providers */ = {
isa = PBXGroup;
children = (
018003B02A136DDB0070226E /* ProvidersView.swift */,
01DCEFB22A150FB300DBBDB3 /* ProxyProvidersRowView.swift */,
01DCEFB02A150E8B00DBBDB3 /* RuleProvidersRowView.swift */,
017DCADC29E83BFD00B9622A /* RuleProviderView.swift */,
01505C4B2A14A1A3001ACC4F /* ProviderRowView.swift */,
01505C4D2A14AAEB001ACC4F /* ProviderProxiesView.swift */,
015278072A15F9FD00516236 /* ProxyProviderInfoView.swift */,
);
path = Providers;
sourceTree = "<group>";
};
018C836929E1703D006366D3 /* Logs */ = {
isa = PBXGroup;
children = (
0192317F29DD5E0B00539EDD /* LogsView.swift */,
);
path = Logs;
sourceTree = "<group>";
};
018C836A29E174CB006366D3 /* Views */ = {
isa = PBXGroup;
children = (
0192316029DD4DCF00539EDD /* ContentView.swift */,
018C836B29E17505006366D3 /* ClashApiDatasStorage.swift */,
010F693929ED638B00BAAFB5 /* APISetting */,
0192318129DD709500539EDD /* SidebarView */,
0192317429DD5D7400539EDD /* ContentTabs */,
019D6A8629F015DF00A6AC02 /* ArrayExtensions.swift */,
0172CB5029E5AE670072DDEF /* SwiftUIViewExtensions.swift */,
);
path = Views;
name = Frameworks;
sourceTree = "<group>";
};
0192315229DD4DCF00539EDD = {
isa = PBXGroup;
children = (
011BFE402A2799550027AD15 /* ClashX Dashboard Kit */,
0192315D29DD4DCF00539EDD /* ClashX Dashboard */,
0192315C29DD4DCF00539EDD /* Products */,
011BFE442A279D1C0027AD15 /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -248,9 +67,8 @@
isa = PBXGroup;
children = (
0192315E29DD4DCF00539EDD /* ClashX_DashboardApp.swift */,
01A3EF022A120032003038B5 /* Models */,
018C836A29E174CB006366D3 /* Views */,
0192B5B529DE506D002CDBF3 /* ClashX Links */,
01FE884B2A29BB590084902C /* AppDelegate.swift */,
01A78B7E2A2B188E0058D1CA /* Main.storyboard */,
0192316229DD4DD100539EDD /* Assets.xcassets */,
0192316729DD4DD100539EDD /* ClashX_Dashboard.entitlements */,
0192316429DD4DD100539EDD /* Preview Content */,
@@ -266,103 +84,6 @@
path = "Preview Content";
sourceTree = "<group>";
};
0192317429DD5D7400539EDD /* ContentTabs */ = {
isa = PBXGroup;
children = (
01A351A529DD9B2D0054894E /* Overview */,
01A351A629DD9BB50054894E /* Proxies */,
018003AF2A136D3E0070226E /* Providers */,
01A351A029DD8F210054894E /* Rules */,
01A351A729DD9C7F0054894E /* Connections */,
01A7335F29E2CBD600205699 /* Config */,
018C836929E1703D006366D3 /* Logs */,
);
path = ContentTabs;
sourceTree = "<group>";
};
0192318129DD709500539EDD /* SidebarView */ = {
isa = PBXGroup;
children = (
0192317029DD566000539EDD /* SidebarView.swift */,
01359A382A21D88B00A2B3FB /* SidebarListView.swift */,
0192318429DD7DCD00539EDD /* SidebarItemView.swift */,
0192318229DD70B400539EDD /* SidebarItem.swift */,
);
path = SidebarView;
sourceTree = "<group>";
};
0192B5B529DE506D002CDBF3 /* ClashX Links */ = {
isa = PBXGroup;
children = (
0172F1362A1FB0CD00EE2B6D /* ApiRequest.swift */,
0172F1382A1FB0E900EE2B6D /* Logger.swift */,
0172F1322A1FB0AA00EE2B6D /* General */,
0172F12B2A1FB05900EE2B6D /* Extensions */,
0172F13A2A1FB10300EE2B6D /* Models */,
);
path = "ClashX Links";
sourceTree = "<group>";
};
01A351A029DD8F210054894E /* Rules */ = {
isa = PBXGroup;
children = (
0192317929DD5DB000539EDD /* RulesView.swift */,
01A351A129DD8F440054894E /* RuleItemView.swift */,
);
path = Rules;
sourceTree = "<group>";
};
01A351A529DD9B2D0054894E /* Overview */ = {
isa = PBXGroup;
children = (
0155D39729F23BDE00869830 /* OverviewView.swift */,
0192318629DD83FF00539EDD /* OverviewTopItemView.swift */,
0155D39529F2342F00869830 /* TrafficGraphView.swift */,
);
path = Overview;
sourceTree = "<group>";
};
01A351A629DD9BB50054894E /* Proxies */ = {
isa = PBXGroup;
children = (
0192317729DD5DA500539EDD /* ProxiesView.swift */,
017F9AAB2A0E0B2300B81497 /* ProxyGroupRowView.swift */,
01F885D229E04E21008241EB /* ProxyGroupView.swift */,
01F885D429E053DE008241EB /* ProxyItemView.swift */,
);
path = Proxies;
sourceTree = "<group>";
};
01A351A729DD9C7F0054894E /* Connections */ = {
isa = PBXGroup;
children = (
0192317B29DD5DF200539EDD /* ConnectionsView.swift */,
01A351A829DD9CB00054894E /* Connections.swift */,
01F885CE29DFD8DF008241EB /* CollectionsTableView.swift */,
01F885D029E03F20008241EB /* CollectionTableCellView.xib */,
);
path = Connections;
sourceTree = "<group>";
};
01A3EF022A120032003038B5 /* Models */ = {
isa = PBXGroup;
children = (
01A3EF032A120103003038B5 /* DBProxyStorage.swift */,
01505C492A147B84001ACC4F /* DBProviderStorage.swift */,
0172F13B2A1FB10C00EE2B6D /* DBConnectionSnapShot.swift */,
);
path = Models;
sourceTree = "<group>";
};
01A7335F29E2CBD600205699 /* Config */ = {
isa = PBXGroup;
children = (
0192317D29DD5E0100539EDD /* ConfigView.swift */,
01F5E3F32A1E53F4008F3DEB /* ConfigItemView.swift */,
);
path = Config;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -380,13 +101,7 @@
);
name = "ClashX Dashboard";
packageProductDependencies = (
0192B5B929DE50F8002CDBF3 /* Alamofire */,
0192B5BC29DE5113002CDBF3 /* SwiftyJSON */,
0192B5BF29DE5134002CDBF3 /* Starscream */,
0192B5D329DE5190002CDBF3 /* CocoaLumberjackSwift */,
01CD0A9129E93ABB00F4C17E /* DifferenceKit */,
019D6A9529F194C600A6AC02 /* DSFSparkline */,
017F9AA92A0DFEBD00B81497 /* Introspect */,
011BFE452A279D1C0027AD15 /* ClashX Dashboard Kit */,
);
productName = "ClashX Dashboard";
productReference = 0192315B29DD4DCF00539EDD /* ClashX Dashboard.app */;
@@ -417,13 +132,6 @@
);
mainGroup = 0192315229DD4DCF00539EDD;
packageReferences = (
0192B5B829DE50F8002CDBF3 /* XCRemoteSwiftPackageReference "Alamofire" */,
0192B5BB29DE5113002CDBF3 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
0192B5BE29DE5134002CDBF3 /* XCRemoteSwiftPackageReference "Starscream" */,
0192B5D229DE5190002CDBF3 /* XCRemoteSwiftPackageReference "CocoaLumberjack" */,
01CD0A9029E93ABB00F4C17E /* XCRemoteSwiftPackageReference "DifferenceKit" */,
019D6A9429F194C600A6AC02 /* XCRemoteSwiftPackageReference "DSFSparkline" */,
017F9AA82A0DFEBD00B81497 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
);
productRefGroup = 0192315C29DD4DCF00539EDD /* Products */;
projectDirPath = "";
@@ -439,8 +147,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
01A78B7F2A2B188F0058D1CA /* Main.storyboard in Resources */,
0192316629DD4DD100539EDD /* Preview Assets.xcassets in Resources */,
01F885D129E03F20008241EB /* CollectionTableCellView.xib in Resources */,
0192316329DD4DD100539EDD /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -452,55 +160,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0192317A29DD5DB000539EDD /* RulesView.swift in Sources */,
019D6A8729F015DF00A6AC02 /* ArrayExtensions.swift in Sources */,
01505C4C2A14A1A3001ACC4F /* ProviderRowView.swift in Sources */,
0192316129DD4DCF00539EDD /* ContentView.swift in Sources */,
01DCEFB32A150FB300DBBDB3 /* ProxyProvidersRowView.swift in Sources */,
0192318729DD83FF00539EDD /* OverviewTopItemView.swift in Sources */,
0192318029DD5E0B00539EDD /* LogsView.swift in Sources */,
0172F1392A1FB0E900EE2B6D /* Logger.swift in Sources */,
01505C4E2A14AAEB001ACC4F /* ProviderProxiesView.swift in Sources */,
0172F1412A1FB10D00EE2B6D /* DBConnectionSnapShot.swift in Sources */,
0192318529DD7DCD00539EDD /* SidebarItemView.swift in Sources */,
0172F1442A1FB10D00EE2B6D /* ClashConfig.swift in Sources */,
0172F1452A1FB10D00EE2B6D /* ClashProvider.swift in Sources */,
0192317E29DD5E0100539EDD /* ConfigView.swift in Sources */,
0155D39629F2342F00869830 /* TrafficGraphView.swift in Sources */,
01A3EF042A120103003038B5 /* DBProxyStorage.swift in Sources */,
0172F1432A1FB10D00EE2B6D /* ClashRule.swift in Sources */,
017F9AAC2A0E0B2300B81497 /* ProxyGroupRowView.swift in Sources */,
0172F1372A1FB0CD00EE2B6D /* ApiRequest.swift in Sources */,
017DCADD29E83BFD00B9622A /* RuleProviderView.swift in Sources */,
0192318329DD70B400539EDD /* SidebarItem.swift in Sources */,
0155D39829F23BDE00869830 /* OverviewView.swift in Sources */,
018C836C29E17505006366D3 /* ClashApiDatasStorage.swift in Sources */,
018A61BD29E9A2ED008608C0 /* APISettingView.swift in Sources */,
0172F1462A1FB10D00EE2B6D /* ClashProxy.swift in Sources */,
010F693B29ED639A00BAAFB5 /* ClashServerAppStorage.swift in Sources */,
01F885D329E04E21008241EB /* ProxyGroupView.swift in Sources */,
01FE884C2A29BB590084902C /* AppDelegate.swift in Sources */,
0192315F29DD4DCF00539EDD /* ClashX_DashboardApp.swift in Sources */,
018003B12A136DDB0070226E /* ProvidersView.swift in Sources */,
01A351A229DD8F440054894E /* RuleItemView.swift in Sources */,
01F885D529E053DE008241EB /* ProxyItemView.swift in Sources */,
0172F1312A1FB06100EE2B6D /* String+Encode.swift in Sources */,
01359A392A21D88B00A2B3FB /* SidebarListView.swift in Sources */,
01DCEFB12A150E8B00DBBDB3 /* RuleProvidersRowView.swift in Sources */,
01F5E3F42A1E53F4008F3DEB /* ConfigItemView.swift in Sources */,
0172F1352A1FB0B900EE2B6D /* ConfigManager.swift in Sources */,
0192317129DD566000539EDD /* SidebarView.swift in Sources */,
0192317C29DD5DF200539EDD /* ConnectionsView.swift in Sources */,
0172F12F2A1FB06100EE2B6D /* DateFormatter+.swift in Sources */,
017753C029EF7FB2006999DB /* APIServerItem.swift in Sources */,
0172F1422A1FB10D00EE2B6D /* ClashRuleProvider.swift in Sources */,
015278082A15F9FD00516236 /* ProxyProviderInfoView.swift in Sources */,
0192317829DD5DA500539EDD /* ProxiesView.swift in Sources */,
01F885CF29DFD8DF008241EB /* CollectionsTableView.swift in Sources */,
0172F1482A1FB90200EE2B6D /* ClashConnection.swift in Sources */,
018AFEA52A1B5F7A0076E66B /* ProgressButton.swift in Sources */,
01505C4A2A147B84001ACC4F /* DBProviderStorage.swift in Sources */,
0172CB5129E5AE670072DDEF /* SwiftUIViewExtensions.swift in Sources */,
01A351A929DD9CB00054894E /* Connections.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -633,6 +294,8 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMainStoryboardFile = Main;
INFOPLIST_KEY_UILaunchStoryboardName = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
@@ -659,6 +322,8 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMainStoryboardFile = Main;
INFOPLIST_KEY_UILaunchStoryboardName = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
@@ -695,100 +360,10 @@
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
017F9AA82A0DFEBD00B81497 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/siteline/SwiftUI-Introspect";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.2.3;
};
};
0192B5B829DE50F8002CDBF3 /* XCRemoteSwiftPackageReference "Alamofire" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Alamofire/Alamofire";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.0.0;
};
};
0192B5BB29DE5113002CDBF3 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.0.0;
};
};
0192B5BE29DE5134002CDBF3 /* XCRemoteSwiftPackageReference "Starscream" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/daltoniam/Starscream";
requirement = {
kind = exactVersion;
version = 3.1.1;
};
};
0192B5D229DE5190002CDBF3 /* XCRemoteSwiftPackageReference "CocoaLumberjack" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/CocoaLumberjack/CocoaLumberjack";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 3.0.0;
};
};
019D6A9429F194C600A6AC02 /* XCRemoteSwiftPackageReference "DSFSparkline" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/dagronf/DSFSparkline";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.0.0;
};
};
01CD0A9029E93ABB00F4C17E /* XCRemoteSwiftPackageReference "DifferenceKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ra1028/DifferenceKit";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
017F9AA92A0DFEBD00B81497 /* Introspect */ = {
011BFE452A279D1C0027AD15 /* ClashX Dashboard Kit */ = {
isa = XCSwiftPackageProductDependency;
package = 017F9AA82A0DFEBD00B81497 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
productName = Introspect;
};
0192B5B929DE50F8002CDBF3 /* Alamofire */ = {
isa = XCSwiftPackageProductDependency;
package = 0192B5B829DE50F8002CDBF3 /* XCRemoteSwiftPackageReference "Alamofire" */;
productName = Alamofire;
};
0192B5BC29DE5113002CDBF3 /* SwiftyJSON */ = {
isa = XCSwiftPackageProductDependency;
package = 0192B5BB29DE5113002CDBF3 /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
productName = SwiftyJSON;
};
0192B5BF29DE5134002CDBF3 /* Starscream */ = {
isa = XCSwiftPackageProductDependency;
package = 0192B5BE29DE5134002CDBF3 /* XCRemoteSwiftPackageReference "Starscream" */;
productName = Starscream;
};
0192B5D329DE5190002CDBF3 /* CocoaLumberjackSwift */ = {
isa = XCSwiftPackageProductDependency;
package = 0192B5D229DE5190002CDBF3 /* XCRemoteSwiftPackageReference "CocoaLumberjack" */;
productName = CocoaLumberjackSwift;
};
019D6A9529F194C600A6AC02 /* DSFSparkline */ = {
isa = XCSwiftPackageProductDependency;
package = 019D6A9429F194C600A6AC02 /* XCRemoteSwiftPackageReference "DSFSparkline" */;
productName = DSFSparkline;
};
01CD0A9129E93ABB00F4C17E /* DifferenceKit */ = {
isa = XCSwiftPackageProductDependency;
package = 01CD0A9029E93ABB00F4C17E /* XCRemoteSwiftPackageReference "DifferenceKit" */;
productName = DifferenceKit;
productName = "ClashX Dashboard Kit";
};
/* End XCSwiftPackageProductDependency section */
};

View File

@@ -0,0 +1,34 @@
//
// AppDelegate.swift
// ClashX Dashboard
//
//
import Cocoa
import ClashX_Dashboard_Kit
@main
class AppDelegate: NSObject, NSApplicationDelegate {
var dashboardWindowController: DashboardWindowController?
func applicationDidFinishLaunching(_ notification: Notification) {
if dashboardWindowController == nil {
dashboardWindowController = DashboardWindowController.create()
dashboardWindowController?.onWindowClose = {
[weak self] in
self?.dashboardWindowController = nil
}
}
dashboardWindowController?.set("http://127.0.0.1:9021")
dashboardWindowController?.showWindow(nil)
}
func applicationWillTerminate(_ notification: Notification) {
}
}

View File

@@ -5,12 +5,13 @@
//
import SwiftUI
import ClashX_Dashboard_Kit
@main
//@main
struct ClashX_DashboardApp: App {
var body: some Scene {
WindowGroup {
ContentView()
DashboardView()
}
.commands {
SidebarCommands()

View File

@@ -0,0 +1,684 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
</dependencies>
<scenes>
<!--Application-->
<scene sceneID="JPo-4y-FX3">
<objects>
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="Fire" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Fire" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About Fire" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide Fire" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit Fire" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="dMs-cI-mzQ">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="File" id="bib-Uj-vzu">
<items>
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
<connections>
<action selector="newDocument:" target="Ady-hI-5gd" id="4Si-XN-c54"/>
</connections>
</menuItem>
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="Ady-hI-5gd" id="bVn-NM-KNZ"/>
</connections>
</menuItem>
<menuItem title="Open Recent" id="tXI-mr-wws">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
<items>
<menuItem title="Clear Menu" id="vNY-rz-j42">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="Daa-9d-B3U"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
<connections>
<action selector="performClose:" target="Ady-hI-5gd" id="HmO-Ls-i7Q"/>
</connections>
</menuItem>
<menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
<connections>
<action selector="saveDocument:" target="Ady-hI-5gd" id="teZ-XB-qJY"/>
</connections>
</menuItem>
<menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
<connections>
<action selector="saveDocumentAs:" target="Ady-hI-5gd" id="mDf-zr-I0C"/>
</connections>
</menuItem>
<menuItem title="Revert to Saved" keyEquivalent="r" id="KaW-ft-85H">
<connections>
<action selector="revertDocumentToSaved:" target="Ady-hI-5gd" id="iJ3-Pv-kwq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
<menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="Ady-hI-5gd" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="print:" target="Ady-hI-5gd" id="qaZ-4w-aoO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Format" id="jxT-CU-nIS">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
<items>
<menuItem title="Font" id="Gi5-1S-RQB">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
<items>
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
<connections>
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
</connections>
</menuItem>
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
</connections>
</menuItem>
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
<connections>
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
</connections>
</menuItem>
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
<connections>
<action selector="underline:" target="Ady-hI-5gd" id="FYS-2b-JAY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
</connections>
</menuItem>
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
<connections>
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
<menuItem title="Kern" id="jBQ-r6-VK2">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
<items>
<menuItem title="Use Default" id="GUa-eO-cwY">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardKerning:" target="Ady-hI-5gd" id="6dk-9l-Ckg"/>
</connections>
</menuItem>
<menuItem title="Use None" id="cDB-IK-hbR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffKerning:" target="Ady-hI-5gd" id="U8a-gz-Maa"/>
</connections>
</menuItem>
<menuItem title="Tighten" id="46P-cB-AYj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="tightenKerning:" target="Ady-hI-5gd" id="hr7-Nz-8ro"/>
</connections>
</menuItem>
<menuItem title="Loosen" id="ogc-rX-tC1">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="loosenKerning:" target="Ady-hI-5gd" id="8i4-f9-FKE"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Ligatures" id="o6e-r0-MWq">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
<items>
<menuItem title="Use Default" id="agt-UL-0e3">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useStandardLigatures:" target="Ady-hI-5gd" id="7uR-wd-Dx6"/>
</connections>
</menuItem>
<menuItem title="Use None" id="J7y-lM-qPV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="turnOffLigatures:" target="Ady-hI-5gd" id="iX2-gA-Ilz"/>
</connections>
</menuItem>
<menuItem title="Use All" id="xQD-1f-W4t">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="useAllLigatures:" target="Ady-hI-5gd" id="KcB-kA-TuK"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Baseline" id="OaQ-X3-Vso">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
<items>
<menuItem title="Use Default" id="3Om-Ey-2VK">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unscript:" target="Ady-hI-5gd" id="0vZ-95-Ywn"/>
</connections>
</menuItem>
<menuItem title="Superscript" id="Rqc-34-cIF">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="superscript:" target="Ady-hI-5gd" id="3qV-fo-wpU"/>
</connections>
</menuItem>
<menuItem title="Subscript" id="I0S-gh-46l">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="subscript:" target="Ady-hI-5gd" id="Q6W-4W-IGz"/>
</connections>
</menuItem>
<menuItem title="Raise" id="2h7-ER-AoG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="raiseBaseline:" target="Ady-hI-5gd" id="4sk-31-7Q9"/>
</connections>
</menuItem>
<menuItem title="Lower" id="1tx-W0-xDw">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowerBaseline:" target="Ady-hI-5gd" id="OF1-bc-KW4"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
<connections>
<action selector="orderFrontColorPanel:" target="Ady-hI-5gd" id="mSX-Xz-DV3"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="copyFont:" target="Ady-hI-5gd" id="GJO-xA-L4q"/>
</connections>
</menuItem>
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteFont:" target="Ady-hI-5gd" id="JfD-CL-leO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Text" id="Fal-I4-PZk">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Text" id="d9c-me-L2H">
<items>
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
<connections>
<action selector="alignLeft:" target="Ady-hI-5gd" id="zUv-R1-uAa"/>
</connections>
</menuItem>
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
<connections>
<action selector="alignCenter:" target="Ady-hI-5gd" id="spX-mk-kcS"/>
</connections>
</menuItem>
<menuItem title="Justify" id="J5U-5w-g23">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="alignJustified:" target="Ady-hI-5gd" id="ljL-7U-jND"/>
</connections>
</menuItem>
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
<connections>
<action selector="alignRight:" target="Ady-hI-5gd" id="r48-bG-YeY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
<menuItem title="Writing Direction" id="H1b-Si-o9J">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
<items>
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="YGs-j5-SAR">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionNatural:" target="Ady-hI-5gd" id="qtV-5e-UBP"/>
</connections>
</menuItem>
<menuItem id="Lbh-J2-qVU">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="S0X-9S-QSf"/>
</connections>
</menuItem>
<menuItem id="jFq-tB-4Kx">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeBaseWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="5fk-qB-AqJ"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem id="Nop-cj-93Q">
<string key="title"> Default</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionNatural:" target="Ady-hI-5gd" id="lPI-Se-ZHp"/>
</connections>
</menuItem>
<menuItem id="BgM-ve-c93">
<string key="title"> Left to Right</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="caW-Bv-w94"/>
</connections>
</menuItem>
<menuItem id="RB4-Sm-HuC">
<string key="title"> Right to Left</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="makeTextWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="EXD-6r-ZUu"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
<menuItem title="Show Ruler" id="vLm-3I-IUL">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleRuler:" target="Ady-hI-5gd" id="FOx-HJ-KwY"/>
</connections>
</menuItem>
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="copyRuler:" target="Ady-hI-5gd" id="71i-fW-3W2"/>
</connections>
</menuItem>
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="pasteRuler:" target="Ady-hI-5gd" id="cSh-wd-qM2"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="toggleToolbarShown:" target="Ady-hI-5gd" id="BXY-wc-z0C"/>
</connections>
</menuItem>
<menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="runToolbarCustomizationPalette:" target="Ady-hI-5gd" id="pQI-g3-MTW"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="hB3-LF-h0Y"/>
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleSidebar:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
</connections>
</menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="Ady-hI-5gd" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="Ady-hI-5gd" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="Ady-hI-5gd" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="Ady-hI-5gd" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="Fire Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="ClashX_Dashboard" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
</scene>
</scenes>
</document>

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<tableCellView identifier="CollectionTableCellView" id="E1E-Yw-tO2">
<rect key="frame" x="0.0" y="0.0" width="100" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="EkO-g0-3Rf">
<rect key="frame" x="0.0" y="1" width="100" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="mZD-CG-RaE">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="EkO-g0-3Rf" secondAttribute="trailing" constant="2" id="cZb-2s-5cP"/>
<constraint firstItem="EkO-g0-3Rf" firstAttribute="centerY" secondItem="E1E-Yw-tO2" secondAttribute="centerY" id="g8v-Gi-bjM"/>
<constraint firstItem="EkO-g0-3Rf" firstAttribute="leading" secondItem="E1E-Yw-tO2" secondAttribute="leading" constant="2" id="nDQ-TP-ijK"/>
</constraints>
<accessibility identifier="CollectionTableCellView"/>
<connections>
<outlet property="textField" destination="EkO-g0-3Rf" id="Kzu-HK-Onq"/>
</connections>
<point key="canvasLocation" x="-108" y="-102"/>
</tableCellView>
</objects>
</document>

View File

@@ -1,78 +0,0 @@
//
// LogsView.swift
// ClashX Dashboard
//
//
import SwiftUI
struct LogsView: View {
@EnvironmentObject var logStorage: ClashLogStorage
@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)
}
.searchable(text: $searchString)
.toolbar {
ToolbarItem {
Picker("", selection: $logLevel) {
ForEach([
ClashLogLevel.silent,
.error,
.warning,
.info,
.debug
], id: \.self) {
Text($0.rawValue.capitalized).tag($0)
}
}
.pickerStyle(.menu)
.onChange(of: logLevel) { newValue in
guard newValue != ConfigManager.selectLoggingApiLevel else { return }
logStorage.logs.removeAll()
ConfigManager.selectLoggingApiLevel = newValue
ApiRequest.shared.resetLogStreamApi()
}
}
}
}
}
struct LogsView_Previews: PreviewProvider {
static var previews: some View {
LogsView()
}
}

View File

@@ -1,33 +0,0 @@
//
// SidebarItem.swift
// ClashX Dashboard
//
//
import Cocoa
import SwiftUI
class SidebarItems: ObservableObject, Identifiable {
let id = UUID()
@Published var items: [SidebarItem]
@Published var selectedIndex = 0
init(_ items: [SidebarItem]) {
self.items = items
}
}
class SidebarItem: ObservableObject {
let id = UUID()
let name: String
let icon: String
let view: AnyView
init(name: String, icon: String, view: AnyView) {
self.name = name
self.icon = icon
self.view = view
}
}

View File

@@ -1,28 +0,0 @@
//
// SidebarItemView.swift
// ClashX Dashboard
//
//
import SwiftUI
struct SidebarItemView: View {
@State var item: SidebarItem
@Binding var selectionName: String?
var body: some View {
NavigationLink(destination: item.view, tag: item.name, selection: $selectionName) {
Label(item.name, systemImage: item.icon)
}
}
}
//struct SidebarItemView_Previews: PreviewProvider {
// static var previews: some View {
// SidebarItemView(item: .init(name: "Overview",
// icon: "chart.bar.xaxis",
// view: AnyView(OverviewView())))
// }
//}

View File

@@ -1,68 +0,0 @@
//
// SidebarListView.swift
// ClashX Dashboard
//
//
import SwiftUI
struct SidebarListView: View {
@Binding var selectionName: String?
var body: some View {
List {
NavigationLink(destination: OverviewView(),
tag: "Overview",
selection: $selectionName) {
Label("Overview", systemImage: "chart.bar.xaxis")
}
NavigationLink(destination: ProxiesView(),
tag: "Proxies",
selection: $selectionName) {
Label("Proxies", systemImage: "globe.asia.australia")
}
NavigationLink(destination: ProvidersView(),
tag: "Providers",
selection: $selectionName) {
Label("Providers", systemImage: "link.icloud")
}
NavigationLink(destination: RulesView(),
tag: "Rules",
selection: $selectionName) {
Label("Rules", systemImage: "waveform.and.magnifyingglass")
}
NavigationLink(destination: ConnectionsView(),
tag: "Conns",
selection: $selectionName) {
Label("Conns", systemImage: "app.connected.to.app.below.fill")
}
NavigationLink(destination: ConfigView(),
tag: "Config",
selection: $selectionName) {
Label("Config", systemImage: "slider.horizontal.3")
}
NavigationLink(destination: LogsView(),
tag: "Logs",
selection: $selectionName) {
Label("Logs", systemImage: "wand.and.stars.inverse")
}
}
.listStyle(.sidebar)
}
}
//struct SidebarListView_Previews: PreviewProvider {
// static var previews: some View {
// SidebarListView()
// }
//}