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:
@@ -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))
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user