UBERF-8999 Presence service (#7539)

Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
Alexander Onnikov
2024-12-24 20:41:57 +07:00
committed by GitHub
parent 30edf4ea8e
commit 949e7c2edd
57 changed files with 1080 additions and 44 deletions
+105 -5
View File
@@ -431,6 +431,9 @@ dependencies:
'@rush-temp/model-preference':
specifier: file:./projects/model-preference.tgz
version: file:projects/model-preference.tgz
'@rush-temp/model-presence':
specifier: file:./projects/model-presence.tgz
version: file:projects/model-presence.tgz
'@rush-temp/model-presentation':
specifier: file:./projects/model-presentation.tgz
version: file:projects/model-presentation.tgz
@@ -686,6 +689,12 @@ dependencies:
'@rush-temp/preference-assets':
specifier: file:./projects/preference-assets.tgz
version: file:projects/preference-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/presence':
specifier: file:./projects/presence.tgz
version: file:projects/presence.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/presence-resources':
specifier: file:./projects/presence-resources.tgz
version: file:projects/presence-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
'@rush-temp/presentation':
specifier: file:./projects/presentation.tgz
version: file:projects/presentation.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
@@ -1607,9 +1616,6 @@ dependencies:
fast-copy:
specifier: ~3.0.1
version: 3.0.1
fast-equals:
specifier: ^5.0.1
version: 5.0.1
file-api:
specifier: ^0.10.4
version: 0.10.4
@@ -25797,7 +25803,7 @@ packages:
dev: false
file:projects/model-all.tgz:
resolution: {integrity: sha512-t+T01n/lk1uFmDQvCw+ldE7yBemi2GcvJHXQKaRBXGprTf//JM9Wd9GgvQoYyQsQO7HA8ilAkmnVH3gfP7xxFg==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-POSTd7U69WG8HMf3/Budl2bpSfRrigifVT+i8kAOYT8EfuJvT0squXq0TMjYThMNQak91IwJF040+PjIWtlTBw==, tarball: file:projects/model-all.tgz}
name: '@rush-temp/model-all'
version: 0.0.0
dependencies:
@@ -26216,6 +26222,24 @@ packages:
- supports-color
dev: false
file:projects/model-presence.tgz:
resolution: {integrity: sha512-bE9NfhmtkeenHePBuorhYSQloGaNnGqtlw2i8ObXBD2ecu21ArwfbrJdHnQiy+Qtq8rs3j+rcCV/BcWpJSBJYQ==, tarball: file:projects/model-presence.tgz}
name: '@rush-temp/model-presence'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
prettier: 3.2.5
typescript: 5.6.2
transitivePeerDependencies:
- supports-color
dev: false
file:projects/model-presentation.tgz:
resolution: {integrity: sha512-fwOpgUcuXhN/upzKDq9tRE1T4bWjBYLHGqHoqu3yrOo82+0aIM7qtCu4bzhl9AdLaOtD/5VYYzFCFWYo2KfRBQ==, tarball: file:projects/model-presentation.tgz}
name: '@rush-temp/model-presentation'
@@ -28693,6 +28717,82 @@ packages:
- ts-node
dev: false
file:projects/presence-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-wPkp1Gl14BRxVtSA7GHeRGse3P/mNyw8ZMFaB+GWPy6uzKem9XOIIk6KKgvNgyZBZROcI9KxlEc5nCn6JUZNUw==, tarball: file:projects/presence-resources.tgz}
id: file:projects/presence-resources.tgz
name: '@rush-temp/presence-resources'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.19)(ts-node@10.9.2)
fast-equals: 5.0.1
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
prettier-plugin-svelte: 3.2.2(prettier@3.2.5)(svelte@4.2.19)
sass: 1.71.1
svelte: 4.2.19
svelte-check: 3.6.9(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.19)
svelte-eslint-parser: 0.33.1(svelte@4.2.19)
svelte-loader: 3.2.0(svelte@4.2.19)
svelte-preprocess: 5.1.3(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.19)(typescript@5.6.2)
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.6.2)
typescript: 5.6.2
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- coffeescript
- esbuild
- less
- node-notifier
- postcss
- postcss-load-config
- pug
- stylus
- sugarss
- supports-color
- ts-node
dev: false
file:projects/presence.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-UAKP+9sJPe3sFFYkuaoRLl5OGmzjdjb35+WCRBenMndS9HljrLekTZp2DL9F1XiyBa4OkB38vYYy2OoobeXx6Q==, tarball: file:projects/presence.tgz}
id: file:projects/presence.tgz
name: '@rush-temp/presence'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.6.2)
typescript: 5.6.2
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/presentation.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-oPHDMQuOu5zTYR4++5vrc3LWTZgdZLCxCmnFGSHhNSOG0uRezdWBXlJyN0IL85CI2S03RO/WT16siUOrjG5vwA==, tarball: file:projects/presentation.tgz}
id: file:projects/presentation.tgz
@@ -28854,7 +28954,7 @@ packages:
dev: false
file:projects/prod.tgz(bufferutil@4.0.8)(sass@1.71.1)(ts-node@10.9.2)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-5Lih7OOjrS1TenY/2rwnCFmfxjY0Ef0XgCiKURsCa6Rr5BixyOBIKDznMdFXIogr6vxKf0QWbpJVYm6iZE4J/A==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-Vhce2P4UQlBdcnn6eYAjevEqIDE2pkstFrPKam87+2O8XmnR7ZPzuMVZwkWVbVmXsNjE0N9lKiKNYMdcW2ByRQ==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
+2
View File
@@ -182,6 +182,8 @@
"@hcengineering/guest": "^0.6.4",
"@hcengineering/guest-assets": "^0.6.0",
"@hcengineering/guest-resources": "^0.6.0",
"@hcengineering/presence": "^0.6.0",
"@hcengineering/presence-resources": "^0.6.0",
"@hcengineering/uploader": "^0.6.0",
"@hcengineering/uploader-assets": "^0.6.0",
"@hcengineering/uploader-resources": "^0.6.0",
+4 -1
View File
@@ -29,6 +29,7 @@ import { leadId } from '@hcengineering/lead'
import login, { loginId } from '@hcengineering/login'
import notification, { notificationId } from '@hcengineering/notification'
import onboard, { onboardId } from '@hcengineering/onboard'
import presence, { presenceId } from '@hcengineering/presence'
import { recruitId } from '@hcengineering/recruit'
import rekoni from '@hcengineering/rekoni'
import { requestId } from '@hcengineering/request'
@@ -245,6 +246,7 @@ export async function configurePlatform (): Promise<void> {
setMetadata(uiPlugin.metadata.DefaultApplication, login.component.LoginApp)
setMetadata(analyticsCollector.metadata.EndpointURL, config.ANALYTICS_COLLECTOR_URL)
setMetadata(aiBot.metadata.EndpointURL, config.AI_URL)
setMetadata(presence.metadata.PresenceUrl, config.PRESENCE_URL ?? '')
const languages = myBranding.languages !== undefined && myBranding.languages !== '' ? myBranding.languages.split(',').map((l) => l.trim()) : ['en', 'ru', 'es', 'pt', 'zh', 'fr', 'cs', 'it']
@@ -303,6 +305,7 @@ export async function configurePlatform (): Promise<void> {
addLocation(productsId, async () => await import('@hcengineering/products-resources'))
addLocation(documentsId, async () => await import('@hcengineering/controlled-documents-resources'))
addLocation(uploaderId, async () => await import('@hcengineering/uploader-resources'))
addLocation(presenceId, async () => await import('@hcengineering/presence-resources'))
addLocation(githubId, async () => await import(/* webpackChunkName: "github" */ '@hcengineering/github-resources'))
addLocation(
desktopPreferencesId,
@@ -313,7 +316,7 @@ export async function configurePlatform (): Promise<void> {
addLocation(printId, () => import(/* webpackChunkName: "print" */ '@hcengineering/print-resources'))
addLocation(textEditorId, () => import(/* webpackChunkName: "text-editor" */ '@hcengineering/text-editor-resources'))
addLocation(testManagementId, () => import(/* webpackChunkName: "test-management" */ '@hcengineering/test-management-resources'))
addLocation(surveyId, () => import(/* webpackChunkName: "uploader" */ '@hcengineering/survey-resources'))
addLocation(surveyId, () => import(/* webpackChunkName: "survey" */ '@hcengineering/survey-resources'))
setMetadata(client.metadata.FilterModel, 'ui')
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
+1
View File
@@ -35,6 +35,7 @@ export interface Config {
DESKTOP_UPDATES_URL?: string
DESKTOP_UPDATES_CHANNEL?: string
TELEGRAM_BOT_URL?: string
PRESENCE_URL?: string
STATS_URL?: string
}
+2
View File
@@ -213,6 +213,8 @@
"@hcengineering/print": "^0.6.0",
"@hcengineering/print-assets": "^0.6.0",
"@hcengineering/print-resources": "^0.6.0",
"@hcengineering/presence": "^0.6.0",
"@hcengineering/presence-resources": "^0.6.0",
"@hcengineering/uploader": "^0.6.0",
"@hcengineering/uploader-assets": "^0.6.0",
"@hcengineering/uploader-resources": "^0.6.0",
+5 -1
View File
@@ -62,6 +62,7 @@ import { viewId } from '@hcengineering/view'
import workbench, { workbenchId } from '@hcengineering/workbench'
import { testManagementId } from '@hcengineering/test-management'
import { surveyId } from '@hcengineering/survey'
import presence, { presenceId } from '@hcengineering/presence'
import { bitrixId } from '@hcengineering/bitrix'
@@ -161,6 +162,7 @@ export interface Config {
PREVIEW_CONFIG?: string
UPLOAD_CONFIG?: string
STATS_URL?: string
PRESENCE_URL?: string
USE_BINARY_PROTOCOL?: boolean,
TRANSACTOR_OVERRIDE?: string
}
@@ -342,6 +344,7 @@ export async function configurePlatform() {
setMetadata(love.metadata.WebSocketURL, config.LIVEKIT_WS)
setMetadata(print.metadata.PrintURL, config.PRINT_URL)
setMetadata(sign.metadata.SignURL, config.SIGN_URL)
setMetadata(presence.metadata.PresenceUrl, config.PRESENCE_URL ?? '')
const languages = myBranding.languages ? (myBranding.languages as string).split(',').map((l) => l.trim()) : ['en', 'ru', 'es', 'pt', 'zh', 'fr', 'cs', 'it']
@@ -409,7 +412,8 @@ export async function configurePlatform() {
addLocation(textEditorId, () => import(/* webpackChunkName: "text-editor" */ '@hcengineering/text-editor-resources'))
addLocation(uploaderId, () => import(/* webpackChunkName: "uploader" */ '@hcengineering/uploader-resources'))
addLocation(testManagementId, () => import(/* webpackChunkName: "test-management" */ '@hcengineering/test-management-resources'))
addLocation(surveyId, () => import(/* webpackChunkName: "uploader" */ '@hcengineering/survey-resources'))
addLocation(surveyId, () => import(/* webpackChunkName: "survey" */ '@hcengineering/survey-resources'))
addLocation(presenceId, () => import(/* webpackChunkName: "presence" */ '@hcengineering/presence-resources'))
setMetadata(client.metadata.FilterModel, 'ui')
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
+2 -1
View File
@@ -112,6 +112,7 @@
"@hcengineering/model-ai-bot": "^0.6.0",
"@hcengineering/model-server-fulltext": "^0.6.0",
"@hcengineering/model-test-management": "^0.6.0",
"@hcengineering/model-survey": "^0.6.0"
"@hcengineering/model-survey": "^0.6.0",
"@hcengineering/model-presence": "^0.6.0"
}
}
+2
View File
@@ -106,6 +106,7 @@ import {
} from '@hcengineering/model-server-controlled-documents'
import { serverFulltextId, createModel as serverFulltextModel } from '@hcengineering/model-server-fulltext'
import { surveyId, createModel as surveyModel } from '@hcengineering/model-survey'
import { presenceId, createModel as presenceModel } from '@hcengineering/model-presence'
import { type Plugin } from '@hcengineering/platform'
@@ -422,6 +423,7 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
}
],
[surveyModel, surveyId],
[presenceModel, presenceId],
[serverCoreModel, serverCoreId],
[serverAttachmentModel, serverAttachmentId],
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "model"
}
+41
View File
@@ -0,0 +1,41 @@
{
"name": "@hcengineering/model-presence",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Hardcore Engineering Inc.",
"template": "@hcengineering/model-package",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"_phase:build": "compile transpile src",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/core": "^0.6.32",
"@hcengineering/model": "^0.6.11",
"@hcengineering/model-core": "^0.6.0",
"@hcengineering/model-presentation": "^0.6.0",
"@hcengineering/model-workbench": "^0.6.1",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/presence": "^0.6.0",
"@hcengineering/presence-resources": "^0.6.0"
}
}
+31
View File
@@ -0,0 +1,31 @@
//
// Copyright © 2024 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 { type Builder } from '@hcengineering/model'
import core from '@hcengineering/model-core'
import presentation from '@hcengineering/model-presentation'
import workbench from '@hcengineering/model-workbench'
import presence from './plugin'
export { presenceId } from '@hcengineering/presence'
export { presence as default }
export function createModel (builder: Builder): void {
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
extension: workbench.extensions.WorkbenchExtensions,
component: presence.component.WorkbenchExtension
})
}
+25
View File
@@ -0,0 +1,25 @@
//
// Copyright © 2024 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 { mergeIds } from '@hcengineering/platform'
import { type AnyComponent } from '@hcengineering/ui'
import { presenceId } from '@hcengineering/presence'
import presence from '@hcengineering/presence-resources/src/plugin'
export default mergeIds(presenceId, presence, {
component: {
WorkbenchExtension: '' as AnyComponent
}
})
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+2 -1
View File
@@ -45,6 +45,7 @@
"@hcengineering/chunter": "^0.6.20",
"@hcengineering/presentation": "^0.6.3",
"@hcengineering/activity": "^0.6.0",
"@hcengineering/calendar": "^0.6.24"
"@hcengineering/calendar": "^0.6.24",
"@hcengineering/presence": "^0.6.0"
}
}
@@ -29,6 +29,7 @@
} from '@hcengineering/ui'
import type { ButtonItem } from '@hcengineering/ui'
import { getResource } from '@hcengineering/platform'
import presence from '@hcengineering/presence'
export let title: string | undefined = undefined
export let withoutActivity: boolean = false
@@ -41,6 +42,7 @@
export let isSub: boolean = true
export let isAside: boolean = true
export let isUtils: boolean = true
export let isPresence: boolean = true
export let isCustomAttr: boolean = true
export let floatAside: boolean = false
export let allowClose: boolean = true
@@ -146,6 +148,8 @@
dispatch('select', result.detail)
}}
>
<Component is={presence.component.Presence} props={{ object }} />
<svelte:fragment slot="title">
{#if !withoutTitle}
{#if $$slots.title}
@@ -156,6 +160,12 @@
{/if}
</svelte:fragment>
<svelte:fragment slot="presence">
{#if isPresence}
<Component is={presence.component.PresenceAvatars} props={{ object, size: 'x-small', limit: 5 }} />
{/if}
</svelte:fragment>
<svelte:fragment slot="pre-utils">
<slot name="pre-utils" />
</svelte:fragment>
+13
View File
@@ -35,6 +35,7 @@
export let hideSearch: boolean = false
export let hideActions: boolean = false
export let hideExtra: boolean = false
export let hidePresence: boolean = false
export let overflowExtra: boolean = false
export let noPrint: boolean = false
export let freezeBefore: boolean = false
@@ -149,6 +150,11 @@
</div>
<!-- <div class="hulyHeader-row__divider" /> -->
<div class="hulyHeader-row no-print" class:between={$$slots.search} class:reverse={!$$slots.search}>
{#if $$slots.presence && !hidePresence}
<div class="hulyHeader-buttonsGroup presence no-print">
<slot name="presence" {doubleRow} />
</div>
{/if}
{#if $$slots.search}
<div class="hulyHeader-buttonsGroup search">
<slot name="search" {doubleRow} />
@@ -214,12 +220,19 @@
/>
{/if}
{#if $$slots.presence && !hidePresence}
<div class="hulyHeader-buttonsGroup presence no-print">
<slot name="presence" {doubleRow} />
</div>
{/if}
{#if $$slots.search && !hideSearch}
<div class="hulyHeader-buttonsGroup search no-print">
<slot name="search" {doubleRow} />
</div>
{#if $$slots.actions && !hideActions}<div class="hulyHeader-divider no-print" />{/if}
{/if}
{#if $$slots.extra && !hideExtra}
<div
class="hulyHeader-buttonsGroup extra"
+1
View File
@@ -202,6 +202,7 @@
</svelte:fragment>
<svelte:fragment slot="actions">
<slot name="actions" />
<slot name="presence" />
<slot name="pre-utils" />
<slot name="utils" />
{#if useMaxWidth !== undefined}
+1
View File
@@ -62,6 +62,7 @@
"@hcengineering/view-resources": "^0.6.0",
"@hcengineering/workbench": "^0.6.16",
"@hcengineering/workbench-resources": "^0.6.1",
"@hcengineering/presence-resources": "^0.6.0",
"fast-equals": "^5.0.1",
"svelte": "^4.2.19",
"@hcengineering/text-editor-resources": "^0.6.0",
@@ -32,6 +32,7 @@
export let canOpen: boolean = false
export let withAside: boolean = false
export let withSearch: boolean = true
export let withPresence: boolean = true
export let isAsideShown: boolean = false
export let filters: Ref<ActivityMessagesFilter>[] = []
export let canOpenInSidebar: boolean = false
@@ -80,6 +81,7 @@
{withAside}
{isAsideShown}
{withSearch}
{withPresence}
{canOpenInSidebar}
{closeOnEscape}
bind:realWidth
@@ -20,6 +20,7 @@
import { Widget } from '@hcengineering/workbench'
import { ActivityMessage } from '@hcengineering/activity'
import { ChatWidgetTab } from '@hcengineering/chunter'
import { Presence } from '@hcengineering/presence-resources'
import { updateTabData } from '@hcengineering/workbench-resources'
import Channel from './Channel.svelte'
@@ -76,6 +77,7 @@
</script>
{#if object && renderChannel && visible}
<Presence {object} />
<div class="channel" class:invisible={threadId !== undefined} style:height style:width>
<ChannelHeader
_id={object._id}
@@ -14,14 +14,15 @@
-->
<script lang="ts">
import { onMount } from 'svelte'
import chunter, { TypingInfo } from '@hcengineering/chunter'
import chunter from '@hcengineering/chunter'
import { getName, Person } from '@hcengineering/contact'
import { personByIdStore } from '@hcengineering/contact-resources'
import { getCurrentAccount, IdMap } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
import { PresenceTyping } from '../types'
export let typingInfo: TypingInfo[] = []
export let typingInfo: PresenceTyping[] = []
const typingDelay = 2000
const maxTypingPersons = 3
@@ -34,7 +35,7 @@
$: updateTypingPersons($personByIdStore, typingInfo)
function updateTypingPersons (personById: IdMap<Person>, typingInfo: TypingInfo[]) {
function updateTypingPersons (personById: IdMap<Person>, typingInfo: PresenceTyping[]): void {
const now = Date.now()
const personIds = new Set(
typingInfo
@@ -30,6 +30,7 @@
import { Channel, ObjectChatPanel } from '@hcengineering/chunter'
import view from '@hcengineering/view'
import { messageInFocus } from '@hcengineering/activity-resources'
import { Presence } from '@hcengineering/presence-resources'
import ChannelComponent from './Channel.svelte'
import ChannelHeader from './ChannelHeader.svelte'
@@ -103,6 +104,8 @@
}
</script>
<Presence {object} />
<div class="popupPanel">
<ChannelHeader
_id={object._id}
@@ -19,7 +19,6 @@
import ChannelSidebarView from './ChannelSidebarView.svelte'
import chunter from '../plugin'
import ThreadSidebarView from './threads/ThreadSidebarView.svelte'
export let widget: Widget | undefined
export let tab: ChatWidgetTab | undefined
@@ -33,6 +33,7 @@
import { Doc, Ref } from '@hcengineering/core'
import { ActivityMessagesFilter } from '@hcengineering/activity'
import workbench from '@hcengineering/workbench'
import { PresenceAvatars } from '@hcengineering/presence-resources'
import { userSearch } from '../index'
import chunter from '../plugin'
@@ -53,6 +54,7 @@
export let titleKind: 'default' | 'breadcrumbs' = 'default'
export let withFilters: boolean = false
export let withSearch: boolean = true
export let withPresence: boolean = true
export let filters: Ref<ActivityMessagesFilter>[] = []
export let adaptive: HeaderAdaptive = 'default'
export let hideActions: boolean = false
@@ -118,6 +120,12 @@
{/if}
</svelte:fragment>
<svelte:fragment slot="presence">
{#if withPresence && object}
<PresenceAvatars {object} size="x-small" limit={3} />
{/if}
</svelte:fragment>
<svelte:fragment slot="search" let:doubleRow>
{#if withSearch}
<SearchInput
@@ -16,7 +16,7 @@
import activity, { ActivityMessage } from '@hcengineering/activity'
import { Analytics } from '@hcengineering/analytics'
import { AttachmentRefInput } from '@hcengineering/attachment-resources'
import chunter, { ChatMessage, ChunterEvents, ThreadMessage, TypingInfo } from '@hcengineering/chunter'
import chunter, { ChatMessage, ChunterEvents, ThreadMessage } from '@hcengineering/chunter'
import { Class, Doc, generateId, Ref, type CommitResult, getCurrentAccount } from '@hcengineering/core'
import { createQuery, DraftController, draftsStore, getClient } from '@hcengineering/presentation'
import { EmptyMarkup, isEmptyMarkup } from '@hcengineering/text'
@@ -25,7 +25,9 @@
import { ThrottledCaller } from '@hcengineering/ui'
import { getSpace } from '@hcengineering/activity-resources'
import { PersonAccount } from '@hcengineering/contact'
import { presenceByObjectId, updateMyPresence } from '@hcengineering/presence-resources'
import { type PresenceTyping } from '../../types'
import { getChannelSpace } from '../../utils'
import ChannelTypingInfo from '../ChannelTypingInfo.svelte'
@@ -75,15 +77,9 @@
createdMessageQuery.unsubscribe()
}
const typingInfoQuery = createQuery()
let typingInfo: TypingInfo[] = []
$: if (withTypingInfo) {
typingInfoQuery.query(chunter.class.TypingInfo, { objectId: object._id, space: getSpace(object) }, (res) => {
typingInfo = res
})
} else {
typingInfoQuery.unsubscribe()
}
let typingInfo: PresenceTyping[] = []
$: presence = $presenceByObjectId.get(object._id) ?? []
$: typingInfo = presence.map((p) => p.presence.typing).filter((p) => p !== undefined)
function clear (): void {
currentMessage = getDefault()
@@ -111,29 +107,18 @@
async function deleteTypingInfo (): Promise<void> {
if (!withTypingInfo) return
const myTypingInfo = typingInfo.find((info) => info.person === me.person)
if (myTypingInfo === undefined) return
await client.remove(myTypingInfo)
const room = { objectId: object._id, objectClass: object._class }
updateMyPresence(room, { typing: undefined })
}
async function updateTypingInfo (): Promise<void> {
if (!withTypingInfo) return
const myTypingInfo = typingInfo.find((info) => info.person === me.person)
if (myTypingInfo === undefined) {
await client.createDoc(chunter.class.TypingInfo, getSpace(object), {
objectId: object._id,
objectClass: object._class,
person: me.person,
lastTyping: Date.now()
})
} else {
throttle.call(() => {
void client.update(myTypingInfo, {
lastTyping: Date.now()
})
})
}
throttle.call(() => {
const room = { objectId: object._id, objectClass: object._class }
const typing = { person: me.person, lastTyping: Date.now() }
updateMyPresence(room, { typing })
})
}
function onUpdate (event: CustomEvent): void {
+22
View File
@@ -0,0 +1,22 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// 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 type { Person } from '@hcengineering/contact'
import type { Ref, Timestamp } from '@hcengineering/core'
export interface PresenceTyping {
person: Ref<Person>
lastTyping: Timestamp
}
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname }
}
+22
View File
@@ -0,0 +1,22 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true,
"proseWrap": "preserve",
"plugins": [
"prettier-plugin-svelte"
],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "ui"
}
@@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)']
}
+54
View File
@@ -0,0 +1,54 @@
{
"name": "@hcengineering/presence-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Hardcore Engineering Inc.",
"license": "EPL-2.0",
"scripts": {
"build": "compile ui",
"build:docs": "api-extractor run --local",
"format": "format src",
"svelte-check": "do-svelte-check",
"_phase:svelte-check": "do-svelte-check",
"build:watch": "compile ui",
"_phase:build": "compile ui",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"svelte-loader": "^3.2.0",
"sass": "^1.53.0",
"svelte-preprocess": "^5.1.3",
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-svelte": "^2.35.1",
"prettier-plugin-svelte": "^3.2.2",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"svelte-check": "^3.6.9",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"svelte-eslint-parser": "^0.33.1"
},
"dependencies": {
"@hcengineering/platform": "^0.6.11",
"@hcengineering/core": "^0.6.32",
"@hcengineering/presentation": "^0.6.3",
"@hcengineering/theme": "^0.6.5",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/view": "^0.6.13",
"@hcengineering/view-resources": "^0.6.0",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/contact-resources": "^0.6.0",
"@hcengineering/presence": "^0.6.0",
"svelte": "^4.2.19",
"fast-equals": "^5.0.1"
}
}
@@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}
+174
View File
@@ -0,0 +1,174 @@
//
// Copyright © 2024 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 { type Ref, concatLink, getCurrentAccount } from '@hcengineering/core'
import { type Person, type PersonAccount } from '@hcengineering/contact'
import { getMetadata } from '@hcengineering/platform'
import presence from '@hcengineering/presence'
import presentation from '@hcengineering/presentation'
import { type Unsubscriber, get } from 'svelte/store'
import { myPresence, onPersonUpdate, onPersonLeave } from './store'
import { type RoomPresence } from './types'
interface Message {
id: Ref<Person>
type: 'update' | 'remove'
presence?: RoomPresence[]
lastUpdate?: number
}
export class PresenceClient implements Disposable {
private ws: WebSocket | null = null
private closed = false
private reconnectTimeout: number | undefined
private readonly reconnectInterval = 1000
private presence: RoomPresence[]
private readonly myPresenceUnsub: Unsubscriber
constructor (private readonly url: string | URL) {
this.presence = get(myPresence)
this.myPresenceUnsub = myPresence.subscribe((presence) => {
this.handlePresenceChanged(presence)
})
this.connect()
}
close (): void {
this.closed = true
clearTimeout(this.reconnectTimeout)
this.myPresenceUnsub()
if (this.ws !== null) {
this.ws.close()
this.ws = null
}
}
private connect (): void {
try {
const ws = new WebSocket(this.url)
this.ws = ws
ws.onopen = () => {
if (this.ws !== ws) {
return
}
this.handleConnect()
}
ws.onclose = (event: CloseEvent) => {
if (this.ws !== ws) {
ws.close()
return
}
this.reconnect()
}
ws.onmessage = (event: MessageEvent) => {
if (this.closed || this.ws !== ws) {
return
}
this.handleMessage(event.data)
}
ws.onerror = (event: Event) => {
if (this.ws !== ws) {
return
}
console.log('client websocket error', event)
}
} catch (err: any) {
this.reconnect()
}
}
private reconnect (): void {
clearTimeout(this.reconnectTimeout)
if (!this.closed) {
this.reconnectTimeout = window.setTimeout(() => {
this.connect()
}, this.reconnectInterval)
}
}
private handleConnect (): void {
const me = getCurrentAccount() as PersonAccount
this.sendPresence(me.person, this.presence)
}
private handleMessage (data: string): void {
try {
const message = JSON.parse(data) as Message
if (message.type === 'update' && message.presence !== undefined) {
onPersonUpdate(message.id, message.presence ?? [])
} else if (message.type === 'remove') {
onPersonLeave(message.id)
} else {
console.warn('Unknown message type', message)
}
} catch (err: any) {
console.error('Error parsing message', err, data)
}
}
private handlePresenceChanged (presence: RoomPresence[]): void {
const me = getCurrentAccount() as PersonAccount
this.presence = presence
this.sendPresence(me.person, this.presence)
}
private sendPresence (person: Ref<Person>, presence: RoomPresence[]): void {
if (!this.closed && this.ws !== null && this.ws.readyState === WebSocket.OPEN) {
const message: Message = { id: person, type: 'update', presence }
this.ws.send(JSON.stringify(message))
}
}
[Symbol.dispose] (): void {
this.close()
}
}
export function connect (): PresenceClient | undefined {
const workspaceId = getMetadata(presentation.metadata.WorkspaceId)
if (workspaceId === undefined) {
console.warn('Workspace ID is not defined')
return undefined
}
const token = getMetadata(presentation.metadata.Token)
const presenceUrl = getMetadata(presence.metadata.PresenceUrl)
if (presenceUrl === undefined || presenceUrl === '') {
console.warn('Presence URL is not defined')
return undefined
}
const url = new URL(concatLink(presenceUrl, workspaceId))
if (token !== undefined) {
url.searchParams.set('token', token)
}
return new PresenceClient(url)
}
@@ -0,0 +1,26 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
-->
<script lang="ts">
import { Doc } from '@hcengineering/core'
import PresenceContext from './PresenceContext.svelte'
export let object: Doc
</script>
{#key object._id}
<PresenceContext {object} />
{/key}
@@ -0,0 +1,50 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
-->
<script lang="ts">
import { type Doc } from '@hcengineering/core'
import contact, { formatName } from '@hcengineering/contact'
import { Avatar, CombineAvatars, personByIdStore } from '@hcengineering/contact-resources'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { IconSize, tooltip } from '@hcengineering/ui'
import { presenceByObjectId } from '../store'
export let object: Doc
export let size: IconSize = 'small'
export let limit: number = 5
export let hideLimit: boolean = false
export let combine = false
$: presence = $presenceByObjectId?.get(object._id) ?? []
$: items = presence.map((it) => it.person)
</script>
{#if items.length > 0}
{#if combine}
<CombineAvatars _class={contact.mixin.Employee} {items} {size} {limit} {hideLimit} />
{:else}
<div class="flex-row-center flex-gap-0-5">
{#each items as item}
{@const person = $personByIdStore.get(item)}
{#if person}
<div use:tooltip={{ label: getEmbeddedLabel(formatName(person.name)) }}>
<Avatar name={person.name} {size} {person} />
</div>
{/if}
{/each}
</div>
{/if}
{/if}
@@ -0,0 +1,33 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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.
-->
<script lang="ts">
import { Doc } from '@hcengineering/core'
import { onDestroy, onMount } from 'svelte'
import { updateMyPresence, removeMyPresence } from '../store'
export let object: Doc
onMount(() => {
updateMyPresence({ objectId: object._id, objectClass: object._class }, {})
})
onDestroy(() => {
removeMyPresence({ objectId: object._id, objectClass: object._class })
})
</script>
<slot />
@@ -0,0 +1,18 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte'
import { type PresenceClient, connect } from '../client'
let parentElement: HTMLDivElement
let client: PresenceClient | undefined
onMount(() => {
client = connect()
})
onDestroy(() => {
client?.close()
})
</script>
<div bind:this={parentElement} class="hidden"></div>
+33
View File
@@ -0,0 +1,33 @@
//
// Copyright © 2024 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 { type Resources } from '@hcengineering/platform'
import Presence from './components/Presence.svelte'
import PresenceAvatars from './components/PresenceAvatars.svelte'
import WorkbenchExtension from './components/WorkbenchExtension.svelte'
export { Presence, PresenceAvatars }
export { updateMyPresence, removeMyPresence, presenceByObjectId } from './store'
export * from './types'
export default async (): Promise<Resources> => ({
component: {
Presence,
PresenceAvatars,
WorkbenchExtension
}
})
+19
View File
@@ -0,0 +1,19 @@
//
// Copyright © 2024 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 { mergeIds } from '@hcengineering/platform'
import presence, { presenceId } from '@hcengineering/presence'
export default mergeIds(presenceId, presence, {})
+81
View File
@@ -0,0 +1,81 @@
//
// Copyright © 2024 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 { type Doc, type Ref, getCurrentAccount } from '@hcengineering/core'
import { type Person, type PersonAccount } from '@hcengineering/contact'
import { type PresenceData } from '@hcengineering/presence'
import { type Readable, derived, writable } from 'svelte/store'
import { type PersonRoomPresence, type Room, type RoomPresence } from './types'
type PersonPresenceMap = Map<Ref<Person>, RoomPresence[]>
export const myPresence = writable<RoomPresence[]>([])
export const otherPresence = writable<PersonPresenceMap>(new Map())
export const presenceByObjectId = derived<Readable<PersonPresenceMap>, Map<Ref<Doc>, PersonRoomPresence[]>>(
otherPresence,
($presence) => {
const me = getCurrentAccount() as PersonAccount
const map = new Map<Ref<Doc>, PersonRoomPresence[]>()
for (const [person, presences] of $presence.entries()) {
if (person === me.person) continue
presences.forEach((presence) => {
const values = map.get(presence.room.objectId) ?? []
values.push({ person, ...presence })
map.set(presence.room.objectId, values)
})
}
return map
}
)
export function updateMyPresence (room: Room, presence: PresenceData): void {
myPresence.update((rooms) => {
const value = { room, presence, lastUpdated: Date.now() }
const index = rooms.findIndex((it) => it.room.objectId === room.objectId)
if (index >= 0) {
rooms[index] = value
} else {
rooms.push(value)
}
return rooms
})
}
export function removeMyPresence (room: Room): void {
myPresence.update((old) => {
return old.filter((it) => it.room.objectId !== room.objectId)
})
}
export function onPersonUpdate (person: Ref<Person>, presence: RoomPresence[]): void {
otherPresence.update((map) => {
map.set(person, [...presence])
return map
})
}
export function onPersonLeave (person: Ref<Person>): void {
otherPresence.update((map) => {
map.delete(person)
return map
})
}
+42
View File
@@ -0,0 +1,42 @@
//
// Copyright © 2024 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 { type Class, type Doc, type Ref } from '@hcengineering/core'
import { type Person } from '@hcengineering/contact'
import { type PresenceData } from '@hcengineering/presence'
export interface Room {
objectId: Ref<Doc>
objectClass: Ref<Class<Doc>>
}
export interface RoomPresence {
room: Room
presence: PresenceData
lastUpdated: number
}
export interface PersonRoomPresence {
person: Ref<Person>
room: Room
presence: PresenceData
lastUpdated: number
}
export interface PersonPresence {
person: Ref<Person>
presence: RoomPresence[]
lastUpdated: number
}
@@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types"
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+48
View File
@@ -0,0 +1,48 @@
{
"name": "@hcengineering/presence",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"files": [
"lib/**/*",
"types/**/*",
"tsconfig.json"
],
"author": "Hardcore Engineering Inc.",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/platform": "^0.6.11",
"@hcengineering/core": "^0.6.32",
"@hcengineering/ui": "^0.6.15"
},
"repository": "https://github.com/hcengineering/platform",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}
+21
View File
@@ -0,0 +1,21 @@
//
// Copyright © 2024 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 { presenceId, presencePlugin } from './plugin'
export * from './types'
export { presenceId }
export default presencePlugin
+33
View File
@@ -0,0 +1,33 @@
//
// Copyright © 2024 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 type { Metadata, Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { AnyComponent } from '@hcengineering/ui'
/** @public */
export const presenceId = 'presence' as Plugin
export const presencePlugin = plugin(presenceId, {
metadata: {
PresenceUrl: '' as Metadata<string>
},
component: {
Presence: '' as AnyComponent,
PresenceAvatars: '' as AnyComponent
}
})
export default presencePlugin
+16
View File
@@ -0,0 +1,16 @@
//
// Copyright © 2024 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.
//
export type PresenceData = Record<string, any>
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
@@ -71,7 +71,6 @@
} from '@hcengineering/text-editor'
import { addTableHandler } from '../utils'
import CollaborationUsers from './CollaborationUsers.svelte'
import TextEditorToolbar from './TextEditorToolbar.svelte'
import { noSelectionRender, renderCursor } from './editor/collaboration'
import { defaultEditorAttributes } from './editor/editorProps'
@@ -584,11 +583,11 @@
<div class="textInput">
<div class="select-text" class:hidden={loading} style="width: 100%;" bind:this={element} />
<div class="collaborationUsers-container flex-col flex-gap-2 pt-2">
<!-- <div class="collaborationUsers-container flex-col flex-gap-2 pt-2">
{#if remoteProvider && editor && userComponent}
<CollaborationUsers provider={remoteProvider} {editor} component={userComponent} />
{/if}
</div>
</div> -->
</div>
{#if refActions.length > 0}
+1
View File
@@ -37,6 +37,7 @@ startFront(metricsContext, {
LOVE_ENDPOINT: process.env.LOVE_ENDPOINT ?? '',
SIGN_URL: process.env.SIGN_URL ?? '',
PRINT_URL: process.env.PRINT_URL ?? '',
PRESENCE_URL: process.env.PRESENCE_URL ?? '',
PUSH_PUBLIC_KEY: process.env.PUSH_PUBLIC_KEY ?? '',
POSTHOG_API_KEY: process.env.POSTHOG_API_KEY,
POSTHOG_HOST: process.env.POSTHOG_HOST,
+15
View File
@@ -510,6 +510,21 @@
"projectFolder": "models/uploader",
"shouldPublish": false
},
{
"packageName": "@hcengineering/presence",
"projectFolder": "plugins/presence",
"shouldPublish": false
},
{
"packageName": "@hcengineering/presence-resources",
"projectFolder": "plugins/presence-resources",
"shouldPublish": false
},
{
"packageName": "@hcengineering/model-presence",
"projectFolder": "models/presence",
"shouldPublish": false
},
{
"packageName": "@hcengineering/collaborator-client",
"projectFolder": "packages/collaborator-client",