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

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

View File

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

View File

@ -1,24 +1,8 @@
<template>
<form
class="url-bar"
@submit.prevent="handleSubmit"
>
<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 class="url-bar" @submit.prevent="handleSubmit">
<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>
</template>
@ -32,26 +16,26 @@ const props = defineProps({
type: Boolean,
default: false,
},
});
})
const emit = defineEmits(['submit']);
const emit = defineEmits(['submit'])
const inputValue = ref(props.url);
const inputValue = ref(props.url)
watch(
() => props.url,
(newUrl) => {
inputValue.value = newUrl;
inputValue.value = newUrl
}
);
)
const handleSubmit = () => {
// URL validieren und ggf. Protokoll hinzufügen
let processedUrl = inputValue.value.trim();
let processedUrl = inputValue.value.trim()
if (processedUrl && !processedUrl.match(/^[a-zA-Z]+:\/\//)) {
processedUrl = 'https://' + processedUrl;
processedUrl = 'https://' + processedUrl
}
emit('submit', processedUrl);
};
emit('submit', processedUrl)
}
</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>
<span>
<!-- <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">
<div>
<fieldset class="join w-full">
<slot name="prepend" />
<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">
<input
@ -90,12 +22,13 @@
<label class="input-floating-label" :for="id">{{ label }}</label>
</div>
<Icon :name="appendIcon" class="my-auto shrink-0" />
<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}`)"
>
@ -108,61 +41,61 @@
{{ error }}
</span>
</span>
</span>
</div>
</template>
<script setup lang="ts">
import { type ZodSchema } from "zod";
import { type ZodSchema } from 'zod'
const inputRef = useTemplateRef("inputRef");
defineExpose({ inputRef });
const inputRef = useTemplateRef('inputRef')
defineExpose({ inputRef })
defineOptions({
inheritAttrs: false,
});
})
const props = defineProps({
placeholder: {
type: String,
default: "",
default: '',
},
type: {
type: String as PropType<
| "button"
| "checkbox"
| "color"
| "date"
| "datetime-local"
| "email"
| "file"
| "hidden"
| "image"
| "month"
| "number"
| "password"
| "radio"
| "range"
| "reset"
| "search"
| "submit"
| "tel"
| "text"
| "time"
| "url"
| "week"
| 'button'
| 'checkbox'
| 'color'
| 'date'
| 'datetime-local'
| 'email'
| 'file'
| 'hidden'
| 'image'
| 'month'
| 'number'
| 'password'
| 'radio'
| 'range'
| 'reset'
| 'search'
| 'submit'
| 'tel'
| 'text'
| 'time'
| 'url'
| 'week'
>,
default: "text",
default: 'text',
},
label: String,
name: String,
prependIcon: {
type: String,
default: "",
default: '',
},
prependLabel: String,
appendIcon: {
type: String,
default: "",
default: '',
},
appendLabel: String,
rules: Object as PropType<ZodSchema>,
@ -170,45 +103,45 @@ const props = defineProps({
withCopyButton: Boolean,
autofocus: Boolean,
read_only: Boolean,
});
})
const input = defineModel<string | number | undefined | null>({
default: "",
default: '',
required: true,
});
})
const { currentScreenSize } = storeToRefs(useUiStore());
const { currentScreenSize } = storeToRefs(useUiStore())
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(
() => props.checkInput,
() => {
checkInput();
checkInput()
}
);
)
const emit = defineEmits(["error"]);
const emit = defineEmits(['error'])
const checkInput = () => {
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);
if (!result.success) {
errors.value = result.error.errors.map((error) => error.message);
emit("error", errors.value);
errors.value = result.error.errors.map((error) => error.message)
emit('error', errors.value)
} else {
errors.value = [];
errors.value = []
}
}
};
}
const { copy, copied } = useClipboard();
const { copy, copied } = useClipboard()
</script>

View File

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

View File

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

View File

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