macOS: Tunnel detail: Make the Activate button part of the list view
This commit is contained in:
parent
9622128a80
commit
1fdc8eb13a
|
@ -55,6 +55,7 @@
|
||||||
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
|
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
|
||||||
6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; };
|
6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; };
|
||||||
6F5D0C22218352EF000F85AD /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
6F5D0C22218352EF000F85AD /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5EA59A223E58A8002B380A /* ButtonRow.swift */; };
|
||||||
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */; };
|
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */; };
|
||||||
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1E821B932F700483816 /* WireGuardAppError.swift */; };
|
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1E821B932F700483816 /* WireGuardAppError.swift */; };
|
||||||
6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */; };
|
6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */; };
|
||||||
|
@ -270,6 +271,7 @@
|
||||||
6F5D0C1F218352EF000F85AD /* WireGuardNetworkExtension_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuardNetworkExtension_iOS.entitlements; sourceTree = "<group>"; };
|
6F5D0C1F218352EF000F85AD /* WireGuardNetworkExtension_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuardNetworkExtension_iOS.entitlements; sourceTree = "<group>"; };
|
||||||
6F5D0C3421839E37000F85AD /* WireGuardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuardNetworkExtension-Bridging-Header.h"; sourceTree = "<group>"; };
|
6F5D0C3421839E37000F85AD /* WireGuardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuardNetworkExtension-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelSettingsGenerator.swift; sourceTree = "<group>"; };
|
6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelSettingsGenerator.swift; sourceTree = "<group>"; };
|
||||||
|
6F5EA59A223E58A8002B380A /* ButtonRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonRow.swift; sourceTree = "<group>"; };
|
||||||
6F613D9A21DE33B8004B217A /* KeyValueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueRow.swift; sourceTree = "<group>"; };
|
6F613D9A21DE33B8004B217A /* KeyValueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueRow.swift; sourceTree = "<group>"; };
|
||||||
6F61F1E821B932F700483816 /* WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardAppError.swift; sourceTree = "<group>"; };
|
6F61F1E821B932F700483816 /* WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardAppError.swift; sourceTree = "<group>"; };
|
||||||
6F61F1EA21B937EF00483816 /* WireGuardResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardResult.swift; sourceTree = "<group>"; };
|
6F61F1EA21B937EF00483816 /* WireGuardResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardResult.swift; sourceTree = "<group>"; };
|
||||||
|
@ -425,6 +427,7 @@
|
||||||
5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */,
|
5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */,
|
||||||
6F9B582721E8CD4300544D02 /* PopupRow.swift */,
|
6F9B582721E8CD4300544D02 /* PopupRow.swift */,
|
||||||
6FE3661C21F64F6B00F78C7D /* ConfTextColorTheme.swift */,
|
6FE3661C21F64F6B00F78C7D /* ConfTextColorTheme.swift */,
|
||||||
|
6F5EA59A223E58A8002B380A /* ButtonRow.swift */,
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1165,6 +1168,7 @@
|
||||||
6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */,
|
6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */,
|
||||||
6FBA103F21D6B6FF0051C35F /* TunnelImporter.swift in Sources */,
|
6FBA103F21D6B6FF0051C35F /* TunnelImporter.swift in Sources */,
|
||||||
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */,
|
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */,
|
||||||
|
6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */,
|
||||||
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */,
|
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */,
|
||||||
6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */,
|
6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */,
|
||||||
5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */,
|
5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */,
|
||||||
|
|
|
@ -15,6 +15,7 @@ class TunnelViewModel {
|
||||||
case mtu
|
case mtu
|
||||||
case dns
|
case dns
|
||||||
case status
|
case status
|
||||||
|
case toggleStatus
|
||||||
|
|
||||||
var localizedUIString: String {
|
var localizedUIString: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -27,6 +28,7 @@ class TunnelViewModel {
|
||||||
case .mtu: return tr("tunnelInterfaceMTU")
|
case .mtu: return tr("tunnelInterfaceMTU")
|
||||||
case .dns: return tr("tunnelInterfaceDNS")
|
case .dns: return tr("tunnelInterfaceDNS")
|
||||||
case .status: return tr("tunnelInterfaceStatus")
|
case .status: return tr("tunnelInterfaceStatus")
|
||||||
|
case .toggleStatus: return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class ButtonRow: NSView {
|
||||||
|
let button: NSButton = {
|
||||||
|
let button = NSButton()
|
||||||
|
button.title = ""
|
||||||
|
button.setButtonType(.momentaryPushIn)
|
||||||
|
button.bezelStyle = .rounded
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
var buttonTitle: String {
|
||||||
|
get { return button.title }
|
||||||
|
set(value) { button.title = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var isButtonEnabled: Bool {
|
||||||
|
get { return button.isEnabled }
|
||||||
|
set(value) { button.isEnabled = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonToolTip: String {
|
||||||
|
get { return button.toolTip ?? "" }
|
||||||
|
set(value) { button.toolTip = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var onButtonClicked: (() -> Void)?
|
||||||
|
var observationToken: AnyObject?
|
||||||
|
|
||||||
|
override var intrinsicContentSize: NSSize {
|
||||||
|
return NSSize(width: NSView.noIntrinsicMetric, height: button.intrinsicContentSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(frame: CGRect.zero)
|
||||||
|
|
||||||
|
button.target = self
|
||||||
|
button.action = #selector(buttonClicked)
|
||||||
|
|
||||||
|
addSubview(button)
|
||||||
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||||
|
button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 155),
|
||||||
|
button.widthAnchor.constraint(greaterThanOrEqualToConstant: 100)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder decoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func buttonClicked() {
|
||||||
|
onButtonClicked?()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
buttonTitle = ""
|
||||||
|
buttonToolTip = ""
|
||||||
|
onButtonClicked = nil
|
||||||
|
observationToken = nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ class TunnelDetailTableViewController: NSViewController {
|
||||||
|
|
||||||
static let interfaceFields: [TunnelViewModel.InterfaceField] = [
|
static let interfaceFields: [TunnelViewModel.InterfaceField] = [
|
||||||
.name, .status, .publicKey, .addresses,
|
.name, .status, .publicKey, .addresses,
|
||||||
.listenPort, .mtu, .dns
|
.listenPort, .mtu, .dns, .toggleStatus
|
||||||
]
|
]
|
||||||
|
|
||||||
static let peerFields: [TunnelViewModel.PeerField] = [
|
static let peerFields: [TunnelViewModel.PeerField] = [
|
||||||
|
@ -51,16 +51,6 @@ class TunnelDetailTableViewController: NSViewController {
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let toggleStatusButton: NSButton = {
|
|
||||||
let button = NSButton()
|
|
||||||
button.title = ""
|
|
||||||
button.setButtonType(.momentaryPushIn)
|
|
||||||
button.bezelStyle = .rounded
|
|
||||||
button.toolTip = "Toggle status (⌘T)"
|
|
||||||
button.widthAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
let editButton: NSButton = {
|
let editButton: NSButton = {
|
||||||
let button = NSButton()
|
let button = NSButton()
|
||||||
button.title = tr("Edit")
|
button.title = tr("Edit")
|
||||||
|
@ -114,9 +104,6 @@ class TunnelDetailTableViewController: NSViewController {
|
||||||
tableView.dataSource = self
|
tableView.dataSource = self
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
|
|
||||||
toggleStatusButton.target = self
|
|
||||||
toggleStatusButton.action = #selector(handleToggleActiveStatusAction)
|
|
||||||
|
|
||||||
editButton.target = self
|
editButton.target = self
|
||||||
editButton.action = #selector(handleEditTunnelAction)
|
editButton.action = #selector(handleEditTunnelAction)
|
||||||
|
|
||||||
|
@ -134,11 +121,9 @@ class TunnelDetailTableViewController: NSViewController {
|
||||||
containerView.addLayoutGuide(bottomControlsContainer)
|
containerView.addLayoutGuide(bottomControlsContainer)
|
||||||
containerView.addSubview(box)
|
containerView.addSubview(box)
|
||||||
containerView.addSubview(scrollView)
|
containerView.addSubview(scrollView)
|
||||||
containerView.addSubview(toggleStatusButton)
|
|
||||||
containerView.addSubview(editButton)
|
containerView.addSubview(editButton)
|
||||||
box.translatesAutoresizingMaskIntoConstraints = false
|
box.translatesAutoresizingMaskIntoConstraints = false
|
||||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
toggleStatusButton.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
editButton.translatesAutoresizingMaskIntoConstraints = false
|
editButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
@ -150,8 +135,6 @@ class TunnelDetailTableViewController: NSViewController {
|
||||||
bottomControlsContainer.heightAnchor.constraint(equalToConstant: 32),
|
bottomControlsContainer.heightAnchor.constraint(equalToConstant: 32),
|
||||||
scrollView.bottomAnchor.constraint(equalTo: bottomControlsContainer.topAnchor),
|
scrollView.bottomAnchor.constraint(equalTo: bottomControlsContainer.topAnchor),
|
||||||
bottomControlsContainer.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
bottomControlsContainer.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
||||||
toggleStatusButton.leadingAnchor.constraint(equalTo: bottomControlsContainer.leadingAnchor),
|
|
||||||
bottomControlsContainer.bottomAnchor.constraint(equalTo: toggleStatusButton.bottomAnchor, constant: 0),
|
|
||||||
editButton.trailingAnchor.constraint(equalTo: bottomControlsContainer.trailingAnchor),
|
editButton.trailingAnchor.constraint(equalTo: bottomControlsContainer.trailingAnchor),
|
||||||
bottomControlsContainer.bottomAnchor.constraint(equalTo: editButton.bottomAnchor, constant: 0)
|
bottomControlsContainer.bottomAnchor.constraint(equalTo: editButton.bottomAnchor, constant: 0)
|
||||||
])
|
])
|
||||||
|
@ -176,7 +159,7 @@ class TunnelDetailTableViewController: NSViewController {
|
||||||
|
|
||||||
var interfaceSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
|
var interfaceSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
|
||||||
for field in TunnelDetailTableViewController.interfaceFields {
|
for field in TunnelDetailTableViewController.interfaceFields {
|
||||||
let isStatus = field == .status
|
let isStatus = field == .status || field == .toggleStatus
|
||||||
let isEmpty = tunnelViewModel.interfaceData[field].isEmpty
|
let isEmpty = tunnelViewModel.interfaceData[field].isEmpty
|
||||||
interfaceSection.append((isVisible: isStatus || !isEmpty, modelRow: .interfaceFieldRow(field)))
|
interfaceSection.append((isVisible: isStatus || !isEmpty, modelRow: .interfaceFieldRow(field)))
|
||||||
}
|
}
|
||||||
|
@ -204,26 +187,6 @@ class TunnelDetailTableViewController: NSViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStatus() {
|
func updateStatus() {
|
||||||
let toggleStatusButtonText: String
|
|
||||||
switch tunnel.status {
|
|
||||||
case .waiting:
|
|
||||||
toggleStatusButtonText = tr("macToggleStatusButtonWaiting")
|
|
||||||
case .inactive:
|
|
||||||
toggleStatusButtonText = tr("macToggleStatusButtonActivate")
|
|
||||||
case .activating:
|
|
||||||
toggleStatusButtonText = tr("macToggleStatusButtonActivating")
|
|
||||||
case .active:
|
|
||||||
toggleStatusButtonText = tr("macToggleStatusButtonDeactivate")
|
|
||||||
case .deactivating:
|
|
||||||
toggleStatusButtonText = tr("macToggleStatusButtonDeactivating")
|
|
||||||
case .reasserting:
|
|
||||||
toggleStatusButtonText = tr("macToggleStatusButtonReasserting")
|
|
||||||
case .restarting:
|
|
||||||
toggleStatusButtonText = tr("macToggleStatusButtonRestarting")
|
|
||||||
}
|
|
||||||
toggleStatusButton.title = toggleStatusButtonText
|
|
||||||
let shouldBeEnabled = (tunnel.status == .active || tunnel.status == .inactive)
|
|
||||||
toggleStatusButton.isEnabled = shouldBeEnabled
|
|
||||||
if tunnel.status == .active {
|
if tunnel.status == .active {
|
||||||
startUpdatingRuntimeConfiguration()
|
startUpdatingRuntimeConfiguration()
|
||||||
} else if tunnel.status == .inactive {
|
} else if tunnel.status == .inactive {
|
||||||
|
@ -392,6 +355,8 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
||||||
case .interfaceFieldRow(let field):
|
case .interfaceFieldRow(let field):
|
||||||
if field == .status {
|
if field == .status {
|
||||||
return statusCell()
|
return statusCell()
|
||||||
|
} else if field == .toggleStatus {
|
||||||
|
return toggleStatusCell()
|
||||||
} else {
|
} else {
|
||||||
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
||||||
let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString
|
let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString
|
||||||
|
@ -437,6 +402,22 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toggleStatusCell() -> NSView {
|
||||||
|
let cell: ButtonRow = tableView.dequeueReusableCell()
|
||||||
|
cell.buttonTitle = TunnelDetailTableViewController.localizedToggleStatusActionText(forStatus: tunnel.status)
|
||||||
|
cell.isButtonEnabled = (tunnel.status == .active || tunnel.status == .inactive)
|
||||||
|
cell.buttonToolTip = "Toggle status (⌘T)"
|
||||||
|
cell.onButtonClicked = { [weak self] in
|
||||||
|
self?.handleToggleActiveStatusAction()
|
||||||
|
}
|
||||||
|
cell.observationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in
|
||||||
|
guard let cell = cell else { return }
|
||||||
|
cell.buttonTitle = TunnelDetailTableViewController.localizedToggleStatusActionText(forStatus: tunnel.status)
|
||||||
|
cell.isButtonEnabled = (tunnel.status == .active || tunnel.status == .inactive)
|
||||||
|
}
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
private static func localizedStatusDescription(forStatus status: TunnelStatus) -> String {
|
private static func localizedStatusDescription(forStatus status: TunnelStatus) -> String {
|
||||||
switch status {
|
switch status {
|
||||||
case .inactive:
|
case .inactive:
|
||||||
|
@ -467,6 +448,25 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
||||||
return NSImage(named: NSImage.statusNoneName)
|
return NSImage(named: NSImage.statusNoneName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func localizedToggleStatusActionText(forStatus status: TunnelStatus) -> String {
|
||||||
|
switch status {
|
||||||
|
case .waiting:
|
||||||
|
return tr("macToggleStatusButtonWaiting")
|
||||||
|
case .inactive:
|
||||||
|
return tr("macToggleStatusButtonActivate")
|
||||||
|
case .activating:
|
||||||
|
return tr("macToggleStatusButtonActivating")
|
||||||
|
case .active:
|
||||||
|
return tr("macToggleStatusButtonDeactivate")
|
||||||
|
case .deactivating:
|
||||||
|
return tr("macToggleStatusButtonDeactivating")
|
||||||
|
case .reasserting:
|
||||||
|
return tr("macToggleStatusButtonReasserting")
|
||||||
|
case .restarting:
|
||||||
|
return tr("macToggleStatusButtonRestarting")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TunnelDetailTableViewController: TunnelEditViewControllerDelegate {
|
extension TunnelDetailTableViewController: TunnelEditViewControllerDelegate {
|
||||||
|
|
Loading…
Reference in New Issue