[comp] add AppCatalogDetails modal to display app info before install

This commit is contained in:
axolotle 2022-11-08 14:57:24 +01:00
parent 581a919044
commit daf8e58ce0
3 changed files with 217 additions and 4 deletions

View file

@ -54,6 +54,43 @@
"api_not_found": "Seems like the web-admin tried to query something that doesn't exist.",
"api_not_responding": "The YunoHost API is not responding. Maybe 'yunohost-api' is down or got restarted?",
"api_waiting": "Waiting for the server's response...",
"app": {
"antifeatures": "Antifeatures:",
"preview": {
"before_install": "Things to know before install",
"integration": {
"archs": "Supported architectures:",
"ldap": {
"false": "Does not use YunoHost accounts to login (LDAP)",
"null": "No information about LDAP integration",
"true": "Use YunoHost accounts to login (LDAP)"
},
"multi_instance": {
"false": "Can be installed only once",
"true": "Can be installed several times"
},
"resources": "Typical resource usage: {ram} RAM, {disk} disk",
"sso": {
"false": "Single sign-on is not available (SSO)",
"null": "No information about SSO integration",
"true": "Single sign-on is available (SSO)"
},
"title": "YunoHost integration"
},
"links": {
"admindoc": "Official documentation",
"code": "Upstream code repository",
"forum": "Topics about this app on YunoHost's forum",
"package": "YunoHost package repository",
"title": "Links",
"website": "Website"
},
"title": "App details",
"try_demo": "Try the demo",
"version": "Current version: {version}"
},
"potential_alternative_to": "Potential alternative to:"
},
"app_choose_category": "Choose a category",
"app_config_panel": "Config panel",
"app_config_panel_label": "Configure this app",

View file

@ -106,10 +106,15 @@
</b-card>
</card-deck-feed>
<b-modal
<app-catalog-details
v-if="selectedApp"
id="modal-app-info"
:app-id="selectedApp"
:antifeatures="antifeatures"
@ok="onInstallClick(selectedApp)"
@hide="selectedApp = undefined"
/>
<template #bot>
<!-- INSTALL CUSTOM APP -->
<card-form
@ -154,6 +159,7 @@
import { validationMixin } from 'vuelidate'
import CardDeckFeed from '@/components/CardDeckFeed'
import AppCatalogDetails from './AppCatalogDetails'
import { required, appRepoUrl } from '@/helpers/validators'
import { randint } from '@/helpers/commons'
@ -161,18 +167,20 @@ export default {
name: 'AppCatalog',
components: {
CardDeckFeed
CardDeckFeed,
AppCatalogDetails
},
data () {
return {
queries: [
['GET', 'apps/catalog?full&with_categories']
['GET', 'apps/catalog?full&with_categories&with_antifeatures']
],
// Data
apps: undefined,
selectedApp: undefined,
antifeatures: undefined,
// Filtering options
qualityOptions: [
@ -291,6 +299,7 @@ export default {
data.categories.forEach(({ title, id, icon, subtags, description }) => {
this.categories.push({ text: title, value: id, icon, subtags, description })
})
this.antifeatures = Object.fromEntries(data.antifeatures.map((af) => ([af.id, af])))
},
setCategory () {
@ -301,7 +310,8 @@ export default {
},
// INSTALL APP
async onInstallClick (app) {
async onInstallClick (appId) {
const app = this.apps.find((app) => app.id === appId)
if (!app.decent_quality) {
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_install_app_' + app.state))
if (!confirmed) return

View file

@ -0,0 +1,166 @@
<template>
<b-modal
v-bind="$attrs" v-on="$listeners"
body-class="p-0"
static lazy size="lg"
:title="$t('app.preview.title')" :ok-title="$t('install')"
>
<b-overlay :show="app === undefined">
<template v-if="app">
<section class="p-3">
<h3>{{ app.name }}</h3>
<p v-if="app.alternatives" class="mt-3">
<strong v-t="'app.potential_alternative_to'" />
{{ app.alternatives }}
</p>
<vue-showdown :markdown="app.description" flavor="github" />
<b-img
v-if="app.image"
:src="app.image"
aria-hidden="true" class="d-block mb-3" fluid
/>
<p>{{ $t('app.preview.version', { version: app.version }) }}</p>
<b-button
v-if="app.demo"
:href="app.demo" variant="primary" target="_blank"
>
<icon iname="external-link" />
{{ $t('app.preview.try_demo') }}
</b-button>
</section>
<card-collapse
id="app-warning" flush variant="warning"
:title="$t('app.preview.before_install')"
>
<b-card-body>
<strong v-t="'app.antifeatures'" class="d-block mb-1" />
<ul class="antifeatures">
<li v-for="antifeature in app.antifeatures" :key="antifeature.id">
<icon :iname="antifeature.icon" class="md mr-1" />
{{ antifeature.title }}
<explain-what
:id="antifeature.id"
:title="antifeature.title"
:content="antifeature.description"
/>
</li>
</ul>
<vue-showdown :markdown="app.preInstall" flavor="github" />
</b-card-body>
</card-collapse>
<card-collapse id="app-integration" flush :title="$t('app.preview.integration.title')">
<b-list-group flush tag="section">
<yuno-list-group-item variant="info">
{{ $t('app.preview.integration.archs') }} {{ app.integration.archs }}
</yuno-list-group-item>
<yuno-list-group-item :variant="app.integration.ldap ? 'success' : 'warning'">
{{ $t(`app.preview.integration.ldap.${app.integration.ldap}`) }}
</yuno-list-group-item>
<yuno-list-group-item :variant="app.integration.sso ? 'success' : 'warning'">
{{ $t(`app.preview.integration.sso.${app.integration.sso}`) }}
</yuno-list-group-item>
<yuno-list-group-item variant="info">
{{ $t(`app.preview.integration.multi_instance.${app.integration.multi_instance}`) }}
</yuno-list-group-item>
<yuno-list-group-item variant="info">
{{ $t('app.preview.integration.resources', app.integration.resources) }}
</yuno-list-group-item>
</b-list-group>
</card-collapse>
<card-collapse id="app-links" flush :title="$t('app.preview.links.title')">
<b-list-group flush tag="section">
<yuno-list-group-item v-for="[key, link] in app.links" :key="key" no-status>
<b-link :href="link" target="_blank">
{{ $t('app.preview.links.' + key) }}
</b-link>
</yuno-list-group-item>
</b-list-group>
</card-collapse>
</template>
</b-overlay>
</b-modal>
</template>
<script>
import api from '@/api'
import CardCollapse from '@/components/CardCollapse'
import { formatI18nField } from '@/helpers/yunohostArguments'
export default {
name: 'AppCatalogDetails',
components: {
CardCollapse
},
props: {
appId: { type: String, required: true },
antifeatures: { type: Object, required: true }
},
data () {
return {
app: undefined
}
},
async created () {
let { id, name, version, potential_alternative_to: alternatives, ...app } = await api.get('apps/manifest?app=' + this.appId)
const archs = app.integration.architectures
const integration = {
archs: Array.isArray(archs) ? archs.join(this.$i18n.t('words.separator')) : archs,
ldap: app.integration.ldap === '?' ? null : app.integration.ldap,
sso: app.integration.sso === '?' ? null : app.integration.sso,
multi_instance: app.integration.multi_instance,
resources: {
ram: app.integration.ram.runtime,
disk: app.integration.disk
}
}
const links = [
...['website', 'admindoc', 'code'].map((key) => ([key, app.upstream[key]])),
['package', app.remote.url],
['forum', `https://forum.yunohost.org/tag/${id}`]
].filter(([key, val]) => !!val)
this.app = {
id,
name,
alternatives: alternatives && alternatives.length ? alternatives.join(this.$i18n.t('words.separator')) : null,
description: formatI18nField(app.doc.DESCRIPTION),
image: app.image,
demo: app.upstream.demo,
version,
preInstall: formatI18nField(app.notifications.pre_install.main),
antifeatures: app.antifeatures?.map((af) => this.antifeatures[af]),
integration,
links
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .modal-body .b-overlay-wrap {
min-height: 50vh;
}
.antifeatures {
padding-left: 1rem;
li {
list-style: none;
}
}
</style>