qml: confirm destructive actions

This commit is contained in:
Marco Allegretti 2026-01-25 10:03:46 +01:00
parent b961a8cc8f
commit e754d88eb0
2 changed files with 253 additions and 24 deletions

View file

@ -19,6 +19,7 @@ Kirigami.ApplicationWindow {
height: Kirigami.Units.gridUnit * 40 height: Kirigami.Units.gridUnit * 40
property var selectedGame: null property var selectedGame: null
property var pendingRemoveGame: null
property string currentSource: "all" property string currentSource: "all"
property bool searchActive: false property bool searchActive: false
property bool settingsLayerOpen: false property bool settingsLayerOpen: false
@ -498,10 +499,29 @@ Kirigami.ApplicationWindow {
} }
onRemoveRequested: { onRemoveRequested: {
if (root.selectedGame) { if (!root.selectedGame) return
let gameId = root.selectedGame.id root.pendingRemoveGame = root.selectedGame
let gameName = root.selectedGame.name removeGameConfirmDialog.open()
App.removeGame(root.selectedGame) }
onClosed: {
libraryView.restoreFocus()
}
}
Kirigami.PromptDialog {
id: removeGameConfirmDialog
title: i18n("Remove Game")
subtitle: root.pendingRemoveGame
? i18n("Are you sure you want to remove '%1'?", root.pendingRemoveGame.name)
: i18n("Are you sure you want to remove this game?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onAccepted: {
if (!root.pendingRemoveGame) return
let gameId = root.pendingRemoveGame.id
let gameName = root.pendingRemoveGame.name
App.removeGame(root.pendingRemoveGame)
root.pendingRemoveGame = null
detailsSheet.close() detailsSheet.close()
root.selectedGame = null root.selectedGame = null
showPassiveNotification( showPassiveNotification(
@ -511,10 +531,8 @@ Kirigami.ApplicationWindow {
function() { App.restoreGame(gameId) } function() { App.restoreGame(gameId) }
) )
} }
} onRejected: {
root.pendingRemoveGame = null
onClosed: {
libraryView.restoreFocus()
} }
} }

View file

@ -17,6 +17,17 @@ ColumnLayout {
showPlatformBadgesDelegate.forceActiveFocus() showPlatformBadgesDelegate.forceActiveFocus()
} }
property var pendingDisableImportApply: null
property var pendingDisableImportDelegate: null
property string pendingDisableImportName: ""
function requestDisableImport(delegate, sourceName, applyFn) {
pendingDisableImportDelegate = delegate
pendingDisableImportName = sourceName
pendingDisableImportApply = applyFn
disableImportConfirmDialog.open()
}
FormCard.FormHeader { FormCard.FormHeader {
Layout.fillWidth: true Layout.fillWidth: true
title: i18n("Appearance") title: i18n("Appearance")
@ -88,8 +99,26 @@ FormCard.FormHeader {
secondary: "steam" secondary: "steam"
resourceFallback: Qt.resolvedUrl("icons/brand/steam-symbolic.svg") resourceFallback: Qt.resolvedUrl("icons/brand/steam-symbolic.svg")
} }
property bool restoring: false
checked: App.config.importSteam checked: App.config.importSteam
onToggled: App.config.importSteam = checked onToggled: {
if (restoring) return
if (checked === App.config.importSteam) return
if (!checked && App.gameModel.hasPlatformPrefix("Steam")) {
restoring = true
checked = Qt.binding(function() { return App.config.importSteam })
restoring = false
settingsPage.requestDisableImport(this, i18n("Steam"), function() { App.config.importSteam = false })
return
}
App.config.importSteam = checked
restoring = true
checked = Qt.binding(function() { return App.config.importSteam })
restoring = false
}
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -101,8 +130,26 @@ FormCard.FormHeader {
primary: "lutris" primary: "lutris"
secondary: "applications-games" secondary: "applications-games"
} }
property bool restoring: false
checked: App.config.importLutris checked: App.config.importLutris
onToggled: App.config.importLutris = checked onToggled: {
if (restoring) return
if (checked === App.config.importLutris) return
if (!checked && App.gameModel.hasPlatformPrefix("Lutris")) {
restoring = true
checked = Qt.binding(function() { return App.config.importLutris })
restoring = false
settingsPage.requestDisableImport(this, i18n("Lutris"), function() { App.config.importLutris = false })
return
}
App.config.importLutris = checked
restoring = true
checked = Qt.binding(function() { return App.config.importLutris })
restoring = false
}
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -115,8 +162,26 @@ FormCard.FormHeader {
primary: "com.heroicgameslauncher.hgl" primary: "com.heroicgameslauncher.hgl"
secondary: "applications-games" secondary: "applications-games"
} }
property bool restoring: false
checked: App.config.importHeroic checked: App.config.importHeroic
onToggled: App.config.importHeroic = checked onToggled: {
if (restoring) return
if (checked === App.config.importHeroic) return
if (!checked && App.gameModel.hasPlatformPrefix("Heroic")) {
restoring = true
checked = Qt.binding(function() { return App.config.importHeroic })
restoring = false
settingsPage.requestDisableImport(this, i18n("Heroic Games Launcher"), function() { App.config.importHeroic = false })
return
}
App.config.importHeroic = checked
restoring = true
checked = Qt.binding(function() { return App.config.importHeroic })
restoring = false
}
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -129,8 +194,26 @@ FormCard.FormHeader {
primary: "user-desktop" primary: "user-desktop"
secondary: "computer" secondary: "computer"
} }
property bool restoring: false
checked: App.config.importDesktop checked: App.config.importDesktop
onToggled: App.config.importDesktop = checked onToggled: {
if (restoring) return
if (checked === App.config.importDesktop) return
if (!checked && App.gameModel.hasPlatformPrefix("Desktop")) {
restoring = true
checked = Qt.binding(function() { return App.config.importDesktop })
restoring = false
settingsPage.requestDisableImport(this, i18n("Desktop Entries"), function() { App.config.importDesktop = false })
return
}
App.config.importDesktop = checked
restoring = true
checked = Qt.binding(function() { return App.config.importDesktop })
restoring = false
}
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -143,8 +226,26 @@ FormCard.FormHeader {
primary: "com.usebottles.bottles" primary: "com.usebottles.bottles"
secondary: "application-x-executable" secondary: "application-x-executable"
} }
property bool restoring: false
checked: App.config.importBottles checked: App.config.importBottles
onToggled: App.config.importBottles = checked onToggled: {
if (restoring) return
if (checked === App.config.importBottles) return
if (!checked && App.gameModel.hasPlatformPrefix("Bottles")) {
restoring = true
checked = Qt.binding(function() { return App.config.importBottles })
restoring = false
settingsPage.requestDisableImport(this, i18n("Bottles"), function() { App.config.importBottles = false })
return
}
App.config.importBottles = checked
restoring = true
checked = Qt.binding(function() { return App.config.importBottles })
restoring = false
}
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -157,8 +258,26 @@ FormCard.FormHeader {
primary: "flatpak-discover" primary: "flatpak-discover"
secondary: "applications-games" secondary: "applications-games"
} }
property bool restoring: false
checked: App.config.importFlatpak checked: App.config.importFlatpak
onToggled: App.config.importFlatpak = checked onToggled: {
if (restoring) return
if (checked === App.config.importFlatpak) return
if (!checked && App.gameModel.hasPlatformPrefix("Flatpak")) {
restoring = true
checked = Qt.binding(function() { return App.config.importFlatpak })
restoring = false
settingsPage.requestDisableImport(this, i18n("Flatpak"), function() { App.config.importFlatpak = false })
return
}
App.config.importFlatpak = checked
restoring = true
checked = Qt.binding(function() { return App.config.importFlatpak })
restoring = false
}
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -172,8 +291,26 @@ FormCard.FormHeader {
secondary: "itch" secondary: "itch"
resourceFallback: Qt.resolvedUrl("icons/brand/itchdotio-symbolic.svg") resourceFallback: Qt.resolvedUrl("icons/brand/itchdotio-symbolic.svg")
} }
property bool restoring: false
checked: App.config.importItch checked: App.config.importItch
onToggled: App.config.importItch = checked onToggled: {
if (restoring) return
if (checked === App.config.importItch) return
if (!checked && App.gameModel.hasPlatformPrefix("itch.io")) {
restoring = true
checked = Qt.binding(function() { return App.config.importItch })
restoring = false
settingsPage.requestDisableImport(this, i18n("itch.io"), function() { App.config.importItch = false })
return
}
App.config.importItch = checked
restoring = true
checked = Qt.binding(function() { return App.config.importItch })
restoring = false
}
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -186,8 +323,26 @@ FormCard.FormHeader {
primary: "legendary" primary: "legendary"
secondary: "applications-games" secondary: "applications-games"
} }
property bool restoring: false
checked: App.config.importLegendary checked: App.config.importLegendary
onToggled: App.config.importLegendary = checked onToggled: {
if (restoring) return
if (checked === App.config.importLegendary) return
if (!checked && App.gameModel.hasPlatformPrefix("Legendary")) {
restoring = true
checked = Qt.binding(function() { return App.config.importLegendary })
restoring = false
settingsPage.requestDisableImport(this, i18n("Legendary"), function() { App.config.importLegendary = false })
return
}
App.config.importLegendary = checked
restoring = true
checked = Qt.binding(function() { return App.config.importLegendary })
restoring = false
}
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -201,8 +356,26 @@ FormCard.FormHeader {
secondary: "retroarch" secondary: "retroarch"
resourceFallback: Qt.resolvedUrl("icons/brand/retroarch-symbolic.svg") resourceFallback: Qt.resolvedUrl("icons/brand/retroarch-symbolic.svg")
} }
property bool restoring: false
checked: App.config.importRetroArch checked: App.config.importRetroArch
onToggled: App.config.importRetroArch = checked onToggled: {
if (restoring) return
if (checked === App.config.importRetroArch) return
if (!checked && App.gameModel.hasPlatformPrefix("RetroArch")) {
restoring = true
checked = Qt.binding(function() { return App.config.importRetroArch })
restoring = false
settingsPage.requestDisableImport(this, i18n("RetroArch"), function() { App.config.importRetroArch = false })
return
}
App.config.importRetroArch = checked
restoring = true
checked = Qt.binding(function() { return App.config.importRetroArch })
restoring = false
}
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -309,7 +482,7 @@ FormCard.FormHeader {
description: App.steamGridDB.busy ? i18n("Fetching...") : i18n("Download covers for all games") description: App.steamGridDB.busy ? i18n("Fetching...") : i18n("Download covers for all games")
icon.name: "download" icon.name: "download"
enabled: App.steamGridDB.enabled && App.steamGridDB.apiKey.length > 0 && !App.steamGridDB.busy enabled: App.steamGridDB.enabled && App.steamGridDB.apiKey.length > 0 && !App.steamGridDB.busy
onClicked: App.steamGridDB.fetchAllCovers() onClicked: fetchAllCoversConfirmDialog.open()
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -355,7 +528,7 @@ FormCard.FormHeader {
text: i18n("Remove Missing Games") text: i18n("Remove Missing Games")
description: i18n("Remove games whose executables no longer exist") description: i18n("Remove games whose executables no longer exist")
icon.name: "edit-delete" icon.name: "edit-delete"
onClicked: App.removeMissingGames() onClicked: removeMissingConfirmDialog.open()
} }
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
@ -377,6 +550,44 @@ FormCard.FormHeader {
} }
} }
Kirigami.PromptDialog {
id: disableImportConfirmDialog
title: i18n("Disable Import Source")
subtitle: i18n("Disabling %1 will remove all games imported from that source. Are you sure?", settingsPage.pendingDisableImportName)
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onAccepted: {
if (settingsPage.pendingDisableImportApply) {
settingsPage.pendingDisableImportApply()
}
settingsPage.pendingDisableImportApply = null
settingsPage.pendingDisableImportDelegate = null
settingsPage.pendingDisableImportName = ""
}
onRejected: {
settingsPage.pendingDisableImportApply = null
settingsPage.pendingDisableImportDelegate = null
settingsPage.pendingDisableImportName = ""
}
}
Kirigami.PromptDialog {
id: fetchAllCoversConfirmDialog
title: i18n("Fetch All Covers")
subtitle: App.steamGridDB.preferSteamGridDB
? i18n("This will download cover art for all games and may replace existing covers. Continue?")
: i18n("This will download cover art for games that are missing covers. Continue?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onAccepted: App.steamGridDB.fetchAllCovers()
}
Kirigami.PromptDialog {
id: removeMissingConfirmDialog
title: i18n("Remove Missing Games")
subtitle: i18n("This will remove games whose executables cannot be found. This cannot be undone. Continue?")
standardButtons: Kirigami.Dialog.Yes | Kirigami.Dialog.No
onAccepted: App.removeMissingGames()
}
Kirigami.PromptDialog { Kirigami.PromptDialog {
id: clearConfirmDialog id: clearConfirmDialog
title: i18n("Clear Library") title: i18n("Clear Library")