switch to nuxt ui

This commit is contained in:
2025-09-11 00:58:55 +02:00
parent 3975d26caa
commit 0a7de8b78b
143 changed files with 19019 additions and 9899 deletions

View File

@ -1,8 +1,9 @@
<template>
<div class="items-center justify-center flex w-full relative min-h-full">
<div class="absolute top-2 right-2">
<UiDropdownLocale @select="setLocale" />
<div class="items-center justify-center flex w-full h-full relative">
<div class="absolute top-2 right-4">
<UiDropdownLocale @select="onSelectLocale" />
</div>
<div class="flex flex-col justify-center items-center gap-5 max-w-3xl">
<UiLogoHaexhub class="bg-primary p-3 size-16 rounded-full shrink-0" />
<span
@ -32,16 +33,23 @@
</div>
<div
class="relative border-base-content/25 divide-base-content/25 flex w-full flex-col divide-y rounded-md border first:*:rounded-t-md last:*:rounded-b-md overflow-scroll"
class="relative border-base-content/25 divide-base-content/25 flex w-full flex-col divide-y rounded-md border overflow-scroll"
>
<div
v-for="vault in lastVaults"
:key="vault.path"
class="flex items-center justify-between group h-12 overflow-x-scroll"
class="flex items-center justify-between group overflow-x-scroll"
>
<button
class="link link-accent flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full py-2 px-4"
@click=";((passwordPromptOpen = true), (vaultPath = vault.path))"
<UButton
variant="ghost"
color="neutral"
class="flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full px-3"
@click="
() => {
passwordPromptOpen = true
vaultPath = vault.path
}
"
>
<span class="block md:hidden">
{{ vault.name }}
@ -49,15 +57,17 @@
<span class="hidden md:block">
{{ vault.path }}
</span>
</button>
<button
class="absolute right-2 btn btn-square btn-error btn-xs hidden group-hover:flex min-w-6"
</UButton>
<UButton
color="error"
square
class="absolute right-2 hidden group-hover:flex min-w-6"
>
<Icon
name="mdi:trash-can-outline"
@click="removeVaultAsync(vault.path)"
/>
</button>
</UButton>
</div>
</div>
</div>
@ -65,9 +75,12 @@
<div class="flex flex-col items-center gap-2">
<h4>{{ t('sponsors') }}</h4>
<div>
<button @click="openUrl('https://itemis.com')">
<UButton
variant="link"
@click="openUrl('https://itemis.com')"
>
<UiLogoItemis class="text-[#00457C]" />
</button>
</UButton>
</div>
</div>
</div>
@ -75,9 +88,8 @@
</template>
<script setup lang="ts">
import { UiLogoHaexhub } from '#components'
import { openUrl } from '@tauri-apps/plugin-opener'
import type { Locale } from 'vue-i18n'
definePageMeta({
name: 'vaultOpen',
})
@ -91,19 +103,20 @@ const { syncLastVaultsAsync, removeVaultAsync } = useLastVaultStore()
const { lastVaults } = storeToRefs(useLastVaultStore())
await syncLastVaultsAsync()
const onSelectLocale = async (locale: Locale) => {
await setLocale(locale)
}
</script>
<i18n lang="json">
{
"de": {
"welcome": "Viel Spass mit",
"lastUsed": "Zuletzt verwendete Vaults",
"sponsors": "Supported by"
},
"en": {
"welcome": "Have fun with",
"lastUsed": "Last used Vaults",
"sponsors": "Supported by"
}
}
<i18n lang="yaml">
de:
welcome: 'Viel Spass mit'
lastUsed: 'Zuletzt verwendete Vaults'
sponsors: 'Supported by'
en:
welcome: 'Have fun with'
lastUsed: 'Last used Vaults'
sponsors: 'Supported by'
</i18n>

View File

@ -1,10 +1,9 @@
<template>
<UiSidebarTest />
<div>test</div>
</template>
<script setup lang="ts">
definePageMeta({
name: 'test',
});
})
</script>

View File

@ -13,18 +13,20 @@
confirm-icon="mdi:content-save-outline"
v-model:open="showNewDeviceDialog"
>
<div class="flex flex-col gap-4">
<p>{{ t('newDevice.intro') }}</p>
<p>
{{ t('newDevice.setName') }}
</p>
{{ deviceId }}
<UiInput
v-model="newDeviceName"
:label="t('newDevice.label')"
:rules="vaultDeviceNameSchema"
/>
</div>
<template #body>
<div class="flex flex-col gap-4">
<p>{{ t('newDevice.intro') }}</p>
<p>
{{ t('newDevice.setName') }}
</p>
{{ deviceId }}
<UiInput
v-model="newDeviceName"
:label="t('newDevice.label')"
:rules="vaultDeviceNameSchema"
/>
</div>
</template>
</UiDialogConfirm>
</div>
</div>
@ -61,7 +63,7 @@ onMounted(async () => {
}
})
const { add } = useSnackbar()
const { add } = useToast()
const onSetDeviceNameAsync = async () => {
try {
const check = vaultDeviceNameSchema.safeParse(newDeviceName.value)
@ -72,9 +74,9 @@ const onSetDeviceNameAsync = async () => {
await addDeviceNameAsync({ name: newDeviceName.value })
showNewDeviceDialog.value = false
add({ type: 'success', text: t('newDevice.success') })
add({ color: 'success', description: t('newDevice.success') })
} catch (error) {
add({ type: 'error', text: t('newDevice.error') })
add({ color: 'error', description: t('newDevice.error') })
}
}
</script>

View File

@ -1,34 +0,0 @@
<template>
<div class="w-full h-full overflow-scroll">
<div>
{{ iframeIndex }}
</div>
<iframe
v-if="iframeIndex"
ref="iFrameRef"
class="w-full h-full"
:src="iframeIndex"
sandbox="allow-scripts allow-same-origin"
allow="autoplay; speaker-selection; encrypted-media;"
/>
</div>
</template>
<script setup lang="ts">
definePageMeta({
name: 'haexExtension',
})
const { extensionEntry: iframeSrc } = storeToRefs(useExtensionsStore())
const iframeIndex = computed(() =>
iframeSrc.value ? `${iframeSrc.value}/index.html` : '',
)
</script>
<i18n lang="yaml">
de:
loading: Erweiterung wird geladen
en:
loading: Extension is loading
</i18n>

View File

@ -1,251 +0,0 @@
<template>
<div class="flex flex-col p-4 relative h-full">
<div
v-if="extensionStore.availableExtensions.length"
class="flex"
>
<UiButton
class="fixed top-20 right-4 btn-square btn-primary"
@click="prepareInstallExtensionAsyn"
>
<Icon
name="mdi:plus"
size="1.5em"
/>
</UiButton>
<HaexExtensionCard
v-for="_extension in extensionStore.availableExtensions"
v-bind="_extension"
:key="_extension.id"
@remove="onShowRemoveDialog(_extension)"
/>
</div>
<div
v-else
class="h-full w-full"
>
<Icon
name="my-icon:extensions-overview"
class="size-full md:size-2/3 md:translate-x-1/5 md:translate-y-1/3"
/>
<div class="fixed top-30 right-10">
<UiTooltip :tooltip="t('extension.add')">
<UiButton
class="btn-square btn-primary btn-xl btn-gradient rotate-45"
@click="prepareInstallExtensionAsyn"
>
<Icon
name="mdi:plus"
size="1.5em"
class="rotate-45"
/>
</UiButton>
</UiTooltip>
</div>
</div>
<HaexExtensionDialogReinstall
v-model:open="openOverwriteDialog"
:manifest="extension.manifest"
@confirm="addExtensionAsync"
/>
<HaexExtensionDialogInstall
v-model:open="showConfirmation"
:manifest="extension.manifest"
@confirm="addExtensionAsync"
/>
<HaexExtensionDialogRemove
v-model:open="showRemoveDialog"
:extension="extensionToBeRemoved"
@confirm="removeExtensionAsync"
/>
</div>
</template>
<script setup lang="ts">
import { join } from '@tauri-apps/api/path'
import { open } from '@tauri-apps/plugin-dialog'
import { readTextFile } from '@tauri-apps/plugin-fs'
import type {
IHaexHubExtension,
IHaexHubExtensionManifest,
} from '~/types/haexhub'
definePageMeta({
name: 'extensionOverview',
})
const { t } = useI18n()
const extensionStore = useExtensionsStore()
const showConfirmation = ref(false)
const openOverwriteDialog = ref(false)
const extension = reactive<{
manifest: IHaexHubExtensionManifest | null | undefined
path: string | null
}>({
manifest: null,
path: '',
})
const loadExtensionManifestAsync = async () => {
try {
extension.path = await open({ directory: true, recursive: true })
if (!extension.path) return
const manifestFile = JSON.parse(
await readTextFile(await join(extension.path, 'manifest.json')),
)
if (!extensionStore.checkManifest(manifestFile))
throw new Error(`Manifest fehlerhaft ${JSON.stringify(manifestFile)}`)
return manifestFile
} catch (error) {
console.error('Fehler loadExtensionManifestAsync:', error)
add({ type: 'error', text: JSON.stringify(error) })
await addNotificationAsync({ text: JSON.stringify(error), type: 'error' })
}
}
const { add } = useSnackbar()
const { addNotificationAsync } = useNotificationStore()
const prepareInstallExtensionAsyn = async () => {
try {
const manifest = await loadExtensionManifestAsync()
if (!manifest) throw new Error('No valid Manifest found')
extension.manifest = manifest
const isAlreadyInstalled = await extensionStore.isExtensionInstalledAsync({
id: manifest.id,
version: manifest.version,
})
if (isAlreadyInstalled) {
openOverwriteDialog.value = true
} else {
await addExtensionAsync()
}
} catch (error) {
add({ type: 'error', text: JSON.stringify(error) })
await addNotificationAsync({ text: JSON.stringify(error), type: 'error' })
}
}
const addExtensionAsync = async () => {
try {
await extensionStore.installAsync(extension.path)
await extensionStore.loadExtensionsAsync()
add({
type: 'success',
title: t('extension.success.title', {
extension: extension.manifest?.name,
}),
text: t('extension.success.text'),
})
await addNotificationAsync({
text: t('extension.success.text'),
type: 'success',
title: t('extension.success.title', {
extension: extension.manifest?.name,
}),
})
} catch (error) {
console.error('Fehler addExtensionAsync:', error)
add({ type: 'error', text: JSON.stringify(error) })
await addNotificationAsync({ text: JSON.stringify(error), type: 'error' })
}
}
const showRemoveDialog = ref(false)
const extensionToBeRemoved = ref<IHaexHubExtension>()
const onShowRemoveDialog = (extension: IHaexHubExtension) => {
extensionToBeRemoved.value = extension
showRemoveDialog.value = true
}
const removeExtensionAsync = async () => {
if (!extensionToBeRemoved.value?.id || !extensionToBeRemoved.value?.version) {
add({ type: 'error', text: 'Erweiterung kann nicht gelöscht werden' })
return
}
try {
await extensionStore.removeExtensionAsync(
extensionToBeRemoved.value.id,
extensionToBeRemoved.value.version,
)
await extensionStore.loadExtensionsAsync()
add({
type: 'success',
title: t('extension.remove.success.title', {
extensionName: extensionToBeRemoved.value.name,
}),
text: t('extension.remove.success.text', {
extensionName: extensionToBeRemoved.value.name,
}),
})
await addNotificationAsync({
text: t('extension.remove.success.text', {
extensionName: extensionToBeRemoved.value.name,
}),
type: 'success',
title: t('extension.remove.success.title', {
extensionName: extensionToBeRemoved.value.name,
}),
})
} catch (error) {
add({
type: 'error',
title: t('extension.remove.error.title'),
text: t('extension.remove.error.text', { error: JSON.stringify(error) }),
})
await addNotificationAsync({
type: 'error',
title: t('extension.remove.error.title'),
text: t('extension.remove.error.text', { error: JSON.stringify(error) }),
})
}
}
</script>
<i18n lang="yaml">
de:
title: 'Erweiterung installieren'
extension:
remove:
success:
text: 'Erweiterung {extensionName} wurde erfolgreich entfernt'
title: '{extensionName} entfernt'
error:
text: "Erweiterung {extensionName} konnte nicht entfernt werden. \n {error}"
title: 'Fehler beim Entfernen von {extensionName}'
add: 'Erweiterung hinzufügen'
success:
title: '{extension} hinzugefügt'
text: 'Die Erweiterung wurde erfolgreich hinzugefügt'
en:
title: 'Install extension'
extension:
remove:
success:
text: 'Extension {extensionName} was removed'
title: '{extensionName} removed'
error:
text: "Extension {extensionName} couldn't be removed. \n {error}"
title: 'Exception during uninstall {extensionName}'
add: 'Add Extension'
success:
title: '{extension} added'
text: 'Extensions was added successfully'
</i18n>

View File

@ -1,12 +1,12 @@
<template>
<div class="h-full text-base-content flex bg-base-200">
<HaexExtensionCard
v-for="extension in extensionStore.availableExtensions"
v-bind="extension"
:key="extension.id"
/>
<UiButton @click="requesty()">Storage Request</UiButton>
res: {{ res }}
<div class="text-base-content flex flex-col">
<div class="h-screen bg-amber-300">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</div>
<div class="h-screen bg-teal-300">
abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
availableThemes:{{ uiStore.availableThemes }}
</div>
</div>
</template>
@ -15,17 +15,5 @@ definePageMeta({
name: 'vaultOverview',
})
const storage = useAndroidStorage()
const extensionStore = useExtensionsStore()
const res = ref()
const requesty = async () => {
try {
res.value = await storage.requestStoragePermission()
res.value += ' wat the fuk'
} catch (error) {
res.value = error
}
}
const uiStore = useUiStore()
</script>

View File

@ -1,148 +0,0 @@
<template>
<div class="">
<div class="p-6">
<UiButton
class="btn-error"
@click="onDeleteNotificationsAsync"
>
{{ t('delete') }}
</UiButton>
</div>
<table class="table">
<thead>
<tr>
<th>
<input
v-model="selectAll"
type="checkbox"
class="checkbox checkbox-primary checkbox-sm"
aria-label="notification"
/>
</th>
<th>{{ t('title') }}</th>
<th>{{ t('text') }}</th>
<th>{{ t('date') }}</th>
<th>{{ t('type') }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="notification in notifications"
:key="notification.id"
>
<td>
<label>
<input
v-model="selectedNotificationIds"
:name="notification.id"
:value="notification.id"
aria-label="notification"
class="checkbox checkbox-primary checkbox-sm"
type="checkbox"
/>
</label>
</td>
<td>{{ notification.title }}</td>
<td>{{ notification.text }}</td>
<td>
{{
notification.date
? new Date(notification.date).toLocaleDateString(locale, {
dateStyle: 'short',
})
: ''
}}
</td>
<td>
<span
class="badge badge-soft text-xs"
:class="badgeClass[notification.type]"
>
{{ t(`types.${notification.type}`) }}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
import type { SelectHaexNotifications } from '~~/src-tauri/database/schemas/vault'
definePageMeta({
name: 'notifications',
})
const { t, locale } = useI18n()
const notifications = ref<SelectHaexNotifications[]>([])
const { deleteNotificationsAsync, syncNotificationsAsync } =
useNotificationStore()
onMounted(async () => {
await syncNotificationsAsync()
})
const selectedNotificationIds = ref<string[]>([])
const selectAll = computed({
get() {
return (
notifications.value.length > 0 &&
notifications.value.length === selectedNotificationIds.value.length
)
},
set(value: boolean) {
selectedNotificationIds.value = value
? [...notifications.value.map((notification) => notification.id)]
: []
},
})
const { add } = useSnackbar()
const onDeleteNotificationsAsync = async () => {
try {
console.log('onDeleteNotificationsAsync', selectedNotificationIds.value)
await deleteNotificationsAsync(selectedNotificationIds.value)
} catch (error) {
console.error(error)
add({ type: 'error', text: JSON.stringify(error) })
}
}
const badgeClass: Record<SelectHaexNotifications['type'], string> = {
error: 'badge-error',
info: 'badge-info',
success: 'badge-success',
warning: 'badge-warning',
log: 'badge-accent',
}
</script>
<i18n lang="yaml">
de:
title: Titel
text: Text
type: Typ
date: Datum
delete: Benachrichtigungen löschen
types:
error: Fehler
info: Info
success: Erfolg
warning: Warnung
en:
title: Title
text: Text
type: Type
date: Date
delete: Delete Notifications
types:
error: Error
info: Info
success: Success
warning: Warning
</i18n>

View File

@ -1,5 +1,5 @@
<template>
<div class="h-full overflow-auto p-2 relative">
<div class="flex-1 p-2 relative h-full">
<NuxtPage />
</div>
</template>

View File

@ -1,6 +1,5 @@
<template>
<div>
{{ currentGroupId }}
<HaexPassGroup
v-model="group"
mode="create"
@ -9,20 +8,19 @@
/>
<HaexPassMenuBottom
@close="onClose"
@save="createAsync"
show-close-button
show-save-button
:has-changes
>
</HaexPassMenuBottom>
@close="onClose"
@save="createAsync"
/>
<HaexPassDialogUnsavedChanges
v-model:ignore-changes="ignoreChanges"
v-model:open="showUnsavedChangesDialog"
:has-changes
@abort="showUnsavedChangesDialog = false"
@confirm="onConfirmIgnoreChanges"
v-model:ignore-changes="ignoreChanges"
v-model:open="showUnsavedChangesDialog"
/>
</div>
</template>
@ -45,6 +43,7 @@ const group = ref<SelectHaexPasswordsGroups>({
parentId: currentGroupId.value || null,
createdAt: null,
updateAt: null,
haex_tombstone: null,
})
const errors = ref({

View File

@ -1,43 +1,41 @@
<template>
<div>
<HaexPassGroup
:read_only
v-model="group"
:read-only
mode="edit"
@close="onClose"
@submit="onSaveAsync"
mode="edit"
v-model="group"
/>
<HaexPassMenuBottom
:show-edit-button="read_only && !hasChanges"
:show-readonly-button="!read_only && !hasChanges"
:show-edit-button="readOnly && !hasChanges"
:show-readonly-button="!readOnly && !hasChanges"
:show-save-button="hasChanges"
:has-changes
@close="onClose()"
@delete="showConfirmDeleteDialog = true"
@edit="read_only = false"
@readonly="read_only = true"
@save="onSaveAsync"
show-close-button
show-delete-button
>
</HaexPassMenuBottom>
@close="onClose()"
@delete="showConfirmDeleteDialog = true"
@edit="readOnly = false"
@readonly="readOnly = true"
@save="onSaveAsync"
/>
<HaexPassDialogDeleteItem
v-model:open="showConfirmDeleteDialog"
@abort="showConfirmDeleteDialog = false"
@confirm="onDeleteAsync"
:item-name="group.name"
:final="inTrashGroup"
>
</HaexPassDialogDeleteItem>
@abort="showConfirmDeleteDialog = false"
@confirm="onDeleteAsync"
/>
<HaexPassDialogUnsavedChanges
:has-changes="hasChanges"
v-model:ignore-changes="ignoreChanges"
v-model:open="showUnsavedChangesDialog"
:has-changes="hasChanges"
@abort="showUnsavedChangesDialog = false"
@confirm="onConfirmIgnoreChanges"
v-model:open="showUnsavedChangesDialog"
/>
</div>
</template>
@ -59,47 +57,47 @@ const group = ref<SelectHaexPasswordsGroups>({
description: null,
icon: null,
id: '',
name: null,
name: '',
order: null,
parentId: null,
updateAt: null,
haex_tombstone: null,
})
const original = ref<string>('')
const ignoreChanges = ref(false)
const { readGroupAsync } = usePasswordGroupStore()
watch(
currentGroupId,
async () => {
if (!currentGroupId.value) return
ignoreChanges.value = false
try {
const foundGroup = await readGroupAsync(currentGroupId.value)
if (foundGroup) {
original.value = JSON.parse(JSON.stringify(foundGroup))
group.value = foundGroup
}
} catch (error) {
console.error(error)
watchImmediate(currentGroupId, async () => {
if (!currentGroupId.value) return
ignoreChanges.value = false
try {
const foundGroup = await readGroupAsync(currentGroupId.value)
if (foundGroup) {
original.value = JSON.parse(JSON.stringify(foundGroup))
group.value = foundGroup
}
},
{ immediate: true },
)
} catch (error) {
console.error(error)
}
})
const read_only = ref(false)
const { areItemsEqual } = usePasswordGroup()
const hasChanges = computed(() => !!!areItemsEqual(group.value, original.value))
const hasChanges = computed(() => {
const current = JSON.stringify(group.value)
const origin = JSON.stringify(original.value)
console.log('hasChanges', current, origin)
return !(current === origin)
})
const readOnly = ref(false)
const onClose = () => {
if (showConfirmDeleteDialog.value || showUnsavedChangesDialog.value) return
read_only.value = true
readOnly.value = true
useRouter().back()
}
const { add } = useSnackbar()
const { add } = useToast()
const { updateAsync, syncGroupItemsAsync, deleteGroupAsync } =
usePasswordGroupStore()
@ -111,21 +109,21 @@ const onSaveAsync = async () => {
ignoreChanges.value = true
await updateAsync(group.value)
await syncGroupItemsAsync()
add({ type: 'success', text: t('change.success') })
add({ color: 'success', description: t('change.success') })
onClose()
} catch (error) {
add({ type: 'error', text: t('change.error') })
add({ color: 'error', description: t('change.error') })
console.log(error)
}
}
const showConfirmDeleteDialog = ref(false)
const showUnsavedChangesDialog = ref(false)
const onConfirmIgnoreChanges = () => {
showUnsavedChangesDialog.value = false
onClose()
}
const showConfirmDeleteDialog = ref(false)
const onDeleteAsync = async () => {
try {
const parentId = group.value.parentId

View File

@ -1,67 +1,72 @@
<template>
<div class="h-full">
<div class="min-h-full flex flex-col">
<div class="flex-1 h-full">
<div class="h-full flex flex-col">
<HaexPassGroupBreadcrumbs
:items="breadCrumbs"
class="px-2 sticky -top-2 z-10 bg-base-200"
v-show="breadCrumbs.length"
:items="breadCrumbs"
class="px-2 sticky -top-2 z-10"
/>
<div class="flex-1 py-1 flex">
<HaexPassMobileMenu
:menu-items="groupItems"
ref="listRef"
v-model:selected-items="selectedItems"
:menu-items="groupItems"
/>
</div>
<div
class="fixed bottom-4 flex justify-between transition-all pointer-events-none right-0 sm:items-center items-end px-8"
:class="[isVisible ? 'left-15 ' : 'left-0']"
class="fixed bottom-4 flex justify-between transition-all w-full sm:items-center items-end px-8"
>
<div class="w-full"></div>
<UiButtonAction
v-if="!inTrashGroup"
:menu
/>
<div class="w-full" />
<UDropdownMenu
v-model:open="open"
:items="menu"
>
<UButton
icon="mdi:plus"
:ui="{
base: 'rotate-45 ',
leadingIcon: [open ? 'rotate-0' : 'rotate-45', 'transition-all'],
}"
size="xl"
/>
</UDropdownMenu>
<div
class="flex flex-col sm:flex-row gap-4 w-full justify-end items-end"
>
<UiButton
v-show="selectedItems.size === 1"
class="btn-square btn-accent"
@click="onEditAsync"
color="secondary"
icon="mdi:pencil"
:tooltip="t('edit')"
>
<Icon name="mdi:pencil" />
</UiButton>
@click="onEditAsync"
/>
<UiButton
class="btn-square btn-accent"
v-show="selectedItems.size"
@click="onCut"
color="secondary"
:tooltip="t('cut')"
>
<Icon name="mdi:scissors" />
</UiButton>
icon="mdi:scissors"
@click="onCut"
/>
<UiButton
class="btn-square btn-accent"
v-show="selectedGroupItems?.length"
@click="onPasteAsync"
color="secondary"
icon="proicons:clipboard-paste"
:tooltip="t('paste')"
>
<Icon name="proicons:clipboard-paste" />
</UiButton>
@click="onPasteAsync"
/>
<UiButton
v-show="selectedItems.size"
class="btn-square btn-accent"
@click="onDeleteAsync"
color="secondary"
icon="mdi:trash-outline"
:tooltip="t('delete')"
>
<Icon name="mdi:trash" />
</UiButton>
@click="onDeleteAsync"
/>
</div>
</div>
</div>
@ -70,15 +75,18 @@
<script setup lang="ts">
import type { IPasswordMenuItem } from '~/components/haex/pass/mobile/menu/types'
import { useMagicKeys } from '@vueuse/core'
//import { useMagicKeys, whenever } from '@vueuse/core'
import Fuse from 'fuse.js'
definePageMeta({
name: 'passwordGroupItems',
})
const open = ref(false)
const { t } = useI18n()
const { add } = useSnackbar()
const { add } = useToast()
const selectedItems = ref<Set<IPasswordMenuItem>>(new Set())
const { menu } = storeToRefs(usePasswordsActionMenuStore())
@ -88,7 +96,9 @@ const { syncGroupItemsAsync } = usePasswordGroupStore()
onMounted(async () => {
try {
await Promise.allSettled([syncItemsAsync(), syncGroupItemsAsync()])
} catch (error) {}
} catch (error) {
console.error(error)
}
})
const {
@ -146,8 +156,6 @@ const groupItems = computed<IPasswordMenuItem[]>(() => {
return menuItems
})
const { isVisible } = storeToRefs(useSidebarStore())
const onEditAsync = async () => {
const item = selectedItems.value.values().next().value
@ -200,7 +208,7 @@ const onPasteAsync = async () => {
} catch (error) {
console.error(error)
selectedGroupItems.value = []
add({ type: 'error', text: t('error.paste') })
add({ color: 'error', description: t('error.paste') })
}
}
onKeyStroke('v', async (event) => {
@ -209,10 +217,12 @@ onKeyStroke('v', async (event) => {
}
})
const { escape } = useMagicKeys()
watch(escape, () => {
/* const { escape } = useMagicKeys()
whenever(escape, () => {
selectedItems.value.clear()
})
}) */
onKeyStroke('escape', () => selectedItems.value.clear())
onKeyStroke('a', (event) => {
if (event.ctrlKey) {
@ -237,10 +247,11 @@ const onDeleteAsync = async () => {
selectedItems.value.clear()
await syncGroupItemsAsync()
}
const keys = useMagicKeys()
watch(keys.delete, async () => {
/* const keys = useMagicKeys()
whenever(keys, async () => {
await onDeleteAsync()
})
}) */
onKeyStroke('delete', () => onDeleteAsync())
const listRef = useTemplateRef<HTMLElement>('listRef')
onClickOutside(listRef, () => setTimeout(() => selectedItems.value.clear(), 50))
@ -252,6 +263,7 @@ de:
paste: Einfügen
delete: Löschen
edit: Bearbeiten
wtf: 'wtf'
en:
cut: Cut
paste: Paste

View File

@ -1,56 +1,53 @@
<template>
<div>
<div class="flex flex-col">
<!-- <div class="flex flex-col">
<p>
{{ item.originalDetails }}
</p>
{{ item.details }}
</div>
</div> -->
<HaexPassItem
:history="item.history"
:read_only
@close="onClose()"
@submit="onUpdateAsync"
v-model:details="item.details"
v-model:key-values-add="item.keyValuesAdd"
v-model:key-values-delete="item.keyValuesDelete"
v-model:key-values="item.keyValues"
:history="item.history"
:read-only
@close="onClose()"
@submit="onUpdateAsync"
/>
<HaexPassMenuBottom
:has-changes
:show-edit-button="read_only && !hasChanges"
:show-readonly-button="!read_only && !hasChanges"
:show-save-button="!read_only && hasChanges"
@close="onClose"
@delete="showConfirmDeleteDialog = true"
@edit="read_only = false"
@readonly="read_only = true"
@save="onUpdateAsync"
:show-edit-button="readOnly && !hasChanges"
:show-readonly-button="!readOnly && !hasChanges"
:show-save-button="!readOnly && hasChanges"
show-close-button
show-delete-button
>
</HaexPassMenuBottom>
@close="onClose"
@delete="showConfirmDeleteDialog = true"
@edit="readOnly = false"
@readonly="readOnly = true"
@save="onUpdateAsync"
/>
<HaexPassDialogDeleteItem
v-model:open="showConfirmDeleteDialog"
@abort="showConfirmDeleteDialog = false"
@confirm="deleteItemAsync"
>
</HaexPassDialogDeleteItem>
/>
<HaexPassDialogUnsavedChanges
:has-changes="hasChanges"
v-model:ignore-changes="ignoreChanges"
v-model:open="showUnsavedChangesDialog"
:has-changes="hasChanges"
@abort="showUnsavedChangesDialog = false"
@confirm="onConfirmIgnoreChanges"
v-model:open="showUnsavedChangesDialog"
/>
</div>
</template>
<script setup lang="ts">
import { usePasswordGroup } from '~/components/haex/pass/group/composables'
import type {
SelectHaexPasswordsItemDetails,
SelectHaexPasswordsItemHistory,
@ -61,13 +58,13 @@ definePageMeta({
name: 'passwordItemEdit',
})
defineProps({
/* defineProps({
icon: String,
title: String,
withCopyButton: Boolean,
})
}) */
const read_only = ref(true)
const readOnly = ref(true)
const showConfirmDeleteDialog = ref(false)
const { t } = useI18n()
@ -91,6 +88,7 @@ const item = reactive<{
updateAt: null,
url: null,
username: null,
haex_tombstone: null,
},
keyValues: [],
history: [],
@ -107,6 +105,7 @@ const item = reactive<{
updateAt: null,
url: null,
username: null,
haex_tombstone: null,
},
originalKeyValues: null,
})
@ -132,7 +131,7 @@ watch(
{ immediate: true },
)
const { add } = useSnackbar()
const { add } = useToast()
const { deleteAsync, updateAsync } = usePasswordItemStore()
const { syncGroupItemsAsync } = usePasswordGroupStore()
const { currentGroupId, inTrashGroup } = storeToRefs(usePasswordGroupStore())
@ -147,12 +146,13 @@ const onUpdateAsync = async () => {
keyValuesAdd: item.keyValuesAdd,
keyValuesDelete: item.keyValuesDelete,
})
if (newId) add({ type: 'success', text: t('success.update') })
if (newId) add({ color: 'success', description: t('success.update') })
syncGroupItemsAsync()
ignoreChanges.value = true
onClose()
} catch (error) {
add({ type: 'error', text: t('error.update') })
console.log(error)
add({ color: 'error', description: t('error.update') })
}
}
@ -162,7 +162,7 @@ const onClose = () => {
if (hasChanges.value && !ignoreChanges.value)
return (showUnsavedChangesDialog.value = true)
read_only.value = true
readOnly.value = true
useRouter().back()
}
@ -170,27 +170,26 @@ const deleteItemAsync = async () => {
try {
await deleteAsync(item.details.id, inTrashGroup.value)
showConfirmDeleteDialog.value = false
add({ type: 'success', text: t('success.delete') })
add({ color: 'success', description: t('success.delete') })
await syncGroupItemsAsync()
onClose()
} catch (errro) {
console.log(errro)
add({
type: 'error',
text: t('error.delete'),
color: 'error',
description: t('error.delete'),
})
}
}
const { areItemsEqual } = usePasswordGroup()
const hasChanges = computed(
() =>
!!(
!areItemsEqual(item.originalDetails, item.details) ||
!areItemsEqual(item.originalKeyValues, item.keyValues) ||
item.keyValuesAdd.length ||
item.keyValuesDelete.length
),
)
const hasChanges = computed(() => {
return !(
JSON.stringify(item.originalDetails) === JSON.stringify(item.details) &&
JSON.stringify(item.originalKeyValues) === JSON.stringify(item.keyValues) &&
!item.keyValuesAdd.length &&
!item.keyValuesDelete.length
)
})
const showUnsavedChangesDialog = ref(false)
const onConfirmIgnoreChanges = () => {

View File

@ -1,30 +1,28 @@
<template>
<div>
{{ currentGroup?.id }} {{ currentGroupId }}
<HaexPassItem
v-model:details="item.details"
v-model:key-values-add="item.keyValuesAdd"
:default-icon="currentGroup?.icon"
:history="item.history"
@close="onClose"
@submit="onCreateAsync"
v-model:details="item.details"
v-model:key-values-add="item.keyValuesAdd"
/>
<HaexPassMenuBottom
:has-changes
:show-close-button="true"
:show-save-button="true"
@close="onClose"
@save="onCreateAsync"
show-close-button
show-save-button
>
</HaexPassMenuBottom>
/>
<HaexPassDialogUnsavedChanges
:has-changes="hasChanges"
v-model:ignore-changes="ignoreChanges"
v-model:open="showUnsavedChangesDialog"
:has-changes="hasChanges"
@abort="showUnsavedChangesDialog = false"
@confirm="onConfirmIgnoreChanges"
v-model:open="showUnsavedChangesDialog"
/>
</div>
</template>
@ -40,11 +38,11 @@ definePageMeta({
name: 'passwordItemCreate',
})
defineProps({
icon: String,
title: String,
withCopyButton: Boolean,
})
defineProps<{
icon: string
title: string
withCopyButton: boolean
}>()
const { t } = useI18n()
@ -56,9 +54,10 @@ const item = reactive<{
originalKeyValuesAdd: []
}>({
details: {
id: '',
createdAt: null,
haex_tombstone: null,
icon: null,
id: '',
note: null,
password: null,
tags: null,
@ -70,9 +69,10 @@ const item = reactive<{
history: [],
keyValuesAdd: [],
originalDetails: {
id: '',
createdAt: null,
haex_tombstone: null,
icon: null,
id: '',
note: null,
password: null,
tags: null,
@ -84,8 +84,8 @@ const item = reactive<{
originalKeyValuesAdd: [],
})
const { add } = useSnackbar()
const { currentGroup, currentGroupId } = storeToRefs(usePasswordGroupStore())
const { add } = useToast()
const { currentGroup } = storeToRefs(usePasswordGroupStore())
const { syncGroupItemsAsync } = usePasswordGroupStore()
const { addAsync } = usePasswordItemStore()
@ -99,12 +99,13 @@ const onCreateAsync = async () => {
if (newId) {
ignoreChanges.value = true
add({ type: 'success', text: t('success') })
add({ color: 'success', description: t('success') })
await syncGroupItemsAsync()
onClose()
}
} catch (error) {
add({ type: 'error', text: t('error') })
console.log(error)
add({ color: 'error', description: t('error') })
}
}

View File

@ -13,28 +13,16 @@
<UiInput
v-model="currentVaultName"
:placeholder="t('vaultName.label')"
>
<template #append>
<UiTooltip :tooltip="t('save')">
<UiButton
class="btn-primary"
@click="onSetVaultNameAsync"
>
<Icon name="mdi:content-save-outline" />
</UiButton>
</UiTooltip>
</template>
</UiInput>
@change="onSetVaultNameAsync"
/>
</div>
<div class="p-2">{{ t('notifications.label') }}</div>
<div class="flex items-center">
<div>
<UiButton
class="btn-primary"
:label="t('notifications.requestPermission')"
@click="requestNotificationPermissionAsync"
>
{{ t('notifications.requestPermission') }}
</UiButton>
/>
</div>
<div class="p-2">{{ t('deviceName.label') }}</div>
@ -42,18 +30,8 @@
<UiInput
v-model="deviceName"
:placeholder="t('deviceName.label')"
>
<template #append>
<UiButton
class="btn-primary"
@click="onUpdateDeviceNameAsync"
:tooltip="t('save')"
:rules="vaultDeviceNameSchema"
>
<Icon name="mdi:content-save-outline" />
</UiButton>
</template>
</UiInput>
@change="onUpdateDeviceNameAsync"
/>
</div>
</div>
</template>
@ -76,21 +54,23 @@ const onSelectLocaleAsync = async (locale: Locale) => {
await setLocale(locale)
}
const { currentTheme } = storeToRefs(useUiStore())
const { currentThemeName } = storeToRefs(useUiStore())
const onSelectThemeAsync = async (theme: ITheme) => {
await updateThemeAsync(theme.value)
currentTheme.value = theme
const onSelectThemeAsync = async (theme: string) => {
currentThemeName.value = theme
console.log('onSelectThemeAsync', currentThemeName.value)
await updateThemeAsync(theme)
}
const { add } = useSnackbar()
const { add } = useToast()
const onSetVaultNameAsync = async () => {
try {
await updateVaultNameAsync(currentVaultName.value)
add({ text: t('vaultName.update.success'), type: 'success' })
add({ description: t('vaultName.update.success'), color: 'success' })
} catch (error) {
console.error(error)
add({ text: t('vaultName.update.error'), type: 'error' })
add({ description: t('vaultName.update.error'), color: 'error' })
}
}
@ -102,14 +82,16 @@ const { updateDeviceNameAsync, readDeviceNameAsync } = useDeviceStore()
onMounted(async () => {
await readDeviceNameAsync()
})
const onUpdateDeviceNameAsync = async () => {
const check = vaultDeviceNameSchema.safeParse(deviceName.value)
if (!check.success) return
try {
await updateDeviceNameAsync({ name: deviceName.value })
add({ text: t('deviceName.update.success'), type: 'success' })
add({ description: t('deviceName.update.success'), color: 'success' })
} catch (error) {
add({ text: t('deviceName.update.error'), type: 'error' })
console.log(error)
add({ description: t('deviceName.update.error'), color: 'error' })
}
}
</script>