mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 14:10:52 +01:00
Add context menu for vault actions and trash functionality
- Add UiButtonContext component for context menu support on buttons - Implement vault trash functionality using trash crate - Move vaults to system trash on desktop (with fallback to permanent delete on mobile) - Add context menu to vault list items for better mobile UX - Keep hover delete button for desktop users
This commit is contained in:
108
src-tauri/Cargo.lock
generated
108
src-tauri/Cargo.lock
generated
@ -1715,6 +1715,7 @@ dependencies = [
|
||||
"tauri-plugin-persisted-scope",
|
||||
"tauri-plugin-store",
|
||||
"thiserror 2.0.17",
|
||||
"trash",
|
||||
"ts-rs",
|
||||
"uhlc",
|
||||
"url",
|
||||
@ -4431,7 +4432,7 @@ dependencies = [
|
||||
"tao-macros",
|
||||
"unicode-segmentation",
|
||||
"url",
|
||||
"windows",
|
||||
"windows 0.61.1",
|
||||
"windows-core 0.61.0",
|
||||
"windows-version",
|
||||
"x11-dl",
|
||||
@ -4503,7 +4504,7 @@ dependencies = [
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"window-vibrancy",
|
||||
"windows",
|
||||
"windows 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4687,7 +4688,7 @@ dependencies = [
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.17",
|
||||
"url",
|
||||
"windows",
|
||||
"windows 0.61.1",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
@ -4763,7 +4764,7 @@ dependencies = [
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4789,7 +4790,7 @@ dependencies = [
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.61.1",
|
||||
"wry",
|
||||
]
|
||||
|
||||
@ -4849,7 +4850,7 @@ checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
|
||||
dependencies = [
|
||||
"quick-xml 0.37.5",
|
||||
"thiserror 2.0.17",
|
||||
"windows",
|
||||
"windows 0.61.1",
|
||||
"windows-version",
|
||||
]
|
||||
|
||||
@ -5189,6 +5190,24 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trash"
|
||||
version = "5.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22746c6b0c6d85d60a8f0d858f7057dfdf11297c132679f452ec908fba42b871"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"libc",
|
||||
"log",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"scopeguard",
|
||||
"urlencoding",
|
||||
"windows 0.56.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tray-icon"
|
||||
version = "0.21.0"
|
||||
@ -5353,6 +5372,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "urlpattern"
|
||||
version = "0.3.0"
|
||||
@ -5638,10 +5663,10 @@ checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4"
|
||||
dependencies = [
|
||||
"webview2-com-macros",
|
||||
"webview2-com-sys",
|
||||
"windows",
|
||||
"windows 0.61.1",
|
||||
"windows-core 0.61.0",
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-implement 0.60.0",
|
||||
"windows-interface 0.59.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5662,7 +5687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c"
|
||||
dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"windows",
|
||||
"windows 0.61.1",
|
||||
"windows-core 0.61.0",
|
||||
]
|
||||
|
||||
@ -5712,6 +5737,16 @@ dependencies = [
|
||||
"windows-version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
|
||||
dependencies = [
|
||||
"windows-core 0.56.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.1"
|
||||
@ -5743,16 +5778,28 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6"
|
||||
dependencies = [
|
||||
"windows-implement 0.56.0",
|
||||
"windows-interface 0.56.0",
|
||||
"windows-result 0.1.2",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-implement 0.60.0",
|
||||
"windows-interface 0.59.1",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings 0.4.0",
|
||||
]
|
||||
|
||||
@ -5766,6 +5813,17 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
@ -5777,6 +5835,17 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
@ -5810,11 +5879,20 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings 0.3.1",
|
||||
"windows-targets 0.53.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.2"
|
||||
@ -6226,7 +6304,7 @@ dependencies = [
|
||||
"webkit2gtk",
|
||||
"webkit2gtk-sys",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.61.1",
|
||||
"windows-core 0.61.0",
|
||||
"windows-version",
|
||||
"x11-dl",
|
||||
|
||||
@ -49,6 +49,7 @@ tauri-plugin-os = "2.3.2"
|
||||
tauri-plugin-persisted-scope = "2.3.4"
|
||||
tauri-plugin-store = "2.4.1"
|
||||
thiserror = "2.0.17"
|
||||
trash = "5.2.0"
|
||||
ts-rs = { version = "11.1.0", features = ["serde-compat"] }
|
||||
uhlc = "0.8.2"
|
||||
url = "2.5.7"
|
||||
|
||||
Binary file not shown.
@ -20,6 +20,7 @@ use std::time::UNIX_EPOCH;
|
||||
use std::{fs, sync::Arc};
|
||||
use tauri::{path::BaseDirectory, AppHandle, Manager, State};
|
||||
use tauri_plugin_fs::FsExt;
|
||||
use trash;
|
||||
use ts_rs::TS;
|
||||
|
||||
pub struct DbConnection(pub Arc<Mutex<Option<Connection>>>);
|
||||
@ -212,7 +213,46 @@ pub fn vault_exists(app_handle: AppHandle, vault_name: String) -> Result<bool, D
|
||||
Ok(Path::new(&vault_path).exists())
|
||||
}
|
||||
|
||||
/// Deletes a vault database file
|
||||
/// Moves a vault database file to trash (or deletes permanently if trash is unavailable)
|
||||
#[tauri::command]
|
||||
pub fn move_vault_to_trash(
|
||||
app_handle: AppHandle,
|
||||
vault_name: String,
|
||||
) -> Result<String, DatabaseError> {
|
||||
let vault_path = get_vault_path(&app_handle, &vault_name)?;
|
||||
let vault_shm_path = format!("{}-shm", vault_path);
|
||||
let vault_wal_path = format!("{}-wal", vault_path);
|
||||
|
||||
if !Path::new(&vault_path).exists() {
|
||||
return Err(DatabaseError::IoError {
|
||||
path: vault_path,
|
||||
reason: "Vault does not exist".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Try to move to trash first (works on desktop systems)
|
||||
let moved_to_trash = trash::delete(&vault_path).is_ok();
|
||||
|
||||
if moved_to_trash {
|
||||
// Also try to move auxiliary files to trash (ignore errors as they might not exist)
|
||||
let _ = trash::delete(&vault_shm_path);
|
||||
let _ = trash::delete(&vault_wal_path);
|
||||
|
||||
Ok(format!(
|
||||
"Vault '{}' successfully moved to trash",
|
||||
vault_name
|
||||
))
|
||||
} else {
|
||||
// Fallback: Permanent deletion (e.g., on mobile devices without trash)
|
||||
println!(
|
||||
"Trash not available, falling back to permanent deletion for vault '{}'",
|
||||
vault_name
|
||||
);
|
||||
delete_vault(app_handle, vault_name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes a vault database file permanently (bypasses trash)
|
||||
#[tauri::command]
|
||||
pub fn delete_vault(app_handle: AppHandle, vault_name: String) -> Result<String, DatabaseError> {
|
||||
let vault_path = get_vault_path(&app_handle, &vault_name)?;
|
||||
|
||||
@ -68,6 +68,7 @@ pub fn run() {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
database::create_encrypted_database,
|
||||
database::delete_vault,
|
||||
database::move_vault_to_trash,
|
||||
database::list_vaults,
|
||||
database::open_encrypted_database,
|
||||
database::sql_execute_with_crdt,
|
||||
|
||||
28
src/components/ui/button/context.vue
Normal file
28
src/components/ui/button/context.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<UContextMenu :items="contextMenuItems">
|
||||
<UiButton
|
||||
v-bind="$attrs"
|
||||
@click="$emit('click', $event)"
|
||||
>
|
||||
<template
|
||||
v-for="(_, slotName) in $slots"
|
||||
#[slotName]="slotProps"
|
||||
>
|
||||
<slot
|
||||
:name="slotName"
|
||||
v-bind="slotProps"
|
||||
/>
|
||||
</template>
|
||||
</UiButton>
|
||||
</UContextMenu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ContextMenuItem } from '@nuxt/ui'
|
||||
|
||||
defineProps<{
|
||||
contextMenuItems: ContextMenuItem[]
|
||||
}>()
|
||||
|
||||
defineEmits<{ click: [Event] }>()
|
||||
</script>
|
||||
@ -54,10 +54,18 @@
|
||||
:key="vault.name"
|
||||
class="flex items-center justify-between group overflow-x-scroll"
|
||||
>
|
||||
<UButton
|
||||
<UiButtonContext
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
class="flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full px-3"
|
||||
:context-menu-items="[
|
||||
{
|
||||
icon: 'mdi:trash-can-outline',
|
||||
label: t('remove.button'),
|
||||
onSelect: () => prepareRemoveVault(vault.name),
|
||||
color: 'error',
|
||||
},
|
||||
]"
|
||||
@click="
|
||||
() => {
|
||||
passwordPromptOpen = true
|
||||
@ -68,7 +76,7 @@
|
||||
<span class="block">
|
||||
{{ vault.name }}
|
||||
</span>
|
||||
</UButton>
|
||||
</UiButtonContext>
|
||||
<UButton
|
||||
color="error"
|
||||
square
|
||||
@ -129,6 +137,9 @@ const showRemoveDialog = ref(false)
|
||||
|
||||
const { lastVaults } = storeToRefs(useLastVaultStore())
|
||||
|
||||
const { syncLastVaultsAsync, moveVaultToTrashAsync } = useLastVaultStore()
|
||||
const { syncDeviceIdAsync } = useDeviceStore()
|
||||
|
||||
const vaultToBeRemoved = ref('')
|
||||
const prepareRemoveVault = (vaultName: string) => {
|
||||
vaultToBeRemoved.value = vaultName
|
||||
@ -138,7 +149,7 @@ const prepareRemoveVault = (vaultName: string) => {
|
||||
const toast = useToast()
|
||||
const onConfirmRemoveAsync = async () => {
|
||||
try {
|
||||
await removeVaultAsync(vaultToBeRemoved.value)
|
||||
await moveVaultToTrashAsync(vaultToBeRemoved.value)
|
||||
showRemoveDialog.value = false
|
||||
await syncLastVaultsAsync()
|
||||
} catch (error) {
|
||||
@ -149,9 +160,6 @@ const onConfirmRemoveAsync = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const { syncLastVaultsAsync, removeVaultAsync } = useLastVaultStore()
|
||||
const { syncDeviceIdAsync } = useDeviceStore()
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await syncLastVaultsAsync()
|
||||
@ -168,6 +176,7 @@ de:
|
||||
lastUsed: 'Zuletzt verwendete Vaults'
|
||||
sponsors: Supported by
|
||||
remove:
|
||||
button: Löschen
|
||||
title: Vault löschen
|
||||
description: Möchtest du die Vault {vaultName} wirklich löschen?
|
||||
|
||||
@ -176,6 +185,7 @@ en:
|
||||
lastUsed: 'Last used Vaults'
|
||||
sponsors: 'Supported by'
|
||||
remove:
|
||||
button: Delete
|
||||
title: Delete Vault
|
||||
description: Are you sure you really want to delete {vaultName}?
|
||||
</i18n>
|
||||
|
||||
@ -22,9 +22,14 @@ export const useLastVaultStore = defineStore('lastVaultStore', () => {
|
||||
return await invoke('delete_vault', { vaultName })
|
||||
}
|
||||
|
||||
const moveVaultToTrashAsync = async (vaultName: string) => {
|
||||
return await invoke('move_vault_to_trash', { vaultName })
|
||||
}
|
||||
|
||||
return {
|
||||
syncLastVaultsAsync,
|
||||
lastVaults,
|
||||
removeVaultAsync,
|
||||
moveVaultToTrashAsync,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user