diff options
Diffstat (limited to 'apps/settings/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue')
-rw-r--r-- | apps/settings/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/apps/settings/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue b/apps/settings/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue new file mode 100644 index 00000000000..bb91940c763 --- /dev/null +++ b/apps/settings/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue @@ -0,0 +1,119 @@ +<!-- + - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> +<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="mdiEyeOffOutline" :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 { mdiEyeOffOutline } 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/components/NcEmptyContent' +import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' +import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' + +import logger from '../../logger' +import { parseApiResponse, filterElements } from '../../utils/appDiscoverParser.ts' + +const PostType = defineAsyncComponent(() => import('./PostType.vue')) +const CarouselType = defineAsyncComponent(() => import('./CarouselType.vue')) +const ShowcaseType = defineAsyncComponent(() => import('./ShowcaseType.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 = <T, >(array: T[]): T[] => { + 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<Record<string, unknown>[]>(generateUrl('/settings/api/apps/discover')) + if (data.length === 0) { + logger.info('No app discover elements available (empty response)') + hasError.value = true + return + } + // Parse data to ensure dates are useable and then filter out expired or future elements + const parsedElements = data.map(parseApiResponse).filter(filterElements) + // Shuffle elements to make it looks more interesting + const shuffledElements = shuffleArray(parsedElements) + // Sort pinned elements first + shuffledElements.sort((a, b) => (a.order ?? Infinity) < (b.order ?? Infinity) ? -1 : 1) + // Set the elements to the UI + elements.value = shuffledElements + } 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 + } else if (type === 'carousel') { + return CarouselType + } else if (type === 'showcase') { + return ShowcaseType + } + 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> |