AppStoreDiscoverSection.vue 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. <template>
  2. <div class="app-discover">
  3. <NcEmptyContent v-if="hasError"
  4. :name="t('settings', 'Nothing to show')"
  5. :description="t('settings', 'Could not load section content from app store.')">
  6. <template #icon>
  7. <NcIconSvgWrapper :path="mdiEyeOff" :size="64" />
  8. </template>
  9. </NcEmptyContent>
  10. <NcEmptyContent v-else-if="elements.length === 0"
  11. :name="t('settings', 'Loading')"
  12. :description="t('settings', 'Fetching the latest news…')">
  13. <template #icon>
  14. <NcLoadingIcon :size="64" />
  15. </template>
  16. </NcEmptyContent>
  17. <template v-else>
  18. <component :is="getComponent(entry.type)"
  19. v-for="entry, index in elements"
  20. :key="entry.id ?? index"
  21. v-bind="entry" />
  22. </template>
  23. </div>
  24. </template>
  25. <script setup lang="ts">
  26. import type { IAppDiscoverElements } from '../../constants/AppDiscoverTypes.ts'
  27. import { mdiEyeOff } from '@mdi/js'
  28. import { showError } from '@nextcloud/dialogs'
  29. import { translate as t } from '@nextcloud/l10n'
  30. import { generateUrl } from '@nextcloud/router'
  31. import { defineAsyncComponent, defineComponent, onBeforeMount, ref } from 'vue'
  32. import axios from '@nextcloud/axios'
  33. import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
  34. import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
  35. import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
  36. import logger from '../../logger'
  37. const PostType = defineAsyncComponent(() => import('./PostType.vue'))
  38. const hasError = ref(false)
  39. const elements = ref<IAppDiscoverElements[]>([])
  40. /**
  41. * Shuffle using the Fisher-Yates algorithm
  42. * @param array The array to shuffle (in place)
  43. */
  44. const shuffleArray = (array) => {
  45. for (let i = array.length - 1; i > 0; i--) {
  46. const j = Math.floor(Math.random() * (i + 1));
  47. [array[i], array[j]] = [array[j], array[i]]
  48. }
  49. return array
  50. }
  51. /**
  52. * Load the app discover section information
  53. */
  54. onBeforeMount(async () => {
  55. try {
  56. const { data } = await axios.get<IAppDiscoverElements[]>(generateUrl('/settings/api/apps/discover'))
  57. elements.value = shuffleArray(data)
  58. } catch (error) {
  59. hasError.value = true
  60. logger.error(error as Error)
  61. showError(t('settings', 'Could not load app discover section'))
  62. }
  63. })
  64. const getComponent = (type) => {
  65. if (type === 'post') {
  66. return PostType
  67. }
  68. return defineComponent({
  69. mounted: () => logger.error('Unknown component requested ', type),
  70. render: (h) => h('div', t('settings', 'Could not render element')),
  71. })
  72. }
  73. </script>
  74. <style scoped lang="scss">
  75. .app-discover {
  76. max-width: 1008px; /* 900px + 2x 54px padding for the carousel controls */
  77. margin-inline: auto;
  78. padding-inline: 54px;
  79. /* Padding required to make last element not bound to the bottom */
  80. padding-block-end: var(--default-clickable-area, 44px);
  81. display: flex;
  82. flex-direction: column;
  83. gap: var(--default-clickable-area, 44px);
  84. }
  85. </style>