diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-02-20 13:11:04 +0100 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-03-11 16:02:34 +0100 |
commit | e678b77031db91ba8e20aca07736dc0550cbf42e (patch) | |
tree | 44963b196a86059b70e4bdddc65fda1392528ceb /apps/settings | |
parent | 14bdc11deeaee97cac24c8c80622bf81fc34c2db (diff) | |
download | nextcloud-server-e678b77031db91ba8e20aca07736dc0550cbf42e.tar.gz nextcloud-server-e678b77031db91ba8e20aca07736dc0550cbf42e.zip |
feat(settings): Split appstore code into better maintainable pieces
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'apps/settings')
-rw-r--r-- | apps/settings/src/components/AppStoreSidebar/AppReleasesTab.vue | 75 | ||||
-rw-r--r-- | apps/settings/src/constants/AppstoreCategoryIcons.ts | 76 | ||||
-rw-r--r-- | apps/settings/src/router/routes.ts | 2 | ||||
-rw-r--r-- | apps/settings/src/views/AppStore.vue | 63 | ||||
-rw-r--r-- | apps/settings/src/views/AppStoreNavigation.vue | 136 |
5 files changed, 351 insertions, 1 deletions
diff --git a/apps/settings/src/components/AppStoreSidebar/AppReleasesTab.vue b/apps/settings/src/components/AppStoreSidebar/AppReleasesTab.vue new file mode 100644 index 00000000000..cd520220337 --- /dev/null +++ b/apps/settings/src/components/AppStoreSidebar/AppReleasesTab.vue @@ -0,0 +1,75 @@ +<!-- + - @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> + - + - @author Julius Härtl <jus@bitgrid.net> + - @author Ferdinand Thiessen <opensource@fthiessen.de> + - + - @license AGPL-3.0-or-later + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> +<template> + <NcAppSidebarTab v-if="hasChangelog" + id="desca" + :name="t('settings', 'Changelog')" + :order="1"> + <template #icon> + <NcIconSvgWrapper :path="mdiClockFast" :size="24" /> + </template> + <div v-for="release in app.releases" :key="release.version" class="app-sidebar-tabs__release"> + <h2>{{ release.version }}</h2> + <Markdown class="app-sidebar-tabs__release-text" :text="createChangelogFromRelease(release)" /> + </div> + </NcAppSidebarTab> +</template> + +<script setup lang="ts"> +import type { IAppstoreApp, IAppstoreAppRelease } from '../../app-types.ts' + +import { mdiClockFast } from '@mdi/js' +import { getLanguage, translate as t } from '@nextcloud/l10n' + +import NcAppSidebarTab from '@nextcloud/vue/dist/Components/NcAppSidebarTab.js' +import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' +import Markdown from '../Markdown.vue' +import { computed, watch } from 'vue' + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const props = defineProps<{ app: IAppstoreApp }>() + +watch([props], () => console.warn(props.app.releases)) +const hasChangelog = computed(() => Object.values(props.app.releases[0]?.translations ?? {}).some(({ changelog }) => !!changelog)) + +const createChangelogFromRelease = (release: IAppstoreAppRelease) => release.translations?.[getLanguage()]?.changelog ?? release.translations?.en?.changelog ?? '' +</script> + +<style scoped lang="scss"> +.app-sidebar-tabs__release { + h2 { + border-bottom: 1px solid var(--color-border); + font-size: 24px; + } + + &-text { + // Overwrite changelog heading styles + :deep(h3) { + font-size: 20px; + } + :deep(h4) { + font-size: 17px; + } + } +} +</style> diff --git a/apps/settings/src/constants/AppstoreCategoryIcons.ts b/apps/settings/src/constants/AppstoreCategoryIcons.ts new file mode 100644 index 00000000000..67b32431a81 --- /dev/null +++ b/apps/settings/src/constants/AppstoreCategoryIcons.ts @@ -0,0 +1,76 @@ +/** + * @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de> + * + * @author Ferdinand Thiessen <opensource@fthiessen.de> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +import { + mdiAccount, + mdiAccountMultiple, + mdiArchive, + mdiCheck, + mdiClipboardFlow, + mdiClose, + mdiCog, + mdiControllerClassic, + mdiDownload, + mdiFileDocumentEdit, + mdiFolder, + mdiKey, + mdiMagnify, + mdiMonitorEye, + mdiMultimedia, + mdiOfficeBuilding, + mdiOpenInApp, + mdiSecurity, + mdiStar, + mdiStarShooting, + mdiTools, + mdiViewDashboard, +} from '@mdi/js' + +/** + * SVG paths used for appstore category icons + */ +export default Object.freeze({ + // system special categories + installed: mdiAccount, + enabled: mdiCheck, + disabled: mdiClose, + bundles: mdiArchive, + supported: mdiStarShooting, + featured: mdiStar, + updates: mdiDownload, + + // generic categories + auth: mdiKey, + customization: mdiCog, + dashboard: mdiViewDashboard, + files: mdiFolder, + games: mdiControllerClassic, + integration: mdiOpenInApp, + monitoring: mdiMonitorEye, + multimedia: mdiMultimedia, + office: mdiFileDocumentEdit, + organization: mdiOfficeBuilding, + search: mdiMagnify, + security: mdiSecurity, + social: mdiAccountMultiple, + tools: mdiTools, + workflow: mdiClipboardFlow, +}) diff --git a/apps/settings/src/router/routes.ts b/apps/settings/src/router/routes.ts index 7d6b2da3bee..3aae9a0e178 100644 --- a/apps/settings/src/router/routes.ts +++ b/apps/settings/src/router/routes.ts @@ -5,7 +5,7 @@ import { defineAsyncComponent } from 'vue' // Dynamic loading const AppStore = defineAsyncComponent(() => import(/* webpackChunkName: 'settings-apps-view' */'../views/AppStore.vue')) const AppStoreNavigation = defineAsyncComponent(() => import(/* webpackChunkName: 'settings-apps-view' */'../views/AppStoreNavigation.vue')) -const AppstoreSidebar = defineAsyncComponent(() => import(/* webpackChunkName: 'settings-apps-view' */'../views/AppstoreSidebar.vue')) +const AppstoreSidebar = defineAsyncComponent(() => import(/* webpackChunkName: 'settings-apps-view' */'../views/AppStoreSidebar.vue')) const UserManagement = defineAsyncComponent(() => import(/* webpackChunkName: 'settings-users' */'../views/UserManagement.vue')) const UserManagementNavigation = defineAsyncComponent(() => import(/* webpackChunkName: 'settings-users' */'../views/UserManagementNavigation.vue')) diff --git a/apps/settings/src/views/AppStore.vue b/apps/settings/src/views/AppStore.vue new file mode 100644 index 00000000000..06c47611238 --- /dev/null +++ b/apps/settings/src/views/AppStore.vue @@ -0,0 +1,63 @@ +<!-- + - @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> + - + - @author Julius Härtl <jus@bitgrid.net> + - @author Ferdinand Thiessen <opensource@fthiessen.de> + - + - @license AGPL-3.0-or-later + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> + +<template> + <!-- Apps list --> + <NcAppContent class="app-settings-content" + :page-heading="pageHeading"> + <AppList :category="currentCategory" /> + </NcAppContent> +</template> + +<script setup lang="ts"> +import { translate as t } from '@nextcloud/l10n' +import { computed, watch } from 'vue' +import { useRoute } from 'vue-router/composables' +import { APPS_SECTION_ENUM } from '../constants/AppsConstants.js' +import { useAppsStore } from '../store/apps-store' + +import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js' +import AppList from '../components/AppList.vue' + +const route = useRoute() +const store = useAppsStore() + +/** + * ID of the current active category, default is `installed` + */ +const currentCategory = computed(() => route.params?.category ?? 'installed') + +/** + * The H1 to be used on the website + */ +const pageHeading = computed(() => { + if (currentCategory.value in APPS_SECTION_ENUM) { + return APPS_SECTION_ENUM[currentCategory.value] + } + const category = store.getCategoryById(currentCategory.value) + return category?.displayName ?? t('settings', 'Apps') +}) +watch([pageHeading], () => { + window.document.title = `${pageHeading.value} - Apps - Nextcloud` +}) +</script> diff --git a/apps/settings/src/views/AppStoreNavigation.vue b/apps/settings/src/views/AppStoreNavigation.vue new file mode 100644 index 00000000000..1d5399a75ca --- /dev/null +++ b/apps/settings/src/views/AppStoreNavigation.vue @@ -0,0 +1,136 @@ +<template> + <!-- Categories & filters --> + <NcAppNavigation :aria-label="t('settings', 'Apps')"> + <template #list> + <NcAppNavigationItem id="app-category-your-apps" + :to="{ name: 'apps' }" + :exact="true" + :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> |