feat(settings): Split appstore code into better maintainable pieces

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/44092/head
Ferdinand Thiessen 2 years ago
parent 14bdc11dee
commit e678b77031
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
  1. 75
      apps/settings/src/components/AppStoreSidebar/AppReleasesTab.vue
  2. 76
      apps/settings/src/constants/AppstoreCategoryIcons.ts
  3. 2
      apps/settings/src/router/routes.ts
  4. 63
      apps/settings/src/views/AppStore.vue
  5. 136
      apps/settings/src/views/AppStoreNavigation.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>

@ -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,
})

@ -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'))

@ -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>

@ -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>
Loading…
Cancel
Save