laden von erweiterungen implementiert

This commit is contained in:
Martin Drechsel
2025-05-15 09:28:45 +02:00
parent a653111071
commit ad3aa4293a
40 changed files with 1454 additions and 784 deletions

View File

@ -0,0 +1,53 @@
<template>
<div class="card">
<slot name="image" />
<div class="absolute top-2 right-2">
<UiButton class="btn-error btn-outline btn-sm btn-square" @click="$emit('remove')">
<Icon name="mdi:trash" />
</UiButton>
</div>
<div class="card-header">
<div v-if="$slots.title || name">
<div class="flex justify-start gap-4">
<div v-html="icon" class="shrink-0 size-10" />
<h5 v-if="name" class="card-title m-0 my-auto">
{{ name }}
</h5>
</div>
</div>
<div class="text-base-content/50">{{ manifest }}</div>
</div>
<div class="card-body">
<slot />
<div class="card-actions" v-if="$slots.action">
<slot name="action" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { IHaexHubExtension } from "~/types/haexhub";
const emit = defineEmits(["close", "submit", "remove"]);
defineProps<IHaexHubExtension>();
const { escape, enter } = useMagicKeys();
watchEffect(async () => {
if (escape.value) {
await nextTick();
emit("close");
}
});
watchEffect(async () => {
if (enter.value) {
await nextTick();
emit("submit");
}
});
</script>

View File

@ -0,0 +1,54 @@
<template>
<UiDialog :title="t('title')" v-model:open="open">
<div>
<i18n-t keypath="question" tag="p">
<template #name>
<span class="font-bold text-primary">{{ extension?.name }}</span>
</template>
</i18n-t>
</div>
<template #buttons>
<UiButton class="btn-outline btn-error" @click="open = false">
<Icon name="mdi:cancel" /> {{ t("abort") }}
</UiButton>
<UiButton class="btn-error" @click="onConfirm">
<Icon name="mdi:trash" /> {{ t("remove") }}
</UiButton>
</template>
</UiDialog>
</template>
<script setup lang="ts">
import type { IHaexHubExtension } from "~/types/haexhub";
const emit = defineEmits(["confirm"]);
const { t } = useI18n();
defineProps<{ extension?: IHaexHubExtension }>();
const open = defineModel<boolean>("open");
const onConfirm = () => {
open.value = false;
emit("confirm");
};
</script>
<i18n lang="json">
{
"de": {
"title": "Erweiterung löschen",
"question": "Soll {name} wirklich gelöscht werden?",
"abort": "Abbrechen",
"remove": "Löschen"
},
"en": {
"title": "Remove Extension",
"question": "Soll {name} wirklich gelöscht werden?",
"abort": "Abort",
"remove": "Remove"
}
}
</i18n>

View File

@ -54,10 +54,12 @@
</template>
<script setup lang="ts">
import type { IHaexHubExtensionManifest } from "~/types/haexhub";
const { t } = useI18n();
const open = defineModel<boolean>("open", { default: false });
defineProps<{ manifest?: IHaexHubExtensionManifest }>();
defineProps<{ manifest?: IHaexHubExtensionManifest | null }>();
const emit = defineEmits(["deny", "confirm"]);

View File

@ -1,25 +1,14 @@
<template>
<button
class="btn join-item"
:class="{
'btn-sm':
currentScreenSize === 'sm' ||
currentScreenSize === '' ||
currentScreenSize === 'xs',
}"
:type
>
<button class="btn join-item" :type>
<slot />
</button>
</template>
<script setup lang="ts">
const { currentScreenSize } = storeToRefs(useUiStore());
defineProps({
type: {
type: String as PropType<'reset' | 'submit' | 'button'>,
default: 'button',
type: String as PropType<"reset" | "submit" | "button">,
default: "button",
},
});
</script>

View File

@ -50,27 +50,10 @@ export interface IDom {
const id = useId();
defineProps({
trigger: {
type: Object as PropType<IDom>,
default: () => ({
class: "",
text: "",
}),
},
title: {
type: String,
default: "",
},
description: {
type: Object as PropType<IDom>,
default: () => ({
class: "",
text: "",
}),
required: false,
},
});
const open = defineModel<boolean>("open", { default: false });
@ -84,8 +67,8 @@ watch(open, async () => {
if (open.value) {
await modal.value?.open();
} else {
const res = await modal.value?.close(true);
console.log("close dialog", res);
await modal.value?.close(true);
console.log("close dialog");
}
});

View File

@ -1,6 +1,6 @@
<template>
<span>
<fieldset class="join w-full">
<!-- <fieldset class="join w-full">
<slot name="prepend" />
<span class="input-group join-item">
@ -66,30 +66,45 @@
>
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
</UiButton>
<!-- <button
v-if="withCopyButton"
class="btn btn-outline btn-accent join-item h-auto"
:class="{
'btn-sm':
currentScreenSize === 'sm' ||
currentScreenSize === '' ||
currentScreenSize === 'xs',
}"
</fieldset> -->
<fieldset class="join w-full p-1">
<slot name="prepend" />
<div class="input join-item">
<Icon :name="prependIcon" class="my-auto shrink-0" />
<div class="input-floating grow">
<input
:id
:name="name ?? id"
:placeholder="placeholder || label"
:type
:autofocus
class="ps-3"
v-bind="$attrs"
v-model="input"
ref="inputRef"
:readonly="read_only"
/>
<label class="input-floating-label" :for="id">{{ label }}</label>
</div>
<Icon :name="appendIcon" class="my-auto shrink-0" />
</div>
<slot name="append" class="h-auto" />
<UiButton
class="btn-outline btn-accent btn-square join-item h-auto"
@click="copy(`${input}`)"
type="button"
>
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
</button> -->
</UiButton>
</fieldset>
<span
class="flex flex-col px-2 pt-0.5"
v-show="errors"
>
<span
v-for="error in errors"
class="label-text-alt text-error"
>
<span class="flex flex-col px-2 pt-0.5" v-show="errors">
<span v-for="error in errors" class="label-text-alt text-error">
{{ error }}
</span>
</span>
@ -97,9 +112,9 @@
</template>
<script setup lang="ts">
import { type ZodSchema } from 'zod';
import { type ZodSchema } from "zod";
const inputRef = useTemplateRef('inputRef');
const inputRef = useTemplateRef("inputRef");
defineExpose({ inputRef });
defineOptions({
@ -109,45 +124,45 @@ defineOptions({
const props = defineProps({
placeholder: {
type: String,
default: '',
default: "",
},
type: {
type: String as PropType<
| 'button'
| 'checkbox'
| 'color'
| 'date'
| 'datetime-local'
| 'email'
| 'file'
| 'hidden'
| 'image'
| 'month'
| 'number'
| 'password'
| 'radio'
| 'range'
| 'reset'
| 'search'
| 'submit'
| 'tel'
| 'text'
| 'time'
| 'url'
| 'week'
| "button"
| "checkbox"
| "color"
| "date"
| "datetime-local"
| "email"
| "file"
| "hidden"
| "image"
| "month"
| "number"
| "password"
| "radio"
| "range"
| "reset"
| "search"
| "submit"
| "tel"
| "text"
| "time"
| "url"
| "week"
>,
default: 'text',
default: "text",
},
label: String,
name: String,
prependIcon: {
type: String,
default: '',
default: "",
},
prependLabel: String,
appendIcon: {
type: String,
default: '',
default: "",
},
appendLabel: String,
rules: Object as PropType<ZodSchema>,
@ -158,7 +173,7 @@ const props = defineProps({
});
const input = defineModel<string | number | undefined | null>({
default: '',
default: "",
required: true,
});
@ -167,7 +182,7 @@ onMounted(() => {
if (props.autofocus && inputRef.value) inputRef.value.focus();
});
const errors = defineModel<string[] | undefined>('errors');
const errors = defineModel<string[] | undefined>("errors");
const id = useId();
@ -180,7 +195,7 @@ watch(
}
);
const emit = defineEmits(['error']);
const emit = defineEmits(["error"]);
const checkInput = () => {
if (props.rules) {
@ -188,7 +203,7 @@ const checkInput = () => {
//console.log('check result', result.error, props.rules);
if (!result.success) {
errors.value = result.error.errors.map((error) => error.message);
emit('error', errors.value);
emit("error", errors.value);
} else {
errors.value = [];
}

View File

@ -9,10 +9,7 @@
v-model="value"
>
<template #append>
<UiButton
class="btn-outline btn-accent h-auto"
@click="tooglePasswordType"
>
<UiButton class="btn-outline btn-accent btn-square h-auto" @click="tooglePasswordType">
<Icon :name="type === 'password' ? 'mdi:eye' : 'mdi:eye-off'" />
</UiButton>
</template>
@ -20,7 +17,7 @@
</template>
<script setup lang="ts">
import type { ZodSchema } from 'zod';
import type { ZodSchema } from "zod";
const { t } = useI18n();
const { currentScreenSize } = storeToRefs(useUiStore());
@ -35,10 +32,10 @@ defineProps({
autofocus: 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>

View File

@ -1,49 +0,0 @@
<template>
<li
@click="triggerNavigate"
class="hover:text-primary rounded"
:class="{ ['bg-base-300']: isActive }"
>
<UiTooltip :tooltip="tooltip ?? name" direction="right-start">
<NuxtLinkLocale
:to="{ name: 'haexExtension', params: { extensionId: props.id } }"
class="flex items-center justify-center cursor-pointer tooltip-toogle"
ref="link"
>
<div v-html="icon" class="shrink-0 size-6" />
<!-- <Icon mode="svg" :name="icon" class="shrink-0 size-6" /> -->
</NuxtLinkLocale>
</UiTooltip>
</li>
</template>
<script setup lang="ts">
import { type ISidebarItem } from "#imports";
const props = defineProps<ISidebarItem>();
console.log("image", props.icon);
const router = useRouter();
const isActive = computed(() => {
if (props.to?.name === "haexExtension") {
return getSingleRouteParam(router.currentRoute.value.params.extensionId) === props.id;
} else {
return props.to?.name === router.currentRoute.value.meta.name;
}
});
const link = useTemplateRef("link");
const triggerNavigate = () => link.value?.$el.click();
/* computed(() => {
const found = useRouter()
.getRoutes()
.find((route) => route.name === useLocaleRoute()(props.to)?.name);
console.log('found route', found, useRoute());
return (
found?.name === useRoute().name ||
found?.children.some((child) => child.name === useRoute().name)
);
}); */
</script>

View File

@ -8,9 +8,10 @@
<NuxtLinkLocale
:to
class="flex items-center justify-center cursor-pointer tooltip-toogle"
ref="link"
ref="linkRef"
>
<Icon :name="icon" class="shrink-0 size-6" />
<div v-if="iconType === 'svg'" v-html="icon" class="shrink-0 size-5" />
<Icon v-else :name="icon" size="1.5em" />
</NuxtLinkLocale>
</UiTooltip>
</li>
@ -23,6 +24,7 @@ const props = defineProps<ISidebarItem>();
const router = useRouter();
console.log("to", props.to);
const isActive = computed(() => {
if (props.to?.name === "haexExtension") {
return getSingleRouteParam(router.currentRoute.value.params.extensionId) === props.id;
@ -31,9 +33,9 @@ const isActive = computed(() => {
}
});
const link = useTemplateRef("link");
const linkRef = useTemplateRef("linkRef");
const triggerNavigate = () => link.value?.$el.click();
const triggerNavigate = () => linkRef.value?.$el.click();
/* computed(() => {
const found = useRouter()

View File

@ -1,52 +1,51 @@
<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>
<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"
/>
<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>
<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>
<template #buttons>
<UiButton class="btn-error" @click="onClose">
{{ t("abort") }}
</UiButton>
<UiButton class="btn-primary" @click="onCreateAsync">
{{ t("create") }}
</UiButton>
</template>
</UiDialog>
<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";
import Database from "@tauri-apps/plugin-sql";
const check = ref(false);
const open = ref();
@ -54,22 +53,22 @@ const open = ref();
const { t } = useI18n();
const database = reactive<{
name: string;
password: string;
path: string | null;
type: "password" | "text";
name: string;
password: string;
path: string | null;
type: "password" | "text";
}>({
name: "",
password: "",
path: "",
type: "password",
name: "",
password: "",
path: "",
type: "password",
});
const initDatabase = () => {
database.name = t("database.name");
database.password = "";
database.path = "";
database.type = "password";
database.name = t("database.name");
database.password = "";
database.path = "";
database.type = "password";
};
initDatabase();
@ -78,69 +77,71 @@ const { add } = useSnackbar();
const { createAsync } = useVaultStore();
const onCreateAsync = async () => {
check.value = true;
check.value = true;
const nameCheck = vaultDatabaseSchema.name.safeParse(database.name);
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password);
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;
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.endsWith(".db") ? database.name : `${database.name}.db`,
});
open.value = false;
try {
database.path = await save({
defaultPath: database.name.endsWith(".db") ? database.name : `${database.name}.db`,
});
console.log("data", database);
console.log("data", database);
if (database.path && database.password) {
const vaultId = await createAsync({
path: database.path,
password: database.password,
});
if (database.path && database.password) {
const vaultId = await createAsync({
path: database.path,
password: database.password,
});
console.log("vaultId", vaultId);
await navigateTo(useLocaleRoute()({ name: "vaultOverview", params: { vaultId } }));
}
} catch (error) {
console.error(error);
add({ type: "error", text: JSON.stringify(error) });
console.log("vaultId", vaultId);
if (vaultId) {
await navigateTo(useLocaleRoute()({ name: "vaultOverview", params: { vaultId } }));
}
}
} catch (error) {
console.error(error);
add({ type: "error", text: JSON.stringify(error) });
}
};
const onClose = () => {
open.value = false;
initDatabase();
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"
"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"
}
"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>

View File

@ -102,6 +102,7 @@ const onLoadDatabase = async () => {
const localePath = useLocalePath();
const { currentVault, currentVaultId } = storeToRefs(useVaultStore());
const onOpenDatabase = async () => {
try {
check.value = true;
@ -110,7 +111,10 @@ const onOpenDatabase = async () => {
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password);
if (!pathCheck.success || !passwordCheck.success || !path) {
add({ type: "error", text: "params falsch" });
add({
type: "error",
text: `Params falsch. Path: ${pathCheck.error} | Password: ${passwordCheck.error}`,
});
return;
}
@ -131,15 +135,14 @@ const onOpenDatabase = async () => {
onClose();
currentVaultId.value = vaultId;
console.log("vault before navigation", currentVault.value, currentVaultId.value, vaultId);
await navigateTo(
localePath({
name: "vaultOverview",
params: {
vaultId,
},
query: {
showSidebar: "true",
},
})
);
} catch (error) {

View File

@ -3,14 +3,19 @@
<slot name="image" />
<div class="card-header">
<h5 class="card-title" v-if="$slots.title">
<slot name="title" />
</h5>
<div v-if="$slots.title || title">
<Icon :name="icon" />
<h5 v-if="title" class="card-title mb-0">
{{ title }}
</h5>
<slot v-else name="title" />
</div>
<div class="text-base-content/50">Your journey starts here</div>
</div>
<div class="card-body">
<slot />
aaaaaaaaa
<div class="card-actions" v-if="$slots.action">
<slot name="action" />
</div>
@ -36,6 +41,8 @@
<script setup lang="ts">
const emit = defineEmits(["close", "submit"]);
defineProps<{ title?: string; icon?: string }>();
const { escape, enter } = useMagicKeys();
watchEffect(async () => {