mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-25 20:42:56 +03:00
Change room ui to document view (#7167)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
parent
e493d35ac6
commit
99a35e08db
@ -28,6 +28,7 @@
|
|||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hcengineering/attachment": "^0.6.14",
|
||||||
"@hcengineering/activity": "^0.6.0",
|
"@hcengineering/activity": "^0.6.0",
|
||||||
"@hcengineering/chunter": "^0.6.20",
|
"@hcengineering/chunter": "^0.6.20",
|
||||||
"@hcengineering/contact": "^0.6.24",
|
"@hcengineering/contact": "^0.6.24",
|
||||||
|
@ -14,7 +14,14 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import contact, { type Employee, type Person } from '@hcengineering/contact'
|
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 {
|
import {
|
||||||
type DevicesPreference,
|
type DevicesPreference,
|
||||||
type Floor,
|
type Floor,
|
||||||
@ -34,18 +41,21 @@ import {
|
|||||||
} from '@hcengineering/love'
|
} from '@hcengineering/love'
|
||||||
import {
|
import {
|
||||||
type Builder,
|
type Builder,
|
||||||
|
Collection,
|
||||||
Collection as PropCollection,
|
Collection as PropCollection,
|
||||||
Hidden,
|
Hidden,
|
||||||
Index,
|
Index,
|
||||||
Mixin,
|
Mixin,
|
||||||
Model,
|
Model,
|
||||||
Prop,
|
Prop,
|
||||||
|
ReadOnly,
|
||||||
|
TypeCollaborativeDoc,
|
||||||
TypeRef,
|
TypeRef,
|
||||||
TypeString,
|
TypeString,
|
||||||
UX
|
UX
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import calendar, { TEvent } from '@hcengineering/model-calendar'
|
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 preference, { TPreference } from '@hcengineering/model-preference'
|
||||||
import presentation from '@hcengineering/model-presentation'
|
import presentation from '@hcengineering/model-presentation'
|
||||||
import view, { createAction } from '@hcengineering/model-view'
|
import view, { createAction } from '@hcengineering/model-view'
|
||||||
@ -55,22 +65,32 @@ import setting from '@hcengineering/setting'
|
|||||||
import workbench, { WidgetType } from '@hcengineering/workbench'
|
import workbench, { WidgetType } from '@hcengineering/workbench'
|
||||||
import activity from '@hcengineering/activity'
|
import activity from '@hcengineering/activity'
|
||||||
import chunter from '@hcengineering/chunter'
|
import chunter from '@hcengineering/chunter'
|
||||||
|
import attachment from '@hcengineering/attachment'
|
||||||
|
|
||||||
import love from './plugin'
|
import love from './plugin'
|
||||||
|
|
||||||
export { loveId } from '@hcengineering/love'
|
export { loveId } from '@hcengineering/love'
|
||||||
export * from './migration'
|
export * from './migration'
|
||||||
export const DOMAIN_LOVE = 'love' as Domain
|
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)
|
@Model(love.class.Room, core.class.Doc, DOMAIN_LOVE)
|
||||||
|
@UX(love.string.Room, love.icon.Love)
|
||||||
export class TRoom extends TDoc implements Room {
|
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
|
type!: RoomType
|
||||||
|
|
||||||
access!: RoomAccess
|
access!: RoomAccess
|
||||||
|
|
||||||
@Prop(TypeRef(love.class.Floor), love.string.Floor)
|
@Prop(TypeRef(love.class.Floor), love.string.Floor)
|
||||||
|
@ReadOnly()
|
||||||
// @Index(IndexKind.Indexed)
|
// @Index(IndexKind.Indexed)
|
||||||
floor!: Ref<Floor>
|
floor!: Ref<Floor>
|
||||||
|
|
||||||
@ -81,12 +101,20 @@ export class TRoom extends TDoc implements Room {
|
|||||||
|
|
||||||
language!: RoomLanguage
|
language!: RoomLanguage
|
||||||
startWithTranscription!: boolean
|
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)
|
@Model(love.class.Office, love.class.Room)
|
||||||
|
@UX(love.string.Office, love.icon.Love)
|
||||||
export class TOffice extends TRoom implements Office {
|
export class TOffice extends TRoom implements Office {
|
||||||
@Prop(TypeRef(contact.mixin.Employee), contact.string.Employee)
|
@Prop(TypeRef(contact.mixin.Employee), contact.string.Employee)
|
||||||
@Index(IndexKind.Indexed)
|
@Index(IndexKind.Indexed)
|
||||||
|
@ReadOnly()
|
||||||
person!: Ref<Employee> | null
|
person!: Ref<Employee> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,22 +183,27 @@ export class TMeeting extends TEvent implements Meeting {
|
|||||||
room!: Ref<Room>
|
room!: Ref<Room>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(love.class.MeetingMinutes, core.class.Doc, DOMAIN_LOVE)
|
@Model(love.class.MeetingMinutes, core.class.Doc, DOMAIN_MEETING_MINUTES)
|
||||||
@UX(love.string.Meeting)
|
@UX(love.string.MeetingMinutes, love.icon.Cam)
|
||||||
export class TMeetingMinutes extends TDoc implements MeetingMinutes {
|
export class TMeetingMinutes extends TAttachedDoc implements MeetingMinutes {
|
||||||
@Hidden()
|
@Hidden()
|
||||||
sid!: string
|
sid!: string
|
||||||
|
|
||||||
@Prop(TypeString(), view.string.Title)
|
@Prop(TypeString(), view.string.Title)
|
||||||
|
@Index(IndexKind.FullText)
|
||||||
title!: string
|
title!: string
|
||||||
|
|
||||||
@Prop(TypeRef(love.class.Room), love.string.Room)
|
@Prop(TypeCollaborativeDoc(), core.string.Description)
|
||||||
room!: Ref<Room>
|
@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
|
transcription?: number
|
||||||
|
|
||||||
@Prop(PropCollection(activity.class.ActivityMessage), activity.string.Messages)
|
@Prop(PropCollection(chunter.class.ChatMessage), activity.string.Messages)
|
||||||
messages?: number
|
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, {
|
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
|
||||||
ofClass: love.class.MeetingMinutes,
|
ofClass: love.class.MeetingMinutes,
|
||||||
components: { input: chunter.component.ChatMessageInput }
|
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, {
|
builder.mixin(love.class.MeetingMinutes, core.class.Class, view.mixin.ObjectPresenter, {
|
||||||
presenter: love.component.MeetingMinutesPresenter
|
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, {
|
builder.mixin(love.class.Room, core.class.Class, core.mixin.IndexConfiguration, {
|
||||||
indexes: [],
|
indexes: [],
|
||||||
searchDisabled: true
|
searchDisabled: true
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import contact from '@hcengineering/contact'
|
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 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 {
|
import {
|
||||||
createDefaultSpace,
|
createDefaultSpace,
|
||||||
migrateSpace,
|
migrateSpace,
|
||||||
@ -28,7 +28,7 @@ import {
|
|||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import core from '@hcengineering/model-core'
|
import core from '@hcengineering/model-core'
|
||||||
import love from './plugin'
|
import love from './plugin'
|
||||||
import { DOMAIN_LOVE } from '.'
|
import { DOMAIN_LOVE, DOMAIN_MEETING_MINUTES } from '.'
|
||||||
|
|
||||||
async function createDefaultFloor (tx: TxOperations): Promise<void> {
|
async function createDefaultFloor (tx: TxOperations): Promise<void> {
|
||||||
const current = await tx.findOne(love.class.Floor, {
|
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))
|
const data = createDefaultRooms(employees.map((p) => p._id))
|
||||||
for (const room of data) {
|
for (const room of data) {
|
||||||
const _class = isOffice(room) ? love.class.Office : love.class.Room
|
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,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
startWithTranscription: false
|
startWithTranscription: false,
|
||||||
|
description: makeCollaborativeDoc(love.ids.Reception, 'description')
|
||||||
},
|
},
|
||||||
love.ids.Reception
|
love.ids.Reception
|
||||||
)
|
)
|
||||||
@ -109,15 +110,38 @@ export const loveOperation: MigrateOperation = {
|
|||||||
)
|
)
|
||||||
await client.update(
|
await client.update(
|
||||||
DOMAIN_LOVE,
|
DOMAIN_LOVE,
|
||||||
{ _class: love.class.Room, startWithTranscription: { $exists: false } },
|
{ _class: love.class.Room, type: RoomType.Video, startWithTranscription: { $exists: false } },
|
||||||
{ startWithTranscription: true }
|
{ startWithTranscription: true }
|
||||||
)
|
)
|
||||||
|
await client.update(
|
||||||
|
DOMAIN_LOVE,
|
||||||
|
{ _class: love.class.Room, startWithTranscription: { $exists: false } },
|
||||||
|
{ startWithTranscription: false }
|
||||||
|
)
|
||||||
await client.update(
|
await client.update(
|
||||||
DOMAIN_LOVE,
|
DOMAIN_LOVE,
|
||||||
{ _class: love.class.Office, startWithTranscription: { $exists: false } },
|
{ _class: love.class.Office, startWithTranscription: { $exists: false } },
|
||||||
{ startWithTranscription: 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.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { type Doc, type Ref } from '@hcengineering/core'
|
import { type Client, type Doc, type Ref } from '@hcengineering/core'
|
||||||
import { type NotificationGroup } from '@hcengineering/notification'
|
import { type NotificationType, type NotificationGroup } from '@hcengineering/notification'
|
||||||
import { mergeIds } from '@hcengineering/platform'
|
import { type Resource, mergeIds } from '@hcengineering/platform'
|
||||||
import { type AnyComponent } from '@hcengineering/ui'
|
import { type AnyComponent } from '@hcengineering/ui'
|
||||||
import { type ActionCategory, type ViewAction } from '@hcengineering/view'
|
import { type ActionCategory, type ViewAction } from '@hcengineering/view'
|
||||||
import { loveId } from '@hcengineering/love'
|
import { loveId } from '@hcengineering/love'
|
||||||
@ -43,6 +43,10 @@ export default mergeIds(loveId, love, {
|
|||||||
},
|
},
|
||||||
ids: {
|
ids: {
|
||||||
Settings: '' as Ref<Doc>,
|
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>>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -27,14 +27,15 @@
|
|||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hcengineering/core": "^0.6.32",
|
|
||||||
"@hcengineering/contact": "^0.6.24",
|
"@hcengineering/contact": "^0.6.24",
|
||||||
|
"@hcengineering/core": "^0.6.32",
|
||||||
|
"@hcengineering/love": "^0.6.0",
|
||||||
"@hcengineering/model": "^0.6.11",
|
"@hcengineering/model": "^0.6.11",
|
||||||
"@hcengineering/platform": "^0.6.11",
|
|
||||||
"@hcengineering/server-core": "^0.6.1",
|
|
||||||
"@hcengineering/model-core": "^0.6.0",
|
"@hcengineering/model-core": "^0.6.0",
|
||||||
"@hcengineering/model-love": "^0.6.0",
|
"@hcengineering/model-love": "^0.6.0",
|
||||||
"@hcengineering/love": "^0.6.0",
|
"@hcengineering/platform": "^0.6.11",
|
||||||
"@hcengineering/server-love": "^0.6.0"
|
"@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 serverCore from '@hcengineering/server-core'
|
||||||
import love from '@hcengineering/love'
|
import love from '@hcengineering/love'
|
||||||
import serverLove from '@hcengineering/server-love'
|
import serverLove from '@hcengineering/server-love'
|
||||||
|
import serverNotification from '@hcengineering/server-notification'
|
||||||
|
|
||||||
export { serverLoveId } from '@hcengineering/server-love'
|
export { serverLoveId } from '@hcengineering/server-love'
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
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, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverLove.trigger.OnEmployee,
|
trigger: serverLove.trigger.OnEmployee,
|
||||||
txMatch: {
|
txMatch: {
|
||||||
|
@ -371,6 +371,12 @@ export function fitPopupElement (
|
|||||||
newProps.left = '1px'
|
newProps.left = '1px'
|
||||||
newProps.right = '1px'
|
newProps.right = '1px'
|
||||||
show = true
|
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) {
|
} else if (element === 'content' && contentPanel !== undefined) {
|
||||||
const rect = contentPanel.getBoundingClientRect()
|
const rect = contentPanel.getBoundingClientRect()
|
||||||
newProps.top = `${rect.top}px`
|
newProps.top = `${rect.top}px`
|
||||||
|
@ -231,7 +231,8 @@ export const posAlignment = [
|
|||||||
'centered',
|
'centered',
|
||||||
'center',
|
'center',
|
||||||
'status',
|
'status',
|
||||||
'movable'
|
'movable',
|
||||||
|
'full-centered'
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export type PopupPosAlignment = (typeof posAlignment)[number]
|
export type PopupPosAlignment = (typeof posAlignment)[number]
|
||||||
|
@ -15,24 +15,36 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import workbench, { Widget, WidgetTab } from '@hcengineering/workbench'
|
import workbench, { Widget, WidgetTab } from '@hcengineering/workbench'
|
||||||
import { FilePreview, DownloadFileButton, FilePreviewPopup, FileTypeIcon } from '@hcengineering/presentation'
|
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 { getResource } from '@hcengineering/platform'
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
|
|
||||||
import attachment from '../plugin'
|
import attachment from '../plugin'
|
||||||
|
|
||||||
export let widget: Widget
|
export let widget: Widget
|
||||||
export let tab: WidgetTab
|
export let tab: WidgetTab | undefined
|
||||||
|
|
||||||
$: file = tab.data?.file
|
$: file = tab?.data?.file
|
||||||
$: fileName = tab.data?.name ?? ''
|
$: fileName = tab?.data?.name ?? ''
|
||||||
$: contentType = tab.data?.contentType
|
$: contentType = tab?.data?.contentType
|
||||||
$: metadata = tab.data?.metadata
|
$: metadata = tab?.data?.metadata
|
||||||
|
|
||||||
async function closeTab (): Promise<void> {
|
async function closeTab (): Promise<void> {
|
||||||
|
if (tab === undefined) return
|
||||||
const fn = await getResource(workbench.function.CloseWidgetTab)
|
const fn = await getResource(workbench.function.CloseWidgetTab)
|
||||||
await fn(widget, tab.id)
|
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>
|
</script>
|
||||||
|
|
||||||
<Header
|
<Header
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
export let icon: Asset
|
export let icon: Asset
|
||||||
export let header: IntlString
|
export let header: IntlString
|
||||||
export let label: IntlString
|
export let label: IntlString | undefined = undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
@ -26,9 +26,11 @@
|
|||||||
<div class="an-element__label header">
|
<div class="an-element__label header">
|
||||||
<Label label={header} />
|
<Label label={header} />
|
||||||
</div>
|
</div>
|
||||||
<span class="an-element__label">
|
{#if label}
|
||||||
<Label {label} />
|
<span class="an-element__label">
|
||||||
</span>
|
<Label {label} />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
import { Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||||
import notification, { DocNotifyContext } from '@hcengineering/notification'
|
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 { getClient, isSpace } from '@hcengineering/presentation'
|
||||||
import { getMessageFromLoc, messageInFocus } from '@hcengineering/activity-resources'
|
import { getMessageFromLoc, messageInFocus } from '@hcengineering/activity-resources'
|
||||||
import { location as locationStore } from '@hcengineering/ui'
|
import { location as locationStore } from '@hcengineering/ui'
|
||||||
@ -26,10 +26,11 @@
|
|||||||
import ReverseChannelScrollView from './ReverseChannelScrollView.svelte'
|
import ReverseChannelScrollView from './ReverseChannelScrollView.svelte'
|
||||||
|
|
||||||
export let object: Doc
|
export let object: Doc
|
||||||
export let context: DocNotifyContext | undefined
|
export let context: DocNotifyContext | undefined = undefined
|
||||||
export let syncLocation = true
|
export let syncLocation = true
|
||||||
export let autofocus = true
|
export let autofocus = true
|
||||||
export let freeze = false
|
export let freeze = false
|
||||||
|
export let readonly = false
|
||||||
export let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
|
export let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
|
||||||
export let collection: string | undefined = undefined
|
export let collection: string | undefined = undefined
|
||||||
export let withInput: boolean = true
|
export let withInput: boolean = true
|
||||||
@ -113,6 +114,7 @@
|
|||||||
{autofocus}
|
{autofocus}
|
||||||
loadMoreAllowed={!isDocChannel}
|
loadMoreAllowed={!isDocChannel}
|
||||||
{withInput}
|
{withInput}
|
||||||
|
{readonly}
|
||||||
{onReply}
|
{onReply}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
export let threadId: Ref<ActivityMessage> | undefined
|
export let threadId: Ref<ActivityMessage> | undefined
|
||||||
export let collection: string | undefined = undefined
|
export let collection: string | undefined = undefined
|
||||||
export let withInput: boolean = true
|
export let withInput: boolean = true
|
||||||
|
export let readonly: boolean = false
|
||||||
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
||||||
|
|
||||||
const notificationsClient = InboxNotificationsClientImpl.getClient()
|
const notificationsClient = InboxNotificationsClientImpl.getClient()
|
||||||
@ -43,6 +44,7 @@
|
|||||||
{#if renderChannel && visible}
|
{#if renderChannel && visible}
|
||||||
<div class="channel" class:invisible={threadId !== undefined} style:height style:width>
|
<div class="channel" class:invisible={threadId !== undefined} style:height style:width>
|
||||||
{#key object._id}
|
{#key object._id}
|
||||||
|
<slot name="header" />
|
||||||
<Channel
|
<Channel
|
||||||
{object}
|
{object}
|
||||||
{context}
|
{context}
|
||||||
@ -51,13 +53,14 @@
|
|||||||
{collection}
|
{collection}
|
||||||
{withInput}
|
{withInput}
|
||||||
{onReply}
|
{onReply}
|
||||||
|
{readonly}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if threadId && visible}
|
{#if threadId && visible}
|
||||||
<div class="thread" style:height style:width>
|
<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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
export let context: DocNotifyContext | undefined
|
export let context: DocNotifyContext | undefined
|
||||||
export let autofocus = true
|
export let autofocus = true
|
||||||
export let embedded: boolean = false
|
export let embedded: boolean = false
|
||||||
|
export let readonly: boolean = false
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
@ -54,7 +55,7 @@
|
|||||||
isThreadOpened = newLocation.path[4] != null
|
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)
|
$: showJoinOverlay = shouldShowJoinOverlay(object)
|
||||||
$: isDocChat = !hierarchy.isDerived(object._class, chunter.class.ChunterSpace)
|
$: isDocChat = !hierarchy.isDerived(object._class, chunter.class.ChunterSpace)
|
||||||
$: withAside =
|
$: withAside =
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
export let loadMoreAllowed = true
|
export let loadMoreAllowed = true
|
||||||
export let autofocus = true
|
export let autofocus = true
|
||||||
export let withInput: boolean = true
|
export let withInput: boolean = true
|
||||||
|
export let readonly: boolean = false
|
||||||
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
||||||
|
|
||||||
const minMsgHeightRem = 2
|
const minMsgHeightRem = 2
|
||||||
@ -113,7 +114,9 @@
|
|||||||
$: notifyContext = $contextByDocStore.get(doc._id)
|
$: notifyContext = $contextByDocStore.get(doc._id)
|
||||||
$: isThread = hierarchy.isDerived(doc._class, activity.class.ActivityMessage)
|
$: isThread = hierarchy.isDerived(doc._class, activity.class.ActivityMessage)
|
||||||
$: isChunterSpace = hierarchy.isDerived(doc._class, chunter.class.ChunterSpace)
|
$: 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 =
|
$: separatorIndex =
|
||||||
$newTimestampStore !== undefined
|
$newTimestampStore !== undefined
|
||||||
@ -575,7 +578,7 @@
|
|||||||
removeTxListener(newMessageTxListener)
|
removeTxListener(newMessageTxListener)
|
||||||
})
|
})
|
||||||
|
|
||||||
$: showBlankView = !$isLoadingStore && messages.length === 0 && !isThread && !readonly
|
$: showBlankView = !$isLoadingStore && messages.length === 0 && !isThread
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-col relative" class:h-full={fullHeight}>
|
<div class="flex-col relative" class:h-full={fullHeight}>
|
||||||
@ -597,7 +600,7 @@
|
|||||||
<BlankView
|
<BlankView
|
||||||
icon={chunter.icon.Thread}
|
icon={chunter.icon.Thread}
|
||||||
header={chunter.string.NoMessagesInChannel}
|
header={chunter.string.NoMessagesInChannel}
|
||||||
label={chunter.string.SendMessagesInChannel}
|
label={readonly ? undefined : chunter.string.SendMessagesInChannel}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -644,7 +647,7 @@
|
|||||||
{#if loadMoreAllowed && $canLoadNextForwardStore}
|
{#if loadMoreAllowed && $canLoadNextForwardStore}
|
||||||
<HistoryLoading isLoading={$isLoadingMoreStore} />
|
<HistoryLoading isLoading={$isLoadingMoreStore} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if !fixedInput && withInput}
|
{#if !fixedInput && withInput && !readonly}
|
||||||
<ChannelInput {object} {readonly} boundary={scrollDiv} {collection} {isThread} {autofocus} />
|
<ChannelInput {object} {readonly} boundary={scrollDiv} {collection} {isThread} {autofocus} />
|
||||||
{/if}
|
{/if}
|
||||||
</BaseChatScroller>
|
</BaseChatScroller>
|
||||||
@ -661,10 +664,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if fixedInput && withInput}
|
{#if fixedInput && withInput && !readonly}
|
||||||
<ChannelInput {object} {readonly} boundary={scrollDiv} {collection} {isThread} {autofocus} />
|
<ChannelInput {object} {readonly} boundary={scrollDiv} {collection} {isThread} {autofocus} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if readonly}
|
||||||
|
<div class="h-6" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.selectedDate {
|
.selectedDate {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
export let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
|
export let selectedMessageId: Ref<ActivityMessage> | undefined = undefined
|
||||||
export let message: ActivityMessage
|
export let message: ActivityMessage
|
||||||
export let autofocus = true
|
export let autofocus = true
|
||||||
|
export let readonly: boolean = false
|
||||||
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
@ -56,8 +57,8 @@
|
|||||||
|
|
||||||
$: messagesStore = dataProvider?.messagesStore
|
$: messagesStore = dataProvider?.messagesStore
|
||||||
$: readonly = hierarchy.isDerived(message.attachedToClass, core.class.Space)
|
$: readonly = hierarchy.isDerived(message.attachedToClass, core.class.Space)
|
||||||
? (channel as Space)?.archived ?? false
|
? (readonly || (channel as Space)?.archived) ?? false
|
||||||
: false
|
: readonly
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="hulyComponent-content hulyComponent-content__container noShrink">
|
<div class="hulyComponent-content hulyComponent-content__container noShrink">
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
export let showHeader: boolean = true
|
export let showHeader: boolean = true
|
||||||
export let syncLocation = true
|
export let syncLocation = true
|
||||||
export let autofocus = true
|
export let autofocus = true
|
||||||
|
export let readonly: boolean = false
|
||||||
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
@ -145,7 +146,7 @@
|
|||||||
|
|
||||||
{#if message}
|
{#if message}
|
||||||
{#key _id}
|
{#key _id}
|
||||||
<ThreadContent bind:selectedMessageId {message} {autofocus} {onReply} />
|
<ThreadContent bind:selectedMessageId {message} {autofocus} {readonly} {onReply} />
|
||||||
{/key}
|
{/key}
|
||||||
{:else if isLoading}
|
{:else if isLoading}
|
||||||
<Loading />
|
<Loading />
|
||||||
|
@ -67,6 +67,10 @@
|
|||||||
"Meeting": "Meeting",
|
"Meeting": "Meeting",
|
||||||
"Transcription": "Transcription",
|
"Transcription": "Transcription",
|
||||||
"StartWithTranscription": "Start with 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",
|
"Meeting": "Reunión",
|
||||||
"Transcription": "Transcripción",
|
"Transcription": "Transcripción",
|
||||||
"StartWithTranscription": "Iniciar con 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",
|
"Meeting": "Réunion",
|
||||||
"Transcription": "Transcription",
|
"Transcription": "Transcription",
|
||||||
"StartWithTranscription": "Démarrer avec la 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",
|
"Meeting": "Riunione",
|
||||||
"Transcription": "Trascrizione",
|
"Transcription": "Trascrizione",
|
||||||
"StartWithTranscription": "Inizia con la 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",
|
"Meeting": "Reunião",
|
||||||
"Transcription": "Transcrição",
|
"Transcription": "Transcrição",
|
||||||
"StartWithTranscription": "Começar com 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": "Встреча",
|
"Meeting": "Встреча",
|
||||||
"Transcription": "Транскрипция",
|
"Transcription": "Транскрипция",
|
||||||
"StartWithTranscription": "Начинать с транскрипцией",
|
"StartWithTranscription": "Начинать с транскрипцией",
|
||||||
"MeetingMinutes": "Протоколы встреч"
|
"MeetingMinutes": "Результаты встреч",
|
||||||
|
"StartMeeting": "Начать встречу",
|
||||||
|
"Video": "Видео",
|
||||||
|
"NoMeetingMinutes": "Нет результатов встреч",
|
||||||
|
"JoinMeeting": "Присоединиться к встрече"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,10 @@
|
|||||||
"Meeting": "会议",
|
"Meeting": "会议",
|
||||||
"Transcription": "转录",
|
"Transcription": "转录",
|
||||||
"StartWithTranscription": "开始转录",
|
"StartWithTranscription": "开始转录",
|
||||||
"MeetingMinutes": "会议记录"
|
"MeetingMinutes": "会议记录",
|
||||||
|
"StartMeeting": "开始会议",
|
||||||
|
"Video": "视频",
|
||||||
|
"NoMeetingMinutes": "无会议记录",
|
||||||
|
"JoinMeeting": "加入会议"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,8 @@
|
|||||||
"@hcengineering/core": "^0.6.32",
|
"@hcengineering/core": "^0.6.32",
|
||||||
"@hcengineering/login": "^0.6.12",
|
"@hcengineering/login": "^0.6.12",
|
||||||
"@hcengineering/love": "^0.6.0",
|
"@hcengineering/love": "^0.6.0",
|
||||||
|
"@hcengineering/notification": "^0.6.23",
|
||||||
|
"@hcengineering/notification-resources": "^0.6.0",
|
||||||
"@hcengineering/panel": "^0.6.23",
|
"@hcengineering/panel": "^0.6.23",
|
||||||
"@hcengineering/platform": "^0.6.11",
|
"@hcengineering/platform": "^0.6.11",
|
||||||
"@hcengineering/presentation": "^0.6.3",
|
"@hcengineering/presentation": "^0.6.3",
|
||||||
|
90
plugins/love-resources/src/components/ActiveMeeting.svelte
Normal file
90
plugins/love-resources/src/components/ActiveMeeting.svelte
Normal file
@ -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">
|
<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 { translate } from '@hcengineering/platform'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Button, DropdownIntlItem } from '@hcengineering/ui'
|
import { Button, DropdownIntlItem } from '@hcengineering/ui'
|
||||||
@ -51,6 +51,7 @@
|
|||||||
const client = getClient()
|
const client = getClient()
|
||||||
const floorRooms = $rooms.filter((r) => r.floor === floor)
|
const floorRooms = $rooms.filter((r) => r.floor === floor)
|
||||||
const pos = getFreePosition(floorRooms, 2, 1)
|
const pos = getFreePosition(floorRooms, 2, 1)
|
||||||
|
const _id = generateId<Room>()
|
||||||
const data: Data<Room> = {
|
const data: Data<Room> = {
|
||||||
floor,
|
floor,
|
||||||
name: val._class === love.class.Office ? '' : await translate(val.label, {}),
|
name: val._class === love.class.Office ? '' : await translate(val.label, {}),
|
||||||
@ -61,12 +62,13 @@
|
|||||||
type: val.type,
|
type: val.type,
|
||||||
access: val.access,
|
access: val.access,
|
||||||
language: 'en',
|
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) {
|
if (val._class === love.class.Office) {
|
||||||
;(data as Data<Office>).person = null
|
;(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')
|
dispatch('close')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -27,14 +27,12 @@
|
|||||||
showPopup,
|
showPopup,
|
||||||
type AnySvelteComponent,
|
type AnySvelteComponent,
|
||||||
type CompAndProps,
|
type CompAndProps,
|
||||||
resizeObserver,
|
|
||||||
IconMoreV,
|
IconMoreV,
|
||||||
ButtonMenu,
|
ButtonMenu,
|
||||||
DropdownIntlItem
|
DropdownIntlItem
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import view, { Action } from '@hcengineering/view'
|
import view, { Action } from '@hcengineering/view'
|
||||||
import { getActions } from '@hcengineering/view-resources'
|
import { getActions } from '@hcengineering/view-resources'
|
||||||
import { afterUpdate } from 'svelte'
|
|
||||||
|
|
||||||
import love from '../plugin'
|
import love from '../plugin'
|
||||||
import { currentRoom, myInfo, myOffice } from '../stores'
|
import { currentRoom, myInfo, myOffice } from '../stores'
|
||||||
@ -61,19 +59,17 @@
|
|||||||
import MicSettingPopup from './MicSettingPopup.svelte'
|
import MicSettingPopup from './MicSettingPopup.svelte'
|
||||||
import RoomAccessPopup from './RoomAccessPopup.svelte'
|
import RoomAccessPopup from './RoomAccessPopup.svelte'
|
||||||
import RoomLanguageSelector from './RoomLanguageSelector.svelte'
|
import RoomLanguageSelector from './RoomLanguageSelector.svelte'
|
||||||
|
import ControlBarContainer from './ControlBarContainer.svelte'
|
||||||
|
|
||||||
export let room: Room
|
export let room: Room
|
||||||
export let fullScreen: boolean = false
|
export let fullScreen: boolean = false
|
||||||
|
export let onFullScreen: (() => void) | undefined = undefined
|
||||||
|
|
||||||
let allowCam: boolean = false
|
let allowCam: boolean = false
|
||||||
const allowShare: boolean = true
|
const allowShare: boolean = true
|
||||||
let allowLeave: boolean = false
|
let allowLeave: boolean = false
|
||||||
let popup: CompAndProps | undefined = undefined
|
let popup: CompAndProps | undefined = undefined
|
||||||
let grow: HTMLElement
|
|
||||||
let leftPanel: HTMLElement
|
|
||||||
let leftPanelSize: number = 0
|
|
||||||
let noLabel: boolean = false
|
let noLabel: boolean = false
|
||||||
let combinePanel: boolean = false
|
|
||||||
|
|
||||||
$: allowCam = $currentRoom?.type === RoomType.Video
|
$: allowCam = $currentRoom?.type === RoomType.Video
|
||||||
$: allowLeave = $myInfo?.room !== ($myOffice?._id ?? love.ids.Reception)
|
$: 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 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 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 actions: Action[] = []
|
||||||
let moreItems: DropdownIntlItem[] = []
|
let moreItems: DropdownIntlItem[] = []
|
||||||
|
|
||||||
@ -173,86 +158,88 @@
|
|||||||
const fn = await getResource(action.action)
|
const fn = await getResource(action.action)
|
||||||
await fn(room)
|
await fn(room)
|
||||||
}
|
}
|
||||||
|
$: withVideo = $screenSharing || room.type === RoomType.Video
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bar w-full flex-center flex-gap-2 flex-no-shrink" class:combinePanel use:resizeObserver={checkBar}>
|
<ControlBarContainer bind:noLabel>
|
||||||
<div class="bar__right-panel flex-gap-2 flex-center">
|
<svelte:fragment slot="right">
|
||||||
{#if $isConnected && isTranscriptionAllowed() && $isTranscription}
|
{#if $isConnected && isTranscriptionAllowed() && $isTranscription}
|
||||||
<RoomLanguageSelector {room} kind="icon" />
|
<RoomLanguageSelector {room} kind="icon" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</svelte:fragment>
|
||||||
<div bind:this={grow} class="flex-grow" />
|
<svelte:fragment slot="center">
|
||||||
{#if room._id !== love.ids.Reception}
|
{#if room._id !== love.ids.Reception}
|
||||||
<ModernButton
|
<ModernButton
|
||||||
icon={roomAccessIcon[room.access]}
|
icon={roomAccessIcon[room.access]}
|
||||||
tooltip={{ label: love.string.ChangeAccess }}
|
tooltip={{ label: love.string.ChangeAccess }}
|
||||||
kind={'secondary'}
|
kind={'secondary'}
|
||||||
size={'large'}
|
size={'large'}
|
||||||
disabled={isOffice(room) && room.person !== me}
|
disabled={isOffice(room) && room.person !== me}
|
||||||
on:click={setAccess}
|
on:click={setAccess}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $isConnected}
|
{#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}
|
|
||||||
<SplitButton
|
<SplitButton
|
||||||
size={'large'}
|
size={'large'}
|
||||||
icon={$isCameraEnabled ? love.icon.CamEnabled : love.icon.CamDisabled}
|
icon={$isMicEnabled ? love.icon.MicEnabled : love.icon.MicDisabled}
|
||||||
showTooltip={{ label: $isCameraEnabled ? love.string.StopVideo : love.string.StartVideo, keys: camKeys }}
|
showTooltip={{ label: $isMicEnabled ? love.string.Mute : love.string.UnMute, keys: micKeys }}
|
||||||
disabled={!$isConnected}
|
action={changeMute}
|
||||||
action={changeCam}
|
|
||||||
secondIcon={IconUpOutline}
|
secondIcon={IconUpOutline}
|
||||||
secondAction={camSettings}
|
secondAction={micSettings}
|
||||||
separate
|
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}
|
||||||
{#if allowShare}
|
</svelte:fragment>
|
||||||
<ModernButton
|
<svelte:fragment slot="left">
|
||||||
icon={$isSharingEnabled ? love.icon.SharingEnabled : love.icon.SharingDisabled}
|
{#if $isConnected && withVideo && onFullScreen}
|
||||||
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}
|
|
||||||
<ModernButton
|
<ModernButton
|
||||||
icon={$isFullScreen ? love.icon.ExitFullScreen : love.icon.FullScreen}
|
icon={$isFullScreen ? love.icon.ExitFullScreen : love.icon.FullScreen}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
@ -287,54 +274,23 @@
|
|||||||
on:click={leave}
|
on:click={leave}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</svelte:fragment>
|
||||||
<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>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<svelte:fragment slot="extra">
|
||||||
.bar {
|
{#if popup && fullScreen}
|
||||||
overflow-x: auto;
|
<PopupInstance
|
||||||
position: relative;
|
is={popup.is}
|
||||||
padding: 1rem;
|
props={popup.props}
|
||||||
border-top: 1px solid var(--theme-divider-color);
|
element={popup.element}
|
||||||
|
onClose={popup.onClose}
|
||||||
&__left-panel {
|
onUpdate={popup.onUpdate}
|
||||||
position: absolute;
|
zIndex={1}
|
||||||
top: 0;
|
top={true}
|
||||||
bottom: 0;
|
close={popup.close}
|
||||||
right: 1rem;
|
overlay={popup.options.overlay}
|
||||||
height: 100%;
|
contentPanel={undefined}
|
||||||
}
|
{popup}
|
||||||
|
/>
|
||||||
&__right-panel {
|
{/if}
|
||||||
position: absolute;
|
</svelte:fragment>
|
||||||
top: 0;
|
</ControlBarContainer>
|
||||||
bottom: 0;
|
|
||||||
left: 1rem;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.combinePanel .bar__left-panel {
|
|
||||||
position: static;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.combinePanel .bar__right-panel {
|
|
||||||
position: static;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -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,
|
Invite,
|
||||||
isOffice,
|
isOffice,
|
||||||
JoinRequest,
|
JoinRequest,
|
||||||
loveId,
|
|
||||||
Office,
|
Office,
|
||||||
ParticipantInfo,
|
ParticipantInfo,
|
||||||
RequestStatus,
|
RequestStatus,
|
||||||
@ -40,14 +39,7 @@
|
|||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { onDestroy } from 'svelte'
|
import { onDestroy } from 'svelte'
|
||||||
import workbench from '@hcengineering/workbench'
|
import workbench from '@hcengineering/workbench'
|
||||||
import {
|
import { closeWidget, closeWidgetTab, sidebarStore } from '@hcengineering/workbench-resources'
|
||||||
closeWidget,
|
|
||||||
closeWidgetTab,
|
|
||||||
minimizeSidebar,
|
|
||||||
sidebarStore,
|
|
||||||
SidebarVariant,
|
|
||||||
updateWidgetState
|
|
||||||
} from '@hcengineering/workbench-resources'
|
|
||||||
|
|
||||||
import love from '../plugin'
|
import love from '../plugin'
|
||||||
import { activeInvites, currentRoom, infos, myInfo, myInvites, myOffice, myRequests, rooms } from '../stores'
|
import { activeInvites, currentRoom, infos, myInfo, myInvites, myOffice, myRequests, rooms } from '../stores'
|
||||||
@ -69,37 +61,6 @@
|
|||||||
|
|
||||||
const client = getClient()
|
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 {
|
interface ActiveRoom extends Room {
|
||||||
participants: ParticipantInfo[]
|
participants: ParticipantInfo[]
|
||||||
}
|
}
|
||||||
@ -124,18 +85,8 @@
|
|||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
||||||
// let selectedFloor: Floor | undefined = $floors.find((f) => f._id === $activeFloor)
|
|
||||||
// $: selectedFloor = $floors.find((f) => f._id === $activeFloor)
|
|
||||||
|
|
||||||
$: activeRooms = getActiveRooms($rooms, $infos)
|
$: 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()
|
const query = createQuery()
|
||||||
let requests: JoinRequest[] = []
|
let requests: JoinRequest[] = []
|
||||||
query.query(love.class.JoinRequest, { status: RequestStatus.Pending }, (res) => {
|
query.query(love.class.JoinRequest, { status: RequestStatus.Pending }, (res) => {
|
||||||
@ -261,32 +212,6 @@
|
|||||||
|
|
||||||
$: checkActiveInvites($activeInvites)
|
$: 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 {
|
function checkActiveVideo (loc: Location, video: boolean, room: Ref<Room> | undefined): void {
|
||||||
const meetingWidgetState = $sidebarStore.widgetsState.get(love.ids.MeetingWidget)
|
const meetingWidgetState = $sidebarStore.widgetsState.get(love.ids.MeetingWidget)
|
||||||
const isMeetingWidgetCreated = meetingWidgetState !== undefined
|
const isMeetingWidgetCreated = meetingWidgetState !== undefined
|
||||||
@ -302,44 +227,18 @@
|
|||||||
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: love.ids.MeetingWidget })[0]
|
const widget = client.getModel().findAllSync(workbench.class.Widget, { _id: love.ids.MeetingWidget })[0]
|
||||||
if (widget === undefined) return
|
if (widget === undefined) return
|
||||||
|
|
||||||
// Create widget in sidebar if not created
|
|
||||||
if (!isMeetingWidgetCreated) {
|
if (!isMeetingWidgetCreated) {
|
||||||
prevLocation = loc
|
createMeetingWidget(widget, room, video)
|
||||||
createMeetingWidget(widget, room, loc, video)
|
|
||||||
} else if (video && !meetingWidgetState.tabs.some(({ id }) => id === 'video')) {
|
} else if (video && !meetingWidgetState.tabs.some(({ id }) => id === 'video')) {
|
||||||
createMeetingVideoWidgetTab(widget, loc)
|
createMeetingVideoWidgetTab(widget)
|
||||||
} else if (!video && meetingWidgetState.tabs.some(({ id }) => id === 'video')) {
|
} else if (!video && meetingWidgetState.tabs.some(({ id }) => id === 'video')) {
|
||||||
void closeWidgetTab(widget, '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 {
|
} else {
|
||||||
if (isMeetingWidgetCreated) {
|
if (isMeetingWidgetCreated) {
|
||||||
closeWidget(love.ids.MeetingWidget)
|
closeWidget(love.ids.MeetingWidget)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prevLocation = loc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: checkActiveVideo(
|
$: checkActiveVideo(
|
||||||
@ -384,56 +283,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-row-center flex-gap-2">
|
<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}
|
{#if activeRooms.length > 0}
|
||||||
<!-- <div class="divider" />-->
|
<!-- <div class="divider" />-->
|
||||||
{#each activeRooms as active}
|
{#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>
|
104
plugins/love-resources/src/components/EditRoom.svelte
Normal file
104
plugins/love-resources/src/components/EditRoom.svelte
Normal file
@ -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.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AccountRole, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
|
import { AccountRole, Ref, getCurrentAccount, hasAccountRole, WithLookup } from '@hcengineering/core'
|
||||||
import { Breadcrumb, Header, IconEdit, ModernButton, Switcher } from '@hcengineering/ui'
|
import { Breadcrumb, Header, IconEdit, ModernButton, Component } from '@hcengineering/ui'
|
||||||
import { Floor, Room } from '@hcengineering/love'
|
import { Floor, Room } from '@hcengineering/love'
|
||||||
import view from '@hcengineering/view'
|
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { ViewletSelector } from '@hcengineering/view-resources'
|
||||||
|
import { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
|
|
||||||
import lovePlg from '../plugin'
|
import lovePlg from '../plugin'
|
||||||
import { currentRoom, floors } from '../stores'
|
import { currentRoom, floors } from '../stores'
|
||||||
import ControlBar from './ControlBar.svelte'
|
import ControlBar from './ControlBar.svelte'
|
||||||
import MeetingsTable from './MeetingMinutesTable.svelte'
|
|
||||||
import FloorView from './FloorView.svelte'
|
|
||||||
|
|
||||||
export let rooms: Room[] = []
|
export let rooms: Room[] = []
|
||||||
export let floor: Ref<Floor>
|
export let floor: Ref<Floor>
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
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]
|
$: selectedFloor = $floors.filter((fl) => fl._id === floor)[0]
|
||||||
|
|
||||||
@ -43,18 +45,7 @@
|
|||||||
<Header allowFullsize adaptive={'disabled'}>
|
<Header allowFullsize adaptive={'disabled'}>
|
||||||
<Breadcrumb title={selectedFloor?.name ?? ''} size={'large'} isCurrent />
|
<Breadcrumb title={selectedFloor?.name ?? ''} size={'large'} isCurrent />
|
||||||
<svelte:fragment slot="beforeTitle">
|
<svelte:fragment slot="beforeTitle">
|
||||||
<Switcher
|
<ViewletSelector bind:viewlet bind:preference bind:loading viewletQuery={{ attachTo: lovePlg.class.Floor }} />
|
||||||
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
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="actions">
|
<svelte:fragment slot="actions">
|
||||||
{#if editable}
|
{#if editable}
|
||||||
@ -68,10 +59,8 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</Header>
|
</Header>
|
||||||
<div class="hulyComponent-content__column content">
|
<div class="hulyComponent-content__column content">
|
||||||
{#if selectedViewlet === 'meetingMinutes'}
|
{#if viewlet?.$lookup?.descriptor?.component}
|
||||||
<MeetingsTable />
|
<Component is={viewlet.$lookup.descriptor.component} props={{ floor, rooms }} on:open />
|
||||||
{:else}
|
|
||||||
<FloorView {rooms} />
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if $currentRoom}
|
{#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>
|
<Scroller padding="1rem" bottomPadding="4rem" horizontal>
|
||||||
<FloorGrid bind:floorContainer {rows} preview>
|
<FloorGrid bind:floorContainer {rows} preview>
|
||||||
{#each rooms as room}
|
{#each rooms as room}
|
||||||
<RoomPreview {room} info={getInfo(room._id, $infos)} />
|
<RoomPreview {room} info={getInfo(room._id, $infos)} on:open />
|
||||||
{/each}
|
{/each}
|
||||||
</FloorGrid>
|
</FloorGrid>
|
||||||
</Scroller>
|
</Scroller>
|
||||||
|
@ -83,6 +83,11 @@
|
|||||||
on:configure={() => (configure = false)}
|
on:configure={() => (configure = false)}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { RoomType } from '@hcengineering/love'
|
|
||||||
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||||
import { currentRoom } from '../stores'
|
import { onDestroy } from 'svelte'
|
||||||
import { screenSharing } from '../utils'
|
|
||||||
import Hall from './Hall.svelte'
|
import Hall from './Hall.svelte'
|
||||||
import RoomComponent from './Room.svelte'
|
import { currentRoom } from '../stores'
|
||||||
import { onMount, onDestroy } from 'svelte'
|
import { isConnected } from '../utils'
|
||||||
|
import ActiveMeeting from './ActiveMeeting.svelte'
|
||||||
|
|
||||||
const localNav: boolean = $deviceInfo.navigator.visible
|
const localNav: boolean = $deviceInfo.navigator.visible
|
||||||
const savedNav = localStorage.getItem('love-visibleNav')
|
const savedNav = localStorage.getItem('love-visibleNav')
|
||||||
@ -31,9 +31,9 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="hulyPanels-container" class:left-divider={$screenSharing || $currentRoom?.type === RoomType.Video}>
|
<div class="hulyPanels-container">
|
||||||
{#if ($currentRoom !== undefined && $screenSharing) || $currentRoom?.type === RoomType.Video}
|
{#if $currentRoom && $isConnected}
|
||||||
<RoomComponent withVideo={$currentRoom.type === RoomType.Video} room={$currentRoom} />
|
<ActiveMeeting room={$currentRoom} />
|
||||||
{:else}
|
{:else}
|
||||||
<Hall />
|
<Hall />
|
||||||
{/if}
|
{/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">
|
<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>
|
</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)
|
const handleFullScreen = () => ($isFullScreen = document.fullscreenElement != null)
|
||||||
|
|
||||||
function toggleFullscreen () {
|
function checkFullscreen (): void {
|
||||||
if (!document.fullscreenElement) {
|
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
|
roomEl
|
||||||
.requestFullscreen()
|
.requestFullscreen()
|
||||||
.then(() => ($isFullScreen = true))
|
.then(() => {
|
||||||
|
$isFullScreen = true
|
||||||
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(`Error attempting to enable fullscreen mode: ${err.message} (${err.name})`)
|
console.log(`Error attempting to enable fullscreen mode: ${err.message} (${err.name})`)
|
||||||
$isFullScreen = false
|
$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[] {
|
function getActiveParticipants (participants: ParticipantData[]): ParticipantData[] {
|
||||||
return participants.filter((p) => !p.isAgent || $infos.some(({ person }) => person === p._id))
|
return participants.filter((p) => !p.isAgent || $infos.some(({ person }) => person === p._id))
|
||||||
@ -396,7 +432,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if $currentRoom}
|
{#if $currentRoom}
|
||||||
<ControlBar room={$currentRoom} fullScreen={$isFullScreen} />
|
<ControlBar room={$currentRoom} fullScreen={$isFullScreen} {onFullScreen} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
43
plugins/love-resources/src/components/RoomModal.svelte
Normal file
43
plugins/love-resources/src/components/RoomModal.svelte
Normal file
@ -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 { Avatar, personByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { IdMap, getCurrentAccount } from '@hcengineering/core'
|
import { IdMap, getCurrentAccount } from '@hcengineering/core'
|
||||||
import { isOffice, ParticipantInfo, Room, RoomAccess, RoomType } from '@hcengineering/love'
|
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 { createEventDispatcher } from 'svelte'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { openDoc } from '@hcengineering/view-resources'
|
||||||
|
import { get } from 'svelte/store'
|
||||||
|
|
||||||
import love from '../plugin'
|
import love from '../plugin'
|
||||||
import { invites, myInfo, myRequests } from '../stores'
|
import { myInfo, selectedRoomPlace, currentRoom, meetingMinutesStore } from '../stores'
|
||||||
import { getRoomLabel, tryConnect } from '../utils'
|
import { getRoomLabel, lk } from '../utils'
|
||||||
import PersonActionPopup from './PersonActionPopup.svelte'
|
import PersonActionPopup from './PersonActionPopup.svelte'
|
||||||
import RoomLanguage from './RoomLanguage.svelte'
|
import RoomLanguage from './RoomLanguage.svelte'
|
||||||
|
|
||||||
@ -36,7 +40,6 @@
|
|||||||
const meName = $personByIdStore.get(me.person)?.name
|
const meName = $personByIdStore.get(me.person)?.name
|
||||||
const meAvatar = $personByIdStore.get(me.person)
|
const meAvatar = $personByIdStore.get(me.person)
|
||||||
|
|
||||||
let container: HTMLDivElement
|
|
||||||
let hoveredRoomX: number | undefined = undefined
|
let hoveredRoomX: number | undefined = undefined
|
||||||
let hoveredRoomY: number | undefined = undefined
|
let hoveredRoomY: number | undefined = undefined
|
||||||
|
|
||||||
@ -61,12 +64,23 @@
|
|||||||
hovered = false
|
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 (person !== undefined) {
|
||||||
if (room._id === $myInfo?.room || $myInfo === undefined) return
|
if (room._id === $myInfo?.room || $myInfo === undefined) return
|
||||||
showPopup(PersonActionPopup, { room, person: person._id }, eventToHTMLElement(e))
|
showPopup(PersonActionPopup, { room, person: person._id }, eventToHTMLElement(e))
|
||||||
} else {
|
} 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-no-static-element-interactions -->
|
||||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
bind:this={container}
|
|
||||||
class="floorGrid-room"
|
class="floorGrid-room"
|
||||||
class:preview
|
class:preview
|
||||||
class:hovered
|
class:hovered
|
||||||
|
@ -28,6 +28,12 @@
|
|||||||
TrackPublication
|
TrackPublication
|
||||||
} from 'livekit-client'
|
} from 'livekit-client'
|
||||||
import { createEventDispatcher, onDestroy, onMount, tick } from 'svelte'
|
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 love from '../plugin'
|
||||||
import { currentRoom, infos, myInfo, myOffice } from '../stores'
|
import { currentRoom, infos, myInfo, myOffice } from '../stores'
|
||||||
import {
|
import {
|
||||||
@ -44,8 +50,6 @@
|
|||||||
setShare
|
setShare
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import ParticipantView from './ParticipantView.svelte'
|
import ParticipantView from './ParticipantView.svelte'
|
||||||
import { Ref } from '@hcengineering/core'
|
|
||||||
import { MessageBox } from '@hcengineering/presentation'
|
|
||||||
|
|
||||||
export let isDock: boolean = false
|
export let isDock: boolean = false
|
||||||
export let room: Ref<TypeRoom>
|
export let room: Ref<TypeRoom>
|
||||||
@ -57,8 +61,12 @@
|
|||||||
muted: boolean
|
muted: boolean
|
||||||
mirror: boolean
|
mirror: boolean
|
||||||
connecting: boolean
|
connecting: boolean
|
||||||
|
isAgent: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let aiPersonId: Ref<Person> | undefined = undefined
|
||||||
|
$: aiPersonId = $personIdByAccountId.get(aiBot.account.AIBot as Ref<PersonAccount>)
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let participants: ParticipantData[] = []
|
let participants: ParticipantData[] = []
|
||||||
@ -126,7 +134,8 @@
|
|||||||
name: participant.name ?? '',
|
name: participant.name ?? '',
|
||||||
muted: !participant.isMicrophoneEnabled,
|
muted: !participant.isMicrophoneEnabled,
|
||||||
mirror: participant.isLocal,
|
mirror: participant.isLocal,
|
||||||
connecting: false
|
connecting: false,
|
||||||
|
isAgent: participant.isAgent
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
participants = participants
|
participants = participants
|
||||||
@ -149,7 +158,8 @@
|
|||||||
name: participant.name ?? '',
|
name: participant.name ?? '',
|
||||||
muted: !participant.isMicrophoneEnabled,
|
muted: !participant.isMicrophoneEnabled,
|
||||||
mirror: participant.isLocal,
|
mirror: participant.isLocal,
|
||||||
connecting: false
|
connecting: false,
|
||||||
|
isAgent: participant.isAgent
|
||||||
}
|
}
|
||||||
participants.push(value)
|
participants.push(value)
|
||||||
participants = participants
|
participants = participants
|
||||||
@ -227,7 +237,8 @@
|
|||||||
name: info.name,
|
name: info.name,
|
||||||
muted: true,
|
muted: true,
|
||||||
mirror: false,
|
mirror: false,
|
||||||
connecting: true
|
connecting: true,
|
||||||
|
isAgent: info.person === aiPersonId
|
||||||
}
|
}
|
||||||
participants.push(value)
|
participants.push(value)
|
||||||
}
|
}
|
||||||
@ -291,6 +302,12 @@
|
|||||||
}
|
}
|
||||||
}, 10)
|
}, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getActiveParticipants (participants: ParticipantData[]): ParticipantData[] {
|
||||||
|
return participants.filter((p) => !p.isAgent || $infos.some(({ person }) => person === p._id))
|
||||||
|
}
|
||||||
|
|
||||||
|
$: activeParticipants = getActiveParticipants(participants)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="antiPopup videoPopup-container" class:isDock>
|
<div class="antiPopup videoPopup-container" class:isDock>
|
||||||
@ -346,7 +363,7 @@
|
|||||||
<video class="screen" bind:this={screen}></video>
|
<video class="screen" bind:this={screen}></video>
|
||||||
</div>
|
</div>
|
||||||
<Scroller bind:divScroll noStretch padding={'0 .5rem'} gap={'flex-gap-2'} onResize={dispatchFit} stickedScrollBars>
|
<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">
|
<div class="video">
|
||||||
<ParticipantView bind:this={participantElements[i]} {...participant} small />
|
<ParticipantView bind:this={participantElements[i]} {...participant} small />
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,13 +13,15 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import love, { MeetingMinutes } from '@hcengineering/love'
|
import love, { MeetingMinutes, Room } from '@hcengineering/love'
|
||||||
import { ChannelEmbeddedContent } from '@hcengineering/chunter-resources'
|
import { ChannelEmbeddedContent } from '@hcengineering/chunter-resources'
|
||||||
import { ActivityMessage } from '@hcengineering/activity'
|
import { ActivityMessage } from '@hcengineering/activity'
|
||||||
import { updateTabData, WidgetState } from '@hcengineering/workbench-resources'
|
import { updateTabData, WidgetState } from '@hcengineering/workbench-resources'
|
||||||
|
import MeetingWidgetHeader from './MeetingWidgetHeader.svelte'
|
||||||
|
|
||||||
export let widgetState: WidgetState
|
export let widgetState: WidgetState
|
||||||
export let meetingMinutes: MeetingMinutes
|
export let meetingMinutes: MeetingMinutes
|
||||||
|
export let room: Room
|
||||||
export let height: string
|
export let height: string
|
||||||
export let width: string
|
export let width: string
|
||||||
|
|
||||||
@ -28,7 +30,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeThread (): void {
|
function closeThread (): void {
|
||||||
console.log('closeThread')
|
|
||||||
updateTabData(love.ids.MeetingWidget, 'chat', { thread: undefined })
|
updateTabData(love.ids.MeetingWidget, 'chat', { thread: undefined })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -42,4 +43,8 @@
|
|||||||
on:channel={closeThread}
|
on:channel={closeThread}
|
||||||
onReply={replyToThread}
|
onReply={replyToThread}
|
||||||
on:close
|
on:close
|
||||||
/>
|
>
|
||||||
|
<svelte:fragment slot="header">
|
||||||
|
<MeetingWidgetHeader doc={meetingMinutes} {room} on:close />
|
||||||
|
</svelte:fragment>
|
||||||
|
</ChannelEmbeddedContent>
|
||||||
|
@ -14,15 +14,14 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { closeWidget, minimizeSidebar, WidgetState } from '@hcengineering/workbench-resources'
|
import { closeWidget, minimizeSidebar, WidgetState } from '@hcengineering/workbench-resources'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import core, { Ref } from '@hcengineering/core'
|
|
||||||
import { MeetingMinutes, Room } from '@hcengineering/love'
|
import { MeetingMinutes, Room } from '@hcengineering/love'
|
||||||
import { Loading } from '@hcengineering/ui'
|
import { Loading } from '@hcengineering/ui'
|
||||||
|
|
||||||
import love from '../../plugin'
|
import love from '../../plugin'
|
||||||
import VideoTab from './VideoTab.svelte'
|
import VideoTab from './VideoTab.svelte'
|
||||||
import { isCurrentInstanceConnected, lk } from '../../utils'
|
import { isCurrentInstanceConnected, lk } from '../../utils'
|
||||||
import { rooms } from '../../stores'
|
import { currentRoom, meetingMinutesStore } from '../../stores'
|
||||||
import ChatTab from './ChatTab.svelte'
|
import ChatTab from './ChatTab.svelte'
|
||||||
import TranscriptionTab from './TranscriptionTab.svelte'
|
import TranscriptionTab from './TranscriptionTab.svelte'
|
||||||
|
|
||||||
@ -31,39 +30,38 @@
|
|||||||
export let width: string
|
export let width: string
|
||||||
|
|
||||||
const meetingQuery = createQuery()
|
const meetingQuery = createQuery()
|
||||||
const client = getClient()
|
|
||||||
|
|
||||||
let meetingMinutes: MeetingMinutes | undefined = undefined
|
let meetingMinutes: MeetingMinutes | undefined = undefined
|
||||||
let isMeetingMinutesLoaded = false
|
let isMeetingMinutesLoaded = false
|
||||||
|
|
||||||
let roomId: Ref<Room> | undefined = undefined
|
|
||||||
let room: Room | undefined = undefined
|
let room: Room | undefined = undefined
|
||||||
let sid: string | undefined = undefined
|
let sid: string | undefined = undefined
|
||||||
|
|
||||||
$: roomId = widgetState?.data?.room
|
$: room = $currentRoom
|
||||||
$: room = roomId !== undefined ? $rooms.find((r) => r._id === roomId) : undefined
|
|
||||||
|
|
||||||
void lk.getSid().then((res) => {
|
void lk.getSid().then((res) => {
|
||||||
sid = 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)
|
closeWidget(love.ids.MeetingWidget)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (roomId !== meetingMinutes?.room) {
|
$: if (meetingMinutes?.sid !== sid) {
|
||||||
meetingMinutes = undefined
|
meetingMinutes = undefined
|
||||||
isMeetingMinutesLoaded = false
|
isMeetingMinutesLoaded = false
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if ($isCurrentInstanceConnected && room && sid) {
|
$: if (sid != null && room !== undefined) {
|
||||||
meetingQuery.query(love.class.MeetingMinutes, { room: room._id, sid }, async (res) => {
|
meetingQuery.query(love.class.MeetingMinutes, { sid, attachedTo: room._id }, async (res) => {
|
||||||
meetingMinutes = res[0]
|
meetingMinutes = res[0]
|
||||||
if (meetingMinutes !== undefined) {
|
meetingMinutesStore.set(meetingMinutes)
|
||||||
isMeetingMinutesLoaded = true
|
isMeetingMinutesLoaded = true
|
||||||
} else {
|
|
||||||
void createMeetingMinutes()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
meetingQuery.unsubscribe()
|
meetingQuery.unsubscribe()
|
||||||
@ -71,16 +69,6 @@
|
|||||||
isMeetingMinutesLoaded = sid !== undefined
|
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 {
|
function handleClose (): void {
|
||||||
minimizeSidebar()
|
minimizeSidebar()
|
||||||
}
|
}
|
||||||
@ -88,18 +76,18 @@
|
|||||||
|
|
||||||
{#if widgetState && room}
|
{#if widgetState && room}
|
||||||
{#if widgetState.tab === 'video'}
|
{#if widgetState.tab === 'video'}
|
||||||
<VideoTab {room} />
|
<VideoTab {room} doc={meetingMinutes} on:close={handleClose} />
|
||||||
{:else if widgetState.tab === 'chat'}
|
{:else if widgetState.tab === 'chat'}
|
||||||
{#if !isMeetingMinutesLoaded}
|
{#if !isMeetingMinutesLoaded}
|
||||||
<Loading />
|
<Loading />
|
||||||
{:else if meetingMinutes}
|
{:else if meetingMinutes}
|
||||||
<ChatTab {meetingMinutes} {widgetState} {height} {width} on:close={handleClose} />
|
<ChatTab {meetingMinutes} {room} {widgetState} {height} {width} on:close={handleClose} />
|
||||||
{/if}
|
{/if}
|
||||||
{:else if widgetState.tab === 'transcription'}
|
{:else if widgetState.tab === 'transcription'}
|
||||||
{#if !isMeetingMinutesLoaded}
|
{#if !isMeetingMinutesLoaded}
|
||||||
<Loading />
|
<Loading />
|
||||||
{:else if meetingMinutes}
|
{:else if meetingMinutes}
|
||||||
<TranscriptionTab {meetingMinutes} {widgetState} {height} {width} on:close={handleClose} />
|
<TranscriptionTab {meetingMinutes} {room} {widgetState} {height} {width} on:close={handleClose} />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/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.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import love, { MeetingMinutes } from '@hcengineering/love'
|
import { MeetingMinutes, Room } from '@hcengineering/love'
|
||||||
import { ChannelEmbeddedContent } from '@hcengineering/chunter-resources'
|
import { ChannelEmbeddedContent } from '@hcengineering/chunter-resources'
|
||||||
import { ActivityMessage } from '@hcengineering/activity'
|
import { WidgetState } from '@hcengineering/workbench-resources'
|
||||||
import { updateTabData, WidgetState } from '@hcengineering/workbench-resources'
|
import MeetingWidgetHeader from './MeetingWidgetHeader.svelte'
|
||||||
|
|
||||||
export let widgetState: WidgetState
|
export let widgetState: WidgetState
|
||||||
export let meetingMinutes: MeetingMinutes
|
export let meetingMinutes: MeetingMinutes
|
||||||
|
export let room: Room
|
||||||
export let height: string
|
export let height: string
|
||||||
export let width: 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>
|
</script>
|
||||||
|
|
||||||
<ChannelEmbeddedContent
|
<ChannelEmbeddedContent
|
||||||
{width}
|
{width}
|
||||||
{height}
|
{height}
|
||||||
|
readonly
|
||||||
object={meetingMinutes}
|
object={meetingMinutes}
|
||||||
threadId={widgetState.tabs.find((tab) => tab.id === 'transcription')?.data?.thread}
|
threadId={undefined}
|
||||||
collection="transcription"
|
collection="transcription"
|
||||||
on:channel={closeThread}
|
|
||||||
onReply={replyToThread}
|
|
||||||
on:close
|
on:close
|
||||||
/>
|
>
|
||||||
|
<svelte:fragment slot="header">
|
||||||
|
<MeetingWidgetHeader doc={meetingMinutes} {room} on:close />
|
||||||
|
</svelte:fragment>
|
||||||
|
</ChannelEmbeddedContent>
|
||||||
|
@ -13,13 +13,16 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Room } from '@hcengineering/love'
|
import { MeetingMinutes, Room } from '@hcengineering/love'
|
||||||
|
|
||||||
import VideoPopup from '../VideoPopup.svelte'
|
import VideoPopup from '../VideoPopup.svelte'
|
||||||
|
import MeetingWidgetHeader from './MeetingWidgetHeader.svelte'
|
||||||
|
|
||||||
export let room: Room
|
export let room: Room
|
||||||
|
export let doc: MeetingMinutes | undefined = undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<MeetingWidgetHeader {doc} {room} on:close />
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<VideoPopup room={room._id} isDock canUnpin={false} />
|
<VideoPopup room={room._id} isDock canUnpin={false} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,6 +12,12 @@ import WorkbenchExtension from './components/WorkbenchExtension.svelte'
|
|||||||
import LoveWidget from './components/LoveWidget.svelte'
|
import LoveWidget from './components/LoveWidget.svelte'
|
||||||
import MeetingWidget from './components/widget/MeetingWidget.svelte'
|
import MeetingWidget from './components/widget/MeetingWidget.svelte'
|
||||||
import MeetingMinutesPresenter from './components/MeetingMinutesPresenter.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 {
|
import {
|
||||||
copyGuestLink,
|
copyGuestLink,
|
||||||
@ -20,7 +26,8 @@ import {
|
|||||||
startTranscription,
|
startTranscription,
|
||||||
stopTranscription,
|
stopTranscription,
|
||||||
toggleMic,
|
toggleMic,
|
||||||
toggleVideo
|
toggleVideo,
|
||||||
|
getMeetingMinutesTitle
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
export { setCustomCreateScreenTracks } from './utils'
|
export { setCustomCreateScreenTracks } from './utils'
|
||||||
@ -36,7 +43,13 @@ export default async (): Promise<Resources> => ({
|
|||||||
EditMeetingData,
|
EditMeetingData,
|
||||||
LoveWidget,
|
LoveWidget,
|
||||||
MeetingWidget,
|
MeetingWidget,
|
||||||
MeetingMinutesPresenter
|
MeetingMinutesPresenter,
|
||||||
|
MeetingMinutesSection,
|
||||||
|
EditMeetingMinutes,
|
||||||
|
EditRoom,
|
||||||
|
FloorAttributePresenter,
|
||||||
|
FloorView,
|
||||||
|
MeetingMinutesTable
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
CreateMeeting: createMeeting,
|
CreateMeeting: createMeeting,
|
||||||
@ -50,7 +63,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
},
|
},
|
||||||
CanCopyGuestLink: () => {
|
CanCopyGuestLink: () => {
|
||||||
return hasAccountRole(getCurrentAccount(), AccountRole.User)
|
return hasAccountRole(getCurrentAccount(), AccountRole.User)
|
||||||
}
|
},
|
||||||
|
MeetingMinutesTitleProvider: getMeetingMinutesTitle
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
ToggleMic: toggleMic,
|
ToggleMic: toggleMic,
|
||||||
|
@ -24,7 +24,13 @@ export default mergeIds(loveId, love, {
|
|||||||
ControlExt: '' as AnyComponent,
|
ControlExt: '' as AnyComponent,
|
||||||
MeetingData: '' as AnyComponent,
|
MeetingData: '' as AnyComponent,
|
||||||
EditMeetingData: '' 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: {
|
function: {
|
||||||
CreateMeeting: '' as Resource<DocCreateFunction>,
|
CreateMeeting: '' as Resource<DocCreateFunction>,
|
||||||
|
@ -8,7 +8,8 @@ import {
|
|||||||
type JoinRequest,
|
type JoinRequest,
|
||||||
type Office,
|
type Office,
|
||||||
type ParticipantInfo,
|
type ParticipantInfo,
|
||||||
type Room
|
type Room,
|
||||||
|
type MeetingMinutes
|
||||||
} from '@hcengineering/love'
|
} from '@hcengineering/love'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { derived, get, writable } from 'svelte/store'
|
import { derived, get, writable } from 'svelte/store'
|
||||||
@ -59,6 +60,9 @@ export const activeInvites = derived(invites, (val) => {
|
|||||||
export const myPreferences = writable<DevicesPreference | undefined>()
|
export const myPreferences = writable<DevicesPreference | undefined>()
|
||||||
export let $myPreferences: 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[] {
|
function filterParticipantInfo (value: ParticipantInfo[]): ParticipantInfo[] {
|
||||||
const map = new Map<string, ParticipantInfo>()
|
const map = new Map<string, ParticipantInfo>()
|
||||||
const aiPersonId = get(personIdByAccountId).get(aiBot.account.AIBot as Ref<PersonAccount>)
|
const aiPersonId = get(personIdByAccountId).get(aiBot.account.AIBot as Ref<PersonAccount>)
|
||||||
|
@ -5,8 +5,10 @@ import core, {
|
|||||||
AccountRole,
|
AccountRole,
|
||||||
concatLink,
|
concatLink,
|
||||||
type Data,
|
type Data,
|
||||||
|
generateId,
|
||||||
getCurrentAccount,
|
getCurrentAccount,
|
||||||
type IdMap,
|
type IdMap,
|
||||||
|
makeCollaborativeDoc,
|
||||||
type Ref,
|
type Ref,
|
||||||
type Space,
|
type Space,
|
||||||
type TxOperations
|
type TxOperations
|
||||||
@ -27,7 +29,8 @@ import {
|
|||||||
RoomAccess,
|
RoomAccess,
|
||||||
RoomType,
|
RoomType,
|
||||||
TranscriptionStatus,
|
TranscriptionStatus,
|
||||||
type RoomMetadata
|
type RoomMetadata,
|
||||||
|
type MeetingMinutes
|
||||||
} from '@hcengineering/love'
|
} from '@hcengineering/love'
|
||||||
import { getEmbeddedLabel, getMetadata, getResource, type IntlString } from '@hcengineering/platform'
|
import { getEmbeddedLabel, getMetadata, getResource, type IntlString } from '@hcengineering/platform'
|
||||||
import presentation, {
|
import presentation, {
|
||||||
@ -36,7 +39,7 @@ import presentation, {
|
|||||||
type DocCreatePhase,
|
type DocCreatePhase,
|
||||||
getClient
|
getClient
|
||||||
} from '@hcengineering/presentation'
|
} 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 { isKrispNoiseFilterSupported, KrispNoiseFilter } from '@livekit/krisp-noise-filter'
|
||||||
import { BackgroundBlur, type BackgroundOptions, type ProcessorWrapper } from '@livekit/track-processors'
|
import { BackgroundBlur, type BackgroundOptions, type ProcessorWrapper } from '@livekit/track-processors'
|
||||||
import {
|
import {
|
||||||
@ -57,13 +60,20 @@ import {
|
|||||||
import { get, writable } from 'svelte/store'
|
import { get, writable } from 'svelte/store'
|
||||||
import aiBot from '@hcengineering/ai-bot'
|
import aiBot from '@hcengineering/ai-bot'
|
||||||
import { connectMeeting, disconnectMeeting } from '@hcengineering/ai-bot-resources'
|
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 { type Widget, type WidgetTab } from '@hcengineering/workbench'
|
||||||
import view from '@hcengineering/view'
|
import view from '@hcengineering/view'
|
||||||
|
import chunter from '@hcengineering/chunter'
|
||||||
|
|
||||||
import { sendMessage } from './broadcast'
|
import { sendMessage } from './broadcast'
|
||||||
import love from './plugin'
|
import love from './plugin'
|
||||||
import { $myPreferences, currentRoom } from './stores'
|
import { $myPreferences, meetingMinutesStore, currentRoom } from './stores'
|
||||||
import RoomSettingsPopup from './components/RoomSettingsPopup.svelte'
|
import RoomSettingsPopup from './components/RoomSettingsPopup.svelte'
|
||||||
|
|
||||||
export const selectedCamId = 'selectedDevice_cam'
|
export const selectedCamId = 'selectedDevice_cam'
|
||||||
@ -92,7 +102,9 @@ export async function getToken (
|
|||||||
|
|
||||||
function getTokenRoomName (roomName: string, roomId: Ref<Room>): string {
|
function getTokenRoomName (roomName: string, roomId: Ref<Room>): string {
|
||||||
const loc = getCurrentLocation()
|
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({
|
export const lk: LKRoom = new LKRoom({
|
||||||
@ -432,6 +444,7 @@ export async function disconnect (): Promise<void> {
|
|||||||
isMicEnabled.set(false)
|
isMicEnabled.set(false)
|
||||||
isCameraEnabled.set(false)
|
isCameraEnabled.set(false)
|
||||||
isSharingEnabled.set(false)
|
isSharingEnabled.set(false)
|
||||||
|
meetingMinutesStore.set(undefined)
|
||||||
sendMessage({ type: 'mic', value: false })
|
sendMessage({ type: 'mic', value: false })
|
||||||
sendMessage({ type: 'cam', value: false })
|
sendMessage({ type: 'cam', value: false })
|
||||||
sendMessage({ type: 'share', 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 (
|
export async function connectRoom (
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@ -590,6 +643,7 @@ export async function connectRoom (
|
|||||||
await disconnect()
|
await disconnect()
|
||||||
await moveToRoom(x, y, currentInfo, currentPerson, room, getMetadata(presentation.metadata.SessionId) ?? null)
|
await moveToRoom(x, y, currentInfo, currentPerson, room, getMetadata(presentation.metadata.SessionId) ?? null)
|
||||||
await connectLK(currentPerson, room)
|
await connectLK(currentPerson, room)
|
||||||
|
await createMeetingMinutes(room)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const joinRequest: Ref<JoinRequest> | undefined = undefined
|
export const joinRequest: Ref<JoinRequest> | undefined = undefined
|
||||||
@ -910,13 +964,13 @@ export function isTranscriptionAllowed (): boolean {
|
|||||||
return url !== ''
|
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[] = [
|
const tabs: WidgetTab[] = [
|
||||||
...(video
|
...(video
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
id: 'video',
|
id: 'video',
|
||||||
name: 'Video',
|
label: love.string.Video,
|
||||||
icon: love.icon.Cam,
|
icon: love.icon.Cam,
|
||||||
readonly: true
|
readonly: true
|
||||||
}
|
}
|
||||||
@ -924,13 +978,13 @@ export function createMeetingWidget (widget: Widget, room: Ref<Room>, loc: Locat
|
|||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
id: 'chat',
|
id: 'chat',
|
||||||
name: 'Chat',
|
label: chunter.string.Chat,
|
||||||
icon: view.icon.Bubble,
|
icon: view.icon.Bubble,
|
||||||
readonly: true
|
readonly: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'transcription',
|
id: 'transcription',
|
||||||
name: 'Transcription',
|
label: love.string.Transcription,
|
||||||
icon: view.icon.Feather,
|
icon: view.icon.Feather,
|
||||||
readonly: true
|
readonly: true
|
||||||
}
|
}
|
||||||
@ -940,12 +994,12 @@ export function createMeetingWidget (widget: Widget, room: Ref<Room>, loc: Locat
|
|||||||
{
|
{
|
||||||
room
|
room
|
||||||
},
|
},
|
||||||
{ active: loc.path[2] !== loveId, openedByUser: false },
|
{ active: true, openedByUser: false },
|
||||||
tabs
|
tabs
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMeetingVideoWidgetTab (widget: Widget, loc: Location): void {
|
export function createMeetingVideoWidgetTab (widget: Widget): void {
|
||||||
const state = get(sidebarStore)
|
const state = get(sidebarStore)
|
||||||
const { widgetsState } = state
|
const { widgetsState } = state
|
||||||
const widgetState = widgetsState.get(widget._id)
|
const widgetState = widgetsState.get(widget._id)
|
||||||
@ -954,12 +1008,23 @@ export function createMeetingVideoWidgetTab (widget: Widget, loc: Location): voi
|
|||||||
|
|
||||||
const tab: WidgetTab = {
|
const tab: WidgetTab = {
|
||||||
id: 'video',
|
id: 'video',
|
||||||
name: 'Video',
|
label: love.string.Video,
|
||||||
icon: love.icon.Cam,
|
icon: love.icon.Cam,
|
||||||
readonly: true
|
readonly: true
|
||||||
}
|
}
|
||||||
updateWidgetState(widget._id, {
|
updateWidgetState(widget._id, {
|
||||||
tabs: [tab, ...widgetState.tabs],
|
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 { Event } from '@hcengineering/calendar'
|
||||||
import { Person } from '@hcengineering/contact'
|
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 { Drive } from '@hcengineering/drive'
|
||||||
import { NotificationType } from '@hcengineering/notification'
|
import { NotificationType } from '@hcengineering/notification'
|
||||||
import { Asset, IntlString, Metadata, Plugin, plugin } from '@hcengineering/platform'
|
import { Asset, IntlString, Metadata, Plugin, plugin } from '@hcengineering/platform'
|
||||||
import { Preference } from '@hcengineering/preference'
|
import { Preference } from '@hcengineering/preference'
|
||||||
import { AnyComponent } from '@hcengineering/ui/src/types'
|
import { AnyComponent } from '@hcengineering/ui/src/types'
|
||||||
import { Action } from '@hcengineering/view'
|
import { Action, Viewlet, ViewletDescriptor } from '@hcengineering/view'
|
||||||
import { Widget } from '@hcengineering/workbench'
|
import { Widget } from '@hcengineering/workbench'
|
||||||
|
|
||||||
export const loveId = 'love' as Plugin
|
export const loveId = 'love' as Plugin
|
||||||
@ -102,6 +102,9 @@ export interface Room extends Doc {
|
|||||||
y: number
|
y: number
|
||||||
language: RoomLanguage
|
language: RoomLanguage
|
||||||
startWithTranscription: boolean
|
startWithTranscription: boolean
|
||||||
|
description: CollaborativeDoc
|
||||||
|
attachments?: number
|
||||||
|
meetings?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Office extends Room {
|
export interface Office extends Room {
|
||||||
@ -155,12 +158,13 @@ export interface DevicesPreference extends Preference {
|
|||||||
camEnabled: boolean
|
camEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MeetingMinutes extends Doc {
|
export interface MeetingMinutes extends AttachedDoc {
|
||||||
sid: string
|
sid: string
|
||||||
title: string
|
title: string
|
||||||
room: Ref<Room>
|
|
||||||
transcription?: number
|
transcription?: number
|
||||||
messages?: number
|
messages?: number
|
||||||
|
description: CollaborativeDoc
|
||||||
|
attachments?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
@ -200,7 +204,11 @@ const love = plugin(loveId, {
|
|||||||
Meeting: '' as IntlString,
|
Meeting: '' as IntlString,
|
||||||
Transcription: '' as IntlString,
|
Transcription: '' as IntlString,
|
||||||
StartWithTranscription: '' as IntlString,
|
StartWithTranscription: '' as IntlString,
|
||||||
MeetingMinutes: '' as IntlString
|
MeetingMinutes: '' as IntlString,
|
||||||
|
StartMeeting: '' as IntlString,
|
||||||
|
Video: '' as IntlString,
|
||||||
|
NoMeetingMinutes: '' as IntlString,
|
||||||
|
JoinMeeting: '' as IntlString
|
||||||
},
|
},
|
||||||
ids: {
|
ids: {
|
||||||
MainFloor: '' as Ref<Floor>,
|
MainFloor: '' as Ref<Floor>,
|
||||||
@ -243,6 +251,13 @@ const love = plugin(loveId, {
|
|||||||
},
|
},
|
||||||
component: {
|
component: {
|
||||||
SelectScreenSourcePopup: '' as AnyComponent
|
SelectScreenSourcePopup: '' as AnyComponent
|
||||||
|
},
|
||||||
|
viewlet: {
|
||||||
|
TableMeetingMinutes: '' as Ref<Viewlet>,
|
||||||
|
MeetingMinutesDescriptor: '' as Ref<ViewletDescriptor>,
|
||||||
|
FloorDescriptor: '' as Ref<ViewletDescriptor>,
|
||||||
|
Floor: '' as Ref<Viewlet>,
|
||||||
|
FloorMeetingMinutes: '' as Ref<Viewlet>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Employee, Person } from '@hcengineering/contact'
|
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 '.'
|
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
|
return (room as Office).person !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Office>[] {
|
export function createDefaultRooms (employees: Ref<Employee>[]): (Data<Room | Office> & { _id: Ref<Room> })[] {
|
||||||
const res: Data<Room | Office>[] = []
|
const res: (Data<Room | Office> & { _id: Ref<Room> })[] = []
|
||||||
// create 12 offices
|
// create 12 offices
|
||||||
for (let index = 0; index < 12; index++) {
|
for (let index = 0; index < 12; index++) {
|
||||||
const office: Data<Office> = {
|
const _id = generateId<Office>()
|
||||||
|
const office: Data<Office> & { _id: Ref<Office> } = {
|
||||||
|
_id,
|
||||||
name: '',
|
name: '',
|
||||||
type: RoomType.Audio,
|
type: RoomType.Audio,
|
||||||
access: RoomAccess.Knock,
|
access: RoomAccess.Knock,
|
||||||
@ -30,11 +32,15 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
|||||||
y: index - (index % 2),
|
y: index - (index % 2),
|
||||||
person: employees[index] ?? null,
|
person: employees[index] ?? null,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
startWithTranscription: false
|
startWithTranscription: false,
|
||||||
|
description: makeCollaborativeDoc(_id, 'description')
|
||||||
}
|
}
|
||||||
res.push(office)
|
res.push(office)
|
||||||
}
|
}
|
||||||
|
const allHands = generateId<Room>()
|
||||||
|
|
||||||
res.push({
|
res.push({
|
||||||
|
_id: allHands,
|
||||||
name: 'All hands',
|
name: 'All hands',
|
||||||
type: RoomType.Video,
|
type: RoomType.Video,
|
||||||
access: RoomAccess.Open,
|
access: RoomAccess.Open,
|
||||||
@ -44,9 +50,13 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
|||||||
x: 6,
|
x: 6,
|
||||||
y: 0,
|
y: 0,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
startWithTranscription: true
|
startWithTranscription: true,
|
||||||
|
description: makeCollaborativeDoc(allHands, 'description')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const meetingRoom1 = generateId<Room>()
|
||||||
res.push({
|
res.push({
|
||||||
|
_id: meetingRoom1,
|
||||||
name: 'Meeting Room 1',
|
name: 'Meeting Room 1',
|
||||||
type: RoomType.Video,
|
type: RoomType.Video,
|
||||||
access: RoomAccess.Open,
|
access: RoomAccess.Open,
|
||||||
@ -56,9 +66,12 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
|||||||
x: 6,
|
x: 6,
|
||||||
y: 4,
|
y: 4,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
startWithTranscription: true
|
startWithTranscription: true,
|
||||||
|
description: makeCollaborativeDoc(meetingRoom1, 'description')
|
||||||
})
|
})
|
||||||
|
const meetingRoom2 = generateId<Room>()
|
||||||
res.push({
|
res.push({
|
||||||
|
_id: meetingRoom2,
|
||||||
name: 'Meeting Room 2',
|
name: 'Meeting Room 2',
|
||||||
type: RoomType.Video,
|
type: RoomType.Video,
|
||||||
access: RoomAccess.Open,
|
access: RoomAccess.Open,
|
||||||
@ -68,9 +81,12 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
|||||||
x: 11,
|
x: 11,
|
||||||
y: 4,
|
y: 4,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
startWithTranscription: true
|
startWithTranscription: true,
|
||||||
|
description: makeCollaborativeDoc(meetingRoom2, 'description')
|
||||||
})
|
})
|
||||||
|
const voiceRoom1 = generateId<Room>()
|
||||||
res.push({
|
res.push({
|
||||||
|
_id: voiceRoom1,
|
||||||
name: 'Voice Room 1',
|
name: 'Voice Room 1',
|
||||||
type: RoomType.Audio,
|
type: RoomType.Audio,
|
||||||
access: RoomAccess.Open,
|
access: RoomAccess.Open,
|
||||||
@ -80,9 +96,12 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
|||||||
x: 6,
|
x: 6,
|
||||||
y: 8,
|
y: 8,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
startWithTranscription: true
|
startWithTranscription: false,
|
||||||
|
description: makeCollaborativeDoc(voiceRoom1, 'description')
|
||||||
})
|
})
|
||||||
|
const voiceRoom2 = generateId<Room>()
|
||||||
res.push({
|
res.push({
|
||||||
|
_id: voiceRoom2,
|
||||||
name: 'Voice Room 2',
|
name: 'Voice Room 2',
|
||||||
type: RoomType.Audio,
|
type: RoomType.Audio,
|
||||||
access: RoomAccess.Open,
|
access: RoomAccess.Open,
|
||||||
@ -92,7 +111,8 @@ export function createDefaultRooms (employees: Ref<Employee>[]): Data<Room | Off
|
|||||||
x: 11,
|
x: 11,
|
||||||
y: 8,
|
y: 8,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
startWithTranscription: true
|
startWithTranscription: false,
|
||||||
|
description: makeCollaborativeDoc(voiceRoom2, 'description')
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -226,49 +226,45 @@ export async function unarchiveContextNotifications (doc?: DocNotifyContext): Pr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum OpWithMe {
|
export async function subscribeDoc (
|
||||||
Add = 'add',
|
|
||||||
Remove = 'remove'
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateMeInCollaborators (
|
|
||||||
client: TxOperations,
|
client: TxOperations,
|
||||||
docClass: Ref<Class<Doc>>,
|
docClass: Ref<Class<Doc>>,
|
||||||
docId: Ref<Doc>,
|
docId: Ref<Doc>,
|
||||||
op: OpWithMe
|
op: 'add' | 'remove',
|
||||||
|
doc?: Doc
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const me = getCurrentAccount()._id
|
const me = getCurrentAccount()._id
|
||||||
const hierarchy = client.getHierarchy()
|
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) {
|
if (hierarchy.classHierarchyMixin(docClass, notification.mixin.ClassCollaborators) === undefined) return
|
||||||
collabUpdate = {
|
|
||||||
$pull: {
|
const target = doc ?? (await client.findOne(docClass, { _id: docId }))
|
||||||
collaborators: me
|
if (target === undefined) return
|
||||||
}
|
if (hierarchy.hasMixin(target, notification.mixin.Collaborators)) {
|
||||||
}
|
const collab = hierarchy.as(target, notification.mixin.Collaborators)
|
||||||
} else if (!collab.collaborators.includes(me) && op === OpWithMe.Add) {
|
let collabUpdate: DocumentUpdate<Collaborators> | undefined
|
||||||
collabUpdate = {
|
|
||||||
$push: {
|
if (collab.collaborators.includes(me) && op === 'remove') {
|
||||||
collaborators: me
|
collabUpdate = {
|
||||||
}
|
$pull: {
|
||||||
|
collaborators: me
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (!collab.collaborators.includes(me) && op === 'add') {
|
||||||
if (collabUpdate !== undefined) {
|
collabUpdate = {
|
||||||
await client.updateMixin(
|
$push: {
|
||||||
collab._id,
|
collaborators: me
|
||||||
collab._class,
|
}
|
||||||
collab.space,
|
|
||||||
notification.mixin.Collaborators,
|
|
||||||
collabUpdate
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
export async function unsubscribe (context: DocNotifyContext): Promise<void> {
|
||||||
const client = getClient()
|
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> {
|
export async function subscribe (docClass: Ref<Class<Doc>>, docId: Ref<Doc>): Promise<void> {
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
await updateMeInCollaborators(client, docClass, docId, OpWithMe.Add)
|
await subscribeDoc(client, docClass, docId, 'add')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pinDocNotifyContext (object: DocNotifyContext): Promise<void> {
|
export async function pinDocNotifyContext (object: DocNotifyContext): Promise<void> {
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
export let _class: Ref<Class<Doc>>
|
export let _class: Ref<Class<Doc>>
|
||||||
export let embedded: boolean = false
|
export let embedded: boolean = false
|
||||||
export let readonly: boolean = false
|
export let readonly: boolean = false
|
||||||
|
export let selectedAside: boolean | undefined = undefined
|
||||||
|
|
||||||
let realObjectClass: Ref<Class<Doc>> = _class
|
let realObjectClass: Ref<Class<Doc>> = _class
|
||||||
let lastId: Ref<Doc> | undefined
|
let lastId: Ref<Doc> | undefined
|
||||||
@ -225,6 +226,7 @@
|
|||||||
allowClose={!embedded}
|
allowClose={!embedded}
|
||||||
isAside={true}
|
isAside={true}
|
||||||
{embedded}
|
{embedded}
|
||||||
|
{selectedAside}
|
||||||
bind:content
|
bind:content
|
||||||
bind:panelWidth
|
bind:panelWidth
|
||||||
bind:innerWidth
|
bind:innerWidth
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<ModernTab
|
<ModernTab
|
||||||
label={tab.name}
|
label={tab.name}
|
||||||
labelIntl={widget.label}
|
labelIntl={tab.label ?? widget.label}
|
||||||
highlighted={selected}
|
highlighted={selected}
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
kind={tab.isPinned ? 'secondary' : 'primary'}
|
kind={tab.isPinned ? 'secondary' : 'primary'}
|
||||||
|
@ -26,7 +26,7 @@ import ServerManager from './components/ServerManager.svelte'
|
|||||||
import WorkbenchTabs from './components/WorkbenchTabs.svelte'
|
import WorkbenchTabs from './components/WorkbenchTabs.svelte'
|
||||||
import { isAdminUser } from '@hcengineering/presentation'
|
import { isAdminUser } from '@hcengineering/presentation'
|
||||||
import { canCloseTab, closeTab, pinTab, unpinTab } from './workbench'
|
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> {
|
async function hasArchiveSpaces (spaces: Space[]): Promise<boolean> {
|
||||||
return spaces.find((sp) => sp.archived) !== undefined
|
return spaces.find((sp) => sp.archived) !== undefined
|
||||||
@ -59,6 +59,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
CanCloseTab: canCloseTab,
|
CanCloseTab: canCloseTab,
|
||||||
CreateWidgetTab: createWidgetTab,
|
CreateWidgetTab: createWidgetTab,
|
||||||
CloseWidgetTab: closeWidgetTab,
|
CloseWidgetTab: closeWidgetTab,
|
||||||
|
CloseWidget: closeWidget,
|
||||||
GetSidebarObject: getSidebarObject
|
GetSidebarObject: getSidebarObject
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
|
@ -18,7 +18,7 @@ import { get, writable } from 'svelte/store'
|
|||||||
import { getCurrentLocation, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
import { getCurrentLocation, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
|
|
||||||
import { workspaceStore } from './utils'
|
import { locationWorkspaceStore } from './utils'
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
|
|
||||||
export enum SidebarVariant {
|
export enum SidebarVariant {
|
||||||
@ -50,14 +50,14 @@ export const defaultSidebarState: SidebarState = {
|
|||||||
|
|
||||||
export const sidebarStore = writable<SidebarState>(defaultSidebarState)
|
export const sidebarStore = writable<SidebarState>(defaultSidebarState)
|
||||||
|
|
||||||
workspaceStore.subscribe((workspace) => {
|
locationWorkspaceStore.subscribe((workspace) => {
|
||||||
sidebarStore.set(getSidebarStateFromLocalStorage(workspace ?? ''))
|
sidebarStore.set(getSidebarStateFromLocalStorage(workspace ?? ''))
|
||||||
})
|
})
|
||||||
|
|
||||||
sidebarStore.subscribe(setSidebarStateToLocalStorage)
|
sidebarStore.subscribe(setSidebarStateToLocalStorage)
|
||||||
|
|
||||||
export function syncSidebarState (): void {
|
export function syncSidebarState (): void {
|
||||||
const workspace = get(workspaceStore)
|
const workspace = get(locationWorkspaceStore)
|
||||||
sidebarStore.set(getSidebarStateFromLocalStorage(workspace ?? ''))
|
sidebarStore.set(getSidebarStateFromLocalStorage(workspace ?? ''))
|
||||||
}
|
}
|
||||||
function getSideBarLocalStorageKey (workspace: string): string | undefined {
|
function getSideBarLocalStorageKey (workspace: string): string | undefined {
|
||||||
@ -89,7 +89,7 @@ function getSidebarStateFromLocalStorage (workspace: string): SidebarState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setSidebarStateToLocalStorage (state: SidebarState): void {
|
function setSidebarStateToLocalStorage (state: SidebarState): void {
|
||||||
const workspace = get(workspaceStore)
|
const workspace = get(locationWorkspaceStore)
|
||||||
if (workspace == null || workspace === '') return
|
if (workspace == null || workspace === '') return
|
||||||
|
|
||||||
const sidebarStateLocalStorageKey = getSideBarLocalStorageKey(workspace)
|
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 })
|
Analytics.handleEvent(WorkbenchEvents.SidebarOpenWidget, { widget, tab: newTab?.name })
|
||||||
sidebarStore.set({
|
sidebarStore.set({
|
||||||
...state,
|
...state,
|
||||||
|
widget,
|
||||||
|
variant: SidebarVariant.EXPANDED,
|
||||||
widgetsState
|
widgetsState
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,13 @@ export async function showApplication (app: Application): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const workspacesStore = writable<Workspace[]>([])
|
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
|
* @public
|
||||||
|
@ -40,7 +40,7 @@ import { type Asset, type IntlString, getMetadata, getResource, translate } from
|
|||||||
import { parseLinkId } from '@hcengineering/view-resources'
|
import { parseLinkId } from '@hcengineering/view-resources'
|
||||||
import notification, { notificationId } from '@hcengineering/notification'
|
import notification, { notificationId } from '@hcengineering/notification'
|
||||||
|
|
||||||
import { workspaceStore } from './utils'
|
import { locationWorkspaceStore } from './utils'
|
||||||
import workbench from './plugin'
|
import workbench from './plugin'
|
||||||
|
|
||||||
export const tabIdStore = writable<Ref<WorkbenchTab> | undefined>()
|
export const tabIdStore = writable<Ref<WorkbenchTab> | undefined>()
|
||||||
@ -56,7 +56,7 @@ tabIdStore.subscribe((value) => {
|
|||||||
prevTabId = value
|
prevTabId = value
|
||||||
})
|
})
|
||||||
|
|
||||||
workspaceStore.subscribe((workspace) => {
|
locationWorkspaceStore.subscribe((workspace) => {
|
||||||
tabIdStore.set(getTabFromLocalStorage(workspace ?? ''))
|
tabIdStore.set(getTabFromLocalStorage(workspace ?? ''))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ tabIdStore.subscribe(saveTabToLocalStorage)
|
|||||||
|
|
||||||
const syncTabLoc = reduceCalls(async (): Promise<void> => {
|
const syncTabLoc = reduceCalls(async (): Promise<void> => {
|
||||||
const loc = getCurrentLocation()
|
const loc = getCurrentLocation()
|
||||||
const workspace = get(workspaceStore)
|
const workspace = get(locationWorkspaceStore)
|
||||||
if (workspace == null || workspace === '') return
|
if (workspace == null || workspace === '') return
|
||||||
const tab = get(currentTabStore)
|
const tab = get(currentTabStore)
|
||||||
if (tab == null) return
|
if (tab == null) return
|
||||||
@ -132,7 +132,7 @@ locationStore.subscribe((l: Location) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export function syncWorkbenchTab (): void {
|
export function syncWorkbenchTab (): void {
|
||||||
const workspace = get(workspaceStore)
|
const workspace = get(locationWorkspaceStore)
|
||||||
tabIdStore.set(getTabFromLocalStorage(workspace ?? ''))
|
tabIdStore.set(getTabFromLocalStorage(workspace ?? ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ function getTabFromLocalStorage (workspace: string): Ref<WorkbenchTab> | undefin
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saveTabToLocalStorage (_id: Ref<WorkbenchTab> | undefined): void {
|
function saveTabToLocalStorage (_id: Ref<WorkbenchTab> | undefined): void {
|
||||||
const workspace = get(workspaceStore)
|
const workspace = get(locationWorkspaceStore)
|
||||||
if (workspace == null || workspace === '') return
|
if (workspace == null || workspace === '') return
|
||||||
|
|
||||||
const localStorageKey = getTabIdLocalStorageKey(workspace)
|
const localStorageKey = getTabIdLocalStorageKey(workspace)
|
||||||
|
@ -89,6 +89,7 @@ export interface WidgetPreference extends Preference {
|
|||||||
export interface WidgetTab {
|
export interface WidgetTab {
|
||||||
id: string
|
id: string
|
||||||
name?: string
|
name?: string
|
||||||
|
label?: IntlString
|
||||||
icon?: Asset | AnySvelteComponent
|
icon?: Asset | AnySvelteComponent
|
||||||
iconComponent?: AnyComponent
|
iconComponent?: AnyComponent
|
||||||
iconProps?: Record<string, any>
|
iconProps?: Record<string, any>
|
||||||
@ -280,6 +281,7 @@ export default plugin(workbenchId, {
|
|||||||
function: {
|
function: {
|
||||||
CreateWidgetTab: '' as Resource<(widget: Widget, tab: WidgetTab, newTab: boolean) => Promise<void>>,
|
CreateWidgetTab: '' as Resource<(widget: Widget, tab: WidgetTab, newTab: boolean) => Promise<void>>,
|
||||||
CloseWidgetTab: '' as Resource<(widget: Widget, tab: string) => 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'>>>
|
GetSidebarObject: '' as Resource<() => Partial<Pick<Doc, '_id' | '_class'>>>
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
|
@ -62,6 +62,7 @@ export async function createAccountRequest (workspace: WorkspaceId, ctx: Measure
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
ctx.info('Requesting AI account creation', { url, workspace })
|
||||||
await fetch(concatLink(url, '/connect'), {
|
await fetch(concatLink(url, '/connect'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
"prettier-plugin-svelte": "^3.2.2"
|
"prettier-plugin-svelte": "^3.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hcengineering/view": "^0.6.13",
|
||||||
"@hcengineering/core": "^0.6.32",
|
"@hcengineering/core": "^0.6.32",
|
||||||
"@hcengineering/contact": "^0.6.24",
|
"@hcengineering/contact": "^0.6.24",
|
||||||
"@hcengineering/notification": "^0.6.23",
|
"@hcengineering/notification": "^0.6.23",
|
||||||
|
@ -23,11 +23,14 @@ import core, {
|
|||||||
TxMixin,
|
TxMixin,
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
TxUpdateDoc,
|
TxUpdateDoc,
|
||||||
UserStatus
|
UserStatus,
|
||||||
|
Doc,
|
||||||
|
concatLink
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import love, {
|
import love, {
|
||||||
Invite,
|
Invite,
|
||||||
JoinRequest,
|
JoinRequest,
|
||||||
|
MeetingMinutes,
|
||||||
ParticipantInfo,
|
ParticipantInfo,
|
||||||
RequestStatus,
|
RequestStatus,
|
||||||
RoomAccess,
|
RoomAccess,
|
||||||
@ -35,14 +38,15 @@ import love, {
|
|||||||
loveId
|
loveId
|
||||||
} from '@hcengineering/love'
|
} from '@hcengineering/love'
|
||||||
import notification from '@hcengineering/notification'
|
import notification from '@hcengineering/notification'
|
||||||
import { translate } from '@hcengineering/platform'
|
import { getMetadata, translate } from '@hcengineering/platform'
|
||||||
import { TriggerControl } from '@hcengineering/server-core'
|
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||||
import {
|
import {
|
||||||
createPushNotification,
|
createPushNotification,
|
||||||
getNotificationProviderControl,
|
getNotificationProviderControl,
|
||||||
isAllowed
|
isAllowed
|
||||||
} from '@hcengineering/server-notification-resources'
|
} from '@hcengineering/server-notification-resources'
|
||||||
import { workbenchId } from '@hcengineering/workbench'
|
import { workbenchId } from '@hcengineering/workbench'
|
||||||
|
import view from '@hcengineering/view'
|
||||||
|
|
||||||
export async function OnEmployee (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
export async function OnEmployee (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||||
const result: Tx[] = []
|
const result: Tx[] = []
|
||||||
@ -370,8 +374,31 @@ export async function OnInvite (txes: Tx[], control: TriggerControl): Promise<Tx
|
|||||||
return []
|
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
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export default async () => ({
|
export default async () => ({
|
||||||
|
function: {
|
||||||
|
MeetingMinutesHTMLPresenter: meetingMinutesHTMLPresenter,
|
||||||
|
MeetingMinutesTextPresenter: meetingMinutesTextPresenter
|
||||||
|
},
|
||||||
trigger: {
|
trigger: {
|
||||||
OnEmployee,
|
OnEmployee,
|
||||||
OnUserStatus,
|
OnUserStatus,
|
||||||
|
@ -38,7 +38,9 @@
|
|||||||
"prettier-plugin-svelte": "^3.2.2"
|
"prettier-plugin-svelte": "^3.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hcengineering/core": "^0.6.32",
|
||||||
"@hcengineering/platform": "^0.6.11",
|
"@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 type { Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
import { TriggerFunc } from '@hcengineering/server-core'
|
import { TriggerFunc } from '@hcengineering/server-core'
|
||||||
|
import { Presenter } from '@hcengineering/server-notification'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -11,6 +12,10 @@ export const serverLoveId = 'server-love' as Plugin
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export default plugin(serverLoveId, {
|
export default plugin(serverLoveId, {
|
||||||
|
function: {
|
||||||
|
MeetingMinutesHTMLPresenter: '' as Resource<Presenter>,
|
||||||
|
MeetingMinutesTextPresenter: '' as Resource<Presenter>
|
||||||
|
},
|
||||||
trigger: {
|
trigger: {
|
||||||
OnEmployee: '' as Resource<TriggerFunc>,
|
OnEmployee: '' as Resource<TriggerFunc>,
|
||||||
OnUserStatus: '' as Resource<TriggerFunc>,
|
OnUserStatus: '' as Resource<TriggerFunc>,
|
||||||
|
@ -290,7 +290,10 @@ export class AIControl {
|
|||||||
if (workspace === null) return
|
if (workspace === null) return
|
||||||
|
|
||||||
const wsClient = await this.getWorkspaceClient(workspace)
|
const wsClient = await this.getWorkspaceClient(workspace)
|
||||||
if (wsClient === undefined) return
|
if (wsClient === undefined) {
|
||||||
|
this.ctx.error('Workspace not found', { workspace })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return await wsClient.getLoveIdentity()
|
return await wsClient.getLoveIdentity()
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
aiBotAccountEmail
|
aiBotAccountEmail
|
||||||
} from '@hcengineering/ai-bot'
|
} from '@hcengineering/ai-bot'
|
||||||
import { extractToken } from '@hcengineering/server-client'
|
import { extractToken } from '@hcengineering/server-client'
|
||||||
|
import { MeasureContext } from '@hcengineering/core'
|
||||||
|
|
||||||
import { ApiError } from './error'
|
import { ApiError } from './error'
|
||||||
import { AIControl } from '../controller'
|
import { AIControl } from '../controller'
|
||||||
@ -54,7 +55,7 @@ const wrapRequest = (fn: AsyncRequestHandler) => (req: Request, res: Response, n
|
|||||||
void handleRequest(fn, req, res, next)
|
void handleRequest(fn, req, res, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createServer (controller: AIControl): Express {
|
export function createServer (controller: AIControl, ctx: MeasureContext): Express {
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use(cors())
|
app.use(cors())
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
@ -78,6 +79,7 @@ export function createServer (controller: AIControl): Express {
|
|||||||
app.post(
|
app.post(
|
||||||
'/connect',
|
'/connect',
|
||||||
wrapRequest(async (_, res, token) => {
|
wrapRequest(async (_, res, token) => {
|
||||||
|
ctx.info('Request to connect to workspace', { workspace: token.workspace.name })
|
||||||
await controller.connect(token.workspace.name)
|
await controller.connect(token.workspace.name)
|
||||||
|
|
||||||
res.status(200)
|
res.status(200)
|
||||||
|
@ -51,7 +51,7 @@ export const start = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
const aiControl = new AIControl(storage, ctx)
|
const aiControl = new AIControl(storage, ctx)
|
||||||
|
|
||||||
const app = createServer(aiControl)
|
const app = createServer(aiControl, ctx)
|
||||||
const server = listen(app, config.Port)
|
const server = listen(app, config.Port)
|
||||||
|
|
||||||
const onClose = (): void => {
|
const onClose = (): void => {
|
||||||
|
@ -64,6 +64,7 @@ export async function tryAssignToWorkspace (
|
|||||||
const info = await tryGetWorkspaceInfo(workspace, ctx)
|
const info = await tryGetWorkspaceInfo(workspace, ctx)
|
||||||
|
|
||||||
if (info === undefined) {
|
if (info === undefined) {
|
||||||
|
ctx.error('Workspace not found', { workspace })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,8 +9,7 @@ import core, {
|
|||||||
TxCreateDoc,
|
TxCreateDoc,
|
||||||
TxUpdateDoc,
|
TxUpdateDoc,
|
||||||
MeasureContext,
|
MeasureContext,
|
||||||
Markup,
|
Markup
|
||||||
generateId
|
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { Person } from '@hcengineering/contact'
|
import { Person } from '@hcengineering/contact'
|
||||||
import love, {
|
import love, {
|
||||||
@ -22,27 +21,11 @@ import love, {
|
|||||||
TranscriptionStatus
|
TranscriptionStatus
|
||||||
} from '@hcengineering/love'
|
} from '@hcengineering/love'
|
||||||
import { ConnectMeetingRequest } from '@hcengineering/ai-bot'
|
import { ConnectMeetingRequest } from '@hcengineering/ai-bot'
|
||||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
import chunter from '@hcengineering/chunter'
|
||||||
import { jsonToMarkup, MarkupNodeType } from '@hcengineering/text'
|
import { jsonToMarkup, MarkupNodeType } from '@hcengineering/text'
|
||||||
|
|
||||||
import config from '../config'
|
import config from '../config'
|
||||||
|
|
||||||
class Transcriptions {
|
|
||||||
private readonly transcriptionByPerson = new Map<Ref<Person>, { _id: Ref<ChatMessage>, text: string }>()
|
|
||||||
|
|
||||||
get (person: Ref<Person>): { _id: Ref<ChatMessage>, text: string } | undefined {
|
|
||||||
return this.transcriptionByPerson.get(person)
|
|
||||||
}
|
|
||||||
|
|
||||||
set (person: Ref<Person>, value: { _id: Ref<ChatMessage>, text: string }): void {
|
|
||||||
this.transcriptionByPerson.set(person, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete (person: Ref<Person>): void {
|
|
||||||
this.transcriptionByPerson.delete(person)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LoveController {
|
export class LoveController {
|
||||||
private readonly roomSidById = new Map<Ref<Room>, string>()
|
private readonly roomSidById = new Map<Ref<Room>, string>()
|
||||||
private readonly connectedRooms = new Set<Ref<Room>>()
|
private readonly connectedRooms = new Set<Ref<Room>>()
|
||||||
@ -50,7 +33,6 @@ export class LoveController {
|
|||||||
private participantsInfo: ParticipantInfo[] = []
|
private participantsInfo: ParticipantInfo[] = []
|
||||||
private rooms: Room[] = []
|
private rooms: Room[] = []
|
||||||
private readonly meetingMinutes: MeetingMinutes[] = []
|
private readonly meetingMinutes: MeetingMinutes[] = []
|
||||||
private readonly activeTranscriptions = new Map<Ref<Room>, Transcriptions>()
|
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private readonly workspace: string,
|
private readonly workspace: string,
|
||||||
@ -142,6 +124,7 @@ export class LoveController {
|
|||||||
|
|
||||||
const room = await this.getRoom(request.roomId)
|
const room = await this.getRoom(request.roomId)
|
||||||
if (room === undefined) {
|
if (room === undefined) {
|
||||||
|
this.ctx.error('Room not found', request)
|
||||||
this.roomSidById.delete(request.roomId)
|
this.roomSidById.delete(request.roomId)
|
||||||
this.connectedRooms.delete(request.roomId)
|
this.connectedRooms.delete(request.roomId)
|
||||||
return
|
return
|
||||||
@ -166,8 +149,6 @@ export class LoveController {
|
|||||||
async disconnect (roomId: Ref<Room>): Promise<void> {
|
async disconnect (roomId: Ref<Room>): Promise<void> {
|
||||||
this.ctx.info('Disconnecting', { roomId })
|
this.ctx.info('Disconnecting', { roomId })
|
||||||
|
|
||||||
this.activeTranscriptions.delete(roomId)
|
|
||||||
|
|
||||||
const participant = await this.getRoomParticipant(roomId, this.currentPerson._id)
|
const participant = await this.getRoomParticipant(roomId, this.currentPerson._id)
|
||||||
if (participant !== undefined) {
|
if (participant !== undefined) {
|
||||||
await this.client.remove(participant)
|
await this.client.remove(participant)
|
||||||
@ -191,44 +172,32 @@ export class LoveController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const doc = await this.getMeetingMinutes(roomId, this.roomSidById.get(roomId) ?? '')
|
const sid = this.roomSidById.get(roomId)
|
||||||
const personAccount = this.client.getModel().getAccountByPersonId(participant.person)[0]
|
|
||||||
if (doc === undefined) return
|
|
||||||
|
|
||||||
const transcriptions = this.activeTranscriptions.get(roomId) ?? new Transcriptions()
|
if (sid === undefined) {
|
||||||
const activeTranscription = transcriptions.get(participant.person)
|
return
|
||||||
|
|
||||||
if (activeTranscription === undefined) {
|
|
||||||
const _id = generateId<ChatMessage>()
|
|
||||||
if (!final) {
|
|
||||||
transcriptions.set(participant.person, { _id, text })
|
|
||||||
this.activeTranscriptions.set(roomId, transcriptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.client.addCollection(
|
|
||||||
chunter.class.ChatMessage,
|
|
||||||
core.space.Workspace,
|
|
||||||
doc._id,
|
|
||||||
doc._class,
|
|
||||||
'transcription',
|
|
||||||
{
|
|
||||||
message: this.transcriptToMarkup(text)
|
|
||||||
},
|
|
||||||
_id,
|
|
||||||
undefined,
|
|
||||||
personAccount._id
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const mergedText = activeTranscription.text + ' ' + text
|
|
||||||
if (!final) {
|
|
||||||
transcriptions.set(participant.person, { _id: activeTranscription._id, text: mergedText })
|
|
||||||
} else {
|
|
||||||
transcriptions.delete(participant.person)
|
|
||||||
}
|
|
||||||
await this.client.updateDoc(chunter.class.ChatMessage, core.space.Workspace, activeTranscription._id, {
|
|
||||||
message: this.transcriptToMarkup(mergedText)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const personAccount = this.client.getModel().getAccountByPersonId(participant.person)[0]
|
||||||
|
const doc = await this.getMeetingMinutes(room, sid)
|
||||||
|
|
||||||
|
if (doc === undefined) return
|
||||||
|
const op = this.client.apply(undefined, undefined, true)
|
||||||
|
|
||||||
|
await op.addCollection(
|
||||||
|
chunter.class.ChatMessage,
|
||||||
|
core.space.Workspace,
|
||||||
|
doc._id,
|
||||||
|
doc._class,
|
||||||
|
'transcription',
|
||||||
|
{
|
||||||
|
message: this.transcriptToMarkup(text)
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
personAccount._id
|
||||||
|
)
|
||||||
|
await op.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
hasActiveConnections (): boolean {
|
hasActiveConnections (): boolean {
|
||||||
@ -246,18 +215,15 @@ export class LoveController {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMeetingMinutes (room: Ref<Room>, sid: string): Promise<MeetingMinutes | undefined> {
|
async getMeetingMinutes (room: Room, sid: string): Promise<MeetingMinutes | undefined> {
|
||||||
if (sid === '') return undefined
|
if (sid === '') return undefined
|
||||||
|
|
||||||
const existing = this.meetingMinutes.find((m) => m.room === room && m.sid === sid)
|
const doc =
|
||||||
if (existing !== undefined) return existing
|
this.meetingMinutes.find((m) => m.sid === sid) ?? (await this.client.findOne(love.class.MeetingMinutes, { sid }))
|
||||||
|
|
||||||
const doc = await this.client.findOne(love.class.MeetingMinutes, {
|
if (doc === undefined) {
|
||||||
room,
|
return undefined
|
||||||
sid
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if (doc === undefined) return
|
|
||||||
|
|
||||||
this.meetingMinutes.push(doc)
|
this.meetingMinutes.push(doc)
|
||||||
return doc
|
return doc
|
||||||
|
@ -727,7 +727,10 @@ export class WorkspaceClient {
|
|||||||
// Just wait initialization
|
// Just wait initialization
|
||||||
await this.opClient
|
await this.opClient
|
||||||
|
|
||||||
if (this.love === undefined) return
|
if (this.love === undefined) {
|
||||||
|
this.ctx.error('Love is not initialized')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return this.love.getIdentity()
|
return this.love.getIdentity()
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ export const main = async (): Promise<void> => {
|
|||||||
const metadata = language != null ? { transcription, language } : { transcription }
|
const metadata = language != null ? { transcription, language } : { transcription }
|
||||||
try {
|
try {
|
||||||
await updateMetadata(roomClient, roomName, metadata)
|
await updateMetadata(roomClient, roomName, metadata)
|
||||||
res.send()
|
res.status(200).send()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
res.status(500).send()
|
res.status(500).send()
|
||||||
|
Loading…
Reference in New Issue
Block a user