mirror of https://github.com/nextcloud/server.git
feat(settings): Implement new app discover section for app management
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>pull/44129/head
parent
072393d017
commit
4cadb82850
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="app-discover">
|
||||
<NcEmptyContent v-if="hasError"
|
||||
:name="t('settings', 'Nothing to show')"
|
||||
:description="t('settings', 'Could not load section content from app store.')">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiEyeOff" :size="64" />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
<NcEmptyContent v-else-if="elements.length === 0"
|
||||
:name="t('settings', 'Loading')"
|
||||
:description="t('settings', 'Fetching the latest news…')">
|
||||
<template #icon>
|
||||
<NcLoadingIcon :size="64" />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
<template v-else>
|
||||
<component :is="getComponent(entry.type)"
|
||||
v-for="entry, index in elements"
|
||||
:key="entry.id ?? index"
|
||||
v-bind="entry" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IAppDiscoverElements } from '../../constants/AppDiscoverTypes.ts'
|
||||
|
||||
import { mdiEyeOff } from '@mdi/js'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { defineAsyncComponent, defineComponent, onBeforeMount, ref } from 'vue'
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
|
||||
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
||||
|
||||
import logger from '../../logger'
|
||||
|
||||
const PostType = defineAsyncComponent(() => import('./PostType.vue'))
|
||||
|
||||
const hasError = ref(false)
|
||||
const elements = ref<IAppDiscoverElements[]>([])
|
||||
|
||||
/**
|
||||
* Shuffle using the Fisher-Yates algorithm
|
||||
* @param array The array to shuffle (in place)
|
||||
*/
|
||||
const shuffleArray = (array) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]]
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the app discover section information
|
||||
*/
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
const { data } = await axios.get<IAppDiscoverElements[]>(generateUrl('/settings/api/apps/discover'))
|
||||
elements.value = shuffleArray(data)
|
||||
} catch (error) {
|
||||
hasError.value = true
|
||||
logger.error(error as Error)
|
||||
showError(t('settings', 'Could not load app discover section'))
|
||||
}
|
||||
})
|
||||
|
||||
const getComponent = (type) => {
|
||||
if (type === 'post') {
|
||||
return PostType
|
||||
}
|
||||
return defineComponent({
|
||||
mounted: () => logger.error('Unknown component requested ', type),
|
||||
render: (h) => h('div', t('settings', 'Could not render element')),
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-discover {
|
||||
max-width: 1008px; /* 900px + 2x 54px padding for the carousel controls */
|
||||
margin-inline: auto;
|
||||
padding-inline: 54px;
|
||||
/* Padding required to make last element not bound to the bottom */
|
||||
padding-block-end: var(--default-clickable-area, 44px);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--default-clickable-area, 44px);
|
||||
}
|
||||
</style>
|
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<article class="app-discover-post"
|
||||
:class="{ 'app-discover-post--reverse': media && media.alignment === 'start' }">
|
||||
<div v-if="headline || text" class="app-discover-post__text">
|
||||
<h3>{{ translatedHeadline }}</h3>
|
||||
<p>{{ translatedText }}</p>
|
||||
</div>
|
||||
<div v-if="media">
|
||||
<img class="app-discover-post__media" :alt="mediaAlt" :src="mediaSource">
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getLanguage } from '@nextcloud/l10n'
|
||||
import { computed } from 'vue'
|
||||
|
||||
type ILocalizedValue<T> = Record<string, T | undefined> & { en: T }
|
||||
|
||||
const props = defineProps<{
|
||||
type: string
|
||||
|
||||
headline: ILocalizedValue<string>
|
||||
text: ILocalizedValue<string>
|
||||
link?: string
|
||||
media: {
|
||||
alignment: 'start'|'end'
|
||||
content: ILocalizedValue<{ src: string, alt: string}>
|
||||
}
|
||||
}>()
|
||||
|
||||
const language = getLanguage()
|
||||
|
||||
const getLocalizedValue = <T, >(dict: ILocalizedValue<T>) => dict[language] ?? dict[language.split('_')[0]] ?? dict.en
|
||||
|
||||
const translatedText = computed(() => getLocalizedValue(props.text))
|
||||
const translatedHeadline = computed(() => getLocalizedValue(props.headline))
|
||||
|
||||
const localizedMedia = computed(() => getLocalizedValue(props.media.content))
|
||||
|
||||
const mediaSource = computed(() => localizedMedia.value?.src)
|
||||
const mediaAlt = ''
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-discover-post {
|
||||
width: 100%;
|
||||
background-color: var(--color-primary-element-light);
|
||||
border-radius: var(--border-radius-rounded);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
&--reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-block: 0 1em;
|
||||
}
|
||||
|
||||
&__text {
|
||||
padding: var(--border-radius-rounded);
|
||||
}
|
||||
|
||||
&__media {
|
||||
max-height: 300px;
|
||||
max-width: 450px;
|
||||
border-radius: var(--border-radius-rounded);
|
||||
border-end-start-radius: 0;
|
||||
border-start-start-radius: 0;
|
||||
}
|
||||
|
||||
&--reverse &__media {
|
||||
border-radius: var(--border-radius-rounded);
|
||||
border-end-end-radius: 0;
|
||||
border-start-end-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue