]> source.dussan.org Git - nextcloud-server.git/commitdiff
feat(app-store): Add in-app search on the navigation component
authorFerdinand Thiessen <opensource@fthiessen.de>
Wed, 23 Oct 2024 10:44:18 +0000 (12:44 +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/store/appStoreSearch.ts [new file with mode: 0644]
apps/settings/src/views/AppStore/AppStoreNavigation.vue [new file with mode: 0644]
apps/settings/src/views/AppStore/AppStoreViewNoResults.vue [new file with mode: 0644]
apps/settings/src/views/AppStoreNavigation.vue [deleted file]
core/src/views/UnifiedSearch.vue

diff --git a/apps/settings/src/store/appStoreSearch.ts b/apps/settings/src/store/appStoreSearch.ts
new file mode 100644 (file)
index 0000000..88f6869
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { IAppStoreApp } from '../constants/AppStoreTypes'
+
+import { defineStore } from 'pinia'
+import { computed, ref } from 'vue'
+import { useAppStore } from './appStore'
+
+export const useAppStoreSearchStore = defineStore('app-store-search', () => {
+       const appStore = useAppStore()
+
+       /**
+        * The current search query
+        */
+       const query = ref('')
+
+       const searchResults = computed(() => {
+               if (query.value === '') {
+                       return []
+               }
+               return appStore.apps
+                       .filter(filterAppsByQuery)
+       })
+
+       /**
+        * Return true if the app would be included with current search query, false otherwise.
+        * @param app app to check
+        */
+       function filterAppsByQuery(app: IAppStoreApp): boolean {
+               if (query.value === '') {
+                       return true
+               }
+
+               const lowerCaseQuery = query.value.toLocaleLowerCase()
+               return app.id.toLocaleLowerCase().includes(lowerCaseQuery)
+                       || app.name.toLocaleLowerCase().includes(lowerCaseQuery)
+       }
+
+       return {
+               filterAppsByQuery,
+               query,
+               searchResults,
+       }
+})
diff --git a/apps/settings/src/views/AppStore/AppStoreNavigation.vue b/apps/settings/src/views/AppStore/AppStoreNavigation.vue
new file mode 100644 (file)
index 0000000..64bfe7d
--- /dev/null
@@ -0,0 +1,161 @@
+<!--
+  - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+  - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+<template>
+       <!-- Categories & filters -->
+       <NcAppNavigation :aria-label="t('settings', 'Apps')">
+               <template #search>
+                       <NcAppNavigationSearch v-model="searchStore.query" />
+               </template>
+               <template #list>
+                       <NcAppNavigationItem v-if="appstoreEnabled"
+                               id="app-category-discover"
+                               :to="{ name: 'discover' }"
+                               :name="AppStoreSectionNames.discover">
+                               <template #icon>
+                                       <NcIconSvgWrapper :path="AppStoreCategoryIcons.discover" />
+                               </template>
+                       </NcAppNavigationItem>
+                       <NcAppNavigationItem id="app-category-installed"
+                               :to="{ name: 'app-category', params: { category: 'installed'} }"
+                               :name="AppStoreSectionNames.installed">
+                               <template #icon>
+                                       <NcIconSvgWrapper :path="AppStoreCategoryIcons.installed" />
+                               </template>
+                       </NcAppNavigationItem>
+                       <NcAppNavigationItem id="app-category-enabled"
+                               :to="{ name: 'app-category', params: { category: 'enabled' } }"
+                               :name="AppStoreSectionNames.enabled">
+                               <template #icon>
+                                       <NcIconSvgWrapper :path="AppStoreCategoryIcons.enabled" />
+                               </template>
+                       </NcAppNavigationItem>
+                       <NcAppNavigationItem id="app-category-disabled"
+                               :to="{ name: 'app-category', params: { category: 'disabled' } }"
+                               :name="AppStoreSectionNames.disabled">
+                               <template #icon>
+                                       <NcIconSvgWrapper :path="AppStoreCategoryIcons.disabled" />
+                               </template>
+                       </NcAppNavigationItem>
+                       <NcAppNavigationItem v-if="updateCount > 0"
+                               id="app-category-updates"
+                               :to="{ name: 'app-category', params: { category: 'updates' } }"
+                               :name="AppStoreSectionNames.updates">
+                               <template #counter>
+                                       <NcCounterBubble>{{ updateCount }}</NcCounterBubble>
+                               </template>
+                               <template #icon>
+                                       <NcIconSvgWrapper :path="AppStoreCategoryIcons.updates" />
+                               </template>
+                       </NcAppNavigationItem>
+                       <NcAppNavigationItem id="app-category-your-bundles"
+                               :to="{ name: 'app-category', params: { category: 'app-bundles' } }"
+                               :name="AppStoreSectionNames['app-bundles']">
+                               <template #icon>
+                                       <NcIconSvgWrapper :path="AppStoreCategoryIcons.bundles" />
+                               </template>
+                       </NcAppNavigationItem>
+
+                       <NcAppNavigationSpacer />
+
+                       <!-- App store categories -->
+                       <li v-if="appstoreEnabled && categoriesLoading" class="categories--loading">
+                               <NcLoadingIcon :size="20" :aria-label="t('settings', 'Loading categories')" />
+                       </li>
+                       <template v-else-if="appstoreEnabled && !categoriesLoading">
+                               <NcAppNavigationItem v-if="isSubscribed"
+                                       id="app-category-supported"
+                                       :to="{ name: 'app-category', params: { category: 'supported' } }"
+                                       :name="AppStoreSectionNames.supported">
+                                       <template #icon>
+                                               <NcIconSvgWrapper :path="AppStoreCategoryIcons.supported" />
+                                       </template>
+                               </NcAppNavigationItem>
+                               <NcAppNavigationItem id="app-category-featured"
+                                       :to="{ name: 'app-category', params: { category: 'featured' } }"
+                                       :name="AppStoreSectionNames.featured">
+                                       <template #icon>
+                                               <NcIconSvgWrapper :path="AppStoreCategoryIcons.featured" />
+                                       </template>
+                               </NcAppNavigationItem>
+
+                               <NcAppNavigationItem v-for="category in categories"
+                                       :id="`app-category-${category.id}`"
+                                       :key="category.id"
+                                       :name="category.displayName"
+                                       :to="{
+                                               name: 'app-category',
+                                               params: { category: category.id },
+                                       }">
+                                       <template #icon>
+                                               <NcIconSvgWrapper :path="category.icon" />
+                                       </template>
+                               </NcAppNavigationItem>
+                       </template>
+
+                       <NcAppNavigationItem id="app-developer-docs"
+                               :name="t('settings', 'Developer documentation ↗')"
+                               :href="developerDocsUrl" />
+               </template>
+       </NcAppNavigation>
+</template>
+
+<script setup lang="ts">
+import { subscribe } from '@nextcloud/event-bus'
+import { loadState } from '@nextcloud/initial-state'
+import { translate as t } from '@nextcloud/l10n'
+import { useIsSmallMobile } from '@nextcloud/vue/dist/Composables/useIsMobile.js'
+import { computed, onBeforeMount } from 'vue'
+import { useAppStore } from '../../store/appStore'
+import { useAppStoreSearchStore } from '../../store/appStoreSearch'
+import { AppStoreSectionNames } from '../../constants/AppStoreConstants'
+
+import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
+import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
+import NcAppNavigationSearch from '@nextcloud/vue/dist/Components/NcAppNavigationSearch.js'
+import NcAppNavigationSpacer from '@nextcloud/vue/dist/Components/NcAppNavigationSpacer.js'
+import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js'
+import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
+import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
+import AppStoreCategoryIcons from '../../constants/AppStoreCategoryIcons'
+
+const updateCount = loadState<number>('settings', 'appstoreUpdateCount', 0)
+const appstoreEnabled = loadState<boolean>('settings', 'appstoreEnabled', true)
+const developerDocsUrl = loadState<string>('settings', 'appstoreDeveloperDocs', '')
+
+const store = useAppStore()
+const searchStore = useAppStoreSearchStore()
+const categories = computed(() => store.categories)
+const categoriesLoading = computed(() => store.loading.categories)
+
+/**
+ * Check if the current instance has a support subscription from the Nextcloud GmbH
+ *
+ * For customers of the Nextcloud GmbH the app level will be set to `300` for apps that are supported in their subscription
+ */
+const isSubscribed = computed(() => store.apps.find(({ level }) => level === 300) !== undefined)
+
+const isSmallMobile = useIsSmallMobile()
+// Subscribe to unified search to use the search input for small mobile
+subscribe('nextcloud:unified-search:search', ({ query: text }) => {
+       if (isSmallMobile.value) {
+               searchStore.query = text
+       }
+})
+
+// load categories when component is mounted
+onBeforeMount(() => {
+       store.loadApps()
+})
+</script>
+
+<style scoped>
+/* The categories-loading indicator */
+.categories--loading {
+       flex: 1;
+       display: flex;
+       align-items: center;
+       justify-content: center;
+}
+</style>
diff --git a/apps/settings/src/views/AppStore/AppStoreViewNoResults.vue b/apps/settings/src/views/AppStore/AppStoreViewNoResults.vue
new file mode 100644 (file)
index 0000000..5e701b2
--- /dev/null
@@ -0,0 +1,41 @@
+<!--
+  - 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 { useAppStoreSearchStore } from '../../store/appStoreSearch'
+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'
+
+const searchStore = useAppStoreSearchStore()
+
+/**
+ * Clear the search query to reset the search
+ */
+function clearSearch() {
+       searchStore.query = ''
+}
+</script>
+
+<template>
+       <NcEmptyContent class="app-store-no-results"
+               :name="t('settings', 'No search results')">
+               <template #icon>
+                       <IconMagnify :size="64" />
+               </template>
+               <template #action>
+                       <NcButton @click="clearSearch">
+                               {{ t('settings', 'Clear search') }}
+                       </NcButton>
+               </template>
+       </NcEmptyContent>
+</template>
+
+<style scoped>
+.app-store-no-results {
+       height: 100%;
+}
+</style>
diff --git a/apps/settings/src/views/AppStoreNavigation.vue b/apps/settings/src/views/AppStoreNavigation.vue
deleted file mode 100644 (file)
index b7ba24c..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-<!--
-  - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
-  - SPDX-License-Identifier: AGPL-3.0-or-later
--->
-<template>
-       <!-- Categories & filters -->
-       <NcAppNavigation :aria-label="t('settings', 'Apps')">
-               <template #list>
-                       <NcAppNavigationItem v-if="appstoreEnabled"
-                               id="app-category-discover"
-                               :to="{ name: 'apps-category', params: { category: 'discover'} }"
-                               :name="APPS_SECTION_ENUM.discover">
-                               <template #icon>
-                                       <NcIconSvgWrapper :path="APPSTORE_CATEGORY_ICONS.discover" />
-                               </template>
-                       </NcAppNavigationItem>
-                       <NcAppNavigationItem id="app-category-installed"
-                               :to="{ name: 'apps-category', params: { category: 'installed'} }"
-                               :name="APPS_SECTION_ENUM.installed">
-                               <template #icon>
-                                       <NcIconSvgWrapper :path="APPSTORE_CATEGORY_ICONS.installed" />
-                               </template>
-                       </NcAppNavigationItem>
-                       <NcAppNavigationItem id="app-category-enabled"
-                               :to="{ name: 'apps-category', params: { category: 'enabled' } }"
-                               :name="APPS_SECTION_ENUM.enabled">
-                               <template #icon>
-                                       <NcIconSvgWrapper :path="APPSTORE_CATEGORY_ICONS.enabled" />
-                               </template>
-                       </NcAppNavigationItem>
-                       <NcAppNavigationItem id="app-category-disabled"
-                               :to="{ name: 'apps-category', params: { category: 'disabled' } }"
-                               :name="APPS_SECTION_ENUM.disabled">
-                               <template #icon>
-                                       <NcIconSvgWrapper :path="APPSTORE_CATEGORY_ICONS.disabled" />
-                               </template>
-                       </NcAppNavigationItem>
-                       <NcAppNavigationItem v-if="updateCount > 0"
-                               id="app-category-updates"
-                               :to="{ name: 'apps-category', params: { category: 'updates' } }"
-                               :name="APPS_SECTION_ENUM.updates">
-                               <template #counter>
-                                       <NcCounterBubble>{{ updateCount }}</NcCounterBubble>
-                               </template>
-                               <template #icon>
-                                       <NcIconSvgWrapper :path="APPSTORE_CATEGORY_ICONS.updates" />
-                               </template>
-                       </NcAppNavigationItem>
-                       <NcAppNavigationItem id="app-category-your-bundles"
-                               :to="{ name: 'apps-category', params: { category: 'app-bundles' } }"
-                               :name="APPS_SECTION_ENUM['app-bundles']">
-                               <template #icon>
-                                       <NcIconSvgWrapper :path="APPSTORE_CATEGORY_ICONS.bundles" />
-                               </template>
-                       </NcAppNavigationItem>
-
-                       <NcAppNavigationSpacer />
-
-                       <!-- App store categories -->
-                       <li v-if="appstoreEnabled && categoriesLoading" class="categories--loading">
-                               <NcLoadingIcon :size="20" :aria-label="t('settings', 'Loading categories')" />
-                       </li>
-                       <template v-else-if="appstoreEnabled && !categoriesLoading">
-                               <NcAppNavigationItem v-if="isSubscribed"
-                                       id="app-category-supported"
-                                       :to="{ name: 'apps-category', params: { category: 'supported' } }"
-                                       :name="APPS_SECTION_ENUM.supported">
-                                       <template #icon>
-                                               <NcIconSvgWrapper :path="APPSTORE_CATEGORY_ICONS.supported" />
-                                       </template>
-                               </NcAppNavigationItem>
-                               <NcAppNavigationItem id="app-category-featured"
-                                       :to="{ name: 'apps-category', params: { category: 'featured' } }"
-                                       :name="APPS_SECTION_ENUM.featured">
-                                       <template #icon>
-                                               <NcIconSvgWrapper :path="APPSTORE_CATEGORY_ICONS.featured" />
-                                       </template>
-                               </NcAppNavigationItem>
-
-                               <NcAppNavigationItem v-for="category in categories"
-                                       :id="`app-category-${category.id}`"
-                                       :key="category.id"
-                                       :name="category.displayName"
-                                       :to="{
-                                               name: 'apps-category',
-                                               params: { category: category.id },
-                                       }">
-                                       <template #icon>
-                                               <NcIconSvgWrapper :path="category.icon" />
-                                       </template>
-                               </NcAppNavigationItem>
-                       </template>
-
-                       <NcAppNavigationItem id="app-developer-docs"
-                               :name="t('settings', 'Developer documentation ↗')"
-                               :href="developerDocsUrl" />
-               </template>
-       </NcAppNavigation>
-</template>
-
-<script setup lang="ts">
-import { loadState } from '@nextcloud/initial-state'
-import { translate as t } from '@nextcloud/l10n'
-import { computed, onBeforeMount } from 'vue'
-import { APPS_SECTION_ENUM } from '../constants/AppsConstants'
-import { useAppsStore } from '../store/apps-store'
-
-import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
-import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
-import NcAppNavigationSpacer from '@nextcloud/vue/dist/Components/NcAppNavigationSpacer.js'
-import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js'
-import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
-import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
-
-import APPSTORE_CATEGORY_ICONS from '../constants/AppstoreCategoryIcons.ts'
-
-const updateCount = loadState<number>('settings', 'appstoreUpdateCount', 0)
-const appstoreEnabled = loadState<boolean>('settings', 'appstoreEnabled', true)
-const developerDocsUrl = loadState<string>('settings', 'appstoreDeveloperDocs', '')
-
-const store = useAppsStore()
-const categories = computed(() => store.categories)
-const categoriesLoading = computed(() => store.loading.categories)
-
-/**
- * Check if the current instance has a support subscription from the Nextcloud GmbH
- *
- * For customers of the Nextcloud GmbH the app level will be set to `300` for apps that are supported in their subscription
- */
-const isSubscribed = computed(() => store.apps.find(({ level }) => level === 300) !== undefined)
-
-// load categories when component is mounted
-onBeforeMount(() => {
-       store.loadCategories()
-       store.loadApps()
-})
-</script>
-
-<style scoped>
-/* The categories-loading indicator */
-.categories--loading {
-       flex: 1;
-       display: flex;
-       align-items: center;
-       justify-content: center;
-}
-</style>
index fc1d456247c7ba88a4ca3e008e5d11accbc06124..7ce17afbad75db873322209766ff6bb8019aeaaf 100644 (file)
@@ -26,6 +26,7 @@
 <script lang="ts">
 import { emit, subscribe } from '@nextcloud/event-bus'
 import { translate } from '@nextcloud/l10n'
+import { useIsSmallMobile } from '@nextcloud/vue/dist/Composables/useIsMobile.js'
 import { useBrowserLocation } from '@vueuse/core'
 import { defineComponent } from 'vue'
 
@@ -49,9 +50,11 @@ export default defineComponent({
 
        setup() {
                const currentLocation = useBrowserLocation()
+               const isSmallMobile = useIsSmallMobile()
 
                return {
                        currentLocation,
+                       isSmallMobile,
                        t: translate,
                }
        },
@@ -80,7 +83,11 @@ export default defineComponent({
                 */
                supportsLocalSearch() {
                        // TODO: Make this an API
-                       const providerPaths = ['/settings/users', '/apps/deck', '/settings/apps']
+                       const providerPaths = ['/settings/users', '/apps/deck']
+                       // additional providers which only use the search bar for mobile view
+                       if (this.isSmallMobile) {
+                               providerPaths.push(...['/settings/apps', '/apps/files'])
+                       }
                        return providerPaths.some((path) => this.currentLocation.pathname?.includes?.(path))
                },
        },