Merge pull request #5770 from nextcloud-libraries/backport/5757/next
[next] feat: migrate `NcCollectionList` component from `nextcloud-vue-collections`
This commit is contained in:
Коммит
a7a1c028a5
|
@ -27,6 +27,9 @@ msgstr ""
|
|||
msgid "Activities"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add to a project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Animals & Nature"
|
||||
msgstr ""
|
||||
|
||||
|
@ -102,6 +105,9 @@ msgstr ""
|
|||
msgid "Confirm changes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Connect items to a project to make them easier to find"
|
||||
msgstr ""
|
||||
|
||||
msgid "Custom"
|
||||
msgstr ""
|
||||
|
||||
|
@ -136,6 +142,15 @@ msgstr ""
|
|||
msgid "External documentation for {name}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to add the item to the project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to create a project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to rename the project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
|
@ -161,6 +176,9 @@ msgstr ""
|
|||
msgid "Gold"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide password"
|
||||
msgstr ""
|
||||
|
||||
|
@ -295,6 +313,9 @@ msgstr ""
|
|||
msgid "Related team resources"
|
||||
msgstr ""
|
||||
|
||||
msgid "Rename project"
|
||||
msgstr ""
|
||||
|
||||
#. TRANSLATORS: A color name for RGB(191, 103, 139)
|
||||
msgid "Rosy brown"
|
||||
msgstr ""
|
||||
|
@ -337,6 +358,9 @@ msgstr ""
|
|||
msgid "Settings navigation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show password"
|
||||
msgstr ""
|
||||
|
||||
|
@ -370,6 +394,9 @@ msgstr ""
|
|||
msgid "Travel & Places"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type to search for existing projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type to search time zone"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<docs>
|
||||
Provides a Vue standalone component for Nextcloud Projects feature introduced in Nextcloud 16. Replaces deprecated `nextcloud-vue-collections` library.
|
||||
|
||||
Projects feature is deprecated since Nextcloud 25, and superseded by Related resources. See [NcRelatedResourcesPanel](#/Components/NcRelatedResourcesPanel) documentation for more information.
|
||||
|
||||
### Usage
|
||||
|
||||
To enable feature in Nextcloud, run following command:
|
||||
```sh
|
||||
occ config:system:set --value true 'projects.enabled'
|
||||
```
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<ul v-if="collections && type && id" id="collection-list" class="collection-list">
|
||||
<li @click="showSelect">
|
||||
<div class="avatar">
|
||||
<span class="icon-projects" />
|
||||
</div>
|
||||
<div id="collection-select-container">
|
||||
<NcSelect ref="select"
|
||||
v-model="value"
|
||||
:aria-label-combobox="t('Add to a project')"
|
||||
:options="options"
|
||||
:placeholder="placeholder"
|
||||
label="title"
|
||||
:limit="5"
|
||||
@close="isSelectOpen = false"
|
||||
@open="isSelectOpen = true"
|
||||
@option:selected="select"
|
||||
@search="search">
|
||||
<template #selected-option="option">
|
||||
<span class="option__desc">
|
||||
<span class="option__title">{{ option.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template #option="option">
|
||||
<span class="option__wrapper">
|
||||
<span v-if="option.class" :class="option.class" class="avatar" />
|
||||
<NcAvatar v-else-if="option.method !== 2" allow-placeholder :display-name="option.title" />
|
||||
<span class="option__title">{{ option.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<p class="hint">
|
||||
{{ t('Connect items to a project to make them easier to find') }}
|
||||
</p>
|
||||
</NcSelect>
|
||||
</div>
|
||||
</li>
|
||||
<transition name="fade">
|
||||
<li v-if="error" class="error">
|
||||
{{ error }}
|
||||
</li>
|
||||
</transition>
|
||||
<NcCollectionListItem v-for="collection in collections"
|
||||
:key="collection.id"
|
||||
:collection="collection"
|
||||
:error="collectionsError[collection.id]"
|
||||
@rename-collection="renameCollectionFromItem"
|
||||
@remove-resource="removeResourceFromCollection" />
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'debounce'
|
||||
import { ref } from 'vue'
|
||||
import { t } from '../../l10n.js'
|
||||
|
||||
import NcAvatar from '../NcAvatar/index.js'
|
||||
import NcSelect from '../NcSelect/index.js'
|
||||
import NcCollectionListItem from './NcCollectionListItem.vue'
|
||||
|
||||
import { useCollections } from './useCollections.js'
|
||||
import { searchService } from './service.ts'
|
||||
|
||||
const METHOD_CREATE_COLLECTION = 0
|
||||
const METHOD_ADD_TO_COLLECTION = 1
|
||||
|
||||
export default {
|
||||
name: 'NcCollectionList',
|
||||
|
||||
components: {
|
||||
NcCollectionListItem,
|
||||
NcAvatar,
|
||||
NcSelect,
|
||||
},
|
||||
|
||||
props: {
|
||||
/**
|
||||
* Resource type identifier
|
||||
*/
|
||||
type: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
/**
|
||||
* Unique id of the resource
|
||||
*/
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
/**
|
||||
* Name of the resource
|
||||
*/
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* Whether the component is active (to start fetch resources)
|
||||
*/
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const {
|
||||
storedCollections,
|
||||
fetchCollectionsByResource,
|
||||
createCollection,
|
||||
addResourceToCollection,
|
||||
removeResourceFromCollection,
|
||||
renameCollection,
|
||||
} = useCollections()
|
||||
|
||||
const searchCollections = ref([])
|
||||
const search = debounce(function(query, loading) {
|
||||
if (query !== '') {
|
||||
loading(true)
|
||||
searchService(query).then(collections => {
|
||||
searchCollections.value = collections
|
||||
}).catch(e => {
|
||||
console.error('Failed to search for collections', e)
|
||||
}).finally(() => {
|
||||
loading(false)
|
||||
})
|
||||
}
|
||||
}, 500)
|
||||
|
||||
return {
|
||||
storedCollections,
|
||||
fetchCollectionsByResource,
|
||||
createCollection,
|
||||
addResourceToCollection,
|
||||
removeResourceFromCollection,
|
||||
renameCollection,
|
||||
searchCollections,
|
||||
search,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectIsOpen: false,
|
||||
generatingCodes: false,
|
||||
codes: undefined,
|
||||
value: null,
|
||||
model: {},
|
||||
collectionsError: {},
|
||||
error: null,
|
||||
isSelectOpen: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
collections() {
|
||||
return this.storedCollections.filter(collection => collection.resources
|
||||
.some(resource => resource && resource.id === String(this.id) && resource.type === this.type),
|
||||
)
|
||||
},
|
||||
|
||||
placeholder() {
|
||||
return this.isSelectOpen
|
||||
? t('Type to search for existing projects')
|
||||
: t('Add to a project')
|
||||
},
|
||||
|
||||
options() {
|
||||
const options = []
|
||||
window.OCP.Collaboration.getTypes().sort().forEach(type => {
|
||||
options.push({
|
||||
method: METHOD_CREATE_COLLECTION,
|
||||
type,
|
||||
title: window.OCP.Collaboration.getLabel(type),
|
||||
class: window.OCP.Collaboration.getIcon(type),
|
||||
action: () => window.OCP.Collaboration.trigger(type),
|
||||
})
|
||||
})
|
||||
for (const index in this.searchCollections) {
|
||||
if (!this.collections.find(collection => collection.id === this.searchCollections[index].id)) {
|
||||
options.push({
|
||||
method: METHOD_ADD_TO_COLLECTION,
|
||||
title: this.searchCollections[index].name,
|
||||
collectionId: this.searchCollections[index].id,
|
||||
})
|
||||
}
|
||||
}
|
||||
return options
|
||||
},
|
||||
|
||||
resourceIdentifier() {
|
||||
return {
|
||||
resourceType: this.type,
|
||||
resourceId: this.id,
|
||||
isActive: this.isActive,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
resourceIdentifier: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler(resourceIdentifier) {
|
||||
if (!resourceIdentifier.isActive || !resourceIdentifier.resourceId || !resourceIdentifier.resourceType) {
|
||||
return
|
||||
}
|
||||
this.fetchCollectionsByResource(resourceIdentifier)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
t,
|
||||
|
||||
select(selectedOption) {
|
||||
if (selectedOption.method === METHOD_CREATE_COLLECTION) {
|
||||
selectedOption.action().then(resourceId => {
|
||||
this.createCollection({
|
||||
baseResourceType: this.type,
|
||||
baseResourceId: this.id,
|
||||
resourceType: selectedOption.type,
|
||||
resourceId,
|
||||
name: this.name,
|
||||
}).catch((e) => {
|
||||
this.setError(t('Failed to create a project'), e)
|
||||
})
|
||||
}).catch((e) => {
|
||||
console.error('No resource selected', e)
|
||||
})
|
||||
}
|
||||
|
||||
if (selectedOption.method === METHOD_ADD_TO_COLLECTION) {
|
||||
this.addResourceToCollection({
|
||||
collectionId: selectedOption.collectionId, resourceType: this.type, resourceId: this.id,
|
||||
}).catch((e) => {
|
||||
this.setError(t('Failed to add the item to the project'), e)
|
||||
})
|
||||
}
|
||||
|
||||
this.value = null
|
||||
},
|
||||
|
||||
showSelect() {
|
||||
this.selectIsOpen = true
|
||||
this.$refs.select.$el.focus()
|
||||
},
|
||||
|
||||
setError(error, e) {
|
||||
console.error(error, e)
|
||||
this.error = error
|
||||
setTimeout(() => {
|
||||
this.error = null
|
||||
}, 5000)
|
||||
},
|
||||
|
||||
renameCollectionFromItem({ collectionId, name }) {
|
||||
this.renameCollection({ collectionId, name })
|
||||
.catch((e) => {
|
||||
console.error(t('Failed to rename the project'), e)
|
||||
this.collectionsError[collectionId] = t('Failed to rename the project')
|
||||
setTimeout(() => {
|
||||
this.collectionsError[collectionId] = null
|
||||
}, 5000)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.collection-list * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.collection-list > li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
& > .avatar {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#collection-select-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.v-select {
|
||||
// NcAvatar in the dropdown
|
||||
span.avatar {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
opacity: .7;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.hint {
|
||||
z-index: 1;
|
||||
// fix alignment
|
||||
margin-top: -16px;
|
||||
padding: 8px 8px;
|
||||
color: var(--color-text-maxcontrast);
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
div.avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
background-color: var(--color-background-dark);
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
/** TODO provide white icon in core */
|
||||
.icon-projects {
|
||||
display: block;
|
||||
padding: 8px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.option__wrapper {
|
||||
display: flex;
|
||||
|
||||
.avatar {
|
||||
display: block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: var(--color-background-darker) !important;
|
||||
}
|
||||
|
||||
.option__title {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,326 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<li class="collection-list-item">
|
||||
<NcAvatar :display-name="collection.name" allow-placeholder class="collection-avatar" />
|
||||
<span v-if="newName === null"
|
||||
class="collection-item-name"
|
||||
title=""
|
||||
@click="showDetails">{{ collection.name }}</span>
|
||||
<form v-else :class="{'should-shake': error }" @submit.prevent="renameCollection">
|
||||
<input v-model="newName"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
autocapitalize="off">
|
||||
<input type="submit" value="" class="icon-confirm">
|
||||
</form>
|
||||
<div v-if="!detailsOpen && newName === null" class="linked-icons">
|
||||
<component :is="getComponent(resource).component"
|
||||
v-for="resource in resources.slice(0, 2)"
|
||||
:key="resource.type + '|' + resource.id"
|
||||
:title="resource.name"
|
||||
:to="getComponent(resource).to"
|
||||
:href="getComponent(resource).href"
|
||||
:class="typeClass(resource)">
|
||||
<img :src="iconUrl(resource)" :alt="resource.name">
|
||||
</component>
|
||||
</div>
|
||||
|
||||
<span v-if="newName === null" class="sharingOptionsGroup">
|
||||
<NcActions>
|
||||
<NcActionButton icon="icon-info"
|
||||
@click.prevent="toggleDetails">
|
||||
{{ detailsOpen ? t('Hide details') : t('Show details') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton icon="icon-rename"
|
||||
@click.prevent="openRename">
|
||||
{{ t('Rename project') }}
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</span>
|
||||
|
||||
<transition name="fade">
|
||||
<div v-if="error" class="error">
|
||||
{{ error }}
|
||||
</div>
|
||||
</transition>
|
||||
<transition name="fade">
|
||||
<ul v-if="detailsOpen" class="resource-list-details">
|
||||
<li v-for="resource in resources"
|
||||
:key="resource.type + '|' + resource.id"
|
||||
:class="typeClass(resource)">
|
||||
<component :is="getComponent(resource).component"
|
||||
:to="getComponent(resource).to"
|
||||
:href="getComponent(resource).href">
|
||||
<img :src="iconUrl(resource)" :alt="resource.name">
|
||||
<span class="resource-name">{{ resource.name || '' }}</span>
|
||||
</component>
|
||||
<span class="icon-close" @click="removeResource(collection, resource)" />
|
||||
</li>
|
||||
</ul>
|
||||
</transition>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { t } from '../../l10n.js'
|
||||
import { getRoute } from '../NcRichText/autolink.ts'
|
||||
|
||||
import NcActions from '../NcActions/index.js'
|
||||
import NcActionButton from '../NcActionButton/index.js'
|
||||
import NcAvatar from '../NcAvatar/index.js'
|
||||
|
||||
export default {
|
||||
name: 'NcCollectionListItem',
|
||||
|
||||
components: {
|
||||
NcAvatar,
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
collection: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
|
||||
error: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['remove-resource', 'rename-collection'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
detailsOpen: false,
|
||||
newName: null,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
getIcon() {
|
||||
return (resource) => [resource.iconClass]
|
||||
},
|
||||
|
||||
typeClass() {
|
||||
return (resource) => 'resource-type-' + resource.type
|
||||
},
|
||||
|
||||
resources() {
|
||||
// invalid resources come from server as empty array ([]) and not an object
|
||||
return this.collection.resources?.filter(resource => !Array.isArray(resource)) ?? []
|
||||
},
|
||||
|
||||
getComponent() {
|
||||
return (resource) => {
|
||||
const route = getRoute(this.$router, resource.link)
|
||||
|
||||
return route
|
||||
? { component: 'router-link', to: route, href: undefined }
|
||||
: { component: 'a', to: undefined, href: resource.link }
|
||||
}
|
||||
},
|
||||
|
||||
iconUrl() {
|
||||
return (resource) => {
|
||||
if (resource.mimetype) {
|
||||
return OC.MimeType.getIconUrl(resource.mimetype)
|
||||
}
|
||||
if (resource.iconUrl) {
|
||||
return resource.iconUrl
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
t,
|
||||
|
||||
toggleDetails() {
|
||||
this.detailsOpen = !this.detailsOpen
|
||||
},
|
||||
|
||||
showDetails() {
|
||||
this.detailsOpen = true
|
||||
},
|
||||
|
||||
removeResource(collection, resource) {
|
||||
this.$emit('remove-resource', {
|
||||
collectionId: collection.id,
|
||||
resourceType: resource.type,
|
||||
resourceId: resource.id,
|
||||
})
|
||||
},
|
||||
|
||||
openRename() {
|
||||
this.newName = this.collection.name
|
||||
},
|
||||
|
||||
renameCollection() {
|
||||
if (this.newName) {
|
||||
this.$emit('rename-collection', {
|
||||
collectionId: this.collection.id,
|
||||
name: this.newName,
|
||||
})
|
||||
}
|
||||
this.newName = null
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .3s ease;
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-to
|
||||
/* .fade-leave-active below version 2.1.8 */
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.linked-icons {
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
padding: 12px;
|
||||
height: 44px;
|
||||
display: block;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popovermenu {
|
||||
display: none;
|
||||
|
||||
&.open {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
li.collection-list-item {
|
||||
flex-wrap: wrap;
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
margin-bottom: 0 !important;
|
||||
|
||||
.collection-avatar {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
form, .collection-item-name {
|
||||
flex-basis: 10%;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.collection-item-name {
|
||||
padding: 12px 9px;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-top: 4px;
|
||||
border-color: var(--color-border-maxcontrast);
|
||||
|
||||
&[type=text] {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
flex-basis: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.resource-list-details {
|
||||
flex-basis: 100%;
|
||||
width: 100%;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
margin-left: 44px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-dark);
|
||||
}
|
||||
|
||||
a {
|
||||
flex-grow: 1;
|
||||
padding: 3px;
|
||||
max-width: calc(100% - 30px);
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
span.resource-name {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
flex-grow: 1;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.icon-close {
|
||||
opacity: .7;
|
||||
|
||||
&:hover, &:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.should-shake {
|
||||
animation: shake 0.6s 1 linear;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0% {
|
||||
transform: translate(15px);
|
||||
}
|
||||
20% {
|
||||
transform: translate(-15px);
|
||||
}
|
||||
40% {
|
||||
transform: translate(7px);
|
||||
}
|
||||
60% {
|
||||
transform: translate(-7px);
|
||||
}
|
||||
80% {
|
||||
transform: translate(3px);
|
||||
}
|
||||
100% {
|
||||
transform: translate(0px);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
export { default } from './NcCollectionList.vue'
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import axios, { type AxiosResponse } from '@nextcloud/axios'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import type { OCSResponse } from '@nextcloud/typings/ocs'
|
||||
|
||||
/**
|
||||
* Extracts the OCS data from a response
|
||||
* @param response OCS response
|
||||
*/
|
||||
function extractOcsData(response: AxiosResponse<OCSResponse>) {
|
||||
return response.data.ocs.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all collections
|
||||
* @param collectionId Collection ID
|
||||
*/
|
||||
export function listCollectionService(collectionId: number) {
|
||||
return axios.get(generateOcsUrl('collaboration/resources/collections/{collectionId}', { collectionId })).then(extractOcsData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames a collection
|
||||
* @param collectionId Collection ID
|
||||
* @param collectionName New collection name
|
||||
*/
|
||||
export function renameCollectionService(collectionId: number, collectionName: string) {
|
||||
return axios.put(generateOcsUrl('collaboration/resources/collections/{collectionId}', { collectionId }), { collectionName }).then(extractOcsData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all collections for a resource
|
||||
* @param resourceType Resource type
|
||||
* @param resourceId Resource ID
|
||||
*/
|
||||
export function getCollectionsByResourceService(resourceType: string, resourceId: string) {
|
||||
return axios.get(generateOcsUrl('collaboration/resources/{resourceType}/{resourceId}', { resourceType, resourceId })).then(extractOcsData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a collection
|
||||
* @param resourceType Resource type
|
||||
* @param resourceId Resource ID
|
||||
* @param name Collection name
|
||||
*/
|
||||
export function createCollectionService(resourceType: string, resourceId: string, name: string) {
|
||||
return axios.post(generateOcsUrl('collaboration/resources/{resourceType}/{resourceId}', { resourceType, resourceId }), { name }).then(extractOcsData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a resource to a collection
|
||||
* @param collectionId Collection ID
|
||||
* @param resourceType Resource type
|
||||
* @param resourceId Resource ID
|
||||
*/
|
||||
export function addResourceService(collectionId: number, resourceType: string, resourceId: string) {
|
||||
return axios.post(generateOcsUrl('collaboration/resources/collections/{collectionId}', { collectionId }), { resourceType, resourceId }).then(extractOcsData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a resource from a collection
|
||||
* @param collectionId Collection ID
|
||||
* @param resourceType Resource type
|
||||
* @param resourceId Resource ID
|
||||
*/
|
||||
export function removeResourceService(collectionId: number, resourceType: string, resourceId: string) {
|
||||
return axios.delete(generateOcsUrl('collaboration/resources/collections/{collectionId}', { collectionId }), { params: { resourceType, resourceId } }).then(extractOcsData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for collections
|
||||
* @param query Search query
|
||||
*/
|
||||
export function searchService(query: string) {
|
||||
return axios.get(generateOcsUrl('collaboration/resources/collections/search/{query}', { query })).then(extractOcsData)
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
renameCollectionService,
|
||||
getCollectionsByResourceService,
|
||||
createCollectionService,
|
||||
addResourceService,
|
||||
removeResourceService,
|
||||
} from './service.ts'
|
||||
|
||||
/**
|
||||
* Use collections composable
|
||||
*/
|
||||
export function useCollections() {
|
||||
// State
|
||||
const storedCollections = ref([])
|
||||
|
||||
// Mutations
|
||||
const addCollections = (collections) => {
|
||||
storedCollections.value = collections
|
||||
}
|
||||
|
||||
const addCollection = (collection) => {
|
||||
storedCollections.value.push(collection)
|
||||
}
|
||||
|
||||
const removeCollection = (collectionId) => {
|
||||
storedCollections.value = storedCollections.value.filter(item => item.id !== collectionId)
|
||||
}
|
||||
|
||||
const updateCollection = (collection) => {
|
||||
const index = storedCollections.value.findIndex(item => item.id === collection.id)
|
||||
if (index !== -1) {
|
||||
storedCollections.value[index] = collection
|
||||
} else {
|
||||
addCollection(collection)
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
const fetchCollectionsByResource = async ({ resourceType, resourceId }) => {
|
||||
const collections = await getCollectionsByResourceService(resourceType, resourceId)
|
||||
|
||||
addCollections(collections)
|
||||
}
|
||||
|
||||
const createCollection = async ({ baseResourceType, baseResourceId, resourceType, resourceId, name }) => {
|
||||
const collection = await createCollectionService(baseResourceType, baseResourceId, name)
|
||||
|
||||
addCollection(collection)
|
||||
await addResourceToCollection({
|
||||
collectionId: collection.id,
|
||||
resourceType,
|
||||
resourceId,
|
||||
})
|
||||
}
|
||||
|
||||
const renameCollection = async ({ collectionId, name }) => {
|
||||
const collection = await renameCollectionService(collectionId, name)
|
||||
|
||||
updateCollection(collection)
|
||||
}
|
||||
|
||||
const addResourceToCollection = async ({ collectionId, resourceType, resourceId }) => {
|
||||
const collection = await addResourceService(collectionId, resourceType, String(resourceId))
|
||||
|
||||
updateCollection(collection)
|
||||
}
|
||||
|
||||
const removeResourceFromCollection = async ({ collectionId, resourceType, resourceId }) => {
|
||||
const collection = await removeResourceService(collectionId, resourceType, String(resourceId))
|
||||
|
||||
if (collection.resources.length > 0) {
|
||||
updateCollection(collection)
|
||||
} else {
|
||||
removeCollection(collectionId)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
storedCollections,
|
||||
fetchCollectionsByResource,
|
||||
createCollection,
|
||||
renameCollection,
|
||||
addResourceToCollection,
|
||||
removeResourceFromCollection,
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ export { default as NcBreadcrumbs } from './NcBreadcrumbs/index.js'
|
|||
export { default as NcButton } from './NcButton/index'
|
||||
export { default as NcCheckboxRadioSwitch } from './NcCheckboxRadioSwitch/index.js'
|
||||
export { default as NcChip } from './NcChip/index'
|
||||
export { default as NcCollectionList } from './NcCollectionList/index.js'
|
||||
export { default as NcColorPicker } from './NcColorPicker/index.js'
|
||||
export { default as NcContent } from './NcContent/index.js'
|
||||
export { default as NcCounterBubble } from './NcCounterBubble/index.js'
|
||||
|
|
|
@ -120,6 +120,7 @@ module.exports = async () => {
|
|||
'src/components/NcAppSidebar*/*.vue',
|
||||
'src/components/NcBreadcrumb*/*.vue',
|
||||
'src/components/NcCheckboxRadioSwitch/NcCheckboxContent.vue',
|
||||
'src/components/NcCollectionList/!(NcCollectionList).vue',
|
||||
'src/components/NcContent/*.vue',
|
||||
'src/components/NcDashboard*/*.vue',
|
||||
'src/components/NcDialog*/*.vue',
|
||||
|
|
Загрузка…
Ссылка в новой задаче