This commit is contained in:
mrFq1
2023-06-05 23:39:23 +08:00
parent e5860ead2c
commit f488f41f8d
72 changed files with 247 additions and 45 deletions
@@ -0,0 +1,77 @@
//
// ProxiesView.swift
// ClashX Dashboard
//
//
import SwiftUI
import Introspect
class ProxiesSearchString: ObservableObject, Identifiable {
let id = UUID().uuidString
@Published var string: String = ""
}
struct ProxiesView: View {
@ObservedObject var proxyStorage = DBProxyStorage()
@State private var searchString = ProxiesSearchString()
@State private var isGlobalMode = false
@StateObject private var hideProxyNames = HideProxyNames()
var body: some View {
NavigationView {
List(proxyStorage.groups, id: \.id) { group in
ProxyGroupRowView(proxyGroup: group)
}
.introspectTableView {
$0.refusesFirstResponder = true
$0.doubleAction = nil
}
.listStyle(.plain)
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()
}
.environmentObject(hideProxyNames)
.toolbar {
ToolbarItem {
Button {
hideProxyNames.hide = !hideProxyNames.hide
} label: {
Image(systemName: hideProxyNames.hide ? "eyeglasses" : "wand.and.stars")
}
}
}
}
func loadProxies() {
// self.isGlobalMode = ConfigManager.shared.currentConfig?.mode == .global
ApiRequest.getMergedProxyData {
guard let resp = $0 else { return }
proxyStorage.groups = DBProxyStorage(resp).groups.filter {
isGlobalMode ? true : $0.name != "GLOBAL"
}
}
}
}
//struct ProxiesView_Previews: PreviewProvider {
// static var previews: some View {
// ProxiesView()
// }
//}
@@ -0,0 +1,53 @@
//
// ProxyGroupInfoView.swift
// ClashX Dashboard
//
//
import SwiftUI
struct ProxyGroupRowView: View {
@ObservedObject var proxyGroup: DBProxyGroup
@EnvironmentObject var hideProxyNames: HideProxyNames
var body: some View {
NavigationLink {
ProxyGroupView(proxyGroup: proxyGroup)
} label: {
labelView
}
}
var labelView: some View {
VStack(spacing: 2) {
HStack(alignment: .center) {
Text(hideProxyNames.hide
? String(proxyGroup.id.prefix(8))
: proxyGroup.name)
.font(.system(size: 15))
Spacer()
if let proxy = proxyGroup.currentProxy {
Text(proxy.delayString)
.foregroundColor(proxy.delayColor)
.font(.system(size: 12))
}
}
HStack {
Text(proxyGroup.type.rawValue)
Spacer()
if let proxy = proxyGroup.currentProxy {
Text(hideProxyNames.hide
? String(proxy.id.prefix(8))
: proxy.name)
}
}
.font(.system(size: 11))
.foregroundColor(.secondary)
}
.padding(EdgeInsets(top: 3, leading: 4, bottom: 3, trailing: 4))
}
}
@@ -0,0 +1,151 @@
//
// ProxyView.swift
// ClashX Dashboard
//
//
import SwiftUI
struct ProxyGroupView: View {
@ObservedObject var proxyGroup: DBProxyGroup
@EnvironmentObject var searchString: ProxiesSearchString
@EnvironmentObject var hideProxyNames: HideProxyNames
@State private var columnCount: Int = 3
@State private var isUpdatingSelect = false
@State private var selectable = false
@State private var isTesting = false
var proxies: [DBProxy] {
if searchString.string.isEmpty {
return proxyGroup.proxies
} else {
return proxyGroup.proxies.filter {
$0.name.lowercased().contains(searchString.string.lowercased())
}
}
}
var body: some View {
ScrollView {
Section {
proxyListView
} header: {
proxyInfoView
}
.padding()
}
.background {
GeometryReader { geometry in
Rectangle()
.fill(.clear)
.frame(height: 1)
.onChange(of: geometry.size.width) { newValue in
updateColumnCount(newValue)
}
.onAppear {
updateColumnCount(geometry.size.width)
}
}.padding()
}
.onAppear {
self.selectable = [.select, .fallback].contains(proxyGroup.type)
}
}
func updateColumnCount(_ width: Double) {
let v = Int(Int(width) / 180)
let new = v == 0 ? 1 : v
if new != columnCount {
columnCount = new
}
}
var proxyInfoView: some View {
HStack() {
Text(hideProxyNames.hide
? String(proxyGroup.id.prefix(8))
: proxyGroup.name)
.font(.system(size: 17))
Text(proxyGroup.type.rawValue)
.font(.system(size: 13))
.foregroundColor(.secondary)
Text("\(proxyGroup.proxies.count)")
.font(.system(size: 11))
.padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4))
.background(Color.gray.opacity(0.5))
.cornerRadius(4)
Spacer()
ProgressButton(
title: proxyGroup.type == .urltest ? "Retest" : "Benchmark",
title2: "Testing",
iconName: "bolt.fill",
inProgress: $isTesting) {
startBenchmark()
}
}
}
var proxyListView: some View {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()),
count: columnCount)) {
ForEach(proxies, id: \.id) { proxy in
ProxyItemView(
proxy: proxy,
selectable: selectable
)
.background(proxyGroup.now == proxy.name ? Color.pink.opacity(0.3) : Color(nsColor: .textBackgroundColor))
.cornerRadius(8)
.onTapGesture {
let item = proxy
updateSelect(item.name)
}
}
}
}
func startBenchmark() {
isTesting = true
ApiRequest.getGroupDelay(groupName: proxyGroup.name) { delays in
proxyGroup.proxies.enumerated().forEach {
var delay = 0
if let d = delays[$0.element.name], d != 0 {
delay = d
}
guard $0.offset < proxyGroup.proxies.count,
proxyGroup.proxies[$0.offset].name == $0.element.name
else { return }
proxyGroup.proxies[$0.offset].delay = delay
if proxyGroup.currentProxy?.name == $0.element.name {
proxyGroup.currentProxy = proxyGroup.proxies[$0.offset]
}
}
isTesting = false
}
}
func updateSelect(_ name: String) {
guard selectable, !isUpdatingSelect else { return }
isUpdatingSelect = true
ApiRequest.updateProxyGroup(group: proxyGroup.name, selectProxy: name) { success in
isUpdatingSelect = false
guard success else { return }
proxyGroup.now = name
}
}
}
//struct ProxyView_Previews: PreviewProvider {
// static var previews: some View {
// ProxyGroupView()
// }
//}
//
@@ -0,0 +1,78 @@
//
// ProxyItemView.swift
// ClashX Dashboard
//
//
import SwiftUI
struct ProxyItemView: View {
@ObservedObject var proxy: DBProxy
@State var selectable: Bool
@EnvironmentObject var hideProxyNames: HideProxyNames
init(proxy: DBProxy, selectable: Bool) {
self.proxy = proxy
self.selectable = selectable
self.isBuiltInProxy = [.pass, .direct, .reject].contains(proxy.type)
}
@State private var isBuiltInProxy: Bool
@State private var mouseOver = false
var body: some View {
VStack {
HStack(alignment: .center) {
Text(hideProxyNames.hide
? String(proxy.id.prefix(8))
: proxy.name)
.truncationMode(.tail)
.lineLimit(1)
Spacer(minLength: 6)
Text(proxy.udpString)
.foregroundColor(.secondary)
.font(.system(size: 11))
.show(isVisible: !isBuiltInProxy)
}
Spacer(minLength: 6)
.show(isVisible: !isBuiltInProxy)
HStack(alignment: .center) {
Text(proxy.type.rawValue)
.foregroundColor(.secondary)
.font(.system(size: 12))
Text("[TFO]")
.font(.system(size: 9))
.show(isVisible: proxy.tfo)
Spacer(minLength: 6)
Text(proxy.delayString)
.foregroundColor(proxy.delayColor)
.font(.system(size: 11))
}
.show(isVisible: !isBuiltInProxy)
}
.onHover {
guard selectable else { return }
mouseOver = $0
}
.frame(height: 36)
.padding(12)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(mouseOver ? .secondary : Color.clear, lineWidth: 2)
.padding(1)
)
}
}
//struct ProxyItemView_Previews: PreviewProvider {
// static var previews: some View {
// ProxyItemView()
// }
//}