UBERF-11415: Optimise contact UI stores (#9185)

This commit is contained in:
Alexey Zinoviev
2025-06-10 09:44:14 +07:00
committed by GitHub
parent f1c76f6846
commit 4bf8176bad
115 changed files with 1838 additions and 943 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
import { getPersonBySocialId, formatName } from '@hcengineering/contact'
import { getPersonByPersonId, formatName } from '@hcengineering/contact'
import { Ref, TxOperations } from '@hcengineering/core'
import notification, { DocNotifyContext, CommonInboxNotification, ActivityInboxNotification, InboxNotification } from '@hcengineering/notification'
import { IntlString, addEventListener, translate } from '@hcengineering/platform'
@@ -67,7 +67,7 @@ async function hydrateNotificationAsYouCan (lastNotification: InboxNotification)
body: ''
}
const person = await getPersonBySocialId(client, lastNotification.modifiedBy)
const person = await getPersonByPersonId(client, lastNotification.modifiedBy)
if (person == null) {
return noPersonData
}
+1 -1
View File
@@ -11,7 +11,7 @@
"skipLibCheck": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"lib": ["es2016", "dom", "ES2021.String", "ESNext.Array"]
"lib": ["es2023", "dom", "ES2021.String", "ESNext.Array"]
},
"include": ["src/**/*", "declarations.d.ts"],
"exclude": ["node_modules", "lib", "dist", "types", "bundle"]
+1 -1
View File
@@ -13,7 +13,7 @@
"verbatimModuleSyntax": true,
"allowSyntheticDefaultImports": true,
"lib": [
"es2016",
"es2023",
"dom",
"ES2021.String",
"ESNext.Array"
-4
View File
@@ -826,10 +826,6 @@ export function createModel (builder: Builder): void {
presenter: contact.component.PersonPresenter
})
builder.mixin(core.class.TypePersonId, core.class.Class, view.mixin.ArrayEditor, {
inlineEditor: contact.component.PersonIdArrayEditor
})
builder.mixin(core.class.TypeAccountUuid, core.class.Class, view.mixin.ArrayEditor, {
inlineEditor: contact.component.AccountArrayEditor
})
+16
View File
@@ -14,6 +14,22 @@ export function groupByArray<T, K> (array: T[], keyProvider: (item: T) => K): Ma
return result
}
export async function groupByArrayAsync<T, K> (array: T[], keyProvider: (item: T) => Promise<K>): Promise<Map<K, T[]>> {
const result = new Map<K, T[]>()
for (const item of array) {
const key = await keyProvider(item)
if (!result.has(key)) {
result.set(key, [item])
} else {
result.get(key)?.push(item)
}
}
return result
}
export function flipSet<T> (set: Set<T>, item: T): Set<T> {
if (set.has(item)) {
set.delete(item)
+8
View File
@@ -944,6 +944,14 @@ export function notEmpty<T> (id: T | undefined | null): id is T {
return id !== undefined && id !== null && id !== ''
}
export function unique<T> (arr: T[]): T[] {
return Array.from(new Set(arr))
}
export function uniqueNotEmpty<T extends NonNullable<unknown>> (arr: Array<T | undefined | null>): T[] {
return unique(arr).filter(notEmpty)
}
/**
* Return a current performance timestamp
*/
@@ -19,7 +19,7 @@ import core, {
type Client,
type Collection,
type Doc,
groupByArray,
groupByArrayAsync,
type Hierarchy,
type Mixin,
type Ref,
@@ -37,8 +37,7 @@ import {
import contact, { type Person } from '@hcengineering/contact'
import { type IntlString } from '@hcengineering/platform'
import { type AnyComponent } from '@hcengineering/ui'
import { get } from 'svelte/store'
import { personRefByPersonIdStore } from '@hcengineering/contact-resources'
import { getPersonRefByPersonId } from '@hcengineering/contact-resources'
import activity, {
type ActivityMessage,
type DisplayActivityMessage,
@@ -257,7 +256,10 @@ export async function combineActivityMessages (
const result: Array<DisplayActivityMessage | undefined> = [...uncombined]
const groupedByType: Map<string, DocUpdateMessage[]> = groupByArray(docUpdateMessages, getDocUpdateMessageKey)
const groupedByType: Map<string, DocUpdateMessage[]> = await groupByArrayAsync(
docUpdateMessages,
getDocUpdateMessageKey
)
for (const [, groupedMessages] of groupedByType) {
const cantMerge = groupedMessages.filter(
@@ -365,8 +367,8 @@ function groupByTime<T extends ActivityMessage> (messages: T[]): T[][] {
return result
}
function getDocUpdateMessageKey (message: DocUpdateMessage): string {
const personRef = get(personRefByPersonIdStore).get(message.createdBy as any)
async function getDocUpdateMessageKey (message: DocUpdateMessage): Promise<string> {
const personRef = await getPersonRefByPersonId(message.createdBy as any)
if (message.action === 'update') {
return [message._class, message.attachedTo, message.action, personRef, getAttributeUpdatesKey(message)].join('_')
@@ -16,8 +16,8 @@
<script lang="ts">
import { ComponentExtensions, getClient, LiteMessageViewer } from '@hcengineering/presentation'
import { Person } from '@hcengineering/contact'
import { Avatar, personByPersonIdStore, SystemAvatar } from '@hcengineering/contact-resources'
import core, { PersonId, Doc, Ref, Timestamp, type WithLookup } from '@hcengineering/core'
import { Avatar, getPersonByPersonIdCb, SystemAvatar } from '@hcengineering/contact-resources'
import core, { PersonId, Doc, Timestamp } from '@hcengineering/core'
import { Icon, Label, resizeObserver, TimeSince, tooltip } from '@hcengineering/ui'
import { Asset, getEmbeddedLabel, IntlString } from '@hcengineering/platform'
import activity, { ActivityMessage, ActivityMessagePreviewType } from '@hcengineering/activity'
@@ -42,12 +42,18 @@
const tooltipLimit = 512
let isActionsOpened = false
let person: WithLookup<Person> | undefined = undefined
let person: Person | undefined = undefined
let width: number
$: isCompact = width < limit
$: person = account !== undefined ? $personByPersonIdStore.get(account) : undefined
$: if (account !== undefined) {
getPersonByPersonIdCb(account, (p) => {
person = p ?? undefined
})
} else {
person = undefined
}
export function onActionsOpened (): void {
isActionsOpened = true
@@ -14,8 +14,8 @@
-->
<script lang="ts">
import { Person } from '@hcengineering/contact'
import { personByIdStore, Avatar } from '@hcengineering/contact-resources'
import { Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
import { Avatar, getPersonByPersonRefStore } from '@hcengineering/contact-resources'
import { Doc, IdMap, notEmpty, Ref, WithLookup } from '@hcengineering/core'
import { Label, TimeSince } from '@hcengineering/ui'
import activity, { ActivityMessage } from '@hcengineering/activity'
import notification, {
@@ -49,7 +49,8 @@
$: notificationsByContextStore = inboxClient?.inboxNotificationsByContext
$: hasNew = hasNewReplies(object, $contextByDocStore, $notificationsByContextStore)
$: updateQuery(persons, $personByIdStore)
$: personByRefStore = getPersonByPersonRefStore(Array.from(persons))
$: updateQuery(persons, $personByRefStore)
function hasNewReplies (
message: ActivityMessage,
@@ -73,7 +74,7 @@
function updateQuery (personIds: Set<Ref<Person>>, personById: IdMap<Person>): void {
displayPersons = Array.from(personIds)
.map((id) => personById.get(id))
.filter((person): person is Person => person !== undefined)
.filter(notEmpty)
.slice(0, maxDisplayPersons - 1)
}
@@ -14,13 +14,14 @@
-->
<script lang="ts">
import { ActivityInfoMessage } from '@hcengineering/activity'
import { Avatar, SystemAvatar, personByPersonIdStore } from '@hcengineering/contact-resources'
import { Avatar, SystemAvatar, getPersonByPersonIdCb } from '@hcengineering/contact-resources'
import { translateCB } from '@hcengineering/platform'
import { HTMLViewer } from '@hcengineering/presentation'
import { Action, themeStore } from '@hcengineering/ui'
import ActivityMessageHeader from '../activity-message/ActivityMessageHeader.svelte'
import ActivityMessageTemplate from '../activity-message/ActivityMessageTemplate.svelte'
import { Person } from '@hcengineering/contact'
export let value: ActivityInfoMessage
export let showNotify: boolean = false
@@ -36,7 +37,10 @@
export let readonly: boolean = false
export let onClick: (() => void) | undefined = undefined
$: person = $personByPersonIdStore.get(value.createdBy ?? value.modifiedBy)
let person: Person | undefined
$: getPersonByPersonIdCb(value.createdBy ?? value.modifiedBy, (p) => {
person = p ?? undefined
})
let content = ''
@@ -16,9 +16,9 @@
import activity, { ActivityReference } from '@hcengineering/activity'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Action, Label, ShowMore } from '@hcengineering/ui'
import { personByPersonIdStore } from '@hcengineering/contact-resources'
import { getPersonByPersonIdCb } from '@hcengineering/contact-resources'
import { Doc } from '@hcengineering/core'
import { getCurrentEmployee } from '@hcengineering/contact'
import { getCurrentEmployee, Person } from '@hcengineering/contact'
import view, { ObjectPanel } from '@hcengineering/view'
import { DocNavLink, getDocLinkTitle } from '@hcengineering/view-resources'
@@ -59,7 +59,10 @@
let targetTitle: string | undefined = undefined
$: person = $personByPersonIdStore.get(value.createdBy ?? value.modifiedBy)
let person: Person | undefined
$: getPersonByPersonIdCb(value.createdBy ?? value.modifiedBy, (p) => {
person = p ?? undefined
})
$: srcDocQuery.query(value.srcDocClass, { _id: value.srcDocId }, (result) => {
srcDoc = result.shift()
@@ -21,7 +21,7 @@
DocUpdateMessage,
DocUpdateMessageViewlet
} from '@hcengineering/activity'
import { personByPersonIdStore } from '@hcengineering/contact-resources'
import { getPersonByPersonIdCb } from '@hcengineering/contact-resources'
import { AttachedDoc, Class, Collection, Doc, Ref, Space } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
@@ -36,6 +36,7 @@
import { getAttributeModel, getCollectionAttribute } from '../../activityMessagesUtils'
import { getIsTextType } from '../../utils'
import { Person } from '@hcengineering/contact'
export let value: DisplayDocUpdateMessage
export let doc: Doc | undefined = undefined
@@ -101,7 +102,14 @@
parentMessage = res as DisplayActivityMessage
})
$: person = value.createdBy !== undefined ? $personByPersonIdStore.get(value.createdBy) : undefined
let person: Person | undefined
$: if (value.createdBy !== undefined) {
getPersonByPersonIdCb(value.createdBy, (p) => {
person = p ?? undefined
})
} else {
person = undefined
}
$: void loadObject(value.objectId, value.objectClass, doc)
$: void loadParentObject(value, parentMessage, doc)
@@ -74,7 +74,7 @@
class="hulyReactions-button"
class:highlight={includesAny(emojiInfo.persons, me.socialIds)}
class:cursor-pointer={!readonly}
use:tooltip={{ component: ReactionsTooltip, props: { reactionAccounts: emojiInfo.persons } }}
use:tooltip={{ component: ReactionsTooltip, props: { socialIds: emojiInfo.persons } }}
on:click={getClickHandler({ text: emoji, image: emojiInfo.image })}
>
<span class="emoji">
@@ -2,11 +2,12 @@
import { PersonId } from '@hcengineering/core'
import { ObjectPresenter } from '@hcengineering/view-resources'
import contact from '@hcengineering/contact'
import { personRefByPersonIdStore } from '@hcengineering/contact-resources'
import { getPersonRefByPersonIdStore } from '@hcengineering/contact-resources'
export let reactionAccounts: PersonId[]
export let socialIds: PersonId[] = []
$: persons = reactionAccounts.map((user) => $personRefByPersonIdStore.get(user))
$: personRefByPersonIdStore = getPersonRefByPersonIdStore(socialIds)
$: persons = socialIds.map((si) => $personRefByPersonIdStore.get(si))
</script>
<div class="m-2 flex-col flex-gap-2">
+1 -9
View File
@@ -12,22 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { derived, writable } from 'svelte/store'
import { writable } from 'svelte/store'
import contact, { type SocialIdentity } from '@hcengineering/contact'
import { personRefByPersonIdStore } from '@hcengineering/contact-resources'
import { createQuery, onClient } from '@hcengineering/presentation'
import { aiBotEmailSocialKey } from '@hcengineering/ai-bot'
export const aiBotSocialIdentityStore = writable<SocialIdentity>()
const identityQuery = createQuery(true)
export const aiBotPersonRefStore = derived(
[personRefByPersonIdStore, aiBotSocialIdentityStore],
([personRefByPersonId, aiBotSocialIdentity]) => {
return personRefByPersonId.get(aiBotSocialIdentity?._id)
}
)
onClient(() => {
identityQuery.query(contact.class.SocialIdentity, { key: aiBotEmailSocialKey }, (res) => {
aiBotSocialIdentityStore.set(res[0])
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import contact, { Person } from '@hcengineering/contact'
import { CreateGuest, personRefByPersonIdStore } from '@hcengineering/contact-resources'
import { CreateGuest, getPersonRefByPersonId } from '@hcengineering/contact-resources'
import { Ref, type PersonId } from '@hcengineering/core'
import { IntlString, translateCB } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
@@ -117,12 +117,11 @@
}
)
$: findCompletions(value, integrations, $personRefByPersonIdStore, excluded)
$: findCompletions(value, integrations, excluded)
async function findCompletions (
val: string | undefined,
integrations: Integration[],
personRefByPersonIdStore: Map<PersonId, Ref<Person>>,
excluded: Ref<Person>[]
): Promise<void> {
if (val === undefined || val.length < 3) {
@@ -133,8 +132,8 @@
const res = new Set<Ref<Person>>()
for (const integration of integrations) {
if (integration.value.includes(val)) {
const authorPerson = personRefByPersonIdStore.get(integration.createdBy ?? integration.modifiedBy)
if (authorPerson !== undefined && !excluded.includes(authorPerson)) {
const authorPerson = await getPersonRefByPersonId(integration.createdBy ?? integration.modifiedBy)
if (authorPerson != null && !excluded.includes(authorPerson)) {
res.add(authorPerson)
}
}
@@ -15,8 +15,8 @@
<script lang="ts">
import { Person } from '@hcengineering/contact'
import { ButtonIcon, IconDelete, ModernButton, Scroller } from '@hcengineering/ui'
import { IconAddMember, personByIdStore, UserDetails } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { getPersonByPersonRefStore, IconAddMember, UserDetails } from '@hcengineering/contact-resources'
import { notEmpty, Ref } from '@hcengineering/core'
import { createEventDispatcher } from 'svelte'
import chunter from '../plugin'
@@ -27,13 +27,8 @@
const dispatch = createEventDispatcher()
let persons: Person[] = []
$: updatePersons(ids)
function updatePersons (ids: Ref<Person>[]): void {
persons = ids.map((_id) => $personByIdStore.get(_id)).filter((person): person is Person => !!person)
}
$: personByRefStore = getPersonByPersonRefStore(ids)
$: persons = ids.map((_id) => $personByRefStore.get(_id)).filter(notEmpty)
</script>
<div class="root">
@@ -15,9 +15,8 @@
<script lang="ts">
import { onMount } from 'svelte'
import chunter from '@hcengineering/chunter'
import { getName, Person, getCurrentEmployee } from '@hcengineering/contact'
import { personByIdStore } from '@hcengineering/contact-resources'
import { IdMap } from '@hcengineering/core'
import { getName, getCurrentEmployee } from '@hcengineering/contact'
import { getPersonsByPersonRefs } from '@hcengineering/contact-resources'
import { getClient } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
import { PresenceTyping } from '../types'
@@ -33,16 +32,15 @@
let typingPersonsCount = 0
let moreCount: number = 0
$: updateTypingPersons($personByIdStore, typingInfo)
$: void updateTypingPersons(typingInfo)
function updateTypingPersons (personById: IdMap<Person>, typingInfo: PresenceTyping[]): void {
async function updateTypingPersons (typingInfo: PresenceTyping[]): Promise<void> {
const now = Date.now()
const personIds = new Set(
typingInfo.filter((info) => info.person !== me && now - info.lastTyping < typingDelay).map((info) => info.person)
)
const names = Array.from(personIds)
.map((personId) => personById.get(personId))
.filter((person): person is Person => person !== undefined)
const persons = await getPersonsByPersonRefs(Array.from(personIds))
const names = Array.from(persons.values())
.map((person) => getName(hierarchy, person))
.sort((name1, name2) => name1.localeCompare(name2))
@@ -53,7 +51,7 @@
onMount(() => {
const interval = setInterval(() => {
updateTypingPersons($personByIdStore, typingInfo)
void updateTypingPersons(typingInfo)
}, typingDelay)
return () => {
clearInterval(interval)
@@ -15,7 +15,7 @@
<script lang="ts">
import { DirectMessage } from '@hcengineering/chunter'
import contact, { getCurrentEmployee } from '@hcengineering/contact'
import { CombineAvatars, personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { CombineAvatars, employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { type Ref, notEmpty } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { SearchEdit } from '@hcengineering/ui'
@@ -40,7 +40,7 @@
$: query.query(chunter.class.DirectMessage, { _id: spaceId }, (result) => {
dm = result[0]
})
$: dmPersons = dm !== undefined ? dm.members.map((m) => $personRefByAccountUuidStore.get(m)).filter(notEmpty) : []
$: dmPersons = dm !== undefined ? dm.members.map((m) => $employeeRefByAccountUuidStore.get(m)).filter(notEmpty) : []
$: dmPersonsToDisplay = dmPersons.length === 1 ? dmPersons : dmPersons.filter((p) => p !== me)
async function onSpaceEdit (): Promise<void> {
@@ -15,14 +15,9 @@
<script lang="ts">
import { DocAttributeUpdates, DocUpdateMessage } from '@hcengineering/activity'
import { Person } from '@hcengineering/contact'
import { AccountUuid, notEmpty, PersonId, Ref } from '@hcengineering/core'
import {
personRefByAccountUuidStore,
personRefByPersonIdStore,
personByIdStore,
PersonPresenter
} from '@hcengineering/contact-resources'
import { Employee, Person } from '@hcengineering/contact'
import { AccountUuid, notEmpty, PersonId } from '@hcengineering/core'
import { PersonPresenter, employeeByAccountStore, employeeByPersonIdStore } from '@hcengineering/contact-resources'
import { ChunterSpace } from '@hcengineering/chunter'
import { Label } from '@hcengineering/ui'
import view from '@hcengineering/view'
@@ -37,40 +32,32 @@
let addedPersons: Person[] = []
let removedPersons: Person[] = []
$: addedPersons = getPersons(value.added.length > 0 ? value.added : value.set, $personRefByAccountUuidStore)
$: removedPersons = getPersons(value.removed, $personRefByAccountUuidStore)
$: addedPersons = getPersons(value.added.length > 0 ? value.added : value.set, $employeeByAccountStore)
$: removedPersons = getPersons(value.removed, $employeeByAccountStore)
function getPersons (
accounts: DocAttributeUpdates['removed' | 'added' | 'set'],
personRefByAccountUuid: Map<AccountUuid, Ref<Person>>
employeeByAccountStore: Map<AccountUuid, Employee>
): Person[] {
const persons = new Set<Ref<Person>>()
const accs = new Set(accounts) as Set<AccountUuid>
for (const acc of accounts) {
const person = personRefByAccountUuid.get(acc as AccountUuid)
if (person === undefined) continue
persons.add(person)
}
return Array.from(persons)
.map((personRef) => $personByIdStore.get(personRef))
return Array.from(accs)
.map((acc) => employeeByAccountStore.get(acc))
.filter(notEmpty)
}
$: creatorPersonRef = $personRefByPersonIdStore.get(message.createdBy as PersonId)
$: creatorPerson = $employeeByPersonIdStore.get(message.createdBy as PersonId)
$: isJoined =
creatorPersonRef !== undefined &&
creatorPerson !== undefined &&
removedPersons.length === 0 &&
addedPersons.length === 1 &&
addedPersons[0]._id === creatorPersonRef
addedPersons[0]._id === creatorPerson._id
$: isLeave =
creatorPersonRef !== undefined &&
creatorPerson !== undefined &&
removedPersons.length === 1 &&
addedPersons.length === 0 &&
removedPersons[0]._id === creatorPersonRef
removedPersons[0]._id === creatorPerson._id
$: differentActions = addedPersons.length > 0 && removedPersons.length > 0
</script>
@@ -18,12 +18,8 @@
import { Attachment } from '@hcengineering/attachment'
import { AttachmentDocList, AttachmentImageSize } from '@hcengineering/attachment-resources'
import chunter, { ChatMessage, ChatMessageViewlet } from '@hcengineering/chunter'
import contact, { getCurrentEmployee, type SocialIdentityRef } from '@hcengineering/contact'
import {
personByPersonIdStore,
primarySocialIdByPersonIdStore,
socialIdsStore
} from '@hcengineering/contact-resources'
import contact, { getCurrentEmployee, Person, SocialIdentity } from '@hcengineering/contact'
import { getPersonByPersonIdCb, getSocialIdByPersonIdCb } from '@hcengineering/contact-resources'
import { Class, Doc, Markup, Ref, Space, WithLookup } from '@hcengineering/core'
import { getClient, MessageViewer, pendingCreatedDocs } from '@hcengineering/presentation'
import { EmptyMarkup } from '@hcengineering/text'
@@ -80,9 +76,19 @@
: []
$: personId = value?.createdBy
$: person = personId !== undefined ? $personByPersonIdStore.get(personId) : undefined
$: socialId = personId !== undefined ? $socialIdsStore.get(personId as SocialIdentityRef) : undefined
let person: Person | undefined
let socialId: SocialIdentity | undefined
$: if (personId !== undefined) {
getPersonByPersonIdCb(personId, (p) => {
person = p ?? undefined
})
getSocialIdByPersonIdCb(personId, (s) => {
socialId = s ?? undefined
})
} else {
person = undefined
socialId = undefined
}
let originalText = value?.message
@@ -21,10 +21,10 @@
import { Employee, Person } from '@hcengineering/contact'
import {
EmployeeBox,
personRefByPersonIdStore,
personRefByAccountUuidStore,
employeeRefByAccountUuidStore,
SelectUsersPopup,
employeeByIdStore
employeeByIdStore,
getPersonRefByPersonIdCb
} from '@hcengineering/contact-resources'
import ChannelMembers from '../ChannelMembers.svelte'
@@ -38,7 +38,13 @@
let members = new Set<Ref<Person>>()
$: creatorPersonRef = object.createdBy !== undefined ? $personRefByPersonIdStore.get(object.createdBy) : undefined
let creatorPersonRef: Ref<Person>
$: if (object.createdBy !== undefined) {
getPersonRefByPersonIdCb(object.createdBy, (personRef) => {
if (personRef == null) return
creatorPersonRef = personRef
})
}
$: disabledRemoveFor =
object.createdBy !== undefined && !socialStrings.includes(object.createdBy) && creatorPersonRef !== undefined
@@ -52,7 +58,7 @@
return
}
members = new Set(object.members.map((account) => $personRefByAccountUuidStore.get(account)).filter(notEmpty))
members = new Set(object.members.map((account) => $employeeRefByAccountUuidStore.get(account)).filter(notEmpty))
}
function getAccountsByPersons (persons: Ref<Person>[]): AccountUuid[] {
@@ -15,9 +15,9 @@
<script lang="ts">
import { Attachment, SavedAttachments } from '@hcengineering/attachment'
import { AttachmentPreview, savedAttachmentsStore } from '@hcengineering/attachment-resources'
import { Person, getName as getContactName } from '@hcengineering/contact'
import { personByPersonIdStore } from '@hcengineering/contact-resources'
import { getDisplayTime, PersonId, Ref, WithLookup } from '@hcengineering/core'
import { getName as getContactName } from '@hcengineering/contact'
import { getPersonByPersonId } from '@hcengineering/contact-resources'
import { getDisplayTime, Ref, WithLookup } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Label, Scroller, Lazy } from '@hcengineering/ui'
import activity, { ActivityMessage, SavedMessage } from '@hcengineering/activity'
@@ -48,10 +48,10 @@
})
}
function getName (attach: Attachment, personByPersonId: Map<PersonId, Person>): string | undefined {
const person = personByPersonId.get(attach.modifiedBy)
async function getName (attach: Attachment): Promise<string | undefined> {
const person = await getPersonByPersonId(attach.modifiedBy)
if (person !== undefined) {
if (person != null) {
return getContactName(client.getHierarchy(), person)
}
}
@@ -94,13 +94,15 @@
<Lazy>
<AttachmentPreview value={attach.$lookup.attachedTo} isSaved={true} />
<div class="label">
<Label
label={chunter.string.SharedBy}
params={{
name: getName(attach.$lookup.attachedTo, $personByPersonIdStore),
time: getDisplayTime(attach.modifiedOn)
}}
/>
{#await getName(attach.$lookup.attachedTo) then name}
<Label
label={chunter.string.SharedBy}
params={{
name,
time: getDisplayTime(attach.modifiedOn)
}}
/>
{/await}
</div>
</Lazy>
</div>
@@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { personByPersonIdStore } from '@hcengineering/contact-resources'
import { getPersonByPersonIdCb } from '@hcengineering/contact-resources'
import { getCurrentAccount, Markup } from '@hcengineering/core'
import { MessageViewer } from '@hcengineering/presentation'
import { Action, IconEdit, IconDelete, ShowMore } from '@hcengineering/ui'
@@ -22,6 +22,7 @@
import { ActivityMessageTemplate } from '@hcengineering/activity-resources'
import { EmptyMarkup } from '@hcengineering/text'
import { ReferenceInput } from '@hcengineering/text-editor-resources'
import { Person } from '@hcengineering/contact'
export let value: any
export let showNotify: boolean = false
@@ -48,7 +49,14 @@
const currentAccount = getCurrentAccount()
$: creatorSocialString = value?.createdBy
$: person = creatorSocialString !== undefined ? $personByPersonIdStore.get(creatorSocialString) : undefined
let person: Person | undefined
$: if (creatorSocialString !== undefined) {
getPersonByPersonIdCb(creatorSocialString, (p) => {
person = p ?? undefined
})
} else {
person = undefined
}
let isEditing = false
let additionalActions: Action[] = []
@@ -12,14 +12,17 @@
<!-- limitations under the License. -->
<script lang="ts">
import { PersonId } from '@hcengineering/core'
import { notEmpty, PersonId, Ref } from '@hcengineering/core'
import { ObjectPresenter } from '@hcengineering/view-resources'
import contact from '@hcengineering/contact'
import { personRefByPersonIdStore } from '@hcengineering/contact-resources'
import contact, { Person } from '@hcengineering/contact'
import { getPersonRefsByPersonIdsCb } from '@hcengineering/contact-resources'
export let socialIds: PersonId[] = []
$: persons = new Set(socialIds.map((user) => $personRefByPersonIdStore.get(user)))
let persons: Set<Ref<Person>>
$: getPersonRefsByPersonIdsCb(socialIds, (personsMap) => {
persons = new Set(personsMap.values().filter(notEmpty))
})
</script>
<div class="m-2 flex-col flex-gap-2">
@@ -12,9 +12,9 @@
<!-- limitations under the License. -->
<script lang="ts">
import { onMount } from 'svelte'
import { getName, Person, getCurrentEmployee } from '@hcengineering/contact'
import { personByIdStore } from '@hcengineering/contact-resources'
import { IdMap, notEmpty } from '@hcengineering/core'
import { getName, getCurrentEmployee } from '@hcengineering/contact'
import { getPersonsByPersonRefs } from '@hcengineering/contact-resources'
import { notEmpty } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
import { presenceByObjectId } from '@hcengineering/presence-resources'
@@ -37,16 +37,15 @@
$: presence = $presenceByObjectId.get(cardId) ?? []
$: typing = presence.map((p) => p.presence.typing).filter(notEmpty)
$: updateTypingPersons($personByIdStore, typing)
$: void updateTypingPersons(typing)
function updateTypingPersons (personById: IdMap<Person>, typingInfo: PresenceTyping[]): void {
async function updateTypingPersons (typingInfo: PresenceTyping[]): Promise<void> {
const now = Date.now()
const personIds = new Set(
typingInfo.filter((info) => info.person !== me && now - info.lastTyping < typingDelay).map((info) => info.person)
)
const names = Array.from(personIds)
.map((personId) => personById.get(personId))
.filter((person): person is Person => person !== undefined)
const persons = await getPersonsByPersonRefs(Array.from(personIds))
const names = Array.from(persons.values())
.map((person) => getName(hierarchy, person))
.sort((name1, name2) => name1.localeCompare(name2))
@@ -57,7 +56,7 @@
onMount(() => {
const interval = setInterval(() => {
updateTypingPersons($personByIdStore, typing)
void updateTypingPersons(typing)
}, typingDelay)
return () => {
clearInterval(interval)
@@ -14,7 +14,8 @@
-->
<script lang="ts">
import { getPersonBySocialId, Person } from '@hcengineering/contact'
import { Person } from '@hcengineering/contact'
import { employeeByPersonIdStore, getPersonByPersonId } from '@hcengineering/contact-resources'
import { getClient, getCommunicationClient } from '@hcengineering/presentation'
import { Card } from '@hcengineering/card'
import { getCurrentAccount } from '@hcengineering/core'
@@ -26,7 +27,6 @@
IconEdit,
Menu
} from '@hcengineering/ui'
import { personByPersonIdStore } from '@hcengineering/contact-resources'
import type { SocialID } from '@hcengineering/communication-types'
import { Message, MessageType } from '@hcengineering/communication-types'
import emojiPlugin from '@hcengineering/emoji'
@@ -77,10 +77,10 @@
}
async function updateAuthor (socialId: SocialID): Promise<void> {
author = $personByPersonIdStore.get(socialId)
author = $employeeByPersonIdStore.get(socialId)
if (author === undefined) {
author = await getPersonBySocialId(client, socialId)
author = (await getPersonByPersonId(socialId)) ?? undefined
}
}
+143
View File
@@ -0,0 +1,143 @@
//
// Copyright © 2025 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 { derived, get, type Readable, writable } from 'svelte/store'
import { type PersonId, type Ref } from '@hcengineering/core'
import { contactCache, type ContactCacheChange, type Person } from '@hcengineering/contact'
import { getPersonRefsByPersonIds, getPersonsByPersonIds, getPersonsByPersonRefs } from './utils'
function notEmptyValue<K, V> (entry: [K, V | null | undefined]): entry is [K, V] {
return entry[1] != null
}
export default class ContactCacheStoreManager {
private static _instance: ContactCacheStoreManager
private constructor () {
contactCache.addChangeListener(this.handlePersonCacheChange)
}
public cleanup (): void {
contactCache.removeChangeListener(this.handlePersonCacheChange)
}
public static get instance (): ContactCacheStoreManager {
if (this._instance === undefined) {
this._instance = new ContactCacheStoreManager()
}
return this._instance
}
private readonly _personRefByPersonIdStoreBase = writable<Map<PersonId, Ref<Person> | null>>(new Map())
private readonly _personRefByPersonIdStore = derived([this._personRefByPersonIdStoreBase], ([store]) => {
return new Map(Array.from(store.entries()).filter(notEmptyValue))
})
private readonly _personByPersonIdStoreBase = writable<Map<PersonId, Readonly<Person> | null>>(new Map())
private readonly _personByPersonIdStore = derived([this._personByPersonIdStoreBase], ([store]) => {
return new Map(Array.from(store.entries()).filter(notEmptyValue))
})
private readonly _personByPersonRefStoreBase = writable<Map<Ref<Person>, Readonly<Person> | null>>(new Map())
private readonly _personByPersonRefStore = derived([this._personByPersonRefStoreBase], ([store]) => {
return new Map(Array.from(store.entries()).filter(notEmptyValue))
})
public getPersonRefByPersonIdStore (personIds: PersonId[]): Readable<Map<PersonId, Ref<Person>>> {
if (personIds.length > 0) {
const oldKeys = new Set(get(this._personRefByPersonIdStoreBase).keys())
if (personIds.some((id) => !oldKeys.has(id))) {
void getPersonRefsByPersonIds(personIds).then((refsByIds) => {
this._personRefByPersonIdStoreBase.update((store) => {
for (const [id, ref] of refsByIds) {
store.set(id, ref)
}
return store
})
})
}
}
return this._personRefByPersonIdStore
}
public getPersonByPersonIdStore (personIds: PersonId[]): Readable<Map<PersonId, Readonly<Person>>> {
if (personIds.length > 0) {
const oldKeys = new Set(get(this._personByPersonIdStoreBase).keys())
if (personIds.some((id) => !oldKeys.has(id))) {
void getPersonsByPersonIds(personIds).then((personsByIds) => {
this._personByPersonIdStoreBase.update((store) => {
for (const [id, person] of personsByIds) {
store.set(id, person)
}
return store
})
})
}
}
return this._personByPersonIdStore
}
public getPersonByPersonRefStore (personRefs: Array<Ref<Person>>): Readable<Map<Ref<Person>, Readonly<Person>>> {
if (personRefs.length > 0) {
const oldKeys = new Set(get(this._personByPersonRefStoreBase).keys())
if (personRefs.some((ref) => !oldKeys.has(ref))) {
void getPersonsByPersonRefs(personRefs).then((personsByRefs) => {
this._personByPersonRefStoreBase.update((store) => {
for (const [ref, person] of personsByRefs) {
store.set(ref, person)
}
return store
})
})
}
}
return this._personByPersonRefStore
}
private readonly handlePersonCacheChange = async (change: ContactCacheChange): Promise<void> => {
const { personIds, personRef } = change
if (personIds.length > 0) {
this._personRefByPersonIdStoreBase.update((store) => {
for (const id of personIds) {
store.set(id, contactCache.personRefByPersonId.get(id) ?? null)
}
return store
})
this._personByPersonIdStoreBase.update((store) => {
for (const id of personIds) {
store.set(id, contactCache.personByPersonId.get(id) ?? null)
}
return store
})
}
if (personRef != null) {
this._personByPersonRefStoreBase.update((store) => {
store.set(personRef, contactCache.personByRef.get(personRef) ?? null)
return store
})
}
}
}
@@ -14,13 +14,13 @@
-->
<script lang="ts">
import { Contact, Employee, getCurrentEmployee, getName, Person } from '@hcengineering/contact'
import { AccountUuid, Ref } from '@hcengineering/core'
import { AccountUuid, notEmpty, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import contact from '../plugin'
import { employeeByIdStore, personRefByAccountUuidStore } from '../utils'
import { employeeByIdStore, employeeRefByAccountUuidStore } from '../utils'
import UserBoxList from './UserBoxList.svelte'
export let label: IntlString
@@ -41,10 +41,10 @@
$: valueByPersonRef = new Map(
(value ?? []).map((p) => {
const person = $personRefByAccountUuidStore.get(p)
const person = $employeeRefByAccountUuidStore.get(p)
if (person === undefined) {
console.error('Person not found for social id', p)
console.error('Person not found by account id', p)
}
return [person, p] as const
@@ -86,7 +86,7 @@
void update?.()
})
$: employees = (value ?? []).map((p) => $personRefByAccountUuidStore.get(p)).filter((p) => p !== undefined)
$: employees = (value ?? []).map((p) => $employeeRefByAccountUuidStore.get(p)).filter(notEmpty)
$: docQuery =
excludeItems.length === 0 && includeItems.length === 0
? {}
@@ -13,13 +13,13 @@
// limitations under the License.
-->
<script lang="ts">
import { Contact, Person } from '@hcengineering/contact'
import { PersonId, Ref } from '@hcengineering/core'
import { Employee } from '@hcengineering/contact'
import { notEmpty, PersonId, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { ButtonKind, ButtonSize, IconSize } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import contact from '../plugin'
import { personRefByPersonIdStore, primarySocialIdByPersonRefStore } from '../utils'
import { employeeByPersonIdStore, primarySocialIdByEmployeeRefStore } from '../utils'
import UserBox from './UserBox.svelte'
export let label: IntlString = contact.string.Employee
@@ -37,20 +37,18 @@
? {}
: {
_id: {
$in: include
.map((personId) => $personRefByPersonIdStore.get(personId))
.filter((p) => p !== undefined) as Ref<Contact>[]
$in: include.map((personId) => $employeeByPersonIdStore.get(personId)?._id).filter(notEmpty)
}
}
$: selectedEmp = value != null ? $personRefByPersonIdStore.get(value) : value
$: selectedEmp = value != null ? $employeeByPersonIdStore.get(value)?._id : value
const dispatch = createEventDispatcher()
function change (e: CustomEvent<Ref<Person> | null>): void {
function change (e: CustomEvent<Ref<Employee> | null>): void {
if (e.detail === null) {
dispatch('change', null)
} else {
const socialString = $primarySocialIdByPersonRefStore.get(e.detail)
const socialString = $primarySocialIdByEmployeeRefStore.get(e.detail)
if (socialString === undefined) {
console.error('Social id not found for person', e.detail)
return
@@ -1,21 +1,21 @@
<script lang="ts">
import { Person, getName } from '@hcengineering/contact'
import { Employee, Person, getName } from '@hcengineering/contact'
import { Ref, Space, notEmpty } from '@hcengineering/core'
import presentation, { getClient } from '@hcengineering/presentation'
import { ActionIcon, Button, IconClose, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import contact from '../plugin'
import UsersPopup from './UsersPopup.svelte'
import { personRefByAccountUuidStore, personByIdStore, primarySocialIdByPersonRefStore } from '../utils'
import { employeeByIdStore, employeeRefByAccountUuidStore } from '../utils'
export let value: Space
const dispatch = createEventDispatcher()
const client = getClient()
let membersToAdd: Ref<Person>[] = []
let membersToAdd: Ref<Employee>[] = []
const channelMembers: Ref<Person>[] = value.members
.map((acc) => {
const personRef = $personRefByAccountUuidStore.get(acc)
const personRef = $employeeRefByAccountUuidStore.get(acc)
if (personRef === undefined) {
console.error(`Person with social id ${acc} not found`)
@@ -26,11 +26,13 @@
})
.filter(notEmpty)
async function changeMembersToAdd (employees: Ref<Person>[]): Promise<void> {
$: memberAccountsToAdd = membersToAdd.map((m) => $employeeByIdStore.get(m)?.personUuid).filter(notEmpty)
async function changeMembersToAdd (employees: Ref<Employee>[]): Promise<void> {
membersToAdd = employees
}
function removeMember (_id: Ref<Person>): void {
function removeMember (_id: Ref<Employee>): void {
membersToAdd = membersToAdd.filter((m) => m !== _id)
}
</script>
@@ -53,7 +55,7 @@
{#if membersToAdd.length}
<div class="flex-row-top flex-wrap ml-6 mr-6 mt-4">
{#each membersToAdd as m}
{@const employee = $personByIdStore.get(m)}
{@const employee = $employeeByIdStore.get(m)}
<div class="mr-2 p-1 item">
{employee !== undefined ? getName(client.getHierarchy(), employee) : ''}
<div class="tool">
@@ -86,10 +88,7 @@
</div>
<Button
on:click={() => {
dispatch(
'close',
membersToAdd.map((m) => $primarySocialIdByPersonRefStore.get(m))
)
dispatch('close', memberAccountsToAdd)
}}
label={presentation.string.Add}
/>
@@ -34,7 +34,7 @@
import view from '@hcengineering/view'
import { openDoc } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import { PersonLabelTooltip, personByIdStore } from '..'
import { PersonLabelTooltip, getPersonByPersonRefStore } from '..'
import { AssigneeCategory } from '../assignee'
import AssigneePopup from './AssigneePopup.svelte'
import EmployeePresenter from './EmployeePresenter.svelte'
@@ -77,13 +77,16 @@
const client = getClient()
const updateSelected = reduceCalls(async function (value: Ref<Person> | null | undefined) {
selected = value
? $personByIdStore.get(value) ?? (await client.findOne(contact.class.Person, { _id: value }))
: undefined
$: personByRefStore = getPersonByPersonRefStore(value != null ? [value] : [])
const updateSelected = reduceCalls(async function (
value: Ref<Person> | null | undefined,
personByRefStore: Map<Ref<Person>, Readonly<Person>>
) {
selected = value ? personByRefStore.get(value) ?? undefined : undefined
})
$: void updateSelected(value)
$: void updateSelected(value, $personByRefStore)
const mgr = getFocusManager()
@@ -14,17 +14,16 @@
-->
<script lang="ts">
import contact, { type Contact, type Employee } from '@hcengineering/contact'
import { type Ref, type WithLookup } from '@hcengineering/core'
import contact, { Person, type Contact } from '@hcengineering/contact'
import { type Ref } from '@hcengineering/core'
import { Asset } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { AnySvelteComponent, IconSize } from '@hcengineering/ui'
import { employeeByIdStore, personByIdStore } from '../utils'
import { getPersonByPersonRefCb } from '../utils'
import Avatar from './Avatar.svelte'
export let _id: Ref<Contact>
export let name: string | null | undefined = undefined
export let size: IconSize
export let icon: Asset | AnySvelteComponent | undefined = undefined
@@ -32,18 +31,20 @@
export let borderColor: number | undefined = undefined
export let showStatus: boolean = false
$: empValue = $employeeByIdStore.get(_id as Ref<Employee>) ?? $personByIdStore.get(_id)
let personVal: Person | undefined
$: getPersonByPersonRefCb(_id, (person) => {
personVal = person ?? undefined
})
let _contact: WithLookup<Contact> | undefined
$: if (empValue === undefined) {
let _contact: Contact | undefined
$: if (personVal === undefined) {
void getClient()
.findOne(contact.class.Contact, { _id })
.then((c) => {
_contact = c
})
} else {
_contact = $employeeByIdStore.get(_id as Ref<Employee>) ?? $personByIdStore.get(_id)
_contact = personVal
}
</script>
@@ -19,8 +19,9 @@
import { CollaborationUser } from '@hcengineering/text-editor'
import { IconSize } from '@hcengineering/ui'
import { personByPersonIdStore } from '../utils'
import { getPersonByPersonIdCb } from '../utils'
import Avatar from './Avatar.svelte'
import { Person } from '@hcengineering/contact'
export let user: CollaborationUser
export let lastUpdate: number
@@ -29,7 +30,10 @@
let avatar: Avatar | undefined
$: lastUpdate !== 0 && avatar?.pulse()
$: person = $personByPersonIdStore.get(user.id)
let person: Person | undefined
$: getPersonByPersonIdCb(user.id, (p) => {
person = p ?? undefined
})
</script>
{#if person}
@@ -13,17 +13,17 @@
// limitations under the License.
-->
<script lang="ts">
import { getCurrentEmployee } from '@hcengineering/contact'
import { AccountRole, Doc, getCurrentAccount } from '@hcengineering/core'
import { getCurrentEmployee, Person } from '@hcengineering/contact'
import { AccountRole, Doc, getCurrentAccount, PersonId, Ref, uniqueNotEmpty } from '@hcengineering/core'
import { Card, isAdminUser } from '@hcengineering/presentation'
import ui, { Button, Label } from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources'
import view from '@hcengineering/view-resources/src/plugin'
import { createEventDispatcher } from 'svelte'
import { personRefByPersonIdStore } from '../utils'
import { PersonRefPresenter } from '..'
import { IntlString } from '@hcengineering/platform'
import { getPersonRefsByPersonIdsCb, PersonRefPresenter } from '..'
export let object: Doc | Doc[]
export let deleteAction: () => void | Promise<void>
export let skipCheck: boolean = false
@@ -33,9 +33,11 @@
const me = getCurrentEmployee()
const objectArray = Array.isArray(object) ? object : [object]
const dispatch = createEventDispatcher()
$: creators = [...new Set(objectArray.map((obj) => obj.createdBy))]
.map((pid) => (pid !== undefined ? $personRefByPersonIdStore.get(pid) : undefined))
.filter((p) => p !== undefined)
let creators: Ref<Person>[]
$: getPersonRefsByPersonIdsCb(uniqueNotEmpty(objectArray.map((obj) => obj.createdBy)), (refs) => {
creators = Array.from(refs.values())
})
$: canDelete =
(skipCheck ||
(creators.length === 1 && creators[0] === me) ||
@@ -1,10 +1,10 @@
<script lang="ts">
import { Person } from '@hcengineering/contact'
import { Employee, Person } from '@hcengineering/contact'
import { Ref, WithLookup } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import ui, { IconSize } from '@hcengineering/ui'
import { personByIdStore, PersonLabelTooltip } from '..'
import { employeeByIdStore, PersonLabelTooltip } from '..'
import PersonPresenter from '../components/PersonPresenter.svelte'
import contact from '../plugin'
import { getPreviewPopup } from './person/utils'
@@ -30,10 +30,8 @@
const client = getClient()
const h = client.getHierarchy()
$: person = typeof value === 'string' ? ($personByIdStore.get(value) as Person) : (value as Person)
$: person = typeof value === 'string' ? $employeeByIdStore.get(value as Ref<Employee>) : (value as Person)
$: employeeValue = person != null ? h.as(person, contact.mixin.Employee) : undefined
$: active = employeeValue?.active ?? false
</script>
@@ -14,13 +14,13 @@
-->
<script lang="ts">
import { Contact, Employee, getCurrentEmployee, getName, Person } from '@hcengineering/contact'
import { PersonId, Ref } from '@hcengineering/core'
import { notEmpty, PersonId, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import contact from '../plugin'
import { personRefByPersonIdStore, primarySocialIdByPersonRefStore } from '../utils'
import { employeeByPersonIdStore, primarySocialIdByEmployeeRefStore } from '../utils'
import UserBoxList from './UserBoxList.svelte'
export let label: IntlString
@@ -39,16 +39,20 @@
const client = getClient()
let update: (() => Promise<void>) | undefined
$: valueByPersonRef = new Map(
value.map((p) => {
const person = $personRefByPersonIdStore.get(p)
$: valueByPersonRef = new Map<Ref<Employee>, PersonId>(
value
.map((p) => {
const employee = $employeeByPersonIdStore.get(p)
const ref = employee?._id
if (person === undefined) {
console.error('Person not found for social id', p)
}
if (ref == null) {
console.error('Employee not found for social id', p)
return null
}
return [person, p] as const
})
return [ref, p] as const
})
.filter(notEmpty)
)
function onUpdate (evt: CustomEvent<Ref<Employee>[]>): void {
@@ -63,7 +67,7 @@
if (socialId !== undefined) {
newSocialIds.push(socialId)
} else {
const primaryId = $primarySocialIdByPersonRefStore.get(person)
const primaryId = $primarySocialIdByEmployeeRefStore.get(person)
if (primaryId === undefined) {
console.error('Primary social id not found for person', person)
@@ -89,7 +93,7 @@
void update?.()
})
$: employees = value.map((p) => $personRefByPersonIdStore.get(p)).filter((p) => p !== undefined) as Ref<Employee>[]
$: employees = value.map((p) => $employeeByPersonIdStore.get(p)?._id).filter((p) => p !== undefined)
$: docQuery =
excludeItems.length === 0 && includeItems.length === 0
? {}
@@ -16,7 +16,7 @@
import { getName, Person } from '@hcengineering/contact'
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
import type { LabelAndProps, IconSize } from '@hcengineering/ui'
import { getPersonTooltip, personByIdStore, PersonLabelTooltip } from '..'
import { getPersonByPersonRefStore, getPersonTooltip, PersonLabelTooltip } from '..'
import PersonContent from './PersonContent.svelte'
import { getClient } from '@hcengineering/presentation'
import { Ref } from '@hcengineering/core'
@@ -49,7 +49,9 @@
export let shrink: boolean = false
const client = getClient()
$: personValue = typeof value === 'string' ? $personByIdStore.get(value) : value
$: personByRefStore = typeof value === 'string' ? getPersonByPersonRefStore([value]) : undefined
$: personValue = typeof value === 'string' ? $personByRefStore?.get(value) : value
function getTooltip (
tooltipLabels: PersonLabelTooltip | undefined,
@@ -30,7 +30,7 @@
import { ActionIcon, IconAdd, IconClose, Label, SearchEdit, showPopup, themeStore } from '@hcengineering/ui'
import AddMembersPopup from './AddMembersPopup.svelte'
import UserInfo from './UserInfo.svelte'
import { personRefByAccountUuidStore, primarySocialIdByPersonRefStore } from '../utils'
import { employeeByIdStore, employeeRefByAccountUuidStore } from '../utils'
export let space: Space
export let withAddButton: boolean = false
@@ -38,7 +38,7 @@
const client = getClient()
const hierarchy = client.getHierarchy()
const initialMembers = space.members.reduce<Record<Ref<Person>, AccountUuid>>((acc, m) => {
const personRef = $personRefByAccountUuidStore.get(m)
const personRef = $employeeRefByAccountUuidStore.get(m)
if (personRef === undefined) return acc
acc[personRef] = m
return acc
@@ -51,7 +51,7 @@
let members: Set<Ref<Person>> = new Set<Ref<Person>>()
async function getUsers (accounts: AccountUuid[], search: string): Promise<Employee[]> {
const employeeRefs = accounts.map((acc) => $personRefByAccountUuidStore.get(acc)).filter(notEmpty)
const employeeRefs = accounts.map((acc) => $employeeRefByAccountUuidStore.get(acc)).filter(notEmpty)
const query: DocumentQuery<Employee> =
isSearch > 0 ? { name: { $like: '%' + search + '%' } } : { _id: { $in: employeeRefs } }
const employees = await client.findAll(contact.mixin.Employee, query, { sort: { name: SortingOrder.Descending } })
@@ -60,8 +60,8 @@
return employees
}
async function add (person: Ref<Person>): Promise<void> {
const pid = initialMembers[person] ?? $primarySocialIdByPersonRefStore.get(person)
async function add (person: Ref<Employee>): Promise<void> {
const pid = initialMembers[person] ?? $employeeByIdStore.get(person)?.personUuid
if (pid === undefined) return
await client.update(space, {
@@ -71,8 +71,8 @@
})
}
async function removeMember (person: Ref<Person>): Promise<void> {
const pid = initialMembers[person] ?? $primarySocialIdByPersonRefStore.get(person)
async function removeMember (person: Ref<Employee>): Promise<void> {
const pid = initialMembers[person] ?? $employeeByIdStore.get(person)?.personUuid
if (pid === undefined) return
await client.update(space, { $pull: { members: pid } })
@@ -21,7 +21,7 @@
import { Button, Label, showPopup } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import plugin from '../plugin'
import { personByIdStore } from '../utils'
import { getPersonByPersonRefStore } from '../utils'
import CombineAvatars from './CombineAvatars.svelte'
import UserInfo from './UserInfo.svelte'
import UsersPopup from './UsersPopup.svelte'
@@ -47,11 +47,10 @@
return (items ?? []).filter((it, idx, arr) => arr.indexOf(it) === idx)
}
let persons: Person[] = filter(items)
.map((p) => $personByIdStore.get(p))
.filter((p) => p !== undefined) as Person[]
$: personByRefStore = getPersonByPersonRefStore(items)
let persons: Person[] = []
$: persons = filter(items)
.map((p) => $personByIdStore.get(p))
.map((p) => $personByRefStore.get(p))
.filter((p) => p !== undefined) as Person[]
const dispatch = createEventDispatcher()
@@ -23,9 +23,9 @@
import ModernProfilePopup from './ModernProfilePopup.svelte'
import contact from '../../plugin'
import Avatar from '../Avatar.svelte'
import { employeeByIdStore, personByIdStore } from '../../utils'
import { employeeByIdStore } from '../../utils'
import { getPersonTimezone } from './utils'
import { EmployeePresenter } from '../../index'
import { EmployeePresenter, getPersonByPersonRefStore } from '../../index'
import TimePresenter from './TimePresenter.svelte'
import DeactivatedHeader from './DeactivatedHeader.svelte'
@@ -39,7 +39,8 @@
let timezone: string | undefined = undefined
let isEmployee: boolean = false
$: employee = $employeeByIdStore.get(_id) ?? $personByIdStore.get(_id)
$: personByRefStore = getPersonByPersonRefStore([_id])
$: employee = $employeeByIdStore.get(_id) ?? $personByRefStore.get(_id)
$: isEmployee = $employeeByIdStore.has(_id)
$: void loadPersonTimezone(employee)
@@ -3,14 +3,15 @@
import { Ref, WithLookup } from '@hcengineering/core'
import { tooltip } from '@hcengineering/ui'
import { personByIdStore } from '../..'
import { getPersonByPersonRefStore } from '../..'
import { getPreviewPopup } from './utils'
export let value: Ref<Person> | WithLookup<Person> | null | undefined
export let showPopup: boolean = true
export let inline: boolean = false
$: person = typeof value === 'string' ? ($personByIdStore.get(value) as Person) : (value as Person)
$: personByRefStore = typeof value === 'string' ? getPersonByPersonRefStore([value]) : undefined
$: person = typeof value === 'string' ? ($personByRefStore?.get(value) as Person) : (value as Person)
</script>
{#if inline}
+208 -186
View File
@@ -28,6 +28,21 @@ import {
getFirstName,
getLastName,
getName,
contactCache,
getPersonRefByPersonId as getPersonRefByPersonIdBase,
getPersonRefsByPersonIds as getPersonRefsByPersonIdsBase,
getPersonByPersonId as getPersonByPersonIdBase,
getPersonsByPersonIds as getPersonsByPersonIdsBase,
getPersonByPersonRef as getPersonByPersonRefBase,
getPersonsByPersonRefs as getPersonsByPersonRefsBase,
getPersonRefByPersonIdCb as getPersonRefByPersonIdCbBase,
getPersonRefsByPersonIdsCb as getPersonRefsByPersonIdsCbBase,
getPersonByPersonIdCb as getPersonByPersonIdCbBase,
getPersonsByPersonIdsCb as getPersonsByPersonIdsCbBase,
getPersonByPersonRefCb as getPersonByPersonRefCbBase,
getPersonsByPersonRefsCb as getPersonsByPersonRefsCbBase,
getSocialIdByPersonId as getSocialIdByPersonIdBase,
getSocialIdByPersonIdCb as getSocialIdByPersonIdCbBase,
type PermissionsBySpace,
type PermissionsStore,
type Person,
@@ -63,7 +78,7 @@ import core, {
import login from '@hcengineering/login'
import notification, { type DocNotifyContext, type InboxNotification } from '@hcengineering/notification'
import { getMetadata, getResource, type IntlString, translate } from '@hcengineering/platform'
import presentation, { createQuery, getClient, onClient } from '@hcengineering/presentation'
import presentation, { addTxListener, createQuery, getClient, onClient } from '@hcengineering/presentation'
import { type TemplateDataProvider } from '@hcengineering/templates'
import {
getCurrentResolvedLocation,
@@ -80,6 +95,7 @@ import { derived, get, type Readable, writable } from 'svelte/store'
import contact from './plugin'
import { getPreviewPopup } from './components/person/utils'
import ContactCacheStoreManager from './cache'
export function formatDate (dueDateMs: Timestamp): string {
return new Date(dueDateMs).toLocaleString('default', {
@@ -318,6 +334,9 @@ async function generateLocation (loc: Location, id: Ref<Contact>): Promise<Resol
*/
export const employeeByIdStore = writable<IdMap<WithLookup<Employee>>>(new Map())
/**
* Tracks current employee
*/
export const currentEmployeeRefStore = writable<Ref<Employee> | undefined>(getCurrentEmployee())
addEmployeeListenrer((ref) => {
@@ -330,118 +349,32 @@ export const myEmployeeStore = derived(
return currentEmployeeRef !== undefined ? employeeById.get(currentEmployeeRef) : undefined
}
)
/**
* [Ref<Person> => Person] mapping
* Does not contain ALL persons.
* Only employees for now. Later need to be extended to possibly include guests and GitHub persons.
*/
export const personByIdStore = writable<IdMap<WithLookup<Person>>>(new Map())
export const socialIdsStore = writable<IdMap<WithLookup<SocialIdentity>>>(new Map())
/**
* [Ref<Person> => SocialIdentity[]] mapping
* [Ref<Employee> => PersonId (primary)] mapping
*/
export const socialIdsByPersonRefStore: Readable<Map<Ref<Person>, SocialIdentity[]>> = derived(
[socialIdsStore],
([socialIds]) => {
const sidsByPersonRef = Array.from(socialIds.values()).reduce<Record<Ref<Person>, SocialIdentity[]>>((acc, si) => {
acc[si.attachedTo] = acc[si.attachedTo] ?? []
acc[si.attachedTo].push(si)
return acc
}, {})
export const primarySocialIdByEmployeeRefStore = writable<Map<Ref<Employee>, PersonId>>(new Map())
return new Map(Object.entries(sidsByPersonRef) as Array<[Ref<Person>, SocialIdentity[]]>)
}
)
/**
* [Ref<Person> => PersonId (primary)] mapping
*/
export const primarySocialIdByPersonRefStore: Readable<Map<Ref<Person>, PersonId>> = derived(
socialIdsByPersonRefStore,
(socialIdsByPersonRef) => {
const mapped = Array.from(socialIdsByPersonRef.entries())
.filter(([_, socialIds]) => socialIds.length > 0)
.map(([_id, socialIds]) => [_id, pickPrimarySocialId(socialIds)._id] as const)
return new Map(mapped)
}
)
/**
* [PersonId (social ID) => Ref<Person>] mapping
*/
export const personRefByPersonIdStore: Readable<Map<PersonId, Ref<Person>>> = derived(socialIdsStore, (socialIds) => {
const mapped = Array.from(socialIds.values()).map((si) => [si._id, si.attachedTo] as const)
return new Map(mapped)
})
/**
* [string (social key) => Ref<Person>] mapping
*/
export const personRefBySocialKeyStore: Readable<Map<string, Ref<Person>>> = derived(socialIdsStore, (socialIds) => {
const mapped = Array.from(socialIds.values()).map((si) => [si.key, si.attachedTo] as const)
return new Map(mapped)
})
/**
* [AccountUuid => Ref<Person>] mapping
*/
export const personRefByAccountUuidStore = writable<Map<AccountUuid, Ref<Employee>>>(new Map())
/**
* [PersonId (social ID) => Person] mapping
*/
export const personByPersonIdStore: Readable<Map<PersonId, Person>> = derived(
[personRefByPersonIdStore, personByIdStore],
([personRefByPersonId, personById]) => {
const mapped = Array.from(personRefByPersonId.entries())
.map(([personId, personRef]) => {
const person = personById.get(personRef)
if (person === undefined) {
return undefined
}
return [personId, person] as const
})
.filter(notEmpty)
return new Map(mapped)
}
)
export const employeeRefByAccountUuidStore = writable<Map<AccountUuid, Ref<Employee>>>(new Map())
/**
* [PersonId (social ID) => Employee] mapping
*/
export const employeeByPersonIdStore: Readable<Map<PersonId, Employee>> = derived(
[personByPersonIdStore, employeeByIdStore],
([personByPersonId, employeeById]) => {
const mapped = Array.from(personByPersonId.entries())
.map(([personId, person]) => {
const employee = employeeById.get(person._id as Ref<Employee>)
if (employee === undefined) return undefined
return [personId, employee] as const
})
.filter(notEmpty)
return new Map(mapped)
}
)
export const employeeByPersonIdStore = writable<Map<PersonId, Employee>>(new Map())
/**
* [string (social key) => Employee] mapping
*/
export const employeeBySocialKeyStore: Readable<Map<string, Employee>> = derived(
[personRefBySocialKeyStore, employeeByIdStore],
([personRefBySocialKey, employeeById]) => {
const mapped = Array.from(personRefBySocialKey.entries())
.map(([socialKey, personRef]) => {
const employee = employeeById.get(personRef as Ref<Employee>)
if (employee === undefined) {
return undefined
}
return [socialKey, employee] as const
})
.filter(notEmpty)
return new Map(mapped)
}
)
export const employeeBySocialKeyStore = writable<Map<string, Employee>>(new Map())
/**
* [AccountUuid => Person] mapping
*/
export const employeeByAccountStore = derived(
[personRefByAccountUuidStore, employeeByIdStore],
[employeeRefByAccountUuidStore, employeeByIdStore],
([personRefByAccount, employeeById]) => {
const mapped = Array.from(personRefByAccount.entries())
.map(([account, employeeRef]) => {
@@ -455,66 +388,67 @@ export const employeeByAccountStore = derived(
return new Map(mapped)
}
)
/**
* [PersonId (social ID) => SocialIdentity[]] mapping
*/
export const socialIdsByPersonIdStore: Readable<Map<PersonId, SocialIdentity[]>> = derived(
[personRefByPersonIdStore, socialIdsByPersonRefStore],
([personRefByPersonId, socialIdsByPersonRef]) => {
const mapped = Array.from(personRefByPersonId.entries()).map(([personId, personRef]) => {
const socialIds = socialIdsByPersonRef.get(personRef) ?? []
return [personId, socialIds] as const
})
return new Map(mapped)
}
)
/**
* [PersonId (social ID) => PersonId (primary)] mapping
*/
export const primarySocialIdByPersonIdStore: Readable<Map<PersonId, PersonId>> = derived(
socialIdsByPersonIdStore,
(socialIdsByPersonId) => {
const mapped = Array.from(socialIdsByPersonId.entries())
.filter(([_, socialIds]) => socialIds.length > 0)
.map(([personId, socialIds]) => [personId, pickPrimarySocialId(socialIds)._id] as const)
return new Map(mapped)
}
)
export const channelProviders = writable<ChannelProvider[]>([])
export const statusByUserStore = writable<Map<AccountUuid, UserStatus>>(new Map())
const providerQuery = createQuery(true)
const employeesQuery = createQuery(true)
const siQuery = createQuery(true)
onClient(() => {
providerQuery.query(contact.class.ChannelProvider, {}, (res) => {
channelProviders.set(res)
})
employeesQuery.query(contact.mixin.Employee, { active: { $in: [true, false] } }, (res) => {
employeeByIdStore.set(toIdMap(res))
// We may need to extend this later with guests and github users
personRefByAccountUuidStore.set(
new Map(
res.filter((p) => p.active && p.personUuid != null).map((p) => [p.personUuid as AccountUuid, p._id] as const)
)
)
})
siQuery.query(
contact.class.SocialIdentity,
{},
employeesQuery.query(
contact.mixin.Employee,
{ active: { $in: [true, false] } },
(res) => {
socialIdsStore.set(toIdMap(res))
const persons = res.map((it) => it.$lookup?.attachedTo).filter((it) => it !== undefined) as Person[]
personByIdStore.set(toIdMap(persons))
const personIdToEmployee = new Map<PersonId, Employee>()
const socialKeyToEmployee = new Map<string, Employee>()
for (const employee of res) {
// warmup persons cache with employees
contactCache.fillCachesForPersonRef(employee._id, employee)
for (const socialId of (employee.$lookup?.socialIds ?? []) as SocialIdentity[]) {
personIdToEmployee.set(socialId._id, employee)
socialKeyToEmployee.set(socialId.key, employee)
}
}
employeeByPersonIdStore.set(personIdToEmployee)
employeeBySocialKeyStore.set(socialKeyToEmployee)
primarySocialIdByEmployeeRefStore.set(
new Map(
res
.map((e) => {
const socialIds = (e.$lookup?.socialIds ?? []) as SocialIdentity[]
const primaryId = socialIds.length !== 0 ? pickPrimarySocialId(socialIds) : undefined
if (primaryId === undefined) {
return null
}
return [e._id, primaryId._id] as const
})
.filter(notEmpty)
)
)
// Remove lookups before storing the map as the interface doesn't expose it anyways
for (const employee of res) {
delete employee.$lookup
}
employeeByIdStore.set(toIdMap(res))
// We may need to extend this later with guests and github users
employeeRefByAccountUuidStore.set(
new Map(
res.filter((p) => p.active && p.personUuid != null).map((p) => [p.personUuid as AccountUuid, p._id] as const)
)
)
},
{
lookup: {
attachedTo: contact.class.Person
_id: { socialIds: contact.class.SocialIdentity }
}
}
)
@@ -750,65 +684,68 @@ export function getPermittedPersons (
const spacesStore = writable<Space[]>([])
export const permissionsStore = derived([spacesStore, personRefByAccountUuidStore], ([spaces, personRefByAccount]) => {
const whitelistedSpaces = new Set<Ref<Space>>()
const permissionsBySpace: PermissionsBySpace = {}
const employeesByPermission: PersonsByPermission = {}
const membersBySpace: MembersBySpace = {}
const client = getClient()
const hierarchy = client.getHierarchy()
export const permissionsStore = derived(
[spacesStore, employeeRefByAccountUuidStore],
([spaces, personRefByAccount]) => {
const whitelistedSpaces = new Set<Ref<Space>>()
const permissionsBySpace: PermissionsBySpace = {}
const employeesByPermission: PersonsByPermission = {}
const membersBySpace: MembersBySpace = {}
const client = getClient()
const hierarchy = client.getHierarchy()
for (const s of spaces) {
membersBySpace[s._id] = new Set(s.members.map((m) => personRefByAccount.get(m)).filter(notEmpty))
if (hierarchy.isDerived(s._class, core.class.TypedSpace)) {
const type = client.getModel().findAllSync(core.class.SpaceType, { _id: (s as TypedSpace).type })[0]
const mixin = type?.targetClass
for (const s of spaces) {
membersBySpace[s._id] = new Set(s.members.map((m) => personRefByAccount.get(m)).filter(notEmpty))
if (hierarchy.isDerived(s._class, core.class.TypedSpace)) {
const type = client.getModel().findAllSync(core.class.SpaceType, { _id: (s as TypedSpace).type })[0]
const mixin = type?.targetClass
if (mixin === undefined) {
permissionsBySpace[s._id] = new Set()
employeesByPermission[s._id] = {}
continue
}
const asMixin = hierarchy.as(s, mixin)
const roles = client.getModel().findAllSync(core.class.Role, { attachedTo: type._id })
const myRoles = roles.filter((r) => ((asMixin as any)[r._id] ?? []).includes(getCurrentAccount().uuid))
permissionsBySpace[s._id] = new Set(myRoles.flatMap((r) => r.permissions))
employeesByPermission[s._id] = {}
for (const role of roles) {
const assignment: AccountUuid[] = (asMixin as any)[role._id] ?? []
if (assignment.length === 0) {
if (mixin === undefined) {
permissionsBySpace[s._id] = new Set()
employeesByPermission[s._id] = {}
continue
}
for (const permissionId of role.permissions) {
if (employeesByPermission[s._id][permissionId] === undefined) {
employeesByPermission[s._id][permissionId] = new Set()
const asMixin = hierarchy.as(s, mixin)
const roles = client.getModel().findAllSync(core.class.Role, { attachedTo: type._id })
const myRoles = roles.filter((r) => ((asMixin as any)[r._id] ?? []).includes(getCurrentAccount().uuid))
permissionsBySpace[s._id] = new Set(myRoles.flatMap((r) => r.permissions))
employeesByPermission[s._id] = {}
for (const role of roles) {
const assignment: AccountUuid[] = (asMixin as any)[role._id] ?? []
if (assignment.length === 0) {
continue
}
assignment.forEach((acc) => {
const personRef = personRefByAccount.get(acc)
if (personRef !== undefined) {
employeesByPermission[s._id][permissionId].add(personRef)
for (const permissionId of role.permissions) {
if (employeesByPermission[s._id][permissionId] === undefined) {
employeesByPermission[s._id][permissionId] = new Set()
}
})
assignment.forEach((acc) => {
const personRef = personRefByAccount.get(acc)
if (personRef !== undefined) {
employeesByPermission[s._id][permissionId].add(personRef)
}
})
}
}
} else {
whitelistedSpaces.add(s._id)
}
} else {
whitelistedSpaces.add(s._id)
}
return {
ps: permissionsBySpace,
ap: employeesByPermission,
ms: membersBySpace,
whitelist: whitelistedSpaces
}
}
return {
ps: permissionsBySpace,
ap: employeesByPermission,
ms: membersBySpace,
whitelist: whitelistedSpaces
}
})
)
const spaceTypesQuery = createQuery(true)
const permissionsQuery = createQuery(true)
@@ -844,3 +781,88 @@ export function getAccountClient (): AccountClient {
return getAccountClientRaw(accountsUrl, token)
}
export async function getPersonRefByPersonId (personId: PersonId): Promise<Ref<Person> | null> {
return await getPersonRefByPersonIdBase(getClient(), personId)
}
export function getPersonRefByPersonIdCb (personId: PersonId, cb: (person: Ref<Person> | null) => void): void {
getPersonRefByPersonIdCbBase(getClient(), personId, cb)
}
export async function getPersonRefsByPersonIds (personIds: PersonId[]): Promise<Map<PersonId, Ref<Person>>> {
return await getPersonRefsByPersonIdsBase(getClient(), personIds)
}
export function getPersonRefsByPersonIdsCb (
personIds: PersonId[],
cb: (personRefs: Map<PersonId, Ref<Person>>) => void
): void {
getPersonRefsByPersonIdsCbBase(getClient(), personIds, cb)
}
export async function getPersonByPersonId (personId: PersonId): Promise<Person | null> {
return await getPersonByPersonIdBase(getClient(), personId)
}
export function getPersonByPersonIdCb (personId: PersonId, cb: (person: Readonly<Person> | null) => void): void {
getPersonByPersonIdCbBase(getClient(), personId, cb)
}
export async function getPersonsByPersonIds (personIds: PersonId[]): Promise<Map<PersonId, Readonly<Person>>> {
return await getPersonsByPersonIdsBase(getClient(), personIds)
}
export function getPersonsByPersonIdsCb (
personIds: PersonId[],
cb: (persons: Map<PersonId, Readonly<Person>>) => void
): void {
getPersonsByPersonIdsCbBase(getClient(), personIds, cb)
}
export async function getPersonByPersonRef (personRef: Ref<Person>): Promise<Person | null> {
return await getPersonByPersonRefBase(getClient(), personRef)
}
export function getPersonByPersonRefCb (personRef: Ref<Person>, cb: (person: Readonly<Person> | null) => void): void {
getPersonByPersonRefCbBase(getClient(), personRef, cb)
}
export async function getPersonsByPersonRefs (
personRefs: Array<Ref<Person>>
): Promise<Map<Ref<Person>, Readonly<Person>>> {
return await getPersonsByPersonRefsBase(getClient(), personRefs)
}
export function getPersonsByPersonRefsCb (
personRefs: Array<Ref<Person>>,
cb: (persons: Map<Ref<Person>, Readonly<Person>>) => void
): void {
getPersonsByPersonRefsCbBase(getClient(), personRefs, cb)
}
export async function getSocialIdByPersonId (personId: PersonId): Promise<SocialIdentity | null> {
return await getSocialIdByPersonIdBase(getClient(), personId)
}
export function getSocialIdByPersonIdCb (personId: PersonId, cb: (socialId: SocialIdentity | null) => void): void {
getSocialIdByPersonIdCbBase(getClient(), personId, cb)
}
addTxListener(contactCache.handleTx)
export const contactCacheStoreManager = ContactCacheStoreManager.instance
export function getPersonRefByPersonIdStore (personIds: PersonId[]): Readable<Map<PersonId, Ref<Person>>> {
return contactCacheStoreManager.getPersonRefByPersonIdStore(personIds)
}
export function getPersonByPersonIdStore (personIds: PersonId[]): Readable<Map<PersonId, Readonly<Person>>> {
return contactCacheStoreManager.getPersonByPersonIdStore(personIds)
}
export function getPersonByPersonRefStore (
personRefs: Array<Ref<Person>>
): Readable<Map<Ref<Person>, Readonly<Person>>> {
return contactCacheStoreManager.getPersonByPersonRefStore(personRefs)
}
+246
View File
@@ -0,0 +1,246 @@
//
// Copyright © 2025 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 core, {
Doc,
PersonId,
Ref,
Tx,
TxCreateDoc,
TxCUD,
TxMixin,
TxProcessor,
TxUpdateDoc,
WithLookup
} from '@hcengineering/core'
import contact, { Person, SocialIdentity, SocialIdentityRef } from '.'
function isCreateTx (tx: Tx): tx is TxCreateDoc<Doc> {
return tx._class === core.class.TxCreateDoc
}
function isUpdateTx (tx: Tx): tx is TxUpdateDoc<Doc> {
return tx._class === core.class.TxUpdateDoc
}
function isMixinTx (tx: Tx): tx is TxMixin<Doc, any> {
return tx._class === core.class.TxMixin
}
function isPersonTx (tx: TxCUD<Doc>): tx is TxCUD<Person> {
return tx.objectClass === contact.class.Person
}
function isSocialIdentityTx (tx: TxCUD<Doc>): tx is TxCUD<SocialIdentity> {
return tx.objectClass === contact.class.SocialIdentity
}
export interface Change {
personRef: Ref<Person>
personIds: PersonId[]
}
export default class ContactCache {
private static _instance: ContactCache
/**
* [PersonId (socialId _id) => Ref<Person>] cache
*/
private readonly _personIdsByPersonRef = new Map<Ref<Person>, Set<PersonId>>()
/**
* [PersonId (socialId _id) => Ref<Person>] cache
*/
private readonly _personRefByPersonId = new Map<PersonId, Ref<Person> | null>()
/**
* [PersonId (socialId _id) => Person] cache
*/
private readonly _personByPersonId = new Map<PersonId, Person | null>()
/**
* [Ref<Person> => Person] cache
*/
private readonly _personByRef = new Map<Ref<Person>, Person | null>()
/**
* [PersonId (socialId _id) => SocialIdentity] cache
*/
private readonly _socialIdByPersonId = new Map<PersonId, SocialIdentity | null>()
private readonly _changeListeners: Array<(change: Change) => void | Promise<void>> = []
private constructor () {
// Private constructor to prevent direct instantiation
}
public static get instance (): ContactCache {
if (this._instance === undefined) {
this._instance = new ContactCache()
}
return this._instance
}
public get personRefByPersonId (): ReadonlyMap<PersonId, Ref<Person> | null> {
return this._personRefByPersonId
}
public get personByPersonId (): ReadonlyMap<PersonId, Readonly<Person> | null> {
return this._personByPersonId
// Alternatively, can build from two other maps but might be not as performant
// return new Map(this._personRefByPersonId.entries().map(([personId, personRef]) => [personId, personRef != null ? this._personByRef.get(personRef) ?? null : null]))
}
public get personByRef (): ReadonlyMap<Ref<Person>, Readonly<Person> | null> {
return this._personByRef
}
public get socialIdByPersonId (): ReadonlyMap<PersonId, Readonly<SocialIdentity> | null> {
return this._socialIdByPersonId
}
private addPersonIdToPersonRef (personRef: Ref<Person>, personId: PersonId): void {
this._personIdsByPersonRef.set(personRef, (this._personIdsByPersonRef.get(personRef) ?? new Set()).add(personId))
}
public fillCachesForPersonId (personId: PersonId, socialId: WithLookup<SocialIdentity> | null | undefined): void {
if (socialId == null) {
// Shouldn't normally be the case, if the social id with the given id is not found at all, it's weird from where the id came from.
// So might be some garbage data in the database, so just set nulls to skip it.
this._personRefByPersonId.set(personId, null)
this._personByPersonId.set(personId, null)
this._socialIdByPersonId.set(personId, null)
return
}
const person = socialId.$lookup?.attachedTo
this._personRefByPersonId.set(personId, socialId.attachedTo)
this.addPersonIdToPersonRef(socialId.attachedTo, personId)
this._personByPersonId.set(personId, person ?? null)
this._socialIdByPersonId.set(personId, socialId)
if (person != null) {
this._personByRef.set(person._id, person)
}
}
public fillCachesForPersonRef (personRef: Ref<Person>, person: WithLookup<Person> | null | undefined): void {
this._personByRef.set(personRef, person ?? null)
for (const sidObj of (person?.$lookup?.socialIds ?? []) as SocialIdentity[]) {
this._personRefByPersonId.set(sidObj._id, personRef)
this.addPersonIdToPersonRef(personRef, sidObj._id)
this._personByPersonId.set(sidObj._id, person ?? null)
}
}
private shouldHandleCreateTx (tx: Tx): tx is TxCreateDoc<Person> | TxCreateDoc<SocialIdentity> {
return isCreateTx(tx) && (isPersonTx(tx) || isSocialIdentityTx(tx))
}
private shouldHandleUpdateOrMixinTx (tx: Tx): tx is TxUpdateDoc<Person> | TxMixin<Person, any> {
return (isUpdateTx(tx) || isMixinTx(tx)) && isPersonTx(tx)
}
public handleTx = (txes: Tx[]): void => {
for (const tx of txes) {
if (this.shouldHandleCreateTx(tx)) {
if (isPersonTx(tx)) {
this.handleCreatePersonTx(tx)
} else if (isSocialIdentityTx(tx)) {
this.handleCreateSocialIdentityTx(tx)
}
} else if (this.shouldHandleUpdateOrMixinTx(tx)) {
this.handleUpdateOrMixinPersonTx(tx)
}
}
}
private handleCreatePersonTx (tx: TxCreateDoc<Person>): void {
const ref = tx.objectId
const person = this._personByRef.get(ref)
if (person === undefined) return
const createdPerson = TxProcessor.createDoc2Doc(tx)
this._personByRef.set(ref, createdPerson)
const personIds = Array.from(this._personIdsByPersonRef.get(ref) ?? [])
for (const personId of personIds) {
this._personByPersonId.set(personId, createdPerson)
}
this.broadcastChange({
personRef: ref,
personIds
})
}
private handleCreateSocialIdentityTx (tx: TxCreateDoc<SocialIdentity>): void {
const personId = tx.objectId as SocialIdentityRef
const personRef = this._personRefByPersonId.get(personId)
if (personRef === undefined) return
const newPersonRef = tx.attachedTo as Ref<Person>
this._personRefByPersonId.set(personId, newPersonRef)
this.addPersonIdToPersonRef(newPersonRef, personId)
const createdSocialId = TxProcessor.createDoc2Doc(tx)
this._socialIdByPersonId.set(personId, createdSocialId)
this.broadcastChange({
personRef: newPersonRef,
personIds: [personId]
})
}
private handleUpdateOrMixinPersonTx (tx: TxUpdateDoc<Person> | TxMixin<Person, any>): void {
const ref = tx.objectId
const person = this._personByRef.get(ref)
if (person == null) return
const updatedPerson = isUpdateTx(tx)
? TxProcessor.updateDoc2Doc(person, tx)
: TxProcessor.updateMixin4Doc(person, tx)
this._personByRef.set(ref, updatedPerson)
const personIds = Array.from(this._personIdsByPersonRef.get(ref) ?? [])
for (const personId of personIds) {
this._personByPersonId.set(personId, updatedPerson)
}
this.broadcastChange({
personRef: ref,
personIds
})
}
public addChangeListener (listener: (change: Change) => void | Promise<void>): void {
this._changeListeners.push(listener)
}
public removeChangeListener (listener: (change: Change) => void | Promise<void>): void {
const pos = this._changeListeners.findIndex((it) => it === listener)
if (pos !== -1) {
this._changeListeners.splice(pos, 1)
}
}
private broadcastChange (change: Change): void {
for (const listener of this._changeListeners) {
void listener(change)
}
}
}
+301 -18
View File
@@ -37,9 +37,19 @@ import {
} from '@hcengineering/core'
import { getMetadata } from '@hcengineering/platform'
import { ColorDefinition } from '@hcengineering/ui'
import contact, { AvatarProvider, AvatarType, Channel, Contact, Employee, Person, SocialIdentityRef } from '.'
import contact, {
AvatarProvider,
AvatarType,
Channel,
Contact,
Employee,
Person,
SocialIdentity,
SocialIdentityRef
} from '.'
import { AVATAR_COLORS, GravatarPlaceholderType } from './types'
import ContactCache from './cache'
let currentEmployee: Ref<Employee>
@@ -294,14 +304,6 @@ export async function getPersonBySocialKey (client: Client, socialKey: string):
return await client.findOne(contact.class.Person, { _id: socialId?.attachedTo, _class: socialId?.attachedToClass })
}
export async function getPersonBySocialId (client: Client, socialIdString: PersonId): Promise<Person | undefined> {
const socialId = await client.findOne(contact.class.SocialIdentity, { _id: socialIdString as SocialIdentityRef })
if (socialId === undefined) return undefined
return await client.findOne(contact.class.Person, { _id: socialId?.attachedTo, _class: socialId?.attachedToClass })
}
export async function getEmployeeBySocialId (client: Client, socialIdString: PersonId): Promise<Employee | undefined> {
const socialId = await client.findOne(contact.class.SocialIdentity, { _id: socialIdString as SocialIdentityRef })
@@ -310,15 +312,6 @@ export async function getEmployeeBySocialId (client: Client, socialIdString: Per
return await client.findOne(contact.mixin.Employee, { _id: socialId.attachedTo as Ref<Employee> })
}
export async function getPersonRefBySocialId (
client: Client,
socialIdString: PersonId
): Promise<Ref<Person> | undefined> {
const socialId = await client.findOne(contact.class.SocialIdentity, { _id: socialIdString as SocialIdentityRef })
return socialId?.attachedTo
}
export async function getPersonRefsBySocialIds (
client: Client,
ids: PersonId[] = []
@@ -549,3 +542,293 @@ export async function ensureEmployeeForPerson (
// TODO: check for merged persons with this one and do the merge
return personRef as Ref<Employee>
}
export const contactCache = ContactCache.instance
export async function loadCachesForPersonId (client: Client, personId: PersonId): Promise<void> {
const sidObj = await client.findOne(
contact.class.SocialIdentity,
{
_id: personId as SocialIdentityRef
},
{
lookup: {
attachedTo: contact.class.Person
}
}
)
contactCache.fillCachesForPersonId(personId, sidObj)
}
export async function loadCachesForPersonIds (client: Client, personIds: PersonId[]): Promise<void> {
const sidObjsMap = toIdMap(
await client.findAll(
contact.class.SocialIdentity,
{
_id: { $in: personIds as SocialIdentityRef[] }
},
{
lookup: {
attachedTo: contact.class.Person
}
}
)
)
for (const personId of personIds) {
const sidObj = sidObjsMap.get(personId as SocialIdentityRef)
contactCache.fillCachesForPersonId(personId, sidObj)
}
}
export async function loadCachesForPersonRef (client: Client, personRef: Ref<Person>): Promise<void> {
const person = await client.findOne(
contact.class.Person,
{
_id: personRef
},
{
lookup: {
_id: { socialIds: contact.class.SocialIdentity }
}
}
)
contactCache.fillCachesForPersonRef(personRef, person)
}
export async function loadCachesForPersonRefs (client: Client, personRefs: Array<Ref<Person>>): Promise<void> {
const persons = toIdMap(
await client.findAll(
contact.class.Person,
{
_id: { $in: personRefs }
},
{
lookup: {
_id: { socialIds: contact.class.SocialIdentity }
}
}
)
)
for (const personRef of personRefs) {
const person = persons.get(personRef)
contactCache.fillCachesForPersonRef(personRef, person)
}
}
export async function getPersonRefByPersonId (client: Client, personId: PersonId): Promise<Ref<Person> | null> {
if (!contactCache.personRefByPersonId.has(personId)) {
await loadCachesForPersonId(client, personId)
}
return contactCache.personRefByPersonId.get(personId) ?? null
}
export function getPersonRefByPersonIdCb (
client: Client,
personId: PersonId,
cb: (person: Ref<Person> | null) => void
): void {
let personRef: Ref<Person> | null | undefined = contactCache.personRefByPersonId.get(personId)
if (personRef !== undefined) {
cb(personRef)
} else {
void loadCachesForPersonId(client, personId).then(() => {
personRef = contactCache.personRefByPersonId.get(personId)
cb(personRef ?? null)
})
}
}
function getPersonRefsByPersonIdsFromCache (personIds: PersonId[]): Map<PersonId, Ref<Person>> {
return new Map(
personIds
.map((pid) => {
const ref = contactCache.personRefByPersonId.get(pid)
return ref != null ? ([pid, ref] as const) : undefined
})
.filter(notEmpty)
)
}
export async function getPersonRefsByPersonIds (
client: Client,
personIds: PersonId[]
): Promise<Map<PersonId, Ref<Person>>> {
if (personIds.some((personId) => !contactCache.personRefByPersonId.has(personId))) {
await loadCachesForPersonIds(client, personIds)
}
return getPersonRefsByPersonIdsFromCache(personIds)
}
export function getPersonRefsByPersonIdsCb (
client: Client,
personIds: PersonId[],
cb: (personRefs: Map<PersonId, Ref<Person>>) => void
): void {
if (personIds.some((personId) => !contactCache.personRefByPersonId.has(personId))) {
void loadCachesForPersonIds(client, personIds).then(() => {
const personRefs = getPersonRefsByPersonIdsFromCache(personIds)
cb(personRefs)
})
} else {
const personRefs = getPersonRefsByPersonIdsFromCache(personIds)
cb(personRefs)
}
}
export async function getPersonByPersonId (client: Client, personId: PersonId): Promise<Readonly<Person> | null> {
if (!contactCache.personByPersonId.has(personId)) {
await loadCachesForPersonId(client, personId)
}
return contactCache.personByPersonId.get(personId) ?? null
}
export function getPersonByPersonIdCb (
client: Client,
personId: PersonId,
cb: (person: Readonly<Person> | null) => void
): void {
let person: Readonly<Person> | null | undefined = contactCache.personByPersonId.get(personId)
if (person !== undefined) {
cb(person)
} else {
void loadCachesForPersonId(client, personId).then(() => {
person = contactCache.personByPersonId.get(personId) ?? null
cb(person ?? null)
})
}
}
function getPersonsByPersonIdsFromCache (personIds: PersonId[]): Map<PersonId, Readonly<Person>> {
return new Map(
personIds
.map((pid) => {
const person = contactCache.personByPersonId.get(pid)
return person != null ? ([pid, person] as const) : undefined
})
.filter(notEmpty)
)
}
export async function getPersonsByPersonIds (
client: Client,
personIds: PersonId[]
): Promise<Map<PersonId, Readonly<Person>>> {
if (personIds.some((personId) => !contactCache.personByPersonId.has(personId))) {
await loadCachesForPersonIds(client, personIds)
}
return getPersonsByPersonIdsFromCache(personIds)
}
export function getPersonsByPersonIdsCb (
client: Client,
personIds: PersonId[],
cb: (persons: Map<PersonId, Readonly<Person>>) => void
): void {
if (personIds.some((personId) => !contactCache.personByPersonId.has(personId))) {
void loadCachesForPersonIds(client, personIds).then(() => {
const persons = getPersonsByPersonIdsFromCache(personIds)
cb(persons)
})
} else {
const persons = getPersonsByPersonIdsFromCache(personIds)
cb(persons)
}
}
export async function getPersonByPersonRef (client: Client, personRef: Ref<Person>): Promise<Readonly<Person> | null> {
if (!contactCache.personByRef.has(personRef)) {
await loadCachesForPersonRef(client, personRef)
}
return contactCache.personByRef.get(personRef) ?? null
}
export function getPersonByPersonRefCb (
client: Client,
personRef: Ref<Person>,
cb: (person: Readonly<Person> | null) => void
): void {
let person: Readonly<Person> | null | undefined = contactCache.personByRef.get(personRef)
if (person !== undefined) {
cb(person)
} else {
void loadCachesForPersonRef(client, personRef).then(() => {
person = contactCache.personByRef.get(personRef) ?? null
cb(person ?? null)
})
}
}
function getPersonsByPersonRefsFromCache (personRefs: Array<Ref<Person>>): Map<Ref<Person>, Readonly<Person>> {
return new Map(
personRefs
.map((personRef) => {
const person = contactCache.personByRef.get(personRef)
return person != null ? ([personRef, person] as const) : undefined
})
.filter(notEmpty)
)
}
export async function getPersonsByPersonRefs (
client: Client,
personRefs: Array<Ref<Person>>
): Promise<Map<Ref<Person>, Readonly<Person>>> {
if (personRefs.some((personRef) => !contactCache.personByRef.has(personRef))) {
await loadCachesForPersonRefs(client, personRefs)
}
return getPersonsByPersonRefsFromCache(personRefs)
}
export function getPersonsByPersonRefsCb (
client: Client,
personRefs: Array<Ref<Person>>,
cb: (persons: Map<Ref<Person>, Readonly<Person>>) => void
): void {
if (personRefs.some((personRef) => !contactCache.personByRef.has(personRef))) {
void loadCachesForPersonRefs(client, personRefs).then(() => {
const persons = getPersonsByPersonRefsFromCache(personRefs)
cb(persons)
})
} else {
const persons = getPersonsByPersonRefsFromCache(personRefs)
cb(persons)
}
}
export async function getSocialIdByPersonId (client: Client, personId: PersonId): Promise<SocialIdentity | null> {
if (!contactCache.personRefByPersonId.has(personId)) {
await loadCachesForPersonId(client, personId)
}
return contactCache.socialIdByPersonId.get(personId) ?? null
}
export function getSocialIdByPersonIdCb (
client: Client,
personId: PersonId,
cb: (socialId: SocialIdentity | null) => void
): void {
let socialId: SocialIdentity | null | undefined = contactCache.socialIdByPersonId.get(personId)
if (socialId !== undefined) {
cb(socialId)
} else {
void loadCachesForPersonId(client, personId).then(() => {
socialId = contactCache.socialIdByPersonId.get(personId) ?? null
cb(socialId ?? null)
})
}
}
export type { Change as ContactCacheChange } from './cache'
@@ -15,7 +15,7 @@
<script lang="ts">
import { deepEqual } from 'fast-equals'
import { createEventDispatcher } from 'svelte'
import { AccountArrayEditor, personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { AccountArrayEditor, employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
import core, {
Data,
DocumentUpdate,
@@ -56,7 +56,7 @@
let rolesAssignment: RolesAssignment = {}
$: isNew = docSpace === undefined
$: membersPersons = members.map((m) => $personRefByAccountUuidStore.get(m)).filter(notEmpty)
$: membersPersons = members.map((m) => $employeeRefByAccountUuidStore.get(m)).filter(notEmpty)
let typeId: Ref<DocumentSpaceType> | undefined = docSpace?.type ?? documents.spaceType.DocumentSpaceType
let spaceType: WithLookup<DocumentSpaceType> | undefined
@@ -14,11 +14,12 @@
-->
<script lang="ts">
import { Employee, Person, formatName } from '@hcengineering/contact'
import { employeeByIdStore, personRefByPersonIdStore } from '@hcengineering/contact-resources'
import { employeeByIdStore } from '@hcengineering/contact-resources'
import documents, {
ControlledDocument,
DocumentRequest,
emptyBundle,
extractValidationWorkflow
DocumentValidationState,
emptyBundle
} from '@hcengineering/controlled-documents'
import { Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
@@ -30,7 +31,7 @@
$controlledDocument as controlledDocument,
$documentSnapshots as documentSnapshots
} from '../../stores/editors/document/editor'
import { formatSignatureDate } from '../../utils'
import { formatSignatureDate, extractValidationWorkflow } from '../../utils'
let requests: DocumentRequest[] = []
@@ -45,16 +46,15 @@
})
}
$: workflow = extractValidationWorkflow(
hierarchy,
{
...emptyBundle(),
ControlledDocument: doc ? [doc] : [],
DocumentRequest: requests,
DocumentSnapshot: $documentSnapshots
},
(ref) => $personRefByPersonIdStore.get(ref)
)
let workflow: Map<Ref<ControlledDocument>, DocumentValidationState[]>
$: void extractValidationWorkflow(hierarchy, {
...emptyBundle(),
ControlledDocument: doc ? [doc] : [],
DocumentRequest: requests,
DocumentSnapshot: $documentSnapshots
}).then((res) => {
workflow = res
})
$: state = (doc ? workflow?.get(doc._id) ?? [] : [])[0]
$: signers = (state?.approvals ?? [])
@@ -16,7 +16,7 @@
<script lang="ts">
import documents, { Document } from '@hcengineering/controlled-documents'
import { Employee } from '@hcengineering/contact'
import { EmployeeBox, EmployeePresenter, personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { EmployeeBox, EmployeePresenter, employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
import core, { Ref, Space, notEmpty } from '@hcengineering/core'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { Button, Icon, Label } from '@hcengineering/ui'
@@ -60,7 +60,7 @@
$: isOwner = isDocOwner(object)
$: members = space?.members ?? []
$: employees = members.map((m) => $personRefByAccountUuidStore.get(m) as Ref<Employee>).filter(notEmpty)
$: employees = members.map((m) => $employeeRefByAccountUuidStore.get(m) as Ref<Employee>).filter(notEmpty)
$: docQuery = space?.private ?? false ? { active: true, _id: { $in: employees } } : { active: true }
</script>
@@ -1,17 +1,17 @@
<script lang="ts">
import documents, {
ControlledDocument,
ControlledDocumentState,
DocumentRequest,
DocumentState,
emptyBundle,
extractValidationWorkflow
DocumentValidationState,
emptyBundle
} from '@hcengineering/controlled-documents'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Label, Scroller } from '@hcengineering/ui'
import chunter, { ChatMessage } from '@hcengineering/chunter'
import { personRefByPersonIdStore } from '@hcengineering/contact-resources'
import documentsRes from '../../../plugin'
import {
$controlledDocument as controlledDocument,
@@ -21,6 +21,8 @@
import DocumentApprovalGuideItem from './DocumentApprovalGuideItem.svelte'
import DocumentApprovalItem from './DocumentApprovalItem.svelte'
import RightPanelTabHeader from './RightPanelTabHeader.svelte'
import { extractValidationWorkflow } from '../../../utils'
import { Ref } from '@hcengineering/core'
const client = getClient()
const hierarchy = client.getHierarchy()
@@ -43,19 +45,18 @@
})
}
$: workflow = extractValidationWorkflow(
hierarchy,
{
...emptyBundle(),
ControlledDocument: doc ? [doc] : [],
DocumentRequest: requests,
DocumentSnapshot: $documentSnapshots,
ChatMessage: messages
},
(ref) => $personRefByPersonIdStore.get(ref)
)
let workflow: Map<Ref<ControlledDocument>, DocumentValidationState[]> | undefined
$: void extractValidationWorkflow(hierarchy, {
...emptyBundle(),
ControlledDocument: doc ? [doc] : [],
DocumentRequest: requests,
DocumentSnapshot: $documentSnapshots,
ChatMessage: messages
}).then((res) => {
workflow = res
})
$: validationStates = ((doc ? workflow.get(doc._id) : []) ?? []).slice()
$: validationStates = ((doc ? workflow?.get(doc._id) : []) ?? []).slice()
const noGuideStates: (ControlledDocumentState | undefined)[] = [
ControlledDocumentState.Approved,
@@ -29,7 +29,9 @@ import documents, {
type ProjectDocument,
type ProjectMeta,
ControlledDocumentState,
type DocumentApprovalState,
DocumentState,
type DocumentValidationState,
ProjectDocumentTree,
compareDocumentVersions,
emptyBundle,
@@ -65,6 +67,7 @@ import { makeRank } from '@hcengineering/rank'
import { getProjectDocumentLink } from './navigation'
import documentsResources from './plugin'
import { wizardOpened } from './stores/wizards/create-document'
import { getPersonRefByPersonId, getPersonRefsByPersonIds } from '@hcengineering/contact-resources'
export type TranslatedDocumentStates = Readonly<Record<DocumentState, string>>
@@ -1024,3 +1027,125 @@ export async function syncDocumentMetaTitle (
await client.update(meta, { title: `${code} ${title}` })
}
}
export async function extractValidationWorkflow (
hierarchy: Hierarchy,
bundle: DocumentBundle
): Promise<Map<Ref<ControlledDocument>, DocumentValidationState[]>> {
const result = new Map<Ref<ControlledDocument>, DocumentValidationState[]>()
const getApprovalStates = async (request: DocumentRequest | undefined): Promise<DocumentApprovalState[]> => {
if (request === undefined) return []
const role = hierarchy.isDerived(request._class, documents.class.DocumentReviewRequest) ? 'reviewer' : 'approver'
const rejected: DocumentApprovalState[] =
request.rejected !== undefined
? [
{
person: request.rejected,
role,
state: 'rejected',
timestamp: request.modifiedOn
}
]
: []
const approved: DocumentApprovalState[] = request.approved.map((person, idx) => {
return {
person,
role,
state: 'approved',
timestamp: request.approvedDates?.[idx] ?? request.modifiedOn
}
})
const ignored: DocumentApprovalState[] = request.requested
.filter((person) => person !== request.rejected)
.filter((person) => !request.approved.includes(person))
.map((person) => {
return {
person,
role,
state: request.rejected !== undefined ? 'cancelled' : 'waiting'
}
})
const states = [...rejected, ...approved, ...ignored]
const messages = bundle.ChatMessage.filter((m) => m.attachedTo === request._id)
const personRefByPersonId = await getPersonRefsByPersonIds(messages.map((m) => m.createdBy ?? m.modifiedBy))
for (const state of states) {
state.messages = messages.filter((m) => personRefByPersonId.get(m.createdBy ?? m.modifiedBy) === state.person)
}
return states
}
for (const document of bundle.ControlledDocument) {
const snapshots = bundle.DocumentSnapshot.filter((s) => s.attachedTo === document._id).sort(
(a, b) => (a.createdOn ?? 0) - (b.createdOn ?? 0)
)
const requests = bundle.DocumentRequest.filter((s) => s.attachedTo === document._id).sort(
(a, b) => (a.createdOn ?? 0) - (b.createdOn ?? 0)
)
const states = [...snapshots, undefined].map((snapshot) => {
const state: DocumentValidationState = {
requests: [],
snapshot,
document,
approvals: [],
messages: []
}
return state
})
for (const request of requests) {
if (request.status === RequestStatus.Cancelled) {
continue
}
const state =
states.find((s) => (s.snapshot?.createdOn ?? 0) > (request.createdOn ?? 0)) ?? states[states.length - 1]
state.requests.push(request)
}
for (const state of states) {
const review = state.requests.findLast((r) =>
hierarchy.isDerived(r._class, documents.class.DocumentReviewRequest)
)
let approval = state.requests.findLast((r) =>
hierarchy.isDerived(r._class, documents.class.DocumentApprovalRequest)
)
if ((approval?.createdOn ?? 0) < (review?.createdOn ?? 0)) approval = undefined
const anchor = review ?? approval
const author =
anchor?.createdBy !== undefined
? (await getPersonRefByPersonId(anchor.createdBy)) ?? document.author
: document.author
state.approvals = [
{
person: author,
role: 'author',
state: anchor !== undefined ? 'approved' : 'waiting',
timestamp: anchor !== undefined ? anchor.createdOn ?? document.createdOn : undefined
},
...(await getApprovalStates(review)),
...(await getApprovalStates(approval))
]
if (state.requests.length > 0) {
state.modifiedOn = Math.max(...state.requests.map((r) => r.modifiedOn ?? 0))
}
}
states.reverse()
result.set(document._id, states)
}
return result
}
+2 -122
View File
@@ -20,15 +20,13 @@ import {
Doc,
DocumentQuery,
DocumentUpdate,
Hierarchy,
Rank,
Ref,
SortingOrder,
Space,
Timestamp,
toIdMap,
TxOperations,
type PersonId
TxOperations
} from '@hcengineering/core'
import { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank'
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
@@ -53,7 +51,6 @@ import {
ProjectDocument,
ProjectMeta
} from './types'
import { RequestStatus } from '@hcengineering/request'
/**
* @public
@@ -678,127 +675,10 @@ export interface DocumentValidationState {
snapshot?: DocumentSnapshot
document: ControlledDocument
approvals: DocumentApprovalState[]
messages: ChatMessage[]
modifiedOn?: Timestamp
}
export function extractValidationWorkflow (
hierarchy: Hierarchy,
bundle: DocumentBundle,
accountIdToPerson: (ref: PersonId) => Ref<Person> | undefined
): Map<Ref<ControlledDocument>, DocumentValidationState[]> {
const result: ReturnType<typeof extractValidationWorkflow> = new Map()
const getApprovalStates = (request: DocumentRequest | undefined): DocumentApprovalState[] => {
if (request === undefined) return []
const role = hierarchy.isDerived(request._class, documents.class.DocumentReviewRequest) ? 'reviewer' : 'approver'
const rejected: DocumentApprovalState[] =
request.rejected !== undefined
? [
{
person: request.rejected,
role,
state: 'rejected',
timestamp: request.modifiedOn
}
]
: []
const approved: DocumentApprovalState[] = request.approved.map((person, idx) => {
return {
person,
role,
state: 'approved',
timestamp: request.approvedDates?.[idx] ?? request.modifiedOn
}
})
const ignored: DocumentApprovalState[] = request.requested
.filter((person) => person !== request.rejected)
.filter((person) => !request.approved.includes(person))
.map((person) => {
return {
person,
role,
state: request.rejected !== undefined ? 'cancelled' : 'waiting'
}
})
const states = [...rejected, ...approved, ...ignored]
const messages = bundle.ChatMessage.filter((m) => m.attachedTo === request._id)
for (const state of states) {
state.messages = messages.filter((m) => accountIdToPerson(m.createdBy ?? m.modifiedBy) === state.person)
}
return states
}
for (const document of bundle.ControlledDocument) {
const snapshots = bundle.DocumentSnapshot.filter((s) => s.attachedTo === document._id).sort(
(a, b) => (a.createdOn ?? 0) - (b.createdOn ?? 0)
)
const requests = bundle.DocumentRequest.filter((s) => s.attachedTo === document._id).sort(
(a, b) => (a.createdOn ?? 0) - (b.createdOn ?? 0)
)
const states: DocumentValidationState[] = [...snapshots, undefined].map((snapshot) => {
return {
requests: [],
snapshot,
document,
approvals: [],
messages: []
}
})
for (const request of requests) {
if (request.status === RequestStatus.Cancelled) {
continue
}
const state =
states.find((s) => (s.snapshot?.createdOn ?? 0) > (request.createdOn ?? 0)) ?? states[states.length - 1]
state.requests.push(request)
}
for (const state of states) {
const review = state.requests.findLast((r) =>
hierarchy.isDerived(r._class, documents.class.DocumentReviewRequest)
)
let approval = state.requests.findLast((r) =>
hierarchy.isDerived(r._class, documents.class.DocumentApprovalRequest)
)
if ((approval?.createdOn ?? 0) < (review?.createdOn ?? 0)) approval = undefined
const anchor = review ?? approval
const author =
anchor?.createdBy !== undefined ? accountIdToPerson?.(anchor.createdBy) ?? document.author : document.author
state.approvals = [
{
person: author,
role: 'author',
state: anchor !== undefined ? 'approved' : 'waiting',
timestamp: anchor !== undefined ? anchor.createdOn ?? document.createdOn : undefined
},
...getApprovalStates(review),
...getApprovalStates(approval)
]
if (state.requests.length > 0) {
state.modifiedOn = Math.max(...state.requests.map((r) => r.modifiedOn ?? 0))
}
}
states.reverse()
result.set(document._id, states)
}
return result
}
/**
* @public
*/
@@ -15,13 +15,21 @@
//
-->
<script lang="ts">
import { EmployeePresenter, personByPersonIdStore } from '@hcengineering/contact-resources'
import { Person } from '@hcengineering/contact'
import { EmployeePresenter, getPersonByPersonIdCb } from '@hcengineering/contact-resources'
import { DocumentSnapshot } from '@hcengineering/document'
import { TimeSince } from '@hcengineering/ui'
export let value: DocumentSnapshot
$: employee = value.createdBy !== undefined ? $personByPersonIdStore.get(value.createdBy) : undefined
let employee: Person | undefined
$: if (value.createdBy !== undefined) {
getPersonByPersonIdCb(value.createdBy, (p) => {
employee = p ?? undefined
})
} else {
employee = undefined
}
</script>
<div class="container flex-col flex-gap-2 flex-no-shrink">
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import { deepEqual } from 'fast-equals'
import { AccountArrayEditor, personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { AccountArrayEditor, employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
import core, {
Data,
DocumentUpdate,
@@ -70,7 +70,7 @@
let rolesAssignment: RolesAssignment = {}
$: isNew = teamspace === undefined
$: membersPersons = members.map((m) => $personRefByAccountUuidStore.get(m)).filter(notEmpty)
$: membersPersons = members.map((m) => $employeeRefByAccountUuidStore.get(m)).filter(notEmpty)
let typeId: Ref<SpaceType> | undefined = teamspace?.type ?? document.spaceType.DefaultTeamspaceType
let spaceType: WithLookup<SpaceType> | undefined
@@ -15,7 +15,7 @@
<script lang="ts">
import { deepEqual } from 'fast-equals'
import { createEventDispatcher } from 'svelte'
import { AccountArrayEditor, personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { AccountArrayEditor, employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
import core, {
Data,
DocumentUpdate,
@@ -55,7 +55,7 @@
let typeId: Ref<SpaceType> | undefined = drive?.type ?? driveRes.spaceType.DefaultDrive
let spaceType: WithLookup<SpaceType> | undefined
$: membersPersons = members.map((m) => $personRefByAccountUuidStore.get(m)).filter(notEmpty)
$: membersPersons = members.map((m) => $employeeRefByAccountUuidStore.get(m)).filter(notEmpty)
$: void loadSpaceType(typeId)
const loadSpaceType = reduceCalls(async (id: typeof typeId): Promise<void> => {
spaceType =
@@ -81,7 +81,7 @@
object._class,
'gmailSharedMessages',
{
messages: convertMessages(object, channel, selectedMessages)
messages: await convertMessages(object, channel, selectedMessages)
}
)
await inboxClient.readDoc(channel._id)
@@ -126,12 +126,14 @@
</div>
{#if messages && messages.length > 0}
<div class="antiVSpacer x2" />
<Scroller padding={'.5rem 1rem'}>
<Messages messages={convertMessages(object, channel, messages)} {selectable} bind:selected on:select />
{#await convertMessages(object, channel, messages) then convertedMessages}
<div class="antiVSpacer x2" />
</Scroller>
<div class="antiVSpacer x2" />
<Scroller padding={'.5rem 1rem'}>
<Messages messages={convertedMessages} {selectable} bind:selected on:select />
<div class="antiVSpacer x2" />
</Scroller>
<div class="antiVSpacer x2" />
{/await}
{:else}
<div class="flex-col-center justify-center h-full">
<Icon icon={IconInbox} size={'full'} />
+21 -15
View File
@@ -1,7 +1,7 @@
import { get } from 'svelte/store'
import { getName as getContactName } from '@hcengineering/contact'
import contact, { type Channel, type Contact } from '@hcengineering/contact'
import { personByPersonIdStore, employeeBySocialKeyStore } from '@hcengineering/contact-resources'
import { employeeBySocialKeyStore, getPersonByPersonId } from '@hcengineering/contact-resources'
import { buildSocialIdString, type PersonId, SocialIdType, type Client, type Doc, type Ref } from '@hcengineering/core'
import { type Message, type SharedMessage } from '@hcengineering/gmail'
import { getClient } from '@hcengineering/presentation'
@@ -65,41 +65,47 @@ export async function checkHasEmail (doc: Doc | Doc[] | undefined): Promise<bool
const EMAIL_REGEX =
/(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/
export function convertMessages (object: Contact, channel: Channel, messages: Message[]): SharedMessage[] {
return messages.map((m) => {
return {
export async function convertMessages (
object: Contact,
channel: Channel,
messages: Message[]
): Promise<SharedMessage[]> {
const res: SharedMessage[] = []
for (const m of messages) {
res.push({
...m,
_id: m._id as string as Ref<SharedMessage>,
sender: getName(object, channel, m, true),
receiver: getName(object, channel, m, false)
}
})
sender: await getName(object, channel, m, true),
receiver: await getName(object, channel, m, false)
})
}
return res
}
export async function convertMessage (object: Contact, channel: Channel, message: Message): Promise<SharedMessage> {
return {
...message,
_id: message._id as string as Ref<SharedMessage>,
sender: getName(object, channel, message, true),
receiver: getName(object, channel, message, false)
sender: await getName(object, channel, message, true),
receiver: await getName(object, channel, message, false)
}
}
export function getName (object: Contact, channel: Channel, message: Message, sender: boolean): string {
export async function getName (object: Contact, channel: Channel, message: Message, sender: boolean): Promise<string> {
const h = getClient().getHierarchy()
if (message._class === gmail.class.NewMessage) {
if (!sender) return `${getContactName(h, object)} (${channel.value})`
return getPersonName(message.from ?? message.createdBy ?? message.modifiedBy)
return await getPersonName(message.from ?? message.createdBy ?? message.modifiedBy)
}
if (message.incoming === sender) {
return `${getContactName(h, object)} (${channel.value})`
} else {
return getPersonName(message.modifiedBy)
return await getPersonName(message.modifiedBy)
}
}
export function getPersonName (emailOrId: string): string {
const personName = get(personByPersonIdStore).get(emailOrId as PersonId)?.name
export async function getPersonName (emailOrId: string): Promise<string> {
const personName = (await getPersonByPersonId(emailOrId as PersonId))?.name
if (personName != null) return personName
const emailSearch = emailOrId.match(EMAIL_REGEX)
const email = emailSearch?.[0]
@@ -13,14 +13,15 @@
-->
<script lang="ts">
import { Notification, ReactionNotificationContent } from '@hcengineering/communication-types'
import { Notification, ReactionNotificationContent, SocialID } from '@hcengineering/communication-types'
import { EmojiPresenter } from '@hcengineering/emoji-resources'
import { Card } from '@hcengineering/card'
import { Label } from '@hcengineering/ui'
import { Person } from '@hcengineering/contact'
import { employeeByPersonIdStore, getPersonByPersonId } from '@hcengineering/contact-resources'
import NotificationPreview from './preview/NotificationPreview.svelte'
import PreviewTemplate from './preview/PreviewTemplate.svelte'
import { Label } from '@hcengineering/ui'
import inbox from '../plugin'
export let notification: Notification
@@ -28,6 +29,17 @@
let content = notification.content as ReactionNotificationContent
$: content = notification.content as ReactionNotificationContent
let author: Person | undefined
$: void updateAuthor(content.creator)
async function updateAuthor (socialId: SocialID): Promise<void> {
author = $employeeByPersonIdStore.get(socialId)
if (author === undefined) {
author = (await getPersonByPersonId(socialId)) ?? undefined
}
}
</script>
{#if notification.message}
@@ -12,13 +12,13 @@
<!-- limitations under the License. -->
<script lang="ts">
import { getClient, LiteMessageViewer } from '@hcengineering/presentation'
import { LiteMessageViewer } from '@hcengineering/presentation'
import { Card } from '@hcengineering/card'
import { type WithLookup } from '@hcengineering/core'
import { Message, MessageType, SocialID } from '@hcengineering/communication-types'
import { getPersonBySocialId, Person } from '@hcengineering/contact'
import { Person } from '@hcengineering/contact'
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
import { personByPersonIdStore } from '@hcengineering/contact-resources'
import { employeeByPersonIdStore, getPersonByPersonId } from '@hcengineering/contact-resources'
import { markdownToMarkup } from '@hcengineering/text-markdown'
import { jsonToMarkup, markupToText } from '@hcengineering/text'
import { ActivityMessageViewer, ThreadMessageViewer, isActivityMessage } from '@hcengineering/communication-resources'
@@ -33,8 +33,6 @@
export let kind: 'default' | 'column' = 'default'
export let padding: string | undefined = undefined
const client = getClient()
const tooltipLimit = 512
let person: WithLookup<Person> | undefined = undefined
@@ -50,7 +48,7 @@
}
async function updatePerson (socialId: SocialID): Promise<void> {
person = $personByPersonIdStore.get(socialId) ?? (await getPersonBySocialId(client, socialId))
person = $employeeByPersonIdStore.get(socialId) ?? (await getPersonByPersonId(socialId)) ?? undefined
}
</script>
@@ -12,12 +12,17 @@
<!-- limitations under the License. -->
<script lang="ts">
import { Person, formatName, getPersonBySocialId } from '@hcengineering/contact'
import { Person, formatName } from '@hcengineering/contact'
import { IntlString } from '@hcengineering/platform'
import { resizeObserver, TimeSince, tooltip } from '@hcengineering/ui'
import { Avatar, PersonPreviewProvider, SystemAvatar, personByPersonIdStore } from '@hcengineering/contact-resources'
import {
Avatar,
PersonPreviewProvider,
SystemAvatar,
employeeByPersonIdStore,
getPersonByPersonId
} from '@hcengineering/contact-resources'
import { SocialID } from '@hcengineering/communication-types'
import { getClient } from '@hcengineering/presentation'
export let tooltipLabel: IntlString | undefined = undefined
export let person: Person | undefined = undefined
@@ -28,7 +33,6 @@
export let padding: string | undefined = undefined
export let fixHeight: boolean = false
const client = getClient()
let clientWidth: number
$: showPersonName = clientWidth > 300
@@ -37,9 +41,9 @@
$: void updatePerson(socialId, person)
async function updatePerson (socialId: SocialID, person: Person | undefined): Promise<void> {
_person = person ?? $personByPersonIdStore.get(socialId)
_person = person ?? $employeeByPersonIdStore.get(socialId)
if (!_person) {
_person = await getPersonBySocialId(client, socialId)
_person = (await getPersonByPersonId(socialId)) ?? undefined
}
}
</script>
@@ -14,7 +14,7 @@
// limitations under the License.
-->
<script lang="ts">
import { AccountArrayEditor, personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { AccountArrayEditor, employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
import core, {
getCurrentAccount,
Ref,
@@ -54,7 +54,7 @@
funnel?.members !== undefined ? hierarchy.clone(funnel.members) : [getCurrentAccount().uuid]
let owners: AccountUuid[] = funnel?.owners !== undefined ? hierarchy.clone(funnel.owners) : [getCurrentAccount().uuid]
$: membersPersons = members.map((m) => $personRefByAccountUuidStore.get(m)).filter(notEmpty)
$: membersPersons = members.map((m) => $employeeRefByAccountUuidStore.get(m)).filter(notEmpty)
$: void loadSpaceType(typeId)
async function loadSpaceType (id: typeof typeId): Promise<void> {
spaceType =
@@ -36,7 +36,6 @@
ViewletSelector,
ViewletSettingButton
} from '@hcengineering/view-resources'
import { socialIdsByPersonRefStore } from '@hcengineering/contact-resources'
import { createEventDispatcher } from 'svelte'
import lead from '../plugin'
@@ -48,9 +47,9 @@
let search = ''
const dispatch = createEventDispatcher()
const me = getCurrentEmployee()
$: mySocialStrings = ($socialIdsByPersonRefStore.get(me) ?? []).map((si) => si.key)
const myAcc = getCurrentAccount()
const assigned = { assignee: me }
$: created = { createdBy: { $in: mySocialStrings } }
const created = { createdBy: { $in: myAcc.socialIds } }
let subscribed = { _id: { $in: [] as Ref<Lead>[] } }
let mode: string | undefined = undefined
let baseQuery: DocumentQuery<Lead> | undefined = undefined
@@ -70,7 +69,7 @@
function getSubscribed () {
subscribedQuery.query(
_class,
{ 'notification:mixin:Collaborators.collaborators': { $in: getCurrentAccount().socialIds } },
{ 'notification:mixin:Collaborators.collaborators': myAcc.uuid },
(result) => {
const newSub = result.map((p) => p._id as Ref<AttachedDoc> as Ref<Lead>)
const curSub = subscribed._id.$in
@@ -13,13 +13,12 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Person } from '@hcengineering/contact'
import { Avatar, CombineAvatars, personByIdStore } from '@hcengineering/contact-resources'
import contact from '@hcengineering/contact'
import { CombineAvatars } from '@hcengineering/contact-resources'
import { getClient } from '@hcengineering/presentation'
import { Button, Label } from '@hcengineering/ui'
import { Invite, RequestStatus } from '@hcengineering/love'
import love from '../plugin'
import { Ref } from '@hcengineering/core'
export let invites: Invite[]
@@ -13,9 +13,8 @@
// limitations under the License.
-->
<script lang="ts">
import { personByIdStore } from '@hcengineering/contact-resources'
import { IdMap, Ref, toIdMap } from '@hcengineering/core'
import { Person, getCurrentEmployee } from '@hcengineering/contact'
import { getCurrentEmployee } from '@hcengineering/contact'
import {
Invite,
isOffice,
@@ -60,6 +59,7 @@
import RequestingPopup from './RequestingPopup.svelte'
import RoomPopup from './RoomPopup.svelte'
import RoomButton from './RoomButton.svelte'
import { getPersonByPersonRef } from '@hcengineering/contact-resources'
const client = getClient()
@@ -174,7 +174,6 @@
infos: ParticipantInfo[],
myInfo: ParticipantInfo | undefined,
myOffice: Office | undefined,
personByIdStore: IdMap<Person>,
isConnected: boolean
): Promise<void> {
if (myInfo !== undefined && myInfo.room === (myOffice?._id ?? love.ids.Reception)) {
@@ -188,14 +187,14 @@
await disconnect()
}
} else if (!isConnected) {
const myPerson = personByIdStore.get(getCurrentEmployee())
if (myPerson === undefined) return
const myPerson = await getPersonByPersonRef(getCurrentEmployee())
if (myPerson == null) return
await connectRoom(0, 0, myInfo, myPerson, myOffice)
}
}
}
$: checkOwnRoomConnection($infos, $myInfo, $myOffice, $personByIdStore, $isCurrentInstanceConnected)
$: checkOwnRoomConnection($infos, $myInfo, $myOffice, $isCurrentInstanceConnected)
const myInvitesCategory = 'myInvites'
@@ -300,22 +299,26 @@
{#if activeRooms.length > 0}
<!-- <div class="divider" />-->
{#each activeRooms as active}
<RoomButton
label={getRoomName(active, $personByIdStore)}
participants={active.participants}
active={joined.find((r) => r._id === active._id) != null}
on:click={openRoom(active)}
/>
{#await getRoomName(active) then name}
<RoomButton
label={name}
participants={active.participants}
active={joined.find((r) => r._id === active._id) != null}
on:click={openRoom(active)}
/>
{/await}
{/each}
{/if}
{#if reception !== undefined && receptionParticipants.length > 0}
{#if activeRooms.length > 0}
<div class="divider" />
{/if}
<RoomButton
label={getRoomName(reception, $personByIdStore)}
participants={receptionParticipants.map((p) => ({ ...p, onclick: getParticipantClickHandler(p) }))}
/>
{#await getRoomName(reception) then name}
<RoomButton
label={name}
participants={receptionParticipants.map((p) => ({ ...p, onclick: getParticipantClickHandler(p) }))}
/>
{/await}
{/if}
</div>
@@ -16,7 +16,6 @@
import { EditBox, ModernButton } from '@hcengineering/ui'
import { Room, isOffice, type ParticipantInfo } from '@hcengineering/love'
import { createEventDispatcher, onMount } from 'svelte'
import { personByIdStore } from '@hcengineering/contact-resources'
import { IntlString } from '@hcengineering/platform'
import love from '../plugin'
@@ -27,7 +26,11 @@
const dispatch = createEventDispatcher()
$: roomName = getRoomName(object, $personByIdStore)
let roomName: string
$: void getRoomName(object).then((name) => {
roomName = name
})
let connecting = false
onMount(() => {
@@ -40,7 +43,6 @@
tryConnecting = true
const place = $selectedRoomPlace
await tryConnect(
$personByIdStore,
$myInfo,
object,
$infos,
@@ -14,7 +14,6 @@
-->
<script lang="ts">
import { Contact, Person } from '@hcengineering/contact'
import { personByIdStore } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import love, { Floor as FloorType, Office, Room, RoomInfo, isOffice } from '@hcengineering/love'
import { getClient } from '@hcengineering/presentation'
@@ -57,8 +56,7 @@
if (info === undefined) return
const room = $rooms.find((p) => p._id === info.room)
if (room === undefined) return
tryConnect(
$personByIdStore,
await tryConnect(
$myInfo,
room,
$infos.filter((p) => p.room === room._id),
@@ -77,7 +75,7 @@
await connectToSession(sessionId)
} else if (meetId) {
await waitForOfficeLoaded()
await connectToMeeting($personByIdStore, $myInfo, $infos, $myRequests, $invites, meetId)
await connectToMeeting($myInfo, $infos, $myRequests, $invites, meetId)
}
})
</script>
@@ -13,19 +13,23 @@
// limitations under the License.
-->
<script lang="ts">
import { formatName } from '@hcengineering/contact'
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
import { formatName, Person } from '@hcengineering/contact'
import { Avatar, getPersonByPersonRef, getPersonByPersonRefCb } from '@hcengineering/contact-resources'
import { getClient, playNotificationSound } from '@hcengineering/presentation'
import { Button, Label } from '@hcengineering/ui'
import { Invite, RequestStatus, getFreeRoomPlace } from '@hcengineering/love'
import { onDestroy, onMount } from 'svelte'
import love from '../plugin'
import { infos, myInfo, rooms } from '../stores'
import { connectRoom } from '../utils'
import { onDestroy, onMount } from 'svelte'
export let invite: Invite
$: person = $personByIdStore.get(invite.from)
let person: Person | undefined = undefined
$: getPersonByPersonRefCb(invite.from, (p) => {
person = p ?? undefined
})
const client = getClient()
let stopSound: (() => void) | null = null
@@ -33,8 +37,8 @@
async function accept (): Promise<void> {
const room = $rooms.find((p) => p._id === invite.room)
if (room === undefined) return
const myPerson = $personByIdStore.get(invite.target)
if (myPerson === undefined) return
const myPerson = await getPersonByPersonRef(invite.target)
if (myPerson == null) return
if ($myInfo === undefined) return
await client.update(invite, { status: RequestStatus.Approved })
const place = getFreeRoomPlace(
@@ -16,7 +16,6 @@
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { onDestroy, onMount } from 'svelte'
import presentation from '@hcengineering/presentation'
import { personByIdStore } from '@hcengineering/contact-resources'
import { RoomType } from '@hcengineering/love'
import Hall from './Hall.svelte'
@@ -54,7 +53,7 @@
$myInfo.sessionId === getMetadata(presentation.metadata.SessionId)
) {
const info = $infos.filter((p) => p.room === room._id)
await tryConnect($personByIdStore, $myInfo, room, info, $myRequests, $invites)
await tryConnect($myInfo, room, info, $myRequests, $invites)
}
})
</script>
@@ -1,5 +1,5 @@
<script lang="ts">
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
import { Avatar, getPersonByPersonRefStore } from '@hcengineering/contact-resources'
import { isOffice } from '@hcengineering/love'
import { Button } from '@hcengineering/ui'
import view from '@hcengineering/view'
@@ -46,6 +46,9 @@
let now = Date.now()
$: participants = $infos.filter((p) => p.room === $currentRoom?._id)
$: personByRefStore = getPersonByPersonRefStore(participants.map((p) => p.person))
onMount(() => {
const interval = setInterval(() => {
now = Date.now()
@@ -58,7 +61,6 @@
</script>
{#if $isCurrentInstanceConnected && $currentRoom != null}
{@const participants = $infos.filter((p) => p.room === $currentRoom._id)}
{@const overLimit = participants.length > limit}
<div class="m-1 p-2">
@@ -101,9 +103,9 @@
data-over={i === limit - 1 && overLimit ? `+${participants.length - limit + 1}` : undefined}
>
<Avatar
name={$personByIdStore.get(participant.person)?.name ?? participant.name}
name={$personByRefStore.get(participant.person)?.name ?? participant.name}
size={'x-small'}
person={$personByIdStore.get(participant.person)}
person={$personByRefStore.get(participant.person)}
/>
</div>
{/each}
@@ -112,9 +114,9 @@
<div class="flex-row-center flex-gap-0-5">
{#each participants as participant (participant._id)}
<Avatar
name={$personByIdStore.get(participant.person)?.name ?? participant.name}
name={$personByRefStore.get(participant.person)?.name ?? participant.name}
size={'x-small'}
person={$personByIdStore.get(participant.person)}
person={$personByRefStore.get(participant.person)}
/>
{/each}
</div>
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Person, formatName } from '@hcengineering/contact'
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
import { Avatar, getPersonByPersonRefStore } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { Loading } from '@hcengineering/ui'
@@ -54,7 +54,8 @@
activeTrack = !value
}
$: user = $personByIdStore.get(_id as Ref<Person>)
$: personByRefStore = getPersonByPersonRefStore([_id as Ref<Person>])
$: user = $personByRefStore.get(_id as Ref<Person>)
$: speach = $currentRoomAudioLevels.get(_id as Ref<Person>) ?? 0
</script>
@@ -13,12 +13,14 @@
// limitations under the License.
-->
<script lang="ts">
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
import { Avatar, getPersonByPersonRefStore } from '@hcengineering/contact-resources'
import { ParticipantInfo } from '@hcengineering/love'
import { Scroller } from '@hcengineering/ui'
import { formatName } from '@hcengineering/contact'
export let items: (ParticipantInfo & { onclick?: (e: MouseEvent) => void })[]
$: personByRefStore = getPersonByPersonRefStore(items.map((p) => p.person))
</script>
<Scroller padding={'.25rem'} gap={'flex-gap-2'}>
@@ -32,9 +34,9 @@
>
<div class="min-w-6">
<Avatar
name={$personByIdStore.get(participant.person)?.name ?? participant.name}
name={$personByRefStore.get(participant.person)?.name ?? participant.name}
size={items.length < 10 ? 'small' : 'card'}
person={$personByIdStore.get(participant.person)}
person={$personByRefStore.get(participant.person)}
/>
</div>
{formatName(participant.name)}
@@ -1,6 +1,5 @@
<script lang="ts">
import { Person } from '@hcengineering/contact'
import { personByIdStore } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { isOffice, Room, RoomAccess } from '@hcengineering/love'
import { ActionIcon } from '@hcengineering/ui'
@@ -42,7 +41,7 @@
label={love.string.KnockAction}
icon={love.icon.Knock}
action={() => {
tryConnect($personByIdStore, $myInfo, room, info, $myRequests, $invites)
tryConnect($myInfo, room, info, $myRequests, $invites)
}}
/>
{/if}
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import { formatName, getCurrentEmployee } from '@hcengineering/contact'
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
import { Avatar, getPersonByPersonRef, getPersonByPersonRefStore } from '@hcengineering/contact-resources'
import { getClient, playNotificationSound } from '@hcengineering/presentation'
import { Button, Label } from '@hcengineering/ui'
import { JoinRequest, RequestStatus } from '@hcengineering/love'
@@ -25,7 +25,8 @@
export let request: JoinRequest
$: person = $personByIdStore.get(request.person)
$: personByRefStore = getPersonByPersonRefStore([request.person])
$: person = $personByRefStore.get(request.person)
const client = getClient()
let stopSound: (() => void) | null = null
@@ -33,9 +34,8 @@
async function accept (): Promise<void> {
await client.update(request, { status: RequestStatus.Approved })
if (request.room === $myOffice?._id && !$isConnected) {
const me = getCurrentEmployee()
const person = $personByIdStore.get(me)
if (person === undefined) return
const person = await getPersonByPersonRef(getCurrentEmployee())
if (person == null) return
await connectRoom(0, 0, $myInfo, person, $myOffice)
}
}
@@ -19,13 +19,12 @@
import love from '../plugin'
import { rooms } from '../stores'
import { getRoomLabel } from '../utils'
import { personByIdStore } from '@hcengineering/contact-resources'
export let request: JoinRequest
$: room = $rooms.find((p) => p._id === request.room)
async function cancel () {
async function cancel (): Promise<void> {
if (request.status === RequestStatus.Pending) {
const client = getClient()
await client.remove(request)
@@ -38,7 +37,9 @@
<Label label={love.string.KnockingTo} params={{ name: room?.name }} />
<span class="title">
{#if room}
<Label label={getRoomLabel(room, $personByIdStore)} />
{#await getRoomLabel(room) then label}
<Label {label} />
{/await}
{/if}
</span>
</div>
@@ -14,7 +14,6 @@
-->
<script lang="ts">
import { Analytics } from '@hcengineering/analytics'
import { personByIdStore, personRefByPersonIdStore } from '@hcengineering/contact-resources'
import { Room as TypeRoom } from '@hcengineering/love'
import { getMetadata } from '@hcengineering/platform'
import { Label, Loading, resizeObserver, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
@@ -46,6 +45,9 @@
} from '../utils'
import ControlBar from './ControlBar.svelte'
import ParticipantView from './ParticipantView.svelte'
import { Ref } from '@hcengineering/core'
import { Person } from '@hcengineering/contact'
import { getPersonRefByPersonIdCb } from '@hcengineering/contact-resources'
export let withVideo: boolean
export let canMaximize: boolean = true
@@ -65,8 +67,16 @@
let screen: HTMLVideoElement
let roomEl: HTMLDivElement
$: aiPersonId =
$aiBotSocialIdentityStore != null ? $personRefByPersonIdStore.get($aiBotSocialIdentityStore._id) : undefined
let aiPersonRef: Ref<Person> | undefined
$: if ($aiBotSocialIdentityStore != null) {
getPersonRefByPersonIdCb($aiBotSocialIdentityStore?._id, (ref) => {
if (ref != null) {
aiPersonRef = ref
}
})
} else {
aiPersonRef = undefined
}
function handleTrackSubscribed (
track: RemoteTrack,
@@ -239,7 +249,7 @@
$myInfo?.sessionId === getMetadata(presentation.metadata.SessionId)
) {
const info = $infos.filter((p) => p.room === room._id)
await tryConnect($personByIdStore, $myInfo, room, info, $myRequests, $invites)
await tryConnect($myInfo, room, info, $myRequests, $invites)
}
await awaitConnect()
@@ -297,7 +307,7 @@
muted: true,
mirror: false,
connecting: true,
isAgent: aiPersonId === info.person
isAgent: aiPersonRef === info.person
}
participants.push(value)
}
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
import { Avatar, getPersonByPersonRefStore } from '@hcengineering/contact-resources'
import { tooltip, deviceOptionsStore as deviceInfo, checkAdaptiveMatching } from '@hcengineering/ui'
import { ParticipantInfo } from '@hcengineering/love'
import { formatName } from '@hcengineering/contact'
@@ -27,6 +27,7 @@
$: overLimit = participants.length > limit
$: adaptive = checkAdaptiveMatching($deviceInfo.size, 'md') || overLimit
$: personByRefStore = getPersonByPersonRefStore(participants.map((p) => p.person))
</script>
{#if adaptive}
@@ -46,9 +47,9 @@
data-over={i === limit - 1 && overLimit ? `+${participants.length - limit + 1}` : undefined}
>
<Avatar
name={$personByIdStore.get(participant.person)?.name ?? participant.name}
name={$personByRefStore.get(participant.person)?.name ?? participant.name}
size={'card'}
person={$personByIdStore.get(participant.person)}
person={$personByRefStore.get(participant.person)}
/>
</div>
{/each}
@@ -66,9 +67,9 @@
on:click={participant.onclick}
>
<Avatar
name={$personByIdStore.get(participant.person)?.name ?? participant.name}
name={$personByRefStore.get(participant.person)?.name ?? participant.name}
size={'card'}
person={$personByIdStore.get(participant.person)}
person={$personByRefStore.get(participant.person)}
/>
</div>
{/each}
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import contact, { Contact, Person } from '@hcengineering/contact'
import { AssigneeBox, personByIdStore } from '@hcengineering/contact-resources'
import { AssigneeBox } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { ActionIcon, EditBox, Icon, IconDelete, resizeObserver } from '@hcengineering/ui'
@@ -26,6 +26,7 @@
import { infos, lockedRoom } from '../stores'
import { RoomSide, shadowNormal } from '../types'
import { getRoomLabel } from '../utils'
import { IntlString } from '@hcengineering/platform'
export let room: Room
export let cellSize: number
@@ -160,6 +161,11 @@
: undefined
}
let roomLabel: IntlString
$: void getRoomLabel(room).then((label) => {
roomLabel = label
})
onMount(() => {
if (container) roomRect = container.getBoundingClientRect()
})
@@ -215,12 +221,7 @@
{/each}
{/each}
<div class="floorGrid-configureRoom__header">
<EditBox
bind:value={room.name}
on:change={updateName}
placeholder={getRoomLabel(room, $personByIdStore)}
kind={'editbox'}
/>
<EditBox bind:value={room.name} on:change={updateName} placeholder={roomLabel} kind={'editbox'} />
{#if showButtons}
<div
class="flex-row-center flex-no-shrink h-full {zoomOut ? 'flex-gap-1' : 'flex-gap-2'}"
@@ -14,8 +14,8 @@
-->
<script lang="ts">
import { Person, getCurrentEmployee } from '@hcengineering/contact'
import { UserInfo, personByIdStore } from '@hcengineering/contact-resources'
import { Class, Doc, IdMap, Ref } from '@hcengineering/core'
import { UserInfo, getPersonByPersonRef } from '@hcengineering/contact-resources'
import { Class, Doc, Ref } from '@hcengineering/core'
import {
IconArrowLeft,
@@ -73,10 +73,12 @@
export let room: Room
const client = getClient()
function getPerson (info: ParticipantInfo | undefined, employees: IdMap<Person>): Person | undefined {
if (info !== undefined) {
return employees.get(info.person)
async function getPerson (info: ParticipantInfo | undefined): Promise<Person | null> {
if (info === undefined) {
return null
}
return await getPersonByPersonRef(info.person)
}
let joined: boolean = false
@@ -128,7 +130,7 @@
}
async function connect (): Promise<void> {
await tryConnect($personByIdStore, $myInfo, room, info, $myRequests, $invites)
await tryConnect($myInfo, room, info, $myRequests, $invites)
dispatch('close')
}
@@ -180,16 +182,19 @@
<div class="antiPopup room-popup">
<div class="room-label"><Label label={love.string.Room} /></div>
<div class="title overflow-label">
{getRoomName(room, $personByIdStore)}
{#await getRoomName(room) then name}
{name}
{/await}
</div>
<div class="room-popup__content">
<Scroller padding={'0.5rem'} stickedScrollBars>
<div class="room-popup__content-grid">
{#each info as inf}
{@const person = getPerson(inf, $personByIdStore)}
{#if person}
<div class="person"><UserInfo value={person} size={'medium'} showStatus={false} /></div>
{/if}
{#await getPerson(inf) then person}
{#if person}
<div class="person"><UserInfo value={person} size={'medium'} showStatus={false} /></div>
{/if}
{/await}
{/each}
</div>
</Scroller>
@@ -19,7 +19,6 @@
import { getEmbeddedLabel } from '@hcengineering/platform'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import { tooltip, Icon } from '@hcengineering/ui'
import { personByIdStore } from '@hcengineering/contact-resources'
import { getRoomName } from '../utils'
@@ -31,7 +30,10 @@
export let shouldShowAvatar = true
export let type: ObjectPresenterType = 'link'
$: roomName = getRoomName(value, $personByIdStore)
let roomName: string
$: void getRoomName(value).then((name) => {
roomName = name
})
</script>
{#if value}
@@ -14,9 +14,8 @@
-->
<script lang="ts">
import { getCurrentEmployee, Person } from '@hcengineering/contact'
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
import { IdMap } from '@hcengineering/core'
import { isOffice, ParticipantInfo, Room, RoomAccess, RoomType, MeetingStatus } from '@hcengineering/love'
import { Avatar, myEmployeeStore, getPersonByPersonRef } from '@hcengineering/contact-resources'
import { ParticipantInfo, Room, RoomAccess, RoomType, MeetingStatus } from '@hcengineering/love'
import { Icon, Label, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { getClient } from '@hcengineering/presentation'
@@ -24,9 +23,9 @@
import love from '../plugin'
import { myInfo, selectedRoomPlace, currentRoom, currentMeetingMinutes } from '../stores'
import { getRoomLabel, lk, isConnected } from '../utils'
import { getRoomLabel, isConnected } from '../utils'
import PersonActionPopup from './PersonActionPopup.svelte'
import RoomLanguage from './RoomLanguage.svelte'
import { IntlString } from '@hcengineering/platform'
export let room: Room
export let info: ParticipantInfo[]
@@ -36,18 +35,24 @@
const dispatch = createEventDispatcher()
const me = getCurrentEmployee()
const meName = $personByIdStore.get(me)?.name
const meAvatar = $personByIdStore.get(me)
$: myName = $myEmployeeStore?.name
let hoveredRoomX: number | undefined = undefined
let hoveredRoomY: number | undefined = undefined
let roomLabel: IntlString
$: void getRoomLabel(room).then((label) => {
roomLabel = label
})
$: disabled = room._class === love.class.Office && info.length === 0
function getPerson (info: ParticipantInfo | undefined, employees: IdMap<Person>): Person | undefined {
if (info !== undefined) {
return employees.get(info.person)
async function getPerson (info: ParticipantInfo | undefined): Promise<Person | undefined> {
if (info === undefined) {
return
}
return (await getPersonByPersonRef(info.person)) ?? undefined
}
function getPersonInfo (y: number, x: number, info: ParticipantInfo[]): ParticipantInfo | undefined {
@@ -56,7 +61,7 @@
function mouseEnter (): void {
hovered = true
dispatch('hover', { name: getRoomLabel(room, $personByIdStore) })
dispatch('hover', { name: roomLabel })
}
function mouseLeave (): void {
@@ -162,39 +167,40 @@
{#each new Array(room.height) as _, y}
{#each new Array(room.width + extraRow) as _, x}
{@const personInfo = getPersonInfo(y, x, info)}
{@const person = getPerson(personInfo, $personByIdStore)}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="floorGrid-room__field"
class:hovered={hoveredRoomX === x && hoveredRoomY === y}
class:person={personInfo || person || $myInfo?.room === room._id}
on:mouseenter={() => {
if (!(personInfo || person) && !disabled && $myInfo?.room !== room._id) {
hoveredRoomX = x
hoveredRoomY = y
}
}}
on:mouseout={() => {
hoveredRoomX = undefined
hoveredRoomY = undefined
}}
on:click={(e) => {
placeClickHandler(e, x, y, person)
}}
>
{#if personInfo}
<Avatar name={person?.name ?? personInfo.name} {person} size={'large'} showStatus={false} adaptiveName />
{:else if hoveredRoomX === x && hoveredRoomY === y}
<Avatar name={meName} person={meAvatar} size={'large'} showStatus={false} adaptiveName />
{/if}
</div>
{#await getPerson(personInfo) then person}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="floorGrid-room__field"
class:hovered={hoveredRoomX === x && hoveredRoomY === y}
class:person={personInfo || person || $myInfo?.room === room._id}
on:mouseenter={() => {
if (!(personInfo || person) && !disabled && $myInfo?.room !== room._id) {
hoveredRoomX = x
hoveredRoomY = y
}
}}
on:mouseout={() => {
hoveredRoomX = undefined
hoveredRoomY = undefined
}}
on:click={(e) => {
placeClickHandler(e, x, y, person)
}}
>
{#if personInfo}
<Avatar name={person?.name ?? personInfo.name} {person} size={'large'} showStatus={false} adaptiveName />
{:else if hoveredRoomX === x && hoveredRoomY === y}
<Avatar name={myName} person={$myEmployeeStore} size={'large'} showStatus={false} adaptiveName />
{/if}
</div>
{/await}
{/each}
{/each}
{#if !preview}
<div class="floorGrid-room__header">
<span class="overflow-label text-md flex-grow">
<Label label={getRoomLabel(room, $personByIdStore)} />
<Label label={roomLabel} />
</span>
<!-- {#if !isOffice(room)}
<RoomLanguage {room} />
@@ -14,9 +14,9 @@
-->
<script lang="ts">
import { getCurrentEmployee, formatName } from '@hcengineering/contact'
import { personByIdStore } from '@hcengineering/contact-resources'
import { getPersonByPersonRefStore } from '@hcengineering/contact-resources'
import { translate } from '@hcengineering/platform'
import { Ref } from '@hcengineering/core'
import { notEmpty, Ref } from '@hcengineering/core'
import love, { isOffice, Room } from '@hcengineering/love'
import { Dropdown, Icon } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
@@ -42,6 +42,12 @@
})
.map((room) => makeRoomItem(room, false))
$: personByRefStore = getPersonByPersonRefStore(
$rooms
.filter(isOffice)
.map((r) => r.person)
.filter(notEmpty)
)
$: selectedRoom = $rooms.find((p) => p._id === value)
$: selected = selectedRoom !== undefined ? makeRoomItem(selectedRoom, true) : undefined
@@ -62,7 +68,7 @@
console.error(err)
})
} else if (room.person !== null) {
const person = $personByIdStore.get(room.person)
const person = $personByRefStore.get(room.person)
if (person !== undefined) {
translate(love.string.Office, {})
.then((res) => {
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import { aiBotSocialIdentityStore } from '@hcengineering/ai-bot-resources'
import { personRefByPersonIdStore } from '@hcengineering/contact-resources'
import { getPersonRefByPersonIdCb } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { Room as TypeRoom } from '@hcengineering/love'
import { Scroller } from '@hcengineering/ui'
@@ -34,6 +34,7 @@
import { infos } from '../stores'
import { awaitConnect, isSharingEnabled, lk, screenSharing } from '../utils'
import ParticipantView from './ParticipantView.svelte'
import { Person } from '@hcengineering/contact'
export let isDock: boolean = false
export let room: Ref<TypeRoom>
@@ -47,8 +48,16 @@
isAgent: boolean
}
$: aiPersonId =
$aiBotSocialIdentityStore != null ? $personRefByPersonIdStore.get($aiBotSocialIdentityStore._id) : undefined
let aiPersonRef: Ref<Person> | undefined
$: if ($aiBotSocialIdentityStore != null) {
getPersonRefByPersonIdCb($aiBotSocialIdentityStore?._id, (ref) => {
if (ref != null) {
aiPersonRef = ref
}
})
} else {
aiPersonRef = undefined
}
const dispatch = createEventDispatcher()
@@ -221,7 +230,7 @@
muted: true,
mirror: false,
connecting: true,
isAgent: info.person === aiPersonId
isAgent: info.person === aiPersonRef
}
participants.push(value)
}
+7 -5
View File
@@ -13,8 +13,9 @@ import {
} from '@hcengineering/love'
import { createQuery, onClient } from '@hcengineering/presentation'
import { derived, get, writable } from 'svelte/store'
import { aiBotPersonRefStore } from '@hcengineering/ai-bot-resources'
import { aiBotSocialIdentityStore } from '@hcengineering/ai-bot-resources'
import { type MediaSession } from '@hcengineering/media'
import { getPersonRefByPersonId } from '@hcengineering/contact-resources'
import love from './plugin'
@@ -61,9 +62,10 @@ export const currentMeetingMinutes = writable<MeetingMinutes | undefined>(undefi
export const selectedRoomPlace = writable<{ _id: Ref<Room>, x: number, y: number } | undefined>(undefined)
export const currentSession = writable<MediaSession | undefined>(undefined)
function filterParticipantInfo (value: ParticipantInfo[]): ParticipantInfo[] {
async function filterParticipantInfo (value: ParticipantInfo[]): Promise<ParticipantInfo[]> {
const map = new Map<string, ParticipantInfo>()
const aiPerson = get(aiBotPersonRefStore)
const aiSid = get(aiBotSocialIdentityStore)
const aiPerson = aiSid !== undefined ? await getPersonRefByPersonId(aiSid._id) : undefined
for (const val of value) {
if (aiPerson !== undefined && val.person === aiPerson) {
map.set(val._id, val)
@@ -91,8 +93,8 @@ onClient(() => {
})
)
const infoPromise = new Promise<void>((resolve) =>
statusQuery.query(love.class.ParticipantInfo, {}, (res) => {
infos.set(filterParticipantInfo(res))
statusQuery.query(love.class.ParticipantInfo, {}, async (res) => {
infos.set(await filterParticipantInfo(res))
resolve()
})
)
+9 -13
View File
@@ -4,7 +4,6 @@ import { Analytics } from '@hcengineering/analytics'
import calendar, { type Event, type Schedule, getAllEvents } from '@hcengineering/calendar'
import chunter from '@hcengineering/chunter'
import contact, { getCurrentEmployee, getName, type Person } from '@hcengineering/contact'
import { personByIdStore } from '@hcengineering/contact-resources'
import core, {
AccountRole,
type Client,
@@ -15,7 +14,6 @@ import core, {
generateId,
getCurrentAccount,
type Hierarchy,
type IdMap,
type Ref,
type RelatedDocument,
type Space,
@@ -104,6 +102,7 @@ import {
selectedRoomPlace
} from './stores'
import MeetingMinutesSearchItem from './components/MeetingMinutesSearchItem.svelte'
import { getPersonByPersonRef } from '@hcengineering/contact-resources'
export async function getToken (
roomName: string,
@@ -714,10 +713,10 @@ export async function setShare (value: boolean, withAudio: boolean = false): Pro
}
}
export function getRoomName (room: Room, personByIdStore: IdMap<Person>): string {
export async function getRoomName (room: Room): Promise<string> {
if (isOffice(room) && room.person !== null && room.name === '') {
const employee = personByIdStore.get(room.person)
if (employee !== undefined) {
const employee = await getPersonByPersonRef(room.person)
if (employee != null) {
const client = getClient()
return getName(client.getHierarchy(), employee)
}
@@ -725,8 +724,8 @@ export function getRoomName (room: Room, personByIdStore: IdMap<Person>): string
return room.name
}
export function getRoomLabel (room: Room, personByIdStore: IdMap<Person>): IntlString {
const name = getRoomName(room, personByIdStore)
export async function getRoomLabel (room: Room): Promise<IntlString> {
const name = await getRoomName(room)
if (name !== '') return getEmbeddedLabel(name)
return isOffice(room) ? love.string.Office : love.string.Room
}
@@ -804,7 +803,7 @@ async function initMeetingMinutes (room: Room): Promise<void> {
attachedToClass: room._class,
collection: 'meetings',
space: core.space.Workspace,
title: `${getRoomName(room, get(personByIdStore))} ${date}`,
title: `${await getRoomName(room)} ${date}`,
description: null,
status: MeetingStatus.Active,
modifiedBy: getCurrentAccount().primarySocialId,
@@ -873,7 +872,6 @@ function checkPlace (room: Room, info: ParticipantInfo[], x: number, y: number):
}
export async function connectToMeeting (
personByIdStore: IdMap<Person>,
currentInfo: ParticipantInfo | undefined,
info: ParticipantInfo[],
currentRequests: JoinRequest[],
@@ -895,7 +893,6 @@ export async function connectToMeeting (
}
await tryConnect(
personByIdStore,
currentInfo,
room,
info.filter((p) => p.room === room._id),
@@ -905,7 +902,6 @@ export async function connectToMeeting (
}
export async function tryConnect (
personByIdStore: IdMap<Person>,
currentInfo: ParticipantInfo | undefined,
room: Room,
info: ParticipantInfo[],
@@ -914,8 +910,8 @@ export async function tryConnect (
place?: { x: number, y: number }
): Promise<void> {
const me = getCurrentEmployee()
const currentPerson = personByIdStore.get(me)
if (currentPerson === undefined) return
const currentPerson = await getPersonByPersonRef(me)
if (currentPerson == null) return
const client = getClient()
// guests can't join without invite
@@ -23,9 +23,9 @@
import { createQuery, getClient } from '@hcengineering/presentation'
import { getDocTitle, getDocIdentifier, Menu } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import { Class, Doc, PersonId, Ref, WithLookup } from '@hcengineering/core'
import { Class, Doc, Ref, WithLookup } from '@hcengineering/core'
import chunter from '@hcengineering/chunter'
import { personRefByPersonIdStore } from '@hcengineering/contact-resources'
import { getPersonRefsByPersonIds } from '@hcengineering/contact-resources'
import { Person } from '@hcengineering/contact'
import InboxNotificationPresenter from './inbox/InboxNotificationPresenter.svelte'
@@ -85,7 +85,9 @@
let groupedNotifications: Array<InboxNotification[]> = []
$: groupedNotifications = groupNotificationsByUser(notifications, $personRefByPersonIdStore)
$: void groupNotificationsByUser(notifications).then((res) => {
groupedNotifications = res
})
function isTextMessage (_class: Ref<Class<Doc>>): boolean {
return hierarchy.isDerived(_class, chunter.class.ChatMessage)
@@ -99,13 +101,13 @@
return isMentionNotification(it) && isTextMessage(it.mentionedInClass)
}
function groupNotificationsByUser (
notifications: WithLookup<InboxNotification>[],
personRefByPersonId: Map<PersonId, Ref<Person>>
): Array<InboxNotification[]> {
async function groupNotificationsByUser (
notifications: WithLookup<InboxNotification>[]
): Promise<Array<InboxNotification[]>> {
const result: Array<InboxNotification[]> = []
let group: InboxNotification[] = []
let person: Ref<Person> | undefined = undefined
const personRefByPersonId = await getPersonRefsByPersonIds(notifications.map((it) => it.createdBy ?? it.modifiedBy))
for (const it of notifications) {
const pid = it.createdBy ?? it.modifiedBy
@@ -1,5 +1,5 @@
<script lang="ts">
import { Avatar, personByPersonIdStore } from '@hcengineering/contact-resources'
import { Avatar, getPersonByPersonIdCb } from '@hcengineering/contact-resources'
import { Class, Doc, Ref } from '@hcengineering/core'
import { BrowserNotification } from '@hcengineering/notification'
import { Button, navigate, Notification as PlatformNotification, NotificationToast } from '@hcengineering/ui'
@@ -11,6 +11,7 @@
import { pushAvailable, subscribePush } from '../utils'
import plugin from '../plugin'
import { onMount } from 'svelte'
import { Person } from '@hcengineering/contact'
export let notification: PlatformNotification
export let onRemove: () => void
@@ -19,7 +20,15 @@
const hierarchy = client.getHierarchy()
$: value = notification.params?.value as BrowserNotification
$: sender = value.senderId !== undefined ? $personByPersonIdStore.get(value.senderId) : undefined
let sender: Person | undefined
$: if (value.senderId !== undefined) {
getPersonByPersonIdCb(value.senderId, (p) => {
sender = p ?? undefined
})
} else {
sender = undefined
}
async function openChannelInSidebar (): Promise<void> {
if (!value.onClickLocation) return
@@ -15,7 +15,7 @@
<script lang="ts">
import { Icon, IconAdd, IconDelete, Label } from '@hcengineering/ui'
import { personRefByAccountUuidStore, PersonRefPresenter } from '@hcengineering/contact-resources'
import { employeeRefByAccountUuidStore, PersonRefPresenter } from '@hcengineering/contact-resources'
import { Person } from '@hcengineering/contact'
import { type Ref, type AccountUuid, notEmpty } from '@hcengineering/core'
import activity, { DocAttributeUpdates } from '@hcengineering/activity'
@@ -23,8 +23,8 @@
export let value: DocAttributeUpdates
$: removed = getPersonRefs(value.removed, $personRefByAccountUuidStore)
$: added = getPersonRefs(value.added.length > 0 ? value.added : value.set, $personRefByAccountUuidStore)
$: removed = getPersonRefs(value.removed, $employeeRefByAccountUuidStore)
$: added = getPersonRefs(value.added.length > 0 ? value.added : value.set, $employeeRefByAccountUuidStore)
function getPersonRefs (
values: DocAttributeUpdates['removed' | 'added' | 'set'],
@@ -14,24 +14,24 @@
-->
<script lang="ts">
import { type Doc } from '@hcengineering/core'
import { type Person, formatName } from '@hcengineering/contact'
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
import { notEmpty, type Doc } from '@hcengineering/core'
import { formatName } from '@hcengineering/contact'
import { Avatar, getPersonByPersonRefStore } from '@hcengineering/contact-resources'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { IconSize, tooltip, deviceOptionsStore as deviceInfo, checkAdaptiveMatching } from '@hcengineering/ui'
import PresenceList from './PresenceList.svelte'
import { presenceByObjectId, followee, toggleFollowee } from '../store'
export let object: Doc
export let size: IconSize = 'small'
export let limit: number = 4
$: presence = $presenceByObjectId?.get(object._id) ?? []
$: personByRefStore = getPersonByPersonRefStore(presence.map((p) => p.person))
$: persons = presence
.map((it) => it.person)
.map((p) => $personByIdStore.get(p))
.filter((p): p is Person => p !== undefined)
.map((p) => $personByRefStore.get(p))
.filter(notEmpty)
$: overLimit = persons.length > limit
$: adaptive = checkAdaptiveMatching($deviceInfo.size, 'md') || overLimit
</script>
+11 -11
View File
@@ -16,7 +16,7 @@
import { type Doc, type Ref } from '@hcengineering/core'
import { getCurrentEmployee, type Person } from '@hcengineering/contact'
import { type PresenceData } from '@hcengineering/presence'
import { personByIdStore } from '@hcengineering/contact-resources'
import { getPersonByPersonRef } from '@hcengineering/contact-resources'
import { type Readable, derived, writable, get } from 'svelte/store'
import type { PersonRoomPresence, Room, RoomPresence, MyDataItem } from './types'
@@ -29,7 +29,7 @@ export const otherPresence = writable<PersonPresenceMap>(new Map())
export const followee = writable<Ref<Person> | undefined>(undefined)
const personDataMap = new Map<Ref<Person>, Map<string, any>>()
const followeeDataHandlers = new Map<string, Set<(data: any) => void>>()
const followeeDataHandlers = new Map<string, Set<(data: any) => Promise<void>>>()
export const presenceByObjectId = derived<Readable<PersonPresenceMap>, Map<Ref<Doc>, PersonRoomPresence[]>>(
otherPresence,
@@ -100,13 +100,13 @@ export function onPersonData (person: Ref<Person>, topic: string, data: any): vo
const handlers = followeeDataHandlers.get(topic)
if (handlers !== undefined) {
for (const handler of handlers) {
handler(data)
void handler(data)
}
}
}
}
export function followeeDataSubscribe (topic: string, handler: (data: any) => void): void {
export function followeeDataSubscribe (topic: string, handler: (data: any) => Promise<void>): void {
const handlers = followeeDataHandlers.get(topic)
if (handlers !== undefined) {
handlers.add(handler)
@@ -119,13 +119,13 @@ export function followeeDataSubscribe (topic: string, handler: (data: any) => vo
if (followeeData !== undefined) {
const data = followeeData.get(topic)
if (data !== undefined) {
handler(data)
void handler(data)
}
}
}
}
export function followeeDataUnsubscribe (topic: string, handler: (data: any) => void): void {
export function followeeDataUnsubscribe (topic: string, handler: (data: any) => Promise<void>): void {
const handlers = followeeDataHandlers.get(topic)
if (handlers !== undefined) {
handlers.delete(handler)
@@ -143,7 +143,7 @@ export function toggleFollowee (person: Ref<Person> | undefined): void {
const handlers = followeeDataHandlers.get(topic)
if (handlers !== undefined) {
for (const handler of handlers) {
handler(data)
void handler(data)
}
}
}
@@ -151,19 +151,19 @@ export function toggleFollowee (person: Ref<Person> | undefined): void {
} else {
for (const handlers of followeeDataHandlers.values()) {
for (const handler of handlers) {
handler(undefined)
void handler(undefined)
}
}
}
}
export function getFollowee (): Person | undefined {
export async function getFollowee (): Promise<Person | undefined> {
const followeeId = get(followee)
if (followeeId === undefined) {
return undefined
}
const personMap = get(personByIdStore)
return personMap.get(followeeId)
return (await getPersonByPersonRef(followeeId)) ?? undefined
}
export function publishData (topic: string, data: any): void {
+3 -3
View File
@@ -31,9 +31,9 @@ export const presencePlugin = plugin(presenceId, {
},
function: {
PublishData: '' as Resource<(topic: string, data: any) => void>,
GetFollowee: '' as Resource<() => Person | undefined>,
FolloweeDataSubscribe: '' as Resource<(topic: string, handler: (data: any) => void) => void>,
FolloweeDataUnsubscribe: '' as Resource<(topic: string, handler: (data: any) => void) => void>
GetFollowee: '' as Resource<() => Promise<Person | undefined>>,
FolloweeDataSubscribe: '' as Resource<(topic: string, handler: (data: any) => Promise<void>) => void>,
FolloweeDataUnsubscribe: '' as Resource<(topic: string, handler: (data: any) => Promise<void>) => void>
}
})
@@ -23,7 +23,7 @@
import { type Product, ProductVersionState } from '@hcengineering/products'
import { type Attachment } from '@hcengineering/attachment'
import { AttachmentPresenter, AttachmentStyledBox } from '@hcengineering/attachment-resources'
import { AccountArrayEditor, personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { AccountArrayEditor, employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
import core, {
AccountUuid,
Data,
@@ -71,7 +71,7 @@
let typeId: Ref<DocumentSpaceType> = products.spaceType.ProductType
let spaceType: WithLookup<SpaceType> | undefined
$: membersPersons = object.members.map((m) => $personRefByAccountUuidStore.get(m)).filter(notEmpty)
$: membersPersons = object.members.map((m) => $employeeRefByAccountUuidStore.get(m)).filter(notEmpty)
let roles: Role[] = []
const rolesQuery = createQuery()
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import contact, { Person } from '@hcengineering/contact'
import { personRefByPersonIdStore, PersonRefPresenter } from '@hcengineering/contact-resources'
import { getPersonRefsByPersonIdsCb, PersonRefPresenter } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { createQuery, MessageViewer } from '@hcengineering/presentation'
import { Request, RequestDecisionComment } from '@hcengineering/request'
@@ -22,17 +22,24 @@
import request from '../plugin'
export let value: Request
let comments = new Map<Ref<Person> | undefined, RequestDecisionComment>()
let comments: RequestDecisionComment[] = []
let commentsByPersonRefs = new Map<Ref<Person> | undefined, RequestDecisionComment>()
const query = createQuery()
$: query.query(request.mixin.RequestDecisionComment, { attachedTo: value._id }, (res) => {
comments = new Map(
res.map((r) => {
const person = $personRefByPersonIdStore.get(r.modifiedBy)
return [person, r]
})
)
comments = res
})
$: getPersonRefsByPersonIdsCb(
comments.map((it) => it.modifiedBy),
(res) => {
commentsByPersonRefs = new Map(
comments.map((c) => {
const personRef = res.get(c.modifiedBy)
return [personRef, c]
})
)
}
)
interface RequestDecision {
employee: Ref<Person>
@@ -66,7 +73,7 @@
</tr>
</thead>
<tbody>
{#each convert(value, comments) as requested}
{#each convert(value, commentsByPersonRefs) as requested}
<tr class="antiTable-body__row">
<td><PersonRefPresenter value={requested.employee} /></td>
<td><BooleanIcon value={requested.decision} /></td>
@@ -13,8 +13,8 @@
// limitations under the License.
-->
<script lang="ts">
import { getName } from '@hcengineering/contact'
import { personByPersonIdStore } from '@hcengineering/contact-resources'
import { getName, Person } from '@hcengineering/contact'
import { getPersonByPersonIdCb } from '@hcengineering/contact-resources'
import { Doc, TxCUD } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Request } from '@hcengineering/request'
@@ -27,7 +27,10 @@
export let value: Request
const client = getClient()
$: employee = $personByPersonIdStore.get(value.tx.modifiedBy)
let employee: Person | undefined
$: getPersonByPersonIdCb(value.tx.modifiedBy, (p) => {
employee = p ?? undefined
})
$: txCud = value.tx as TxCUD<Doc>
</script>
@@ -13,8 +13,8 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { combineName, getCurrentEmployee, getFirstName, getLastName } from '@hcengineering/contact'
import { ChannelsEditor, EditableAvatar, personByIdStore } from '@hcengineering/contact-resources'
import contact, { combineName, getFirstName, getLastName } from '@hcengineering/contact'
import { ChannelsEditor, EditableAvatar, myEmployeeStore } from '@hcengineering/contact-resources'
import { getCurrentAccount, SocialIdType } from '@hcengineering/core'
import login, { loginId } from '@hcengineering/login'
import { getResource } from '@hcengineering/platform'
@@ -35,22 +35,20 @@
const client = getClient()
const account = getCurrentAccount()
const me = getCurrentEmployee()
const employee = $personByIdStore.get(me)
const email = account.fullSocialIds.find((si) => si.type === SocialIdType.EMAIL)?.value ?? ''
let firstName = employee !== undefined ? getFirstName(employee.name) : ''
let lastName = employee !== undefined ? getLastName(employee.name) : ''
let firstName = $myEmployeeStore !== undefined ? getFirstName($myEmployeeStore.name) : ''
let lastName = $myEmployeeStore !== undefined ? getLastName($myEmployeeStore.name) : ''
let avatarEditor: EditableAvatar
async function onAvatarDone (e: any): Promise<void> {
if (employee === undefined) return
if ($myEmployeeStore === undefined) return
if (employee.avatar != null) {
await avatarEditor.removeAvatar(employee.avatar)
if ($myEmployeeStore.avatar != null) {
await avatarEditor.removeAvatar($myEmployeeStore.avatar)
}
const avatar = await avatarEditor.createAvatar()
await client.diffUpdate(employee, avatar)
await client.diffUpdate($myEmployeeStore, avatar)
}
const manager = createFocusManager()
@@ -75,8 +73,8 @@
}
async function nameChange (): Promise<void> {
if (employee !== undefined) {
await client.diffUpdate(employee, {
if ($myEmployeeStore !== undefined) {
await client.diffUpdate($myEmployeeStore, {
name: combineName(firstName, lastName)
})
}
@@ -90,14 +88,14 @@
<Breadcrumb icon={setting.icon.AccountSettings} label={setting.string.AccountSettings} size={'large'} isCurrent />
</Header>
<div class="ac-body p-10">
{#if employee}
{#if $myEmployeeStore}
<div class="flex flex-grow w-full">
<div class="mr-8">
<EditableAvatar
person={employee}
person={$myEmployeeStore}
{email}
size={'x-large'}
name={employee.name}
name={$myEmployeeStore.name}
bind:this={avatarEditor}
on:done={onAvatarDone}
/>
@@ -122,15 +120,15 @@
<AttributeEditor
maxWidth="20rem"
_class={contact.class.Person}
object={employee}
object={$myEmployeeStore}
focusIndex={3}
key="city"
/>
</div>
<div class="separator" />
<ChannelsEditor
attachedTo={employee._id}
attachedClass={employee._class}
attachedTo={$myEmployeeStore._id}
attachedClass={$myEmployeeStore._class}
focusIndex={10}
allowOpen={false}
restricted={[contact.channelProvider.Email]}
@@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { AccountArrayEditor, personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { AccountArrayEditor, employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { type AccountUuid, TypedSpace, notEmpty } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
@@ -27,7 +27,7 @@
export let size: ButtonSize = 'large'
export let width: string | undefined = undefined
$: persons = (object?.members ?? []).map((m) => $personRefByAccountUuidStore.get(m)).filter(notEmpty)
$: persons = (object?.members ?? []).map((m) => $employeeRefByAccountUuidStore.get(m)).filter(notEmpty)
</script>
{#if object !== undefined}
@@ -15,7 +15,7 @@
<script lang="ts">
import { deepEqual } from 'fast-equals'
import { createEventDispatcher } from 'svelte'
import { AccountArrayEditor, personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { AccountArrayEditor, employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { Asset } from '@hcengineering/platform'
import core, {
Data,
@@ -63,7 +63,7 @@
let members: AccountUuid[] =
project?.members !== undefined ? hierarchy.clone(project.members) : [getCurrentAccount().uuid]
$: membersPersons = members.map((m) => $personRefByAccountUuidStore.get(m)).filter(notEmpty)
$: membersPersons = members.map((m) => $employeeRefByAccountUuidStore.get(m)).filter(notEmpty)
let owners: AccountUuid[] =
project?.owners !== undefined ? hierarchy.clone(project.owners) : [getCurrentAccount().uuid]
let rolesAssignment: RolesAssignment = {}
@@ -56,7 +56,7 @@
let oldSelected = false
let oldReadonly = false
let sendLiveData: ((key: string, data: any) => void) | undefined
let getFollowee: (() => Person | undefined) | undefined
let getFollowee: (() => Promise<Person | undefined>) | undefined
let panning = false
let followee: Person | undefined
const dataTopicOffset = 'drawing-board-offset'
@@ -155,14 +155,14 @@
}
}
function onFolloweeData (data: any): void {
async function onFolloweeData (data: any): Promise<void> {
if (data === undefined) {
followee = undefined
personCursorVisible = false
return
}
if (followee === undefined && getFollowee !== undefined) {
followee = getFollowee()
followee = await getFollowee()
}
if (data.boardId === boardId) {
const newOffset = data.offset
@@ -8,7 +8,7 @@
import WithTeamData from '../WithTeamData.svelte'
import { toSlots } from '../utils'
import DayPlan from './DayPlan.svelte'
import { personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
export let space: Ref<Project>
export let currentDate: Date
@@ -39,7 +39,7 @@
)
$: persons = (project?.members ?? [])
.map((it) => $personRefByAccountUuidStore.get(it))
.map((it) => $employeeRefByAccountUuidStore.get(it))
.filter((it) => it !== undefined)
</script>
@@ -15,13 +15,20 @@
<script lang="ts">
import calendar, { Event, getAllEvents } from '@hcengineering/calendar'
import { calendarByIdStore } from '@hcengineering/calendar-resources'
import { getCurrentEmployee, Person } from '@hcengineering/contact'
import {
personRefByAccountUuidStore,
personRefByPersonIdStore,
socialIdsByPersonRefStore
} from '@hcengineering/contact-resources'
import core, { Doc, IdMap, Ref, Timestamp, Tx, TxCreateDoc, TxCUD, TxUpdateDoc } from '@hcengineering/core'
import contact, { getCurrentEmployee, Person } from '@hcengineering/contact'
import { employeeRefByAccountUuidStore, getPersonRefsByPersonIdsCb } from '@hcengineering/contact-resources'
import core, {
Doc,
IdMap,
PersonId,
Ref,
Timestamp,
Tx,
TxCreateDoc,
TxCUD,
TxUpdateDoc,
unique
} from '@hcengineering/core'
import { Asset } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Project } from '@hcengineering/task'
@@ -48,29 +55,42 @@
let events: Event[] = []
let todos: IdMap<ToDo> = new Map()
$: persons = (project?.members ?? [])
.map((it) => $personRefByAccountUuidStore.get(it))
$: personsRefs = (project?.members ?? [])
.map((it) => $employeeRefByAccountUuidStore.get(it))
.filter((it) => it !== undefined)
const txCreateQuery = createQuery()
let txes = new Map<Ref<Person>, Tx[]>()
let personsSocialIds: PersonId[] = []
let txes: Tx[]
let txesMap = new Map<Ref<Person>, Tx[]>()
const socialIdsQuery = createQuery()
$: if (personsRefs.length > 0) {
socialIdsQuery.query(contact.class.SocialIdentity, { attachedTo: { $in: personsRefs } }, (res) => {
personsSocialIds = res.map((si) => si._id).flat()
})
} else {
socialIdsQuery.unsubscribe()
}
$: personsSocialStrings = persons.map((p) => ($socialIdsByPersonRefStore.get(p) ?? []).map((si) => si._id)).flat()
$: txCreateQuery.query(
core.class.Tx,
{ modifiedBy: { $in: personsSocialStrings }, modifiedOn: { $gt: fromDate, $lt: toDate } },
{ modifiedBy: { $in: personsSocialIds }, modifiedOn: { $gt: fromDate, $lt: toDate } },
(res) => {
const map = new Map<Ref<Person>, Tx[]>()
for (const t of res) {
const personId = t.createdBy ?? t.modifiedBy
const personRef = $personRefByPersonIdStore.get(personId)
if (personRef === undefined) continue
map.set(personRef, [...(map.get(personRef) ?? []), t])
}
txes = map
txes = res
}
)
$: getPersonRefsByPersonIdsCb(unique(txes.map((it) => it.createdBy ?? it.modifiedBy)), (res) => {
const map = new Map<Ref<Person>, Tx[]>()
for (const t of txes) {
const personId = t.createdBy ?? t.modifiedBy
const personRef = res.get(personId)
if (personRef === undefined) continue
map.set(personRef, [...(map.get(personRef) ?? []), t])
}
txesMap = map
})
const client = getClient()
@@ -134,9 +154,9 @@
$: allEvents = getAllEvents(events, fromDate, toDate)
</script>
<WithTeamData {space} {fromDate} {toDate} bind:project bind:todos bind:slots bind:events bind:persons />
<WithTeamData {space} {fromDate} {toDate} bind:project bind:todos bind:slots bind:events bind:persons={personsRefs} />
<PersonCalendar {persons} startDate={currentDate} {maxDays}>
<PersonCalendar persons={personsRefs} startDate={currentDate} {maxDays}>
<svelte:fragment slot="day" let:day let:today let:weekend let:person let:height>
{@const dayFrom = new Date(day).setHours(0, 0, 0, 0)}
{@const dayTo = new Date(day).setHours(23, 59, 59, 999)}
@@ -151,7 +171,7 @@
{@const planned = gitem?.mappings.reduce((it, val) => it + val.total, 0) ?? 0}
{@const pevents = gitem?.events.reduce((it, val) => it + (val.dueDate - val.date), 0) ?? 0}
{@const busy = gitem?.busy.slots.reduce((it, val) => it + (val.dueDate - val.date), 0) ?? 0}
{@const txInfo = group(txes.get(person) ?? [], dayFrom, dayTo)}
{@const txInfo = group(txesMap.get(person) ?? [], dayFrom, dayTo)}
<div style:overflow="auto" style:height="{height}rem" class="p-1">
<div class="flex-row-center p-1">
<Icon icon={time.icon.Team} size={'small'} />
@@ -23,7 +23,7 @@
import { groupTeamData, toSlots } from '../utils'
import EventElement from './EventElement.svelte'
import PersonCalendar from './PersonCalendar.svelte'
import { personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
export let space: Ref<Project>
export let currentDate: Date
@@ -40,7 +40,7 @@
let todos: IdMap<ToDo> = new Map()
$: persons = (project?.members ?? [])
.map((it) => $personRefByAccountUuidStore.get(it))
.map((it) => $employeeRefByAccountUuidStore.get(it))
.filter((it) => it !== undefined)
function calcHourWidth (events: Event[], totalWidth: number): number[] {
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import contact, { Employee, Person } from '@hcengineering/contact'
import { AssigneeBox, AssigneePopup, personRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { AssigneeBox, AssigneePopup, employeeRefByAccountUuidStore } from '@hcengineering/contact-resources'
import { AssigneeCategory } from '@hcengineering/contact-resources/src/assignee'
import { Doc, DocumentQuery, notEmpty, Ref, Space } from '@hcengineering/core'
import { RuleApplyResult, getClient, getDocRules } from '@hcengineering/presentation'
@@ -123,7 +123,7 @@
}
const allMembers = projects.map((p) => p.members).flat()
const allPersonsSet = new Set(allMembers.map((p) => $personRefByAccountUuidStore.get(p)).filter(notEmpty))
const allPersonsSet = new Set(allMembers.map((p) => $employeeRefByAccountUuidStore.get(p)).filter(notEmpty))
return Array.from(allPersonsSet)
}

Some files were not shown because too many files have changed in this diff Show More