]> source.dussan.org Git - nextcloud-server.git/commitdiff
refactor(app-store): Split app store views into grid and list view
authorFerdinand Thiessen <opensource@fthiessen.de>
Wed, 23 Oct 2024 10:43:24 +0000 (12:43 +0200)
committerFerdinand Thiessen <opensource@fthiessen.de>
Wed, 23 Oct 2024 11:01:28 +0000 (13:01 +0200)
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
apps/settings/src/views/AppStore.vue [deleted file]
apps/settings/src/views/AppStore/AppStore.vue [new file with mode: 0644]
apps/settings/src/views/AppStore/AppStoreSectionApps.vue [new file with mode: 0644]
apps/settings/src/views/AppStore/AppStoreViewGrid.vue [new file with mode: 0644]
apps/settings/src/views/AppStore/AppStoreViewLoading.vue [new file with mode: 0644]
apps/settings/src/views/AppStore/AppStoreViewNotFound.vue [new file with mode: 0644]

diff --git a/apps/settings/src/views/AppStore.vue b/apps/settings/src/views/AppStore.vue
deleted file mode 100644 (file)
index 614cb9d..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<!--
-  - SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
-  - SPDX-License-Identifier: AGPL-3.0-or-later
--->
-
-<template>
-       <!-- Apps list -->
-       <NcAppContent class="app-settings-content"
-               :page-heading="appStoreLabel">
-               <h2 class="app-settings-content__label" v-text="viewLabel" />
-
-               <AppStoreDiscoverSection v-if="currentCategory === 'discover'" />
-               <NcEmptyContent v-else-if="isLoading"
-                       class="empty-content__loading"
-                       :name="t('settings', 'Loading app list')">
-                       <template #icon>
-                               <NcLoadingIcon :size="64" />
-                       </template>
-               </NcEmptyContent>
-               <AppList v-else :category="currentCategory" />
-       </NcAppContent>
-</template>
-
-<script setup lang="ts">
-import { translate as t } from '@nextcloud/l10n'
-import { computed, getCurrentInstance, onBeforeMount, watchEffect } from 'vue'
-import { useRoute } from 'vue-router/composables'
-
-import { useAppsStore } from '../store/apps-store'
-import { APPS_SECTION_ENUM } from '../constants/AppsConstants'
-
-import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
-import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
-import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
-import AppList from '../components/AppList.vue'
-import AppStoreDiscoverSection from '../components/AppStoreDiscover/AppStoreDiscoverSection.vue'
-
-const route = useRoute()
-const store = useAppsStore()
-
-/**
- * ID of the current active category, default is `discover`
- */
-const currentCategory = computed(() => route.params?.category ?? 'discover')
-
-const appStoreLabel = t('settings', 'App Store')
-const viewLabel = computed(() => APPS_SECTION_ENUM[currentCategory.value] ?? store.getCategoryById(currentCategory.value)?.displayName ?? appStoreLabel)
-
-watchEffect(() => {
-       window.document.title = `${viewLabel.value} - ${appStoreLabel} - Nextcloud`
-})
-
-// TODO this part should be migrated to pinia
-const instance = getCurrentInstance()
-/** Is the app list loading */
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-const isLoading = computed(() => (instance?.proxy as any).$store.getters.loading('list'))
-onBeforeMount(() => {
-       // eslint-disable-next-line @typescript-eslint/no-explicit-any
-       (instance?.proxy as any).$store.dispatch('getCategories', { shouldRefetchCategories: true });
-       // eslint-disable-next-line @typescript-eslint/no-explicit-any
-       (instance?.proxy as any).$store.dispatch('getAllApps')
-})
-</script>
-
-<style scoped>
-.empty-content__loading {
-       height: 100%;
-}
-
-.app-settings-content__label {
-       margin-block-start: var(--app-navigation-padding);
-       margin-inline-start: calc(var(--default-clickable-area) + var(--app-navigation-padding) * 2);
-       min-height: var(--default-clickable-area);
-       line-height: var(--default-clickable-area);
-       vertical-align: center;
-}
-</style>
diff --git a/apps/settings/src/views/AppStore/AppStore.vue b/apps/settings/src/views/AppStore/AppStore.vue
new file mode 100644 (file)
index 0000000..37033d0
--- /dev/null
@@ -0,0 +1,55 @@
+<!--
+  - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+  - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<template>
+       <!-- Apps list -->
+       <NcAppContent :page-heading="appStoreLabel">
+               <h2 class="app-store__label" v-text="viewLabel" />
+               <router-view />
+       </NcAppContent>
+</template>
+
+<script setup lang="ts">
+import { t } from '@nextcloud/l10n'
+import { computed, onBeforeMount, watchEffect } from 'vue'
+import { useRoute } from 'vue-router/composables'
+
+import { useAppStore } from '../../store/appStore'
+import { AppStoreSectionNames } from '../../constants/AppStoreConstants'
+
+import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
+
+const route = useRoute()
+const store = useAppStore()
+
+/**
+ * ID of the current active category
+ */
+const currentCategory = computed(() => route.params?.category ?? route.name!)
+
+const appStoreLabel = t('settings', 'App Store')
+const viewLabel = computed(() => AppStoreSectionNames[currentCategory.value]
+               ?? store.getCategoryById(currentCategory.value)?.displayName
+               ?? appStoreLabel,
+)
+
+// Update the window title based on the current category
+watchEffect(() => {
+       window.document.title = `${viewLabel.value} - ${appStoreLabel} - Nextcloud`
+})
+// Load apps as both discover and normal app view require them to be loaded
+onBeforeMount(() => store.loadApps())
+onBeforeMount(() => store.loadCategories())
+</script>
+
+<style scoped>
+.app-store__label {
+       margin-block-start: var(--app-navigation-padding);
+       margin-inline-start: calc(var(--default-clickable-area) + var(--app-navigation-padding) * 2);
+       min-height: var(--default-clickable-area);
+       line-height: var(--default-clickable-area);
+       font-size: calc(var(--default-clickable-area) / 1.5);
+}
+</style>
diff --git a/apps/settings/src/views/AppStore/AppStoreSectionApps.vue b/apps/settings/src/views/AppStore/AppStoreSectionApps.vue
new file mode 100644 (file)
index 0000000..6173cee
--- /dev/null
@@ -0,0 +1,46 @@
+<!--
+  - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+  - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+<script setup lang="ts">
+import { computed } from 'vue'
+import { useCurrentCategory } from '../../composables/useCurrentCategory'
+import { AppStoreListViewCategories } from '../../constants/AppStoreConstants'
+import { useAppStore } from '../../store/appStore'
+import { useAppStoreSearchStore } from '../../store/appStoreSearch'
+
+import AppStoreViewGrid from './AppStoreViewGrid.vue'
+import AppStoreViewList from './AppStoreViewList.vue'
+import AppStoreViewLoading from './AppStoreViewLoading.vue'
+import AppStoreViewNotFound from './AppStoreViewNotFound.vue'
+import AppStoreViewNoResults from './AppStoreViewNoResults.vue'
+
+const store = useAppStore()
+const searchStore = useAppStoreSearchStore()
+const {
+       currentCategory,
+       currentCategoryName,
+} = useCurrentCategory()
+
+/** True if the category could not be found */
+const invalidCategory = computed(() => currentCategoryName.value === undefined)
+/** True if the current category should be displayed in a list-view */
+const isListView = computed(() => AppStoreListViewCategories.includes(currentCategory.value))
+
+/**
+ * True if apps / categories are currently loading (being fetched)
+ */
+const isLoading = computed(() => store.loading.categories || store.loading.apps)
+/**
+ * True if there are no search results for the current query
+ */
+const noSearchResults = computed(() => searchStore.query !== '' && searchStore.searchResults.length === 0)
+</script>
+
+<template>
+       <AppStoreViewLoading v-if="isLoading" />
+       <AppStoreViewNotFound v-else-if="invalidCategory" />
+       <AppStoreViewNoResults v-else-if="noSearchResults" />
+       <AppStoreViewList v-else-if="isListView" />
+       <AppStoreViewGrid v-else />
+</template>
diff --git a/apps/settings/src/views/AppStore/AppStoreViewGrid.vue b/apps/settings/src/views/AppStore/AppStoreViewGrid.vue
new file mode 100644 (file)
index 0000000..be3340e
--- /dev/null
@@ -0,0 +1,64 @@
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import { useElementSize } from '@vueuse/core'
+import { useCurrentCategory } from '../../composables/useCurrentCategory'
+import { useAppStoreSearchStore } from '../../store/appStoreSearch'
+import { useAppStore } from '../../store/appStore'
+import AppItem from '../../components/AppStore/AppItem/AppItem.vue'
+
+const store = useAppStore()
+const searchStore = useAppStoreSearchStore()
+const { currentCategory } = useCurrentCategory()
+
+const apps = computed(() => store.getAppsByCategory(currentCategory.value).filter(searchStore.filterAppsByQuery))
+
+const listElement = ref<HTMLUListElement>()
+const { width: containerWidth } = useElementSize(listElement)
+
+const itemWidth = computed(() => {
+       if (containerWidth.value >= 1400) {
+               return '25%'
+       } else if (containerWidth.value >= 900) {
+               return '33%'
+       } else if (containerWidth.value >= 600) {
+               return '50%'
+       } else {
+               return '100%'
+       }
+})
+</script>
+
+<template>
+       <TransitionGroup ref="listElement"
+               name="grid-transition"
+               tag="ul"
+               class="app-store-view-grid">
+               <AppItem v-for="app in apps"
+                       :key="app.id"
+                       :app="app"
+                       :category="currentCategory"
+                       class="app-store-view-grid__item" />
+       </TransitionGroup>
+</template>
+
+<style scoped lang="scss">
+.grid-transition-enter-active, .grid-transition-leave-active {
+  transition: all var(--animation-slow);
+}
+.grid-transition-enter, .grid-transition-leave-to {
+  opacity: 0;
+  transform: scale(.8);
+}
+
+.app-store-view-grid {
+       display: flex;
+       flex-wrap: wrap;
+       gap: var(--default-grid-baseline);
+       padding: var(--app-navigation-padding);
+       width: 100%;
+
+       &__item {
+               width: calc(v-bind('itemWidth') - var(--default-grid-baseline));
+       }
+}
+</style>
diff --git a/apps/settings/src/views/AppStore/AppStoreViewLoading.vue b/apps/settings/src/views/AppStore/AppStoreViewLoading.vue
new file mode 100644 (file)
index 0000000..7794aef
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+  - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+  - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<script setup lang="ts">
+import { t } from '@nextcloud/l10n'
+import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
+import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
+</script>
+
+<template>
+       <NcEmptyContent class="app-store-loading"
+               :name="t('settings', 'Loading app list')">
+               <template #icon>
+                       <NcLoadingIcon :size="64" />
+               </template>
+       </NcEmptyContent>
+</template>
+
+<style scoped>
+.app-store-loading {
+       height: 100%;
+}
+</style>
diff --git a/apps/settings/src/views/AppStore/AppStoreViewNotFound.vue b/apps/settings/src/views/AppStore/AppStoreViewNotFound.vue
new file mode 100644 (file)
index 0000000..fdfba43
--- /dev/null
@@ -0,0 +1,31 @@
+<!--
+  - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+  - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+
+<script setup lang="ts">
+import { t } from '@nextcloud/l10n'
+import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
+import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
+import IconMagnify from 'vue-material-design-icons/Magnify.vue'
+</script>
+
+<template>
+       <NcEmptyContent class="app-store-not-found"
+               :name="t('settings', 'Category not found')">
+               <template #icon>
+                       <IconMagnify :size="64" />
+               </template>
+               <template #action>
+                       <NcButton type="primary" :to="{ name: 'apps' }">
+                               {{ t('settings', 'Go back') }}
+                       </NcButton>
+               </template>
+       </NcEmptyContent>
+</template>
+
+<style scoped>
+.app-store-not-found {
+       height: 100%;
+}
+</style>