mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2026-03-28 06:50:08 +01:00
♻️ Refactor "Open With" with third-party apps
- Icons are now loaded if possible (if the path could be inferred)
- Some new apps that are detected by default:
- Browsers: Safari, Google Chrome, Microsoft Edge, Firefox, Brave,
Arc, Zen
- Editors: WebStorm, VSCodium
- Git GUI: Tower, SourceTree
- Terminal: Ghostty
- `openWithEditor` has been refactored to `openWithApp` which now lets
browsers open domains from their URL, not from their local folder.
This commit is contained in:
@@ -84,8 +84,8 @@ class ExtensionMenuItem: NSMenuItem {
|
|||||||
var phpExtension: PhpExtension?
|
var phpExtension: PhpExtension?
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditorMenuItem: NSMenuItem {
|
class ApplicationMenuItem: NSMenuItem {
|
||||||
var editor: Application?
|
var app: Application?
|
||||||
}
|
}
|
||||||
|
|
||||||
class PresetMenuItem: NSMenuItem {
|
class PresetMenuItem: NSMenuItem {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import Foundation
|
|||||||
class Application {
|
class Application {
|
||||||
|
|
||||||
enum AppType {
|
enum AppType {
|
||||||
case editor, browser, git_gui, terminal, user_supplied
|
case editor, ide, browser, git_gui, terminal, user_supplied
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Container
|
// MARK: - Container
|
||||||
@@ -29,24 +29,50 @@ class Application {
|
|||||||
/// Application type. Depending on the type, a different action might occur.
|
/// Application type. Depending on the type, a different action might occur.
|
||||||
let type: AppType
|
let type: AppType
|
||||||
|
|
||||||
|
/// The full path to the application bundle (if found)
|
||||||
|
var path: String?
|
||||||
|
|
||||||
/// Initializer. Used to detect a specific app of a specific type.
|
/// Initializer. Used to detect a specific app of a specific type.
|
||||||
init(_ container: Container, _ name: String, _ type: AppType) {
|
init(_ container: Container, _ name: String, _ type: AppType) {
|
||||||
self.container = container
|
self.container = container
|
||||||
self.name = name
|
self.name = name
|
||||||
self.type = type
|
self.type = type
|
||||||
|
self.path = determinePath()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Attempt to open a specific directory in the app of choice.
|
Attempt to open a specific string (path or URL) in the app of choice.
|
||||||
(This will open the app if it isn't open yet.)
|
(This will open the app if it isn't open yet.)
|
||||||
*/
|
*/
|
||||||
@objc public func openDirectory(file: String) {
|
@objc public func open(arg: String) {
|
||||||
Task { await container.shell.quiet("/usr/bin/open -a \"\(name)\" \"\(file)\"") }
|
Task { await container.shell.quiet("/usr/bin/open -a \"\(name)\" \"\(arg)\"") }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Checks if the app is installed. */
|
/**
|
||||||
func isInstalled() async -> Bool {
|
Attempt to see if we can locate the app bundle in one of the two default locations:
|
||||||
|
- - First in `/Applications` (system-wide installed apps)
|
||||||
|
- - Second in `~/Applications` (user-specific installed apps)
|
||||||
|
|
||||||
|
If not in one of these default locations, the path will be `nil` and certain operations
|
||||||
|
will not be possible (i.e. determining icon via path to application).
|
||||||
|
*/
|
||||||
|
func determinePath() -> String? {
|
||||||
|
// Check global applications
|
||||||
|
if container.filesystem.directoryExists("/Applications/\(name).app") {
|
||||||
|
return "/Applications/\(name).app"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check user applications
|
||||||
|
if container.filesystem.directoryExists("~/Applications/\(name).app") {
|
||||||
|
return "~/Applications/\(name).app".replacingTildeWithHomeDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Checks if the app is installed and stores its path. */
|
||||||
|
func isInstalled() async -> Bool {
|
||||||
|
// Then verify it's actually installed using the shell command
|
||||||
let (process, output) = try! await container.shell.attach(
|
let (process, output) = try! await container.shell.attach(
|
||||||
"/usr/bin/open -Ra \"\(name)\"",
|
"/usr/bin/open -Ra \"\(name)\"",
|
||||||
didReceiveOutput: { _, _ in },
|
didReceiveOutput: { _, _ in },
|
||||||
@@ -71,11 +97,30 @@ class Application {
|
|||||||
var detected: [Application] = []
|
var detected: [Application] = []
|
||||||
|
|
||||||
let detectable = [
|
let detectable = [
|
||||||
Application(container, "PhpStorm", .editor),
|
// Browsers (for future Open In > Browser context menu)
|
||||||
|
Application(container, "Safari", .browser),
|
||||||
|
Application(container, "Google Chrome", .browser),
|
||||||
|
Application(container, "Microsoft Edge", .browser),
|
||||||
|
Application(container, "Firefox", .browser),
|
||||||
|
Application(container, "Brave", .browser),
|
||||||
|
Application(container, "Arc", .browser),
|
||||||
|
Application(container, "Zen", .browser),
|
||||||
|
|
||||||
|
// Editors
|
||||||
|
Application(container, "PhpStorm", .ide),
|
||||||
|
Application(container, "WebStorm", .ide),
|
||||||
Application(container, "Visual Studio Code", .editor),
|
Application(container, "Visual Studio Code", .editor),
|
||||||
|
Application(container, "VSCodium", .editor),
|
||||||
Application(container, "Sublime Text", .editor),
|
Application(container, "Sublime Text", .editor),
|
||||||
|
|
||||||
|
// Git
|
||||||
Application(container, "Sublime Merge", .git_gui),
|
Application(container, "Sublime Merge", .git_gui),
|
||||||
Application(container, "iTerm", .terminal)
|
Application(container, "Tower", .git_gui),
|
||||||
|
Application(container, "SourceTree", .git_gui),
|
||||||
|
|
||||||
|
// Terminals
|
||||||
|
Application(container, "iTerm", .terminal),
|
||||||
|
Application(container, "Ghostty", .terminal)
|
||||||
]
|
]
|
||||||
|
|
||||||
for app in detectable where await app.isInstalled() {
|
for app in detectable where await app.isInstalled() {
|
||||||
|
|||||||
@@ -38,9 +38,18 @@ extension DomainListVC {
|
|||||||
Task { await App.shared.container.shell.quiet("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") }
|
Task { await App.shared.container.shell.quiet("open -b com.apple.terminal '\(selectedSite!.absolutePath)'") }
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func openWithEditor(sender: EditorMenuItem) {
|
@objc func openWithApp(sender: ApplicationMenuItem) {
|
||||||
guard let editor = sender.editor else { return }
|
guard let site = selectedSite else { return }
|
||||||
editor.openDirectory(file: selectedSite!.absolutePath)
|
guard let app = sender.app else { return }
|
||||||
|
|
||||||
|
if app.type == .browser {
|
||||||
|
guard let url = site.getListableUrl() else { return }
|
||||||
|
// Open the URL for the domain
|
||||||
|
app.open(arg: url.absoluteString)
|
||||||
|
} else {
|
||||||
|
// Open the directory for the domain
|
||||||
|
app.open(arg: site.absolutePath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UI interaction
|
// MARK: - UI interaction
|
||||||
|
|||||||
@@ -98,15 +98,22 @@ extension DomainListVC {
|
|||||||
menu.addItem(NSMenuItem.separator())
|
menu.addItem(NSMenuItem.separator())
|
||||||
menu.addItem(HeaderView.asMenuItem(text: "domain_list.detected_apps".localized))
|
menu.addItem(HeaderView.asMenuItem(text: "domain_list.detected_apps".localized))
|
||||||
|
|
||||||
for editor in applications {
|
for app in applications where app.type != .browser {
|
||||||
let editorMenuItem = EditorMenuItem(
|
let menuItem = ApplicationMenuItem(
|
||||||
title: "domain_list.open_in".localized(editor.name),
|
title: "domain_list.open_in".localized(app.name),
|
||||||
action: #selector(self.openWithEditor(sender:)),
|
action: #selector(self.openWithApp(sender:)),
|
||||||
keyEquivalent: "",
|
keyEquivalent: "",
|
||||||
systemImage: "arrow.up.right"
|
systemImage: "arrow.up.right"
|
||||||
)
|
)
|
||||||
editorMenuItem.editor = editor
|
|
||||||
menu.addItem(editorMenuItem)
|
if let applicationPath = app.path {
|
||||||
|
let icon = NSWorkspace.shared.icon(forFile: applicationPath)
|
||||||
|
icon.size = NSSize(width: 16, height: 16)
|
||||||
|
menuItem.image = icon
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItem.app = app
|
||||||
|
menu.addItem(menuItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user