switch to nuxt layers

This commit is contained in:
Martin Drechsel
2025-05-22 06:55:53 +02:00
parent 2a69c07743
commit 96fd11d3d6
36 changed files with 101 additions and 1477 deletions

View File

@ -1,7 +0,0 @@
// ~/app.config.ts
export default defineAppConfig({
icon: {
mode: "css",
cssLayer: "base",
},
});

View File

@ -2,19 +2,11 @@ import tailwindcss from "@tailwindcss/vite";
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: "2024-11-01",
modules: [
"nuxt-zod-i18n",
"@nuxtjs/i18n",
"@pinia/nuxt",
"@vueuse/nuxt",
"@nuxt/icon",
"nuxt-snackbar",
extends: [
"github:haexhub/haex-base-ui"
],
compatibilityDate: "2024-11-01",
imports: {
@ -39,14 +31,7 @@ export default defineNuxtConfig({
],
},
/* svgo: {
autoImportPath: '~/assets/svg/',
dts: true,
}, */
i18n: {
/* i18n: {
strategy: "prefix_and_default",
defaultLocale: "de",
vueI18n: "~/i18n/i18n.config.ts",
@ -65,14 +50,9 @@ export default defineNuxtConfig({
bundle: {
optimizeTranslationDirective: false,
},
},
}, */
zodI18n: {
localeCodesMapping: {
"en-GB": "en",
"de-DE": "de",
},
},
runtimeConfig: {
public: {
@ -83,8 +63,6 @@ export default defineNuxtConfig({
},
},
css: ["~/assets/css/main.css"],
devtools: { enabled: true },
srcDir: "./src",

View File

@ -41,6 +41,7 @@
"nuxt-zod-i18n": "^1.11.5",
"tailwindcss": "^4.1.7",
"vue": "^3.5.14",
"vue-router": "^4.5.1",
"zod": "^3.25.4"
},
"devDependencies": {

4
pnpm-lock.yaml generated
View File

@ -86,6 +86,9 @@ importers:
vue:
specifier: ^3.5.14
version: 3.5.14(typescript@5.8.3)
vue-router:
specifier: ^4.5.1
version: 4.5.1(vue@3.5.14(typescript@5.8.3))
zod:
specifier: ^3.25.4
version: 3.25.4
@ -3307,7 +3310,6 @@ packages:
libsql@0.5.10:
resolution: {integrity: sha512-lQu5RLqDLFuo6H5SuR3CnEstGVph77Jd8lm3fdW64p6tUjOC0X8Z9PlfAdZYxWyirYLH9uwcEZUrABsNKYwOiQ==}
cpu: [x64, arm64, wasm32, arm]
os: [darwin, linux, win32]
lightningcss-darwin-arm64@1.30.1:

View File

@ -19,17 +19,18 @@
],
"security": {
"csp": {
"default-src": "'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost; default-src 'self' asset: http://asset.localhost",
"script-src": ["'self'", "haex-extension:"],
"style-src": ["'self'", "haex-extension:"],
"connect-src": ["'self'", "haex-extension:"],
"img-src": ["'self'", "haex-extension:", "data:"],
"font-src": ["'self'", "haex-extension:", "data:"],
"media-src": "'self' haex-extension: data: blob: asset:"
"default-src": ["'self'", "ipc: http://ipc.localhost", "blob:"],
"media-src": [
"'self'",
"asset:",
"http://asset.localhost",
"blob:",
"asset: http://asset.localhost"
]
},
"assetProtocol": {
"enable": true,
"scope": ["$RESOURCE/**", "$APPDATA/**"]
"scope": ["*"]
}
}
},

View File

@ -1,9 +0,0 @@
@import "tailwindcss";
@import "flyonui/variants.css";
@plugin "@iconify/tailwind4";
@plugin "flyonui" {
themes: all;
}
@source "../../node_modules/flyonui/flyonui.js";

View File

@ -39,15 +39,4 @@ const isActive = computed(() => {
const linkRef = useTemplateRef('linkRef')
const triggerNavigate = () => linkRef.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

@ -1,43 +0,0 @@
<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>

View File

@ -1,79 +0,0 @@
<template>
<div class="fixed z-10">
<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"
aria-haspopup="menu"
aria-expanded="false"
aria-label="Menu"
>
<Icon
:name="icon"
size="46"
/>
</button>
<div
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-60 rtl:left-0 bg-transparent"
role="menu"
aria-orientation="vertical"
:aria-labelledby="id"
>
<ul
class="dropdown-open:ease-in dropdown-open:translate-x-0 -translate-x-1 rtl:translate-x-1 transition duration-300 ease-out"
data-dropdown-transition
>
<li
v-for="link in menu"
class="dropdown-item hover:bg-transparent"
>
<NuxtLinkLocale
v-if="link.to"
:to="link.to"
class="btn btn-primary flex items-center no-underline rounded-lg flex-nowrap"
>
<Icon
v-if="link.icon"
:name="link.icon"
class="me-3"
/>
{{ link.label }}
</NuxtLinkLocale>
<button
v-else
@click="link.action"
class="link hover:link-primary flex items-center no-underline w-full"
>
<Icon
v-if="link.icon"
:name="link.icon"
class="me-3"
/>
{{ link.label }}
</button>
</li>
</ul>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { IActionMenuItem } from './types';
defineProps({
menu: {
type: Array as PropType<IActionMenuItem[]>,
},
icon: {
type: String,
default: 'mdi:plus',
},
});
const id = useId();
</script>

View File

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

View File

@ -1,8 +0,0 @@
import type { RouteLocationRaw } from 'vue-router';
export interface IActionMenuItem {
label: string;
icon?: string;
action?: () => Promise<unknown>;
to?: RouteLocationRaw;
}

View File

@ -1,95 +0,0 @@
<template>
<slot name="trigger" :id> </slot>
<div
:id
class="overlay modal overlay-open:opacity-100 hidden modal-middle [--tab-accessibility-limited:false] overflow-scroll p-0 sm:p-4"
role="dialog"
ref="modalRef"
>
<div
class="overlay-animation-target overlay-open:mt-4 overlay-open:duration-500 mt-12 transition-all ease-out modal-dialog overlay-open:opacity-100"
>
<div class="modal-content">
<div class="modal-header">
<slot name="title">
<h3 v-if="title" class="modal-title text-base sm:text-lg">
{{ title }}
</h3>
</slot>
<button
type="button"
class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
:aria-label="t('close')"
@click="open = false"
tabindex="1"
>
<Icon name="mdi:close" size="18" />
</button>
</div>
<div class="modal-body text-sm sm:text-base py-1">
<slot />
</div>
<div class="modal-footer flex-wrap">
<slot name="buttons" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { HSOverlay } from "flyonui/flyonui";
export interface IDom {
class?: String;
text: String;
}
const id = useId();
defineProps({
title: {
type: String,
default: "",
},
});
const open = defineModel<boolean>("open", { default: false });
const { t } = useI18n();
const modalRef = useTemplateRef("modalRef");
const modal = ref<HSOverlay>();
watch(open, async () => {
console.log("open modal", open.value);
if (open.value) {
await modal.value?.open();
} else {
await modal.value?.close(true);
console.log("close dialog");
}
});
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);
open.value = false;
});
});
</script>
<i18n lang="json">
{
"de": {
"close": "Schließen"
},
"en": {
"close": "Close"
}
}
</i18n>

View File

@ -1,58 +0,0 @@
<template>
<button
type="button"
class="btn btn-primary"
aria-haspopup="dialog"
aria-expanded="false"
aria-controls="basic-modal"
data-overlay="#basic-modal"
>
Open modal
</button>
<div
id="basic-modal"
class="overlay modal overlay-open:opacity-100 hidden"
role="dialog"
tabindex="-1"
>
<div class="modal-dialog overlay-open:opacity-100">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Dialog Title</h3>
<button
type="button"
class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
aria-label="Close"
data-overlay="#basic-modal"
>
<span class="icon-[tabler--x] size-4"></span>
</button>
</div>
<div class="modal-body">
This is some placeholder content to show the scrolling behavior for
modals. Instead of repeating the text in the modal, we use an inline
style to set a minimum height, thereby extending the length of the
overall modal and demonstrating the overflow scrolling. When content
becomes longer than the height of the viewport, scrolling will move
the modal as needed.
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-soft btn-secondary"
data-overlay="#basic-modal"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
>
Save changes
</button>
</div>
</div>
</div>
</div>
</template>

View File

@ -1,5 +0,0 @@
<template>
<h3 class="modal-title">
<slot />
</h3>
</template>

View File

@ -1,55 +0,0 @@
<template>
<div class="dropdown relative inline-flex" :class="dropdownClass">
<button
:id
class="dropdown-toggle"
:class="activatorClass"
aria-haspopup="menu"
aria-expanded="false"
:aria-label="label"
>
<slot name="activator">
<slot name="label">
{{ label }}
</slot>
<span
class="icon-[tabler--chevron-down] dropdown-open:rotate-180 size-4"
>
</span>
</slot>
</button>
<slot name="items" :items>
<ul
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-28"
role="menu"
aria-orientation="vertical"
:aria-labelledby="id"
>
<component
:is="itemIs"
class="dropdown-item"
v-for="item in items"
@click="$emit('select', item)"
>
<slot name="item" :item>
{{ item }}
</slot>
</component>
</ul>
</slot>
</div>
</template>
<script setup lang="ts" generic="T">
const { itemIs = 'li' } = defineProps<{
label?: string
items?: T[]
itemIs?: string
activatorClass?: string
dropdownClass?: string
}>()
defineEmits<{ select: [T] }>()
const id = useId()
</script>

View File

@ -1,33 +0,0 @@
<template>
<UiDropdown
:items="availableLocales"
@select="(locale) => $emit('select', locale)"
activator-class="btn btn-primary"
>
<template #label>
<Icon :name="flags[locale]" />
</template>
<template #item="{ item }">
<div class="flex gap-2 justify-center">
<Icon :name="flags[item]" class="my-auto" />
<p>
{{ item }}
</p>
</div>
</template>
</UiDropdown>
</template>
<script setup lang="ts">
import { type Locale } from 'vue-i18n'
const flags = {
de: 'emojione:flag-for-germany',
en: 'emojione:flag-for-united-kingdom',
}
const { availableLocales, locale } = useI18n()
defineEmits<{ select: [Locale] }>()
</script>

View File

@ -1,26 +0,0 @@
<template>
<UiDropdown
:items="availableThemes"
@select="(theme) => $emit('select', theme)"
activator-class="btn btn-primary"
>
<template #label>
<Icon :name="currentTheme.icon" />
</template>
<template #item="{ item }">
<div class="flex gap-2 justify-center">
<Icon :name="item.icon" class="my-auto" />
<p>
{{ item.name }}
</p>
</div>
</template>
</UiDropdown>
</template>
<script setup lang="ts">
const { availableThemes, currentTheme } = storeToRefs(useUiStore())
defineEmits<{ select: [ITheme] }>()
</script>

View File

@ -1,147 +0,0 @@
<template>
<div>
<fieldset class="join w-full pt-0.5">
<slot name="prepend" />
<div class="input join-item">
<Icon v-if="prependIcon" :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 v-if="appendIcon" :name="appendIcon" class="my-auto shrink-0" />
</div>
<slot name="append" class="h-auto" />
<UiButton
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'" />
</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">
{{ error }}
</span>
</span>
</div>
</template>
<script setup lang="ts">
import { type ZodSchema } from 'zod'
const inputRef = useTemplateRef('inputRef')
defineExpose({ inputRef })
defineOptions({
inheritAttrs: false,
})
const props = defineProps({
placeholder: {
type: String,
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'
>,
default: 'text',
},
label: String,
name: String,
prependIcon: {
type: String,
default: '',
},
prependLabel: String,
appendIcon: {
type: String,
default: '',
},
appendLabel: String,
rules: Object as PropType<ZodSchema>,
checkInput: Boolean,
withCopyButton: Boolean,
autofocus: Boolean,
read_only: Boolean,
})
const input = defineModel<string | number | undefined | null>({
default: '',
required: true,
})
const { currentScreenSize } = storeToRefs(useUiStore())
onMounted(() => {
if (props.autofocus && inputRef.value) inputRef.value.focus()
})
const errors = defineModel<string[] | undefined>('errors')
const id = useId()
watch(input, () => checkInput())
watch(
() => props.checkInput,
() => {
checkInput()
}
)
const emit = defineEmits(['error'])
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>

View File

@ -1,53 +0,0 @@
<template>
<UiInput
:check-input
:label="label || t('password')"
:placeholder="placeholder || t('password')"
:rules
:type="type"
:autofocus
v-model="value"
>
<template #append>
<UiButton
class="btn-outline btn-accent btn-square h-auto"
@click="tooglePasswordType"
>
<Icon :name="type === 'password' ? 'mdi:eye-off' : 'mdi:eye'" />
</UiButton>
</template>
</UiInput>
</template>
<script setup lang="ts">
import type { ZodSchema } from 'zod'
const { t } = useI18n()
const value = defineModel<string | number | null | undefined>()
defineProps({
label: String,
placeholder: String,
checkInput: Boolean,
rules: Object as PropType<ZodSchema>,
autofocus: Boolean,
})
const type = ref<'password' | 'text'>('password')
const tooglePasswordType = () => {
type.value = type.value === 'password' ? 'text' : 'password'
}
</script>
<i18n lang="json">
{
"de": {
"password": "Passwort"
},
"en": {
"password": "Password"
}
}
</i18n>

View File

@ -1,56 +0,0 @@
<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
v-if="read_only"
@click="openUrl(`${value}`)"
class="btn-outline btn-accent h-auto"
:class="{
disabled: !value?.length,
}"
>
<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 { currentScreenSize } = storeToRefs(useUiStore());
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>

View File

@ -1,91 +0,0 @@
<template>
<svg
id="logo"
class="fill-current stroke-current w-[160px]"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 286.3 85"
xml:space="preserve"
>
<switch>
<g>
<g class="logo-imagesss">
<circle
fill="white"
cx="42.5"
cy="42.5"
r="40"
></circle>
<path
d="M42.3,83.4c-22.6,0-40.9-18.4-40.9-40.9c0-22.6,18.4-40.9,40.9-40.9c22.6,0,40.9,18.4,40.9,40.9
C83.3,65.1,64.9,83.4,42.3,83.4z M42.3,5.8C22.1,5.8,5.7,22.3,5.7,42.5s16.5,36.7,36.7,36.7S79,62.7,79,42.5S62.6,5.8,42.3,5.8z
"
></path>
<g>
<g>
<polygon
points="38.8,69.8 38.8,31.7 22.3,31.7 22.3,38.5 29.8,38.5 29.8,69.8 "
></polygon>
<path
d="M34.1,13.2c-3.3,0-6,2.6-6,5.9c0,3.3,2.6,6,5.9,6c3.3,0,6-2.6,6-6
C39.9,15.9,37.3,13.2,34.1,13.2z"
></path>
</g>
<g>
<polygon
points="45.9,69.8 45.9,31.7 62.4,31.7 62.4,38.5 54.9,38.5 54.9,69.8 "
></polygon>
<path
d="M50.6,13.2c3.3,0,6,2.6,6,5.9c0,3.3-2.6,6-5.9,6c-3.3,0-6-2.6-6-6
C44.8,15.9,47.4,13.2,50.6,13.2z"
></path>
</g>
</g>
</g>
<g class="logo-textsss">
<path
d="M136.1,63.6c-4,0-5.3-2.6-5.3-6V38.5h10.6v-6.7h-10.6v-6.7h-9c0,7,0,29.1,0,32.7
c0,4.2,1.6,7.5,3.8,9.7c2.3,2.2,5.6,3.3,9.8,3.3c5.1,0,8.4-1.8,10.6-4.2l-4.7-6C140.2,62.1,138.5,63.6,136.1,63.6z"
></path>
<path
d="M217.7,30.7c-4.9,0-8.2,1.6-10.4,3.8c-2.2-2.2-5.5-3.8-10.4-3.8c-15,0-14.9,12.1-14.9,15
s0,24.1,0,24.1h9V45.7c0-8.5,4.9-8.3,5.9-8.3c1,0,5.9-0.3,5.9,8.3v24.1h0h9h0V45.7c0-8.5,4.9-8.3,5.9-8.3c1,0,5.9-0.3,5.9,8.3
v24.1h9c0,0,0-21.2,0-24.1C232.6,42.8,232.7,30.7,217.7,30.7z"
></path>
<path
d="M273.2,46.4c-4.3-1.4-6-2.5-6-5.2c0-2,1.1-3.8,4.3-3.8c3.2,0,4.5,3.3,5.1,4.8
c2.7-1.5,5.3-2.9,6.6-3.6c-2.5-6-6.3-7.9-12-7.9c-8,0-11.7,5.5-11.7,10.6c0,6.5,2.9,9.8,11.2,12.2c6,1.8,6.5,4.7,6.2,6.2
c-0.3,1.7-1.6,3.6-5.3,3.6c-3.6,0-5.8-3.8-6.8-5.4c-1.8,1.1-3.4,2.1-6.4,3.8c2.1,5,6.8,9.1,13.5,9.1c7.9,0,12.9-5.1,12.9-12.1
C284.9,51,279.6,48.5,273.2,46.4z"
></path>
<g>
<polygon
points="239.7,69.8 239.7,31.7 256.2,31.7 256.2,38.5 248.7,38.5 248.7,69.8 "
></polygon>
<path
d="M244.4,13.2c3.3,0,6,2.6,6,5.9c0,3.3-2.6,6-5.9,6c-3.3,0-6-2.6-6-6
C238.6,15.9,241.2,13.2,244.4,13.2z"
></path>
</g>
<g>
<polygon
points="114.7,69.8 114.7,31.7 98.1,31.7 98.1,38.5 105.7,38.5 105.7,69.8 "
></polygon>
<path
d="M110,13.2c-3.3,0-6,2.6-6,5.9c0,3.3,2.6,6,5.9,6c3.3,0,6-2.6,6-6C115.8,15.9,113.2,13.2,110,13.2
z"
></path>
</g>
<path
d="M176.4,52.4v-3.7c0-12.3-4.7-18-14.8-18c-9.3,0-14.7,6.6-14.7,18v4c0,11.5,5.8,18.2,15.8,18.2
c6.6,0,10.8-3.7,12.7-7.9c-2.2-1.4-4.6-2.8-6.1-3.8c-1,1.7-2.9,4.4-6.7,4.4c-5.8,0-7-5.9-7-10.9v-0.2H176.4z M155.7,45.7
c0.2-7.1,3.3-8.2,6-8.2c2.6,0,5.9,1,6,8.2H155.7z"
></path>
</g>
</g>
</switch>
</svg>
</template>

View File

@ -1,24 +0,0 @@
<template>
<UiTooltip
:tooltip="tooltip ?? label"
direction="right-end"
>
<button
class="link flex items-center justify-center py-3 hover:text-primary tooltip-toogle bg w-full"
@click="$emit('click')"
>
<Icon
:name="icon"
class="size-8"
/>
</button>
</UiTooltip>
</template>
<script setup lang="ts">
defineProps<{
label: string;
tooltip?: string;
icon: string;
}>();
</script>

View File

@ -1,62 +0,0 @@
<template>
<aside
class="flex shrink-0 transition-[width] ease-in duration-300 z-30 h-full overflow-hidden fixed sm:relative left-0 shadow border-r border-base-300/90"
>
<div class="sm:flex flex-col w-14 bg-base-200 shrink-0 h-full hidden">
<img src="/logo.svg" class="bg-primary p-3 size-16" />
<div class="flex flex-col justify-between h-full overflow-y-scroll z-10">
<div class="flex flex-col space-y-2 text-base-content/90"></div>
</div>
</div>
<!-- <div class="bg-base-100 flex flex-col w-full overflow-clip">
<div
class="h-16 flex items-center sm:justify-center justify-end md:justify-start bg-base-300 shrink-0"
>
<button
class="top-3 left-2 absolute z-30 duration-1000 btn btn-square btn-primary transition-opacity btn-outline sm:hidden"
@click="show = !show"
>
<Icon
name="mdi:menu"
size="28"
/>
</button>
<span
class="px-4 font-semibold text-base-content shrink-0 sm:bg-transparent bg-primary h-full flex items-center rounded-l-lg"
>
<p>Haex Vault</p>
</span>
<img
src="/logo.svg"
class="bg-primary p-3 size-16 shrink-0 sm:hidden rounded-r-lg"
/>
<button
class="btn btn-square btn-primary btn-outline mr-2 ml-auto hidden sm:flex"
@click="show = false"
>
<Icon
name="mdi:close"
size="28"
/>
</button>
</div>
<div class="overflow-scroll flex pb-4 relative">
<slot />
</div>
</div> -->
</aside>
</template>
<script lang="ts" setup>
defineProps({
menu: {
type: Object as PropType<ISidebarItem>,
default: () => {},
},
})
</script>

View File

@ -1,268 +0,0 @@
<template>
<nav
class="navbar bg-base-100 max-sm:rounded-box max-sm:shadow sm:border-b border-base-content/25 sm:z-[1] relative"
>
<button
type="button"
class="btn btn-text max-sm:btn-square sm:hidden 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>
</div>
<div class="navbar-end flex items-center gap-4">
<div
class="dropdown relative inline-flex [--auto-close:inside] [--offset:8] [--placement:bottom-end]"
>
<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>
<span
class="icon-[tabler--bell] text-base-content size-[1.375rem]"
></span>
</div>
</button>
<div
class="dropdown-menu dropdown-open:opacity-100 hidden"
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 max-md:max-w-60"
>
<div
class="dropdown-item"
v-for="notification in notifications"
>
<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.description }}
</small>
</div>
</div>
</div>
<a
href="#"
class="dropdown-footer justify-center gap-1"
>
<span class="icon-[tabler--eye] size-4"></span>
{{ t('notifications.view_all') }}
</a>
</div>
</div>
<div
class="dropdown relative inline-flex [--auto-close:inside] [--offset:8] [--placement:bottom-end]"
>
<button
id="dropdown-scrollable"
type="button"
class="dropdown-toggle flex items-center"
aria-haspopup="menu"
aria-expanded="false"
aria-label="Dropdown"
>
<div class="avatar">
<div class="size-9.5 rounded-full">
<img
src="https://cdn.flyonui.com/fy-assets/avatar/avatar-1.png"
alt="avatar 1"
/>
</div>
</div>
</button>
<ul
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-60"
role="menu"
aria-orientation="vertical"
aria-labelledby="dropdown-avatar"
>
<li class="dropdown-header gap-2">
<div class="avatar">
<div class="w-10 rounded-full">
<img
src="https://cdn.flyonui.com/fy-assets/avatar/avatar-1.png"
alt="avatar"
/>
</div>
</div>
<div>
<h6 class="text-base-content text-base font-semibold">
John Doe
</h6>
<small class="text-base-content/50">Admin</small>
</div>
</li>
<li>
<a
class="dropdown-item"
href="#"
>
<span class="icon-[tabler--user]"></span>
My Profile
</a>
</li>
<li>
<a
class="dropdown-item"
href="#"
>
<span class="icon-[tabler--settings]"></span>
Settings
</a>
</li>
<li>
<a
class="dropdown-item"
href="#"
>
<span class="icon-[tabler--receipt-rupee]"></span>
Billing
</a>
</li>
<li>
<a
class="dropdown-item"
href="#"
>
<span class="icon-[tabler--help-triangle]"></span>
FAQs
</a>
</li>
<li class="dropdown-footer gap-2">
<a
class="btn btn-error btn-soft btn-block"
href="#"
>
<span class="icon-[tabler--logout]"></span>
Sign out
</a>
</li>
</ul>
</div>
</div>
</nav>
<aside
id="sidebar"
class="overlay sm:shadow-none overlay-open:translate-x-0 drawer drawer-start hidden max-w-64 sm:absolute sm:z-0 sm:flex sm:translate-x-0 pt-16"
role="dialog"
tabindex="-1"
>
<div class="drawer-body px-2 pt-4">
<ul class="menu p-0">
<li v-for="item in menu">
<a href="#">
<span class="icon-[tabler--home] size-5"></span>
Home
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--user] size-5"></span>
Account
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--message] size-5"></span>
Notifications
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--mail] size-5"></span>
Email
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--calendar] size-5"></span>
Calendar
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--shopping-bag] size-5"></span>
Product
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--login] size-5"></span>
Sign In
</a>
</li>
<li>
<a href="#">
<span class="icon-[tabler--logout-2] size-5"></span>
Sign Out
</a>
</li>
</ul>
</div>
</aside>
</template>
<script setup lang="ts">
const { t } = useI18n();
const { notifications } = storeToRefs(useNotificationStore());
const { menu } = storeToRefs(useSidebarStore());
</script>
<i18n lang="yaml">
de:
notifications:
label: Benachrichtigungen
view_all: Alle ansehen
en:
notifications:
label: Notifications
view_all: View all
</i18n>

View File

@ -1,7 +0,0 @@
<template>
<p
class="bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent font-black"
>
<slot />
</p>
</template>

View File

@ -1,61 +0,0 @@
<template>
<div class="tooltip [--prevent-popper:false]">
<div
class="tooltip-toggle"
aria-label="Tooltip"
>
<slot>
<button class="btn btn-square">
<Icon name="mdi:chevron-up-box-outline" />
</button>
</slot>
<span
class="tooltip-content tooltip-shown:opacity-100 tooltip-shown:visible z-40"
role="tooltip"
>
<span
class="tooltip-body"
v-bind="$attrs"
>
{{ tooltip }}
</span>
</span>
</div>
</div>
</template>
<script setup lang="ts">
import type { PropType } from 'vue';
const props = defineProps({
direction: {
type: String as PropType<
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'right'
| 'right-start'
| 'right-end'
| 'left'
| 'left-start'
| 'left-end'
>,
default: 'top',
},
tooltip: String,
trigger: {
type: String as PropType<'focus' | 'hover' | 'click'>,
default: 'hover',
},
});
defineOptions({
inheritAttrs: false,
});
</script>

View File

@ -1,13 +1,11 @@
<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>
<UiDialog
:title="t('title')"
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1 whitespace-nowrap flex-nowrap"
>
<template #trigger>
<Icon name="mdi:plus" />
{{ t('database.create') }}
</template>
<form class="flex flex-col gap-4" @submit="onCreateAsync">

View File

@ -1,14 +1,19 @@
<template>
<UiDialog v-model:open="isOpen">
<UiDialog
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1"
v-model:open="isOpen"
@click="onLoadDatabase"
>
<!-- @close="initDatabase" -->
<template #trigger>
<button
<!-- <button
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1"
@click="onLoadDatabase"
>
<Icon name="mdi:folder-open-outline" />
{{ t('database.open') }}
</button>
> -->
<Icon name="mdi:folder-open-outline" />
{{ t('database.open') }}
<!-- </button> -->
</template>
<UiInputPassword

View File

@ -1,115 +0,0 @@
export const bytesToBase64DataUrlAsync = async (
bytes: Uint8Array,
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 }));
});
};
export const blobToImageAsync = (blob: Blob): Promise<HTMLImageElement> => {
return new Promise((resolve) => {
console.log("transform blob", blob);
const url = URL.createObjectURL(blob);
let img = new Image();
img.onload = () => {
URL.revokeObjectURL(url);
resolve(img);
};
img.src = url;
});
};
export const deepToRaw = <T extends Record<string, any>>(sourceObj: T): T => {
const objectIterator = (input: any): any => {
if (Array.isArray(input)) {
return input.map((item) => objectIterator(item));
}
if (isRef(input) || isReactive(input) || isProxy(input)) {
return objectIterator(toRaw(input));
}
if (input && typeof input === "object") {
return Object.keys(input).reduce((acc, key) => {
acc[key as keyof typeof acc] = objectIterator(input[key]);
return acc;
}, {} as T);
}
return input;
};
return objectIterator(sourceObj);
};
export const readableFileSize = (sizeInByte: number | string = 0) => {
if (!sizeInByte) {
return "0 KB";
}
const size = typeof sizeInByte === "string" ? parseInt(sizeInByte) : sizeInByte;
const sizeInKb = size / 1024;
const sizeInMb = sizeInKb / 1024;
const sizeInGb = sizeInMb / 1024;
const sizeInTb = sizeInGb / 1024;
if (sizeInTb > 1) return `${sizeInTb.toFixed(2)} TB`;
if (sizeInGb > 1) return `${sizeInGb.toFixed(2)} GB`;
if (sizeInMb > 1) return `${sizeInMb.toFixed(2)} MB`;
return `${sizeInKb.toFixed(2)} KB`;
};
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 ?? "";
//console.log('found param', _param, param);
return decodeURIComponent(_param);
};
export const isRouteActive = (to: RouteLocationRawI18n, exact: boolean = false) =>
computed(() => {
const found = useRouter()
.getRoutes()
.find((route) => route.name === useLocaleRoute()(to)?.name);
//console.log('found route', found, useRouter().currentRoute.value, to);
return exact
? found?.name === useRouter().currentRoute.value.name
: found?.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 => {
return k in x;
};
export const filterAsync = async <T>(
arr: T[],
predicate: (value: T, index: number, array: T[]) => Promise<boolean>
) => {
// 1. Mappe jedes Element auf ein Promise, das zu true/false auflöst
const results = await Promise.all(arr.map(predicate));
// 2. Filtere das ursprüngliche Array basierend auf den Ergebnissen
return arr.filter((_value, index) => results[index]);
};
export const stringToHex = (str: string) =>
str
.split("")
.map((char) => char.charCodeAt(0).toString(16).padStart(2, "0"))
.join(""); // Join array into a single string
export const hexToString = (hex: string) => {
if (!hex) return "";
const parsedValue = hex
.match(/.{1,2}/g) // Split hex into pairs
?.map((byte) => String.fromCharCode(parseInt(byte, 16))) // Convert hex to char
.join(""); // Join array into a single string
return parsedValue ? parsedValue : "";
};

View File

@ -1,16 +0,0 @@
/* import de from '@/stores/sidebar/de.json';
import en from '@/stores/sidebar/en.json'; */
export default defineI18nConfig(() => {
return {
legacy: false,
messages: {
de: {
//sidebar: de,
},
en: {
//sidebar: en,
},
},
};
});

View File

@ -116,8 +116,12 @@
>
<div class="drawer-body h-full">
<ul class="menu p-0 h-full rounded-none">
<UiSidebarLink v-bind="item" v-for="item in menu" :key="item.id" />
<UiSidebarLink
<HaexSidebarLink
v-bind="item"
v-for="item in menu"
:key="item.id"
/>
<HaexSidebarLink
v-for="item in extensionLinks"
:key="item.id"
v-bind="item"

View File

@ -26,6 +26,64 @@
v-model:isOpen="passwordPromptOpen"
:path="vaultPath"
/>
<UiDialogTest />
<UiDialog> aaaaaa </UiDialog>
<button
type="button"
class="btn btn-primary"
aria-haspopup="dialog"
aria-expanded="false"
aria-controls="basic-modal"
data-overlay="#basic-modal1"
>
Open modal
</button>
<div
id="basic-modal1"
class="overlay modal overlay-open:opacity-100 hidden overlay-open:duration-300"
role="dialog"
tabindex="-1"
>
<div
class="modal-dialog overlay-open:opacity-100 overlay-open:duration-300"
>
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Dialog Title</h3>
<button
type="button"
class="btn btn-text btn-circle btn-sm absolute end-3 top-3"
aria-label="Close"
data-overlay="#basic-modal1"
>
<span class="icon-[tabler--x] size-4"></span>
</button>
</div>
<div class="modal-body">
This is some placeholder content to show the scrolling behavior
for modals. Instead of repeating the text in the modal, we use
an inline style to set a minimum height, thereby extending the
length of the overall modal and demonstrating the overflow
scrolling. When content becomes longer than the height of the
viewport, scrolling will move the modal as needed.
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-soft btn-secondary"
data-overlay="#basic-modal1"
>
Close
</button>
<button type="button" class="btn btn-primary">
Save changes
</button>
</div>
</div>
</div>
</div>
</div>
<div v-show="lastVaults.length" class="w-full">

View File

@ -1,13 +0,0 @@
import 'flyonui/flyonui';
import { type IStaticMethods } from 'flyonui/flyonui';
declare global {
interface Window {
HSStaticMethods: IStaticMethods;
}
}
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('page:finish', () => {
window.HSStaticMethods.autoInit();
});
});

View File

@ -1,6 +0,0 @@
{
"light": "Hell",
"dark": "Dunkel",
"soft": "Soft",
"corporate": "Corporate"
}

View File

@ -1,6 +0,0 @@
{
"light": "Light",
"dark": "Dark",
"soft": "Soft",
"corporate": "Corporate"
}

View File

@ -1,55 +0,0 @@
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
import de from './de.json';
import en from './en.json';
export interface ITheme {
value: string,
name: string,
icon: string
}
export const useUiStore = defineStore('uiStore', () => {
const breakpoints = useBreakpoints(breakpointsTailwind);
const currentScreenSize = computed(() =>
breakpoints.active().value.length > 0 ? breakpoints.active().value : 'xs'
);
const { t } = useI18n({
messages: {
de: { ui: de },
en: { ui: en },
},
});
const availableThemes = ref([
{
value: 'dark',
name: t('ui.dark'),
icon: 'line-md:moon-rising-alt-loop',
},
{
value: 'light',
name: t('ui.light'),
icon: 'line-md:moon-to-sunny-outline-loop-transition',
},
{ value: 'soft', name: t('ui.soft'), icon: 'line-md:paint-drop' },
{
value: 'corporate',
name: t('ui.corporate'),
icon: 'hugeicons:corporate',
},
]);
const defaultTheme = ref(availableThemes.value[0])
const currentTheme = ref(defaultTheme);
return {
availableThemes,
breakpoints,
currentScreenSize,
currentTheme,
defaultTheme,
};
});