Skip to content

Commit 8860058

Browse files
committed
feat(appstore): show new column with groups the app is limited to
- resolves: #30503 If there is enough space we can directly show the groups this app is limited to in the table. This is especially helpful if you want to quickly check your enabled apps. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 6879d86 commit 8860058

5 files changed

Lines changed: 110 additions & 11 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
})

0 commit comments

Comments
 (0)