summaryrefslogtreecommitdiffstats
path: root/apps/settings
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-02-20 13:11:04 +0100
committerFerdinand Thiessen <opensource@fthiessen.de>2024-03-11 16:02:34 +0100
commite678b77031db91ba8e20aca07736dc0550cbf42e (patch)
tree44963b196a86059b70e4bdddc65fda1392528ceb /apps/settings
parent14bdc11deeaee97cac24c8c80622bf81fc34c2db (diff)
downloadnextcloud-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.vue75
-rw-r--r--apps/settings/src/constants/AppstoreCategoryIcons.ts76
-rw-r--r--apps/settings/src/router/routes.ts2
-rw-r--r--apps/settings/src/views/AppStore.vue63
-rw-r--r--apps/settings/src/views/AppStoreNavigation.vue136
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>