diff --git a/ClashX Dashboard Kit/.gitignore b/ClashX Dashboard Kit/.gitignore new file mode 100644 index 0000000..3b29812 --- /dev/null +++ b/ClashX Dashboard Kit/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/ClashX Dashboard Kit/Package.swift b/ClashX Dashboard Kit/Package.swift new file mode 100644 index 0000000..fee3204 --- /dev/null +++ b/ClashX Dashboard Kit/Package.swift @@ -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"]), + ] +) diff --git a/ClashX Dashboard Kit/README.md b/ClashX Dashboard Kit/README.md new file mode 100644 index 0000000..d3e3905 --- /dev/null +++ b/ClashX Dashboard Kit/README.md @@ -0,0 +1,3 @@ +# ClashX Dashboard Kit + +A description of this package. diff --git a/ClashX Dashboard/ClashX Links/ApiRequest.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/ApiRequest.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/ApiRequest.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/ApiRequest.swift diff --git a/ClashX Dashboard/ClashX Links/Extensions/DateFormatter+.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Extensions/DateFormatter+.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/Extensions/DateFormatter+.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Extensions/DateFormatter+.swift diff --git a/ClashX Dashboard/ClashX Links/Extensions/String+Encode.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Extensions/String+Encode.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/Extensions/String+Encode.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Extensions/String+Encode.swift diff --git a/ClashX Dashboard/ClashX Links/General/Managers/ConfigManager.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/General/Managers/ConfigManager.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/General/Managers/ConfigManager.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/General/Managers/ConfigManager.swift diff --git a/ClashX Dashboard/ClashX Links/Logger.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Logger.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/Logger.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Logger.swift diff --git a/ClashX Dashboard/ClashX Links/Models/ClashConfig.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashConfig.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/Models/ClashConfig.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashConfig.swift diff --git a/ClashX Dashboard/ClashX Links/Models/ClashConnection.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashConnection.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/Models/ClashConnection.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashConnection.swift diff --git a/ClashX Dashboard/ClashX Links/Models/ClashProvider.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashProvider.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/Models/ClashProvider.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashProvider.swift diff --git a/ClashX Dashboard/ClashX Links/Models/ClashProxy.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashProxy.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/Models/ClashProxy.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashProxy.swift diff --git a/ClashX Dashboard/ClashX Links/Models/ClashRule.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashRule.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/Models/ClashRule.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashRule.swift diff --git a/ClashX Dashboard/ClashX Links/Models/ClashRuleProvider.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashRuleProvider.swift similarity index 100% rename from ClashX Dashboard/ClashX Links/Models/ClashRuleProvider.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX Links/Models/ClashRuleProvider.swift diff --git a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX_Dashboard_Kit.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX_Dashboard_Kit.swift new file mode 100644 index 0000000..de23b0d --- /dev/null +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/ClashX_Dashboard_Kit.swift @@ -0,0 +1,13 @@ +import SwiftUI + +@available(macOS 12.0, *) +public struct DashboardView: View { + + public init() { + + } + + public var body: some View { + ContentView() + } +} diff --git a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/DashboardViewContoller.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/DashboardViewContoller.swift new file mode 100644 index 0000000..b7af820 --- /dev/null +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/DashboardViewContoller.swift @@ -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 + ] + } +} diff --git a/ClashX Dashboard/Models/DBConnectionSnapShot.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Models/DBConnectionSnapShot.swift similarity index 100% rename from ClashX Dashboard/Models/DBConnectionSnapShot.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Models/DBConnectionSnapShot.swift diff --git a/ClashX Dashboard/Models/DBProviderStorage.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Models/DBProviderStorage.swift similarity index 100% rename from ClashX Dashboard/Models/DBProviderStorage.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Models/DBProviderStorage.swift diff --git a/ClashX Dashboard/Models/DBProxyStorage.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Models/DBProxyStorage.swift similarity index 100% rename from ClashX Dashboard/Models/DBProxyStorage.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Models/DBProxyStorage.swift diff --git a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/NotificationNames.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/NotificationNames.swift new file mode 100644 index 0000000..a3cf2a5 --- /dev/null +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/NotificationNames.swift @@ -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") +} diff --git a/ClashX Dashboard/Views/APISetting/APIServerItem.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/APISetting/APIServerItem.swift similarity index 100% rename from ClashX Dashboard/Views/APISetting/APIServerItem.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/APISetting/APIServerItem.swift diff --git a/ClashX Dashboard/Views/APISetting/APISettingView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/APISetting/APISettingView.swift similarity index 100% rename from ClashX Dashboard/Views/APISetting/APISettingView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/APISetting/APISettingView.swift diff --git a/ClashX Dashboard/Views/APISetting/ClashServerAppStorage.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/APISetting/ClashServerAppStorage.swift similarity index 100% rename from ClashX Dashboard/Views/APISetting/ClashServerAppStorage.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/APISetting/ClashServerAppStorage.swift diff --git a/ClashX Dashboard/Views/APISetting/ProgressButton.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/APISetting/ProgressButton.swift similarity index 100% rename from ClashX Dashboard/Views/APISetting/ProgressButton.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/APISetting/ProgressButton.swift diff --git a/ClashX Dashboard/Views/ArrayExtensions.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ArrayExtensions.swift similarity index 100% rename from ClashX Dashboard/Views/ArrayExtensions.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ArrayExtensions.swift diff --git a/ClashX Dashboard/Views/ClashApiDatasStorage.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ClashApiDatasStorage.swift similarity index 91% rename from ClashX Dashboard/Views/ClashApiDatasStorage.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ClashApiDatasStorage.swift index 6577606..addab5a 100644 --- a/ClashX Dashboard/Views/ClashApiDatasStorage.swift +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ClashApiDatasStorage.swift @@ -7,6 +7,7 @@ import Cocoa import SwiftUI import CocoaLumberjackSwift +import DifferenceKit class ClashApiDatasStorage: NSObject, ObservableObject { @@ -125,14 +126,17 @@ class ClashOverviewData: ObservableObject, Identifiable { class ClashLogStorage: ObservableObject { @Published var logs = [ClashLog]() - class ClashLog: NSObject, ObservableObject, Identifiable { + class ClashLog: NSObject, ObservableObject, Identifiable, Differentiable { let id: String + var differenceIdentifier: String { + return id + } let date: Date let level: ClashLogLevel @objc let log: String - let levelColor: Color + let levelColor: NSColor @objc let levelString: String init(level: String, log: String) { @@ -144,13 +148,13 @@ class ClashLogStorage: ObservableObject { self.levelString = level switch self.level { case .info: - levelColor = .blue + levelColor = .systemBlue case .warning: - levelColor = .yellow + levelColor = .systemYellow case .error: - levelColor = .red + levelColor = .systemRed case .debug: - levelColor = .green + levelColor = .systemGreen default: levelColor = .white } diff --git a/ClashX Dashboard/Views/ContentTabs/Config/ConfigItemView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Config/ConfigItemView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Config/ConfigItemView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Config/ConfigItemView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Config/ConfigView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Config/ConfigView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Config/ConfigView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Config/ConfigView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Connections/CollectionsTableView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Connections/CollectionsTableView.swift similarity index 82% rename from ClashX Dashboard/Views/ContentTabs/Connections/CollectionsTableView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Connections/CollectionsTableView.swift index 20e07ed..1c81bf9 100644 --- a/ClashX Dashboard/Views/ContentTabs/Connections/CollectionsTableView.swift +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Connections/CollectionsTableView.swift @@ -48,6 +48,7 @@ struct CollectionsTableView: 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: 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: 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: NSViewRepresentable { } }() - return cell + return cellView } func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) { @@ -216,6 +216,7 @@ struct CollectionsTableView: 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 + } + } diff --git a/ClashX Dashboard/Views/ContentTabs/Connections/Connections.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Connections/Connections.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Connections/Connections.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Connections/Connections.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Connections/ConnectionsView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Connections/ConnectionsView.swift similarity index 64% rename from ClashX Dashboard/Views/ContentTabs/Connections/ConnectionsView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Connections/ConnectionsView.swift index bee6d84..b4c6d9b 100644 --- a/ClashX Dashboard/Views/ContentTabs/Connections/ConnectionsView.swift +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Connections/ConnectionsView.swift @@ -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 { diff --git a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsTableView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsTableView.swift new file mode 100644 index 0000000..b7ea20c --- /dev/null +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsTableView.swift @@ -0,0 +1,158 @@ +// +// LogsTableView.swift +// +// +// + +import Cocoa +import SwiftUI +import DifferenceKit + +struct LogsTableView: NSViewRepresentable { + + enum TableColumn: String, CaseIterable { + case date = "Date" + case level = "Level" + case log = "Log" + } + + var data: [Item] + var filterString: String + + class NonRespondingScrollView: NSScrollView { + override var acceptsFirstResponder: Bool { false } + } + + class NonRespondingTableView: NSTableView { + override var acceptsFirstResponder: Bool { false } + } + + func makeNSView(context: Context) -> NSScrollView { + + let scrollView = NonRespondingScrollView() + scrollView.hasVerticalScroller = true + scrollView.hasHorizontalScroller = false + scrollView.autohidesScrollers = true + + let tableView = NonRespondingTableView() + tableView.usesAlternatingRowBackgroundColors = true + + tableView.delegate = context.coordinator + tableView.dataSource = context.coordinator + + TableColumn.allCases.forEach { + let tableColumn = NSTableColumn(identifier: .init($0.rawValue)) + tableColumn.title = $0.rawValue + tableColumn.isEditable = false + + switch $0 { + case .date: + tableColumn.minWidth = 60 + tableColumn.maxWidth = 140 + tableColumn.width = 135 + case .level: + tableColumn.minWidth = 40 + tableColumn.maxWidth = 65 + default: + tableColumn.minWidth = 120 + tableColumn.maxWidth = .infinity + } + + tableView.addTableColumn(tableColumn) + } + + scrollView.documentView = tableView + + return scrollView + } + + func updateNSView(_ nsView: NSScrollView, context: Context) { + context.coordinator.parent = self + guard let tableView = nsView.documentView as? NSTableView, + let data = data as? [ClashLogStorage.ClashLog] else { + return + } + + let target = updateSorts(data, tableView: tableView) + + let source = context.coordinator.logs + let changeset = StagedChangeset(source: source, target: target) + + + tableView.reload(using: changeset) { data in + context.coordinator.logs = data + } + } + + func updateSorts(_ objects: [ClashLogStorage.ClashLog], + tableView: NSTableView) -> [ClashLogStorage.ClashLog] { + var re = objects + + let filterKeys = [ + "levelString", + "log", + ] + + re = re.filtered(filterString, for: filterKeys) + + return re + } + + + func makeCoordinator() -> Coordinator { + Coordinator(parent: self) + } + + + class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource { + + var parent: LogsTableView + var logs = [ClashLogStorage.ClashLog]() + + let dateFormatter = { + let df = DateFormatter() + df.dateFormat = "MM/dd HH:mm:ss.SSS" + return df + }() + + + init(parent: LogsTableView) { + self.parent = parent + } + + + func numberOfRows(in tableView: NSTableView) -> Int { + logs.count + } + + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + + guard let cellView = tableView.createCellView(with: "LogsTableCellView"), + let s = tableColumn?.identifier.rawValue.split(separator: ".").last, + let tc = TableColumn(rawValue: String(s)) + else { return nil } + + let log = logs[row] + let tf = cellView.textField + + switch tc { + case .date: + tf?.lineBreakMode = .byTruncatingHead + tf?.textColor = .orange + tf?.stringValue = dateFormatter.string(from: log.date) + case .level: + tf?.lineBreakMode = .byTruncatingTail + tf?.textColor = log.levelColor + tf?.stringValue = log.levelString + case .log: + tf?.lineBreakMode = .byTruncatingTail + tf?.textColor = .labelColor + tf?.stringValue = log.log + } + + return cellView + } + + } +} diff --git a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsView.swift new file mode 100644 index 0000000..5928e32 --- /dev/null +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Logs/LogsView.swift @@ -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() + } +} diff --git a/ClashX Dashboard/Views/ContentTabs/Overview/OverviewTopItemView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Overview/OverviewTopItemView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Overview/OverviewTopItemView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Overview/OverviewTopItemView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Overview/OverviewView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Overview/OverviewView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Overview/OverviewView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Overview/OverviewView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Overview/TrafficGraphView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Overview/TrafficGraphView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Overview/TrafficGraphView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Overview/TrafficGraphView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Providers/ProviderProxiesView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProviderProxiesView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Providers/ProviderProxiesView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProviderProxiesView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Providers/ProviderRowView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProviderRowView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Providers/ProviderRowView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProviderRowView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Providers/ProvidersView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProvidersView.swift similarity index 85% rename from ClashX Dashboard/Views/ContentTabs/Providers/ProvidersView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProvidersView.swift index d4a2bc7..3ab574d 100644 --- a/ClashX Dashboard/Views/ContentTabs/Providers/ProvidersView.swift +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProvidersView.swift @@ -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() diff --git a/ClashX Dashboard/Views/ContentTabs/Providers/ProxyProviderInfoView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProxyProviderInfoView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Providers/ProxyProviderInfoView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProxyProviderInfoView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Providers/ProxyProvidersRowView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProxyProvidersRowView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Providers/ProxyProvidersRowView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/ProxyProvidersRowView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Providers/RuleProviderView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/RuleProviderView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Providers/RuleProviderView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/RuleProviderView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Providers/RuleProvidersRowView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/RuleProvidersRowView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Providers/RuleProvidersRowView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Providers/RuleProvidersRowView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Proxies/ProxiesView.swift similarity index 80% rename from ClashX Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Proxies/ProxiesView.swift index 77ebb09..48790e1 100644 --- a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxiesView.swift +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Proxies/ProxiesView.swift @@ -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() diff --git a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupRowView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Proxies/ProxyGroupRowView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupRowView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Proxies/ProxyGroupRowView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Proxies/ProxyGroupView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Proxies/ProxyGroupView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Proxies/ProxyGroupView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Proxies/ProxyItemView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Proxies/ProxyItemView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Proxies/ProxyItemView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Proxies/ProxyItemView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Rules/RuleItemView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Rules/RuleItemView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentTabs/Rules/RuleItemView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Rules/RuleItemView.swift diff --git a/ClashX Dashboard/Views/ContentTabs/Rules/RulesView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Rules/RulesView.swift similarity index 81% rename from ClashX Dashboard/Views/ContentTabs/Rules/RulesView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Rules/RulesView.swift index c122ae0..4b000aa 100644 --- a/ClashX Dashboard/Views/ContentTabs/Rules/RulesView.swift +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentTabs/Rules/RulesView.swift @@ -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 { diff --git a/ClashX Dashboard/Views/ContentView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentView.swift similarity index 100% rename from ClashX Dashboard/Views/ContentView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/ContentView.swift diff --git a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SidebarView/SidebarItem.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SidebarView/SidebarItem.swift new file mode 100644 index 0000000..2d4514b --- /dev/null +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SidebarView/SidebarItem.swift @@ -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" +} diff --git a/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SidebarView/SidebarListView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SidebarView/SidebarListView.swift new file mode 100644 index 0000000..e19e4c0 --- /dev/null +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SidebarView/SidebarListView.swift @@ -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() +// } +//} diff --git a/ClashX Dashboard/Views/SidebarView/SidebarView.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SidebarView/SidebarView.swift similarity index 80% rename from ClashX Dashboard/Views/SidebarView/SidebarView.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SidebarView/SidebarView.swift index 33128da..3a847c9 100644 --- a/ClashX Dashboard/Views/SidebarView/SidebarView.swift +++ b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SidebarView/SidebarView.swift @@ -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 { diff --git a/ClashX Dashboard/Views/SwiftUIViewExtensions.swift b/ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SwiftUIViewExtensions.swift similarity index 100% rename from ClashX Dashboard/Views/SwiftUIViewExtensions.swift rename to ClashX Dashboard Kit/Sources/ClashX Dashboard Kit/Views/SwiftUIViewExtensions.swift diff --git a/ClashX Dashboard Kit/Tests/ClashX Dashboard KitTests/ClashX_Dashboard_KitTests.swift b/ClashX Dashboard Kit/Tests/ClashX Dashboard KitTests/ClashX_Dashboard_KitTests.swift new file mode 100644 index 0000000..3638d71 --- /dev/null +++ b/ClashX Dashboard Kit/Tests/ClashX Dashboard KitTests/ClashX_Dashboard_KitTests.swift @@ -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!") + } +} diff --git a/ClashX Dashboard.xcodeproj/project.pbxproj b/ClashX Dashboard.xcodeproj/project.pbxproj index db35c82..023a167 100644 --- a/ClashX Dashboard.xcodeproj/project.pbxproj +++ b/ClashX Dashboard.xcodeproj/project.pbxproj @@ -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 = ""; }; - 01359A382A21D88B00A2B3FB /* SidebarListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarListView.swift; sourceTree = ""; }; - 01505C492A147B84001ACC4F /* DBProviderStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBProviderStorage.swift; sourceTree = ""; }; - 01505C4B2A14A1A3001ACC4F /* ProviderRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderRowView.swift; sourceTree = ""; }; - 01505C4D2A14AAEB001ACC4F /* ProviderProxiesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProxiesView.swift; sourceTree = ""; }; - 015278072A15F9FD00516236 /* ProxyProviderInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyProviderInfoView.swift; sourceTree = ""; }; - 0155D39529F2342F00869830 /* TrafficGraphView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficGraphView.swift; sourceTree = ""; }; - 0155D39729F23BDE00869830 /* OverviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverviewView.swift; sourceTree = ""; }; - 0172CB5029E5AE670072DDEF /* SwiftUIViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIViewExtensions.swift; sourceTree = ""; }; - 0172F12C2A1FB06100EE2B6D /* DateFormatter+.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; - 0172F12E2A1FB06100EE2B6D /* String+Encode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Encode.swift"; sourceTree = ""; }; - 0172F1342A1FB0B900EE2B6D /* ConfigManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigManager.swift; sourceTree = ""; }; - 0172F1362A1FB0CD00EE2B6D /* ApiRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiRequest.swift; sourceTree = ""; }; - 0172F1382A1FB0E900EE2B6D /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; - 0172F13B2A1FB10C00EE2B6D /* DBConnectionSnapShot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DBConnectionSnapShot.swift; sourceTree = ""; }; - 0172F13C2A1FB10D00EE2B6D /* ClashRuleProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashRuleProvider.swift; sourceTree = ""; }; - 0172F13D2A1FB10D00EE2B6D /* ClashRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashRule.swift; sourceTree = ""; }; - 0172F13E2A1FB10D00EE2B6D /* ClashConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConfig.swift; sourceTree = ""; }; - 0172F13F2A1FB10D00EE2B6D /* ClashProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashProvider.swift; sourceTree = ""; }; - 0172F1402A1FB10D00EE2B6D /* ClashProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashProxy.swift; sourceTree = ""; }; - 0172F1472A1FB90200EE2B6D /* ClashConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConnection.swift; sourceTree = ""; }; - 017753BF29EF7FB1006999DB /* APIServerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIServerItem.swift; sourceTree = ""; }; - 017DCADC29E83BFD00B9622A /* RuleProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleProviderView.swift; sourceTree = ""; }; - 017F9AAB2A0E0B2300B81497 /* ProxyGroupRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupRowView.swift; sourceTree = ""; }; - 018003B02A136DDB0070226E /* ProvidersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProvidersView.swift; sourceTree = ""; }; - 018A61BC29E9A2ED008608C0 /* APISettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APISettingView.swift; sourceTree = ""; }; - 018AFEA42A1B5F7A0076E66B /* ProgressButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressButton.swift; sourceTree = ""; }; - 018C836B29E17505006366D3 /* ClashApiDatasStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClashApiDatasStorage.swift; sourceTree = ""; }; + 011BFE402A2799550027AD15 /* ClashX Dashboard Kit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = "ClashX Dashboard Kit"; sourceTree = ""; }; 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 = ""; }; - 0192316029DD4DCF00539EDD /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 0192316229DD4DD100539EDD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0192316529DD4DD100539EDD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 0192316729DD4DD100539EDD /* ClashX_Dashboard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ClashX_Dashboard.entitlements; sourceTree = ""; }; - 0192317029DD566000539EDD /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; - 0192317729DD5DA500539EDD /* ProxiesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxiesView.swift; sourceTree = ""; }; - 0192317929DD5DB000539EDD /* RulesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RulesView.swift; sourceTree = ""; }; - 0192317B29DD5DF200539EDD /* ConnectionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsView.swift; sourceTree = ""; }; - 0192317D29DD5E0100539EDD /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = ""; }; - 0192317F29DD5E0B00539EDD /* LogsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsView.swift; sourceTree = ""; }; - 0192318229DD70B400539EDD /* SidebarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItem.swift; sourceTree = ""; }; - 0192318429DD7DCD00539EDD /* SidebarItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarItemView.swift; sourceTree = ""; }; - 0192318629DD83FF00539EDD /* OverviewTopItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverviewTopItemView.swift; sourceTree = ""; }; - 019D6A8629F015DF00A6AC02 /* ArrayExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtensions.swift; sourceTree = ""; }; - 01A351A129DD8F440054894E /* RuleItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleItemView.swift; sourceTree = ""; }; - 01A351A829DD9CB00054894E /* Connections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connections.swift; sourceTree = ""; }; - 01A3EF032A120103003038B5 /* DBProxyStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBProxyStorage.swift; sourceTree = ""; }; - 01DCEFB02A150E8B00DBBDB3 /* RuleProvidersRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleProvidersRowView.swift; sourceTree = ""; }; - 01DCEFB22A150FB300DBBDB3 /* ProxyProvidersRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyProvidersRowView.swift; sourceTree = ""; }; - 01F5E3F32A1E53F4008F3DEB /* ConfigItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigItemView.swift; sourceTree = ""; }; - 01F885CE29DFD8DF008241EB /* CollectionsTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionsTableView.swift; sourceTree = ""; }; - 01F885D029E03F20008241EB /* CollectionTableCellView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CollectionTableCellView.xib; sourceTree = ""; }; - 01F885D229E04E21008241EB /* ProxyGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupView.swift; sourceTree = ""; }; - 01F885D429E053DE008241EB /* ProxyItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyItemView.swift; sourceTree = ""; }; + 01A78B7E2A2B188E0058D1CA /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + 01FE884B2A29BB590084902C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; /* 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 = ""; - }; - 0172F12B2A1FB05900EE2B6D /* Extensions */ = { - isa = PBXGroup; - children = ( - 0172F12C2A1FB06100EE2B6D /* DateFormatter+.swift */, - 0172F12E2A1FB06100EE2B6D /* String+Encode.swift */, - ); - path = Extensions; - sourceTree = ""; - }; - 0172F1322A1FB0AA00EE2B6D /* General */ = { - isa = PBXGroup; - children = ( - 0172F1332A1FB0B000EE2B6D /* Managers */, - ); - path = General; - sourceTree = ""; - }; - 0172F1332A1FB0B000EE2B6D /* Managers */ = { - isa = PBXGroup; - children = ( - 0172F1342A1FB0B900EE2B6D /* ConfigManager.swift */, - ); - path = Managers; - sourceTree = ""; - }; - 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 = ""; - }; - 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 = ""; - }; - 018C836929E1703D006366D3 /* Logs */ = { - isa = PBXGroup; - children = ( - 0192317F29DD5E0B00539EDD /* LogsView.swift */, - ); - path = Logs; - sourceTree = ""; - }; - 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 = ""; }; 0192315229DD4DCF00539EDD = { isa = PBXGroup; children = ( + 011BFE402A2799550027AD15 /* ClashX Dashboard Kit */, 0192315D29DD4DCF00539EDD /* ClashX Dashboard */, 0192315C29DD4DCF00539EDD /* Products */, + 011BFE442A279D1C0027AD15 /* Frameworks */, ); sourceTree = ""; }; @@ -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 = ""; }; - 0192317429DD5D7400539EDD /* ContentTabs */ = { - isa = PBXGroup; - children = ( - 01A351A529DD9B2D0054894E /* Overview */, - 01A351A629DD9BB50054894E /* Proxies */, - 018003AF2A136D3E0070226E /* Providers */, - 01A351A029DD8F210054894E /* Rules */, - 01A351A729DD9C7F0054894E /* Connections */, - 01A7335F29E2CBD600205699 /* Config */, - 018C836929E1703D006366D3 /* Logs */, - ); - path = ContentTabs; - sourceTree = ""; - }; - 0192318129DD709500539EDD /* SidebarView */ = { - isa = PBXGroup; - children = ( - 0192317029DD566000539EDD /* SidebarView.swift */, - 01359A382A21D88B00A2B3FB /* SidebarListView.swift */, - 0192318429DD7DCD00539EDD /* SidebarItemView.swift */, - 0192318229DD70B400539EDD /* SidebarItem.swift */, - ); - path = SidebarView; - sourceTree = ""; - }; - 0192B5B529DE506D002CDBF3 /* ClashX Links */ = { - isa = PBXGroup; - children = ( - 0172F1362A1FB0CD00EE2B6D /* ApiRequest.swift */, - 0172F1382A1FB0E900EE2B6D /* Logger.swift */, - 0172F1322A1FB0AA00EE2B6D /* General */, - 0172F12B2A1FB05900EE2B6D /* Extensions */, - 0172F13A2A1FB10300EE2B6D /* Models */, - ); - path = "ClashX Links"; - sourceTree = ""; - }; - 01A351A029DD8F210054894E /* Rules */ = { - isa = PBXGroup; - children = ( - 0192317929DD5DB000539EDD /* RulesView.swift */, - 01A351A129DD8F440054894E /* RuleItemView.swift */, - ); - path = Rules; - sourceTree = ""; - }; - 01A351A529DD9B2D0054894E /* Overview */ = { - isa = PBXGroup; - children = ( - 0155D39729F23BDE00869830 /* OverviewView.swift */, - 0192318629DD83FF00539EDD /* OverviewTopItemView.swift */, - 0155D39529F2342F00869830 /* TrafficGraphView.swift */, - ); - path = Overview; - sourceTree = ""; - }; - 01A351A629DD9BB50054894E /* Proxies */ = { - isa = PBXGroup; - children = ( - 0192317729DD5DA500539EDD /* ProxiesView.swift */, - 017F9AAB2A0E0B2300B81497 /* ProxyGroupRowView.swift */, - 01F885D229E04E21008241EB /* ProxyGroupView.swift */, - 01F885D429E053DE008241EB /* ProxyItemView.swift */, - ); - path = Proxies; - sourceTree = ""; - }; - 01A351A729DD9C7F0054894E /* Connections */ = { - isa = PBXGroup; - children = ( - 0192317B29DD5DF200539EDD /* ConnectionsView.swift */, - 01A351A829DD9CB00054894E /* Connections.swift */, - 01F885CE29DFD8DF008241EB /* CollectionsTableView.swift */, - 01F885D029E03F20008241EB /* CollectionTableCellView.xib */, - ); - path = Connections; - sourceTree = ""; - }; - 01A3EF022A120032003038B5 /* Models */ = { - isa = PBXGroup; - children = ( - 01A3EF032A120103003038B5 /* DBProxyStorage.swift */, - 01505C492A147B84001ACC4F /* DBProviderStorage.swift */, - 0172F13B2A1FB10C00EE2B6D /* DBConnectionSnapShot.swift */, - ); - path = Models; - sourceTree = ""; - }; - 01A7335F29E2CBD600205699 /* Config */ = { - isa = PBXGroup; - children = ( - 0192317D29DD5E0100539EDD /* ConfigView.swift */, - 01F5E3F32A1E53F4008F3DEB /* ConfigItemView.swift */, - ); - path = Config; - sourceTree = ""; - }; /* 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 */ }; diff --git a/ClashX Dashboard/AppDelegate.swift b/ClashX Dashboard/AppDelegate.swift new file mode 100644 index 0000000..1d1aebb --- /dev/null +++ b/ClashX Dashboard/AppDelegate.swift @@ -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) { + + } +} diff --git a/ClashX Dashboard/ClashX_DashboardApp.swift b/ClashX Dashboard/ClashX_DashboardApp.swift index c291fba..f492a15 100644 --- a/ClashX Dashboard/ClashX_DashboardApp.swift +++ b/ClashX Dashboard/ClashX_DashboardApp.swift @@ -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() diff --git a/ClashX Dashboard/Main.storyboard b/ClashX Dashboard/Main.storyboard new file mode 100644 index 0000000..77e2634 --- /dev/null +++ b/ClashX Dashboard/Main.storyboard @@ -0,0 +1,684 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ClashX Dashboard/Views/ContentTabs/Connections/CollectionTableCellView.xib b/ClashX Dashboard/Views/ContentTabs/Connections/CollectionTableCellView.xib deleted file mode 100644 index aab0fcd..0000000 --- a/ClashX Dashboard/Views/ContentTabs/Connections/CollectionTableCellView.xib +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ClashX Dashboard/Views/ContentTabs/Logs/LogsView.swift b/ClashX Dashboard/Views/ContentTabs/Logs/LogsView.swift deleted file mode 100644 index 5b11790..0000000 --- a/ClashX Dashboard/Views/ContentTabs/Logs/LogsView.swift +++ /dev/null @@ -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() - } -} diff --git a/ClashX Dashboard/Views/SidebarView/SidebarItem.swift b/ClashX Dashboard/Views/SidebarView/SidebarItem.swift deleted file mode 100644 index c445966..0000000 --- a/ClashX Dashboard/Views/SidebarView/SidebarItem.swift +++ /dev/null @@ -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 - } -} diff --git a/ClashX Dashboard/Views/SidebarView/SidebarItemView.swift b/ClashX Dashboard/Views/SidebarView/SidebarItemView.swift deleted file mode 100644 index 3f6fdf8..0000000 --- a/ClashX Dashboard/Views/SidebarView/SidebarItemView.swift +++ /dev/null @@ -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()))) -// } -//} diff --git a/ClashX Dashboard/Views/SidebarView/SidebarListView.swift b/ClashX Dashboard/Views/SidebarView/SidebarListView.swift deleted file mode 100644 index 80ae113..0000000 --- a/ClashX Dashboard/Views/SidebarView/SidebarListView.swift +++ /dev/null @@ -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() -// } -//}