fixed sync locale and theme

This commit is contained in:
Martin Drechsel
2025-05-19 22:55:44 +02:00
parent 0699dbef31
commit 379db8da07
13 changed files with 3498 additions and 1846 deletions

View File

@ -11,9 +11,12 @@ export default defineNuxtConfig({
"@vueuse/nuxt",
"@nuxt/icon",
"nuxt-snackbar",
"nuxt-svgo-loader",
],
imports: {
dirs: ["composables/**", "stores/**", "components/**", "pages/**", "types/**"],
},
@ -26,10 +29,23 @@ export default defineNuxtConfig({
scan: true,
includeCustomCollections: true,
},
serverBundle: { collections: ["mdi", "line-md", "solar", "gg", "emojione"] }
//collections: ["mdi", "line-md"]
serverBundle: { collections: ["mdi", "line-md", "solar", "gg", "emojione"] },
customCollections: [
{
prefix: 'my-icon',
dir: './src/assets/icons/'
},
],
},
/* svgo: {
autoImportPath: '~/assets/svg/',
dts: true,
}, */
i18n: {
strategy: "prefix_and_default",
defaultLocale: "de",
@ -90,4 +106,4 @@ export default defineNuxtConfig({
strictPort: true,
},
},
});
})

View File

@ -15,12 +15,12 @@
"drizzle:migrate": "drizzle-kit migrate"
},
"dependencies": {
"@libsql/client": "^0.15.4",
"@libsql/client": "^0.15.6",
"@nuxt/icon": "1.11.0",
"@nuxt/image": "1.10.0",
"@nuxtjs/i18n": "^9.5.3",
"@nuxtjs/i18n": "^9.5.4",
"@pinia/nuxt": "^0.11.0",
"@tailwindcss/vite": "^4.1.5",
"@tailwindcss/vite": "^4.1.7",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-dialog": "^2.2.1",
"@tauri-apps/plugin-fs": "^2.2.1",
@ -29,27 +29,29 @@
"@tauri-apps/plugin-os": "^2.2.1",
"@tauri-apps/plugin-sql": "~2.2.0",
"@tauri-apps/plugin-store": "^2.2.0",
"@vueuse/core": "^13.1.0",
"@vueuse/nuxt": "^13.1.0",
"drizzle-orm": "^0.43.0",
"@vueuse/core": "^13.2.0",
"@vueuse/nuxt": "^13.2.0",
"drizzle-orm": "^0.43.1",
"flyonui": "^2.1.0",
"nuxt": "^3.17.0",
"nuxt": "^3.17.3",
"nuxt-icons": "3.2.1",
"nuxt-snackbar": "1.3.0",
"nuxt-svgo": "4.1.2",
"nuxt-svgo-loader": "0.5.0",
"nuxt-zod-i18n": "^1.11.5",
"tailwindcss": "^4.1.5",
"vue": "^3.5.13",
"zod": "^3.24.3"
"tailwindcss": "^4.1.7",
"vue": "^3.5.14",
"zod": "^3.25.4"
},
"devDependencies": {
"@egoist/tailwindcss-icons": "^1.9.0",
"@iconify/json": "^2.2.338",
"@iconify/json": "^2.2.339",
"@iconify/tailwind4": "^1.0.6",
"@tauri-apps/cli": "^2.5.0",
"@vitejs/plugin-vue": "^5.2.3",
"@vitejs/plugin-vue": "^5.2.4",
"drizzle-kit": "^0.31.1",
"typescript": "~5.8.3",
"vite": "^6.3.3",
"vite": "^6.3.5",
"vue-tsc": "^2.2.10"
},
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",

3212
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 164 KiB

View File

@ -1,5 +1,8 @@
<template>
<UiDropdown activator-class="btn btn-text btn-circle">
<UiDropdown
activator-class="btn btn-text btn-circle "
dropdown-class="[--offset:20]"
>
<template #activator>
<div
class="size-9.5 rounded-full items-center justify-center text-base-content text-base"

View File

@ -1,5 +1,5 @@
<template>
<div class="dropdown relative inline-flex">
<div class="dropdown relative inline-flex" :class="dropdownClass">
<button
:id
class="dropdown-toggle"
@ -47,6 +47,7 @@ const { itemIs = 'li' } = defineProps<{
items?: T[]
itemIs?: string
activatorClass?: string
dropdownClass?: string
}>()
defineEmits<{ select: [T] }>()

View File

@ -1,6 +1,6 @@
<template>
<div>
<fieldset class="join w-full">
<fieldset class="join w-full pt-0.5">
<slot name="prepend" />
<div class="input join-item">

View File

@ -1,33 +1,12 @@
<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"
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">
<template v-for="item in menu.top">
<UiSidebarLink v-if="item.to" v-bind="item" />
<UiSidebarButton v-else :icon="item.icon" :label="$t(item.label)" @click="item.click" />
</template>
</div>
<div class="flex flex-col space-y-2 text-base-content/90">
<template v-for="item in menu.bottom">
<UiSidebarLink v-if="item.to" v-bind="item" />
<UiSidebarButton v-else :icon="item.icon" :label="$t(item.label)" @click="item.click" />
</template>
<!-- <UiSidebarLink
v-for="item in menu.bottom"
:icon="item.icon"
:to="item.to ?? ''"
:label="item.label"
@click="item.click"
/> -->
</div>
<div class="flex flex-col space-y-2 text-base-content/90"></div>
</div>
</div>
@ -76,11 +55,8 @@
<script lang="ts" setup>
defineProps({
menu: {
type: Object as PropType<ISidebarMenu>,
type: Object as PropType<ISidebarItem>,
default: () => {},
},
});
//const show = ref(true);
const { show } = storeToRefs(useSidebarStore());
})
</script>

View File

@ -2,7 +2,7 @@
<li
@click="triggerNavigate"
class="hover:text-primary rounded"
:class="{ ['bg-base-300']: isActive }"
:class="{ ['bg-base-200 text-base-content']: isActive }"
>
<UiTooltip :tooltip="tooltip ?? name" direction="right-start">
<NuxtLinkLocale
@ -18,24 +18,27 @@
</template>
<script setup lang="ts">
import { type ISidebarItem } from "#imports";
import { type ISidebarItem } from '#imports'
const props = defineProps<ISidebarItem>();
const props = defineProps<ISidebarItem>()
const router = useRouter();
const router = useRouter()
console.log("to", props.to);
console.log('to', props.to)
const isActive = computed(() => {
if (props.to?.name === "haexExtension") {
return getSingleRouteParam(router.currentRoute.value.params.extensionId) === props.id;
if (props.to?.name === 'haexExtension') {
return (
getSingleRouteParam(router.currentRoute.value.params.extensionId) ===
props.id
)
} else {
return props.to?.name === router.currentRoute.value.meta.name;
return props.to?.name === router.currentRoute.value.meta.name
}
});
})
const linkRef = useTemplateRef("linkRef");
const linkRef = useTemplateRef('linkRef')
const triggerNavigate = () => linkRef.value?.$el.click();
const triggerNavigate = () => linkRef.value?.$el.click()
/* computed(() => {
const found = useRouter()

View File

@ -43,9 +43,9 @@
<script setup lang="ts">
import { save } from '@tauri-apps/plugin-dialog'
import { onKeyStroke } from '@vueuse/core'
import { useVaultStore } from '~/stores/vault'
import { vaultDatabaseSchema } from './schema'
import { onKeyStroke } from '@vueuse/core'
onKeyStroke('Enter', (e) => {
e.preventDefault()

View File

@ -7,7 +7,7 @@
@click="onLoadDatabase"
>
<Icon name="mdi:folder-open-outline" />
{{ t("database.open") }}
{{ t('database.open') }}
</button>
</template>
@ -22,62 +22,60 @@
<template #buttons>
<UiButton class="btn-error" @click="onClose">
{{ t("abort") }}
{{ t('abort') }}
</UiButton>
<UiButton type="submit" class="btn-primary" @click="onOpenDatabase">
{{ t("open") }}
{{ t('open') }}
</UiButton>
</template>
</UiDialog>
</template>
<script setup lang="ts">
import { open } from "@tauri-apps/plugin-dialog";
import { vaultDatabaseSchema } from "./schema";
import { open } from '@tauri-apps/plugin-dialog'
import { vaultDatabaseSchema } from './schema'
const { t } = useI18n();
const { t } = useI18n()
const isOpen = defineModel("isOpen", { type: Boolean });
const isOpen = defineModel('isOpen', { type: Boolean })
const props = defineProps({
path: String,
});
})
const check = ref(false);
const check = ref(false)
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 = "";
database.password = "";
database.path = "";
database.type = "password";
};
database.name = ''
database.password = ''
database.path = ''
database.type = 'password'
}
initDatabase();
initDatabase()
const { add } = useSnackbar();
const { add } = useSnackbar()
const handleError = (error: unknown) => {
isOpen.value = false;
console.log("handleError", error, typeof error);
add({ type: "error", text: "Passwort falsch" });
//console.error(error);
};
isOpen.value = false
console.error('handleError', error, typeof error)
add({ type: 'error', text: 'Passwort falsch' })
}
const { openAsync } = useVaultStore();
//const { show } = storeToRefs(useSidebarStore());
const { openAsync } = useVaultStore()
const onLoadDatabase = async () => {
try {
@ -86,75 +84,77 @@ const onLoadDatabase = async () => {
directory: false,
filters: [
{
name: "HaexVault",
extensions: ["db"],
name: 'HaexVault',
extensions: ['db'],
},
],
});
})
if (!database.path) return;
if (!database.path) return
isOpen.value = true;
isOpen.value = true
} catch (error) {
handleError(error);
handleError(error)
}
};
}
const localePath = useLocalePath();
const localePath = useLocalePath()
const { currentVault, currentVaultId } = storeToRefs(useVaultStore());
const { syncLocaleAsync, syncThemeAsync, syncVaultNameAsync } = useVaultStore()
const onOpenDatabase = async () => {
try {
check.value = true;
const path = database.path || props.path;
const pathCheck = vaultDatabaseSchema.path.safeParse(path);
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password);
check.value = true
const path = database.path || props.path
const pathCheck = vaultDatabaseSchema.path.safeParse(path)
const passwordCheck = vaultDatabaseSchema.password.safeParse(
database.password
)
if (!pathCheck.success || !passwordCheck.success || !path) {
add({
type: "error",
type: 'error',
text: `Params falsch. Path: ${pathCheck.error} | Password: ${passwordCheck.error}`,
});
return;
})
return
}
console.log("try to open", path);
const vaultId = await openAsync({
path,
password: database.password,
});
})
if (!vaultId) {
add({
type: "error",
text: "Vault konnte nicht geöffnet werden. \n Vermutlich ist das Passwort falsch",
});
return;
type: 'error',
text: 'Vault konnte nicht geöffnet werden. \n Vermutlich ist das Passwort falsch',
})
return
}
onClose();
onClose()
currentVaultId.value = vaultId;
console.log("vault before navigation", currentVault.value, currentVaultId.value, vaultId);
await navigateTo(
localePath({
name: "vaultOverview",
name: 'vaultOverview',
params: {
vaultId,
},
})
);
)
await Promise.allSettled([
syncLocaleAsync(),
syncThemeAsync(),
syncVaultNameAsync(),
])
} catch (error) {
console.log(error);
handleError(error);
handleError(error)
}
};
}
const onClose = () => {
initDatabase();
isOpen.value = false;
};
initDatabase()
isOpen.value = false
}
</script>
<i18n lang="json">

View File

@ -1,14 +1,13 @@
<template>
<div class="flex flex-col p-1 relative">
<UiButton
class="fixed top-20 right-4 btn-square btn-primary"
@click="loadExtensionManifestAsync"
>
<Icon name="mdi:plus" size="1.5em" />
</UiButton>
<h1>{{ t("title") }}</h1>
<div class="flex flex-col p-1 relative h-full">
<div class="flex" v-if="extensionStore.availableExtensions.length">
<UiButton
class="fixed top-20 right-4 btn-square btn-primary"
@click="loadExtensionManifestAsync"
>
<Icon name="mdi:plus" size="1.5em" />
</UiButton>
<div class="flex">
<HaexExtensionCard
v-for="extension in extensionStore.availableExtensions"
v-bind="extension"
@ -16,9 +15,25 @@
>
</HaexExtensionCard>
</div>
<!-- <UiButton @click="loadExtensionManifestAsync">
{{ t("extension.add") }}
</UiButton> -->
<!-- <SvgoExtensionsOverview class="h-screen w-screen" /> -->
<!-- <nuxt-icon name="extensions-overview" class="size-full" /> -->
<div v-else class="h-full w-full">
<Icon
name="my-icon:extensions-overview"
class="size-full md:size-2/3 md:translate-x-1/5 md:translate-y-1/3"
/>
<div class="fixed top-30 right-10">
<UiTooltip :tooltip="t('extension.add')">
<UiButton
class="btn-square btn-primary btn-xl btn-gradient rotate-45"
@click="loadExtensionManifestAsync"
>
<Icon name="mdi:plus" size="1.5em" class="rotate-45" />
</UiButton>
</UiTooltip>
</div>
</div>
<HaexExtensionManifestConfirm
:manifest="extension.manifest"
@ -26,7 +41,6 @@
@confirm="addExtensionAsync"
/>
{{ showRemoveDialog }}
<HaexExtensionDialogRemove
v-model:open="showRemoveDialog"
:extension="extensionToBeRemoved"
@ -37,121 +51,126 @@
</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";
import type { IHaexHubExtension, IHaexHubExtensionManifest } from "~/types/haexhub";
import { join } from '@tauri-apps/api/path'
import { open } from '@tauri-apps/plugin-dialog'
import { readTextFile } from '@tauri-apps/plugin-fs'
import type {
IHaexHubExtension,
IHaexHubExtensionManifest,
} from '~/types/haexhub'
definePageMeta({
name: "extensionOverview",
});
name: 'extensionOverview',
})
const { t } = useI18n();
const extensionStore = useExtensionsStore();
const { t } = useI18n()
const extensionStore = useExtensionsStore()
const showConfirmation = ref(false);
const showConfirmation = ref(false)
const extension = reactive<{
manifest: IHaexHubExtensionManifest | null | undefined;
path: string | null;
manifest: IHaexHubExtensionManifest | null | undefined
path: string | null
}>({
manifest: null,
path: "",
});
onMounted(() => console.log("extension overview"));
path: '',
})
const loadExtensionManifestAsync = async () => {
try {
extension.path = await open({ directory: true, recursive: true });
if (!extension.path) return;
extension.path = await open({ directory: true, recursive: true })
if (!extension.path) return
const manifestFile = JSON.parse(
await readTextFile(await join(extension.path, "manifest.json"))
);
await readTextFile(await join(extension.path, 'manifest.json'))
)
if (!extensionStore.checkManifest(manifestFile))
throw new Error(`Manifest fehlerhaft ${JSON.stringify(manifestFile)}`);
throw new Error(`Manifest fehlerhaft ${JSON.stringify(manifestFile)}`)
extension.manifest = manifestFile;
showConfirmation.value = true;
extension.manifest = manifestFile
showConfirmation.value = true
} catch (error) {
console.error("Fehler loadExtensionManifestAsync:", error);
add({ type: "error", text: JSON.stringify(error) });
console.error('Fehler loadExtensionManifestAsync:', error)
add({ type: 'error', text: JSON.stringify(error) })
}
};
}
const { add } = useSnackbar();
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 addExtensionAsync:", error);
add({ type: "error", text: JSON.stringify(error) });
}
};
await extensionStore.installAsync(extension.path)
await extensionStore.loadExtensionsAsync()
const showRemoveDialog = ref(false);
const extensionToBeRemoved = ref<IHaexHubExtension>();
add({
type: 'success',
title: t('extension.success.title', {
extension: extension.manifest?.name,
}),
text: t('extension.success.text'),
})
} catch (error) {
console.error('Fehler addExtensionAsync:', error)
add({ type: 'error', text: JSON.stringify(error) })
}
}
const showRemoveDialog = ref(false)
const extensionToBeRemoved = ref<IHaexHubExtension>()
const onShowRemoveDialog = (extension: IHaexHubExtension) => {
extensionToBeRemoved.value = extension;
showRemoveDialog.value = true;
};
extensionToBeRemoved.value = extension
showRemoveDialog.value = true
}
const removeExtensionAsync = async () => {
if (!extensionToBeRemoved.value?.id || !extensionToBeRemoved.value?.version) {
add({ type: "error", text: "Erweiterung kann nicht gelöscht werden" });
return;
add({ type: 'error', text: 'Erweiterung kann nicht gelöscht werden' })
return
}
try {
await extensionStore.removeExtensionAsync(
extensionToBeRemoved.value.id,
extensionToBeRemoved.value.version
);
await extensionStore.loadExtensionsAsync();
)
await extensionStore.loadExtensionsAsync()
add({
type: "success",
title: t("extension.remove.success.title", {
type: 'success',
title: t('extension.remove.success.title', {
extensionName: extensionToBeRemoved.value.name,
}),
text: t("extension.remove.success.text", { extensionName: extensionToBeRemoved.value.name }),
});
text: t('extension.remove.success.text', {
extensionName: extensionToBeRemoved.value.name,
}),
})
} catch (error) {
add({
type: "error",
title: t("extension.remove.error.title"),
text: t("extension.remove.error.text", { error: JSON.stringify(error) }),
});
type: 'error',
title: t('extension.remove.error.title'),
text: t('extension.remove.error.text', { error: JSON.stringify(error) }),
})
}
};
}
</script>
<i18n lang="yaml">
de:
title: "Erweiterung installieren"
title: 'Erweiterung installieren'
extension:
remove:
success:
text: "Erweiterung {extensionName} wurde erfolgreich entfernt"
title: "{extensionName} entfernt"
text: 'Erweiterung {extensionName} wurde erfolgreich entfernt'
title: '{extensionName} entfernt'
error:
text: "Erweiterung {extensionName} konnte nicht entfernt werden. \n {error}"
title: "Fehler beim Entfernen von {extensionName}"
title: 'Fehler beim Entfernen von {extensionName}'
add: "Erweiterung hinzufügen"
add: 'Erweiterung hinzufügen'
success:
title: "{extension} hinzugefügt"
text: "Die Erweiterung wurde erfolgreich hinzugefügt"
title: '{extension} hinzugefügt'
text: 'Die Erweiterung wurde erfolgreich hinzugefügt'
en:
title: "Install extension"
title: 'Install extension'
</i18n>

View File

@ -43,17 +43,16 @@ export const useVaultStore = defineStore("vaultStore", () => {
},
});
const openVaults = ref<IOpenVaults | undefined>();
const openVaults = ref<IOpenVaults>({});
const currentVault = ref<IVault | undefined>();
const currentVault = computed(() => openVaults.value?.[currentVaultId.value ?? ""]) //ref<IVault>();
watch(
/* watch(
currentVaultId,
async () => {
() => {
currentVault.value = openVaults.value?.[currentVaultId.value ?? ""];
},
{ immediate: true }
);
}
); */
const hostKey = computedAsync(async () => "".concat(type(), version(), await hostname() ?? ""))
@ -106,11 +105,7 @@ export const useVaultStore = defineStore("vaultStore", () => {
};
const { addVaultAsync } = useLastVaultStore();
addVaultAsync({ path });
syncLocaleAsync()
syncThemeAsync()
syncVaultNameAsync()
await addVaultAsync({ path })
return vaultId;
} catch (error) {
@ -158,8 +153,6 @@ export const useVaultStore = defineStore("vaultStore", () => {
const syncLocaleAsync = async () => {
try {
const app = useNuxtApp()
app.$i18n.availableLocales
//const { availableLocales, defaultLocale, setLocale, locale } = useI18n()
const currentLocaleRow = await currentVault.value?.drizzle
.select()
@ -197,7 +190,7 @@ export const useVaultStore = defineStore("vaultStore", () => {
await currentVault.value?.drizzle.insert(schema.haexSettings).values({
id: crypto.randomUUID(),
key: 'theme',
value: currentTheme.value.name,
value: currentTheme.value.value,
})
}
}
@ -234,6 +227,9 @@ export const useVaultStore = defineStore("vaultStore", () => {
openVaults,
read_only,
refreshDatabaseAsync,
syncLocaleAsync,
syncThemeAsync,
syncVaultNameAsync,
updateVaultNameAsync,
};
});