mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-17 06:30:50 +01:00
zwischenstand
This commit is contained in:
102
src/components/haex/extension/manifest/confirm.vue
Normal file
102
src/components/haex/extension/manifest/confirm.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<UiDialog :title="t('title')" v-model:open="open">
|
||||
<div>
|
||||
<i18n-t keypath="question" tag="p">
|
||||
<template #extension>
|
||||
<span class="font-bold text-primary">{{ manifest?.name }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
|
||||
<!-- {{ t("question", { extension: manifest?.name }) }}
|
||||
<span class="font-bold text-primary">{{ manifest?.name }}</span> zu HaexHub hinzufügen? -->
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<HaexExtensionManifestPermissionsFilesystem
|
||||
v-if="manifest?.permissions?.filesystem"
|
||||
:filesystem="manifest?.permissions?.filesystem"
|
||||
/>
|
||||
|
||||
<HaexExtensionManifestPermissionsDatabase
|
||||
v-if="manifest?.permissions?.database"
|
||||
:database="manifest?.permissions?.database"
|
||||
/>
|
||||
|
||||
<HaexExtensionManifestPermissionsHttp
|
||||
v-if="manifest?.permissions?.http"
|
||||
:http="manifest?.permissions?.http"
|
||||
/>
|
||||
<!-- <VaultCard>
|
||||
<template #header>
|
||||
<h3>{{ t("filesystem.title") }}</h3>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
{{ manifest?.permissions.filesystem }}
|
||||
</div>
|
||||
</VaultCard>
|
||||
|
||||
<VaultCard>
|
||||
<template #header>
|
||||
<h3>{{ t("http.title") }}</h3>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
{{ manifest?.permissions.http }}
|
||||
</div>
|
||||
</VaultCard> -->
|
||||
</div>
|
||||
|
||||
<template #buttons>
|
||||
<UiButton @click="onDeny" class="btn-error btn-outline">{{ t("deny") }} </UiButton>
|
||||
<UiButton @click="onConfirm" class="btn-success btn-outline">{{ t("confirm") }}</UiButton>
|
||||
</template>
|
||||
</UiDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n();
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
defineProps<{ manifest?: IHaexHubExtensionManifest }>();
|
||||
|
||||
const emit = defineEmits(["deny", "confirm"]);
|
||||
|
||||
const onDeny = () => {
|
||||
open.value = false;
|
||||
console.log("onDeny open", open.value);
|
||||
emit("deny");
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
open.value = false;
|
||||
console.log("onConfirm open", open.value);
|
||||
emit("confirm");
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"title": "Erweiterung hinzufügen",
|
||||
"question": "Möchtest du die Erweiterung {extension} hinzufügen?",
|
||||
"confirm": "Bestätigen",
|
||||
"deny": "Ablehnen",
|
||||
|
||||
"permission": {
|
||||
"read": "Lesen",
|
||||
"write": "Schreiben"
|
||||
},
|
||||
|
||||
"database": {
|
||||
"title": "Datenbank Berechtigungen"
|
||||
},
|
||||
"http": {
|
||||
"title": "Internet Berechtigungen"
|
||||
},
|
||||
"filesystem": {
|
||||
"title": "Dateisystem Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
</i18n>
|
||||
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div>
|
||||
<HaexExtensionManifestPermissionsTitle>
|
||||
{{ t("database.title") }}
|
||||
</HaexExtensionManifestPermissionsTitle>
|
||||
|
||||
<div v-if="database?.read?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.read") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li class="flex items-center justify-between px-4 py-0.5" v-for="read in database?.read">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ read }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
|
||||
<div v-if="database?.write?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.write") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li
|
||||
class="flex items-center justify-between px-4 py-0.5"
|
||||
v-for="write in database?.write"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ write }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ database: IHaexHubExtensionManifest["permissions"]["database"] }>();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"permission": {
|
||||
"read": "Lesen",
|
||||
"write": "Schreiben"
|
||||
},
|
||||
|
||||
"database": {
|
||||
"title": "Datenbank Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
</i18n>
|
||||
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<HaexExtensionManifestPermissionsTitle>
|
||||
{{ t("filesystem.title") }}
|
||||
</HaexExtensionManifestPermissionsTitle>
|
||||
|
||||
<div v-if="filesystem?.read?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.read") }}</h3>
|
||||
</template>
|
||||
<ul class="space-y-0.5">
|
||||
<li
|
||||
class="flex items-center justify-between px-4 py-0.5"
|
||||
v-for="read in filesystem?.read"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ read }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
|
||||
<div v-if="filesystem?.write?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.write") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li
|
||||
class="flex items-center justify-between px-4 py-0.5"
|
||||
v-for="write in filesystem?.write"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ write }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ filesystem: IHaexHubExtensionManifest["permissions"]["filesystem"] }>();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"permission": {
|
||||
"read": "Lesen",
|
||||
"write": "Schreiben"
|
||||
},
|
||||
|
||||
"filesystem": {
|
||||
"title": "Dateisystem Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
</i18n>
|
||||
43
src/components/haex/extension/manifest/permissions/http.vue
Normal file
43
src/components/haex/extension/manifest/permissions/http.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
<HaexExtensionManifestPermissionsTitle>
|
||||
{{ t("http.title") }}
|
||||
</HaexExtensionManifestPermissionsTitle>
|
||||
|
||||
<div v-if="http?.length">
|
||||
<UiAccordion>
|
||||
<template #title>
|
||||
<h3>{{ t("permission.access") }}</h3>
|
||||
</template>
|
||||
|
||||
<ul class="space-y-0.5">
|
||||
<li class="flex items-center justify-between px-4 py-0.5" v-for="access in http">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ access }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{ http: IHaexHubExtensionManifest["permissions"]["http"] }>();
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"permission": {
|
||||
"access": "Zugriff"
|
||||
},
|
||||
|
||||
"http": {
|
||||
"title": "Internet Berechtigungen"
|
||||
}
|
||||
},
|
||||
"en": { "title": "Confirm Permission" }
|
||||
}
|
||||
</i18n>
|
||||
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="text-base-content/50 px-4 py-2 text-md font-medium">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
43
src/components/ui/accordion/index.vue
Normal file
43
src/components/ui/accordion/index.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="accordion divide-neutral/20 divide-y">
|
||||
<div class="accordion-item active" :id="itemId" ref="accordionRef">
|
||||
<button
|
||||
class="accordion-toggle inline-flex items-center gap-x-4 text-start"
|
||||
:aria-controls="collapseId"
|
||||
aria-expanded="true"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="icon-[tabler--chevron-right] accordion-item-active:rotate-90 size-5 shrink-0 transition-transform duration-300 rtl:rotate-180"
|
||||
></span>
|
||||
<slot name="title" />
|
||||
</button>
|
||||
<div
|
||||
:id="collapseId"
|
||||
class="accordion-content w-full overflow-hidden transition-[height] duration-300"
|
||||
:aria-labelledby="itemId"
|
||||
role="region"
|
||||
>
|
||||
<div class="px-5 pb-4">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HSAccordion } from "flyonui/flyonui";
|
||||
|
||||
const itemId = useId();
|
||||
const collapseId = useId();
|
||||
|
||||
const accordionRef = useTemplateRef("accordionRef");
|
||||
const accordion = ref<HSAccordion>();
|
||||
|
||||
onMounted(() => {
|
||||
if (accordionRef.value) {
|
||||
accordion.value = new HSAccordion(accordionRef.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -1,9 +1,5 @@
|
||||
<template>
|
||||
<slot
|
||||
name="trigger"
|
||||
:id
|
||||
>
|
||||
</slot>
|
||||
<slot name="trigger" :id> </slot>
|
||||
|
||||
<div
|
||||
:id
|
||||
@ -17,10 +13,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<slot name="title">
|
||||
<h3
|
||||
v-if="title"
|
||||
class="modal-title text-base sm:text-lg"
|
||||
>
|
||||
<h3 v-if="title" class="modal-title text-base sm:text-lg">
|
||||
{{ title }}
|
||||
</h3>
|
||||
</slot>
|
||||
@ -32,10 +25,7 @@
|
||||
@click="open = false"
|
||||
tabindex="1"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:close"
|
||||
size="18"
|
||||
/>
|
||||
<Icon name="mdi:close" size="18" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body text-sm sm:text-base py-1">
|
||||
@ -50,7 +40,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { HSOverlay } from 'flyonui/flyonui';
|
||||
import { HSOverlay } from "flyonui/flyonui";
|
||||
|
||||
export interface IDom {
|
||||
class?: String;
|
||||
@ -63,38 +53,39 @@ defineProps({
|
||||
trigger: {
|
||||
type: Object as PropType<IDom>,
|
||||
default: () => ({
|
||||
class: '',
|
||||
text: '',
|
||||
class: "",
|
||||
text: "",
|
||||
}),
|
||||
},
|
||||
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: "",
|
||||
},
|
||||
|
||||
description: {
|
||||
type: Object as PropType<IDom>,
|
||||
default: () => ({
|
||||
class: '',
|
||||
text: '',
|
||||
class: "",
|
||||
text: "",
|
||||
}),
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
|
||||
const { t } = useI18n();
|
||||
const modalRef = useTemplateRef('modalRef');
|
||||
const modalRef = useTemplateRef("modalRef");
|
||||
const modal = ref<HSOverlay>();
|
||||
|
||||
watch(open, async () => {
|
||||
console.log("open modal", open.value);
|
||||
if (open.value) {
|
||||
//console.log('open modal', modal.value?.open);
|
||||
await modal.value?.open();
|
||||
} else {
|
||||
await modal.value?.close(true);
|
||||
const res = await modal.value?.close(true);
|
||||
console.log("close dialog", res);
|
||||
}
|
||||
});
|
||||
|
||||
@ -102,8 +93,8 @@ onMounted(() => {
|
||||
if (!modalRef.value) return;
|
||||
modal.value = new HSOverlay(modalRef.value, { isClosePrev: true });
|
||||
|
||||
modal.value.on('close', () => {
|
||||
console.log('close it from event', open.value);
|
||||
modal.value.on("close", () => {
|
||||
console.log("close it from event", open.value);
|
||||
open.value = false;
|
||||
});
|
||||
});
|
||||
|
||||
49
src/components/ui/sidebar/link/extension.vue
Normal file
49
src/components/ui/sidebar/link/extension.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<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>
|
||||
@ -71,7 +71,8 @@ const { add } = useSnackbar();
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
isOpen.value = false;
|
||||
add({ type: "error", text: JSON.stringify(error) });
|
||||
console.log("handleError", error, typeof error);
|
||||
add({ type: "error", text: "Passwort falsch" });
|
||||
//console.error(error);
|
||||
};
|
||||
|
||||
@ -100,6 +101,7 @@ const onLoadDatabase = async () => {
|
||||
};
|
||||
|
||||
const localePath = useLocalePath();
|
||||
|
||||
const onOpenDatabase = async () => {
|
||||
try {
|
||||
check.value = true;
|
||||
@ -120,7 +122,10 @@ const onOpenDatabase = async () => {
|
||||
});
|
||||
|
||||
if (!vaultId) {
|
||||
add({ type: "error", text: "Vault konnte nicht geöffnet werden" });
|
||||
add({
|
||||
type: "error",
|
||||
text: "Vault konnte nicht geöffnet werden. \n Vermutlich ist das Passwort falsch",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +1,27 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-base-100 w-full mx-auto shadow h-full overflow-hidden pt-[7.5rem]"
|
||||
>
|
||||
<div class="card">
|
||||
<slot name="image" />
|
||||
|
||||
<div class="card-header">
|
||||
<h5 class="card-title" v-if="$slots.title">
|
||||
<slot name="title" />
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<slot />
|
||||
|
||||
<div class="card-actions" v-if="$slots.action">
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="bg-base-100 w-full mx-auto shadow h-full overflow-hidden pt-[7.5rem]">
|
||||
<div
|
||||
class="fixed top-0 right-0 z-10 transition-all duration-700 w-full font-semibold text-lg h-[7.5rem]"
|
||||
:class="{ 'pl-96': show }"
|
||||
>
|
||||
<div
|
||||
class="justify-center items-center flex flex-wrap border-b rounded-b border-secondary h-full"
|
||||
:class="{ 'pl-12': !show }"
|
||||
>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
@ -17,26 +30,25 @@
|
||||
<div class="h-full overflow-scroll bg-base-200">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { show } = storeToRefs(useSidebarStore());
|
||||
const emit = defineEmits(['close', 'submit']);
|
||||
const emit = defineEmits(["close", "submit"]);
|
||||
|
||||
const { escape, enter } = useMagicKeys();
|
||||
|
||||
watchEffect(async () => {
|
||||
if (escape.value) {
|
||||
await nextTick();
|
||||
emit('close');
|
||||
emit("close");
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(async () => {
|
||||
if (enter.value) {
|
||||
await nextTick();
|
||||
emit('submit');
|
||||
emit("submit");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import { H3Error } from 'h3';
|
||||
import { H3Error } from "h3";
|
||||
|
||||
export const bytesToBase64DataUrlAsync = async (
|
||||
bytes: Uint8Array,
|
||||
type = 'application/octet-stream'
|
||||
type = "application/octet-stream"
|
||||
) => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const reader = Object.assign(new FileReader(), {
|
||||
onload: () => resolve(reader.result),
|
||||
onerror: () => reject(reader.error),
|
||||
});
|
||||
reader.readAsDataURL(new File([new Blob([bytes])], '', { type }));
|
||||
reader.readAsDataURL(new File([new Blob([bytes])], "", { type }));
|
||||
});
|
||||
};
|
||||
|
||||
export const blobToImageAsync = (blob: Blob): Promise<HTMLImageElement> => {
|
||||
return new Promise((resolve) => {
|
||||
console.log('transform blob', blob);
|
||||
console.log("transform blob", blob);
|
||||
const url = URL.createObjectURL(blob);
|
||||
let img = new Image();
|
||||
img.onload = () => {
|
||||
@ -34,7 +34,7 @@ export const deepToRaw = <T extends Record<string, any>>(sourceObj: T): T => {
|
||||
if (isRef(input) || isReactive(input) || isProxy(input)) {
|
||||
return objectIterator(toRaw(input));
|
||||
}
|
||||
if (input && typeof input === 'object') {
|
||||
if (input && typeof input === "object") {
|
||||
return Object.keys(input).reduce((acc, key) => {
|
||||
acc[key as keyof typeof acc] = objectIterator(input[key]);
|
||||
return acc;
|
||||
@ -48,10 +48,9 @@ export const deepToRaw = <T extends Record<string, any>>(sourceObj: T): T => {
|
||||
|
||||
export const readableFileSize = (sizeInByte: number | string = 0) => {
|
||||
if (!sizeInByte) {
|
||||
return '0 KB';
|
||||
return "0 KB";
|
||||
}
|
||||
const size =
|
||||
typeof sizeInByte === 'string' ? parseInt(sizeInByte) : sizeInByte;
|
||||
const size = typeof sizeInByte === "string" ? parseInt(sizeInByte) : sizeInByte;
|
||||
const sizeInKb = size / 1024;
|
||||
const sizeInMb = sizeInKb / 1024;
|
||||
const sizeInGb = sizeInMb / 1024;
|
||||
@ -64,20 +63,17 @@ export const readableFileSize = (sizeInByte: number | string = 0) => {
|
||||
return `${sizeInKb.toFixed(2)} KB`;
|
||||
};
|
||||
|
||||
import type { LocationQueryValue, RouteLocationRawI18n } from 'vue-router';
|
||||
import type { LocationQueryValue, RouteLocationRawI18n } from "vue-router";
|
||||
|
||||
export const getSingleRouteParam = (
|
||||
param: string | string[] | LocationQueryValue | LocationQueryValue[]
|
||||
): string => {
|
||||
const _param = Array.isArray(param) ? param.at(0) ?? '' : param ?? '';
|
||||
const _param = Array.isArray(param) ? param.at(0) ?? "" : param ?? "";
|
||||
//console.log('found param', _param, param);
|
||||
return _param;
|
||||
return decodeURIComponent(_param);
|
||||
};
|
||||
|
||||
export const isRouteActive = (
|
||||
to: RouteLocationRawI18n,
|
||||
exact: boolean = false
|
||||
) =>
|
||||
export const isRouteActive = (to: RouteLocationRawI18n, exact: boolean = false) =>
|
||||
computed(() => {
|
||||
const found = useRouter()
|
||||
.getRoutes()
|
||||
@ -86,9 +82,7 @@ export const isRouteActive = (
|
||||
return exact
|
||||
? found?.name === useRouter().currentRoute.value.name
|
||||
: found?.name === useRouter().currentRoute.value.name ||
|
||||
found?.children.some(
|
||||
(child) => child.name === useRouter().currentRoute.value.name
|
||||
);
|
||||
found?.children.some((child) => child.name === useRouter().currentRoute.value.name);
|
||||
});
|
||||
|
||||
export const isKey = <T extends object>(x: T, k: PropertyKey): k is keyof T => {
|
||||
|
||||
@ -1,32 +1,27 @@
|
||||
<template>
|
||||
<div class="w-full h-full flex flex-col">
|
||||
<div class="w-full h-full flex flex-col min-w-min">
|
||||
<nav
|
||||
class="navbar bg-base-100 max-sm:rounded-box max-sm:shadow sm:border-b border-base-content/25 sm:z-20 relative px-2"
|
||||
class="navbar bg-base-100 rounded-b max-sm:shadow border-b border-base-content/25 sm:z-20 relative px-2"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-text btn-square me-2"
|
||||
class="btn btn-text btn-square me-2 z-50"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-controls="sidebar"
|
||||
data-overlay="#sidebar"
|
||||
@click="toogleSidebar"
|
||||
ref="sidebarToogleRef"
|
||||
>
|
||||
<Icon name="mage:dash-menu" size="28" />
|
||||
</button>
|
||||
<!-- <button
|
||||
type="button"
|
||||
class="btn btn-text max-sm:btn-square me-2"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-controls="sidebar"
|
||||
data-overlay="#sidebar"
|
||||
>
|
||||
<span class="icon-[tabler--menu-2] size-5"></span>
|
||||
</button> -->
|
||||
|
||||
<div class="flex flex-1 items-center">
|
||||
<a class="link text-base-content link-neutral text-xl font-semibold no-underline" href="#">
|
||||
<UiTextGradient>Haex Hub</UiTextGradient>
|
||||
</a>
|
||||
<NuxtLinkLocale
|
||||
class="link text-base-content link-neutral text-xl font-semibold no-underline"
|
||||
:to="{ name: 'vaultOverview' }"
|
||||
>
|
||||
<UiTextGradient class="text-nowrap">Haex Hub</UiTextGradient>
|
||||
</NuxtLinkLocale>
|
||||
</div>
|
||||
<div class="navbar-end flex items-center gap-4 me-4">
|
||||
<div
|
||||
@ -158,53 +153,35 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<aside
|
||||
id="sidebar"
|
||||
class="overlay sm:shadow-none overlay-open:translate-x-0 drawer drawer-start hidden sm:absolute max-w-14 sm:flex sm:translate-x-0 sm:pt-12 z-10"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="drawer-body px-0 pt-4">
|
||||
<ul class="menu p-0">
|
||||
<!-- <li
|
||||
|
||||
|
||||
<div class="flex h-full">
|
||||
<aside
|
||||
id="sidebar"
|
||||
class="sm:shadow-none drawer max-w-14 transition-all"
|
||||
:class="[!isVisible ? 'w-0' : 'w-14']"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="drawer-body px-0">
|
||||
<ul class="menu p-0">
|
||||
<UiSidebarLink v-bind="item" v-for="item in menu" :key="item.id" />
|
||||
<UiSidebarLinkExtension
|
||||
v-bind="item"
|
||||
v-for="item in availableExtensions"
|
||||
:key="item.id"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
> -->
|
||||
<UiSidebarLink v-bind="item" v-for="item in menu" :key="item.id" />
|
||||
<!-- <UiTooltip
|
||||
:tooltip="item.tooltip || item.name"
|
||||
direction="right-start"
|
||||
>
|
||||
<NuxtLinkLocale
|
||||
:class="{ ['bg-base-300']: isActive(item.id).value }"
|
||||
:to="{
|
||||
name: 'extension',
|
||||
params: { extensionId: item.id, vaultId: 'test' },
|
||||
}"
|
||||
class="flex items-center justify-start hover:text-primary cursor-pointer tooltip-toogle"
|
||||
>
|
||||
<Icon
|
||||
:name="item.icon"
|
||||
class="size-6"
|
||||
/>
|
||||
<i
|
||||
:class="item.icon"
|
||||
class="shrink-0 size-8"
|
||||
/>
|
||||
</NuxtLinkLocale>
|
||||
</UiTooltip> -->
|
||||
<!-- </li> -->
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="overflow-hidden transition-all relative w-full">
|
||||
<div class="h-full overflow-scroll sm:pl-14">
|
||||
<slot />
|
||||
<div class="overflow-hidden transition-all relative w-full">
|
||||
<div
|
||||
class="h-full overflow-scroll transition-all pl-0"
|
||||
:class="[isVisible ? 'sm:pl-14 ' : ' pl-0']"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <main class="sm:pl-14">
|
||||
<NuxtPage />
|
||||
</main> -->
|
||||
@ -212,14 +189,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n();
|
||||
import { NuxtLinkLocale } from "#components";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { menu, isVisible } = storeToRefs(useSidebarStore());
|
||||
const sidebarToogleRef = useTemplateRef("sidebarToogleRef");
|
||||
onClickOutside(sidebarToogleRef, () => {
|
||||
if (currentScreenSize.value === "xs") {
|
||||
isVisible.value = false;
|
||||
}
|
||||
});
|
||||
const { notifications } = storeToRefs(useNotificationStore());
|
||||
|
||||
const { menu } = storeToRefs(useSidebarStore());
|
||||
const { isActive } = useExtensionsStore();
|
||||
const { closeAsync } = useVaultStore();
|
||||
const { currentScreenSize } = storeToRefs(useUiStore());
|
||||
const onExtensionSelectAsync = async (id: string) => {};
|
||||
const { availableExtensions } = storeToRefs(useExtensionsStore());
|
||||
const toogleSidebar = () => {
|
||||
isVisible.value = !isVisible.value;
|
||||
};
|
||||
|
||||
const onVaultCloseAsync = async () => {
|
||||
await closeAsync();
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
<template>
|
||||
<div class="text-white">
|
||||
<NuxtLayout name="app">
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
<div class="text-white h-full">
|
||||
<NuxtLayout name="app">
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { createTable } = useVaultStore();
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
<template>
|
||||
{{ iframeSrc }}
|
||||
<div class="w-full h-full">
|
||||
<iframe class="w-full h-full" @load="" ref="iFrameRef"> </iframe>
|
||||
<p>{{ t("loading") }}</p>
|
||||
<iframe
|
||||
v-if="iframeSrc"
|
||||
class="w-full h-full"
|
||||
@load=""
|
||||
ref="iFrameRef"
|
||||
:src="iframeSrc"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
>
|
||||
</iframe>
|
||||
<p v-else>{{ t("loading") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -10,8 +19,21 @@ definePageMeta({
|
||||
name: "haexExtension",
|
||||
});
|
||||
|
||||
const iframeRef = useTemplateRef("iFrameRef");
|
||||
const { t } = useI18n();
|
||||
const iframeRef = useTemplateRef("iFrameRef");
|
||||
|
||||
const { extensionEntry: iframeSrc, currentExtension } = storeToRefs(useExtensionsStore());
|
||||
const extensionStore = useExtensionsStore();
|
||||
|
||||
watch(iframeSrc, () => console.log("iframeSrc", iframeSrc.value), { immediate: true });
|
||||
|
||||
onMounted(async () => {
|
||||
const minfest = await extensionStore.readManifestFileAsync(
|
||||
currentExtension.value!.id,
|
||||
currentExtension.value!.version
|
||||
);
|
||||
console.log("manifest", minfest, extensionStore.extensionEntry);
|
||||
});
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
||||
@ -1,9 +1,92 @@
|
||||
<template>
|
||||
<div>add extension</div>
|
||||
<div class="flex flex-col">
|
||||
<h1>{{ t("title") }}</h1>
|
||||
<UiButton @click="loadExtensionManifestAsync">
|
||||
{{ t("extension.add") }}
|
||||
</UiButton>
|
||||
|
||||
<HaexExtensionManifestConfirm
|
||||
:manifest="extension.manifest!"
|
||||
v-model:open="showConfirmation"
|
||||
@confirm="addExtensionAsync"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { join } from "@tauri-apps/api/path";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||
|
||||
definePageMeta({
|
||||
name: "haexExtensionAdd",
|
||||
name: "extensionOverview",
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const extensionStore = useExtensionsStore();
|
||||
|
||||
const showConfirmation = ref(false);
|
||||
|
||||
const extension = reactive<{
|
||||
manifest: IHaexHubExtensionManifest | null | undefined;
|
||||
path: string | null;
|
||||
}>({
|
||||
manifest: null,
|
||||
path: "",
|
||||
});
|
||||
|
||||
const loadExtensionManifestAsync = async () => {
|
||||
try {
|
||||
extension.path = await open({ directory: true, recursive: true });
|
||||
if (!extension.path) return;
|
||||
|
||||
const manifestFile = JSON.parse(
|
||||
await readTextFile(await join(extension.path, "manifest.json"))
|
||||
);
|
||||
|
||||
if (!extensionStore.checkManifest(manifestFile))
|
||||
throw new Error(`Manifest fehlerhaft ${JSON.stringify(manifestFile)}`);
|
||||
|
||||
extension.manifest = manifestFile;
|
||||
showConfirmation.value = true;
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden des Moduls:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const { add } = useSnackbar();
|
||||
|
||||
const addExtensionAsync = async () => {
|
||||
try {
|
||||
await extensionStore.installAsync(extension.path);
|
||||
await extensionStore.loadExtensionsAsync();
|
||||
console.log("Modul erfolgreich geladen");
|
||||
add({
|
||||
type: "success",
|
||||
title: t("extension.success.title", { extension: extension.manifest?.name }),
|
||||
text: t("extension.success.text"),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden des Moduls:", error);
|
||||
add({ type: "error", text: JSON.stringify(error) });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"de": {
|
||||
"title": "Erweiterung installieren",
|
||||
"extension": {
|
||||
"add": "Erweiterung hinzufügen",
|
||||
"success": {
|
||||
"title": "{extension} hinzugefügt",
|
||||
"text": "Die Erweiterung wurde erfolgreich hinzugefügt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"title": "Install extension"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
<template>
|
||||
<div class="h-screen bg-blue-200"></div>
|
||||
<div class="bg-blue-200 h-full">aaaaa</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
name: "vaultOverview",
|
||||
name: "vaultOverview",
|
||||
});
|
||||
|
||||
const extensionStore = useExtensionsStore();
|
||||
|
||||
onMounted(async () => {
|
||||
await extensionStore.loadExtensionsAsync();
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -2,19 +2,29 @@ import { invoke } from "@tauri-apps/api/core";
|
||||
import { join, resourceDir } from "@tauri-apps/api/path";
|
||||
import { readTextFile, readDir } from "@tauri-apps/plugin-fs";
|
||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
||||
import { haexExtensions } from "~~/src-tauri/database/schemas/vault";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const manifestFileName = "manifest.json";
|
||||
const logoFileName = "logo.svg";
|
||||
|
||||
export interface IHaexHubExtensionLink {
|
||||
name: string;
|
||||
icon: string;
|
||||
tooltip?: string;
|
||||
id: string;
|
||||
version: string;
|
||||
manifest?: IHaexHubExtensionManifest;
|
||||
}
|
||||
|
||||
export interface IHaexHubExtensionManifest {
|
||||
name: string;
|
||||
id: string;
|
||||
entry: string;
|
||||
author: string;
|
||||
url: string;
|
||||
version: string;
|
||||
icon: string;
|
||||
permissions: {
|
||||
database?: {
|
||||
read?: string[];
|
||||
@ -30,80 +40,142 @@ export interface IHaexHubExtensionManifest {
|
||||
}
|
||||
|
||||
export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
const availableExtensions = ref<IHaexHubExtensionLink[]>([
|
||||
{
|
||||
id: "haex-browser",
|
||||
name: "Haex Browser",
|
||||
icon: "solar:global-outline",
|
||||
},
|
||||
|
||||
{
|
||||
id: "extensions",
|
||||
name: "sidebar.extensions",
|
||||
icon: "gg:extension",
|
||||
},
|
||||
|
||||
{
|
||||
id: "settings",
|
||||
name: "sidebar.settings",
|
||||
icon: "ph:gear-six",
|
||||
},
|
||||
]);
|
||||
const availableExtensions = ref<IHaexHubExtensionLink[]>([]);
|
||||
|
||||
const currentRoute = useRouter().currentRoute;
|
||||
|
||||
const isActive = (id: string) =>
|
||||
computed(
|
||||
() =>
|
||||
currentRoute.value.name === "extension" &&
|
||||
currentRoute.value.params.extensionId === id
|
||||
() => currentRoute.value.name === "extension" && currentRoute.value.params.extensionId === id
|
||||
);
|
||||
|
||||
const currentExtension = computed(() => {
|
||||
if (currentRoute.value.name !== "haexExtension") return;
|
||||
|
||||
const extensionId = getSingleRouteParam(
|
||||
currentRoute.value.params.extensionId
|
||||
);
|
||||
console.log("computed currentExtension", currentRoute.value.params);
|
||||
if (currentRoute.value.meta.name !== "haexExtension") return;
|
||||
|
||||
const extensionId = getSingleRouteParam(currentRoute.value.params.extensionId);
|
||||
console.log("extensionId from param", extensionId);
|
||||
if (!extensionId) return;
|
||||
|
||||
return availableExtensions.value.find(
|
||||
(extension) => extension.id === extensionId
|
||||
);
|
||||
const extension = availableExtensions.value.find((extension) => extension.id === extensionId);
|
||||
console.log("currentExtension", extension);
|
||||
return extension;
|
||||
});
|
||||
|
||||
const checkExtensionDirectoryAsync = async (extensionDirectory: string) => {
|
||||
const getExtensionPathAsync = async (extensionId?: string, version?: string) => {
|
||||
if (!extensionId || !version) return "";
|
||||
return await join(await resourceDir(), "extensions", extensionId, version);
|
||||
};
|
||||
|
||||
const checkSourceExtensionDirectoryAsync = async (extensionDirectory: string) => {
|
||||
try {
|
||||
const dir = await readDir(extensionDirectory);
|
||||
const manifest = dir.find(
|
||||
(entry) => entry.name === manifestFileName && entry.isFile
|
||||
);
|
||||
const manifest = dir.find((entry) => entry.name === manifestFileName && entry.isFile);
|
||||
if (!manifest) throw new Error("Kein Manifest für Erweiterung gefunden");
|
||||
|
||||
const logo = dir.find((item) => item.isFile && item.name === logoFileName);
|
||||
if (!logo) throw new Error("Logo fehlt");
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Keine Leseberechtigung für Ordner ${extensionDirectory}`
|
||||
);
|
||||
throw new Error(`Keine Leseberechtigung für Ordner ${extensionDirectory}`);
|
||||
}
|
||||
};
|
||||
|
||||
const installAsync = async (
|
||||
extensionDirectory: string | null,
|
||||
global: boolean = true
|
||||
): Promise<void> => {
|
||||
const checkManifest = (manifestFile: unknown): manifestFile is IHaexHubExtensionManifest => {
|
||||
const errors = [];
|
||||
|
||||
if (typeof manifestFile !== "object" || manifestFile === null) {
|
||||
errors.push("Manifest ist falsch");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!("id" in manifestFile) || typeof manifestFile.id !== "string")
|
||||
errors.push("Keine ID vergeben");
|
||||
|
||||
if (!("name" in manifestFile) || typeof manifestFile.name !== "string")
|
||||
errors.push("Name fehlt");
|
||||
|
||||
if (!("entry" in manifestFile) || typeof manifestFile.entry !== "string")
|
||||
errors.push("Entry fehlerhaft");
|
||||
|
||||
if (!("author" in manifestFile) || typeof manifestFile.author !== "string")
|
||||
errors.push("Author fehlt");
|
||||
|
||||
if (!("url" in manifestFile) || typeof manifestFile.url !== "string") errors.push("Url fehlt");
|
||||
|
||||
if (!("version" in manifestFile) || typeof manifestFile.version !== "string")
|
||||
errors.push("Version fehlt");
|
||||
|
||||
if (
|
||||
!("permissions" in manifestFile) ||
|
||||
typeof manifestFile.permissions !== "object" ||
|
||||
manifestFile.permissions === null
|
||||
) {
|
||||
errors.push("Berechtigungen fehlen");
|
||||
}
|
||||
|
||||
if (errors.length) throw errors;
|
||||
|
||||
/* const permissions = manifestFile.permissions as Partial<IHaexHubExtensionManifest["permissions"]>;
|
||||
if (
|
||||
("database" in permissions &&
|
||||
(typeof permissions.database !== "object" || permissions.database === null)) ||
|
||||
("filesystem" in permissions && typeof permissions.filesystem !== "object") ||
|
||||
permissions.filesystem === null
|
||||
) {
|
||||
return false;
|
||||
} */
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const readManifestFileAsync = async (extensionId: string, version: string | number) => {
|
||||
try {
|
||||
if (!extensionDirectory)
|
||||
throw new Error("Kein Ordner für Erweiterung angegeben");
|
||||
const checkDirectory = await checkExtensionDirectoryAsync(
|
||||
extensionDirectory
|
||||
);
|
||||
const extensionPath = await getExtensionPathAsync(extensionId, `${version}`);
|
||||
const manifestPath = await join(extensionPath, manifestFileName);
|
||||
const manifest = (await JSON.parse(
|
||||
await readTextFile(manifestPath)
|
||||
)) as IHaexHubExtensionManifest;
|
||||
|
||||
const manifestPath = await join(extensionDirectory, "manifest.json");
|
||||
const manifest = await JSON.parse(await readTextFile(manifestPath));
|
||||
/*
|
||||
TODO implement await checkManifets(manifest);
|
||||
*/
|
||||
return manifest;
|
||||
} catch (error) {
|
||||
console.error("ERROR readManifestFileAsync", error);
|
||||
}
|
||||
};
|
||||
|
||||
console.log("manifest", manifest);
|
||||
return;
|
||||
const installAsync = async (extensionDirectory: string | null, global: boolean = true) => {
|
||||
try {
|
||||
if (!extensionDirectory) throw new Error("Kein Ordner für Erweiterung angegeben");
|
||||
const manifestPath = await join(extensionDirectory, manifestFileName);
|
||||
const manifest = (await JSON.parse(
|
||||
await readTextFile(manifestPath)
|
||||
)) as IHaexHubExtensionManifest;
|
||||
|
||||
const destination = await getExtensionPathAsync(manifest.id, manifest.version);
|
||||
|
||||
await checkSourceExtensionDirectoryAsync(extensionDirectory);
|
||||
|
||||
await invoke("copy_directory", { source: extensionDirectory, destination });
|
||||
|
||||
const logoFilePath = await join(destination, "logo.svg");
|
||||
const logoSvg = await readTextFile(logoFilePath);
|
||||
|
||||
const { currentVault } = storeToRefs(useVaultStore());
|
||||
const res = await currentVault.value?.drizzle.insert(haexExtensions).values({
|
||||
id: manifest.id,
|
||||
name: manifest.name,
|
||||
author: manifest.author,
|
||||
enabled: true,
|
||||
url: manifest.url,
|
||||
version: manifest.version,
|
||||
icon: logoSvg,
|
||||
});
|
||||
|
||||
console.log("insert extensions", res);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
/*
|
||||
@ -213,9 +285,158 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||
}
|
||||
};
|
||||
|
||||
const extensionEntry = computedAsync(
|
||||
async () => {
|
||||
console.log("extensionEntry start", currentExtension.value);
|
||||
const regex = /((href|src)=["'])([^"']+)(["'])/g;
|
||||
if (!currentExtension.value?.id || !currentExtension.value.version) {
|
||||
console.log("extension id or entry missing", currentExtension.value);
|
||||
return "no mani: " + currentExtension.value;
|
||||
}
|
||||
//return "wadahadedzdz";
|
||||
|
||||
const extensionPath = await getExtensionPathAsync(
|
||||
currentExtension.value?.id,
|
||||
currentExtension.value?.version
|
||||
); //await join(await resourceDir(), currentExtension.value.. extensionDir, entryFileName);
|
||||
|
||||
console.log("extensionEntry extensionPath", extensionPath);
|
||||
const manifest = await readManifestFileAsync(
|
||||
currentExtension.value.id,
|
||||
currentExtension.value.version
|
||||
);
|
||||
|
||||
if (!manifest) return "no manifest readable";
|
||||
|
||||
const entryPath = await join(extensionPath, manifest.entry);
|
||||
|
||||
return `asset://localhost/${entryPath}`;
|
||||
let entryHtml = await readTextFile(entryPath);
|
||||
|
||||
console.log("entryHtml", entryHtml);
|
||||
const replacements = [];
|
||||
let match;
|
||||
while ((match = regex.exec(entryHtml)) !== null) {
|
||||
const [fullMatch, prefix, attr, resource, suffix] = match;
|
||||
if (!resource.startsWith("http")) {
|
||||
replacements.push({ match: fullMatch, resource, prefix, suffix });
|
||||
}
|
||||
}
|
||||
|
||||
for (const { match, resource, prefix, suffix } of replacements) {
|
||||
const fileContent = await readTextFile(await join(extensionPath, resource));
|
||||
const blob = new Blob([fileContent], { type: getMimeType(resource) });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
console.log("blob", resource, blobUrl);
|
||||
entryHtml = entryHtml.replace(match, `${prefix}${blobUrl}${suffix}`);
|
||||
}
|
||||
|
||||
console.log("entryHtml", entryHtml);
|
||||
|
||||
const blob = new Blob([entryHtml], { type: "text/html" });
|
||||
const iframeSrc = URL.createObjectURL(blob);
|
||||
|
||||
console.log("iframeSrc", iframeSrc);
|
||||
|
||||
/* const path = convertFileSrc(extensionDir, manifest.entry);
|
||||
console.log("final path", path); */
|
||||
//manifest.entry = iframeSrc;
|
||||
return iframeSrc;
|
||||
/* await join(
|
||||
path, //`file:/${extensionDirectory}`,
|
||||
manifest.entry
|
||||
); */
|
||||
// Modul-Datei laden
|
||||
//const modulePathFull = await join(basePath, manifest.main);
|
||||
/* const manifest: PluginManifest = await invoke('load_plugin', {
|
||||
manifestPath,
|
||||
}); */
|
||||
/* const iframe = document.createElement('iframe');
|
||||
iframe.src = manifest.entry;
|
||||
iframe.setAttribute('sandbox', 'allow-scripts');
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.height = '100%';
|
||||
iframe.style.border = 'none'; */
|
||||
/* const addonApi = {
|
||||
db_execute: async (sql: string, params: string[] = []) => {
|
||||
return invoke('db_execute', {
|
||||
addonId: manifest.name,
|
||||
sql,
|
||||
params,
|
||||
});
|
||||
},
|
||||
db_select: async (sql: string, params: string[] = []) => {
|
||||
return invoke('db_select', {
|
||||
addonId: manifest.name,
|
||||
sql,
|
||||
params,
|
||||
});
|
||||
},
|
||||
}; */
|
||||
/* iframe.onload = () => {
|
||||
iframe.contentWindow?.postMessage(
|
||||
{ type: 'init', payload: addonApi },
|
||||
'*'
|
||||
);
|
||||
};
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.source === iframe.contentWindow) {
|
||||
const { type } = event.data;
|
||||
if (type === 'ready') {
|
||||
console.log(`Plugin ${manifest.name} ist bereit`);
|
||||
}
|
||||
}
|
||||
}); */
|
||||
/* plugins.value.push({ name: manifest.name, entry: manifest.entry });
|
||||
|
||||
console.log(`Plugin ${manifest.name} geladen.`); */
|
||||
},
|
||||
null,
|
||||
{ lazy: true }
|
||||
);
|
||||
|
||||
const loadExtensionsAsync = async () => {
|
||||
const { currentVault } = storeToRefs(useVaultStore());
|
||||
|
||||
/* const query = db
|
||||
.select()
|
||||
.from(haexExtensions)
|
||||
//.where(sql`${haexExtensions.enabled} = "1"`);
|
||||
.where(eq(haexExtensions.enabled, true)); */
|
||||
const extensions = await currentVault.value?.drizzle
|
||||
.select()
|
||||
.from(haexExtensions)
|
||||
.where(eq(haexExtensions.enabled, true));
|
||||
|
||||
//const manifest = readTextFile(manifestFileName)
|
||||
//const { sql, params } = query.toSQL();
|
||||
//const extensions = await invoke<any>("sql_select", { sql, params });
|
||||
console.log("loadExtensionsAsync ", extensions);
|
||||
availableExtensions.value =
|
||||
extensions?.map((extension) => ({
|
||||
id: extension.id,
|
||||
name: extension.name ?? "",
|
||||
icon: extension.icon ?? "",
|
||||
tooltip: extension.name ?? "",
|
||||
version: extension.version ?? "",
|
||||
})) ?? [];
|
||||
};
|
||||
|
||||
return {
|
||||
availableExtensions,
|
||||
checkManifest,
|
||||
currentExtension,
|
||||
extensionEntry,
|
||||
installAsync,
|
||||
isActive,
|
||||
loadExtensionsAsync,
|
||||
readManifestFileAsync,
|
||||
};
|
||||
});
|
||||
|
||||
const getMimeType = (file: string) => {
|
||||
if (file.endsWith(".css")) return "text/css";
|
||||
if (file.endsWith(".js")) return "text/javascript";
|
||||
return "text/plain";
|
||||
};
|
||||
|
||||
@ -2,31 +2,33 @@ import { getSingleRouteParam } from "~/composables/helper";
|
||||
import type { RouteLocationRaw, RouteLocationAsRelativeGeneric } from "vue-router";
|
||||
|
||||
export interface ISidebarItem {
|
||||
name: string;
|
||||
icon: string;
|
||||
tooltip?: string;
|
||||
id: string;
|
||||
to?: RouteLocationAsRelativeGeneric;
|
||||
name: string;
|
||||
icon: string;
|
||||
tooltip?: string;
|
||||
id: string;
|
||||
to?: RouteLocationAsRelativeGeneric;
|
||||
}
|
||||
|
||||
export const useSidebarStore = defineStore("sidebarStore", () => {
|
||||
const menu = ref<ISidebarItem[]>([
|
||||
{
|
||||
id: "haex-browser",
|
||||
name: "Haex Browser",
|
||||
icon: "solar:global-outline",
|
||||
to: { name: "haexBrowser" },
|
||||
},
|
||||
const isVisible = ref(true);
|
||||
|
||||
{
|
||||
id: "haex-extensions-add",
|
||||
name: "Haex Extensions",
|
||||
icon: "gg:extension",
|
||||
to: { name: "haexExtensionAdd" },
|
||||
},
|
||||
]);
|
||||
const menu = ref<ISidebarItem[]>([
|
||||
{
|
||||
id: "haex-browser",
|
||||
name: "Haex Browser",
|
||||
icon: "solar:global-outline",
|
||||
to: { name: "haexBrowser" },
|
||||
},
|
||||
|
||||
/* const loadAsync = async (id: string) => {
|
||||
{
|
||||
id: "haex-extensions-add",
|
||||
name: "Haex Extensions",
|
||||
icon: "gg:extension",
|
||||
to: { name: "extensionOverview" },
|
||||
},
|
||||
]);
|
||||
|
||||
/* const loadAsync = async (id: string) => {
|
||||
extensions.value.some(async (extension) => {
|
||||
if (extension.id === id) {
|
||||
await navigateTo(
|
||||
@ -37,8 +39,9 @@ export const useSidebarStore = defineStore("sidebarStore", () => {
|
||||
});
|
||||
}; */
|
||||
|
||||
return {
|
||||
menu,
|
||||
//loadAsync,
|
||||
};
|
||||
return {
|
||||
menu,
|
||||
isVisible,
|
||||
//loadAsync,
|
||||
};
|
||||
});
|
||||
|
||||
@ -57,85 +57,83 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
);
|
||||
|
||||
const openAsync = async ({ path = "", password }: { path: string; password: string }) => {
|
||||
const sqlitePath = path?.startsWith("sqlite:") ? path : `sqlite:${path}`;
|
||||
try {
|
||||
console.log("try to open db", path, password);
|
||||
|
||||
console.log("try to open db", path, password);
|
||||
|
||||
const result = await invoke<string>("open_encrypted_database", {
|
||||
path,
|
||||
key: password,
|
||||
});
|
||||
|
||||
console.log("open vault from store", result);
|
||||
if (!(await testDatabaseReadAsync())) throw new Error("Passwort falsch");
|
||||
//const db = await Database.load(sqlitePath);
|
||||
|
||||
const vaultId = await getVaultIdAsync(path);
|
||||
const seperator = platform() === "windows" ? "\\" : "/";
|
||||
const fileName = path.split(seperator).pop();
|
||||
console.log("opened db fileName", fileName, vaultId);
|
||||
|
||||
openVaults.value = {
|
||||
...openVaults.value,
|
||||
[vaultId]: {
|
||||
//database: db,
|
||||
const result = await invoke<string>("open_encrypted_database", {
|
||||
path,
|
||||
password,
|
||||
name: fileName ?? path,
|
||||
drizzle: drizzle<typeof schema>(
|
||||
async (sql, params, method) => {
|
||||
let rows: any = [];
|
||||
let results = [];
|
||||
key: password,
|
||||
});
|
||||
|
||||
// If the query is a SELECT, use the select method
|
||||
if (isSelectQuery(sql)) {
|
||||
rows = await invoke("db_select", { sql, params }).catch((e) => {
|
||||
console.error("SQL Error:", e);
|
||||
return [];
|
||||
});
|
||||
} else {
|
||||
// Otherwise, use the execute method
|
||||
rows = await invoke("db_execute", { sql, params }).catch((e) => {
|
||||
console.error("SQL Error:", e);
|
||||
return [];
|
||||
});
|
||||
return { rows: [] };
|
||||
}
|
||||
console.log("open vault from store", result);
|
||||
//const db = await Database.load(sqlitePath);
|
||||
|
||||
rows = rows.map((row: any) => {
|
||||
return Object.values(row);
|
||||
});
|
||||
const vaultId = await getVaultIdAsync(path);
|
||||
const seperator = platform() === "windows" ? "\\" : "/";
|
||||
const fileName = path.split(seperator).pop();
|
||||
console.log("opened db fileName", fileName, vaultId);
|
||||
|
||||
// If the method is "all", return all rows
|
||||
results = method === "all" ? rows : rows[0];
|
||||
openVaults.value = {
|
||||
...openVaults.value,
|
||||
[vaultId]: {
|
||||
//database: db,
|
||||
path,
|
||||
password,
|
||||
name: fileName ?? path,
|
||||
drizzle: drizzle<typeof schema>(
|
||||
async (sql, params: unknown[], method) => {
|
||||
let rows: any = [];
|
||||
let results = [];
|
||||
|
||||
return { rows: results };
|
||||
},
|
||||
// Pass the schema to the drizzle instance
|
||||
{ schema: schema, logger: true }
|
||||
),
|
||||
},
|
||||
};
|
||||
// If the query is a SELECT, use the select method
|
||||
if (isSelectQuery(sql)) {
|
||||
console.log("sql_select", sql, params);
|
||||
rows = await invoke("sql_select", { sql, params }).catch((e) => {
|
||||
console.error("SQL select Error:", e, sql, params);
|
||||
return [];
|
||||
});
|
||||
console.log("select", rows);
|
||||
} else {
|
||||
// Otherwise, use the execute method
|
||||
rows = await invoke("sql_execute", { sql, params }).catch((e) => {
|
||||
console.error("SQL execute Error:", e, sql, params);
|
||||
return [];
|
||||
});
|
||||
return { rows: [] };
|
||||
}
|
||||
|
||||
const { addVaultAsync } = useLastVaultStore();
|
||||
await addVaultAsync({ path });
|
||||
/* rows = rows.map((row: any) => {
|
||||
return Object.values(row);
|
||||
}); */
|
||||
|
||||
return vaultId;
|
||||
};
|
||||
console.log("select after map", rows);
|
||||
// If the method is "all", return all rows
|
||||
results = method === "all" ? rows : rows[0];
|
||||
|
||||
const createTable = () => {
|
||||
console.log("ddd", schema.testTable.getSQL().queryChunks);
|
||||
return { rows: results };
|
||||
},
|
||||
// Pass the schema to the drizzle instance
|
||||
{ schema: schema, logger: true }
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
schema.testTable.getSQL().queryChunks.forEach((chunk) => {
|
||||
const res = currentVault.value?.drizzle.run(chunk);
|
||||
console.log("create table", res);
|
||||
});
|
||||
//if (!(await testDatabaseReadAsync())) throw new Error("Passwort falsch");
|
||||
|
||||
const { addVaultAsync } = useLastVaultStore();
|
||||
await addVaultAsync({ path });
|
||||
|
||||
return vaultId;
|
||||
} catch (error) {
|
||||
console.error("Error openAsync ", error);
|
||||
return false;
|
||||
//if (error === "file is not a database") throw new Error("Passwort falsch");
|
||||
}
|
||||
};
|
||||
|
||||
const testDatabaseReadAsync = async () => {
|
||||
try {
|
||||
currentVault.value?.drizzle.select({ count: count() }).from(schema.haexExtensions);
|
||||
return true;
|
||||
return currentVault.value?.drizzle.select({ count: count() }).from(schema.haexExtensions);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
@ -186,7 +184,6 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
||||
openVaults,
|
||||
refreshDatabaseAsync,
|
||||
read_only,
|
||||
createTable,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user