mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-17 06:30:50 +01:00
item handling
This commit is contained in:
@ -1,104 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<fieldset class="join w-full">
|
||||
<slot name="prepend" />
|
||||
|
||||
|
||||
<!-- <div class="">
|
||||
-->
|
||||
<HaexButton
|
||||
v-if="withCopyButton" class="btn-outline btn-accent btn-square join-item h-auto"
|
||||
@click="copy(`${input}`)">
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
</Haexbutton>
|
||||
|
||||
<!-- <div class="">
|
||||
<input :id :name="name ?? id" :placeholder="placeholder || label" :type :autofocus class="" v-bind="$attrs"
|
||||
v-model="input" ref="inputRef" :readonly="read_only" />
|
||||
<label class="floating-label" :for="id">{{ label }}</label>
|
||||
</div> -->
|
||||
<label class="floating-label input join-item">
|
||||
<Icon v-if="iconPrepend" :name="iconPrepend" class="my-auto size-6" />
|
||||
<span>Your Email</span>
|
||||
<input type="text" placeholder="mail@site.com" class=" join-item " >
|
||||
<Icon v-if="iconAppend" :name="iconAppend" class="my-auto shrink-0" />
|
||||
</label>
|
||||
|
||||
<!-- <Icon v-if="iconAppend" :name="iconAppend" class="my-auto shrink-0" />
|
||||
</div> -->
|
||||
|
||||
<slot name="append" class="h-auto" />
|
||||
|
||||
<HaexButton
|
||||
v-if="withCopyButton" class="btn-outline btn-accent btn-square join-item h-auto"
|
||||
@click="copy(`${input}`)">
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
</Haexbutton>
|
||||
</fieldset>
|
||||
|
||||
<span v-show="errors" class="flex flex-col px-2 pt-0.5">
|
||||
<span v-for="error in errors" class="label-text-alt text-error">
|
||||
{{ error }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div class="relative w-full max-w-sm items-center">
|
||||
<span class="absolute start-0 inset-y-0 flex items-center justify-center px-2">
|
||||
<Icon v-if="iconPrepend" :name="iconPrepend" class="size-6" />
|
||||
<button>aa</button>
|
||||
</span>
|
||||
|
||||
<Input id="search" type="text" placeholder="Search..." :class="{ 'pl-10': iconPrepend, 'pr-10': iconAppend }" />
|
||||
|
||||
<span class="absolute end-0 inset-y-0 flex items-center justify-center px-2">
|
||||
<Icon v-if="iconAppend" :name="iconAppend" class="size-6" />
|
||||
</span>
|
||||
</div> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HaexButton } from '#components';
|
||||
import type { ZodSchema } from 'zod';
|
||||
|
||||
const id = useId()
|
||||
|
||||
const props = defineProps<{ iconAppend?: string, iconPrepend?: string, placeholder?: string, type?: string, label?: string, withCopyButton?: boolean, rules?: ZodSchema, read_only?: boolean, autofocus?: boolean, checkInput?: boolean, name?: string }>()
|
||||
|
||||
const inputRef = useTemplateRef('inputRef')
|
||||
|
||||
const input = defineModel<string | number | undefined | null>({
|
||||
default: '',
|
||||
required: true,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autofocus && inputRef.value) inputRef.value.focus()
|
||||
})
|
||||
|
||||
watch(input, () => checkInput())
|
||||
|
||||
watch(
|
||||
() => props.checkInput,
|
||||
() => {
|
||||
checkInput()
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits(['error'])
|
||||
|
||||
const errors = defineModel<string[] | undefined>('errors')
|
||||
const checkInput = () => {
|
||||
if (props.rules) {
|
||||
const result = props.rules.safeParse(input.value)
|
||||
//console.log('check result', result.error, props.rules);
|
||||
if (!result.success) {
|
||||
errors.value = result.error.errors.map((error) => error.message)
|
||||
emit('error', errors.value)
|
||||
} else {
|
||||
errors.value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { copy, copied } = useClipboard()
|
||||
</script>
|
||||
90
src/components/haex/menu/notifications.vue
Normal file
90
src/components/haex/menu/notifications.vue
Normal file
@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div
|
||||
class="dropdown relative inline-flex [--auto-close:inside] [--offset:18] [--placement:bottom]"
|
||||
>
|
||||
<UiTooltip :tooltip="t('notifications.label')">
|
||||
<button
|
||||
id="dropdown-scrollable"
|
||||
type="button"
|
||||
class="dropdown-toggle btn btn-text btn-circle dropdown-open:bg-base-content/10 size-10"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
aria-label="Dropdown"
|
||||
>
|
||||
<div class="indicator">
|
||||
<span
|
||||
v-show="notifications.length"
|
||||
class="indicator-item bg-error size-2 rounded-full text-sm"
|
||||
/>
|
||||
<span class="icon-[tabler--bell] text-base-content size-[1.375rem]" />
|
||||
</div>
|
||||
</button>
|
||||
</UiTooltip>
|
||||
<div
|
||||
class="dropdown-menu dropdown-open:opacity-100 hidden w-full max-w-96 shadow"
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-labelledby="dropdown-scrollable"
|
||||
>
|
||||
<div class="dropdown-header justify-center">
|
||||
<h6 class="text-base-content text-base">
|
||||
{{ t('notifications.label') }}
|
||||
</h6>
|
||||
</div>
|
||||
<div
|
||||
class="vertical-scrollbar horizontal-scrollbar rounded-scrollbar text-base-content/80 max-h-56 overflow-auto"
|
||||
>
|
||||
<div
|
||||
v-for="notification in notifications"
|
||||
:key="notification.id"
|
||||
class="dropdown-item"
|
||||
>
|
||||
<div class="avatar">
|
||||
<div class="w-10 rounded-full">
|
||||
<img
|
||||
v-if="notification.image"
|
||||
:src="notification.image"
|
||||
:alt="notification.alt ?? 'notification avatar'"
|
||||
/>
|
||||
<Icon
|
||||
v-else-if="notification.icon"
|
||||
:name="notification.icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-60">
|
||||
<h6 class="truncate text-base">
|
||||
{{ notification.title }}
|
||||
</h6>
|
||||
<small class="text-base-content/50 truncate">
|
||||
{{ notification.text }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NuxtLinkLocale
|
||||
:to="{ name: 'notifications' }"
|
||||
class="dropdown-footer justify-center gap-1 hover:bg-base-content/10"
|
||||
>
|
||||
<span class="icon-[tabler--eye] size-4" />
|
||||
{{ t('notifications.view_all') }}
|
||||
</NuxtLinkLocale>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const { notifications } = storeToRefs(useNotificationStore())
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
notifications:
|
||||
label: Benachrichtigungen
|
||||
view_all: Alle ansehen
|
||||
en:
|
||||
notifications:
|
||||
label: Notifications
|
||||
view_all: View all
|
||||
</i18n>
|
||||
107
src/components/haex/pass/card/group.vue
Normal file
107
src/components/haex/pass/card/group.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<HaexPassCard
|
||||
:title
|
||||
@close="onClose"
|
||||
>
|
||||
<div class="flex flex-col gap-4 w-full p-4">
|
||||
<slot />
|
||||
|
||||
<UiInput
|
||||
v-show="!read_only"
|
||||
v-model.trim="passwordGroup.name"
|
||||
:label="t('group.name')"
|
||||
:placeholder="t('group.name')"
|
||||
:with-copy-button="read_only"
|
||||
:read_only
|
||||
autofocus
|
||||
/>
|
||||
|
||||
<UiInput
|
||||
v-show="!read_only || passwordGroup.description?.length"
|
||||
v-model.trim="passwordGroup.description"
|
||||
:read_only
|
||||
:label="t('group.description')"
|
||||
:placeholder="t('group.description')"
|
||||
:with-copy-button="read_only"
|
||||
/>
|
||||
|
||||
<UiSelectColor
|
||||
v-model="passwordGroup.color"
|
||||
:read_only
|
||||
:label="t('group.color')"
|
||||
:placeholder="t('group.color')"
|
||||
/>
|
||||
|
||||
<UiSelectIcon
|
||||
v-model="passwordGroup.icon"
|
||||
:read_only
|
||||
:label="t('group.icon')"
|
||||
:placeholder="t('group.icon')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<slot name="footer" />
|
||||
</HaexPassCard>
|
||||
</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 passwordGroup = defineModel<SelectHaexPasswordsGroups>({ required: true })
|
||||
const read_only = defineModel<boolean>('read_only')
|
||||
const props = defineProps<{
|
||||
originally: SelectHaexPasswordsGroups
|
||||
title: string
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
submit: [to?: RouteLocationNormalizedLoadedGeneric]
|
||||
close: [void]
|
||||
back: [void]
|
||||
reject: [to?: RouteLocationNormalizedLoadedGeneric]
|
||||
}>()
|
||||
|
||||
const hasChanges = computed(() => {
|
||||
console.log('group has changes', props.originally, passwordGroup.value)
|
||||
if (!props.originally) {
|
||||
if (
|
||||
passwordGroup.value.color?.length ||
|
||||
passwordGroup.value.description?.length ||
|
||||
passwordGroup.value.icon?.length ||
|
||||
passwordGroup.value.name?.length
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return (
|
||||
JSON.stringify(props.originally) !== JSON.stringify(passwordGroup.value)
|
||||
)
|
||||
})
|
||||
|
||||
const onClose = () => {
|
||||
/* if (props.originally) passwordGroup.value = { ...props.originally };
|
||||
emit('close'); */
|
||||
console.log('close group card')
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
group:
|
||||
name: Name
|
||||
description: Beschreibung
|
||||
icon: Icon
|
||||
color: Farbe
|
||||
|
||||
en:
|
||||
group:
|
||||
name: Name
|
||||
description: Description
|
||||
icon: Icon
|
||||
color: Color
|
||||
</i18n>
|
||||
12
src/components/haex/pass/card/index.vue
Normal file
12
src/components/haex/pass/card/index.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<UiCard
|
||||
:title
|
||||
:icon
|
||||
>
|
||||
<slot />
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ title: string; icon?: string }>()
|
||||
</script>
|
||||
@ -1,190 +0,0 @@
|
||||
<template>
|
||||
<VaultCardEdit
|
||||
v-if="vaultEntry.details" v-model:read_only="read_only" :color="currentGroup?.color || 'text-base-content'"
|
||||
:has-changes="hasChanges" :icon="vaultEntry.details?.icon || icon || 'mdi:key-outline'"
|
||||
:title="vaultEntry.details?.title ?? ''" @back="$emit('back')" @close="$emit('close')"
|
||||
@reject="(to) => $emit('reject', to)" @submit="(to) => $emit('submit', to)">
|
||||
<div class="h-full relative overflow-hidden">
|
||||
<nav
|
||||
aria-label="Tabs Vault Entry" aria-orientation="horizontal"
|
||||
class="tabs tabs-bordered w-full transition-all duration-700 sticky top-0 z-10 bg-base-200" role="tablist">
|
||||
<button
|
||||
:id="id.details" aria-controls="vaultDetailsId" aria-selected="true"
|
||||
class="tab active-tab:tab-active active w-full" data-tab="#vaultDetailsId" role="tab" type="button">
|
||||
<Icon name="material-symbols:key-outline" class="me-2" />
|
||||
<span class="hidden sm:block">
|
||||
{{ t('tab.details') }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
:id="id.keyValue" aria-controls="tabs-basic-2" aria-selected="false"
|
||||
class="tab active-tab:tab-active w-full" data-tab="#tabs-basic-2" role="tab" type="button">
|
||||
<Icon name="fluent:group-list-20-filled" class="me-2" />
|
||||
<span class="hidden sm:block">
|
||||
{{ t('tab.keyValue') }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
:id="id.history" aria-controls="tabs-basic-3" aria-selected="false"
|
||||
class="tab active-tab:tab-active w-full" data-tab="#tabs-basic-3" role="tab" type="button">
|
||||
<Icon name="material-symbols:history" class="me-2" />
|
||||
<span class="hidden sm:block">
|
||||
{{ t('tab.history') }}
|
||||
</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="h-full pb-8">
|
||||
<div id="vaultDetailsId" role="tabpanel" :aria-labelledby="id.details" class="h-full">
|
||||
<VaultEntryDetails v-if="vaultEntry.details" v-model="vaultEntry.details" :with-copy-button :read_only/>
|
||||
</div>
|
||||
|
||||
<div id="tabs-basic-2" class="hidden" role="tabpanel" :aria-labelledby="id.keyValue">
|
||||
{{ originally }}
|
||||
</div>
|
||||
|
||||
<div id="tabs-basic-3" class="hidden h-full" role="tabpanel" :aria-labelledby="id.history">
|
||||
<VaultEntryHistory v-if="vaultEntry.history" :history="vaultEntry.history" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VaultCardEdit>
|
||||
<!-- <VaultModalSaveChanges
|
||||
v-model="showConfirmation"
|
||||
@reject="onReject"
|
||||
@submit="onSubmit"
|
||||
/> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const vaultEntry = defineModel<IVaultEntryComplete>({ required: true });
|
||||
|
||||
|
||||
const { currentGroup } = storeToRefs(useVaultGroupStore());
|
||||
|
||||
/* watch(
|
||||
() => vaultEntry.value.details,
|
||||
() => {
|
||||
header.value.text = vaultEntry.value.details?.title;
|
||||
header.value.icon =
|
||||
vaultEntry.value.details?.icon || currentGroup.value?.icon;
|
||||
},
|
||||
{ immediate: true }
|
||||
); */
|
||||
|
||||
const id = reactive({
|
||||
details: useId(),
|
||||
keyValue: useId(),
|
||||
history: useId(),
|
||||
content: {},
|
||||
});
|
||||
|
||||
const read_only = defineModel<boolean>('read_only', { default: false });
|
||||
|
||||
const props = defineProps({
|
||||
icon: String,
|
||||
originally: Object as PropType<IVaultEntryComplete>,
|
||||
title: String,
|
||||
withCopyButton: Boolean,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [to?: RouteLocationNormalizedLoadedGeneric];
|
||||
close: [void];
|
||||
back: [void];
|
||||
reject: [to?: RouteLocationNormalizedLoadedGeneric];
|
||||
}>();
|
||||
|
||||
const showConfirmation = ref(false);
|
||||
|
||||
const hasChanges = computed(() => {
|
||||
if (!props.originally?.details) {
|
||||
if (
|
||||
vaultEntry.value.details?.note?.length ||
|
||||
vaultEntry.value.details?.password?.length ||
|
||||
vaultEntry.value.details?.tags?.length ||
|
||||
vaultEntry.value.details?.title?.length ||
|
||||
vaultEntry.value.details?.url?.length ||
|
||||
vaultEntry.value.details?.urlAliases?.length ||
|
||||
vaultEntry.value.details?.username?.length
|
||||
) {
|
||||
console.log('has changes', props.originally, vaultEntry.value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return (
|
||||
JSON.stringify(props.originally.details) !==
|
||||
JSON.stringify(vaultEntry.value.details)
|
||||
);
|
||||
});
|
||||
|
||||
const to = ref<RouteLocationNormalizedLoadedGeneric>();
|
||||
|
||||
const isSaved = ref(false);
|
||||
const isRejected = ref(false);
|
||||
|
||||
const onSubmit = () => {
|
||||
console.log('entry onSubmit');
|
||||
showConfirmation.value = false;
|
||||
isSaved.value = true;
|
||||
emit('submit', to.value);
|
||||
};
|
||||
|
||||
const onReject = () => {
|
||||
console.log('entry onReject');
|
||||
showConfirmation.value = false;
|
||||
isRejected.value = true;
|
||||
emit('reject', to.value);
|
||||
};
|
||||
|
||||
const onBack = () => {
|
||||
console.log('entry onBack', read_only.value);
|
||||
if (hasChanges.value) {
|
||||
showConfirmation.value = true;
|
||||
} else {
|
||||
emit('back');
|
||||
}
|
||||
};
|
||||
|
||||
/* onBeforeRouteLeave((_to, _from, next) => {
|
||||
console.log('check before leave', _to, _from);
|
||||
to.value = _to;
|
||||
if (isSaved.value || isRejected.value) {
|
||||
isSaved.value = false;
|
||||
isRejected.value = false;
|
||||
next();
|
||||
} else if (hasChanges.value) {
|
||||
showConfirmation.value = true;
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}); */
|
||||
</script>
|
||||
|
||||
<i18n lang="json">{
|
||||
"de": {
|
||||
"create": "Anlegen",
|
||||
"abort": "Abbrechen",
|
||||
"tab": {
|
||||
"details": "Details",
|
||||
"keyValue": "Extra",
|
||||
"history": "Verlauf"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"create": "Create",
|
||||
"abort": "Abort",
|
||||
"tab": {
|
||||
"details": "Details",
|
||||
"keyValue": "Extra",
|
||||
"history": "History"
|
||||
}
|
||||
}
|
||||
}</i18n>
|
||||
45
src/components/haex/pass/group/breadcrumbs.vue
Normal file
45
src/components/haex/pass/group/breadcrumbs.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<NuxtLinkLocale :to="{ name: 'passwordGroupItems' }">
|
||||
<Icon
|
||||
name="mdi:safe"
|
||||
size="24"
|
||||
/>
|
||||
</NuxtLinkLocale>
|
||||
</li>
|
||||
<template v-for="item in items">
|
||||
<li class="breadcrumbs-separator rtl:rotate-180">
|
||||
<Icon name="tabler:chevron-right" />
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<NuxtLinkLocale
|
||||
:to="{ name: 'passwordGroupItems', params: { groupId: item.id } }"
|
||||
>
|
||||
{{ item.name }}
|
||||
</NuxtLinkLocale>
|
||||
</li>
|
||||
</template>
|
||||
<li class="ml-2">
|
||||
<NuxtLinkLocale
|
||||
:to="{
|
||||
name: 'passwordGroupEdit',
|
||||
params: { groupId: lastGroup?.id },
|
||||
}"
|
||||
>
|
||||
<Icon name="mdi:pencil" />
|
||||
</NuxtLinkLocale>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
const groups = defineProps<{ items: SelectHaexPasswordsGroups[] }>()
|
||||
|
||||
const lastGroup = computed(() => groups.items.at(-1))
|
||||
</script>
|
||||
@ -1 +1,98 @@
|
||||
<template></template>
|
||||
<template>
|
||||
<UiCard
|
||||
v-if="modelValue"
|
||||
:title="mode === 'create' ? t('title.create') : t('title.edit')"
|
||||
icon="mdi:folder-plus-outline"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<form
|
||||
class="flex flex-col gap-4 w-full p-4"
|
||||
@submit.prevent="$emit('submit')"
|
||||
>
|
||||
<UiInput
|
||||
:check-input="check"
|
||||
:label="t('name')"
|
||||
:placeholder="t('name')"
|
||||
autofocus
|
||||
v-model="modelValue.name"
|
||||
ref="nameRef"
|
||||
/>
|
||||
|
||||
<UiInput
|
||||
v-model="modelValue.description"
|
||||
:check-input="check"
|
||||
:label="t('description')"
|
||||
:placeholder="t('description')"
|
||||
/>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<UiSelectIcon
|
||||
v-model="modelValue.icon"
|
||||
default-icon="mdi:folder-outline"
|
||||
/>
|
||||
|
||||
<UiSelectColor v-model="modelValue.color" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap justify-end gap-4">
|
||||
<UiButton
|
||||
class="btn-error btn-outline flex-1"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
{{ t('abort') }}
|
||||
<Icon name="mdi:close" />
|
||||
</UiButton>
|
||||
|
||||
<UiButton
|
||||
class="btn-primary flex-1"
|
||||
@click="$emit('submit')"
|
||||
>
|
||||
{{ mode === 'create' ? t('create') : t('save') }}
|
||||
<Icon name="mdi:check" />
|
||||
</UiButton>
|
||||
</div>
|
||||
</form>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
defineModel<SelectHaexPasswordsGroups | null>()
|
||||
defineEmits(['close', 'submit', 'back'])
|
||||
defineProps<{ mode: 'create' | 'edit' }>()
|
||||
const { t } = useI18n()
|
||||
|
||||
const check = ref<boolean>(false)
|
||||
|
||||
const nameRef = useTemplateRef('nameRef')
|
||||
onStartTyping(() => {
|
||||
nameRef.value?.inputRef?.focus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
name: Name
|
||||
description: Beschreibung
|
||||
icon: Icon
|
||||
color: Farbe
|
||||
create: Erstellen
|
||||
save: Speichern
|
||||
abort: Abbrechen
|
||||
title:
|
||||
create: Gruppe erstellen
|
||||
edit: Gruppe ändern
|
||||
|
||||
en:
|
||||
name: Name
|
||||
description: Description
|
||||
icon: Icon
|
||||
color: Color
|
||||
create: Create
|
||||
save: Save
|
||||
abort: Abort
|
||||
title:
|
||||
create: Create group
|
||||
edit: Edit group
|
||||
</i18n>
|
||||
|
||||
119
src/components/haex/pass/item/details.vue
Normal file
119
src/components/haex/pass/item/details.vue
Normal file
@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="h-full overflow-scroll">
|
||||
<div class="flex flex-col gap-4 w-full p-4">
|
||||
<UiInput
|
||||
v-show="!read_only || itemDetails.title"
|
||||
:check-input="check"
|
||||
:label="t('item.title')"
|
||||
:placeholder="t('item.title')"
|
||||
:read_only
|
||||
:with-copy-button
|
||||
autofocus
|
||||
ref="titleRef"
|
||||
v-model.trim="itemDetails.title"
|
||||
/>
|
||||
|
||||
<UiInput
|
||||
v-show="!read_only || itemDetails.username"
|
||||
:check-input="check"
|
||||
:label="t('item.username')"
|
||||
:placeholder="t('item.username')"
|
||||
:with-copy-button
|
||||
:read_only
|
||||
v-model.trim="itemDetails.username"
|
||||
/>
|
||||
|
||||
<UiInputPassword
|
||||
v-show="!read_only || itemDetails.password"
|
||||
:check-input="check"
|
||||
:read_only
|
||||
:with-copy-button
|
||||
v-model.trim="itemDetails.password"
|
||||
>
|
||||
<template #append>
|
||||
<UiDialogPasswordGenerator
|
||||
v-if="!read_only"
|
||||
class="join-item"
|
||||
:password="itemDetails.password"
|
||||
v-model="preventClose"
|
||||
/>
|
||||
</template>
|
||||
</UiInputPassword>
|
||||
|
||||
<UiInputUrl
|
||||
v-show="!read_only || itemDetails.url"
|
||||
:label="t('item.url')"
|
||||
:placeholder="t('item.url')"
|
||||
:read_only
|
||||
:with-copy-button
|
||||
v-model="itemDetails.url"
|
||||
/>
|
||||
|
||||
<UiSelectIcon
|
||||
v-show="!read_only"
|
||||
:default-icon="defaultIcon || 'mdi:key-outline'"
|
||||
:read_only
|
||||
v-model="itemDetails.icon"
|
||||
/>
|
||||
|
||||
<UiTextarea
|
||||
v-show="!read_only || itemDetails.note"
|
||||
v-model="itemDetails.note"
|
||||
:label="t('item.note')"
|
||||
:placeholder="t('item.note')"
|
||||
:read_only
|
||||
:with-copy-button
|
||||
@keyup.enter.stop
|
||||
class="h-52"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectHaexPasswordsItemDetails } from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
defineProps<{
|
||||
defaultIcon?: string | null
|
||||
read_only?: boolean
|
||||
withCopyButton?: boolean
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const itemDetails = defineModel<SelectHaexPasswordsItemDetails>({
|
||||
required: true,
|
||||
})
|
||||
|
||||
const preventClose = defineModel<boolean>('preventClose')
|
||||
|
||||
const check = defineModel<boolean>('check-input', { default: false })
|
||||
|
||||
onKeyStroke('escape', (e) => {
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
})
|
||||
|
||||
const titleRef = useTemplateRef('titleRef')
|
||||
onStartTyping(() => {
|
||||
titleRef.value?.inputRef?.focus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
item:
|
||||
title: Titel
|
||||
username: Nutzername
|
||||
password: Passwort
|
||||
url: Url
|
||||
note: Notiz
|
||||
|
||||
en:
|
||||
item:
|
||||
title: Title
|
||||
username: Username
|
||||
password: Password
|
||||
url: Url
|
||||
note: Note
|
||||
</i18n>
|
||||
118
src/components/haex/pass/item/history.vue
Normal file
118
src/components/haex/pass/item/history.vue
Normal file
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="h-full overflow-scroll flex">
|
||||
{{ _history }}
|
||||
<UiList v-show="_history.length">
|
||||
<!-- <UiListButton v-for="item in _history">
|
||||
<div
|
||||
class="flex items-start bg-slate-100 gap-x-2 w-full h-20 overflow-clip"
|
||||
>
|
||||
<div class="flex flex-col justify-between h-full py-2">
|
||||
<h6 class="text-sm whitespace-nowrap bg-orange-200">
|
||||
vorheriger {{ item.changedProperty }}
|
||||
</h6>
|
||||
<UiInput
|
||||
:model-value="item.oldValue"
|
||||
with-copy-button
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="sm:flex flex-col justify-between h-full py-2 hidden">
|
||||
<h6 class="text-sm">neuer Wert</h6>
|
||||
<UiInput
|
||||
:model-value="item.newValue"
|
||||
with-copy-button
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-between h-full py-2">
|
||||
<h6 class="text-sm md:text-base bg-orange-200">geändert_am</h6>
|
||||
<span class="bg-red-100 py-1 md:py-2">
|
||||
{{ item.createdAt }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</UiListButton>
|
||||
-->
|
||||
</UiList>
|
||||
|
||||
<div
|
||||
v-show="!_history.length"
|
||||
class="content-center w-full text-center"
|
||||
>
|
||||
{{ t('noHistory') }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- <UiTable
|
||||
v-if="history?.length"
|
||||
:headers
|
||||
:items="_history"
|
||||
autofocus
|
||||
>
|
||||
<template #column-oldValue="{ item }: { item: string }">
|
||||
<UiInput
|
||||
:model-value="item"
|
||||
with-copy-button
|
||||
class="min-w-24"
|
||||
/>
|
||||
</template>
|
||||
</UiTable> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
SelectHaexPasswordsGroupItems,
|
||||
SelectHaexPasswordsItemDetails,
|
||||
SelectHaexPasswordsItemHistory,
|
||||
} from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
const history = defineModel<SelectHaexPasswordsItemHistory[]>()
|
||||
|
||||
const _history = computed(
|
||||
() =>
|
||||
history.value?.map((change) => ({
|
||||
changedProperty: t(change.changedProperty!),
|
||||
createdAt: new Date(change.createdAt!).toLocaleDateString(),
|
||||
newValue: change.newValue,
|
||||
oldValue: change.oldValue,
|
||||
})) ?? [],
|
||||
)
|
||||
|
||||
const { t } = useI18n()
|
||||
interface ITableHeader {
|
||||
label?: string
|
||||
'item-value': string
|
||||
}
|
||||
const headers: ITableHeader[] = [
|
||||
{ 'item-value': 'changedProperty', label: t('changedProperty') },
|
||||
{ 'item-value': 'oldValue', label: t('oldValue') },
|
||||
{ 'item-value': 'newValue', label: t('newValue') },
|
||||
{ 'item-value': 'createdAt', label: t('createdAt') },
|
||||
]
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"noHistory": "Eintrag wurde bisher nicht geändert",
|
||||
"changedProperty": "Änderung",
|
||||
"createdAt": "geändert am",
|
||||
"newValue": "neuer Wert",
|
||||
"oldValue": "alter Wert",
|
||||
"password": "Passwort",
|
||||
"title": "Titel",
|
||||
"url": "Url",
|
||||
"username": "Nutzername"
|
||||
},
|
||||
"en": {
|
||||
"noHistory": "No changes so far",
|
||||
"changedProperty": "Changes",
|
||||
"createdAt": "changed at",
|
||||
"newValue": "new Value",
|
||||
"oldValue": "old Value",
|
||||
"password": "Password",
|
||||
"title": "Title",
|
||||
"url": "Url",
|
||||
"username": "Username"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
188
src/components/haex/pass/item/index.vue
Normal file
188
src/components/haex/pass/item/index.vue
Normal file
@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<UiCard
|
||||
body-class="rounded overflow-auto px-0 h-full"
|
||||
@close="onClose"
|
||||
>
|
||||
<div class="">
|
||||
<nav
|
||||
aria-label="Tabs Password Item"
|
||||
aria-orientation="horizontal"
|
||||
class="tabs tabs-bordered w-full transition-all duration-700 sticky top-0 z-10"
|
||||
role="tablist"
|
||||
>
|
||||
<button
|
||||
:id="id.details"
|
||||
aria-controls="vaultDetailsId"
|
||||
aria-selected="true"
|
||||
class="tab active-tab:tab-active active w-full"
|
||||
data-tab="#vaultDetailsId"
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
<Icon
|
||||
name="material-symbols:key-outline"
|
||||
class="me-2"
|
||||
/>
|
||||
<span class="hidden sm:block">
|
||||
{{ t('tab.details') }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
:id="id.keyValue"
|
||||
aria-controls="tabs-basic-2"
|
||||
aria-selected="false"
|
||||
class="tab active-tab:tab-active w-full"
|
||||
data-tab="#tabs-basic-2"
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
<Icon
|
||||
name="fluent:group-list-20-filled"
|
||||
class="me-2"
|
||||
/>
|
||||
<span class="hidden sm:block">
|
||||
{{ t('tab.keyValue') }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
:id="id.history"
|
||||
aria-controls="tabs-basic-3"
|
||||
aria-selected="false"
|
||||
class="tab active-tab:tab-active w-full"
|
||||
data-tab="#tabs-basic-3"
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
<Icon
|
||||
name="material-symbols:history"
|
||||
class="me-2"
|
||||
/>
|
||||
<span class="hidden sm:block">
|
||||
{{ t('tab.history') }}
|
||||
</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="h-full pb-8">
|
||||
<div
|
||||
id="vaultDetailsId"
|
||||
role="tabpanel"
|
||||
class="h-full"
|
||||
:aria-labelledby="id.details"
|
||||
>
|
||||
<HaexPassItemDetails
|
||||
v-if="details"
|
||||
v-model="details"
|
||||
with-copy-button
|
||||
:read_only
|
||||
:defaultIcon
|
||||
v-model:prevent-close="preventClose"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="tabs-basic-2"
|
||||
class="hidden"
|
||||
role="tabpanel"
|
||||
:aria-labelledby="id.keyValue"
|
||||
>
|
||||
<HaexPassItemKeyValue
|
||||
v-if="keyValues"
|
||||
v-model="keyValues"
|
||||
v-model:items-to-add="keyValuesAdd"
|
||||
v-model:items-to-delete="keyValuesDelete"
|
||||
:read_only
|
||||
:item-id="details!.id"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="tabs-basic-3"
|
||||
class="hidden h-full"
|
||||
role="tabpanel"
|
||||
:aria-labelledby="id.history"
|
||||
>
|
||||
<!-- <HaexPassItemHistory v-model="itemHistory" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
SelectHaexPasswordsItemDetails,
|
||||
SelectHaexPasswordsItemHistory,
|
||||
SelectHaexPasswordsItemKeyValues,
|
||||
} from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
defineProps<{
|
||||
defaultIcon?: string | null
|
||||
history: SelectHaexPasswordsItemHistory[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [void]
|
||||
addKeyValue: [void]
|
||||
removeKeyValue: [string]
|
||||
}>()
|
||||
|
||||
const read_only = defineModel<boolean>('read_only', { default: false })
|
||||
|
||||
const details = defineModel<SelectHaexPasswordsItemDetails | null>('details', {
|
||||
required: true,
|
||||
})
|
||||
|
||||
const keyValues = defineModel<SelectHaexPasswordsItemKeyValues[]>('keyValues', {
|
||||
default: [],
|
||||
})
|
||||
|
||||
const keyValuesAdd = defineModel<SelectHaexPasswordsItemKeyValues[]>(
|
||||
'keyValuesAdd',
|
||||
{ default: [] },
|
||||
)
|
||||
const keyValuesDelete = defineModel<SelectHaexPasswordsItemKeyValues[]>(
|
||||
'keyValuesDelete',
|
||||
{ default: [] },
|
||||
)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const id = reactive({
|
||||
details: useId(),
|
||||
keyValue: useId(),
|
||||
history: useId(),
|
||||
content: {},
|
||||
})
|
||||
|
||||
const preventClose = ref(false)
|
||||
|
||||
const onClose = () => {
|
||||
if (preventClose.value) return
|
||||
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"create": "Anlegen",
|
||||
"abort": "Abbrechen",
|
||||
"tab": {
|
||||
"details": "Details",
|
||||
"keyValue": "Extra",
|
||||
"history": "Verlauf"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"create": "Create",
|
||||
"abort": "Abort",
|
||||
"tab": {
|
||||
"details": "Details",
|
||||
"keyValue": "Extra",
|
||||
"history": "History"
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
128
src/components/haex/pass/item/keyValue.vue
Normal file
128
src/components/haex/pass/item/keyValue.vue
Normal file
@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<UiList
|
||||
v-if="items.length || itemsToAdd.length"
|
||||
class="flex-1"
|
||||
>
|
||||
<li
|
||||
v-for="item in [...items, ...itemsToAdd]"
|
||||
:key="item.id"
|
||||
:class="{ 'bg-primary/20': currentSelected === item }"
|
||||
class="flex gap-2 hover:bg-primary/20 px-4 items-center"
|
||||
@click="currentSelected = item"
|
||||
>
|
||||
<button class="link flex items-center no-underline w-full py-2">
|
||||
<input
|
||||
v-model="item.key"
|
||||
:readonly="currentSelected !== item || read_only"
|
||||
class="flex-1 cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<UiButton
|
||||
v-if="!read_only"
|
||||
:class="[currentSelected === item ? 'visible' : 'invisible']"
|
||||
class="inline-flex btn-square btn-error btn-outline"
|
||||
@click="deleteItem(item.id)"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:trash-outline"
|
||||
class="size-5"
|
||||
/>
|
||||
</UiButton>
|
||||
</li>
|
||||
</UiList>
|
||||
|
||||
<UiTextarea
|
||||
v-if="items.length || itemsToAdd.length"
|
||||
:read_only="read_only || !currentSelected"
|
||||
class="flex-1 min-w-52 border-base-content/25"
|
||||
rows="6"
|
||||
v-model="currentValue"
|
||||
with-copy-button
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-show="!read_only"
|
||||
class="flex py-4 gap-2 justify-center items-end flex-wrap"
|
||||
>
|
||||
<UiButton
|
||||
@click="addItem"
|
||||
class="btn-primary btn-outline flex-1-1 min-w-40"
|
||||
>
|
||||
<Icon name="mdi:plus" />
|
||||
<p class="hidden sm:inline-flex">{{ t('add') }}</p>
|
||||
</UiButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectHaexPasswordsItemKeyValues } from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
const { itemId } = defineProps<{ read_only?: boolean; itemId: string }>()
|
||||
|
||||
const items = defineModel<SelectHaexPasswordsItemKeyValues[]>({ default: [] })
|
||||
|
||||
const itemsToDelete = defineModel<SelectHaexPasswordsItemKeyValues[]>(
|
||||
'itemsToDelete',
|
||||
{ default: [] },
|
||||
)
|
||||
const itemsToAdd = defineModel<SelectHaexPasswordsItemKeyValues[]>(
|
||||
'itemsToAdd',
|
||||
{ default: [] },
|
||||
)
|
||||
|
||||
defineEmits<{ add: [void]; remove: [string] }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const currentSelected = ref<SelectHaexPasswordsItemKeyValues | undefined>(
|
||||
items.value?.at(0),
|
||||
)
|
||||
|
||||
watch(
|
||||
() => itemId,
|
||||
() => (currentSelected.value = items.value?.at(0)),
|
||||
)
|
||||
//const currentValue = computed(() => currentSelected.value?.value || '')
|
||||
const currentValue = computed({
|
||||
get: () => currentSelected.value?.value || '',
|
||||
set(newValue: string) {
|
||||
if (currentSelected.value) currentSelected.value.value = newValue
|
||||
},
|
||||
})
|
||||
|
||||
const addItem = () => {
|
||||
itemsToAdd.value?.push({
|
||||
id: crypto.randomUUID(),
|
||||
itemId,
|
||||
key: '',
|
||||
value: '',
|
||||
updateAt: null,
|
||||
})
|
||||
}
|
||||
|
||||
const deleteItem = (id: string) => {
|
||||
const item = items.value.find((item) => item.id === id)
|
||||
if (item) {
|
||||
itemsToDelete.value?.push(item)
|
||||
items.value = items.value.filter((item) => item.id !== id)
|
||||
}
|
||||
|
||||
itemsToAdd.value = itemsToAdd.value?.filter((item) => item.id !== id) ?? []
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
add: Hinzufügen
|
||||
key: Schlüssel
|
||||
value: Wert
|
||||
|
||||
en:
|
||||
add: Add
|
||||
key: Key
|
||||
value: Value
|
||||
</i18n>
|
||||
@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<button
|
||||
aria-disabled
|
||||
class="flex gap-4 w-full"
|
||||
@click="$emit('click', group)"
|
||||
>
|
||||
<Icon
|
||||
:name="groupIcon"
|
||||
size="24"
|
||||
class="shrink-0"
|
||||
/>
|
||||
<p class="w-full flex-1 text-start truncate">
|
||||
{{ group.name }}
|
||||
</p>
|
||||
|
||||
<Icon
|
||||
name="mdi:chevron-right"
|
||||
size="24"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectHaexPasswordsGroups } from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
defineEmits<{ click: [group: SelectHaexPasswordsGroups] }>()
|
||||
|
||||
const { group } = defineProps<{
|
||||
group: SelectHaexPasswordsGroups
|
||||
}>()
|
||||
|
||||
const groupIcon = computed(() => group.icon ?? 'mdi:folder-outline')
|
||||
</script>
|
||||
@ -1,78 +1,124 @@
|
||||
<template>
|
||||
<ul
|
||||
class="flex flex-col w-full h-full gap-y-2 *:first:rounded-t-md *:last:rounded-b-md"
|
||||
ref="listRef"
|
||||
<div v-if="menuItems?.length">
|
||||
<ul
|
||||
class="flex flex-col w-full h-full gap-y-2 *:first:rounded-t-md *:last:rounded-b-md"
|
||||
ref="listRef"
|
||||
>
|
||||
<li
|
||||
v-for="(item, index) in menuItems"
|
||||
:key="item.id"
|
||||
class="bg-base-100 rounded-lg hover:bg-base-content/20 origin-to intersect:motion-preset-slide-down intersect:motion-ease-spring-bouncier intersect:motion-delay ease-in-out shadow"
|
||||
:class="{
|
||||
'bg-base-content/30 outline outline-accent hover:bg-base-content/20':
|
||||
selectedItems.has(item) ||
|
||||
(currentSelectedItem?.id === item.id &&
|
||||
longPressedHook &&
|
||||
!selectedItems.has(item)),
|
||||
'opacity-60 shadow-accent': selectedGroupItems?.some(
|
||||
(_item) => _item.id === item.id,
|
||||
),
|
||||
}"
|
||||
:style="{ '--motion-delay': `${50 * index}ms` }"
|
||||
v-on-long-press="[
|
||||
onLongPressCallbackHook,
|
||||
{
|
||||
delay: 1000,
|
||||
},
|
||||
]"
|
||||
@mousedown="
|
||||
longPressedHook
|
||||
? (currentSelectedItem = null)
|
||||
: (currentSelectedItem = item)
|
||||
"
|
||||
>
|
||||
<HaexPassMobileMenuItem
|
||||
v-bind="item"
|
||||
@click="onClickItemAsync(item)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex justify-center items-center px-20 h-full"
|
||||
>
|
||||
<li
|
||||
v-for="(group, index) in groupItems.groups"
|
||||
class="bg-base-100 rounded-lg hover:bg-base-content/20 origin-to intersect:motion-preset-slide-down intersect:motion-ease-spring-bouncier intersect:motion-delay ease-in-out shadow"
|
||||
:class="{
|
||||
'bg-base-content/20 outline outline-accent hover:bg-base-content/20':
|
||||
selectedItems.has(group.id),
|
||||
}"
|
||||
:style="{ '--motion-delay': `${50 * index}ms` }"
|
||||
:key="group.id"
|
||||
v-on-long-press="[
|
||||
onLongPressCallbackHook,
|
||||
{
|
||||
delay: 1000,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<HaexPassMobileMenuGroup
|
||||
:group
|
||||
@click="onClickGroupAsync"
|
||||
class="px-4 py-2"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
v-for="item in groupItems.items"
|
||||
:key="item.id"
|
||||
>
|
||||
<HaexPassMobileMenuItem :item />
|
||||
</li>
|
||||
</ul>
|
||||
<UiIconNoData class="text-primary size-24 shrink-0" />
|
||||
<!-- <p>{{ t('empty') }}</p> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
SelectHaexPasswordsGroups,
|
||||
SelectHaexPasswordsItems,
|
||||
} from '~~/src-tauri/database/schemas/vault'
|
||||
|
||||
import { vOnLongPress } from '@vueuse/components'
|
||||
import type { IPasswordMenuItem } from './types'
|
||||
|
||||
defineProps<{
|
||||
groupItems: {
|
||||
items: SelectHaexPasswordsItems[]
|
||||
groups: SelectHaexPasswordsGroups[]
|
||||
}
|
||||
menuItems: IPasswordMenuItem[]
|
||||
}>()
|
||||
|
||||
const selectedItems = ref<Set<string>>(new Set())
|
||||
const longPressedHook = shallowRef(false)
|
||||
defineEmits(['add'])
|
||||
const selectedItems = defineModel<Set<IPasswordMenuItem>>('selectedItems', {
|
||||
default: new Set(),
|
||||
})
|
||||
|
||||
const currentSelectedItem = ref<IPasswordMenuItem | null>()
|
||||
|
||||
const longPressedHook = ref(false)
|
||||
|
||||
const onLongPressCallbackHook = (_: PointerEvent) => {
|
||||
longPressedHook.value = true
|
||||
}
|
||||
|
||||
watch(longPressedHook, () => {
|
||||
if (!longPressedHook.value) selectedItems.value.clear()
|
||||
})
|
||||
|
||||
watch(selectedItems, () => {
|
||||
if (!selectedItems.value.size) longPressedHook.value = false
|
||||
})
|
||||
|
||||
const localePath = useLocalePath()
|
||||
const onClickGroupAsync = async (group: SelectHaexPasswordsGroups) => {
|
||||
if (longPressedHook.value) {
|
||||
if (selectedItems.value.has(group.id)) {
|
||||
selectedItems.value.delete(group.id)
|
||||
const { ctrl } = useMagicKeys()
|
||||
|
||||
const onClickItemAsync = async (item: IPasswordMenuItem) => {
|
||||
currentSelectedItem.value = null
|
||||
|
||||
if (longPressedHook.value || selectedItems.value.size || ctrl.value) {
|
||||
if (selectedItems.value?.has(item)) {
|
||||
selectedItems.value.delete(item)
|
||||
} else {
|
||||
selectedItems.value.add(group.id)
|
||||
selectedItems.value?.add(item)
|
||||
}
|
||||
|
||||
if (!selectedItems.value.size) longPressedHook.value = false
|
||||
} else {
|
||||
await navigateTo(localePath({ name: 'passwordGroupEdit' }))
|
||||
if (item.type === 'group')
|
||||
await navigateTo(
|
||||
localePath({
|
||||
name: 'passwordGroupItems',
|
||||
params: {
|
||||
...useRouter().currentRoute.value.params,
|
||||
groupId: item.id,
|
||||
},
|
||||
}),
|
||||
)
|
||||
else {
|
||||
await navigateTo(
|
||||
localePath({
|
||||
name: 'passwordItemEdit',
|
||||
params: { ...useRouter().currentRoute.value.params, itemId: item.id },
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const listRef = useTemplateRef('listRef')
|
||||
onClickOutside(listRef, () => {
|
||||
selectedItems.value.clear()
|
||||
longPressedHook.value = false
|
||||
onClickOutside(listRef, async () => {
|
||||
// needed cause otherwise the unselect is to fast for other processing like "edit selected group"
|
||||
setTimeout(() => {
|
||||
longPressedHook.value = false
|
||||
}, 50)
|
||||
})
|
||||
|
||||
const { selectedGroupItems } = storeToRefs(usePasswordGroupStore())
|
||||
</script>
|
||||
|
||||
@ -1,13 +1,40 @@
|
||||
<template>
|
||||
<NuxtLinkLocale>
|
||||
{{ item.title }}
|
||||
</NuxtLinkLocale>
|
||||
<button
|
||||
aria-disabled
|
||||
class="flex gap-4 w-full px-4 py-2"
|
||||
@click="$emit('click', menuItem)"
|
||||
:style="{ color: menuItem.color ?? '' }"
|
||||
>
|
||||
<Icon
|
||||
:name="menuIcon"
|
||||
size="24"
|
||||
class="shrink-0"
|
||||
/>
|
||||
<p class="w-full flex-1 text-start truncate font-bold">
|
||||
{{ menuItem?.name }}
|
||||
</p>
|
||||
|
||||
<Icon
|
||||
v-if="menuItem.type === 'group'"
|
||||
name="mdi:chevron-right"
|
||||
size="24"
|
||||
class="text-base-content"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectHaexPasswordsItems } from '~~/src-tauri/database/schemas/vault'
|
||||
import type { IPasswordMenuItem } from './types'
|
||||
|
||||
defineProps<{
|
||||
item: SelectHaexPasswordsItems
|
||||
}>()
|
||||
defineEmits<{ click: [group?: IPasswordMenuItem] }>()
|
||||
|
||||
const menuItem = defineProps<IPasswordMenuItem>()
|
||||
|
||||
const menuIcon = computed(() =>
|
||||
menuItem?.icon
|
||||
? menuItem.icon
|
||||
: menuItem.type === 'group'
|
||||
? 'mdi:folder-outline'
|
||||
: 'mdi:key-outline',
|
||||
)
|
||||
</script>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
export interface IPassMenuItem {
|
||||
name: string
|
||||
type: 'group' | 'item'
|
||||
export interface IPasswordMenuItem {
|
||||
color?: string | null
|
||||
icon: string | null
|
||||
id: string
|
||||
name: string | null
|
||||
type: 'group' | 'item'
|
||||
}
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="z-10">
|
||||
<div class="z-10 pointer-events-auto">
|
||||
<div
|
||||
class="dropdown relative inline-flex [--placement:top] [--strategy:absolute]"
|
||||
>
|
||||
<button
|
||||
:id
|
||||
class="dropdown-toggle btn btn-primary btn-lg btn-square dropdown-open:rotate-45 transition-transform"
|
||||
class="dropdown-toggle btn btn-primary btn-xl btn-square dropdown-open:rotate-45 transition-transform"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
aria-label="Menu"
|
||||
>
|
||||
<Icon
|
||||
:name="icon"
|
||||
size="46"
|
||||
class="size-11 shrink-0"
|
||||
/>
|
||||
</button>
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
>
|
||||
<li
|
||||
v-for="link in menu"
|
||||
class="dropdown-item hover:bg-transparent px-0"
|
||||
class="dropdown-item hover:bg-transparent px-0 py-1"
|
||||
>
|
||||
<NuxtLinkLocale
|
||||
v-if="link.to"
|
||||
|
||||
@ -1,14 +1,22 @@
|
||||
<template>
|
||||
<button class="btn join-item" :type>
|
||||
<slot />
|
||||
<button
|
||||
class="btn join-item pointer-events-auto"
|
||||
:type
|
||||
>
|
||||
<UiTooltip
|
||||
:tooltip
|
||||
v-if="tooltip"
|
||||
>
|
||||
<slot />
|
||||
</UiTooltip>
|
||||
|
||||
<slot v-else />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
type: {
|
||||
type: String as PropType<"reset" | "submit" | "button">,
|
||||
default: "button",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
const { type = 'button' } = defineProps<{
|
||||
type?: 'reset' | 'submit' | 'button'
|
||||
tooltip?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@ -2,7 +2,10 @@
|
||||
<div class="card">
|
||||
<slot name="image" />
|
||||
|
||||
<div class="card-header">
|
||||
<div
|
||||
class="card-header"
|
||||
v-if="$slots.title || title"
|
||||
>
|
||||
<slot name="header">
|
||||
<div
|
||||
v-if="$slots.title || title"
|
||||
@ -28,7 +31,10 @@
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="card-body px-2 sm:px-6">
|
||||
<div
|
||||
class="card-body"
|
||||
:class="bodyClass"
|
||||
>
|
||||
<slot />
|
||||
<div
|
||||
v-if="$slots.action"
|
||||
@ -37,13 +43,25 @@
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="$slots.footer"
|
||||
class="card-footer"
|
||||
>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits(['close', 'submit'])
|
||||
|
||||
defineProps<{ title?: string; subtitle?: string; icon?: string }>()
|
||||
defineProps<{
|
||||
title?: string
|
||||
subtitle?: string
|
||||
icon?: string
|
||||
bodyClass?: string
|
||||
}>()
|
||||
|
||||
const { escape, enter } = useMagicKeys()
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<UiDialog
|
||||
v-model:open="open"
|
||||
:title
|
||||
@close="onAbort"
|
||||
v-model:open="open"
|
||||
>
|
||||
<template #trigger>
|
||||
<slot name="trigger" />
|
||||
@ -33,19 +34,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ confirmLabel?: string; abortLabel?: string }>()
|
||||
defineProps<{ confirmLabel?: string; abortLabel?: string; title?: string }>()
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
|
||||
const { t } = useI18n()
|
||||
const emit = defineEmits(['confirm', 'abort'])
|
||||
|
||||
const onAbort = () => {
|
||||
emit('abort')
|
||||
open.value = false
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
emit('confirm')
|
||||
open.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
:aria-label="label"
|
||||
class="--prevent-on-load-init"
|
||||
@click="$emit('open')"
|
||||
>
|
||||
<slot name="trigger">
|
||||
@ -14,55 +13,58 @@
|
||||
</slot>
|
||||
</button>
|
||||
|
||||
<div
|
||||
:id
|
||||
ref="modalRef"
|
||||
class="overlay modal overlay-open:opacity-100 hidden overlay-open:duration-300 sm:modal-middle p-0 xs:p-2"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
class="overlay-animation-target overlay-open:duration-300 transition-all ease-out modal-dialog overlay-open:opacity-100 pointer-events-auto overflow-y-auto"
|
||||
:id
|
||||
ref="modalRef"
|
||||
class="overlay modal overlay-open:opacity-100 overlay-open:duration-300 hidden modal-middle p-0 xs:p-2 --prevent-on-load-init pointer-events-auto max-w-none"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="modal-content justify-between h-full max-h-none">
|
||||
<div class="modal-header">
|
||||
<div
|
||||
v-if="title || $slots.title"
|
||||
class="modal-title py-4 break-all"
|
||||
>
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
<div
|
||||
class="overlay-animation-target overlay-open:duration-300 overlay-open:opacity-100 transition-all ease-out modal-dialog"
|
||||
>
|
||||
<div class="modal-content justify-between">
|
||||
<div class="modal-header">
|
||||
<div
|
||||
v-if="title || $slots.title"
|
||||
class="modal-title py-4 break-all"
|
||||
>
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
|
||||
:aria-label="t('close')"
|
||||
tabindex="1"
|
||||
@click="open = false"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:close"
|
||||
size="18"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
|
||||
:aria-label="t('close')"
|
||||
tabindex="1"
|
||||
@click="open = false"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:close"
|
||||
size="18"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body text-sm sm:text-base grow">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="modal-body text-sm sm:text-base">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="modal-footer flex-col sm:flex-row">
|
||||
<slot name="buttons" />
|
||||
<div class="modal-footer flex-col sm:flex-row">
|
||||
<slot name="buttons" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HSOverlay } from 'flyonui/flyonui'
|
||||
const { currentTheme } = storeToRefs(useUiStore())
|
||||
|
||||
defineProps<{ title?: string; label?: string }>()
|
||||
|
||||
@ -81,10 +83,12 @@ defineExpose({ modalRef })
|
||||
const modal = ref<HSOverlay>()
|
||||
|
||||
watch(open, async () => {
|
||||
if (!modal.value) return
|
||||
|
||||
if (open.value) {
|
||||
await modal.value?.open()
|
||||
await modal.value.open()
|
||||
} else {
|
||||
await modal.value?.close(true)
|
||||
await modal.value.close(true)
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
@ -92,10 +96,9 @@ watch(open, async () => {
|
||||
onMounted(async () => {
|
||||
if (!modalRef.value) return
|
||||
|
||||
modal.value = new window.HSOverlay(modalRef.value, {
|
||||
isClosePrev: true,
|
||||
})
|
||||
modal.value = new window.HSOverlay(modalRef.value)
|
||||
|
||||
modal.value.isLayoutAffect = true
|
||||
modal.value.on('close', () => {
|
||||
open.value = false
|
||||
})
|
||||
|
||||
55
src/components/ui/dialog/passwordGenerator.vue
Normal file
55
src/components/ui/dialog/passwordGenerator.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<UiDialogConfirm
|
||||
:confirm-label="t('apply')"
|
||||
:title="t('title')"
|
||||
@abort="open = false"
|
||||
@click="open = true"
|
||||
class="btn btn-square btn-accent btn-outline"
|
||||
v-model:open="open"
|
||||
>
|
||||
<template #trigger>
|
||||
<Icon name="mdi:dice" />
|
||||
</template>
|
||||
|
||||
<form class="flex flex-col gap-4">
|
||||
<UiInputPassword
|
||||
v-model="newPassword"
|
||||
prepend-icon="mdi:key-outline"
|
||||
with-copy-button
|
||||
>
|
||||
<template #append>
|
||||
<UiButton class="btn-square btn-accent btn-outline">
|
||||
<Icon name="mdi:refresh" />
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiInputPassword>
|
||||
</form>
|
||||
</UiDialogConfirm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const open = defineModel<boolean>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { password } = defineProps<{
|
||||
autofocus?: boolean
|
||||
checkInput?: boolean
|
||||
label?: string
|
||||
placeholder?: string
|
||||
withCopyButton?: boolean
|
||||
password: string | null
|
||||
}>()
|
||||
|
||||
const newPassword = computed(() => password)
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
title: Passwortgenerator
|
||||
apply: Übernehmen
|
||||
|
||||
en:
|
||||
title: Passwordgenerator
|
||||
apply: Apply
|
||||
</i18n>
|
||||
@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
class="dropdown relative inline-flex"
|
||||
:class="offset"
|
||||
class="dropdown relative inline-flex"
|
||||
>
|
||||
<button
|
||||
:id
|
||||
class="dropdown-toggle"
|
||||
v-bind="$attrs"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
:aria-label="label"
|
||||
:id
|
||||
aria-expanded="false"
|
||||
aria-haspopup="menu"
|
||||
class="dropdown-toggle"
|
||||
type="button"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot name="activator">
|
||||
{{ label }}
|
||||
@ -20,21 +21,24 @@
|
||||
</button>
|
||||
|
||||
<ul
|
||||
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-28"
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
:aria-labelledby="id"
|
||||
aria-orientation="vertical"
|
||||
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-28 z-20 shadow shadow-primary"
|
||||
role="menu"
|
||||
>
|
||||
<slot name="items">
|
||||
<slot
|
||||
name="items"
|
||||
:items
|
||||
>
|
||||
<li
|
||||
:is="itemIs"
|
||||
v-for="item in items"
|
||||
class="dropdown-item"
|
||||
@click="$emit('select', item)"
|
||||
class="dropdown-item"
|
||||
v-for="item in items"
|
||||
>
|
||||
<slot
|
||||
name="item"
|
||||
:item
|
||||
name="item"
|
||||
>
|
||||
{{ item }}
|
||||
</slot>
|
||||
@ -45,6 +49,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T">
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const { itemIs = 'li', offset = '[--offset:0]' } = defineProps<{
|
||||
label?: string
|
||||
items?: T[]
|
||||
@ -53,10 +61,6 @@ const { itemIs = 'li', offset = '[--offset:0]' } = defineProps<{
|
||||
offset?: string
|
||||
}>()
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
defineEmits<{ select: [T] }>()
|
||||
|
||||
const id = useId()
|
||||
|
||||
132
src/components/ui/icon/addFiles.vue
Normal file
132
src/components/ui/icon/addFiles.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 782.04441 701.88002"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
role="img"
|
||||
artist="Katerina Limpitsouni"
|
||||
source="https://undraw.co/"
|
||||
>
|
||||
<path
|
||||
d="M609.48783,100.59015l-25.44631,6.56209L270.53735,187.9987,245.091,194.56079A48.17927,48.17927,0,0,0,210.508,253.17865L320.849,681.05606a48.17924,48.17924,0,0,0,58.61776,34.58317l.06572-.01695,364.26536-93.93675.06572-.01695a48.17923,48.17923,0,0,0,34.58309-58.6178l-110.341-427.87741A48.17928,48.17928,0,0,0,609.48783,100.59015Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#f2f2f2"
|
||||
/>
|
||||
<path
|
||||
d="M612.94784,114.00532l-30.13945,7.77236L278.68955,200.20385l-30.139,7.77223a34.30949,34.30949,0,0,0-24.6275,41.74308l110.341,427.87741a34.30946,34.30946,0,0,0,41.7431,24.62736l.06572-.01695,364.26536-93.93674.06619-.01707a34.30935,34.30935,0,0,0,24.627-41.7429l-110.341-427.87741A34.30938,34.30938,0,0,0,612.94784,114.00532Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M590.19,252.56327,405.917,300.08359a8.01411,8.01411,0,0,1-4.00241-15.52046l184.273-47.52033A8.01412,8.01412,0,0,1,590.19,252.56327Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#f2f2f2"
|
||||
/>
|
||||
<path
|
||||
d="M628.955,270.49906,412.671,326.27437a8.01411,8.01411,0,1,1-4.00241-15.52046l216.284-55.77531a8.01411,8.01411,0,0,1,4.00242,15.52046Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#f2f2f2"
|
||||
/>
|
||||
<path
|
||||
d="M620.45825,369.93676l-184.273,47.52032a8.01411,8.01411,0,1,1-4.00242-15.52046l184.273-47.52032a8.01411,8.01411,0,1,1,4.00241,15.52046Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#f2f2f2"
|
||||
/>
|
||||
<path
|
||||
d="M659.22329,387.87255l-216.284,55.77531a8.01411,8.01411,0,1,1-4.00242-15.52046l216.284-55.77531a8.01411,8.01411,0,0,1,4.00242,15.52046Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#f2f2f2"
|
||||
/>
|
||||
<path
|
||||
d="M650.72653,487.31025l-184.273,47.52033a8.01412,8.01412,0,0,1-4.00242-15.52047l184.273-47.52032a8.01411,8.01411,0,0,1,4.00242,15.52046Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#f2f2f2"
|
||||
/>
|
||||
<path
|
||||
d="M689.49156,505.246l-216.284,55.77532a8.01412,8.01412,0,1,1-4.00241-15.52047l216.284-55.77531a8.01411,8.01411,0,0,1,4.00242,15.52046Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#f2f2f2"
|
||||
/>
|
||||
<path
|
||||
d="M374.45884,348.80871l-65.21246,16.817a3.847,3.847,0,0,1-4.68062-2.76146L289.5963,304.81607a3.847,3.847,0,0,1,2.76145-4.68061l65.21247-16.817a3.847,3.847,0,0,1,4.68061,2.76145l14.96947,58.04817A3.847,3.847,0,0,1,374.45884,348.80871Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M404.72712,466.1822l-65.21247,16.817a3.847,3.847,0,0,1-4.68062-2.76146l-14.96946-58.04816A3.847,3.847,0,0,1,322.626,417.509l65.21246-16.817a3.847,3.847,0,0,1,4.68062,2.76145l14.96946,58.04817A3.847,3.847,0,0,1,404.72712,466.1822Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M434.99539,583.55569l-65.21246,16.817a3.847,3.847,0,0,1-4.68062-2.76145l-14.96946-58.04817a3.847,3.847,0,0,1,2.76145-4.68062l65.21247-16.817a3.847,3.847,0,0,1,4.68061,2.76146l14.96947,58.04816A3.847,3.847,0,0,1,434.99539,583.55569Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M863.63647,209.0517H487.31811a48.17928,48.17928,0,0,0-48.125,48.12512V699.05261a48.17924,48.17924,0,0,0,48.125,48.12507H863.63647a48.17924,48.17924,0,0,0,48.125-48.12507V257.17682A48.17928,48.17928,0,0,0,863.63647,209.0517Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M863.637,222.90589H487.31811a34.30948,34.30948,0,0,0-34.271,34.27093V699.05261a34.30947,34.30947,0,0,0,34.271,34.27088H863.637a34.30936,34.30936,0,0,0,34.27051-34.27088V257.17682A34.30937,34.30937,0,0,0,863.637,222.90589Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#fff"
|
||||
/>
|
||||
<circle
|
||||
cx="694.19401"
|
||||
cy="614.02963"
|
||||
r="87.85039"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M945.18722,701.63087H914.63056V671.07421a11.45875,11.45875,0,0,0-22.9175,0v30.55666H861.1564a11.45875,11.45875,0,0,0,0,22.9175h30.55666V755.105a11.45875,11.45875,0,1,0,22.9175,0V724.54837h30.55666a11.45875,11.45875,0,0,0,0-22.9175Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M807.00068,465.71551H616.699a8.01412,8.01412,0,1,1,0-16.02823H807.00068a8.01412,8.01412,0,0,1,0,16.02823Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M840.05889,492.76314H616.699a8.01412,8.01412,0,1,1,0-16.02823H840.05889a8.01411,8.01411,0,1,1,0,16.02823Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M807.00068,586.929H616.699a8.01412,8.01412,0,1,1,0-16.02823H807.00068a8.01411,8.01411,0,0,1,0,16.02823Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M840.05889,613.97661H616.699a8.01412,8.01412,0,1,1,0-16.02823H840.05889a8.01412,8.01412,0,1,1,0,16.02823Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M574.07028,505.04162H506.72434a3.847,3.847,0,0,1-3.84278-3.84278V441.25158a3.847,3.847,0,0,1,3.84278-3.84278h67.34594a3.847,3.847,0,0,1,3.84278,3.84278v59.94726A3.847,3.847,0,0,1,574.07028,505.04162Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M574.07028,626.25509H506.72434a3.847,3.847,0,0,1-3.84278-3.84278V562.46505a3.847,3.847,0,0,1,3.84278-3.84278h67.34594a3.847,3.847,0,0,1,3.84278,3.84278v59.94726A3.847,3.847,0,0,1,574.07028,626.25509Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M807.21185,330.781H666.91017a8.01411,8.01411,0,0,1,0-16.02823H807.21185a8.01411,8.01411,0,0,1,0,16.02823Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#ccc"
|
||||
/>
|
||||
<path
|
||||
d="M840.27007,357.82862H666.91017a8.01411,8.01411,0,1,1,0-16.02822h173.3599a8.01411,8.01411,0,0,1,0,16.02822Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="#ccc"
|
||||
/>
|
||||
<path
|
||||
d="M635.85911,390.6071H506.51316a3.847,3.847,0,0,1-3.84277-3.84277V285.81706a3.847,3.847,0,0,1,3.84277-3.84277H635.85911a3.847,3.847,0,0,1,3.84277,3.84277V386.76433A3.847,3.847,0,0,1,635.85911,390.6071Z"
|
||||
transform="translate(-208.9778 -99.05999)"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
65
src/components/ui/icon/noData.vue
Normal file
65
src/components/ui/icon/noData.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 647.63626 632.17383"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
role="img"
|
||||
artist="Katerina Limpitsouni"
|
||||
source="https://undraw.co/"
|
||||
>
|
||||
<path
|
||||
d="M687.3279,276.08691H512.81813a15.01828,15.01828,0,0,0-15,15v387.85l-2,.61005-42.81006,13.11a8.00676,8.00676,0,0,1-9.98974-5.31L315.678,271.39691a8.00313,8.00313,0,0,1,5.31006-9.99l65.97022-20.2,191.25-58.54,65.96972-20.2a7.98927,7.98927,0,0,1,9.99024,5.3l32.5498,106.32Z"
|
||||
transform="translate(-276.18187 -133.91309)"
|
||||
fill="#f2f2f2"
|
||||
/>
|
||||
<path
|
||||
d="M725.408,274.08691l-39.23-128.14a16.99368,16.99368,0,0,0-21.23-11.28l-92.75,28.39L380.95827,221.60693l-92.75,28.4a17.0152,17.0152,0,0,0-11.28028,21.23l134.08008,437.93a17.02661,17.02661,0,0,0,16.26026,12.03,16.78926,16.78926,0,0,0,4.96972-.75l63.58008-19.46,2-.62v-2.09l-2,.61-64.16992,19.65a15.01489,15.01489,0,0,1-18.73-9.95l-134.06983-437.94a14.97935,14.97935,0,0,1,9.94971-18.73l92.75-28.4,191.24024-58.54,92.75-28.4a15.15551,15.15551,0,0,1,4.40966-.66,15.01461,15.01461,0,0,1,14.32032,10.61l39.0498,127.56.62012,2h2.08008Z"
|
||||
transform="translate(-276.18187 -133.91309)"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<path
|
||||
d="M398.86279,261.73389a9.0157,9.0157,0,0,1-8.61133-6.3667l-12.88037-42.07178a8.99884,8.99884,0,0,1,5.9712-11.24023l175.939-53.86377a9.00867,9.00867,0,0,1,11.24072,5.9707l12.88037,42.07227a9.01029,9.01029,0,0,1-5.9707,11.24072L401.49219,261.33887A8.976,8.976,0,0,1,398.86279,261.73389Z"
|
||||
transform="translate(-276.18187 -133.91309)"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<circle
|
||||
cx="190.15351"
|
||||
cy="24.95465"
|
||||
r="20"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<circle
|
||||
cx="190.15351"
|
||||
cy="24.95465"
|
||||
r="12.66462"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M878.81836,716.08691h-338a8.50981,8.50981,0,0,1-8.5-8.5v-405a8.50951,8.50951,0,0,1,8.5-8.5h338a8.50982,8.50982,0,0,1,8.5,8.5v405A8.51013,8.51013,0,0,1,878.81836,716.08691Z"
|
||||
transform="translate(-276.18187 -133.91309)"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M723.31813,274.08691h-210.5a17.02411,17.02411,0,0,0-17,17v407.8l2-.61v-407.19a15.01828,15.01828,0,0,1,15-15H723.93825Zm183.5,0h-394a17.02411,17.02411,0,0,0-17,17v458a17.0241,17.0241,0,0,0,17,17h394a17.0241,17.0241,0,0,0,17-17v-458A17.02411,17.02411,0,0,0,906.81813,274.08691Zm15,475a15.01828,15.01828,0,0,1-15,15h-394a15.01828,15.01828,0,0,1-15-15v-458a15.01828,15.01828,0,0,1,15-15h394a15.01828,15.01828,0,0,1,15,15Z"
|
||||
transform="translate(-276.18187 -133.91309)"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<path
|
||||
d="M801.81836,318.08691h-184a9.01015,9.01015,0,0,1-9-9v-44a9.01016,9.01016,0,0,1,9-9h184a9.01016,9.01016,0,0,1,9,9v44A9.01015,9.01015,0,0,1,801.81836,318.08691Z"
|
||||
transform="translate(-276.18187 -133.91309)"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<circle
|
||||
cx="433.63626"
|
||||
cy="105.17383"
|
||||
r="20"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<circle
|
||||
cx="433.63626"
|
||||
cy="105.17383"
|
||||
r="12.18187"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@ -29,8 +29,9 @@
|
||||
<label
|
||||
class="input-floating-label"
|
||||
:for="id"
|
||||
>{{ label }}</label
|
||||
>
|
||||
{{ label }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Icon
|
||||
@ -47,7 +48,8 @@
|
||||
|
||||
<UiButton
|
||||
v-if="withCopyButton"
|
||||
class="btn-outline btn-accent btn-square join-item h-auto"
|
||||
:tooltip="t('copy')"
|
||||
class="btn-outline btn-accent btn-square"
|
||||
@click="copy(`${input}`)"
|
||||
>
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
@ -167,4 +169,14 @@ const checkInput = () => {
|
||||
}
|
||||
|
||||
const { copy, copied } = useClipboard()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
copy: Kopieren
|
||||
|
||||
en:
|
||||
copy: Copy
|
||||
</i18n>
|
||||
|
||||
@ -1,9 +1,21 @@
|
||||
<template>
|
||||
<UiInput
|
||||
v-model="value" :check-input :label="label || t('password')" :placeholder="placeholder || t('password')" :rules
|
||||
:type="type" :autofocus>
|
||||
v-model="value"
|
||||
:autofocus
|
||||
:check-input
|
||||
:label="label || t('password')"
|
||||
:placeholder="placeholder || t('password')"
|
||||
:rules
|
||||
:type="type"
|
||||
:with-copy-button
|
||||
>
|
||||
<template #append>
|
||||
<UiButton class="btn-outline btn-accent btn-square h-auto" @click="tooglePasswordType">
|
||||
<slot name="append" />
|
||||
|
||||
<UiButton
|
||||
class="btn-outline btn-accent btn-square join-item"
|
||||
@click="tooglePasswordType"
|
||||
>
|
||||
<Icon :name="type === 'password' ? 'mdi:eye-off' : 'mdi:eye'" />
|
||||
</UiButton>
|
||||
</template>
|
||||
@ -11,33 +23,35 @@ v-model="value" :check-input :label="label || t('password')" :placeholder="place
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ZodSchema } from "zod";
|
||||
import type { ZodSchema } from 'zod'
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n()
|
||||
|
||||
const value = defineModel<string | number | null | undefined>();
|
||||
const value = defineModel<string | number | null | undefined>()
|
||||
|
||||
defineProps({
|
||||
label: String,
|
||||
placeholder: String,
|
||||
checkInput: Boolean,
|
||||
rules: Object as PropType<ZodSchema>,
|
||||
autofocus: Boolean,
|
||||
});
|
||||
defineProps<{
|
||||
autofocus?: boolean
|
||||
checkInput?: boolean
|
||||
label?: string
|
||||
placeholder?: string
|
||||
rules?: ZodSchema
|
||||
withCopyButton?: boolean
|
||||
}>()
|
||||
|
||||
const type = ref<"password" | "text">("password");
|
||||
const type = ref<'password' | 'text'>('password')
|
||||
|
||||
const tooglePasswordType = () => {
|
||||
type.value = type.value === "password" ? "text" : "password";
|
||||
};
|
||||
|
||||
type.value = type.value === 'password' ? 'text' : 'password'
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="json">{
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"password": "Passwort"
|
||||
},
|
||||
"en": {
|
||||
"password": "Password"
|
||||
}
|
||||
}</i18n>
|
||||
}
|
||||
</i18n>
|
||||
|
||||
52
src/components/ui/input/url.vue
Normal file
52
src/components/ui/input/url.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<UiInput
|
||||
:autofocus
|
||||
:check-input="checkInput"
|
||||
:label="label || t('url')"
|
||||
:placeholder="placeholder || t('url')"
|
||||
:read_only
|
||||
:rules
|
||||
:with-copy-button
|
||||
v-model.trim="value"
|
||||
>
|
||||
<template #append>
|
||||
<UiButton
|
||||
:disabled="!value?.length"
|
||||
@click="openUrl(`${value}`)"
|
||||
class="btn-outline btn-accent btn-square"
|
||||
>
|
||||
<Icon name="streamline:web" />
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiInput>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ZodSchema } from 'zod'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const value = defineModel<string | null | undefined>()
|
||||
|
||||
defineProps({
|
||||
label: String,
|
||||
placeholder: String,
|
||||
checkInput: Boolean,
|
||||
rules: Object as PropType<ZodSchema>,
|
||||
autofocus: Boolean,
|
||||
withCopyButton: Boolean,
|
||||
read_only: Boolean,
|
||||
})
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"url": "Url"
|
||||
},
|
||||
"en": {
|
||||
"url": "Url"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
7
src/components/ui/list/index.vue
Normal file
7
src/components/ui/list/index.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col h-fit border border-base-content/25 divide-base-content/25 divide-y rounded-md first:rounded-t-md last:rounded-b-md"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@ -1,29 +1,34 @@
|
||||
<template>
|
||||
<div class="flex items-center gap-4">
|
||||
<label
|
||||
:for="id"
|
||||
class="font-medium"
|
||||
<div class="flex items-center gap-4 relative">
|
||||
<UiButton
|
||||
:style="{ 'background-color': model }"
|
||||
:class="[textColorClass]"
|
||||
@click="colorRef?.click()"
|
||||
>
|
||||
{{ t('label') }}
|
||||
</label>
|
||||
</UiButton>
|
||||
|
||||
<input
|
||||
:id
|
||||
:readonly="read_only"
|
||||
:disabled="read_only"
|
||||
:title="t('title')"
|
||||
class="p-0 cursor-pointer disabled:opacity-50 disabled:pointer-events-none w-14 h-10"
|
||||
:title="t('pick')"
|
||||
class="top-0 left-0 absolute size-0"
|
||||
type="color"
|
||||
v-model="model"
|
||||
ref="colorRef"
|
||||
/>
|
||||
|
||||
<button
|
||||
@click="model = null"
|
||||
class="btn btn-sm text-sm"
|
||||
:class="{ 'btn-disabled': read_only }"
|
||||
>
|
||||
{{ t('reset') }}
|
||||
</button>
|
||||
<UiTooltip :tooltip="t('reset')">
|
||||
<button
|
||||
@click="model = ''"
|
||||
class="btn btn-sm text-sm btn-outline btn-error"
|
||||
:class="{ 'btn-disabled': read_only }"
|
||||
type="button"
|
||||
>
|
||||
<Icon name="mdi:refresh" />
|
||||
</button>
|
||||
</UiTooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -31,11 +36,20 @@
|
||||
const id = useId()
|
||||
const { t } = useI18n()
|
||||
|
||||
const model = defineModel()
|
||||
|
||||
const model = defineModel<string | null>()
|
||||
const colorRef = useTemplateRef('colorRef')
|
||||
defineProps({
|
||||
read_only: Boolean,
|
||||
})
|
||||
|
||||
const { currentTheme } = storeToRefs(useUiStore())
|
||||
const textColorClass = computed(() => {
|
||||
if (!model.value)
|
||||
return currentTheme.value.value === 'dark' ? 'text-black' : 'text-white'
|
||||
|
||||
const color = getContrastingTextColor(model.value)
|
||||
return color === 'white' ? 'text-white' : 'text-black'
|
||||
})
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
@ -43,12 +57,14 @@ defineProps({
|
||||
"de": {
|
||||
"label": "Farbauswahl",
|
||||
"title": "Wähle eine Farbe aus",
|
||||
"reset": "zurücksetzen"
|
||||
"reset": "zurücksetzen",
|
||||
"pick": "Auswahl"
|
||||
},
|
||||
"en": {
|
||||
"label": "Color Picker",
|
||||
"title": "Choose a color",
|
||||
"reset": "Reset"
|
||||
"reset": "Reset",
|
||||
"pick": "Pick"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@ -1,37 +1,77 @@
|
||||
<template>
|
||||
<UiSelect
|
||||
v-model="icon"
|
||||
:options="icons"
|
||||
label="Icon Picker"
|
||||
<UiDropdown
|
||||
:items="icons"
|
||||
class="btn"
|
||||
@select="(newIcon) => (iconName = newIcon)"
|
||||
>
|
||||
<template #value="{ value }">
|
||||
<Icon
|
||||
:name="value"
|
||||
v-if="value"
|
||||
/>
|
||||
<template #activator>
|
||||
<Icon :name="iconName ? iconName : defaultIcon || icons.at(0)" />
|
||||
</template>
|
||||
<template #option="{ option }">
|
||||
<Icon :name="option ?? ''" />
|
||||
</template>
|
||||
</UiSelect>
|
||||
|
||||
<UiDropdown :items="icons">
|
||||
<template #activator> {{ icons.find((_icon) => _icon === icon) }}</template>
|
||||
<template #item="{ item }">
|
||||
<Icon :name="`mdi:${item}`" />
|
||||
{{ item }}
|
||||
<template #items="{ items }">
|
||||
<div class="grid grid-cols-6 -ml-2">
|
||||
<li
|
||||
class="dropdown-item"
|
||||
v-for="item in items"
|
||||
@click="iconName = item"
|
||||
>
|
||||
<Icon
|
||||
:name="item"
|
||||
size="36"
|
||||
/>
|
||||
</li>
|
||||
</div>
|
||||
</template>
|
||||
</UiDropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const icons = [
|
||||
'streamline:money-bank-institution-money-saving-bank-payment-finance',
|
||||
'material-symbols:star-outline-rounded',
|
||||
'mdi:folder-outline',
|
||||
'mdi:key-outline',
|
||||
'pepicons-pop:smartphone-home-button',
|
||||
'majesticons:desktop-computer-line',
|
||||
'mdi:folder',
|
||||
'mdi:amazon',
|
||||
'proicons:bank',
|
||||
'mdi:bitcoin',
|
||||
'mdi:piggy-bank-outline',
|
||||
'mdi:account-outline',
|
||||
'proicons:computer',
|
||||
'proicons:cloud',
|
||||
'proicons:game',
|
||||
'proicons:github',
|
||||
'proicons:wrench',
|
||||
'proicons:vehicle-car',
|
||||
'proicons:wi-fi',
|
||||
'meteor-icons:microchip',
|
||||
'meteor-icons:headphones',
|
||||
'meteor-icons:star',
|
||||
'fe:mail',
|
||||
'fe:rocket',
|
||||
'fxemoji:trolleybus',
|
||||
'mdi:folder-outline',
|
||||
'mdi:key-outline',
|
||||
'pepicons-pop:smartphone-home-button',
|
||||
'mdi:amazon',
|
||||
'proicons:bank',
|
||||
'mdi:bitcoin',
|
||||
'mdi:piggy-bank-outline',
|
||||
'mdi:account-outline',
|
||||
'proicons:computer',
|
||||
'proicons:cloud',
|
||||
'proicons:game',
|
||||
'proicons:github',
|
||||
'proicons:wrench',
|
||||
'proicons:vehicle-car',
|
||||
'proicons:wi-fi',
|
||||
'meteor-icons:microchip',
|
||||
'meteor-icons:headphones',
|
||||
'meteor-icons:star',
|
||||
'fe:mail',
|
||||
'fe:rocket',
|
||||
'fxemoji:trolleybus',
|
||||
]
|
||||
|
||||
const icon = defineModel<string | undefined | null>({ default: '' })
|
||||
const iconName = defineModel<string | undefined | null>()
|
||||
|
||||
defineProps<{ defaultIcon?: string }>()
|
||||
</script>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
:value
|
||||
class=""
|
||||
>
|
||||
<span>{{ value }}</span>
|
||||
<span>{{ value ?? label }}</span>
|
||||
</slot>
|
||||
</button>
|
||||
<button
|
||||
|
||||
54
src/components/ui/textarea/index.vue
Normal file
54
src/components/ui/textarea/index.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<UiButton
|
||||
v-if="withCopyButton"
|
||||
:tooltip="t('copy')"
|
||||
class="btn-square btn-outline btn-accent absolute z-10 top-2 right-2"
|
||||
@click="copy(`${value}`)"
|
||||
>
|
||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||
</UiButton>
|
||||
|
||||
<div class="textarea-floating">
|
||||
<textarea
|
||||
:class="{ 'pr-10': withCopyButton }"
|
||||
:id
|
||||
:placeholder
|
||||
:readonly="read_only"
|
||||
class="textarea"
|
||||
v-bind="$attrs"
|
||||
v-model="value"
|
||||
></textarea>
|
||||
<label
|
||||
class="textarea-floating-label"
|
||||
:for="id"
|
||||
>
|
||||
{{ label }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
placeholder?: string
|
||||
label?: string
|
||||
read_only?: boolean
|
||||
withCopyButton?: boolean
|
||||
}>()
|
||||
|
||||
const id = useId()
|
||||
|
||||
const value = defineModel<string | null | undefined>()
|
||||
|
||||
const { copy, copied } = useClipboard()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
copy: Kopieren
|
||||
en:
|
||||
copy: Copy
|
||||
</i18n>
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="tooltip [--prevent-popper:false]">
|
||||
<div
|
||||
class="tooltip-toggle"
|
||||
aria-label="Tooltip"
|
||||
:aria-label="tooltip"
|
||||
>
|
||||
<slot>
|
||||
<button class="btn btn-square">
|
||||
@ -11,7 +11,7 @@
|
||||
</slot>
|
||||
|
||||
<span
|
||||
class="tooltip-content tooltip-shown:opacity-100 tooltip-shown:visible z-40"
|
||||
class="tooltip-content tooltip-shown:opacity-100 tooltip-shown:visible z-40 pointer-events-none"
|
||||
role="tooltip"
|
||||
>
|
||||
<span
|
||||
|
||||
@ -1,15 +1,28 @@
|
||||
<template>
|
||||
<UiDialog
|
||||
<UiDialogConfirm
|
||||
v-model:open="open"
|
||||
:title="t('title')"
|
||||
class="btn btn-primary btn-outline shadow-md btn-lg"
|
||||
@click="open = true"
|
||||
@abort="open = false"
|
||||
@confirm="onCreateAsync"
|
||||
:confirm-label="t('create')"
|
||||
>
|
||||
<template #trigger>
|
||||
<Icon name="mdi:plus" />
|
||||
{{ t('database.create') }}
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
<div class="flex gap-x-2 items-center">
|
||||
<Icon
|
||||
name="mdi:safe"
|
||||
class="text-primary"
|
||||
/>
|
||||
<p>
|
||||
{{ t('title') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<form
|
||||
class="flex flex-col gap-4"
|
||||
@submit="onCreateAsync"
|
||||
@ -32,11 +45,12 @@
|
||||
/>
|
||||
</form>
|
||||
|
||||
<template #buttons>
|
||||
<!-- <template #buttons>
|
||||
<UiButton
|
||||
class="btn-error w-full sm:w-auto"
|
||||
class="btn-error btn-outline w-full sm:w-auto"
|
||||
@click="onClose"
|
||||
>
|
||||
<Icon name="mdi:x" />
|
||||
{{ t('abort') }}
|
||||
</UiButton>
|
||||
|
||||
@ -46,8 +60,8 @@
|
||||
>
|
||||
{{ t('create') }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiDialog>
|
||||
</template> -->
|
||||
</UiDialogConfirm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -151,7 +165,7 @@ const onClose = () => {
|
||||
"create": "Neue Vault anlegen",
|
||||
"name": "HaexVault"
|
||||
},
|
||||
"title": "Neue Datenbank anlegen",
|
||||
"title": "Neue Vault anlegen",
|
||||
"create": "Erstellen",
|
||||
"abort": "Abbrechen",
|
||||
"description": "Haex Vault für deine geheimsten Geheimnisse"
|
||||
@ -163,7 +177,7 @@ const onClose = () => {
|
||||
"create": "Create new Vault",
|
||||
"name": "HaexVault"
|
||||
},
|
||||
"title": "Create New Database",
|
||||
"title": "Create New Vault",
|
||||
"create": "Create",
|
||||
"abort": "Abort",
|
||||
"description": "Haex Vault for your most secret secrets"
|
||||
|
||||
@ -78,7 +78,7 @@ const { add } = useSnackbar()
|
||||
const handleError = (error: unknown) => {
|
||||
open.value = false
|
||||
console.error('handleError', error, typeof error)
|
||||
add({ type: 'error', text: JSON.stringify(error) })
|
||||
add({ type: 'error', text: `${error}` })
|
||||
}
|
||||
|
||||
const { openAsync } = useVaultStore()
|
||||
|
||||
Reference in New Issue
Block a user