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:
@ -137,33 +137,46 @@ import type { ExtensionPreview } from '~~/src-tauri/bindings/ExtensionPreview'
|
||||
const { t } = useI18n()
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
const props = defineProps<{
|
||||
preview?: ExtensionPreview | null
|
||||
}>()
|
||||
const preview = defineModel<ExtensionPreview | null>('preview', {
|
||||
default: null,
|
||||
})
|
||||
|
||||
const databasePermissions = ref(
|
||||
props.preview?.editable_permissions?.database || [],
|
||||
)
|
||||
const filesystemPermissions = ref(
|
||||
props.preview?.editable_permissions?.filesystem || [],
|
||||
)
|
||||
const httpPermissions = ref(props.preview?.editable_permissions?.http || [])
|
||||
const shellPermissions = ref(props.preview?.editable_permissions?.shell || [])
|
||||
|
||||
// Watch for preview changes
|
||||
watch(
|
||||
() => props.preview,
|
||||
(newPreview) => {
|
||||
if (newPreview?.editable_permissions) {
|
||||
databasePermissions.value = newPreview.editable_permissions.database || []
|
||||
filesystemPermissions.value =
|
||||
newPreview.editable_permissions.filesystem || []
|
||||
httpPermissions.value = newPreview.editable_permissions.http || []
|
||||
shellPermissions.value = newPreview.editable_permissions.shell || []
|
||||
const databasePermissions = computed({
|
||||
get: () => preview.value?.editable_permissions?.database || [],
|
||||
set: (value) => {
|
||||
if (preview.value?.editable_permissions) {
|
||||
preview.value.editable_permissions.database = value
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
})
|
||||
|
||||
const filesystemPermissions = computed({
|
||||
get: () => preview.value?.editable_permissions?.filesystem || [],
|
||||
set: (value) => {
|
||||
if (preview.value?.editable_permissions) {
|
||||
preview.value.editable_permissions.filesystem = value
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const httpPermissions = computed({
|
||||
get: () => preview.value?.editable_permissions?.http || [],
|
||||
set: (value) => {
|
||||
if (preview.value?.editable_permissions) {
|
||||
preview.value.editable_permissions.http = value
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const shellPermissions = computed({
|
||||
get: () => preview.value?.editable_permissions?.shell || [],
|
||||
set: (value) => {
|
||||
if (preview.value?.editable_permissions) {
|
||||
preview.value.editable_permissions.shell = value
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
const permissionAccordionItems = computed(() => {
|
||||
const items = []
|
||||
@ -213,12 +226,7 @@ const onDeny = () => {
|
||||
|
||||
const onConfirm = () => {
|
||||
open.value = false
|
||||
emit('confirm', {
|
||||
database: databasePermissions.value,
|
||||
filesystem: filesystemPermissions.value,
|
||||
http: httpPermissions.value,
|
||||
shell: shellPermissions.value,
|
||||
})
|
||||
emit('confirm')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,33 +1,87 @@
|
||||
<template>
|
||||
<UiDialogConfirm v-model:open="open">
|
||||
<UiDialogConfirm
|
||||
v-model:open="open"
|
||||
@abort="onDeny"
|
||||
@confirm="onConfirm"
|
||||
>
|
||||
<template #title>
|
||||
<i18n-t keypath="title" tag="p">
|
||||
<template #extensionName>
|
||||
<span class="font-bold text-primary">{{ manifest?.name }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
{{ t('title', { extensionName: preview?.manifest.name }) }}
|
||||
</template>
|
||||
|
||||
<p>{{ t("question", { extensionName: manifest?.name }) }}</p>
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-4">
|
||||
<p>{{ t('question', { extensionName: preview?.manifest.name }) }}</p>
|
||||
|
||||
<UAlert
|
||||
color="warning"
|
||||
variant="soft"
|
||||
:title="t('warning.title')"
|
||||
:description="t('warning.description')"
|
||||
icon="i-heroicons-exclamation-triangle"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="preview"
|
||||
class="bg-gray-100 dark:bg-gray-800 rounded-lg p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<UIcon
|
||||
v-if="preview.manifest.icon"
|
||||
:name="preview.manifest.icon"
|
||||
class="w-12 h-12"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<h4 class="font-semibold">
|
||||
{{ preview.manifest.name }}
|
||||
</h4>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('version') }}: {{ preview.manifest.version }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UiDialogConfirm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IHaexHubExtensionManifest } from "~/types/haexhub";
|
||||
import type { ExtensionPreview } from '~~/src-tauri/bindings/ExtensionPreview'
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n()
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
const { manifest } = defineProps<{ manifest?: IHaexHubExtensionManifest | null }>();
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
const preview = defineModel<ExtensionPreview | null>('preview', {
|
||||
default: null,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['deny', 'confirm'])
|
||||
|
||||
const onDeny = () => {
|
||||
open.value = false
|
||||
emit('deny')
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
open.value = false
|
||||
emit('confirm')
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
title: "{extensionName} bereits installiert"
|
||||
question: Soll die Erweiterung {extensionName} erneut installiert werden?
|
||||
de:
|
||||
title: '{extensionName} bereits installiert'
|
||||
question: Soll die Erweiterung {extensionName} erneut installiert werden?
|
||||
warning:
|
||||
title: Achtung
|
||||
description: Die vorhandene Version wird vollständig entfernt und durch die neue Version ersetzt. Dieser Vorgang kann nicht rückgängig gemacht werden.
|
||||
version: Version
|
||||
|
||||
en:
|
||||
title: "{extensionName} is already installed"
|
||||
question: Do you want to reinstall {extensionName}?
|
||||
en:
|
||||
title: '{extensionName} is already installed'
|
||||
question: Do you want to reinstall {extensionName}?
|
||||
warning:
|
||||
title: Warning
|
||||
description: The existing version will be completely removed and replaced with the new version. This action cannot be undone.
|
||||
version: Version
|
||||
</i18n>
|
||||
@ -1,43 +1,106 @@
|
||||
<template>
|
||||
<UiDialogConfirm v-model:open="open" :title="t('title')" @confirm="onConfirm">
|
||||
<div>
|
||||
<i18n-t keypath="question" tag="p">
|
||||
<template #name>
|
||||
<span class="font-bold text-primary">{{ extension?.name }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<UiDialogConfirm
|
||||
v-model:open="open"
|
||||
@abort="onAbort"
|
||||
@confirm="onConfirm"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('title') }}
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-4">
|
||||
<i18n-t
|
||||
keypath="question"
|
||||
tag="p"
|
||||
>
|
||||
<template #name>
|
||||
<span class="font-bold text-primary">{{ extension?.name }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
|
||||
<UAlert
|
||||
color="error"
|
||||
variant="soft"
|
||||
:title="t('warning.title')"
|
||||
:description="t('warning.description')"
|
||||
icon="i-heroicons-exclamation-triangle"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="extension"
|
||||
class="bg-gray-100 dark:bg-gray-800 rounded-lg p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<UIcon
|
||||
v-if="extension.icon"
|
||||
:name="extension.icon"
|
||||
class="w-12 h-12"
|
||||
/>
|
||||
<UIcon
|
||||
v-else
|
||||
name="i-heroicons-puzzle-piece"
|
||||
class="w-12 h-12 text-gray-400"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<h4 class="font-semibold">
|
||||
{{ extension.name }}
|
||||
</h4>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('version') }}: {{ extension.version }}
|
||||
</p>
|
||||
<p
|
||||
v-if="extension.author"
|
||||
class="text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
{{ t('author') }}: {{ extension.author }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UiDialogConfirm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IHaexHubExtension } from "~/types/haexhub";
|
||||
import type { IHaexHubExtension } from '~/types/haexhub'
|
||||
|
||||
const emit = defineEmits(["confirm"]);
|
||||
const emit = defineEmits(['confirm', 'abort'])
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n()
|
||||
|
||||
defineProps<{ extension?: IHaexHubExtension }>();
|
||||
defineProps<{ extension?: IHaexHubExtension }>()
|
||||
|
||||
const open = defineModel<boolean>("open");
|
||||
const open = defineModel<boolean>('open')
|
||||
|
||||
const onAbort = () => {
|
||||
open.value = false
|
||||
emit('abort')
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
open.value = false;
|
||||
emit("confirm");
|
||||
};
|
||||
open.value = false
|
||||
emit('confirm')
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="json">{
|
||||
"de": {
|
||||
"title": "Erweiterung löschen",
|
||||
"question": "Soll {name} wirklich gelöscht werden?",
|
||||
"abort": "Abbrechen",
|
||||
"remove": "Löschen"
|
||||
},
|
||||
"en": {
|
||||
"title": "Remove Extension",
|
||||
"question": "Should {name} really be deleted?",
|
||||
"abort": "Abort",
|
||||
"remove": "Remove"
|
||||
}
|
||||
}</i18n>
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
title: Erweiterung entfernen
|
||||
question: Möchtest du {name} wirklich entfernen?
|
||||
warning:
|
||||
title: Achtung
|
||||
description: Diese Aktion kann nicht rückgängig gemacht werden. Alle Daten der Erweiterung werden dauerhaft gelöscht.
|
||||
version: Version
|
||||
author: Autor
|
||||
|
||||
en:
|
||||
title: Remove Extension
|
||||
question: Do you really want to remove {name}?
|
||||
warning:
|
||||
title: Warning
|
||||
description: This action cannot be undone. All extension data will be permanently deleted.
|
||||
version: Version
|
||||
author: Author
|
||||
</i18n>
|
||||
|
||||
157
src/components/haex/extension/installed-card.vue
Normal file
157
src/components/haex/extension/installed-card.vue
Normal file
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<UCard
|
||||
:ui="{
|
||||
root: 'hover:shadow-lg transition-shadow duration-200 cursor-pointer',
|
||||
body: 'flex flex-col gap-3',
|
||||
}"
|
||||
@click="$emit('open')"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<!-- Icon -->
|
||||
<div class="flex-shrink-0">
|
||||
<div
|
||||
v-if="extension.icon"
|
||||
class="w-16 h-16 rounded-lg bg-primary/10 flex items-center justify-center"
|
||||
>
|
||||
<UIcon
|
||||
:name="extension.icon"
|
||||
class="w-10 h-10 text-primary"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="w-16 h-16 rounded-lg bg-gray-200 dark:bg-gray-700 flex items-center justify-center"
|
||||
>
|
||||
<UIcon
|
||||
name="i-heroicons-puzzle-piece"
|
||||
class="w-10 h-10 text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-semibold truncate">
|
||||
{{ extension.name }}
|
||||
</h3>
|
||||
<p
|
||||
v-if="extension.author"
|
||||
class="text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
{{ t('by') }} {{ extension.author }}
|
||||
</p>
|
||||
</div>
|
||||
<UBadge
|
||||
:label="extension.version"
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="extension.description"
|
||||
class="text-sm text-gray-600 dark:text-gray-300 mt-2 line-clamp-2"
|
||||
>
|
||||
{{ extension.description }}
|
||||
</p>
|
||||
|
||||
<!-- Installed Badge -->
|
||||
<div class="flex items-center gap-2 mt-3">
|
||||
<UBadge
|
||||
:label="t('installed')"
|
||||
color="success"
|
||||
variant="subtle"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon name="i-heroicons-check-circle" />
|
||||
</template>
|
||||
</UBadge>
|
||||
<UBadge
|
||||
v-if="extension.enabled"
|
||||
:label="t('enabled')"
|
||||
color="primary"
|
||||
variant="soft"
|
||||
/>
|
||||
<UBadge
|
||||
v-else
|
||||
:label="t('disabled')"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<UButton
|
||||
:label="t('open')"
|
||||
color="primary"
|
||||
icon="i-heroicons-arrow-right"
|
||||
size="sm"
|
||||
@click.stop="$emit('open')"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<UButton
|
||||
:label="t('settings')"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-heroicons-cog-6-tooth"
|
||||
size="sm"
|
||||
@click.stop="$emit('settings')"
|
||||
/>
|
||||
<UButton
|
||||
:label="t('remove')"
|
||||
color="error"
|
||||
variant="ghost"
|
||||
icon="i-heroicons-trash"
|
||||
size="sm"
|
||||
@click.stop="$emit('remove')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface InstalledExtension {
|
||||
id: string
|
||||
name: string
|
||||
version: string
|
||||
author?: string
|
||||
description?: string
|
||||
icon?: string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
extension: InstalledExtension
|
||||
}>()
|
||||
|
||||
defineEmits(['open', 'settings', 'remove'])
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
by: von
|
||||
installed: Installiert
|
||||
enabled: Aktiviert
|
||||
disabled: Deaktiviert
|
||||
open: Öffnen
|
||||
settings: Einstellungen
|
||||
remove: Entfernen
|
||||
en:
|
||||
by: by
|
||||
installed: Installed
|
||||
enabled: Enabled
|
||||
disabled: Disabled
|
||||
open: Open
|
||||
settings: Settings
|
||||
remove: Remove
|
||||
</i18n>
|
||||
173
src/components/haex/extension/marketplace-card.vue
Normal file
173
src/components/haex/extension/marketplace-card.vue
Normal file
@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<UCard
|
||||
:ui="{
|
||||
root: 'hover:shadow-lg transition-shadow duration-200 cursor-pointer',
|
||||
body: 'flex flex-col gap-3',
|
||||
}"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<!-- Icon -->
|
||||
<div class="flex-shrink-0">
|
||||
<div
|
||||
v-if="extension.icon"
|
||||
class="w-16 h-16 rounded-lg bg-primary/10 flex items-center justify-center"
|
||||
>
|
||||
<UIcon
|
||||
:name="extension.icon"
|
||||
class="w-10 h-10 text-primary"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="w-16 h-16 rounded-lg bg-gray-200 dark:bg-gray-700 flex items-center justify-center"
|
||||
>
|
||||
<UIcon
|
||||
name="i-heroicons-puzzle-piece"
|
||||
class="w-10 h-10 text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-semibold truncate">
|
||||
{{ extension.name }}
|
||||
</h3>
|
||||
<p
|
||||
v-if="extension.author"
|
||||
class="text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
{{ t('by') }} {{ extension.author }}
|
||||
</p>
|
||||
</div>
|
||||
<UBadge
|
||||
:label="extension.version"
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="extension.description"
|
||||
class="text-sm text-gray-600 dark:text-gray-300 mt-2 line-clamp-2"
|
||||
>
|
||||
{{ extension.description }}
|
||||
</p>
|
||||
|
||||
<!-- Stats -->
|
||||
<div
|
||||
class="flex items-center gap-4 mt-3 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<div
|
||||
v-if="extension.downloads"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<UIcon name="i-heroicons-arrow-down-tray" />
|
||||
<span>{{ formatNumber(extension.downloads) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="extension.rating"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<UIcon name="i-heroicons-star-solid" />
|
||||
<span>{{ extension.rating }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="extension.verified"
|
||||
class="flex items-center gap-1 text-green-600 dark:text-green-400"
|
||||
>
|
||||
<UIcon name="i-heroicons-check-badge-solid" />
|
||||
<span>{{ t('verified') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
<div
|
||||
v-if="extension.tags?.length"
|
||||
class="flex flex-wrap gap-1 mt-2"
|
||||
>
|
||||
<UBadge
|
||||
v-for="tag in extension.tags.slice(0, 3)"
|
||||
:key="tag"
|
||||
:label="tag"
|
||||
size="xs"
|
||||
color="primary"
|
||||
variant="soft"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<UButton
|
||||
:label="isInstalled ? t('installed') : t('install')"
|
||||
:color="isInstalled ? 'neutral' : 'primary'"
|
||||
:disabled="isInstalled"
|
||||
:icon="
|
||||
isInstalled ? 'i-heroicons-check' : 'i-heroicons-arrow-down-tray'
|
||||
"
|
||||
size="sm"
|
||||
@click.stop="$emit('install')"
|
||||
/>
|
||||
<UButton
|
||||
:label="t('details')"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click.stop="$emit('details')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface MarketplaceExtension {
|
||||
id: string
|
||||
name: string
|
||||
version: string
|
||||
author?: string
|
||||
description?: string
|
||||
icon?: string
|
||||
downloads?: number
|
||||
rating?: number
|
||||
verified?: boolean
|
||||
tags?: string[]
|
||||
downloadUrl?: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
extension: MarketplaceExtension
|
||||
isInstalled?: boolean
|
||||
}>()
|
||||
|
||||
defineEmits(['click', 'install', 'details'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const formatNumber = (num: number) => {
|
||||
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`
|
||||
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`
|
||||
return num.toString()
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
de:
|
||||
by: von
|
||||
install: Installieren
|
||||
installed: Installiert
|
||||
details: Details
|
||||
verified: Verifiziert
|
||||
en:
|
||||
by: by
|
||||
install: Install
|
||||
installed: Installed
|
||||
details: Details
|
||||
verified: Verified
|
||||
</i18n>
|
||||
@ -20,8 +20,8 @@
|
||||
<USelectMenu
|
||||
v-model="menuEntry"
|
||||
:items="statusOptions"
|
||||
value-attribute="value"
|
||||
class="w-44"
|
||||
:search-input="false"
|
||||
>
|
||||
<template #leading>
|
||||
<UIcon
|
||||
Reference in New Issue
Block a user