From d91b271e3b8b6adad7f24b5003def205a163ade4 Mon Sep 17 00:00:00 2001 From: Janina Hiob Date: Tue, 28 Jul 2026 08:53:06 +0200 Subject: [PATCH] feat: removed teams UI Signed-off-by: Janina Hiob --- src/components/AppContent/CircleContent.vue | 168 --- .../AppNavigation/CircleNavigationItem.vue | 137 -- .../AppNavigation/RootNavigation.vue | 164 +-- src/components/CircleDetails.vue | 1260 ----------------- .../CircleDetails/CircleConfigs.vue | 105 -- .../CircleDetails/CirclePasswordSettings.vue | 262 ---- .../CircleDetails/CircleSettings.vue | 162 --- .../CircleDetails/ContentHeading.vue | 38 - .../CircleDetails/TeamResourceButton.vue | 198 --- src/components/EntityPicker/EntityBubble.vue | 2 +- .../EntityPicker/NewCircleIntro.vue | 220 --- src/components/MemberList/MemberGridItem.vue | 413 ------ src/components/MemberList/MemberList.vue | 316 ----- src/components/MemberList/MemberListGroup.vue | 64 - src/components/MemberList/MemberListItem.vue | 388 ----- src/mixins/CircleActionsMixin.js | 168 --- src/mixins/RouterMixin.js | 3 - src/models/circle.d.ts | 167 --- src/models/circle.ts | 321 ----- src/models/constants.d.ts | 46 +- src/models/constants.ts | 142 +- src/models/member.d.ts | 26 +- src/models/member.ts | 62 +- src/router/index.js | 7 +- src/services/circles.d.ts | 124 -- src/services/circles.ts | 199 --- src/services/collaborationAutocompletion.js | 25 +- src/services/isCirclesEnabled.js | 9 - src/store/circles.js | 319 ----- src/store/index.js | 7 - src/views/Contacts.vue | 63 +- 31 files changed, 30 insertions(+), 5555 deletions(-) delete mode 100644 src/components/AppContent/CircleContent.vue delete mode 100644 src/components/AppNavigation/CircleNavigationItem.vue delete mode 100644 src/components/CircleDetails.vue delete mode 100644 src/components/CircleDetails/CircleConfigs.vue delete mode 100644 src/components/CircleDetails/CirclePasswordSettings.vue delete mode 100644 src/components/CircleDetails/CircleSettings.vue delete mode 100644 src/components/CircleDetails/ContentHeading.vue delete mode 100644 src/components/CircleDetails/TeamResourceButton.vue delete mode 100644 src/components/EntityPicker/NewCircleIntro.vue delete mode 100644 src/components/MemberList/MemberGridItem.vue delete mode 100644 src/components/MemberList/MemberList.vue delete mode 100644 src/components/MemberList/MemberListGroup.vue delete mode 100644 src/components/MemberList/MemberListItem.vue delete mode 100644 src/mixins/CircleActionsMixin.js delete mode 100644 src/models/circle.d.ts delete mode 100644 src/models/circle.ts delete mode 100644 src/services/circles.d.ts delete mode 100644 src/services/circles.ts delete mode 100644 src/services/isCirclesEnabled.js delete mode 100644 src/store/circles.js diff --git a/src/components/AppContent/CircleContent.vue b/src/components/AppContent/CircleContent.vue deleted file mode 100644 index 1098299bdd..0000000000 --- a/src/components/AppContent/CircleContent.vue +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - diff --git a/src/components/AppNavigation/CircleNavigationItem.vue b/src/components/AppNavigation/CircleNavigationItem.vue deleted file mode 100644 index fd98946839..0000000000 --- a/src/components/AppNavigation/CircleNavigationItem.vue +++ /dev/null @@ -1,137 +0,0 @@ - - - - diff --git a/src/components/AppNavigation/RootNavigation.vue b/src/components/AppNavigation/RootNavigation.vue index d74dd869d1..de14c2c74d 100644 --- a/src/components/AppNavigation/RootNavigation.vue +++ b/src/components/AppNavigation/RootNavigation.vue @@ -157,56 +157,6 @@ :route-state="routeState" :group="group" @update-route-state="updateRouteState" /> - - @@ -240,7 +190,6 @@ import { NcAppNavigationCaption as AppNavigationCaption, NcAppNavigationItem as AppNavigationItem, NcLoadingIcon as IconLoading, - NcActionButton, NcButton, NcCounterBubble, } from '@nextcloud/vue' @@ -254,15 +203,12 @@ import IconError from 'vue-material-design-icons/AlertCircleOutline.vue' import IconAddressBook from 'vue-material-design-icons/BookAccountOutline.vue' import Cog from 'vue-material-design-icons/CogOutline.vue' import IconAdd from 'vue-material-design-icons/Plus.vue' -import NewCircleIntro from '../EntityPicker/NewCircleIntro.vue' import IconRecentlyContacted from '../Icons/IconRecentlyContacted.vue' -import CircleNavigationItem from './CircleNavigationItem.vue' import ContactsSettings from './ContactsSettings.vue' import GroupNavigationItem from './GroupNavigationItem.vue' import SettingsNewAddressbook from './Settings/SettingsNewAddressbook.vue' import RouterMixin from '../../mixins/RouterMixin.js' -import { CHART_ALL_CONTACTS, CIRCLE_DESC, CONTACTS_SETTINGS, ELLIPSIS_COUNT, GROUP_ALL_CONTACTS, GROUP_NO_GROUP_CONTACTS, GROUP_RECENTLY_CONTACTED, ROUTE_ADDRESSBOOK } from '../../models/constants.ts' -import isCirclesEnabled from '../../services/isCirclesEnabled.js' +import { CHART_ALL_CONTACTS, CONTACTS_SETTINGS, ELLIPSIS_COUNT, GROUP_ALL_CONTACTS, GROUP_NO_GROUP_CONTACTS, GROUP_RECENTLY_CONTACTED, ROUTE_ADDRESSBOOK } from '../../models/constants.ts' import isContactsInteractionEnabled from '../../services/isContactsInteractionEnabled.js' import useUserGroupStore from '../../store/userGroup.ts' @@ -272,12 +218,10 @@ export default { components: { ActionInput, ActionText, - NcActionButton, AppNavigation, NcCounterBubble, AppNavigationItem, AppNavigationCaption, - CircleNavigationItem, Cog, ContactsSettings, GroupNavigationItem, @@ -290,7 +234,6 @@ export default { IconError, IconLoading, IconRecentlyContacted, - NewCircleIntro, NcButton, SettingsNewAddressbook, }, @@ -306,7 +249,6 @@ export default { data() { return { - CIRCLE_DESC, CONTACTS_SETTINGS, ELLIPSIS_COUNT, GROUP_ALL_CONTACTS, @@ -319,16 +261,9 @@ export default { isNewGroupMenuOpen: false, createGroupError: null, - // create circle - isNewCircleModalOpen: false, - createCircleLoading: false, - createCircleError: null, - - isCirclesEnabled, isContactsInteractionEnabled, collapsedGroups: true, - collapsedCircles: true, showSettings: false, @@ -346,10 +281,6 @@ export default { return this.addressbooks.filter((ab) => ab.enabled) }, - circles() { - return this.$store.getters.getCircles - }, - contacts() { const contacts = this.$store.getters.getContacts if (contacts.undefined) { @@ -408,47 +339,12 @@ export default { }, ellipsisGroupsMenu() { - // If circles is not enabled, we show everything - if (this.isCirclesEnabled && this.collapsedGroups) { + if (this.collapsedGroups) { return this.groupsMenu.slice(0, ELLIPSIS_COUNT) } return this.groupsMenu }, - // generate circles menu from the circles store - circlesMenu() { - const menu = [...(this.circles || []), ...(this.userGroups || [])] - - menu.sort((a, b) => { - // If user is member of a and b, sort by level - if (a?.initiator?.level !== b?.initiator?.level && a?.initiator?.level && b?.initiator?.level) { - return b.initiator.level - a.initiator.level - } - - // If user is member of a and not b, sort a first - if (a.initiator && !b.initiator) { - return -1 - } - - // If user is member of b and not a, sort b first - if (!a.initiator && b.initiator) { - return 1 - } - - // Else we sort by name - return naturalCompare(a.toString(), b.toString(), { caseInsensitive: true }) - }) - - return menu - }, - - ellipsisCirclesMenu() { - if (this.collapsedCircles) { - return this.circlesMenu.slice(0, ELLIPSIS_COUNT) - } - return this.circlesMenu - }, - // Recently contacted data recentlyContactedContacts() { return this.groups.find((group) => group.name === GROUP_RECENTLY_CONTACTED) @@ -461,12 +357,6 @@ export default { : t('contacts', 'Collapse groups') }, - collapseCirclesTitle() { - return this.collapsedCircles - ? t('contacts', 'Show all teams') - : t('contacts', 'Collapse teams') - }, - ...mapStores(useUserGroupStore), }, @@ -513,48 +403,6 @@ export default { this.collapsedGroups = !this.collapsedGroups }, - onToggleCircles() { - this.collapsedCircles = !this.collapsedCircles - }, - - toggleNewCircleModal() { - this.isNewCircleModalOpen = true - }, - - async createNewCircle(circleName, isPersonal, isLocal) { - this.logger.debug('Creating new team', { circleName }) - - this.createCircleLoading = true - - // Check if already exists - if (this.circles.find((circle) => circle.name === circleName)) { - this.createCircleError = t('contacts', 'This team already exists') - return - } - this.createCircleError = null - - try { - const circle = await this.$store.dispatch('createCircle', { circleName, isPersonal, isLocal }) - this.closeNewCircleIntro() - - // Select group - this.$router.push({ - name: 'circle', - params: { - selectedCircle: circle.id, - }, - }) - } catch (error) { - showError(t('contacts', 'An error happened during the creation of the team')) - } finally { - this.createCircleLoading = false - } - }, - - closeNewCircleIntro() { - this.isNewCircleModalOpen = false - }, - /** * Shows the contacts settings */ @@ -576,8 +424,7 @@ $caption-padding: 22px; padding: calc(var(--default-grid-baseline, 4px) * 2); } -#newgroup, -#newcircle { +#newgroup { margin-top: $caption-padding; :deep(a) { @@ -585,11 +432,6 @@ $caption-padding: 22px; } } -.app-navigation__circle-desc { - margin: var(--default-grid-baseline, 4px) calc(var(--default-grid-baseline, 4px) * 2); - color: var(--color-text-maxcontrast); -} - .app-navigation__collapse :deep(a) { color: var(--color-text-maxcontrast) } diff --git a/src/components/CircleDetails.vue b/src/components/CircleDetails.vue deleted file mode 100644 index fb478bcdda..0000000000 --- a/src/components/CircleDetails.vue +++ /dev/null @@ -1,1260 +0,0 @@ - - - - - - - - - diff --git a/src/components/CircleDetails/CircleConfigs.vue b/src/components/CircleDetails/CircleConfigs.vue deleted file mode 100644 index 60fb574c37..0000000000 --- a/src/components/CircleDetails/CircleConfigs.vue +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - diff --git a/src/components/CircleDetails/CirclePasswordSettings.vue b/src/components/CircleDetails/CirclePasswordSettings.vue deleted file mode 100644 index 878d5e633b..0000000000 --- a/src/components/CircleDetails/CirclePasswordSettings.vue +++ /dev/null @@ -1,262 +0,0 @@ - - - - - - diff --git a/src/components/CircleDetails/CircleSettings.vue b/src/components/CircleDetails/CircleSettings.vue deleted file mode 100644 index 4fda50199a..0000000000 --- a/src/components/CircleDetails/CircleSettings.vue +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - - diff --git a/src/components/CircleDetails/ContentHeading.vue b/src/components/CircleDetails/ContentHeading.vue deleted file mode 100644 index 96c483b1b4..0000000000 --- a/src/components/CircleDetails/ContentHeading.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - diff --git a/src/components/CircleDetails/TeamResourceButton.vue b/src/components/CircleDetails/TeamResourceButton.vue deleted file mode 100644 index 0cb560ec30..0000000000 --- a/src/components/CircleDetails/TeamResourceButton.vue +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - - diff --git a/src/components/EntityPicker/EntityBubble.vue b/src/components/EntityPicker/EntityBubble.vue index 4253ad2325..f70c4277f2 100644 --- a/src/components/EntityPicker/EntityBubble.vue +++ b/src/components/EntityPicker/EntityBubble.vue @@ -48,7 +48,7 @@ export default { }, /** - * Type of the entity. e.g user, circle, group... + * Type of the entity. e.g user, group... */ type: { type: String, diff --git a/src/components/EntityPicker/NewCircleIntro.vue b/src/components/EntityPicker/NewCircleIntro.vue deleted file mode 100644 index 6bd9cbc0c8..0000000000 --- a/src/components/EntityPicker/NewCircleIntro.vue +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - diff --git a/src/components/MemberList/MemberGridItem.vue b/src/components/MemberList/MemberGridItem.vue deleted file mode 100644 index e4d1335a4b..0000000000 --- a/src/components/MemberList/MemberGridItem.vue +++ /dev/null @@ -1,413 +0,0 @@ - - - - - - - diff --git a/src/components/MemberList/MemberList.vue b/src/components/MemberList/MemberList.vue deleted file mode 100644 index 8b812a956d..0000000000 --- a/src/components/MemberList/MemberList.vue +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - - diff --git a/src/components/MemberList/MemberListGroup.vue b/src/components/MemberList/MemberListGroup.vue deleted file mode 100644 index d8c0a18204..0000000000 --- a/src/components/MemberList/MemberListGroup.vue +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - diff --git a/src/components/MemberList/MemberListItem.vue b/src/components/MemberList/MemberListItem.vue deleted file mode 100644 index 7dcf03f872..0000000000 --- a/src/components/MemberList/MemberListItem.vue +++ /dev/null @@ -1,388 +0,0 @@ - - - - - - - diff --git a/src/mixins/CircleActionsMixin.js b/src/mixins/CircleActionsMixin.js deleted file mode 100644 index fa127b1619..0000000000 --- a/src/mixins/CircleActionsMixin.js +++ /dev/null @@ -1,168 +0,0 @@ -import { showConfirmation, showError, showSuccess } from '@nextcloud/dialogs' -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import { emit } from '@nextcloud/event-bus' -import Circle from '../models/circle.ts' -import Member from '../models/member.ts' -import { joinCircle } from '../services/circles.ts' -import logger from '../services/logger.js' -import CopyToClipboardMixin from './CopyToClipboardMixin.js' - -export default { - - props: { - circle: { - type: Circle, - required: true, - }, - }, - - mixins: [CopyToClipboardMixin], - - data() { - return { - loadingAction: false, - loadingJoin: 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 team') - }, - }, - - methods: { - confirmLeaveCircle() { - window.OC.dialogs.confirmDestructive( - t('contacts', 'You are about to leave {circle}.\nAre you sure?', { - circle: this.circle.displayName, - }), - t('contacts', 'Please confirm team leave'), - window.OC.dialogs.YES_NO_BUTTONS, - this.leaveCircle, - true, - ) - }, - async leaveCircle(confirm) { - if (!confirm) { - this.logger.debug('Circle leave cancelled') - return - } - - this.loadingAction = true - const member = this.circle.initiator - - try { - await this.$store.dispatch('deleteMemberFromCircle', { - member, - leave: true, - }) - - // Reset initiator - this.circle.initiator = null - } catch (error) { - logger.error('Could not leave the circle', { member, error }) - showError(t('contacts', 'Could not leave the team {displayName}', this.circle)) - } finally { - this.loadingAction = false - } - }, - - async joinCircle() { - this.loadingJoin = true - try { - const initiator = await joinCircle(this.circle.id) - const member = new Member(initiator, this.circle) - - // Update initiator with newest membership values - this.circle.initiator = member - - // Append new member - member.circle.addMember(member) - } catch (error) { - showError(t('contacts', 'Unable to join the team')) - logger.error('Unable to join the circle', { error }) - } finally { - this.loadingJoin = false - } - }, - - confirmDeleteCircle() { - window.OC.dialogs.confirmDestructive( - t('contacts', 'You are about to delete {circle}.\nAre you sure?', { - circle: this.circle.displayName, - }), - t('contacts', 'Please confirm team deletion'), - window.OC.dialogs.YES_NO_BUTTONS, - this.deleteCircle, - true, - ) - }, - async deleteCircle(confirm) { - if (!confirm) { - this.logger.debug('Circle deletion cancelled') - return - } - - this.loadingAction = true - - try { - await this.$store.dispatch('deleteCircle', this.circle.id) - } catch (error) { - showError(t('contacts', 'Unable to delete the team')) - } finally { - this.loadingAction = false - } - }, - - async confirmEnableFederationForCircle() { - const confirmed = await showConfirmation({ - name: t('contacts', 'Confirm enabling federation'), - text: t('contacts', 'Enabling this will prevent {circle} from being a member of other teams.\nAre you sure?', { - circle: this.circle.displayName, - }), - labelConfirm: t('contacts', 'Enable federation'), - labelReject: t('contacts', 'Cancel'), - severity: 'warning', - }) - if (!confirmed) { - this.logger.debug('Enable federation cancelled') - return false - } - return true - }, - - /** - * Trigger the entity picker view - */ - async addMemberToCircle() { - try { - // Avoid VueRouter NavigationDuplicated - await this.$router.push(this.circle.router) - } catch (error) { - logger.error('Could not open circle member picker', { error }) - } - emit('contacts:circles:append', this.circle.id) - }, - }, -} diff --git a/src/mixins/RouterMixin.js b/src/mixins/RouterMixin.js index 988c965351..254e269d3d 100644 --- a/src/mixins/RouterMixin.js +++ b/src/mixins/RouterMixin.js @@ -11,9 +11,6 @@ export default { selectedGroup() { return this.$route.params.selectedGroup }, - selectedCircle() { - return this.$route.params.selectedCircle - }, selectedUserGroup() { return this.$route.params.selectedUserGroup }, diff --git a/src/models/circle.d.ts b/src/models/circle.d.ts deleted file mode 100644 index a5c78c85b3..0000000000 --- a/src/models/circle.d.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import type Member from './member.ts' -type MemberList = Record -export default class Circle { - _data: any - _members: MemberList - _owner: Member - _initiator: Member - /** - * Creates an instance of Circle - * - * @param data - */ - constructor(data: object) - /** - * Update inner circle data, owner and initiator - * - * @param data - */ - updateData(data: any): void - /** - * Circle id - */ - get id(): string - /** - * Formatted display name - */ - get displayName(): string - /** - * Set the display name - */ - set displayName(text: string) - /** - * Circle creation date - */ - get creation(): number - /** - * Circle description - */ - get description(): string - /** - * Circle description - */ - set description(text: string) - /** - * Circle direct member count (excluding nested circles) - */ - get population(): any - /** - * Circle total member count (direct + inherited from nested circles) - */ - get populationInherited(): any - /** - * Circle ini_initiator the current - * user info for this circle - * null if not a member - */ - get initiator(): Member | null - /** - * Set new circle initiator - * null if not a member - */ - set initiator(initiator: Member | null) - /** - * Circle ownership - */ - get owner(): Member - /** - * Set new circle owner - */ - set owner(owner: Member) - /** - * Circle members - */ - get members(): MemberList - /** - * Define members circle - */ - set members(members: MemberList) - /** - * Add a member to this circle - * - * @param member - */ - addMember(member: Member): void - /** - * Remove a member from this circle - * - * @param member - */ - deleteMember(member: Member): void - get settings(): any - /** - * Circle config - */ - get config(): number - /** - * Define circle config - */ - set config(config: number) - /** - * Circle is personal - */ - get isPersonal(): boolean - /** - * Circle requires invite to be confirmed by moderator or above - */ - get requireJoinAccept(): boolean - /** - * Circle can be requested to join - */ - get canJoin(): boolean - /** - * Circle is visible to others - */ - get isVisible(): boolean - /** - * Circle requires invite to be accepted by the member - */ - get requireInviteAccept(): boolean - /** - * Can the initiator add members to this circle? - */ - get isOwner(): boolean - /** - * Is the initiator an admin of this circle? - */ - get isAdmin(): boolean - /** - * Is the initiator a member of this circle? - */ - get isMember(): boolean | 0 | undefined - /** - * Is the initiator a pending member of this circle? - */ - get isPendingMember(): boolean - /** - * Can the initiator delete this circle? - */ - get canDelete(): boolean - /** - * Can the initiator leave this circle? - */ - get canLeave(): boolean | 0 | undefined - /** - * Can the initiator add/remove members to this circle? - */ - get canManageMembers(): boolean - /** - * Vue router param - */ - get router(): { - name: string - params: { - selectedCircle: string - } - } - /** - * Default javascript fallback - * Used for sorting as well - */ - toString(): string -} -export {} diff --git a/src/models/circle.ts b/src/models/circle.ts deleted file mode 100644 index df3f92f479..0000000000 --- a/src/models/circle.ts +++ /dev/null @@ -1,321 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import logger from '../services/logger.js' -import { CircleConfigs, MemberLevels, ROUTE_CIRCLE } from './constants.ts' -import Member from './member.ts' - -type MemberList = Record - -export default class Circle { - _data: any = {} - _members: MemberList = {} - _owner: Member - _initiator: Member - - /** - * Creates an instance of Circle - * - * @param data - */ - constructor(data: object) { - this.updateData(data) - } - - /** - * Update inner circle data, owner and initiator - * - * @param data - */ - updateData(data: any) { - if (typeof data !== 'object') { - throw new Error('Invalid circle') - } - - // if no uid set, fail - if (!data.id) { - throw new Error('This circle do not have a proper uid') - } - - this._data = data - this._owner = new Member(data.owner, this) - - if (data.initiator) { - this._initiator = new Member(data.initiator, this) - } - } - - // METADATA ----------------------------------------- - /** - * Circle id - */ - get id(): string { - return this._data.id - } - - /** - * Formatted display name - */ - get displayName(): string { - return this._data.displayName - } - - /** - * Set the display name - */ - set displayName(text: string) { - this._data.displayName = text - } - - /** - * Circle creation date - */ - get creation(): number { - return this._data.creation - } - - /** - * Circle description - */ - get description(): string { - return this._data.description - } - - /** - * Circle description - */ - set description(text: string) { - this._data.description = text - } - - /** - * Circle direct member count (excluding nested circles) - */ - get population() { - return this._data.population - } - - /** - * Circle total member count (direct + inherited from nested circles) - */ - get populationInherited() { - return this._data.populationInherited - } - - // MEMBERSHIP ----------------------------------------- - /** - * Circle ini_initiator the current - * user info for this circle - * null if not a member - */ - get initiator(): Member | null { - return this._initiator - } - - /** - * Set new circle initiator - * null if not a member - */ - set initiator(initiator: Member) { - if (initiator && initiator.constructor.name !== Member.name) { - throw new Error('Initiator must be a Member type') - } - this._initiator = initiator - } - - /** - * Circle ownership - */ - get owner(): Member { - return this._owner - } - - /** - * Set new circle owner - */ - set owner(owner: Member) { - if (owner.constructor.name !== Member.name) { - throw new Error('Owner must be a Member type') - } - this._owner = owner - } - - /** - * Circle members - */ - get members(): MemberList { - return this._members - } - - /** - * Define members circle - */ - set members(members: MemberList) { - this._members = members - } - - /** - * Add a member to this circle - * - * @param member - */ - addMember(member: Member) { - if (member.constructor.name !== Member.name) { - throw new Error('Member must be a Member type') - } - - const singleId = member.singleId - if (this._members[singleId]) { - logger.warn('Replacing existing member data', { member }) - } - this._members[singleId] = member - } - - /** - * Remove a member from this circle - * - * @param member - */ - deleteMember(member: Member) { - if (member.constructor.name !== Member.name) { - throw new Error('Member must be a Member type') - } - - const singleId = member.singleId - if (!this._members[singleId]) { - logger.warn('The member was not in this circle. Nothing was done.', { member }) - } - - // Delete and clear memory - delete this._members[singleId] - } - - // CONFIGS -------------------------------------------- - get settings() { - return this._data.settings - } - - /** - * Circle config - */ - get config(): number { - return this._data.config - } - - /** - * Define circle config - */ - set config(config: number) { - this._data.config = config - } - - /** - * Circle is personal - */ - get isPersonal() { - return (this._data.config & CircleConfigs.PERSONAL) !== 0 - } - - /** - * Circle requires invite to be confirmed by moderator or above - */ - get requireJoinAccept() { - return (this._data.config & CircleConfigs.VISIBLE) !== 0 - } - - /** - * Circle can be requested to join - */ - get canJoin() { - return (this._data.config & CircleConfigs.OPEN) !== 0 - } - - /** - * Circle is visible to others - */ - get isVisible() { - return (this._data.config & CircleConfigs.VISIBLE) !== 0 - } - - /** - * Circle requires invite to be accepted by the member - */ - get requireInviteAccept() { - return (this._data.config & CircleConfigs.INVITE) !== 0 - } - - // PERMISSIONS SHORTCUTS ------------------------------ - /** - * Can the initiator add members to this circle? - */ - get isOwner() { - return this.initiator?.level === MemberLevels.OWNER - } - - // PERMISSIONS SHORTCUTS ------------------------------ - /** - * Is the initiator an admin of this circle? - */ - get isAdmin() { - return this.initiator?.level === MemberLevels.ADMIN - } - - /** - * Is the initiator a member of this circle? - */ - get isMember() { - return this.initiator?.level - && this.initiator?.level > MemberLevels.NONE - } - - /** - * Is the initiator a pending member of this circle? - */ - get isPendingMember() { - return this.initiator?.level === MemberLevels.NONE - } - - /** - * Can the initiator delete this circle? - */ - get canDelete() { - return this.isOwner - } - - /** - * Can the initiator leave this circle? - */ - get canLeave() { - return this.isMember && !this.isOwner - } - - /** - * Can the initiator add/remove members to this circle? - */ - get canManageMembers() { - return (this.initiator?.level - && this.initiator?.level >= MemberLevels.MODERATOR) - || (this.config & CircleConfigs.FRIEND) !== 0 - } - - // PARAMS --------------------------------------------- - /** - * Vue router param - */ - get router() { - return { - name: 'circle', - params: { selectedCircle: this.id, selectedGroup: ROUTE_CIRCLE }, - } - } - - /** - * Default javascript fallback - * Used for sorting as well - */ - toString() { - return this.displayName - } -} diff --git a/src/models/constants.d.ts b/src/models/constants.d.ts index 6403f8114e..93bb6e3cef 100644 --- a/src/models/constants.d.ts +++ b/src/models/constants.d.ts @@ -5,7 +5,6 @@ import type { ShareType } from '@nextcloud/sharing' export type DefaultGroup = string export type DefaultChart = string -export type CircleConfig = number export type MemberLevel = number export type MemberType = number export declare const LIST_SIZE = 60 @@ -13,28 +12,10 @@ export declare const GROUP_ALL_CONTACTS: DefaultGroup export declare const GROUP_NO_GROUP_CONTACTS: DefaultGroup export declare const GROUP_RECENTLY_CONTACTED: DefaultGroup export declare const CHART_ALL_CONTACTS: DefaultChart -export declare const ROUTE_CIRCLE = 'circle' export declare const ROUTE_CHART = 'chart' export declare const CONTACTS_SETTINGS: DefaultGroup export declare const ELLIPSIS_COUNT = 5 -export declare const CIRCLE_DESC: string -export declare const CIRCLES_MEMBER_TYPES: { - [x: number]: string -} -export declare const CIRCLES_MEMBER_LEVELS: { - [x: number]: string -} -export declare const PUBLIC_CIRCLE_CONFIG: { - [x: string]: { - [x: number]: string - } -} -export declare const CIRCLES_MEMBER_GROUPING: { - id: string - label: string - share: ShareType - type: number -}[] + export declare const SHARES_TYPES_MEMBER_MAP: object export declare enum MemberLevels { NONE, @@ -43,30 +24,7 @@ export declare enum MemberLevels { ADMIN, OWNER, } -export declare enum MemberTypes { - CIRCLE, - USER, - GROUP, - MAIL, - CONTACT, -} -export declare enum CircleConfigs { - PERSONAL, - SYSTEM, - VISIBLE, - OPEN, - INVITE, - REQUEST, - FRIEND, - PROTECTED, - NO_OWNER, - HIDDEN, - BACKEND, - LOCAL, - ROOT, - CIRCLE_INVITE, - FEDERATED, -} + export declare enum MemberStatus { INVITED = 'Invited', MEMBER = 'Member', diff --git a/src/models/constants.ts b/src/models/constants.ts index 8f67f8749f..fc69c8de80 100644 --- a/src/models/constants.ts +++ b/src/models/constants.ts @@ -5,11 +5,9 @@ /// import { translate as t } from '@nextcloud/l10n' -import { ShareType } from '@nextcloud/sharing' export type DefaultGroup = string export type DefaultChart = string -export type CircleConfig = number export type MemberLevel = number export type MemberType = number @@ -24,8 +22,6 @@ export const GROUP_RECENTLY_CONTACTED: DefaultGroup = 'Recently contacted' // Organization default chart for all contacts export const CHART_ALL_CONTACTS: DefaultChart = t('contacts', 'Organization chart') -// Circle route, see vue-router conf -export const ROUTE_CIRCLE = 'circle' export const ROUTE_CHART = 'chart' export const ROUTE_USER_GROUP = 'user_group' export const ROUTE_ADDRESSBOOK = 'addressbook' @@ -36,7 +32,7 @@ export const CONTACTS_SETTINGS: DefaultGroup = t('contacts', 'Contacts settings' // Default max number of items to show in the navigation export const ELLIPSIS_COUNT = 5 -// Circles member levels +// member levels const MEMBER_LEVEL_NONE: MemberLevel = 0 const MEMBER_LEVEL_MEMBER: MemberLevel = 1 const MEMBER_LEVEL_MODERATOR: MemberLevel = 4 @@ -44,7 +40,6 @@ const MEMBER_LEVEL_ADMIN: MemberLevel = 8 const MEMBER_LEVEL_OWNER: MemberLevel = 9 // Circles member types - const MEMBER_TYPE_SINGLEID: MemberType = 0 const MEMBER_TYPE_USER: MemberType = 1 const MEMBER_TYPE_GROUP: MemberType = 2 @@ -52,26 +47,6 @@ const MEMBER_TYPE_MAIL: MemberType = 4 const MEMBER_TYPE_CONTACT: MemberType = 8 const MEMBER_TYPE_CIRCLE: MemberType = 16 -export const CIRCLE_DESC = t('contacts', 'Create your own teams for sharing. Add Nextcloud users, contacts, or anyone via email.') - -// Circles config flags - -const CIRCLE_CONFIG_PERSONAL: CircleConfig = 2 // Personal circle, only the owner can see it. -const CIRCLE_CONFIG_SYSTEM: CircleConfig = 4 // System Circle (not managed by the official front-end). Meaning some config are limited -const CIRCLE_CONFIG_VISIBLE: CircleConfig = 8 // Visible to everyone, if not visible, people have to know its name to be able to find it -const CIRCLE_CONFIG_OPEN: CircleConfig = 16 // Circle is open, people can join -const CIRCLE_CONFIG_INVITE: CircleConfig = 32 // Adding a member generate an invitation that needs to be accepted -const CIRCLE_CONFIG_REQUEST: CircleConfig = 64 // Request to join Circles needs to be confirmed by a moderator -const CIRCLE_CONFIG_FRIEND: CircleConfig = 128 // Members of the circle can invite their friends -const CIRCLE_CONFIG_PROTECTED: CircleConfig = 256 // Password protected to join/request -const CIRCLE_CONFIG_NO_OWNER: CircleConfig = 512 // no owner, only members -const CIRCLE_CONFIG_HIDDEN: CircleConfig = 1024 // hidden from listing, but available as a share entity -const CIRCLE_CONFIG_BACKEND: CircleConfig = 2048 // Fully hidden, only backend Circles -const CIRCLE_CONFIG_LOCAL: CircleConfig = 4096 // Circle is not shared to other instance in globalscale -const CIRCLE_CONFIG_ROOT: CircleConfig = 8192 // Circle cannot be a member of another Circle -const CIRCLE_CONFIG_CIRCLE_INVITE: CircleConfig = 16384 // Circle must confirm when invited in another circle -const CIRCLE_CONFIG_FEDERATED: CircleConfig = 32768 // Federated - // Existing members types export const CIRCLES_MEMBER_TYPES = { [MEMBER_TYPE_CIRCLE]: t('contacts', 'Team'), @@ -81,102 +56,6 @@ export const CIRCLES_MEMBER_TYPES = { [MEMBER_TYPE_CONTACT]: t('contacts', 'Contact'), } -// Available circles promote/demote levels -export const CIRCLES_MEMBER_LEVELS = { - // [MEMBER_LEVEL_NONE]: t('contacts', 'Pending'), - [MEMBER_LEVEL_MEMBER]: t('contacts', 'Member'), - [MEMBER_LEVEL_MODERATOR]: t('contacts', 'Moderator'), - [MEMBER_LEVEL_ADMIN]: t('contacts', 'Admin'), - [MEMBER_LEVEL_OWNER]: t('contacts', 'Owner'), -} - -// Available circle configs in the circle details view -export const PUBLIC_CIRCLE_CONFIG = { - [t('contacts', 'Invites')]: { - [CIRCLE_CONFIG_OPEN]: t('contacts', 'Anyone can request membership'), - [CIRCLE_CONFIG_INVITE]: t('contacts', 'Members need to accept invitation'), - [CIRCLE_CONFIG_REQUEST]: t('contacts', 'Memberships must be confirmed/accepted by a Moderator (requires "Anyone can request membership")'), - [CIRCLE_CONFIG_FRIEND]: t('contacts', 'Members can also invite'), - }, - - [t('contacts', 'Membership')]: { - // TODO: implement backend - // [CIRCLE_CONFIG_CIRCLE_INVITE]: t('contacts', 'Team must confirm when invited in another circle'), - [CIRCLE_CONFIG_ROOT]: t('contacts', 'Prevent teams from being a member of another team'), - }, - - [t('contacts', 'Federation')]: { - [CIRCLE_CONFIG_FEDERATED]: t('contacts', 'Allow federated members'), - }, - - [t('contacts', 'Privacy')]: { - [CIRCLE_CONFIG_VISIBLE]: t('contacts', 'Visible to everyone'), - }, -} - -// Represents the picker options (label is used lower case in a list of options: Search users, groups, teams, ...) -// labelStandalone is used as heading for the member list where we need it starting captialized -export const CIRCLES_MEMBER_GROUPING = [ - { - id: `picker-${ShareType.User}`, - label: t('contacts', 'users'), - labelStandalone: t('contacts', 'Users'), - share: ShareType.User, - type: MEMBER_TYPE_USER, - }, - { - id: `picker-${ShareType.Group}`, - label: t('contacts', 'groups'), - labelStandalone: t('contacts', 'Groups'), - share: ShareType.Group, - type: MEMBER_TYPE_GROUP, - }, - { - id: `picker-${ShareType.Remote}`, - label: t('contacts', 'federated users'), - labelStandalone: t('contacts', 'Federated users'), - share: ShareType.Remote, - type: MEMBER_TYPE_USER, - }, - // { - // id: `picker-${ShareType.RemoteGroup}`, - // label: t('contacts', 'federated groups'), - // share: ShareType.RemoteGroup, - // type: MEMBER_TYPE_GROUP - // }, - { - id: `picker-${ShareType.Team}`, - label: t('contacts', 'teams'), - labelStandalone: t('contacts', 'Teams'), - share: ShareType.Team, - type: MEMBER_TYPE_CIRCLE, - }, - { - id: `picker-${ShareType.Email}`, - label: t('contacts', 'email addresses'), - labelStandalone: t('contacts', 'Email addresses'), - share: ShareType.Email, - type: MEMBER_TYPE_MAIL, - }, - // TODO: implement SHARE_TYPE_CONTACT - { - id: 'picker-contact', - label: t('contacts', 'contacts'), - labelStandalone: t('contacts', 'Contacts'), - share: ShareType.Email, - type: MEMBER_TYPE_CONTACT, - }, -] - -// Generating a map between share types and circle member types -export const SHARES_TYPES_MEMBER_MAP = CIRCLES_MEMBER_GROUPING.reduce((list, entry) => { - // ! Ignore duplicate share types - if (!list[entry.share]) { - list[entry.share] = entry.type - } - return list -}, {}) - export enum MemberLevels { NONE = MEMBER_LEVEL_NONE, MEMBER = MEMBER_LEVEL_MEMBER, @@ -184,7 +63,6 @@ export enum MemberLevels { ADMIN = MEMBER_LEVEL_ADMIN, OWNER = MEMBER_LEVEL_OWNER, } - export enum MemberTypes { CIRCLE = MEMBER_TYPE_CIRCLE, USER = MEMBER_TYPE_USER, @@ -193,24 +71,6 @@ export enum MemberTypes { CONTACT = MEMBER_TYPE_CONTACT, } -export enum CircleConfigs { - PERSONAL = CIRCLE_CONFIG_PERSONAL, - SYSTEM = CIRCLE_CONFIG_SYSTEM, - VISIBLE = CIRCLE_CONFIG_VISIBLE, - OPEN = CIRCLE_CONFIG_OPEN, - INVITE = CIRCLE_CONFIG_INVITE, - REQUEST = CIRCLE_CONFIG_REQUEST, - FRIEND = CIRCLE_CONFIG_FRIEND, - PROTECTED = CIRCLE_CONFIG_PROTECTED, - NO_OWNER = CIRCLE_CONFIG_NO_OWNER, - HIDDEN = CIRCLE_CONFIG_HIDDEN, - BACKEND = CIRCLE_CONFIG_BACKEND, - LOCAL = CIRCLE_CONFIG_LOCAL, - ROOT = CIRCLE_CONFIG_ROOT, - CIRCLE_INVITE = CIRCLE_CONFIG_CIRCLE_INVITE, - FEDERATED = CIRCLE_CONFIG_FEDERATED, -} - export enum MemberStatus { INVITED = 'Invited', MEMBER = 'Member', diff --git a/src/models/member.d.ts b/src/models/member.d.ts index da28f9f057..3397c43b35 100644 --- a/src/models/member.d.ts +++ b/src/models/member.d.ts @@ -1,4 +1,3 @@ -import type Circle from './circle.ts' /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -6,25 +5,12 @@ import type Circle from './circle.ts' import type { MemberLevel, MemberType } from './constants.ts' export default class Member { _data: any - _circle: Circle /** * Creates an instance of Member * * @param data - * @param circle - */ - constructor(data: any, circle: Circle) - /** - * Get the circle of this member - */ - get circle(): Circle - /** - * Set the circle of this member - */ - set circle(circle: Circle) - /** - * Member id */ + constructor(data: any) get id(): string /** * Single uid @@ -38,10 +24,6 @@ export default class Member { * Member userId */ get userId(): string - /** - * Member type - */ - get userType(): MemberType /** * Member based on source */ @@ -65,11 +47,7 @@ export default class Member { */ get isUser(): boolean /** - * Is the current member without a circle? - */ - get isOrphan(): boolean - /** - * Delete this member and any reference from its circle + * Delete this member */ delete(): void } diff --git a/src/models/member.ts b/src/models/member.ts index f7f427df88..51935744d5 100644 --- a/src/models/member.ts +++ b/src/models/member.ts @@ -3,23 +3,20 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import type { MemberLevel, MemberType } from './constants.ts' +import type { MemberLevel } from './constants.ts' import logger from '../services/logger.js' -import Circle from './circle.ts' -import { MemberLevels, MemberTypes } from './constants.ts' +import { MemberLevels } from './constants.ts' export default class Member { _data: any = {} - _circle: Circle /** * Creates an instance of Member * * @param data - * @param circle */ - constructor(data: any, circle: Circle) { + constructor(data: any) { if (typeof data !== 'object') { throw new Error('Invalid member') } @@ -30,27 +27,9 @@ export default class Member { throw new Error('This member do not have a proper uid') } - this._circle = circle this._data = data } - /** - * Get the circle of this member - */ - get circle(): Circle { - return this._circle - } - - /** - * Set the circle of this member - */ - set circle(circle: Circle) { - if (circle.constructor.name !== Circle.name) { - throw new Error('circle must be a Circle type') - } - this._circle = circle - } - /** * Member id */ @@ -79,23 +58,6 @@ export default class Member { return this._data.userId } - /** - * Member type - */ - get userType(): MemberType { - // If the user type is a circle, this could originate from multiple sources - return this._data.userType !== MemberTypes.CIRCLE - ? this._data.userType - : this.basedOn.source - } - - /** - * Member based on source - */ - get basedOn(): any { - return this._data.basedOn - } - /** * Member level * @@ -128,22 +90,4 @@ export default class Member { get isUser() { return this._data.userType === MemberLevels.MEMBER } - - /** - * Is the current member without a circle? - */ - get isOrphan() { - return this._circle?.constructor?.name !== Circle.name - } - - /** - * Delete this member and any reference from its circle - */ - delete() { - if (this.isOrphan) { - throw new Error('Cannot delete this member as it doesn\'t belong to any circle') - } - this.circle.deleteMember(this) - this._data = undefined - } } diff --git a/src/router/index.js b/src/router/index.js index 6c00fc875f..b620616ac2 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -6,7 +6,7 @@ import { generateUrl } from '@nextcloud/router' import { createRouter, createWebHistory } from 'vue-router' import Contacts from '../views/Contacts.vue' -import { GROUP_ALL_CONTACTS, ROUTE_ADDRESSBOOK, ROUTE_CHART, ROUTE_CIRCLE, ROUTE_USER_GROUP } from '../models/constants.ts' +import { GROUP_ALL_CONTACTS, ROUTE_ADDRESSBOOK, ROUTE_CHART, ROUTE_USER_GROUP } from '../models/constants.ts' import { generateContactKey } from '../models/contact.js' // if index.php is in the url AND we got this far, then it's working: @@ -38,11 +38,6 @@ export default createRouter({ name: 'import', component: Contacts, }, - { - path: `${ROUTE_CIRCLE}/:selectedCircle`, - name: 'circle', - component: Contacts, - }, { path: ':selectedGroup', name: 'group', diff --git a/src/services/circles.d.ts b/src/services/circles.d.ts deleted file mode 100644 index c8a8859736..0000000000 --- a/src/services/circles.d.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import type { MemberLevel, MemberType } from '../models/constants.ts' -interface MemberPairs { - id: string - type: MemberType -} -type CircleEditType = 'name' | 'description' | 'settings' | 'config' -export declare enum CircleEdit { - Name = 'name', - Description = 'description', - Settings = 'settings', - Config = 'config', -} -interface CircleSetting { - setting: string - value: string -} -/** - * Get the circles list without the members - * - * @return {Array} - */ -export declare const getCircles: () => Promise -/** - * Get a specific circle - * - * @param {string} circleId - * @return {object} - */ -export declare const getCircle: (circleId: string) => Promise -/** - * Create a new circle - * - * @param {string} name the circle name - * @param personal - * @param local - * @return {object} - */ -export declare const createCircle: (name: string, personal: boolean, local: boolean) => Promise -/** - * Delete an existing circle - * - * @param {string} circleId the circle id - * @return {object} - */ -export declare const deleteCircle: (circleId: string) => Promise -/** - * Edit an existing circle - * - * @param {string} circleId the circle id - * @param {CircleEditType} type the edit type - * @param {any} data the data - * @param value - * @return {object} - */ -export declare const editCircle: (circleId: string, type: CircleEditType, value: any) => Promise -/** - * Join a circle - * - * @param {string} circleId the circle id - * @return {Array} - */ -export declare const joinCircle: (circleId: string) => Promise -/** - * Leave a circle - * - * @param {string} circleId the circle id - * @return {Array} - */ -export declare const leaveCircle: (circleId: string) => Promise -/** - * Get the circle members without the members - * - * @param {string} circleId the circle id - * @return {Array} - */ -export declare const getCircleMembers: (circleId: string) => Promise -/** - * Search a potential circle member - * - * @param {string} term the search query - * @return {Array} - */ -export declare const searchMember: (term: string) => Promise -/** - * Add a circle member - * - * @param {string} circleId the circle id - * @param {string} members the member id - * @return {Array} - */ -export declare const addMembers: (circleId: string, members: Array) => Promise -/** - * Delete a circle member - * - * @param {string} circleId the circle id - * @param {string} memberId the member id - * @return {Array} - */ -export declare const deleteMember: (circleId: string, memberId: string) => Promise -/** - * change a member level - * - * @see levels file src/models/constants.js - * - * @param {string} circleId the circle id - * @param {string} memberId the member id - * @param {number} level the new member level - * @return {Array} - */ -export declare const changeMemberLevel: (circleId: string, memberId: string, level: MemberLevel) => Promise -/** - * Accept a circle member request - * - * @param {string} circleId the circle id - * @param {string} memberId the member id - * @return {Array} - */ -export declare const acceptMember: (circleId: string, memberId: string) => Promise -export declare const editCircleSetting: (circleId: string, setting: CircleSetting) => Promise -export {} diff --git a/src/services/circles.ts b/src/services/circles.ts deleted file mode 100644 index d833200f51..0000000000 --- a/src/services/circles.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import type { MemberLevel, MemberType } from '../models/constants.ts' - -import axios from '@nextcloud/axios' -import { generateOcsUrl } from '@nextcloud/router' -import { MemberLevels } from '../models/constants.ts' -interface MemberPairs { - id: string - type: MemberType -} - -type CircleEditType = 'name' | 'description' | 'settings' | 'config' -export enum CircleEdit { - Name = 'name', - Description = 'description', - Settings = 'settings', - Config = 'config', -} - -interface CircleSetting { - setting: string - value: string -} - -/** - * Get the circles list without the members - * - * @return - */ -export async function getCircles() { - const response = await axios.get(generateOcsUrl('apps/circles/circles')) - return response.data.ocs.data -} - -/** - * Get a specific circle - * - * @param circleId - * @return - */ -export async function getCircle(circleId: string) { - const response = await axios.get(generateOcsUrl('apps/circles/circles/{circleId}', { circleId })) - return response.data.ocs.data -} - -/** - * Create a new circle - * - * @param name the circle name - * @param personal - * @param local - * @return - */ -export async function createCircle(name: string, personal: boolean, local: boolean) { - const response = await axios.post(generateOcsUrl('apps/circles/circles'), { - name, - personal, - local, - }) - return response.data.ocs.data -} - -/** - * Delete an existing circle - * - * @param circleId the circle id - * @return - */ -export async function deleteCircle(circleId: string) { - const response = await axios.delete(generateOcsUrl('apps/circles/circles/{circleId}', { circleId })) - return response.data.ocs.data -} - -/** - * Edit an existing circle - * - * @param circleId the circle id - * @param type the edit type - * @param data the data - * @param value - * @return - */ -export async function editCircle(circleId: string, type: CircleEditType, value: any) { - const response = await axios.put(generateOcsUrl('apps/circles/circles/{circleId}/{type}', { circleId, type }), { value }) - return response.data.ocs.data -} - -/** - * Join a circle - * - * @param circleId the circle id - * @return - */ -export async function joinCircle(circleId: string) { - const response = await axios.put(generateOcsUrl('apps/circles/circles/{circleId}/join', { circleId })) - return response.data.ocs.data -} - -/** - * Leave a circle - * - * @param circleId the circle id - * @return - */ -export async function leaveCircle(circleId: string) { - const response = await axios.put(generateOcsUrl('apps/circles/circles/{circleId}/leave', { circleId })) - return response.data.ocs.data -} - -/** - * Get the circle members without the members - * - * @param circleId the circle id - * @return - */ -export async function getCircleMembers(circleId: string) { - const response = await axios.get(generateOcsUrl('apps/circles/circles/{circleId}/members', { circleId })) - return response.data.ocs.data -} - -/** - * Search a potential circle member - * - * @param term the search query - * @return - */ -export async function searchMember(term: string) { - const response = await axios.get(generateOcsUrl('apps/circles/search?term={term}', { term })) - return response.data.ocs.data -} - -/** - * Add a circle member - * - * @param circleId the circle id - * @param members the member id - * @return - */ -export async function addMembers(circleId: string, members: Array) { - const response = await axios.post(generateOcsUrl('apps/circles/circles/{circleId}/members/multi', { circleId }), { members }) - return response.data.ocs.data -} - -/** - * Delete a circle member - * - * @param circleId the circle id - * @param memberId the member id - * @return - */ -export async function deleteMember(circleId: string, memberId: string) { - const response = await axios.delete(generateOcsUrl('apps/circles/circles/{circleId}/members/{memberId}', { circleId, memberId })) - return Object.values(response.data.ocs.data) -} - -/** - * change a member level - * - * @see levels file src/models/constants.js - * - * @param circleId the circle id - * @param memberId the member id - * @param level the new member level - * @return - */ -export async function changeMemberLevel(circleId: string, memberId: string, level: MemberLevel) { - if (!(level in MemberLevels)) { - throw new Error('Invalid level.') - } - - const response = await axios.put(generateOcsUrl('apps/circles/circles/{circleId}/members/{memberId}/level', { circleId, memberId }), { - level, - }) - return Object.values(response.data.ocs.data) -} - -/** - * Accept a circle member request - * - * @param circleId the circle id - * @param memberId the member id - * @return - */ -export async function acceptMember(circleId: string, memberId: string) { - const response = await axios.put(generateOcsUrl('apps/circles/circles/{circleId}/members/{memberId}', { circleId, memberId })) - return response.data.ocs.data -} - -export async function editCircleSetting(circleId: string, setting: CircleSetting) { - const response = await axios.put( - generateOcsUrl('apps/circles/circles/{circleId}/setting', { circleId }), - setting, - ) - return response.data.ocs.data -} diff --git a/src/services/collaborationAutocompletion.js b/src/services/collaborationAutocompletion.js index 7f7c9c5951..2b2ade62db 100644 --- a/src/services/collaborationAutocompletion.js +++ b/src/services/collaborationAutocompletion.js @@ -6,7 +6,7 @@ import axios from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' import { ShareType } from '@nextcloud/sharing' -import { CircleConfigs, SHARES_TYPES_MEMBER_MAP } from '../models/constants.ts' +import { SHARES_TYPES_MEMBER_MAP } from '../models/constants.ts' import logger from './logger.js' // generate allowed shareType from SHARES_TYPES_MEMBER_MAP @@ -17,9 +17,8 @@ const maxAutocompleteResults = parseInt(window.OC.config['sharing.maxAutocomplet * Get suggestions * * @param {string} search the search query - * @param {object|null} circle - the circle object */ -export async function getSuggestions(search, circle = null) { +export async function getSuggestions(search) { const request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1/sharees'), { params: { format: 'json', @@ -39,21 +38,11 @@ export async function getSuggestions(search, circle = null) { let rawExactSuggestions = Object.values(exact).reduce((arr, elem) => arr.concat(elem), []) let rawSuggestions = Object.values(data).reduce((arr, elem) => arr.concat(elem), []) - // check if federation flag is enabled using bitwise AND on circle config - const isCircleFederated = circle ? (circle.config & CircleConfigs.FEDERATED) !== 0 : false - if (isCircleFederated) { - // remove results from untrusted remote servers - rawExactSuggestions = rawExactSuggestions - .filter((result) => !(result.value?.shareType === ShareType.Remote && result.value?.isTrustedServer === false)) - rawSuggestions = rawSuggestions - .filter((result) => !(result.value?.shareType === ShareType.Remote && result.value?.isTrustedServer === false)) - } else { - // remove all results from remote servers - rawExactSuggestions = rawExactSuggestions - .filter((result) => result.value?.shareType !== ShareType.Remote) - rawSuggestions = rawSuggestions - .filter((result) => result.value?.shareType !== ShareType.Remote) - } + // remove all results from remote servers + rawExactSuggestions = rawExactSuggestions + .filter((result) => result.value?.shareType !== ShareType.Remote) + rawSuggestions = rawSuggestions + .filter((result) => result.value?.shareType !== ShareType.Remote) // remove invalid data and format to user-select layout const exactSuggestions = rawExactSuggestions diff --git a/src/services/isCirclesEnabled.js b/src/services/isCirclesEnabled.js deleted file mode 100644 index 39cbbe98b2..0000000000 --- a/src/services/isCirclesEnabled.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { loadState } from '@nextcloud/initial-state' - -const isCirclesEnabled = loadState('contacts', 'isCirclesEnabled', false) -export default isCirclesEnabled diff --git a/src/store/circles.js b/src/store/circles.js deleted file mode 100644 index 7851babb1e..0000000000 --- a/src/store/circles.js +++ /dev/null @@ -1,319 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { showError } from '@nextcloud/dialogs' -import Circle from '../models/circle.ts' -import Member from '../models/member.ts' -import { - acceptMember, - addMembers, - createCircle, - deleteCircle, - deleteMember, - editCircleSetting, - getCircle, - getCircleMembers, - getCircles, - leaveCircle, -} from '../services/circles.ts' -import logger from '../services/logger.js' - -const state = { - /** @type {Object} Circle */ - circles: {}, -} - -const mutations = { - - /** - * Add a circle into state - * - * @param {object} state the store data - * @param {Circle} circle the circle to add - */ - addCircle(state, circle) { - if (circle.constructor.name !== Circle.name) { - throw new Error('circle must be a Circle type') - } - state.circles[circle.id] = circle - }, - - /** - * Delete circle - * - * @param {object} state the store data - * @param {Circle} circle the circle to delete - */ - deleteCircle(state, circle) { - if (!(circle.id in state.circles)) { - logger.warn('Skipping deletion of unknown circle', { circle }) - } - delete state.circles[circle.id] - }, - - /** - * Append a list of members to a circle - * and remove duplicates - * - * @param {object} state the store data - * @param {Members[]} members array of members to append - */ - appendMembersToCircle(state, members) { - members.forEach((member) => member.circle.addMember(member)) - }, - - /** - * Add a member to a circle and overwrite if duplicate uid - * - * @param {object} state the store data - * @param {object} data destructuring object - * @param {string} data.circleId the circle to add the members to - * @param {Member} data.member array of contacts to append - */ - addMemberToCircle(state, { circleId, member }) { - const circle = state.circles[circleId] - circle.addMember(member) - }, - - /** - * Delete a contact in a specified circle - * - * @param {object} state the store data - * @param {Member} member the member to add - */ - deleteMemberFromCircle(state, member) { - // Circles dependencies are managed directly from the model - member.delete() - }, - - setCircleSettings(state, { circleId, settings }) { - state.circles[circleId]._data.settings = settings - }, - - /** - * Update circle population count - * - * @param {object} state the store data - * @param {object} data destructuring object - * @param {string} data.circleId the circle to update the population - * @param {number} data.populationInherited the inherited population count - */ - updateCirclePopulationCount(state, { circleId, populationInherited }) { - state.circles[circleId]._data.populationInherited = populationInherited - }, -} - -const getters = { - getCircles: (state) => Object.values(state.circles), - getCircle: (state) => (id) => state.circles[id], -} - -const actions = { - /** - * Retrieve and commit circles - * - * @param {object} context the store mutations - * @return {object[]} the circles - */ - async getCircles(context) { - const circles = await getCircles() - logger.debug(`Retrieved ${circles.length} circle(s)`, { circles }) - - let failure = false - circles.forEach((circle) => { - try { - const newCircle = new Circle(circle) - context.commit('addCircle', newCircle) - } catch (error) { - failure = true - logger.error('This circle failed to be processed', { circle, error }) - } - }) - - if (failure) { - showError(t('contacts', 'An error has occurred in team(s). Check the console for more details.')) - } - - return circles - }, - - /** - * Retrieve and commit circles - * - * @param {object} context the store mutations - * @param {string} circleId the circle id - * @return {object[]} the circles - */ - async getCircle(context, circleId) { - const circle = await getCircle(circleId) - logger.debug('Retrieved 1 circle', { circle }) - - try { - const newCircle = new Circle(circle) - context.commit('addCircle', newCircle) - } catch (error) { - logger.error('This circle failed to be processed', { circle, error }) - } - - return circle - }, - - /** - * Retrieve and commit circle members - * - * @param {object} context the store mutations - * @param {string} circleId the circle id - */ - async getCircleMembers(context, circleId) { - const circle = context.getters.getCircle(circleId) - // skip if current user is not a member (e.g. visible circle) - if (!circle.isMember) { - return - } - const members = await getCircleMembers(circleId) - - logger.debug(`${circleId} have ${members.length} member(s)`, { members }) - context.commit('appendMembersToCircle', members.map((member) => new Member(member, circle))) - }, - - /** - * Create circle - * - * @param {object} context the store mutations Current context - * @param {object} data destructuring object - * @param {string} data.circleName the circle name - * @param {boolean} data.isPersonal the circle is a personal one - * @param {boolean} data.isLocal the circle is not distributed to the GlobalScale - * @return {Circle} the new circle - */ - async createCircle(context, { circleName, isPersonal, isLocal }) { - try { - const response = await createCircle(circleName, isPersonal, isLocal) - const circle = new Circle(response) - context.commit('addCircle', circle) - logger.debug('Created circle', { circleName, circle }) - context.dispatch('updateCirclesPopulationCount') - return circle - } catch (error) { - logger.error(error) - showError(t('contacts', 'Unable to create team {circleName}', { circleName })) - } - }, - - /** - * Delete circle - * - * @param {object} context the store mutations Current context - * @param {Circle} circleId the circle to delete - */ - async deleteCircle(context, circleId) { - const circle = context.getters.getCircle(circleId) - try { - await deleteCircle(circleId) - context.commit('deleteCircle', circle) - logger.debug('Deleted circle', { circleId }) - context.dispatch('updateCirclesPopulationCount') - } catch (error) { - logger.error(error) - showError(t('contacts', 'Unable to delete team {circleId}', { circleId })) - } - }, - - /** - * Update population count for all circles - * - * @param {object} context the store mutations Current context - */ - async updateCirclesPopulationCount(context) { - const circles = await getCircles() - circles.forEach((circle) => { - context.commit('updateCirclePopulationCount', { - circleId: circle.id, - populationInherited: circle.populationInherited, - }) - }) - logger.debug('Updated population count for all circles') - }, - - /** - * Add members to a circle - * - * @param {object} context the store mutations Current context - * @param {object} data destructuring object - * @param {string} data.circleId the circle to manage - * @param {Array} data.selection the members to add, see addMembers service - * @return {Member[]} - */ - async addMembersToCircle(context, { circleId, selection }) { - const circle = context.getters.getCircle(circleId) - const results = await addMembers(circleId, selection) - const members = results.map((member) => new Member(member, circle)) - - context.commit('appendMembersToCircle', members) - logger.debug('Added members to circle', { circle, members }) - context.dispatch('updateCirclesPopulationCount') - - return members - }, - - /** - * Delete a member from a circle - * - * @param {object} context the store mutations Current context - * @param {Member} member the member to remove - * @param {boolean} [leave] leave the circle instead of removing the member - */ - async deleteMemberFromCircle(context, { member, leave = false }) { - const circleId = member.circle.id - const memberId = member.id - - if (leave) { - const circle = await leaveCircle(circleId) - member.circle.updateData(circle) - - // If the circle is not visible, we remove it from the list - if (!member.circle.isVisible && !member.circle.isMember) { - await context.commit('deleteCircle', circle) - logger.debug('Deleted circle', { circleId, memberId }) - } - } else { - await deleteMember(circleId, memberId) - } - - // success, let's remove from store - context.commit('deleteMemberFromCircle', member) - logger.debug('Deleted member', { circleId, memberId }) - context.dispatch('updateCirclesPopulationCount') - }, - - /** - * Accept a circle member request - * - * @param {object} context the store mutations Current context - * @param {object} data destructuring object - * @param {string} data.circleId the circle id - * @param {string} data.memberId the member id - */ - async acceptCircleMember(context, { circleId, memberId }) { - const circle = context.getters.getCircle(circleId) - - const result = await acceptMember(circleId, memberId) - const member = new Member(result, circle) - - await context.commit('addMemberToCircle', { circleId, member }) - context.dispatch('updateCirclesPopulationCount') - }, - - async editCircleSetting(context, { circleId, setting }) { - const { settings } = await editCircleSetting(circleId, setting) - await context.commit('setCircleSettings', { - circleId, - settings, - }) - }, - -} - -export default { state, mutations, getters, actions } diff --git a/src/store/index.js b/src/store/index.js index ef220836ef..e88e59de56 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -4,9 +4,7 @@ */ import { Store } from 'vuex' -import isCirclesEnabled from '../services/isCirclesEnabled.js' import addressbooks from './addressbooks.js' -import circles from './circles.js' import contacts from './contacts.js' import groups from './groups.js' import importState from './importState.js' @@ -20,11 +18,6 @@ const modules = { importState, } -// If circles is enabled let's init the store -if (isCirclesEnabled) { - modules.circles = circles -} - export default new Store({ modules, mutations, diff --git a/src/views/Contacts.vue b/src/views/Contacts.vue index 4ec9c2a244..c232deaa4b 100644 --- a/src/views/Contacts.vue +++ b/src/views/Contacts.vue @@ -7,9 +7,9 @@ + :loading="loadingContacts">
- + - {{ isCirclesView ? t('contacts', 'Add member') : t('contacts', 'New contact') }} + {{ t('contacts', 'New contact') }}
- - - +