Notification presenter (#1055)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov
2022-02-25 16:04:57 +07:00
committed by GitHub
parent 7fc26783bc
commit af15115c8c
32 changed files with 212 additions and 76 deletions
+2 -1
View File
@@ -38,6 +38,7 @@
"@anticrm/core": "~0.6.11",
"@anticrm/chunter": "~0.6.0",
"@anticrm/presentation": "~0.6.2",
"@anticrm/activity": "~0.6.0"
"@anticrm/activity": "~0.6.0",
"@anticrm/notification": "~0.6.0"
}
}
+4 -4
View File
@@ -16,9 +16,9 @@
<script lang="ts">
import activity from '@anticrm/activity'
import type { Doc } from '@anticrm/core'
import notification from '@anticrm/notification'
import type { Asset } from '@anticrm/platform'
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
import { Icon, IconClose, IconExpand, IconMoreH, Component, ActionIcon, Scroller } from '@anticrm/ui'
import { ActionIcon,AnyComponent,AnySvelteComponent,Component,Icon,IconClose,IconExpand,IconMoreH,Scroller } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
export let title: string
@@ -43,7 +43,7 @@
{#if subtitle }<span class="ac-header__description">{subtitle}</span>{/if}
</div>
</div>
<!-- <ActionIcon icon={IconMoreH} size={'medium'} /> -->
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
</div>
{#if $$slots.subtitle}
<div class="ac-subtitle">
@@ -68,7 +68,7 @@
{#if subtitle }<span class="ac-header__description">{subtitle}</span>{/if}
</div>
</div>
<ActionIcon icon={IconMoreH} size={'medium'} />
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
</div>
{#if $$slots.subtitle}
<div class="ac-subtitle">
+1 -1
View File
@@ -51,7 +51,7 @@
btn.focus()
if (!opened) {
opened = true
showPopup(DropdownPopup, { title: label, caption: 'suggested', items }, container, (result) => {
showPopup(DropdownPopup, { title: label, items }, container, (result) => {
if (result) selected = result
opened = false
})
@@ -60,7 +60,7 @@
config={descr.config}
options={descr.options}
query={ resultQuery }
enableChecking
showNotification
/>
{/if}
{/await}
@@ -66,7 +66,7 @@
config={descr.config}
options={descr.options}
query={resultQuery}
enableChecking
showNotification
/>
{/if}
{/await}
+2 -1
View File
@@ -45,6 +45,7 @@
"@anticrm/view-resources": "~0.6.0",
"@anticrm/attachment-resources": "~0.6.0",
"@anticrm/contact-resources": "~0.6.0",
"@anticrm/chunter-resources": "~0.6.0"
"@anticrm/chunter-resources": "~0.6.0",
"@anticrm/notification": "~0.6.0"
}
}
@@ -52,7 +52,7 @@
config={descr.config}
options={descr.options}
query={ resultQuery }
enableChecking
showNotification
/>
{/if}
{/await}
@@ -19,10 +19,11 @@
import { ContactPresenter } from '@anticrm/contact-resources'
import type { WithLookup } from '@anticrm/core'
import type { Lead } from '@anticrm/lead'
import { ActionIcon, IconMoreH, showPanel, showPopup } from '@anticrm/ui'
import { ActionIcon, Component, IconMoreH, showPanel, showPopup } from '@anticrm/ui'
import view from '@anticrm/view'
import { ContextMenu } from '@anticrm/view-resources'
import lead from '../plugin'
import notification from '@anticrm/notification'
export let object: WithLookup<Lead>
export let draggable: boolean
@@ -37,16 +38,27 @@
</script>
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
<div class="content">
<div class="flex-between mb-4">
<div class="flex-col">
<div class="fs-title cursor-pointer" on:click={showLead}>{object.title}</div>
</div>
<div class="flex-row-center">
<div class="flex-col ml-2">
<div class="fs-title cursor-pointer" on:click={showLead}>{object.title}</div>
<div class="mr-2">
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
</div>
<ActionIcon
label={lead.string.More}
action={(evt) => {
showMenu(evt)
}}
icon={IconMoreH}
size={'small'}
/>
</div>
</div>
<div class="flex-between">
{#if object.$lookup?.attachedTo}
<ContactPresenter value={object.$lookup?.attachedTo} />
<ContactPresenter value={object.$lookup.attachedTo} />
{/if}
<div class="flex-row-center">
{#if (object.attachments ?? 0) > 0}
@@ -55,16 +67,6 @@
{#if (object.comments ?? 0) > 0}
<div class="step-lr75"><CommentsPresenter value={object} /></div>
{/if}
<div class="step-lr75">
<ActionIcon
label={lead.string.More}
action={(evt) => {
showMenu(evt)
}}
icon={IconMoreH}
size={'small'}
/>
</div>
</div>
</div>
</div>
@@ -79,11 +81,6 @@
user-select: none;
backdrop-filter: blur(10px);
.content {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
}
&.draggable {
cursor: grab;
}
@@ -2,4 +2,12 @@
<symbol id="notification" viewBox="0 0 24 24">
<path d="M9.78261 18.7959C9.78261 20.0132 10.7754 21 12 21C13.2246 21 14.2174 20.0132 14.2174 18.7959M19.3905 14.7551C18.7778 14.7551 18.2826 14.2631 18.2826 13.6524V9.24462C18.2826 5.79553 15.4703 3 12 3C8.52965 3 5.71739 5.79481 5.71739 9.24462V13.6524C5.71739 14.2609 5.2205 14.7551 4.60947 14.7551C3.99732 14.7551 3.5 15.2496 3.5 15.8571C3.5 16.4658 3.99753 16.9592 4.61235 16.9592H19.3876C20.0015 16.9592 20.5 16.4646 20.5 15.8571C20.5 15.2497 20.0014 14.7551 19.3905 14.7551Z"/>
</symbol>
<symbol id="track" viewBox="0 0 24 24">
<path d="M9.78261 18.7959C9.78261 20.0132 10.7754 21 12 21C13.2246 21 14.2174 20.0132 14.2174 18.7959M19.3905 14.7551C18.7778 14.7551 18.2826 14.2631 18.2826 13.6524V9.24462C18.2826 5.79553 15.4703 3 12 3C8.52965 3 5.71739 5.79481 5.71739 9.24462V13.6524C5.71739 14.2609 5.2205 14.7551 4.60947 14.7551C3.99732 14.7551 3.5 15.2496 3.5 15.8571C3.5 16.4658 3.99753 16.9592 4.61235 16.9592H19.3876C20.0015 16.9592 20.5 16.4646 20.5 15.8571C20.5 15.2497 20.0014 14.7551 19.3905 14.7551Z" fill="none"
stroke="currentColor"/>
</symbol>
<symbol id="donttrack" viewBox="0 0 24 24">
<path stroke="currentColor" fill="none" d="m9.78261,18.7959c0,1.2173 0.99279,2.2041 2.21739,2.2041c1.2246,0 2.2174,-0.9868 2.2174,-2.2041m5.1731,-4.0408c-0.6127,0 -1.1079,-0.492 -1.1079,-1.1027l0,-4.40778c0,-3.44909 -2.8123,-6.24462 -6.2826,-6.24462c-3.47035,0 -6.28261,2.79481 -6.28261,6.24462l0,4.40778c0,0.6085 -0.49689,1.1027 -1.10792,1.1027c-0.61215,0 -1.10947,0.4945 -1.10947,1.102c0,0.6087 0.49753,1.1021 1.11235,1.1021l14.77525,0c0.6139,0 1.1124,-0.4946 1.1124,-1.1021c0,-0.6074 -0.4986,-1.102 -1.1095,-1.102z" id="svg_1"/>
<line id="svg_2" y2="2.59025" x2="21.49529" y1="21.40975" x1="2.6758" stroke="currentColor" fill="none"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 1.9 KiB

+3 -1
View File
@@ -6,6 +6,8 @@
"NoNotifications": "No notifications",
"MentionNotification": "Mentioned",
"EmailNotification": "by email",
"PlatformNotification": "in platform"
"PlatformNotification": "in platform",
"Track": "Track",
"DontTrack": "Don't track"
}
}
+3 -1
View File
@@ -6,6 +6,8 @@
"NoNotifications": "Нет уведомлений",
"MentionNotification": "Упомянул",
"EmailNotification": "по email",
"PlatformNotification": "в системе"
"PlatformNotification": "в системе",
"Track": "Отслеживать",
"DontTrack": "Не отслеживать"
}
}
+3 -1
View File
@@ -18,7 +18,9 @@ import notification, { notificationId } from '@anticrm/notification'
const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(notification.icon, {
Notifications: `${icons}#notification`
Notifications: `${icons}#notification`,
Track: `${icons}#track`,
DontTrack: `${icons}#donttrack`
})
addStringsLoader(notificationId, async (lang: string) => await import(`../lang/${lang}.json`))
@@ -0,0 +1,33 @@
<!--
// Copyright © 2022 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.
-->
<script lang="ts">
import { Doc } from '@anticrm/core'
import { ActionIcon } from '@anticrm/ui'
import notification from '../plugin'
import { NotificationClient } from '../utils'
export let value: Doc
const notificationClient = NotificationClient.getClient()
const lastViews = notificationClient.getLastViews()
$: subscribed = $lastViews.has(value._id)
</script>
{#if !subscribed}
<ActionIcon size='medium' label={notification.string.Track} icon={notification.icon.Track} action={() => { notificationClient.updateLastView(value._id, value._class, undefined, true) }} />
{:else}
<ActionIcon size='medium' label={notification.string.DontTrack} icon={notification.icon.DontTrack} action={() => { notificationClient.unsubscribe(value._id) }} />
{/if}
@@ -0,0 +1,39 @@
<!--
// Copyright © 2022 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.
-->
<script lang="ts">
import { Doc } from '@anticrm/core'
import { getPlatformColor } from '@anticrm/ui'
import { NotificationClient } from '../utils'
export let value: Doc
const notificationClient = NotificationClient.getClient()
const lastViews = notificationClient.getLastViews()
$: lastView = $lastViews.get(value._id)
$: hasNotification = lastView !== undefined && lastView < value.modifiedOn
</script>
{#if hasNotification}
<div class="color" style="background-color: {getPlatformColor(11)}" />
{/if}
<style lang="scss">
.color {
width: .5rem;
height: .5rem;
border-radius: .5rem;
}
</style>
+5 -1
View File
@@ -17,12 +17,16 @@
import { Resources } from '@anticrm/platform'
import NotificationsPopup from './components/NotificationsPopup.svelte'
import NotificationSettings from './components/NotificationSettings.svelte'
import NotificationPresenter from './components/NotificationPresenter.svelte'
import LastViewEditor from './components/LastViewEditor.svelte'
export * from './utils'
export default async (): Promise<Resources> => ({
component: {
NotificationsPopup,
NotificationSettings
NotificationPresenter,
NotificationSettings,
LastViewEditor
}
})
+4 -2
View File
@@ -14,13 +14,15 @@
// limitations under the License.
*/
import type { IntlString } from '@anticrm/platform'
import type { Asset, IntlString } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import notification, { notificationId } from '@anticrm/notification'
export default mergeIds(notificationId, notification, {
string: {
NoNotifications: '' as IntlString
NoNotifications: '' as IntlString,
Track: '' as IntlString,
DontTrack: '' as IntlString
}
})
+10 -2
View File
@@ -24,7 +24,7 @@ import { writable, Writable } from 'svelte/store'
*/
export class NotificationClient {
protected static _instance: NotificationClient | undefined = undefined
private readonly lastViews = new Map<Ref<Doc>, LastView>()
private lastViews = new Map<Ref<Doc>, LastView>()
private readonly lastViewsStore = writable(new Map<Ref<Doc>, Timestamp>())
private readonly lastViewQuery = createQuery()
@@ -32,10 +32,12 @@ export class NotificationClient {
private constructor () {
this.lastViewQuery.query(notification.class.LastView, { user: getCurrentAccount()._id }, (result) => {
const res: Map<Ref<Doc>, Timestamp> = new Map<Ref<Doc>, Timestamp>()
const lastViews: Map<Ref<Doc>, LastView> = new Map<Ref<Doc>, LastView>()
result.forEach((p) => {
res.set(p.attachedTo, p.lastView)
this.lastViews.set(p.attachedTo, p)
lastViews.set(p.attachedTo, p)
})
this.lastViews = lastViews
this.lastViewsStore.set(res)
})
}
@@ -59,6 +61,9 @@ export class NotificationClient {
): Promise<void> {
const client = getClient()
const user = getCurrentAccount()._id
console.log('SUBSCRIBE')
console.log('USER')
console.log(user)
const lastView = time ?? new Date().getTime()
const current = this.lastViews.get(_id)
if (current !== undefined) {
@@ -85,6 +90,9 @@ export class NotificationClient {
async unsubscribe (_id: Ref<Doc>): Promise<void> {
const client = getClient()
const user = getCurrentAccount()._id
console.log('UNSUBSCRIBE')
console.log('USER')
console.log(user)
const current = await client.findOne(notification.class.LastView, { attachedTo: _id, user })
if (current !== undefined) {
await client.removeDoc(current._class, current.space, current._id)
+6 -2
View File
@@ -102,10 +102,14 @@ const notification = plugin(notificationId, {
NotificationSettings: '' as Ref<Doc>
},
component: {
NotificationsPopup: '' as AnyComponent
NotificationsPopup: '' as AnyComponent,
NotificationPresenter: '' as AnyComponent,
LastViewEditor: '' as AnyComponent
},
icon: {
Notifications: '' as Asset
Notifications: '' as Asset,
Track: '' as Asset,
DontTrack: '' as Asset
},
space: {
Notifications: '' as Ref<Space>
+1
View File
@@ -54,6 +54,7 @@
"@anticrm/task-resources": "~0.6.0",
"@anticrm/contact-resources": "~0.6.0",
"@anticrm/rekoni": "~0.6.0",
"@anticrm/notification": "~0.6.0",
"@anticrm/tags": "~0.6.0"
}
}
@@ -82,7 +82,7 @@
config={descr.config}
options={descr.options}
query={ resultQuery }
enableChecking
showNotification
/>
{/if}
{/await}
@@ -19,9 +19,10 @@
import type { WithLookup } from '@anticrm/core'
import { Avatar } from '@anticrm/presentation'
import type { Applicant } from '@anticrm/recruit'
import { ActionIcon, IconMoreH, showPanel } from '@anticrm/ui'
import { ActionIcon, Component, IconMoreH, showPanel } from '@anticrm/ui'
import view from '@anticrm/view'
import ApplicationPresenter from './ApplicationPresenter.svelte'
import notification from '@anticrm/notification'
export let object: WithLookup<Applicant>
export let draggable: boolean
@@ -33,14 +34,21 @@
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
<div class="flex-between mb-3">
<Avatar avatar={object.$lookup?.attachedTo?.avatar} size={'medium'} />
<div class="flex-grow flex-col min-w-0 ml-2">
<div class="fs-title over-underline lines-limit-2" on:click={showCandidate}>
{formatName(object.$lookup?.attachedTo?.name)}
<div class="flex-row-center">
<Avatar avatar={object.$lookup?.attachedTo?.avatar} size={'medium'} />
<div class="flex-grow flex-col min-w-0 ml-2">
<div class="fs-title over-underline lines-limit-2" on:click={showCandidate}>
{formatName(object.$lookup?.attachedTo?.name)}
</div>
<div class="text-sm lines-limit-2">{object.$lookup?.attachedTo?.title ?? ''}</div>
</div>
<div class="text-sm lines-limit-2">{object.$lookup?.attachedTo?.title ?? ''}</div>
</div>
<div class="tool"><ActionIcon label={undefined} icon={IconMoreH} size={'small'} /></div>
<div class="tool mr-1 flex-row-center">
<div class="mr-2">
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
</div>
<ActionIcon label={undefined} icon={IconMoreH} size={'small'} />
</div>
</div>
<div class="flex-between">
<div class="flex-row-center">
@@ -101,6 +101,6 @@
]}
options={opt}
query={resultQuery}
enableChecking
showNotification
/>
</Scroller>
+2 -1
View File
@@ -46,6 +46,7 @@
"@anticrm/login": "~0.6.1",
"@anticrm/attachment-resources": "~0.6.0",
"@anticrm/chunter-resources": "~0.6.0",
"@anticrm/workbench": "~0.6.1"
"@anticrm/workbench": "~0.6.1",
"@anticrm/notification": "~0.6.0"
}
}
@@ -18,10 +18,11 @@
import type { WithLookup } from '@anticrm/core'
import { Avatar } from '@anticrm/presentation'
import type { Issue } from '@anticrm/task'
import { ActionIcon, IconMoreH, showPopup } from '@anticrm/ui'
import { ActionIcon, Component, IconMoreH, showPopup } from '@anticrm/ui'
import { ContextMenu } from '@anticrm/view-resources'
import task from '../plugin'
import TaskPresenter from './TaskPresenter.svelte'
import notification from '@anticrm/notification'
export let object: WithLookup<Issue>
export let draggable: boolean
@@ -34,7 +35,12 @@
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
<div class="flex-between mb-2">
<TaskPresenter value={object} />
<ActionIcon label={task.string.More} action={(evt) => { showMenu(evt) }} icon={IconMoreH} size={'small'} />
<div class="flex-row-center">
<div class="mr-2">
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
</div>
<ActionIcon label={task.string.More} action={(evt) => { showMenu(evt) }} icon={IconMoreH} size={'small'} />
</div>
</div>
<div class="caption-color mb-3 lines-limit-4">{object.name}</div>
<!-- <div class="text-sm lines-limit-2">{object.description}</div> -->
@@ -47,9 +53,7 @@
<div class="step-lr75"><CommentsPresenter value={object} /></div>
{/if}
</div>
{#if object.$lookup?.assignee}
<Avatar avatar={object.$lookup?.assignee?.avatar} size={'x-small'} />
{/if}
<Avatar avatar={object.$lookup?.assignee?.avatar} size={'x-small'} />
</div>
</div>
@@ -146,7 +146,7 @@
</div>
<div class="statustableview-container">
<ScrollBox vertical stretch noShift>
<Table {_class} {query} config={resConfig} {options} enableChecking />
<Table {_class} {query} config={resConfig} {options} showNotification />
</ScrollBox>
</div>
@@ -31,5 +31,5 @@
</script>
<div class="sm-tool-icon" on:click={show}>
<span class="icon"><Icon icon={task.icon.Task} size={'small'} /></span>{#if shortLabel}<Label label={shortLabel} />-{/if}-{value.number}
<span class="icon"><Icon icon={task.icon.Task} size={'small'} /></span>{#if shortLabel}<Label label={shortLabel} />-{/if}{value.number}
</div>
+1
View File
@@ -38,6 +38,7 @@
"@anticrm/view": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/notification": "~0.6.0",
"@anticrm/presentation": "~0.6.2"
}
}
@@ -17,15 +17,17 @@
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@anticrm/core'
import { SortingOrder } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import { CheckBox, IconDown, IconUp, Label, Loading, showPopup, Spinner } from '@anticrm/ui'
import { Component, CheckBox, IconDown, IconUp, Label, Loading, showPopup, Spinner } from '@anticrm/ui'
import { BuildModelKey } from '@anticrm/view'
import { buildModel, LoadingProps } from '../utils'
import MoreV from './icons/MoreV.svelte'
import Menu from './Menu.svelte'
import notification from '@anticrm/notification'
export let _class: Ref<Class<Doc>>
export let query: DocumentQuery<Doc>
export let enableChecking: boolean = false
export let showNotification: boolean = false
export let options: FindOptions<Doc> | undefined = undefined
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
export let config: (BuildModelKey | string)[]
@@ -123,23 +125,26 @@
<Loading />
{/if}
{:then model}
<table class="table-body" class:enableChecking>
<table class="table-body" class:enableChecking class:showNotification>
<thead class="scroller-thead">
<tr class="tr-head scroller-thead__tr">
{#each model as attribute, cellHead}
{#if enableChecking && !cellHead}
<th>
<div class="checkCell" class:checkall={checked.size > 0}>
<CheckBox
symbol={'minus'}
checked={objects?.length === checked.size && objects?.length > 0}
on:change={(e) => {
objects.map((o) => check(o._id, e))
}}
/>
</div>
</th>
{/if}
{#if enableChecking}
<th>
<div class="checkCell" class:checkall={checked.size > 0}>
<CheckBox
symbol={'minus'}
checked={objects?.length === checked.size && objects?.length > 0}
on:change={(e) => {
objects.map((o) => check(o._id, e))
}}
/>
</div>
</th>
{/if}
{#if showNotification}
<th></th>
{/if}
{#each model as attribute}
<th
class:sortable={attribute.sortingKey}
class:sorted={attribute.sortingKey === sortKey}
@@ -179,6 +184,11 @@
</div>
</td>
{/if}
{#if showNotification}
<td class="notificationCell">
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
</td>
{/if}
<td>
<div class="firstCell">
<svelte:component
@@ -237,6 +247,12 @@
width: 100%;
}
.notificationCell {
padding: 0.5rem;
padding-left: 1rem !important;
padding-right: 0rem;
}
.firstCell {
display: flex;
// justify-content: space-between;
@@ -268,7 +284,7 @@
}
}
.enableChecking {
.enableChecking, .showNotification {
th,
td {
&:first-child {
@@ -30,5 +30,5 @@
</script>
<Scroller>
<Table {_class} {config} {options} query={resultQuery} {baseMenuClass} enableChecking />
<Table {_class} {config} {options} query={resultQuery} {baseMenuClass} showNotification/>
</Scroller>
+1
View File
@@ -41,6 +41,7 @@
"@anticrm/login": "~0.6.1",
"@anticrm/setting": "~0.6.0",
"@anticrm/notification": "~0.6.0",
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/contact": "~0.6.2",
"@anticrm/view-resources": "~0.6.0"
}
@@ -64,7 +64,6 @@
_class={spaceSample._class}
config={['name', 'company', 'location', 'modifiedOn']}
options={{}}
enableChecking={true}
baseMenuClass={core.class.Space}
query={{
_class: { $in: model?.spaces.map(x => x.spaceClass) ?? [] },
@@ -42,10 +42,12 @@
import Navigator from './Navigator.svelte'
import SpaceView from './SpaceView.svelte'
import notification, { NotificationStatus } from '@anticrm/notification'
import { NotificationClient } from '@anticrm/notification-resources'
export let client: Client
setClient(client)
NotificationClient.getClient()
let currentApp: Ref<Application> | undefined
let currentSpace: Ref<Space> | undefined