1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-08 04:20:07 +02:00

👌 Polish for v5.0

This commit is contained in:
2021-12-05 15:08:43 +01:00
parent be80d74141
commit 29d34a6b62
15 changed files with 238 additions and 200 deletions

View File

@ -27,11 +27,11 @@ extension App {
// Make sure we can parse the JSON into the desired format
guard let keybindPref = GlobalKeybindPreference.fromJson(hotkey) else {
print("No global hotkey loaded, could not be parsed!")
self.shortcutHotkey = nil
shortcutHotkey = nil
return
}
self.shortcutHotkey = HotKey(keyCombo: KeyCombo(
shortcutHotkey = HotKey(keyCombo: KeyCombo(
carbonKeyCode: keybindPref.keyCode,
carbonModifiers: keybindPref.carbonFlags
))
@ -42,7 +42,7 @@ extension App {
(opens the menu).
*/
func setupGlobalHotkeyListener() {
guard let hotkey = self.shortcutHotkey else {
guard let hotkey = shortcutHotkey else {
return
}

View File

@ -90,7 +90,7 @@ class App {
*/
var shortcutHotkey: HotKey? = nil {
didSet {
self.setupGlobalHotkeyListener()
setupGlobalHotkeyListener()
}
}

View File

@ -26,7 +26,7 @@ class Startup {
performEnvironmentCheck(
!Shell.fileExists("\(Paths.binPath)/php"),
messageText: "startup.errors.php_binary.title".localized,
informativeText: "startup.errors.php_binary_desc".localized,
informativeText: "startup.errors.php_binary.desc".localized,
breaking: true
)

View File

@ -28,7 +28,13 @@ class Alert {
}
public static func notify(message: String, info: String, style: NSAlert.Style = .informational) {
_ = self.present(messageText: message, informativeText: info, buttonTitle: "OK", secondButtonTitle: "", style: style)
_ = present(
messageText: message,
informativeText: info,
buttonTitle: "OK",
secondButtonTitle: "",
style: style
)
}
}

View File

@ -22,11 +22,11 @@ class PMWindowController: NSWindowController, NSWindowDelegate {
override func showWindow(_ sender: Any?) {
super.showWindow(sender)
App.shared.register(window: self.windowName)
App.shared.register(window: windowName)
}
func windowWillClose(_ notification: Notification) {
App.shared.remove(window: self.windowName)
App.shared.remove(window: windowName)
}
deinit {

View File

@ -18,8 +18,8 @@ class HomebrewDiagnostics {
var errors: [HomebrewDiagnostics.Errors] = []
init() {
if self.determineAliasConflicts() {
self.errors.append(.aliasConflict)
if determineAliasConflicts() {
errors.append(.aliasConflict)
}
}

View File

@ -18,35 +18,35 @@ class Valet {
var sites: [Site] = []
init() {
self.version = Actions.valet("--version")
version = Actions.valet("--version")
.replacingOccurrences(of: "Laravel Valet ", with: "")
.trimmingCharacters(in: .whitespacesAndNewlines)
let file = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".config/valet/config.json")
self.config = try! JSONDecoder().decode(
config = try! JSONDecoder().decode(
Valet.Configuration.self,
from: try! String(contentsOf: file, encoding: .utf8).data(using: .utf8)!
)
print("PHP Monitor should scan the following paths:")
print(self.config.paths)
print(config.paths)
resolvePaths(tld: self.config.tld)
resolvePaths(tld: config.tld)
}
public func reloadSites() {
resolvePaths(tld: self.config.tld)
resolvePaths(tld: config.tld)
}
private func resolvePaths(tld: String) {
self.sites = []
sites = []
for path in self.config.paths {
for path in config.paths {
let entries = try! FileManager.default.contentsOfDirectory(atPath: path)
for entry in entries {
self.resolvePath(entry, forPath: path, tld: tld)
resolvePath(entry, forPath: path, tld: tld)
}
}
}
@ -61,9 +61,9 @@ class Valet {
let type = attrs[FileAttributeKey.type] as! FileAttributeType
if type == FileAttributeType.typeSymbolicLink {
self.sites.append(Site(aliasPath: siteDir, tld: tld))
sites.append(Site(aliasPath: siteDir, tld: tld))
} else if type == FileAttributeType.typeDirectory {
self.sites.append(Site(absolutePath: siteDir, tld: tld))
sites.append(Site(absolutePath: siteDir, tld: tld))
}
}
@ -99,7 +99,7 @@ class Valet {
}
public func determineSecured(_ tld: String) {
self.secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key")
secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key")
}
public func determineDriver() {

View File

@ -32,7 +32,7 @@ class ActivePhpInstallation {
init() {
// Show information about the current version
self.getVersion()
getVersion()
// If an error occurred, exit early
if (version.error) {
@ -47,9 +47,9 @@ class ActivePhpInstallation {
// Get configuration values
configuration = Configuration(
memory_limit: self.getByteCount(key: "memory_limit"),
upload_max_filesize: self.getByteCount(key: "upload_max_filesize"),
post_max_size: self.getByteCount(key: "post_max_size")
memory_limit: getByteCount(key: "memory_limit"),
upload_max_filesize: getByteCount(key: "upload_max_filesize"),
post_max_size: getByteCount(key: "post_max_size")
)
// Return a list of .ini files parsed after php.ini
@ -60,9 +60,9 @@ class ActivePhpInstallation {
// See if any extensions are present in said .ini files
paths.forEach { (iniFilePath) in
let extensions = PhpExtension.load(from: URL(fileURLWithPath: iniFilePath))
if extensions.count > 0 {
self.extensions.append(contentsOf: extensions)
let exts = PhpExtension.load(from: URL(fileURLWithPath: iniFilePath))
if exts.count > 0 {
extensions.append(contentsOf: exts)
}
}
}

View File

@ -26,7 +26,7 @@ class Preferences {
public init() {
Preferences.handleFirstTimeLaunch()
self.cachedPreferences = Self.cache()
cachedPreferences = Self.cache()
}
// MARK: - First Time Run

View File

@ -75,7 +75,7 @@ class PrefsVC: NSViewController {
}
override func viewWillDisappear() {
if self.listeningForGlobalHotkey {
if listeningForGlobalHotkey {
listeningForGlobalHotkey = false
}
}

View File

@ -32,7 +32,7 @@ class PrefsWC: PMWindowController {
override func keyDown(with event: NSEvent) {
super.keyDown(with: event)
if let vc = self.contentViewController as? PrefsVC {
if let vc = contentViewController as? PrefsVC {
if vc.listeningForGlobalHotkey {
if event.keyCode == Keys.Escape || event.keyCode == Keys.Space {
print("A blacklisted key was pressed, canceling listen")

View File

@ -22,4 +22,28 @@ class SiteListCell: NSTableCellView
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
func populateCell(with site: Valet.Site) {
// Make sure to show the TLD
labelSiteName.stringValue = "\(site.name!).\(Valet.shared.config.tld)"
// Show the absolute path, except make sure to replace the /Users/username segment with ~ for readability
labelPathName.stringValue = site.absolutePath
.replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~")
// If the `aliasPath` is nil, we're dealing with a parked site (otherwise: linked).
imageViewType.image = NSImage(
named: site.aliasPath == nil
? "IconParked"
: "IconLinked"
)
imageViewType.contentTintColor = NSColor.tertiaryLabelColor
// Show the green or red lock based on whether the site was secured
imageViewLock.contentTintColor = site.secured ? NSColor.systemGreen
: NSColor.red
// Show the current driver
labelDriver.stringValue = site.driver
}
}

View File

@ -23,6 +23,15 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
var editorAvailability: [String] = []
var lastSearchedFor = ""
// MARK: - Helper Variables
var selectedSite: Valet.Site? {
if tableView.selectedRow == -1 {
return nil
}
return sites[tableView.selectedRow]
}
// MARK: - Display
public static func create(delegate: NSWindowDelegate?) {
@ -56,103 +65,196 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
// MARK: - Lifecycle
override func viewDidLoad() {
if (Shell.fileExists("/usr/local/bin/code")) {
self.editorAvailability.append("vscode")
}
determineEditorAvailability()
sites = Valet.shared.sites
setUINotBusy()
}
if (Shell.fileExists("/Applications/PhpStorm.app/Contents/Info.plist")) {
self.editorAvailability.append("phpstorm")
}
// MARK: - Async Operations
self.sites = Valet.shared.sites
self.progressIndicator.stopAnimation(nil)
/**
Disables the UI so the user cannot interact with it.
Also shows a spinner to indicate that we're busy.
*/
private func setUIBusy() {
progressIndicator.startAnimation(nil)
tableView.alphaValue = 0.3
tableView.isEnabled = false
}
/**
Re-enables the UI so the user can interact with it.
*/
private func setUINotBusy() {
progressIndicator.stopAnimation(nil)
tableView.alphaValue = 1.0
tableView.isEnabled = true
}
/**
Executes a specific callback and fires the completion callback,
while updating the UI as required. As long as the completion callback
does not fire, the app is presumed to be busy and the UI reflects this.
- Parameter execute: Callback of the work that needs to happen.
- Parameter completion: Callback that is fired when the work is done.
*/
private func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
{
setUIBusy()
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
execute()
DispatchQueue.main.async { [self] in
completion()
setUINotBusy()
}
}
}
// MARK: - Site Data Loading
func reloadSites() {
// Start spinner and reset view (no items)
self.progressIndicator.startAnimation(nil)
self.tableView.alphaValue = 0.3
self.tableView.isEnabled = false
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
// Reload site information
waitAndExecute {
Valet.shared.reloadSites()
DispatchQueue.main.async { [self] in
// Update the site list
self.sites = Valet.shared.sites
// Stop spinner
self.progressIndicator.stopAnimation(nil)
self.tableView.alphaValue = 1.0
self.tableView.isEnabled = true
// Re-apply any existing search
self.searchedFor(text: lastSearchedFor)
}
} completion: { [self] in
sites = Valet.shared.sites
searchedFor(text: lastSearchedFor)
}
}
// MARK: - Table View
// MARK: - Table View Delegate
func numberOfRows(in tableView: NSTableView) -> Int {
return self.sites.count
return sites.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let userCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "siteItem"), owner: self) as? SiteListCell else { return nil }
guard let userCell = tableView.makeView(
withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "siteItem"), owner: self
) as? SiteListCell else { return nil }
let item = self.sites[row]
/// Make sure to show the TLD
userCell.labelSiteName.stringValue = "\(item.name!).\(Valet.shared.config.tld)"
/// Show the absolute path, except make sure to replace the /Users/username segment with ~ for readability
userCell.labelPathName.stringValue = item.absolutePath
.replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~")
/// If the `aliasPath` is nil, we're dealing with a parked site. Otherwise, it's a link that was explicitly created.
userCell.imageViewType.image = NSImage(
named: item.aliasPath == nil
? "IconParked"
: "IconLinked"
)
userCell.imageViewType.contentTintColor = NSColor.tertiaryLabelColor
/// Show the green or red lock based on whether the site was secured
userCell.imageViewLock.contentTintColor = item.secured ? NSColor.systemGreen
: NSColor.red
/// Show the current driver
userCell.labelDriver.stringValue = item.driver
userCell.populateCell(with: sites[row])
return userCell
}
func tableViewSelectionDidChange(_ notification: Notification) {
reloadContextMenu()
}
// MARK: Secure & Unsecure
@objc public func toggleSecure() {
let rowToReload = tableView.selectedRow
let originalSecureStatus = selectedSite!.secured
let action = selectedSite!.secured ? "unsecure" : "secure"
let selectedSite = selectedSite!
let command = "cd \(selectedSite.absolutePath!) && sudo \(Paths.valet) \(action) && exit;"
waitAndExecute {
Shell.run(command, requiresPath: true)
} completion: { [self] in
selectedSite.determineSecured(Valet.shared.config.tld)
if selectedSite.secured == originalSecureStatus {
Alert.notify(
message: "site_list.alerts_status_changed.title".localized,
info: "\("site_list.alerts_status_changed.desc".localized) `\(command)`")
} else {
let newState = selectedSite.secured ? "secured" : "unsecured"
LocalNotification.send(
title: "site_list.alerts_status_changed.title".localized,
subtitle: "site_list.alerts_status_changed.desc"
.localized
.replacingOccurrences(of: "{@1}", with: "\(selectedSite.name!).\(Valet.shared.config.tld)")
.replacingOccurrences(of: "{@2}", with: newState)
)
}
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0])
tableView.deselectRow(rowToReload)
tableView.selectRowIndexes([rowToReload], byExtendingSelection: true)
}
}
// MARK: Open with IDE / Editor
/**
Find out which editors are available on the users system.
Currently only PHPStorm and Visual Studio Code are detected.
*/
private func determineEditorAvailability() {
if (Shell.fileExists("/usr/local/bin/code")) {
editorAvailability.append("vscode")
}
if (Shell.fileExists("/Applications/PhpStorm.app/Contents/Info.plist")) {
editorAvailability.append("phpstorm")
}
}
@objc public func openWithPhpStorm() {
Shell.run("open -a /Applications/PhpStorm.app \(selectedSite!.absolutePath!)")
}
@objc public func openWithVSCode() {
Shell.run("/usr/local/bin/code \(selectedSite!.absolutePath!)")
}
// MARK: Open in Browser & Finder
@objc public func openInBrowser() {
let prefix = selectedSite!.secured ? "https://" : "http://"
let url = "\(prefix)\(selectedSite!.name!).\(Valet.shared.config.tld)"
NSWorkspace.shared.open(URL(string: url)!)
}
@objc public func openInFinder() {
Shell.run("open \(selectedSite!.absolutePath!)")
}
// MARK: - (Search) Text Field Delegate
func searchedFor(text: String) {
lastSearchedFor = text
let searchString = text.lowercased()
if searchString.isEmpty {
sites = Valet.shared.sites
tableView.reloadData()
return
}
sites = Valet.shared.sites.filter({ site in
return site.name.lowercased().contains(searchString)
})
tableView.reloadData()
}
// MARK: - Context Menu
private func reloadContextMenu() {
let menu = NSMenu()
if self.tableView.selectedRow == -1 {
guard let site = selectedSite else {
tableView.menu = nil
return
}
let site = self.sites[self.tableView.selectedRow]
menu.addItem(
withTitle: site.secured
? "site_list.unsecure".localized
: "site_list.secure".localized,
? "site_list.unsecure".localized
: "site_list.secure".localized,
action: #selector(toggleSecure),
keyEquivalent: "L"
)
if (self.editorAvailability.count > 0) {
if (editorAvailability.count > 0) {
menu.addItem(NSMenuItem.separator())
if self.editorAvailability.contains("vscode") {
if editorAvailability.contains("vscode") {
menu.addItem(
withTitle: "site_list.open_with_vs_code".localized,
action: #selector(self.openWithVSCode),
@ -181,97 +283,10 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
action: #selector(self.openInBrowser),
keyEquivalent: "O"
)
tableView.menu = menu
}
// MARK: Secure / unsecure
@objc public func toggleSecure() {
let rowToReload = self.tableView.selectedRow
let site = self.sites[self.tableView.selectedRow]
let previous = site.secured
let action = site.secured ? "unsecure" : "secure"
self.progressIndicator.startAnimation(nil)
self.tableView.alphaValue = 0.3
self.tableView.isEnabled = false
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
let command = "cd \(site.absolutePath!) && sudo \(Paths.valet) \(action) && exit;"
let _ = Shell.pipe(command, requiresPath: true)
site.determineSecured(Valet.shared.config.tld)
DispatchQueue.main.async { [self] in
if site.secured == previous {
Alert.notify(
message: "SSL status not changed",
info: "Something went wrong. Try running the command in your terminal manually: `\(command)`")
} else {
let newState = site.secured ? "secured" : "unsecured"
LocalNotification.send(
title: "SSL status changed",
subtitle: "The domain '\(site.name!).\(Valet.shared.config.tld)' is now \(newState)."
)
}
progressIndicator.stopAnimation(nil)
self.tableView.alphaValue = 1
self.tableView.isEnabled = true
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0])
tableView.deselectRow(rowToReload)
tableView.selectRowIndexes([rowToReload], byExtendingSelection: true)
}
}
}
// MARK: Open with IDE / Editor
@objc public func openWithPhpStorm() {
let site = self.sites[self.tableView.selectedRow]
Shell.run("open -a /Applications/PhpStorm.app \(site.absolutePath!)")
}
@objc public func openWithVSCode() {
let site = self.sites[self.tableView.selectedRow]
Shell.run("/usr/local/bin/code \(site.absolutePath!)")
}
// MARK: Open in Browser & Finder
@objc public func openInBrowser() {
let site = self.sites[self.tableView.selectedRow]
let prefix = site.secured ? "https://" : "http://"
let url = "\(prefix)\(site.name!).\(Valet.shared.config.tld)"
NSWorkspace.shared.open(URL(string: url)!)
}
@objc public func openInFinder() {
let site = self.sites[self.tableView.selectedRow]
Shell.run("open \(site.absolutePath!)")
}
// MARK: - (Search) Text Field Delegate
func searchedFor(text: String) {
self.lastSearchedFor = text
let searchString = text.lowercased()
if searchString.isEmpty {
self.sites = Valet.shared.sites
tableView.reloadData()
return
}
self.sites = Valet.shared.sites.filter({ site in
return site.name.lowercased().contains(searchString)
})
tableView.reloadData()
}
// MARK: - Deinitialization
deinit {

View File

@ -15,7 +15,7 @@ class Shell {
_ command: String,
requiresPath: Bool = false
) {
Shell.user.run(command)
Shell.user.run(command, requiresPath: requiresPath)
}
public static func pipe(
@ -27,23 +27,10 @@ class Shell {
// MARK: - Singleton
var shell: String
init() {
// Determine if we're using macOS Catalina or newer (that support /bin/zsh as default shell)
let at_least_10_15 = ProcessInfo.processInfo.isOperatingSystemAtLeast(
.init(majorVersion: 10, minorVersion: 15, patchVersion: 0))
// If macOS Mojave is being used, we'll default to /bin/bash
shell = at_least_10_15
? "/bin/sh"
: "/bin/bash"
print(at_least_10_15
? "Detected recent macOS (> 10.15): defaulting to /bin/sh"
: "Detected older macOS (< 10.15): defaulting to /bin/bash"
)
}
/**
We now require macOS 11, so no need to detect which terminal to use.
*/
var shell: String = "/bin/sh"
/**
Singleton to access a user shell (with --login)

View File

@ -53,6 +53,12 @@
"site_list.title" = "Domains";
"site_list.subtitle" = "Linked & Parked";
"site_list.alerts_status_changed.title" = "SSL Status Not Changed";
"site_list.alerts_status_changed.desc" = "Something went wrong. Try running the command in your terminal manually:";
"site_list.alerts_status_changed.title" = "SSL Status Changed";
"site_list.alerts_status_changed.desc" = "The domain '{@1}' is now {@2}.";
// SITE LIST ACTIONS
"site_list.secure" = "Secure";
@ -127,7 +133,7 @@
/// 1. PHP binary not found
"startup.errors.php_binary.title" = "PHP is not correctly installed";
"startup.errors.php_binary_desc" = "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php` (or `/opt/homebrew/bin/php`). The app will not work correctly until you resolve this issue. (Usually `brew link php` resolves this issue.)";
"startup.errors.php_binary.desc" = "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php` (or `/opt/homebrew/bin/php`). The app will not work correctly until you resolve this issue. (Usually `brew link php` resolves this issue.)";
/// 2. PHP not found in /usr/local/opt or /opt/homebrew/opt
"startup.errors.php_opt.title" = "PHP is not correctly installed";