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

Compare commits

...

5 Commits
v4.1 ... v4.1.2

13 changed files with 218 additions and 102 deletions

View File

@ -34,6 +34,8 @@
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; };
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
C41CA5ED2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* SiteListVC+Actions.swift */; };
C41CA5EE2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* SiteListVC+Actions.swift */; };
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; };
C41E871A2763D42300161EE0 /* SiteListVC+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41E87192763D42300161EE0 /* SiteListVC+ContextMenu.swift */; };
C41E871B2763D42300161EE0 /* SiteListVC+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41E87192763D42300161EE0 /* SiteListVC+ContextMenu.swift */; };
@ -155,6 +157,7 @@
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; };
C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePhpInstallation.swift; sourceTree = "<group>"; };
C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
C41CA5EC2774F8EE00A2C80E /* SiteListVC+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SiteListVC+Actions.swift"; sourceTree = "<group>"; };
C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalKeybindPreference.swift; sourceTree = "<group>"; };
C41E87192763D42300161EE0 /* SiteListVC+ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SiteListVC+ContextMenu.swift"; sourceTree = "<group>"; };
C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
@ -337,6 +340,7 @@
C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */,
C464ADAE275A7A69003FCD53 /* SiteListVC.swift */,
C41E87192763D42300161EE0 /* SiteListVC+ContextMenu.swift */,
C41CA5EC2774F8EE00A2C80E /* SiteListVC+Actions.swift */,
C464ADB1275A87CA003FCD53 /* SiteListCell.swift */,
);
path = SiteList;
@ -597,6 +601,7 @@
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */,
54FCFD2A276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
C41CA5ED2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */,
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */,
54AB03262763858F00A29D5F /* Timer.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
@ -624,6 +629,7 @@
buildActionMask = 2147483647;
files = (
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */,
C41CA5EE2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */,
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */,
54AB03272763858F00A29D5F /* Timer.swift in Sources */,
54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
@ -825,7 +831,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 135;
CURRENT_PROJECT_VERSION = 137;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
@ -834,7 +840,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 4.1;
MARKETING_VERSION = 4.1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -850,7 +856,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 135;
CURRENT_PROJECT_VERSION = 137;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = phpmon/Info.plist;
@ -859,7 +865,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 4.1;
MARKETING_VERSION = 4.1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -111,9 +111,15 @@ If you're on an Apple Silicon-based Mac, you'll need to add:
# on an M1 Mac
export PATH=$HOME/bin:/opt/homebrew/bin:$PATH
and add the following to your .zshrc:
and add the following to your .zshrc, but add this BEFORE the homebrew PATH additions:
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
If you're adding composer and Homebrew binaries, ensure that Homebrew binaries are preferred by adding these to the path last. On my system, that looks like this:
export PATH=$HOME/bin:/usr/local/bin:$PATH
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
export PATH=$HOME/bin:/opt/homebrew/bin:$PATH
Make sure PHP is linked correctly:

View File

@ -50,7 +50,5 @@ class Constants {
// dev release. In this case, that means that the version below is detected.
"8.2"
]
}

View File

@ -497,13 +497,35 @@
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="IconLinked" id="2ng-pK-kvv"/>
<color key="contentTintColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
</imageView>
<button translatesAutoresizingMaskIntoConstraints="NO" id="ypa-iv-wLD">
<rect key="frame" x="211" y="18" width="18" height="18"/>
<constraints>
<constraint firstAttribute="width" constant="18" id="jKJ-Xn-BPA"/>
<constraint firstAttribute="height" constant="18" id="lSH-of-WzD"/>
</constraints>
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSCaution" imagePosition="only" alignment="center" imageScaling="proportionallyUpOrDown" inset="2" id="9XB-KO-aSI">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="150" translatesAutoresizingMaskIntoConstraints="NO" id="MD8-ef-Ht8">
<rect key="frame" x="235" y="16" width="182" height="22"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Warning: This is a warning message. Please take this into account." id="iub-KH-clf">
<font key="font" metaFont="system" size="9"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="0NQ-ZD-CqD" firstAttribute="leading" secondItem="MD8-ef-Ht8" secondAttribute="trailing" constant="20" id="1Rb-Or-Nnn"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="TbX-e2-3QL" secondAttribute="trailing" constant="20" symbolic="YES" id="3vE-LR-S7N"/>
<constraint firstItem="TbX-e2-3QL" firstAttribute="leading" secondItem="0NQ-ZD-CqD" secondAttribute="trailing" constant="8" symbolic="YES" id="4cb-D9-8d1"/>
<constraint firstItem="XJL-Uw-frD" firstAttribute="leading" secondItem="QPX-eu-eV8" secondAttribute="trailing" constant="10" id="55y-3V-RYt"/>
<constraint firstItem="syz-LF-l6P" firstAttribute="leading" secondItem="5GY-nN-BWd" secondAttribute="leading" id="8QK-nf-Fiw"/>
<constraint firstItem="QPX-eu-eV8" firstAttribute="top" secondItem="XJL-Uw-frD" secondAttribute="top" id="9QB-jZ-k1V"/>
<constraint firstItem="ypa-iv-wLD" firstAttribute="centerY" secondItem="5GY-nN-BWd" secondAttribute="centerY" id="9d8-P2-iSk"/>
<constraint firstItem="MD8-ef-Ht8" firstAttribute="leading" secondItem="ypa-iv-wLD" secondAttribute="trailing" constant="8" symbolic="YES" id="C90-wQ-3Gf"/>
<constraint firstItem="QPX-eu-eV8" firstAttribute="leading" secondItem="5GY-nN-BWd" secondAttribute="leading" constant="10" id="GOj-sw-ZlZ"/>
<constraint firstItem="TbX-e2-3QL" firstAttribute="top" secondItem="jKi-Ls-7FZ" secondAttribute="bottom" constant="-1" id="J29-wT-Uex"/>
<constraint firstItem="CXK-Q9-CpO" firstAttribute="leading" secondItem="XJL-Uw-frD" secondAttribute="leading" id="Ojw-VZ-3EG"/>
@ -515,15 +537,19 @@
<constraint firstItem="TbX-e2-3QL" firstAttribute="centerY" secondItem="5GY-nN-BWd" secondAttribute="centerY" constant="5" id="cN8-zO-fnc"/>
<constraint firstAttribute="bottom" secondItem="syz-LF-l6P" secondAttribute="bottom" id="gj7-cJ-Lle"/>
<constraint firstItem="0NQ-ZD-CqD" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="CXK-Q9-CpO" secondAttribute="trailing" constant="8" symbolic="YES" id="iEd-Y3-zhp"/>
<constraint firstItem="ypa-iv-wLD" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="XJL-Uw-frD" secondAttribute="trailing" constant="30" id="koV-Sj-tO8"/>
<constraint firstItem="MD8-ef-Ht8" firstAttribute="centerY" secondItem="ypa-iv-wLD" secondAttribute="centerY" id="lIN-pm-mCo"/>
<constraint firstItem="0NQ-ZD-CqD" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="XJL-Uw-frD" secondAttribute="trailing" constant="8" symbolic="YES" id="lLA-Jx-Q4W"/>
<constraint firstItem="jKi-Ls-7FZ" firstAttribute="leading" secondItem="TbX-e2-3QL" secondAttribute="leading" id="zjN-s3-2Ww"/>
</constraints>
<connections>
<outlet property="buttonWarning" destination="ypa-iv-wLD" id="NwX-H3-8um"/>
<outlet property="imageViewLock" destination="QPX-eu-eV8" id="Nnh-kB-adG"/>
<outlet property="imageViewType" destination="0NQ-ZD-CqD" id="Cph-FN-LaY"/>
<outlet property="labelDriver" destination="TbX-e2-3QL" id="qJh-Ak-Dge"/>
<outlet property="labelPathName" destination="CXK-Q9-CpO" id="iVZ-cL-azB"/>
<outlet property="labelSiteName" destination="XJL-Uw-frD" id="f0t-vd-W68"/>
<outlet property="labelWarning" destination="MD8-ef-Ht8" id="Faw-CY-9R5"/>
</connections>
</tableCellView>
</prototypeCellViews>
@ -573,12 +599,13 @@
</viewController>
<customObject id="HgD-aB-bQb" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="288" y="765"/>
<point key="canvasLocation" x="288" y="764.5"/>
</scene>
</scenes>
<resources>
<image name="IconLinked" width="512" height="512"/>
<image name="Lock" width="30" height="30"/>
<image name="NSCaution" width="32" height="32"/>
<image name="arrow.clockwise" catalog="system" width="14" height="16"/>
</resources>
</document>

View File

@ -59,7 +59,6 @@ class Startup {
// Check for Valet; it can be symlinked or in .composer/vendor/bin
!(Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet")
|| Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/opt/homebrew/bin/valet")
|| Shell.pipe("cat /private/etc/sudoers.d/valet").contains(".composer/vendor/bin/valet")
),
messageText: "startup.errors.sudoers_valet.title".localized,
informativeText: "startup.errors.sudoers_valet.desc".localized,

View File

@ -34,7 +34,7 @@ class Application {
(This will open the app if it isn't open yet.)
*/
@objc public func openDirectory(file: String) {
return Shell.run("/usr/bin/open -a \"\(name)\" \(file)")
return Shell.run("/usr/bin/open -a \"\(name)\" \"\(file)\"")
}
/** Checks if the app is installed. */

View File

@ -37,10 +37,14 @@ class Valet {
}
public func startPreloadingSites() {
if self.sites.count <= 10 {
let maximumPreload = 10
let foundSites = self.countPaths()
if foundSites <= maximumPreload {
// Preload the sites and their drivers
print("Fewer than or 11 sites found, preloading list of sites...")
print("Fewer than or \(maximumPreload) sites found, preloading list of sites...")
self.reloadSites()
} else {
print("\(foundSites) sites found, exceeds \(maximumPreload) for preload at launch!")
}
}
@ -64,6 +68,25 @@ class Valet {
}
}
/**
Returns a count of how many sites are linked and parked.
*/
private func countPaths() -> Int {
var count = 0
for path in config.paths {
let entries = try! FileManager.default.contentsOfDirectory(atPath: path)
for entry in entries {
if resolveSite(entry, forPath: path) {
count += 1
}
}
}
return count
}
/**
Resolves all paths and creates linked or parked site instances that can be referenced later.
*/
private func resolvePaths(tld: String) {
sites = []
@ -75,6 +98,28 @@ class Valet {
}
}
/**
Determines whether the site can be resolved as a symbolic link or as a directory.
Regular files are ignored. Returns true if the path can be parsed.
*/
private func resolveSite(_ entry: String, forPath path: String) -> Bool {
let siteDir = path + "/" + entry
let attrs = try! FileManager.default.attributesOfItem(atPath: siteDir)
let type = attrs[FileAttributeKey.type] as! FileAttributeType
if type == FileAttributeType.typeSymbolicLink || type == FileAttributeType.typeDirectory {
return true
}
return false
}
/**
Determines whether the site can be resolved as a symbolic link or as a directory.
Regular files are ignored, and the site is added to Valet's list of sites.
*/
private func resolvePath(_ entry: String, forPath path: String, tld: String) {
let siteDir = path + "/" + entry
@ -84,6 +129,12 @@ class Valet {
// We can also determine whether the thing at the path is a directory, too
let type = attrs[FileAttributeKey.type] as! FileAttributeType
// We should also check that we can interpret the path correctly
if URL(fileURLWithPath: siteDir).lastPathComponent == "" {
print("Warning: could not parse the site: \(siteDir), skipping!")
return
}
if type == FileAttributeType.typeSymbolicLink {
sites.append(Site(aliasPath: siteDir, tld: tld))
} else if type == FileAttributeType.typeDirectory {
@ -114,7 +165,7 @@ class Valet {
convenience init(absolutePath: String, tld: String) {
self.init()
self.absolutePath = absolutePath
self.name = URL(string: absolutePath)!.lastPathComponent
self.name = URL(fileURLWithPath: absolutePath).lastPathComponent
self.aliasPath = nil
determineSecured(tld)
determineDriver()
@ -123,7 +174,7 @@ class Valet {
convenience init(aliasPath: String, tld: String) {
self.init()
self.absolutePath = try! FileManager.default.destinationOfSymbolicLink(atPath: aliasPath)
self.name = URL(string: aliasPath)!.lastPathComponent
self.name = URL(fileURLWithPath: aliasPath).lastPathComponent
self.aliasPath = aliasPath
determineSecured(tld)
determineDriver()
@ -134,7 +185,7 @@ class Valet {
}
public func determineDriver() {
let driver = Shell.pipe("cd \(absolutePath!) && valet which", requiresPath: true)
let driver = Shell.pipe("cd '\(absolutePath!)' && valet which", requiresPath: true)
if driver.contains("This site is served by") {
self.driver = driver
// TODO: Use a regular expression to retrieve the driver instead?

View File

@ -19,6 +19,9 @@ class SiteListCell: NSTableCellView
@IBOutlet weak var labelDriver: NSTextField!
@IBOutlet weak var buttonWarning: NSButton!
@IBOutlet weak var labelWarning: NSTextField!
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
@ -27,6 +30,11 @@ class SiteListCell: NSTableCellView
// Make sure to show the TLD
labelSiteName.stringValue = "\(site.name!).\(Valet.shared.config.tld)"
let isProblematic = site.name.contains(" ")
buttonWarning.isHidden = !isProblematic
labelWarning.isHidden = !isProblematic
labelWarning.stringValue = "site_list.warning.spaces".localized
// 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: "~")

View File

@ -0,0 +1,97 @@
//
// SiteListVC+Actions.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 23/12/2021.
// Copyright © 2021 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
extension SiteListVC {
@objc 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_not_changed.title".localized,
info: "site_list.alerts_status_not_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(
"\(selectedSite.name!).\(Valet.shared.config.tld)",
newState
)
)
}
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0])
tableView.deselectRow(rowToReload)
tableView.selectRowIndexes([rowToReload], byExtendingSelection: true)
}
}
@objc func openInBrowser() {
let prefix = selectedSite!.secured ? "https://" : "http://"
let url = URL(string: "\(prefix)\(selectedSite!.name!).\(Valet.shared.config.tld)")
if url != nil {
NSWorkspace.shared.open(url!)
} else {
_ = Alert.present(
messageText: "site_list.alert.invalid_folder_name".localized,
informativeText: "site_list.alert.invalid_folder_name_desc".localized
)
}
}
@objc func openInFinder() {
Shell.run("open '\(selectedSite!.absolutePath!)'")
}
@objc func openInTerminal() {
Shell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath!)'")
}
@objc func openWithEditor(sender: EditorMenuItem) {
guard let editor = sender.editor else { return }
editor.openDirectory(file: selectedSite!.absolutePath!)
}
@objc func unlinkSite() {
guard let site = selectedSite else {
return
}
if site.aliasPath == nil {
return
}
Alert.confirm(
onWindow: view.window!,
messageText: "site_list.confirm_unlink".localized(site.name),
informativeText: "site_link.confirm_link".localized,
buttonTitle: "site_list.unlink".localized,
secondButtonTitle: "Cancel",
style: .critical,
onFirstButtonPressed: {
Shell.run("valet unlink '\(site.name!)'", requiresPath: true)
self.reloadSites()
}
)
}
}

View File

@ -112,12 +112,11 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
- 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 = {})
internal 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()
@ -164,81 +163,6 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
self.openInBrowser()
}
// 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_not_changed.title".localized,
info: "site_list.alerts_status_not_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(
"\(selectedSite.name!).\(Valet.shared.config.tld)",
newState
)
)
}
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0])
tableView.deselectRow(rowToReload)
tableView.selectRowIndexes([rowToReload], byExtendingSelection: true)
}
}
// 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!)")
}
@objc public func openInTerminal() {
Shell.run("open -b com.apple.terminal \(selectedSite!.absolutePath!)")
}
@objc public func unlinkSite() {
guard let site = selectedSite else {
return
}
if site.aliasPath == nil {
return
}
Alert.confirm(
onWindow: view.window!,
messageText: "site_list.confirm_unlink".localized(site.name),
informativeText: "site_link.confirm_link".localized,
buttonTitle: "site_list.unlink".localized,
secondButtonTitle: "Cancel",
style: .critical,
onFirstButtonPressed: {
Shell.run("valet unlink \(site.name!)", requiresPath: true)
self.reloadSites()
}
)
}
// MARK: - (Search) Text Field Delegate
func searchedFor(text: String) {
@ -258,13 +182,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
tableView.reloadData()
}
// MARK: - Context Menu
@objc func openWithEditor(sender: EditorMenuItem) {
guard let editor = sender.editor else { return }
editor.openDirectory(file: selectedSite!.absolutePath!)
}
// MARK: - Deinitialization
deinit {

View File

@ -38,7 +38,7 @@ class Paths {
// - MARK: Binaries
public static var valet: String {
return "/Users/\(whoami)/.composer/vendor/bin/valet"
return "\(binPath)/valet"
}
public static var brew: String {

View File

@ -116,7 +116,8 @@ class Shell {
Uses `/bin/echo` instead of the `builtin` (which does not support `-n`).
*/
public static func fileExists(_ path: String) -> Bool {
return Shell.pipe("if [ -f \(path) ]; then /bin/echo -n \"0\"; fi") == "0"
let escapedPath = path.replacingOccurrences(of: " ", with: "\\ ")
return Shell.pipe("if [ -f \(escapedPath) ]; then /bin/echo -n \"0\"; fi") == "0"
}
}

View File

@ -74,6 +74,11 @@
"site_list.detected_apps" = "Detected Applications";
"site_list.system_apps" = "System Applications";
"site_list.warning.spaces" = "Warning! This site has a space in its folder.\nThe site will not be reachable via the browser.";
"site_list.alert.invalid_folder_name" = "Invalid folder name";
"site_list.alert.invalid_folder_name_desc" = "This folder could not be resolved to a valid URL. This is usually because theres a space in the folder name. Please rename the folder, reload the list of sites, and try again.";
// EDITORS
"editors.alert.try_again" = "Try Again";
@ -177,7 +182,7 @@ You can do this by running `composer global update` in your terminal. After that
/// 5. Valet & sudoers
"startup.errors.sudoers_valet.title" = "Valet has not been added to sudoers.d";
"startup.errors.sudoers_valet.desc" = "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.";
"startup.errors.sudoers_valet.desc" = "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue. If you did this before, please run `sudo valet trust` again.";
/// 6. Multiple services active
"startup.errors.services.title" = "Multiple PHP services are active";