mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-17 06:30:50 +01:00
polyfill for spa added. works now on android
This commit is contained in:
@ -1,46 +1,49 @@
|
||||
<template>
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="h-screen w-screen flex flex-col">
|
||||
<!-- Tab Bar -->
|
||||
<div class="flex gap-2 p-2 bg-default overflow-x-auto border-b">
|
||||
<div
|
||||
<div
|
||||
class="flex gap-2 bg-base-200 overflow-x-auto border-b border-base-300 flex-shrink-0"
|
||||
>
|
||||
<UButton
|
||||
v-for="tab in tabsStore.sortedTabs"
|
||||
:key="tab.extension.id"
|
||||
:class="[
|
||||
'btn btn-sm gap-2',
|
||||
tabsStore.activeTabId === tab.extension.id
|
||||
? 'btn-primary'
|
||||
: 'btn-ghost',
|
||||
'gap-2',
|
||||
tabsStore.activeTabId === tab.extension.id ? 'primary' : 'neutral',
|
||||
]"
|
||||
@click="tabsStore.setActiveTab(tab.extension.id)"
|
||||
>
|
||||
{{ tab.extension.name }}
|
||||
<button
|
||||
class="ml-1 hover:text-error"
|
||||
@click.stop="tabsStore.closeTab(tab.extension.id)"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:close"
|
||||
size="16"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<template #trailing>
|
||||
<div
|
||||
class="ml-1 hover:text-error"
|
||||
@click.stop="tabsStore.closeTab(tab.extension.id)"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:close"
|
||||
size="16"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- IFrame Container -->
|
||||
<div class="flex-1 relative overflow-hidden">
|
||||
<div class="flex-1 relative min-h-0">
|
||||
<div
|
||||
v-for="tab in tabsStore.sortedTabs"
|
||||
:key="tab.extension.id"
|
||||
:style="{ display: tab.isVisible ? 'block' : 'none' }"
|
||||
class="w-full h-full"
|
||||
class="absolute inset-0"
|
||||
>
|
||||
<iframe
|
||||
:ref="
|
||||
(el) => registerIFrame(tab.extension.id, el as HTMLIFrameElement)
|
||||
"
|
||||
class="w-full h-full"
|
||||
class="w-full h-full border-0"
|
||||
:src="getExtensionUrl(tab.extension)"
|
||||
sandbox="allow-scripts"
|
||||
sandbox="allow-scripts allow-storage-access-by-user-activation allow-forms"
|
||||
allow="autoplay; speaker-selection; encrypted-media;"
|
||||
/>
|
||||
</div>
|
||||
@ -48,7 +51,7 @@
|
||||
<!-- Loading State -->
|
||||
<div
|
||||
v-if="tabsStore.tabCount === 0"
|
||||
class="flex items-center justify-center h-full"
|
||||
class="absolute inset-0 flex items-center justify-center"
|
||||
>
|
||||
<p>{{ t('loading') }}</p>
|
||||
</div>
|
||||
@ -57,9 +60,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useExtensionMessageHandler } from '~/composables/extensionMessageHandler'
|
||||
import {
|
||||
useExtensionMessageHandler,
|
||||
registerExtensionIFrame,
|
||||
unregisterExtensionIFrame,
|
||||
} from '~/composables/extensionMessageHandler'
|
||||
import { useExtensionTabsStore } from '~/stores/extensions/tabs'
|
||||
import type { IHaexHubExtension } from '~/types/haexhub'
|
||||
import { platform } from '@tauri-apps/plugin-os'
|
||||
|
||||
definePageMeta({
|
||||
name: 'haexExtension',
|
||||
@ -79,43 +87,77 @@ watchEffect(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const messageHandlers = new Map<string, boolean>()
|
||||
|
||||
watch(
|
||||
() => tabsStore.openTabs,
|
||||
(tabs) => {
|
||||
tabs.forEach((tab, id) => {
|
||||
if (tab.iframe && !messageHandlers.has(id)) {
|
||||
const iframeRef = ref(tab.iframe)
|
||||
const extensionRef = computed(() => tab.extension)
|
||||
useExtensionMessageHandler(iframeRef, extensionRef)
|
||||
messageHandlers.set(id, true)
|
||||
}
|
||||
})
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
// IFrame Registrierung und Message Handler Setup
|
||||
/* const iframeRefs = new Map<string, HTMLIFrameElement>()
|
||||
const setupMessageHandlers = new Set<string>() */
|
||||
// Setup global message handler EINMAL im Setup-Kontext
|
||||
// Dies registriert den globalen Event Listener
|
||||
const dummyIframeRef = ref<HTMLIFrameElement | null>(null)
|
||||
const dummyExtensionRef = computed(() => null)
|
||||
useExtensionMessageHandler(dummyIframeRef, dummyExtensionRef)
|
||||
|
||||
const registerIFrame = (extensionId: string, el: HTMLIFrameElement | null) => {
|
||||
if (!el) return
|
||||
|
||||
// Registriere IFrame im Store
|
||||
tabsStore.registerIFrame(extensionId, el)
|
||||
|
||||
// Registriere IFrame im globalen Message Handler Registry
|
||||
const tab = tabsStore.openTabs.get(extensionId)
|
||||
if (tab?.extension) {
|
||||
registerExtensionIFrame(el, tab.extension)
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup wenn Tabs geschlossen werden
|
||||
watch(
|
||||
() => tabsStore.openTabs,
|
||||
(newTabs, oldTabs) => {
|
||||
if (oldTabs) {
|
||||
// Finde gelöschte Tabs
|
||||
oldTabs.forEach((tab, id) => {
|
||||
if (!newTabs.has(id) && tab.iframe) {
|
||||
unregisterExtensionIFrame(tab.iframe)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
const os = await platform()
|
||||
|
||||
// Extension URL generieren
|
||||
const getExtensionUrl = (extension: IHaexHubExtension) => {
|
||||
const info = { id: extension.id, version: extension.version }
|
||||
// Extract key_hash from full_extension_id (everything before first underscore)
|
||||
const firstUnderscoreIndex = extension.id.indexOf('_')
|
||||
if (firstUnderscoreIndex === -1) {
|
||||
console.error('Invalid full_extension_id format:', extension.id)
|
||||
return ''
|
||||
}
|
||||
|
||||
const keyHash = extension.id.substring(0, firstUnderscoreIndex)
|
||||
|
||||
const info = {
|
||||
key_hash: keyHash,
|
||||
name: extension.name,
|
||||
version: extension.version,
|
||||
}
|
||||
|
||||
const jsonString = JSON.stringify(info)
|
||||
const bytes = new TextEncoder().encode(jsonString)
|
||||
const encoded = Array.from(bytes)
|
||||
const encodedInfo = Array.from(bytes)
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
|
||||
const url = `haex-extension://${encoded}/index.html`
|
||||
console.log('Extension URL:', url, 'for', extension.name)
|
||||
return url
|
||||
// 'android', 'ios', 'windows' etc.
|
||||
let schemeUrl: string
|
||||
|
||||
if (os === 'android' || os === 'windows') {
|
||||
// Android/Windows: http://<scheme>.localhost/path
|
||||
schemeUrl = `http://haex-extension.localhost/${encodedInfo}/index.html`
|
||||
} else {
|
||||
// macOS/Linux/iOS: Klassisch scheme://localhost/path
|
||||
schemeUrl = `haex-extension://localhost/${encodedInfo}/index.html`
|
||||
}
|
||||
|
||||
return schemeUrl
|
||||
}
|
||||
|
||||
// Context Changes an alle Tabs broadcasten
|
||||
|
||||
@ -1,48 +1,117 @@
|
||||
<template>
|
||||
<div class="flex flex-col p-4 relative h-full">
|
||||
<!-- <div
|
||||
v-if="extensionStore.availableExtensions.length"
|
||||
class="flex"
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Header with Actions -->
|
||||
<div
|
||||
class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 p-6 border-b border-gray-200 dark:border-gray-800"
|
||||
>
|
||||
<UiButton
|
||||
class="fixed top-20 right-4"
|
||||
@click="onSelectExtensionAsync"
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">
|
||||
{{ t('title') }}
|
||||
</h1>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
{{ t('subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-stretch sm:items-center gap-3"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:plus"
|
||||
size="1.5em"
|
||||
/>
|
||||
</UiButton>
|
||||
|
||||
<HaexExtensionCard
|
||||
v-for="_extension in extensionStore.availableExtensions"
|
||||
v-bind="_extension"
|
||||
:key="_extension.id"
|
||||
@remove="onShowRemoveDialog(_extension)"
|
||||
/>
|
||||
</div> -->
|
||||
|
||||
{{ preview }}
|
||||
<div class="h-full w-full">
|
||||
<div class="fixed top-30 right-10">
|
||||
<UiButton
|
||||
:tooltip="t('extension.add')"
|
||||
@click="onSelectExtensionAsync"
|
||||
square
|
||||
size="xl"
|
||||
<!-- Marketplace Selector -->
|
||||
<USelectMenu
|
||||
v-model="selectedMarketplace"
|
||||
:items="marketplaces"
|
||||
value-key="id"
|
||||
class="w-full sm:w-48"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:plus"
|
||||
size="1.5em"
|
||||
<template #leading>
|
||||
<UIcon name="i-heroicons-building-storefront" />
|
||||
</template>
|
||||
</USelectMenu>
|
||||
|
||||
<!-- Install from File Button -->
|
||||
<UiButton
|
||||
:label="t('extension.installFromFile')"
|
||||
icon="i-heroicons-arrow-up-tray"
|
||||
color="neutral"
|
||||
@click="onSelectExtensionAsync"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filters -->
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-stretch sm:items-center gap-4 p-6 border-b border-gray-200 dark:border-gray-800"
|
||||
>
|
||||
<UInput
|
||||
v-model="searchQuery"
|
||||
:placeholder="t('search.placeholder')"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
class="flex-1"
|
||||
/>
|
||||
<USelectMenu
|
||||
v-model="selectedCategory"
|
||||
:items="categories"
|
||||
:placeholder="t('filter.category')"
|
||||
value-key="id"
|
||||
class="w-full sm:w-48"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon name="i-heroicons-tag" />
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</div>
|
||||
|
||||
<!-- Extensions Grid -->
|
||||
<div class="flex-1 overflow-auto p-6">
|
||||
<div
|
||||
v-if="filteredExtensions.length"
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
>
|
||||
<template
|
||||
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)"
|
||||
/>
|
||||
</UiButton>
|
||||
<!-- Marketplace Extension Card -->
|
||||
<HaexExtensionMarketplaceCard
|
||||
v-else
|
||||
:extension="ext"
|
||||
:is-installed="isExtensionInstalled(ext.id)"
|
||||
@install="onInstallFromMarketplace(ext)"
|
||||
@details="onShowExtensionDetails(ext)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col items-center justify-center h-full text-center"
|
||||
>
|
||||
<UIcon
|
||||
name="i-heroicons-magnifying-glass"
|
||||
class="w-16 h-16 text-gray-400 mb-4"
|
||||
/>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('empty.title') }}
|
||||
</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mt-2">
|
||||
{{ t('empty.description') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<HaexExtensionDialogReinstall
|
||||
v-model:open="openOverwriteDialog"
|
||||
:manifest="extension.manifest"
|
||||
@confirm="addExtensionAsync"
|
||||
v-model:preview="preview"
|
||||
@confirm="reinstallExtensionAsync"
|
||||
/>
|
||||
|
||||
<HaexExtensionDialogInstall
|
||||
@ -110,6 +179,220 @@ const { addNotificationAsync } = useNotificationStore()
|
||||
|
||||
const preview = ref<ExtensionPreview>()
|
||||
|
||||
// Marketplace State
|
||||
const selectedMarketplace = ref('official')
|
||||
const searchQuery = ref('')
|
||||
const selectedCategory = ref('all')
|
||||
|
||||
// Marketplaces (später von API laden)
|
||||
const marketplaces = [
|
||||
{
|
||||
id: 'official',
|
||||
label: t('marketplace.official'),
|
||||
icon: 'i-heroicons-building-storefront',
|
||||
},
|
||||
{
|
||||
id: 'community',
|
||||
label: t('marketplace.community'),
|
||||
icon: 'i-heroicons-users',
|
||||
},
|
||||
]
|
||||
|
||||
// Categories
|
||||
const categories = computed(() => [
|
||||
{ id: 'all', label: t('category.all') },
|
||||
{ id: 'productivity', label: t('category.productivity') },
|
||||
{ id: 'security', label: t('category.security') },
|
||||
{ id: 'utilities', label: t('category.utilities') },
|
||||
{ id: 'integration', label: t('category.integration') },
|
||||
])
|
||||
|
||||
// Dummy Marketplace Extensions (später von API laden)
|
||||
const marketplaceExtensions = ref([
|
||||
{
|
||||
id: 'haex-passy',
|
||||
name: 'HaexPassDummy',
|
||||
version: '1.0.0',
|
||||
author: 'HaexHub Team',
|
||||
description:
|
||||
'Sicherer Passwort-Manager mit Ende-zu-Ende-Verschlüsselung und Autofill-Funktion.',
|
||||
icon: 'i-heroicons-lock-closed',
|
||||
downloads: 15420,
|
||||
rating: 4.8,
|
||||
verified: true,
|
||||
tags: ['security', 'password', 'productivity'],
|
||||
category: 'security',
|
||||
downloadUrl: '/extensions/haex-pass-1.0.0.haextension',
|
||||
},
|
||||
{
|
||||
id: 'haex-notes',
|
||||
name: 'HaexNotes',
|
||||
version: '2.1.0',
|
||||
author: 'HaexHub Team',
|
||||
description:
|
||||
'Markdown-basierter Notizen-Editor mit Syntax-Highlighting und Live-Preview.',
|
||||
icon: 'i-heroicons-document-text',
|
||||
downloads: 8930,
|
||||
rating: 4.5,
|
||||
verified: true,
|
||||
tags: ['productivity', 'notes', 'markdown'],
|
||||
category: 'productivity',
|
||||
downloadUrl: '/extensions/haex-notes-2.1.0.haextension',
|
||||
},
|
||||
{
|
||||
id: 'haex-backup',
|
||||
name: 'HaexBackup',
|
||||
version: '1.5.2',
|
||||
author: 'Community',
|
||||
description:
|
||||
'Automatische Backups deiner Daten mit Cloud-Sync-Unterstützung.',
|
||||
icon: 'i-heroicons-cloud-arrow-up',
|
||||
downloads: 5240,
|
||||
rating: 4.6,
|
||||
verified: false,
|
||||
tags: ['backup', 'cloud', 'utilities'],
|
||||
category: 'utilities',
|
||||
downloadUrl: '/extensions/haex-backup-1.5.2.haextension',
|
||||
},
|
||||
{
|
||||
id: 'haex-calendar',
|
||||
name: 'HaexCalendar',
|
||||
version: '3.0.1',
|
||||
author: 'HaexHub Team',
|
||||
description:
|
||||
'Integrierter Kalender mit Event-Management und Synchronisation.',
|
||||
icon: 'i-heroicons-calendar',
|
||||
downloads: 12100,
|
||||
rating: 4.7,
|
||||
verified: true,
|
||||
tags: ['productivity', 'calendar', 'events'],
|
||||
category: 'productivity',
|
||||
downloadUrl: '/extensions/haex-calendar-3.0.1.haextension',
|
||||
},
|
||||
{
|
||||
id: 'haex-2fa',
|
||||
name: 'Haex2FA',
|
||||
version: '1.2.0',
|
||||
author: 'Security Team',
|
||||
description:
|
||||
'2-Faktor-Authentifizierung Manager mit TOTP und Backup-Codes.',
|
||||
icon: 'i-heroicons-shield-check',
|
||||
downloads: 7800,
|
||||
rating: 4.9,
|
||||
verified: true,
|
||||
tags: ['security', '2fa', 'authentication'],
|
||||
category: 'security',
|
||||
downloadUrl: '/extensions/haex-2fa-1.2.0.haextension',
|
||||
},
|
||||
{
|
||||
id: 'haex-github',
|
||||
name: 'GitHub Integration',
|
||||
version: '1.0.5',
|
||||
author: 'Community',
|
||||
description:
|
||||
'Direkter Zugriff auf GitHub Repositories, Issues und Pull Requests.',
|
||||
icon: 'i-heroicons-code-bracket',
|
||||
downloads: 4120,
|
||||
rating: 4.3,
|
||||
verified: false,
|
||||
tags: ['integration', 'github', 'development'],
|
||||
category: 'integration',
|
||||
downloadUrl: '/extensions/haex-github-1.0.5.haextension',
|
||||
},
|
||||
])
|
||||
|
||||
// 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,
|
||||
}))
|
||||
|
||||
console.log('Installed extensions count:', installed.length)
|
||||
console.log('All extensions:', [...installed, ...marketplaceExtensions.value])
|
||||
|
||||
// Merge with marketplace extensions
|
||||
return [...installed, ...marketplaceExtensions.value]
|
||||
})
|
||||
|
||||
// Filtered Extensions
|
||||
const filteredExtensions = computed(() => {
|
||||
return allExtensions.value.filter((ext) => {
|
||||
const matchesSearch =
|
||||
!searchQuery.value ||
|
||||
ext.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
ext.description.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
|
||||
const matchesCategory =
|
||||
selectedCategory.value === 'all' ||
|
||||
ext.category === selectedCategory.value
|
||||
|
||||
return matchesSearch && matchesCategory
|
||||
})
|
||||
})
|
||||
|
||||
// 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)
|
||||
// TODO: Download extension from marketplace and install
|
||||
add({ color: 'info', description: t('extension.marketplace.comingSoon') })
|
||||
}
|
||||
|
||||
// Show extension details
|
||||
const onShowExtensionDetails = (ext: unknown) => {
|
||||
console.log('Show details:', ext)
|
||||
// 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 })
|
||||
@ -119,11 +402,11 @@ const onSelectExtensionAsync = async () => {
|
||||
|
||||
if (!preview.value) return
|
||||
|
||||
// Check if already installed
|
||||
const isAlreadyInstalled = await extensionStore.isExtensionInstalledAsync({
|
||||
id: preview.value.manifest.id,
|
||||
version: preview.value.manifest.version,
|
||||
})
|
||||
// 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
|
||||
)
|
||||
|
||||
if (isAlreadyInstalled) {
|
||||
openOverwriteDialog.value = true
|
||||
@ -138,7 +421,14 @@ const onSelectExtensionAsync = async () => {
|
||||
|
||||
const addExtensionAsync = async () => {
|
||||
try {
|
||||
await extensionStore.installAsync(extension.path)
|
||||
console.log(
|
||||
'preview.value?.editable_permissions',
|
||||
preview.value?.editable_permissions,
|
||||
)
|
||||
await extensionStore.installAsync(
|
||||
extension.path,
|
||||
preview.value?.editable_permissions,
|
||||
)
|
||||
await extensionStore.loadExtensionsAsync()
|
||||
|
||||
add({
|
||||
@ -162,16 +452,46 @@ const addExtensionAsync = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const showRemoveDialog = ref(false)
|
||||
const extensionToBeRemoved = ref<IHaexHubExtension>()
|
||||
const reinstallExtensionAsync = async () => {
|
||||
try {
|
||||
if (!preview.value) return
|
||||
|
||||
const onShowRemoveDialog = (extension: IHaexHubExtension) => {
|
||||
extensionToBeRemoved.value = extension
|
||||
showRemoveDialog.value = true
|
||||
// Calculate full_extension_id
|
||||
const fullExtensionId = `${preview.value.key_hash}_${preview.value.manifest.name}_${preview.value.manifest.version}`
|
||||
|
||||
// Remove old extension first
|
||||
await extensionStore.removeExtensionByFullIdAsync(fullExtensionId)
|
||||
|
||||
// Then install new version
|
||||
await addExtensionAsync()
|
||||
} catch (error) {
|
||||
console.error('Fehler reinstallExtensionAsync:', error)
|
||||
add({ color: 'error', description: JSON.stringify(error) })
|
||||
await addNotificationAsync({ text: JSON.stringify(error), type: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
const extensionToBeRemoved = ref<IHaexHubExtension>()
|
||||
const showRemoveDialog = ref(false)
|
||||
|
||||
// Load extensions on mount
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await extensionStore.loadExtensionsAsync()
|
||||
console.log('Loaded extensions:', extensionStore.availableExtensions)
|
||||
} catch (error) {
|
||||
console.error('Failed to load extensions:', error)
|
||||
add({ color: 'error', description: 'Failed to load installed extensions' })
|
||||
}
|
||||
})
|
||||
|
||||
/* const onShowRemoveDialog = (extension: IHaexHubExtension) => {
|
||||
extensionToBeRemoved.value = extension
|
||||
showRemoveDialog.value = true
|
||||
} */
|
||||
|
||||
const removeExtensionAsync = async () => {
|
||||
if (!extensionToBeRemoved.value?.id || !extensionToBeRemoved.value?.version) {
|
||||
if (!extensionToBeRemoved.value?.id) {
|
||||
add({
|
||||
color: 'error',
|
||||
description: 'Erweiterung kann nicht gelöscht werden',
|
||||
@ -180,9 +500,9 @@ const removeExtensionAsync = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
await extensionStore.removeExtensionAsync(
|
||||
// Use removeExtensionByFullIdAsync since ext.id is already the full_extension_id
|
||||
await extensionStore.removeExtensionByFullIdAsync(
|
||||
extensionToBeRemoved.value.id,
|
||||
extensionToBeRemoved.value.version,
|
||||
)
|
||||
await extensionStore.loadExtensionsAsync()
|
||||
add({
|
||||
@ -222,8 +542,14 @@ const removeExtensionAsync = async () => {
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
title: 'Erweiterung installieren'
|
||||
title: Erweiterungen
|
||||
subtitle: Entdecke und installiere Erweiterungen für HaexHub
|
||||
extension:
|
||||
installFromFile: Von Datei installieren
|
||||
add: Erweiterung hinzufügen
|
||||
success:
|
||||
title: '{extension} hinzugefügt'
|
||||
text: Die Erweiterung wurde erfolgreich hinzugefügt
|
||||
remove:
|
||||
success:
|
||||
text: 'Erweiterung {extensionName} wurde erfolgreich entfernt'
|
||||
@ -231,14 +557,34 @@ de:
|
||||
error:
|
||||
text: "Erweiterung {extensionName} konnte nicht entfernt werden. \n {error}"
|
||||
title: 'Fehler beim Entfernen von {extensionName}'
|
||||
marketplace:
|
||||
comingSoon: Marketplace-Installation kommt bald!
|
||||
marketplace:
|
||||
official: Offizieller Marketplace
|
||||
community: Community Marketplace
|
||||
category:
|
||||
all: Alle
|
||||
productivity: Produktivität
|
||||
security: Sicherheit
|
||||
utilities: Werkzeuge
|
||||
integration: Integration
|
||||
search:
|
||||
placeholder: Erweiterungen durchsuchen...
|
||||
filter:
|
||||
category: Kategorie auswählen
|
||||
empty:
|
||||
title: Keine Erweiterungen gefunden
|
||||
description: Versuche einen anderen Suchbegriff oder eine andere Kategorie
|
||||
|
||||
add: 'Erweiterung hinzufügen'
|
||||
success:
|
||||
title: '{extension} hinzugefügt'
|
||||
text: 'Die Erweiterung wurde erfolgreich hinzugefügt'
|
||||
en:
|
||||
title: 'Install extension'
|
||||
title: Extensions
|
||||
subtitle: Discover and install extensions for HaexHub
|
||||
extension:
|
||||
installFromFile: Install from file
|
||||
add: Add Extension
|
||||
success:
|
||||
title: '{extension} added'
|
||||
text: Extension was added successfully
|
||||
remove:
|
||||
success:
|
||||
text: 'Extension {extensionName} was removed'
|
||||
@ -246,9 +592,22 @@ en:
|
||||
error:
|
||||
text: "Extension {extensionName} couldn't be removed. \n {error}"
|
||||
title: 'Exception during uninstall {extensionName}'
|
||||
|
||||
add: 'Add Extension'
|
||||
success:
|
||||
title: '{extension} added'
|
||||
text: 'Extensions was added successfully'
|
||||
marketplace:
|
||||
comingSoon: Marketplace installation coming soon!
|
||||
marketplace:
|
||||
official: Official Marketplace
|
||||
community: Community Marketplace
|
||||
category:
|
||||
all: All
|
||||
productivity: Productivity
|
||||
security: Security
|
||||
utilities: Utilities
|
||||
integration: Integration
|
||||
search:
|
||||
placeholder: Search extensions...
|
||||
filter:
|
||||
category: Select category
|
||||
empty:
|
||||
title: No extensions found
|
||||
description: Try a different search term or category
|
||||
</i18n>
|
||||
|
||||
Reference in New Issue
Block a user