1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2026-03-30 00:20:08 +02:00

♻️ Various extension list improvements (#274)

Installing and removing extensions now scrolls to the extension afterwards, and animates this. This is done to emphasise that the operation succeeded.
This commit is contained in:
2024-08-31 15:57:08 +02:00
parent 3c0a4a6142
commit e026ecf60d
5 changed files with 53 additions and 20 deletions

View File

@@ -45,7 +45,6 @@ func grepContains(file: String, query: String) async -> Bool {
/** /**
Attempts to introduce sleep for a particular duration. Use with caution. Attempts to introduce sleep for a particular duration. Use with caution.
Only intended for testing purposes.
*/ */
func delay(seconds: Double) async { func delay(seconds: Double) async {
try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))

View File

@@ -951,9 +951,9 @@ Gw
</textFieldCell> </textFieldCell>
</textField> </textField>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="3Wp-DX-An9"> <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="3Wp-DX-An9">
<rect key="frame" x="5" y="4.5" width="19" height="45"/> <rect key="frame" x="5" y="4" width="20" height="47"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="Q76-fI-lkW"> <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="Q76-fI-lkW">
<imageReference key="image" image="star.square.fill" catalog="system" symbolScale="large"/> <imageReference key="image" image="star.circle.fill" catalog="system" symbolScale="large"/>
</imageCell> </imageCell>
<color key="contentTintColor" name="AccentColor"/> <color key="contentTintColor" name="AccentColor"/>
</imageView> </imageView>
@@ -1547,7 +1547,7 @@ Gw
<image name="Lock" width="30" height="30"/> <image name="Lock" width="30" height="30"/>
<image name="arrow.clockwise" catalog="system" width="14" height="16"/> <image name="arrow.clockwise" catalog="system" width="14" height="16"/>
<image name="plus" catalog="system" width="14" height="13"/> <image name="plus" catalog="system" width="14" height="13"/>
<image name="star.square.fill" catalog="system" width="19" height="18"/> <image name="star.circle.fill" catalog="system" width="20" height="20"/>
<namedColor name="AccentColor"> <namedColor name="AccentColor">
<color red="0.0" green="0.46000000000000002" blue="0.89000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.0" green="0.46000000000000002" blue="0.89000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor> </namedColor>

View File

@@ -27,13 +27,14 @@ extension PhpExtensionManagerView {
) )
} }
public func install(_ ext: BrewPhpExtension) { public func install(_ ext: BrewPhpExtension, onCompletion: @escaping () -> Void = {}) {
Task { Task {
await self.runCommand(InstallPhpExtensionCommand(install: [ext])) await self.runCommand(InstallPhpExtensionCommand(install: [ext]))
onCompletion()
} }
} }
public func confirmUninstall(_ ext: BrewPhpExtension) { public func confirmUninstall(_ ext: BrewPhpExtension, onCompletion: @escaping () -> Void = {}) {
Alert.confirm( Alert.confirm(
onWindow: App.shared.phpExtensionManagerWindowController!.window!, onWindow: App.shared.phpExtensionManagerWindowController!.window!,
messageText: "phpextman.warnings.removal.title".localized(ext.name), messageText: "phpextman.warnings.removal.title".localized(ext.name),
@@ -45,6 +46,7 @@ extension PhpExtensionManagerView {
onFirstButtonPressed: { onFirstButtonPressed: {
Task { Task {
await self.runCommand(RemovePhpExtensionCommand(remove: ext)) await self.runCommand(RemovePhpExtensionCommand(remove: ext))
onCompletion()
} }
} }
) )

View File

@@ -13,6 +13,7 @@ struct PhpExtensionManagerView: View {
@ObservedObject var manager: BrewExtensionsObservable @ObservedObject var manager: BrewExtensionsObservable
@ObservedObject var status: BusyStatus @ObservedObject var status: BusyStatus
@State var searchText: String @State var searchText: String
@State private var highlightedExtension: String?
init() { init() {
self.searchText = "" self.searchText = ""
@@ -24,9 +25,12 @@ struct PhpExtensionManagerView: View {
var filteredExtensions: [BrewPhpExtension] { var filteredExtensions: [BrewPhpExtension] {
guard !searchText.isEmpty else { guard !searchText.isEmpty else {
return manager.extensions return manager.extensions.sorted { $0.isInstalled && !$1.isInstalled }
} }
return manager.extensions.filter { $0.name.contains(searchText) }
return manager.extensions
.filter { $0.name.contains(searchText) }
.sorted { $0.isInstalled && !$1.isInstalled }
} }
var body: some View { var body: some View {
@@ -48,14 +52,19 @@ struct PhpExtensionManagerView: View {
title: self.status.title, title: self.status.title,
text: self.status.description text: self.status.description
) { ) {
ScrollViewReader { proxy in
List(Array(self.filteredExtensions.enumerated()), id: \.1.name) { (_, ext) in List(Array(self.filteredExtensions.enumerated()), id: \.1.name) { (_, ext) in
listContent(for: ext) listContent(for: ext, proxy: proxy)
.padding(.vertical, 8)
.padding(.horizontal, 8)
} }
.edgesIgnoringSafeArea(.top) .edgesIgnoringSafeArea(.top)
.listStyle(PlainListStyle()) .listStyle(PlainListStyle())
.searchable(text: $searchText) .searchable(text: $searchText)
.onChange(of: manager.phpVersion, perform: { _ in
if let ext = self.filteredExtensions.first {
proxy.scrollTo(ext.name)
}
})
}
} }
} }
.frame(minWidth: 600, minHeight: 600) .frame(minWidth: 600, minHeight: 600)
@@ -147,7 +156,20 @@ struct PhpExtensionManagerView: View {
} }
} }
private func listContent(for ext: BrewPhpExtension) -> some View { private func scrollAndAnimate(_ ext: BrewPhpExtension, _ proxy: ScrollViewProxy) {
withAnimation {
highlightedExtension = ext.name
proxy.scrollTo(ext.name, anchor: .top)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
withAnimation {
highlightedExtension = nil
}
}
}
private func listContent(for ext: BrewPhpExtension, proxy: ScrollViewProxy) -> some View {
HStack(alignment: .center, spacing: 7.0) { HStack(alignment: .center, spacing: 7.0) {
VStack(alignment: .center, spacing: 0) { VStack(alignment: .center, spacing: 0) {
HStack { HStack {
@@ -184,15 +206,25 @@ struct PhpExtensionManagerView: View {
HStack { HStack {
if ext.isInstalled { if ext.isInstalled {
Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) { Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) {
self.confirmUninstall(ext) self.confirmUninstall(ext, onCompletion: {
}.disabled(ext.firstDependent(in: self.manager.extensions) != nil) scrollAndAnimate(ext, proxy)
})
}
.disabled(ext.firstDependent(in: self.manager.extensions) != nil)
} else { } else {
Button("phpman.buttons.install".localizedForSwiftUI) { Button("phpman.buttons.install".localizedForSwiftUI) {
self.install(ext) self.install(ext, onCompletion: {
scrollAndAnimate(ext, proxy)
})
}.disabled(ext.hasAlternativeInstall) }.disabled(ext.hasAlternativeInstall)
} }
} }
} }
.padding(.vertical, 8)
.padding(.horizontal, 8)
.background(highlightedExtension == ext.name ? Color.accentColor.opacity(0.3) : Color.clear)
.cornerRadius(8)
.animation(.easeInOut(duration: 0.5), value: highlightedExtension)
} }
} }

View File

@@ -120,7 +120,7 @@
"phpextman.list.status.external" = "This extension is already installed via another source, and cannot be managed."; "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.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.list.status.can_manage" = "This extension is installed and managed by PHP Monitor.";
"phpextman.errors.not_found.title" = "Uh oh. No extensions discovered!"; "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: "phpextman.errors.not_found.desc" = "This is not supposed to happen. You may need to run the following command in your terminal: