mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-19 23:30:51 +01:00
extensions fixed
This commit is contained in:
@ -27,14 +27,34 @@
|
||||
</div>
|
||||
</template>
|
||||
</UButton>
|
||||
|
||||
<!-- Console Tab -->
|
||||
<UButton
|
||||
:class="['gap-2', showConsole ? 'primary' : 'neutral']"
|
||||
@click="showConsole = !showConsole"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:console"
|
||||
size="16"
|
||||
/>
|
||||
Console
|
||||
<UBadge
|
||||
v-if="visibleLogs.length > 0"
|
||||
size="xs"
|
||||
color="primary"
|
||||
>
|
||||
{{ visibleLogs.length }}
|
||||
</UBadge>
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- IFrame Container -->
|
||||
<div class="flex-1 relative min-h-0">
|
||||
<!-- Extension IFrames -->
|
||||
<div
|
||||
v-for="tab in tabsStore.sortedTabs"
|
||||
:key="tab.extension.id"
|
||||
:style="{ display: tab.isVisible ? 'block' : 'none' }"
|
||||
:style="{ display: tab.isVisible && !showConsole ? 'block' : 'none' }"
|
||||
class="absolute inset-0"
|
||||
>
|
||||
<iframe
|
||||
@ -48,6 +68,81 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Console View -->
|
||||
<div
|
||||
v-if="showConsole"
|
||||
class="absolute inset-0 bg-base-100 flex flex-col"
|
||||
>
|
||||
<!-- Console Header -->
|
||||
<div
|
||||
class="p-2 border-b border-base-300 flex justify-between items-center"
|
||||
>
|
||||
<h3 class="font-semibold">Console Output</h3>
|
||||
<UButton
|
||||
size="xs"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
@click="$clearConsoleLogs()"
|
||||
>
|
||||
Clear
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- Console Logs -->
|
||||
<div class="flex-1 overflow-y-auto p-2 font-mono text-sm">
|
||||
<!-- Info banner if logs are limited -->
|
||||
<div
|
||||
v-if="consoleLogs.length > maxVisibleLogs"
|
||||
class="mb-2 p-2 bg-warning/10 border border-warning/30 rounded text-xs"
|
||||
>
|
||||
Showing last {{ maxVisibleLogs }} of {{ consoleLogs.length }} logs
|
||||
</div>
|
||||
|
||||
<!-- Simple log list instead of accordion for better performance -->
|
||||
<div
|
||||
v-if="visibleLogs.length > 0"
|
||||
class="space-y-1"
|
||||
>
|
||||
<div
|
||||
v-for="(log, index) in visibleLogs"
|
||||
:key="index"
|
||||
class="border-b border-base-200 pb-2"
|
||||
>
|
||||
<!-- Log header with timestamp and level -->
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<span class="text-xs opacity-60">
|
||||
[{{ log.timestamp }}] [{{ log.level.toUpperCase() }}]
|
||||
</span>
|
||||
<UButton
|
||||
size="xs"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-heroicons-clipboard-document"
|
||||
@click="copyToClipboard(log.message)"
|
||||
/>
|
||||
</div>
|
||||
<!-- Log message -->
|
||||
<pre
|
||||
:class="[
|
||||
'text-xs whitespace-pre-wrap break-all',
|
||||
log.level === 'error' ? 'text-error' : '',
|
||||
log.level === 'warn' ? 'text-warning' : '',
|
||||
log.level === 'info' ? 'text-info' : '',
|
||||
log.level === 'debug' ? 'text-base-content/70' : '',
|
||||
]"
|
||||
>{{ log.message }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="visibleLogs.length === 0"
|
||||
class="text-center text-base-content/50 py-8"
|
||||
>
|
||||
No console messages yet
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div
|
||||
v-if="tabsStore.tabCount === 0"
|
||||
@ -68,7 +163,10 @@ import {
|
||||
import { useExtensionTabsStore } from '~/stores/extensions/tabs'
|
||||
import type { IHaexHubExtension } from '~/types/haexhub'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
import { EXTENSION_PROTOCOL_NAME, EXTENSION_PROTOCOL_PREFIX } from '~/config/constants'
|
||||
import {
|
||||
EXTENSION_PROTOCOL_NAME,
|
||||
EXTENSION_PROTOCOL_PREFIX,
|
||||
} from '~/config/constants'
|
||||
|
||||
definePageMeta({
|
||||
name: 'haexExtension',
|
||||
@ -78,6 +176,23 @@ const { t } = useI18n()
|
||||
|
||||
const tabsStore = useExtensionTabsStore()
|
||||
|
||||
// Console logging - use global logs from plugin
|
||||
const { $consoleLogs, $clearConsoleLogs } = useNuxtApp()
|
||||
const showConsole = ref(false)
|
||||
const maxVisibleLogs = ref(100) // Limit for performance on mobile
|
||||
const consoleLogs = $consoleLogs as Ref<
|
||||
Array<{
|
||||
timestamp: string
|
||||
level: 'log' | 'info' | 'warn' | 'error' | 'debug'
|
||||
message: string
|
||||
}>
|
||||
>
|
||||
|
||||
// Only show last N logs for performance
|
||||
const visibleLogs = computed(() => {
|
||||
return consoleLogs.value.slice(-maxVisibleLogs.value)
|
||||
})
|
||||
|
||||
// Extension aus Route öffnen
|
||||
//const extensionId = computed(() => route.params.extensionId as string)
|
||||
|
||||
@ -94,19 +209,71 @@ const dummyIframeRef = ref<HTMLIFrameElement | null>(null)
|
||||
const dummyExtensionRef = computed(() => null)
|
||||
useExtensionMessageHandler(dummyIframeRef, dummyExtensionRef)
|
||||
|
||||
// Track which iframes have been registered to prevent duplicate registrations
|
||||
const registeredIFrames = new WeakSet<HTMLIFrameElement>()
|
||||
|
||||
const registerIFrame = (extensionId: string, el: HTMLIFrameElement | null) => {
|
||||
if (!el) return
|
||||
|
||||
// Prevent duplicate registration (Vue calls ref functions on every render)
|
||||
if (registeredIFrames.has(el)) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[Vue Debug] ========== registerIFrame called ==========')
|
||||
console.log('[Vue Debug] Extension ID:', extensionId)
|
||||
console.log('[Vue Debug] Element:', 'HTMLIFrameElement')
|
||||
|
||||
// Mark as registered
|
||||
registeredIFrames.add(el)
|
||||
|
||||
// Registriere IFrame im Store
|
||||
tabsStore.registerIFrame(extensionId, el)
|
||||
|
||||
// Registriere IFrame im globalen Message Handler Registry
|
||||
const tab = tabsStore.openTabs.get(extensionId)
|
||||
if (tab?.extension) {
|
||||
console.log('[Vue Debug] Registering iframe in message handler for:', tab.extension.name)
|
||||
registerExtensionIFrame(el, tab.extension)
|
||||
console.log('[Vue Debug] Registration complete!')
|
||||
} else {
|
||||
console.error('[Vue Debug] ❌ No tab found for extension ID:', extensionId)
|
||||
}
|
||||
console.log('[Vue Debug] ========================================')
|
||||
}
|
||||
|
||||
// Listen for console messages from extensions (via postMessage)
|
||||
const handleExtensionConsole = (event: MessageEvent) => {
|
||||
if (event.data?.type === 'console.forward') {
|
||||
const { timestamp, level, message } = event.data.data
|
||||
consoleLogs.value.push({
|
||||
timestamp,
|
||||
level,
|
||||
message: `[Extension] ${message}`,
|
||||
})
|
||||
|
||||
// Limit to last 1000 logs
|
||||
if (consoleLogs.value.length > 1000) {
|
||||
consoleLogs.value = consoleLogs.value.slice(-1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('message', handleExtensionConsole)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('message', handleExtensionConsole)
|
||||
|
||||
// Unregister all iframes when the page unmounts
|
||||
tabsStore.openTabs.forEach((tab) => {
|
||||
if (tab.iframe) {
|
||||
unregisterExtensionIFrame(tab.iframe)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Cleanup wenn Tabs geschlossen werden
|
||||
watch(
|
||||
() => tabsStore.openTabs,
|
||||
@ -184,11 +351,16 @@ watch([currentTheme, locale], () => {
|
||||
})
|
||||
})
|
||||
|
||||
// Cleanup beim Verlassen
|
||||
onBeforeUnmount(() => {
|
||||
// Optional: Alle Tabs schließen oder offen lassen
|
||||
// tabsStore.closeAllTabs()
|
||||
})
|
||||
// Copy to clipboard function
|
||||
const copyToClipboard = async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
// Optional: Show success toast
|
||||
console.log('Copied to clipboard')
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
|
||||
@ -67,27 +67,15 @@
|
||||
v-if="filteredExtensions.length"
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
>
|
||||
<template
|
||||
<!-- Marketplace Extension Card -->
|
||||
<HaexExtensionMarketplaceCard
|
||||
v-for="ext in filteredExtensions"
|
||||
:key="ext.id"
|
||||
>
|
||||
<!-- Installed Extension Card -->
|
||||
<HaexExtensionInstalledCard
|
||||
v-if="ext.isInstalled"
|
||||
:extension="ext"
|
||||
@open="navigateToExtension(ext.id)"
|
||||
@settings="onShowExtensionSettings(ext)"
|
||||
@remove="onShowRemoveDialog(ext)"
|
||||
/>
|
||||
<!-- Marketplace Extension Card -->
|
||||
<HaexExtensionMarketplaceCard
|
||||
v-else
|
||||
:extension="ext"
|
||||
:is-installed="isExtensionInstalled(ext.id)"
|
||||
@install="onInstallFromMarketplace(ext)"
|
||||
@details="onShowExtensionDetails(ext)"
|
||||
/>
|
||||
</template>
|
||||
:extension="ext"
|
||||
:is-installed="ext.isInstalled"
|
||||
@install="onInstallFromMarketplace(ext)"
|
||||
@details="onShowExtensionDetails(ext)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
@ -132,6 +120,7 @@
|
||||
import type {
|
||||
IHaexHubExtension,
|
||||
IHaexHubExtensionManifest,
|
||||
IMarketplaceExtension,
|
||||
} from '~/types/haexhub'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import type { ExtensionPreview } from '~~/src-tauri/bindings/ExtensionPreview'
|
||||
@ -208,7 +197,7 @@ const categories = computed(() => [
|
||||
])
|
||||
|
||||
// Dummy Marketplace Extensions (später von API laden)
|
||||
const marketplaceExtensions = ref([
|
||||
const marketplaceExtensions = ref<IMarketplaceExtension[]>([
|
||||
{
|
||||
id: 'haex-passy',
|
||||
name: 'HaexPassDummy',
|
||||
@ -217,12 +206,14 @@ const marketplaceExtensions = ref([
|
||||
description:
|
||||
'Sicherer Passwort-Manager mit Ende-zu-Ende-Verschlüsselung und Autofill-Funktion.',
|
||||
icon: 'i-heroicons-lock-closed',
|
||||
homepage: null,
|
||||
downloads: 15420,
|
||||
rating: 4.8,
|
||||
verified: true,
|
||||
tags: ['security', 'password', 'productivity'],
|
||||
category: 'security',
|
||||
downloadUrl: '/extensions/haex-pass-1.0.0.haextension',
|
||||
isInstalled: false,
|
||||
},
|
||||
{
|
||||
id: 'haex-notes',
|
||||
@ -232,12 +223,14 @@ const marketplaceExtensions = ref([
|
||||
description:
|
||||
'Markdown-basierter Notizen-Editor mit Syntax-Highlighting und Live-Preview.',
|
||||
icon: 'i-heroicons-document-text',
|
||||
homepage: null,
|
||||
downloads: 8930,
|
||||
rating: 4.5,
|
||||
verified: true,
|
||||
tags: ['productivity', 'notes', 'markdown'],
|
||||
category: 'productivity',
|
||||
downloadUrl: '/extensions/haex-notes-2.1.0.haextension',
|
||||
isInstalled: false,
|
||||
},
|
||||
{
|
||||
id: 'haex-backup',
|
||||
@ -247,12 +240,14 @@ const marketplaceExtensions = ref([
|
||||
description:
|
||||
'Automatische Backups deiner Daten mit Cloud-Sync-Unterstützung.',
|
||||
icon: 'i-heroicons-cloud-arrow-up',
|
||||
homepage: null,
|
||||
downloads: 5240,
|
||||
rating: 4.6,
|
||||
verified: false,
|
||||
tags: ['backup', 'cloud', 'utilities'],
|
||||
category: 'utilities',
|
||||
downloadUrl: '/extensions/haex-backup-1.5.2.haextension',
|
||||
isInstalled: false,
|
||||
},
|
||||
{
|
||||
id: 'haex-calendar',
|
||||
@ -262,12 +257,14 @@ const marketplaceExtensions = ref([
|
||||
description:
|
||||
'Integrierter Kalender mit Event-Management und Synchronisation.',
|
||||
icon: 'i-heroicons-calendar',
|
||||
homepage: null,
|
||||
downloads: 12100,
|
||||
rating: 4.7,
|
||||
verified: true,
|
||||
tags: ['productivity', 'calendar', 'events'],
|
||||
category: 'productivity',
|
||||
downloadUrl: '/extensions/haex-calendar-3.0.1.haextension',
|
||||
isInstalled: false,
|
||||
},
|
||||
{
|
||||
id: 'haex-2fa',
|
||||
@ -277,12 +274,14 @@ const marketplaceExtensions = ref([
|
||||
description:
|
||||
'2-Faktor-Authentifizierung Manager mit TOTP und Backup-Codes.',
|
||||
icon: 'i-heroicons-shield-check',
|
||||
homepage: null,
|
||||
downloads: 7800,
|
||||
rating: 4.9,
|
||||
verified: true,
|
||||
tags: ['security', '2fa', 'authentication'],
|
||||
category: 'security',
|
||||
downloadUrl: '/extensions/haex-2fa-1.2.0.haextension',
|
||||
isInstalled: false,
|
||||
},
|
||||
{
|
||||
id: 'haex-github',
|
||||
@ -292,39 +291,26 @@ const marketplaceExtensions = ref([
|
||||
description:
|
||||
'Direkter Zugriff auf GitHub Repositories, Issues und Pull Requests.',
|
||||
icon: 'i-heroicons-code-bracket',
|
||||
homepage: null,
|
||||
downloads: 4120,
|
||||
rating: 4.3,
|
||||
verified: false,
|
||||
tags: ['integration', 'github', 'development'],
|
||||
category: 'integration',
|
||||
downloadUrl: '/extensions/haex-github-1.0.5.haextension',
|
||||
isInstalled: false,
|
||||
},
|
||||
])
|
||||
|
||||
// Combine installed extensions with marketplace extensions
|
||||
const allExtensions = computed(() => {
|
||||
// Map installed extensions to marketplace format
|
||||
const installed = extensionStore.availableExtensions.map((ext) => ({
|
||||
id: ext.id,
|
||||
name: ext.name,
|
||||
version: ext.version,
|
||||
author: ext.author || 'Unknown',
|
||||
description: 'Installed Extension',
|
||||
icon: ext.icon || 'i-heroicons-puzzle-piece',
|
||||
downloads: 0,
|
||||
rating: 0,
|
||||
verified: false,
|
||||
tags: [],
|
||||
category: 'utilities',
|
||||
downloadUrl: '',
|
||||
isInstalled: true,
|
||||
// Mark marketplace extensions as installed if they exist in availableExtensions
|
||||
const allExtensions = computed((): IMarketplaceExtension[] => {
|
||||
return marketplaceExtensions.value.map((ext) => ({
|
||||
...ext,
|
||||
// Check if this marketplace extension is already installed
|
||||
isInstalled: extensionStore.availableExtensions.some(
|
||||
(installed) => installed.name === ext.name,
|
||||
),
|
||||
}))
|
||||
|
||||
console.log('Installed extensions count:', installed.length)
|
||||
console.log('All extensions:', [...installed, ...marketplaceExtensions.value])
|
||||
|
||||
// Merge with marketplace extensions
|
||||
return [...installed, ...marketplaceExtensions.value]
|
||||
})
|
||||
|
||||
// Filtered Extensions
|
||||
@ -333,7 +319,7 @@ const filteredExtensions = computed(() => {
|
||||
const matchesSearch =
|
||||
!searchQuery.value ||
|
||||
ext.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
ext.description.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
ext.description?.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
|
||||
const matchesCategory =
|
||||
selectedCategory.value === 'all' ||
|
||||
@ -343,14 +329,6 @@ const filteredExtensions = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
// Check if extension is installed
|
||||
const isExtensionInstalled = (extensionId: string) => {
|
||||
return (
|
||||
extensionStore.availableExtensions.some((ext) => ext.id === extensionId) ||
|
||||
allExtensions.value.some((ext) => ext.id === extensionId)
|
||||
)
|
||||
}
|
||||
|
||||
// Install from marketplace
|
||||
const onInstallFromMarketplace = async (ext: unknown) => {
|
||||
console.log('Install from marketplace:', ext)
|
||||
@ -364,35 +342,6 @@ const onShowExtensionDetails = (ext: unknown) => {
|
||||
// TODO: Show extension details modal
|
||||
}
|
||||
|
||||
// Navigate to installed extension
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
const navigateToExtension = (extensionId: string) => {
|
||||
router.push(
|
||||
localePath({
|
||||
name: 'haexExtension',
|
||||
params: {
|
||||
vaultId: route.params.vaultId,
|
||||
extensionId,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// Show extension settings
|
||||
const onShowExtensionSettings = (ext: unknown) => {
|
||||
console.log('Show settings:', ext)
|
||||
// TODO: Show extension settings modal
|
||||
}
|
||||
|
||||
// Show remove dialog
|
||||
const onShowRemoveDialog = (ext: any) => {
|
||||
extensionToBeRemoved.value = ext
|
||||
showRemoveDialog.value = true
|
||||
}
|
||||
|
||||
const onSelectExtensionAsync = async () => {
|
||||
try {
|
||||
extension.path = await open({ directory: false, recursive: true })
|
||||
@ -405,7 +354,7 @@ const onSelectExtensionAsync = async () => {
|
||||
// Check if already installed using full_extension_id
|
||||
const fullExtensionId = `${preview.value.key_hash}_${preview.value.manifest.name}_${preview.value.manifest.version}`
|
||||
const isAlreadyInstalled = extensionStore.availableExtensions.some(
|
||||
ext => ext.id === fullExtensionId
|
||||
(ext) => ext.id === fullExtensionId,
|
||||
)
|
||||
|
||||
if (isAlreadyInstalled) {
|
||||
|
||||
Reference in New Issue
Block a user