mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
Merge pull request #60355 from nextcloud/feat/groups-column-appstore
feat(appstore): show new column with groups the app is limited to
This commit is contained in:
commit
f76278f8e3
10 changed files with 139 additions and 40 deletions
|
|
@ -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)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table ref="table" :class="[$style.appTable, { [$style.appTable_narrow]: isNarrow }]">
|
||||
<table
|
||||
ref="table"
|
||||
:class="[$style.appTable, {
|
||||
[$style.appTable_narrow]: isNarrow,
|
||||
[$style.appTable_wide]: isWide,
|
||||
}]">
|
||||
<colgroup>
|
||||
<col :class="$style.appTable__colName">
|
||||
<col :class="$style.appTable__colVersion">
|
||||
<col v-if="!isNarrow" :class="$style.appTable__colSupport">
|
||||
<col v-if="isWide" :class="$style.appTable__colGroups">
|
||||
<col :class="$style.appTable__colActions">
|
||||
</colgroup>
|
||||
<thead hidden>
|
||||
|
|
@ -36,6 +43,9 @@ const isNarrow = computed(() => tableWidth.value < 768)
|
|||
<th v-if="!isNarrow">
|
||||
{{ t('appstore', 'Support level') }}
|
||||
</th>
|
||||
<th v-if="isWide">
|
||||
{{ t('appstore', 'Groups') }}
|
||||
</th>
|
||||
<th>{{ t('appstore', 'Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -44,7 +54,8 @@ const isNarrow = computed(() => tableWidth.value < 768)
|
|||
v-for="app in apps"
|
||||
:key="app.id"
|
||||
:app
|
||||
:isNarrow />
|
||||
:isNarrow
|
||||
:isWide />
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<AppAction>(() => ({
|
|||
inline: false,
|
||||
}))
|
||||
|
||||
const groupsAppIsLimitedTo = useLimitedGroups(() => app)
|
||||
const rawActions = useActions(() => app)
|
||||
const actions = computed(() => [
|
||||
...rawActions.value,
|
||||
|
|
@ -80,6 +84,21 @@ const actions = computed(() => [
|
|||
<BadgeAppDaemon v-if="'daemon' in app && app.daemon" :daemon="app.daemon" />
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="isWide">
|
||||
<ul
|
||||
v-if="groupsAppIsLimitedTo.length > 0"
|
||||
:class="$style.appTableRow__groupsCell"
|
||||
:title="groupsAppIsLimitedTo.map((group) => group.displayName).join(', ')">
|
||||
<template v-for="group, index in groupsAppIsLimitedTo" :key="group.id">
|
||||
<li v-if="index === 3" aria-hidden="true">
|
||||
…
|
||||
</li>
|
||||
<li :class="{ 'hidden-visually': index > 2 }">
|
||||
<NcChip :text="group.displayName" noClose />
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<div :class="$style.appTableRow__actionsCell">
|
||||
<AppActions
|
||||
|
|
@ -117,6 +136,11 @@ 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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
</ul>
|
||||
</NcNoteCard>
|
||||
|
||||
<div v-if="groupsAppIsLimitedto.length" :class="$style.appstoreDetailsTab__section">
|
||||
<div v-if="groupsAppIsLimitedTo.length" :class="$style.appstoreDetailsTab__section">
|
||||
<h4 :id="idLimitedToGroups">
|
||||
{{ t('appstore', 'Limited to groups') }}
|
||||
</h4>
|
||||
<ul :aria-labelledby="idLimitedToGroups" :class="$style.appstoreDetailsTab__sectionDetails">
|
||||
<li
|
||||
v-for="group of groupsAppIsLimitedto"
|
||||
v-for="group of groupsAppIsLimitedTo"
|
||||
:key="group.id"
|
||||
:title="group.id">
|
||||
{{ group.name }}
|
||||
{{ group.displayName }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
33
apps/appstore/src/composables/useLimitedGroups.ts
Normal file
33
apps/appstore/src/composables/useLimitedGroups.ts
Normal file
|
|
@ -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<IAppstoreApp | IAppstoreExApp>) {
|
||||
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)
|
||||
}
|
||||
|
|
@ -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<string, NcSelectUsersModel>())
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
2
dist/AppstoreDiscover-C2ow8_w3.chunk.mjs
vendored
2
dist/AppstoreDiscover-C2ow8_w3.chunk.mjs
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
dist/appstore-main.css
vendored
2
dist/appstore-main.css
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/* extracted by css-entry-points-plugin */
|
||||
@import './appstore-appstore-main-eATUKVIF.chunk.css';
|
||||
@import './appstore-appstore-main-fIugqNvM.chunk.css';
|
||||
@import './common-Web-C_oBIsvc.chunk.css';
|
||||
@import './common-ArrowRight-D7L4ZBkR.chunk.css';
|
||||
@import './common-NcModal-kyWZ3UFC-CBh34man.chunk.css';
|
||||
|
|
|
|||
46
dist/appstore-main.mjs
vendored
46
dist/appstore-main.mjs
vendored
File diff suppressed because one or more lines are too long
2
dist/appstore-main.mjs.map
vendored
2
dist/appstore-main.mjs.map
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue