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,127 +1,101 @@
<template>
<UiDialogConfirm
v-model:open="open"
class="btn btn-primary btn-outline shadow-md btn-lg"
@click="open = true"
@abort="open = false"
@confirm="onCreateAsync"
:confirm-label="t('create')"
@confirm="onCreateAsync"
>
<template #trigger>
<Icon name="mdi:plus" />
{{ t('database.create') }}
</template>
<UiButton
:label="t('vault.create')"
:ui="{
base: 'px-3 py-2',
}"
icon="mdi:plus"
size="xl"
variant="outline"
block
/>
<template #title>
<div class="flex gap-x-2 items-center">
<Icon
name="mdi:safe"
class="text-primary"
/>
<p>
{{ t('title') }}
</p>
</div>
<i18n-t
keypath="title"
tag="p"
class="flex gap-x-2 flex-wrap"
>
<template #haexvault>
<UiTextGradient>HaexVault</UiTextGradient>
</template>
</i18n-t>
</template>
<form
class="flex flex-col gap-4"
@submit="onCreateAsync"
>
<UiInput
v-model="database.name"
:check-input="check"
:label="t('database.label')"
:placeholder="t('database.placeholder')"
:rules="vaultDatabaseSchema.name"
autofocus
prepend-icon="mdi:safe"
/>
<UiInputPassword
v-model="database.password"
:check-input="check"
:rules="vaultDatabaseSchema.password"
prepend-icon="mdi:key-outline"
/>
</form>
<!-- <template #buttons>
<UiButton
class="btn-error btn-outline w-full sm:w-auto"
@click="onClose"
<template #body>
<UForm
:state="vault"
class="flex flex-col gap-4 w-full h-full justify-center"
>
<Icon name="mdi:x" />
{{ t('abort') }}
</UiButton>
<UiInput
v-model="vault.name"
leading-icon="mdi:safe"
:label="t('vault.label')"
:placeholder="t('vault.placeholder')"
/>
<UiInputPassword
v-model="vault.password"
leading-icon="mdi:key-outline"
/>
<UiButton
class="btn-primary w-full sm:w-auto"
@click="onCreateAsync"
>
{{ t('create') }}
</UiButton>
</template> -->
<UButton
hidden
type="submit"
@click="onCreateAsync"
/>
</UForm>
</template>
</UiDialogConfirm>
</template>
<script setup lang="ts">
import { save } from '@tauri-apps/plugin-dialog'
import { onKeyStroke } from '@vueuse/core'
import { useVaultStore } from '~/stores/vault'
import { vaultDatabaseSchema } from './schema'
import { BaseDirectory, readFile, writeFile } from '@tauri-apps/plugin-fs'
import { resolveResource } from '@tauri-apps/api/path'
//import { convertFileSrc } from "@tauri-apps/api/tauri";
onKeyStroke('Enter', (e) => {
e.preventDefault()
onCreateAsync()
})
const check = ref(false)
const open = ref()
import { vaultSchema } from './schema'
//import type { FormSubmitEvent } from '@nuxt/ui'
const { t } = useI18n()
const database = reactive<{
//type Schema = z.output<typeof vaultSchema>
const vault = reactive<{
name: string
password: string
path: string | null
type: 'password' | 'text'
}>({
name: '',
name: 'HaexVault',
password: '',
path: '',
type: 'password',
})
const initDatabase = () => {
database.name = t('database.name')
database.password = ''
database.path = ''
database.type = 'password'
const initVault = () => {
vault.name = 'HaexVault'
vault.password = ''
vault.path = ''
vault.type = 'password'
}
initDatabase()
const { add } = useSnackbar()
const { createAsync } = useVaultStore()
const { add } = useToast()
const check = ref(false)
const open = ref()
const onCreateAsync = async () => {
check.value = true
const nameCheck = vaultDatabaseSchema.name.safeParse(database.name)
const passwordCheck = vaultDatabaseSchema.password.safeParse(
database.password,
)
const nameCheck = vaultSchema.name.safeParse(vault.name)
const passwordCheck = vaultSchema.password.safeParse(vault.password)
console.log(
'checks',
database.name,
nameCheck,
database.password,
passwordCheck,
)
console.log('checks', vault.name, nameCheck, vault.password, passwordCheck)
if (!nameCheck.success || !passwordCheck.success) return
open.value = false
@ -130,28 +104,27 @@ const onCreateAsync = async () => {
const template_vault = await readFile(template_vault_path)
database.path = await save({
defaultPath: database.name.endsWith('.db')
? database.name
: `${database.name}.db`,
vault.path = await save({
defaultPath: vault.name.endsWith('.db') ? vault.name : `${vault.name}.db`,
})
if (!database.path) return
if (!vault.path) return
await writeFile('temp_vault.db', template_vault, {
baseDir: BaseDirectory.AppLocalData,
})
console.log('data', database)
console.log('data', vault)
if (database.path && database.password) {
if (vault.path && vault.password) {
const vaultId = await createAsync({
path: database.path,
password: database.password,
path: vault.path,
password: vault.password,
})
console.log('vaultId', vaultId)
if (vaultId) {
initVault()
await navigateTo(
useLocaleRoute()({ name: 'vaultOverview', params: { vaultId } }),
)
@ -159,41 +132,27 @@ const onCreateAsync = async () => {
}
} catch (error) {
console.error(error)
add({ type: 'error', text: `${error}` })
add({ color: 'error', description: `${error}` })
}
}
const onClose = () => {
open.value = false
initDatabase()
}
</script>
<i18n lang="json">
{
"de": {
"database": {
"label": "Vaultname",
"placeholder": "Vaultname",
"create": "Neue Vault anlegen",
"name": "HaexVault"
},
"title": "Neue Vault anlegen",
"create": "Erstellen",
"abort": "Abbrechen",
"description": "Haex Vault für deine geheimsten Geheimnisse"
},
"en": {
"database": {
"label": "Vaultname",
"placeholder": "Vaultname",
"create": "Create new Vault",
"name": "HaexVault"
},
"title": "Create New Vault",
"create": "Create",
"abort": "Abort",
"description": "Haex Vault for your most secret secrets"
}
}
<i18n lang="yaml">
de:
vault:
create: Neue Vault erstellen
label: Vaultname
placeholder: Vaultname
name: HaexVault
title: Neue {haexvault} erstellen
create: Erstellen
en:
vault:
create: Create new vault
label: Vaultname
placeholder: Vaultname
name: HaexVault
title: Create new {haexvault}
create: Create
</i18n>

View File

@ -1,67 +1,62 @@
<template>
<UiDialogConfirm
v-model:open="open"
class="btn btn-primary btn-outline shadow-md btn-lg"
:confirm-label="t('open')"
:abort-label="t('abort')"
@abort="onAbort"
:description="vault.path || path"
@confirm="onOpenDatabase"
@open="onLoadDatabase"
>
<UiButton
:label="t('vault.open')"
:ui="{
base: 'px-3 py-2',
}"
icon="mdi:folder-open-outline"
size="xl"
variant="outline"
block
@click.stop="onLoadDatabase"
/>
<template #title>
<i18n-t
keypath="title"
tag="p"
class="flex gap-x-2 flex-wrap"
class="flex gap-x-2 text-wrap"
>
<template #haexvault>
<UiTextGradient>HaexVault</UiTextGradient>
</template>
</i18n-t>
Path1: {{ test }}
<div class="text-sm">{{ props.path ?? database.path }}</div>
</template>
<template #trigger>
<Icon name="mdi:folder-open-outline" />
{{ t('database.open') }}
</template>
<template #body>
<UForm
:state="vault"
class="flex flex-col gap-4 w-full h-full justify-center"
>
<UiInputPassword
v-model="vault.password"
class="w-full"
autofocus
/>
<UiInputPassword
:check-input="check"
:rules="vaultDatabaseSchema.password"
@keyup.enter="onOpenDatabase"
autofocus
prepend-icon="mdi:key-outline"
v-model="database.password"
/>
<UButton
hidden
type="submit"
@click="onOpenDatabase"
/>
</UForm>
</template>
</UiDialogConfirm>
</template>
<script setup lang="ts">
import { open as openVault } from '@tauri-apps/plugin-dialog'
import { vaultDatabaseSchema } from './schema'
import {
BaseDirectory,
copyFile,
mkdir,
exists,
readFile,
writeFile,
stat,
} from '@tauri-apps/plugin-fs'
import { vaultSchema } from './schema'
const { t } = useI18n()
const open = defineModel('open', { type: Boolean })
const props = defineProps<{
path: string
}>()
const check = ref(false)
const database = reactive<{
const vault = reactive<{
name: string
password: string
path: string | null
@ -73,28 +68,13 @@ const database = reactive<{
type: 'password',
})
const initDatabase = () => {
database.name = ''
database.password = ''
database.path = ''
database.type = 'password'
}
const open = defineModel('open', { type: Boolean })
initDatabase()
const { add } = useSnackbar()
const handleError = (error: unknown) => {
open.value = false
console.error('handleError', error, typeof error)
add({ type: 'error', text: `${error}` })
}
const { openAsync } = useVaultStore()
const { add } = useToast()
const onLoadDatabase = async () => {
try {
database.path = await openVault({
vault.path = await openVault({
multiple: false,
directory: false,
filters: [
@ -105,50 +85,62 @@ const onLoadDatabase = async () => {
],
})
console.log('onLoadDatabase', database.path)
if (!database.path) return
console.log('onLoadDatabase', vault.path)
if (!vault.path) {
open.value = false
return
}
open.value = true
} catch (error) {
handleError(error)
open.value = false
console.error('handleError', error, typeof error)
add({ color: 'error', description: `${error}` })
}
}
const localePath = useLocalePath()
const { syncLocaleAsync, syncThemeAsync, syncVaultNameAsync } =
useVaultSettingsStore()
const props = defineProps<{
path: string
}>()
const check = ref(false)
const initVault = () => {
vault.name = ''
vault.password = ''
vault.path = ''
vault.type = 'password'
}
const onAbort = () => {
initVault()
open.value = false
}
const onOpenDatabase = async () => {
try {
const { openAsync } = useVaultStore()
const localePath = useLocalePath()
check.value = true
const path = database.path || props.path
const pathCheck = vaultDatabaseSchema.path.safeParse(path)
const passwordCheck = vaultDatabaseSchema.password.safeParse(
database.password,
)
const path = vault.path || props.path
const pathCheck = vaultSchema.path.safeParse(path)
const passwordCheck = vaultSchema.password.safeParse(vault.password)
/* if (!pathCheck.success || !passwordCheck.success || !path) {
add({
type: 'error',
text: `Params falsch. Path: ${pathCheck.error} | Password: ${passwordCheck.error}`,
})
return
} */
//const vaultName = await copyDatabaseInAppDirectoryAsync(path)
//if (!vaultName) return
if (pathCheck.error || passwordCheck.error) return
const vaultId = await openAsync({
path,
password: database.password,
password: vault.password,
})
if (!vaultId) {
add({
type: 'error',
text: 'Vault konnte nicht geöffnet werden. \n Vermutlich ist das Passwort falsch',
color: 'error',
description: t('error.open'),
})
return
}
@ -169,53 +161,31 @@ const onOpenDatabase = async () => {
syncVaultNameAsync(),
])
} catch (error) {
handleError(error)
open.value = false
console.error('handleError', error, typeof error)
add({ color: 'error', description: `${error}` })
}
}
const test = ref()
const copyDatabaseInAppDirectoryAsync = async (source: string) => {
const vaultName = getFileName(source)
const vaultsDirExists = await exists('vaults', {
baseDir: BaseDirectory.AppLocalData,
})
//convertFileSrc
if (!vaultsDirExists) {
await mkdir('vaults', {
baseDir: BaseDirectory.AppLocalData,
})
}
test.value = `source: ${source} - target: vaults/${vaultName}`
console.log('copyDatabaseInAppDirectoryAsync', `vaults/${vaultName}`)
const sourceFile = await readFile(source)
await writeFile(`vaults/HaexVault.db`, sourceFile, {
baseDir: BaseDirectory.AppLocalData,
})
/* await copyFile(source, `vaults/HaexVault.db`, {
toPathBaseDir: BaseDirectory.AppLocalData,
}) */
return vaultName
}
const onAbort = () => {
initDatabase()
open.value = false
}
</script>
<i18n lang="yaml">
de:
open: Öffnen
abort: Abbrechen
open: Entsperren
title: '{haexvault} entsperren'
database:
password: Passwort
vault:
open: Vault öffnen
description: Öffne eine vorhandene Vault
error:
open: Vault konnte nicht geöffnet werden. \n Vermutlich ist das Passwort falsch
en:
open: Open
abort: Abort
open: Unlock
title: Unlock {haexvault}
database:
password: Passwort
description: Open your existing vault
vault:
open: Open Vault
error:
open: Vault couldn't be opened. \n The password is probably wrong
</i18n>

View File

@ -1,7 +1,7 @@
import { z } from 'zod';
import { z } from 'zod'
export const vaultDatabaseSchema = {
export const vaultSchema = {
password: z.string().min(6).max(255),
name: z.string().min(1).max(255),
path: z.string().min(4).endsWith('.db'),
};
}

View File

@ -1,244 +0,0 @@
<template>
<VaultCard
@close="onClose"
@submit="onSubmit"
>
<template #header>
<div class="flex flex-wrap items-center justify-between w-full px-2 py-3">
<div class="w-full flex gap-2 justify-between items-center">
<div class="flex items-center gap-2">
<button
class="btn btn-square btn-primary btn-outline"
@click="onBack"
>
<Icon
name="mdi:chevron-left"
size="32"
/>
</button>
<button
class="btn btn-square btn-error btn-outline"
@click="onBack"
>
<Icon
name="mdi:trash-can-outline"
size="28"
/>
</button>
</div>
<slot name="buttons">
<div
v-if="read_only"
class="h-full"
>
<button
class="btn btn-square btn-primary btn-outline"
@click="read_only = false"
>
<Icon
name="mdi:pencil-outline"
size="24"
/>
</button>
</div>
<div
v-else
class="gap-2 h-full hidden md:flex"
>
<button
class="btn btn-square btn-error btn-outline"
@click="onClose"
>
<Icon name="mdi:close" />
<span class="hidden"> {{ t('abort') }} </span>
</button>
<button
class="btn btn-square btn-success btn-outline"
@click="onSubmit"
>
<Icon name="mdi:check" />
<span class="hidden"> {{ t('create') }} </span>
</button>
</div>
</slot>
</div>
<div
class="flex items-center w-full min-h-14 gap-2 py-1"
:style="{ color }"
>
<Icon
v-if="icon"
:name="icon"
size="28"
/>
<h5
v-show="read_only"
class="overflow-hidden whitespace-nowrap"
>
a{{ title }}
</h5>
</div>
</div>
</template>
<div class="h-full">
<slot />
<div
v-show="!read_only"
class="fixed bottom-2 left-0 w-full flex items-center justify-between px-4 md:hidden"
>
<div class="transition-all duration-500">
aa
<button
class="btn btn-square btn-error btn-outline"
@click="onClose"
>
<Icon name="mdi:close" />
<span class="hidden"> {{ t('abort') }} </span>
</button>
</div>
<div>
<button
class="btn btn-square btn-success"
@click="onSubmit"
>
<Icon name="mdi:check" />
<span class="hidden"> {{ t('create') }} </span>
</button>
</div>
<div />
</div>
<!-- <UiButtonAction
class=""
icon="mdi:content-save-outline"
><Icon name="mdi:content-save-outline" />
</UiButtonAction> -->
</div>
</VaultCard>
<VaultModalSaveChanges
v-model="showConfirmation"
@reject="onReject"
@submit="onSubmit"
/>
</template>
<script setup lang="ts">
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
const { t } = useI18n()
const read_only = defineModel<boolean>('read_only', { default: false })
const props = defineProps<{
color: string
hasChanges: boolean
icon: string
title: string
}>()
const emit = defineEmits<{
back: [void]
close: [void]
reject: [to?: RouteLocationNormalizedLoadedGeneric]
submit: [to?: RouteLocationNormalizedLoadedGeneric]
}>()
const showConfirmation = ref(false)
const to = ref<RouteLocationNormalizedLoadedGeneric>()
const isApprovedForLeave = ref(false)
const wantToGoBack = ref(false)
const onSubmit = () => {
showConfirmation.value = false
isApprovedForLeave.value = true
if (wantToGoBack.value) {
wantToGoBack.value = false
read_only.value = true
emit('submit')
} else {
emit('submit', to.value)
}
}
const onReject = () => {
showConfirmation.value = false
isApprovedForLeave.value = true
read_only.value = true
if (wantToGoBack.value) {
wantToGoBack.value = false
emit('back')
} else emit('reject', to.value)
}
const onBack = () => {
if (props.hasChanges) {
wantToGoBack.value = true
showConfirmation.value = true
} else {
emit('back')
}
}
const onClose = () => {
if (props.hasChanges) {
showConfirmation.value = true
} else {
emit('close') //read_only.value = true;
}
}
onBeforeRouteLeave((_to, _from, next) => {
//console.log('check before leave', _to, _from);
to.value = _to
if (isApprovedForLeave.value) {
isApprovedForLeave.value = false
next()
} else if (props.hasChanges) {
showConfirmation.value = true
} else {
next()
}
})
</script>
<i18n lang="json">
{
"de": {
"create": "Anlegen",
"abort": "Abbrechen",
"entry": {
"title": "Titel",
"username": "Nutzername",
"password": "Passwort",
"url": "Url"
},
"tab": {
"details": "Details",
"keyValue": "Extra",
"history": "Verlauf"
}
},
"en": {
"create": "Create",
"abort": "Abort",
"entry": {
"title": "Title",
"username": "Username",
"password": "Password",
"url": "Url"
},
"tab": {
"details": "Details",
"keyValue": "Extra",
"history": "History"
}
}
}
</i18n>

View File

@ -1,63 +0,0 @@
<template>
<div class="card">
<slot name="image" />
<div class="card-header">
<slot name="header">
<div
v-if="$slots.title || title"
class="flex items-center gap-2"
>
<Icon
v-if="icon"
:name="icon"
size="28"
/>
<h5
v-if="title"
class="card-title mb-0"
>
{{ title }}
</h5>
<slot
v-else
name="title"
/>
</div>
<div class="text-base-content/50">{{ subtitle }}</div>
</slot>
</div>
<div class="card-body px-2 sm:px-6">
<slot />
<div
v-if="$slots.action"
class="card-actions"
>
<slot name="action" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(['close', 'submit'])
defineProps<{ title?: string; subtitle?: string; icon?: string }>()
const { escape, enter } = useMagicKeys()
watchEffect(async () => {
if (escape.value) {
await nextTick()
emit('close')
}
})
watchEffect(async () => {
if (enter.value) {
await nextTick()
emit('submit')
}
})
</script>

View File

@ -1 +0,0 @@
<template><div>first time</div></template>

View File

@ -1,32 +0,0 @@
<template>
<UiDialog :title="t('title')">
<form class="flex flex-col">
<UiInput v-model="vaultItem.title" />
<UiInput v-model="vaultItem.username" />
<UiInput v-model="vaultItem.password" />
<UiInput v-model="vaultItem.note" />
</form>
</UiDialog>
</template>
<script setup lang="ts">
const { t } = useI18n();
const vaultItem = reactive({
title: '',
username: '',
password: '',
note: '',
});
</script>
<i18n lang="json">
{
"de": {
"title": "Eintrag erstellen"
},
"en": {
"title": "Create Entry"
}
}
</i18n>

View File

@ -1,112 +0,0 @@
<template>
<VaultCardEdit
v-if="vaultGroup"
v-model:read_only="read_only"
:color="vaultGroup.color ?? 'text-base-content'"
:has-changes="hasChanges"
:icon="vaultGroup.icon ?? 'mdi:folder-outline'"
:title="vaultGroup.name ?? ''"
@back="$emit('back')"
@close="$emit('close')"
@reject="(to) => $emit('reject', to)"
@submit="(to) => $emit('submit', to)"
>
<div class="flex flex-col gap-4 w-full p-4">
<UiInput
v-show="!read_only"
v-model.trim="vaultGroup.name"
:label="t('vaultGroup.name')"
:placeholder="t('vaultGroup.name')"
:with-copy-button="read_only"
:read_only
autofocus
/>
<UiInput
v-show="!read_only || vaultGroup.description?.length"
v-model.trim="vaultGroup.description"
:read_only
:label="t('vaultGroup.description')"
:placeholder="t('vaultGroup.description')"
:with-copy-button="read_only"
/>
<UiSelectColor
v-model="vaultGroup.color"
:read_only
:label="t('vaultGroup.color')"
:placeholder="t('vaultGroup.color')"
/>
<UiSelectIcon
v-model="vaultGroup.icon"
:read_only
:label="t('vaultGroup.icon')"
:placeholder="t('vaultGroup.icon')"
/>
</div>
</VaultCardEdit>
</template>
<script setup lang="ts">
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault'
const { t } = useI18n()
const showConfirmation = ref(false)
const vaultGroup = defineModel<SelectHaexPasswordsGroups>({ required: true })
const read_only = defineModel<boolean>('read_only')
const props = defineProps({
originally: Object as PropType<SelectHaexPasswordsGroups>,
})
defineEmits<{
submit: [to?: RouteLocationNormalizedLoadedGeneric]
close: [void]
back: [void]
reject: [to?: RouteLocationNormalizedLoadedGeneric]
}>()
const hasChanges = computed(() => {
console.log('group has changes', props.originally, vaultGroup.value)
if (!props.originally) {
if (
vaultGroup.value.color?.length ||
vaultGroup.value.description?.length ||
vaultGroup.value.icon?.length ||
vaultGroup.value.name?.length
) {
return true
} else {
return false
}
}
return JSON.stringify(props.originally) !== JSON.stringify(vaultGroup.value)
})
/* const onClose = () => {
if (props.originally) vaultGroup.value = { ...props.originally };
emit('close');
}; */
</script>
<i18n lang="json">
{
"de": {
"vaultGroup": {
"name": "Name",
"description": "Beschreibung",
"icon": "Icon",
"color": "Farbe"
}
},
"en": {
"vaultGroup": {
"name": "Name",
"description": "Description",
"icon": "Icon",
"color": "Color"
}
}
}
</i18n>

View File

@ -1,7 +0,0 @@
<template>
<UiList>
<slot />
</UiList>
</template>
<script setup lang="ts"></script>

View File

@ -1,77 +0,0 @@
<template>
<UiDialog
v-model:open="showConfirmation"
:title="t('dialog.title')"
>
{{ t('dialog.question') }}
<template #buttons>
<UiButton
class="btn-outline btn-error focus:bg-primary"
tabindex="10"
@click="$emit('reject')"
>
<Icon name="mdi:cancel" />
<span class="hidden sm:block">
{{ t('dialog.reject') }}
</span>
</UiButton>
<UiButton
ref="abortButtonRef"
class="btn-outline focus:bg-primary"
tabindex="11"
@click="showConfirmation = false"
>
<Icon name="mdi:close" />
<span class="hidden sm:block">
{{ t('dialog.abort') }}
</span>
</UiButton>
<UiButton
class="btn-outline btn-success"
tabindex="12"
@click="$emit('submit')"
>
<Icon name="mdi:check" />
<span class="hidden sm:block">
{{ t('dialog.save') }}
</span>
</UiButton>
</template>
</UiDialog>
</template>
<script setup lang="ts">
const showConfirmation = defineModel<boolean>()
const abortButtonRef = useTemplateRef('abortButtonRef')
const { t } = useI18n()
onUpdated(() => {
abortButtonRef.value?.$el.focus()
})
defineEmits(['submit', 'reject'])
</script>
<i18n lang="json">
{
"de": {
"dialog": {
"title": "Ungespeicherte Änderungen",
"question": "Möchten Sie die Änderungen speichern?",
"reject": "Verwerfen",
"abort": "Abbrechen",
"save": "Speichern"
}
},
"en": {
"dialog": {
"title": "Unsaved Changes",
"question": "Would you like to save the changes?",
"reject": "Reject",
"abort": "Abort",
"save": "Save"
}
}
}
</i18n>