added settings page, cleanup

This commit is contained in:
Martin Drechsel
2025-05-19 12:29:37 +02:00
parent 6a1351752b
commit 0699dbef31
35 changed files with 889 additions and 653 deletions

View File

@ -22,11 +22,11 @@ export default defineNuxtConfig({
provider: 'server', provider: 'server',
mode: "svg", mode: "svg",
clientBundle: { clientBundle: {
icons: ["solar:global-outline", "gg:extension"], icons: ["solar:global-outline", "gg:extension", "hugeicons:corporate"],
scan: true, scan: true,
includeCustomCollections: true, includeCustomCollections: true,
}, },
serverBundle: { collections: ["mdi", "line-md", "solar", "gg"] } serverBundle: { collections: ["mdi", "line-md", "solar", "gg", "emojione"] }
//collections: ["mdi", "line-md"] //collections: ["mdi", "line-md"]
}, },

View File

@ -25,6 +25,7 @@
"opener:allow-open-url", "opener:allow-open-url",
"opener:default", "opener:default",
"os:default", "os:default",
"os:allow-hostname",
"store:default" "store:default"
] ]
} }

View File

@ -0,0 +1,4 @@
ALTER TABLE `haex_settings` RENAME COLUMN "value_text" TO "value";--> statement-breakpoint
DROP TABLE `testTable`;--> statement-breakpoint
ALTER TABLE `haex_settings` DROP COLUMN `value_json`;--> statement-breakpoint
ALTER TABLE `haex_settings` DROP COLUMN `value_number`;

View File

@ -0,0 +1,180 @@
{
"version": "6",
"dialect": "sqlite",
"id": "ea3507ca-77bc-4f3c-a605-8426614f5803",
"prevId": "6fb5396b-9f87-4fb5-87a2-22d4eecaa11e",
"tables": {
"haex_extensions": {
"name": "haex_extensions",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"author": {
"name": "author",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"enabled": {
"name": "enabled",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"version": {
"name": "version",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"haex_extensions_permissions": {
"name": "haex_extensions_permissions",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"extension_id": {
"name": "extension_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"resource": {
"name": "resource",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"operation": {
"name": "operation",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"path": {
"name": "path",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"haex_extensions_permissions_extension_id_resource_operation_path_unique": {
"name": "haex_extensions_permissions_extension_id_resource_operation_path_unique",
"columns": [
"extension_id",
"resource",
"operation",
"path"
],
"isUnique": true
}
},
"foreignKeys": {
"haex_extensions_permissions_extension_id_haex_extensions_id_fk": {
"name": "haex_extensions_permissions_extension_id_haex_extensions_id_fk",
"tableFrom": "haex_extensions_permissions",
"tableTo": "haex_extensions",
"columnsFrom": [
"extension_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"haex_settings": {
"name": "haex_settings",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {
"\"haex_settings\".\"value_text\"": "\"haex_settings\".\"value\""
}
},
"internal": {
"indexes": {}
}
}

View File

@ -15,6 +15,13 @@
"when": 1746281577722, "when": 1746281577722,
"tag": "0001_wealthy_thaddeus_ross", "tag": "0001_wealthy_thaddeus_ross",
"breakpoints": true "breakpoints": true
},
{
"idx": 2,
"version": "6",
"when": 1747583956679,
"tag": "0002_married_bushwacker",
"breakpoints": true
} }
] ]
} }

View File

@ -1,18 +1,15 @@
import { import {
integer, integer,
numeric,
sqliteTable, sqliteTable,
text, text,
type AnySQLiteColumn,
unique, unique,
type AnySQLiteColumn
} from "drizzle-orm/sqlite-core"; } from "drizzle-orm/sqlite-core";
export const haexSettings = sqliteTable("haex_settings", { export const haexSettings = sqliteTable("haex_settings", {
id: text().primaryKey(), id: text().primaryKey(),
key: text(), key: text(),
value_text: text(), value: text(),
value_json: text({ mode: "json" }),
value_number: numeric(),
}); });
export const haexExtensions = sqliteTable("haex_extensions", { export const haexExtensions = sqliteTable("haex_extensions", {
@ -25,18 +22,13 @@ export const haexExtensions = sqliteTable("haex_extensions", {
version: text(), version: text(),
}); });
export const testTable = sqliteTable("testTable", {
id: text().primaryKey(),
author: text(),
test: text(),
});
export const haexExtensionsPermissions = sqliteTable( export const haexExtensionsPermissions = sqliteTable(
"haex_extensions_permissions", "haex_extensions_permissions",
{ {
id: text().primaryKey(), id: text().primaryKey(),
extensionId: text("extension_id").references((): AnySQLiteColumn => haexExtensions.id), extensionId: text("extension_id").references((): AnySQLiteColumn => haexExtensions.id),
resource: text({ enum: ["fs", "http", "database"] }), resource: text({ enum: ["fs", "http", "db", "shell"] }),
operation: text({ enum: ["read", "write", "create"] }), operation: text({ enum: ["read", "write", "create"] }),
path: text(), path: text(),
}, },

Binary file not shown.

View File

@ -41,7 +41,6 @@ pub fn run() {
}) })
} }
} }
//extension::core::extension_protocol_handler(&context, &request)
}) })
.plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_http::init())
.manage(DbConnection(Mutex::new(None))) .manage(DbConnection(Mutex::new(None)))

View File

@ -19,7 +19,7 @@
], ],
"security": { "security": {
"csp": { "csp": {
"default-src": ["'self'", "haex-extension: data: blob: asset:"], "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:"], "script-src": ["'self'", "haex-extension:"],
"style-src": ["'self'", "haex-extension:"], "style-src": ["'self'", "haex-extension:"],
"connect-src": ["'self'", "haex-extension:"], "connect-src": ["'self'", "haex-extension:"],
@ -29,7 +29,7 @@
}, },
"assetProtocol": { "assetProtocol": {
"enable": true, "enable": true,
"scope": ["$RESOURCE/extensions/**"] "scope": ["$RESOURCE/**", "$APPDATA/**"]
} }
} }
}, },

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<NuxtLayout :data-theme="currentTheme"> <NuxtLayout :data-theme="currentTheme.value">
<NuxtPage /> <NuxtPage />
<NuxtSnackbar /> <NuxtSnackbar />
</NuxtLayout> </NuxtLayout>
@ -8,7 +8,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const { currentTheme } = storeToRefs(useUiStore()); const { currentTheme } = storeToRefs(useUiStore())
</script> </script>
<style> <style>

View File

@ -1,16 +1,10 @@
<template> <template>
<div class="browser"> <div class="browser">
<div class="browser-controls"> <div class="browser-controls">
<button <button @click="$emit('goBack', activeTabId)" :disabled="!activeTabId">
@click="$emit('goBack', activeTabId)"
:disabled="!activeTabId"
>
</button> </button>
<button <button @click="$emit('goForward', activeTabId)" :disabled="!activeTabId">
@click="$emit('goForward', activeTabId)"
:disabled="!activeTabId"
>
</button> </button>
<button @click="$emit('createTab')">+</button> <button @click="$emit('createTab')">+</button>
@ -29,15 +23,9 @@
@activateTab="$emit('activateTab', $event)" @activateTab="$emit('activateTab', $event)"
/> />
<div <div class="browser-content" ref="contentRef">
class="browser-content"
ref="contentRef"
>
<!-- Die eigentlichen Webview-Inhalte werden von Tauri verwaltet --> <!-- Die eigentlichen Webview-Inhalte werden von Tauri verwaltet -->
<div <div v-if="!activeTabId" class="empty-state">
v-if="!activeTabId"
class="empty-state"
>
<p> <p>
Kein Tab geöffnet. Erstellen Sie einen neuen Tab mit dem + Button. Kein Tab geöffnet. Erstellen Sie einen neuen Tab mit dem + Button.
</p> </p>
@ -47,9 +35,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { invoke } from '@tauri-apps/api/core'; import { Webview } from '@tauri-apps/api/webview'
import { Window } from '@tauri-apps/api/window'; import { Window } from '@tauri-apps/api/window'
import { getCurrentWebview, Webview } from '@tauri-apps/api/webview';
/* const appWindow = new Window('uniqueLabel'); /* const appWindow = new Window('uniqueLabel');
const webview = new Webview(appWindow, 'theUniqueLabel', { const webview = new Webview(appWindow, 'theUniqueLabel', {
url: 'https://www.google.de', url: 'https://www.google.de',
@ -64,46 +51,46 @@ webview.once('tauri://created', function () {
}); */ }); */
interface Tab { interface Tab {
id: string; id: string
title: string; title: string
url: string; url: string
isLoading: boolean; isLoading: boolean
isActive: boolean; isActive: boolean
window_label: string; window_label: string
} }
interface Props { interface Props {
tabs: Tab[]; tabs: Tab[]
activeTabId: string | null; activeTabId: string | null
} }
const props = defineProps<Props>(); const props = defineProps<Props>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'createTab'): void; (e: 'createTab'): void
(e: 'closeTab', tabId: string): void; (e: 'closeTab', tabId: string): void
(e: 'navigate', tabId: string, url: string): void; (e: 'navigate', tabId: string, url: string): void
(e: 'goBack', tabId: string | null): void; (e: 'goBack', tabId: string | null): void
(e: 'goForward', tabId: string | null): void; (e: 'goForward', tabId: string | null): void
(e: 'activateTab', tabId: string | null): void; (e: 'activateTab', tabId: string | null): void
}>(); }>()
const { initializeAsync, processNavigation, injectContentScripts } = const { initializeAsync, processNavigation, injectContentScripts } =
useBrowserExtensionStore(); useBrowserExtensionStore()
const contentRef = ref<HTMLDivElement | null>(null); const contentRef = ref<HTMLDivElement | null>(null)
//const extensionManager = ref<ExtensionManager>(new ExtensionManager()); //const extensionManager = ref<ExtensionManager>(new ExtensionManager());
const activeTab = computed(() => const activeTab = computed(() =>
props.tabs?.find((tab) => tab.id === props.activeTabId) props.tabs?.find((tab) => tab.id === props.activeTabId)
); )
onMounted(async () => { onMounted(async () => {
// Initialisiere das Erweiterungssystem // Initialisiere das Erweiterungssystem
await initializeAsync(); await initializeAsync()
// Aktualisiere die Webview-Größe // Aktualisiere die Webview-Größe
await updateWebviewBoundsAsync(); await updateWebviewBoundsAsync()
//window.addEventListener('resize', updateWebviewBounds); //window.addEventListener('resize', updateWebviewBounds);
}); })
// Wenn ein neuer Tab aktiviert wird, injiziere Content-Scripts // Wenn ein neuer Tab aktiviert wird, injiziere Content-Scripts
/* watch( /* watch(
@ -124,32 +111,46 @@ onMounted(async () => {
} }
); */ ); */
const createNewTabAsync = async () => {
const appWindow = new Window(crypto.randomUUID())
appWindow.setAlwaysOnTop(true)
appWindow.setDecorations(false)
const webview = new Webview(appWindow, 'theUniqueLabel', {
url: 'https://www.google.de',
x: 0,
y: 0,
height: 1000,
width: 1000,
})
}
const handleUrlSubmit = (url: string) => { const handleUrlSubmit = (url: string) => {
createNewTabAsync()
if (props.activeTabId) { if (props.activeTabId) {
// Prüfe URL mit Erweiterungen vor der Navigation // Prüfe URL mit Erweiterungen vor der Navigation
if (processNavigation(url)) { /* if (processNavigation(url)) {
emit('navigate', props.activeTabId, url); //emit('navigate', props.activeTabId, url);
} else { } else {
console.log('Navigation blockiert durch Erweiterung'); console.log('Navigation blockiert durch Erweiterung')
// Hier könnten Sie eine Benachrichtigung anzeigen // Hier könnten Sie eine Benachrichtigung anzeigen
} } */
} }
}; }
const updateWebviewBoundsAsync = async () => { const updateWebviewBoundsAsync = async () => {
if (!contentRef.value) return; if (!contentRef.value) return
const rect = contentRef.value.getBoundingClientRect(); const rect = contentRef.value.getBoundingClientRect()
const bounds = { const bounds = {
x: rect.left, x: rect.left,
y: rect.top, y: rect.top,
width: rect.width, width: rect.width,
height: rect.height, height: rect.height,
}; }
/* await invoke('update_window_bounds', { /* await invoke('update_window_bounds', {
contentBounds: { x: bounds.x, y: bounds.y }, contentBounds: { x: bounds.x, y: bounds.y },
contentSize: { width: bounds.width, height: bounds.height }, contentSize: { width: bounds.width, height: bounds.height },
}); */ }); */
}; }
</script> </script>

View File

@ -10,10 +10,7 @@
<span class="tab-title"> <span class="tab-title">
{{ tab.title || 'Neuer Tab' }} {{ tab.title || 'Neuer Tab' }}
</span> </span>
<button <button class="tab-close" @click.stop="$emit('closeTab', tab.id)">
class="tab-close"
@click.stop="$emit('closeTab', tab.id)"
>
× ×
</button> </button>
</div> </div>
@ -22,22 +19,22 @@
<script setup lang="ts"> <script setup lang="ts">
interface Tab { interface Tab {
id: string; id: string
title: string; title: string
url: string; url: string
isLoading: boolean; isLoading: boolean
isActive: boolean; isActive: boolean
} }
interface Props { interface Props {
tabs: Tab[]; tabs: Tab[]
activeTabId: string | null; activeTabId: string | null
} }
defineProps<Props>(); defineProps<Props>()
defineEmits<{ defineEmits<{
(e: 'closeTab', tabId: string): void; (e: 'closeTab', tabId: string): void
(e: 'activateTab', tabId: string): void; (e: 'activateTab', tabId: string): void
}>(); }>()
</script> </script>

View File

@ -1,24 +1,8 @@
<template> <template>
<form <form class="url-bar" @submit.prevent="handleSubmit">
class="url-bar" <input type="text" v-model="inputValue" placeholder="URL eingeben" />
@submit.prevent="handleSubmit" <span v-if="isLoading" class="loading-indicator">Laden...</span>
> <button v-else type="submit">Go</button>
<input
type="text"
v-model="inputValue"
placeholder="URL eingeben"
/>
<span
v-if="isLoading"
class="loading-indicator"
>Laden...</span
>
<button
v-else
type="submit"
>
Go
</button>
</form> </form>
</template> </template>
@ -32,26 +16,26 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}); })
const emit = defineEmits(['submit']); const emit = defineEmits(['submit'])
const inputValue = ref(props.url); const inputValue = ref(props.url)
watch( watch(
() => props.url, () => props.url,
(newUrl) => { (newUrl) => {
inputValue.value = newUrl; inputValue.value = newUrl
} }
); )
const handleSubmit = () => { const handleSubmit = () => {
// URL validieren und ggf. Protokoll hinzufügen // URL validieren und ggf. Protokoll hinzufügen
let processedUrl = inputValue.value.trim(); let processedUrl = inputValue.value.trim()
if (processedUrl && !processedUrl.match(/^[a-zA-Z]+:\/\//)) { if (processedUrl && !processedUrl.match(/^[a-zA-Z]+:\/\//)) {
processedUrl = 'https://' + processedUrl; processedUrl = 'https://' + processedUrl
} }
emit('submit', processedUrl); emit('submit', processedUrl)
}; }
</script> </script>

View File

@ -0,0 +1,59 @@
<template>
<UiDropdown activator-class="btn btn-text btn-circle">
<template #activator>
<div
class="size-9.5 rounded-full items-center justify-center text-base-content text-base"
>
<Icon name="mdi:format-list-bulleted" class="size-full p-2" />
</div>
</template>
<template #items>
<ul
class="dropdown-menu dropdown-open:opacity-100 hidden min-w-60"
role="menu"
aria-orientation="vertical"
aria-labelledby="dropdown-avatar"
>
<li>
<NuxtLinkLocale class="dropdown-item" :to="{ name: 'haexSettings' }">
<span class="icon-[tabler--settings]"></span>
{{ t('settings') }}
</NuxtLinkLocale>
</li>
<li class="dropdown-footer gap-2">
<button
class="btn btn-error btn-soft btn-block"
@click="onVaultCloseAsync"
>
<span class="icon-[tabler--logout]"></span>
{{ t('vault.close') }}
</button>
</li>
</ul>
</template>
</UiDropdown>
</template>
<script setup lang="ts">
const { t } = useI18n()
const { closeAsync } = useVaultStore()
const onVaultCloseAsync = async () => {
await closeAsync()
await navigateTo(useLocalePath()({ name: 'vaultOpen' }))
}
</script>
<i18n lang="yaml">
de:
settings: 'Einstellungen'
vault:
close: 'Vault schließen'
en:
settings: 'Settings'
vault:
close: 'Close Vault'
</i18n>

View File

@ -0,0 +1,54 @@
<template>
<div class="dropdown relative inline-flex">
<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
}>()
defineEmits<{ select: [T] }>()
const id = useId()
</script>

View File

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

@ -0,0 +1,26 @@
<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,78 +1,10 @@
<template> <template>
<span> <div>
<!-- <fieldset class="join w-full"> <fieldset class="join w-full">
<slot name="prepend" />
<span class="input-group join-item">
<span
v-if="prependIcon || prependLabel"
class="input-group-text"
>
<label v-if="prependLabel">
{{ prependLabel }}
</label>
<Icon :name="prependIcon" />
</span>
<div class="relative w-full">
<input
:id
:name="name ?? id"
:placeholder="placeholder || label"
:type
:autofocus
class="input input-floating peer join-item"
:class="{
'input-sm':
currentScreenSize === 'sm' ||
currentScreenSize === '' ||
currentScreenSize === 'xs',
}"
v-bind="$attrs"
v-model="input"
ref="inputRef"
:readonly="read_only"
/>
<label
v-if="label"
:for="id"
class="input-floating-label"
>
{{ label }}
</label>
</div>
<span
v-if="appendIcon || appendLabel"
class="input-group-text"
>
<label
v-if="appendLabel"
class=""
>
{{ appendLabel }}
</label>
<Icon :name="appendIcon" />
</span>
</span>
<slot name="append" />
<UiButton
v-if="withCopyButton"
class="btn-outline btn-accent h-auto"
@click="copy(`${input}`)"
>
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
</UiButton>
</fieldset> -->
<fieldset class="join w-full p-1">
<slot name="prepend" /> <slot name="prepend" />
<div class="input join-item"> <div class="input join-item">
<Icon :name="prependIcon" class="my-auto shrink-0" /> <Icon v-if="prependIcon" :name="prependIcon" class="my-auto shrink-0" />
<div class="input-floating grow"> <div class="input-floating grow">
<input <input
@ -90,12 +22,13 @@
<label class="input-floating-label" :for="id">{{ label }}</label> <label class="input-floating-label" :for="id">{{ label }}</label>
</div> </div>
<Icon :name="appendIcon" class="my-auto shrink-0" /> <Icon v-if="appendIcon" :name="appendIcon" class="my-auto shrink-0" />
</div> </div>
<slot name="append" class="h-auto" /> <slot name="append" class="h-auto" />
<UiButton <UiButton
v-if="withCopyButton"
class="btn-outline btn-accent btn-square join-item h-auto" class="btn-outline btn-accent btn-square join-item h-auto"
@click="copy(`${input}`)" @click="copy(`${input}`)"
> >
@ -108,61 +41,61 @@
{{ error }} {{ error }}
</span> </span>
</span> </span>
</span> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { type ZodSchema } from "zod"; import { type ZodSchema } from 'zod'
const inputRef = useTemplateRef("inputRef"); const inputRef = useTemplateRef('inputRef')
defineExpose({ inputRef }); defineExpose({ inputRef })
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}); })
const props = defineProps({ const props = defineProps({
placeholder: { placeholder: {
type: String, type: String,
default: "", default: '',
}, },
type: { type: {
type: String as PropType< type: String as PropType<
| "button" | 'button'
| "checkbox" | 'checkbox'
| "color" | 'color'
| "date" | 'date'
| "datetime-local" | 'datetime-local'
| "email" | 'email'
| "file" | 'file'
| "hidden" | 'hidden'
| "image" | 'image'
| "month" | 'month'
| "number" | 'number'
| "password" | 'password'
| "radio" | 'radio'
| "range" | 'range'
| "reset" | 'reset'
| "search" | 'search'
| "submit" | 'submit'
| "tel" | 'tel'
| "text" | 'text'
| "time" | 'time'
| "url" | 'url'
| "week" | 'week'
>, >,
default: "text", default: 'text',
}, },
label: String, label: String,
name: String, name: String,
prependIcon: { prependIcon: {
type: String, type: String,
default: "", default: '',
}, },
prependLabel: String, prependLabel: String,
appendIcon: { appendIcon: {
type: String, type: String,
default: "", default: '',
}, },
appendLabel: String, appendLabel: String,
rules: Object as PropType<ZodSchema>, rules: Object as PropType<ZodSchema>,
@ -170,45 +103,45 @@ const props = defineProps({
withCopyButton: Boolean, withCopyButton: Boolean,
autofocus: Boolean, autofocus: Boolean,
read_only: Boolean, read_only: Boolean,
}); })
const input = defineModel<string | number | undefined | null>({ const input = defineModel<string | number | undefined | null>({
default: "", default: '',
required: true, required: true,
}); })
const { currentScreenSize } = storeToRefs(useUiStore()); const { currentScreenSize } = storeToRefs(useUiStore())
onMounted(() => { onMounted(() => {
if (props.autofocus && inputRef.value) inputRef.value.focus(); if (props.autofocus && inputRef.value) inputRef.value.focus()
}); })
const errors = defineModel<string[] | undefined>("errors"); const errors = defineModel<string[] | undefined>('errors')
const id = useId(); const id = useId()
watch(input, () => checkInput()); watch(input, () => checkInput())
watch( watch(
() => props.checkInput, () => props.checkInput,
() => { () => {
checkInput(); checkInput()
} }
); )
const emit = defineEmits(["error"]); const emit = defineEmits(['error'])
const checkInput = () => { const checkInput = () => {
if (props.rules) { if (props.rules) {
const result = props.rules.safeParse(input.value); const result = props.rules.safeParse(input.value)
//console.log('check result', result.error, props.rules); //console.log('check result', result.error, props.rules);
if (!result.success) { if (!result.success) {
errors.value = result.error.errors.map((error) => error.message); errors.value = result.error.errors.map((error) => error.message)
emit("error", errors.value); emit('error', errors.value)
} else { } else {
errors.value = []; errors.value = []
} }
} }
}; }
const { copy, copied } = useClipboard(); const { copy, copied } = useClipboard()
</script> </script>

View File

@ -9,20 +9,22 @@
v-model="value" v-model="value"
> >
<template #append> <template #append>
<UiButton class="btn-outline btn-accent btn-square h-auto" @click="tooglePasswordType"> <UiButton
<Icon :name="type === 'password' ? 'mdi:eye' : 'mdi:eye-off'" /> class="btn-outline btn-accent btn-square h-auto"
@click="tooglePasswordType"
>
<Icon :name="type === 'password' ? 'mdi:eye-off' : 'mdi:eye'" />
</UiButton> </UiButton>
</template> </template>
</UiInput> </UiInput>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { ZodSchema } from "zod"; import type { ZodSchema } from 'zod'
const { t } = useI18n(); const { t } = useI18n()
const { currentScreenSize } = storeToRefs(useUiStore());
const value = defineModel<string | number | null | undefined>(); const value = defineModel<string | number | null | undefined>()
defineProps({ defineProps({
label: String, label: String,
@ -30,13 +32,13 @@ defineProps({
checkInput: Boolean, checkInput: Boolean,
rules: Object as PropType<ZodSchema>, rules: Object as PropType<ZodSchema>,
autofocus: Boolean, autofocus: Boolean,
}); })
const type = ref<"password" | "text">("password"); const type = ref<'password' | 'text'>('password')
const tooglePasswordType = () => { const tooglePasswordType = () => {
type.value = type.value === "password" ? "text" : "password"; type.value = type.value === 'password' ? 'text' : 'password'
}; }
</script> </script>
<i18n lang="json"> <i18n lang="json">

View File

@ -6,12 +6,11 @@
@click="open = true" @click="open = true"
> >
<Icon name="mdi:plus" /> <Icon name="mdi:plus" />
{{ t("database.create") }} {{ t('database.create') }}
</button> </button>
</template> </template>
<form class="flex flex-col gap-4" @submit="onCreateAsync"> <form class="flex flex-col gap-4" @submit="onCreateAsync">
<!-- @keyup.enter="onCreateAsync" -->
<UiInput <UiInput
:check-input="check" :check-input="check"
:label="t('database.label')" :label="t('database.label')"
@ -32,98 +31,116 @@
<template #buttons> <template #buttons>
<UiButton class="btn-error" @click="onClose"> <UiButton class="btn-error" @click="onClose">
{{ t("abort") }} {{ t('abort') }}
</UiButton> </UiButton>
<UiButton class="btn-primary" @click="onCreateAsync"> <UiButton class="btn-primary" @click="onCreateAsync">
{{ t("create") }} {{ t('create') }}
</UiButton> </UiButton>
</template> </template>
</UiDialog> </UiDialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { save } from "@tauri-apps/plugin-dialog"; import { save } from '@tauri-apps/plugin-dialog'
import { useVaultStore } from "~/stores/vault"; import { useVaultStore } from '~/stores/vault'
import { vaultDatabaseSchema } from "./schema"; import { vaultDatabaseSchema } from './schema'
import { onKeyStroke } from '@vueuse/core'
const check = ref(false); onKeyStroke('Enter', (e) => {
const open = ref(); e.preventDefault()
onCreateAsync()
})
const { t } = useI18n(); const check = ref(false)
const open = ref()
const { t } = useI18n()
const database = reactive<{ const database = reactive<{
name: string; name: string
password: string; password: string
path: string | null; path: string | null
type: "password" | "text"; type: 'password' | 'text'
}>({ }>({
name: "", name: '',
password: "", password: '',
path: "", path: '',
type: "password", type: 'password',
}); })
const initDatabase = () => { const initDatabase = () => {
database.name = t("database.name"); database.name = t('database.name')
database.password = ""; database.password = ''
database.path = ""; database.path = ''
database.type = "password"; database.type = 'password'
}; }
initDatabase(); initDatabase()
const { add } = useSnackbar(); const { add } = useSnackbar()
const { createAsync } = useVaultStore(); const { createAsync } = useVaultStore()
const onCreateAsync = async () => { const onCreateAsync = async () => {
check.value = true; check.value = true
const nameCheck = vaultDatabaseSchema.name.safeParse(database.name); const nameCheck = vaultDatabaseSchema.name.safeParse(database.name)
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password); const passwordCheck = vaultDatabaseSchema.password.safeParse(
database.password
)
console.log("checks", database.name, nameCheck, database.password, passwordCheck); console.log(
if (!nameCheck.success || !passwordCheck.success) return; 'checks',
database.name,
nameCheck,
database.password,
passwordCheck
)
if (!nameCheck.success || !passwordCheck.success) return
open.value = false; open.value = false
try { try {
database.path = await save({ database.path = await save({
defaultPath: database.name.endsWith(".db") ? database.name : `${database.name}.db`, defaultPath: database.name.endsWith('.db')
}); ? database.name
: `${database.name}.db`,
})
console.log("data", database); console.log('data', database)
if (database.path && database.password) { if (database.path && database.password) {
const vaultId = await createAsync({ const vaultId = await createAsync({
path: database.path, path: database.path,
password: database.password, password: database.password,
}); })
console.log("vaultId", vaultId); console.log('vaultId', vaultId)
if (vaultId) { if (vaultId) {
await navigateTo(useLocaleRoute()({ name: "vaultOverview", params: { vaultId } })); await navigateTo(
useLocaleRoute()({ name: 'vaultOverview', params: { vaultId } })
)
} }
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error)
add({ type: "error", text: JSON.stringify(error) }); add({ type: 'error', text: JSON.stringify(error) })
} }
}; }
const onClose = () => { const onClose = () => {
open.value = false; open.value = false
initDatabase(); initDatabase()
}; }
</script> </script>
<i18n lang="json"> <i18n lang="json">
{ {
"de": { "de": {
"database": { "database": {
"label": "Datenbankname", "label": "Vaultname",
"placeholder": "Passwörter", "placeholder": "Vaultname",
"create": "Neue Vault anlegen", "create": "Neue Vault anlegen",
"name": "Passwörter" "name": "HaexVault"
}, },
"title": "Neue Datenbank anlegen", "title": "Neue Datenbank anlegen",
"create": "Erstellen", "create": "Erstellen",
@ -133,10 +150,10 @@ const onClose = () => {
"en": { "en": {
"database": { "database": {
"label": "Databasename", "label": "Vaultname",
"placeholder": "Databasename", "placeholder": "Vaultname",
"create": "Create new Vault", "create": "Create new Vault",
"name": "Passwords" "name": "HaexVault"
}, },
"title": "Create New Database", "title": "Create New Database",
"create": "Create", "create": "Create",

View File

@ -0,0 +1 @@
<template><div>first time</div></template>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="w-full h-full flex flex-col min-w-min"> <div class="w-full h-full flex flex-col min-w-min relative overflow-hidden">
<nav <nav
class="navbar bg-base-100 rounded-b max-sm:shadow 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"
> >
@ -14,7 +14,11 @@
ref="sidebarToogleRef" ref="sidebarToogleRef"
> >
<Icon <Icon
:name="isVisible ? 'tabler:layout-sidebar' : 'tabler:layout-sidebar-filled'" :name="
isVisible
? 'tabler:layout-sidebar-filled'
: 'tabler:layout-sidebar'
"
size="28" size="28"
/> />
</button> </button>
@ -25,9 +29,12 @@
class="link text-base-content link-neutral text-xl font-semibold no-underline" class="link text-base-content link-neutral text-xl font-semibold no-underline"
:to="{ name: 'vaultOverview' }" :to="{ name: 'vaultOverview' }"
> >
<UiTextGradient class="text-nowrap">Haex Hub</UiTextGradient> <UiTextGradient class="text-nowrap">{{
currentVaultName
}}</UiTextGradient>
</NuxtLinkLocale> </NuxtLinkLocale>
</div> </div>
<div class="navbar-end flex items-center gap-4 me-4"> <div class="navbar-end flex items-center gap-4 me-4">
<div <div
class="dropdown relative inline-flex [--auto-close:inside] [--offset:8] [--placement:bottom-end]" class="dropdown relative inline-flex [--auto-close:inside] [--offset:8] [--placement:bottom-end]"
@ -45,7 +52,9 @@
v-show="notifications.length" v-show="notifications.length"
class="indicator-item bg-error size-2 rounded-full text-sm" class="indicator-item bg-error size-2 rounded-full text-sm"
></span> ></span>
<span class="icon-[tabler--bell] text-base-content size-[1.375rem]"></span> <span
class="icon-[tabler--bell] text-base-content size-[1.375rem]"
></span>
</div> </div>
</button> </button>
<div <div
@ -56,7 +65,7 @@
> >
<div class="dropdown-header justify-center"> <div class="dropdown-header justify-center">
<h6 class="text-base-content text-base"> <h6 class="text-base-content text-base">
{{ t("notifications.label") }} {{ t('notifications.label') }}
</h6> </h6>
</div> </div>
<div <div
@ -70,7 +79,10 @@
:src="notification.image" :src="notification.image"
:alt="notification.alt ?? 'notification avatar'" :alt="notification.alt ?? 'notification avatar'"
/> />
<Icon v-else-if="notification.icon" :name="notification.icon" /> <Icon
v-else-if="notification.icon"
:name="notification.icon"
/>
</div> </div>
</div> </div>
<div class="w-60"> <div class="w-60">
@ -85,80 +97,16 @@
</div> </div>
<a href="#" class="dropdown-footer justify-center gap-1"> <a href="#" class="dropdown-footer justify-center gap-1">
<span class="icon-[tabler--eye] size-4"></span> <span class="icon-[tabler--eye] size-4"></span>
{{ t("notifications.view_all") }} {{ t('notifications.view_all') }}
</a> </a>
</div> </div>
</div> </div>
<div
class="dropdown relative inline-flex [--auto-close:inside] [--offset:8] [--placement:bottom-end]" <HaexMenuMain />
>
<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">
<button class="btn btn-error btn-soft btn-block" @click="onVaultCloseAsync">
<span class="icon-[tabler--logout]"></span>
{{ t("vault.close") }}
</button>
</li>
</ul>
</div>
</div> </div>
</nav> </nav>
<div class="flex h-full"> <div class="flex h-full overflow-hidden">
<aside <aside
id="sidebar" id="sidebar"
class="sm:shadow-none transition-all h-full overflow-hidden border-r border-base-300" class="sm:shadow-none transition-all h-full overflow-hidden border-r border-base-300"
@ -179,7 +127,7 @@
</div> </div>
</aside> </aside>
<main class="w-full"> <main class="w-full h-full overflow-scroll">
<NuxtPage :transition="{ name: 'fade' }" /> <NuxtPage :transition="{ name: 'fade' }" />
</main> </main>
</div> </div>
@ -187,32 +135,19 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { NuxtLinkLocale } from "#components"; const { t } = useI18n()
const { t } = useI18n(); const { currentVaultName } = storeToRefs(useVaultStore())
const { menu, isVisible } = storeToRefs(useSidebarStore());
const sidebarToogleRef = useTemplateRef("sidebarToogleRef");
/* onClickOutside(sidebarToogleRef, () => { const { menu, isVisible } = storeToRefs(useSidebarStore())
if (currentScreenSize.value === "xs") {
isVisible.value = false; const { notifications } = storeToRefs(useNotificationStore())
}
}); */ const { extensionLinks } = storeToRefs(useExtensionsStore())
const { notifications } = storeToRefs(useNotificationStore());
const { isActive } = useExtensionsStore();
const { closeAsync } = useVaultStore();
const { currentScreenSize } = storeToRefs(useUiStore());
const onExtensionSelectAsync = async (id: string) => {};
const { extensionLinks } = storeToRefs(useExtensionsStore());
const toogleSidebar = () => { const toogleSidebar = () => {
isVisible.value = !isVisible.value; isVisible.value = !isVisible.value
}; }
const onVaultCloseAsync = async () => {
await closeAsync();
await navigateTo(useLocalePath()({ name: "vaultOpen" }));
};
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">
@ -223,7 +158,7 @@ de:
vault: vault:
close: Vault schließen close: Vault schließen
sidebar: sidebar:
close: Sidebar schließen close: Sidebar ausblenden
show: Sidebar anzeigen show: Sidebar anzeigen
en: en:
notifications: notifications:

View File

@ -1,11 +1,20 @@
<template> <template>
<div class="items-center justify-center min-h-full flex w-full"> <div class="items-center justify-center min-h-full flex w-full relative">
<div class="fixed top-2 right-2">
<UiDropdownLocale @select="setLocale" />
</div>
<div class="flex flex-col justify-center items-center gap-5 max-w-3xl"> <div class="flex flex-col justify-center items-center gap-5 max-w-3xl">
<img src="/logo.svg" class="bg-primary p-3 size-16 rounded-full" alt="HaexVault Logo" /> <img
src="/logo.svg"
class="bg-primary p-3 size-16 rounded-full"
alt="HaexVault Logo"
/>
<span class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center"> <span
class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center"
>
<p class="whitespace-nowrap"> <p class="whitespace-nowrap">
{{ t("welcome") }} {{ t('welcome') }}
</p> </p>
<UiTextGradient>Haex Hub</UiTextGradient> <UiTextGradient>Haex Hub</UiTextGradient>
</span> </span>
@ -13,34 +22,15 @@
<div class="flex flex-col md:flex-row gap-4 w-full h-24 md:h-auto"> <div class="flex flex-col md:flex-row gap-4 w-full h-24 md:h-auto">
<VaultButtonCreate /> <VaultButtonCreate />
<VaultButtonOpen v-model:isOpen="passwordPromptOpen" :path="vaultPath" /> <VaultButtonOpen
<!-- <NuxtLinkLocale v-model:isOpen="passwordPromptOpen"
:to="{ :path="vaultPath"
name: 'haexBrowser',
params: { vaultId: 'test' },
}"
>test link</NuxtLinkLocale
> -->
<!-- <button @click="test">test</button>
<NuxtLinkLocale
:to="{ name: 'vaultGroup', params: { vaultId: 'test' } }"
>test link</NuxtLinkLocale
> -->
<!-- <UiTreeFolder
@edit="test"
:value="tests"
v-for="tests in [1, 2, 3]"
/> />
<UiTreeFolder
@edit="test"
value="test123"
/> -->
</div> </div>
<div v-show="lastVaults.length" class="w-full"> <div v-show="lastVaults.length" class="w-full">
<div class="font-thin text-sm justify-start px-2 pb-1"> <div class="font-thin text-sm justify-start px-2 pb-1">
{{ t("lastUsed") }} {{ t('lastUsed') }}
</div> </div>
<div <div
@ -53,10 +43,7 @@
> >
<button <button
class="link link-accent flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full py-2 px-4" class="link link-accent flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full py-2 px-4"
@click=" @click=";(passwordPromptOpen = true), (vaultPath = vault.path)"
passwordPromptOpen = true;
vaultPath = vault.path;
"
> >
<span class="block md:hidden"> <span class="block md:hidden">
{{ vault.name }} {{ vault.name }}
@ -68,14 +55,17 @@
<button <button
class="absolute right-2 btn btn-square btn-error btn-xs hidden group-hover:flex min-w-6" class="absolute right-2 btn btn-square btn-error btn-xs hidden group-hover:flex min-w-6"
> >
<Icon name="mdi:trash-can-outline" @click="removeVaultAsync(vault.path)" /> <Icon
name="mdi:trash-can-outline"
@click="removeVaultAsync(vault.path)"
/>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="flex flex-col items-center gap-2"> <div class="flex flex-col items-center gap-2">
<h4>{{ t("sponsors") }}</h4> <h4>{{ t('sponsors') }}</h4>
<div> <div>
<button @click="openUrl('https://itemis.com')"> <button @click="openUrl('https://itemis.com')">
<UiLogoItemis class="text-[#00457C]" /> <UiLogoItemis class="text-[#00457C]" />
@ -87,41 +77,21 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { openUrl } from "@tauri-apps/plugin-opener"; import { openUrl } from '@tauri-apps/plugin-opener'
const passwordPromptOpen = ref(false);
const vaultPath = ref("");
definePageMeta({ definePageMeta({
name: "vaultOpen", name: 'vaultOpen',
}); })
const { t } = useI18n(); const passwordPromptOpen = ref(false)
const vaultPath = ref('')
const { syncLastVaultsAsync, removeVaultAsync } = useLastVaultStore(); const { t, setLocale } = useI18n()
const { lastVaults } = storeToRefs(useLastVaultStore());
await syncLastVaultsAsync(); const { syncLastVaultsAsync, removeVaultAsync } = useLastVaultStore()
const { lastVaults } = storeToRefs(useLastVaultStore())
/* const { $pluginManager } = useNuxtApp(); await syncLastVaultsAsync()
console.log('$pluginManager', $pluginManager);
async function loadModule() {
try {
// Dynamisches Laden des Moduls
const file = await open({
multiple: false,
directory: false,
});
const moduleUrl =
'/home/haex/Projekte/haex-vault-2/haex-vault/src/extensions/test/testPlugin.ts'; // Pfad relativ zum Server-Root
await $pluginManager.loadDynamicModule(file);
console.log('Modul erfolgreich geladen');
} catch (error) {
console.error('Fehler beim Laden des Moduls:', error);
}
}
//await loadModule(); */
</script> </script>
<i18n lang="json"> <i18n lang="json">

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="text-white h-full"> <div class="w-full h-full">
<NuxtLayout name="app"> <NuxtLayout name="app">
<NuxtPage /> <NuxtPage />
</NuxtLayout> </NuxtLayout>
@ -8,6 +8,6 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
middleware: "database", middleware: 'database',
}); })
</script> </script>

View File

@ -4,7 +4,7 @@
{{ iframeSrc }} {{ iframeSrc }}
</div> --> </div> -->
<iframe <iframe
v-if="iframeSrc" v-if="iframeIndex"
class="w-full h-full" class="w-full h-full"
@load="" @load=""
ref="iFrameRef" ref="iFrameRef"
@ -13,14 +13,25 @@
allow="autoplay; speaker-selection; encrypted-media;" allow="autoplay; speaker-selection; encrypted-media;"
> >
</iframe> </iframe>
<UiButton @click="go = true">Go</UiButton>
<!-- <p v-else>{{ t("loading") }}</p> --> <!-- <p v-else>{{ t("loading") }}</p> -->
<audio controls :src="audioTest"> {{ audioTest }}
<audio v-if="go" controls :src="audioTest">
Dein Browser unterstützt das Audio-Element nicht. Dein Browser unterstützt das Audio-Element nicht.
</audio> </audio>
<video v-if="go" controls width="600" :src="demoVideo"></video>
<div v-if="audioError">
Fehler beim Laden der Audio-Datei: {{ audioError }}
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { convertFileSrc } from '@tauri-apps/api/core'
import { appDataDir, join, resourceDir } from '@tauri-apps/api/path'
definePageMeta({ definePageMeta({
name: 'haexExtension', name: 'haexExtension',
}) })
@ -30,25 +41,34 @@ const iframeRef = useTemplateRef('iFrameRef')
const { extensionEntry: iframeSrc, currentExtension } = storeToRefs( const { extensionEntry: iframeSrc, currentExtension } = storeToRefs(
useExtensionsStore() useExtensionsStore()
) )
const audioTest = computed(() => `${iframeSrc.value}/sounds/music/demo.mp3`) const audioAssetUrl = ref('')
watch(audioTest, () => console.log('audioTest', audioTest.value), { const audioError = ref('')
immediate: true, const audioTest = convertFileSrc(
}) await join(await appDataDir(), 'resources/demo.mp3')
)
//computed(() => `${iframeSrc.value}/sounds/music/demo.mp3`)
const go = ref(false)
const iframeIndex = computed(() => `${iframeSrc.value}/index.html`) const iframeIndex = computed(() => `${iframeSrc.value}/index.html`)
const demoVideo = computed(() => `${iframeSrc.value}/sounds/music/demo.mp3`)
const extensionStore = useExtensionsStore() const extensionStore = useExtensionsStore()
watch(iframeSrc, () => console.log('iframeSrc', iframeSrc.value), { watch(
immediate: true, demoVideo,
}) async () => {
const res = await fetch(
'/home/haex/.local/share/space.haex.hub/extensions/pokedemo/1.0/sounds/music/demo.mp3'
)
console.log('respo', res)
onMounted(async () => { console.log('iframeSrc', iframeSrc.value)
/* const minfest = await extensionStore.readManifestFileAsync( },
currentExtension.value!.id, {
currentExtension.value!.version immediate: true,
); }
console.log("manifest", minfest, extensionStore.extensionEntry); */ )
})
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -1,15 +1,15 @@
<template> <template>
<div class="h-full"></div> <div class="h-full text-base-content"></div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
name: "vaultOverview", name: 'vaultOverview',
}); })
const extensionStore = useExtensionsStore(); const extensionStore = useExtensionsStore()
onMounted(async () => { onMounted(async () => {
await extensionStore.loadExtensionsAsync(); await extensionStore.loadExtensionsAsync()
}); })
</script> </script>

View File

@ -0,0 +1,77 @@
<template>
<div
class="grid grid-rows-2 sm:grid-cols-2 sm:gap-2 p-2 max-w-2xl w-full h-fit"
>
<div class="p-2">{{ t('language') }}</div>
<div><UiDropdownLocale @select="onSelectLocaleAsync" /></div>
<div class="p-2">{{ t('design') }}</div>
<div><UiDropdownTheme @select="onSelectThemeAsync" /></div>
<div class="p-2">{{ t('vaultName') }}</div>
<div>
<UiInput v-model="currentVaultName" :placeholder="t('vaultName')">
<template #append>
<UiTooltip :tooltip="t('save')">
<UiButton class="btn-primary" @click="onSetVaultNameAsync">
<Icon name="mdi:content-save-outline" />
</UiButton>
</UiTooltip>
</template>
</UiInput>
</div>
</div>
</template>
<script setup lang="ts">
import { eq } from 'drizzle-orm'
import { type Locale } from 'vue-i18n'
import { haexSettings } from '~~/src-tauri/database/schemas/vault'
definePageMeta({
name: 'haexSettings',
})
const { t, setLocale } = useI18n()
const { currentVault, currentVaultName } = storeToRefs(useVaultStore())
const { updateVaultNameAsync } = useVaultStore()
const onSelectLocaleAsync = async (locale: Locale) => {
console.log('onSelectLocaleAsync', locale)
const update = await currentVault.value?.drizzle
.update(haexSettings)
.set({ key: 'locale', value: locale })
.where(eq(haexSettings.key, 'locale'))
await setLocale(locale)
console.log('update locale', update)
}
const { currentTheme } = storeToRefs(useUiStore())
const onSelectThemeAsync = async (theme: ITheme) => {
const update = await currentVault.value?.drizzle
.update(haexSettings)
.set({ key: 'theme', value: theme.name })
.where(eq(haexSettings.key, 'theme'))
currentTheme.value = theme
}
const onSetVaultNameAsync = async (vaultName: string) => {
updateVaultNameAsync(vaultName)
}
</script>
<i18n lang="yaml">
de:
language: Sprache
design: Design
vaultName: Vaultname
save: Änderung speichern
en:
language: Language
design: Design
vaultName: Vault Name
save: save changes
</i18n>

View File

@ -46,6 +46,7 @@ export const useBrowserExtensionStore = defineStore(
const initializeAsync = async () => { const initializeAsync = async () => {
const { isInitialized } = storeToRefs(useBrowserExtensionStore()); const { isInitialized } = storeToRefs(useBrowserExtensionStore());
return
if (isInitialized.value) return; if (isInitialized.value) return;
// Lade Erweiterungen aus dem Erweiterungsverzeichnis // Lade Erweiterungen aus dem Erweiterungsverzeichnis
@ -87,4 +88,4 @@ const processNavigation = (url: string) => {
return true; return true;
}; };
const injectContentScripts = (t: string) => {}; const injectContentScripts = (t: string) => { };

View File

@ -285,12 +285,12 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
const extensionEntry = computedAsync( const extensionEntry = computedAsync(
async () => { async () => {
try { try {
console.log("extensionEntry start", currentExtension.value); /* console.log("extensionEntry start", currentExtension.value);
const regex = /((href|src)=["'])([^"']+)(["'])/g; const regex = /((href|src)=["'])([^"']+)(["'])/g; */
if (!currentExtension.value?.id || !currentExtension.value.version) { if (!currentExtension.value?.id || !currentExtension.value.version) {
console.log("extension id or entry missing", currentExtension.value); console.log("extension id or entry missing", currentExtension.value);
return "no mani: " + currentExtension.value; return ""// "no mani: " + currentExtension.value;
} }
const extensionPath = await getExtensionPathAsync( const extensionPath = await getExtensionPathAsync(
@ -304,7 +304,7 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
currentExtension.value.version currentExtension.value.version
); );
if (!manifest) return "no manifest readable"; if (!manifest) return ""//"no manifest readable";
const entryPath = await join(extensionPath, manifest.entry); const entryPath = await join(extensionPath, manifest.entry);
@ -322,12 +322,12 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
console.log("entryHtml", entryHtml); console.log("entryHtml", entryHtml);
const replacements = []; const replacements = [];
let match; let match;
while ((match = regex.exec(entryHtml)) !== null) { /* while ((match = regex.exec(entryHtml)) !== null) {
const [fullMatch, prefix, attr, resource, suffix] = match; const [fullMatch, prefix, attr, resource, suffix] = match;
if (!resource.startsWith("http")) { if (!resource.startsWith("http")) {
replacements.push({ match: fullMatch, resource, prefix, suffix }); replacements.push({ match: fullMatch, resource, prefix, suffix });
} }
} } */
for (const { match, resource, prefix, suffix } of replacements) { for (const { match, resource, prefix, suffix } of replacements) {
const srcFile = convertFileSrc(await join(extensionPath, resource)); const srcFile = convertFileSrc(await join(extensionPath, resource));

View File

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

View File

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

View File

@ -2,6 +2,12 @@ import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
import de from './de.json'; import de from './de.json';
import en from './en.json'; import en from './en.json';
export interface ITheme {
value: string,
name: string,
icon: string
}
export const useUiStore = defineStore('uiStore', () => { export const useUiStore = defineStore('uiStore', () => {
const breakpoints = useBreakpoints(breakpointsTailwind); const breakpoints = useBreakpoints(breakpointsTailwind);
@ -28,14 +34,22 @@ export const useUiStore = defineStore('uiStore', () => {
icon: 'line-md:moon-to-sunny-outline-loop-transition', icon: 'line-md:moon-to-sunny-outline-loop-transition',
}, },
{ value: 'soft', name: t('ui.soft'), icon: 'line-md:paint-drop' }, { value: 'soft', name: t('ui.soft'), icon: 'line-md:paint-drop' },
{
value: 'corporate',
name: t('ui.corporate'),
icon: 'hugeicons:corporate',
},
]); ]);
const currentTheme = ref(availableThemes.value[0].value); const defaultTheme = ref(availableThemes.value[0])
const currentTheme = ref(defaultTheme);
return { return {
availableThemes,
breakpoints, breakpoints,
currentScreenSize, currentScreenSize,
currentTheme, currentTheme,
availableThemes, defaultTheme,
}; };
}); });

View File

@ -13,13 +13,6 @@ export const useSidebarStore = defineStore("sidebarStore", () => {
const isVisible = ref(true); const isVisible = ref(true);
const menu = ref<ISidebarItem[]>([ const menu = ref<ISidebarItem[]>([
{
id: "haex-browser",
name: "Haex Browser",
icon: "solar:global-outline",
to: { name: "haexBrowser" },
},
{ {
id: "haex-extensions-add", id: "haex-extensions-add",
name: "Haex Extensions", name: "Haex Extensions",

View File

@ -1,11 +1,9 @@
//import Database from '@tauri-apps/plugin-sql';
import { drizzle, SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy";
//import Database from "tauri-plugin-sql-api";
import * as schema from "@/../src-tauri/database/schemas/vault";
import * as schema from "@/../src-tauri/database/schemas/vault";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { and, count, eq } from "drizzle-orm"; import { hostname, platform, type, version } from "@tauri-apps/plugin-os";
import { platform } from "@tauri-apps/plugin-os"; import { eq } from "drizzle-orm";
import { drizzle, SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy";
interface IVault { interface IVault {
name: string; name: string;
@ -16,6 +14,7 @@ interface IOpenVaults {
} }
export const useVaultStore = defineStore("vaultStore", () => { export const useVaultStore = defineStore("vaultStore", () => {
const currentVaultId = computed<string | undefined>({ const currentVaultId = computed<string | undefined>({
get: () => getSingleRouteParam(useRouter().currentRoute.value.params.vaultId), get: () => getSingleRouteParam(useRouter().currentRoute.value.params.vaultId),
set: (newVaultId) => { set: (newVaultId) => {
@ -23,6 +22,9 @@ export const useVaultStore = defineStore("vaultStore", () => {
}, },
}); });
const defaultVaultName = ref("HaexHub")
const currentVaultName = ref(defaultVaultName.value)
const read_only = computed<boolean>({ const read_only = computed<boolean>({
get: () => { get: () => {
console.log("query showSidebar", useRouter().currentRoute.value.query.readonly); console.log("query showSidebar", useRouter().currentRoute.value.query.readonly);
@ -53,6 +55,8 @@ export const useVaultStore = defineStore("vaultStore", () => {
{ immediate: true } { immediate: true }
); );
const hostKey = computedAsync(async () => "".concat(type(), version(), await hostname() ?? ""))
const openAsync = async ({ path = "", password }: { path: string; password: string }) => { const openAsync = async ({ path = "", password }: { path: string; password: string }) => {
try { try {
const result = await invoke<string>("open_encrypted_database", { const result = await invoke<string>("open_encrypted_database", {
@ -102,7 +106,11 @@ export const useVaultStore = defineStore("vaultStore", () => {
}; };
const { addVaultAsync } = useLastVaultStore(); const { addVaultAsync } = useLastVaultStore();
await addVaultAsync({ path }); addVaultAsync({ path });
syncLocaleAsync()
syncThemeAsync()
syncVaultNameAsync()
return vaultId; return vaultId;
} catch (error) { } catch (error) {
@ -147,15 +155,86 @@ export const useVaultStore = defineStore("vaultStore", () => {
delete openVaults.value?.[currentVaultId.value]; delete openVaults.value?.[currentVaultId.value];
}; };
const syncLocaleAsync = async () => {
try {
const app = useNuxtApp()
app.$i18n.availableLocales
//const { availableLocales, defaultLocale, setLocale, locale } = useI18n()
const currentLocaleRow = await currentVault.value?.drizzle
.select()
.from(schema.haexSettings)
.where(eq(schema.haexSettings.key, 'locale'))
if (currentLocaleRow?.[0]?.value) {
const currentLocale = app.$i18n.availableLocales.find(
(locale) => locale === currentLocaleRow[0].value
)
await app.$i18n.setLocale(currentLocale ?? app.$i18n.defaultLocale)
} else {
await currentVault.value?.drizzle
.insert(schema.haexSettings)
.values({ id: crypto.randomUUID(), key: 'locale', value: app.$i18n.locale.value })
}
} catch (error) {
console.log("ERROR syncLocaleAsync", error)
}
}
const syncThemeAsync = async () => {
const { availableThemes, defaultTheme, currentTheme } = storeToRefs(useUiStore())
const currentThemeRow = await currentVault.value?.drizzle
.select()
.from(schema.haexSettings)
.where(eq(schema.haexSettings.key, 'theme'))
if (currentThemeRow?.[0]?.value) {
const theme = availableThemes.value.find(
(theme) => theme.name === currentThemeRow[0].value
)
currentTheme.value = theme ?? defaultTheme.value
} else {
await currentVault.value?.drizzle.insert(schema.haexSettings).values({
id: crypto.randomUUID(),
key: 'theme',
value: currentTheme.value.name,
})
}
}
const syncVaultNameAsync = async () => {
const currentVaultNameRow = await currentVault.value?.drizzle
.select()
.from(schema.haexSettings)
.where(eq(schema.haexSettings.key, 'vaultName'))
if (currentVaultNameRow?.[0]?.value) {
currentVaultName.value = currentVaultNameRow.at(0)?.value ?? defaultVaultName.value
} else {
await currentVault.value?.drizzle.insert(schema.haexSettings).values({
id: crypto.randomUUID(),
key: 'vaultName',
value: currentVaultName.value,
})
}
}
const updateVaultNameAsync = async (newVaultName?: string | null) => {
return currentVault.value?.drizzle.update(schema.haexSettings).set({ value: newVaultName ?? defaultVaultName.value }).where(eq(schema.haexSettings.key, "vaultName"))
}
return { return {
closeAsync, closeAsync,
createAsync, createAsync,
currentVault, currentVault,
currentVaultId, currentVaultId,
currentVaultName,
hostKey,
openAsync, openAsync,
openVaults, openVaults,
refreshDatabaseAsync,
read_only, read_only,
refreshDatabaseAsync,
updateVaultNameAsync,
}; };
}); });
@ -174,3 +253,6 @@ const isSelectQuery = (sql: string) => {
const selectRegex = /^\s*SELECT\b/i; const selectRegex = /^\s*SELECT\b/i;
return selectRegex.test(sql); return selectRegex.test(sql);
}; };

View File

@ -1,148 +0,0 @@
import { defineConfig } from '@nuxtjs/tailwindcss/config';
//import { iconsPlugin, dynamicIconsPlugin } from '@egoist/tailwindcss-icons';
//import colors from 'tailwindcss/colors';
import themes from 'flyonui/src/theming/themes';
//import * as tailwindMotion from 'tailwindcss-motion';
import { addDynamicIconSelectors } from '@iconify/tailwind';
export default defineConfig({
content: ['./src/**/*.{vue,ts,svg}', './node_modules/flyonui/dist/js/*.js'],
darkMode: 'selector',
plugins: [
/* iconsPlugin(),
dynamicIconsPlugin(), */
addDynamicIconSelectors(),
require('flyonui'),
require('flyonui/plugin'),
//tailwindMotion,
],
flyonui: {
themes: [
{
light: {
...themes.light,
/* primary: colors.teal[500],
secondary: colors.purple[500], */
},
soft: {
...themes.soft,
/* primary: colors.teal[500],
secondary: colors.purple[500], */
},
dark: {
...themes.dark,
/* primary: colors.cyan[700], //colors.teal[600],
secondary: colors.purple[500], */
/* 'primary-content': '#000516',
'secondary': '#008f9c',
'secondary-content': '#000709',
'accent': '#007f7a',
'accent-content': '#d3e5e3',
'neutral': '#321a15',
'neutral-content': '#d3ccca',
'base-100': '#002732',
'base-200': '#00202a',
'base-300': '#001a22',
'base-content': '#c8cfd2',
'info': '#0086b2',
'info-content': '#00060c',
'success': '#a5da00',
'success-content': '#0a1100',
'warning': '#ff8d00',
'warning-content': '#160700',
'error': '#c83849',
'error-content': '#f9d9d9', */
},
},
],
/* themes: [
{
dark: {
'primary': colors.teal[500],
'primary-content': '#010811',
'secondary': colors.purple[500],
'secondary-content': '#130201',
'accent': '#9b59b6',
'accent-content': '#ebddf1',
'neutral': '#95a5a6',
'neutral-content': '#080a0a',
'base-100': colors.slate[100],
'base-200': colors.slate[400],
'base-300': colors.slate[900],
'base-content': colors.slate[800],
'info': '#1abc9c',
'info-content': '#000d09',
'success': '#2ecc71',
'success-content': '#010f04',
'warning': '#f1c40f',
'warning-content': '#140e00',
'error': '#e74c3c',
'error-content': '#130201',
},
light: {
'primary': colors.teal[500],
'primary-content': '#010811',
'secondary': colors.purple[500],
'secondary-content': '#130201',
'accent': '#9b59b6',
'accent-content': '#ebddf1',
'neutral': '#95a5a6',
'neutral-content': '#080a0a',
'base-100': '#ecf0f1',
'base-200': '#cdd1d2',
'base-300': '#afb2b3',
'base-content': '#131414',
'info': '#1abc9c',
'info-content': '#000d09',
'success': '#2ecc71',
'success-content': '#010f04',
'warning': '#f1c40f',
'warning-content': '#140e00',
'error': '#e74c3c',
'error-content': '#130201',
},
},
], */
},
/* theme: {
extend: {
colors: {
'primary-active': colors.teal[600],
'primary-focus': colors.teal[400],
'primary-hover': colors.teal[400],
'primary': colors.teal[500],
'dark': {
'primary-active': colors.teal[700],
'primary-focus': colors.teal[600],
'primary-hover': colors.teal[600],
'primary': colors.teal[500],
},
'secondary': colors.sky[500],
},
fontFamily: {
sans: [
'Adelle',
'Roboto Slab',
'DejaVu Serif',
'Georgia',
'Graphik',
'sans-serif',
],
serif: ['Merriweather', 'serif'],
},
screens: {
xs: '360px',
},
transitionProperty: {
height: 'height',
},
},
}, */
}); // satisfies Config;