mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-17 14:30:52 +01:00
init commit
This commit is contained in:
165
src/components/vault/button/create.vue
Normal file
165
src/components/vault/button/create.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<UiDialog
|
||||
:title="t('title')"
|
||||
v-model:open="open"
|
||||
>
|
||||
<template #trigger="{ id }">
|
||||
<button
|
||||
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1 whitespace-nowrap flex-nowrap"
|
||||
@click="open = true"
|
||||
>
|
||||
<Icon name="mdi:plus" />
|
||||
{{ t('database.create') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<form
|
||||
class="flex flex-col gap-4"
|
||||
@submit="onCreateAsync"
|
||||
>
|
||||
<!-- @keyup.enter="onCreateAsync" -->
|
||||
<UiInput
|
||||
:check-input="check"
|
||||
:label="t('database.label')"
|
||||
:placeholder="t('database.placeholder')"
|
||||
:rules="vaultDatabaseSchema.name"
|
||||
autofocus
|
||||
prepend-icon="mdi:safe"
|
||||
v-model="database.name"
|
||||
/>
|
||||
|
||||
<UiInputPassword
|
||||
:check-input="check"
|
||||
:rules="vaultDatabaseSchema.password"
|
||||
prepend-icon="mdi:key-outline"
|
||||
v-model="database.password"
|
||||
/>
|
||||
</form>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton
|
||||
class="btn-error"
|
||||
@click="onClose"
|
||||
>
|
||||
{{ t('abort') }}
|
||||
</UiButton>
|
||||
|
||||
<UiButton
|
||||
class="btn-primary"
|
||||
@click="onCreateAsync"
|
||||
>
|
||||
{{ t('create') }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { save } from '@tauri-apps/plugin-dialog';
|
||||
import { useVaultStore } from '~/stores/vault';
|
||||
import { vaultDatabaseSchema } from './schema';
|
||||
|
||||
const check = ref(false);
|
||||
const open = ref();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const database = reactive<{
|
||||
name: string;
|
||||
password: string;
|
||||
path: string | null;
|
||||
type: 'password' | 'text';
|
||||
}>({
|
||||
name: '',
|
||||
password: '',
|
||||
path: '',
|
||||
type: 'password',
|
||||
});
|
||||
|
||||
const initDatabase = () => {
|
||||
database.name = t('database.name');
|
||||
database.password = '';
|
||||
database.path = '';
|
||||
database.type = 'password';
|
||||
};
|
||||
|
||||
initDatabase();
|
||||
|
||||
const { add } = useSnackbar();
|
||||
const { createAsync } = useVaultStore();
|
||||
//const { show } = storeToRefs(useSidebarStore());
|
||||
|
||||
const onCreateAsync = async () => {
|
||||
check.value = true;
|
||||
|
||||
const nameCheck = vaultDatabaseSchema.name.safeParse(database.name);
|
||||
const passwordCheck = vaultDatabaseSchema.password.safeParse(
|
||||
database.password
|
||||
);
|
||||
|
||||
console.log(
|
||||
'checks',
|
||||
database.name,
|
||||
nameCheck,
|
||||
database.password,
|
||||
passwordCheck
|
||||
);
|
||||
if (!nameCheck.success || !passwordCheck.success) return;
|
||||
|
||||
open.value = false;
|
||||
try {
|
||||
database.path = await save({ defaultPath: `${database.name}.db` });
|
||||
|
||||
console.log('data', database);
|
||||
if (database.path && database.password) {
|
||||
const vaultId = await createAsync({
|
||||
path: database.path,
|
||||
password: database.password,
|
||||
});
|
||||
//show.value = true;
|
||||
|
||||
await navigateTo(
|
||||
useLocaleRoute()({ name: 'vault', params: { vaultId } })
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
add({ type: 'error', text: JSON.stringify(error) });
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
open.value = false;
|
||||
initDatabase();
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"database": {
|
||||
"label": "Datenbankname",
|
||||
"placeholder": "Passwörter",
|
||||
"create": "Neue Vault anlegen",
|
||||
"name": "Passwörter"
|
||||
},
|
||||
"title": "Neue Datenbank anlegen",
|
||||
"create": "Erstellen",
|
||||
"abort": "Abbrechen",
|
||||
"description": "Haex Vault für deine geheimsten Geheimnisse"
|
||||
},
|
||||
|
||||
"en": {
|
||||
"database": {
|
||||
"label": "Databasename",
|
||||
"placeholder": "Databasename",
|
||||
"create": "Create new Vault",
|
||||
"name": "Passwords"
|
||||
},
|
||||
"title": "Create New Database",
|
||||
"create": "Create",
|
||||
"abort": "Abort",
|
||||
"description": "Haex Vault for your most secret secrets"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
179
src/components/vault/button/open.vue
Normal file
179
src/components/vault/button/open.vue
Normal file
@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<UiDialog v-model:open="isOpen">
|
||||
<!-- @close="initDatabase" -->
|
||||
<template #trigger>
|
||||
<button
|
||||
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1"
|
||||
@click="onLoadDatabase"
|
||||
>
|
||||
<Icon name="mdi:folder-open-outline" />
|
||||
{{ t('database.open') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<UiInputPassword
|
||||
:check-input="check"
|
||||
:rules="vaultDatabaseSchema.password"
|
||||
@keyup.enter="onOpenDatabase"
|
||||
autofocus
|
||||
prepend-icon="mdi:key-outline"
|
||||
v-model="database.password"
|
||||
/>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton
|
||||
class="btn-error"
|
||||
@click="onClose"
|
||||
>
|
||||
{{ t('abort') }}
|
||||
</UiButton>
|
||||
|
||||
<UiButton
|
||||
type="submit"
|
||||
class="btn-primary"
|
||||
@click="onOpenDatabase"
|
||||
>
|
||||
{{ t('open') }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { vaultDatabaseSchema } from './schema';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const isOpen = defineModel('isOpen', { type: Boolean });
|
||||
|
||||
const props = defineProps({
|
||||
path: String,
|
||||
});
|
||||
|
||||
const check = ref(false);
|
||||
|
||||
const database = reactive<{
|
||||
name: string;
|
||||
password: string;
|
||||
path: string | null;
|
||||
type: 'password' | 'text';
|
||||
}>({
|
||||
name: '',
|
||||
password: '',
|
||||
path: '',
|
||||
type: 'password',
|
||||
});
|
||||
|
||||
const initDatabase = () => {
|
||||
database.name = '';
|
||||
database.password = '';
|
||||
database.path = '';
|
||||
database.type = 'password';
|
||||
};
|
||||
|
||||
initDatabase();
|
||||
|
||||
const { add } = useSnackbar();
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
isOpen.value = false;
|
||||
add({ type: 'error', text: JSON.stringify(error) });
|
||||
//console.error(error);
|
||||
};
|
||||
|
||||
const { openAsync } = useVaultStore();
|
||||
//const { show } = storeToRefs(useSidebarStore());
|
||||
|
||||
const onLoadDatabase = async () => {
|
||||
try {
|
||||
database.path = await open({
|
||||
multiple: false,
|
||||
directory: false,
|
||||
filters: [
|
||||
{
|
||||
name: 'HaexVault',
|
||||
extensions: ['db'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!database.path) return;
|
||||
|
||||
isOpen.value = true;
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
};
|
||||
|
||||
const localePath = useLocalePath();
|
||||
const onOpenDatabase = async () => {
|
||||
try {
|
||||
check.value = true;
|
||||
const path = database.path || props.path;
|
||||
const pathCheck = vaultDatabaseSchema.path.safeParse(path);
|
||||
const passwordCheck = vaultDatabaseSchema.password.safeParse(
|
||||
database.password
|
||||
);
|
||||
|
||||
if (!pathCheck.success || !passwordCheck.success || !path) {
|
||||
add({ type: 'error', text: 'params falsch' });
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log('try to open', path);
|
||||
|
||||
const vaultId = await openAsync({
|
||||
path,
|
||||
password: database.password,
|
||||
});
|
||||
|
||||
if (!vaultId) {
|
||||
add({ type: 'error', text: 'Vault konnte nicht geöffnet werden' });
|
||||
return;
|
||||
}
|
||||
|
||||
onClose();
|
||||
|
||||
/* await navigateTo(
|
||||
localePath({
|
||||
name: 'vaultGroup',
|
||||
params: {
|
||||
vaultId,
|
||||
},
|
||||
query: {
|
||||
showSidebar: 'true',
|
||||
},
|
||||
})
|
||||
); */
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
handleError(error);
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
initDatabase();
|
||||
isOpen.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"open": "Öffnen",
|
||||
"abort": "Abbrechen",
|
||||
"database": {
|
||||
"open": "Vault öffnen"
|
||||
}
|
||||
},
|
||||
|
||||
"en": {
|
||||
"open": "Open",
|
||||
"abort": "Abort",
|
||||
"database": {
|
||||
"open": "Open Vault"
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
7
src/components/vault/button/schema.ts
Normal file
7
src/components/vault/button/schema.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const vaultDatabaseSchema = {
|
||||
password: z.string().min(6).max(255),
|
||||
name: z.string().min(1).max(255),
|
||||
path: z.string().min(4).endsWith('.db'),
|
||||
};
|
||||
250
src/components/vault/card/edit.vue
Normal file
250
src/components/vault/card/edit.vue
Normal file
@ -0,0 +1,250 @@
|
||||
<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 flex-col items-center w-full min-h-14 gap-2 py-1"
|
||||
:class="{ '-ml-6': !show }"
|
||||
:style="{ color }"
|
||||
>
|
||||
<Icon
|
||||
v-if="icon"
|
||||
:name="icon"
|
||||
size="28"
|
||||
/>
|
||||
|
||||
<h5
|
||||
v-show="read_only"
|
||||
class="overflow-hidden whitespace-nowrap"
|
||||
>
|
||||
{{ 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"
|
||||
:class="{ 'pl-96': show }"
|
||||
>
|
||||
<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>
|
||||
</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 { show } = storeToRefs(useSidebarStore());
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
const onDelete = () => {};
|
||||
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>
|
||||
42
src/components/vault/card/index.vue
Normal file
42
src/components/vault/card/index.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-base-100 w-full mx-auto shadow h-full overflow-hidden pt-[7.5rem]"
|
||||
>
|
||||
<div
|
||||
class="fixed top-0 right-0 z-10 transition-all duration-700 w-full font-semibold text-lg h-[7.5rem]"
|
||||
:class="{ 'pl-96': show }"
|
||||
>
|
||||
<div
|
||||
class="justify-center items-center flex flex-wrap border-b rounded-b border-secondary h-full"
|
||||
:class="{ 'pl-12': !show }"
|
||||
>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-full overflow-scroll bg-base-200">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { show } = storeToRefs(useSidebarStore());
|
||||
const emit = defineEmits(['close', 'submit']);
|
||||
|
||||
const { escape, enter } = useMagicKeys();
|
||||
|
||||
watchEffect(async () => {
|
||||
if (escape.value) {
|
||||
await nextTick();
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(async () => {
|
||||
if (enter.value) {
|
||||
await nextTick();
|
||||
emit('submit');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
32
src/components/vault/dialog/item/create.vue
Normal file
32
src/components/vault/dialog/item/create.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<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>
|
||||
117
src/components/vault/group/index.vue
Normal file
117
src/components/vault/group/index.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<VaultCardEdit
|
||||
v-if="vaultGroup"
|
||||
: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)"
|
||||
v-model:read_only="read_only"
|
||||
>
|
||||
<div class="flex flex-col gap-4 w-full p-4">
|
||||
<UiInput
|
||||
v-show="!read_only"
|
||||
:label="t('vaultGroup.name')"
|
||||
:placeholder="t('vaultGroup.name')"
|
||||
:rules="vaultGroupSchema.name"
|
||||
:with-copy-button="read_only"
|
||||
:read_only
|
||||
autofocus
|
||||
v-model.trim="vaultGroup.name"
|
||||
/>
|
||||
|
||||
<UiInput
|
||||
v-show="!read_only || vaultGroup.description?.length"
|
||||
:read_only
|
||||
:label="t('vaultGroup.description')"
|
||||
:placeholder="t('vaultGroup.description')"
|
||||
:rules="vaultGroupSchema.description"
|
||||
:with-copy-button="read_only"
|
||||
v-model.trim="vaultGroup.description"
|
||||
/>
|
||||
|
||||
<UiColorPicker
|
||||
:read_only
|
||||
:label="t('vaultGroup.color')"
|
||||
:placeholder="t('vaultGroup.color')"
|
||||
v-model="vaultGroup.color"
|
||||
/>
|
||||
|
||||
<UiIconPicker
|
||||
:read_only
|
||||
:label="t('vaultGroup.icon')"
|
||||
:placeholder="t('vaultGroup.icon')"
|
||||
v-model="vaultGroup.icon"
|
||||
/>
|
||||
</div>
|
||||
</VaultCardEdit>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
|
||||
import {
|
||||
vaultGroupSchema,
|
||||
type SelectVaultGroup,
|
||||
} from '~/database/schemas/vault';
|
||||
|
||||
const { t } = useI18n();
|
||||
const showConfirmation = ref(false);
|
||||
const vaultGroup = defineModel<SelectVaultGroup>({ required: true });
|
||||
const read_only = defineModel<boolean>('read_only');
|
||||
const props = defineProps({
|
||||
originally: Object as PropType<SelectVaultGroup>,
|
||||
});
|
||||
|
||||
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>
|
||||
42
src/components/vault/list/entry.vue
Normal file
42
src/components/vault/list/entry.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<UiListButton
|
||||
v-if="entry"
|
||||
:key="entry.id"
|
||||
@click="navigateToEntryAsync(entry.id)"
|
||||
class="text-base-content"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8">
|
||||
<Icon
|
||||
v-if="entry.icon || groupIcon"
|
||||
:name="entry.icon || groupIcon!"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-start">
|
||||
<div v-if="!entry.title && !entry.username && !entry.url">
|
||||
{{ entry.id }}
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
{{ entry.title }}
|
||||
</div>
|
||||
<span class="text-sm">
|
||||
{{ entry.username }}
|
||||
</span>
|
||||
<span class="text-sm">
|
||||
{{ entry.url }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</UiListButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectVaultEntry } from '~/database/schemas/vault';
|
||||
|
||||
defineProps({
|
||||
entry: Object as PropType<SelectVaultEntry>,
|
||||
groupIcon: [String, null],
|
||||
});
|
||||
|
||||
const { navigateToEntryAsync } = useVaultEntryStore();
|
||||
</script>
|
||||
7
src/components/vault/list/index.vue
Normal file
7
src/components/vault/list/index.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<UiList>
|
||||
<slot />
|
||||
</UiList>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
78
src/components/vault/modal/saveChanges.vue
Normal file
78
src/components/vault/modal/saveChanges.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<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
|
||||
class="btn-outline focus:bg-primary"
|
||||
tabindex="11"
|
||||
ref="abortButtonRef"
|
||||
@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();
|
||||
const { currentScreenSize } = storeToRefs(useUiStore());
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user