mirror of
https://github.com/yJason/ClashX-Dashboard.git
synced 2026-02-04 10:02:26 +08:00
feat: AppKit toolbar
This commit is contained in:
@@ -0,0 +1,270 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 .conns, .rules:
|
||||||
|
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
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// NotificationNames.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension NSNotification.Name {
|
||||||
|
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")
|
||||||
|
}
|
||||||
@@ -18,16 +18,27 @@ struct ConnectionsView: View {
|
|||||||
filterString: searchString)
|
filterString: searchString)
|
||||||
.background(Color(nsColor: .textBackgroundColor))
|
.background(Color(nsColor: .textBackgroundColor))
|
||||||
.searchable(text: $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: .stopConns)) { _ in
|
||||||
|
stopConns()
|
||||||
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem {
|
ToolbarItem {
|
||||||
Button {
|
Button {
|
||||||
ApiRequest.closeAllConnection()
|
stopConns()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "stop.circle.fill")
|
Image(systemName: "stop.circle.fill")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stopConns() {
|
||||||
|
ApiRequest.closeAllConnection()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ConnectionsView_Previews: PreviewProvider {
|
struct ConnectionsView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ struct LogsView: View {
|
|||||||
TableColumn("", value: \.log)
|
TableColumn("", value: \.log)
|
||||||
}
|
}
|
||||||
.searchable(text: $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 {
|
.toolbar {
|
||||||
ToolbarItem {
|
ToolbarItem {
|
||||||
Picker("", selection: $logLevel) {
|
Picker("", selection: $logLevel) {
|
||||||
@@ -62,13 +70,17 @@ struct LogsView: View {
|
|||||||
.pickerStyle(.menu)
|
.pickerStyle(.menu)
|
||||||
.onChange(of: logLevel) { newValue in
|
.onChange(of: logLevel) { newValue in
|
||||||
guard newValue != ConfigManager.selectLoggingApiLevel else { return }
|
guard newValue != ConfigManager.selectLoggingApiLevel else { return }
|
||||||
logStorage.logs.removeAll()
|
logLevelChanged(newValue)
|
||||||
ConfigManager.selectLoggingApiLevel = newValue
|
|
||||||
ApiRequest.shared.resetLogStreamApi()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logLevelChanged(_ level: ClashLogLevel) {
|
||||||
|
logStorage.logs.removeAll()
|
||||||
|
ConfigManager.selectLoggingApiLevel = level
|
||||||
|
ApiRequest.shared.resetLogStreamApi()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LogsView_Previews: PreviewProvider {
|
struct LogsView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ struct ProvidersView: View {
|
|||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
.searchable(text: $searchString.string)
|
.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)
|
.environmentObject(searchString)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
loadProviders()
|
loadProviders()
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ struct ProxiesView: View {
|
|||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
.searchable(text: $searchString.string)
|
.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)
|
.environmentObject(searchString)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
loadProxies()
|
loadProxies()
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ struct RulesView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.searchable(text: $searchString)
|
.searchable(text: $searchString)
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: .toolbarSearchString)) {
|
||||||
|
guard let string = $0.userInfo?["String"] as? String else { return }
|
||||||
|
searchString = string
|
||||||
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
ruleItems.removeAll()
|
ruleItems.removeAll()
|
||||||
ApiRequest.getRules {
|
ApiRequest.getRules {
|
||||||
|
|||||||
@@ -7,27 +7,12 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
enum SidebarItem: String {
|
||||||
class SidebarItems: ObservableObject, Identifiable {
|
case overview = "Overview"
|
||||||
let id = UUID()
|
case proxies = "Proxies"
|
||||||
@Published var items: [SidebarItem]
|
case providers = "Providers"
|
||||||
@Published var selectedIndex = 0
|
case rules = "Rules"
|
||||||
|
case conns = "Conns"
|
||||||
init(_ items: [SidebarItem]) {
|
case config = "Config"
|
||||||
self.items = items
|
case logs = "Logs"
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())))
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -14,45 +14,45 @@ struct SidebarListView: View {
|
|||||||
List {
|
List {
|
||||||
|
|
||||||
NavigationLink(destination: OverviewView(),
|
NavigationLink(destination: OverviewView(),
|
||||||
tag: "Overview",
|
tag: SidebarItem.overview.rawValue,
|
||||||
selection: $selectionName) {
|
selection: $selectionName) {
|
||||||
Label("Overview", systemImage: "chart.bar.xaxis")
|
Label(SidebarItem.overview.rawValue, systemImage: "chart.bar.xaxis")
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(destination: ProxiesView(),
|
NavigationLink(destination: ProxiesView(),
|
||||||
tag: "Proxies",
|
tag: SidebarItem.proxies.rawValue,
|
||||||
selection: $selectionName) {
|
selection: $selectionName) {
|
||||||
Label("Proxies", systemImage: "globe.asia.australia")
|
Label(SidebarItem.proxies.rawValue, systemImage: "globe.asia.australia")
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(destination: ProvidersView(),
|
NavigationLink(destination: ProvidersView(),
|
||||||
tag: "Providers",
|
tag: SidebarItem.providers.rawValue,
|
||||||
selection: $selectionName) {
|
selection: $selectionName) {
|
||||||
Label("Providers", systemImage: "link.icloud")
|
Label(SidebarItem.providers.rawValue, systemImage: "link.icloud")
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(destination: RulesView(),
|
NavigationLink(destination: RulesView(),
|
||||||
tag: "Rules",
|
tag: SidebarItem.rules.rawValue,
|
||||||
selection: $selectionName) {
|
selection: $selectionName) {
|
||||||
Label("Rules", systemImage: "waveform.and.magnifyingglass")
|
Label(SidebarItem.rules.rawValue, systemImage: "waveform.and.magnifyingglass")
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(destination: ConnectionsView(),
|
NavigationLink(destination: ConnectionsView(),
|
||||||
tag: "Conns",
|
tag: SidebarItem.conns.rawValue,
|
||||||
selection: $selectionName) {
|
selection: $selectionName) {
|
||||||
Label("Conns", systemImage: "app.connected.to.app.below.fill")
|
Label(SidebarItem.conns.rawValue, systemImage: "app.connected.to.app.below.fill")
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(destination: ConfigView(),
|
NavigationLink(destination: ConfigView(),
|
||||||
tag: "Config",
|
tag: SidebarItem.config.rawValue,
|
||||||
selection: $selectionName) {
|
selection: $selectionName) {
|
||||||
Label("Config", systemImage: "slider.horizontal.3")
|
Label(SidebarItem.config.rawValue, systemImage: "slider.horizontal.3")
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(destination: LogsView(),
|
NavigationLink(destination: LogsView(),
|
||||||
tag: "Logs",
|
tag: SidebarItem.logs.rawValue,
|
||||||
selection: $selectionName) {
|
selection: $selectionName) {
|
||||||
Label("Logs", systemImage: "wand.and.stars.inverse")
|
Label(SidebarItem.logs.rawValue, systemImage: "wand.and.stars.inverse")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ struct SidebarView: View {
|
|||||||
ConfigManager.selectLoggingApiLevel = .info
|
ConfigManager.selectLoggingApiLevel = .info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sidebarItemChanged(sidebarSelectionName)
|
||||||
|
|
||||||
clashApiDatasStorage.resetStreamApi()
|
clashApiDatasStorage.resetStreamApi()
|
||||||
connsQueue.sync {
|
connsQueue.sync {
|
||||||
clashApiDatasStorage.connsStorage.conns
|
clashApiDatasStorage.connsStorage.conns
|
||||||
@@ -35,9 +37,13 @@ struct SidebarView: View {
|
|||||||
|
|
||||||
updateConnections()
|
updateConnections()
|
||||||
}
|
}
|
||||||
|
.onChange(of: sidebarSelectionName) { newValue in
|
||||||
|
sidebarItemChanged(newValue)
|
||||||
|
}
|
||||||
.onReceive(timer, perform: { _ in
|
.onReceive(timer, perform: { _ in
|
||||||
updateConnections()
|
updateConnections()
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateConnections() {
|
func updateConnections() {
|
||||||
@@ -50,6 +56,13 @@ struct SidebarView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sidebarItemChanged(_ name: String?) {
|
||||||
|
guard let str = name,
|
||||||
|
let item = SidebarItem(rawValue: str) else { return }
|
||||||
|
|
||||||
|
NotificationCenter.default.post(name: .sidebarItemChanged, object: nil, userInfo: ["item": item])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//struct SidebarView_Previews: PreviewProvider {
|
//struct SidebarView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -11,8 +11,21 @@ import ClashX_Dashboard_Kit
|
|||||||
@main
|
@main
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
|
var dashboardWindowController: DashboardWindowController?
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
|
|
||||||
|
if dashboardWindowController == nil {
|
||||||
|
dashboardWindowController = DashboardWindowController.create()
|
||||||
|
dashboardWindowController?.onWindowClose = {
|
||||||
|
[weak self] in
|
||||||
|
self?.dashboardWindowController = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dashboardWindowController?.showWindow(nil)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user