mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-12 04:37:44 +02:00
fix: refactor presence and typing to use svelte actions (#9908)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
@@ -13,13 +13,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import { getName, getCurrentEmployee, Person } from '@hcengineering/contact'
|
||||
import { getPersonsByPersonRefs } from '@hcengineering/contact-resources'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { subscribeTyping, TypingInfo } from '@hcengineering/presence-resources'
|
||||
import { typing } from '@hcengineering/presence-resources'
|
||||
import { Doc, type Ref } from '@hcengineering/core'
|
||||
|
||||
const maxTypingPersons = 3
|
||||
@@ -45,37 +44,19 @@
|
||||
moreCount = Math.max(names.length - maxTypingPersons, 0)
|
||||
}
|
||||
|
||||
function handleTypingInfo (key: string, value: TypingInfo | undefined): void {
|
||||
if (value === undefined) {
|
||||
typingInfo.delete(key)
|
||||
typingInfo = typingInfo
|
||||
return
|
||||
}
|
||||
|
||||
if (typingInfo.has(key) || value.personId === me) {
|
||||
return
|
||||
}
|
||||
|
||||
typingInfo.set(key, value.personId)
|
||||
typingInfo = typingInfo
|
||||
function handleTyping (typing: Map<string, Ref<Person>>): void {
|
||||
typingInfo = typing
|
||||
}
|
||||
|
||||
let unsubscribe: (() => Promise<boolean>) | undefined
|
||||
|
||||
async function updateTypingSub (objectId: Ref<Doc>): Promise<void> {
|
||||
await unsubscribe?.()
|
||||
typingInfo = new Map<string, Ref<Person>>()
|
||||
unsubscribe = await subscribeTyping(objectId, handleTypingInfo)
|
||||
}
|
||||
|
||||
$: void updateTypingSub(object._id)
|
||||
|
||||
onDestroy(() => {
|
||||
void unsubscribe?.()
|
||||
})
|
||||
</script>
|
||||
|
||||
<span class="root h-4 mt-1 mb-1 ml-0-5 overflow-label">
|
||||
<span
|
||||
class="root h-4 mt-1 mb-1 ml-0-5 overflow-label"
|
||||
use:typing={{
|
||||
personId: me,
|
||||
objectId: object._id,
|
||||
onTyping: handleTyping
|
||||
}}
|
||||
>
|
||||
{#if typingPersonsLabel !== ''}
|
||||
<span class="fs-bold">
|
||||
{typingPersonsLabel}
|
||||
|
||||
@@ -11,13 +11,12 @@
|
||||
<!-- See the License for the specific language governing permissions and -->
|
||||
<!-- limitations under the License. -->
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte'
|
||||
import { getName, getCurrentEmployee, Person } from '@hcengineering/contact'
|
||||
import { getPersonsByPersonRefs } from '@hcengineering/contact-resources'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { subscribeTyping, TypingInfo } from '@hcengineering/presence-resources'
|
||||
import { typing } from '@hcengineering/presence-resources'
|
||||
import { CardID } from '@hcengineering/communication-types'
|
||||
|
||||
import communication from '../plugin'
|
||||
@@ -45,37 +44,19 @@
|
||||
moreCount = Math.max(names.length - maxTypingPersons, 0)
|
||||
}
|
||||
|
||||
function handleTypingInfo (key: string, value: TypingInfo | undefined): void {
|
||||
if (value === undefined) {
|
||||
typingInfo.delete(key)
|
||||
typingInfo = typingInfo
|
||||
return
|
||||
}
|
||||
|
||||
if (typingInfo.has(key) || value.personId === me) {
|
||||
return
|
||||
}
|
||||
|
||||
typingInfo.set(key, value.personId)
|
||||
typingInfo = typingInfo
|
||||
function handleTyping (typing: Map<string, Ref<Person>>): void {
|
||||
typingInfo = typing
|
||||
}
|
||||
|
||||
let unsubscribe: (() => Promise<boolean>) | undefined
|
||||
|
||||
async function updateTypingSub (cardId: CardID): Promise<void> {
|
||||
await unsubscribe?.()
|
||||
typingInfo = new Map<string, Ref<Person>>()
|
||||
unsubscribe = await subscribeTyping(cardId, handleTypingInfo)
|
||||
}
|
||||
|
||||
$: void updateTypingSub(cardId)
|
||||
|
||||
onDestroy(() => {
|
||||
void unsubscribe?.()
|
||||
})
|
||||
</script>
|
||||
|
||||
<span class="root h-4 mt-1 mb-1 ml-0-5 overflow-label">
|
||||
<span
|
||||
class="root h-4 mt-1 mb-1 ml-0-5 overflow-label"
|
||||
use:typing={{
|
||||
personId: me,
|
||||
objectId: cardId,
|
||||
onTyping: handleTyping
|
||||
}}
|
||||
>
|
||||
{#if typingPersonsLabel !== ''}
|
||||
<span class="fs-bold">
|
||||
{typingPersonsLabel}
|
||||
|
||||
@@ -14,15 +14,14 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { type Class, type Doc, type Ref, notEmpty } from '@hcengineering/core'
|
||||
import { type Doc, type Ref, notEmpty } from '@hcengineering/core'
|
||||
import { type Person, formatName, getCurrentEmployee } from '@hcengineering/contact'
|
||||
import { Avatar, getPersonsByPersonRefs } from '@hcengineering/contact-resources'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { IconSize, tooltip, deviceOptionsStore as deviceInfo, checkAdaptiveMatching } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
import PresenceList from './PresenceList.svelte'
|
||||
import { PresenceInfo, subscribePresence } from '../presence'
|
||||
import { presence } from '../presence'
|
||||
import { followee, toggleFollowee } from '../store'
|
||||
|
||||
export let object: Doc
|
||||
@@ -37,8 +36,6 @@
|
||||
$: overLimit = persons.length > limit
|
||||
$: adaptive = checkAdaptiveMatching($deviceInfo.size, 'md') || overLimit
|
||||
|
||||
let unsubscribe: (() => Promise<boolean>) | undefined
|
||||
|
||||
async function updatePresence (presenceInfo: Map<string, Ref<Person>>): Promise<void> {
|
||||
const personByRef = await getPersonsByPersonRefs(Array.from(presenceInfo.values()))
|
||||
persons = presenceInfo
|
||||
@@ -48,68 +45,56 @@
|
||||
.toArray()
|
||||
}
|
||||
|
||||
function handlePresenceInfo (key: string, value: PresenceInfo | undefined): void {
|
||||
if (value === undefined) {
|
||||
presenceInfo.delete(key)
|
||||
presenceInfo = presenceInfo
|
||||
return
|
||||
}
|
||||
|
||||
if (presenceInfo.has(key) || value.personId === me) {
|
||||
return
|
||||
}
|
||||
|
||||
presenceInfo.set(key, value.personId)
|
||||
presenceInfo = presenceInfo
|
||||
function onPresence (presence: Map<string, Ref<Person>>): void {
|
||||
presenceInfo = presence
|
||||
}
|
||||
|
||||
async function updatePresenceSub (objectClass: Ref<Class<Doc>>, objectId: Ref<Doc>): Promise<void> {
|
||||
await unsubscribe?.()
|
||||
presenceInfo = new Map<string, Ref<Person>>()
|
||||
unsubscribe = await subscribePresence(objectClass, objectId, handlePresenceInfo)
|
||||
}
|
||||
|
||||
$: void updatePresenceSub(object._class, object._id)
|
||||
$: void updatePresence(presenceInfo)
|
||||
onDestroy(() => {
|
||||
void unsubscribe?.()
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if persons.length > 0}
|
||||
{#if adaptive}
|
||||
<div
|
||||
class="hulyCombineAvatars-container"
|
||||
use:tooltip={{ component: PresenceList, props: { persons, size }, direction: 'bottom' }}
|
||||
>
|
||||
{#each persons.slice(0, limit) as person, i}
|
||||
<div
|
||||
class="hulyCombineAvatar tiny"
|
||||
data-over={i === limit - 1 && overLimit ? `+${persons.length - limit + 1}` : undefined}
|
||||
>
|
||||
<Avatar name={person.name} {size} {person} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex-row-center flex-gap-1">
|
||||
{#each persons as person}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
use:tooltip={{ label: getEmbeddedLabel(formatName(person.name)) }}
|
||||
class="avatar-button"
|
||||
class:followee-avatar={$followee === person._id}
|
||||
on:click={() => {
|
||||
toggleFollowee(person._id)
|
||||
}}
|
||||
>
|
||||
<Avatar name={person.name} {size} {person} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div
|
||||
use:presence={{
|
||||
personId: me,
|
||||
objectId: object._id,
|
||||
objectClass: object._class,
|
||||
onPresence
|
||||
}}
|
||||
>
|
||||
{#if persons.length > 0}
|
||||
{#if adaptive}
|
||||
<div
|
||||
class="hulyCombineAvatars-container"
|
||||
use:tooltip={{ component: PresenceList, props: { persons, size }, direction: 'bottom' }}
|
||||
>
|
||||
{#each persons.slice(0, limit) as person, i}
|
||||
<div
|
||||
class="hulyCombineAvatar tiny"
|
||||
data-over={i === limit - 1 && overLimit ? `+${persons.length - limit + 1}` : undefined}
|
||||
>
|
||||
<Avatar name={person.name} {size} {person} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex-row-center flex-gap-1">
|
||||
{#each persons as person}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
use:tooltip={{ label: getEmbeddedLabel(formatName(person.name)) }}
|
||||
class="avatar-button"
|
||||
class:followee-avatar={$followee === person._id}
|
||||
on:click={() => {
|
||||
toggleFollowee(person._id)
|
||||
}}
|
||||
>
|
||||
<Avatar name={person.name} {size} {person} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.avatar-button {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License
|
||||
|
||||
import { type Person } from '@hcengineering/contact'
|
||||
import { type Employee, type Person } from '@hcengineering/contact'
|
||||
import { type UnsubscribeCallback, type Callback } from '@hcengineering/hulypulse-client'
|
||||
import { type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
@@ -25,6 +25,64 @@ export interface PresenceInfo {
|
||||
objectClass: Ref<Class<Doc>>
|
||||
}
|
||||
|
||||
export interface PresenceActionParams {
|
||||
personId: Ref<Employee>
|
||||
objectId: Ref<Doc>
|
||||
objectClass: Ref<Class<Doc>>
|
||||
onPresence: (presence: Map<string, Ref<Person>>) => void
|
||||
}
|
||||
|
||||
export function presence (node: HTMLElement, params: PresenceActionParams): any {
|
||||
let unsubscribe: Promise<UnsubscribeCallback> | undefined
|
||||
let presence = new Map<string, Ref<Person>>()
|
||||
|
||||
let personId = params.personId
|
||||
let objectId = params.objectId
|
||||
let objectClass = params.objectClass
|
||||
let onPresence = params.onPresence
|
||||
|
||||
function handlePresenceInfo (key: string, value: PresenceInfo | undefined): void {
|
||||
if (value?.personId === personId) {
|
||||
return
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
presence.delete(key)
|
||||
} else {
|
||||
presence.set(key, value.personId)
|
||||
}
|
||||
|
||||
onPresence(presence)
|
||||
}
|
||||
|
||||
unsubscribe = subscribePresence(params.objectClass, params.objectId, handlePresenceInfo)
|
||||
|
||||
return {
|
||||
update: (params: PresenceActionParams) => {
|
||||
if (objectId !== params.objectId || objectClass !== params.objectClass) {
|
||||
personId = params.personId
|
||||
objectId = params.objectId
|
||||
objectClass = params.objectClass
|
||||
onPresence = params.onPresence
|
||||
|
||||
void unsubscribe?.then((unsub) => {
|
||||
void unsub()
|
||||
})
|
||||
|
||||
presence = new Map<string, Ref<Person>>()
|
||||
unsubscribe = subscribePresence(params.objectClass, params.objectId, handlePresenceInfo)
|
||||
|
||||
onPresence(presence)
|
||||
}
|
||||
},
|
||||
destroy: () => {
|
||||
void unsubscribe?.then((unsub) => {
|
||||
void unsub()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function subscribePresence (
|
||||
objectClass: Ref<Class<Doc>>,
|
||||
objectId: Ref<Doc>,
|
||||
|
||||
@@ -29,6 +29,61 @@ export interface TypingInfo {
|
||||
objectId: Ref<Doc>
|
||||
}
|
||||
|
||||
export interface TypingActionParams {
|
||||
personId: Ref<Employee>
|
||||
objectId: Ref<Doc>
|
||||
onTyping: (presence: Map<string, Ref<Person>>) => void
|
||||
}
|
||||
|
||||
export function typing (node: HTMLElement, params: TypingActionParams): any {
|
||||
let unsubscribe: Promise<UnsubscribeCallback> | undefined
|
||||
let presence = new Map<string, Ref<Person>>()
|
||||
|
||||
let personId = params.personId
|
||||
let objectId = params.objectId
|
||||
let onTyping = params.onTyping
|
||||
|
||||
function handleTypingInfo (key: string, value: TypingInfo | undefined): void {
|
||||
if (value?.personId === personId) {
|
||||
return
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
presence.delete(key)
|
||||
} else {
|
||||
presence.set(key, value.personId)
|
||||
}
|
||||
|
||||
onTyping(presence)
|
||||
}
|
||||
|
||||
unsubscribe = subscribeTyping(params.objectId, handleTypingInfo)
|
||||
|
||||
return {
|
||||
update: (params: TypingActionParams) => {
|
||||
if (objectId !== params.objectId) {
|
||||
personId = params.personId
|
||||
objectId = params.objectId
|
||||
onTyping = params.onTyping
|
||||
|
||||
void unsubscribe?.then((unsub) => {
|
||||
void unsub()
|
||||
})
|
||||
|
||||
presence = new Map<string, Ref<Person>>()
|
||||
unsubscribe = subscribeTyping(params.objectId, handleTypingInfo)
|
||||
|
||||
onTyping(presence)
|
||||
}
|
||||
},
|
||||
destroy: () => {
|
||||
void unsubscribe?.then((unsub) => {
|
||||
void unsub()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function subscribeTyping (
|
||||
objectId: Ref<Doc>,
|
||||
callback: Callback<TypingInfo | undefined>
|
||||
|
||||
Reference in New Issue
Block a user