Skip to content

Commit f62fe19

Browse files
authored
Merge pull request #60497 from nextcloud/backport/60355/stable34
[stable34] feat(appstore): show new column with groups the app is limited to
2 parents 6879d86 + 2194add commit f62fe19

10 files changed

Lines changed: 132 additions & 33 deletions

File tree

apps/appstore/src/components/AppTable/AppTable.vue

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,21 @@ const tableElement = useTemplateRef('table')
1919
const { width: tableWidth } = useElementSize(tableElement)
2020
2121
const isNarrow = computed(() => tableWidth.value < 768)
22+
const isWide = computed(() => tableWidth.value >= 1280)
2223
</script>
2324

2425
<template>
25-
<table ref="table" :class="[$style.appTable, { [$style.appTable_narrow]: isNarrow }]">
26+
<table
27+
ref="table"
28+
:class="[$style.appTable, {
29+
[$style.appTable_narrow]: isNarrow,
30+
[$style.appTable_wide]: isWide,
31+
}]">
2632
<colgroup>
2733
<col :class="$style.appTable__colName">
2834
<col :class="$style.appTable__colVersion">
2935
<col v-if="!isNarrow" :class="$style.appTable__colSupport">
36+
<col v-if="isWide" :class="$style.appTable__colGroups">
3037
<col :class="$style.appTable__colActions">
3138
</colgroup>
3239
<thead hidden>
@@ -36,6 +43,9 @@ const isNarrow = computed(() => tableWidth.value < 768)
3643
<th v-if="!isNarrow">
3744
{{ t('appstore', 'Support level') }}
3845
</th>
46+
<th v-if="isWide">
47+
{{ t('appstore', 'Groups') }}
48+
</th>
3949
<th>{{ t('appstore', 'Actions') }}</th>
4050
</tr>
4151
</thead>
@@ -44,7 +54,8 @@ const isNarrow = computed(() => tableWidth.value < 768)
4454
v-for="app in apps"
4555
:key="app.id"
4656
:app
47-
:isNarrow />
57+
:isNarrow
58+
:isWide />
4859
</tbody>
4960
</table>
5061
</template>
@@ -63,14 +74,26 @@ const isNarrow = computed(() => tableWidth.value < 768)
6374
width: 60%;
6475
}
6576
77+
.appTable_wide .appTable__colName {
78+
width: 37%;
79+
}
80+
6681
.appTable__colSupport {
6782
width: 15%;
6883
}
6984
85+
.appTable_wide .appTable__colSupport {
86+
width: 12%;
87+
}
88+
7089
.appTable__colActions {
7190
width: 25%;
7291
}
7392
93+
.appTable_wide .appTable__colActions {
94+
width: 20%;
95+
}
96+
7497
.appTable_narrow .appTable__colActions {
7598
width: calc(3 * var(--default-grid-baseline) + 2 * var(--default-clickable-area));
7699
}

apps/appstore/src/components/AppTable/AppTableRow.vue

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,19 @@ import { t } from '@nextcloud/l10n'
1212
import { computed } from 'vue'
1313
import { useRoute } from 'vue-router'
1414
import NcButton from '@nextcloud/vue/components/NcButton'
15+
import NcChip from '@nextcloud/vue/components/NcChip'
1516
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
1617
import AppActions from '../AppActions.vue'
1718
import AppIcon from '../AppIcon.vue'
1819
import BadgeAppDaemon from '../BadgeAppDaemon.vue'
1920
import BadgeAppLevel from '../BadgeAppLevel.vue'
2021
import { useActions } from '../../composables/useActions.ts'
22+
import { useLimitedGroups } from '../../composables/useLimitedGroups.ts'
2123
2224
const { app, isNarrow } = defineProps<{
2325
app: IAppstoreApp | IAppstoreExApp
2426
isNarrow?: boolean
27+
isWide?: boolean
2528
}>()
2629
2730
const route = useRoute()
@@ -46,6 +49,7 @@ const detailsAction = computed<AppAction>(() => ({
4649
inline: false,
4750
}))
4851
52+
const groupsAppIsLimitedTo = useLimitedGroups(() => app)
4953
const rawActions = useActions(() => app)
5054
const actions = computed(() => [
5155
...rawActions.value,
@@ -80,6 +84,21 @@ const actions = computed(() => [
8084
<BadgeAppDaemon v-if="'daemon' in app && app.daemon" :daemon="app.daemon" />
8185
</div>
8286
</td>
87+
<td v-if="isWide">
88+
<ul
89+
v-if="groupsAppIsLimitedTo.length > 0"
90+
:class="$style.appTableRow__groupsCell"
91+
:title="groupsAppIsLimitedTo.map((group) => group.displayName).join(', ')">
92+
<template v-for="group, index in groupsAppIsLimitedTo" :key="group.id">
93+
<li v-if="index === 3" aria-hidden="true">
94+
95+
</li>
96+
<li :class="{ 'hidden-visually': index > 2 }">
97+
<NcChip :text="group.displayName" noClose />
98+
</li>
99+
</template>
100+
</ul>
101+
</td>
83102
<td>
84103
<div :class="$style.appTableRow__actionsCell">
85104
<AppActions
@@ -117,6 +136,11 @@ const actions = computed(() => [
117136
color: var(--color-text-maxcontrast);
118137
}
119138
139+
.appTableRow__groupsCell {
140+
display: flex;
141+
gap: var(--default-grid-baseline);
142+
}
143+
120144
.appTableRow__actionsCell {
121145
display: flex;
122146
gap: var(--default-grid-baseline);

apps/appstore/src/components/AppstoreSidebar/AppDetailsTab.vue

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
1616
import BadgeAppDaemon from '../BadgeAppDaemon.vue'
1717
import BadgeAppLevel from '../BadgeAppLevel.vue'
1818
import BadgeAppScore from '../BadgeAppScore.vue'
19+
import { useLimitedGroups } from '../../composables/useLimitedGroups.ts'
1920
import { useAppsStore } from '../../store/apps.ts'
2021
import { canLimitToGroups } from '../../utils/appStatus.ts'
2122
@@ -44,15 +45,8 @@ const appAuthors = computed(() => {
4445
.join(', ')
4546
})
4647
47-
const groupsAppIsLimitedTo = computed(() => {
48-
if (!app.groups) {
49-
return []
50-
}
51-
52-
return app.groups.map((group) => ({ id: group, name: group }))
53-
})
54-
5548
const appstoreUrl = computed(() => `https://apps.nextcloud.com/apps/${app.id}`)
49+
const groupsAppIsLimitedTo = useLimitedGroups(() => app)
5650
5751
/**
5852
* Further external resources (e.g. website)
@@ -162,7 +156,7 @@ function authorName(xmlNode): string {
162156
v-for="group of groupsAppIsLimitedTo"
163157
:key="group.id"
164158
:title="group.id">
165-
{{ group.name }}
159+
{{ group.displayName }}
166160
</li>
167161
</ul>
168162
</div>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { MaybeRefOrGetter } from 'vue'
7+
import type { IAppstoreApp, IAppstoreExApp } from '../apps.d.ts'
8+
9+
import { readonly, ref, toValue, watch } from 'vue'
10+
import { useGroupsStore } from '../store/groups.ts'
11+
12+
/**
13+
* Get the groups an app is limited to and keep it up to date
14+
*
15+
* @param app - The app to get the groups
16+
*/
17+
export function useLimitedGroups(app: MaybeRefOrGetter<IAppstoreApp | IAppstoreExApp>) {
18+
const groupsStore = useGroupsStore()
19+
const groupsAppIsLimitedTo = ref<{ id: string, displayName: string }[]>([])
20+
watch(() => toValue(app).groups, async () => {
21+
const groups = toValue(app).groups
22+
if (groups === undefined) {
23+
groupsAppIsLimitedTo.value = []
24+
return
25+
}
26+
27+
const promises = groups.map((group) => groupsStore.fetchGroupById(group))
28+
const results = await Promise.all(promises)
29+
groupsAppIsLimitedTo.value = results.filter(Boolean) as { id: string, displayName: string }[]
30+
}, { immediate: true })
31+
32+
return readonly(groupsAppIsLimitedTo)
33+
}

apps/appstore/src/store/groups.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,25 @@ import type { NcSelectUsersModel } from '@nextcloud/vue/components/NcSelectUsers
88

99
import axios from '@nextcloud/axios'
1010
import { generateOcsUrl } from '@nextcloud/router'
11+
import PQueue from 'p-queue'
1112
import { defineStore } from 'pinia'
1213
import { computed, ref } from 'vue'
1314
import logger from '../utils/logger.ts'
1415

16+
const queue = new PQueue({ concurrency: 3 })
17+
1518
export const useGroupsStore = defineStore('groups', () => {
1619
const groups = ref(new Map<string, NcSelectUsersModel>())
1720

21+
/**
22+
* Get group details by id
23+
*
24+
* @param groupId - The id of the group to fetch
25+
*/
26+
async function fetchGroupById(groupId: string) {
27+
return await queue.add(() => internalFetchGroupById(groupId))
28+
}
29+
1830
/**
1931
* Search the API for groups matching the query
2032
*
@@ -59,5 +71,18 @@ export const useGroupsStore = defineStore('groups', () => {
5971
groups: computed(() => Array.from(groups.value.values())),
6072
searchGroups,
6173
getGroupById,
74+
fetchGroupById,
75+
}
76+
77+
/**
78+
* Handle fetching group details by id
79+
*
80+
* @param groupId - The id of the group to fetch
81+
*/
82+
async function internalFetchGroupById(groupId: string) {
83+
if (!groups.value.has(groupId)) {
84+
await searchGroups(groupId)
85+
}
86+
return groups.value.get(groupId)
6287
}
6388
})

dist/AppstoreDiscover-BeCUy-2u.chunk.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

dist/appstore-appstore-main-eATUKVIF.chunk.css renamed to dist/appstore-appstore-main-fIugqNvM.chunk.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/appstore-main.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* extracted by css-entry-points-plugin */
2-
@import './appstore-appstore-main-eATUKVIF.chunk.css';
2+
@import './appstore-appstore-main-fIugqNvM.chunk.css';
33
@import './common-createElementId-DhjFt1I9-C_oBIsvc.chunk.css';
44
@import './common-logger-D3RVzcfQ-BE3lcFW4.chunk.css';
55
@import './common-NcModal-DyOZxq9E-BXyCVcuI.chunk.css';

0 commit comments

Comments
 (0)