mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
UBERF-11415: Optimise contact UI stores (#9185)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"verbatimModuleSyntax": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"lib": [
|
||||
"es2016",
|
||||
"es2023",
|
||||
"dom",
|
||||
"ES2021.String",
|
||||
"ESNext.Array"
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -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 = ''
|
||||
|
||||
|
||||
+6
-3
@@ -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()
|
||||
|
||||
+10
-2
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
+10
-2
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
+2
-2
@@ -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
-14
@@ -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 ?? [])
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
+16
-15
@@ -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
|
||||
}
|
||||
|
||||
@@ -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'} />
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user