Refactor circle actions

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ (skjnldsv) 2021-04-27 19:38:41 +02:00
parent bb5f38e923
commit 9facd8fbf3
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
14 changed files with 405 additions and 166 deletions

View File

@ -36,34 +36,36 @@
</EmptyContent>
</AppContentDetails>
<!-- not a member -->
<AppContentDetails v-else-if="!circle.isMember">
<EmptyContent v-if="!loadingJoin" icon="icon-circles">
{{ t('contacts', 'You are not a member of this circle') }}
<!-- Only show the join button if the circle is accepting requests -->
<template v-if="circle.canJoin" #desc>
<button :disabled="loadingJoin" class="primary" @click="requestJoin">
{{ t('contacts', 'Request to join') }}
</button>
</template>
</EmptyContent>
<EmptyContent v-else-if="circle.isPendingJoin" icon="icon-loading">
{{ t('contacts', 'Your request to join this circle is pending approval') }}
</EmptyContent>
<EmptyContent v-else icon="icon-loading">
{{ t('contacts', 'Joining circle') }}
</EmptyContent>
</AppContentDetails>
<template v-else>
<!-- member list -->
<MemberList :list="members" />
<!-- main contacts details -->
<CircleDetails :circle-id="selectedCircle" />
<CircleDetails :circle="circle">
<!-- not a member -->
<template v-if="!circle.isMember">
<!-- Join request in progress -->
<EmptyContent v-if="loadingJoin" icon="icon-loading">
{{ t('contacts', 'Joining circle') }}
</EmptyContent>
<!-- Pending request validation -->
<EmptyContent v-else-if="circle.isPendingJoin" icon="icon-loading">
{{ t('contacts', 'Your request to join this circle is pending approval') }}
</EmptyContent>
<EmptyContent v-else icon="icon-circles">
{{ t('contacts', 'You are not a member of this circle') }}
<!-- Only show the join button if the circle is accepting requests -->
<template v-if="circle.canJoin" #desc>
<button :disabled="loadingJoin" class="primary" @click="requestJoin">
{{ t('contacts', 'Request to join') }}
</button>
</template>
</EmptyContent>
</template>
</CircleDetails>
</template>
</div>
</AppContent>

View File

@ -1,5 +1,5 @@
<!--
- @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
- @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
@ -25,7 +25,7 @@
:to="circle.router"
:title="circle.displayName"
:icon="circle.icon">
<template v-if="loading" slot="actions">
<template v-if="loadingAction" slot="actions">
<ActionText icon="icon-loading-small">
{{ t('contacts', 'Loading …') }}
</ActionText>
@ -50,7 +50,7 @@
<!-- leave circle -->
<ActionButton
v-if="circle.canLeave"
@click="leaveCircle">
@click="confirmLeaveCircle">
{{ t('contacts', 'Leave circle') }}
<ExitToApp slot="icon"
:size="16"
@ -71,7 +71,7 @@
<ActionButton
v-if="circle.canDelete"
icon="icon-delete"
@click="deleteCircle">
@click="confirmDeleteCircle">
{{ t('contacts', 'Delete') }}
</ActionButton>
</template>
@ -83,8 +83,6 @@
</template>
<script>
import { emit } from '@nextcloud/event-bus'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import ActionLink from '@nextcloud/vue/dist/Components/ActionLink'
import ActionText from '@nextcloud/vue/dist/Components/ActionText'
@ -93,10 +91,8 @@ import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
import ExitToApp from 'vue-material-design-icons/ExitToApp'
import LocationEnter from 'vue-material-design-icons/LocationEnter'
import { joinCircle } from '../../services/circles.ts'
import { showError } from '@nextcloud/dialogs'
import Circle from '../../models/circle.ts'
import CopyToClipboardMixin from '../../mixins/CopyToClipboardMixin'
import CircleActionsMixin from '../../mixins/CircleActionsMixin'
export default {
name: 'CircleNavigationItem',
@ -111,7 +107,7 @@ export default {
LocationEnter,
},
mixins: [CopyToClipboardMixin],
mixins: [CircleActionsMixin],
props: {
circle: {
@ -120,87 +116,10 @@ export default {
},
},
data() {
return {
loading: false,
}
},
computed: {
copyButtonText() {
if (this.copied) {
return this.copySuccess
? t('contacts', 'Copied')
: t('contacts', 'Could not copy')
}
return t('contacts', 'Copy link')
},
circleUrl() {
const route = this.$router.resolve(this.circle.router)
return window.location.origin + route.href
},
joinButtonTitle() {
if (this.circle.requireJoinAccept) {
return t('contacts', 'Request to join')
}
return t('contacts', 'Join circle')
},
memberCount() {
return Object.values(this.circle?.members || []).length
},
},
methods: {
// Trigger the entity picker view
async addMemberToCircle() {
await this.$router.push(this.circle.router)
emit('contacts:circles:append', this.circle.id)
},
async joinCircle() {
this.loading = true
try {
await joinCircle(this.circle.id)
} catch (error) {
showError(t('contacts', 'Unable to join the circle'))
} finally {
this.loading = false
}
},
async leaveCircle() {
this.loading = true
const member = this.circle.initiator
try {
await this.$store.dispatch('deleteMemberFromCircle', {
member,
leave: true,
})
} catch (error) {
console.error('Could not leave the circle', member, error)
showError(t('contacts', 'Could not leave the circle {displayName}', this.circle))
} finally {
this.loading = false
}
},
async deleteCircle() {
this.loading = true
try {
this.$store.dispatch('deleteCircle', this.circle.id)
} catch (error) {
showError(t('contacts', 'Unable to delete the circle'))
} finally {
this.loading = false
}
},
},
}
</script>

View File

@ -69,7 +69,7 @@
</AppNavigationCounter>
</AppNavigationItem>
<AppNavigationItem
<AppNavigationCaption
id="newgroup"
:force-menu="true"
:menu-open.sync="isNewGroupMenuOpen"
@ -85,7 +85,7 @@
:placeholder="t('contacts','Group name')"
@submit.prevent.stop="createNewGroup" />
</template>
</AppNavigationItem>
</AppNavigationCaption>
<!-- Custom groups -->
<GroupNavigationItem
@ -101,7 +101,7 @@
icon=""
@click="onToggleGroups" />
<AppNavigationItem
<AppNavigationCaption
id="newcircle"
:force-menu="true"
:menu-open.sync="isNewCircleMenuOpen"
@ -117,7 +117,7 @@
:placeholder="t('contacts','Circle name')"
@submit.prevent.stop="createNewCircle" />
</template>
</AppNavigationItem>
</AppNavigationCaption>
<!-- Circles -->
<CircleNavigationItem
@ -152,6 +152,7 @@ import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
import AppNavigationCounter from '@nextcloud/vue/dist/Components/AppNavigationCounter'
import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
import AppNavigationSettings from '@nextcloud/vue/dist/Components/AppNavigationSettings'
import AppNavigationCaption from '@nextcloud/vue/dist/Components/AppNavigationCaption'
import naturalCompare from 'string-natural-compare'
@ -171,6 +172,7 @@ export default {
AppNavigationCounter,
AppNavigationItem,
AppNavigationSettings,
AppNavigationCaption,
CircleNavigationItem,
GroupNavigationItem,
SettingsSection,

View File

@ -44,44 +44,101 @@
autocorrect="off"
spellcheck="false"
name="displayname"
@input="debounceUpdateCircle">
@input="onDisplayNameChangeDebounce">
<!-- org, title -->
<template #subtitle>
<template v-if="!circle.isOwner" #subtitle>
{{ t('contacts', 'Circle owned by {owner}', { owner: circle.owner.displayName}) }}
</template>
<!-- actions -->
<template #actions>
<Actions>
<!-- leave circle -->
<ActionButton
v-if="circle.canLeave"
@click="confirmLeaveCircle">
{{ t('contacts', 'Leave circle') }}
<ExitToApp slot="icon"
:size="16"
decorative />
</ActionButton>
<!-- join circle -->
<ActionButton
v-else-if="!circle.isMember && circle.canJoin"
@click="joinCircle">
{{ joinButtonTitle }}
<LocationEnter slot="icon"
:size="16"
decorative />
</ActionButton>
</Actions>
<Actions>
<!-- copy circle link -->
<ActionLink
:href="circleUrl"
:icon="copyLoading ? 'icon-loading-small' : 'icon-public'"
@click.stop.prevent="copyToClipboard(circleUrl)">
{{ copyButtonText }}
</ActionLink>
</Actions>
</template>
<!-- menu actions -->
<template #actions-menu>
<!-- delete circle -->
<ActionButton
v-if="circle.canDelete"
icon="icon-delete"
@click="confirmDeleteCircle">
{{ t('contacts', 'Delete') }}
</ActionButton>
</template>
</DetailsHeader>
<section class="circle-details-section">
<ContentHeading>{{ t('contacts', 'Description') }}</ContentHeading>
<ContentHeading :loading="loadingDescription">
{{ t('contacts', 'Description') }}
</ContentHeading>
<RichContenteditable class="circle-details-section__description"
:value="circle.description"
<RichContenteditable
:value.sync="circle.description"
:auto-complete="onAutocomplete"
:maxlength="1024"
:multiline="true"
:disabled="loading"
:placeholder="t('contacts', 'Enter a description for the circle')"
@submit="onDescriptionSubmit" />
:contenteditable="circle.isOwner"
:placeholder="descriptionPlaceholder"
class="circle-details-section__description"
@update:value="onDescriptionChangeDebounce" />
</section>
<section class="circle-details-section">
<section v-if="circle.isOwner" class="circle-details-section">
<CircleConfigs class="circle-details-section__configs" :circle="circle" />
</section>
<section v-else>
<slot />
</section>
</AppContentDetails>
</template>
<script>
import { showError } from '@nextcloud/dialogs'
import debounce from 'debounce'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import ActionLink from '@nextcloud/vue/dist/Components/ActionLink'
import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails'
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import RichContenteditable from '@nextcloud/vue/dist/Components/RichContenteditable'
import ExitToApp from 'vue-material-design-icons/ExitToApp'
import LocationEnter from 'vue-material-design-icons/LocationEnter'
import { CircleEdit, editCircle } from '../services/circles.ts'
import CircleActionsMixin from '../mixins/CircleActionsMixin'
import DetailsHeader from './DetailsHeader'
import CircleConfigs from './CircleDetails/CircleConfigs'
import ContentHeading from './CircleDetails/ContentHeading'
@ -90,26 +147,36 @@ export default {
name: 'CircleDetails',
components: {
ActionButton,
ActionLink,
Actions,
AppContentDetails,
Avatar,
CircleConfigs,
ContentHeading,
DetailsHeader,
ExitToApp,
LocationEnter,
RichContenteditable,
},
props: {
circleId: {
type: String,
required: true,
},
mixins: [CircleActionsMixin],
data() {
return {
loadingDescription: false,
}
},
computed: {
circle() {
return this.$store.getters.getCircle(this.circleId)
descriptionPlaceholder() {
if (this.circle.description.trim() === '') {
return t('contacts', 'There is no description for this circle')
}
return t('contacts', 'Enter a description for the circle')
},
},
methods: {
/**
* Autocomplete @mentions on the description
@ -122,8 +189,34 @@ export default {
callback([])
},
onDescriptionSubmit() {
console.info(...arguments)
onDescriptionChangeDebounce: debounce(function() {
this.onDescriptionChange(...arguments)
}, 500),
async onDescriptionChange(description) {
this.loadingDescription = true
try {
await editCircle(this.circle.id, CircleEdit.Description, description)
} catch (error) {
console.error('Unable to edit circle description', description, error)
showError(t('contacts', 'An error happened during description sync'))
} finally {
this.loadingDescription = false
}
},
onDisplayNameChangeDebounce: debounce(function() {
this.onDisplayNameChange(...arguments)
}, 500),
async onDisplayNameChange(description) {
this.loadingDescription = true
try {
await editCircle(this.circle.id, CircleEdit.Description, description)
} catch (error) {
console.error('Unable to edit circle description', description, error)
showError(t('contacts', 'An error happened during description sync'))
} finally {
this.loadingDescription = false
}
},
},
}
@ -133,17 +226,16 @@ export default {
.app-content-details {
flex: 1 1 100%;
min-width: 0;
padding: 0 80px;
}
.circle-details-section {
padding: 0 80px;
&:not(:first-of-type) {
margin-top: 24px;
}
&__description {
max-width: 400px;
max-width: 800px;
}
}
</style>

View File

@ -28,31 +28,34 @@
</ContentHeading>
<ul class="circle-config__list">
<CheckboxRadio v-for="(label, config) in configs"
<CheckboxRadioSwitch v-for="(label, config) in configs"
:key="'circle-config' + config"
:checked="isChecked(config)"
:loading="loading === config"
:disabled="loading !== false"
wrapper-element="li"
@update:checked="onChange(config, $event)">
{{ label }}
</CheckboxRadio>
</CheckboxRadioSwitch>
</ul>
</li>
</ul>
</template>
<script>
import CheckboxRadio from '@nextcloud/vue/dist/Components/CheckboxRadio'
import CheckboxRadioSwitch from '@nextcloud/vue/dist/Components/CheckboxRadioSwitch'
import ContentHeading from './ContentHeading'
import { PUBLIC_CIRCLE_CONFIG } from '../../models/constants.ts'
import Circle from '../../models/circle.ts'
import { CircleEdit, editCircle } from '../../services/circles'
import { CircleEdit, editCircle } from '../../services/circles.ts'
import { showError } from '@nextcloud/dialogs'
export default {
name: 'CircleConfigs',
components: {
CheckboxRadio,
CheckboxRadioSwitch,
ContentHeading,
},
@ -66,6 +69,8 @@ export default {
data() {
return {
PUBLIC_CIRCLE_CONFIG,
loading: false,
}
},
@ -80,21 +85,29 @@ export default {
* @param {boolean} checked checked or not
*/
async onChange(config, checked) {
console.debug('Circle config', `'${PUBLIC_CIRCLE_CONFIG[config]}'`, 'is set to', checked)
console.debug('Circle config', config, 'is set to', checked)
this.loading = config
const prevConfig = this.circle.config
if (checked) {
// eslint-disable-next-line vue/no-mutating-props
this.circle.config = prevConfig | config
config = prevConfig | config
} else {
// eslint-disable-next-line vue/no-mutating-props
this.circle.config = prevConfig & ~config
config = prevConfig & ~config
}
const data = await editCircle(this.circle.id, CircleEdit.Config, this.circle.config)
console.info(data)
try {
const circleData = await editCircle(this.circle.id, CircleEdit.Config, config)
// eslint-disable-next-line vue/no-mutating-props
this.circle.config = circleData.config
} catch (error) {
console.error('Unable to edit circle config', prevConfig, config, error)
showError(t('contacts', 'An error happened during the config change'))
} finally {
this.loading = false
}
},
},
}

View File

@ -23,17 +23,30 @@
<template>
<h3 class="app-content-heading">
<slot />
<div v-if="loading" class="app-content-heading__loader icon-loading-small" />
</h3>
</template>
<script>
export default {
name: 'ContentHeading',
props: {
loading: {
type: Boolean,
default: false,
},
},
}
</script>
<style lang="scss" scoped>
.app-content-heading {
font-weight: bold;
display: flex;
&__loader {
margin-left: 8px;
}
}
</style>

View File

@ -771,6 +771,7 @@ export default {
.app-content-details {
flex: 1 1 100%;
min-width: 0;
padding: 0 80px;
}
// List of all properties

View File

@ -32,7 +32,7 @@
<h2 class="contact-header__infos-title">
<slot name="title" />
</h2>
<div class="contact-header__infos-subtitle">
<div v-if="$slots.subtitle" class="contact-header__infos-subtitle">
<slot name="subtitle" />
</div>
</div>
@ -84,14 +84,12 @@ export default {
display: flex;
align-items: center;
padding: 50px 0 20px;
font-weight: bold;
&__avatar {
position: relative;
flex: 1 1 var(--avatar-size);
min-width: var(--avatar-size);
max-width: 120px;
flex: 0 0 var(--avatar-size);
margin: 10px;
margin-left: 0;
display: flex;
justify-content: flex-end;
}
@ -114,7 +112,7 @@ export default {
min-width: 100px;
max-width: 100%;
margin: 0;
padding: 4px 5px;
padding: 0;
white-space: nowrap;
text-overflow: ellipsis;
border: none;

View File

@ -165,7 +165,7 @@ export default {
// Object.keys returns those as string
.map(level => parseInt(level, 10))
// we cannot set to a level higher than the current user's level
.filter(level => level < this.currentUserLevel)
.filter(level => level <= this.currentUserLevel)
// we cannot set to the level this member is already
.filter(level => level !== this.source.level)
},
@ -209,6 +209,10 @@ export default {
* @returns {string}
*/
levelChangeLabel(level) {
if (level === MemberLevels.OWNER) {
return t('contacts', 'Promote as sole owner')
}
if (this.source.level < level) {
return t('contacts', 'Promote to {level}', { level: CIRCLES_MEMBER_LEVELS[level] })
}
@ -245,6 +249,13 @@ export default {
await changeMemberLevel(this.circle.id, this.source.id, level)
this.showLevelMenu = false
// If we changed an owner, let's refresh the whole dataset to update all ownership & memberships
if (level === MemberLevels.OWNER) {
await this.$store.dispatch('getCircle', this.circle.id)
await this.$store.dispatch('getCircleMembers', this.circle.id)
return
}
// this.source is a class. We're modifying the class setter, not the prop itself
// eslint-disable-next-line vue/no-mutating-props
this.source.level = level

View File

@ -0,0 +1,152 @@
/**
* @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { emit } from '@nextcloud/event-bus'
import { showError } from '@nextcloud/dialogs'
import { joinCircle } from '../services/circles.ts'
import Circle from '../models/circle.ts'
import CopyToClipboardMixin from './CopyToClipboardMixin'
export default {
props: {
circle: {
type: Circle,
required: true,
},
},
mixins: [CopyToClipboardMixin],
data() {
return {
loadingAction: false,
}
},
computed: {
copyButtonText() {
if (this.copied) {
return this.copySuccess
? t('contacts', 'Copied')
: t('contacts', 'Could not copy')
}
return t('contacts', 'Copy link')
},
circleUrl() {
const route = this.$router.resolve(this.circle.router)
return window.location.origin + route.href
},
joinButtonTitle() {
if (this.circle.requireJoinAccept) {
return t('contacts', 'Request to join')
}
return t('contacts', 'Join circle')
},
},
methods: {
confirmLeaveCircle() {
OC.dialogs.confirmDestructive(
t('contacts', 'You are about to leave {circle}.\n Are you sure ?', {
circle: this.circle.displayName,
}),
t('contacts', 'Please confirm circle leave'),
OC.dialogs.YES_NO_BUTTONS,
this.leaveCircle,
true
)
},
async leaveCircle(confirm) {
if (!confirm) {
console.debug('Circle leave cancelled')
return
}
this.loadingAction = true
const member = this.circle.initiator
try {
await this.$store.dispatch('deleteMemberFromCircle', {
member,
leave: true,
})
} catch (error) {
console.error('Could not leave the circle', member, error)
showError(t('contacts', 'Could not leave the circle {displayName}', this.circle))
} finally {
this.loadingAction = false
}
},
async joinCircle() {
this.loadingAction = true
try {
await joinCircle(this.circle.id)
} catch (error) {
showError(t('contacts', 'Unable to join the circle'))
} finally {
this.loadingAction = false
}
},
confirmDeleteCircle() {
OC.dialogs.confirmDestructive(
t('contacts', 'You are about to delete {circle}.\n Are you sure ?', {
circle: this.circle.displayName,
}),
t('contacts', 'Please confirm circle deletion'),
OC.dialogs.YES_NO_BUTTONS,
this.deleteCircle,
true
)
},
async deleteCircle(confirm) {
if (!confirm) {
console.debug('Circle deletion cancelled')
return
}
this.loadingAction = true
try {
this.$store.dispatch('deleteCircle', this.circle.id)
} catch (error) {
showError(t('contacts', 'Unable to delete the circle'))
} finally {
this.loadingAction = false
}
},
/**
* Trigger the entity picker view
*/
async addMemberToCircle() {
await this.$router.push(this.circle.router)
emit('contacts:circles:append', this.circle.id)
},
},
}

View File

@ -96,7 +96,7 @@ export const PUBLIC_CIRCLE_CONFIG = {
[t('contacts', 'Invites')]: {
[CIRCLE_CONFIG_OPEN]: t('contacts', 'Open, anyone can join'),
[CIRCLE_CONFIG_INVITE]: t('contacts', 'Members need to accept invitation'),
[CIRCLE_CONFIG_REQUEST]: t('contacts', 'Members need to be accepted by a moderator'),
[CIRCLE_CONFIG_REQUEST]: t('contacts', 'Members need to be accepted by a moderator (requires Open)'),
[CIRCLE_CONFIG_FRIEND]: t('contacts', 'Members can also invite'),
// Let's manage password protection independently as we also need a password
// [CIRCLE_CONFIG_PROTECTED]: t('contacts', 'Password protect'),
@ -104,11 +104,11 @@ export const PUBLIC_CIRCLE_CONFIG = {
[t('contacts', 'Visibility')]: {
[CIRCLE_CONFIG_VISIBLE]: t('contacts', 'Visible to everyone'),
[CIRCLE_CONFIG_HIDDEN]: t('contacts', 'Hide this circle from listings'),
},
[t('contacts', 'Circle membership')]: {
[CIRCLE_CONFIG_CIRCLE_INVITE]: t('contacts', 'Circle must confirm when invited in another circle'),
// TODO: implement backend
// [CIRCLE_CONFIG_CIRCLE_INVITE]: t('contacts', 'Circle must confirm when invited in another circle'),
[CIRCLE_CONFIG_ROOT]: t('contacts', 'Prevent circle from being a member of another circle'),
},
}

View File

@ -37,6 +37,12 @@ export declare enum CircleEdit {
* @returns {Array}
*/
export declare const getCircles: () => Promise<any>;
/**
* Get a specific circle
* @param {string} circleId
* @returns {Object}
*/
export declare const getCircle: (circleId: string) => Promise<any>;
/**
* Create a new circle
*
@ -47,14 +53,14 @@ export declare const createCircle: (name: string) => Promise<any>;
/**
* Delete an existing circle
*
* @param {string} circleId the circle name
* @param {string} circleId the circle id
* @returns {Object}
*/
export declare const deleteCircle: (circleId: string) => Promise<any>;
/**
* Edit an existing circle
*
* @param {string} circleId the circle name
* @param {string} circleId the circle id
* @param {CircleEditType} type the edit type
* @param {any} data the data
* @returns {Object}
@ -63,14 +69,14 @@ export declare const editCircle: (circleId: string, type: CircleEditType, value:
/**
* Join a circle
*
* @param {string} circleId the circle name
* @param {string} circleId the circle id
* @returns {Array}
*/
export declare const joinCircle: (circleId: string) => Promise<any>;
/**
* Leave a circle
*
* @param {string} circleId the circle name
* @param {string} circleId the circle id
* @returns {Array}
*/
export declare const leaveCircle: (circleId: string) => Promise<any>;

View File

@ -46,6 +46,16 @@ export const getCircles = async function() {
return response.data.ocs.data
}
/**
* Get a specific circle
* @param {string} circleId
* @returns {Object}
*/
export const getCircle = async function(circleId: string) {
const response = await axios.get(generateOcsUrl('apps/circles/circles/{circleId}', { circleId }))
return response.data.ocs.data
}
/**
* Create a new circle
*
@ -62,7 +72,7 @@ export const createCircle = async function(name: string) {
/**
* Delete an existing circle
*
* @param {string} circleId the circle name
* @param {string} circleId the circle id
* @returns {Object}
*/
export const deleteCircle = async function(circleId: string) {
@ -73,7 +83,7 @@ export const deleteCircle = async function(circleId: string) {
/**
* Edit an existing circle
*
* @param {string} circleId the circle name
* @param {string} circleId the circle id
* @param {CircleEditType} type the edit type
* @param {any} data the data
* @returns {Object}
@ -86,7 +96,7 @@ export const editCircle = async function(circleId: string, type: CircleEditType,
/**
* Join a circle
*
* @param {string} circleId the circle name
* @param {string} circleId the circle id
* @returns {Array}
*/
export const joinCircle = async function(circleId: string) {
@ -97,7 +107,7 @@ export const joinCircle = async function(circleId: string) {
/**
* Leave a circle
*
* @param {string} circleId the circle name
* @param {string} circleId the circle id
* @returns {Array}
*/
export const leaveCircle = async function(circleId: string) {

View File

@ -23,7 +23,7 @@
import { showError } from '@nextcloud/dialogs'
import Vue from 'vue'
import { createCircle, deleteCircle, deleteMember, getCircleMembers, getCircles, leaveCircle, addMembers } from '../services/circles.ts'
import { createCircle, deleteCircle, deleteMember, getCircleMembers, getCircle, getCircles, leaveCircle, addMembers } from '../services/circles.ts'
import Member from '../models/member.ts'
import Circle from '../models/circle.ts'
@ -102,7 +102,6 @@ const getters = {
}
const actions = {
/**
* Retrieve and commit circles
*
@ -131,6 +130,27 @@ const actions = {
return circles
},
/**
* Retrieve and commit circles
*
* @param {Object} context the store mutations
* @param {string} circleId the circle id
* @returns {Object[]} the circles
*/
async getCircle(context, circleId) {
const circle = await getCircle(circleId)
console.debug('Retrieved 1 circle', circle)
try {
const newCircle = new Circle(circle)
context.commit('addCircle', newCircle)
} catch (error) {
console.error('This circle failed to be processed', circle, error)
}
return circle
},
/**
* Retrieve and commit circle members
*