mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-08-06 19:40: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:
@ -45,7 +45,6 @@ func grepContains(file: String, query: String) async -> Bool {
|
||||
|
||||
/**
|
||||
Attempts to introduce sleep for a particular duration. Use with caution.
|
||||
Only intended for testing purposes.
|
||||
*/
|
||||
func delay(seconds: Double) async {
|
||||
try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
|
||||
|
@ -951,9 +951,9 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<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">
|
||||
<imageReference key="image" image="star.square.fill" catalog="system" symbolScale="large"/>
|
||||
<imageReference key="image" image="star.circle.fill" catalog="system" symbolScale="large"/>
|
||||
</imageCell>
|
||||
<color key="contentTintColor" name="AccentColor"/>
|
||||
</imageView>
|
||||
@ -1547,7 +1547,7 @@ Gw
|
||||
<image name="Lock" width="30" height="30"/>
|
||||
<image name="arrow.clockwise" catalog="system" width="14" height="16"/>
|
||||
<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">
|
||||
<color red="0.0" green="0.46000000000000002" blue="0.89000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
|
@ -27,13 +27,14 @@ extension PhpExtensionManagerView {
|
||||
)
|
||||
}
|
||||
|
||||
public func install(_ ext: BrewPhpExtension) {
|
||||
public func install(_ ext: BrewPhpExtension, onCompletion: @escaping () -> Void = {}) {
|
||||
Task {
|
||||
await self.runCommand(InstallPhpExtensionCommand(install: [ext]))
|
||||
onCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
public func confirmUninstall(_ ext: BrewPhpExtension) {
|
||||
public func confirmUninstall(_ ext: BrewPhpExtension, onCompletion: @escaping () -> Void = {}) {
|
||||
Alert.confirm(
|
||||
onWindow: App.shared.phpExtensionManagerWindowController!.window!,
|
||||
messageText: "phpextman.warnings.removal.title".localized(ext.name),
|
||||
@ -45,6 +46,7 @@ extension PhpExtensionManagerView {
|
||||
onFirstButtonPressed: {
|
||||
Task {
|
||||
await self.runCommand(RemovePhpExtensionCommand(remove: ext))
|
||||
onCompletion()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -13,6 +13,7 @@ struct PhpExtensionManagerView: View {
|
||||
@ObservedObject var manager: BrewExtensionsObservable
|
||||
@ObservedObject var status: BusyStatus
|
||||
@State var searchText: String
|
||||
@State private var highlightedExtension: String?
|
||||
|
||||
init() {
|
||||
self.searchText = ""
|
||||
@ -24,9 +25,12 @@ struct PhpExtensionManagerView: View {
|
||||
|
||||
var filteredExtensions: [BrewPhpExtension] {
|
||||
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 {
|
||||
@ -48,14 +52,19 @@ struct PhpExtensionManagerView: View {
|
||||
title: self.status.title,
|
||||
text: self.status.description
|
||||
) {
|
||||
List(Array(self.filteredExtensions.enumerated()), id: \.1.name) { (_, ext) in
|
||||
listContent(for: ext)
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 8)
|
||||
ScrollViewReader { proxy in
|
||||
List(Array(self.filteredExtensions.enumerated()), id: \.1.name) { (_, ext) in
|
||||
listContent(for: ext, proxy: proxy)
|
||||
}
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.listStyle(PlainListStyle())
|
||||
.searchable(text: $searchText)
|
||||
.onChange(of: manager.phpVersion, perform: { _ in
|
||||
if let ext = self.filteredExtensions.first {
|
||||
proxy.scrollTo(ext.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
.edgesIgnoringSafeArea(.top)
|
||||
.listStyle(PlainListStyle())
|
||||
.searchable(text: $searchText)
|
||||
}
|
||||
}
|
||||
.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) {
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
HStack {
|
||||
@ -184,15 +206,25 @@ struct PhpExtensionManagerView: View {
|
||||
HStack {
|
||||
if ext.isInstalled {
|
||||
Button("phpman.buttons.uninstall".localizedForSwiftUI, role: .destructive) {
|
||||
self.confirmUninstall(ext)
|
||||
}.disabled(ext.firstDependent(in: self.manager.extensions) != nil)
|
||||
self.confirmUninstall(ext, onCompletion: {
|
||||
scrollAndAnimate(ext, proxy)
|
||||
})
|
||||
}
|
||||
.disabled(ext.firstDependent(in: self.manager.extensions) != nil)
|
||||
} else {
|
||||
Button("phpman.buttons.install".localizedForSwiftUI) {
|
||||
self.install(ext)
|
||||
self.install(ext, onCompletion: {
|
||||
scrollAndAnimate(ext, proxy)
|
||||
})
|
||||
}.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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@
|
||||
"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.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.desc" = "This is not supposed to happen. You may need to run the following command in your terminal:
|
||||
|
Reference in New Issue
Block a user