diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 332d8f2..62fb4e4 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; }; C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; }; C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */; }; + C41C708D28AA7F7900E8D498 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; }; C41CA5ED2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */; }; C41CA5EE2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */; }; C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; }; @@ -85,16 +86,21 @@ C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */; }; C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; }; C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4205A7D27F4D21800191A39 /* ValetProxy.swift */; }; + C422DDAA28A2C49900CEAC97 /* WarningListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* WarningListView.swift */; }; + C422DDAD28A2DAC600CEAC97 /* WarningsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */; }; C4232EE52612526500158FC6 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = C4232EE42612526500158FC6 /* Credits.html */; }; C42337A3281F19F000459A48 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; }; C42759672627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; }; C42759682627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; }; C42800AA28452AA10099C999 /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42800A928452AA10099C999 /* StatusMenu+Items.swift */; }; C42800AB28452AA50099C999 /* StatusMenu+Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42800A928452AA10099C999 /* StatusMenu+Items.swift */; }; + C4297F7A28970D59004C4630 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; }; C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; }; C42CFB1627DFDE7900862737 /* nginx-site.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nginx-site.test */; }; C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */; }; C42CFB1A27DFE8BD00862737 /* NginxConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */; }; + C42E3BF428A9BF5100AFECFC /* Shell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */; }; + C42E3BF528A9BF5100AFECFC /* Shell+PATH.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */; }; C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42F26722805B4B400938AC7 /* DomainListable.swift */; }; C42F26762805FEE200938AC7 /* nginx-secure-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */; }; @@ -114,8 +120,10 @@ C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; }; C449B4F327EE7FC600C47E8A /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; }; C449B4F427EE7FC800C47E8A /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; - C44C198D276E3A1C0072762D /* ProgressWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* ProgressWindow.swift */; }; - C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* ProgressWindow.swift */; }; + C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; }; + C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; }; + C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; }; + C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; }; C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; }; C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; }; C44CCD4027AFE2FC00CE40E5 /* AlertableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */; }; @@ -128,8 +136,8 @@ C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45E76132854A65300B4FE0C /* ServicesManager.swift */; }; C463E380284930EE00422731 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; }; C463E381284930EE00422731 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; }; - C464ADAC275A7A3F003FCD53 /* DomainListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWC.swift */; }; - C464ADAD275A7A3F003FCD53 /* DomainListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWC.swift */; }; + C464ADAC275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */; }; + C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */; }; C464ADAF275A7A69003FCD53 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; }; C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* DomainListVC.swift */; }; C464ADB2275A87CA003FCD53 /* DomainListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */; }; @@ -144,6 +152,8 @@ C473319F2470923A009A0597 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; + C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; }; + C47699F128A2F3150060FEB8 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; }; C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; }; C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; @@ -159,8 +169,10 @@ C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; }; C493084A279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; }; C493084B279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; }; - C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; }; - C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; }; + C495F5AF28A42E080087F70A /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; + C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */; }; + C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; }; + C4998F0B2617633900B2526E /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; }; C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */; }; C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; }; C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; }; @@ -204,6 +216,8 @@ C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; }; C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; }; C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CCBA6B275C567B008C7055 /* PMWindowController.swift */; }; + C4CDA893288F1A71007CE25F /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; }; + C4CDA894288F1A71007CE25F /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CDA892288F1A71007CE25F /* Keys.swift */; }; C4CE3BB827B31F2E0086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; }; C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; }; C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; }; @@ -226,6 +240,7 @@ C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; }; C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; }; + C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; }; C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E428551F9B006F9937 /* HeaderView.swift */; }; C4EB53E728553117006F9937 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E628553117006F9937 /* ArrayExtension.swift */; }; C4EC1E73279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; }; @@ -263,6 +278,9 @@ C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; }; C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; }; + C4FACE80288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */; }; + C4FACE81288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */; }; + C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */; }; C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FBFC512616485F00CDB8E1 /* PhpVersionDetectionTest.swift */; }; C4FC21B128391F8E00D368BB /* MainMenu+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F361602836BFD9003598CC /* MainMenu+Actions.swift */; }; C4FE011128084FC200D1DE6D /* SelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FE011028084FC200D1DE6D /* SelectionVC.swift */; }; @@ -326,18 +344,23 @@ C41C1B4022B0098000E7CF16 /* phpmon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = phpmon.entitlements; sourceTree = ""; }; C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = ""; }; C41C1B4A22B019FF00E7CF16 /* ActivePhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePhpInstallation.swift; sourceTree = ""; }; + C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoWarningsView.swift; sourceTree = ""; }; C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DomainListVC+Actions.swift"; sourceTree = ""; }; C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalKeybindPreference.swift; sourceTree = ""; }; C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DomainListVC+ContextMenu.swift"; sourceTree = ""; }; C4205A7D27F4D21800191A39 /* ValetProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetProxy.swift; sourceTree = ""; }; + C422DDA928A2C49900CEAC97 /* WarningListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningListView.swift; sourceTree = ""; }; + C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningsWindowController.swift; sourceTree = ""; }; C4232EE42612526500158FC6 /* Credits.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = Credits.html; sourceTree = ""; }; C42337A2281F19F000459A48 /* Xdebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xdebug.swift; sourceTree = ""; }; C42759662627662800093CAE /* NSMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuExtension.swift; sourceTree = ""; }; C42800A928452AA10099C999 /* StatusMenu+Items.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Items.swift"; sourceTree = ""; }; + C4297F7928970D59004C4630 /* WarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningView.swift; sourceTree = ""; }; C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+FixMyValet.swift"; sourceTree = ""; }; C42CFB1527DFDE7900862737 /* nginx-site.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site.test"; sourceTree = ""; }; C42CFB1727DFDFDC00862737 /* nginx-site-isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-site-isolated.test"; sourceTree = ""; }; C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationTest.swift; sourceTree = ""; }; + C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shell+PATH.swift"; sourceTree = ""; }; C42F26722805B4B400938AC7 /* DomainListable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListable.swift; sourceTree = ""; }; C42F26752805FEE200938AC7 /* nginx-secure-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-secure-proxy.test"; sourceTree = ""; }; C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = ""; }; @@ -350,7 +373,8 @@ C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTLSCell.swift; sourceTree = ""; }; C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIHelper.swift; sourceTree = ""; }; C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionPopoverView.swift; sourceTree = ""; }; - C44C198C276E3A1C0072762D /* ProgressWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressWindow.swift; sourceTree = ""; }; + C44A874728905BB000498BC4 /* ProgressVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressVC.swift; sourceTree = ""; }; + C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalProgressWindowController.swift; sourceTree = ""; }; C44C1990276E44CB0072762D /* ProgressWindow.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProgressWindow.storyboard; sourceTree = ""; }; C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertableError.swift; sourceTree = ""; }; C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Async.swift"; sourceTree = ""; }; @@ -358,7 +382,7 @@ C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = ""; }; C45E76132854A65300B4FE0C /* ServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesManager.swift; sourceTree = ""; }; C463E37F284930EE00422731 /* PresetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetHelper.swift; sourceTree = ""; }; - C464ADAB275A7A3F003FCD53 /* DomainListWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListWC.swift; sourceTree = ""; }; + C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListWindowController.swift; sourceTree = ""; }; C464ADAE275A7A69003FCD53 /* DomainListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListVC.swift; sourceTree = ""; }; C464ADB1275A87CA003FCD53 /* DomainListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListCellProtocol.swift; sourceTree = ""; }; C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateChecker.swift; sourceTree = ""; }; @@ -370,6 +394,8 @@ C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = ""; }; C474B00524C0E98C00066A22 /* LocalNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = ""; }; + C47699EE28A2F2A30060FEB8 /* WarningManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningManager.swift; sourceTree = ""; }; + C47699F028A2F3150060FEB8 /* Warning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Warning.swift; sourceTree = ""; }; C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = ""; }; @@ -379,7 +405,8 @@ C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = ""; }; C4927F0A27B2DFC200C55AFD /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = ""; }; - C4998F092617633900B2526E /* PrefsWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsWC.swift; sourceTree = ""; }; + C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentCheck.swift; sourceTree = ""; }; + C4998F092617633900B2526E /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; C4AC51FB27E27F47008528CA /* DomainListKindCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListKindCell.swift; sourceTree = ""; }; C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = ""; }; C4AF9F70275445FF00D44ED0 /* valet-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "valet-config.json"; sourceTree = ""; }; @@ -406,6 +433,7 @@ C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = ""; }; C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpConfigWatcher.swift; sourceTree = ""; }; C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = ""; }; + C4CDA892288F1A71007CE25F /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Switcher.swift"; sourceTree = ""; }; C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerWindow.swift; sourceTree = ""; }; C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationFile.swift; sourceTree = ""; }; @@ -420,6 +448,7 @@ C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = ""; }; C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = ""; }; C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = ""; }; + C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; C4EB53E428551F9B006F9937 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; }; C4EB53E628553117006F9937 /* ArrayExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = ""; }; C4EC1E72279DFCF40010F296 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = ""; }; @@ -438,6 +467,8 @@ C4F780AD25D80B37000DBC97 /* PhpExtensionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtensionTest.swift; sourceTree = ""; }; C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = ""; }; C4F8C0A522D4FA41002EFE61 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PreferencesWindowController+Hotkey.swift"; sourceTree = ""; }; + C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingWindowController.swift; sourceTree = ""; }; C4FBFC512616485F00CDB8E1 /* PhpVersionDetectionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionDetectionTest.swift; sourceTree = ""; }; C4FE011028084FC200D1DE6D /* SelectionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionVC.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -463,7 +494,8 @@ 5420395726135DB800FB00FA /* Preferences */ = { isa = PBXGroup; children = ( - C4998F092617633900B2526E /* PrefsWC.swift */, + C4998F092617633900B2526E /* PreferencesWindowController.swift */, + C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */, 5420395826135DC100FB00FA /* PrefsVC.swift */, 5420395E2613607600FB00FA /* Preferences.swift */, C4C3ED4227834C5200AB15D8 /* CustomPrefs.swift */, @@ -471,6 +503,7 @@ C4DEB7D327A5D60B00834718 /* Stats.swift */, C41CD0272628D8E20065BBED /* Keybinds */, 54FCFD28276C88C0004CE748 /* Views */, + C4CDA892288F1A71007CE25F /* Keys.swift */, ); path = Preferences; sourceTree = ""; @@ -580,6 +613,7 @@ C4B5853D2770FE3900DA4FBE /* Command.swift */, C4B5853B2770FE3900DA4FBE /* Paths.swift */, C4B5853C2770FE3900DA4FBE /* Shell.swift */, + C42E3BF328A9BF5100AFECFC /* Shell+PATH.swift */, C4C1019A27C65C6F001FACC2 /* Process.swift */, C40C7F2F27722E8D00DDDCDC /* Logger.swift */, C417DC73277614690015E6EE /* Helpers.swift */, @@ -645,6 +679,8 @@ C4D9ADBD27761084007277F4 /* PHP */, C47331A0247093AC009A0597 /* Menu */, C464ADAA275A7A25003FCD53 /* DomainList */, + C422DDAB28A2DAA100CEAC97 /* Warnings */, + C44A874628905B8500498BC4 /* Onboarding */, 5420395726135DB800FB00FA /* Preferences */, C44C198F276E3A380072762D /* Progress */, C4C8E81D276F5686003AC782 /* Watcher */, @@ -654,6 +690,16 @@ path = Domain; sourceTree = ""; }; + C422DDAB28A2DAA100CEAC97 /* Warnings */ = { + isa = PBXGroup; + children = ( + C47699F028A2F3150060FEB8 /* Warning.swift */, + C47699EE28A2F2A30060FEB8 /* WarningManager.swift */, + C422DDAC28A2DAC600CEAC97 /* WarningsWindowController.swift */, + ); + path = Warnings; + sourceTree = ""; + }; C42337A1281F19DC00459A48 /* Extensions */ = { isa = PBXGroup; children = ( @@ -662,6 +708,16 @@ path = Extensions; sourceTree = ""; }; + C4297F7828970D4E004C4630 /* Warning */ = { + isa = PBXGroup; + children = ( + C4297F7928970D59004C4630 /* WarningView.swift */, + C422DDA928A2C49900CEAC97 /* WarningListView.swift */, + C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */, + ); + path = Warning; + sourceTree = ""; + }; C44067F327E256560045BD4E /* Cells */ = { isa = PBXGroup; children = ( @@ -675,10 +731,19 @@ path = Cells; sourceTree = ""; }; + C44A874628905B8500498BC4 /* Onboarding */ = { + isa = PBXGroup; + children = ( + C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */, + ); + path = Onboarding; + sourceTree = ""; + }; C44C198F276E3A380072762D /* Progress */ = { isa = PBXGroup; children = ( - C44C198C276E3A1C0072762D /* ProgressWindow.swift */, + C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */, + C44A874728905BB000498BC4 /* ProgressVC.swift */, C44C1990276E44CB0072762D /* ProgressWindow.storyboard */, ); path = Progress; @@ -742,7 +807,7 @@ isa = PBXGroup; children = ( C44067F327E256560045BD4E /* Cells */, - C464ADAB275A7A3F003FCD53 /* DomainListWC.swift */, + C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */, C464ADAE275A7A69003FCD53 /* DomainListVC.swift */, C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */, C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */, @@ -835,6 +900,7 @@ C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */, C4EED88827A48778006D7272 /* InterAppHandler.swift */, C4D8016522B1584700C6DA1B /* Startup.swift */, + C495F5AE28A42E080087F70A /* EnvironmentCheck.swift */, C46E206C28299B3800D909D6 /* AppUpdateChecker.swift */, C40FE736282ABA4F00A302C2 /* AppVersion.swift */, C45E76132854A65300B4FE0C /* ServicesManager.swift */, @@ -1005,9 +1071,19 @@ path = Switcher; sourceTree = ""; }; + C4E9D2BE2878B32D008FFDAD /* Onboarding */ = { + isa = PBXGroup; + children = ( + C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */, + ); + path = Onboarding; + sourceTree = ""; + }; C4EE55B027708BB2001DF387 /* SwiftUI */ = { isa = PBXGroup; children = ( + C4297F7828970D4E004C4630 /* Warning */, + C4E9D2BE2878B32D008FFDAD /* Onboarding */, C4B609182853AAA700C95265 /* Domains */, C4B609172853AA9E00C95265 /* Menu */, C4B609162853AA9A00C95265 /* Common */, @@ -1199,12 +1275,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */, C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */, C4D8016622B1584700C6DA1B /* Startup.swift in Sources */, C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */, C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */, C4B585412770FE3900DA4FBE /* Shell.swift in Sources */, - C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */, + C4998F0A2617633900B2526E /* PreferencesWindowController.swift in Sources */, C46FA9882822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */, C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */, C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */, @@ -1214,6 +1291,7 @@ C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */, 5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */, C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, + C41C708D28AA7F7900E8D498 /* NoWarningsView.swift in Sources */, C4080FF627BD8C6400BF2C6B /* BetterAlert.swift in Sources */, C4E0F7ED27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */, @@ -1228,9 +1306,11 @@ C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */, C4C0E8DF27F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */, C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */, + C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */, C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */, C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */, C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */, + C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */, C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */, C4B585442770FE3900DA4FBE /* Command.swift in Sources */, C44067F527E2582B0045BD4E /* DomainListNameCell.swift in Sources */, @@ -1238,27 +1318,33 @@ C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */, C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */, C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */, + C4FACE80288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */, C42800AA28452AA10099C999 /* StatusMenu+Items.swift in Sources */, C415D3B72770F294005EF286 /* Actions.swift in Sources */, C4AC51FC27E27F47008528CA /* DomainListKindCell.swift in Sources */, + C4CDA893288F1A71007CE25F /* Keys.swift in Sources */, C4F361612836BFD9003598CC /* MainMenu+Actions.swift in Sources */, - C44C198D276E3A1C0072762D /* ProgressWindow.swift in Sources */, + C44C198D276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */, 54D9E0B827E4F51E003B9AD9 /* KeyCombo.swift in Sources */, C4C0E8E727F88B41002D32A9 /* ProxyScanner.swift in Sources */, C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */, 54B48B5F275F66AE006D90C5 /* Application.swift in Sources */, C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, C4CE3BB827B31F2E0086CA49 /* MainMenu+Switcher.swift in Sources */, + C422DDAD28A2DAC600CEAC97 /* WarningsWindowController.swift in Sources */, C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */, C4811D2422D70A4700B5F6B3 /* App.swift in Sources */, + C495F5AF28A42E080087F70A /* EnvironmentCheck.swift in Sources */, C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */, C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */, 54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */, + C4297F7A28970D59004C4630 /* WarningView.swift in Sources */, C4C0E8E227F88B13002D32A9 /* ValetSiteScanner.swift in Sources */, C42F26732805B4B400938AC7 /* DomainListable.swift in Sources */, 5420395F2613607600FB00FA /* Preferences.swift in Sources */, C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */, 54FCFD2A276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */, + C47699F128A2F3150060FEB8 /* Warning.swift in Sources */, 54D9E0B227E4F51E003B9AD9 /* HotKeysController.swift in Sources */, C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */, C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */, @@ -1275,6 +1361,7 @@ C484437B2804BB560041A78A /* ValetProxyScanner.swift in Sources */, C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */, C42759672627662800093CAE /* NSMenuExtension.swift in Sources */, + C422DDAA28A2C49900CEAC97 /* WarningListView.swift in Sources */, C464ADAF275A7A69003FCD53 /* DomainListVC.swift in Sources */, C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */, C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */, @@ -1296,6 +1383,7 @@ C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */, C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */, C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */, + C4FACE83288F1F9700FC478F /* OnboardingWindowController.swift in Sources */, C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */, C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */, 54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */, @@ -1306,10 +1394,11 @@ C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */, C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */, C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */, + C42E3BF428A9BF5100AFECFC /* Shell+PATH.swift in Sources */, C42337A3281F19F000459A48 /* Xdebug.swift in Sources */, C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */, C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */, - C464ADAC275A7A3F003FCD53 /* DomainListWC.swift in Sources */, + C464ADAC275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */, C464ADB2275A87CA003FCD53 /* DomainListCellProtocol.swift in Sources */, C4EE188422D3386B00E126E5 /* Constants.swift in Sources */, C493084A279F331F009C240B /* AddSiteVC.swift in Sources */, @@ -1324,6 +1413,7 @@ C449B4F427EE7FC800C47E8A /* DomainListKindCell.swift in Sources */, 54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */, C41CA5EE2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, + C4FACE81288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */, 54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */, C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */, C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */, @@ -1334,6 +1424,7 @@ C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */, C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */, C493084B279F331F009C240B /* AddSiteVC.swift in Sources */, + C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */, C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */, C41C02AA27E61CA3009F26CB /* SiteScanner.swift in Sources */, C4080FFB27BD956700BF2C6B /* BetterAlertVC.swift in Sources */, @@ -1348,6 +1439,7 @@ C449B4F027EE7FB800C47E8A /* DomainListTLSCell.swift in Sources */, C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */, C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */, + C42E3BF528A9BF5100AFECFC /* Shell+PATH.swift in Sources */, C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */, C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */, C4F319C927B034A500AFF46F /* Stats.swift in Sources */, @@ -1376,6 +1468,7 @@ C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */, C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */, C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */, + C495F5B028A42E080087F70A /* EnvironmentCheck.swift in Sources */, C41E871B2763D42300161EE0 /* DomainListVC+ContextMenu.swift in Sources */, C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */, C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, @@ -1398,14 +1491,15 @@ C46E20702829D27F00D909D6 /* AppUpdaterCheckTest.swift in Sources */, C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */, C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */, + C4CDA894288F1A71007CE25F /* Keys.swift in Sources */, C4D936CA27E3EB6100BD69FE /* PhpHelper.swift in Sources */, C449B4F127EE7FC200C47E8A /* DomainListNameCell.swift in Sources */, C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */, 54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */, - C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */, + C4998F0B2617633900B2526E /* PreferencesWindowController.swift in Sources */, C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */, C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */, - C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */, + C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */, C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */, C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */, C449B4F227EE7FC400C47E8A /* DomainListPhpCell.swift in Sources */, @@ -1434,7 +1528,7 @@ C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */, C4B585422770FE3900DA4FBE /* Shell.swift in Sources */, C45E76152854A65300B4FE0C /* ServicesManager.swift in Sources */, - C464ADAD275A7A3F003FCD53 /* DomainListWC.swift in Sources */, + C464ADAD275A7A3F003FCD53 /* DomainListWindowController.swift in Sources */, C40C7F1F2772136000DDDCDC /* PhpEnv.swift in Sources */, C464ADB0275A7A6A003FCD53 /* DomainListVC.swift in Sources */, C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */, @@ -1593,7 +1687,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 912; + CURRENT_PROJECT_VERSION = 951; DEBUG = YES; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -1603,7 +1697,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.5.0; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1620,7 +1714,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 912; + CURRENT_PROJECT_VERSION = 951; DEBUG = NO; DEVELOPMENT_TEAM = 8M54J5J787; ENABLE_HARDENED_RUNTIME = YES; @@ -1630,7 +1724,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.5.0; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme index 6d711af..730932a 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor.xcscheme @@ -68,6 +68,11 @@ + + **Note** > If this software has been useful to you, I ask that you **please star the repository**, that way I know that the software is being used. Also, please consider leaving [a one-time donation](https://nicoverbruggen.be/sponsor) to support the project, as this is something I make in my free time. **Thank you!** ⭐️

PHP Monitor Logo

@@ -342,6 +343,7 @@ Here's an example of a working preset:
 {
     "scan_apps": [],
+    "services": [],
     "presets": [
         {
             "name": "Legacy Project",
@@ -355,11 +357,67 @@ Here's an example of a working preset:
                 "post_max_size": "128M"
             }
         }
-    ]
+    ],
+    "export": {}
 }
 
You can omit the `php` key in the preset if you do not wish for the preset to switch to a given PHP version. + +> **Warning** +> You must restart PHP Monitor for these changes to be detected. + + +
+How do I ensure additional Homebrew services are shown in the app? + +You must set these services up in a JSON file, located in `~/.config/phpmon/config.json`. + +You can specify custom services in the configuration file for Homebrew services that run as your own user (not root). + +> **Info** +> If your service must run as root, it cannot currently be added to PHP Monitor. + +You can find out which services are available by running `brew services list`. + +Here's an example where we add the `mailhog` and `mysql` services to PHP Monitor: + +
+{
+    "scan_apps": [],
+    "services": ["mailhog", "mysql"],
+    "presets": [],
+    "export": {}
+}
+
+ +> **Warning** +> You must restart PHP Monitor for these changes to be detected. +
+ +
+How do I set custom environment variables? + +You must configure these custom environment variables up in a JSON file, located in `~/.config/phpmon/config.json`. + +PHP Monitor uses a default Shell environment, with no custom environment variables. You need to set custom environment variables manually. These are then used for e.g. Composer. + +Here's an example of a working `COMPOSER_HOME` environment variable which is respected: + +
+{
+    "scan_apps": [],
+    "services": [],
+    "presets": [],
+    "export": {
+        "COMPOSER_HOME": "/absolute/path/to/composer/folder"
+    }
+}
+
+ +> **Warning** +> You must restart PHP Monitor for these changes to be detected. +
@@ -377,12 +435,14 @@ You can add your own apps by creating and editing a `~/.config/phpmon/config.jso
 {
-    "scan_apps": ["Xcode", "Kraken"],
-    "presets": []
+    "scan_apps": ["Xcode", "Kraken"]
 }
 
You can put as many apps as you'd like in the `scan_apps` array, and PHP Monitor will check for the existence of these apps. You do not need to set the full path, just the name of the app should work. Not all apps support opening a folder, though, so your success might vary. + +> **Warning** +> You must restart PHP Monitor for these changes to be detected.
@@ -472,14 +532,14 @@ Donations really help with the Apple Developer Program cost, and keep me motivat ## 😎 Acknowledgements -While I did make this application during my own free time, PHP Monitor started out from various learning experiments during work hours at my employer, DIVE. I'd also like to shout out the following folks: +Special thanks go out to: -* My colleagues at [DIVE](https://dive.be) +* Everyone supporting me via [GitHub Sponsors](https://github.com/sponsors/nicoverbruggen) +* Everyone who has donated via [my sponsor page](https://nicoverbruggen.be/sponsor) * The [Homebrew](https://brew.sh/) team & [Valet maintainers](https://github.com/laravel/valet/graphs/contributors) * Various folks who [reached](https://twitter.com/stauffermatt) [out](https://twitter.com/marcelpociot) when PHP Monitor was still very much a small app with a handful of stars or so -* My [GitHub Sponsors](https://github.com/sponsors/nicoverbruggen) and those who have donated -* Everyone who has left feedback and reported bugs (appreciate it!) -* Everyone in the Laravel community who shared the app (thanks!) +* Everyone who has left feedback and reported bugs +* Everyone in the Laravel community who shared the app, especially on Twitter Thank you very much for your contributions, kind words and support. @@ -513,7 +573,8 @@ If an extension or other process writes to a single file a bunch of times in a s 1. **Sites secured or not secured**: Whether the directory has been secured is determined by checking if a matching certificate exists under Valet's `Certificates` directory for that site name. 1. **Project type**: PHP Monitor checks your `composer.json` file for "notable dependencies". If you have `laravel/framework` in your `require`, there's a good chance the project type is `Laravel`, after all. -*Note*: If you have linked a folder in Documents, Desktop or Downloads you might need to grant PHP Monitor access to those directories for PHP Monitor to work correctly. +> **Note** +> If you have linked a folder in Documents, Desktop or Downloads you might need to grant PHP Monitor access to those directories for PHP Monitor to work correctly. ### Want to know more? diff --git a/phpmon/Common/Core/Shell+PATH.swift b/phpmon/Common/Core/Shell+PATH.swift new file mode 100644 index 0000000..cd29071 --- /dev/null +++ b/phpmon/Common/Core/Shell+PATH.swift @@ -0,0 +1,27 @@ +// +// Shell+PATH.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 15/08/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +extension Shell { + + var PATH: String { + let task = Process() + task.launchPath = "/bin/zsh" + + // We need an interactive shell so the user's PATH is loaded in correctly + task.arguments = ["--login", "-ilc", "echo $PATH"] + + let pipe = Pipe() + task.standardOutput = pipe + task.launch() + let data = pipe.fileHandleForReading.readDataToEndOfFile() + + return String(data: data, encoding: String.Encoding.utf8) ?? "" + } +} diff --git a/phpmon/Common/Core/Shell.swift b/phpmon/Common/Core/Shell.swift index 3258e8d..132309e 100644 --- a/phpmon/Common/Core/Shell.swift +++ b/phpmon/Common/Core/Shell.swift @@ -32,6 +32,9 @@ public class Shell { */ public var shell: String = "/bin/sh" + /** Additional exports that are sent if `requiresPath` is set to true. */ + public var exports: String = "" + /** Singleton to access a user shell (with --login) */ @@ -114,13 +117,23 @@ public class Shell { Creates a new process with the correct PATH and shell. */ public func createTask(for command: String, requiresPath: Bool) -> Process { - let tailoredCommand = requiresPath - ? "export PATH=\(Paths.binPath):$PATH && \(command)" - : command + var completeCommand = "" + + if requiresPath { + // Basic export (PATH) + completeCommand += "export PATH=\(Paths.binPath):$PATH && " + + // Put additional exports in between + if !self.exports.isEmpty { + completeCommand += "\(self.exports) && " + } + } + + completeCommand += command let task = Process() task.launchPath = self.shell - task.arguments = ["--noprofile", "-norc", "--login", "-c", tailoredCommand] + task.arguments = ["--noprofile", "-norc", "--login", "-c", completeCommand] return task } diff --git a/phpmon/Common/Extensions/StringExtension.swift b/phpmon/Common/Extensions/StringExtension.swift index 897832d..a8746c1 100644 --- a/phpmon/Common/Extensions/StringExtension.swift +++ b/phpmon/Common/Extensions/StringExtension.swift @@ -5,6 +5,7 @@ // Copyright © 2022 Nico Verbruggen. All rights reserved. // import Foundation +import SwiftUI extension String { var localized: String { @@ -17,6 +18,10 @@ extension String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") } + var localizedForSwiftUI: LocalizedStringKey { + return LocalizedStringKey(self.localized) + } + func localized(_ args: CVarArg...) -> String { String(format: self.localized, arguments: args) } diff --git a/phpmon/Common/Helpers/Filesystem.swift b/phpmon/Common/Helpers/Filesystem.swift index 129763c..652ae44 100644 --- a/phpmon/Common/Helpers/Filesystem.swift +++ b/phpmon/Common/Helpers/Filesystem.swift @@ -1,5 +1,5 @@ // -// FileSystem.swift +// Filesystem.swift // PHP Monitor // // Created by Nico Verbruggen on 07/12/2021. @@ -7,17 +7,52 @@ // import Cocoa +import Foundation class Filesystem { /** - Checks if a file exists at the provided path. - Uses `FileManager`. + Checks if a file or directory exists at the provided path. */ - public static func fileExists(_ path: String) -> Bool { + public static func exists(_ path: String) -> Bool { return FileManager.default.fileExists( atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)") ) } + /** + Checks if a file exists at the provided path. + */ + public static func fileExists(_ path: String) -> Bool { + var isDirectory: ObjCBool = true + let exists = FileManager.default.fileExists( + atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)"), + isDirectory: &isDirectory + ) + + return exists && !isDirectory.boolValue + } + + public static func fileIsSymlink(_ path: String) -> Bool { + do { + let attribs = try FileManager.default.attributesOfItem(atPath: path) + return attribs[.type] as! FileAttributeType == FileAttributeType.typeSymbolicLink + } catch { + return false + } + } + + /** + Checks if a directory exists at the provided path. + */ + public static func directoryExists(_ path: String) -> Bool { + var isDirectory: ObjCBool = true + let exists = FileManager.default.fileExists( + atPath: path.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)"), + isDirectory: &isDirectory + ) + + return exists && isDirectory.boolValue + } + } diff --git a/phpmon/Common/Helpers/LocalNotification.swift b/phpmon/Common/Helpers/LocalNotification.swift index 91ffa4b..0c61e40 100644 --- a/phpmon/Common/Helpers/LocalNotification.swift +++ b/phpmon/Common/Helpers/LocalNotification.swift @@ -10,7 +10,7 @@ import UserNotifications class LocalNotification { - public static func send(title: String, subtitle: String, preference: PreferenceName) { + @MainActor public static func send(title: String, subtitle: String, preference: PreferenceName) { if !Preferences.isEnabled(preference) { return } diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 33935fa..9224635 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -16,8 +16,18 @@ class PhpHelper { // Take the PHP version (e.g. "7.2") and generate a dotless version let dotless = version.replacingOccurrences(of: ".", with: "") + // Determine the dotless name for this PHP version + let destination = "/Users/\(Paths.whoami)/.config/phpmon/bin/pm\(dotless)" + + // Check if the ~/.config/phpmon/bin directory is in the PATH + let inPath = Shell.user.PATH.contains("/Users/\(Paths.whoami)/.config/phpmon/bin") + + // Check if we can create symlinks (`/usr/local/bin` must be writable) + let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/") + do { - let destination = "/usr/local/bin/pm\(dotless)" + Shell.run("mkdir -p ~/.config/phpmon/bin") + if FileManager.default.fileExists(atPath: destination) { let contents = try String(contentsOfFile: destination) if !contents.contains(keyPhrase) { @@ -52,10 +62,40 @@ class PhpHelper { // Make sure the file is executable Shell.run("chmod +x \(destination)") + + // Create a symlink if the folder is not in the PATH + if !inPath { + // First, check if we can create symlinks at all + if !canWriteSymlinks { + Log.err("PHP Monitor does not have permission to symlink `/usr/local/bin/\(dotless)`.") + return + } + + // Write the symlink + self.createSymlink(dotless) + } } catch { print(error) - Log.err("Could not write PHP Monitor helper for PHP \(version) to /usr/local/bin/pm\(dotless)") + Log.err("Could not write PHP Monitor helper for PHP \(version) to \(destination))") } } + private static func createSymlink(_ dotless: String) { + let source = "/Users/\(Paths.whoami)/.config/phpmon/bin/pm\(dotless)" + let destination = "/usr/local/bin/pm\(dotless)" + + if !Filesystem.fileExists(destination) { + Log.info("Creating new symlink: \(destination)") + Shell.run("ln -s \(source) \(destination)") + return + } + + if !Filesystem.fileIsSymlink(destination) { + Log.info("Overwriting existing file with new symlink: \(destination)") + Shell.run("ln -fs \(source) \(destination)") + return + } + + Log.info("Symlink in \(destination) already exists, OK.") + } } diff --git a/phpmon/Credits.html b/phpmon/Credits.html index 40bcc83..d2e9a84 100644 --- a/phpmon/Credits.html +++ b/phpmon/Credits.html @@ -13,9 +13,9 @@
-

Want to spread the love? Leave a star on GitHub!

-

Having issues? Consult the FAQ & Troubleshooting section.

-

Want to support me? You can financially support the continued development of this app.

+

Do you enjoy using the app? Leave a star on GitHub!

+

Having issues? Consult the FAQ section, I did my best to ensure everything is documented.

+

Want to support further development of PHP Monitor? You can financially support the continued development of this app.

Get the latest on Twitter Give me a follow on Twitter to learn about the latest and greatest updates of this app.


diff --git a/phpmon/Domain/App/App.swift b/phpmon/Domain/App/App.swift index fcbee6f..df288d8 100644 --- a/phpmon/Domain/App/App.swift +++ b/phpmon/Domain/App/App.swift @@ -51,10 +51,16 @@ class App { var preferences: [PreferenceName: Bool]! /** The window controller of the currently active preferences window. */ - var preferencesWindowController: PrefsWC? + var preferencesWindowController: PreferencesWindowController? /** The window controller of the currently active site list window. */ - var domainListWindowController: DomainListWC? + var domainListWindowController: DomainListWindowController? + + /** The window controller of the onboarding window. */ + var onboardingWindowController: OnboardingWindowController? + + /** The window controller of the warnings window. */ + var warningsWindowController: WarningsWindowController? /** List of detected (installed) applications that PHP Monitor can work with. */ var detectedApplications: [Application] = [] @@ -62,6 +68,9 @@ class App { /** The services manager, responsible for figuring out what services are active/inactive. */ var services = ServicesManager.shared + /** The warning manager, responsible for keeping track of warnings. */ + var warnings = WarningManager.shared + /** Timer that will periodically reload info about the user's PHP installation. */ var timer: Timer? diff --git a/phpmon/Domain/App/AppUpdateChecker.swift b/phpmon/Domain/App/AppUpdateChecker.swift index 4620ccd..7ac48da 100644 --- a/phpmon/Domain/App/AppUpdateChecker.swift +++ b/phpmon/Domain/App/AppUpdateChecker.swift @@ -161,14 +161,14 @@ class AppUpdateChecker { private static func notifyAboutConnectionIssue() { DispatchQueue.main.async { BetterAlert().withInformation( - title: "updater.errors.cannot_check_for_update.title".localized, - subtitle: "updater.errors.cannot_check_for_update.subtitle".localized, - description: "updater.errors.cannot_check_for_update.description".localized( + title: "updater.alerts.cannot_check_for_update.title".localized, + subtitle: "updater.alerts.cannot_check_for_update.subtitle".localized, + description: "updater.alerts.cannot_check_for_update.description".localized( App.version ) ) .withTertiary( - text: "updater.errors.buttons.releases_on_github".localized, + text: "updater.alerts.buttons.releases_on_github".localized, action: { _ in NSWorkspace.shared.open(Constants.Urls.GitHubReleases) } diff --git a/phpmon/Domain/App/Base.lproj/Main.storyboard b/phpmon/Domain/App/Base.lproj/Main.storyboard index 679485d..1198b94 100644 --- a/phpmon/Domain/App/Base.lproj/Main.storyboard +++ b/phpmon/Domain/App/Base.lproj/Main.storyboard @@ -1,7 +1,6 @@ - @@ -325,7 +324,7 @@ - + @@ -399,7 +398,7 @@ - + @@ -819,7 +818,7 @@ Gw - + diff --git a/phpmon/Domain/App/EnvironmentCheck.swift b/phpmon/Domain/App/EnvironmentCheck.swift new file mode 100644 index 0000000..a044ddf --- /dev/null +++ b/phpmon/Domain/App/EnvironmentCheck.swift @@ -0,0 +1,45 @@ +// +// EnvironmentCheck.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 10/08/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +/** + The `EnvironmentCheck` is used to defer the execution of all of these commands until necessary. + Checks that require an app restart will always lead to an alert and app termination shortly after. + */ +struct EnvironmentCheck { + let command: () async -> Bool + let name: String + let titleText: String + let subtitleText: String + let descriptionText: String + let buttonText: String + let requiresAppRestart: Bool + + init( + command: @escaping () async -> Bool, + name: String, + titleText: String, + subtitleText: String, + descriptionText: String = "", + buttonText: String = "OK", + requiresAppRestart: Bool = false + ) { + self.command = command + self.name = name + self.titleText = titleText + self.subtitleText = subtitleText + self.descriptionText = descriptionText + self.buttonText = buttonText + self.requiresAppRestart = requiresAppRestart + } + + public func succeeds() async -> Bool { + return await !self.command() + } +} diff --git a/phpmon/Domain/App/InterAppHandler.swift b/phpmon/Domain/App/InterAppHandler.swift index 227fde3..73e6477 100644 --- a/phpmon/Domain/App/InterAppHandler.swift +++ b/phpmon/Domain/App/InterAppHandler.swift @@ -56,12 +56,14 @@ class InterApp { if PhpEnv.shared.availablePhpVersions.contains(version) { MainMenu.shared.switchToPhpVersion(version) } else { - BetterAlert().withInformation( - title: "alert.php_switch_unavailable.title".localized, - subtitle: "alert.php_switch_unavailable.subtitle".localized(version) - ).withPrimary( - text: "alert.php_switch_unavailable.ok".localized - ).show() + DispatchQueue.main.async { + BetterAlert().withInformation( + title: "alert.php_switch_unavailable.title".localized, + subtitle: "alert.php_switch_unavailable.subtitle".localized(version) + ).withPrimary( + text: "alert.php_switch_unavailable.ok".localized + ).show() + } } }) ]} diff --git a/phpmon/Domain/App/Startup.swift b/phpmon/Domain/App/Startup.swift index 38ebf9b..8d80154 100644 --- a/phpmon/Domain/App/Startup.swift +++ b/phpmon/Domain/App/Startup.swift @@ -167,6 +167,18 @@ class Startup { descriptionText: "startup.errors.services_json_error.desc".localized ), // ================================================================================= + // Determine that Valet is installed + // ================================================================================= + EnvironmentCheck( + command: { + return !Filesystem.directoryExists("~/.config/valet") + }, + name: "`.config/valet` not empty (Valet installed)", + titleText: "startup.errors.valet_not_installed.title".localized, + subtitleText: "startup.errors.valet_not_installed.subtitle".localized, + descriptionText: "startup.errors.valet_not_installed.desc".localized + ), + // ================================================================================= // Determine that the Valet configuration JSON file is valid. // ================================================================================= EnvironmentCheck( @@ -217,8 +229,19 @@ class Startup { EnvironmentCheck( command: { let output = valet("--version", sudo: false) + // Failure condition #1: does not contain Laravel Valet + if !output.contains("Laravel Valet") { + return true + } + // Failure condition #2: version cannot be parsed + let versionString = output + .trimmingCharacters(in: .whitespacesAndNewlines) + .components(separatedBy: "Laravel Valet")[1] + .trimmingCharacters(in: .whitespaces) + // Extract the version number Valet.shared.version = VersionExtractor.from(output) - return Valet.shared.version == nil && output.contains("Laravel Valet") + // Get the actual version + return Valet.shared.version == nil }, name: "`valet --version` was loaded", titleText: "startup.errors.valet_version_unknown.title".localized, @@ -226,42 +249,4 @@ class Startup { descriptionText: "startup.errors.valet_version_unknown.desc".localized ) ] - - // MARK: - EnvironmentCheck struct - - /** - The `EnvironmentCheck` is used to defer the execution of all of these commands until necessary. - Checks that require an app restart will always lead to an alert and app termination shortly after. - */ - struct EnvironmentCheck { - let command: () async -> Bool - let name: String - let titleText: String - let subtitleText: String - let descriptionText: String - let buttonText: String - let requiresAppRestart: Bool - - init( - command: @escaping () async -> Bool, - name: String, - titleText: String, - subtitleText: String, - descriptionText: String = "", - buttonText: String = "OK", - requiresAppRestart: Bool = false - ) { - self.command = command - self.name = name - self.titleText = titleText - self.subtitleText = subtitleText - self.descriptionText = descriptionText - self.buttonText = buttonText - self.requiresAppRestart = requiresAppRestart - } - - public func succeeds() async -> Bool { - return await !self.command() - } - } } diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index 43c2603..7a0193e 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -55,7 +55,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { let path = pathControl.url!.path let name = inputDomainName.stringValue - if !FileManager.default.fileExists(atPath: path) { + if !Filesystem.exists(path) { Alert.confirm( onWindow: view.window!, messageText: "domain_list.alert.folder_missing.title".localized, diff --git a/phpmon/Domain/DomainList/DomainListVC.swift b/phpmon/Domain/DomainList/DomainListVC.swift index 9d8925d..68257bb 100644 --- a/phpmon/Domain/DomainList/DomainListVC.swift +++ b/phpmon/Domain/DomainList/DomainListVC.swift @@ -64,17 +64,16 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource let windowController = storyboard.instantiateController( withIdentifier: "domainListWindow" - ) as! DomainListWC + ) as! DomainListWindowController - windowController.window!.title = "domain_list.title".localized - windowController.window!.subtitle = "domain_list.subtitle".localized - windowController.window!.delegate = delegate - windowController.window!.styleMask = [ - .titled, .closable, .resizable, .miniaturizable - ] - windowController.window!.minSize = NSSize(width: 550, height: 200) - windowController.window!.delegate = windowController - windowController.window!.setFrameAutosaveName("domainListWindow") + guard let window = windowController.window else { return } + + window.title = "domain_list.title".localized + window.subtitle = "domain_list.subtitle".localized + window.delegate = delegate ?? windowController + window.styleMask = [.titled, .closable, .resizable, .miniaturizable] + window.minSize = NSSize(width: 550, height: 200) + window.setFrameAutosaveName("domainListWindow") App.shared.domainListWindowController = windowController } diff --git a/phpmon/Domain/DomainList/DomainListWC.swift b/phpmon/Domain/DomainList/DomainListWindowController.swift similarity index 94% rename from phpmon/Domain/DomainList/DomainListWC.swift rename to phpmon/Domain/DomainList/DomainListWindowController.swift index 6917680..df92f75 100644 --- a/phpmon/Domain/DomainList/DomainListWC.swift +++ b/phpmon/Domain/DomainList/DomainListWindowController.swift @@ -1,5 +1,5 @@ // -// DomainListWC.swift +// DomainListWindowController.swift // PHP Monitor // // Created by Nico Verbruggen on 03/12/2021. @@ -8,7 +8,7 @@ import Cocoa -class DomainListWC: PMWindowController, NSSearchFieldDelegate, NSToolbarDelegate { +class DomainListWindowController: PMWindowController, NSSearchFieldDelegate, NSToolbarDelegate { // MARK: - Window Identifier @@ -124,8 +124,6 @@ class DomainListWC: PMWindowController, NSSearchFieldDelegate, NSToolbarDelegate withIdentifier: "addProxyWindow" ) as! NSWindowController - // let viewController = windowController.window!.contentViewController as! AddSiteVC - self.window?.beginSheet(windowController.window!) } } diff --git a/phpmon/Domain/DomainList/SelectionVC.swift b/phpmon/Domain/DomainList/SelectionVC.swift index 1f4a82c..80c2323 100644 --- a/phpmon/Domain/DomainList/SelectionVC.swift +++ b/phpmon/Domain/DomainList/SelectionVC.swift @@ -11,7 +11,7 @@ import Cocoa class SelectionVC: NSViewController { - weak var domainListWC: DomainListWC? + weak var domainListWC: DomainListWindowController? @IBOutlet weak var textFieldTitle: NSTextField! @IBOutlet weak var textFieldDescription: NSTextField! diff --git a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift index f9413e0..2868636 100644 --- a/phpmon/Domain/Integrations/Composer/ComposerWindow.swift +++ b/phpmon/Domain/Integrations/Composer/ComposerWindow.swift @@ -13,7 +13,7 @@ class ComposerWindow { private var menu: MainMenu? private var shouldNotify: Bool! = nil private var completion: ((Bool) -> Void)! = nil - private var window: ProgressWindowController? + private var window: TerminalProgressWindowController? /** Updates the global dependencies and runs the completion callback when done. @@ -25,7 +25,9 @@ class ComposerWindow { Paths.shared.detectBinaryPaths() if Paths.composer == nil { - presentMissingAlert() + DispatchQueue.main.async { + self.presentMissingAlert() + } return } @@ -33,7 +35,7 @@ class ComposerWindow { menu?.setBusyImage() menu?.rebuild() - window = ProgressWindowController.display( + window = TerminalProgressWindowController.display( title: "alert.composer_progress.title".localized, description: "alert.composer_progress.info".localized ) @@ -116,7 +118,7 @@ class ComposerWindow { // MARK: Alert - private func presentMissingAlert() { + @MainActor private func presentMissingAlert() { BetterAlert() .withInformation( title: "alert.composer_missing.title".localized, diff --git a/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift b/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift index fa72d92..95fc0d0 100644 --- a/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift +++ b/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift @@ -71,7 +71,7 @@ struct PhpFrameworks { public static func detectFallbackDependency(_ basePath: String) -> String? { for entry in Self.FileMapping { let found = entry.value - .map { path in return Filesystem.fileExists(basePath + path) } + .map { path in return Filesystem.exists(basePath + path) } .contains(true) if found { diff --git a/phpmon/Domain/Menu/MainMenu+Actions.swift b/phpmon/Domain/Menu/MainMenu+Actions.swift index c7dc77e..6e2819f 100644 --- a/phpmon/Domain/Menu/MainMenu+Actions.swift +++ b/phpmon/Domain/Menu/MainMenu+Actions.swift @@ -53,13 +53,11 @@ extension MainMenu { Actions.restartPhpFpm() Actions.restartNginx() } success: { - DispatchQueue.main.async { - LocalNotification.send( - title: "notification.services_restarted".localized, - subtitle: "notification.services_restarted_desc".localized, - preference: .notifyAboutServices - ) - } + LocalNotification.send( + title: "notification.services_restarted".localized, + subtitle: "notification.services_restarted_desc".localized, + preference: .notifyAboutServices + ) } } @@ -67,13 +65,11 @@ extension MainMenu { asyncExecution { Actions.stopValetServices() } success: { - DispatchQueue.main.async { - LocalNotification.send( - title: "notification.services_stopped".localized, - subtitle: "notification.services_stopped_desc".localized, - preference: .notifyAboutServices - ) - } + LocalNotification.send( + title: "notification.services_stopped".localized, + subtitle: "notification.services_stopped_desc".localized, + preference: .notifyAboutServices + ) } } @@ -155,7 +151,7 @@ extension MainMenu { } } - @objc func rollbackPreset() { + @MainActor @objc func rollbackPreset() { guard let preset = PresetHelper.rollbackPreset else { return } @@ -180,7 +176,7 @@ extension MainMenu { } } - @objc func showPresetHelp() { + @MainActor @objc func showPresetHelp() { BetterAlert().withInformation( title: "preset_help_title".localized, subtitle: "preset_help_info".localized, diff --git a/phpmon/Domain/Menu/MainMenu+Async.swift b/phpmon/Domain/Menu/MainMenu+Async.swift index 3a9d273..11aea36 100644 --- a/phpmon/Domain/Menu/MainMenu+Async.swift +++ b/phpmon/Domain/Menu/MainMenu+Async.swift @@ -36,8 +36,8 @@ extension MainMenu { */ func asyncExecution( _ execute: @escaping () throws -> Void, - success: @escaping () -> Void = {}, - failure: @escaping (Error) -> Void = { _ in }, + success: @MainActor @escaping () -> Void = {}, + failure: @MainActor @escaping (Error) -> Void = { _ in }, behaviours: [AsyncBehaviour] = [ .setsBusyUI, .reloadsPhpInstallation, diff --git a/phpmon/Domain/Menu/MainMenu+FixMyValet.swift b/phpmon/Domain/Menu/MainMenu+FixMyValet.swift index aff2fea..00bd5a6 100644 --- a/phpmon/Domain/Menu/MainMenu+FixMyValet.swift +++ b/phpmon/Domain/Menu/MainMenu+FixMyValet.swift @@ -11,7 +11,7 @@ import AppKit extension MainMenu { - @objc func fixMyValet() { + @MainActor @objc func fixMyValet() { let previousVersion = PhpEnv.phpInstall.version.short if !PhpEnv.shared.availablePhpVersions.contains(PhpEnv.brewPhpVersion) { @@ -42,7 +42,7 @@ extension MainMenu { } } - private func presentAlertForMissingFormula() { + @MainActor private func presentAlertForMissingFormula() { BetterAlert() .withInformation( title: "alert.php_formula_missing.title".localized, @@ -52,7 +52,7 @@ extension MainMenu { .show() } - private func presentAlertForSameVersion() { + @MainActor private func presentAlertForSameVersion() { BetterAlert() .withInformation( title: "alert.fix_my_valet_done.title".localized, @@ -63,7 +63,7 @@ extension MainMenu { .show() } - private func presentAlertForDifferentVersion(version: String) { + @MainActor private func presentAlertForDifferentVersion(version: String) { BetterAlert() .withInformation( title: "alert.fix_my_valet_done.title".localized, diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 9258b3a..6cc8e4b 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -57,6 +57,9 @@ extension MainMenu { let installation = PhpEnv.phpInstall installation.notifyAboutBrokenPhpFpm() + // Check for other problems + WarningManager.shared.evaluateWarnings() + // Set up the config watchers on launch (updated automatically when switching) Log.info("Setting up watchers...") App.shared.handlePhpConfigWatcher() @@ -79,6 +82,7 @@ extension MainMenu { // A non-default TLD is not officially supported since Valet 3.2.x Valet.notifyAboutUnsupportedTLD() + // Find out which services are active ServicesManager.shared.loadData() // Start the background refresh timer @@ -88,6 +92,14 @@ extension MainMenu { Stats.incrementSuccessfulLaunchCount() Stats.evaluateSponsorMessageShouldBeDisplayed() + // Present first launch screen if needed + if Stats.successfulLaunchCount == 0 && !isRunningSwiftUIPreview { + Log.info("Should present the first launch screen!") + DispatchQueue.main.async { + OnboardingWindowController.show() + } + } + // Check for updates DispatchQueue.global(qos: .utility).async { AppUpdateChecker.checkIfNewerVersionIsAvailable() diff --git a/phpmon/Domain/Menu/MainMenu+Switcher.swift b/phpmon/Domain/Menu/MainMenu+Switcher.swift index 548aa16..66c61d2 100644 --- a/phpmon/Domain/Menu/MainMenu+Switcher.swift +++ b/phpmon/Domain/Menu/MainMenu+Switcher.swift @@ -52,14 +52,14 @@ extension MainMenu { } } - private func checkForPlatformIssues() { + @MainActor private func checkForPlatformIssues() { if Valet.shared.hasPlatformIssues() { Log.info("Composer platform issue(s) detected.") self.suggestFixMyComposer() } } - private func suggestFixMyValet(failed version: String) { + @MainActor private func suggestFixMyValet(failed version: String) { let outcome = BetterAlert() .withInformation( title: "alert.php_switch_failed.title".localized(version), @@ -74,7 +74,7 @@ extension MainMenu { } } - private func suggestFixMyComposer() { + @MainActor private func suggestFixMyComposer() { BetterAlert().withInformation( title: "alert.global_composer_platform_issues.title".localized, subtitle: "alert.global_composer_platform_issues.subtitle".localized, @@ -111,12 +111,14 @@ extension MainMenu { } private func notifyAboutVersionChange(to version: String) { - LocalNotification.send( - title: String(format: "notification.version_changed_title".localized, version), - subtitle: String(format: "notification.version_changed_desc".localized, version), - preference: .notifyAboutVersionChange - ) + DispatchQueue.main.async { + LocalNotification.send( + title: String(format: "notification.version_changed_title".localized, version), + subtitle: String(format: "notification.version_changed_desc".localized, version), + preference: .notifyAboutVersionChange + ) - PhpEnv.phpInstall.notifyAboutBrokenPhpFpm() + PhpEnv.phpInstall.notifyAboutBrokenPhpFpm() + } } } diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index bf4990e..7fe10b6 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -21,7 +21,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate /** The status bar item with variable length. */ - let statusItem = NSStatusBar.system.statusItem( + @MainActor let statusItem = NSStatusBar.system.statusItem( withLength: NSStatusItem.variableLength ) @@ -77,7 +77,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate /** Sets the status bar image based on a version string. */ - func setStatusBarImage(version: String) { + @MainActor func setStatusBarImage(version: String) { setStatusBar( image: (Preferences.preferences[.iconTypeToDisplay] as! String != MenuBarIcon.noIcon.rawValue) ? MenuBarImageGenerator.textToImageWithIcon(text: version) @@ -89,7 +89,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate Sets the status bar image, based on the provided NSImage. The image will be used as a template image. */ - func setStatusBar(image: NSImage) { + @MainActor func setStatusBar(image: NSImage) { if let button = statusItem.button { image.isTemplate = true button.image = image @@ -125,6 +125,16 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate ServicesManager.shared.loadData() } + /** + Shows the Welcome Tour screen, again. + Did this need a comment? No, probably not. + */ + @objc func showWelcomeTour() { + DispatchQueue.main.async { + OnboardingWindowController.show() + } + } + /** Reloads the menu in the background, using `asyncExecution`. */ @objc func reloadPhpMonitorMenuInBackground() { asyncExecution({ @@ -171,7 +181,11 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate } @objc func openPrefs() { - PrefsWC.show() + PreferencesWindowController.show() + } + + @objc func openWarnings() { + WarningsWindowController.show() } @objc func openDomainList() { diff --git a/phpmon/Domain/Menu/StatusMenu+Items.swift b/phpmon/Domain/Menu/StatusMenu+Items.swift index 58c95be..c6bfde5 100644 --- a/phpmon/Domain/Menu/StatusMenu+Items.swift +++ b/phpmon/Domain/Menu/StatusMenu+Items.swift @@ -157,6 +157,7 @@ extension StatusMenu { return } + self.addItem(NSMenuItem.separator()) let xdebugSwitch = NSMenuItem( title: "mi_xdebug_mode".localized, action: nil, @@ -199,6 +200,14 @@ extension StatusMenu { let servicesMenu = NSMenu() servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_first_aid".localized)) + + servicesMenu.addItem(NSMenuItem(title: "mi_view_onboarding".localized, + action: #selector(MainMenu.showWelcomeTour), keyEquivalent: "")) + + servicesMenu.addItem(NSMenuItem(title: "mi_fa_php_doctor".localized, + action: #selector(MainMenu.openWarnings), keyEquivalent: "")) + servicesMenu.addItem(NSMenuItem.separator()) + let fixMyValetMenuItem = NSMenuItem( title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion), action: #selector(MainMenu.fixMyValet), keyEquivalent: "" @@ -216,22 +225,15 @@ extension StatusMenu { servicesMenu.addItem(NSMenuItem.separator()) servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_services".localized)) - servicesMenu.addItem( - NSMenuItem(title: "mi_restart_dnsmasq".localized, - action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d") - ) - servicesMenu.addItem( - NSMenuItem(title: "mi_restart_php_fpm".localized, - action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "p") - ) - servicesMenu.addItem( - NSMenuItem(title: "mi_restart_nginx".localized, - action: #selector(MainMenu.restartNginx), keyEquivalent: "n") - ) - servicesMenu.addItem( - NSMenuItem(title: "mi_restart_valet_services".localized, - action: #selector(MainMenu.restartValetServices), keyEquivalent: "s") - ) + servicesMenu.addItem(NSMenuItem(title: "mi_restart_dnsmasq".localized, + action: #selector(MainMenu.restartDnsMasq), keyEquivalent: "d")) + servicesMenu.addItem(NSMenuItem(title: "mi_restart_php_fpm".localized, + action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "p")) + + servicesMenu.addItem(NSMenuItem(title: "mi_restart_nginx".localized, + action: #selector(MainMenu.restartNginx), keyEquivalent: "n")) + servicesMenu.addItem(NSMenuItem(title: "mi_restart_valet_services".localized, + action: #selector(MainMenu.restartValetServices), keyEquivalent: "s")) servicesMenu.addItem( NSMenuItem(title: "mi_stop_valet_services".localized, action: #selector(MainMenu.stopValetServices), keyEquivalent: "s"), diff --git a/phpmon/Domain/Menu/StatusMenu.swift b/phpmon/Domain/Menu/StatusMenu.swift index 5da271e..5f14cad 100644 --- a/phpmon/Domain/Menu/StatusMenu.swift +++ b/phpmon/Domain/Menu/StatusMenu.swift @@ -66,16 +66,40 @@ class StatusMenu: NSMenu { self.addExtensionsMenuItems() + self.addXdebugMenuItem() + + self.addPhpDoctorMenuItem() + self.addItem(NSMenuItem.separator()) - self.addXdebugMenuItem() self.addPresetsMenuItem() self.addFirstAidAndServicesMenuItems() } + func addPhpDoctorMenuItem() { + if !Preferences.isEnabled(.showPhpDoctorSuggestions) || + !WarningManager.shared.hasWarnings() { + return + } + + self.addItem(NSMenuItem.separator()) + self.addItem(HeaderView.asMenuItem(text: "mi_php_doctor".localized)) + self.addItem(NSMenuItem( + title: "mi_recommendations_count".localized(WarningManager.shared.warnings.count), + action: nil, + keyEquivalent: "" + )) + self.addItem(NSMenuItem( + title: "mi_view_recommendations".localized, + action: #selector(MainMenu.openWarnings), + keyEquivalent: "" + )) + } + func addCoreMenuItems() { self.addItem(NSMenuItem.separator()) + self.addItem(NSMenuItem(title: "mi_preferences".localized, action: #selector(MainMenu.openPrefs), keyEquivalent: ",")) self.addItem(NSMenuItem(title: "mi_check_for_updates".localized, diff --git a/phpmon/Domain/Notice/BetterAlert.swift b/phpmon/Domain/Notice/BetterAlert.swift index d8dc6db..8013949 100644 --- a/phpmon/Domain/Notice/BetterAlert.swift +++ b/phpmon/Domain/Notice/BetterAlert.swift @@ -103,14 +103,14 @@ class BetterAlert { /** Shows the modal and does not return anything. */ - public func show() { + @MainActor public func show() { _ = self.runModal() } /** Shows the modal for a particular error. */ - public static func show(for error: Error & AlertableError) { + @MainActor public static func show(for error: Error & AlertableError) { let key = error.getErrorMessageKey() return BetterAlert().withInformation( title: "\(key).title".localized, diff --git a/phpmon/Domain/Onboarding/OnboardingWindowController.swift b/phpmon/Domain/Onboarding/OnboardingWindowController.swift new file mode 100644 index 0000000..0f73b9b --- /dev/null +++ b/phpmon/Domain/Onboarding/OnboardingWindowController.swift @@ -0,0 +1,45 @@ +// +// OnboardingWindowController.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 25/06/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Cocoa +import SwiftUI + +class OnboardingWindowController: PMWindowController { + + // MARK: - Window Identifier + + override var windowName: String { + return "Onboarding" + } + + public static func create(delegate: NSWindowDelegate?) { + let windowController = Self() + windowController.window = NSWindow() + + guard let window = windowController.window else { return } + window.title = "" + window.styleMask = [.titled, .closable, .miniaturizable] + window.titlebarAppearsTransparent = true + window.delegate = delegate ?? windowController + window.contentView = NSHostingView(rootView: OnboardingView()) + window.setContentSize(NSSize(width: 600, height: 600)) + + App.shared.onboardingWindowController = windowController + } + + public static func show(delegate: NSWindowDelegate? = nil) { + if App.shared.onboardingWindowController == nil { + Self.create(delegate: delegate) + } + + App.shared.onboardingWindowController?.showWindow(self) + App.shared.onboardingWindowController?.window?.setCenterPosition(offsetY: 70) + + NSApp.activate(ignoringOtherApps: true) + } +} diff --git a/phpmon/Domain/Preferences/CustomPrefs.swift b/phpmon/Domain/Preferences/CustomPrefs.swift index 6706f2e..6f8cec4 100644 --- a/phpmon/Domain/Preferences/CustomPrefs.swift +++ b/phpmon/Domain/Preferences/CustomPrefs.swift @@ -12,6 +12,7 @@ struct CustomPrefs: Decodable { let scanApps: [String] let presets: [Preset]? let services: [String]? + let environmentVariables: [String: String]? public func hasPresets() -> Bool { return self.presets != nil && !self.presets!.isEmpty @@ -21,9 +22,20 @@ struct CustomPrefs: Decodable { return self.services != nil && !self.services!.isEmpty } + public func hasEnvironmentVariables() -> Bool { + return self.environmentVariables != nil && !self.environmentVariables!.keys.isEmpty + } + + public func getEnvironmentVariables() -> String { + return self.environmentVariables!.map { (key, value) in + return "export \(key)=\(value)" + }.joined(separator: "&&") + } + private enum CodingKeys: String, CodingKey { case scanApps = "scan_apps" case presets = "presets" case services = "services" + case environmentVariables = "export" } } diff --git a/phpmon/Domain/Preferences/Keys.swift b/phpmon/Domain/Preferences/Keys.swift new file mode 100644 index 0000000..0216aa3 --- /dev/null +++ b/phpmon/Domain/Preferences/Keys.swift @@ -0,0 +1,14 @@ +// +// Keys.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 25/07/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +struct Keys { + static let Escape = 53 + static let Space = 49 +} diff --git a/phpmon/Domain/Preferences/Preferences.swift b/phpmon/Domain/Preferences/Preferences.swift index bc47a99..ea7f564 100644 --- a/phpmon/Domain/Preferences/Preferences.swift +++ b/phpmon/Domain/Preferences/Preferences.swift @@ -21,6 +21,7 @@ enum PreferenceName: String { case allowProtocolForIntegrations = "allow_protocol_for_integrations" case globalHotkey = "global_hotkey" case automaticBackgroundUpdateCheck = "backgroundUpdateCheck" + case showPhpDoctorSuggestions = "show_php_doctor_suggestions" // APPEARANCE case shouldDisplayDynamicIcon = "use_dynamic_icon" @@ -65,7 +66,7 @@ class Preferences { public init() { Preferences.handleFirstTimeLaunch() cachedPreferences = Self.cache() - customPreferences = CustomPrefs(scanApps: [], presets: [], services: []) + customPreferences = CustomPrefs(scanApps: [], presets: [], services: [], environmentVariables: [:]) loadCustomPreferences() } @@ -88,6 +89,7 @@ class Preferences { PreferenceName.autoComposerGlobalUpdateAfterSwitch.rawValue: false, PreferenceName.allowProtocolForIntegrations.rawValue: true, PreferenceName.automaticBackgroundUpdateCheck.rawValue: true, + PreferenceName.showPhpDoctorSuggestions.rawValue: true, /// Preferences: Appearance PreferenceName.shouldDisplayDynamicIcon.rawValue: true, @@ -174,6 +176,8 @@ class Preferences { forKey: PreferenceName.allowProtocolForIntegrations.rawValue) as Any, .automaticBackgroundUpdateCheck: UserDefaults.standard.bool( forKey: PreferenceName.automaticBackgroundUpdateCheck.rawValue) as Any, + .showPhpDoctorSuggestions: UserDefaults.standard.bool( + forKey: PreferenceName.showPhpDoctorSuggestions.rawValue) as Any, .notifyAboutVersionChange: UserDefaults.standard.bool( forKey: PreferenceName.notifyAboutVersionChange.rawValue) as Any, @@ -252,6 +256,11 @@ class Preferences { if customPreferences.hasServices() { Log.info("There are custom services: \(customPreferences.services!)") } + + if customPreferences.hasEnvironmentVariables() { + Log.info("Configuring the additional exports...") + Shell.user.exports = customPreferences.getEnvironmentVariables() + } } catch { Log.warn("The ~/.config/phpmon/config.json file seems to be missing or malformed.") } diff --git a/phpmon/Domain/Preferences/PreferencesWindowController+Hotkey.swift b/phpmon/Domain/Preferences/PreferencesWindowController+Hotkey.swift new file mode 100644 index 0000000..7ddd785 --- /dev/null +++ b/phpmon/Domain/Preferences/PreferencesWindowController+Hotkey.swift @@ -0,0 +1,38 @@ +// +// PreferencesWindowController+Hotkey.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 25/07/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Cocoa + +extension PreferencesWindowController { + + // MARK: - Key Interaction + + override func keyDown(with event: NSEvent) { + super.keyDown(with: event) + + guard let tabVC = self.contentViewController as? NSTabViewController else { + return + } + + guard let vc = tabVC.tabViewItems[tabVC.selectedTabViewItemIndex].viewController as? GenericPreferenceVC else { + return + } + + if vc.listeningForHotkeyView == nil { + return + } + + if event.keyCode == Keys.Escape || event.keyCode == Keys.Space { + Log.info("A blacklisted key was pressed, canceling listen!") + vc.listeningForHotkeyView!.unregister(nil) + } else { + vc.listeningForHotkeyView!.updateShortcut(event) + } + } + +} diff --git a/phpmon/Domain/Preferences/PrefsWC.swift b/phpmon/Domain/Preferences/PreferencesWindowController.swift similarity index 65% rename from phpmon/Domain/Preferences/PrefsWC.swift rename to phpmon/Domain/Preferences/PreferencesWindowController.swift index dfd080b..652279c 100644 --- a/phpmon/Domain/Preferences/PrefsWC.swift +++ b/phpmon/Domain/Preferences/PreferencesWindowController.swift @@ -1,5 +1,5 @@ // -// PrefsWC.swift +// PreferencesWindowController.swift // PHP Monitor // // Created by Nico Verbruggen on 02/04/2021. @@ -8,12 +8,7 @@ import Cocoa -struct Keys { - static let Escape = 53 - static let Space = 49 -} - -class PrefsWC: PMWindowController { +class PreferencesWindowController: PMWindowController { // MARK: - Window Identifier @@ -26,13 +21,14 @@ class PrefsWC: PMWindowController { let windowController = storyboard.instantiateController( withIdentifier: "preferencesWindow" - ) as! PrefsWC + ) as! PreferencesWindowController - windowController.window!.title = "prefs.title".localized - windowController.window!.subtitle = "prefs.subtitle".localized - windowController.window!.delegate = delegate - windowController.window!.styleMask = [.titled, .closable, .miniaturizable] - windowController.window!.delegate = windowController + guard let window = windowController.window else { return } + + window.title = "prefs.title".localized + window.subtitle = "prefs.subtitle".localized + window.delegate = delegate ?? windowController + window.styleMask = [.titled, .closable, .miniaturizable] App.shared.preferencesWindowController = windowController } @@ -54,7 +50,7 @@ class PrefsWC: PMWindowController { for vc in preferencesWC.tabVCs { tabVC.addChild(vc.viewController) let item = tabVC.tabViewItem(for: vc.viewController) - item?.image = NSImage(systemSymbolName: vc.icon, accessibilityDescription: "") + item?.image = NSImage(systemSymbolName: vc.icon, accessibilityDescription: "\(vc.label) Icon") item?.label = vc.label } @@ -75,6 +71,8 @@ class PrefsWC: PMWindowController { NSApp.activate(ignoringOtherApps: true) } + // MARK: - Tabs + struct PrefTabView { let viewController: GenericPreferenceVC let label: String @@ -101,29 +99,4 @@ class PrefsWC: PMWindowController { ] }() - // MARK: - Key Interaction - - override func keyDown(with event: NSEvent) { - super.keyDown(with: event) - - guard let tabVC = self.contentViewController as? NSTabViewController else { - return - } - - guard let selected = tabVC.tabViewItems[tabVC.selectedTabViewItemIndex].viewController else { - return - } - - if let vc = selected as? GenericPreferenceVC { - if vc.listeningForHotkeyView != nil { - if event.keyCode == Keys.Escape || event.keyCode == Keys.Space { - Log.info("A blacklisted key was pressed, canceling listen!") - vc.listeningForHotkeyView!.unregister(nil) - } else { - vc.listeningForHotkeyView!.updateShortcut(event) - } - } - } - } - } diff --git a/phpmon/Domain/Preferences/PrefsVC.swift b/phpmon/Domain/Preferences/PrefsVC.swift index 71a83c9..ee3f413 100644 --- a/phpmon/Domain/Preferences/PrefsVC.swift +++ b/phpmon/Domain/Preferences/PrefsVC.swift @@ -114,6 +114,20 @@ class GenericPreferenceVC: NSViewController { ) } + func getShowPhpDoctorSuggestionsPV() -> NSView { + return CheckboxPreferenceView.make( + sectionText: "prefs.php_doctor".localized, + descriptionText: "prefs.php_doctor_suggestions_desc".localized, + checkboxText: "prefs.php_doctor_suggestions_title".localized, + preference: .showPhpDoctorSuggestions, + action: { + MainMenu.shared.refreshIcon() + MainMenu.shared.rebuild() + } + ) + + } + func getNotifyAboutVersionChangePV() -> NSView { return CheckboxPreferenceView.make( sectionText: "prefs.notifications".localized, @@ -194,6 +208,7 @@ class GeneralPreferencesVC: GenericPreferenceVC { .instantiateController(withIdentifier: "preferencesTemplateVC") as! GenericPreferenceVC vc.views = [ + vc.getShowPhpDoctorSuggestionsPV(), vc.getAutoRestartPV(), vc.getAutomaticComposerUpdatePV(), vc.getShortcutPV(), diff --git a/phpmon/Domain/Presets/Preset.swift b/phpmon/Domain/Presets/Preset.swift index e278510..e2d9794 100644 --- a/phpmon/Domain/Presets/Preset.swift +++ b/phpmon/Domain/Presets/Preset.swift @@ -107,13 +107,13 @@ struct Preset: Codable, Equatable { // Show the correct notification if wasRollback { - LocalNotification.send( + await LocalNotification.send( title: "notification.preset_reverted_title".localized, subtitle: "notification.preset_reverted_desc".localized, preference: .notifyAboutPresets ) } else { - LocalNotification.send( + await LocalNotification.send( title: "notification.preset_applied_title".localized, subtitle: "notification.preset_applied_desc".localized(self.name), preference: .notifyAboutPresets diff --git a/phpmon/Domain/Progress/ProgressVC.swift b/phpmon/Domain/Progress/ProgressVC.swift new file mode 100644 index 0000000..2b77bb2 --- /dev/null +++ b/phpmon/Domain/Progress/ProgressVC.swift @@ -0,0 +1,24 @@ +// +// ProgressVC.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 26/07/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation +import AppKit + +class ProgressViewController: NSViewController { + + @IBOutlet weak var labelTitle: NSTextField! + @IBOutlet weak var labelDescription: NSTextField! + + @IBOutlet var textView: NSTextView! + @IBOutlet weak var imageViewType: NSImageView! + + deinit { + Log.perf("Deinitializing ProgressViewController") + } + +} diff --git a/phpmon/Domain/Progress/ProgressWindow.storyboard b/phpmon/Domain/Progress/ProgressWindow.storyboard index 05cd210..0d9106b 100644 --- a/phpmon/Domain/Progress/ProgressWindow.storyboard +++ b/phpmon/Domain/Progress/ProgressWindow.storyboard @@ -1,15 +1,14 @@ - + - - + - + @@ -43,7 +42,7 @@ - + @@ -65,11 +64,11 @@ -