mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-11 12:17:44 +02:00
Merge remote-tracking branch 'origin/develop' into staging
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/attachment": "^0.6.14",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.20",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
|
||||
+178
-10
@@ -14,7 +14,14 @@
|
||||
//
|
||||
|
||||
import contact, { type Employee, type Person } from '@hcengineering/contact'
|
||||
import { AccountRole, type Domain, DOMAIN_TRANSIENT, IndexKind, type Ref } from '@hcengineering/core'
|
||||
import {
|
||||
AccountRole,
|
||||
type Domain,
|
||||
DOMAIN_TRANSIENT,
|
||||
IndexKind,
|
||||
type Ref,
|
||||
type CollaborativeDoc
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
type DevicesPreference,
|
||||
type Floor,
|
||||
@@ -34,18 +41,21 @@ import {
|
||||
} from '@hcengineering/love'
|
||||
import {
|
||||
type Builder,
|
||||
Collection,
|
||||
Collection as PropCollection,
|
||||
Hidden,
|
||||
Index,
|
||||
Mixin,
|
||||
Model,
|
||||
Prop,
|
||||
ReadOnly,
|
||||
TypeCollaborativeDoc,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
UX
|
||||
} from '@hcengineering/model'
|
||||
import calendar, { TEvent } from '@hcengineering/model-calendar'
|
||||
import core, { TDoc } from '@hcengineering/model-core'
|
||||
import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core'
|
||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||
import presentation from '@hcengineering/model-presentation'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
@@ -55,22 +65,32 @@ import setting from '@hcengineering/setting'
|
||||
import workbench, { WidgetType } from '@hcengineering/workbench'
|
||||
import activity from '@hcengineering/activity'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import attachment from '@hcengineering/attachment'
|
||||
|
||||
import love from './plugin'
|
||||
|
||||
export { loveId } from '@hcengineering/love'
|
||||
export * from './migration'
|
||||
export const DOMAIN_LOVE = 'love' as Domain
|
||||
export const DOMAIN_MEETING_MINUTES = 'meeting-minutes' as Domain
|
||||
|
||||
@Model(love.class.Room, core.class.Doc, DOMAIN_LOVE)
|
||||
@UX(love.string.Room, love.icon.Love)
|
||||
export class TRoom extends TDoc implements Room {
|
||||
name!: string
|
||||
@Prop(TypeString(), core.string.Name)
|
||||
@Index(IndexKind.FullText)
|
||||
name!: string
|
||||
|
||||
@Prop(TypeCollaborativeDoc(), core.string.Description)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: CollaborativeDoc
|
||||
|
||||
type!: RoomType
|
||||
|
||||
access!: RoomAccess
|
||||
|
||||
@Prop(TypeRef(love.class.Floor), love.string.Floor)
|
||||
@ReadOnly()
|
||||
// @Index(IndexKind.Indexed)
|
||||
floor!: Ref<Floor>
|
||||
|
||||
@@ -81,12 +101,20 @@ export class TRoom extends TDoc implements Room {
|
||||
|
||||
language!: RoomLanguage
|
||||
startWithTranscription!: boolean
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: number
|
||||
|
||||
@Prop(PropCollection(love.class.MeetingMinutes), love.string.MeetingMinutes)
|
||||
meetings?: number
|
||||
}
|
||||
|
||||
@Model(love.class.Office, love.class.Room)
|
||||
@UX(love.string.Office, love.icon.Love)
|
||||
export class TOffice extends TRoom implements Office {
|
||||
@Prop(TypeRef(contact.mixin.Employee), contact.string.Employee)
|
||||
@Index(IndexKind.Indexed)
|
||||
@ReadOnly()
|
||||
person!: Ref<Employee> | null
|
||||
}
|
||||
|
||||
@@ -155,22 +183,27 @@ export class TMeeting extends TEvent implements Meeting {
|
||||
room!: Ref<Room>
|
||||
}
|
||||
|
||||
@Model(love.class.MeetingMinutes, core.class.Doc, DOMAIN_LOVE)
|
||||
@UX(love.string.Meeting)
|
||||
export class TMeetingMinutes extends TDoc implements MeetingMinutes {
|
||||
@Model(love.class.MeetingMinutes, core.class.Doc, DOMAIN_MEETING_MINUTES)
|
||||
@UX(love.string.MeetingMinutes, love.icon.Cam)
|
||||
export class TMeetingMinutes extends TAttachedDoc implements MeetingMinutes {
|
||||
@Hidden()
|
||||
sid!: string
|
||||
|
||||
@Prop(TypeString(), view.string.Title)
|
||||
@Index(IndexKind.FullText)
|
||||
title!: string
|
||||
|
||||
@Prop(TypeRef(love.class.Room), love.string.Room)
|
||||
room!: Ref<Room>
|
||||
@Prop(TypeCollaborativeDoc(), core.string.Description)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: CollaborativeDoc
|
||||
|
||||
@Prop(PropCollection(activity.class.ActivityMessage), love.string.Transcription)
|
||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||
attachments?: number
|
||||
|
||||
@Prop(PropCollection(chunter.class.ChatMessage), love.string.Transcription)
|
||||
transcription?: number
|
||||
|
||||
@Prop(PropCollection(activity.class.ActivityMessage), activity.string.Messages)
|
||||
@Prop(PropCollection(chunter.class.ChatMessage), activity.string.Messages)
|
||||
messages?: number
|
||||
}
|
||||
|
||||
@@ -383,15 +416,150 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
|
||||
ofClass: love.class.Room,
|
||||
components: { input: chunter.component.ChatMessageInput }
|
||||
})
|
||||
|
||||
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
|
||||
ofClass: love.class.Office,
|
||||
components: { input: chunter.component.ChatMessageInput }
|
||||
})
|
||||
|
||||
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
|
||||
ofClass: love.class.MeetingMinutes,
|
||||
components: { input: chunter.component.ChatMessageInput }
|
||||
})
|
||||
|
||||
builder.mixin(love.class.MeetingMinutes, core.class.Class, activity.mixin.ActivityDoc, {})
|
||||
|
||||
builder.mixin(love.class.Room, core.class.Class, activity.mixin.ActivityDoc, {})
|
||||
|
||||
builder.mixin(love.class.MeetingMinutes, core.class.Class, view.mixin.ObjectPresenter, {
|
||||
presenter: love.component.MeetingMinutesPresenter
|
||||
})
|
||||
|
||||
builder.mixin(love.class.MeetingMinutes, core.class.Class, view.mixin.CollectionEditor, {
|
||||
editor: love.component.MeetingMinutesSection
|
||||
})
|
||||
|
||||
builder.mixin(love.class.MeetingMinutes, core.class.Class, view.mixin.ObjectTitle, {
|
||||
titleProvider: love.function.MeetingMinutesTitleProvider
|
||||
})
|
||||
|
||||
builder.mixin(love.class.Room, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: love.component.EditRoom
|
||||
})
|
||||
|
||||
builder.mixin(love.class.MeetingMinutes, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: love.component.EditMeetingMinutes
|
||||
})
|
||||
|
||||
builder.mixin(love.class.Floor, core.class.Class, view.mixin.AttributeEditor, {
|
||||
inlineEditor: love.component.FloorAttributePresenter
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: love.class.MeetingMinutes,
|
||||
descriptor: view.viewlet.Table,
|
||||
config: [
|
||||
'',
|
||||
{ key: 'messages', displayProps: { key: 'messages', suffix: true } },
|
||||
{ key: 'transcription', displayProps: { key: 'transcription', suffix: true } },
|
||||
'modifiedOn',
|
||||
'modifiedBy'
|
||||
],
|
||||
configOptions: {
|
||||
hiddenKeys: ['description'],
|
||||
sortable: true
|
||||
},
|
||||
options: {}
|
||||
},
|
||||
love.viewlet.TableMeetingMinutes
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
label: love.string.Floor,
|
||||
icon: love.icon.Love,
|
||||
component: love.component.FloorView
|
||||
},
|
||||
love.viewlet.FloorDescriptor
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
label: love.string.MeetingMinutes,
|
||||
icon: view.icon.Table,
|
||||
component: love.component.MeetingMinutesTable
|
||||
},
|
||||
love.viewlet.MeetingMinutesDescriptor
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: love.class.Floor,
|
||||
descriptor: love.viewlet.FloorDescriptor,
|
||||
config: []
|
||||
},
|
||||
love.viewlet.Floor
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: love.class.Floor,
|
||||
descriptor: love.viewlet.MeetingMinutesDescriptor,
|
||||
config: []
|
||||
},
|
||||
love.viewlet.FloorMeetingMinutes
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: chunter.string.Chat,
|
||||
generated: false,
|
||||
hidden: false,
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: chunter.class.ChatMessage,
|
||||
attachedToClass: love.class.MeetingMinutes,
|
||||
txMatch: {
|
||||
'attributes.collection': 'messages'
|
||||
},
|
||||
defaultEnabled: false,
|
||||
group: love.ids.LoveNotificationGroup
|
||||
},
|
||||
love.ids.MeetingMinutesChatNotification
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [love.ids.MeetingMinutesChatNotification]
|
||||
})
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.PushNotificationProvider,
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [love.ids.MeetingMinutesChatNotification]
|
||||
})
|
||||
|
||||
builder.mixin(love.class.MeetingMinutes, core.class.Class, notification.mixin.ClassCollaborators, {
|
||||
fields: ['createdBy']
|
||||
})
|
||||
|
||||
builder.mixin(love.class.Room, core.class.Class, core.mixin.IndexConfiguration, {
|
||||
indexes: [],
|
||||
searchDisabled: true
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
//
|
||||
|
||||
import contact from '@hcengineering/contact'
|
||||
import { type Space, TxOperations, type Ref } from '@hcengineering/core'
|
||||
import { type Space, TxOperations, type Ref, makeCollaborativeDoc } from '@hcengineering/core'
|
||||
import drive from '@hcengineering/drive'
|
||||
import { RoomAccess, RoomType, createDefaultRooms, isOffice, loveId, type Floor } from '@hcengineering/love'
|
||||
import { RoomAccess, RoomType, createDefaultRooms, isOffice, loveId, type Floor, type Room } from '@hcengineering/love'
|
||||
import {
|
||||
createDefaultSpace,
|
||||
migrateSpace,
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from '@hcengineering/model'
|
||||
import core from '@hcengineering/model-core'
|
||||
import love from './plugin'
|
||||
import { DOMAIN_LOVE } from '.'
|
||||
import { DOMAIN_LOVE, DOMAIN_MEETING_MINUTES } from '.'
|
||||
|
||||
async function createDefaultFloor (tx: TxOperations): Promise<void> {
|
||||
const current = await tx.findOne(love.class.Floor, {
|
||||
@@ -56,7 +56,7 @@ async function createRooms (client: MigrationUpgradeClient): Promise<void> {
|
||||
const data = createDefaultRooms(employees.map((p) => p._id))
|
||||
for (const room of data) {
|
||||
const _class = isOffice(room) ? love.class.Office : love.class.Room
|
||||
await tx.createDoc(_class, core.space.Workspace, room)
|
||||
await tx.createDoc(_class, core.space.Workspace, room, room._id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,8 @@ async function createReception (client: MigrationUpgradeClient): Promise<void> {
|
||||
x: 0,
|
||||
y: 0,
|
||||
language: 'en',
|
||||
startWithTranscription: false
|
||||
startWithTranscription: false,
|
||||
description: makeCollaborativeDoc(love.ids.Reception, 'description')
|
||||
},
|
||||
love.ids.Reception
|
||||
)
|
||||
@@ -109,15 +110,38 @@ export const loveOperation: MigrateOperation = {
|
||||
)
|
||||
await client.update(
|
||||
DOMAIN_LOVE,
|
||||
{ _class: love.class.Room, startWithTranscription: { $exists: false } },
|
||||
{ _class: love.class.Room, type: RoomType.Video, startWithTranscription: { $exists: false } },
|
||||
{ startWithTranscription: true }
|
||||
)
|
||||
await client.update(
|
||||
DOMAIN_LOVE,
|
||||
{ _class: love.class.Room, startWithTranscription: { $exists: false } },
|
||||
{ startWithTranscription: false }
|
||||
)
|
||||
await client.update(
|
||||
DOMAIN_LOVE,
|
||||
{ _class: love.class.Office, startWithTranscription: { $exists: false } },
|
||||
{ startWithTranscription: false }
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'move-meeting-minutes',
|
||||
func: async (client) => {
|
||||
await client.move(DOMAIN_LOVE, { _class: love.class.MeetingMinutes }, DOMAIN_MEETING_MINUTES)
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'create-description-collaborative',
|
||||
func: async (client) => {
|
||||
const rooms = await client.find<Room>(DOMAIN_LOVE, { _class: { $in: [love.class.Room, love.class.Office] } })
|
||||
for (const room of rooms) {
|
||||
const description = room.description
|
||||
if (description == null) {
|
||||
await client.update(DOMAIN_LOVE, room, { description: makeCollaborativeDoc(room._id, 'description') })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type NotificationGroup } from '@hcengineering/notification'
|
||||
import { mergeIds } from '@hcengineering/platform'
|
||||
import { type Client, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type NotificationType, type NotificationGroup } from '@hcengineering/notification'
|
||||
import { type Resource, mergeIds } from '@hcengineering/platform'
|
||||
import { type AnyComponent } from '@hcengineering/ui'
|
||||
import { type ActionCategory, type ViewAction } from '@hcengineering/view'
|
||||
import { loveId } from '@hcengineering/love'
|
||||
@@ -43,6 +43,10 @@ export default mergeIds(loveId, love, {
|
||||
},
|
||||
ids: {
|
||||
Settings: '' as Ref<Doc>,
|
||||
LoveNotificationGroup: '' as Ref<NotificationGroup>
|
||||
LoveNotificationGroup: '' as Ref<NotificationGroup>,
|
||||
MeetingMinutesChatNotification: '' as Ref<NotificationType>
|
||||
},
|
||||
function: {
|
||||
MeetingMinutesTitleProvider: '' as Resource<(client: Client, ref: Ref<Doc>, doc?: Doc) => Promise<string>>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -44,13 +44,11 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverActivity.trigger.ActivityMessagesHandler,
|
||||
arrays: true,
|
||||
isAsync: true
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverActivity.trigger.OnDocRemoved,
|
||||
arrays: true
|
||||
trigger: serverActivity.trigger.OnDocRemoved
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
|
||||
@@ -37,7 +37,6 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverAiBot.trigger.OnMessageSend,
|
||||
arrays: true,
|
||||
isAsync: true
|
||||
})
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ export { serverCollaborationId } from '@hcengineering/server-collaboration'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCollaboration.trigger.OnDelete,
|
||||
arrays: true
|
||||
trigger: serverCollaboration.trigger.OnDelete
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import serverFulltext from '@hcengineering/server-fulltext'
|
||||
export { serverFulltextId } from '@hcengineering/server-fulltext'
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverFulltext.trigger.OnChange,
|
||||
arrays: true
|
||||
trigger: serverFulltext.trigger.OnChange
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,14 +27,15 @@
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/love": "^0.6.0",
|
||||
"@hcengineering/model": "^0.6.11",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-love": "^0.6.0",
|
||||
"@hcengineering/love": "^0.6.0",
|
||||
"@hcengineering/server-love": "^0.6.0"
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-love": "^0.6.0",
|
||||
"@hcengineering/server-notification": "^0.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,19 @@ import { type Builder } from '@hcengineering/model'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
import love from '@hcengineering/love'
|
||||
import serverLove from '@hcengineering/server-love'
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
|
||||
export { serverLoveId } from '@hcengineering/server-love'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.mixin(love.class.MeetingMinutes, core.class.Class, serverNotification.mixin.HTMLPresenter, {
|
||||
presenter: serverLove.function.MeetingMinutesHTMLPresenter
|
||||
})
|
||||
|
||||
builder.mixin(love.class.MeetingMinutes, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||
presenter: serverLove.function.MeetingMinutesTextPresenter
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverLove.trigger.OnEmployee,
|
||||
txMatch: {
|
||||
|
||||
@@ -89,8 +89,7 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverNotification.trigger.OnDocRemove,
|
||||
arrays: true
|
||||
trigger: serverNotification.trigger.OnDocRemove
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
|
||||
@@ -25,8 +25,7 @@ export { serverRequestId } from '@hcengineering/server-request'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverRequest.trigger.OnRequest,
|
||||
arrays: true
|
||||
trigger: serverRequest.trigger.OnRequest
|
||||
})
|
||||
|
||||
builder.mixin(request.class.Request, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||
|
||||
@@ -23,8 +23,7 @@ export { serverTagsId } from '@hcengineering/server-tags'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverTags.trigger.onTagReference,
|
||||
arrays: true
|
||||
trigger: serverTags.trigger.onTagReference
|
||||
})
|
||||
|
||||
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
||||
|
||||
@@ -22,7 +22,6 @@ export { serverTaskId } from '@hcengineering/server-task'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverTask.trigger.OnStateUpdate,
|
||||
arrays: true
|
||||
trigger: serverTask.trigger.OnStateUpdate
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverTime.trigger.OnTask,
|
||||
arrays: true,
|
||||
isAsync: true
|
||||
})
|
||||
|
||||
|
||||
+25
-29
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { BackupClient, DocChunk } from './backup'
|
||||
import { Account, AttachedDoc, Class, DOMAIN_MODEL, Doc, Domain, Ref, Timestamp } from './classes'
|
||||
import core from './component'
|
||||
@@ -249,12 +250,10 @@ export async function createClient (
|
||||
}
|
||||
lastTx = tx.reduce((cur, it) => (it.modifiedOn > cur ? it.modifiedOn : cur), 0)
|
||||
}
|
||||
const conn = await ctx.with('connect', {}, async () => await connect(txHandler))
|
||||
const conn = await ctx.with('connect', {}, () => connect(txHandler))
|
||||
|
||||
await ctx.with(
|
||||
'load-model',
|
||||
{ reload: false },
|
||||
async (ctx) => await loadModel(ctx, conn, modelFilter, hierarchy, model, false, txPersistence)
|
||||
await ctx.with('load-model', { reload: false }, (ctx) =>
|
||||
loadModel(ctx, conn, modelFilter, hierarchy, model, false, txPersistence)
|
||||
)
|
||||
|
||||
txBuffer = txBuffer.filter((tx) => tx.space !== core.space.Model)
|
||||
@@ -273,10 +272,8 @@ export async function createClient (
|
||||
return
|
||||
}
|
||||
// Find all new transactions and apply
|
||||
const loadModelResponse = await ctx.with(
|
||||
'connect',
|
||||
{ reload: true },
|
||||
async (ctx) => await loadModel(ctx, conn, modelFilter, hierarchy, model, true, txPersistence)
|
||||
const loadModelResponse = await ctx.with('connect', { reload: true }, (ctx) =>
|
||||
loadModel(ctx, conn, modelFilter, hierarchy, model, true, txPersistence)
|
||||
)
|
||||
|
||||
if (event === ClientConnectEvent.Reconnected && loadModelResponse.full) {
|
||||
@@ -284,9 +281,7 @@ export async function createClient (
|
||||
hierarchy = new Hierarchy()
|
||||
model = new ModelDb(hierarchy)
|
||||
|
||||
await ctx.with('build-model', {}, async (ctx) => {
|
||||
await buildModel(ctx, loadModelResponse, modelFilter, hierarchy, model)
|
||||
})
|
||||
await ctx.with('build-model', {}, (ctx) => buildModel(ctx, loadModelResponse, modelFilter, hierarchy, model))
|
||||
await oldOnConnect?.(ClientConnectEvent.Upgraded)
|
||||
|
||||
// No need to fetch more stuff since upgrade was happened.
|
||||
@@ -300,15 +295,12 @@ export async function createClient (
|
||||
}
|
||||
|
||||
// We need to look for last {transactionThreshold} transactions and if it is more since lastTx one we receive, we need to perform full refresh.
|
||||
const atxes = await ctx.with(
|
||||
'find-atx',
|
||||
{},
|
||||
async () =>
|
||||
await conn.findAll(
|
||||
core.class.Tx,
|
||||
{ modifiedOn: { $gt: lastTx }, objectSpace: { $ne: core.space.Model } },
|
||||
{ sort: { modifiedOn: SortingOrder.Ascending, _id: SortingOrder.Ascending }, limit: transactionThreshold }
|
||||
)
|
||||
const atxes = await ctx.with('find-atx', {}, () =>
|
||||
conn.findAll(
|
||||
core.class.Tx,
|
||||
{ modifiedOn: { $gt: lastTx }, objectSpace: { $ne: core.space.Model } },
|
||||
{ sort: { modifiedOn: SortingOrder.Ascending, _id: SortingOrder.Ascending }, limit: transactionThreshold }
|
||||
)
|
||||
)
|
||||
|
||||
let needFullRefresh = false
|
||||
@@ -364,12 +356,16 @@ async function tryLoadModel (
|
||||
}
|
||||
|
||||
// Save concatenated
|
||||
void (await ctx.with('persistence-store', {}, (ctx) =>
|
||||
persistence?.store({
|
||||
...result,
|
||||
transactions: !result.full ? current.transactions.concat(result.transactions) : result.transactions
|
||||
void ctx
|
||||
.with('persistence-store', {}, (ctx) =>
|
||||
persistence?.store({
|
||||
...result,
|
||||
transactions: !result.full ? current.transactions.concat(result.transactions) : result.transactions
|
||||
})
|
||||
)
|
||||
.catch((err) => {
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
))
|
||||
|
||||
if (!result.full && !reload) {
|
||||
result.transactions = current.transactions.concat(result.transactions)
|
||||
@@ -432,7 +428,7 @@ async function buildModel (
|
||||
|
||||
const atxes = modelResponse.transactions
|
||||
|
||||
await ctx.with('split txes', {}, async () => {
|
||||
ctx.withSync('split txes', {}, () => {
|
||||
atxes.forEach((tx) =>
|
||||
((tx.modifiedBy === core.account.ConfigUser || tx.modifiedBy === core.account.System) && !isPersonAccount(tx)
|
||||
? systemTx
|
||||
@@ -448,7 +444,7 @@ async function buildModel (
|
||||
txes = await modelFilter(txes)
|
||||
}
|
||||
|
||||
await ctx.with('build hierarchy', {}, async () => {
|
||||
ctx.withSync('build hierarchy', {}, () => {
|
||||
for (const tx of txes) {
|
||||
try {
|
||||
hierarchy.tx(tx)
|
||||
@@ -461,7 +457,7 @@ async function buildModel (
|
||||
}
|
||||
}
|
||||
})
|
||||
await ctx.with('build model', {}, async (ctx) => {
|
||||
ctx.withSync('build model', {}, (ctx) => {
|
||||
model.addTxes(ctx, txes, false)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ const consoleLogger = (logParams: Record<string, any>): MeasureLogger => ({
|
||||
|
||||
const noParamsLogger = consoleLogger({})
|
||||
|
||||
const nullPromise = Promise.resolve()
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -148,6 +150,9 @@ export class MeasureMetricsContext implements MeasureContext {
|
||||
c.end()
|
||||
})
|
||||
} else {
|
||||
if (value == null) {
|
||||
return nullPromise as Promise<T>
|
||||
}
|
||||
return Promise.resolve(value)
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -104,8 +104,8 @@ export function updateMeasure (
|
||||
param.value += value ?? ed - st
|
||||
param.operations++
|
||||
}
|
||||
|
||||
param.topResult = getUpdatedTopResult(param.topResult, ed - st, fParams)
|
||||
// Do not update top results for params.
|
||||
// param.topResult = getUpdatedTopResult(param.topResult, ed - st, fParams)
|
||||
}
|
||||
// Update leaf data
|
||||
if (override === true) {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Account, Doc, Domain, Ref } from './classes'
|
||||
import type { Account, Doc, DocIndexState, Domain, Ref } from './classes'
|
||||
import { MeasureContext } from './measurements'
|
||||
import { DocumentQuery, FindOptions } from './storage'
|
||||
import type { DocumentUpdate, Tx } from './tx'
|
||||
@@ -58,7 +58,7 @@ export interface SessionData {
|
||||
workspace: WorkspaceIdWithUrl
|
||||
branding: Branding | null
|
||||
|
||||
needWarmupFulltext?: boolean
|
||||
fulltextUpdates?: Map<Ref<DocIndexState>, DocIndexState>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+55
-66
@@ -1317,84 +1317,73 @@ export class LiveQuery implements WithTx, Client {
|
||||
return result
|
||||
}
|
||||
|
||||
triggerInProgress = true
|
||||
|
||||
private async checkUpdateEvents (evt: TxWorkspaceEvent, trigger = true): Promise<void> {
|
||||
if (this.triggerInProgress) {
|
||||
this.triggerInProgress = false
|
||||
const h = this.client.getHierarchy()
|
||||
function hasClass (q: Query, classes: Ref<Class<Doc>>[]): boolean {
|
||||
return (
|
||||
classes.includes(q._class) || classes.some((it) => h.isDerived(q._class, it) || h.isDerived(it, q._class))
|
||||
)
|
||||
const h = this.client.getHierarchy()
|
||||
function hasClass (q: Query, classes: Ref<Class<Doc>>[]): boolean {
|
||||
return classes.includes(q._class) || classes.some((it) => h.isDerived(q._class, it) || h.isDerived(it, q._class))
|
||||
}
|
||||
if (evt.event === WorkspaceEvent.IndexingUpdate) {
|
||||
const indexingParam = evt.params as IndexingUpdateEvent
|
||||
for (const q of [...this.queue]) {
|
||||
if (hasClass(q, indexingParam._class) && q.query.$search !== undefined) {
|
||||
if (!this.removeFromQueue(q)) {
|
||||
try {
|
||||
await this.refresh(q)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
} else {
|
||||
const queries = this.queries.get(q._class)
|
||||
const pos = queries?.indexOf(q) ?? -1
|
||||
if (pos >= 0 && queries !== undefined) {
|
||||
queries.splice(pos, 1)
|
||||
if (queries?.length === 0) {
|
||||
this.queries.delete(q._class)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (evt.event === WorkspaceEvent.IndexingUpdate) {
|
||||
const indexingParam = evt.params as IndexingUpdateEvent
|
||||
for (const q of [...this.queue]) {
|
||||
for (const v of this.queries.values()) {
|
||||
for (const q of v) {
|
||||
if (hasClass(q, indexingParam._class) && q.query.$search !== undefined) {
|
||||
if (!this.removeFromQueue(q)) {
|
||||
try {
|
||||
await this.refresh(q)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
} else {
|
||||
const queries = this.queries.get(q._class)
|
||||
const pos = queries?.indexOf(q) ?? -1
|
||||
if (pos >= 0 && queries !== undefined) {
|
||||
queries.splice(pos, 1)
|
||||
if (queries?.length === 0) {
|
||||
this.queries.delete(q._class)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const v of this.queries.values()) {
|
||||
for (const q of v) {
|
||||
if (hasClass(q, indexingParam._class) && q.query.$search !== undefined) {
|
||||
try {
|
||||
await this.refresh(q)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
try {
|
||||
await this.refresh(q)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (evt.event === WorkspaceEvent.BulkUpdate) {
|
||||
const params = evt.params as BulkUpdateEvent
|
||||
for (const q of [...this.queue]) {
|
||||
}
|
||||
if (evt.event === WorkspaceEvent.BulkUpdate) {
|
||||
const params = evt.params as BulkUpdateEvent
|
||||
for (const q of [...this.queue]) {
|
||||
if (hasClass(q, params._class)) {
|
||||
if (!this.removeFromQueue(q)) {
|
||||
try {
|
||||
await this.refresh(q)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const v of this.queries.values()) {
|
||||
for (const q of v) {
|
||||
if (hasClass(q, params._class)) {
|
||||
if (!this.removeFromQueue(q)) {
|
||||
try {
|
||||
await this.refresh(q)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const v of this.queries.values()) {
|
||||
for (const q of v) {
|
||||
if (hasClass(q, params._class)) {
|
||||
try {
|
||||
await this.refresh(q)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
try {
|
||||
await this.refresh(q)
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.triggerInProgress = true
|
||||
void this.checkUpdateEvents(evt, false)
|
||||
}, 20000)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -371,6 +371,12 @@ export function fitPopupElement (
|
||||
newProps.left = '1px'
|
||||
newProps.right = '1px'
|
||||
show = true
|
||||
} else if (element === 'full-centered') {
|
||||
newProps.top = '20px'
|
||||
newProps.bottom = '20px'
|
||||
newProps.left = '20px'
|
||||
newProps.right = '20px'
|
||||
show = true
|
||||
} else if (element === 'content' && contentPanel !== undefined) {
|
||||
const rect = contentPanel.getBoundingClientRect()
|
||||
newProps.top = `${rect.top}px`
|
||||
|
||||
@@ -231,7 +231,8 @@ export const posAlignment = [
|
||||
'centered',
|
||||
'center',
|
||||
'status',
|
||||
'movable'
|
||||
'movable',
|
||||
'full-centered'
|
||||
] as const
|
||||
|
||||
export type PopupPosAlignment = (typeof posAlignment)[number]
|
||||
|
||||
@@ -15,24 +15,36 @@
|
||||
<script lang="ts">
|
||||
import workbench, { Widget, WidgetTab } from '@hcengineering/workbench'
|
||||
import { FilePreview, DownloadFileButton, FilePreviewPopup, FileTypeIcon } from '@hcengineering/presentation'
|
||||
import { Breadcrumbs, Button, closeTooltip, Header, IconOpen, showPopup } from '@hcengineering/ui'
|
||||
import { Breadcrumbs, Button, closeTooltip, Header, showPopup } from '@hcengineering/ui'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
import attachment from '../plugin'
|
||||
|
||||
export let widget: Widget
|
||||
export let tab: WidgetTab
|
||||
export let tab: WidgetTab | undefined
|
||||
|
||||
$: file = tab.data?.file
|
||||
$: fileName = tab.data?.name ?? ''
|
||||
$: contentType = tab.data?.contentType
|
||||
$: metadata = tab.data?.metadata
|
||||
$: file = tab?.data?.file
|
||||
$: fileName = tab?.data?.name ?? ''
|
||||
$: contentType = tab?.data?.contentType
|
||||
$: metadata = tab?.data?.metadata
|
||||
|
||||
async function closeTab (): Promise<void> {
|
||||
if (tab === undefined) return
|
||||
const fn = await getResource(workbench.function.CloseWidgetTab)
|
||||
await fn(widget, tab.id)
|
||||
}
|
||||
|
||||
async function close (): Promise<void> {
|
||||
const fn = await getResource(workbench.function.CloseWidget)
|
||||
await fn(attachment.ids.PreviewWidget)
|
||||
}
|
||||
|
||||
$: if (tab === undefined) {
|
||||
void close()
|
||||
} else if (tab.data === undefined) {
|
||||
void closeTab()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Header
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
export let icon: Asset
|
||||
export let header: IntlString
|
||||
export let label: IntlString
|
||||
export let label: IntlString | undefined = undefined
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
@@ -26,9 +26,11 @@
|
||||
<div class="an-element__label header">
|
||||
<Label label={header} />
|
||||
</div>
|
||||
<span class="an-element__label">
|
||||
<Label {label} />
|
||||
</span>
|
||||
{#if label}
|
||||
<span class="an-element__label">
|
||||
<Label {label} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import notification, { DocNotifyContext } from '@hcengineering/notification'
|
||||
import activity, { ActivityMessage, ActivityMessagesFilter, WithReferences } from '@hcengineering/activity'
|
||||
import activity, { ActivityMessage, WithReferences } from '@hcengineering/activity'
|
||||
import { getClient, isSpace } from '@hcengineering/presentation'
|
||||
import { getMessageFromLoc, messageInFocus } from '@hcengineering/activity-resources'
|
||||
import { location as locationStore } from '@hcengineering/ui'
|
||||
@@ -26,10 +26,11 @@
|
||||
import ReverseChannelScrollView from './ReverseChannelScrollView.svelte'
|
||||
|
||||
export let object: Doc
|
||||
export let context: DocNotifyContext | undefined
|
||||
export let context: DocNotifyContext | undefined = undefined
|
||||
export let syncLocation = true
|
||||
export let autofocus = true
|
||||
export let freeze = false
|
||||
export let readonly = false
|
||||
export let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
|
||||
export let collection: string | undefined = undefined
|
||||
export let withInput: boolean = true
|
||||
@@ -113,6 +114,7 @@
|
||||
{autofocus}
|
||||
loadMoreAllowed={!isDocChannel}
|
||||
{withInput}
|
||||
{readonly}
|
||||
{onReply}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
export let threadId: Ref<ActivityMessage> | undefined
|
||||
export let collection: string | undefined = undefined
|
||||
export let withInput: boolean = true
|
||||
export let readonly: boolean = false
|
||||
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
||||
|
||||
const notificationsClient = InboxNotificationsClientImpl.getClient()
|
||||
@@ -43,6 +44,7 @@
|
||||
{#if renderChannel && visible}
|
||||
<div class="channel" class:invisible={threadId !== undefined} style:height style:width>
|
||||
{#key object._id}
|
||||
<slot name="header" />
|
||||
<Channel
|
||||
{object}
|
||||
{context}
|
||||
@@ -51,13 +53,14 @@
|
||||
{collection}
|
||||
{withInput}
|
||||
{onReply}
|
||||
{readonly}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
{/if}
|
||||
{#if threadId && visible}
|
||||
<div class="thread" style:height style:width>
|
||||
<ThreadView _id={threadId} syncLocation={false} {onReply} on:channel on:close />
|
||||
<ThreadView _id={threadId} syncLocation={false} {onReply} {readonly} on:channel on:close />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import activity, { ActivityExtension } from '@hcengineering/activity'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { AnySvelteComponent, Icon, Label } from '@hcengineering/ui'
|
||||
import { Asset, getResource, translate } from '@hcengineering/platform'
|
||||
import { Asset, getResource, IntlString } from '@hcengineering/platform'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
import { getChannelName, getObjectIcon } from '../utils'
|
||||
@@ -38,10 +38,8 @@
|
||||
$: extensions = client.getModel().findAllSync(activity.class.ActivityExtension, { ofClass: object._class })
|
||||
|
||||
let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
let name: string | undefined = undefined
|
||||
|
||||
$: void updateIcon(object._class)
|
||||
$: void updateName(object)
|
||||
|
||||
async function updateIcon (_class: Ref<Class<Doc>>): Promise<void> {
|
||||
if (isThread) {
|
||||
@@ -58,9 +56,11 @@
|
||||
icon = result
|
||||
}
|
||||
|
||||
async function updateName (object: Doc): Promise<void> {
|
||||
const titleIntl = client.getHierarchy().getClass(object._class).label
|
||||
name = (await getChannelName(object._id, object._class, object)) ?? (await translate(titleIntl, {}))
|
||||
async function getName (object: Doc): Promise<{ name: string | undefined, label: IntlString | undefined }> {
|
||||
const name = await getChannelName(object._id, object._class, object)
|
||||
const label = client.getHierarchy().getClass(object._class).label
|
||||
|
||||
return { name, label }
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -82,9 +82,13 @@
|
||||
{#if icon}
|
||||
<Icon {icon} size="x-small" />
|
||||
{/if}
|
||||
{#if name}
|
||||
{name}
|
||||
{/if}
|
||||
{#await getName(object) then data}
|
||||
{#if data.name}
|
||||
{data.name}
|
||||
{:else if data.label}
|
||||
<Label label={data.label} />
|
||||
{/if}
|
||||
{/await}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
export let context: DocNotifyContext | undefined
|
||||
export let autofocus = true
|
||||
export let embedded: boolean = false
|
||||
export let readonly: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@@ -54,7 +55,7 @@
|
||||
isThreadOpened = newLocation.path[4] != null
|
||||
})
|
||||
|
||||
$: readonly = hierarchy.isDerived(object._class, core.class.Space) ? (object as Space).archived : false
|
||||
$: readonly = hierarchy.isDerived(object._class, core.class.Space) ? readonly || (object as Space).archived : readonly
|
||||
$: showJoinOverlay = shouldShowJoinOverlay(object)
|
||||
$: isDocChat = !hierarchy.isDerived(object._class, chunter.class.ChunterSpace)
|
||||
$: withAside =
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
export let loadMoreAllowed = true
|
||||
export let autofocus = true
|
||||
export let withInput: boolean = true
|
||||
export let readonly: boolean = false
|
||||
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
||||
|
||||
const minMsgHeightRem = 2
|
||||
@@ -113,7 +114,9 @@
|
||||
$: notifyContext = $contextByDocStore.get(doc._id)
|
||||
$: isThread = hierarchy.isDerived(doc._class, activity.class.ActivityMessage)
|
||||
$: isChunterSpace = hierarchy.isDerived(doc._class, chunter.class.ChunterSpace)
|
||||
$: readonly = hierarchy.isDerived(channel._class, core.class.Space) ? (channel as Space).archived : false
|
||||
$: readonly = hierarchy.isDerived(channel._class, core.class.Space)
|
||||
? readonly || (channel as Space).archived
|
||||
: readonly
|
||||
|
||||
$: separatorIndex =
|
||||
$newTimestampStore !== undefined
|
||||
@@ -575,7 +578,7 @@
|
||||
removeTxListener(newMessageTxListener)
|
||||
})
|
||||
|
||||
$: showBlankView = !$isLoadingStore && messages.length === 0 && !isThread && !readonly
|
||||
$: showBlankView = !$isLoadingStore && messages.length === 0 && !isThread
|
||||
</script>
|
||||
|
||||
<div class="flex-col relative" class:h-full={fullHeight}>
|
||||
@@ -597,7 +600,7 @@
|
||||
<BlankView
|
||||
icon={chunter.icon.Thread}
|
||||
header={chunter.string.NoMessagesInChannel}
|
||||
label={chunter.string.SendMessagesInChannel}
|
||||
label={readonly ? undefined : chunter.string.SendMessagesInChannel}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -644,7 +647,7 @@
|
||||
{#if loadMoreAllowed && $canLoadNextForwardStore}
|
||||
<HistoryLoading isLoading={$isLoadingMoreStore} />
|
||||
{/if}
|
||||
{#if !fixedInput && withInput}
|
||||
{#if !fixedInput && withInput && !readonly}
|
||||
<ChannelInput {object} {readonly} boundary={scrollDiv} {collection} {isThread} {autofocus} />
|
||||
{/if}
|
||||
</BaseChatScroller>
|
||||
@@ -661,10 +664,14 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if fixedInput && withInput}
|
||||
{#if fixedInput && withInput && !readonly}
|
||||
<ChannelInput {object} {readonly} boundary={scrollDiv} {collection} {isThread} {autofocus} />
|
||||
{/if}
|
||||
|
||||
{#if readonly}
|
||||
<div class="h-6" />
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.selectedDate {
|
||||
position: absolute;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
export let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
|
||||
export let message: ActivityMessage
|
||||
export let autofocus = true
|
||||
export let readonly: boolean = false
|
||||
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
@@ -56,8 +57,8 @@
|
||||
|
||||
$: messagesStore = dataProvider?.messagesStore
|
||||
$: readonly = hierarchy.isDerived(message.attachedToClass, core.class.Space)
|
||||
? (channel as Space)?.archived ?? false
|
||||
: false
|
||||
? (readonly || (channel as Space)?.archived) ?? false
|
||||
: readonly
|
||||
</script>
|
||||
|
||||
<div class="hulyComponent-content hulyComponent-content__container noShrink">
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
export let showHeader: boolean = true
|
||||
export let syncLocation = true
|
||||
export let autofocus = true
|
||||
export let readonly: boolean = false
|
||||
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
@@ -145,7 +146,7 @@
|
||||
|
||||
{#if message}
|
||||
{#key _id}
|
||||
<ThreadContent bind:selectedMessageId {message} {autofocus} {onReply} />
|
||||
<ThreadContent bind:selectedMessageId {message} {autofocus} {readonly} {onReply} />
|
||||
{/key}
|
||||
{:else if isLoading}
|
||||
<Loading />
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
"Meeting": "Meeting",
|
||||
"Transcription": "Transcription",
|
||||
"StartWithTranscription": "Start with transcription",
|
||||
"MeetingMinutes": "Meeting minutes"
|
||||
"MeetingMinutes": "Meeting minutes",
|
||||
"StartMeeting": "Start meeting",
|
||||
"Video": "Video",
|
||||
"NoMeetingMinutes": "No meeting minutes",
|
||||
"JoinMeeting": "Join meeting"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
"Meeting": "Reunión",
|
||||
"Transcription": "Transcripción",
|
||||
"StartWithTranscription": "Iniciar con transcripción",
|
||||
"MeetingMinutes": "Minutos de la reunión"
|
||||
"MeetingMinutes": "Minutos de la reunión",
|
||||
"StartMeeting": "Iniciar reunión",
|
||||
"Video": "Video",
|
||||
"NoMeetingMinutes": "Sin minutos de reunión",
|
||||
"JoinMeeting": "Unirse a la reunión"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
"Meeting": "Réunion",
|
||||
"Transcription": "Transcription",
|
||||
"StartWithTranscription": "Démarrer avec la transcription",
|
||||
"MeetingMinutes": "Minutes de la réunion"
|
||||
"MeetingMinutes": "Minutes de la réunion",
|
||||
"StartMeeting": "Démarrer la réunion",
|
||||
"Video": "Vidéo",
|
||||
"NoMeetingMinutes": "Pas de minutes de réunion",
|
||||
"JoinMeeting": "Rejoindre la réunion"
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,10 @@
|
||||
"Meeting": "Riunione",
|
||||
"Transcription": "Trascrizione",
|
||||
"StartWithTranscription": "Inizia con la trascrizione",
|
||||
"MeetingMinutes": "Verbale della riunione"
|
||||
"MeetingMinutes": "Verbale della riunione",
|
||||
"StartMeeting": "Inizia riunione",
|
||||
"Video": "Video",
|
||||
"NoMeetingMinutes": "Nessun verbale della riunione",
|
||||
"JoinMeeting": "Unisciti alla riunione"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
"Meeting": "Reunião",
|
||||
"Transcription": "Transcrição",
|
||||
"StartWithTranscription": "Começar com transcrição",
|
||||
"MeetingMinutes": "Minutos da reunião"
|
||||
"MeetingMinutes": "Minutos da reunião",
|
||||
"StartMeeting": "Iniciar reunião",
|
||||
"Video": "Vídeo",
|
||||
"NoMeetingMinutes": "Sem minutos de reunião",
|
||||
"JoinMeeting": "Participar na reunião"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
"Meeting": "Встреча",
|
||||
"Transcription": "Транскрипция",
|
||||
"StartWithTranscription": "Начинать с транскрипцией",
|
||||
"MeetingMinutes": "Протоколы встреч"
|
||||
"MeetingMinutes": "Результаты встреч",
|
||||
"StartMeeting": "Начать встречу",
|
||||
"Video": "Видео",
|
||||
"NoMeetingMinutes": "Нет результатов встреч",
|
||||
"JoinMeeting": "Присоединиться к встрече"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
"Meeting": "会议",
|
||||
"Transcription": "转录",
|
||||
"StartWithTranscription": "开始转录",
|
||||
"MeetingMinutes": "会议记录"
|
||||
"MeetingMinutes": "会议记录",
|
||||
"StartMeeting": "开始会议",
|
||||
"Video": "视频",
|
||||
"NoMeetingMinutes": "无会议记录",
|
||||
"JoinMeeting": "加入会议"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/login": "^0.6.12",
|
||||
"@hcengineering/love": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/notification-resources": "^0.6.0",
|
||||
"@hcengineering/panel": "^0.6.23",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/presentation": "^0.6.3",
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { Room as TypeRoom } from '@hcengineering/love'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { Label, Loading, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import { EditDoc } from '@hcengineering/view-resources'
|
||||
import { subscribeDoc } from '@hcengineering/notification-resources'
|
||||
|
||||
import love from '../plugin'
|
||||
import { storePromise, currentRoom, infos, invites, myInfo, myRequests, meetingMinutesStore } from '../stores'
|
||||
import { awaitConnect, isConnected, isCurrentInstanceConnected, isFullScreen, tryConnect } from '../utils'
|
||||
import ControlBar from './ControlBar.svelte'
|
||||
|
||||
export let room: TypeRoom
|
||||
|
||||
let loading: boolean = false
|
||||
let configured: boolean = false
|
||||
|
||||
onMount(async () => {
|
||||
loading = true
|
||||
|
||||
const wsURL = getMetadata(love.metadata.WebSocketURL)
|
||||
|
||||
if (wsURL === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
configured = true
|
||||
|
||||
await $storePromise
|
||||
|
||||
if (
|
||||
!$isConnected &&
|
||||
!$isCurrentInstanceConnected &&
|
||||
$myInfo?.sessionId === getMetadata(presentation.metadata.SessionId)
|
||||
) {
|
||||
const info = $infos.filter((p) => p.room === room._id)
|
||||
await tryConnect($personByIdStore, $myInfo, room, info, $myRequests, $invites)
|
||||
}
|
||||
|
||||
await awaitConnect()
|
||||
loading = false
|
||||
})
|
||||
|
||||
let replacedPanel: HTMLElement
|
||||
$: $deviceInfo.replacedPanel = replacedPanel
|
||||
onDestroy(() => ($deviceInfo.replacedPanel = undefined))
|
||||
|
||||
$: if ($meetingMinutesStore) {
|
||||
void subscribeDoc(getClient(), $meetingMinutesStore._class, $meetingMinutesStore._id, 'add', $meetingMinutesStore)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiPanel-component filledNav" bind:this={replacedPanel}>
|
||||
<div class="hulyComponent">
|
||||
{#if $isConnected && !$isCurrentInstanceConnected}
|
||||
<div class="flex-center justify-center error h-full w-full clear-mins">
|
||||
<Label label={love.string.AnotherWindowError} />
|
||||
</div>
|
||||
{:else if !configured}
|
||||
<div class="flex-center justify-center error h-full w-full clear-mins">
|
||||
<Label label={love.string.ServiceNotConfigured} />
|
||||
</div>
|
||||
{:else if loading || !$currentRoom || !$meetingMinutesStore}
|
||||
<Loading />
|
||||
{:else}
|
||||
<EditDoc _id={$meetingMinutesStore._id} _class={$meetingMinutesStore._class} embedded selectedAside={false} />
|
||||
{/if}
|
||||
{#if $currentRoom}
|
||||
<div class="flex-grow flex-shrink" />
|
||||
<ControlBar room={$currentRoom} fullScreen={$isFullScreen} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import core, { Class, Data, Ref } from '@hcengineering/core'
|
||||
import core, { Class, Data, generateId, makeCollaborativeDoc, Ref } from '@hcengineering/core'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Button, DropdownIntlItem } from '@hcengineering/ui'
|
||||
@@ -51,6 +51,7 @@
|
||||
const client = getClient()
|
||||
const floorRooms = $rooms.filter((r) => r.floor === floor)
|
||||
const pos = getFreePosition(floorRooms, 2, 1)
|
||||
const _id = generateId<Room>()
|
||||
const data: Data<Room> = {
|
||||
floor,
|
||||
name: val._class === love.class.Office ? '' : await translate(val.label, {}),
|
||||
@@ -61,12 +62,13 @@
|
||||
type: val.type,
|
||||
access: val.access,
|
||||
language: 'en',
|
||||
startWithTranscription: val._class !== love.class.Office
|
||||
startWithTranscription: val._class !== love.class.Office && val.type === RoomType.Video,
|
||||
description: makeCollaborativeDoc(_id, 'description')
|
||||
}
|
||||
if (val._class === love.class.Office) {
|
||||
;(data as Data<Office>).person = null
|
||||
}
|
||||
await client.createDoc(val._class, core.space.Workspace, data)
|
||||
await client.createDoc(val._class, core.space.Workspace, data, _id)
|
||||
dispatch('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -27,14 +27,12 @@
|
||||
showPopup,
|
||||
type AnySvelteComponent,
|
||||
type CompAndProps,
|
||||
resizeObserver,
|
||||
IconMoreV,
|
||||
ButtonMenu,
|
||||
DropdownIntlItem
|
||||
} from '@hcengineering/ui'
|
||||
import view, { Action } from '@hcengineering/view'
|
||||
import { getActions } from '@hcengineering/view-resources'
|
||||
import { afterUpdate } from 'svelte'
|
||||
|
||||
import love from '../plugin'
|
||||
import { currentRoom, myInfo, myOffice } from '../stores'
|
||||
@@ -61,19 +59,17 @@
|
||||
import MicSettingPopup from './MicSettingPopup.svelte'
|
||||
import RoomAccessPopup from './RoomAccessPopup.svelte'
|
||||
import RoomLanguageSelector from './RoomLanguageSelector.svelte'
|
||||
import ControlBarContainer from './ControlBarContainer.svelte'
|
||||
|
||||
export let room: Room
|
||||
export let fullScreen: boolean = false
|
||||
export let onFullScreen: (() => void) | undefined = undefined
|
||||
|
||||
let allowCam: boolean = false
|
||||
const allowShare: boolean = true
|
||||
let allowLeave: boolean = false
|
||||
let popup: CompAndProps | undefined = undefined
|
||||
let grow: HTMLElement
|
||||
let leftPanel: HTMLElement
|
||||
let leftPanelSize: number = 0
|
||||
let noLabel: boolean = false
|
||||
let combinePanel: boolean = false
|
||||
|
||||
$: allowCam = $currentRoom?.type === RoomType.Video
|
||||
$: allowLeave = $myInfo?.room !== ($myOffice?._id ?? love.ids.Reception)
|
||||
@@ -138,17 +134,6 @@
|
||||
const camKeys = client.getModel().findAllSync(view.class.Action, { _id: love.action.ToggleVideo })?.[0]?.keyBinding
|
||||
const micKeys = client.getModel().findAllSync(view.class.Action, { _id: love.action.ToggleMic })?.[0]?.keyBinding
|
||||
|
||||
const checkBar = (): void => {
|
||||
if (grow === undefined || leftPanel === undefined) return
|
||||
if (!noLabel && leftPanel.clientWidth > leftPanelSize) leftPanelSize = leftPanel.clientWidth
|
||||
if (grow.clientWidth - 16 < leftPanel.clientWidth && !noLabel && !combinePanel) noLabel = true
|
||||
else if (grow.clientWidth - 16 < leftPanel.clientWidth && noLabel && !combinePanel) combinePanel = true
|
||||
else if (grow.clientWidth * 2 - 32 > leftPanel.clientWidth && noLabel && combinePanel) combinePanel = false
|
||||
else if (grow.clientWidth - 32 >= leftPanelSize && noLabel && !combinePanel) noLabel = false
|
||||
}
|
||||
afterUpdate(() => {
|
||||
checkBar()
|
||||
})
|
||||
let actions: Action[] = []
|
||||
let moreItems: DropdownIntlItem[] = []
|
||||
|
||||
@@ -173,86 +158,88 @@
|
||||
const fn = await getResource(action.action)
|
||||
await fn(room)
|
||||
}
|
||||
$: withVideo = $screenSharing || room.type === RoomType.Video
|
||||
</script>
|
||||
|
||||
<div class="bar w-full flex-center flex-gap-2 flex-no-shrink" class:combinePanel use:resizeObserver={checkBar}>
|
||||
<div class="bar__right-panel flex-gap-2 flex-center">
|
||||
<ControlBarContainer bind:noLabel>
|
||||
<svelte:fragment slot="right">
|
||||
{#if $isConnected && isTranscriptionAllowed() && $isTranscription}
|
||||
<RoomLanguageSelector {room} kind="icon" />
|
||||
{/if}
|
||||
</div>
|
||||
<div bind:this={grow} class="flex-grow" />
|
||||
{#if room._id !== love.ids.Reception}
|
||||
<ModernButton
|
||||
icon={roomAccessIcon[room.access]}
|
||||
tooltip={{ label: love.string.ChangeAccess }}
|
||||
kind={'secondary'}
|
||||
size={'large'}
|
||||
disabled={isOffice(room) && room.person !== me}
|
||||
on:click={setAccess}
|
||||
/>
|
||||
{/if}
|
||||
{#if $isConnected}
|
||||
<SplitButton
|
||||
size={'large'}
|
||||
icon={$isMicEnabled ? love.icon.MicEnabled : love.icon.MicDisabled}
|
||||
showTooltip={{ label: $isMicEnabled ? love.string.Mute : love.string.UnMute, keys: micKeys }}
|
||||
action={changeMute}
|
||||
secondIcon={IconUpOutline}
|
||||
secondAction={micSettings}
|
||||
separate
|
||||
/>
|
||||
{#if allowCam}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="center">
|
||||
{#if room._id !== love.ids.Reception}
|
||||
<ModernButton
|
||||
icon={roomAccessIcon[room.access]}
|
||||
tooltip={{ label: love.string.ChangeAccess }}
|
||||
kind={'secondary'}
|
||||
size={'large'}
|
||||
disabled={isOffice(room) && room.person !== me}
|
||||
on:click={setAccess}
|
||||
/>
|
||||
{/if}
|
||||
{#if $isConnected}
|
||||
<SplitButton
|
||||
size={'large'}
|
||||
icon={$isCameraEnabled ? love.icon.CamEnabled : love.icon.CamDisabled}
|
||||
showTooltip={{ label: $isCameraEnabled ? love.string.StopVideo : love.string.StartVideo, keys: camKeys }}
|
||||
disabled={!$isConnected}
|
||||
action={changeCam}
|
||||
icon={$isMicEnabled ? love.icon.MicEnabled : love.icon.MicDisabled}
|
||||
showTooltip={{ label: $isMicEnabled ? love.string.Mute : love.string.UnMute, keys: micKeys }}
|
||||
action={changeMute}
|
||||
secondIcon={IconUpOutline}
|
||||
secondAction={camSettings}
|
||||
secondAction={micSettings}
|
||||
separate
|
||||
/>
|
||||
{#if allowCam}
|
||||
<SplitButton
|
||||
size={'large'}
|
||||
icon={$isCameraEnabled ? love.icon.CamEnabled : love.icon.CamDisabled}
|
||||
showTooltip={{ label: $isCameraEnabled ? love.string.StopVideo : love.string.StartVideo, keys: camKeys }}
|
||||
disabled={!$isConnected}
|
||||
action={changeCam}
|
||||
secondIcon={IconUpOutline}
|
||||
secondAction={camSettings}
|
||||
separate
|
||||
/>
|
||||
{/if}
|
||||
{#if allowShare}
|
||||
<ModernButton
|
||||
icon={$isSharingEnabled ? love.icon.SharingEnabled : love.icon.SharingDisabled}
|
||||
tooltip={{ label: $isSharingEnabled ? love.string.StopShare : love.string.Share }}
|
||||
disabled={($screenSharing && !$isSharingEnabled) || !$isConnected}
|
||||
kind={'secondary'}
|
||||
size={'large'}
|
||||
on:click={changeShare}
|
||||
/>
|
||||
{/if}
|
||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User) && $isRecordingAvailable}
|
||||
<ModernButton
|
||||
icon={$isRecording ? love.icon.StopRecord : love.icon.Record}
|
||||
tooltip={{ label: $isRecording ? love.string.StopRecord : love.string.Record }}
|
||||
disabled={!$isConnected}
|
||||
kind={'secondary'}
|
||||
size={'large'}
|
||||
on:click={() => record(room)}
|
||||
/>
|
||||
{/if}
|
||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User) && isTranscriptionAllowed() && $isConnected}
|
||||
<ModernButton
|
||||
icon={view.icon.Feather}
|
||||
iconProps={$isTranscription ? { fill: 'var(--button-negative-BackgroundColor)' } : {}}
|
||||
tooltip={{ label: $isTranscription ? love.string.StopTranscription : love.string.StartTranscription }}
|
||||
kind="secondary"
|
||||
size="large"
|
||||
on:click={() => {
|
||||
if ($isTranscription) {
|
||||
void stopTranscription(room)
|
||||
} else {
|
||||
void startTranscription(room)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if allowShare}
|
||||
<ModernButton
|
||||
icon={$isSharingEnabled ? love.icon.SharingEnabled : love.icon.SharingDisabled}
|
||||
tooltip={{ label: $isSharingEnabled ? love.string.StopShare : love.string.Share }}
|
||||
disabled={($screenSharing && !$isSharingEnabled) || !$isConnected}
|
||||
kind={'secondary'}
|
||||
size={'large'}
|
||||
on:click={changeShare}
|
||||
/>
|
||||
{/if}
|
||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User) && $isRecordingAvailable}
|
||||
<ModernButton
|
||||
icon={$isRecording ? love.icon.StopRecord : love.icon.Record}
|
||||
tooltip={{ label: $isRecording ? love.string.StopRecord : love.string.Record }}
|
||||
disabled={!$isConnected}
|
||||
kind={'secondary'}
|
||||
size={'large'}
|
||||
on:click={() => record(room)}
|
||||
/>
|
||||
{/if}
|
||||
{#if hasAccountRole(getCurrentAccount(), AccountRole.User) && isTranscriptionAllowed() && $isConnected}
|
||||
<ModernButton
|
||||
icon={view.icon.Feather}
|
||||
iconProps={$isTranscription ? { fill: 'var(--button-negative-BackgroundColor)' } : {}}
|
||||
tooltip={{ label: $isTranscription ? love.string.StopTranscription : love.string.StartTranscription }}
|
||||
kind="secondary"
|
||||
size="large"
|
||||
on:click={() => {
|
||||
if ($isTranscription) {
|
||||
void stopTranscription(room)
|
||||
} else {
|
||||
void startTranscription(room)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
<div bind:this={leftPanel} class="bar__left-panel flex-gap-2 flex-center">
|
||||
{#if $isConnected}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="left">
|
||||
{#if $isConnected && withVideo && onFullScreen}
|
||||
<ModernButton
|
||||
icon={$isFullScreen ? love.icon.ExitFullScreen : love.icon.FullScreen}
|
||||
tooltip={{
|
||||
@@ -287,54 +274,23 @@
|
||||
on:click={leave}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
{#if popup && fullScreen}
|
||||
<PopupInstance
|
||||
is={popup.is}
|
||||
props={popup.props}
|
||||
element={popup.element}
|
||||
onClose={popup.onClose}
|
||||
onUpdate={popup.onUpdate}
|
||||
zIndex={1}
|
||||
top={true}
|
||||
close={popup.close}
|
||||
overlay={popup.options.overlay}
|
||||
contentPanel={undefined}
|
||||
{popup}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<style lang="scss">
|
||||
.bar {
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
border-top: 1px solid var(--theme-divider-color);
|
||||
|
||||
&__left-panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__right-panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.combinePanel .bar__left-panel {
|
||||
position: static;
|
||||
}
|
||||
|
||||
&.combinePanel .bar__right-panel {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<svelte:fragment slot="extra">
|
||||
{#if popup && fullScreen}
|
||||
<PopupInstance
|
||||
is={popup.is}
|
||||
props={popup.props}
|
||||
element={popup.element}
|
||||
onClose={popup.onClose}
|
||||
onUpdate={popup.onUpdate}
|
||||
zIndex={1}
|
||||
top={true}
|
||||
close={popup.close}
|
||||
overlay={popup.options.overlay}
|
||||
contentPanel={undefined}
|
||||
{popup}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</ControlBarContainer>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { resizeObserver } from '@hcengineering/ui'
|
||||
import { afterUpdate } from 'svelte'
|
||||
|
||||
export let noLabel: boolean = false
|
||||
|
||||
let grow: HTMLElement
|
||||
let leftPanel: HTMLElement
|
||||
let leftPanelSize: number = 0
|
||||
|
||||
let combinePanel: boolean = false
|
||||
|
||||
const checkBar = (): void => {
|
||||
if (grow === undefined || leftPanel === undefined) return
|
||||
if (!noLabel && leftPanel.clientWidth > leftPanelSize) leftPanelSize = leftPanel.clientWidth
|
||||
if (grow.clientWidth - 16 < leftPanel.clientWidth && !noLabel && !combinePanel) noLabel = true
|
||||
else if (grow.clientWidth - 16 < leftPanel.clientWidth && noLabel && !combinePanel) combinePanel = true
|
||||
else if (grow.clientWidth * 2 - 32 > leftPanel.clientWidth && noLabel && combinePanel) combinePanel = false
|
||||
else if (grow.clientWidth - 32 >= leftPanelSize && noLabel && !combinePanel) noLabel = false
|
||||
}
|
||||
afterUpdate(() => {
|
||||
checkBar()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="bar w-full flex-center flex-gap-2 flex-no-shrink" class:combinePanel use:resizeObserver={checkBar}>
|
||||
<div class="bar__right-panel flex-gap-2 flex-center">
|
||||
<slot name="right" />
|
||||
</div>
|
||||
<div bind:this={grow} class="flex-grow" />
|
||||
<slot name="center" />
|
||||
<div bind:this={leftPanel} class="bar__left-panel flex-gap-2 flex-center">
|
||||
<slot name="left" />
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<slot name="extra" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.bar {
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
border-top: 1px solid var(--theme-divider-color);
|
||||
|
||||
&__left-panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__right-panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.combinePanel .bar__left-panel {
|
||||
position: static;
|
||||
}
|
||||
|
||||
&.combinePanel .bar__right-panel {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,6 @@
|
||||
Invite,
|
||||
isOffice,
|
||||
JoinRequest,
|
||||
loveId,
|
||||
Office,
|
||||
ParticipantInfo,
|
||||
RequestStatus,
|
||||
@@ -40,14 +39,7 @@
|
||||
} from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import workbench from '@hcengineering/workbench'
|
||||
import {
|
||||
closeWidget,
|
||||
closeWidgetTab,
|
||||
minimizeSidebar,
|
||||
sidebarStore,
|
||||
SidebarVariant,
|
||||
updateWidgetState
|
||||
} from '@hcengineering/workbench-resources'
|
||||
import { closeWidget, closeWidgetTab, sidebarStore } from '@hcengineering/workbench-resources'
|
||||
|
||||
import love from '../plugin'
|
||||
import { activeInvites, currentRoom, infos, myInfo, myInvites, myOffice, myRequests, rooms } from '../stores'
|
||||
@@ -69,37 +61,6 @@
|
||||
|
||||
const client = getClient()
|
||||
|
||||
// let allowCam: boolean = false
|
||||
// let allowLeave: boolean = false
|
||||
//
|
||||
// $: allowCam = $currentRoom?.type === RoomType.Video
|
||||
// $: allowLeave = $myInfo !== undefined && $myInfo.room !== ($myOffice?._id ?? love.ids.Reception)
|
||||
|
||||
// async function changeMute (): Promise<void> {
|
||||
// if (!$isConnected || $currentRoom?.type === RoomType.Reception) return
|
||||
// await setMic(!$isMicEnabled)
|
||||
// }
|
||||
//
|
||||
// async function changeCam (): Promise<void> {
|
||||
// if (!$isConnected || !allowCam) return
|
||||
// await setCam(!$isCameraEnabled)
|
||||
// }
|
||||
//
|
||||
// async function changeShare (): Promise<void> {
|
||||
// if (!$isConnected) return
|
||||
// await setShare(!$isSharingEnabled)
|
||||
// }
|
||||
//
|
||||
// async function leave (): Promise<void> {
|
||||
// showPopup(MessageBox, {
|
||||
// label: love.string.LeaveRoom,
|
||||
// message: love.string.LeaveRoomConfirmation,
|
||||
// action: async () => {
|
||||
// await leaveRoom($myInfo, $myOffice)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
interface ActiveRoom extends Room {
|
||||
participants: ParticipantInfo[]
|
||||
}
|
||||
@@ -124,18 +85,8 @@
|
||||
return arr
|
||||
}
|
||||
|
||||
// let selectedFloor: Floor | undefined = $floors.find((f) => f._id === $activeFloor)
|
||||
// $: selectedFloor = $floors.find((f) => f._id === $activeFloor)
|
||||
|
||||
$: activeRooms = getActiveRooms($rooms, $infos)
|
||||
|
||||
// function selectFloor (): void {
|
||||
// showPopup(FloorPopup, { selectedFloor }, myOfficeElement, (res) => {
|
||||
// if (res === undefined) return
|
||||
// selectedFloor = $floors.find((p) => p._id === res)
|
||||
// })
|
||||
// }
|
||||
|
||||
const query = createQuery()
|
||||
let requests: JoinRequest[] = []
|
||||
query.query(love.class.JoinRequest, { status: RequestStatus.Pending }, (res) => {
|
||||
@@ -261,32 +212,6 @@
|
||||
|
||||
$: checkActiveInvites($activeInvites)
|
||||
|
||||
// function micSettings (e: MouseEvent): void {
|
||||
// e.preventDefault()
|
||||
// showPopup(MicSettingPopup, {}, eventToHTMLElement(e))
|
||||
// }
|
||||
//
|
||||
// function camSettings (e: MouseEvent): void {
|
||||
// e.preventDefault()
|
||||
// showPopup(CamSettingPopup, {}, eventToHTMLElement(e))
|
||||
// }
|
||||
|
||||
let prevLocation: Location = $location
|
||||
$: isMeetingWidgetOpened = $sidebarStore.widgetsState.has(love.ids.MeetingWidget)
|
||||
|
||||
$: widgetState = $sidebarStore.widgetsState.get(love.ids.MeetingWidget)
|
||||
$: if (
|
||||
isMeetingWidgetOpened &&
|
||||
$sidebarStore.widget === undefined &&
|
||||
$location.path[2] !== loveId &&
|
||||
widgetState !== undefined &&
|
||||
widgetState.closedByUser !== true &&
|
||||
widgetState.tabs.some(({ id }) => id === 'video')
|
||||
) {
|
||||
sidebarStore.update((s) => ({ ...s, widget: love.ids.MeetingWidget, variant: SidebarVariant.EXPANDED }))
|
||||
updateWidgetState(love.ids.MeetingWidget, { openedByUser: false, tab: 'video' })
|
||||
}
|
||||
|
||||
function checkActiveVideo (loc: Location, video: boolean, room: Ref<Room> | undefined): void {
|
||||
const meetingWidgetState = $sidebarStore.widgetsState.get(love.ids.MeetingWidget)
|
||||
const isMeetingWidgetCreated = meetingWidgetState !== undefined
|
||||
@@ -302,44 +227,18 @@
|
||||
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: love.ids.MeetingWidget })[0]
|
||||
if (widget === undefined) return
|
||||
|
||||
// Create widget in sidebar if not created
|
||||
if (!isMeetingWidgetCreated) {
|
||||
prevLocation = loc
|
||||
createMeetingWidget(widget, room, loc, video)
|
||||
createMeetingWidget(widget, room, video)
|
||||
} else if (video && !meetingWidgetState.tabs.some(({ id }) => id === 'video')) {
|
||||
createMeetingVideoWidgetTab(widget, loc)
|
||||
createMeetingVideoWidgetTab(widget)
|
||||
} else if (!video && meetingWidgetState.tabs.some(({ id }) => id === 'video')) {
|
||||
void closeWidgetTab(widget, 'video')
|
||||
}
|
||||
|
||||
// Show video in sidebar when leave office
|
||||
if (
|
||||
$sidebarStore.widget === love.ids.MeetingWidget &&
|
||||
prevLocation.path[2] === loveId &&
|
||||
loc.path[2] !== loveId &&
|
||||
widgetState !== undefined &&
|
||||
widgetState.tabs.some(({ id }) => id === 'video')
|
||||
) {
|
||||
updateWidgetState(love.ids.MeetingWidget, { openedByUser: false, tab: 'video' })
|
||||
}
|
||||
|
||||
// Hide video in sidebar when open office app
|
||||
if (
|
||||
loc.path[2] === loveId &&
|
||||
prevLocation.path[2] !== loveId &&
|
||||
$sidebarStore.widget === love.ids.MeetingWidget &&
|
||||
widgetState !== undefined &&
|
||||
widgetState.tab === 'video'
|
||||
) {
|
||||
minimizeSidebar()
|
||||
}
|
||||
} else {
|
||||
if (isMeetingWidgetCreated) {
|
||||
closeWidget(love.ids.MeetingWidget)
|
||||
}
|
||||
}
|
||||
|
||||
prevLocation = loc
|
||||
}
|
||||
|
||||
$: checkActiveVideo(
|
||||
@@ -384,56 +283,6 @@
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- <div class="container main flex-row-center flex-gap-2" bind:this={myOfficeElement} on:click={selectFloor}>-->
|
||||
<!-- {#if selectedFloor}-->
|
||||
<!-- <Label label={love.string.Floor} />-->
|
||||
<!-- <span class="label overflow-label">-->
|
||||
<!-- {selectedFloor?.name}-->
|
||||
<!-- </span>-->
|
||||
<!-- {/if}-->
|
||||
<!-- <ActionIcon-->
|
||||
<!-- icon={!$isConnected ? love.icon.Mic : $isMicEnabled ? love.icon.MicEnabled : love.icon.MicDisabled}-->
|
||||
<!-- label={$isMicEnabled ? love.string.Mute : love.string.UnMute}-->
|
||||
<!-- size={'small'}-->
|
||||
<!-- action={changeMute}-->
|
||||
<!-- on:contextmenu={micSettings}-->
|
||||
<!-- disabled={!$isConnected}-->
|
||||
<!-- keys={micKeys}-->
|
||||
<!-- />-->
|
||||
<!-- <ActionIcon-->
|
||||
<!-- icon={!$isConnected || !allowCam-->
|
||||
<!-- ? love.icon.Cam-->
|
||||
<!-- : $isCameraEnabled-->
|
||||
<!-- ? love.icon.CamEnabled-->
|
||||
<!-- : love.icon.CamDisabled}-->
|
||||
<!-- label={$isCameraEnabled ? love.string.StopVideo : love.string.StartVideo}-->
|
||||
<!-- size={'small'}-->
|
||||
<!-- action={changeCam}-->
|
||||
<!-- on:contextmenu={camSettings}-->
|
||||
<!-- disabled={!$isConnected || !allowCam}-->
|
||||
<!-- keys={camKeys}-->
|
||||
<!-- />-->
|
||||
<!-- {#if $isConnected}-->
|
||||
<!-- <ActionIcon-->
|
||||
<!-- icon={$isSharingEnabled ? love.icon.SharingEnabled : love.icon.SharingDisabled}-->
|
||||
<!-- label={$isSharingEnabled ? love.string.StopShare : love.string.Share}-->
|
||||
<!-- disabled={$screenSharing && !$isSharingEnabled}-->
|
||||
<!-- size={'small'}-->
|
||||
<!-- action={changeShare}-->
|
||||
<!-- />-->
|
||||
<!-- {/if}-->
|
||||
<!-- {#if allowLeave}-->
|
||||
<!-- <ActionIcon-->
|
||||
<!-- icon={love.icon.LeaveRoom}-->
|
||||
<!-- iconProps={{ color: '#FF6711' }}-->
|
||||
<!-- label={love.string.LeaveRoom}-->
|
||||
<!-- size={'small'}-->
|
||||
<!-- action={leave}-->
|
||||
<!-- />-->
|
||||
<!-- {/if}-->
|
||||
<!-- </div>-->
|
||||
{#if activeRooms.length > 0}
|
||||
<!-- <div class="divider" />-->
|
||||
{#each activeRooms as active}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { EditBox } from '@hcengineering/ui'
|
||||
import { MeetingMinutes } from '@hcengineering/love'
|
||||
|
||||
import love from '../plugin'
|
||||
|
||||
export let object: MeetingMinutes
|
||||
export let readonly: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function changeTitle (): Promise<void> {
|
||||
await client.diffUpdate(object, { title: object.title })
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-row-stretch">
|
||||
<div class="flex-col flex-grow">
|
||||
<div class="title">
|
||||
<EditBox
|
||||
disabled={readonly}
|
||||
placeholder={love.string.MeetingMinutes}
|
||||
bind:value={object.title}
|
||||
on:change={changeTitle}
|
||||
focusIndex={1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,104 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { EditBox, ModernButton, closePanel } from '@hcengineering/ui'
|
||||
import { Room, isOffice } from '@hcengineering/love'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import { personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
|
||||
import love from '../plugin'
|
||||
import { getRoomName, tryConnect } from '../utils'
|
||||
import { infos, invites, myInfo, myRequests, selectedRoomPlace } from '../stores'
|
||||
|
||||
export let object: Room
|
||||
export let readonly: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let newName = getRoomName(object, $personByIdStore)
|
||||
let connecting = false
|
||||
|
||||
async function changeName (): Promise<void> {
|
||||
if (isOffice(object)) {
|
||||
return
|
||||
}
|
||||
|
||||
await client.diffUpdate(object, { name: newName })
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', { ignoreKeys: ['name'] })
|
||||
})
|
||||
|
||||
async function connect (): Promise<void> {
|
||||
connecting = true
|
||||
const place = $selectedRoomPlace
|
||||
await tryConnect(
|
||||
$personByIdStore,
|
||||
$myInfo,
|
||||
object,
|
||||
$infos,
|
||||
$myRequests,
|
||||
$invites,
|
||||
place?._id === object._id ? { x: place.x, y: place.y } : undefined
|
||||
)
|
||||
connecting = false
|
||||
selectedRoomPlace.set(undefined)
|
||||
closePanel()
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
let connectLabel: IntlString = love.string.StartMeeting
|
||||
|
||||
$: if ($infos.some(({ room }) => room === object._id) && !connecting) {
|
||||
connectLabel = love.string.JoinMeeting
|
||||
} else if (!connecting) {
|
||||
connectLabel = love.string.StartMeeting
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-row-stretch">
|
||||
<div class="row flex-grow">
|
||||
<div class="name">
|
||||
<EditBox
|
||||
disabled={readonly || isOffice(object)}
|
||||
placeholder={love.string.Room}
|
||||
on:change={changeName}
|
||||
bind:value={newName}
|
||||
focusIndex={1}
|
||||
/>
|
||||
</div>
|
||||
<ModernButton label={connectLabel} size="large" kind={'primary'} on:click={connect} loading={connecting} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.name {
|
||||
font-weight: 500;
|
||||
font-size: 1.25rem;
|
||||
color: var(--theme-caption-color);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-1);
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
@@ -13,23 +13,25 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AccountRole, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
||||
import { Breadcrumb, Header, IconEdit, ModernButton, Switcher } from '@hcengineering/ui'
|
||||
import { AccountRole, Ref, getCurrentAccount, hasAccountRole, WithLookup } from '@hcengineering/core'
|
||||
import { Breadcrumb, Header, IconEdit, ModernButton, Component } from '@hcengineering/ui'
|
||||
import { Floor, Room } from '@hcengineering/love'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { ViewletSelector } from '@hcengineering/view-resources'
|
||||
import { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||
|
||||
import lovePlg from '../plugin'
|
||||
import { currentRoom, floors } from '../stores'
|
||||
import ControlBar from './ControlBar.svelte'
|
||||
import MeetingsTable from './MeetingMinutesTable.svelte'
|
||||
import FloorView from './FloorView.svelte'
|
||||
|
||||
export let rooms: Room[] = []
|
||||
export let floor: Ref<Floor>
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let selectedViewlet: 'meetingMinutes' | 'floor' = 'floor'
|
||||
let viewlet: WithLookup<Viewlet> | undefined
|
||||
let preference: ViewletPreference | undefined
|
||||
let loading = false
|
||||
|
||||
$: selectedFloor = $floors.filter((fl) => fl._id === floor)[0]
|
||||
|
||||
@@ -43,18 +45,7 @@
|
||||
<Header allowFullsize adaptive={'disabled'}>
|
||||
<Breadcrumb title={selectedFloor?.name ?? ''} size={'large'} isCurrent />
|
||||
<svelte:fragment slot="beforeTitle">
|
||||
<Switcher
|
||||
selected={selectedViewlet}
|
||||
items={[
|
||||
{ id: 'floor', icon: lovePlg.icon.Love, tooltip: lovePlg.string.Floor },
|
||||
{ id: 'meetingMinutes', icon: view.icon.Table, tooltip: lovePlg.string.MeetingMinutes }
|
||||
]}
|
||||
kind="subtle"
|
||||
name="selector"
|
||||
on:select={(e) => {
|
||||
selectedViewlet = e.detail.id
|
||||
}}
|
||||
/>
|
||||
<ViewletSelector bind:viewlet bind:preference bind:loading viewletQuery={{ attachTo: lovePlg.class.Floor }} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
{#if editable}
|
||||
@@ -68,10 +59,8 @@
|
||||
</svelte:fragment>
|
||||
</Header>
|
||||
<div class="hulyComponent-content__column content">
|
||||
{#if selectedViewlet === 'meetingMinutes'}
|
||||
<MeetingsTable />
|
||||
{:else}
|
||||
<FloorView {rooms} />
|
||||
{#if viewlet?.$lookup?.descriptor?.component}
|
||||
<Component is={viewlet.$lookup.descriptor.component} props={{ floor, rooms }} on:open />
|
||||
{/if}
|
||||
</div>
|
||||
{#if $currentRoom}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { ObjectMention } from '@hcengineering/view-resources'
|
||||
import { Floor } from '@hcengineering/love'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
|
||||
import { floors } from '../stores'
|
||||
|
||||
export let value: Ref<Floor>
|
||||
export let inline: boolean = false
|
||||
|
||||
$: floor = $floors.find((f) => f._id === value)
|
||||
</script>
|
||||
|
||||
{#if floor}
|
||||
{#if inline}
|
||||
<ObjectMention object={floor} />
|
||||
{:else}
|
||||
<div class="flex-presenter overflow-label sm-tool-icon">
|
||||
{floor.name}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -35,7 +35,7 @@
|
||||
<Scroller padding="1rem" bottomPadding="4rem" horizontal>
|
||||
<FloorGrid bind:floorContainer {rows} preview>
|
||||
{#each rooms as room}
|
||||
<RoomPreview {room} info={getInfo(room._id, $infos)} />
|
||||
<RoomPreview {room} info={getInfo(room._id, $infos)} on:open />
|
||||
{/each}
|
||||
</FloorGrid>
|
||||
</Scroller>
|
||||
|
||||
@@ -83,6 +83,11 @@
|
||||
on:configure={() => (configure = false)}
|
||||
/>
|
||||
{:else}
|
||||
<Floor rooms={getRooms($rooms, selectedFloor)} floor={selectedFloor} on:configure={() => (configure = true)} />
|
||||
<Floor
|
||||
rooms={getRooms($rooms, selectedFloor)}
|
||||
floor={selectedFloor}
|
||||
on:configure={() => (configure = true)}
|
||||
on:open
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { RoomType } from '@hcengineering/love'
|
||||
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
import { currentRoom } from '../stores'
|
||||
import { screenSharing } from '../utils'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
import Hall from './Hall.svelte'
|
||||
import RoomComponent from './Room.svelte'
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
import { currentRoom } from '../stores'
|
||||
import { isConnected } from '../utils'
|
||||
import ActiveMeeting from './ActiveMeeting.svelte'
|
||||
|
||||
const localNav: boolean = $deviceInfo.navigator.visible
|
||||
const savedNav = localStorage.getItem('love-visibleNav')
|
||||
@@ -31,9 +31,9 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="hulyPanels-container" class:left-divider={$screenSharing || $currentRoom?.type === RoomType.Video}>
|
||||
{#if ($currentRoom !== undefined && $screenSharing) || $currentRoom?.type === RoomType.Video}
|
||||
<RoomComponent withVideo={$currentRoom.type === RoomType.Video} room={$currentRoom} />
|
||||
<div class="hulyPanels-container">
|
||||
{#if $currentRoom && $isConnected}
|
||||
<ActiveMeeting room={$currentRoom} />
|
||||
{:else}
|
||||
<Hall />
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Class, Doc, Ref, Space } from '@hcengineering/core'
|
||||
import { Label, Section } from '@hcengineering/ui'
|
||||
import { Table } from '@hcengineering/view-resources'
|
||||
import love from '@hcengineering/love'
|
||||
|
||||
export let objectId: Ref<Doc>
|
||||
export let space: Ref<Space>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let readonly: boolean = false
|
||||
export let meetings: number
|
||||
</script>
|
||||
|
||||
<Section label={love.string.MeetingMinutes} icon={love.icon.Cam}>
|
||||
<svelte:fragment slot="content">
|
||||
{#if meetings > 0}
|
||||
<Table
|
||||
_class={love.class.MeetingMinutes}
|
||||
config={['', 'transcription', 'messages']}
|
||||
query={{ attachedTo: objectId }}
|
||||
loadingProps={{ length: meetings }}
|
||||
{readonly}
|
||||
/>
|
||||
{:else}
|
||||
<div class="antiSection-empty solid flex-col mt-3">
|
||||
<span class="content-dark-color">
|
||||
<Label label={love.string.NoMeetingMinutes} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Section>
|
||||
@@ -1,21 +1,57 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { TableBrowser } from '@hcengineering/view-resources'
|
||||
import { Floor, Room } from '@hcengineering/love'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||
import core, { WithLookup } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
|
||||
import love from '../plugin'
|
||||
import lovePlg from '../plugin'
|
||||
|
||||
export let floor: Floor
|
||||
export let rooms: Room[] = []
|
||||
|
||||
const client = getClient()
|
||||
let viewlet: WithLookup<Viewlet> | undefined
|
||||
let viewOptions: ViewOptions | undefined
|
||||
let preference: ViewletPreference | undefined
|
||||
|
||||
const preferenceQuery = createQuery()
|
||||
|
||||
void client
|
||||
.findAll(
|
||||
view.class.Viewlet,
|
||||
{ _id: lovePlg.viewlet.TableMeetingMinutes },
|
||||
{ lookup: { descriptor: view.class.ViewletDescriptor } }
|
||||
)
|
||||
.then((res) => {
|
||||
viewlet = res[0]
|
||||
})
|
||||
|
||||
$: preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
space: core.space.Workspace,
|
||||
attachedTo: lovePlg.viewlet.TableMeetingMinutes
|
||||
},
|
||||
(res) => {
|
||||
preference = res[0]
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
</script>
|
||||
|
||||
<TableBrowser _class={love.class.MeetingMinutes} query={{}} config={['', 'modifiedOn']} />
|
||||
{#if viewlet?.$lookup?.descriptor?.component}
|
||||
<Component
|
||||
is={viewlet.$lookup.descriptor.component}
|
||||
props={{
|
||||
_class: lovePlg.class.MeetingMinutes,
|
||||
config: preference?.config ?? viewlet.config,
|
||||
options: viewlet.options,
|
||||
query: { attachedTo: { $in: rooms.map((p) => p._id) } },
|
||||
viewlet,
|
||||
viewOptions,
|
||||
viewOptionsConfig: viewlet.viewOptions?.other,
|
||||
enableChecking: false
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -327,21 +327,57 @@
|
||||
|
||||
const handleFullScreen = () => ($isFullScreen = document.fullscreenElement != null)
|
||||
|
||||
function toggleFullscreen () {
|
||||
if (!document.fullscreenElement) {
|
||||
function checkFullscreen (): void {
|
||||
const needFullScreen = $isFullScreen
|
||||
if (document.fullscreenElement && !needFullScreen) {
|
||||
document
|
||||
.exitFullscreen()
|
||||
.then(() => {
|
||||
$isFullScreen = false
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`Error exiting fullscreen mode: ${err.message} (${err.name})`)
|
||||
$isFullScreen = false
|
||||
})
|
||||
} else if (!document.fullscreenElement && needFullScreen) {
|
||||
roomEl
|
||||
.requestFullscreen()
|
||||
.then(() => ($isFullScreen = true))
|
||||
.then(() => {
|
||||
$isFullScreen = true
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`Error attempting to enable fullscreen mode: ${err.message} (${err.name})`)
|
||||
$isFullScreen = false
|
||||
})
|
||||
} else {
|
||||
document.exitFullscreen()
|
||||
$isFullScreen = false
|
||||
}
|
||||
}
|
||||
$: if (((document.fullscreenElement && !$isFullScreen) || $isFullScreen) && roomEl) toggleFullscreen()
|
||||
|
||||
function onFullScreen (): void {
|
||||
const needFullScreen = !$isFullScreen
|
||||
if (!document.fullscreenElement && needFullScreen) {
|
||||
roomEl
|
||||
.requestFullscreen()
|
||||
.then(() => {
|
||||
$isFullScreen = true
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`Error attempting to enable fullscreen mode: ${err.message} (${err.name})`)
|
||||
$isFullScreen = false
|
||||
})
|
||||
} else if (!needFullScreen) {
|
||||
document
|
||||
.exitFullscreen()
|
||||
.then(() => {
|
||||
$isFullScreen = false
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`Error exiting fullscreen mode: ${err.message} (${err.name})`)
|
||||
$isFullScreen = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$: if (((document.fullscreenElement && !$isFullScreen) || $isFullScreen) && roomEl) checkFullscreen()
|
||||
|
||||
function getActiveParticipants (participants: ParticipantData[]): ParticipantData[] {
|
||||
return participants.filter((p) => !p.isAgent || $infos.some(({ person }) => person === p._id))
|
||||
@@ -396,7 +432,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if $currentRoom}
|
||||
<ControlBar room={$currentRoom} fullScreen={$isFullScreen} />
|
||||
<ControlBar room={$currentRoom} fullScreen={$isFullScreen} {onFullScreen} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { Modal } from '@hcengineering/ui'
|
||||
import love, { RoomType } from '@hcengineering/love'
|
||||
|
||||
import { currentRoom } from '../stores'
|
||||
import RoomComponent from './Room.svelte'
|
||||
import { screenSharing } from '../utils'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: if ($currentRoom === undefined || (!$screenSharing && $currentRoom.type !== RoomType.Video)) {
|
||||
dispatch('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if ($currentRoom !== undefined && $screenSharing) || $currentRoom?.type === RoomType.Video}
|
||||
<Modal
|
||||
label={love.string.Room}
|
||||
type="type-popup"
|
||||
okLabel={presentation.string.Create}
|
||||
hideFooter
|
||||
padding="0"
|
||||
on:close={() => dispatch('close')}
|
||||
>
|
||||
<RoomComponent withVideo={$currentRoom.type === RoomType.Video} room={$currentRoom} />
|
||||
</Modal>
|
||||
{/if}
|
||||
@@ -17,11 +17,15 @@
|
||||
import { Avatar, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { IdMap, getCurrentAccount } from '@hcengineering/core'
|
||||
import { isOffice, ParticipantInfo, Room, RoomAccess, RoomType } from '@hcengineering/love'
|
||||
import { Icon, Label, eventToHTMLElement, showPopup, DropdownIntlItem } from '@hcengineering/ui'
|
||||
import { Icon, Label, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { openDoc } from '@hcengineering/view-resources'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
import love from '../plugin'
|
||||
import { invites, myInfo, myRequests } from '../stores'
|
||||
import { getRoomLabel, tryConnect } from '../utils'
|
||||
import { myInfo, selectedRoomPlace, currentRoom, meetingMinutesStore } from '../stores'
|
||||
import { getRoomLabel, lk } from '../utils'
|
||||
import PersonActionPopup from './PersonActionPopup.svelte'
|
||||
import RoomLanguage from './RoomLanguage.svelte'
|
||||
|
||||
@@ -36,7 +40,6 @@
|
||||
const meName = $personByIdStore.get(me.person)?.name
|
||||
const meAvatar = $personByIdStore.get(me.person)
|
||||
|
||||
let container: HTMLDivElement
|
||||
let hoveredRoomX: number | undefined = undefined
|
||||
let hoveredRoomY: number | undefined = undefined
|
||||
|
||||
@@ -61,12 +64,23 @@
|
||||
hovered = false
|
||||
}
|
||||
|
||||
function clickHandler (e: MouseEvent, x: number, y: number, person: Person | undefined): void {
|
||||
async function clickHandler (e: MouseEvent, x: number, y: number, person: Person | undefined): Promise<void> {
|
||||
if (person !== undefined) {
|
||||
if (room._id === $myInfo?.room || $myInfo === undefined) return
|
||||
showPopup(PersonActionPopup, { room, person: person._id }, eventToHTMLElement(e))
|
||||
} else {
|
||||
void tryConnect($personByIdStore, $myInfo, room, info, $myRequests, $invites, { x, y })
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
if ($currentRoom?._id === room._id) {
|
||||
const sid = await lk.getSid()
|
||||
const meetingMinutes =
|
||||
get(meetingMinutesStore) ?? (await client.findOne(love.class.MeetingMinutes, { sid, attachedTo: room._id }))
|
||||
if (meetingMinutes === undefined) return
|
||||
await openDoc(hierarchy, meetingMinutes)
|
||||
} else {
|
||||
selectedRoomPlace.set({ _id: room._id, x, y })
|
||||
await openDoc(hierarchy, room)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +126,6 @@
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<div
|
||||
bind:this={container}
|
||||
class="floorGrid-room"
|
||||
class:preview
|
||||
class:hovered
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
TrackPublication
|
||||
} from 'livekit-client'
|
||||
import { createEventDispatcher, onDestroy, onMount, tick } from 'svelte'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { MessageBox } from '@hcengineering/presentation'
|
||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import aiBot from '@hcengineering/ai-bot'
|
||||
import { personIdByAccountId } from '@hcengineering/contact-resources'
|
||||
|
||||
import love from '../plugin'
|
||||
import { currentRoom, infos, myInfo, myOffice } from '../stores'
|
||||
import {
|
||||
@@ -44,8 +50,6 @@
|
||||
setShare
|
||||
} from '../utils'
|
||||
import ParticipantView from './ParticipantView.svelte'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { MessageBox } from '@hcengineering/presentation'
|
||||
|
||||
export let isDock: boolean = false
|
||||
export let room: Ref<TypeRoom>
|
||||
@@ -57,8 +61,12 @@
|
||||
muted: boolean
|
||||
mirror: boolean
|
||||
connecting: boolean
|
||||
isAgent: boolean
|
||||
}
|
||||
|
||||
let aiPersonId: Ref<Person> | undefined = undefined
|
||||
$: aiPersonId = $personIdByAccountId.get(aiBot.account.AIBot as Ref<PersonAccount>)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let participants: ParticipantData[] = []
|
||||
@@ -126,7 +134,8 @@
|
||||
name: participant.name ?? '',
|
||||
muted: !participant.isMicrophoneEnabled,
|
||||
mirror: participant.isLocal,
|
||||
connecting: false
|
||||
connecting: false,
|
||||
isAgent: participant.isAgent
|
||||
})
|
||||
}
|
||||
participants = participants
|
||||
@@ -149,7 +158,8 @@
|
||||
name: participant.name ?? '',
|
||||
muted: !participant.isMicrophoneEnabled,
|
||||
mirror: participant.isLocal,
|
||||
connecting: false
|
||||
connecting: false,
|
||||
isAgent: participant.isAgent
|
||||
}
|
||||
participants.push(value)
|
||||
participants = participants
|
||||
@@ -227,7 +237,8 @@
|
||||
name: info.name,
|
||||
muted: true,
|
||||
mirror: false,
|
||||
connecting: true
|
||||
connecting: true,
|
||||
isAgent: info.person === aiPersonId
|
||||
}
|
||||
participants.push(value)
|
||||
}
|
||||
@@ -291,6 +302,12 @@
|
||||
}
|
||||
}, 10)
|
||||
}
|
||||
|
||||
function getActiveParticipants (participants: ParticipantData[]): ParticipantData[] {
|
||||
return participants.filter((p) => !p.isAgent || $infos.some(({ person }) => person === p._id))
|
||||
}
|
||||
|
||||
$: activeParticipants = getActiveParticipants(participants)
|
||||
</script>
|
||||
|
||||
<div class="antiPopup videoPopup-container" class:isDock>
|
||||
@@ -346,7 +363,7 @@
|
||||
<video class="screen" bind:this={screen}></video>
|
||||
</div>
|
||||
<Scroller bind:divScroll noStretch padding={'0 .5rem'} gap={'flex-gap-2'} onResize={dispatchFit} stickedScrollBars>
|
||||
{#each participants as participant, i (participant._id)}
|
||||
{#each activeParticipants as participant, i (participant._id)}
|
||||
<div class="video">
|
||||
<ParticipantView bind:this={participantElements[i]} {...participant} small />
|
||||
</div>
|
||||
|
||||
@@ -13,13 +13,15 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import love, { MeetingMinutes } from '@hcengineering/love'
|
||||
import love, { MeetingMinutes, Room } from '@hcengineering/love'
|
||||
import { ChannelEmbeddedContent } from '@hcengineering/chunter-resources'
|
||||
import { ActivityMessage } from '@hcengineering/activity'
|
||||
import { updateTabData, WidgetState } from '@hcengineering/workbench-resources'
|
||||
import MeetingWidgetHeader from './MeetingWidgetHeader.svelte'
|
||||
|
||||
export let widgetState: WidgetState
|
||||
export let meetingMinutes: MeetingMinutes
|
||||
export let room: Room
|
||||
export let height: string
|
||||
export let width: string
|
||||
|
||||
@@ -28,7 +30,6 @@
|
||||
}
|
||||
|
||||
function closeThread (): void {
|
||||
console.log('closeThread')
|
||||
updateTabData(love.ids.MeetingWidget, 'chat', { thread: undefined })
|
||||
}
|
||||
</script>
|
||||
@@ -42,4 +43,8 @@
|
||||
on:channel={closeThread}
|
||||
onReply={replyToThread}
|
||||
on:close
|
||||
/>
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
<MeetingWidgetHeader doc={meetingMinutes} {room} on:close />
|
||||
</svelte:fragment>
|
||||
</ChannelEmbeddedContent>
|
||||
|
||||
@@ -14,15 +14,14 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { closeWidget, minimizeSidebar, WidgetState } from '@hcengineering/workbench-resources'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import core, { Ref } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { MeetingMinutes, Room } from '@hcengineering/love'
|
||||
import { Loading } from '@hcengineering/ui'
|
||||
|
||||
import love from '../../plugin'
|
||||
import VideoTab from './VideoTab.svelte'
|
||||
import { isCurrentInstanceConnected, lk } from '../../utils'
|
||||
import { rooms } from '../../stores'
|
||||
import { currentRoom, meetingMinutesStore } from '../../stores'
|
||||
import ChatTab from './ChatTab.svelte'
|
||||
import TranscriptionTab from './TranscriptionTab.svelte'
|
||||
|
||||
@@ -31,39 +30,38 @@
|
||||
export let width: string
|
||||
|
||||
const meetingQuery = createQuery()
|
||||
const client = getClient()
|
||||
|
||||
let meetingMinutes: MeetingMinutes | undefined = undefined
|
||||
let isMeetingMinutesLoaded = false
|
||||
|
||||
let roomId: Ref<Room> | undefined = undefined
|
||||
let room: Room | undefined = undefined
|
||||
let sid: string | undefined = undefined
|
||||
|
||||
$: roomId = widgetState?.data?.room
|
||||
$: room = roomId !== undefined ? $rooms.find((r) => r._id === roomId) : undefined
|
||||
$: room = $currentRoom
|
||||
|
||||
void lk.getSid().then((res) => {
|
||||
sid = res
|
||||
})
|
||||
|
||||
$: if (!$isCurrentInstanceConnected || widgetState?.data?.room === undefined) {
|
||||
$: if (
|
||||
!$isCurrentInstanceConnected ||
|
||||
widgetState?.data?.room === undefined ||
|
||||
$currentRoom === undefined ||
|
||||
$currentRoom._id !== widgetState?.data?.room
|
||||
) {
|
||||
closeWidget(love.ids.MeetingWidget)
|
||||
}
|
||||
|
||||
$: if (roomId !== meetingMinutes?.room) {
|
||||
$: if (meetingMinutes?.sid !== sid) {
|
||||
meetingMinutes = undefined
|
||||
isMeetingMinutesLoaded = false
|
||||
}
|
||||
|
||||
$: if ($isCurrentInstanceConnected && room && sid) {
|
||||
meetingQuery.query(love.class.MeetingMinutes, { room: room._id, sid }, async (res) => {
|
||||
$: if (sid != null && room !== undefined) {
|
||||
meetingQuery.query(love.class.MeetingMinutes, { sid, attachedTo: room._id }, async (res) => {
|
||||
meetingMinutes = res[0]
|
||||
if (meetingMinutes !== undefined) {
|
||||
isMeetingMinutesLoaded = true
|
||||
} else {
|
||||
void createMeetingMinutes()
|
||||
}
|
||||
meetingMinutesStore.set(meetingMinutes)
|
||||
isMeetingMinutesLoaded = true
|
||||
})
|
||||
} else {
|
||||
meetingQuery.unsubscribe()
|
||||
@@ -71,16 +69,6 @@
|
||||
isMeetingMinutesLoaded = sid !== undefined
|
||||
}
|
||||
|
||||
async function createMeetingMinutes (): Promise<void> {
|
||||
if (sid === undefined || room === undefined) return
|
||||
const dateStr = new Date().toISOString().replace('T', '_').slice(0, 19)
|
||||
await client.createDoc(love.class.MeetingMinutes, core.space.Workspace, {
|
||||
title: room.name + '_' + dateStr,
|
||||
room: room._id,
|
||||
sid
|
||||
})
|
||||
}
|
||||
|
||||
function handleClose (): void {
|
||||
minimizeSidebar()
|
||||
}
|
||||
@@ -88,18 +76,18 @@
|
||||
|
||||
{#if widgetState && room}
|
||||
{#if widgetState.tab === 'video'}
|
||||
<VideoTab {room} />
|
||||
<VideoTab {room} doc={meetingMinutes} on:close={handleClose} />
|
||||
{:else if widgetState.tab === 'chat'}
|
||||
{#if !isMeetingMinutesLoaded}
|
||||
<Loading />
|
||||
{:else if meetingMinutes}
|
||||
<ChatTab {meetingMinutes} {widgetState} {height} {width} on:close={handleClose} />
|
||||
<ChatTab {meetingMinutes} {room} {widgetState} {height} {width} on:close={handleClose} />
|
||||
{/if}
|
||||
{:else if widgetState.tab === 'transcription'}
|
||||
{#if !isMeetingMinutesLoaded}
|
||||
<Loading />
|
||||
{:else if meetingMinutes}
|
||||
<TranscriptionTab {meetingMinutes} {widgetState} {height} {width} on:close={handleClose} />
|
||||
<TranscriptionTab {meetingMinutes} {room} {widgetState} {height} {width} on:close={handleClose} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import {
|
||||
Breadcrumbs,
|
||||
Header,
|
||||
BreadcrumbItem,
|
||||
IconMaximize,
|
||||
ButtonIcon,
|
||||
showPopup,
|
||||
PopupResult
|
||||
} from '@hcengineering/ui'
|
||||
import { MeetingMinutes, Room, RoomType } from '@hcengineering/love'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
import love from '../../plugin'
|
||||
import RoomModal from '../RoomModal.svelte'
|
||||
import { currentRoom } from '../../stores'
|
||||
import { screenSharing } from '../../utils'
|
||||
|
||||
export let room: Room
|
||||
export let doc: MeetingMinutes | undefined = undefined
|
||||
|
||||
let breadcrumbs: BreadcrumbItem[]
|
||||
let popup: PopupResult | undefined
|
||||
|
||||
$: breadcrumbs = [
|
||||
{
|
||||
id: 'meeting',
|
||||
icon: love.icon.Cam,
|
||||
title: doc?.title ?? room.name
|
||||
}
|
||||
]
|
||||
|
||||
function maximize (): void {
|
||||
popup = showPopup(RoomModal, { room }, 'full-centered')
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
popup?.close()
|
||||
})
|
||||
</script>
|
||||
|
||||
<Header type={'type-aside'} adaptive={'disabled'} closeOnEscape={false} on:close>
|
||||
<Breadcrumbs items={breadcrumbs} currentOnly />
|
||||
<svelte:fragment slot="actions">
|
||||
{#if ($currentRoom !== undefined && $screenSharing) || $currentRoom?.type === RoomType.Video}
|
||||
<ButtonIcon icon={IconMaximize} kind="tertiary" size="small" noPrint on:click={maximize} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Header>
|
||||
@@ -13,31 +13,28 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import love, { MeetingMinutes } from '@hcengineering/love'
|
||||
import { MeetingMinutes, Room } from '@hcengineering/love'
|
||||
import { ChannelEmbeddedContent } from '@hcengineering/chunter-resources'
|
||||
import { ActivityMessage } from '@hcengineering/activity'
|
||||
import { updateTabData, WidgetState } from '@hcengineering/workbench-resources'
|
||||
import { WidgetState } from '@hcengineering/workbench-resources'
|
||||
import MeetingWidgetHeader from './MeetingWidgetHeader.svelte'
|
||||
|
||||
export let widgetState: WidgetState
|
||||
export let meetingMinutes: MeetingMinutes
|
||||
export let room: Room
|
||||
export let height: string
|
||||
export let width: string
|
||||
|
||||
function replyToThread (message: ActivityMessage): void {
|
||||
updateTabData(love.ids.MeetingWidget, 'transcription', { thread: message._id })
|
||||
}
|
||||
function closeThread (): void {
|
||||
updateTabData(love.ids.MeetingWidget, 'transcription', { thread: undefined })
|
||||
}
|
||||
</script>
|
||||
|
||||
<ChannelEmbeddedContent
|
||||
{width}
|
||||
{height}
|
||||
readonly
|
||||
object={meetingMinutes}
|
||||
threadId={widgetState.tabs.find((tab) => tab.id === 'transcription')?.data?.thread}
|
||||
threadId={undefined}
|
||||
collection="transcription"
|
||||
on:channel={closeThread}
|
||||
onReply={replyToThread}
|
||||
on:close
|
||||
/>
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
<MeetingWidgetHeader doc={meetingMinutes} {room} on:close />
|
||||
</svelte:fragment>
|
||||
</ChannelEmbeddedContent>
|
||||
|
||||
@@ -13,13 +13,16 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Room } from '@hcengineering/love'
|
||||
import { MeetingMinutes, Room } from '@hcengineering/love'
|
||||
|
||||
import VideoPopup from '../VideoPopup.svelte'
|
||||
import MeetingWidgetHeader from './MeetingWidgetHeader.svelte'
|
||||
|
||||
export let room: Room
|
||||
export let doc: MeetingMinutes | undefined = undefined
|
||||
</script>
|
||||
|
||||
<MeetingWidgetHeader {doc} {room} on:close />
|
||||
<div class="root">
|
||||
<VideoPopup room={room._id} isDock canUnpin={false} />
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,12 @@ import WorkbenchExtension from './components/WorkbenchExtension.svelte'
|
||||
import LoveWidget from './components/LoveWidget.svelte'
|
||||
import MeetingWidget from './components/widget/MeetingWidget.svelte'
|
||||
import MeetingMinutesPresenter from './components/MeetingMinutesPresenter.svelte'
|
||||
import MeetingMinutesSection from './components/MeetingMinutesSection.svelte'
|
||||
import EditMeetingMinutes from './components/EditMeetingMinutes.svelte'
|
||||
import EditRoom from './components/EditRoom.svelte'
|
||||
import FloorAttributePresenter from './components/FloorAttributePresenter.svelte'
|
||||
import FloorView from './components/FloorView.svelte'
|
||||
import MeetingMinutesTable from './components/MeetingMinutesTable.svelte'
|
||||
|
||||
import {
|
||||
copyGuestLink,
|
||||
@@ -20,7 +26,8 @@ import {
|
||||
startTranscription,
|
||||
stopTranscription,
|
||||
toggleMic,
|
||||
toggleVideo
|
||||
toggleVideo,
|
||||
getMeetingMinutesTitle
|
||||
} from './utils'
|
||||
|
||||
export { setCustomCreateScreenTracks } from './utils'
|
||||
@@ -36,7 +43,13 @@ export default async (): Promise<Resources> => ({
|
||||
EditMeetingData,
|
||||
LoveWidget,
|
||||
MeetingWidget,
|
||||
MeetingMinutesPresenter
|
||||
MeetingMinutesPresenter,
|
||||
MeetingMinutesSection,
|
||||
EditMeetingMinutes,
|
||||
EditRoom,
|
||||
FloorAttributePresenter,
|
||||
FloorView,
|
||||
MeetingMinutesTable
|
||||
},
|
||||
function: {
|
||||
CreateMeeting: createMeeting,
|
||||
@@ -50,7 +63,8 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
CanCopyGuestLink: () => {
|
||||
return hasAccountRole(getCurrentAccount(), AccountRole.User)
|
||||
}
|
||||
},
|
||||
MeetingMinutesTitleProvider: getMeetingMinutesTitle
|
||||
},
|
||||
actionImpl: {
|
||||
ToggleMic: toggleMic,
|
||||
|
||||
@@ -24,7 +24,13 @@ export default mergeIds(loveId, love, {
|
||||
ControlExt: '' as AnyComponent,
|
||||
MeetingData: '' as AnyComponent,
|
||||
EditMeetingData: '' as AnyComponent,
|
||||
MeetingMinutesPresenter: '' as AnyComponent
|
||||
MeetingMinutesPresenter: '' as AnyComponent,
|
||||
MeetingMinutesSection: '' as AnyComponent,
|
||||
EditMeetingMinutes: '' as AnyComponent,
|
||||
EditRoom: '' as AnyComponent,
|
||||
FloorAttributePresenter: '' as AnyComponent,
|
||||
MeetingMinutesTable: '' as AnyComponent,
|
||||
FloorView: '' as AnyComponent
|
||||
},
|
||||
function: {
|
||||
CreateMeeting: '' as Resource<DocCreateFunction>,
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
type JoinRequest,
|
||||
type Office,
|
||||
type ParticipantInfo,
|
||||
type Room
|
||||
type Room,
|
||||
type MeetingMinutes
|
||||
} from '@hcengineering/love'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { derived, get, writable } from 'svelte/store'
|
||||
@@ -59,6 +60,9 @@ export const activeInvites = derived(invites, (val) => {
|
||||
export const myPreferences = writable<DevicesPreference | undefined>()
|
||||
export let $myPreferences: DevicesPreference | undefined
|
||||
|
||||
export const meetingMinutesStore = writable<MeetingMinutes | undefined>(undefined)
|
||||
export const selectedRoomPlace = writable<{ _id: Ref<Room>, x: number, y: number } | undefined>(undefined)
|
||||
|
||||
function filterParticipantInfo (value: ParticipantInfo[]): ParticipantInfo[] {
|
||||
const map = new Map<string, ParticipantInfo>()
|
||||
const aiPersonId = get(personIdByAccountId).get(aiBot.account.AIBot as Ref<PersonAccount>)
|
||||
|
||||
@@ -5,8 +5,10 @@ import core, {
|
||||
AccountRole,
|
||||
concatLink,
|
||||
type Data,
|
||||
generateId,
|
||||
getCurrentAccount,
|
||||
type IdMap,
|
||||
makeCollaborativeDoc,
|
||||
type Ref,
|
||||
type Space,
|
||||
type TxOperations
|
||||
@@ -27,7 +29,8 @@ import {
|
||||
RoomAccess,
|
||||
RoomType,
|
||||
TranscriptionStatus,
|
||||
type RoomMetadata
|
||||
type RoomMetadata,
|
||||
type MeetingMinutes
|
||||
} from '@hcengineering/love'
|
||||
import { getEmbeddedLabel, getMetadata, getResource, type IntlString } from '@hcengineering/platform'
|
||||
import presentation, {
|
||||
@@ -36,7 +39,7 @@ import presentation, {
|
||||
type DocCreatePhase,
|
||||
getClient
|
||||
} from '@hcengineering/presentation'
|
||||
import { type DropdownTextItem, getCurrentLocation, type Location, navigate, showPopup } from '@hcengineering/ui'
|
||||
import { type DropdownTextItem, getCurrentLocation, navigate, showPopup } from '@hcengineering/ui'
|
||||
import { isKrispNoiseFilterSupported, KrispNoiseFilter } from '@livekit/krisp-noise-filter'
|
||||
import { BackgroundBlur, type BackgroundOptions, type ProcessorWrapper } from '@livekit/track-processors'
|
||||
import {
|
||||
@@ -57,13 +60,20 @@ import {
|
||||
import { get, writable } from 'svelte/store'
|
||||
import aiBot from '@hcengineering/ai-bot'
|
||||
import { connectMeeting, disconnectMeeting } from '@hcengineering/ai-bot-resources'
|
||||
import { openWidget, sidebarStore, updateWidgetState } from '@hcengineering/workbench-resources'
|
||||
import {
|
||||
openWidget,
|
||||
sidebarStore,
|
||||
updateWidgetState,
|
||||
currentWorkspaceStore,
|
||||
openWidgetTab
|
||||
} from '@hcengineering/workbench-resources'
|
||||
import { type Widget, type WidgetTab } from '@hcengineering/workbench'
|
||||
import view from '@hcengineering/view'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
|
||||
import { sendMessage } from './broadcast'
|
||||
import love from './plugin'
|
||||
import { $myPreferences, currentRoom } from './stores'
|
||||
import { $myPreferences, meetingMinutesStore, currentRoom } from './stores'
|
||||
import RoomSettingsPopup from './components/RoomSettingsPopup.svelte'
|
||||
|
||||
export const selectedCamId = 'selectedDevice_cam'
|
||||
@@ -92,7 +102,9 @@ export async function getToken (
|
||||
|
||||
function getTokenRoomName (roomName: string, roomId: Ref<Room>): string {
|
||||
const loc = getCurrentLocation()
|
||||
return `${loc.path[1]}_${roomName}_${roomId}`
|
||||
const currentWorkspace = get(currentWorkspaceStore)
|
||||
|
||||
return `${currentWorkspace?.workspaceId ?? loc.path[1]}_${roomName}_${roomId}`
|
||||
}
|
||||
|
||||
export const lk: LKRoom = new LKRoom({
|
||||
@@ -432,6 +444,7 @@ export async function disconnect (): Promise<void> {
|
||||
isMicEnabled.set(false)
|
||||
isCameraEnabled.set(false)
|
||||
isSharingEnabled.set(false)
|
||||
meetingMinutesStore.set(undefined)
|
||||
sendMessage({ type: 'mic', value: false })
|
||||
sendMessage({ type: 'cam', value: false })
|
||||
sendMessage({ type: 'share', value: false })
|
||||
@@ -580,6 +593,46 @@ async function connectLK (currentPerson: Person, room: Room): Promise<void> {
|
||||
])
|
||||
}
|
||||
|
||||
async function createMeetingMinutes (room: Room): Promise<void> {
|
||||
const client = getClient()
|
||||
const sid = await lk.getSid()
|
||||
|
||||
if (sid !== undefined) {
|
||||
const doc = await client.findOne(love.class.MeetingMinutes, { sid })
|
||||
|
||||
if (doc === undefined) {
|
||||
const dateStr = new Date().toISOString().replace('T', ' ').slice(0, 19)
|
||||
const _id = generateId<MeetingMinutes>()
|
||||
const newDoc: MeetingMinutes = {
|
||||
_id,
|
||||
_class: love.class.MeetingMinutes,
|
||||
sid,
|
||||
attachedTo: room._id,
|
||||
attachedToClass: room._class,
|
||||
collection: 'meetings',
|
||||
space: core.space.Workspace,
|
||||
title: room.name + ' ' + dateStr,
|
||||
description: makeCollaborativeDoc(_id, 'description'),
|
||||
modifiedBy: getCurrentAccount()._id,
|
||||
modifiedOn: Date.now()
|
||||
}
|
||||
|
||||
await client.addCollection(
|
||||
love.class.MeetingMinutes,
|
||||
core.space.Workspace,
|
||||
room._id,
|
||||
room._class,
|
||||
'meetings',
|
||||
{ sid, title: newDoc.title, description: newDoc.description },
|
||||
_id
|
||||
)
|
||||
meetingMinutesStore.set(newDoc)
|
||||
} else {
|
||||
meetingMinutesStore.set(doc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function connectRoom (
|
||||
x: number,
|
||||
y: number,
|
||||
@@ -590,6 +643,7 @@ export async function connectRoom (
|
||||
await disconnect()
|
||||
await moveToRoom(x, y, currentInfo, currentPerson, room, getMetadata(presentation.metadata.SessionId) ?? null)
|
||||
await connectLK(currentPerson, room)
|
||||
await createMeetingMinutes(room)
|
||||
}
|
||||
|
||||
export const joinRequest: Ref<JoinRequest> | undefined = undefined
|
||||
@@ -910,13 +964,13 @@ export function isTranscriptionAllowed (): boolean {
|
||||
return url !== ''
|
||||
}
|
||||
|
||||
export function createMeetingWidget (widget: Widget, room: Ref<Room>, loc: Location, video: boolean): void {
|
||||
export function createMeetingWidget (widget: Widget, room: Ref<Room>, video: boolean): void {
|
||||
const tabs: WidgetTab[] = [
|
||||
...(video
|
||||
? [
|
||||
{
|
||||
id: 'video',
|
||||
name: 'Video',
|
||||
label: love.string.Video,
|
||||
icon: love.icon.Cam,
|
||||
readonly: true
|
||||
}
|
||||
@@ -924,13 +978,13 @@ export function createMeetingWidget (widget: Widget, room: Ref<Room>, loc: Locat
|
||||
: []),
|
||||
{
|
||||
id: 'chat',
|
||||
name: 'Chat',
|
||||
label: chunter.string.Chat,
|
||||
icon: view.icon.Bubble,
|
||||
readonly: true
|
||||
},
|
||||
{
|
||||
id: 'transcription',
|
||||
name: 'Transcription',
|
||||
label: love.string.Transcription,
|
||||
icon: view.icon.Feather,
|
||||
readonly: true
|
||||
}
|
||||
@@ -940,12 +994,12 @@ export function createMeetingWidget (widget: Widget, room: Ref<Room>, loc: Locat
|
||||
{
|
||||
room
|
||||
},
|
||||
{ active: loc.path[2] !== loveId, openedByUser: false },
|
||||
{ active: true, openedByUser: false },
|
||||
tabs
|
||||
)
|
||||
}
|
||||
|
||||
export function createMeetingVideoWidgetTab (widget: Widget, loc: Location): void {
|
||||
export function createMeetingVideoWidgetTab (widget: Widget): void {
|
||||
const state = get(sidebarStore)
|
||||
const { widgetsState } = state
|
||||
const widgetState = widgetsState.get(widget._id)
|
||||
@@ -954,12 +1008,23 @@ export function createMeetingVideoWidgetTab (widget: Widget, loc: Location): voi
|
||||
|
||||
const tab: WidgetTab = {
|
||||
id: 'video',
|
||||
name: 'Video',
|
||||
label: love.string.Video,
|
||||
icon: love.icon.Cam,
|
||||
readonly: true
|
||||
}
|
||||
updateWidgetState(widget._id, {
|
||||
tabs: [tab, ...widgetState.tabs],
|
||||
tab: state.widget === widget._id && loc.path[2] === loveId ? widgetState.tab : 'video'
|
||||
tab: 'video'
|
||||
})
|
||||
openWidgetTab(love.ids.MeetingWidget, 'video')
|
||||
}
|
||||
|
||||
export async function getMeetingMinutesTitle (
|
||||
client: TxOperations,
|
||||
ref: Ref<MeetingMinutes>,
|
||||
doc?: MeetingMinutes
|
||||
): Promise<string> {
|
||||
const meeting = doc ?? (await client.findOne(love.class.MeetingMinutes, { _id: ref }))
|
||||
|
||||
return meeting?.title ?? ''
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Event } from '@hcengineering/calendar'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { Class, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||
import { AttachedDoc, Class, CollaborativeDoc, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||
import { Drive } from '@hcengineering/drive'
|
||||
import { NotificationType } from '@hcengineering/notification'
|
||||
import { Asset, IntlString, Metadata, Plugin, plugin } from '@hcengineering/platform'
|
||||
import { Preference } from '@hcengineering/preference'
|
||||
import { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
import { Action } from '@hcengineering/view'
|
||||
import { Action, Viewlet, ViewletDescriptor } from '@hcengineering/view'
|
||||
import { Widget } from '@hcengineering/workbench'
|
||||
|
||||
export const loveId = 'love' as Plugin
|
||||
@@ -102,6 +102,9 @@ export interface Room extends Doc {
|
||||
y: number
|
||||
language: RoomLanguage
|
||||
startWithTranscription: boolean
|
||||
description: CollaborativeDoc
|
||||
attachments?: number
|
||||
meetings?: number
|
||||
}
|
||||
|
||||
export interface Office extends Room {
|
||||
@@ -155,12 +158,13 @@ export interface DevicesPreference extends Preference {
|
||||
camEnabled: boolean
|
||||
}
|
||||
|
||||
export interface MeetingMinutes extends Doc {
|
||||
export interface MeetingMinutes extends AttachedDoc {
|
||||
sid: string
|
||||
title: string
|
||||
room: Ref<Room>
|
||||
transcription?: number
|
||||
messages?: number
|
||||
description: CollaborativeDoc
|
||||
attachments?: number
|
||||
}
|
||||
|
||||
export * from './utils'
|
||||
@@ -200,7 +204,11 @@ const love = plugin(loveId, {
|
||||
Meeting: '' as IntlString,
|
||||
Transcription: '' as IntlString,
|
||||
StartWithTranscription: '' as IntlString,
|
||||
MeetingMinutes: '' as IntlString
|
||||
MeetingMinutes: '' as IntlString,
|
||||
StartMeeting: '' as IntlString,
|
||||
Video: '' as IntlString,
|
||||
NoMeetingMinutes: '' as IntlString,
|
||||
JoinMeeting: '' as IntlString
|
||||
},
|
||||
ids: {
|
||||
MainFloor: '' as Ref<Floor>,
|
||||
@@ -243,6 +251,13 @@ const love = plugin(loveId, {
|
||||
},
|
||||
component: {
|
||||
SelectScreenSourcePopup: '' as AnyComponent
|
||||
},
|
||||
viewlet: {
|
||||
TableMeetingMinutes: '' as Ref<Viewlet>,
|
||||
MeetingMinutesDescriptor: '' as Ref<ViewletDescriptor>,
|
||||
FloorDescriptor: '' as Ref<ViewletDescriptor>,
|
||||
Floor: '' as Ref<Viewlet>,
|
||||
FloorMeetingMinutes: '' as Ref<Viewlet>
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
+30
-10
@@ -1,5 +1,5 @@
|
||||
import { Employee, Person } from '@hcengineering/contact'
|
||||
import { Data, Ref } from '@hcengineering/core'
|
||||
import { Data, generateId, makeCollaborativeDoc, Ref } from '@hcengineering/core'
|
||||
|
||||
import love, { Office, Room, ParticipantInfo, RoomAccess, RoomType, GRID_WIDTH } from '.'
|
||||
|
||||
@@ -15,11 +15,13 @@ export function isOffice (room: Data<Room>): room is Office {
|
||||
return (room as Office).person !== undefined
|
||||
}
|
||||
|
||||
export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Office>[] {
|
||||
const res: Data<Room | Office>[] = []
|
||||
export function createDefaultRooms (employees: Ref<Employee>[]): (Data<Room | Office> & { _id: Ref<Room> })[] {
|
||||
const res: (Data<Room | Office> & { _id: Ref<Room> })[] = []
|
||||
// create 12 offices
|
||||
for (let index = 0; index < 12; index++) {
|
||||
const office: Data<Office> = {
|
||||
const _id = generateId<Office>()
|
||||
const office: Data<Office> & { _id: Ref<Office> } = {
|
||||
_id,
|
||||
name: '',
|
||||
type: RoomType.Audio,
|
||||
access: RoomAccess.Knock,
|
||||
@@ -30,11 +32,15 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
||||
y: index - (index % 2),
|
||||
person: employees[index] ?? null,
|
||||
language: 'en',
|
||||
startWithTranscription: false
|
||||
startWithTranscription: false,
|
||||
description: makeCollaborativeDoc(_id, 'description')
|
||||
}
|
||||
res.push(office)
|
||||
}
|
||||
const allHands = generateId<Room>()
|
||||
|
||||
res.push({
|
||||
_id: allHands,
|
||||
name: 'All hands',
|
||||
type: RoomType.Video,
|
||||
access: RoomAccess.Open,
|
||||
@@ -44,9 +50,13 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
||||
x: 6,
|
||||
y: 0,
|
||||
language: 'en',
|
||||
startWithTranscription: true
|
||||
startWithTranscription: true,
|
||||
description: makeCollaborativeDoc(allHands, 'description')
|
||||
})
|
||||
|
||||
const meetingRoom1 = generateId<Room>()
|
||||
res.push({
|
||||
_id: meetingRoom1,
|
||||
name: 'Meeting Room 1',
|
||||
type: RoomType.Video,
|
||||
access: RoomAccess.Open,
|
||||
@@ -56,9 +66,12 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
||||
x: 6,
|
||||
y: 4,
|
||||
language: 'en',
|
||||
startWithTranscription: true
|
||||
startWithTranscription: true,
|
||||
description: makeCollaborativeDoc(meetingRoom1, 'description')
|
||||
})
|
||||
const meetingRoom2 = generateId<Room>()
|
||||
res.push({
|
||||
_id: meetingRoom2,
|
||||
name: 'Meeting Room 2',
|
||||
type: RoomType.Video,
|
||||
access: RoomAccess.Open,
|
||||
@@ -68,9 +81,12 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
||||
x: 11,
|
||||
y: 4,
|
||||
language: 'en',
|
||||
startWithTranscription: true
|
||||
startWithTranscription: true,
|
||||
description: makeCollaborativeDoc(meetingRoom2, 'description')
|
||||
})
|
||||
const voiceRoom1 = generateId<Room>()
|
||||
res.push({
|
||||
_id: voiceRoom1,
|
||||
name: 'Voice Room 1',
|
||||
type: RoomType.Audio,
|
||||
access: RoomAccess.Open,
|
||||
@@ -80,9 +96,12 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
||||
x: 6,
|
||||
y: 8,
|
||||
language: 'en',
|
||||
startWithTranscription: true
|
||||
startWithTranscription: false,
|
||||
description: makeCollaborativeDoc(voiceRoom1, 'description')
|
||||
})
|
||||
const voiceRoom2 = generateId<Room>()
|
||||
res.push({
|
||||
_id: voiceRoom2,
|
||||
name: 'Voice Room 2',
|
||||
type: RoomType.Audio,
|
||||
access: RoomAccess.Open,
|
||||
@@ -92,7 +111,8 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
||||
x: 11,
|
||||
y: 8,
|
||||
language: 'en',
|
||||
startWithTranscription: true
|
||||
startWithTranscription: false,
|
||||
description: makeCollaborativeDoc(voiceRoom2, 'description')
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -226,49 +226,45 @@ export async function unarchiveContextNotifications (doc?: DocNotifyContext): Pr
|
||||
}
|
||||
}
|
||||
|
||||
enum OpWithMe {
|
||||
Add = 'add',
|
||||
Remove = 'remove'
|
||||
}
|
||||
|
||||
async function updateMeInCollaborators (
|
||||
export async function subscribeDoc (
|
||||
client: TxOperations,
|
||||
docClass: Ref<Class<Doc>>,
|
||||
docId: Ref<Doc>,
|
||||
op: OpWithMe
|
||||
op: 'add' | 'remove',
|
||||
doc?: Doc
|
||||
): Promise<void> {
|
||||
const me = getCurrentAccount()._id
|
||||
const hierarchy = client.getHierarchy()
|
||||
const target = await client.findOne(docClass, { _id: docId })
|
||||
if (target !== undefined) {
|
||||
if (hierarchy.hasMixin(target, notification.mixin.Collaborators)) {
|
||||
const collab = hierarchy.as(target, notification.mixin.Collaborators)
|
||||
let collabUpdate: DocumentUpdate<Collaborators> | undefined
|
||||
|
||||
if (collab.collaborators.includes(me) && op === OpWithMe.Remove) {
|
||||
collabUpdate = {
|
||||
$pull: {
|
||||
collaborators: me
|
||||
}
|
||||
}
|
||||
} else if (!collab.collaborators.includes(me) && op === OpWithMe.Add) {
|
||||
collabUpdate = {
|
||||
$push: {
|
||||
collaborators: me
|
||||
}
|
||||
if (hierarchy.classHierarchyMixin(docClass, notification.mixin.ClassCollaborators) === undefined) return
|
||||
|
||||
const target = doc ?? (await client.findOne(docClass, { _id: docId }))
|
||||
if (target === undefined) return
|
||||
if (hierarchy.hasMixin(target, notification.mixin.Collaborators)) {
|
||||
const collab = hierarchy.as(target, notification.mixin.Collaborators)
|
||||
let collabUpdate: DocumentUpdate<Collaborators> | undefined
|
||||
|
||||
if (collab.collaborators.includes(me) && op === 'remove') {
|
||||
collabUpdate = {
|
||||
$pull: {
|
||||
collaborators: me
|
||||
}
|
||||
}
|
||||
|
||||
if (collabUpdate !== undefined) {
|
||||
await client.updateMixin(
|
||||
collab._id,
|
||||
collab._class,
|
||||
collab.space,
|
||||
notification.mixin.Collaborators,
|
||||
collabUpdate
|
||||
)
|
||||
} else if (!collab.collaborators.includes(me) && op === 'add') {
|
||||
collabUpdate = {
|
||||
$push: {
|
||||
collaborators: me
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collabUpdate !== undefined) {
|
||||
await client.updateMixin(collab._id, collab._class, collab.space, notification.mixin.Collaborators, collabUpdate)
|
||||
}
|
||||
} else if (op === 'add') {
|
||||
await client.createMixin(docId, docClass, target.space, notification.mixin.Collaborators, {
|
||||
collaborators: [me]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +273,7 @@ async function updateMeInCollaborators (
|
||||
*/
|
||||
export async function unsubscribe (context: DocNotifyContext): Promise<void> {
|
||||
const client = getClient()
|
||||
await updateMeInCollaborators(client, context.objectClass, context.objectId, OpWithMe.Remove)
|
||||
await subscribeDoc(client, context.objectClass, context.objectId, 'remove')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,7 +281,7 @@ export async function unsubscribe (context: DocNotifyContext): Promise<void> {
|
||||
*/
|
||||
export async function subscribe (docClass: Ref<Class<Doc>>, docId: Ref<Doc>): Promise<void> {
|
||||
const client = getClient()
|
||||
await updateMeInCollaborators(client, docClass, docId, OpWithMe.Add)
|
||||
await subscribeDoc(client, docClass, docId, 'add')
|
||||
}
|
||||
|
||||
export async function pinDocNotifyContext (object: DocNotifyContext): Promise<void> {
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let embedded: boolean = false
|
||||
export let readonly: boolean = false
|
||||
export let selectedAside: boolean | undefined = undefined
|
||||
|
||||
let realObjectClass: Ref<Class<Doc>> = _class
|
||||
let lastId: Ref<Doc> | undefined
|
||||
@@ -225,6 +226,7 @@
|
||||
allowClose={!embedded}
|
||||
isAside={true}
|
||||
{embedded}
|
||||
{selectedAside}
|
||||
bind:content
|
||||
bind:panelWidth
|
||||
bind:innerWidth
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Class, Doc, DocumentQuery, Ref, SortingOrder, Space } from '@hcengineering/core'
|
||||
import { Class, Doc, DocumentQuery, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Action, IconEdit, navigate, type Location } from '@hcengineering/ui'
|
||||
import { Action, IconEdit, navigate, type Location, Scroller } from '@hcengineering/ui'
|
||||
import { getResource, type Resource } from '@hcengineering/platform'
|
||||
import { IntlString, Asset } from '@hcengineering/platform'
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
foldersState = newState
|
||||
})
|
||||
|
||||
let selected: Ref<Doc> | undefined
|
||||
let selected: Ref<Doc> = noParentId
|
||||
let visibleItem: Doc | undefined
|
||||
|
||||
const q = createQuery()
|
||||
@@ -65,7 +65,6 @@
|
||||
async function handleFolderSelected (_id: Ref<Doc>): Promise<void> {
|
||||
selected = _id
|
||||
visibleItem = selected !== undefined ? foldersState.folderById.get(selected) : undefined
|
||||
const folder = foldersState.folderById.get(_id)
|
||||
if (getFolderLink) {
|
||||
const getFolderLinkFunction = await getResource(getFolderLink)
|
||||
navigate(getFolderLinkFunction(_id))
|
||||
@@ -100,7 +99,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="folders-browser">
|
||||
<Scroller padding={'1rem 0'}>
|
||||
<TreeNode
|
||||
_id={noParentId}
|
||||
icon={allObjectsIcon}
|
||||
@@ -139,11 +138,4 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TreeNode>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.folders-browser {
|
||||
display: flex;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
</Scroller>
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
Separator,
|
||||
showPopup,
|
||||
getLocation,
|
||||
resolvedLocationStore
|
||||
resolvedLocationStore,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
defineSeparators,
|
||||
workbenchSeparators
|
||||
} from '@hcengineering/ui'
|
||||
import { Doc, DocumentQuery, Ref, Space, mergeQueries } from '@hcengineering/core'
|
||||
import { IntlString, Asset } from '@hcengineering/platform'
|
||||
@@ -64,33 +67,56 @@
|
||||
if (createComponent === undefined) return
|
||||
showPopup(createComponent, { ...createComponentProps, space }, 'top')
|
||||
}
|
||||
defineSeparators('parentsNavigator', workbenchSeparators)
|
||||
</script>
|
||||
|
||||
<div class="hulyComponent-content__container columns">
|
||||
<div class="hulyComponent-content__column">
|
||||
<Header adaptive={'disabled'}>
|
||||
<Breadcrumb icon={navigationComponentIcon} label={navigationComponentLabel} size={'large'} />
|
||||
<svelte:fragment slot="actions">
|
||||
{#if createComponent}
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind={'icon'}
|
||||
on:click={() => {
|
||||
showCreateDialog()
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Header>
|
||||
<Component
|
||||
is={navigationComponent}
|
||||
props={{
|
||||
...navigationComponentProps,
|
||||
query: spaceQuery
|
||||
}}
|
||||
{#if $deviceInfo.navigator.visible}
|
||||
<div
|
||||
class="antiPanel-navigator {$deviceInfo.navigator.direction === 'horizontal'
|
||||
? 'portrait'
|
||||
: 'landscape'} border-left"
|
||||
>
|
||||
<div class="hulyComponent-content__column">
|
||||
<Header adaptive={'disabled'}>
|
||||
<Breadcrumb icon={navigationComponentIcon} label={navigationComponentLabel} size={'large'} />
|
||||
<svelte:fragment slot="actions">
|
||||
{#if createComponent}
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind={'icon'}
|
||||
on:click={() => {
|
||||
showCreateDialog()
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Header>
|
||||
<Component
|
||||
is={navigationComponent}
|
||||
props={{
|
||||
...navigationComponentProps,
|
||||
query: spaceQuery
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Separator
|
||||
name={'parentsNavigator'}
|
||||
float={$deviceInfo.navigator.float ? 'navigator' : true}
|
||||
index={0}
|
||||
color={'transparent'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator
|
||||
name={'parentsNavigator'}
|
||||
float={$deviceInfo.navigator.float}
|
||||
index={0}
|
||||
color={'transparent'}
|
||||
separatorSize={0}
|
||||
short
|
||||
/>
|
||||
</div>
|
||||
<Separator name={'navigationSection'} index={0} color={'var(--theme-divider-color)'} />
|
||||
{/if}
|
||||
<div class="hulyComponent-content__column">
|
||||
<Header adaptive={'disabled'}>
|
||||
<Breadcrumb icon={mainComponentIcon} label={mainComponentLabel} size={'large'} />
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<ModernTab
|
||||
label={tab.name}
|
||||
labelIntl={widget.label}
|
||||
labelIntl={tab.label ?? widget.label}
|
||||
highlighted={selected}
|
||||
orientation="vertical"
|
||||
kind={tab.isPinned ? 'secondary' : 'primary'}
|
||||
|
||||
@@ -26,7 +26,7 @@ import ServerManager from './components/ServerManager.svelte'
|
||||
import WorkbenchTabs from './components/WorkbenchTabs.svelte'
|
||||
import { isAdminUser } from '@hcengineering/presentation'
|
||||
import { canCloseTab, closeTab, pinTab, unpinTab } from './workbench'
|
||||
import { closeWidgetTab, createWidgetTab, getSidebarObject } from './sidebar'
|
||||
import { closeWidget, closeWidgetTab, createWidgetTab, getSidebarObject } from './sidebar'
|
||||
|
||||
async function hasArchiveSpaces (spaces: Space[]): Promise<boolean> {
|
||||
return spaces.find((sp) => sp.archived) !== undefined
|
||||
@@ -59,6 +59,7 @@ export default async (): Promise<Resources> => ({
|
||||
CanCloseTab: canCloseTab,
|
||||
CreateWidgetTab: createWidgetTab,
|
||||
CloseWidgetTab: closeWidgetTab,
|
||||
CloseWidget: closeWidget,
|
||||
GetSidebarObject: getSidebarObject
|
||||
},
|
||||
actionImpl: {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { get, writable } from 'svelte/store'
|
||||
import { getCurrentLocation, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
|
||||
import { workspaceStore } from './utils'
|
||||
import { locationWorkspaceStore } from './utils'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
|
||||
export enum SidebarVariant {
|
||||
@@ -50,14 +50,14 @@ export const defaultSidebarState: SidebarState = {
|
||||
|
||||
export const sidebarStore = writable<SidebarState>(defaultSidebarState)
|
||||
|
||||
workspaceStore.subscribe((workspace) => {
|
||||
locationWorkspaceStore.subscribe((workspace) => {
|
||||
sidebarStore.set(getSidebarStateFromLocalStorage(workspace ?? ''))
|
||||
})
|
||||
|
||||
sidebarStore.subscribe(setSidebarStateToLocalStorage)
|
||||
|
||||
export function syncSidebarState (): void {
|
||||
const workspace = get(workspaceStore)
|
||||
const workspace = get(locationWorkspaceStore)
|
||||
sidebarStore.set(getSidebarStateFromLocalStorage(workspace ?? ''))
|
||||
}
|
||||
function getSideBarLocalStorageKey (workspace: string): string | undefined {
|
||||
@@ -89,7 +89,7 @@ function getSidebarStateFromLocalStorage (workspace: string): SidebarState {
|
||||
}
|
||||
|
||||
function setSidebarStateToLocalStorage (state: SidebarState): void {
|
||||
const workspace = get(workspaceStore)
|
||||
const workspace = get(locationWorkspaceStore)
|
||||
if (workspace == null || workspace === '') return
|
||||
|
||||
const sidebarStateLocalStorageKey = getSideBarLocalStorageKey(workspace)
|
||||
@@ -214,6 +214,8 @@ export function openWidgetTab (widget: Ref<Widget>, tab: string): void {
|
||||
Analytics.handleEvent(WorkbenchEvents.SidebarOpenWidget, { widget, tab: newTab?.name })
|
||||
sidebarStore.set({
|
||||
...state,
|
||||
widget,
|
||||
variant: SidebarVariant.EXPANDED,
|
||||
widgetsState
|
||||
})
|
||||
}
|
||||
|
||||
@@ -157,7 +157,13 @@ export async function showApplication (app: Application): Promise<void> {
|
||||
}
|
||||
|
||||
export const workspacesStore = writable<Workspace[]>([])
|
||||
export const workspaceStore = derived(location, (loc: Location) => loc.path[1])
|
||||
export const locationWorkspaceStore = derived(location, (loc: Location) => loc.path[1])
|
||||
export const currentWorkspaceStore = derived(
|
||||
[workspacesStore, locationWorkspaceStore],
|
||||
([$workspaces, $locationWorkspace]) => {
|
||||
return $workspaces.find((it) => it.workspace === $locationWorkspace)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
||||
@@ -40,7 +40,7 @@ import { type Asset, type IntlString, getMetadata, getResource, translate } from
|
||||
import { parseLinkId } from '@hcengineering/view-resources'
|
||||
import notification, { notificationId } from '@hcengineering/notification'
|
||||
|
||||
import { workspaceStore } from './utils'
|
||||
import { locationWorkspaceStore } from './utils'
|
||||
import workbench from './plugin'
|
||||
|
||||
export const tabIdStore = writable<Ref<WorkbenchTab> | undefined>()
|
||||
@@ -56,7 +56,7 @@ tabIdStore.subscribe((value) => {
|
||||
prevTabId = value
|
||||
})
|
||||
|
||||
workspaceStore.subscribe((workspace) => {
|
||||
locationWorkspaceStore.subscribe((workspace) => {
|
||||
tabIdStore.set(getTabFromLocalStorage(workspace ?? ''))
|
||||
})
|
||||
|
||||
@@ -64,7 +64,7 @@ tabIdStore.subscribe(saveTabToLocalStorage)
|
||||
|
||||
const syncTabLoc = reduceCalls(async (): Promise<void> => {
|
||||
const loc = getCurrentLocation()
|
||||
const workspace = get(workspaceStore)
|
||||
const workspace = get(locationWorkspaceStore)
|
||||
if (workspace == null || workspace === '') return
|
||||
const tab = get(currentTabStore)
|
||||
if (tab == null) return
|
||||
@@ -132,7 +132,7 @@ locationStore.subscribe((l: Location) => {
|
||||
})
|
||||
|
||||
export function syncWorkbenchTab (): void {
|
||||
const workspace = get(workspaceStore)
|
||||
const workspace = get(locationWorkspaceStore)
|
||||
tabIdStore.set(getTabFromLocalStorage(workspace ?? ''))
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ function getTabFromLocalStorage (workspace: string): Ref<WorkbenchTab> | undefin
|
||||
}
|
||||
|
||||
function saveTabToLocalStorage (_id: Ref<WorkbenchTab> | undefined): void {
|
||||
const workspace = get(workspaceStore)
|
||||
const workspace = get(locationWorkspaceStore)
|
||||
if (workspace == null || workspace === '') return
|
||||
|
||||
const localStorageKey = getTabIdLocalStorageKey(workspace)
|
||||
|
||||
@@ -89,6 +89,7 @@ export interface WidgetPreference extends Preference {
|
||||
export interface WidgetTab {
|
||||
id: string
|
||||
name?: string
|
||||
label?: IntlString
|
||||
icon?: Asset | AnySvelteComponent
|
||||
iconComponent?: AnyComponent
|
||||
iconProps?: Record<string, any>
|
||||
@@ -280,6 +281,7 @@ export default plugin(workbenchId, {
|
||||
function: {
|
||||
CreateWidgetTab: '' as Resource<(widget: Widget, tab: WidgetTab, newTab: boolean) => Promise<void>>,
|
||||
CloseWidgetTab: '' as Resource<(widget: Widget, tab: string) => Promise<void>>,
|
||||
CloseWidget: '' as Resource<(widget: Ref<Widget>) => Promise<void>>,
|
||||
GetSidebarObject: '' as Resource<() => Partial<Pick<Doc, '_id' | '_class'>>>
|
||||
},
|
||||
actionImpl: {
|
||||
|
||||
@@ -62,7 +62,11 @@ export function serveStats (ctx: MeasureContext, onClose?: () => void): void {
|
||||
credentials: true
|
||||
})
|
||||
)
|
||||
app.use(bodyParser())
|
||||
app.use(
|
||||
bodyParser({
|
||||
jsonLimit: '150mb'
|
||||
})
|
||||
)
|
||||
|
||||
router.get('/api/v1/overview', (req, res) => {
|
||||
try {
|
||||
|
||||
@@ -53,21 +53,23 @@ import {
|
||||
import { ReferenceTrigger } from './references'
|
||||
import { getAttrName, getCollectionAttribute, getDocUpdateAction, getTxAttributesUpdates } from './utils'
|
||||
|
||||
export async function OnReactionChanged (originTx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const tx = originTx as TxCollectionCUD<ActivityMessage, Reaction>
|
||||
const innerTx = TxProcessor.extractTx(tx) as TxCUD<Reaction>
|
||||
export async function OnReactionChanged (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const originTx of txes) {
|
||||
const tx = originTx as TxCollectionCUD<ActivityMessage, Reaction>
|
||||
const innerTx = TxProcessor.extractTx(tx) as TxCUD<Reaction>
|
||||
|
||||
if (innerTx._class === core.class.TxCreateDoc) {
|
||||
const txes = await createReactionNotifications(tx, control)
|
||||
if (innerTx._class === core.class.TxCreateDoc) {
|
||||
const txes = await createReactionNotifications(tx, control)
|
||||
|
||||
await control.apply(control.ctx, txes)
|
||||
return []
|
||||
}
|
||||
await control.apply(control.ctx, txes)
|
||||
continue
|
||||
}
|
||||
|
||||
if (innerTx._class === core.class.TxRemoveDoc) {
|
||||
const txes = await removeReactionNotifications(tx, control)
|
||||
await control.apply(control.ctx, txes)
|
||||
return []
|
||||
if (innerTx._class === core.class.TxRemoveDoc) {
|
||||
const txes = await removeReactionNotifications(tx, control)
|
||||
await control.apply(control.ctx, txes)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
@@ -304,11 +306,8 @@ export async function generateDocUpdateMessages (
|
||||
switch (tx._class) {
|
||||
case core.class.TxCreateDoc: {
|
||||
const doc = TxProcessor.createDoc2Doc(tx as TxCreateDoc<Doc>)
|
||||
return await ctx.with(
|
||||
'pushDocUpdateMessages',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await pushDocUpdateMessages(ctx, control, res, doc, originTx ?? tx, undefined, objectCache, controlRules)
|
||||
return await ctx.with('pushDocUpdateMessages', {}, (ctx) =>
|
||||
pushDocUpdateMessages(ctx, control, res, doc, originTx ?? tx, undefined, objectCache, controlRules)
|
||||
)
|
||||
}
|
||||
case core.class.TxMixin:
|
||||
@@ -322,20 +321,8 @@ export async function generateDocUpdateMessages (
|
||||
doc = (await control.findAll(ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
||||
objectCache?.docs?.set(tx.objectId, doc)
|
||||
}
|
||||
return await ctx.with(
|
||||
'pushDocUpdateMessages',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await pushDocUpdateMessages(
|
||||
ctx,
|
||||
control,
|
||||
res,
|
||||
doc ?? undefined,
|
||||
originTx ?? tx,
|
||||
undefined,
|
||||
objectCache,
|
||||
controlRules
|
||||
)
|
||||
return await ctx.with('pushDocUpdateMessages', {}, (ctx) =>
|
||||
pushDocUpdateMessages(ctx, control, res, doc ?? undefined, originTx ?? tx, undefined, objectCache, controlRules)
|
||||
)
|
||||
}
|
||||
case core.class.TxCollectionCUD: {
|
||||
@@ -363,20 +350,17 @@ export async function generateDocUpdateMessages (
|
||||
}
|
||||
if (doc !== undefined) {
|
||||
objectCache?.docs?.set(tx.objectId, doc)
|
||||
return await ctx.with(
|
||||
'pushDocUpdateMessages',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await pushDocUpdateMessages(
|
||||
ctx,
|
||||
control,
|
||||
res,
|
||||
doc ?? undefined,
|
||||
originTx ?? tx,
|
||||
undefined,
|
||||
objectCache,
|
||||
controlRules
|
||||
)
|
||||
return await ctx.with('pushDocUpdateMessages', {}, (ctx) =>
|
||||
pushDocUpdateMessages(
|
||||
ctx,
|
||||
control,
|
||||
res,
|
||||
doc ?? undefined,
|
||||
originTx ?? tx,
|
||||
undefined,
|
||||
objectCache,
|
||||
controlRules
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -791,21 +791,27 @@ async function ActivityReferenceRemove (tx: Tx, etx: TxCUD<Doc>, control: Trigge
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function ReferenceTrigger (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function ReferenceTrigger (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
|
||||
const etx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||
if (control.hierarchy.isDerived(etx.objectClass, activity.class.ActivityReference)) return []
|
||||
if (control.hierarchy.isDerived(etx.objectClass, notification.class.InboxNotification)) return []
|
||||
for (const tx of txes) {
|
||||
const etx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||
if (control.hierarchy.isDerived(etx.objectClass, activity.class.ActivityReference)) {
|
||||
continue
|
||||
}
|
||||
if (control.hierarchy.isDerived(etx.objectClass, notification.class.InboxNotification)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (etx._class === core.class.TxCreateDoc) {
|
||||
result.push(...(await ActivityReferenceCreate(tx, etx, control)))
|
||||
}
|
||||
if (etx._class === core.class.TxUpdateDoc) {
|
||||
result.push(...(await ActivityReferenceUpdate(tx, etx, control)))
|
||||
}
|
||||
if (etx._class === core.class.TxRemoveDoc) {
|
||||
result.push(...(await ActivityReferenceRemove(tx, etx, control)))
|
||||
if (etx._class === core.class.TxCreateDoc) {
|
||||
result.push(...(await ActivityReferenceCreate(tx, etx, control)))
|
||||
}
|
||||
if (etx._class === core.class.TxUpdateDoc) {
|
||||
result.push(...(await ActivityReferenceUpdate(tx, etx, control)))
|
||||
}
|
||||
if (etx._class === core.class.TxRemoveDoc) {
|
||||
result.push(...(await ActivityReferenceRemove(tx, etx, control)))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ export async function OnMessageSend (
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnMention (tx: TxCreateDoc<MentionInboxNotification>, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnMention (tx: TxCreateDoc<MentionInboxNotification>[], control: TriggerControl): Promise<Tx[]> {
|
||||
// Note: temporally commented until open ai will be added
|
||||
// if (tx.objectClass !== notification.class.MentionInboxNotification || tx._class !== core.class.TxCreateDoc) {
|
||||
// return []
|
||||
@@ -308,7 +308,7 @@ export async function OnMention (tx: TxCreateDoc<MentionInboxNotification>, cont
|
||||
}
|
||||
|
||||
export async function OnMessageNotified (
|
||||
tx: TxCreateDoc<ActivityInboxNotification>,
|
||||
tx: TxCreateDoc<ActivityInboxNotification>[],
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
// Note: temporally commented until open ai will be added
|
||||
@@ -363,45 +363,47 @@ export async function OnMessageNotified (
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnUserStatus (originTx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const tx = TxProcessor.extractTx(originTx) as TxCUD<UserStatus>
|
||||
export async function OnUserStatus (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const originTx of txes) {
|
||||
const tx = TxProcessor.extractTx(originTx) as TxCUD<UserStatus>
|
||||
|
||||
if (
|
||||
tx.objectClass !== core.class.UserStatus ||
|
||||
![core.class.TxCreateDoc, core.class.TxUpdateDoc].includes(tx._class)
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (tx._class === core.class.TxCreateDoc) {
|
||||
const createTx = tx as TxCreateDoc<UserStatus>
|
||||
const status = TxProcessor.createDoc2Doc(createTx)
|
||||
if (status.user === aiBot.account.AIBot || status.user === core.account.System || !status.online) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
if (tx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = tx as TxUpdateDoc<UserStatus>
|
||||
const val = updateTx.operations.online
|
||||
if (val !== true) {
|
||||
return []
|
||||
if (
|
||||
tx.objectClass !== core.class.UserStatus ||
|
||||
![core.class.TxCreateDoc, core.class.TxUpdateDoc].includes(tx._class)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
const status = (await control.findAll(control.ctx, core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
||||
if (status === undefined || status.user === aiBot.account.AIBot || status.user === core.account.System) {
|
||||
return []
|
||||
if (tx._class === core.class.TxCreateDoc) {
|
||||
const createTx = tx as TxCreateDoc<UserStatus>
|
||||
const status = TxProcessor.createDoc2Doc(createTx)
|
||||
if (status.user === aiBot.account.AIBot || status.user === core.account.System || !status.online) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (tx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = tx as TxUpdateDoc<UserStatus>
|
||||
const val = updateTx.operations.online
|
||||
if (val !== true) {
|
||||
continue
|
||||
}
|
||||
|
||||
const status = (await control.findAll(control.ctx, core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
||||
if (status === undefined || status.user === aiBot.account.AIBot || status.user === core.account.System) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const account = control.modelDb.findAllSync(contact.class.PersonAccount, { email: aiBotAccountEmail })[0]
|
||||
|
||||
if (account !== undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
await createAccountRequest(control.workspace, control.ctx)
|
||||
}
|
||||
|
||||
const account = control.modelDb.findAllSync(contact.class.PersonAccount, { email: aiBotAccountEmail })[0]
|
||||
|
||||
if (account !== undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
await createAccountRequest(control.workspace, control.ctx)
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ export async function createAccountRequest (workspace: WorkspaceId, ctx: Measure
|
||||
}
|
||||
|
||||
try {
|
||||
ctx.info('Requesting AI account creation', { url, workspace })
|
||||
await fetch(concatLink(url, '/connect'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@@ -23,19 +23,24 @@ import type { TriggerControl } from '@hcengineering/server-core'
|
||||
* @public
|
||||
*/
|
||||
export async function OnAttachmentDelete (
|
||||
tx: Tx,
|
||||
txes: Tx[],
|
||||
{ removedMap, ctx, storageAdapter, workspace }: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const rmTx = TxProcessor.extractTx(tx) as TxRemoveDoc<Attachment>
|
||||
const toDelete: string[] = []
|
||||
for (const tx of txes) {
|
||||
const rmTx = TxProcessor.extractTx(tx) as TxRemoveDoc<Attachment>
|
||||
|
||||
// Obtain document being deleted.
|
||||
const attach = removedMap.get(rmTx.objectId) as Attachment
|
||||
// Obtain document being deleted.
|
||||
const attach = removedMap.get(rmTx.objectId) as Attachment
|
||||
|
||||
if (attach === undefined) {
|
||||
return []
|
||||
if (attach === undefined) {
|
||||
continue
|
||||
}
|
||||
toDelete.push(attach.file)
|
||||
}
|
||||
if (toDelete.length > 0) {
|
||||
await storageAdapter.remove(ctx, workspace, toDelete)
|
||||
}
|
||||
|
||||
await storageAdapter.remove(ctx, workspace, [attach.file])
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -83,23 +83,28 @@ export async function ReminderTextPresenter (doc: Doc, control: TriggerControl):
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<PersonAccount>
|
||||
const user = TxProcessor.createDoc2Doc(ctx)
|
||||
export async function OnPersonAccountCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<PersonAccount>
|
||||
const user = TxProcessor.createDoc2Doc(ctx)
|
||||
|
||||
const res: TxCreateDoc<Calendar> = control.txFactory.createTxCreateDoc(
|
||||
calendar.class.Calendar,
|
||||
calendar.space.Calendar,
|
||||
{
|
||||
name: user.email,
|
||||
hidden: false,
|
||||
visibility: 'public'
|
||||
},
|
||||
`${user._id}_calendar` as Ref<Calendar>,
|
||||
undefined,
|
||||
user._id
|
||||
)
|
||||
return [res]
|
||||
result.push(
|
||||
control.txFactory.createTxCreateDoc(
|
||||
calendar.class.Calendar,
|
||||
calendar.space.Calendar,
|
||||
{
|
||||
name: user.email,
|
||||
hidden: false,
|
||||
visibility: 'public'
|
||||
},
|
||||
`${user._id}_calendar` as Ref<Calendar>,
|
||||
undefined,
|
||||
user._id
|
||||
)
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getCalendar (calendars: Calendar[], person: Ref<PersonAccount>): Ref<Calendar> | undefined {
|
||||
@@ -118,17 +123,20 @@ function getEventPerson (current: Event, calendars: Calendar[], control: Trigger
|
||||
return acc.person
|
||||
}
|
||||
|
||||
async function OnEvent (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCUD<Event>
|
||||
if (ctx._class === core.class.TxCreateDoc) {
|
||||
return await onEventCreate(ctx as TxCreateDoc<Event>, control)
|
||||
} else if (ctx._class === core.class.TxUpdateDoc) {
|
||||
return await onEventUpdate(ctx as TxUpdateDoc<Event>, control)
|
||||
} else if (ctx._class === core.class.TxRemoveDoc) {
|
||||
return await onRemoveEvent(ctx as TxRemoveDoc<Event>, control)
|
||||
async function OnEvent (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCUD<Event>
|
||||
if (ctx._class === core.class.TxCreateDoc) {
|
||||
result.push(...(await onEventCreate(ctx as TxCreateDoc<Event>, control)))
|
||||
} else if (ctx._class === core.class.TxUpdateDoc) {
|
||||
result.push(...(await onEventUpdate(ctx as TxUpdateDoc<Event>, control)))
|
||||
} else if (ctx._class === core.class.TxRemoveDoc) {
|
||||
result.push(...(await onRemoveEvent(ctx as TxRemoveDoc<Event>, control)))
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
async function onEventUpdate (ctx: TxUpdateDoc<Event>, control: TriggerControl): Promise<Tx[]> {
|
||||
|
||||
@@ -203,16 +203,20 @@ async function OnChatMessageCreated (ctx: MeasureContext, tx: TxCUD<Doc>, contro
|
||||
return res
|
||||
}
|
||||
|
||||
async function ChatNotificationsHandler (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
|
||||
async function ChatNotificationsHandler (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
|
||||
|
||||
if (actualTx._class !== core.class.TxCreateDoc) {
|
||||
return []
|
||||
if (actualTx._class !== core.class.TxCreateDoc) {
|
||||
continue
|
||||
}
|
||||
|
||||
const chatMessage = TxProcessor.createDoc2Doc(actualTx)
|
||||
|
||||
result.push(...(await createCollaboratorNotifications(control.ctx, tx, control, [chatMessage])))
|
||||
}
|
||||
|
||||
const chatMessage = TxProcessor.createDoc2Doc(actualTx)
|
||||
|
||||
return await createCollaboratorNotifications(control.ctx, tx, control, [chatMessage])
|
||||
return result
|
||||
}
|
||||
|
||||
function joinChannel (control: TriggerControl, channel: Channel, user: Ref<Account>): Tx[] {
|
||||
@@ -263,27 +267,29 @@ async function OnThreadMessageDeleted (tx: Tx, control: TriggerControl): Promise
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function ChunterTrigger (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function ChunterTrigger (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
|
||||
|
||||
if (
|
||||
actualTx._class === core.class.TxCreateDoc &&
|
||||
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ThreadMessage)
|
||||
) {
|
||||
res.push(...(await control.ctx.with('OnThreadMessageCreated', {}, (ctx) => OnThreadMessageCreated(tx, control))))
|
||||
}
|
||||
if (
|
||||
actualTx._class === core.class.TxRemoveDoc &&
|
||||
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ThreadMessage)
|
||||
) {
|
||||
res.push(...(await control.ctx.with('OnThreadMessageDeleted', {}, (ctx) => OnThreadMessageDeleted(tx, control))))
|
||||
}
|
||||
if (
|
||||
actualTx._class === core.class.TxCreateDoc &&
|
||||
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ChatMessage)
|
||||
) {
|
||||
res.push(...(await control.ctx.with('OnChatMessageCreated', {}, (ctx) => OnChatMessageCreated(ctx, tx, control))))
|
||||
if (
|
||||
actualTx._class === core.class.TxCreateDoc &&
|
||||
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ThreadMessage)
|
||||
) {
|
||||
res.push(...(await control.ctx.with('OnThreadMessageCreated', {}, (ctx) => OnThreadMessageCreated(tx, control))))
|
||||
}
|
||||
if (
|
||||
actualTx._class === core.class.TxRemoveDoc &&
|
||||
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ThreadMessage)
|
||||
) {
|
||||
res.push(...(await control.ctx.with('OnThreadMessageDeleted', {}, (ctx) => OnThreadMessageDeleted(tx, control))))
|
||||
}
|
||||
if (
|
||||
actualTx._class === core.class.TxCreateDoc &&
|
||||
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ChatMessage)
|
||||
) {
|
||||
res.push(...(await control.ctx.with('OnChatMessageCreated', {}, (ctx) => OnChatMessageCreated(ctx, tx, control))))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -342,20 +348,21 @@ export async function getChunterNotificationContent (
|
||||
}
|
||||
}
|
||||
|
||||
async function OnChatMessageRemoved (tx: TxCollectionCUD<Doc, ChatMessage>, control: TriggerControl): Promise<Tx[]> {
|
||||
if (tx.tx._class !== core.class.TxRemoveDoc) {
|
||||
return []
|
||||
}
|
||||
|
||||
async function OnChatMessageRemoved (txes: TxCollectionCUD<Doc, ChatMessage>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
const notifications = await control.findAll(control.ctx, notification.class.InboxNotification, {
|
||||
attachedTo: tx.tx.objectId
|
||||
})
|
||||
for (const tx of txes) {
|
||||
if (tx.tx._class !== core.class.TxRemoveDoc) {
|
||||
continue
|
||||
}
|
||||
|
||||
notifications.forEach((notification) => {
|
||||
res.push(control.txFactory.createTxRemoveDoc(notification._class, notification.space, notification._id))
|
||||
})
|
||||
const notifications = await control.findAll(control.ctx, notification.class.InboxNotification, {
|
||||
attachedTo: tx.tx.objectId
|
||||
})
|
||||
|
||||
notifications.forEach((notification) => {
|
||||
res.push(control.txFactory.createTxRemoveDoc(notification._class, notification.space, notification._id))
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -470,22 +477,26 @@ export async function syncChat (control: TriggerControl, status: UserStatus, dat
|
||||
await control.apply(control.ctx, res, true)
|
||||
}
|
||||
|
||||
async function OnUserStatus (originTx: TxCUD<UserStatus>, control: TriggerControl): Promise<Tx[]> {
|
||||
const tx = TxProcessor.extractTx(originTx) as TxCUD<UserStatus>
|
||||
if (tx.objectClass !== core.class.UserStatus) return []
|
||||
if (tx._class === core.class.TxCreateDoc) {
|
||||
const createTx = tx as TxCreateDoc<UserStatus>
|
||||
const { online } = createTx.attributes
|
||||
if (online) {
|
||||
const status = TxProcessor.createDoc2Doc(createTx)
|
||||
await syncChat(control, status, originTx.modifiedOn)
|
||||
async function OnUserStatus (txes: TxCUD<UserStatus>[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const originTx of txes) {
|
||||
const tx = TxProcessor.extractTx(originTx) as TxCUD<UserStatus>
|
||||
if (tx.objectClass !== core.class.UserStatus) {
|
||||
continue
|
||||
}
|
||||
} else if (tx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = tx as TxUpdateDoc<UserStatus>
|
||||
const { online } = updateTx.operations
|
||||
if (online === true) {
|
||||
const status = (await control.findAll(control.ctx, core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
||||
await syncChat(control, status, originTx.modifiedOn)
|
||||
if (tx._class === core.class.TxCreateDoc) {
|
||||
const createTx = tx as TxCreateDoc<UserStatus>
|
||||
const { online } = createTx.attributes
|
||||
if (online) {
|
||||
const status = TxProcessor.createDoc2Doc(createTx)
|
||||
await syncChat(control, status, originTx.modifiedOn)
|
||||
}
|
||||
} else if (tx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = tx as TxUpdateDoc<UserStatus>
|
||||
const { online } = updateTx.operations
|
||||
if (online === true) {
|
||||
const status = (await control.findAll(control.ctx, core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
||||
await syncChat(control, status, originTx.modifiedOn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,63 +50,71 @@ import { getMetadata } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
export async function OnSpaceTypeMembers (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = tx as TxUpdateDoc<SpaceType>
|
||||
export async function OnSpaceTypeMembers (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
const newMember = ctx.operations.$push?.members as Ref<Account>
|
||||
if (newMember !== undefined) {
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { type: ctx.objectId })
|
||||
for (const space of spaces) {
|
||||
if (space.members.includes(newMember)) continue
|
||||
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
$push: {
|
||||
members: newMember
|
||||
}
|
||||
})
|
||||
result.push(pushTx)
|
||||
for (const tx of txes) {
|
||||
const ctx = tx as TxUpdateDoc<SpaceType>
|
||||
const newMember = ctx.operations.$push?.members as Ref<Account>
|
||||
if (newMember !== undefined) {
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { type: ctx.objectId })
|
||||
for (const space of spaces) {
|
||||
if (space.members.includes(newMember)) continue
|
||||
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
$push: {
|
||||
members: newMember
|
||||
}
|
||||
})
|
||||
result.push(pushTx)
|
||||
}
|
||||
}
|
||||
}
|
||||
const oldMember = ctx.operations.$pull?.members as Ref<Account>
|
||||
if (ctx.operations.$pull?.members !== undefined) {
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { type: ctx.objectId })
|
||||
for (const space of spaces) {
|
||||
if (!space.members.includes(oldMember)) continue
|
||||
const pullTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
$pull: {
|
||||
members: oldMember
|
||||
}
|
||||
})
|
||||
result.push(pullTx)
|
||||
const oldMember = ctx.operations.$pull?.members as Ref<Account>
|
||||
if (ctx.operations.$pull?.members !== undefined) {
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { type: ctx.objectId })
|
||||
for (const space of spaces) {
|
||||
if (!space.members.includes(oldMember)) continue
|
||||
const pullTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
$pull: {
|
||||
members: oldMember
|
||||
}
|
||||
})
|
||||
result.push(pullTx)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnEmployeeCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const mixinTx = tx as TxMixin<Person, Employee>
|
||||
if (mixinTx.attributes.active !== true) return []
|
||||
const acc = control.modelDb.getAccountByPersonId(mixinTx.objectId)
|
||||
if (acc.length === 0) return []
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { autoJoin: true })
|
||||
export async function OnEmployeeCreate (_txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of _txes) {
|
||||
const mixinTx = tx as TxMixin<Person, Employee>
|
||||
if (mixinTx.attributes.active !== true) {
|
||||
continue
|
||||
}
|
||||
const acc = control.modelDb.getAccountByPersonId(mixinTx.objectId)
|
||||
if (acc.length === 0) {
|
||||
continue
|
||||
}
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { autoJoin: true })
|
||||
|
||||
const txes = await createPersonSpace(
|
||||
acc.map((it) => it._id),
|
||||
mixinTx.objectId,
|
||||
control
|
||||
)
|
||||
result.push(...txes)
|
||||
const txes = await createPersonSpace(
|
||||
acc.map((it) => it._id),
|
||||
mixinTx.objectId,
|
||||
control
|
||||
)
|
||||
result.push(...txes)
|
||||
|
||||
for (const space of spaces) {
|
||||
const toAdd = acc.filter((it) => !space.members.includes(it._id))
|
||||
if (toAdd.length === 0) continue
|
||||
for (const a of toAdd) {
|
||||
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
$push: {
|
||||
members: a._id
|
||||
}
|
||||
})
|
||||
result.push(pushTx)
|
||||
for (const space of spaces) {
|
||||
const toAdd = acc.filter((it) => !space.members.includes(it._id))
|
||||
if (toAdd.length === 0) continue
|
||||
for (const a of toAdd) {
|
||||
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
$push: {
|
||||
members: a._id
|
||||
}
|
||||
})
|
||||
result.push(pushTx)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -142,32 +150,37 @@ async function createPersonSpace (
|
||||
]
|
||||
}
|
||||
|
||||
export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const acc = TxProcessor.createDoc2Doc(tx as TxCreateDoc<PersonAccount>)
|
||||
const person = (
|
||||
await control.findAll(
|
||||
control.ctx,
|
||||
contact.mixin.Employee,
|
||||
{ _id: acc.person as Ref<Employee>, active: true },
|
||||
{ limit: 1 }
|
||||
)
|
||||
)[0]
|
||||
if (person === undefined) return []
|
||||
export async function OnPersonAccountCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { autoJoin: true })
|
||||
|
||||
const result: Tx[] = []
|
||||
const txes = await createPersonSpace([acc._id], person._id, control)
|
||||
for (const tx of txes) {
|
||||
const acc = TxProcessor.createDoc2Doc(tx as TxCreateDoc<PersonAccount>)
|
||||
const person = (
|
||||
await control.findAll(
|
||||
control.ctx,
|
||||
contact.mixin.Employee,
|
||||
{ _id: acc.person as Ref<Employee>, active: true },
|
||||
{ limit: 1 }
|
||||
)
|
||||
)[0]
|
||||
if (person === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
result.push(...txes)
|
||||
const txes = await createPersonSpace([acc._id], person._id, control)
|
||||
|
||||
for (const space of spaces) {
|
||||
if (space.members.includes(acc._id)) continue
|
||||
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
$push: {
|
||||
members: acc._id
|
||||
}
|
||||
})
|
||||
result.push(pushTx)
|
||||
result.push(...txes)
|
||||
|
||||
for (const space of spaces) {
|
||||
if (space.members.includes(acc._id)) continue
|
||||
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
$push: {
|
||||
members: acc._id
|
||||
}
|
||||
})
|
||||
result.push(pushTx)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -176,29 +189,30 @@ export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): P
|
||||
* @public
|
||||
*/
|
||||
export async function OnContactDelete (
|
||||
tx: Tx,
|
||||
txes: Tx[],
|
||||
{ findAll, hierarchy, storageAdapter, workspace, removedMap, txFactory, ctx }: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const rmTx = tx as TxRemoveDoc<Contact>
|
||||
|
||||
const removeContact = removedMap.get(rmTx.objectId) as Contact
|
||||
if (removeContact === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const rmTx = tx as TxRemoveDoc<Contact>
|
||||
|
||||
const members = await findAll(ctx, contact.class.Member, { contact: removeContact._id })
|
||||
for (const member of members) {
|
||||
const removeTx = txFactory.createTxRemoveDoc(member._class, member.space, member._id)
|
||||
const tx = txFactory.createTxCollectionCUD(
|
||||
member.attachedToClass,
|
||||
member.attachedTo,
|
||||
member.space,
|
||||
member.collection,
|
||||
removeTx
|
||||
)
|
||||
result.push(tx)
|
||||
const removeContact = removedMap.get(rmTx.objectId) as Contact
|
||||
if (removeContact === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const members = await findAll(ctx, contact.class.Member, { contact: removeContact._id })
|
||||
for (const member of members) {
|
||||
const removeTx = txFactory.createTxRemoveDoc(member._class, member.space, member._id)
|
||||
const tx = txFactory.createTxCollectionCUD(
|
||||
member.attachedToClass,
|
||||
member.attachedTo,
|
||||
member.space,
|
||||
member.collection,
|
||||
removeTx
|
||||
)
|
||||
result.push(tx)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -207,36 +221,37 @@ export async function OnContactDelete (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnChannelUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const uTx = tx as TxUpdateDoc<Channel>
|
||||
|
||||
export async function OnChannelUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const uTx = tx as TxUpdateDoc<Channel>
|
||||
|
||||
if (uTx.operations.$inc?.items !== undefined) {
|
||||
const doc = (await control.findAll(control.ctx, uTx.objectClass, { _id: uTx.objectId }, { limit: 1 }))[0]
|
||||
if (doc !== undefined) {
|
||||
if (control.hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
|
||||
const collab = control.hierarchy.as(doc, notification.mixin.Collaborators) as Doc as Collaborators
|
||||
if (collab.collaborators.includes(tx.modifiedBy)) {
|
||||
result.push(
|
||||
control.txFactory.createTxMixin(doc._id, doc._class, doc.space, notification.mixin.Collaborators, {
|
||||
$push: {
|
||||
collaborators: tx.modifiedBy
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const res = control.txFactory.createTxMixin<Doc, Collaborators>(
|
||||
doc._id,
|
||||
doc._class,
|
||||
doc.space,
|
||||
notification.mixin.Collaborators,
|
||||
{
|
||||
collaborators: [tx.modifiedBy]
|
||||
if (uTx.operations.$inc?.items !== undefined) {
|
||||
const doc = (await control.findAll(control.ctx, uTx.objectClass, { _id: uTx.objectId }, { limit: 1 }))[0]
|
||||
if (doc !== undefined) {
|
||||
if (control.hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
|
||||
const collab = control.hierarchy.as(doc, notification.mixin.Collaborators) as Doc as Collaborators
|
||||
if (collab.collaborators.includes(tx.modifiedBy)) {
|
||||
result.push(
|
||||
control.txFactory.createTxMixin(doc._id, doc._class, doc.space, notification.mixin.Collaborators, {
|
||||
$push: {
|
||||
collaborators: tx.modifiedBy
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
result.push(res)
|
||||
} else {
|
||||
const res = control.txFactory.createTxMixin<Doc, Collaborators>(
|
||||
doc._id,
|
||||
doc._class,
|
||||
doc.space,
|
||||
notification.mixin.Collaborators,
|
||||
{
|
||||
collaborators: [tx.modifiedBy]
|
||||
}
|
||||
)
|
||||
result.push(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
//
|
||||
// Copyright © 2023-2024 Hardcore Engineering Inc.
|
||||
//
|
||||
import core, {
|
||||
DocumentQuery,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxFactory,
|
||||
TxUpdateDoc,
|
||||
type Timestamp,
|
||||
type Account,
|
||||
type RolesAssignment,
|
||||
AccountRole,
|
||||
TxCreateDoc
|
||||
} from '@hcengineering/core'
|
||||
import contact, { type Employee, type PersonAccount } from '@hcengineering/contact'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import documents, {
|
||||
ControlledDocument,
|
||||
ControlledDocumentState,
|
||||
@@ -24,13 +9,28 @@ import documents, {
|
||||
DocumentApprovalRequest,
|
||||
DocumentState,
|
||||
DocumentTemplate,
|
||||
type DocumentTraining,
|
||||
getEffectiveDocUpdate,
|
||||
getDocumentId,
|
||||
type DocumentRequest
|
||||
getEffectiveDocUpdate,
|
||||
type DocumentRequest,
|
||||
type DocumentTraining
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import training, { type TrainingRequest, TrainingState } from '@hcengineering/training'
|
||||
import core, {
|
||||
AccountRole,
|
||||
DocumentQuery,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
TxFactory,
|
||||
TxUpdateDoc,
|
||||
type Account,
|
||||
type RolesAssignment,
|
||||
type Timestamp
|
||||
} from '@hcengineering/core'
|
||||
import { RequestStatus } from '@hcengineering/request'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import training, { TrainingState, type TrainingRequest } from '@hcengineering/training'
|
||||
|
||||
async function getDocs (
|
||||
control: TriggerControl,
|
||||
@@ -272,130 +272,145 @@ function updateTemplate (doc: ControlledDocument, olderEffective: ControlledDocu
|
||||
}
|
||||
|
||||
export async function OnDocHasBecomeEffective (
|
||||
tx: TxUpdateDoc<ControlledDocument>,
|
||||
txes: TxUpdateDoc<ControlledDocument>[],
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 })).shift()
|
||||
if (doc === undefined) {
|
||||
return []
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 })).shift()
|
||||
if (doc === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const olderEffective = await getDocsOlderThanDoc(doc, control, [DocumentState.Effective])
|
||||
|
||||
result.push(
|
||||
...updateAuthor(doc, control.txFactory),
|
||||
...archiveDocs(olderEffective, control.txFactory),
|
||||
...updateMeta(doc, control.txFactory),
|
||||
...updateTemplate(doc, olderEffective, control),
|
||||
...(await createDocumentTrainingRequest(doc, control))
|
||||
)
|
||||
}
|
||||
|
||||
const olderEffective = await getDocsOlderThanDoc(doc, control, [DocumentState.Effective])
|
||||
|
||||
return [
|
||||
...updateAuthor(doc, control.txFactory),
|
||||
...archiveDocs(olderEffective, control.txFactory),
|
||||
...updateMeta(doc, control.txFactory),
|
||||
...updateTemplate(doc, olderEffective, control),
|
||||
...(await createDocumentTrainingRequest(doc, control))
|
||||
]
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnDocDeleted (tx: TxUpdateDoc<ControlledDocument>, control: TriggerControl): Promise<Tx[]> {
|
||||
const requests = await control.findAll(control.ctx, documents.class.DocumentRequest, {
|
||||
attachedTo: tx.objectId,
|
||||
status: RequestStatus.Active
|
||||
})
|
||||
const cancelTxes = requests.map((request) =>
|
||||
control.txFactory.createTxUpdateDoc<DocumentRequest>(request._class, request.space, request._id, {
|
||||
status: RequestStatus.Cancelled
|
||||
export async function OnDocDeleted (txes: TxUpdateDoc<ControlledDocument>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const requests = await control.findAll(control.ctx, documents.class.DocumentRequest, {
|
||||
attachedTo: tx.objectId,
|
||||
status: RequestStatus.Active
|
||||
})
|
||||
)
|
||||
await control.apply(control.ctx, [
|
||||
...cancelTxes,
|
||||
control.txFactory.createTxUpdateDoc<ControlledDocument>(tx.objectClass, tx.objectSpace, tx.objectId, {
|
||||
controlledState: undefined
|
||||
})
|
||||
])
|
||||
const cancelTxes = requests.map((request) =>
|
||||
control.txFactory.createTxUpdateDoc<DocumentRequest>(request._class, request.space, request._id, {
|
||||
status: RequestStatus.Cancelled
|
||||
})
|
||||
)
|
||||
await control.apply(control.ctx, [
|
||||
...cancelTxes,
|
||||
control.txFactory.createTxUpdateDoc<ControlledDocument>(tx.objectClass, tx.objectSpace, tx.objectId, {
|
||||
controlledState: undefined
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnDocPlannedEffectiveDateChanged (
|
||||
tx: TxUpdateDoc<ControlledDocument>,
|
||||
txes: TxUpdateDoc<ControlledDocument>[],
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
if (!('plannedEffectiveDate' in tx.operations)) {
|
||||
return []
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
if (!('plannedEffectiveDate' in tx.operations)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 })).shift()
|
||||
if (doc === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
// make doc effective immediately if required
|
||||
if (tx.operations.plannedEffectiveDate === 0 && doc.controlledState === ControlledDocumentState.Approved) {
|
||||
// Create with not derived tx factory in order for notifications to work
|
||||
const factory = new TxFactory(control.txFactory.account)
|
||||
await control.apply(control.ctx, [makeDocEffective(doc, factory)])
|
||||
}
|
||||
}
|
||||
|
||||
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 })).shift()
|
||||
if (doc === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
// make doc effective immediately if required
|
||||
if (tx.operations.plannedEffectiveDate === 0 && doc.controlledState === ControlledDocumentState.Approved) {
|
||||
// Create with not derived tx factory in order for notifications to work
|
||||
const factory = new TxFactory(control.txFactory.account)
|
||||
await control.apply(control.ctx, [makeDocEffective(doc, factory)])
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnDocApprovalRequestApproved (
|
||||
tx: TxCollectionCUD<ControlledDocument, DocumentApprovalRequest>,
|
||||
txes: TxCollectionCUD<ControlledDocument, DocumentApprovalRequest>[],
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId })).shift()
|
||||
if (doc == null || doc.plannedEffectiveDate !== 0) {
|
||||
return []
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId })).shift()
|
||||
if (doc == null || doc.plannedEffectiveDate !== 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create with not derived tx factory in order for notifications to work
|
||||
const factory = new TxFactory(control.txFactory.account)
|
||||
await control.apply(control.ctx, [makeDocEffective(doc, factory)])
|
||||
// make doc effective immediately
|
||||
}
|
||||
|
||||
// Create with not derived tx factory in order for notifications to work
|
||||
const factory = new TxFactory(control.txFactory.account)
|
||||
await control.apply(control.ctx, [makeDocEffective(doc, factory)])
|
||||
|
||||
// make doc effective immediately
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
let ownerId: Ref<PersonAccount> | undefined
|
||||
if (control.hierarchy.isDerived(tx._class, core.class.TxCreateDoc)) {
|
||||
const createTx = tx as TxCreateDoc<PersonAccount>
|
||||
export async function OnWorkspaceOwnerAdded (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
let ownerId: Ref<PersonAccount> | undefined
|
||||
if (control.hierarchy.isDerived(tx._class, core.class.TxCreateDoc)) {
|
||||
const createTx = tx as TxCreateDoc<PersonAccount>
|
||||
|
||||
if (createTx.attributes.role === AccountRole.Owner) {
|
||||
ownerId = createTx.objectId
|
||||
if (createTx.attributes.role === AccountRole.Owner) {
|
||||
ownerId = createTx.objectId
|
||||
}
|
||||
} else if (control.hierarchy.isDerived(tx._class, core.class.TxUpdateDoc)) {
|
||||
const updateTx = tx as TxUpdateDoc<PersonAccount>
|
||||
|
||||
if (updateTx.operations.role === AccountRole.Owner) {
|
||||
ownerId = updateTx.objectId
|
||||
}
|
||||
}
|
||||
} else if (control.hierarchy.isDerived(tx._class, core.class.TxUpdateDoc)) {
|
||||
const updateTx = tx as TxUpdateDoc<PersonAccount>
|
||||
|
||||
if (updateTx.operations.role === AccountRole.Owner) {
|
||||
ownerId = updateTx.objectId
|
||||
if (ownerId === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const targetSpace = (
|
||||
await control.findAll(control.ctx, documents.class.OrgSpace, {
|
||||
_id: documents.space.QualityDocuments
|
||||
})
|
||||
)[0]
|
||||
|
||||
if (targetSpace === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
targetSpace.owners === undefined ||
|
||||
targetSpace.owners.length === 0 ||
|
||||
targetSpace.owners[0] === core.account.System
|
||||
) {
|
||||
const updTx = control.txFactory.createTxUpdateDoc(documents.class.OrgSpace, targetSpace.space, targetSpace._id, {
|
||||
owners: [ownerId]
|
||||
})
|
||||
result.push(updTx)
|
||||
}
|
||||
}
|
||||
|
||||
if (ownerId === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const targetSpace = (
|
||||
await control.findAll(control.ctx, documents.class.OrgSpace, {
|
||||
_id: documents.space.QualityDocuments
|
||||
})
|
||||
)[0]
|
||||
|
||||
if (targetSpace === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (
|
||||
targetSpace.owners === undefined ||
|
||||
targetSpace.owners.length === 0 ||
|
||||
targetSpace.owners[0] === core.account.System
|
||||
) {
|
||||
const updTx = control.txFactory.createTxUpdateDoc(documents.class.OrgSpace, targetSpace.space, targetSpace._id, {
|
||||
owners: [ownerId]
|
||||
})
|
||||
return [updTx]
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
export async function documentTextPresenter (doc: ControlledDocument): Promise<string> {
|
||||
|
||||
@@ -20,28 +20,35 @@ import {
|
||||
type Ref,
|
||||
type Tx,
|
||||
type TxRemoveDoc,
|
||||
TxProcessor,
|
||||
DocumentQuery,
|
||||
FindOptions,
|
||||
FindResult
|
||||
FindResult,
|
||||
TxProcessor
|
||||
} from '@hcengineering/core'
|
||||
import drive, { type FileVersion, type Folder } from '@hcengineering/drive'
|
||||
import type { TriggerControl } from '@hcengineering/server-core'
|
||||
|
||||
/** @public */
|
||||
export async function OnFileVersionDelete (
|
||||
tx: Tx,
|
||||
txes: Tx[],
|
||||
{ removedMap, ctx, storageAdapter, workspace }: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const rmTx = TxProcessor.extractTx(tx) as TxRemoveDoc<FileVersion>
|
||||
const result: Tx[] = []
|
||||
const toDelete: string[] = []
|
||||
for (const tx of txes) {
|
||||
const rmTx = TxProcessor.extractTx(tx) as TxRemoveDoc<FileVersion>
|
||||
|
||||
// Obtain document being deleted.
|
||||
const version = removedMap.get(rmTx.objectId) as FileVersion
|
||||
if (version !== undefined) {
|
||||
await storageAdapter.remove(ctx, workspace, [version.file])
|
||||
// Obtain document being deleted.
|
||||
const version = removedMap.get(rmTx.objectId) as FileVersion
|
||||
if (version !== undefined) {
|
||||
toDelete.push(version.file)
|
||||
}
|
||||
}
|
||||
if (toDelete.length > 0) {
|
||||
await storageAdapter.remove(ctx, workspace, toDelete)
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
||||
@@ -15,18 +15,19 @@
|
||||
|
||||
import core, {
|
||||
DocIndexState,
|
||||
DOMAIN_DOC_INDEX_STATE,
|
||||
getFullTextContext,
|
||||
isClassIndexable,
|
||||
isFullTextAttribute,
|
||||
Tx,
|
||||
TxProcessor,
|
||||
type AnyAttribute,
|
||||
type AttachedDoc,
|
||||
type Class,
|
||||
type Doc,
|
||||
type FullTextSearchContext,
|
||||
type Ref,
|
||||
type TxCollectionCUD,
|
||||
type TxCUD
|
||||
type TxCUD,
|
||||
type TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
|
||||
@@ -38,13 +39,17 @@ export async function OnChange (txes: Tx[], control: TriggerControl): Promise<Tx
|
||||
control.contextCache.set(indexingCtx, contexts)
|
||||
}
|
||||
|
||||
const docsToUpsert = new Map<Ref<DocIndexState>, DocIndexState>()
|
||||
if (control.ctx.contextData.fulltextUpdates === undefined) {
|
||||
control.ctx.contextData.fulltextUpdates = new Map()
|
||||
}
|
||||
const docsToUpsert = control.ctx.contextData.fulltextUpdates
|
||||
|
||||
for (let tx of txes) {
|
||||
if (tx._class === core.class.TxCollectionCUD) {
|
||||
const txcol = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||
tx = txcol.tx
|
||||
}
|
||||
const attrs = new Map<string, AnyAttribute>()
|
||||
if (TxProcessor.isExtendsCUD(tx._class)) {
|
||||
const cud = tx as TxCUD<Doc>
|
||||
|
||||
@@ -52,6 +57,35 @@ export async function OnChange (txes: Tx[], control: TriggerControl): Promise<Tx
|
||||
// No need, since no indixable fields or attachments.
|
||||
continue
|
||||
}
|
||||
|
||||
// We need to check if operation has indexable attributes.
|
||||
if (cud._class === core.class.TxUpdateDoc) {
|
||||
let hasFulltext = false
|
||||
// We could skip updates for a lot of transactions if they have non idexable attributes
|
||||
const upd = cud as TxUpdateDoc<Doc>
|
||||
for (const [k] of Object.entries(upd.operations)) {
|
||||
if (k.startsWith('$') || k === 'modifiedOn') {
|
||||
// Is operator
|
||||
// Skip operator changes
|
||||
} else {
|
||||
const key = upd.objectClass + '.' + k
|
||||
const attr = attrs.get(key) ?? control.hierarchy.getAttribute(upd.objectClass, k)
|
||||
if (attr !== undefined) {
|
||||
attrs.set(key, attr ?? null)
|
||||
}
|
||||
if (attr != null) {
|
||||
if (isFullTextAttribute(attr)) {
|
||||
hasFulltext = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasFulltext) {
|
||||
// No full text changes, no indexing is required
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
docsToUpsert.set(cud.objectId as Ref<DocIndexState>, {
|
||||
_id: cud.objectId as Ref<DocIndexState>,
|
||||
_class: core.class.DocIndexState,
|
||||
@@ -62,25 +96,8 @@ export async function OnChange (txes: Tx[], control: TriggerControl): Promise<Tx
|
||||
space: cud.space,
|
||||
removed: cud._class === core.class.TxRemoveDoc
|
||||
})
|
||||
|
||||
const propagate = getFullTextContext(control.hierarchy, cud.objectClass, contexts).propagate
|
||||
if (propagate !== undefined && propagate.length > 0) {
|
||||
control.ctx.contextData.needWarmupFulltext = true
|
||||
await control.ctx.with('clean-childs', { _class: cud.objectClass }, () => {
|
||||
// We need to propagate all changes to all child's of following classes.
|
||||
return control.lowLevel.rawUpdate<DocIndexState>(
|
||||
DOMAIN_DOC_INDEX_STATE,
|
||||
{ attachedTo: cud.objectClass, objectClass: { $in: propagate } },
|
||||
{ needIndex: true }
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (docsToUpsert.size > 0) {
|
||||
control.ctx.contextData.needWarmupFulltext = true
|
||||
await control.lowLevel.upload(control.ctx, DOMAIN_DOC_INDEX_STATE, Array.from(docsToUpsert.values()))
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
@@ -65,26 +65,27 @@ export async function FindMessages (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
export async function OnMessageCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const createTx = tx as TxCreateDoc<Message>
|
||||
|
||||
const createTx = tx as TxCreateDoc<Message>
|
||||
const message = TxProcessor.createDoc2Doc<Message>(createTx)
|
||||
|
||||
const message = TxProcessor.createDoc2Doc<Message>(createTx)
|
||||
|
||||
const channel = (
|
||||
await control.findAll(control.ctx, contact.class.Channel, { _id: message.attachedTo }, { limit: 1 })
|
||||
)[0]
|
||||
if (channel !== undefined) {
|
||||
if (channel.lastMessage === undefined || channel.lastMessage < message.sendOn) {
|
||||
const tx = control.txFactory.createTxUpdateDoc(channel._class, channel.space, channel._id, {
|
||||
lastMessage: message.sendOn
|
||||
})
|
||||
res.push(tx)
|
||||
const channel = (
|
||||
await control.findAll(control.ctx, contact.class.Channel, { _id: message.attachedTo }, { limit: 1 })
|
||||
)[0]
|
||||
if (channel !== undefined) {
|
||||
if (channel.lastMessage === undefined || channel.lastMessage < message.sendOn) {
|
||||
const tx = control.txFactory.createTxUpdateDoc(channel._class, channel.space, channel._id, {
|
||||
lastMessage: message.sendOn
|
||||
})
|
||||
result.push(tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,24 +35,27 @@ import view from '@hcengineering/view'
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnPublicLinkCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
export async function OnPublicLinkCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const extractedTx = TxProcessor.extractTx(tx)
|
||||
|
||||
const extractedTx = TxProcessor.extractTx(tx)
|
||||
const createTx = extractedTx as TxCreateDoc<PublicLink>
|
||||
|
||||
const createTx = extractedTx as TxCreateDoc<PublicLink>
|
||||
const link = TxProcessor.createDoc2Doc<PublicLink>(createTx)
|
||||
|
||||
const link = TxProcessor.createDoc2Doc<PublicLink>(createTx)
|
||||
if (link.url !== '') {
|
||||
continue
|
||||
}
|
||||
|
||||
if (link.url !== '') return res
|
||||
const resTx = control.txFactory.createTxUpdateDoc(link._class, link.space, link._id, {
|
||||
url: generateUrl(link._id, control.workspace, control.branding?.front)
|
||||
})
|
||||
|
||||
const resTx = control.txFactory.createTxUpdateDoc(link._class, link.space, link._id, {
|
||||
url: generateUrl(link._id, control.workspace, control.branding?.front)
|
||||
})
|
||||
result.push(resTx)
|
||||
}
|
||||
|
||||
res.push(resTx)
|
||||
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
export function getPublicLinkUrl (workspace: WorkspaceIdWithUrl, brandedFront?: string): string {
|
||||
|
||||
@@ -131,135 +131,164 @@ function getTxes (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxMixin<Employee, Staff>
|
||||
export async function OnDepartmentStaff (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxMixin<Employee, Staff>
|
||||
|
||||
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
|
||||
if (targetAccount.length === 0) return []
|
||||
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
|
||||
if (targetAccount.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (ctx.attributes.department !== undefined) {
|
||||
const lastDepartment = await getOldDepartment(ctx, control)
|
||||
if (ctx.attributes.department !== undefined) {
|
||||
const lastDepartment = await getOldDepartment(ctx, control)
|
||||
|
||||
const departmentId = ctx.attributes.department
|
||||
if (departmentId === null) {
|
||||
if (lastDepartment !== undefined) {
|
||||
const removed = await buildHierarchy(lastDepartment, control)
|
||||
return getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
[],
|
||||
removed.map((p) => p._id)
|
||||
const departmentId = ctx.attributes.department
|
||||
if (departmentId === null) {
|
||||
if (lastDepartment !== undefined) {
|
||||
const removed = await buildHierarchy(lastDepartment, control)
|
||||
result.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
[],
|
||||
removed.map((p) => p._id)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
const push = (await buildHierarchy(departmentId, control)).map((p) => p._id)
|
||||
|
||||
if (lastDepartment === undefined) {
|
||||
result.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
push
|
||||
)
|
||||
)
|
||||
} else {
|
||||
let removed = (await buildHierarchy(lastDepartment, control)).map((p) => p._id)
|
||||
const added = exlude(removed, push)
|
||||
removed = exlude(push, removed)
|
||||
result.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
added,
|
||||
removed
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
const push = (await buildHierarchy(departmentId, control)).map((p) => p._id)
|
||||
}
|
||||
|
||||
if (lastDepartment === undefined) {
|
||||
return getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
push
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnDepartmentRemove (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxRemoveDoc<Department>
|
||||
|
||||
const department = control.removedMap.get(ctx.objectId) as Department
|
||||
if (department === undefined) {
|
||||
continue
|
||||
}
|
||||
const nested = await control.findAll(control.ctx, hr.class.Department, { parent: department._id })
|
||||
for (const dep of nested) {
|
||||
result.push(control.txFactory.createTxRemoveDoc(dep._class, dep.space, dep._id))
|
||||
}
|
||||
const targetAccounts = await control.modelDb.findAll(contact.class.PersonAccount, {
|
||||
_id: { $in: department.members }
|
||||
})
|
||||
const employeeIds = targetAccounts.map((acc) => acc.person as Ref<Staff>)
|
||||
|
||||
const employee = await control.findAll(control.ctx, contact.mixin.Employee, {
|
||||
_id: { $in: employeeIds }
|
||||
})
|
||||
const removed = await buildHierarchy(department._id, control)
|
||||
employee.forEach((em) => {
|
||||
result.push(
|
||||
control.txFactory.createTxMixin(em._id, em._class, em.space, hr.mixin.Staff, { department: undefined })
|
||||
)
|
||||
})
|
||||
result.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccounts.map((it) => it._id),
|
||||
[],
|
||||
removed.map((p) => p._id)
|
||||
)
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnEmployee (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxMixin<Person, Employee>
|
||||
|
||||
const person = (await control.findAll(control.ctx, contact.class.Person, { _id: ctx.objectId }))[0]
|
||||
if (person === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
let removed = (await buildHierarchy(lastDepartment, control)).map((p) => p._id)
|
||||
const added = exlude(removed, push)
|
||||
removed = exlude(push, removed)
|
||||
return getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
added,
|
||||
removed
|
||||
const employee = control.hierarchy.as(person, ctx.mixin)
|
||||
if (control.hierarchy.hasMixin(person, hr.mixin.Staff) || !employee.active) {
|
||||
continue
|
||||
}
|
||||
|
||||
result.push(
|
||||
control.txFactory.createTxMixin(ctx.objectId, ctx.objectClass, ctx.objectSpace, hr.mixin.Staff, {
|
||||
department: hr.ids.Head
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnDepartmentRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxRemoveDoc<Department>
|
||||
export async function OnEmployeeDeactivate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (core.class.TxMixin !== actualTx._class) {
|
||||
continue
|
||||
}
|
||||
const ctx = actualTx as TxMixin<Person, Employee>
|
||||
if (ctx.mixin !== contact.mixin.Employee || ctx.attributes.active !== false) {
|
||||
continue
|
||||
}
|
||||
|
||||
const department = control.removedMap.get(ctx.objectId) as Department
|
||||
if (department === undefined) return []
|
||||
const res: Tx[] = []
|
||||
const nested = await control.findAll(control.ctx, hr.class.Department, { parent: department._id })
|
||||
for (const dep of nested) {
|
||||
res.push(control.txFactory.createTxRemoveDoc(dep._class, dep.space, dep._id))
|
||||
}
|
||||
const targetAccounts = await control.modelDb.findAll(contact.class.PersonAccount, {
|
||||
_id: { $in: department.members }
|
||||
})
|
||||
const employeeIds = targetAccounts.map((acc) => acc.person as Ref<Staff>)
|
||||
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
|
||||
if (targetAccount.length === 0) {
|
||||
continue
|
||||
}
|
||||
const set = new Set(targetAccount.map((it) => it._id))
|
||||
|
||||
const employee = await control.findAll(control.ctx, contact.mixin.Employee, {
|
||||
_id: { $in: employeeIds }
|
||||
})
|
||||
const removed = await buildHierarchy(department._id, control)
|
||||
employee.forEach((em) => {
|
||||
res.push(control.txFactory.createTxMixin(em._id, em._class, em.space, hr.mixin.Staff, { department: undefined }))
|
||||
})
|
||||
res.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccounts.map((it) => it._id),
|
||||
[],
|
||||
removed.map((p) => p._id)
|
||||
const departments = await control.queryFind(control.ctx, hr.class.Department, {})
|
||||
const removed = departments.filter((dep) => dep.members.some((p) => set.has(p)))
|
||||
result.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
[],
|
||||
removed.map((p) => p._id)
|
||||
)
|
||||
)
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxMixin<Person, Employee>
|
||||
|
||||
const person = (await control.findAll(control.ctx, contact.class.Person, { _id: ctx.objectId }))[0]
|
||||
if (person === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const employee = control.hierarchy.as(person, ctx.mixin)
|
||||
if (control.hierarchy.hasMixin(person, hr.mixin.Staff) || !employee.active) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
control.txFactory.createTxMixin(ctx.objectId, ctx.objectClass, ctx.objectSpace, hr.mixin.Staff, {
|
||||
department: hr.ids.Head
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnEmployeeDeactivate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (core.class.TxMixin !== actualTx._class) {
|
||||
return []
|
||||
}
|
||||
const ctx = actualTx as TxMixin<Person, Employee>
|
||||
if (ctx.mixin !== contact.mixin.Employee || ctx.attributes.active !== false) {
|
||||
return []
|
||||
}
|
||||
|
||||
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
|
||||
if (targetAccount.length === 0) return []
|
||||
const set = new Set(targetAccount.map((it) => it._id))
|
||||
|
||||
const departments = await control.queryFind(control.ctx, hr.class.Department, {})
|
||||
const removed = departments.filter((dep) => dep.members.some((p) => set.has(p)))
|
||||
return getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
[],
|
||||
removed.map((p) => p._id)
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
// TODO: why we need specific email notifications instead of using general flow?
|
||||
@@ -317,47 +346,63 @@ async function sendEmailNotifications (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRequestCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Request>
|
||||
export async function OnRequestCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Request>
|
||||
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const request = TxProcessor.createDoc2Doc(ctx)
|
||||
const request = TxProcessor.createDoc2Doc(ctx)
|
||||
|
||||
await sendEmailNotifications(control, sender, request, request.department, hr.ids.CreateRequestNotification)
|
||||
await sendEmailNotifications(control, sender, request, request.department, hr.ids.CreateRequestNotification)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRequestUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxUpdateDoc<Request>
|
||||
export async function OnRequestUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxUpdateDoc<Request>
|
||||
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const request = (await control.findAll(control.ctx, hr.class.Request, { _id: ctx.objectId }))[0] as Request
|
||||
if (request === undefined) return []
|
||||
const request = (await control.findAll(control.ctx, hr.class.Request, { _id: ctx.objectId }))[0] as Request
|
||||
if (request === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
await sendEmailNotifications(control, sender, request, request.department, hr.ids.UpdateRequestNotification)
|
||||
await sendEmailNotifications(control, sender, request, request.department, hr.ids.UpdateRequestNotification)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRequestRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Request>
|
||||
export async function OnRequestRemove (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Request>
|
||||
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const request = control.removedMap.get(ctx.objectId) as Request
|
||||
if (request === undefined) return []
|
||||
const request = control.removedMap.get(ctx.objectId) as Request
|
||||
if (request === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
await sendEmailNotifications(control, sender, request, request.department, hr.ids.RemoveRequestNotification)
|
||||
await sendEmailNotifications(control, sender, request, request.department, hr.ids.RemoveRequestNotification)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -400,23 +445,30 @@ export async function RequestTextPresenter (doc: Doc, control: TriggerControl):
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnPublicHolidayCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<PublicHoliday>
|
||||
export async function OnPublicHolidayCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<PublicHoliday>
|
||||
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
const employee = await getEmployee(sender.person as Ref<Employee>, control)
|
||||
if (employee === undefined) return []
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) {
|
||||
continue
|
||||
}
|
||||
const employee = await getEmployee(sender.person as Ref<Employee>, control)
|
||||
if (employee === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const publicHoliday = TxProcessor.createDoc2Doc(ctx)
|
||||
await sendEmailNotifications(
|
||||
control,
|
||||
sender,
|
||||
publicHoliday,
|
||||
publicHoliday.department,
|
||||
hr.ids.CreatePublicHolidayNotification
|
||||
)
|
||||
return []
|
||||
const publicHoliday = TxProcessor.createDoc2Doc(ctx)
|
||||
await sendEmailNotifications(
|
||||
control,
|
||||
sender,
|
||||
publicHoliday,
|
||||
publicHoliday.department,
|
||||
hr.ids.CreatePublicHolidayNotification
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,48 +43,51 @@ export async function leadTextPresenter (doc: Doc): Promise<string> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
let ownerId: Ref<PersonAccount> | undefined
|
||||
if (control.hierarchy.isDerived(tx._class, core.class.TxCreateDoc)) {
|
||||
const createTx = tx as TxCreateDoc<PersonAccount>
|
||||
export async function OnWorkspaceOwnerAdded (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
let ownerId: Ref<PersonAccount> | undefined
|
||||
if (control.hierarchy.isDerived(tx._class, core.class.TxCreateDoc)) {
|
||||
const createTx = tx as TxCreateDoc<PersonAccount>
|
||||
|
||||
if (createTx.attributes.role === AccountRole.Owner) {
|
||||
ownerId = createTx.objectId
|
||||
if (createTx.attributes.role === AccountRole.Owner) {
|
||||
ownerId = createTx.objectId
|
||||
}
|
||||
} else if (control.hierarchy.isDerived(tx._class, core.class.TxUpdateDoc)) {
|
||||
const updateTx = tx as TxUpdateDoc<PersonAccount>
|
||||
|
||||
if (updateTx.operations.role === AccountRole.Owner) {
|
||||
ownerId = updateTx.objectId
|
||||
}
|
||||
}
|
||||
} else if (control.hierarchy.isDerived(tx._class, core.class.TxUpdateDoc)) {
|
||||
const updateTx = tx as TxUpdateDoc<PersonAccount>
|
||||
|
||||
if (updateTx.operations.role === AccountRole.Owner) {
|
||||
ownerId = updateTx.objectId
|
||||
if (ownerId === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const targetFunnel = (
|
||||
await control.findAll(control.ctx, lead.class.Funnel, {
|
||||
_id: lead.space.DefaultFunnel
|
||||
})
|
||||
)[0]
|
||||
|
||||
if (targetFunnel === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
targetFunnel.owners === undefined ||
|
||||
targetFunnel.owners.length === 0 ||
|
||||
targetFunnel.owners[0] === core.account.System
|
||||
) {
|
||||
const updTx = control.txFactory.createTxUpdateDoc(lead.class.Funnel, targetFunnel.space, targetFunnel._id, {
|
||||
owners: [ownerId]
|
||||
})
|
||||
result.push(updTx)
|
||||
}
|
||||
}
|
||||
|
||||
if (ownerId === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const targetFunnel = (
|
||||
await control.findAll(control.ctx, lead.class.Funnel, {
|
||||
_id: lead.space.DefaultFunnel
|
||||
})
|
||||
)[0]
|
||||
|
||||
if (targetFunnel === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (
|
||||
targetFunnel.owners === undefined ||
|
||||
targetFunnel.owners.length === 0 ||
|
||||
targetFunnel.owners[0] === core.account.System
|
||||
) {
|
||||
const updTx = control.txFactory.createTxUpdateDoc(lead.class.Funnel, targetFunnel.space, targetFunnel._id, {
|
||||
owners: [ownerId]
|
||||
})
|
||||
return [updTx]
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"prettier-plugin-svelte": "^3.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
|
||||
@@ -23,11 +23,14 @@ import core, {
|
||||
TxMixin,
|
||||
TxProcessor,
|
||||
TxUpdateDoc,
|
||||
UserStatus
|
||||
UserStatus,
|
||||
Doc,
|
||||
concatLink
|
||||
} from '@hcengineering/core'
|
||||
import love, {
|
||||
Invite,
|
||||
JoinRequest,
|
||||
MeetingMinutes,
|
||||
ParticipantInfo,
|
||||
RequestStatus,
|
||||
RoomAccess,
|
||||
@@ -35,41 +38,51 @@ import love, {
|
||||
loveId
|
||||
} from '@hcengineering/love'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import { getMetadata, translate } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import {
|
||||
createPushNotification,
|
||||
getNotificationProviderControl,
|
||||
isAllowed
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxMixin<Person, Employee>
|
||||
if (actualTx._class !== core.class.TxMixin) return []
|
||||
if (actualTx.mixin !== contact.mixin.Employee) return []
|
||||
const val = actualTx.attributes.active
|
||||
if (val === undefined) return []
|
||||
if (val) {
|
||||
const freeRoom = (await control.findAll(control.ctx, love.class.Office, { person: null }))[0]
|
||||
if (freeRoom !== undefined) {
|
||||
return [
|
||||
control.txFactory.createTxUpdateDoc(freeRoom._class, freeRoom.space, freeRoom._id, {
|
||||
person: actualTx.objectId
|
||||
})
|
||||
]
|
||||
export async function OnEmployee (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxMixin<Person, Employee>
|
||||
if (actualTx._class !== core.class.TxMixin) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
const room = (await control.findAll(control.ctx, love.class.Office, { person: actualTx.objectId }))[0]
|
||||
if (room !== undefined) {
|
||||
return [
|
||||
control.txFactory.createTxUpdateDoc(room._class, room.space, room._id, {
|
||||
person: null
|
||||
})
|
||||
]
|
||||
if (actualTx.mixin !== contact.mixin.Employee) {
|
||||
continue
|
||||
}
|
||||
const val = actualTx.attributes.active
|
||||
if (val === undefined) {
|
||||
continue
|
||||
}
|
||||
if (val) {
|
||||
const freeRoom = (await control.findAll(control.ctx, love.class.Office, { person: null }))[0]
|
||||
if (freeRoom !== undefined) {
|
||||
return [
|
||||
control.txFactory.createTxUpdateDoc(freeRoom._class, freeRoom.space, freeRoom._id, {
|
||||
person: actualTx.objectId
|
||||
})
|
||||
]
|
||||
}
|
||||
} else {
|
||||
const room = (await control.findAll(control.ctx, love.class.Office, { person: actualTx.objectId }))[0]
|
||||
if (room !== undefined) {
|
||||
result.push(
|
||||
control.txFactory.createTxUpdateDoc(room._class, room.space, room._id, {
|
||||
person: null
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
async function createUserInfo (acc: Ref<Account>, control: TriggerControl): Promise<Tx[]> {
|
||||
@@ -124,33 +137,32 @@ async function removeUserInfo (acc: Ref<Account>, control: TriggerControl): Prom
|
||||
return res
|
||||
}
|
||||
|
||||
export async function OnUserStatus (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<UserStatus>
|
||||
if (actualTx.objectClass !== core.class.UserStatus) return []
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const createTx = actualTx as TxCreateDoc<UserStatus>
|
||||
const status = TxProcessor.createDoc2Doc(createTx)
|
||||
return await createUserInfo(status.user, control)
|
||||
} else if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = actualTx as TxUpdateDoc<UserStatus>
|
||||
const val = updateTx.operations.online
|
||||
if (val === undefined) return []
|
||||
const status = (await control.findAll(control.ctx, core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
||||
if (status !== undefined) {
|
||||
if (val) {
|
||||
return await createUserInfo(status.user, control)
|
||||
} else {
|
||||
return await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
void removeUserInfo(status.user, control).then((res) => {
|
||||
resolve(res)
|
||||
})
|
||||
}, 20000)
|
||||
})
|
||||
export async function OnUserStatus (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<UserStatus>
|
||||
if (actualTx.objectClass !== core.class.UserStatus) {
|
||||
continue
|
||||
}
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const createTx = actualTx as TxCreateDoc<UserStatus>
|
||||
const status = TxProcessor.createDoc2Doc(createTx)
|
||||
result.push(...(await createUserInfo(status.user, control)))
|
||||
} else if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = actualTx as TxUpdateDoc<UserStatus>
|
||||
const val = updateTx.operations.online
|
||||
if (val === undefined) return []
|
||||
const status = (await control.findAll(control.ctx, core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
||||
if (status !== undefined) {
|
||||
if (val) {
|
||||
result.push(...(await createUserInfo(status.user, control)))
|
||||
} else {
|
||||
result.push(...(await removeUserInfo(status.user, control)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
async function roomJoinHandler (info: ParticipantInfo, control: TriggerControl): Promise<Tx[]> {
|
||||
@@ -228,118 +240,165 @@ async function setDefaultRoomAccess (info: ParticipantInfo, control: TriggerCont
|
||||
return res
|
||||
}
|
||||
|
||||
export async function OnParticipantInfo (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<ParticipantInfo>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const info = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<ParticipantInfo>)
|
||||
return await roomJoinHandler(info, control)
|
||||
export async function OnParticipantInfo (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<ParticipantInfo>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const info = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<ParticipantInfo>)
|
||||
result.push(...(await roomJoinHandler(info, control)))
|
||||
}
|
||||
if (actualTx._class === core.class.TxRemoveDoc) {
|
||||
const removedInfo = control.removedMap.get(actualTx.objectId) as ParticipantInfo
|
||||
if (removedInfo === undefined) {
|
||||
continue
|
||||
}
|
||||
result.push(...(await setDefaultRoomAccess(removedInfo, control)))
|
||||
continue
|
||||
}
|
||||
if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
const newRoom = (actualTx as TxUpdateDoc<ParticipantInfo>).operations.room
|
||||
if (newRoom === undefined) {
|
||||
continue
|
||||
}
|
||||
const info = (
|
||||
await control.findAll(control.ctx, love.class.ParticipantInfo, { _id: actualTx.objectId }, { limit: 1 })
|
||||
)[0]
|
||||
if (info === undefined) {
|
||||
continue
|
||||
}
|
||||
result.push(...(await rejectJoinRequests(info, control)))
|
||||
result.push(...(await setDefaultRoomAccess(info, control)))
|
||||
result.push(...(await roomJoinHandler(info, control)))
|
||||
}
|
||||
}
|
||||
if (actualTx._class === core.class.TxRemoveDoc) {
|
||||
const removedInfo = control.removedMap.get(actualTx.objectId) as ParticipantInfo
|
||||
if (removedInfo === undefined) return []
|
||||
return await setDefaultRoomAccess(removedInfo, control)
|
||||
}
|
||||
if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
const newRoom = (actualTx as TxUpdateDoc<ParticipantInfo>).operations.room
|
||||
if (newRoom === undefined) return []
|
||||
const info = (
|
||||
await control.findAll(control.ctx, love.class.ParticipantInfo, { _id: actualTx.objectId }, { limit: 1 })
|
||||
)[0]
|
||||
if (info === undefined) return []
|
||||
const res: Tx[] = []
|
||||
res.push(...(await rejectJoinRequests(info, control)))
|
||||
res.push(...(await setDefaultRoomAccess(info, control)))
|
||||
res.push(...(await roomJoinHandler(info, control)))
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnKnock (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<JoinRequest>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const request = TxProcessor.createDoc2Doc(actualTx)
|
||||
if (request.status === RequestStatus.Pending) {
|
||||
const roomInfo = (await control.findAll(control.ctx, love.class.RoomInfo, { room: request.room }))[0]
|
||||
if (roomInfo !== undefined) {
|
||||
const from = (await control.findAll(control.ctx, contact.class.Person, { _id: request.person }))[0]
|
||||
if (from === undefined) {
|
||||
continue
|
||||
}
|
||||
const type = await control.modelDb.findOne(notification.class.NotificationType, {
|
||||
_id: love.ids.KnockNotification
|
||||
})
|
||||
if (type === undefined) {
|
||||
continue
|
||||
}
|
||||
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
|
||||
_id: notification.providers.PushNotificationProvider
|
||||
})
|
||||
if (provider === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const notificationControl = await getNotificationProviderControl(control.ctx, control)
|
||||
for (const user of roomInfo.persons) {
|
||||
const userAcc = control.modelDb.getAccountByPersonId(user) as PersonAccount[]
|
||||
if (userAcc.length === 0) continue
|
||||
if (userAcc.some((it) => isAllowed(control, it._id, type, provider, notificationControl))) {
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||
const title = await translate(love.string.KnockingLabel, {})
|
||||
const body = await translate(love.string.IsKnocking, {
|
||||
name: formatName(from.name, control.branding?.lastNameFirst)
|
||||
})
|
||||
|
||||
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
|
||||
user: userAcc[0]._id
|
||||
})
|
||||
// TODO: Select proper account target
|
||||
await createPushNotification(control, userAcc[0]._id, title, body, request._id, subscriptions, from, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnKnock (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<JoinRequest>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const request = TxProcessor.createDoc2Doc(actualTx)
|
||||
if (request.status === RequestStatus.Pending) {
|
||||
const roomInfo = (await control.findAll(control.ctx, love.class.RoomInfo, { room: request.room }))[0]
|
||||
if (roomInfo !== undefined) {
|
||||
const res: Tx[] = []
|
||||
const from = (await control.findAll(control.ctx, contact.class.Person, { _id: request.person }))[0]
|
||||
if (from === undefined) return []
|
||||
export async function OnInvite (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<Invite>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const invite = TxProcessor.createDoc2Doc(actualTx)
|
||||
if (invite.status === RequestStatus.Pending) {
|
||||
const target = (await control.findAll(control.ctx, contact.class.Person, { _id: invite.target }))[0]
|
||||
if (target === undefined) {
|
||||
continue
|
||||
}
|
||||
const userAcc = control.modelDb.getAccountByPersonId(target._id) as PersonAccount[]
|
||||
if (userAcc.length === 0) {
|
||||
continue
|
||||
}
|
||||
const from = (await control.findAll(control.ctx, contact.class.Person, { _id: invite.from }))[0]
|
||||
const type = await control.modelDb.findOne(notification.class.NotificationType, {
|
||||
_id: love.ids.KnockNotification
|
||||
_id: love.ids.InviteNotification
|
||||
})
|
||||
if (type === undefined) return []
|
||||
if (type === undefined) {
|
||||
continue
|
||||
}
|
||||
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
|
||||
_id: notification.providers.PushNotificationProvider
|
||||
})
|
||||
if (provider === undefined) return []
|
||||
|
||||
const notificationControl = await getNotificationProviderControl(control.ctx, control)
|
||||
for (const user of roomInfo.persons) {
|
||||
const userAcc = control.modelDb.getAccountByPersonId(user) as PersonAccount[]
|
||||
if (userAcc.length === 0) continue
|
||||
if (userAcc.some((it) => isAllowed(control, it._id, type, provider, notificationControl))) {
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||
const title = await translate(love.string.KnockingLabel, {})
|
||||
const body = await translate(love.string.IsKnocking, {
|
||||
name: formatName(from.name, control.branding?.lastNameFirst)
|
||||
})
|
||||
|
||||
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
|
||||
user: userAcc[0]._id
|
||||
})
|
||||
// TODO: Select proper account target
|
||||
await createPushNotification(control, userAcc[0]._id, title, body, request._id, subscriptions, from, path)
|
||||
}
|
||||
if (provider === undefined) {
|
||||
continue
|
||||
}
|
||||
const notificationControl = await getNotificationProviderControl(control.ctx, control)
|
||||
if (userAcc.some((it) => isAllowed(control, it._id, type, provider, notificationControl))) {
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||
const title = await translate(love.string.InivitingLabel, {})
|
||||
const body =
|
||||
from !== undefined
|
||||
? await translate(love.string.InvitingYou, {
|
||||
name: formatName(from.name, control.branding?.lastNameFirst)
|
||||
})
|
||||
: await translate(love.string.InivitingLabel, {})
|
||||
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
|
||||
user: userAcc[0]._id
|
||||
})
|
||||
// TODO: Select a proper user
|
||||
await createPushNotification(control, userAcc[0]._id, title, body, invite._id, subscriptions, from, path)
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnInvite (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<Invite>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const invite = TxProcessor.createDoc2Doc(actualTx)
|
||||
if (invite.status === RequestStatus.Pending) {
|
||||
const target = (await control.findAll(control.ctx, contact.class.Person, { _id: invite.target }))[0]
|
||||
if (target === undefined) return []
|
||||
const userAcc = control.modelDb.getAccountByPersonId(target._id) as PersonAccount[]
|
||||
if (userAcc.length === 0) return []
|
||||
const from = (await control.findAll(control.ctx, contact.class.Person, { _id: invite.from }))[0]
|
||||
const type = await control.modelDb.findOne(notification.class.NotificationType, {
|
||||
_id: love.ids.InviteNotification
|
||||
})
|
||||
if (type === undefined) return []
|
||||
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
|
||||
_id: notification.providers.PushNotificationProvider
|
||||
})
|
||||
if (provider === undefined) return []
|
||||
const notificationControl = await getNotificationProviderControl(control.ctx, control)
|
||||
if (userAcc.some((it) => isAllowed(control, it._id, type, provider, notificationControl))) {
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||
const title = await translate(love.string.InivitingLabel, {})
|
||||
const body =
|
||||
from !== undefined
|
||||
? await translate(love.string.InvitingYou, {
|
||||
name: formatName(from.name, control.branding?.lastNameFirst)
|
||||
})
|
||||
: await translate(love.string.InivitingLabel, {})
|
||||
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
|
||||
user: userAcc[0]._id
|
||||
})
|
||||
// TODO: Select a proper user
|
||||
await createPushNotification(control, userAcc[0]._id, title, body, invite._id, subscriptions, from, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
export async function meetingMinutesHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const meetingMinutes = doc as MeetingMinutes
|
||||
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
|
||||
|
||||
const panelProps = [view.component.EditDoc, meetingMinutes._id, meetingMinutes._class]
|
||||
const fragment = encodeURIComponent(panelProps.join('|'))
|
||||
const path = `${workbenchId}/${control.workspace.workspaceUrl}/${loveId}#${fragment}`
|
||||
const link = concatLink(front, path)
|
||||
return `<a href="${link}">${meetingMinutes.title}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function meetingMinutesTextPresenter (doc: Doc): Promise<string> {
|
||||
const meetingMinutes = doc as MeetingMinutes
|
||||
return meetingMinutes.title
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
function: {
|
||||
MeetingMinutesHTMLPresenter: meetingMinutesHTMLPresenter,
|
||||
MeetingMinutesTextPresenter: meetingMinutesTextPresenter
|
||||
},
|
||||
trigger: {
|
||||
OnEmployee,
|
||||
OnUserStatus,
|
||||
|
||||
@@ -38,7 +38,9 @@
|
||||
"prettier-plugin-svelte": "^3.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-notification": "^0.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { TriggerFunc } from '@hcengineering/server-core'
|
||||
import { Presenter } from '@hcengineering/server-notification'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -11,6 +12,10 @@ export const serverLoveId = 'server-love' as Plugin
|
||||
* @public
|
||||
*/
|
||||
export default plugin(serverLoveId, {
|
||||
function: {
|
||||
MeetingMinutesHTMLPresenter: '' as Resource<Presenter>,
|
||||
MeetingMinutesTextPresenter: '' as Resource<Presenter>
|
||||
},
|
||||
trigger: {
|
||||
OnEmployee: '' as Resource<TriggerFunc>,
|
||||
OnUserStatus: '' as Resource<TriggerFunc>,
|
||||
|
||||
@@ -331,7 +331,7 @@ export async function getDocCollaborators (
|
||||
const collaborators = new Set<Ref<Account>>()
|
||||
for (const field of mixin.fields) {
|
||||
const value = (doc as any)[field]
|
||||
const newCollaborators = await ctx.with('getKeyCollaborators', {}, async (ctx) =>
|
||||
const newCollaborators = ctx.withSync('getKeyCollaborators', {}, (ctx) =>
|
||||
getKeyCollaborators(doc._class, value, field, control)
|
||||
)
|
||||
if (newCollaborators !== undefined) {
|
||||
@@ -991,10 +991,8 @@ export async function createCollabDocInfo (
|
||||
return res
|
||||
}
|
||||
|
||||
const usersInfo = await ctx.with(
|
||||
'get-user-info',
|
||||
{},
|
||||
async (ctx) => await getUsersInfo(ctx, [...Array.from(targets), originTx.modifiedBy as Ref<PersonAccount>], control)
|
||||
const usersInfo = await ctx.with('get-user-info', {}, (ctx) =>
|
||||
getUsersInfo(ctx, [...Array.from(targets), originTx.modifiedBy as Ref<PersonAccount>], control)
|
||||
)
|
||||
const sender: SenderInfo = usersInfo.get(originTx.modifiedBy) ?? {
|
||||
_id: originTx.modifiedBy
|
||||
@@ -1159,36 +1157,22 @@ async function createCollaboratorDoc (
|
||||
}
|
||||
|
||||
const doc = TxProcessor.createDoc2Doc(tx)
|
||||
const collaborators = await ctx.with(
|
||||
'get-collaborators',
|
||||
{},
|
||||
async (ctx) => await getDocCollaborators(ctx, doc, mixin, control)
|
||||
)
|
||||
const collaborators = await ctx.with('get-collaborators', {}, (ctx) => getDocCollaborators(ctx, doc, mixin, control))
|
||||
const mixinTx = getMixinTx(tx, control, collaborators)
|
||||
|
||||
const notificationTxes = await ctx.with(
|
||||
'create-collabdocinfo',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await createCollabDocInfo(
|
||||
ctx,
|
||||
collaborators as Ref<PersonAccount>[],
|
||||
control,
|
||||
tx,
|
||||
originTx,
|
||||
doc,
|
||||
activityMessage,
|
||||
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true }
|
||||
)
|
||||
const notificationTxes = await ctx.with('create-collabdocinfo', {}, (ctx) =>
|
||||
createCollabDocInfo(ctx, collaborators as Ref<PersonAccount>[], control, tx, originTx, doc, activityMessage, {
|
||||
isOwn: true,
|
||||
isSpace: false,
|
||||
shouldUpdateTimestamp: true
|
||||
})
|
||||
)
|
||||
res.push(mixinTx)
|
||||
res.push(...notificationTxes)
|
||||
|
||||
res.push(
|
||||
...(await ctx.with(
|
||||
'get-space-collabtxes',
|
||||
{},
|
||||
async (ctx) => await getSpaceCollabTxes(ctx, control, doc, tx, originTx, activityMessage, cache)
|
||||
...(await ctx.with('get-space-collabtxes', {}, (ctx) =>
|
||||
getSpaceCollabTxes(ctx, control, doc, tx, originTx, activityMessage, cache)
|
||||
))
|
||||
)
|
||||
|
||||
@@ -1268,10 +1252,8 @@ async function updateCollaboratorsMixin (
|
||||
objectId: tx.objectId
|
||||
})
|
||||
|
||||
const infos = await ctx.with(
|
||||
'get-user-info',
|
||||
{},
|
||||
async (ctx) => await getUsersInfo(ctx, [...newCollabs, originTx.modifiedBy] as Ref<PersonAccount>[], control)
|
||||
const infos = await ctx.with('get-user-info', {}, (ctx) =>
|
||||
getUsersInfo(ctx, [...newCollabs, originTx.modifiedBy] as Ref<PersonAccount>[], control)
|
||||
)
|
||||
const sender: SenderInfo = infos.get(originTx.modifiedBy) ?? { _id: originTx.modifiedBy }
|
||||
|
||||
@@ -1332,27 +1314,15 @@ async function collectionCollabDoc (
|
||||
|
||||
cache.set(doc._id, doc)
|
||||
|
||||
const collaborators = await ctx.with(
|
||||
'get-collaborators',
|
||||
{},
|
||||
async (ctx) => await getCollaborators(ctx, doc, control, tx, res)
|
||||
)
|
||||
const collaborators = await ctx.with('get-collaborators', {}, (ctx) => getCollaborators(ctx, doc, control, tx, res))
|
||||
|
||||
res = res.concat(
|
||||
await ctx.with(
|
||||
'create-collab-doc-info',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await createCollabDocInfo(
|
||||
ctx,
|
||||
collaborators as Ref<PersonAccount>[],
|
||||
control,
|
||||
actualTx,
|
||||
tx,
|
||||
doc,
|
||||
activityMessages,
|
||||
{ isOwn: false, isSpace: false, shouldUpdateTimestamp: true }
|
||||
)
|
||||
await ctx.with('create-collab-doc-info', {}, (ctx) =>
|
||||
createCollabDocInfo(ctx, collaborators as Ref<PersonAccount>[], control, actualTx, tx, doc, activityMessages, {
|
||||
isOwn: false,
|
||||
isSpace: false,
|
||||
shouldUpdateTimestamp: true
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1504,11 +1474,7 @@ async function updateCollaboratorDoc (
|
||||
const params: NotifyParams = { isOwn: true, isSpace: false, shouldUpdateTimestamp: true }
|
||||
if (hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
|
||||
// we should handle change field and subscribe new collaborators
|
||||
const collabsInfo = await ctx.with(
|
||||
'get-tx-collaborators',
|
||||
{},
|
||||
async (ctx) => await getTxCollabs(ctx, tx, control, doc)
|
||||
)
|
||||
const collabsInfo = await ctx.with('get-tx-collaborators', {}, (ctx) => getTxCollabs(ctx, tx, control, doc))
|
||||
|
||||
if (collabsInfo.added.length > 0) {
|
||||
res.push(createPushCollaboratorsTx(control, tx.objectId, tx.objectClass, tx.objectSpace, collabsInfo.added))
|
||||
@@ -1519,28 +1485,23 @@ async function updateCollaboratorDoc (
|
||||
}
|
||||
|
||||
res = res.concat(
|
||||
await ctx.with(
|
||||
'create-collab-docinfo',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await createCollabDocInfo(
|
||||
ctx,
|
||||
collabsInfo.result as Ref<PersonAccount>[],
|
||||
control,
|
||||
tx,
|
||||
originTx,
|
||||
doc,
|
||||
activityMessages,
|
||||
params,
|
||||
collabsInfo.removed as Ref<PersonAccount>[]
|
||||
)
|
||||
await ctx.with('create-collab-docinfo', {}, (ctx) =>
|
||||
createCollabDocInfo(
|
||||
ctx,
|
||||
collabsInfo.result as Ref<PersonAccount>[],
|
||||
control,
|
||||
tx,
|
||||
originTx,
|
||||
doc,
|
||||
activityMessages,
|
||||
params,
|
||||
collabsInfo.removed as Ref<PersonAccount>[]
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
const collaborators = await ctx.with(
|
||||
'get-doc-collaborators',
|
||||
{},
|
||||
async (ctx) => await getDocCollaborators(ctx, doc, mixin, control)
|
||||
const collaborators = await ctx.with('get-doc-collaborators', {}, (ctx) =>
|
||||
getDocCollaborators(ctx, doc, mixin, control)
|
||||
)
|
||||
res.push(getMixinTx(tx, control, collaborators))
|
||||
res = res.concat(
|
||||
@@ -1558,14 +1519,12 @@ async function updateCollaboratorDoc (
|
||||
}
|
||||
|
||||
res = res.concat(
|
||||
await ctx.with(
|
||||
'get-space-collabtxes',
|
||||
{},
|
||||
async (ctx) => await getSpaceCollabTxes(ctx, control, doc, tx, originTx, activityMessages, cache)
|
||||
await ctx.with('get-space-collabtxes', {}, (ctx) =>
|
||||
getSpaceCollabTxes(ctx, control, doc, tx, originTx, activityMessages, cache)
|
||||
)
|
||||
)
|
||||
res = res.concat(
|
||||
await ctx.with('update-notify-context-space', {}, async (ctx) => await updateNotifyContextsSpace(ctx, control, tx))
|
||||
await ctx.with('update-notify-context-space', {}, (ctx) => updateNotifyContextsSpace(ctx, control, tx))
|
||||
)
|
||||
|
||||
return res
|
||||
@@ -1574,54 +1533,70 @@ async function updateCollaboratorDoc (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnAttributeCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const attribute = TxProcessor.createDoc2Doc(tx as TxCreateDoc<AnyAttribute>)
|
||||
const group = (
|
||||
await control.modelDb.findAll(notification.class.NotificationGroup, { objectClass: attribute.attributeOf })
|
||||
)[0]
|
||||
if (group === undefined) return []
|
||||
const isCollection: boolean = core.class.Collection === attribute.type._class
|
||||
const objectClass = !isCollection ? attribute.attributeOf : (attribute.type as Collection<AttachedDoc>).of
|
||||
const txClasses = !isCollection
|
||||
? [control.hierarchy.isMixin(attribute.attributeOf) ? core.class.TxMixin : core.class.TxUpdateDoc]
|
||||
: [core.class.TxCreateDoc, core.class.TxRemoveDoc]
|
||||
const data: Data<NotificationType> = {
|
||||
attribute: attribute._id,
|
||||
group: group._id,
|
||||
field: attribute.name,
|
||||
generated: true,
|
||||
objectClass,
|
||||
txClasses,
|
||||
hidden: false,
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{doc} updated'
|
||||
},
|
||||
label: attribute.label
|
||||
export async function OnAttributeCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const attribute = TxProcessor.createDoc2Doc(tx as TxCreateDoc<AnyAttribute>)
|
||||
const group = (
|
||||
await control.modelDb.findAll(notification.class.NotificationGroup, { objectClass: attribute.attributeOf })
|
||||
)[0]
|
||||
if (group === undefined) {
|
||||
continue
|
||||
}
|
||||
const isCollection: boolean = core.class.Collection === attribute.type._class
|
||||
const objectClass = !isCollection ? attribute.attributeOf : (attribute.type as Collection<AttachedDoc>).of
|
||||
const txClasses = !isCollection
|
||||
? [control.hierarchy.isMixin(attribute.attributeOf) ? core.class.TxMixin : core.class.TxUpdateDoc]
|
||||
: [core.class.TxCreateDoc, core.class.TxRemoveDoc]
|
||||
const data: Data<NotificationType> = {
|
||||
attribute: attribute._id,
|
||||
group: group._id,
|
||||
field: attribute.name,
|
||||
generated: true,
|
||||
objectClass,
|
||||
txClasses,
|
||||
hidden: false,
|
||||
defaultEnabled: false,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{doc} updated'
|
||||
},
|
||||
label: attribute.label
|
||||
}
|
||||
if (isCollection) {
|
||||
data.attachedToClass = attribute.attributeOf
|
||||
}
|
||||
const id =
|
||||
`${notification.class.NotificationType}_${attribute.attributeOf}_${attribute.name}` as Ref<NotificationType>
|
||||
result.push(control.txFactory.createTxCreateDoc(notification.class.NotificationType, core.space.Model, data, id))
|
||||
}
|
||||
if (isCollection) {
|
||||
data.attachedToClass = attribute.attributeOf
|
||||
}
|
||||
const id =
|
||||
`${notification.class.NotificationType}_${attribute.attributeOf}_${attribute.name}` as Ref<NotificationType>
|
||||
const res = control.txFactory.createTxCreateDoc(notification.class.NotificationType, core.space.Model, data, id)
|
||||
return [res]
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnAttributeUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = tx as TxUpdateDoc<AnyAttribute>
|
||||
if (ctx.operations.hidden === undefined) return []
|
||||
const type = (await control.findAll(control.ctx, notification.class.NotificationType, { attribute: ctx.objectId }))[0]
|
||||
if (type === undefined) return []
|
||||
const res = control.txFactory.createTxUpdateDoc(type._class, type.space, type._id, {
|
||||
hidden: ctx.operations.hidden
|
||||
})
|
||||
return [res]
|
||||
export async function OnAttributeUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = tx as TxUpdateDoc<AnyAttribute>
|
||||
if (ctx.operations.hidden === undefined) {
|
||||
continue
|
||||
}
|
||||
const type = (
|
||||
await control.findAll(control.ctx, notification.class.NotificationType, { attribute: ctx.objectId })
|
||||
)[0]
|
||||
if (type === undefined) {
|
||||
continue
|
||||
}
|
||||
result.push(
|
||||
control.txFactory.createTxUpdateDoc(type._class, type.space, type._id, {
|
||||
hidden: ctx.operations.hidden
|
||||
})
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function applyUserTxes (
|
||||
@@ -1755,10 +1730,8 @@ export async function createCollaboratorNotifications (
|
||||
): Promise<Tx[]> {
|
||||
if (tx.space === core.space.DerivedTx) {
|
||||
// do not forgot update collaborators for derived tx
|
||||
return await ctx.with(
|
||||
'updateDerivedCollaborators',
|
||||
{},
|
||||
async (ctx) => await updateCollaborators(ctx, control, TxProcessor.extractTx(tx) as TxCUD<Doc>, cache)
|
||||
return await ctx.with('updateDerivedCollaborators', {}, (ctx) =>
|
||||
updateCollaborators(ctx, control, TxProcessor.extractTx(tx) as TxCUD<Doc>, cache)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1768,45 +1741,27 @@ export async function createCollaboratorNotifications (
|
||||
|
||||
switch (tx._class) {
|
||||
case core.class.TxCreateDoc: {
|
||||
const res = await ctx.with(
|
||||
'createCollaboratorDoc',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await createCollaboratorDoc(ctx, tx as TxCreateDoc<Doc>, control, activityMessages, originTx ?? tx, cache)
|
||||
const res = await ctx.with('createCollaboratorDoc', {}, (ctx) =>
|
||||
createCollaboratorDoc(ctx, tx as TxCreateDoc<Doc>, control, activityMessages, originTx ?? tx, cache)
|
||||
)
|
||||
|
||||
return await applyUserTxes(ctx, control, res)
|
||||
}
|
||||
case core.class.TxUpdateDoc:
|
||||
case core.class.TxMixin: {
|
||||
let res = await ctx.with(
|
||||
'updateCollaboratorDoc',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await updateCollaboratorDoc(ctx, tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
|
||||
let res = await ctx.with('updateCollaboratorDoc', {}, (ctx) =>
|
||||
updateCollaboratorDoc(ctx, tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
|
||||
)
|
||||
res = res.concat(
|
||||
await ctx.with(
|
||||
'updateCollaboratorMixin',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await updateCollaboratorsMixin(
|
||||
ctx,
|
||||
tx as TxMixin<Doc, Collaborators>,
|
||||
control,
|
||||
activityMessages,
|
||||
originTx ?? tx
|
||||
)
|
||||
await ctx.with('updateCollaboratorMixin', {}, (ctx) =>
|
||||
updateCollaboratorsMixin(ctx, tx as TxMixin<Doc, Collaborators>, control, activityMessages, originTx ?? tx)
|
||||
)
|
||||
)
|
||||
return await applyUserTxes(ctx, control, res)
|
||||
}
|
||||
case core.class.TxCollectionCUD: {
|
||||
const res = await ctx.with(
|
||||
'collectionCollabDoc',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await collectionCollabDoc(ctx, tx as TxCollectionCUD<Doc, AttachedDoc>, control, activityMessages, cache)
|
||||
const res = await ctx.with('collectionCollabDoc', {}, (ctx) =>
|
||||
collectionCollabDoc(ctx, tx as TxCollectionCUD<Doc, AttachedDoc>, control, activityMessages, cache)
|
||||
)
|
||||
return await applyUserTxes(ctx, control, res)
|
||||
}
|
||||
@@ -1914,29 +1869,33 @@ async function OnActivityMessageRemove (message: ActivityMessage, control: Trigg
|
||||
return res
|
||||
}
|
||||
|
||||
async function OnEmployeeDeactivate (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (core.class.TxMixin !== actualTx._class) {
|
||||
return []
|
||||
}
|
||||
const ctx = actualTx as TxMixin<Person, Employee>
|
||||
if (ctx.mixin !== contact.mixin.Employee || ctx.attributes.active !== false) {
|
||||
return []
|
||||
}
|
||||
async function OnEmployeeDeactivate (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (core.class.TxMixin !== actualTx._class) {
|
||||
continue
|
||||
}
|
||||
const ctx = actualTx as TxMixin<Person, Employee>
|
||||
if (ctx.mixin !== contact.mixin.Employee || ctx.attributes.active !== false) {
|
||||
continue
|
||||
}
|
||||
|
||||
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
|
||||
if (targetAccount.length === 0) return []
|
||||
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
|
||||
if (targetAccount.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
for (const acc of targetAccount) {
|
||||
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
|
||||
user: acc._id
|
||||
})
|
||||
for (const sub of subscriptions) {
|
||||
res.push(control.txFactory.createTxRemoveDoc(sub._class, sub.space, sub._id))
|
||||
for (const acc of targetAccount) {
|
||||
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
|
||||
user: acc._id
|
||||
})
|
||||
for (const sub of subscriptions) {
|
||||
result.push(control.txFactory.createTxRemoveDoc(sub._class, sub.space, sub._id))
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
async function OnDocRemove (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
|
||||
@@ -84,22 +84,23 @@ export async function applicationTextPresenter (doc: Doc, control: TriggerContro
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRecruitUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, recruit.class.Vacancy)) {
|
||||
return []
|
||||
}
|
||||
export async function OnRecruitUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, recruit.class.Vacancy)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
handleVacancyCreate(control, actualTx, res)
|
||||
} else if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
await handleVacancyUpdate(control, actualTx, res)
|
||||
} else if (actualTx._class === core.class.TxRemoveDoc) {
|
||||
handleVacancyRemove(control, actualTx, res)
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
handleVacancyCreate(control, actualTx, result)
|
||||
} else if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
await handleVacancyUpdate(control, actualTx, result)
|
||||
} else if (actualTx._class === core.class.TxRemoveDoc) {
|
||||
handleVacancyRemove(control, actualTx, result)
|
||||
}
|
||||
}
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user