Update to SDK v1.9.10 with centralized method names

- Updated @haexhub/sdk dependency from 1.9.7 to 1.9.10
- Imported HAEXTENSION_METHODS and HAEXTENSION_EVENTS from SDK
- Updated all handler files to use new nested method constants
- Updated extensionMessageHandler to route using constants
- Changed application.open routing in web handler
- All method names now use haextension:subject:action schema
This commit is contained in:
2025-11-14 10:22:52 +01:00
parent 2202415441
commit c1ee8e6bc0
59 changed files with 3294 additions and 472 deletions

View File

@ -86,6 +86,21 @@ const extension = computed(() => {
})
const handleIframeLoad = () => {
console.log('[ExtensionFrame] Iframe loaded successfully for:', extension.value?.name)
// Try to inject a test script to see if JavaScript execution works
try {
if (iframeRef.value?.contentWindow) {
console.log('[ExtensionFrame] Iframe has contentWindow access')
// This will fail with sandboxed iframes without allow-same-origin
console.log('[ExtensionFrame] Iframe origin:', iframeRef.value.contentWindow.location.href)
} else {
console.warn('[ExtensionFrame] Iframe contentWindow is null/undefined')
}
} catch (e) {
console.warn('[ExtensionFrame] Cannot access iframe content (expected with sandbox):', e)
}
// Delay the fade-in slightly to allow window animation to mostly complete
setTimeout(() => {
isLoading.value = false
@ -102,13 +117,28 @@ const sandboxAttributes = computed(() => {
// Generate extension URL
const extensionUrl = computed(() => {
if (!extension.value) return ''
if (!extension.value) {
console.log('[ExtensionFrame] No extension found')
return ''
}
const { publicKey, name, version, devServerUrl } = extension.value
const assetPath = 'index.html'
console.log('[ExtensionFrame] Generating URL for extension:', {
name,
publicKey: publicKey?.substring(0, 10) + '...',
version,
devServerUrl,
platform,
})
if (!publicKey || !name || !version) {
console.error('Missing required extension fields')
console.error('[ExtensionFrame] Missing required extension fields:', {
hasPublicKey: !!publicKey,
hasName: !!name,
hasVersion: !!version,
})
return ''
}
@ -116,7 +146,9 @@ const extensionUrl = computed(() => {
if (devServerUrl) {
const cleanUrl = devServerUrl.replace(/\/$/, '')
const cleanPath = assetPath.replace(/^\//, '')
return cleanPath ? `${cleanUrl}/${cleanPath}` : cleanUrl
const url = cleanPath ? `${cleanUrl}/${cleanPath}` : cleanUrl
console.log('[ExtensionFrame] Using dev server URL:', url)
return url
}
const extensionInfo = {
@ -126,13 +158,18 @@ const extensionUrl = computed(() => {
}
const encodedInfo = btoa(JSON.stringify(extensionInfo))
let url = ''
if (platform === 'android' || platform === 'windows') {
// Android: Tauri uses http://{scheme}.localhost format
return `http://${EXTENSION_PROTOCOL_NAME}.localhost/${encodedInfo}/${assetPath}`
url = `http://${EXTENSION_PROTOCOL_NAME}.localhost/${encodedInfo}/${assetPath}`
console.log('[ExtensionFrame] Generated Android/Windows URL:', url)
} else {
// Desktop: Use custom protocol with base64 as host
return `${EXTENSION_PROTOCOL_PREFIX}${encodedInfo}/${assetPath}`
url = `${EXTENSION_PROTOCOL_PREFIX}${encodedInfo}/${assetPath}`
console.log('[ExtensionFrame] Generated Desktop URL:', url)
}
return url
})
const retryLoad = () => {
@ -150,19 +187,28 @@ onMounted(() => {
// Wait for iframe to be ready
if (iframeRef.value && extension.value) {
console.log(
'[ExtensionFrame] Manually registering iframe on mount',
'[ExtensionFrame] Component MOUNTED',
extension.value.name,
'windowId:',
props.windowId,
)
registerExtensionIFrame(iframeRef.value, extension.value, props.windowId)
} else {
console.warn('[ExtensionFrame] Component mounted but missing iframe or extension:', {
hasIframe: !!iframeRef.value,
hasExtension: !!extension.value,
})
}
})
// Explicit cleanup before unmount
onBeforeUnmount(() => {
console.log('[ExtensionFrame] Component UNMOUNTING', {
extensionId: props.extensionId,
windowId: props.windowId,
hasIframe: !!iframeRef.value,
})
if (iframeRef.value) {
console.log('[ExtensionFrame] Unregistering iframe on unmount')
unregisterExtensionIFrame(iframeRef.value)
}
})

View File

@ -92,130 +92,142 @@
:key="window.id"
>
<!-- Overview Mode: Teleport to window preview -->
<Teleport
<template
v-if="
windowManager.showWindowOverview &&
overviewWindowState.has(window.id)
"
:to="`#window-preview-${window.id}`"
>
<div
class="absolute origin-top-left"
:style="{
transform: `scale(${overviewWindowState.get(window.id)!.scale})`,
width: `${overviewWindowState.get(window.id)!.width}px`,
height: `${overviewWindowState.get(window.id)!.height}px`,
}"
>
<HaexWindow
v-show="
windowManager.showWindowOverview || !window.isMinimized
"
:id="window.id"
v-model:x="overviewWindowState.get(window.id)!.x"
v-model:y="overviewWindowState.get(window.id)!.y"
v-model:width="overviewWindowState.get(window.id)!.width"
v-model:height="overviewWindowState.get(window.id)!.height"
:title="window.title"
:icon="window.icon"
:is-active="windowManager.isWindowActive(window.id)"
:source-x="window.sourceX"
:source-y="window.sourceY"
:source-width="window.sourceWidth"
:source-height="window.sourceHeight"
:is-opening="window.isOpening"
:is-closing="window.isClosing"
:warning-level="
window.type === 'extension' &&
availableExtensions.find(
(ext) => ext.id === window.sourceId,
)?.devServerUrl
? 'warning'
: undefined
"
class="no-swipe"
@close="windowManager.closeWindow(window.id)"
@minimize="windowManager.minimizeWindow(window.id)"
@activate="windowManager.activateWindow(window.id)"
@position-changed="
(x, y) =>
windowManager.updateWindowPosition(window.id, x, y)
"
@size-changed="
(width, height) =>
windowManager.updateWindowSize(window.id, width, height)
"
@drag-start="handleWindowDragStart(window.id)"
@drag-end="handleWindowDragEnd"
<Teleport :to="`#window-preview-${window.id}`">
<div
class="absolute origin-top-left"
:style="{
transform: `scale(${overviewWindowState.get(window.id)!.scale})`,
width: `${overviewWindowState.get(window.id)!.width}px`,
height: `${overviewWindowState.get(window.id)!.height}px`,
}"
>
<!-- System Window: Render Vue Component -->
<component
:is="getSystemWindowComponent(window.sourceId)"
v-if="window.type === 'system'"
/>
<HaexWindow
v-show="
windowManager.showWindowOverview || !window.isMinimized
"
:id="window.id"
v-model:x="overviewWindowState.get(window.id)!.x"
v-model:y="overviewWindowState.get(window.id)!.y"
v-model:width="overviewWindowState.get(window.id)!.width"
v-model:height="
overviewWindowState.get(window.id)!.height
"
:title="window.title"
:icon="window.icon"
:is-active="windowManager.isWindowActive(window.id)"
:source-x="window.sourceX"
:source-y="window.sourceY"
:source-width="window.sourceWidth"
:source-height="window.sourceHeight"
:is-opening="window.isOpening"
:is-closing="window.isClosing"
:warning-level="
window.type === 'extension' &&
availableExtensions.find(
(ext) => ext.id === window.sourceId,
)?.devServerUrl
? 'warning'
: undefined
"
class="no-swipe"
@close="windowManager.closeWindow(window.id)"
@minimize="windowManager.minimizeWindow(window.id)"
@activate="windowManager.activateWindow(window.id)"
@position-changed="
(x, y) =>
windowManager.updateWindowPosition(window.id, x, y)
"
@size-changed="
(width, height) =>
windowManager.updateWindowSize(
window.id,
width,
height,
)
"
@drag-start="handleWindowDragStart(window.id)"
@drag-end="handleWindowDragEnd"
>
<!-- System Window: Render Vue Component -->
<component
:is="getSystemWindowComponent(window.sourceId)"
v-if="window.type === 'system'"
/>
<!-- Extension Window: Render iFrame -->
<HaexDesktopExtensionFrame
v-else
:extension-id="window.sourceId"
:window-id="window.id"
/>
</HaexWindow>
</div>
</Teleport>
<!-- Extension Window: Render iFrame -->
<HaexDesktopExtensionFrame
v-else
:extension-id="window.sourceId"
:window-id="window.id"
/>
</HaexWindow>
</div>
</Teleport>
</template>
<!-- Desktop Mode: Render directly in workspace -->
<HaexWindow
v-else
v-show="windowManager.showWindowOverview || !window.isMinimized"
:id="window.id"
v-model:x="window.x"
v-model:y="window.y"
v-model:width="window.width"
v-model:height="window.height"
:title="window.title"
:icon="window.icon"
:is-active="windowManager.isWindowActive(window.id)"
:source-x="window.sourceX"
:source-y="window.sourceY"
:source-width="window.sourceWidth"
:source-height="window.sourceHeight"
:is-opening="window.isOpening"
:is-closing="window.isClosing"
:warning-level="
window.type === 'extension' &&
availableExtensions.find((ext) => ext.id === window.sourceId)
?.devServerUrl
? 'warning'
: undefined
"
class="no-swipe"
@close="windowManager.closeWindow(window.id)"
@minimize="windowManager.minimizeWindow(window.id)"
@activate="windowManager.activateWindow(window.id)"
@position-changed="
(x, y) => windowManager.updateWindowPosition(window.id, x, y)
"
@size-changed="
(width, height) =>
windowManager.updateWindowSize(window.id, width, height)
"
@drag-start="handleWindowDragStart(window.id)"
@drag-end="handleWindowDragEnd"
>
<!-- System Window: Render Vue Component -->
<component
:is="getSystemWindowComponent(window.sourceId)"
v-if="window.type === 'system'"
/>
<template v-else>
<HaexWindow
v-show="
windowManager.showWindowOverview || !window.isMinimized
"
:id="window.id"
v-model:x="window.x"
v-model:y="window.y"
v-model:width="window.width"
v-model:height="window.height"
:title="window.title"
:icon="window.icon"
:is-active="windowManager.isWindowActive(window.id)"
:source-x="window.sourceX"
:source-y="window.sourceY"
:source-width="window.sourceWidth"
:source-height="window.sourceHeight"
:is-opening="window.isOpening"
:is-closing="window.isClosing"
:warning-level="
window.type === 'extension' &&
availableExtensions.find(
(ext) => ext.id === window.sourceId,
)?.devServerUrl
? 'warning'
: undefined
"
class="no-swipe"
@close="windowManager.closeWindow(window.id)"
@minimize="windowManager.minimizeWindow(window.id)"
@activate="windowManager.activateWindow(window.id)"
@position-changed="
(x, y) =>
windowManager.updateWindowPosition(window.id, x, y)
"
@size-changed="
(width, height) =>
windowManager.updateWindowSize(window.id, width, height)
"
@drag-start="handleWindowDragStart(window.id)"
@drag-end="handleWindowDragEnd"
>
<!-- System Window: Render Vue Component -->
<component
:is="getSystemWindowComponent(window.sourceId)"
v-if="window.type === 'system'"
/>
<!-- Extension Window: Render iFrame -->
<HaexDesktopExtensionFrame
v-else
:extension-id="window.sourceId"
:window-id="window.id"
/>
</HaexWindow>
<!-- Extension Window: Render iFrame -->
<HaexDesktopExtensionFrame
v-else
:extension-id="window.sourceId"
:window-id="window.id"
/>
</HaexWindow>
</template>
</template>
</div>
</UContextMenu>
@ -297,7 +309,7 @@ const currentDraggedItem = reactive({
})
// Track mouse position for showing drop target
const { x: mouseX, y: mouseY } = useMouse()
const { x: mouseX } = useMouse()
const dropTargetZone = computed(() => {
if (!isDragging.value) return null

View File

@ -0,0 +1,167 @@
<template>
<UiDrawer
v-model:open="open"
:title="t('title')"
:description="t('description')"
>
<UiButton
:label="t('button.label')"
:ui="{
base: 'px-3 py-2',
}"
icon="mdi:plus"
size="xl"
variant="outline"
block
/>
<template #content>
<div class="p-6 flex flex-col min-h-[50vh]">
<div class="flex-1 flex items-center justify-center px-4">
<UForm
:state="vault"
class="w-full max-w-md space-y-6"
>
<UFormField
:label="t('vault.label')"
name="name"
>
<UInput
v-model="vault.name"
icon="mdi:safe"
:placeholder="t('vault.placeholder')"
autofocus
size="xl"
class="w-full"
/>
</UFormField>
<UFormField
:label="t('password.label')"
name="password"
>
<UiInput
v-model="vault.password"
type="password"
icon="i-heroicons-key"
:placeholder="t('password.placeholder')"
size="xl"
class="w-full"
/>
</UFormField>
</UForm>
</div>
<div class="flex gap-3 mt-auto pt-6">
<UButton
color="neutral"
variant="outline"
block
size="xl"
@click="open = false"
>
{{ t('cancel') }}
</UButton>
<UButton
color="primary"
block
size="xl"
@click="onCreateAsync"
>
{{ t('create') }}
</UButton>
</div>
</div>
</template>
</UiDrawer>
</template>
<script setup lang="ts">
import { vaultSchema } from './schema'
const open = defineModel<boolean>('open', { default: false })
const { t } = useI18n({
useScope: 'local',
})
const vault = reactive<{
name: string
password: string
type: 'password' | 'text'
}>({
name: 'HaexVault',
password: '',
type: 'password',
})
const initVault = () => {
vault.name = 'HaexVault'
vault.password = ''
vault.type = 'password'
}
const { createAsync } = useVaultStore()
const { add } = useToast()
const check = ref(false)
const onCreateAsync = async () => {
check.value = true
const nameCheck = vaultSchema.name.safeParse(vault.name)
const passwordCheck = vaultSchema.password.safeParse(vault.password)
if (!nameCheck.success || !passwordCheck.success) return
open.value = false
try {
if (vault.name && vault.password) {
const vaultId = await createAsync({
vaultName: vault.name,
password: vault.password,
})
if (vaultId) {
initVault()
await navigateTo(
useLocaleRoute()({ name: 'desktop', params: { vaultId } }),
)
}
}
} catch (error) {
console.error(error)
add({ color: 'error', description: JSON.stringify(error) })
}
}
</script>
<i18n lang="yaml">
de:
button:
label: Vault erstellen
vault:
label: Vaultname
placeholder: Vaultname
password:
label: Passwort
placeholder: Passwort eingeben
title: Neue HaexVault erstellen
create: Erstellen
cancel: Abbrechen
description: Erstelle eine neue Vault für deine Daten
en:
button:
label: Create vault
vault:
label: Vault name
placeholder: Vault name
password:
label: Password
placeholder: Enter password
title: Create new HaexVault
create: Create
cancel: Cancel
description: Create a new vault for your data
</i18n>

View File

@ -1,12 +1,11 @@
<template>
<UiDialogConfirm
<UiDrawer
v-model:open="open"
:confirm-label="t('open')"
:description="vault.path || path"
@confirm="onOpenDatabase"
:title="t('title')"
:description="path || t('description')"
>
<UiButton
:label="t('vault.open')"
:label="t('button.label')"
:ui="{
base: 'px-3 py-2',
}"
@ -16,37 +15,63 @@
block
/>
<template #title>
<i18n-t
keypath="title"
tag="p"
class="flex gap-x-2 text-wrap"
>
<template #haexvault>
<UiTextGradient>HaexVault</UiTextGradient>
</template>
</i18n-t>
</template>
<template #content>
<div class="p-6 flex flex-col min-h-[50vh]">
<div class="flex-1 flex items-center justify-center px-4">
<div class="w-full max-w-md space-y-4">
<div
v-if="path"
class="text-sm text-gray-500 dark:text-gray-400"
>
<span class="font-medium">{{ t('path.label') }}:</span>
{{ path }}
</div>
<template #body>
<UForm
:state="vault"
class="flex flex-col gap-4 w-full h-full justify-center"
>
<UiInputPassword
v-model="vault.password"
class="w-full"
autofocus
/>
<UForm
:state="vault"
class="w-full"
>
<UFormField
:label="t('password.label')"
name="password"
>
<UInput
v-model="vault.password"
type="password"
icon="i-heroicons-key"
:placeholder="t('password.placeholder')"
autofocus
size="xl"
class="w-full"
@keyup.enter="onOpenDatabase"
/>
</UFormField>
</UForm>
</div>
</div>
<UButton
hidden
type="submit"
@click="onOpenDatabase"
/>
</UForm>
<div class="flex gap-3 mt-auto pt-6">
<UButton
color="neutral"
variant="outline"
block
size="xl"
@click="open = false"
>
{{ t('cancel') }}
</UButton>
<UButton
color="primary"
block
size="xl"
@click="onOpenDatabase"
>
{{ t('open') }}
</UButton>
</div>
</div>
</template>
</UiDialogConfirm>
</UiDrawer>
</template>
<script setup lang="ts">
@ -156,7 +181,12 @@ const onOpenDatabase = async () => {
)
} catch (error) {
open.value = false
if (error?.details?.reason === 'file is not a database') {
const errorDetails =
error && typeof error === 'object' && 'details' in error
? (error as { details?: { reason?: string } }).details
: undefined
if (errorDetails?.reason === 'file is not a database') {
add({
color: 'error',
title: t('error.password.title'),
@ -171,25 +201,37 @@ const onOpenDatabase = async () => {
<i18n lang="yaml">
de:
button:
label: Vault öffnen
open: Entsperren
title: '{haexvault} entsperren'
password: Passwort
vault:
open: Vault öffnen
cancel: Abbrechen
title: HaexVault entsperren
path:
label: Pfad
password:
label: Passwort
placeholder: Passwort eingeben
description: Öffne eine vorhandene Vault
error:
open: Vault konnte nicht geöffnet werden
password:
title: Vault konnte nicht geöffnet werden
description: Bitte üperprüfe das Passwort
description: Bitte überprüfe das Passwort
en:
button:
label: Open Vault
open: Unlock
title: Unlock {haexvault}
password: Passwort
cancel: Cancel
title: Unlock HaexVault
path:
label: Path
password:
label: Password
placeholder: Enter password
description: Open your existing vault
vault:
open: Open Vault
error:
open: Vault couldn't be opened
password:
title: Vault couldn't be opened
description: Please check your password

View File

@ -0,0 +1,185 @@
<template>
<div class="w-full h-full bg-default flex flex-col">
<!-- Header with controls -->
<div class="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center gap-2">
<UIcon
name="i-heroicons-bug-ant"
class="w-5 h-5"
/>
<h2 class="text-lg font-semibold">
Debug Logs
</h2>
<span class="text-xs text-gray-500">
{{ logs.length }} logs
</span>
</div>
<div class="flex gap-2">
<UButton
:label="allCopied ? 'Copied!' : 'Copy All'"
:color="allCopied ? 'success' : 'primary'"
size="sm"
@click="copyAllLogs"
/>
<UButton
label="Clear Logs"
color="error"
size="sm"
@click="clearLogs"
/>
</div>
</div>
<!-- Filter Buttons -->
<div class="flex gap-2 p-4 border-b border-gray-200 dark:border-gray-700 overflow-x-auto">
<UButton
v-for="level in ['all', 'log', 'info', 'warn', 'error', 'debug']"
:key="level"
:label="level"
:color="filter === level ? 'primary' : 'neutral'"
size="sm"
@click="filter = level as any"
/>
</div>
<!-- Logs Container -->
<div
ref="logsContainer"
class="flex-1 overflow-y-auto p-4 space-y-2 font-mono text-xs"
>
<div
v-for="(log, index) in filteredLogs"
:key="index"
:class="[
'p-3 rounded-lg border-l-4 relative group',
log.level === 'error'
? 'bg-red-50 dark:bg-red-950/30 border-red-500'
: log.level === 'warn'
? 'bg-yellow-50 dark:bg-yellow-950/30 border-yellow-500'
: log.level === 'info'
? 'bg-blue-50 dark:bg-blue-950/30 border-blue-500'
: log.level === 'debug'
? 'bg-purple-50 dark:bg-purple-950/30 border-purple-500'
: 'bg-gray-50 dark:bg-gray-800 border-gray-400',
]"
>
<!-- Copy Button -->
<button
class="absolute top-2 right-2 p-1.5 rounded bg-white dark:bg-gray-700 shadow-sm hover:bg-gray-100 dark:hover:bg-gray-600 active:scale-95 transition-all"
@click="copyLogToClipboard(log)"
>
<UIcon
:name="copiedIndex === index ? 'i-heroicons-check' : 'i-heroicons-clipboard-document'"
:class="[
'w-4 h-4',
copiedIndex === index ? 'text-green-500' : ''
]"
/>
</button>
<div class="flex items-start gap-2 mb-1">
<span class="text-gray-500 dark:text-gray-400 text-[10px] shrink-0">
{{ log.timestamp }}
</span>
<span
:class="[
'font-semibold text-[10px] uppercase shrink-0',
log.level === 'error'
? 'text-red-600 dark:text-red-400'
: log.level === 'warn'
? 'text-yellow-600 dark:text-yellow-400'
: log.level === 'info'
? 'text-blue-600 dark:text-blue-400'
: log.level === 'debug'
? 'text-purple-600 dark:text-purple-400'
: 'text-gray-600 dark:text-gray-400',
]"
>
{{ log.level }}
</span>
</div>
<pre class="whitespace-pre-wrap break-words text-gray-900 dark:text-gray-100 pr-8">{{ log.message }}</pre>
</div>
<div
v-if="filteredLogs.length === 0"
class="text-center text-gray-500 py-8"
>
<UIcon
name="i-heroicons-document-text"
class="w-12 h-12 mx-auto mb-2 opacity-50"
/>
<p>No logs to display</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { globalConsoleLogs } from '~/plugins/console-interceptor'
import type { ConsoleLog } from '~/plugins/console-interceptor'
const filter = ref<'all' | 'log' | 'info' | 'warn' | 'error' | 'debug'>('all')
const logsContainer = ref<HTMLDivElement>()
const copiedIndex = ref<number | null>(null)
const allCopied = ref(false)
const { $clearConsoleLogs } = useNuxtApp()
const { copy } = useClipboard()
const logs = computed(() => globalConsoleLogs.value)
const filteredLogs = computed(() => {
if (filter.value === 'all') {
return logs.value
}
return logs.value.filter((log) => log.level === filter.value)
})
const clearLogs = () => {
if ($clearConsoleLogs) {
$clearConsoleLogs()
}
}
const copyLogToClipboard = async (log: ConsoleLog) => {
const text = `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`
await copy(text)
// Find the index in filteredLogs for visual feedback
const index = filteredLogs.value.indexOf(log)
copiedIndex.value = index
// Reset after 2 seconds
setTimeout(() => {
copiedIndex.value = null
}, 2000)
}
const copyAllLogs = async () => {
const allLogsText = filteredLogs.value
.map((log) => `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`)
.join('\n')
await copy(allLogsText)
allCopied.value = true
// Reset after 2 seconds
setTimeout(() => {
allCopied.value = false
}, 2000)
}
// Auto-scroll to bottom when new logs arrive
watch(
() => logs.value.length,
() => {
nextTick(() => {
if (logsContainer.value) {
logsContainer.value.scrollTop = logsContainer.value.scrollHeight
}
})
},
{ immediate: true }
)
</script>

View File

@ -1,135 +0,0 @@
<template>
<UiDialogConfirm
:confirm-label="t('create')"
@confirm="onCreateAsync"
:description="t('description')"
>
<UiButton
:label="t('vault.create')"
:ui="{
base: 'px-3 py-2',
}"
icon="mdi:plus"
size="xl"
variant="outline"
block
/>
<template #title>
<i18n-t
keypath="title"
tag="p"
class="flex gap-x-2 flex-wrap"
>
<template #haexvault>
<UiTextGradient>HaexVault</UiTextGradient>
</template>
</i18n-t>
</template>
<template #body>
<UForm
:state="vault"
class="flex flex-col gap-4 w-full h-full justify-center"
>
<UiInput
v-model="vault.name"
leading-icon="mdi:safe"
:label="t('vault.label')"
:placeholder="t('vault.placeholder')"
/>
<UiInputPassword
v-model="vault.password"
leading-icon="mdi:key-outline"
/>
<UButton
hidden
type="submit"
@click="onCreateAsync"
/>
</UForm>
</template>
</UiDialogConfirm>
</template>
<script setup lang="ts">
import { vaultSchema } from './schema'
const { t } = useI18n({
useScope: 'local',
})
const vault = reactive<{
name: string
password: string
type: 'password' | 'text'
}>({
name: 'HaexVault',
password: '',
type: 'password',
})
const initVault = () => {
vault.name = 'HaexVault'
vault.password = ''
vault.type = 'password'
}
const { createAsync } = useVaultStore()
const { add } = useToast()
const check = ref(false)
const open = ref()
const onCreateAsync = async () => {
check.value = true
const nameCheck = vaultSchema.name.safeParse(vault.name)
const passwordCheck = vaultSchema.password.safeParse(vault.password)
if (!nameCheck.success || !passwordCheck.success) return
open.value = false
try {
if (vault.name && vault.password) {
const vaultId = await createAsync({
vaultName: vault.name,
password: vault.password,
})
if (vaultId) {
initVault()
await navigateTo(
useLocaleRoute()({ name: 'desktop', params: { vaultId } }),
)
}
}
} catch (error) {
console.error(error)
add({ color: 'error', description: JSON.stringify(error) })
}
}
</script>
<i18n lang="yaml">
de:
vault:
create: Neue Vault erstellen
label: Vaultname
placeholder: Vaultname
name: HaexVault
title: Neue {haexvault} erstellen
create: Erstellen
description: Erstelle eine neue Vault für deine Daten
en:
vault:
create: Create new vault
label: Vaultname
placeholder: Vaultname
name: HaexVault
title: Create new {haexvault}
create: Create
description: Create a new vault for your data
</i18n>

View File

@ -5,7 +5,6 @@
:readonly="props.readOnly"
:leading-icon="props.leadingIcon"
:ui="{ base: 'peer' }"
size="lg"
@change="(e) => $emit('change', e)"
@blur="(e) => $emit('blur', e)"
@keyup="(e: KeyboardEvent) => $emit('keyup', e)"