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

Compare commits

..

50 Commits

Author SHA1 Message Date
fa9b51aaab 🚀 Version 7.0.3 2024-04-10 13:07:58 +02:00
b8affad5ee 📝 Updated SECURITY.md 2024-04-10 13:07:30 +02:00
41e5f5b4c3 🔧 Bump build for EAP release 2024-04-09 12:33:55 +02:00
79f6a60a16 👌 Cleanup dependencies for site isolation 2024-04-03 14:50:05 +02:00
06bc4ddb9a 👌 Improve removing indirect dependency
Mind you, the interaction with the domain list controller also needs to
be abstracted away, but this is fine, for now.
2024-04-03 14:11:45 +02:00
bf728a24f0 👌 Fix PHP version suggestions in popover
- If your Valet installation supports using site isolation, that will
  be displayed as the suggestion. If not, the traditional "Switch to
  PHP" options will be available.

- Overflowing buttons have been fixed. Three columns are now used with
  a LazyVGrid, to prevent text from being unreadable.
2024-04-02 15:25:06 +02:00
b7cad3af62 👌 Fix Swift 5.10 concurrency warnings 2024-03-30 17:40:09 +01:00
4a3dee3c50 🚀 Version 7.0.2 2024-03-30 17:14:04 +01:00
9d5a0ed745 🔧 Xcode upgrade check 2024-03-20 13:46:56 +01:00
b3b509409a Fix test 2024-03-20 10:47:52 +01:00
4934f35d0b 🍱 Bump width of no results view 2024-03-19 14:23:19 +01:00
92e7418158 👌 Cleanup 2024-03-19 14:22:10 +01:00
52ea64db40 👌 Use base translation for no domain results 2024-03-19 14:20:34 +01:00
f66e9b7340 Add UnavailableContentView to domains 2024-03-19 14:10:08 +01:00
2bf28fe247 👌 Allow resizable windows 2024-03-16 00:32:05 +01:00
c6e4f785bc 🍱 Use PHP icon for phpman 2024-03-15 23:38:12 +01:00
94fe7df3bd 🍱 Custom button for "upgrade all" 2024-03-15 23:18:09 +01:00
f373621a4a 👌 Polish PHP installation management
- Make spinner view opaque to hide incorrect info
  when refreshing PHP installations
- Resolve each PHP installation to a Homebrew version
- Fix an issue where certain PHP upgrades don't show up

In this build, resolving PHP upgrades happens based on the formula name.

This avoids issues where available PHP version upgrades would *not* be
detected correctly (based on the installed version).
2024-03-15 22:09:59 +01:00
5104a865fb 🚀 Version 7.0.1 2024-02-12 18:28:45 +01:00
7b10973330 🔧 Bump build 2024-02-12 16:33:01 +01:00
bc208bddf9 👌 Notify if list of extensions is empty (#275) 2024-02-12 16:30:18 +01:00
321b4aaf8b 🐛 Fix extension detection on Intel (#275) 2024-02-12 15:26:48 +01:00
b26fc3bc4b 🚀 Version 7.0 2024-02-11 21:35:40 +01:00
f758c5d63a 👌 Cut off bottom of marketing screenshot 2024-02-10 16:18:58 +01:00
c7510d778d 🍱 Update marketing screenshot 2024-02-10 16:17:24 +01:00
70c5aadb7f 🔧 Bump build for PHP Monitor EAP 2024-01-25 13:48:39 +01:00
a731f15cf7 🐛 Prevent PHP dropdown from resetting 2024-01-21 14:42:06 +01:00
ab4c436202 🔧 Bump build for PHP Monitor EAP 2024-01-21 14:23:52 +01:00
c0231690d4 🐛 Fix alternative installation check 2024-01-21 14:22:51 +01:00
988e9d3351 👌 Cleanup and add comments 2024-01-13 13:24:06 +01:00
2f119d4332 Fix even more tests 2024-01-13 13:22:21 +01:00
d83c629a7b Fix some tests 2024-01-13 12:46:33 +01:00
e7d98dbeae 🔧 Bump build for PHP Monitor EAP 2024-01-09 21:24:50 +01:00
f3d5946743 🌐 Localize extension management (for domains) 2024-01-09 21:20:43 +01:00
7728a1125c 📝 Update README to reflect php = PHP 8.3 2024-01-09 21:00:06 +01:00
3612351df7 📝 Update README to reflect php = PHP 8.3 2024-01-09 20:59:52 +01:00
8e912151fb Allow toggling extensions via site list 2024-01-09 20:55:16 +01:00
3a2209e604 🌐 Add translations for language choice 2024-01-09 20:06:40 +01:00
1f0b56cab6 Language choice, prompt user to restart app 2024-01-09 20:01:08 +01:00
e08d970edd 🏗️ WIP: Load preferred language strings 2024-01-09 19:44:29 +01:00
32c757e711 🏗️ WIP: Language override option 2024-01-09 19:28:39 +01:00
480cdb94ae 🏗️ WIP: Language picker, fix SelectPreferenceView 2024-01-09 18:58:02 +01:00
7fbcac5dc2 👌 Check symlinks after PHP version modification 2023-12-27 14:47:21 +01:00
4edb5f5015 👌 Use generated asset catalog symbol extensions 2023-12-27 13:03:24 +01:00
294f84ccb2 👌 Further cleanup 2023-12-27 12:57:08 +01:00
155b57eb9e 🐛 Add incorrect PHP symlink purge (#270)
This commit introduces a new diagnostics feature which is executed
when the app boots. When the app boots, the integrity of the PHP
symlinks are checked to ensure that all symlinks correctly link to
a valid PHP version. If any links to an incorrect PHP version,
then those outdated or incorrect symlinks will be removed.

For example, if `php@8.2` links to `../Cellar/php/8.3.0` then that is
an obvious reason for that symlink to be purged because it links to
the incorrect version. (This example behaviour has been noted in #270.)

This occurs prior to the rest of the startup process, so this way
no incorrect PHP versions can pop up in the version switcher, and
no incorrect PHP version is reported as being installed when
managing installed PHP versions.
2023-12-27 12:40:35 +01:00
ff61d8c52e 🚀 Version 6.2.2 2023-11-24 23:30:12 +01:00
da41673855 Fix broken tests 2023-11-24 23:29:25 +01:00
5bda727981 🔧 Bump version 2023-11-24 22:58:04 +01:00
8790b30706 🚀 Version 6.2.1 2023-11-02 17:17:40 +01:00
67 changed files with 944 additions and 315 deletions

View File

@ -53,7 +53,6 @@
C40175B92903108900763A68 /* ValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B72903108900763A68 /* ValetInteractor.swift */; };
C40175BA2903108900763A68 /* ValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B72903108900763A68 /* ValetInteractor.swift */; };
C40175BB2903108900763A68 /* ValetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40175B72903108900763A68 /* ValetInteractor.swift */; };
C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */; };
C40508B128ADAB44008FAC1F /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; };
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
@ -145,6 +144,9 @@
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 */; };
C428311E2B52AD6F0009F9F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
C428311F2B52AD6F0009F9F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
C42831202B52AD700009F9F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
C4292D542B023F61004F0D2A /* PhpExtensionManagerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4292D532B023F61004F0D2A /* PhpExtensionManagerWindowController.swift */; };
C4292D562B024006004F0D2A /* PhpExtensionManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4292D552B024006004F0D2A /* PhpExtensionManagerView.swift */; };
C4297F7A28970D59004C4630 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
@ -173,6 +175,10 @@
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; };
C43A8A2025D9D1D700591B77 /* brew-formula.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew-formula.json */; };
C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */; };
C43B8FD52BA9BAD3000C02BE /* UnavailableContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43B8FD42BA9BAD3000C02BE /* UnavailableContentView.swift */; };
C43B8FD62BA9C689000C02BE /* UnavailableContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43B8FD42BA9BAD3000C02BE /* UnavailableContentView.swift */; };
C43B8FD72BA9C689000C02BE /* UnavailableContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43B8FD42BA9BAD3000C02BE /* UnavailableContentView.swift */; };
C43B8FD82BA9C689000C02BE /* UnavailableContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43B8FD42BA9BAD3000C02BE /* UnavailableContentView.swift */; };
C43BCD4429FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* ModifyPhpVersionCommand.swift */; };
C43BCD4529FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* ModifyPhpVersionCommand.swift */; };
C43BCD4629FBEF40001547BC /* ModifyPhpVersionCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43BCD4329FBEF40001547BC /* ModifyPhpVersionCommand.swift */; };
@ -481,7 +487,6 @@
C471E88128F9BB650021E251 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; };
C471E88228F9BB650021E251 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; };
C471E88328F9BB650021E251 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; };
C471E88428F9BB650021E251 /* NoDomainResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */; };
C471E88528F9BB650021E251 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; };
C471E88628F9BB650021E251 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; };
C471E88728F9BB650021E251 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; };
@ -569,7 +574,6 @@
C471E8E428F9BB8F0021E251 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; };
C471E8E528F9BB8F0021E251 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; };
C471E8E628F9BB8F0021E251 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; };
C471E8E728F9BB8F0021E251 /* NoDomainResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */; };
C471E8E828F9BB8F0021E251 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; };
C471E8E928F9BB8F0021E251 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4709CA128524B3400088BB8 /* StatsView.swift */; };
C471E8EA28F9BB8F0021E251 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; };
@ -818,6 +822,10 @@
C4E6840C2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E684082AF26B830023ED25 /* BrewTapFormulae.swift */; };
C4E9D2C02878B336008FFDAD /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; };
C4E9D90129CBA09E00BD28D4 /* PHP Monitor Self-Updater.app in Resources */ = {isa = PBXBuildFile; fileRef = C4E9D90029CBA09E00BD28D4 /* PHP Monitor Self-Updater.app */; };
C4EA3C472BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EA3C462BA4F947007B0BA7 /* CustomButtonStyles.swift */; };
C4EA3C482BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EA3C462BA4F947007B0BA7 /* CustomButtonStyles.swift */; };
C4EA3C492BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EA3C462BA4F947007B0BA7 /* CustomButtonStyles.swift */; };
C4EA3C4A2BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EA3C462BA4F947007B0BA7 /* CustomButtonStyles.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 */; };
@ -921,7 +929,6 @@
54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HotkeyPreferenceView.xib; sourceTree = "<group>"; };
54FCFD2F276C8DA4004CE748 /* HotkeyPreferenceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotkeyPreferenceView.swift; sourceTree = "<group>"; };
C40175B72903108900763A68 /* ValetInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetInteractor.swift; sourceTree = "<group>"; };
C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoDomainResultsView.swift; sourceTree = "<group>"; };
C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuItemExtension.swift; sourceTree = "<group>"; };
C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = "<group>"; };
C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
@ -995,6 +1002,7 @@
C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; };
C43A8A1F25D9D1D700591B77 /* brew-formula.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "brew-formula.json"; sourceTree = "<group>"; };
C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackageTest.swift; sourceTree = "<group>"; };
C43B8FD42BA9BAD3000C02BE /* UnavailableContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnavailableContentView.swift; sourceTree = "<group>"; };
C43BCD4329FBEF40001547BC /* ModifyPhpVersionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifyPhpVersionCommand.swift; sourceTree = "<group>"; };
C43FDBE829A932B0003D85EC /* PhpConfigChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpConfigChecker.swift; sourceTree = "<group>"; };
C44067F427E2582B0045BD4E /* DomainListNameCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListNameCell.swift; sourceTree = "<group>"; };
@ -1150,6 +1158,7 @@
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
C4E9D90029CBA09E00BD28D4 /* PHP Monitor Self-Updater.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = "PHP Monitor Self-Updater.app"; sourceTree = "<group>"; };
C4EA3C462BA4F947007B0BA7 /* CustomButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButtonStyles.swift; sourceTree = "<group>"; };
C4EB53E428551F9B006F9937 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = "<group>"; };
C4EB53E628553117006F9937 /* ArrayExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = "<group>"; };
C4EC1E72279DFCF40010F296 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = "<group>"; };
@ -1575,7 +1584,6 @@
C4D5576329C77CC5001A44CD /* PhpVersionManagerWindowController.swift */,
C43931C429C4BD610069165B /* PhpVersionManagerView.swift */,
C42106652AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift */,
C48DDD0C29C75C9E00D032D9 /* BlockingOverlayView.swift */,
);
path = UI;
sourceTree = "<group>";
@ -1960,6 +1968,9 @@
children = (
C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */,
C451AFF52969E40F0078E617 /* HelpButton.swift */,
C4EA3C462BA4F947007B0BA7 /* CustomButtonStyles.swift */,
C48DDD0C29C75C9E00D032D9 /* BlockingOverlayView.swift */,
C43B8FD42BA9BAD3000C02BE /* UnavailableContentView.swift */,
);
path = Common;
sourceTree = "<group>";
@ -1979,7 +1990,6 @@
isa = PBXGroup;
children = (
C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */,
C40508AE28ADA23D008FAC1F /* NoDomainResultsView.swift */,
);
path = Domains;
sourceTree = "<group>";
@ -2327,7 +2337,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1530;
ORGANIZATIONNAME = "Nico Verbruggen";
TargetAttributes = {
C406A5EF298AD2CE00B5B85A = {
@ -2412,6 +2422,7 @@
C4E2E85528FC256B003B070C /* brew-services-sudo.json in Resources */,
C4E2E85928FC256B003B070C /* brew-services-normal.json in Resources */,
C40934A8298EEB8700D25014 /* phpmon-dev.rb in Resources */,
C428311F2B52AD6F0009F9F1 /* Assets.xcassets in Resources */,
C4E2E84F28FC22E4003B070C /* brew-formula.json in Resources */,
C4E2E86228FC28A6003B070C /* brew-services.json in Resources */,
);
@ -2425,6 +2436,7 @@
C4E2E85628FC256B003B070C /* brew-services-sudo.json in Resources */,
C4E2E85A28FC256B003B070C /* brew-services-normal.json in Resources */,
C40934A9298EEB8700D25014 /* phpmon-dev.rb in Resources */,
C42831202B52AD700009F9F1 /* Assets.xcassets in Resources */,
C4E2E85028FC22E4003B070C /* brew-formula.json in Resources */,
C4E2E86128FC28A6003B070C /* brew-services.json in Resources */,
);
@ -2436,6 +2448,7 @@
files = (
C4551659297AED7D009B8466 /* valetrc.valid in Resources */,
C4FC8D3E2A49816300FBBD16 /* Localizable.strings in Resources */,
C428311E2B52AD6F0009F9F1 /* Assets.xcassets in Resources */,
54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */,
54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */,
C42CFB1827DFDFDC00862737 /* nginx-site-isolated.test in Resources */,
@ -2537,6 +2550,7 @@
C4205A7E27F4D21800191A39 /* ValetProxy.swift in Sources */,
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
C4E49DE728F764050026AC4E /* ActiveCommand.swift in Sources */,
C43B8FD52BA9BAD3000C02BE /* UnavailableContentView.swift in Sources */,
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */,
C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */,
@ -2559,6 +2573,7 @@
031E2B692B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C4AD38B228ECD9D300FA8D83 /* TestableFileSystem.swift in Sources */,
C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */,
C4EA3C472BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */,
C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */,
C44A874828905BB000498BC4 /* ProgressVC.swift in Sources */,
C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */,
@ -2677,7 +2692,6 @@
54D9E0BA27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */,
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */,
C42106662AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */,
C40508AF28ADA23D008FAC1F /* NoDomainResultsView.swift in Sources */,
C4B79ECB29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
C40D725F2A018AE30054A067 /* BrewFormula+UI.swift in Sources */,
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */,
@ -2795,6 +2809,7 @@
C471E86E28F9BB650021E251 /* MenuBarIcons.swift in Sources */,
C471E86F28F9BB650021E251 /* Stats.swift in Sources */,
C4CE7F9829683B43000102CF /* PhpVersionNumberCollection.swift in Sources */,
C4EA3C492BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */,
C471E87028F9BB650021E251 /* GlobalKeybindPreference.swift in Sources */,
C471E87228F9BB650021E251 /* CheckboxPreferenceView.swift in Sources */,
C471E87428F9BB650021E251 /* SelectPreferenceView.swift in Sources */,
@ -2811,7 +2826,6 @@
C471E88128F9BB650021E251 /* NoWarningsView.swift in Sources */,
C471E88228F9BB650021E251 /* OnboardingView.swift in Sources */,
C471E88328F9BB650021E251 /* VersionPopoverView.swift in Sources */,
C471E88428F9BB650021E251 /* NoDomainResultsView.swift in Sources */,
C4513F952B13E30C001AD760 /* BrewExtensionsObservable.swift in Sources */,
C471E88528F9BB650021E251 /* ServicesView.swift in Sources */,
C471E88628F9BB650021E251 /* StatsView.swift in Sources */,
@ -2828,6 +2842,7 @@
C471E88F28F9BB650021E251 /* ModifierFlagsExtension.swift in Sources */,
C456A0CD2AA6166F0080144F /* BytePhpPreference.swift in Sources */,
C471E7E928F9BAC20021E251 /* Paths.swift in Sources */,
C43B8FD72BA9C689000C02BE /* UnavailableContentView.swift in Sources */,
C45B91552956123A00F4EC78 /* FakeServicesManager.swift in Sources */,
C471E7FE28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */,
C4415E8F2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
@ -2960,6 +2975,7 @@
C471E8B828F9BB8F0021E251 /* StatusMenu+Items.swift in Sources */,
C471E8B928F9BB8F0021E251 /* DomainListCellProtocol.swift in Sources */,
C471E8BA28F9BB8F0021E251 /* DomainListTLSCell.swift in Sources */,
C43B8FD82BA9C689000C02BE /* UnavailableContentView.swift in Sources */,
C471E8BB28F9BB8F0021E251 /* DomainListNameCell.swift in Sources */,
C471E8BC28F9BB8F0021E251 /* DomainListPhpCell.swift in Sources */,
C471E8BD28F9BB8F0021E251 /* DomainListTypeCell.swift in Sources */,
@ -2984,6 +3000,7 @@
C41ADCEB2970CCC700120423 /* FSNotifier.swift in Sources */,
C471E8CA28F9BB8F0021E251 /* OnboardingWindowController.swift in Sources */,
C456A0C92AA614BD0080144F /* PhpPreference.swift in Sources */,
C4EA3C4A2BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */,
C4D4CB3A29C109CF00DB9F93 /* InternalSwitcher+Valet.swift in Sources */,
C471E8CB28F9BB8F0021E251 /* PreferencesWindowController.swift in Sources */,
C471E8CC28F9BB8F0021E251 /* PreferencesWindowController+Hotkey.swift in Sources */,
@ -3018,7 +3035,6 @@
C4B79EBF29CA38DB00A483EE /* BrewCommand.swift in Sources */,
C45D654F29F52F74004C28F9 /* BrewPermissionFixer.swift in Sources */,
C471E8E628F9BB8F0021E251 /* VersionPopoverView.swift in Sources */,
C471E8E728F9BB8F0021E251 /* NoDomainResultsView.swift in Sources */,
C471E8E828F9BB8F0021E251 /* ServicesView.swift in Sources */,
C4E2E85F28FC282B003B070C /* TestableConfiguration.swift in Sources */,
C471E8E928F9BB8F0021E251 /* StatsView.swift in Sources */,
@ -3247,6 +3263,7 @@
C489E0BC2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */,
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */,
C4B79ECC29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
C43B8FD62BA9C689000C02BE /* UnavailableContentView.swift in Sources */,
C4FD87AA29AB9ABD0002D701 /* PhpConfigChecker.swift in Sources */,
C485707D28BF45A200539B36 /* WarningView.swift in Sources */,
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
@ -3266,6 +3283,7 @@
031E2B6A2B1525A7007C29E1 /* BrewPhpExtension.swift in Sources */,
C4998F0B2617633900B2526E /* PreferencesWindowController.swift in Sources */,
C485707228BF453800539B36 /* SwiftUIHelper.swift in Sources */,
C4EA3C482BA4F947007B0BA7 /* CustomButtonStyles.swift in Sources */,
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
C44C198E276E3A1C0072762D /* TerminalProgressWindowController.swift in Sources */,
@ -3521,6 +3539,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -3585,6 +3604,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -3647,7 +3667,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1400;
CURRENT_PROJECT_VERSION = 1460;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3660,7 +3680,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0;
MARKETING_VERSION = 7.0.3;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -3678,7 +3698,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1400;
CURRENT_PROJECT_VERSION = 1460;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3691,7 +3711,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0;
MARKETING_VERSION = 7.0.3;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -3856,6 +3876,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -3918,7 +3939,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1400;
CURRENT_PROJECT_VERSION = 1460;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3931,7 +3952,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0;
MARKETING_VERSION = 7.0.3;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@ -3965,6 +3986,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -4034,7 +4056,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1400;
CURRENT_PROJECT_VERSION = 1460;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -4047,7 +4069,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0;
MARKETING_VERSION = 7.0.3;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.dev;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) DEV";
@ -4081,6 +4103,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -4150,7 +4173,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1400;
CURRENT_PROJECT_VERSION = 1460;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -4163,7 +4186,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0;
MARKETING_VERSION = 7.0.3;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";
@ -4269,6 +4292,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -4331,7 +4355,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1400;
CURRENT_PROJECT_VERSION = 1460;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -4344,7 +4368,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.4;
MARKETING_VERSION = 7.0;
MARKETING_VERSION = 7.0.3;
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon.eap;
PRODUCT_MODULE_NAME = PHP_Monitor;
PRODUCT_NAME = "$(TARGET_NAME) EAP";

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1530"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1530"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1530"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1530"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1530"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -120,7 +120,7 @@ For maximum compatibility with older PHP versions, you may wish to keep using Va
<details>
<summary><strong>How do I install additional versions of PHP, including legacy versions?</strong></summary>
Assuming you have installed the `php` formula, the latest stable version of PHP is installed. At the time of writing, this is PHP 8.2.
Assuming you have installed the `php` formula, the latest stable version of PHP is installed. At the time of writing, this is PHP 8.3.
You can install other supported versions of PHP via PHP Monitor's **PHP Version Manager**. (You can manually install or upgrade PHP versions too, but this is not recommended.)

View File

@ -4,19 +4,20 @@
Generally speaking, only the latest version of **PHP Monitor** is supported, except during transition periods (for example, when particular system requirements go up):
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Recommended Valet Version |
| Version | Apple Silicon | Supported | Supported macOS | Minimum Deployment | Detected PHP Versions | Recommended Valet Version |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 6.2 | ✅ Universal binary | ✅ Yes | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4 | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 7.0 | ✅ Universal binary | ✅ Yes | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
## Legacy versions
These versions of PHP Monitor are no longer supported, but if youre using an older computer with an older version of Homebrew, Valet or macOS, you might want to use one of these versions.
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
| Version | Apple Silicon | Supported | Supported macOS | Minimum Deployment | Detected PHP Versions | Minimum Required Valet Version |
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
| 6.1 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4 | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 6.0 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+) | macOS 12.4 | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.2 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.2 (w/ Valet 4.x) | 3.0 or higher recommended<br/> 2.16.2 minimum |
| 5.8 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+) | macOS 12.4 | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.2 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.2 (w/ Valet 4.x) | 3.0 or higher recommended<br/> 2.16.2 minimum |
| 6.2 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 6.1 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+)<br/>Sonoma (14.0) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.4 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.4 (w/ Valet 4.x)| 3.0 or higher recommended<br/> 2.16.2 minimum |
| 6.0 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.2 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.2 (w/ Valet 4.x) | 3.0 or higher recommended<br/> 2.16.2 minimum |
| 5.8 | ✅ Universal binary | ❌ | Monterey (12.4+)<br/>Ventura (13.0+) | macOS 12.4+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.2 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.2 (w/ Valet 4.x) | 3.0 or higher recommended<br/> 2.16.2 minimum |
| 5.7 | ✅ Universal binary | ❌ | Big Sur (11.0)<br/>Monterey (12.0)<br/>Ventura (13.0) | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.2 (w/ Valet 3.x)<br/>PHP 7.1-PHP 8.2 (w/ Valet 4.x) | 3.0 or higher recommended<br/> 2.16.2 minimum |
| 5.6 | ✅ Universal binary | ❌ | Big Sur (11.0)<br/>Monterey (12.0)<br/>Ventura (13.0) | macOS 11+ | PHP 5.6—PHP 8.2 (w/ Valet 2.x)<br/>PHP 7.0—PHP 8.2 (w/ Valet 3.x) | 3.0 recommended<br/> 2.16.2 minimum |
| 4.1 | ✅ Universal binary | ❌ | Big Sur (11.0)<br/>Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 KiB

After

Width:  |  Height:  |  Size: 723 KiB

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.172",
"green" : "0.182",
"red" : "0.182"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -24,7 +24,7 @@
"components" : {
"alpha" : "1.000",
"blue" : "0.988",
"green" : "0.723",
"green" : "0.444",
"red" : "0.277"
}
},

View File

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.300",
"blue" : "0.180",
"green" : "0.841",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.300",
"blue" : "0.426",
"green" : "0.809",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,25 @@
{
"images" : [
{
"filename" : "php.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" viewBox="0 0 24 24"><path d="M7.01 10.207h-.944l-.515 2.648h.838c.556 0 .97-.105 1.242-.314.272-.21.455-.559.55-1.049.092-.47.05-.802-.124-.995-.175-.193-.523-.29-1.047-.29zM12 5.688C5.373 5.688 0 8.514 0 12s5.373 6.313 12 6.313S24 15.486 24 12c0-3.486-5.373-6.312-12-6.312zm-3.26 7.451c-.261.25-.575.438-.917.551-.336.108-.765.164-1.285.164H5.357l-.327 1.681H3.652l1.23-6.326h2.65c.797 0 1.378.209 1.744.628.366.418.476 1.002.33 1.752a2.836 2.836 0 0 1-.305.847c-.143.255-.33.49-.561.703zm4.024.715.543-2.799c.063-.318.039-.536-.068-.651-.107-.116-.336-.174-.687-.174H11.46l-.704 3.625H9.388l1.23-6.327h1.367l-.327 1.682h1.218c.767 0 1.295.134 1.586.401s.378.7.263 1.299l-.572 2.944h-1.389zm7.597-2.265a2.782 2.782 0 0 1-.305.847c-.143.255-.33.49-.561.703a2.44 2.44 0 0 1-.917.551c-.336.108-.765.164-1.286.164h-1.18l-.327 1.682h-1.378l1.23-6.326h2.649c.797 0 1.378.209 1.744.628.366.417.477 1.001.331 1.751zm-2.595-1.382h-.943l-.516 2.648h.838c.557 0 .971-.105 1.242-.314.272-.21.455-.559.551-1.049.092-.47.049-.802-.125-.995s-.524-.29-1.047-.29z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -30,7 +30,7 @@ struct Constants {
will be displayed to let them know that certain operations
will not work correctly and that they need to update their app.
*/
static let PhpFormulaeCutoffDate = "2024-11-01"
static let PhpFormulaeCutoffDate = "2024-11-01" // YYYY-MM-DD
/**
* The PHP versions that are considered pre-release versions.
@ -54,6 +54,17 @@ struct Constants {
})
}
/**
The Homebrew services that should be automatically
detected and show up in the list of managed services.
*/
static let DetectedHomebrewServices: Set = [
"mailhog",
"mysql@",
"postgresql@",
"redis"
]
/**
* The PHP versions supported by this application.
* Any other PHP versions are considered invalid.
@ -78,14 +89,14 @@ struct Constants {
3: // Valet v3 dropped support for v5.6
[
"7.0", "7.1", "7.2", "7.3", "7.4",
"8.0", "8.1", "8.2",
"8.3", "8.4" // dev
"8.0", "8.1", "8.2", "8.3",
"8.4" // dev
],
4: // Valet v4 dropped support for v7.0
[
"7.1", "7.2", "7.3", "7.4",
"8.0", "8.1", "8.2",
"8.3", "8.4" // dev
"8.0", "8.1", "8.2", "8.3",
"8.4" // dev
]
]

View File

@ -103,6 +103,10 @@ public class Paths {
}
public static var tapPath: String {
if shared.baseDir == .usr {
return "\(shared.baseDir.rawValue)/homebrew/Library/Taps"
}
return "\(shared.baseDir.rawValue)/Library/Taps"
}

View File

@ -8,6 +8,18 @@ import Foundation
import SwiftUI
struct Localization {
static var preferredLanguage: String? {
guard let language = Preferences.preferences[.languageOverride] as? String else {
return nil
}
if language.isEmpty {
return nil
}
return language
}
static var bundle: Bundle = {
if !isRunningTests {
return Bundle.main
@ -32,7 +44,15 @@ struct Localization {
extension String {
var localized: String {
let string = NSLocalizedString(self, tableName: nil, bundle: Localization.bundle, value: "", comment: "")
var preferredBundle: Bundle = Localization.bundle
if let preferred = Localization.preferredLanguage,
let path = Localization.bundle.path(forResource: preferred, ofType: "lproj"),
let bundle = Bundle(path: path) {
preferredBundle = bundle
}
let string = NSLocalizedString(self, tableName: nil, bundle: preferredBundle, value: "", comment: "")
// Fallback to English translation if the localized value is equal to the key (should not happen)
if string == self {

View File

@ -75,7 +75,7 @@ class MenuBarImageGenerator {
// Then we'll fetch the image we want on the left
var iconType = Preferences.preferences[.iconTypeToDisplay] as? String
if iconType == nil {
if iconType == nil || !MenuBarIcon.allCases.map({ $0.rawValue }).contains(iconType) {
Log.warn("Invalid icon type found, using the default")
iconType = MenuBarIcon.iconPhp.rawValue
}

View File

@ -29,6 +29,8 @@ class PMWindowController: NSWindowController, NSWindowDelegate {
App.shared.remove(window: windowName)
}
func windowDidResize(_ notification: Notification) {}
deinit {
Log.perf("deinit: \(String(describing: self)).\(#function)")
}

View File

@ -37,6 +37,7 @@ class PhpEnvironments {
from: brewPhpAlias.data(using: .utf8)!
).first!
PhpEnvironments.brewPhpAlias = self.homebrewPackage.version
Log.info("[BREW] On your system, the `php` formula means version \(homebrewPackage.version).")
// Check if that version actually corresponds to an older version

View File

@ -22,6 +22,16 @@ class PhpInstallation {
return self.iniFiles.flatMap({ $0.extensions })
}
var formulaName: String {
let version = self.versionNumber.short
if version == PhpEnvironments.brewPhpAlias {
return "php"
}
return "php@\(self.versionNumber.short)"
}
/**
In order to determine details about a PHP installation,
well simply run `php-config --version` in the relevant directory.

View File

@ -27,7 +27,7 @@ extension InternalSwitcher {
return corrections.contains(true)
}
// MARK: - PHP FPM pool
// MARK: - Corrections
public func disableDefaultPhpFpmPool(_ version: String) async -> FixApplied {
let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf"
@ -54,37 +54,7 @@ extension InternalSwitcher {
return false
}
func getExpectedConfigurationFiles(for version: String) -> [ExpectedConfigurationFile] {
return [
ExpectedConfigurationFile(
destination: "/php-fpm.d/valet-fpm.conf",
source: "/cli/stubs/etc-phpfpm-valet.conf",
replacements: [
"VALET_USER": Paths.whoami,
"VALET_HOME_PATH": "~/.config/valet".replacingTildeWithHomeDirectory,
"valet.sock": "valet\(version.replacingOccurrences(of: ".", with: "")).sock"
],
applies: { Valet.shared.version!.major > 2 }
),
ExpectedConfigurationFile(
destination: "/conf.d/error_log.ini",
source: "/cli/stubs/etc-phpfpm-error_log.ini",
replacements: [
"VALET_USER": Paths.whoami,
"VALET_HOME_PATH": "~/.config/valet".replacingTildeWithHomeDirectory
],
applies: { return true }
),
ExpectedConfigurationFile(
destination: "/conf.d/php-memory-limits.ini",
source: "/cli/stubs/php-memory-limits.ini",
replacements: [:],
applies: { return true }
)
]
}
func ensureConfigurationFilesExist(_ version: String) async -> FixApplied {
public func ensureConfigurationFilesExist(_ version: String) async -> FixApplied {
let files = self.getExpectedConfigurationFiles(for: version)
// For each of the files, attempt to fix anything that is wrong
@ -124,6 +94,38 @@ extension InternalSwitcher {
return outcomes.contains(true)
}
// MARK: - Internals
private func getExpectedConfigurationFiles(for version: String) -> [ExpectedConfigurationFile] {
return [
ExpectedConfigurationFile(
destination: "/php-fpm.d/valet-fpm.conf",
source: "/cli/stubs/etc-phpfpm-valet.conf",
replacements: [
"VALET_USER": Paths.whoami,
"VALET_HOME_PATH": "~/.config/valet".replacingTildeWithHomeDirectory,
"valet.sock": "valet\(version.replacingOccurrences(of: ".", with: "")).sock"
],
applies: { Valet.shared.version!.major > 2 }
),
ExpectedConfigurationFile(
destination: "/conf.d/error_log.ini",
source: "/cli/stubs/etc-phpfpm-error_log.ini",
replacements: [
"VALET_USER": Paths.whoami,
"VALET_HOME_PATH": "~/.config/valet".replacingTildeWithHomeDirectory
],
applies: { return true }
),
ExpectedConfigurationFile(
destination: "/conf.d/php-memory-limits.ini",
source: "/cli/stubs/php-memory-limits.ini",
replacements: [:],
applies: { return true }
)
]
}
}
public struct ExpectedConfigurationFile {

View File

@ -73,12 +73,26 @@ public struct TestableConfiguration: Codable {
: .fake(.text)
]) { (_, new) in new }
self.commandOutput["/opt/homebrew/opt/php@\(version.short)/bin/php-config --version"]
= version.long
// PHP configuration files
self.shellOutput["/opt/homebrew/opt/php@\(version.short)/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"] =
.instant("/opt/homebrew/etc/php/\(version.short)/conf.d/php-memory-limits.ini")
// PHP Homebrew operations
self.shellOutput["/opt/homebrew/bin/brew unlink php@\(version.short)"] = .delayed(0.2, "OK")
self.shellOutput["sudo /opt/homebrew/bin/brew services stop php@\(version.short)"] = .delayed(0.2, "OK")
self.shellOutput["sudo /opt/homebrew/bin/brew services start php@\(version.short)"] = .delayed(0.2, "OK")
self.shellOutput["/opt/homebrew/bin/brew link php@\(version.short) --overwrite --force"] = .delayed(0.2, "OK")
// PHP version output
self.commandOutput["/opt/homebrew/opt/php@\(version.short)/bin/php-config --version"] = version.long
self.commandOutput["/opt/homebrew/opt/php@\(version.short)/bin/php -v"] = "OK"
if primary {
self.shellOutput["ls /opt/homebrew/opt | grep php"]
= .instant("php")
// Files expected to be present for currently linked PHP version
self.shellOutput["ls /opt/homebrew/opt | grep php"] =
.instant("php")
self.shellOutput["/opt/homebrew/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"] =
.instant("/opt/homebrew/etc/php/\(version.short)/conf.d/php-memory-limits.ini")
self.filesystem["/opt/homebrew/opt/php"]
= .fake(.symlink, "/opt/homebrew/Cellar/php/\(version.long)")
self.filesystem["/opt/homebrew/opt/php/bin/php"]
@ -89,12 +103,8 @@ public struct TestableConfiguration: Codable {
= .fake(.symlink, "/opt/homebrew/Cellar/php/\(version.short)/bin/php-config")
self.commandOutput["/opt/homebrew/bin/php-config --version"]
= version.long
self.commandOutput["/opt/homebrew/bin/php -r echo php_ini_scanned_files();"] =
"""
/opt/homebrew/etc/php/\(version.short)/conf.d/php-memory-limits.ini,
"""
} else {
// Output expected to be present for non-linked PHP versions
self.shellOutput["ls /opt/homebrew/opt | grep php@"] =
BatchFakeShellOutput.instant(
self.secondaryPhpVersions

View File

@ -23,12 +23,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
*/
let state: App
/**
The MainMenu singleton is responsible for rendering the
menu bar item and its menu, as well as its actions.
*/
let menu: MainMenu
/**
The paths singleton that determines where Homebrew is installed,
and where to look for binaries.
@ -96,7 +90,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
}
self.state = App.shared
self.menu = MainMenu.shared
self.paths = Paths.shared
self.valet = Valet.shared
self.brew = Brew.shared
@ -109,6 +102,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
static func initializeTestingProfile(_ path: String) {
Log.info("The configuration with path `\(path)` is being requested...")
// Clear for PHP Guard
Stats.clearCurrentGlobalPhpVersion()
// Load the configuration file
TestableConfiguration.loadFrom(path: path).apply()
}
@ -129,7 +125,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
setupNotifications()
Task { // Make sure the menu performs its initial checks
await menu.startup()
await MainMenu.shared.startup()
}
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22155"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22689"/>
<capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Search Toolbar Item" minToolsVersion="12.0" minSystemVersion="11.0"/>
@ -429,7 +429,7 @@
</toolbarItem>
<searchToolbarItem implicitItemIdentifier="7C834FBE-7118-4082-A09F-7CBECEC1356A" label="Search" paletteLabel="Search" visibilityPriority="1001" id="G2g-jS-RVc">
<nil key="toolTip"/>
<searchField key="view" focusRingType="none" verticalHuggingPriority="750" textCompletion="NO" id="0gE-Yr-MLy">
<searchField key="view" verticalHuggingPriority="750" textCompletion="NO" id="0gE-Yr-MLy">
<rect key="frame" x="0.0" y="0.0" width="100" height="21"/>
<autoresizingMask key="autoresizingMask"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" sendsSearchStringImmediately="YES" id="vp9-vH-goQ">
@ -575,7 +575,7 @@ Gw
<constraint firstAttribute="bottom" secondItem="8zu-cF-KCX" secondAttribute="bottom" constant="20" symbolic="YES" id="wIl-uw-y3p"/>
</constraints>
</visualEffectView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="U1c-qS-cIm">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="U1c-qS-cIm">
<rect key="frame" x="98" y="153" width="384" height="19"/>
<constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="380" id="WgB-hj-d4P"/>
@ -586,7 +586,7 @@ Gw
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="yI6-qf-htf">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="yI6-qf-htf">
<rect key="frame" x="98" y="127" width="384" height="16"/>
<textFieldCell key="cell" selectable="YES" title="This is a slightly more expanded explanation." id="rY3-Nd-Iit">
<font key="font" metaFont="system"/>
@ -610,7 +610,7 @@ Gw
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="7eT-Hw-EL9"/>
</imageView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hml-dl-Cah">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hml-dl-Cah">
<rect key="frame" x="98" y="70" width="384" height="42"/>
<textFieldCell key="cell" selectable="YES" id="7iW-Lc-DqO">
<font key="font" metaFont="smallSystem"/>
@ -699,7 +699,7 @@ Gw
<action selector="pressedCancel:" target="glS-wF-sEU" id="q0L-YZ-F3J"/>
</connections>
</button>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZX9-s1-23i">
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZX9-s1-23i">
<rect key="frame" x="20" y="150" width="440" height="21"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Enter a domain name here." drawsBackground="YES" id="NFa-1D-Bi4">
<font key="font" metaFont="system"/>
@ -710,7 +710,7 @@ Gw
<outlet property="delegate" destination="glS-wF-sEU" id="Dyf-0M-Gwj"/>
</connections>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VzR-5a-cmT">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VzR-5a-cmT">
<rect key="frame" x="18" y="128" width="444" height="14"/>
<textFieldCell key="cell" title="[i18n] Preview text here" id="bJr-s6-tdP">
<font key="font" metaFont="smallSystem"/>
@ -728,7 +728,7 @@ Gw
<action selector="pressedSecure:" target="glS-wF-sEU" id="OIj-Pz-5Ea"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mmQ-7e-dlb">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mmQ-7e-dlb">
<rect key="frame" x="18" y="60" width="444" height="28"/>
<textFieldCell key="cell" title="[i18n] Securing a domain requires administrative privileges. You may be prompted for your password or Touch ID." id="4gd-KM-5Fu">
<font key="font" metaFont="smallSystem"/>
@ -743,7 +743,7 @@ Gw
<url key="url" string="file:///Users/"/>
</pathCell>
</pathControl>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="P0B-Ht-R8n">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="P0B-Ht-R8n">
<rect key="frame" x="18" y="209" width="128" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="[i18n] Link a Folder" id="S4j-ZC-ddT">
<font key="font" textStyle="headline" name=".SFNS-Bold"/>
@ -751,7 +751,7 @@ Gw
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField hidden="YES" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="900-Z2-tID">
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="900-Z2-tID">
<rect key="frame" x="140" y="23" width="180" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="That domain name already exists." id="jOt-n6-TQf">
<font key="font" metaFont="smallSystem"/>
@ -886,7 +886,7 @@ Gw
<rect key="frame" x="69" y="0.0" width="200" height="54"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="XJL-Uw-frD">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="XJL-Uw-frD">
<rect key="frame" x="3" y="26" width="145" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="my-domain-name.test" id="SGC-Gm-Mxd">
<font key="font" metaFont="systemSemibold" size="13"/>
@ -894,7 +894,7 @@ Gw
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="CXK-Q9-CpO">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="CXK-Q9-CpO">
<rect key="frame" x="3" y="12" width="75" height="14"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="~/path/to/site" id="fe7-Ha-mR9">
<font key="font" metaFont="smallSystem"/>
@ -1027,7 +1027,7 @@ Gw
<rect key="frame" x="470" y="0.0" width="97" height="54"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ljl-8B-key">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ljl-8B-key">
<rect key="frame" x="6" y="26" width="93" height="14"/>
<textFieldCell key="cell" alignment="left" title="Laravel" id="0lu-L6-oKr">
<font key="font" metaFont="smallSystem"/>
@ -1035,7 +1035,7 @@ Gw
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aPK-Xc-J4B">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aPK-Xc-J4B">
<rect key="frame" x="6" y="15" width="93" height="11"/>
<textFieldCell key="cell" alignment="left" title="PHP 8.0" id="puf-Jh-ham">
<font key="font" metaFont="miniSystem"/>
@ -1092,9 +1092,18 @@ Gw
<constraint firstAttribute="height" constant="30" id="lfW-dB-Eu3"/>
</constraints>
</progressIndicator>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="wcV-ed-8Bv">
<rect key="frame" x="113" y="5" width="400" height="300"/>
<constraints>
<constraint firstAttribute="width" constant="400" id="HCo-LG-x3N"/>
<constraint firstAttribute="height" constant="300" id="Xpi-Rl-xmb"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="p0j-eB-I2i" firstAttribute="leading" secondItem="rIZ-4U-bhj" secondAttribute="leading" id="2Tx-yb-xrv"/>
<constraint firstItem="wcV-ed-8Bv" firstAttribute="centerX" secondItem="rIZ-4U-bhj" secondAttribute="centerX" id="DPz-kQ-aP0"/>
<constraint firstItem="wcV-ed-8Bv" firstAttribute="centerY" secondItem="rIZ-4U-bhj" secondAttribute="centerY" id="HCW-zJ-gSY"/>
<constraint firstItem="p0j-eB-I2i" firstAttribute="top" secondItem="rIZ-4U-bhj" secondAttribute="top" id="Pst-5A-dI0"/>
<constraint firstAttribute="bottom" secondItem="p0j-eB-I2i" secondAttribute="bottom" id="QEw-5m-u1s"/>
<constraint firstItem="ZiS-Gq-TLQ" firstAttribute="centerY" secondItem="rIZ-4U-bhj" secondAttribute="centerY" constant="-10" id="XqX-Tf-8ck"/>
@ -1103,6 +1112,7 @@ Gw
</constraints>
</view>
<connections>
<outlet property="noResultsView" destination="wcV-ed-8Bv" id="K3s-fb-1aN"/>
<outlet property="progressIndicator" destination="ZiS-Gq-TLQ" id="Ylb-Vk-uub"/>
<outlet property="tableView" destination="cp3-34-pQj" id="sdw-Ac-27X"/>
</connections>
@ -1125,7 +1135,7 @@ Gw
<rect key="frame" x="0.0" y="0.0" width="540" height="286"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QCK-Z9-w7g">
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QCK-Z9-w7g">
<rect key="frame" x="20" y="196" width="500" height="21"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" title="http://127.0.0.1:80" placeholderString="http://127.0.0.1:80" drawsBackground="YES" id="muS-8M-KSy">
<font key="font" metaFont="system"/>
@ -1136,7 +1146,7 @@ Gw
<outlet property="delegate" destination="dwh-CF-6iv" id="lNE-OI-G93"/>
</connections>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Uib-vA-HRc">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Uib-vA-HRc">
<rect key="frame" x="18" y="221" width="325" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="[i18n] Proxy subject (usually: protocol, IP address and port)" id="G1Z-3f-BhL">
<font key="font" metaFont="systemMedium" size="11"/>
@ -1144,7 +1154,7 @@ Gw
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mlA-Zt-Hu8">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mlA-Zt-Hu8">
<rect key="frame" x="18" y="172" width="112" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="[i18n] Domain name" id="dQs-oZ-80e">
<font key="font" metaFont="systemMedium" size="11"/>
@ -1152,7 +1162,7 @@ Gw
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SNw-oQ-bnb">
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SNw-oQ-bnb">
<rect key="frame" x="20" y="147" width="500" height="21"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Enter a domain name here." drawsBackground="YES" id="gTQ-Y2-Y9w">
<font key="font" metaFont="system"/>
@ -1208,7 +1218,7 @@ Gw
<action selector="pressedCancel:" target="dwh-CF-6iv" id="J2T-Zj-A0j"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JSZ-x8-Pqi">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JSZ-x8-Pqi">
<rect key="frame" x="18" y="128" width="504" height="14"/>
<constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="sF1-RG-URI"/>
@ -1229,7 +1239,7 @@ Gw
<action selector="pressedSecure:" target="dwh-CF-6iv" id="b74-8T-AzO"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5x7-ll-2f7">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5x7-ll-2f7">
<rect key="frame" x="18" y="60" width="504" height="28"/>
<textFieldCell key="cell" title="[i18n] Securing a domain requires administrative privileges. You may be prompted for your password or Touch ID." id="IMB-O5-ZOy">
<font key="font" metaFont="smallSystem"/>
@ -1237,7 +1247,7 @@ Gw
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="DAh-br-Dfx">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="DAh-br-Dfx">
<rect key="frame" x="18" y="250" width="123" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="[i18n] Add a Proxy" id="AZ1-04-kUl">
<font key="font" textStyle="headline" name=".SFNS-Bold"/>
@ -1245,7 +1255,7 @@ Gw
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField hidden="YES" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="w0k-CK-0u4">
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="w0k-CK-0u4">
<rect key="frame" x="191" y="23" width="180" height="14"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="That domain name already exists." id="4sH-94-UJl">
<font key="font" metaFont="smallSystem"/>
@ -1389,7 +1399,7 @@ Gw
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJK-Ke-IK3">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJK-Ke-IK3">
<rect key="frame" x="18" y="138" width="504" height="19"/>
<textFieldCell key="cell" selectable="YES" alignment="left" title="[i18n] What kind of domain would you like to set up?" id="agk-Nj-FLd">
<font key="font" metaFont="systemBold" size="15"/>
@ -1397,7 +1407,7 @@ Gw
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField wantsLayer="YES" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="urj-Xq-TrJ">
<textField wantsLayer="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="urj-Xq-TrJ">
<rect key="frame" x="18" y="60" width="504" height="70"/>
<constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="tbl-AV-4qB"/>

View File

@ -27,6 +27,21 @@ class BrewDiagnostics {
}
}
/**
Logs a bunch of useful information during startup.
*/
public static func logBootInformation() {
Log.info(BrewDiagnostics.customCaskInstalled
? "[BREW] The app has been installed via Homebrew Cask."
: "[BREW] The app has been installed directly (optimal)."
)
Log.info(BrewDiagnostics.usesNginxFullFormula
? "[BREW] The app will be using the `nginx-full` formula."
: "[BREW] The app will be using the `nginx` formula."
)
}
/**
Determines whether the PHP Monitor Cask is installed.
*/
@ -46,6 +61,43 @@ class BrewDiagnostics {
return destination.contains("/nginx-full/")
}()
/**
It is possible to have outdated symlinks for PHP installations. This can mean that certain PHP installations
are going to be reported incorrectly (e.g. `php@8.2` links to an installation in a `8.3` folder after an upgrade).
To ensure this does not cause issues, PHP Monitor will automatically remove all incorrect PHP symlinks.
*/
public static func checkForOutdatedPhpInstallationSymlinks() async {
// Set up a regular expression
let regex = try! NSRegularExpression(pattern: "^php@[0-9]+\\.[0-9]+$", options: .caseInsensitive)
// Check for incorrect versions
if let contents = try? FileSystem.getShallowContentsOfDirectory("\(Paths.optPath)")
.filter({
let range = NSRange($0.startIndex..., in: $0)
return regex.firstMatch(in: $0, options: [], range: range) != nil
}) {
for symlink in contents {
let version = symlink.replacingOccurrences(of: "php@", with: "")
if let destination = try? FileSystem.getDestinationOfSymlink("\(Paths.optPath)/\(symlink)") {
if !destination.contains("Cellar/php/\(version)")
&& !destination.contains("Cellar/php@\(version)") {
Log.err("Symlink for \(symlink) is incorrect. Removing...")
do {
try FileSystem.remove("\(Paths.optPath)/\(symlink)")
Log.info("Incorrect symlink for \(symlink) has been successfully removed.")
} catch {
Log.err("Symlink for \(symlink) was incorrect but could not be removed!")
}
}
} else {
Log.warn("Could not read symlink at: \(Paths.optPath)/\(symlink)! Symlink check skipped.")
}
}
}
}
/**
It is possible to have the `shivammathur/php` tap installed, and for the core homebrew information to be outdated.
This will then result in two different aliases claiming to point to the same formula (`php`).

View File

@ -43,11 +43,13 @@ struct BrewPhpExtension: Hashable, Comparable {
}
var hasAlternativeInstall: Bool {
// Extension must be active
let isActive = PhpEnvironments.shared.currentInstall?.extensions
.contains(where: { $0.name == self.name }) ?? false
guard let php = PhpEnvironments.shared.cachedPhpInstallations[self.phpVersion] else {
return false
}
return isActive && !isInstalled
let alreadyDiscovered = php.extensions.contains(where: { $0.name == self.name })
return alreadyDiscovered && !isInstalled
}
internal func firstDependent(in exts: [BrewPhpExtension]) -> BrewPhpExtension? {

View File

@ -44,14 +44,13 @@ class BrewPhpFormulaeHandler: HandlesBrewPhpFormulae {
}
return Brew.phpVersionFormulae.map { (version, formula) in
let fullVersion = PhpEnvironments.shared.cachedPhpInstallations[version]?
.versionNumber.text
var fullVersion: String?
var upgradeVersion: String?
if let version = fullVersion {
if let install = PhpEnvironments.shared.cachedPhpInstallations[version] {
fullVersion = install.versionNumber.text
upgradeVersion = outdated?.first(where: { formula in
return formula.installed_versions.contains(version)
return formula.name == install.formulaName
})?.current_version
}

View File

@ -167,6 +167,9 @@ class ModifyPhpVersionCommand: BrewCommand {
// Reload and restart PHP versions
onProgress(.create(value: 0.95, title: self.title, description: "phpman.steps.reloading".localized))
// Ensure all symlinks are correctly linked
await BrewDiagnostics.checkForOutdatedPhpInstallationSymlinks()
// Check which version of PHP are now installed
await PhpEnvironments.detectPhpVersions()

View File

@ -15,7 +15,7 @@ extension MainMenu {
func startup() async {
// Start with the icon
Task { @MainActor in
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
self.setStatusBar(image: NSImage.statusBarIcon)
}
if await Startup().checkEnvironment() {
@ -32,19 +32,14 @@ extension MainMenu {
// Determine what the `php` formula is aliased to
await PhpEnvironments.shared.determinePhpAlias()
// Make sure that broken symlinks are removed ASAP
await BrewDiagnostics.checkForOutdatedPhpInstallationSymlinks()
// Initialize preferences
_ = Preferences.shared
// Determine install method
Log.info(BrewDiagnostics.customCaskInstalled
? "[BREW] The app has been installed via Homebrew Cask."
: "[BREW] The app has been installed directly (optimal)."
)
Log.info(BrewDiagnostics.usesNginxFullFormula
? "[BREW] The app will be using the `nginx-full` formula."
: "[BREW] The app will be using the `nginx` formula."
)
// Put some useful diagnostics information in log
BrewDiagnostics.logBootInformation()
// Attempt to find out more info about Valet
if Valet.shared.version != nil {
@ -73,7 +68,6 @@ extension MainMenu {
WarningManager.shared.evaluateWarnings()
// Set up the config watchers on launch (updated automatically when switching)
Log.info("Setting up watchers...")
App.shared.handlePhpConfigWatcher()
// Detect built-in and custom applications
@ -108,6 +102,27 @@ extension MainMenu {
// Find out which services are active
Log.info("The services manager knows about \(ServicesManager.shared.services.count) services.")
// Post-launch stats and update check, but only if not running tests
await performPostLaunchActions()
// Check if the linked version has changed between launches of phpmon
PhpGuard().compareToLastGlobalVersion()
// We are ready!
PhpEnvironments.shared.isBusy = false
// Finally!
Log.info("PHP Monitor is ready to serve!")
// Check if we upgraded from a previous version
AppUpdater.checkIfUpdateWasPerformed()
}
/**
Performs a set of post-launch actions, like incrementing stats and checking for updates.
(This code is skipped when running SwiftUI previews.)
*/
private func performPostLaunchActions() async {
if !isRunningSwiftUIPreview {
Stats.incrementSuccessfulLaunchCount()
Stats.evaluateSponsorMessageShouldBeDisplayed()
@ -121,16 +136,6 @@ extension MainMenu {
await AppUpdater().checkForUpdates(userInitiated: false)
}
}
// Check if the linked version has changed between launches of phpmon
PhpGuard().compareToLastGlobalVersion()
// We are ready!
PhpEnvironments.shared.isBusy = false
Log.info("PHP Monitor is ready to serve!")
// Check if we upgraded just now
AppUpdater.checkIfUpdateWasPerformed()
}
/**

View File

@ -155,12 +155,12 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
Task { @MainActor [self] in
if PhpEnvironments.shared.isBusy {
Log.perf("Refreshing icon: currently busy")
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
setStatusBar(image: NSImage.statusBarIcon)
} else {
Log.perf("Refreshing icon: no longer busy")
if Preferences.preferences[.shouldDisplayDynamicIcon] as! Bool == false {
// Static icon has been requested
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIconStatic"))!)
setStatusBar(image: NSImage.statusBarIconStatic)
} else {
// The dynamic icon has been requested
let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool

View File

@ -242,7 +242,7 @@ extension StatusMenu {
addLoadedPresets()
}
private func addEmptyPresetHelp() {
@MainActor private func addEmptyPresetHelp() {
addItem(NSMenuItem(title: "mi_presets_title".localized, submenu: [
NSMenuItem(title: "mi_no_presets".localized),
NSMenuItem.separator(),
@ -251,7 +251,7 @@ extension StatusMenu {
], target: MainMenu.shared))
}
private func addLoadedPresets() {
@MainActor private func addLoadedPresets() {
addItem(NSMenuItem(title: "mi_presets_title".localized, submenu: [
NSMenuItem.separator(),
HeaderView.asMenuItem(text: "mi_apply_presets_title".localized)
@ -266,7 +266,7 @@ extension StatusMenu {
// MARK: - Xdebug
func addXdebugMenuItem() {
@MainActor func addXdebugMenuItem() {
if !Xdebug.enabled {
addItem(NSMenuItem.separator())
return
@ -286,7 +286,7 @@ extension StatusMenu {
// MARK: - PHP Doctor
func addPhpDoctorMenuItem() {
@MainActor func addPhpDoctorMenuItem() {
if !Preferences.isEnabled(.showPhpDoctorSuggestions) ||
!WarningManager.shared.hasWarnings() {
return
@ -302,7 +302,7 @@ extension StatusMenu {
// MARK: - First Aid & Services
func addFirstAidAndServicesMenuItems() {
@MainActor func addFirstAidAndServicesMenuItems() {
let services = NSMenuItem(title: "mi_other".localized)
var items: [NSMenuItem] = [
@ -359,7 +359,7 @@ extension StatusMenu {
// MARK: - Other helper methods to generate menu items
func addExtensionItem(_ phpExtension: PhpExtension, _ shortcutKey: Int) {
@MainActor func addExtensionItem(_ phpExtension: PhpExtension, _ shortcutKey: Int) {
let keyEquivalent = shortcutKey < 9 ? "\(shortcutKey)" : ""
let menuItem = ExtensionMenuItem(

View File

@ -20,6 +20,7 @@ enum PreferenceName: String, Codable {
case globalHotkey = "global_hotkey"
case automaticBackgroundUpdateCheck = "backgroundUpdateCheck"
case showPhpDoctorSuggestions = "show_php_doctor_suggestions"
case languageOverride = "language_override"
// APPEARANCE
case shouldDisplayDynamicIcon = "use_dynamic_icon"
@ -84,7 +85,8 @@ enum PreferenceName: String, Codable {
],
.string: [
.globalHotkey,
.iconTypeToDisplay
.iconTypeToDisplay,
.languageOverride
]
]
}

View File

@ -51,6 +51,7 @@ class Preferences {
PreferenceName.allowProtocolForIntegrations.rawValue: true,
PreferenceName.automaticBackgroundUpdateCheck.rawValue: true,
PreferenceName.showPhpDoctorSuggestions.rawValue: true,
PreferenceName.languageOverride.rawValue: "",
/// Preferences: Appearance
PreferenceName.shouldDisplayDynamicIcon.rawValue: true,

View File

@ -17,7 +17,9 @@ class GeneralPreferencesVC: GenericPreferenceVC {
let vc = NSStoryboard(name: "Main", bundle: nil)
.instantiateController(withIdentifier: "preferencesTemplateVC") as! GenericPreferenceVC
_ = vc.addView(when: true, vc.getShowPhpDoctorSuggestionsPV())
_ = vc
.addView(when: true, vc.getLanguageOptionsPV())
.addView(when: true, vc.getShowPhpDoctorSuggestionsPV())
.addView(when: true, vc.getAutoRestartServicesPV())
.addView(when: true, vc.getAutomaticComposerUpdatePV())
.addView(when: true, vc.getShortcutPV())

View File

@ -48,11 +48,44 @@ class GenericPreferenceVC: NSViewController {
)
}
func getLanguageOptionsPV() -> NSView {
var options = Bundle.main.localizations
.filter({ $0 != "Base"})
.map({ lang in
return PreferenceDropdownOption(
label: Locale.current.localizedString(forLanguageCode: lang)!,
value: lang
)
})
options.insert(PreferenceDropdownOption(label: "System Default", value: ""), at: 0)
return SelectPreferenceView.make(
sectionText: "prefs.language".localized,
descriptionText: "prefs.language_options_desc".localized,
options: options,
preference: .languageOverride,
action: {
MainMenu.shared.refreshIcon()
MainMenu.shared.rebuild()
if let window = App.shared.preferencesWindowController?.window {
let alert = NSAlert()
alert.messageText = "alert.language_changed.title".localized
alert.informativeText = "alert.language_changed.subtitle".localized
alert.alertStyle = .warning
alert.addButton(withTitle: "generic.ok".localized)
alert.beginSheetModal(for: window)
}
}
)
}
func getIconOptionsPV() -> NSView {
return SelectPreferenceView.make(
sectionText: "",
descriptionText: "prefs.icon_options_desc".localized,
options: MenuBarIcon.allCases.map({ return $0.rawValue }),
options: MenuBarIcon.allCases
.map({ return PreferenceDropdownOption(label: $0.rawValue, value: $0.rawValue) }),
localizationPrefix: "prefs.icon_options",
preference: .iconTypeToDisplay,
action: {

View File

@ -68,6 +68,8 @@ class PreferencesWindowController: PMWindowController {
App.shared.preferencesWindowController?.positionWindowInTopRightCorner()
}
App.shared.preferencesWindowController?.window?.orderFrontRegardless()
NSApp.activate(ignoringOtherApps: true)
}

View File

@ -84,6 +84,10 @@ class Stats {
)
}
public static func clearCurrentGlobalPhpVersion() {
UserDefaults.standard.removeObject(forKey: InternalStats.lastGlobalPhpVersion.rawValue)
}
/**
Determine if the sponsor message should be displayed.

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -23,7 +23,7 @@
<action selector="toggled:" target="c22-O7-iKe" id="c9y-JM-TdE"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bcg-X1-qca">
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bcg-X1-qca">
<rect key="frame" x="168" y="5" width="410" height="14"/>
<textFieldCell key="cell" title="DESCRIPTION" id="9fH-up-Sob">
<font key="font" metaFont="smallSystem"/>
@ -31,7 +31,7 @@
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="B8f-nb-Y0A">
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="B8f-nb-Y0A">
<rect key="frame" x="-2" y="27" width="154" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="150" id="euj-t0-xv4"/>

View File

@ -9,30 +9,34 @@
import Foundation
import Cocoa
class SelectPreferenceView: NSView, XibLoadable {
struct PreferenceDropdownOption {
let label: String
let value: String
}
class SelectPreferenceView: NSView, XibLoadable {
@IBOutlet weak var labelSection: NSTextField!
@IBOutlet weak var labelDescription: NSTextField!
@IBOutlet weak var popupButton: NSPopUpButton!
var localizationPrefix: String = ""
var localizationPrefix: String?
var imagePrefix: String?
var options: [String] = [] {
var options: [PreferenceDropdownOption] = [] {
didSet {
self.popupButton.removeAllItems()
self.options.forEach { value in
self.popupButton.addItem(
withTitle: "\(localizationPrefix).\(value)".localized
)
self.options.forEach { option in
if let prefix = localizationPrefix {
self.popupButton.addItem(withTitle: "\(prefix).\(option.label)".localized)
} else {
self.popupButton.addItem(withTitle: option.label)
}
}
if imagePrefix == nil {
return
}
self.popupButton.itemArray.enumerated().forEach { item in
item.element.image = NSImage(named: "\(imagePrefix!)_\(self.options[item.offset])")
if let prefix = imagePrefix {
self.popupButton.itemArray.enumerated().forEach { item in
item.element.image = NSImage(named: "\(prefix)_\(self.options[item.offset].value)")
}
}
}
}
@ -43,19 +47,18 @@ class SelectPreferenceView: NSView, XibLoadable {
didSet {
let value = Preferences.preferences[preference] as! String
self.options.enumerated().forEach { option in
if option.element == value {
if option.element.value == value {
self.popupButton.selectItem(at: option.offset)
}
}
}
}
// swiftlint:disable function_parameter_count
static func make(
sectionText: String,
descriptionText: String,
options: [String],
localizationPrefix: String,
options: [PreferenceDropdownOption],
localizationPrefix: String? = nil,
imagePrefix: String? = nil,
preference: PreferenceName,
action: @escaping () -> Void) -> NSView {
@ -72,11 +75,10 @@ class SelectPreferenceView: NSView, XibLoadable {
return view
}
// swiftlint:enable function_parameter_count
@IBAction func valueChanged(_ sender: Any) {
let index = self.popupButton.indexOfSelectedItem
Preferences.update(.iconTypeToDisplay, value: self.options[index])
Preferences.update(self.preference, value: self.options[index].value)
self.action()
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -13,16 +13,16 @@
<rect key="frame" x="0.0" y="0.0" width="596" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bcg-X1-qca">
<rect key="frame" x="183" y="5" width="395" height="14"/>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bcg-X1-qca">
<rect key="frame" x="168" y="5" width="410" height="14"/>
<textFieldCell key="cell" title="DESCRIPTION" id="9fH-up-Sob">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="B8f-nb-Y0A">
<rect key="frame" x="13" y="29" width="154" height="16"/>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="B8f-nb-Y0A">
<rect key="frame" x="-2" y="29" width="154" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="150" id="euj-t0-xv4"/>
</constraints>
@ -33,7 +33,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YaB-Tg-Ir3">
<rect key="frame" x="182" y="23" width="110" height="25"/>
<rect key="frame" x="167" y="23" width="110" height="25"/>
<popUpButtonCell key="cell" type="push" title="Icon Option" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="SaA-mm-HBo" id="Su6-C4-aGo">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -58,7 +58,7 @@
<constraint firstItem="Bcg-X1-qca" firstAttribute="top" secondItem="YaB-Tg-Ir3" secondAttribute="bottom" constant="8" symbolic="YES" id="Mji-pe-CNl"/>
<constraint firstAttribute="trailing" secondItem="Bcg-X1-qca" secondAttribute="trailing" constant="20" symbolic="YES" id="UPo-Il-l81"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="YaB-Tg-Ir3" secondAttribute="trailing" constant="20" symbolic="YES" id="Zlg-jj-uKY"/>
<constraint firstItem="B8f-nb-Y0A" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" constant="15" id="Ztd-uk-4aw"/>
<constraint firstItem="B8f-nb-Y0A" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" id="aBU-J8-gRK"/>
<constraint firstAttribute="bottom" secondItem="Bcg-X1-qca" secondAttribute="bottom" constant="5" id="hNE-mU-jcu"/>
</constraints>
<connections>

View File

@ -29,7 +29,7 @@ struct BlockingOverlayView<Content: View>: View {
var body: some View {
ZStack(alignment: .center) {
content().opacity(isBlocking ? 0.2 : 1)
content().opacity(isBlocking ? 0 : 1)
if isBlocking {
VStack {
ActivityIndicator()
@ -44,7 +44,8 @@ struct BlockingOverlayView<Content: View>: View {
.padding(.top, -4)
}.padding(60)
}
}.background(Color.white)
}
.background(Color.spinnerBackground)
.disabled(isBlocking)
}
}

View File

@ -0,0 +1,36 @@
//
// CustomButtonStyles.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 15/03/2024.
// Copyright © 2024 Nico Verbruggen. All rights reserved.
//
import SwiftUI
public struct CustomButtonStyle: ButtonStyle {
@Environment(\.isEnabled) var isEnabled
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding(.vertical, 4)
.padding(.horizontal, 8)
.foregroundStyle(.white)
.background(.statusColorBlue, in: .rect(cornerRadius: 8, style: .continuous))
.opacity({
if configuration.isPressed {
return 0.4
}
if !isEnabled {
return 0.2
}
return 1.0
}())
}
}
extension ButtonStyle where Self == CustomButtonStyle {
static var custom: CustomButtonStyle { .init() }
}

View File

@ -27,7 +27,9 @@ var isRunningSwiftUIPreview: Bool {
extension Color {
public static var appPrimary: Color = Color("AppColor")
public static var appSecondary: Color = Color("AppSecondary")
// This next one is generated automatically via asset catalogs now
// public static var appSecondary: Color = Color("AppSecondary")
public static var debug: Color = {
if ProcessInfo.processInfo.environment["PAINT_PHPMON_SWIFTUI_VIEWS"] != nil {

View File

@ -0,0 +1,67 @@
//
// NoDomainsView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 19/03/2024.
// Copyright © 2024 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct UnavailableContentView: View {
var title: String
var description: String
var icon: String
var button: String?
var action: (() -> Void)?
init(
title: String,
description: String,
icon: String,
button: String? = nil,
action: (() -> Void)? = nil
) {
self.title = title
self.description = description
self.icon = icon
self.button = button
self.action = action
}
var body: some View {
Group {
VStack(spacing: 15) {
Image(systemName: self.icon)
.resizable()
.frame(width: 48, height: 48)
.foregroundColor(Color.appPrimary)
.padding(.bottom, 10)
Text(self.title)
.font(.system(size: 18, weight: .bold))
Text(self.description)
.foregroundStyle(Color.secondary)
.multilineTextAlignment(.center)
if self.button != nil {
Button(self.button!) {
self.action!()
}.buttonStyle(.custom)
}
}
}
.padding(30)
.frame(maxWidth: 400)
}
}
#Preview {
UnavailableContentView(
title: "domain_list.domains_empty.title".localized,
description: "domain_list.domains_empty.desc".localized,
icon: "globe",
button: "domain_list.domains_empty.button".localized,
action: {}
)
}

View File

@ -1,35 +0,0 @@
//
// NoDomainResults.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 15/08/2022.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct NoDomainResults: View {
@State var searching: Bool = false
var body: some View {
VStack(alignment: .center, spacing: 15) {
Image(systemName: searching ? "magnifyingglass.circle.fill" : "questionmark.circle.fill")
.resizable()
.renderingMode(.template)
.frame(width: 24, height: 24)
VStack(alignment: .center) {
Text(
searching
? "domain_list.no_domains_for_search_query".localizedForSwiftUI
: "domain_list.no_domains".localizedForSwiftUI
)
}
}
.frame(minWidth: 0, maxWidth: .infinity)
.padding(25)
}
}
#Preview {
NoDomainResults()
}

View File

@ -14,8 +14,16 @@ struct VersionPopoverView: View {
@State var validPhpVersions: [VersionNumber]
@State var prefersIsolationSuggestions: Bool
@State var parent: NSPopover!
let rows = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(getTitleText())
@ -32,14 +40,29 @@ struct VersionPopoverView: View {
message: "alert.php_suggestions".localized,
color: Color("AppColor")
)
HStack {
ForEach(validPhpVersions, id: \.self) { version in
Button("site_link.switch_to_php".localized(version.short), action: {
MainMenu.shared.switchToPhpVersion(version.short)
parent?.close()
})
}
}.padding(EdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 0))
if prefersIsolationSuggestions {
// SITE ISOLATION (preferred)
LazyVGrid(columns: self.rows, alignment: .leading, spacing: 5, content: {
ForEach(validPhpVersions, id: \.self) { version in
Button("site_link.isolate_php".localized(version.short), action: {
App.shared.domainListWindowController?.contentVC
.isolateSite(site: site, version: version.short)
parent?.close()
}).padding(EdgeInsets(top: 3, leading: 0, bottom: 3, trailing: 0))
}
}).padding(EdgeInsets(top: 5, leading: 0, bottom: 0, trailing: 0))
} else {
// GLOBAL SWITCHER
LazyVGrid(columns: self.rows, alignment: .leading, spacing: 5, content: {
ForEach(validPhpVersions, id: \.self) { version in
Button("site_link.switch_to_php".localized(version.short), action: {
MainMenu.shared.switchToPhpVersion(version.short)
parent?.close()
}).padding(EdgeInsets(top: 3, leading: 0, bottom: 3, trailing: 0))
}
}).padding(EdgeInsets(top: 5, leading: 0, bottom: 0, trailing: 0))
}
}
} else {
if site.preferredPhpVersionSource == .unknown {
@ -137,6 +160,7 @@ struct DisclaimerView: View {
constraint: ""
),
validPhpVersions: [],
prefersIsolationSuggestions: false,
parent: nil
)
}
@ -152,6 +176,7 @@ struct DisclaimerView: View {
constraint: "^8.1"
),
validPhpVersions: [],
prefersIsolationSuggestions: false,
parent: nil
)
}
@ -168,6 +193,7 @@ struct DisclaimerView: View {
isolated: "8.0"
),
validPhpVersions: [],
prefersIsolationSuggestions: false,
parent: nil
)
}
@ -184,6 +210,7 @@ struct DisclaimerView: View {
isolated: "7.4"
),
validPhpVersions: [],
prefersIsolationSuggestions: false,
parent: nil
)
}
@ -200,8 +227,12 @@ struct DisclaimerView: View {
),
validPhpVersions: [
VersionNumber(major: 8, minor: 0, patch: 0),
VersionNumber(major: 8, minor: 1, patch: 0)
VersionNumber(major: 8, minor: 1, patch: 0),
VersionNumber(major: 8, minor: 2, patch: 0),
VersionNumber(major: 8, minor: 3, patch: 0),
VersionNumber(major: 8, minor: 4, patch: 0)
],
prefersIsolationSuggestions: true,
parent: nil
)
}

View File

@ -16,21 +16,19 @@ class DomainListKindCell: NSTableCellView, DomainListCellProtocol {
func populateCell(with site: ValetSite) {
// If the `aliasPath` is nil, we're dealing with a parked site (otherwise: linked).
imageViewType.image = NSImage(
named: site.aliasPath == nil
? "IconParked"
: "IconLinked"
)
imageViewType.image = site.aliasPath == nil
? NSImage.iconParked
: NSImage.iconLinked
// Unless, of course, this is a default site
if site.absolutePath == Valet.shared.config.defaultSite {
imageViewType.image = NSImage(named: "IconDefault")
imageViewType.image = NSImage.iconDefault
}
imageViewType.contentTintColor = NSColor.tertiaryLabelColor
}
func populateCell(with proxy: ValetProxy) {
imageViewType.image = NSImage(named: "IconProxy")
imageViewType.image = NSImage.iconProxy
}
}

View File

@ -34,14 +34,13 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol {
if site.isolatedPhpVersion != nil {
imageViewPhpVersionOK.isHidden = false
imageViewPhpVersionOK.image = NSImage(named: "Isolated")
imageViewPhpVersionOK.image = NSImage.isolated
imageViewPhpVersionOK.toolTip = "domain_list.tooltips.isolated".localized(site.servingPhpVersion)
} else {
imageViewPhpVersionOK.isHidden = (site.preferredPhpVersion == "???"
|| !site.isCompatibleWithPreferredPhpVersion)
imageViewPhpVersionOK.image = NSImage(named: "Checkmark")
imageViewPhpVersionOK.image = NSImage.checkmark
imageViewPhpVersionOK.toolTip = "domain_list.tooltips.checkmark".localized(site.preferredPhpVersion)
}
}
@ -71,7 +70,12 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol {
let button = self.buttonPhpVersion!
let popover = NSPopover()
let view = VersionPopoverView(site: site, validPhpVersions: validPhpSuggestions, parent: popover)
let view = VersionPopoverView(
site: site,
validPhpVersions: validPhpSuggestions,
prefersIsolationSuggestions: Valet.enabled(feature: .isolatedSites),
parent: popover
)
popover.contentViewController = NSHostingController(rootView: view)
popover.behavior = .transient

View File

@ -110,17 +110,33 @@ extension DomainListVC {
}
}
@objc func isolateSite(sender: PhpMenuItem) {
guard let site = selectedSite else {
return
}
@objc func toggleExtension(sender: ExtensionMenuItem) {
Task {
self.setUIBusy()
await sender.phpExtension?.toggle()
if Preferences.isEnabled(.autoServiceRestartAfterExtensionToggle) {
await Actions.restartPhpFpm()
}
reloadContextMenu()
self.setUINotBusy()
}
}
public func isolateSite(site: ValetSite, version: String) {
waitAndExecute {
do {
// Instruct Valet to isolate a given PHP version
try await site.isolate(version: sender.version)
// Reload the UI
self.reloadSelectedRow()
try await site.isolate(version: version)
// Reload the UI if it's the same site
if self.selectedSite?.absolutePath == site.absolutePath {
self.reloadSelectedRow()
} else {
await self.reloadDomains()
}
} catch {
// Notify the user about a failed command
let error = error as! ValetInteractionError
@ -129,7 +145,15 @@ extension DomainListVC {
}
}
@objc func removeIsolatedSite() {
@objc func isolateSiteViaMenuItem(sender: PhpMenuItem) {
guard let site = selectedSite else {
return
}
self.isolateSite(site: site, version: sender.version)
}
@objc func removeIsolatedSiteViaMenuItem() {
guard let site = selectedSite else {
return
}

View File

@ -42,7 +42,20 @@ extension DomainListVC {
addDisabledIsolation(to: menu)
}
addSeparator(to: menu)
if let extensions = site.isolatedPhpVersion?.extensions ?? PhpEnvironments.phpInstall?.extensions,
let version = site.isolatedPhpVersion?.versionNumber.short ?? PhpEnvironments.phpInstall?.version.short {
menu.addItem(HeaderView.asMenuItem(text: "mi_detected_extensions".localized))
addMenuItemsForExtensions(
to: menu,
for: extensions,
version: version
)
}
menu.addItem(HeaderView.asMenuItem(text: "domain_list.actions".localized))
addToggleSecure(to: menu, secured: site.secured)
addUnlink(to: menu, with: site)
@ -108,7 +121,7 @@ extension DomainListVC {
for version in PhpEnvironments.shared.availablePhpVersions.reversed() {
let item = PhpMenuItem(
title: "domain_list.always_use_php".localized(version),
action: #selector(self.isolateSite),
action: #selector(self.isolateSiteViaMenuItem),
keyEquivalent: ""
)
if site.servingPhpVersion == version && site.isolatedPhpVersion != nil {
@ -124,7 +137,7 @@ extension DomainListVC {
items.append(NSMenuItem.separator())
items.append(NSMenuItem(
title: "domain_list.remove_isolation".localized,
action: #selector(self.removeIsolatedSite)
action: #selector(self.removeIsolatedSiteViaMenuItem)
))
}
@ -150,6 +163,28 @@ extension DomainListVC {
)
}
private func addMenuItemsForExtensions(to menu: NSMenu, for extensions: [PhpExtension], version: String) {
var items: [NSMenuItem] = [
NSMenuItem(title: "domain_list.applies_to".localized(version))
]
for phpExtension in extensions {
let item = ExtensionMenuItem(
title: "\(phpExtension.name) (\(phpExtension.fileNameOnly))",
action: #selector(self.toggleExtension),
keyEquivalent: ""
)
item.state = phpExtension.enabled ? .on : .off
item.phpExtension = phpExtension
items.append(item)
}
menu.addItem(NSMenuItem(title: "domain_list.extensions".localized, submenu: items))
menu.addItem(NSMenuItem.separator())
}
// MARK: - Menu Items for Proxy
private func addMenuItemsForProxy(_ proxy: ValetProxy) {

View File

@ -8,12 +8,14 @@
import Cocoa
import Carbon
import SwiftUI
class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
// MARK: - Outlets
@IBOutlet weak var tableView: PMTableView!
@IBOutlet weak var noResultsView: NSView!
@IBOutlet weak var progressIndicator: NSProgressIndicator!
// MARK: - Variables
@ -93,6 +95,8 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
override func viewDidLoad() {
tableView.doubleAction = #selector(self.doubleClicked(sender:))
addNoResultsView()
let mapping = [
"SECURE": "domain_list.columns.secure",
"DOMAIN": "domain_list.columns.domain",
@ -115,6 +119,25 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
}
}
private func addNoResultsView() {
let child = NSHostingController(
rootView: UnavailableContentView(
title: "domain_list.domains_empty.title".localized,
description: "domain_list.domains_empty.desc".localized,
icon: "globe",
button: "domain_list.domains_empty.button".localized,
action: {
App.shared.domainListWindowController?
.pressedAddLink(nil)
}
)
.frame(width: 400, height: 300)
).view
self.noResultsView.addSubview(child)
child.frame = self.noResultsView.bounds
}
// MARK: - Async Operations
/**
@ -132,6 +155,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
tableView.alphaValue = 0.3
tableView.isEnabled = false
tableView.selectRowIndexes([], byExtendingSelection: true)
noResultsView.isHidden = true
}
/**
@ -142,6 +166,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
progressIndicator.stopAnimation(nil)
tableView.alphaValue = 1.0
tableView.isEnabled = true
updateNoResultsView()
}
/**
@ -282,9 +307,14 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource
Task { @MainActor in
self.tableView.reloadData()
updateNoResultsView()
}
}
func updateNoResultsView() {
self.noResultsView.isHidden = !domains.isEmpty
}
func searchedFor(text: String) {
lastSearchedFor = text

View File

@ -60,6 +60,8 @@ extension PhpExtensionManagerView {
return
}
let phpVersionManaged = self.manager.phpVersion
do {
self.status.busy = true
try await command.execute { progress in
@ -70,7 +72,7 @@ extension PhpExtensionManagerView {
}
}
self.manager.loadExtensionData(for: self.manager.phpVersion)
self.manager.phpVersion = phpVersionManaged
self.status.busy = false
} catch let error {
let error = error as! BrewCommandError

View File

@ -58,7 +58,19 @@ struct PhpExtensionManagerView: View {
.searchable(text: $searchText)
}
}
.frame(width: 600, height: 600)
.frame(minWidth: 600, minHeight: 600)
.onAppear {
Task {
await delay(seconds: 1)
if self.manager.extensions.isEmpty {
self.presentErrorAlert(
title: "phpextman.errors.not_found.title".localized,
description: "phpextman.errors.not_found.desc".localized,
button: "generic.ok".localized
)
}
}
}
}
// MARK: View Variables

View File

@ -1,5 +1,5 @@
//
// PhpVersionManagerWindowController.swift
// PhpExtensionManagerWindowController.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 13/11/2023.
@ -28,7 +28,7 @@ class PhpExtensionManagerWindowController: PMWindowController {
guard let window = windowController.window else { return }
window.title = "phpextman.window.title".localized
window.styleMask = [.titled, .closable, .miniaturizable]
window.styleMask = [.titled, .closable, .miniaturizable, .resizable]
window.titlebarAppearsTransparent = false
window.delegate = delegate ?? windowController
window.contentView = NSHostingView(rootView: windowController.view)

View File

@ -94,6 +94,8 @@ struct PhpVersionManagerView: View {
VStack {
header.padding(10)
Divider()
if self.hasUpdates {
hasUpdatesView
} else {
@ -134,16 +136,17 @@ struct PhpVersionManagerView: View {
.listStyle(PlainListStyle())
}
}
}.frame(width: 600, height: 600)
}
.frame(minWidth: 600, minHeight: 600)
}
// MARK: View Variables
private var header: some View {
HStack(alignment: .center, spacing: 15) {
Image(systemName: "arrow.down.to.line.circle.fill")
Image.init(.php)
.resizable()
.frame(width: 40, height: 40)
.frame(width: 50, height: 50)
.foregroundColor(Color.blue)
.padding(12)
VStack(alignment: .leading, spacing: 5) {
@ -160,44 +163,43 @@ struct PhpVersionManagerView: View {
private var hasUpdatesView: some View {
Group {
Divider()
HStack(alignment: .center, spacing: 15) {
Text("phpman.has_updates.description".localizedForSwiftUI)
.foregroundColor(.gray)
.font(.system(size: 11))
Button("phpman.has_updates.button".localizedForSwiftUI, action: {
Button(action: {
Task { await self.upgradeAll(self.formulae.upgradeable) }
}, label: {
Label("phpman.has_updates.button".localizedForSwiftUI,
systemImage: "arrowshape.up.circle.fill")
})
.buttonStyle(.custom)
.focusable(false)
.disabled(self.status.busy)
}
.padding(10)
}
.background(.statusColorYellowTranslucent)
.cornerRadius(5)
}
private var noUpdatesView: some View {
Group {
Divider()
HStack(alignment: .center, spacing: 15) {
Button {
Task { await self.reload() }
} label: {
Image(systemName: "arrow.clockwise")
.buttonStyle(.automatic)
.controlSize(.large)
}
.focusable(false)
.disabled(self.status.busy)
Text("phpman.refresh.button.description".localizedForSwiftUI)
.foregroundColor(.gray)
.font(.system(size: 11))
HStack(alignment: .center, spacing: 15) {
Button {
Task { await self.reload() }
} label: {
Image(systemName: "arrow.clockwise")
.buttonStyle(.automatic)
.controlSize(.large)
}
.padding(10)
.focusable(false)
.disabled(self.status.busy)
Text("phpman.refresh.button.description".localizedForSwiftUI)
.foregroundColor(.gray)
.font(.system(size: 11))
}
.padding(10)
}
private var prereleaseBadge: some View {
@ -205,7 +207,7 @@ struct PhpVersionManagerView: View {
.font(.system(size: 9))
.padding(.horizontal, 5)
.padding(.vertical, 1)
.background(Color.appPrimary)
.background(Color.statusColorBlue)
.foregroundColor(Color.white)
.clipShape(Capsule())
.fixedSize(horizontal: true, vertical: true)

View File

@ -30,7 +30,7 @@ class PhpVersionManagerWindowController: PMWindowController {
guard let window = windowController.window else { return }
window.title = ""
window.styleMask = [.titled, .closable, .miniaturizable]
window.styleMask = [.titled, .closable, .miniaturizable, .resizable]
window.titlebarAppearsTransparent = true
window.delegate = delegate ?? windowController
window.contentView = NSHostingView(rootView: windowController.view)

View File

@ -102,9 +102,15 @@
"phpextman.list.status.external" = "Diese Erweiterung ist bereits über eine andere Quelle installiert und kann nicht verwaltet werden.";
"phpextman.list.status.installable" = "Diese Erweiterung kann installiert werden.";
"phpextman.list.status.dependent" = "Sie können diese nicht deinstallieren, bevor Sie **%@** deinstallieren.";
"phpextman.list.status.dependent" = "Sie können diese nicht deinstallieren, bevor Sie %@ deinstallieren.";
"phpextman.list.status.can_manage" = "Diese Erweiterung ist installiert und kann von PHP Monitor verwaltet werden.";
"phpextman.errors.not_found.title" = "Oh oh. Keine Erweiterungen gefunden!";
"phpextman.errors.not_found.desc" = "Das sollte eigentlich nicht passieren. Sie müssen möglicherweise den folgenden Befehl in Ihrem Terminal ausführen:
`brew tap shivammathur/extensions`
und PHP Monitor neu starten, damit die Erweiterungen sichtbar werden. Wenn das Problem nach Ausführen des Befehls und Neustart von PHP Monitor weiterhin besteht, ziehen Sie bitte in Erwägung, ein Issue auf GitHub zu eröffnen.";
// PHPMAN
@ -128,7 +134,7 @@
"phpman.refresh.button" = "Aktualisierungen suchen";
"phpman.refresh.button.description" = "Sie können auf die Schaltfläche Aktualisieren klicken, um zu prüfen, ob Aktualisierungen für die installierten PHP-Versionen verfügbar sind.";
"phpman.has_updates.description" = "Ein oder mehrere Aktualisierungen sind verfügbar. (Bitte beachten Sie, dass PHP Monitor PHP-Versionen immer im Ganzen installiert oder aktualisiert, so dass Sie immer alle Installationen auf einmal aktualisieren).";
"phpman.has_updates.description" = "**Ein oder mehrere Aktualisierungen sind verfügbar.** Bitte beachten Sie, dass PHP Monitor PHP-Versionen immer im Ganzen installiert oder aktualisiert, so dass Sie immer alle Installationen auf einmal aktualisieren.";
"phpman.has_updates.button" = "Alle aktualisieren";
"phpman.warnings.unsupported.title" = "Ihre Version von Homebrew kann Probleme verursachen";
@ -238,6 +244,9 @@ Möglicherweise werden Sie während des Deinstallationsvorgangs nach Ihrem Passw
"domain_list.columns.kind" = "Art";
"domain_list.columns.project_type" = "Projekttyp";
"domain_list.extensions" = "Erweiterungen umschalten";
"domain_list.applies_to" = "Gilt für PHP %@";
// CHOOSE WHAT TO ADD
"selection.title" = "Welche Art von Domain möchten Sie einrichten?";
@ -814,3 +823,11 @@ Bitte beachten Sie, dass einige Funktionen (unten ausgegraut) derzeit nicht verf
"onboarding.tour.feature_unavailable" = "Diese Funktion ist derzeit nicht verfügbar und erfordert die Installation von Laravel Valet.";
"onboarding.tour.once" = "Sie sehen die Willkommenstour nur einmal. Sie können die Willkommenstour später über das Symbol in der Menüleiste (im Menü unter Erste Hilfe & Dienste) erneut öffnen.";
"onboarding.tour.close" = "Tour beenden";
// LANGUAGE CHOICE
"prefs.language" = "Sprache:";
"prefs.language_options_desc" = "Wählen Sie eine andere Sprache für die Verwendung mit PHP Monitor. Um diese Änderung vollständig anzuwenden, müssen Sie die App neu starten.";
"alert.language_changed.title" = "Sie sollten PHP Monitor neu starten!";
"alert.language_changed.subtitle" = "Sie haben soeben die Anzeigesprache von PHP Monitor geändert. Das Menü wird sofort die korrekte Sprache verwenden, aber Sie müssen die App möglicherweise neu starten, damit alle Texte in der App Ihre neue Sprachwahl widerspiegeln.";

View File

@ -117,9 +117,16 @@
"phpextman.list.status.external" = "This extension is already installed via another source, and cannot be managed.";
"phpextman.list.status.installable" = "This extension can be installed.";
"phpextman.list.status.dependent" = "You cannot uninstall this before uninstalling **%@**.";
"phpextman.list.status.dependent" = "You cannot uninstall this before uninstalling %@.";
"phpextman.list.status.can_manage" = "This extension is installed and can be managed by PHP Monitor.";
"phpextman.errors.not_found.title" = "Uh oh. No extensions discovered!";
"phpextman.errors.not_found.desc" = "This is not supposed to happen. You may need to run the following command in your terminal:
`brew tap shivammathur/extensions`
and restart PHP Monitor for extensions to become visible. If the problem persists after running the command and restarting PHP Monitor, please consider opening an issue on GitHub.";
// PHPMAN
"phpman.busy.title" = "Checking for updates!";
@ -151,7 +158,7 @@
"phpman.refresh.button" = "Search for Updates";
"phpman.refresh.button.description" = "You can press the refresh button to check if any updates are available to installed PHP versions.";
"phpman.has_updates.description" = "One or more updates are available. (Please note that PHP Monitor will always install or update PHP versions in bulk, so you will always upgrade all installations at once.)";
"phpman.has_updates.description" = "**One or more updates are available.** PHP Monitor will always install or update PHP versions in bulk, so you will always upgrade all installations at once.";
"phpman.has_updates.button" = "Upgrade All";
"phpman.warnings.outdated.title" = "This version of PHP Monitor is outdated";
@ -232,8 +239,9 @@ You may be asked for your password during the uninstallation process if file per
"domain_list.title" = "Domains";
"domain_list.subtitle" = "";
"domain_list.no_domains" = "You have not set up any domains or proxies yet.";
"domain_list.no_domains_for_search_query" = "There are no results for your search query.";
"domain_list.domains_empty.title" = "No domains available.";
"domain_list.domains_empty.desc" = "No domains were found for this search query or you haven't linked any domains yet.";
"domain_list.domains_empty.button" = "Add domain...";
"domain_list.tooltips.isolated" = "This domain is isolated and using PHP %@ instead of the globally linked PHP.";
"domain_list.tooltips.checkmark" = "This domain is being served with a version of PHP that is compatible with this requirement (PHP %@). Click on the PHP version next to this checkmark to find out more information about how this requirement was determined.";
@ -254,6 +262,7 @@ You may be asked for your password during the uninstallation process if file per
"domain_list.confirm_unlink_desc" = "No files will be removed. You can always link the folder again by clicking on the + button and selecting the original folder.";
"site_link.close" = "Close";
"site_link.switch_to_php" = "Switch to PHP %@";
"site_link.isolate_php" = "Isolate PHP %@";
"domain_list.confirm_unproxy" = "Are you sure you want to remove the proxy '%@'?";
"domain_list.confirm_unproxy_desc" = "You can always recreate proxy the again by clicking on the + button.";
@ -264,6 +273,9 @@ You may be asked for your password during the uninstallation process if file per
"domain_list.columns.kind" = "Kind";
"domain_list.columns.project_type" = "Project Type";
"domain_list.extensions" = "Toggle Extensions";
"domain_list.applies_to" = "Applies to PHP %@";
// CHOOSE WHAT TO ADD
"selection.title" = "What kind of domain would you like to set up?";
@ -840,3 +852,11 @@ Please note that some features (greyed out below) are currently unavailable beca
"onboarding.tour.feature_unavailable" = "This feature is currently unavailable and requires Laravel Valet to be installed.";
"onboarding.tour.once" = "You will only see the Welcome Tour once. You can re-open the Welcome Tour later via the menu bar icon (available in the menu, under First Aid & Services).";
"onboarding.tour.close" = "Close Tour";
// LANGUAGE CHOICE
"prefs.language" = "Language:";
"prefs.language_options_desc" = "Choose a different language to use with PHP Monitor. To fully apply this change, you must restart the app.";
"alert.language_changed.title" = "You must restart PHP Monitor!";
"alert.language_changed.subtitle" = "You just changed the display language of PHP Monitor. The menu will immediately use the correct language, but you may need to restart the app for all text throughout the app to reflect your new language choice.";

View File

@ -117,9 +117,17 @@
"phpextman.list.status.external" = "Cette extension est déjà installée via une autre source et ne peut pas être gérée.";
"phpextman.list.status.installable" = "Cette extension peut être installée.";
"phpextman.list.status.dependent" = "Vous ne pouvez pas désinstaller ceci avant de désinstaller **%@**.";
"phpextman.list.status.dependent" = "Vous ne pouvez pas désinstaller ceci avant de désinstaller %@.";
"phpextman.list.status.can_manage" = "Cette extension est installée et peut être gérée par PHP Monitor.";
"phpextman.errors.not_found.title" = "Oh là là. Aucune extension découverte !";
"phpextman.errors.not_found.desc" = "Ceci n'est pas censé se produire. Vous devrez peut-être exécuter la commande suivante dans votre terminal :
`brew tap shivammathur/extensions`
et redémarrer PHP Monitor pour que les extensions deviennent visibles. Si le problème persiste après avoir exécuté la commande et redémarré PHP Monitor, veuillez envisager d'ouvrir un problème sur GitHub.";
// PHPMAN
"phpman.busy.title" = "Vérification des mises à jour en cours !";
@ -142,7 +150,7 @@
"phpman.refresh.button" = "Rechercher des mises à jour";
"phpman.refresh.button.description" = "Vous pouvez appuyer sur le bouton de rafraîchissement pour vérifier si des mises à jour sont disponibles pour les versions de PHP installées.";
"phpman.has_updates.description" = "Une ou plusieurs mises à jour sont disponibles. (Veuillez noter que PHP Monitor installera ou mettra toujours à jour les versions de PHP en bloc, vous mettrez donc à jour toutes les installations en une seule fois.)";
"phpman.has_updates.description" = "**Une ou plusieurs mises à jour sont disponibles.** PHP Monitor installera ou mettra toujours à jour les versions de PHP en bloc, vous mettrez donc à jour toutes les installations en une seule fois.";
"phpman.has_updates.button" = "Tout mettre à jour";
"phpman.warnings.unsupported.title" = "Votre version de Homebrew peut causer des problèmes";
@ -252,6 +260,9 @@ Il se peut que vous deviez saisir votre mot de passe pendant le processus de dé
"domain_list.columns.kind" = "Sorte";
"domain_list.columns.project_type" = "Type de Projet";
"domain_list.extensions" = "Activer/désactiver les extensions";
"domain_list.applies_to" = "S'applique à PHP %@";
// CHOOSE WHAT TO ADD
"selection.title" = "Quel type de domaine souhaitez-vous configurer ?";
@ -825,3 +836,11 @@ Veuillez noter que certaines fonctionnalités (grisées ci-dessous) sont actuell
"onboarding.tour.feature_unavailable" = "Cette fonctionnalité n'est actuellement pas disponible et nécessite l'installation de Laravel Valet.";
"onboarding.tour.once" = "Vous ne verrez la visite de bienvenue qu'une seule fois. Vous pouvez rouvrir la visite de bienvenue ultérieurement via l'icône de la barre de menu (disponible dans le menu, sous Premiers Secours et Services).";
"onboarding.tour.close" = "Fermer la Visite d'Accueil";
// LANGUAGE CHOICE
"prefs.language" = "Langue :";
"prefs.language_options_desc" = "Choisissez une autre langue à utiliser avec PHP Monitor. Pour appliquer pleinement ce changement, vous devez redémarrer l'application.";
"alert.language_changed.title" = "Vous devriez redémarrer PHP Monitor !";
"alert.language_changed.subtitle" = "Vous venez de changer la langue d'affichage de PHP Monitor. Le menu utilisera immédiatement la bonne langue, mais vous devrez peut-être redémarrer l'application pour que tout le texte dans l'application reflète votre nouveau choix de langue.";

View File

@ -103,9 +103,16 @@
"phpextman.list.status.external" = "Deze extensie is al geïnstalleerd via een andere bron en kan niet worden beheerd.";
"phpextman.list.status.installable" = "Deze extensie kan worden geïnstalleerd.";
"phpextman.list.status.dependent" = "U kunt dit niet deïnstalleren voordat u **%@** deïnstalleert.";
"phpextman.list.status.dependent" = "U kunt dit niet deïnstalleren voordat u %@ deïnstalleert.";
"phpextman.list.status.can_manage" = "Deze extensie is geïnstalleerd en kan worden beheerd door PHP Monitor.";
"phpextman.errors.not_found.title" = "Oeps. Geen extensies gevonden!";
"phpextman.errors.not_found.desc" = "Dit zou niet moeten gebeuren. Mogelijk moet je het volgende commando in je terminal uitvoeren:
`brew tap shivammathur/extensions`
en PHP Monitor herstarten om de extensies zichtbaar te maken. Als het probleem zich blijft voordoen na het uitvoeren van het commando en het herstarten van PHP Monitor, overweeg dan om een issue te openen op GitHub.";
// PHPMAN
"phpman.busy.title" = "Bezig met zoeken naar updates!";
@ -128,7 +135,7 @@
"phpman.refresh.button" = "Zoek naar updates";
"phpman.refresh.button.description" = "Je kunt op de vernieuwen-knop drukken om te controleren of er updates beschikbaar zijn voor geïnstalleerde PHP-versies.";
"phpman.has_updates.description" = "Er zijn één of meer updates beschikbaar. (Houd er rekening mee dat PHP Monitor altijd PHP-versies in bulk installeert of bijwerkt, dus u zult altijd alle installaties tegelijk upgraden.)";
"phpman.has_updates.description" = "**Er zijn één of meer updates beschikbaar.** PHP Monitor werkt alle versies in één keer bij, hou hier rekening mee.";
"phpman.has_updates.button" = "Alles bijwerken";
"phpman.warnings.unsupported.title" = "Uw versie van Homebrew kan problemen veroorzaken";
@ -238,6 +245,9 @@ Tijdens het verwijderingsproces kan u worden gevraagd om uw wachtwoord indien de
"domain_list.columns.kind" = "Type";
"domain_list.columns.project_type" = "Projecttype";
"domain_list.extensions" = "Extensies in-/uitschakelen";
"domain_list.applies_to" = "Van toepassing op PHP %@";
// CHOOSE WHAT TO ADD
"selection.title" = "Wat voor soort domein wilt u instellen?";
@ -812,3 +822,11 @@ Houd er rekening mee dat sommige functies (hieronder grijs weergegeven) momentee
"onboarding.tour.feature_unavailable" = "Deze functie is momenteel niet beschikbaar en vereist de installatie van Laravel Valet.";
"onboarding.tour.once" = "U zult de Welkomsttour slechts één keer zien. U kunt de Welkomsttour later opnieuw openen via het menubalkpictogram (beschikbaar in het menu onder First Aid & Services).";
"onboarding.tour.close" = "Tour sluiten";
// LANGUAGE CHOICE
"prefs.language" = "Taal:";
"prefs.language_options_desc" = "Kies een andere taal om te gebruiken met PHP Monitor. Om deze wijziging volledig toe te passen, moet u de app herstarten.";
"alert.language_changed.title" = "U moet PHP Monitor herstarten!";
"alert.language_changed.subtitle" = "U heeft zojuist de weergavetaal van PHP Monitor gewijzigd. Het menu zal onmiddellijk de juiste taal gebruiken, maar u moet mogelijk de app herstarten om overal de nieuwe taal te zien.";

View File

@ -102,9 +102,16 @@
"phpextman.list.status.external" = "Esta extensão já está instalada por outra fonte e não pode ser gerenciada.";
"phpextman.list.status.installable" = "Esta extensão pode ser instalada.";
"phpextman.list.status.dependent" = "Você não pode desinstalar isso antes de desinstalar **%@**.";
"phpextman.list.status.dependent" = "Você não pode desinstalar isso antes de desinstalar %@.";
"phpextman.list.status.can_manage" = "Esta extensão está instalada e pode ser gerenciada pelo PHP Monitor.";
"phpextman.errors.not_found.title" = "Uh oh. Nenhuma extensão descoberta!";
"phpextman.errors.not_found.desc" = "Isto não deveria acontecer. Pode ser necessário executar o seguinte comando no seu terminal:
`brew tap shivammathur/extensions`
e reiniciar o PHP Monitor para que as extensões se tornem visíveis. Se o problema persistir após executar o comando e reiniciar o PHP Monitor, por favor considere abrir um problema no GitHub.";
// PHPMAN
"phpman.busy.title" = "Procurando atualizações!";
@ -127,7 +134,7 @@
"phpman.refresh.button" = "Procurar atualizações";
"phpman.refresh.button.description" = "Você pode premir o botão de atualização para verificar se há atualizações disponíveis para as versões do PHP instaladas.";
"phpman.has_updates.description" = "Estão disponiveis uma ou mais atualizações. (Por favor, tenha em conta que o PHP Monitor instalará ou atualizará as versões do PHP em bloco, ou seja, atualizará todas as instalações PHP de uma só vez.)";
"phpman.has_updates.description" = "**Estão disponiveis uma ou mais atualizações.** Por favor, tenha em conta que o PHP Monitor instalará ou atualizará as versões do PHP em bloco, ou seja, atualizará todas as instalações PHP de uma só vez.";
"phpman.has_updates.button" = "Atualizar tudo";
"phpman.warnings.unsupported.title" = "A versão instalada do Homebrew pode causar problemas";
@ -237,6 +244,10 @@ Poderá ser-lhe solicitada a sua palavra-passe durante o processo de desinstala
"domain_list.columns.kind" = "Tipo";
"domain_list.columns.project_type" = "Tipo de projeto";
"domain_list.extensions" = "Alternar Extensões";
"domain_list.applies_to" = "Aplica-se ao PHP %@";
// CHOOSE WHAT TO ADD
"selection.title" = "Que tipo de domínio pretende configurar?";
@ -809,3 +820,11 @@ Tenha em conta que algumas funcionalidades (esmaecidos abaixo) estão indisponí
"onboarding.tour.feature_unavailable" = "Esta funcionalidade não está disponível de momento e requer a instalação do Laravel Valet.";
"onboarding.tour.once" = "Apenas irá ver este 'Boas-vindas' uma vez. Pode visualiza-lo mais tarde através da barra de menum em 'Primeiros Socorros e Serviços'.";
"onboarding.tour.close" = "Fechar";
// LANGUAGE CHOICE
"prefs.language" = "Idioma:";
"prefs.language_options_desc" = "Escolha um idioma diferente para usar com o PHP Monitor. Para aplicar completamente esta mudança, deve reiniciar a aplicação.";
"alert.language_changed.title" = "Deve reiniciar o PHP Monitor!";
"alert.language_changed.subtitle" = "Acabou de mudar o idioma de exibição do PHP Monitor. O menu usará imediatamente a língua correta, mas pode ser necessário reiniciar a aplicação para que todo o texto na aplicação reflita a sua nova escolha de idioma.";

View File

@ -102,9 +102,16 @@
"phpextman.list.status.external" = "Tiện ích mở rộng này đã được cài đặt thông qua một nguồn khác và không thể được quản lý.";
"phpextman.list.status.installable" = "Tiện ích mở rộng này có thể được cài đặt.";
"phpextman.list.status.dependent" = "Bạn không thể gỡ cài đặt điều này trước khi gỡ cài đặt **%@**.";
"phpextman.list.status.dependent" = "Bạn không thể gỡ cài đặt điều này trước khi gỡ cài đặt %@.";
"phpextman.list.status.can_manage" = "Tiện ích mở rộng này đã được cài đặt và có thể được quản lý bởi PHP Monitor.";
"phpextman.errors.not_found.title" = "Ồ. Không phát hiện thấy tiện ích mở rộng nào!";
"phpextman.errors.not_found.desc" = "Điều này không nên xảy ra. Bạn có thể cần chạy lệnh sau trong terminal của mình:
`brew tap shivammathur/extensions`
và khởi động lại PHP Monitor để các tiện ích mở rộng trở nên hiển thị. Nếu vấn đề vẫn tiếp diễn sau khi chạy lệnh và khởi động lại PHP Monitor, xin hãy xem xét mở một vấn đề trên GitHub.";
// PHPMAN
"phpman.busy.title" = "Đang kiểm tra cập nhật!";
@ -127,7 +134,7 @@
"phpman.refresh.button" = "Tìm kiếm Cập nhật";
"phpman.refresh.button.description" = "Bạn có thể nhấn nút làm mới để kiểm tra xem có sẵn các cập nhật cho các phiên bản PHP đã cài đặt.";
"phpman.has_updates.description" = "Một hoặc nhiều cập nhật có sẵn. (Vui lòng lưu ý rằng PHP Monitor sẽ luôn cài đặt hoặc cập nhật các phiên bản PHP theo lô, vì vậy bạn sẽ luôn nâng cấp tất cả các cài đặt cùng một lúc.)";
"phpman.has_updates.description" = "**Một hoặc nhiều cập nhật có sẵn.** Vui lòng lưu ý rằng PHP Monitor sẽ luôn cài đặt hoặc cập nhật các phiên bản PHP theo lô, vì vậy bạn sẽ luôn nâng cấp tất cả các cài đặt cùng một lúc.";
"phpman.has_updates.button" = "Nâng cấp Tất cả";
"phpman.warnings.unsupported.title" = "Phiên bản Homebrew của bạn có thể gây ra vấn đề";
@ -317,6 +324,9 @@ Bạn có thể được yêu cầu nhập mật khẩu của mình trong quá t
"domain_list.columns.kind" = "Loại";
"domain_list.columns.project_type" = "Loại dự án";
"domain_list.extensions" = "Bật/tắt Tiện ích mở rộng";
"domain_list.applies_to" = "Áp dụng cho PHP %@";
// DRIVERS
"driver.not_detected" = "Khác";
@ -806,3 +816,11 @@ Vui lòng lưu ý rằng một số tính năng (xám bên dưới) hiện khôn
"onboarding.tour.feature_unavailable" = "Tính năng này hiện không khả dụng và yêu cầu Laravel Valet được cài đặt.";
"onboarding.tour.once" = "Bạn chỉ sẽ thấy Hướng Dẫn Chào Mừng một lần. Bạn có thể mở lại Hướng Dẫn Chào Mừng sau này qua biểu tượng thanh menu (có sẵn trong menu, ở dưới Cứu hộ và Các dịch vụ).";
"onboarding.tour.close" = "Đóng Tour";
// LANGUAGE CHOICE
"prefs.language" = "Ngôn ngữ:";
"prefs.language_options_desc" = "Chọn một ngôn ngữ khác để sử dụng với PHP Monitor. Để áp dụng hoàn toàn thay đổi này, bạn phải khởi động lại ứng dụng.";
"alert.language_changed.title" = "Bạn nên khởi động lại PHP Monitor!";
"alert.language_changed.subtitle" = "Bạn vừa thay đổi ngôn ngữ hiển thị của PHP Monitor. Menu sẽ ngay lập tức sử dụng ngôn ngữ đúng, nhưng bạn có thể cần phải khởi động lại ứng dụng để toàn bộ văn bản trong ứng dụng phản ánh sự lựa chọn ngôn ngữ mới của bạn.";

View File

@ -181,9 +181,6 @@ class TestableConfigurations {
"/opt/homebrew/bin/php -r echo ini_get('memory_limit');": "512M",
"/opt/homebrew/bin/php -r echo ini_get('upload_max_filesize');": "512M",
"/opt/homebrew/bin/php -r echo ini_get('post_max_size');": "512M",
"/opt/homebrew/opt/php@8.2/bin/php -v": "OK (no full output needed for testing)",
"/opt/homebrew/opt/php@8.1/bin/php -v": "OK (no full output needed for testing)",
"/opt/homebrew/opt/php@8.0/bin/php -v": "OK (no full output needed for testing)"
],
preferenceOverrides: [
.automaticBackgroundUpdateCheck: false
@ -191,7 +188,8 @@ class TestableConfigurations {
phpVersions: [
VersionNumber(major: 8, minor: 2, patch: 6),
VersionNumber(major: 8, minor: 1, patch: 0),
VersionNumber(major: 8, minor: 0, patch: 0)
VersionNumber(major: 8, minor: 0, patch: 0),
VersionNumber(major: 7, minor: 4, patch: 33)
]
)
}

View File

@ -90,16 +90,17 @@ final class MainMenuTest: UITestCase {
// Should display loader
assertExists(app.staticTexts["phpman.busy.title".localized], 1)
// After loading, should display PHP 8.2
// After loading, should display PHP 8.2 and PHP 8.3
assertExists(app.staticTexts["PHP 8.2"], 5)
assertExists(app.staticTexts["PHP 8.3"])
// Should also display pre-release version
assertExists(app.staticTexts["PHP 8.3"])
assertExists(app.staticTexts["PHP 8.4"])
assertExists(app.staticTexts["phpman.version.prerelease".localized.uppercased()])
assertExists(app.staticTexts["phpman.version.available_for_installation".localized])
// But not PHP 8.4 (yet)
assertNotExists(app.staticTexts["PHP 8.4"])
// But not PHP 8.5 (yet)
assertNotExists(app.staticTexts["PHP 8.5"])
// Also, PHP 8.2 should have an update available
assertExists(app.staticTexts["phpman.version.has_update".localized(

View File

@ -32,10 +32,10 @@ final class ExtensionEnumeratorTest: XCTestCase {
func testCanParseFormulaeBasedOnSyntax() throws {
let formulae = BrewTapFormulae.from(tap: "shivammathur/homebrew-extensions")
XCTAssertEqual(formulae["8.1"], [BrewPhpExtension(name: "xdebug", phpVersion: "8.1")])
XCTAssertEqual(formulae["8.2"], [BrewPhpExtension(name: "xdebug", phpVersion: "8.2")])
XCTAssertEqual(formulae["8.3"], [BrewPhpExtension(name: "xdebug", phpVersion: "8.3")])
XCTAssertEqual(formulae["8.4"], [BrewPhpExtension(name: "xdebug", phpVersion: "8.4")])
XCTAssertEqual(formulae["8.1"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.1")])
XCTAssertEqual(formulae["8.2"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.2")])
XCTAssertEqual(formulae["8.3"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.3")])
XCTAssertEqual(formulae["8.4"], [BrewPhpExtension(path: "/", name: "xdebug", phpVersion: "8.4")])
}
}

View File

@ -17,9 +17,19 @@ class HomebrewUpgradableTest: XCTestCase {
func test_upgradable_php_versions_can_be_parsed() async throws {
ActiveShell.useTestable([
"/opt/homebrew/bin/brew update >/dev/null && /opt/homebrew/bin/brew outdated --json --formulae"
: .instant(try! String(contentsOf: Self.outdatedFileUrl))
: .instant(try! String(contentsOf: Self.outdatedFileUrl)),
"/opt/homebrew/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"
: .instant("/opt/homebrew/etc/php/8.2/conf.d/php-memory-limits.ini"),
"/opt/homebrew/opt/php@8.1.16/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"
: .instant("/opt/homebrew/etc/php/8.1/conf.d/php-memory-limits.ini"),
"/opt/homebrew/opt/php@8.2.3/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"
: .instant("/opt/homebrew/etc/php/8.2/conf.d/php-memory-limits.ini"),
"/opt/homebrew/opt/php@7.4.11/bin/php --ini | grep -E -o '(/[^ ]+\\.ini)'"
: .instant("/opt/homebrew/etc/php/7.4/conf.d/php-memory-limits.ini")
])
// This config file assumes our PHP alias (`php`) is v8.2
PhpEnvironments.brewPhpAlias = "8.2"
let env = PhpEnvironments.shared
env.cachedPhpInstallations = [
"8.1": PhpInstallation("8.1.16"),