Direct typing and presence (#10147)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina
2025-10-24 21:52:42 +07:00
committed by GitHub
parent 5def0cb231
commit 28fb9a4baf
36 changed files with 256 additions and 211 deletions
+1 -1
View File
@@ -51,7 +51,7 @@
"@hcengineering/ui": "^0.7.0",
"@hcengineering/view": "^0.7.0",
"@hcengineering/view-resources": "^0.7.0",
"@hcengineering/communication-types": "^0.7.7",
"@hcengineering/communication-types": "^0.7.8",
"@hcengineering/emoji": "^0.7.0",
"@hcengineering/emoji-resources": "^0.7.0",
"svelte": "^4.2.20"
+1 -1
View File
@@ -41,7 +41,7 @@
"dependencies": {
"@hcengineering/presence": "^0.7.0",
"@hcengineering/presentation": "^0.7.0",
"@hcengineering/communication-types": "^0.7.7",
"@hcengineering/communication-types": "^0.7.8",
"@hcengineering/communication-resources": "^0.7.0",
"@hcengineering/core": "^0.7.10",
"@hcengineering/ui": "^0.7.0",
@@ -200,8 +200,14 @@
</svelte:fragment>
<svelte:fragment slot="presence">
<Component is={presence.component.Presence} props={{ object: doc }} />
<Component is={presence.component.PresenceAvatars} props={{ object: doc, size: 'x-small', limit: 5 }} />
<Component
is={presence.component.Presence}
props={{ object: doc, presenceId: doc.peerId ? `peer:${doc.peerId}` : doc._id }}
/>
<Component
is={presence.component.PresenceAvatars}
props={{ object: doc, size: 'x-small', limit: 5, presenceId: doc.peerId ? `peer:${doc.peerId}` : doc._id }}
/>
</svelte:fragment>
<svelte:fragment slot="pre-utils">
+2
View File
@@ -57,6 +57,8 @@ export interface Card extends Doc, IconProps {
parent?: Ref<Card> | null
rank: Rank
readonly?: boolean
peerId?: string
}
export interface CardSpace extends Space {
+2 -2
View File
@@ -42,7 +42,7 @@
"@hcengineering/card": "^0.7.0",
"@hcengineering/card-resources": "^0.7.0",
"@hcengineering/chat": "^0.7.0",
"@hcengineering/communication-types": "^0.7.7",
"@hcengineering/communication-types": "^0.7.8",
"@hcengineering/communication": "^0.7.0",
"@hcengineering/contact": "^0.7.0",
"@hcengineering/contact-resources": "^0.7.0",
@@ -55,7 +55,7 @@
"@hcengineering/workbench": "^0.7.0",
"@hcengineering/workbench-resources": "^0.7.0",
"@hcengineering/communication-resources": "^0.7.0",
"@hcengineering/communication-shared": "^0.7.7",
"@hcengineering/communication-shared": "^0.7.8",
"@hcengineering/rank": "^0.7.5",
"@hcengineering/text": "^0.7.5",
"@hcengineering/text-markdown": "^0.7.5",
+2 -2
View File
@@ -49,8 +49,8 @@
"@hcengineering/card": "^0.7.0",
"@hcengineering/chat": "^0.7.0",
"@hcengineering/communication": "^0.7.0",
"@hcengineering/communication-types": "^0.7.7",
"@hcengineering/communication-shared": "^0.7.7",
"@hcengineering/communication-types": "^0.7.8",
"@hcengineering/communication-shared": "^0.7.8",
"@hcengineering/contact": "^0.7.0",
"@hcengineering/contact-resources": "^0.7.0",
"@hcengineering/core": "^0.7.10",
@@ -25,6 +25,7 @@
import communication from '../plugin'
export let cardId: CardID
export let peerId: string | undefined
const maxTypingPersons = 3
const acc = getCurrentAccount()
@@ -40,6 +41,8 @@
let typingInfo = new Map<string, TypingInfo>()
let typingGroups: TypingGroup[] = []
$: objectId = peerId ? `peer${peerId}` : cardId
$: void updateTypingPersons(typingInfo)
async function updateTypingPersons (typingInfo: Map<string, TypingInfo>): Promise<void> {
@@ -91,7 +94,7 @@
class="root h-4 mt-1 mb-1 ml-0-5 overflow-label"
use:typing={{
socialId: acc.primarySocialId,
objectId: cardId,
objectId,
onTyping: handleTyping
}}
>
@@ -461,7 +461,7 @@
if (message !== undefined) return
if (!isEmptyMarkup(draft.content)) {
throttle.call(() => {
void setTyping(acc.primarySocialId, card._id)
void setTyping(acc.primarySocialId, card.peerId ? `peer:${card.peerId}` : card._id)
})
}
}
@@ -705,7 +705,7 @@
</div>
{#if message === undefined}
<TypingPresenter cardId={card._id} />
<TypingPresenter cardId={card._id} peerId={card.peerId} />
{/if}
<style lang="scss">
+1 -1
View File
@@ -38,7 +38,7 @@
},
"dependencies": {
"@hcengineering/platform": "^0.7.5",
"@hcengineering/communication-types": "^0.7.7",
"@hcengineering/communication-types": "^0.7.8",
"@hcengineering/core": "^0.7.10",
"@hcengineering/contact": "^0.7.0",
"@hcengineering/ui": "^0.7.0",
+2 -2
View File
@@ -42,8 +42,8 @@
"@hcengineering/card": "^0.7.0",
"@hcengineering/communication": "^0.7.0",
"@hcengineering/communication-resources": "^0.7.0",
"@hcengineering/communication-types": "^0.7.7",
"@hcengineering/communication-shared": "^0.7.7",
"@hcengineering/communication-types": "^0.7.8",
"@hcengineering/communication-shared": "^0.7.8",
"@hcengineering/contact": "^0.7.0",
"@hcengineering/contact-resources": "^0.7.0",
"@hcengineering/core": "^0.7.10",
@@ -19,8 +19,9 @@
import PresenceContext from './PresenceContext.svelte'
export let object: Doc
export let presenceId: string | undefined = undefined
</script>
{#key object._id}
<PresenceContext {object} />
<PresenceContext {object} {presenceId} />
{/key}
@@ -25,6 +25,7 @@
import { followee, toggleFollowee } from '../store'
export let object: Doc
export let presenceId: string | undefined = undefined
export let size: IconSize = 'small'
export let limit: number = 4
@@ -55,7 +56,7 @@
<div
use:presence={{
personId: me,
objectId: object._id,
objectId: presenceId ?? object._id,
objectClass: object._class,
onPresence
}}
@@ -21,18 +21,19 @@
import { updatePresence, deletePresence } from '../presence'
export let object: Doc
export let presenceId: string | undefined = undefined
export let presenceTtlSeconds: number = 5
export let presenceUpdateSeconds: number = 2
const personId = getCurrentEmployee()
async function doUpdatePresence (): Promise<void> {
const presence = { personId, objectId: object._id, objectClass: object._class }
const presence = { personId, objectId: presenceId ?? object._id, objectClass: object._class }
await updatePresence(presence, presenceTtlSeconds)
}
async function doDeletePresence (object: Doc): Promise<void> {
const presence = { personId, objectId: object._id, objectClass: object._class }
async function doDeletePresence (object: Doc, presenceId?: string): Promise<void> {
const presence = { personId, objectId: presenceId ?? object._id, objectClass: object._class }
await deletePresence(presence)
}
@@ -41,15 +42,20 @@
const interval = setInterval(doUpdatePresence, presenceUpdateSeconds * 1000)
return () => {
clearInterval(interval)
void doDeletePresence(object)
void doDeletePresence(object, presenceId)
}
})
let previousObject: Doc = object
let prevPresenceId: string | undefined = presenceId
$: if (object !== undefined && (object._id !== previousObject._id || object._class !== previousObject._class)) {
$: if (
object !== undefined &&
(object._id !== previousObject._id || object._class !== previousObject._class || presenceId !== prevPresenceId)
) {
void doDeletePresence(previousObject)
previousObject = object
prevPresenceId = presenceId
void doUpdatePresence()
}
</script>
+3 -3
View File
@@ -19,13 +19,13 @@ import presentation, { createPulseClient } from '@hcengineering/presentation'
export interface PresenceInfo {
personId: Ref<Person>
objectId: Ref<Doc>
objectId: string
objectClass: Ref<Class<Doc>>
}
export interface PresenceActionParams {
personId: Ref<Employee>
objectId: Ref<Doc>
objectId: string
objectClass: Ref<Class<Doc>>
onPresence: (presence: Map<string, Ref<Person>>) => void
}
@@ -83,7 +83,7 @@ export function presence (node: HTMLElement, params: PresenceActionParams): any
export async function subscribePresence (
objectClass: Ref<Class<Doc>>,
objectId: Ref<Doc>,
objectId: string,
callback: Callback<PresenceInfo | undefined>
): Promise<UnsubscribeCallback> {
const client = await createPulseClient()
+5 -5
View File
@@ -14,7 +14,7 @@
import { type UnsubscribeCallback, type Callback } from '@hcengineering/hulypulse-client'
import { type IntlString, getMetadata } from '@hcengineering/platform'
import presentation, { createPulseClient } from '@hcengineering/presentation'
import { type Doc, type Ref, type PersonId } from '@hcengineering/core'
import { type PersonId } from '@hcengineering/core'
const typingDelaySeconds = 2
@@ -24,13 +24,13 @@ function getWorkspace (): string {
export interface TypingInfo {
socialId: PersonId
objectId: Ref<Doc>
objectId: string
status?: IntlString
}
export interface TypingActionParams {
socialId: PersonId
objectId: Ref<Doc>
objectId: string
onTyping: (presence: Map<string, TypingInfo>) => void
}
@@ -84,7 +84,7 @@ export function typing (node: HTMLElement, params: TypingActionParams): any {
}
export async function subscribeTyping (
objectId: Ref<Doc>,
objectId: string,
callback: Callback<TypingInfo | undefined>
): Promise<UnsubscribeCallback> {
const client = await createPulseClient()
@@ -100,7 +100,7 @@ export async function subscribeTyping (
return async () => false
}
export async function setTyping (socialId: PersonId, objectId: Ref<Doc>, status?: IntlString): Promise<void> {
export async function setTyping (socialId: PersonId, objectId: string, status?: IntlString): Promise<void> {
const client = await createPulseClient()
if (client !== undefined) {
+1 -1
View File
@@ -58,7 +58,7 @@
"@hcengineering/support-resources": "^0.7.0",
"@hcengineering/view-resources": "^0.7.0",
"@hcengineering/inbox": "^0.7.0",
"@hcengineering/communication-types": "^0.7.7",
"@hcengineering/communication-types": "^0.7.8",
"fast-copy": "^3.0.2",
"@hcengineering/analytics": "^0.7.5",
"@hcengineering/account-client": "^0.7.7",