Bold chunter spaces (#1282)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-04-06 11:35:52 +06:00 committed by GitHub
parent 07062c37b9
commit 3a19d46590
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 106 additions and 13 deletions

View File

@ -37,6 +37,7 @@
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-view": "~0.6.0",
"@anticrm/model-workbench": "~0.6.1",
"@anticrm/model-notification": "~0.6.0",
"@anticrm/activity": "~0.6.0",
"@anticrm/workbench": "~0.6.1"
}

View File

@ -24,13 +24,17 @@ import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import chunter from './plugin'
import contact, { Employee } from '@anticrm/contact'
import notification from '@anticrm/model-notification'
export const DOMAIN_CHUNTER = 'chunter' as Domain
export const DOMAIN_COMMENT = 'comment' as Domain
@Model(chunter.class.Channel, core.class.Space)
@UX(chunter.string.Channel, chunter.icon.Hashtag)
export class TChannel extends TSpace implements Channel {}
export class TChannel extends TSpace implements Channel {
@Prop(TypeTimestamp(), chunter.string.LastMessage)
lastMessage?: Timestamp
}
@Model(chunter.class.Message, core.class.Doc, DOMAIN_CHUNTER)
export class TMessage extends TDoc implements Message {
@ -84,6 +88,10 @@ export function createModel (builder: Builder): void {
presenter: chunter.component.ChannelPresenter
})
builder.mixin(chunter.class.Channel, core.class.Class, notification.mixin.SpaceLastEdit, {
lastEditField: 'lastMessage'
})
builder.createDoc(view.class.ViewletDescriptor, core.space.Model, {
label: chunter.string.Chat,
icon: view.icon.Table,

View File

@ -47,7 +47,8 @@ export default mergeIds(chunterId, chunter, {
Chat: '' as IntlString,
CreateBy: '' as IntlString,
Create: '' as IntlString,
MarkUnread: '' as IntlString
MarkUnread: '' as IntlString,
LastMessage: '' as IntlString
},
viewlet: {
Chat: '' as Ref<ViewletDescriptor>

View File

@ -15,9 +15,9 @@
//
import { Account, Doc, Domain, DOMAIN_MODEL, Ref, Timestamp, TxCUD } from '@anticrm/core'
import { ArrOf, Builder, Model, Prop, TypeRef, TypeString, TypeTimestamp } from '@anticrm/model'
import core, { TAttachedDoc, TDoc } from '@anticrm/model-core'
import type { EmailNotification, LastView, NotificationType, NotificationProvider, NotificationSetting, Notification, NotificationStatus } from '@anticrm/notification'
import { ArrOf, Builder, Mixin, Model, Prop, TypeRef, TypeString, TypeTimestamp } from '@anticrm/model'
import core, { TAttachedDoc, TClass, TDoc } from '@anticrm/model-core'
import type { EmailNotification, LastView, NotificationType, NotificationProvider, NotificationSetting, Notification, NotificationStatus, SpaceLastEdit } from '@anticrm/notification'
import type { IntlString } from '@anticrm/platform'
import notification from './plugin'
import setting from '@anticrm/setting'
@ -81,8 +81,13 @@ export class TNotificationSetting extends TDoc implements NotificationSetting {
enabled!: boolean
}
@Mixin(notification.mixin.SpaceLastEdit, core.class.Class)
export class TSpaceLastEdit extends TClass implements SpaceLastEdit {
lastEditField!: string
}
export function createModel (builder: Builder): void {
builder.createModel(TLastView, TNotification, TEmaiNotification, TNotificationType, TNotificationProvider, TNotificationSetting)
builder.createModel(TLastView, TNotification, TEmaiNotification, TNotificationType, TNotificationProvider, TNotificationSetting, TSpaceLastEdit)
builder.createDoc(notification.class.NotificationType, core.space.Model, {
label: notification.string.MentionNotification
@ -108,3 +113,4 @@ export function createModel (builder: Builder): void {
}
export { notificationOperation } from './migration'
export { notification as default }

View File

@ -35,6 +35,6 @@ export function createModel (builder: Builder): void {
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverChunter.trigger.CommentCreate
trigger: serverChunter.trigger.ChunterTrigger
})
}

View File

@ -86,6 +86,10 @@
font-weight: 500;
color: var(--accent-color);
}
&.bold {
font-weight: 600;
color: var(--theme-caption-color);
}
}
.an-element__icon-arrow {
margin-left: .5rem;

View File

@ -22,7 +22,9 @@ import { AnyComponent } from '@anticrm/ui'
/**
* @public
*/
export interface Channel extends Space {}
export interface Channel extends Space {
lastMessage?: Timestamp
}
/**
* @public

View File

@ -14,7 +14,7 @@
// limitations under the License.
//
import type { Account, AttachedDoc, Class, Doc, Ref, Space, Timestamp, TxCUD } from '@anticrm/core'
import type { Account, AttachedDoc, Class, Doc, Mixin, Ref, Space, Timestamp, TxCUD } from '@anticrm/core'
import type { Asset, IntlString, Plugin, Resource } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
@ -79,6 +79,13 @@ export interface NotificationSetting extends Doc {
enabled: boolean
}
/**
* @public
*/
export interface SpaceLastEdit extends Class<Doc> {
lastEditField: string
}
/**
* @public
*/
@ -89,6 +96,7 @@ export const notificationId = 'notification' as Plugin
*/
export interface NotificationClient {
updateLastView: (_id: Ref<Doc>, _class: Ref<Class<Doc>>, time?: Timestamp, force?: boolean) => Promise<void>
unsubscribe: (_id: Ref<Doc>) => Promise<void>
}
/**
@ -100,6 +108,9 @@ export type NotificationClientFactoy = () => NotificationClient
* @public
*/
const notification = plugin(notificationId, {
mixin: {
SpaceLastEdit: '' as Ref<Mixin<SpaceLastEdit>>
},
class: {
LastView: '' as Ref<Class<LastView>>,
Notification: '' as Ref<Class<Notification>>,

View File

@ -15,6 +15,8 @@
<script lang="ts">
import type { Class, Doc, Ref, Space } from '@anticrm/core'
import core from '@anticrm/core'
import notification from '@anticrm/notification'
import { NotificationClientImpl } from '@anticrm/notification-resources'
import { getResource } from '@anticrm/platform'
import { getClient } from '@anticrm/presentation'
import { Action, AnyComponent, IconAdd, IconEdit, showPanel, showPopup } from '@anticrm/ui'
@ -80,6 +82,23 @@
}
return result
}
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
const hierarchy = client.getHierarchy()
$: clazz = hierarchy.getClass(model.spaceClass)
$: lastEditMixin = hierarchy.as(clazz, notification.mixin.SpaceLastEdit)
function isChanged (space: Space, lastViews: Map<Ref<Doc>, number>): boolean {
const field = lastEditMixin?.lastEditField
const lastView = lastViews.get(space._id)
if (lastView === undefined || lastView === -1) return false
if (field === undefined) return false
const value = (space as any)[field]
if (isNaN(value)) return false
return lastView < value
}
</script>
<TreeNode label={model.label} parent actions={async () => [addSpace]} indent={'ml-2'}>
@ -107,6 +126,7 @@
icon={classIcon(client, space._class)}
selected={currentSpace === space._id}
actions={() => getActions(space)}
bold={isChanged(space, $lastViews)}
on:click={() => {
selectSpace(space._id)
}}

View File

@ -28,6 +28,7 @@
export let parent = false
export let collapsed = false
export let selected = false
export let bold = false
export let actions: () => Promise<Action[]> = async () => []
export let indent: 'default' | 'ml-2' | 'ml-4' | 'ml-8' = 'default'
@ -57,7 +58,7 @@
dispatch('click')
}}
>
<span class="an-element__label" class:title={node}>
<span class="an-element__label" class:bold class:title={node}>
<div class="flex-row-center">
{#if icon && !parent}
<div class="an-element__icon">

View File

@ -26,10 +26,11 @@
export let notifications = 0
export let actions: () => Promise<Action[]> = async () => []
export let selected: boolean = false
export let bold = false
export let indent: 'default' | 'ml-2' | 'ml-4' | 'ml-8' = 'default'
const dispatch = createEventDispatcher()
</script>
<TreeElement {_id} {icon} {title} {notifications} {selected} {actions} collapsed {indent} on:click={() => { dispatch('click') }}/>
<TreeElement {_id} {icon} {title} {notifications} {selected} {actions} {bold} collapsed {indent} on:click={() => { dispatch('click') }}/>

View File

@ -80,10 +80,48 @@ export async function CommentCreate (tx: Tx, control: TriggerControl): Promise<T
return result
}
/**
* @public
*/
export async function MessageCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const hierarchy = control.hierarchy
if (tx._class !== core.class.TxCreateDoc) return []
const doc = TxProcessor.createDoc2Doc(tx as TxCreateDoc<Doc>)
if (!hierarchy.isDerived(doc._class, chunter.class.Message)) {
return []
}
const message = doc as Message
const channel = (await control.findAll(chunter.class.Channel, {
_id: message.space
}, { limit: 1 }))[0]
if (channel.lastMessage === undefined || channel.lastMessage < message.createOn) {
const res = control.txFactory.createTxUpdateDoc<Channel>(channel._class, channel.space, channel._id, {
lastMessage: message.createOn
})
return [res]
}
return []
}
/**
* @public
*/
export async function ChunterTrigger (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const promises = [
MessageCreate(tx, control),
CommentCreate(tx, control)
]
const res = await Promise.all(promises)
return res.flat()
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
trigger: {
CommentCreate
ChunterTrigger
},
function: {
CommentRemove,

View File

@ -28,7 +28,7 @@ export const serverChunterId = 'server-chunter' as Plugin
*/
export default plugin(serverChunterId, {
trigger: {
CommentCreate: '' as Resource<TriggerFunc>
ChunterTrigger: '' as Resource<TriggerFunc>
},
function: {
CommentRemove: '' as Resource<(doc: Doc, hiearachy: Hierarchy, findAll: <T extends Doc> (clazz: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>) => Promise<FindResult<T>>) => Promise<Doc[]>>,