diff --git a/apps/appstore/src/components/AppTable/AppTable.vue b/apps/appstore/src/components/AppTable/AppTable.vue index dc29e1d4496fc..1419ff3da1c82 100644 --- a/apps/appstore/src/components/AppTable/AppTable.vue +++ b/apps/appstore/src/components/AppTable/AppTable.vue @@ -19,14 +19,21 @@ const tableElement = useTemplateRef('table') const { width: tableWidth } = useElementSize(tableElement) const isNarrow = computed(() => tableWidth.value < 768) +const isWide = computed(() => tableWidth.value >= 1280) @@ -63,14 +74,26 @@ const isNarrow = computed(() => tableWidth.value < 768) width: 60%; } +.appTable_wide .appTable__colName { + width: 37%; +} + .appTable__colSupport { width: 15%; } +.appTable_wide .appTable__colSupport { + width: 12%; +} + .appTable__colActions { width: 25%; } +.appTable_wide .appTable__colActions { + width: 20%; +} + .appTable_narrow .appTable__colActions { width: calc(3 * var(--default-grid-baseline) + 2 * var(--default-clickable-area)); } diff --git a/apps/appstore/src/components/AppTable/AppTableRow.vue b/apps/appstore/src/components/AppTable/AppTableRow.vue index 1291d3f0a580f..d6fc9a6c1dafd 100644 --- a/apps/appstore/src/components/AppTable/AppTableRow.vue +++ b/apps/appstore/src/components/AppTable/AppTableRow.vue @@ -12,16 +12,19 @@ import { t } from '@nextcloud/l10n' import { computed } from 'vue' import { useRoute } from 'vue-router' import NcButton from '@nextcloud/vue/components/NcButton' +import NcChip from '@nextcloud/vue/components/NcChip' import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' import AppActions from '../AppActions.vue' import AppIcon from '../AppIcon.vue' import BadgeAppDaemon from '../BadgeAppDaemon.vue' import BadgeAppLevel from '../BadgeAppLevel.vue' import { useActions } from '../../composables/useActions.ts' +import { useLimitedGroups } from '../../composables/useLimitedGroups.ts' const { app, isNarrow } = defineProps<{ app: IAppstoreApp | IAppstoreExApp isNarrow?: boolean + isWide?: boolean }>() const route = useRoute() @@ -46,6 +49,7 @@ const detailsAction = computed(() => ({ inline: false, })) +const groupsAppIsLimitedTo = useLimitedGroups(() => app) const rawActions = useActions(() => app) const actions = computed(() => [ ...rawActions.value, @@ -80,6 +84,21 @@ const actions = computed(() => [ + + +
[ color: var(--color-text-maxcontrast); } +.appTableRow__groupsCell { + display: flex; + gap: var(--default-grid-baseline); +} + .appTableRow__actionsCell { display: flex; gap: var(--default-grid-baseline); diff --git a/apps/appstore/src/components/AppstoreSidebar/AppDetailsTab.vue b/apps/appstore/src/components/AppstoreSidebar/AppDetailsTab.vue index 6d26d20e15be7..3d3ccc29aac77 100644 --- a/apps/appstore/src/components/AppstoreSidebar/AppDetailsTab.vue +++ b/apps/appstore/src/components/AppstoreSidebar/AppDetailsTab.vue @@ -16,6 +16,7 @@ import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' import BadgeAppDaemon from '../BadgeAppDaemon.vue' import BadgeAppLevel from '../BadgeAppLevel.vue' import BadgeAppScore from '../BadgeAppScore.vue' +import { useLimitedGroups } from '../../composables/useLimitedGroups.ts' import { useAppsStore } from '../../store/apps.ts' const { app } = defineProps<{ app: IAppstoreApp | IAppstoreExApp }>() @@ -43,15 +44,8 @@ const appAuthors = computed(() => { .join(', ') }) -const groupsAppIsLimitedto = computed(() => { - if (!app.groups) { - return [] - } - - return app.groups.map((group) => ({ id: group, name: group })) -}) - const appstoreUrl = computed(() => `https://apps.nextcloud.com/apps/${app.id}`) +const groupsAppIsLimitedTo = useLimitedGroups(() => app) /** * Further external resources (e.g. website) @@ -144,16 +138,16 @@ function authorName(xmlNode): string { -
+

{{ t('appstore', 'Limited to groups') }}

  • - {{ group.name }} + {{ group.displayName }}
diff --git a/apps/appstore/src/composables/useLimitedGroups.ts b/apps/appstore/src/composables/useLimitedGroups.ts new file mode 100644 index 0000000000000..d2c8360be2359 --- /dev/null +++ b/apps/appstore/src/composables/useLimitedGroups.ts @@ -0,0 +1,33 @@ +/*! + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { MaybeRefOrGetter } from 'vue' +import type { IAppstoreApp, IAppstoreExApp } from '../apps.d.ts' + +import { readonly, ref, toValue, watch } from 'vue' +import { useGroupsStore } from '../store/groups.ts' + +/** + * Get the groups an app is limited to and keep it up to date + * + * @param app - The app to get the groups + */ +export function useLimitedGroups(app: MaybeRefOrGetter) { + const groupsStore = useGroupsStore() + const groupsAppIsLimitedTo = ref<{ id: string, displayName: string }[]>([]) + watch(() => toValue(app).groups, async () => { + const groups = toValue(app).groups + if (groups === undefined) { + groupsAppIsLimitedTo.value = [] + return + } + + const promises = groups.map((group) => groupsStore.fetchGroupById(group)) + const results = await Promise.all(promises) + groupsAppIsLimitedTo.value = results.filter(Boolean) as { id: string, displayName: string }[] + }, { immediate: true }) + + return readonly(groupsAppIsLimitedTo) +} diff --git a/apps/appstore/src/store/groups.ts b/apps/appstore/src/store/groups.ts index 703b9587dcc1d..3e29bdf48d56f 100644 --- a/apps/appstore/src/store/groups.ts +++ b/apps/appstore/src/store/groups.ts @@ -8,13 +8,25 @@ import type { NcSelectUsersModel } from '@nextcloud/vue/components/NcSelectUsers import axios from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' +import PQueue from 'p-queue' import { defineStore } from 'pinia' import { computed, ref } from 'vue' import logger from '../utils/logger.ts' +const queue = new PQueue({ concurrency: 3 }) + export const useGroupsStore = defineStore('groups', () => { const groups = ref(new Map()) + /** + * Get group details by id + * + * @param groupId - The id of the group to fetch + */ + async function fetchGroupById(groupId: string) { + return await queue.add(() => internalFetchGroupById(groupId)) + } + /** * Search the API for groups matching the query * @@ -59,5 +71,18 @@ export const useGroupsStore = defineStore('groups', () => { groups: computed(() => Array.from(groups.value.values())), searchGroups, getGroupById, + fetchGroupById, + } + + /** + * Handle fetching group details by id + * + * @param groupId - The id of the group to fetch + */ + async function internalFetchGroupById(groupId: string) { + if (!groups.value.has(groupId)) { + await searchGroups(groupId) + } + return groups.value.get(groupId) } })