platform/plugins/contact-resources/src/index.ts
Andrey Sobolev 4cce003364
UBERF-7959: Fix async issues (#6409)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
2024-08-28 11:51:20 +05:00

455 lines
16 KiB
TypeScript

//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import {
getGravatarUrl,
getName,
type AvatarInfo,
type Channel,
type Contact,
type Person,
type PersonAccount
} from '@hcengineering/contact'
import {
DocManager,
type Class,
type Client,
type Data,
type DocumentQuery,
type Ref,
type RelatedDocument,
type WithLookup
} from '@hcengineering/core'
import login from '@hcengineering/login'
import { getResource, type IntlString, type Resources } from '@hcengineering/platform'
import { MessageBox, getBlobRef, getClient, type ObjectSearchResult } from '@hcengineering/presentation'
import {
getPlatformAvatarColorByName,
getPlatformAvatarColorForTextDef,
getPlatformColorDef,
hexColorToNumber,
parseURL,
showPopup,
themeStore,
type AnyComponent,
type AnySvelteComponent,
type ColorDefinition,
type TooltipAlignment
} from '@hcengineering/ui'
import { AggregationManager } from '@hcengineering/view-resources'
import AccountArrayEditor from './components/AccountArrayEditor.svelte'
import AccountBox from './components/AccountBox.svelte'
import AssigneeBox from './components/AssigneeBox.svelte'
import AssigneePopup from './components/AssigneePopup.svelte'
import Avatar from './components/Avatar.svelte'
import AvatarRef from './components/AvatarRef.svelte'
import ChannelFilter from './components/ChannelFilter.svelte'
import ChannelIcon from './components/ChannelIcon.svelte'
import ChannelPanel from './components/ChannelPanel.svelte'
import ChannelPresenter from './components/ChannelPresenter.svelte'
import Channels from './components/Channels.svelte'
import ChannelsDropdown from './components/ChannelsDropdown.svelte'
import ChannelsEditor from './components/ChannelsEditor.svelte'
import ChannelsPresenter from './components/ChannelsPresenter.svelte'
import ChannelsView from './components/ChannelsView.svelte'
import CollaborationUserAvatar from './components/CollaborationUserAvatar.svelte'
import CombineAvatars from './components/CombineAvatars.svelte'
import ContactArrayEditor from './components/ContactArrayEditor.svelte'
import ContactPresenter from './components/ContactPresenter.svelte'
import ContactRefPresenter from './components/ContactRefPresenter.svelte'
import Contacts from './components/Contacts.svelte'
import ContactsTabs from './components/ContactsTabs.svelte'
import CreateEmployee from './components/CreateEmployee.svelte'
import CreateGuest from './components/CreateGuest.svelte'
import CreateOrganization from './components/CreateOrganization.svelte'
import CreatePerson from './components/CreatePerson.svelte'
import DeleteConfirmationPopup from './components/DeleteConfirmationPopup.svelte'
import EditEmployee from './components/EditEmployee.svelte'
import EditMember from './components/EditMember.svelte'
import EditOrganization from './components/EditOrganization.svelte'
import EditOrganizationPanel from './components/EditOrganizationPanel.svelte'
import EditPerson from './components/EditPerson.svelte'
import EditableAvatar from './components/EditableAvatar.svelte'
import EmployeeArrayEditor from './components/EmployeeArrayEditor.svelte'
import EmployeeBox from './components/EmployeeBox.svelte'
import EmployeeBrowser from './components/EmployeeBrowser.svelte'
import EmployeeEditor from './components/EmployeeEditor.svelte'
import EmployeeFilter from './components/EmployeeFilter.svelte'
import EmployeeFilterValuePresenter from './components/EmployeeFilterValuePresenter.svelte'
import EmployeePresenter from './components/EmployeePresenter.svelte'
import EmployeeRefPresenter from './components/EmployeeRefPresenter.svelte'
import MemberPresenter from './components/MemberPresenter.svelte'
import Members from './components/Members.svelte'
import MembersBox from './components/MembersBox.svelte'
import MembersPresenter from './components/MembersPresenter.svelte'
import MergePersons from './components/MergePersons.svelte'
import OrganizationEditor from './components/OrganizationEditor.svelte'
import OrganizationPresenter from './components/OrganizationPresenter.svelte'
import PersonAccountFilterValuePresenter from './components/PersonAccountFilterValuePresenter.svelte'
import PersonAccountPresenter from './components/PersonAccountPresenter.svelte'
import PersonAccountRefPresenter from './components/PersonAccountRefPresenter.svelte'
import PersonEditor from './components/PersonEditor.svelte'
import PersonIcon from './components/PersonIcon.svelte'
import PersonPresenter from './components/PersonPresenter.svelte'
import PersonRefPresenter from './components/PersonRefPresenter.svelte'
import SelectAvatars from './components/SelectAvatars.svelte'
import SelectUsersPopup from './components/SelectUsersPopup.svelte'
import SocialEditor from './components/SocialEditor.svelte'
import SpaceMembers from './components/SpaceMembers.svelte'
import SpaceMembersEditor from './components/SpaceMembersEditor.svelte'
import SystemAvatar from './components/SystemAvatar.svelte'
import UserBox from './components/UserBox.svelte'
import UserBoxItems from './components/UserBoxItems.svelte'
import UserBoxList from './components/UserBoxList.svelte'
import UserDetails from './components/UserDetails.svelte'
import UserInfo from './components/UserInfo.svelte'
import UsersList from './components/UsersList.svelte'
import UsersPopup from './components/UsersPopup.svelte'
import ActivityChannelPresenter from './components/activity/ActivityChannelPresenter.svelte'
import NameChangedActivityMessage from './components/activity/NameChangedActivityMessage.svelte'
import IconAddMember from './components/icons/AddMember.svelte'
import ExpandRightDouble from './components/icons/ExpandRightDouble.svelte'
import IconMembers from './components/icons/Members.svelte'
import { get, writable } from 'svelte/store'
import contact from './plugin'
import {
channelIdentifierProvider,
channelTitleProvider,
contactTitleProvider,
employeeSort,
filterChannelHasMessagesResult,
filterChannelHasNewMessagesResult,
filterChannelInResult,
filterChannelNinResult,
getContactFirstName,
getContactLastName,
getContactLink,
getContactName,
getCurrentEmployeeEmail,
getCurrentEmployeeName,
getCurrentEmployeePosition,
getPersonTooltip,
grouppingPersonManager,
resolveLocation
} from './utils'
export * from './utils'
export { employeeByIdStore, employeesStore } from './utils'
export {
AccountArrayEditor,
AccountBox,
AssigneeBox,
AssigneePopup,
Avatar,
AvatarRef,
Channels,
ChannelsDropdown,
ChannelsEditor,
ChannelsView,
CombineAvatars,
ContactPresenter,
ContactRefPresenter,
CreateGuest,
CreateOrganization,
DeleteConfirmationPopup,
EditPerson,
EditableAvatar,
EmployeeArrayEditor,
EmployeeBox,
EmployeeBrowser,
EmployeeEditor,
EmployeePresenter,
EmployeeRefPresenter,
ExpandRightDouble,
IconAddMember,
IconMembers,
MemberPresenter,
Members,
MembersBox,
MembersPresenter,
OrganizationPresenter,
PersonAccountPresenter,
PersonAccountRefPresenter,
PersonIcon,
PersonPresenter,
PersonRefPresenter,
SelectAvatars,
SelectUsersPopup,
SpaceMembers,
SystemAvatar,
UserBox,
UserBoxItems,
UserBoxList,
UserDetails,
UserInfo,
UsersList,
UsersPopup
}
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({
doc: e,
title: getName(getClient().getHierarchy(), e),
icon: Avatar,
iconProps: { size: 'x-small', avatar: e.avatar },
component: UserInfo,
componentProps: { size: 'smaller' }
})
async function queryContact (
_class: Ref<Class<Contact>>,
client: Client,
search: string,
filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }
): Promise<ObjectSearchResult[]> {
const q: DocumentQuery<Contact> = { name: { $like: `%${search}%` } }
return await doContactQuery(_class, q, filter, client)
}
async function queryEmployee (
client: Client,
search: string,
filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }
): Promise<ObjectSearchResult[]> {
const q1 = await doContactQuery(
contact.mixin.Employee,
{ name: { $like: `%${search}%` }, active: true },
filter,
client
)
const q2 = await doContactQuery(
contact.mixin.Employee,
{ displayName: { $like: `%${search}%` }, active: true },
{
in: filter?.in,
nin: [...(filter?.nin ?? []), ...Array.from(q1.map((it) => ({ _id: it.doc._id, _class: it.doc._class })))]
},
client
)
return q1.concat(q2)
}
async function doContactQuery<T extends Contact> (
_class: Ref<Class<T>>,
q: DocumentQuery<T>,
filter: { in?: RelatedDocument[] | undefined, nin?: RelatedDocument[] | undefined } | undefined,
client: Client
): Promise<ObjectSearchResult[]> {
if (_class === contact.mixin.Employee) {
q = { ...q, active: true }
}
if (filter?.in !== undefined || filter?.nin !== undefined) {
q._id = {}
if (filter.in !== undefined) {
q._id.$in = filter.in?.map((it) => it._id as Ref<T>)
}
if (filter.nin !== undefined) {
q._id.$nin = filter.nin?.map((it) => it._id as Ref<T>)
}
}
return (await client.findAll(_class, q, { limit: 200 })).map(toObjectSearchResult)
}
async function kickEmployee (doc: Person): Promise<void> {
const client = getClient()
const employee = client.getHierarchy().as(doc, contact.mixin.Employee)
const accounts = client.getModel().getAccountByPersonId(doc._id)
if (accounts.length === 0) {
await client.update(employee, { active: false })
} else {
showPopup(MessageBox, {
label: contact.string.KickEmployee,
message: contact.string.KickEmployeeDescr,
action: async () => {
const leaveWorkspace = await getResource(login.function.LeaveWorkspace)
for (const i of accounts) {
await leaveWorkspace(i.email)
}
}
})
}
}
async function openChannelURL (doc: Channel): Promise<void> {
const url = parseURL(doc.value)
if (url.startsWith('http://') || url.startsWith('https://')) {
window.open(url)
}
}
function filterPerson (doc: PersonAccount, target: PersonAccount): boolean {
return doc.person === target.person && doc._id !== target._id
}
export const personStore = writable<DocManager<PersonAccount>>(new DocManager([]))
function setStore (manager: DocManager<PersonAccount>): void {
personStore.set(manager)
}
export interface PersonLabelTooltip {
personLabel?: IntlString
placeholderLabel?: IntlString
direction?: TooltipAlignment
component?: AnySvelteComponent | AnyComponent
props?: any
}
function getPersonColor (person: Data<WithLookup<AvatarInfo>>, name: string): ColorDefinition {
const dark = get(themeStore).dark
if (person.avatarProps?.color !== undefined) {
if (person.avatarProps?.color?.startsWith('#')) {
return getPlatformColorDef(hexColorToNumber(person.avatarProps?.color), dark)
}
return getPlatformAvatarColorByName(person.avatarProps?.color, dark)
}
return getPlatformAvatarColorForTextDef(name, dark)
}
export default async (): Promise<Resources> => ({
actionImpl: {
KickEmployee: kickEmployee,
OpenChannel: openChannelURL
},
activity: {
NameChangedActivityMessage
},
component: {
CreateGuest,
ContactArrayEditor,
PersonEditor,
OrganizationEditor,
ContactPresenter,
ContactRefPresenter,
PersonRefPresenter,
PersonPresenter,
OrganizationPresenter,
ChannelsPresenter,
CreatePerson,
CollaborationUserAvatar,
CreateOrganization,
EditPerson,
EditEmployee,
EditOrganization,
SocialEditor,
Contacts,
ContactsTabs,
PersonAccountPresenter,
EmployeePresenter,
EmployeeRefPresenter,
Members,
MemberPresenter,
MembersPresenter,
EditMember,
EmployeeArrayEditor,
EmployeeEditor,
CreateEmployee,
AccountArrayEditor,
ChannelFilter,
MergePersons,
Avatar,
AvatarRef,
UserBoxList,
ChannelPresenter,
ChannelPanel,
ActivityChannelPresenter,
SpaceMembers,
SelectAvatars,
UserBoxItems,
EmployeeFilter,
EmployeeFilterValuePresenter,
PersonAccountFilterValuePresenter,
DeleteConfirmationPopup,
PersonAccountRefPresenter,
PersonIcon,
EditOrganizationPanel,
ChannelIcon,
SpaceMembersEditor
},
completion: {
EmployeeQuery: async (
client: Client,
query: string,
filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }
) => await queryEmployee(client, query, filter),
PersonQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>
await queryContact(contact.class.Person, client, query, filter),
OrganizationQuery: async (
client: Client,
query: string,
filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }
) => await queryContact(contact.class.Organization, client, query, filter)
},
function: {
GetFileUrl: async (person: Data<WithLookup<AvatarInfo>>, name: string, width: number) => {
if (person.avatar == null) {
return {
color: getPersonColor(person, name)
}
}
const blobRef = await getBlobRef(person.avatar, undefined, width)
return {
url: blobRef.src,
srcSet: blobRef.srcset,
color: getPersonColor(person, name)
}
},
GetGravatarUrl: async (person: Data<WithLookup<AvatarInfo>>, name: string, width: number = 64) => ({
url: person.avatarProps?.url !== undefined ? getGravatarUrl(person.avatarProps?.url, width) : undefined,
srcSet:
person.avatarProps?.url !== undefined
? `${getGravatarUrl(person.avatarProps?.url, width)} 1x, ${getGravatarUrl(person.avatarProps?.url, width * 2)} 2x`
: undefined,
color: getPersonColor(person, name)
}),
GetColorUrl: async (person: Data<WithLookup<AvatarInfo>>, name: string) => ({
color: getPersonColor(person, name)
}),
GetExternalUrl: async (person: Data<WithLookup<AvatarInfo>>, name: string) => ({
color: getPersonColor(person, name),
url: person.avatarProps?.url
}),
EmployeeSort: employeeSort,
FilterChannelInResult: filterChannelInResult,
FilterChannelNinResult: filterChannelNinResult,
FilterChannelHasMessagesResult: filterChannelHasMessagesResult,
FilterChannelHasNewMessagesResult: filterChannelHasNewMessagesResult,
GetCurrentEmployeeName: getCurrentEmployeeName,
GetCurrentEmployeeEmail: getCurrentEmployeeEmail,
GetCurrentEmployeePosition: getCurrentEmployeePosition,
GetContactName: getContactName,
GetContactFirstName: getContactFirstName,
GetContactLastName: getContactLastName,
GetContactLink: getContactLink,
ContactTitleProvider: contactTitleProvider,
PersonTooltipProvider: getPersonTooltip,
ChannelTitleProvider: channelTitleProvider,
ChannelIdentifierProvider: channelIdentifierProvider,
SetPersonStore: setStore,
PersonFilterFunction: filterPerson
},
resolver: {
Location: resolveLocation
},
aggregation: {
// eslint-disable-next-line @typescript-eslint/unbound-method
CreatePersonAggregationManager: AggregationManager.create,
GrouppingPersonManager: grouppingPersonManager
}
})