Add meeting minutes status, fix messages collection

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina Fefelova 2024-11-15 00:21:51 +04:00
parent 6a21296d2b
commit 8cac2f9142
No known key found for this signature in database
GPG Key ID: 750D35EF042F0690
44 changed files with 379 additions and 123 deletions

View File

@ -201,11 +201,8 @@ export class TDocUpdateMessageViewlet extends TDoc implements DocUpdateMessageVi
@Model(activity.class.ActivityExtension, core.class.Doc, DOMAIN_MODEL) @Model(activity.class.ActivityExtension, core.class.Doc, DOMAIN_MODEL)
export class TActivityExtension extends TDoc implements ActivityExtension { export class TActivityExtension extends TDoc implements ActivityExtension {
@Prop(TypeRef(core.class.Class), core.string.Class)
@Index(IndexKind.Indexed)
ofClass!: Ref<Class<Doc>> ofClass!: Ref<Class<Doc>>
components!: Record<ActivityExtensionKind, { component: AnyComponent, props?: Record<string, any> }>
components!: Record<ActivityExtensionKind, AnyComponent>
} }
@Model(activity.class.ActivityMessagesFilter, core.class.Doc, DOMAIN_MODEL) @Model(activity.class.ActivityMessagesFilter, core.class.Doc, DOMAIN_MODEL)

View File

@ -85,7 +85,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: analyticsCollector.class.OnboardingChannel, ofClass: analyticsCollector.class.OnboardingChannel,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc<ActivityMessageControl<OnboardingChannel>>( builder.createDoc<ActivityMessageControl<OnboardingChannel>>(

View File

@ -273,27 +273,27 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: chunter.class.Channel, ofClass: chunter.class.Channel,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: chunter.class.DirectMessage, ofClass: chunter.class.DirectMessage,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: activity.class.DocUpdateMessage, ofClass: activity.class.DocUpdateMessage,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: chunter.class.ChatMessage, ofClass: chunter.class.ChatMessage,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: activity.class.ActivityReference, ofClass: activity.class.ActivityReference,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
// Indexing // Indexing

View File

@ -263,22 +263,22 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: contact.class.Contact, ofClass: contact.class.Contact,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: contact.class.Person, ofClass: contact.class.Person,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: contact.class.Organization, ofClass: contact.class.Organization,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: contact.class.Member, ofClass: contact.class.Member,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(contact.mixin.Employee, core.class.Class, view.mixin.ObjectFactory, { builder.mixin(contact.mixin.Employee, core.class.Class, view.mixin.ObjectFactory, {

View File

@ -514,7 +514,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: documents.class.DocumentCategory, ofClass: documents.class.DocumentCategory,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(documents.class.DocumentCategory, core.class.Class, view.mixin.ObjectPresenter, { builder.mixin(documents.class.DocumentCategory, core.class.Class, view.mixin.ObjectPresenter, {
@ -768,7 +768,7 @@ export function defineNotifications (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: documents.class.DocumentComment, ofClass: documents.class.DocumentComment,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(documents.class.ControlledDocument, core.class.Class, notification.mixin.ClassCollaborators, { builder.mixin(documents.class.ControlledDocument, core.class.Class, notification.mixin.ClassCollaborators, {

View File

@ -492,7 +492,7 @@ function defineDocument (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: document.class.Document, ofClass: document.class.Document,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
// Search // Search

View File

@ -595,7 +595,7 @@ function defineFile (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: drive.class.File, ofClass: drive.class.File,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
// Search // Search

View File

@ -85,17 +85,17 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: inventory.class.Product, ofClass: inventory.class.Product,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: inventory.class.Category, ofClass: inventory.class.Category,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: inventory.class.Variant, ofClass: inventory.class.Variant,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(inventory.class.Category, core.class.Class, view.mixin.ObjectPresenter, { builder.mixin(inventory.class.Category, core.class.Class, view.mixin.ObjectPresenter, {

View File

@ -51,12 +51,12 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: lead.class.Lead, ofClass: lead.class.Lead,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: lead.class.Funnel, ofClass: lead.class.Funnel,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(lead.class.Funnel, core.class.Class, workbench.mixin.SpaceView, { builder.mixin(lead.class.Funnel, core.class.Class, workbench.mixin.SpaceView, {

View File

@ -21,7 +21,8 @@ import {
IndexKind, IndexKind,
type Ref, type Ref,
type CollaborativeDoc, type CollaborativeDoc,
Doc type Doc,
type Timestamp
} from '@hcengineering/core' } from '@hcengineering/core'
import { import {
type DevicesPreference, type DevicesPreference,
@ -38,7 +39,8 @@ import {
type RoomInfo, type RoomInfo,
type RoomType, type RoomType,
type RoomLanguage, type RoomLanguage,
type MeetingMinutes type MeetingMinutes,
type MeetingStatus
} from '@hcengineering/love' } from '@hcengineering/love'
import { import {
type Builder, type Builder,
@ -53,7 +55,9 @@ import {
TypeCollaborativeDoc, TypeCollaborativeDoc,
TypeRef, TypeRef,
TypeString, TypeString,
UX TypeTimestamp,
UX,
TypeAny
} from '@hcengineering/model' } from '@hcengineering/model'
import calendar, { TEvent } from '@hcengineering/model-calendar' import calendar, { TEvent } from '@hcengineering/model-calendar'
import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core' import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core'
@ -108,6 +112,9 @@ export class TRoom extends TDoc implements Room {
@Prop(PropCollection(love.class.MeetingMinutes), love.string.MeetingMinutes) @Prop(PropCollection(love.class.MeetingMinutes), love.string.MeetingMinutes)
meetings?: number meetings?: number
@Prop(PropCollection(chunter.class.ChatMessage), activity.string.Messages)
messages?: number
} }
@Model(love.class.Office, love.class.Room) @Model(love.class.Office, love.class.Room)
@ -203,6 +210,12 @@ export class TMeetingMinutes extends TAttachedDoc implements MeetingMinutes {
@Index(IndexKind.FullText) @Index(IndexKind.FullText)
description!: CollaborativeDoc description!: CollaborativeDoc
@Prop(TypeAny(love.component.MeetingMinutesStatusPresenter, love.string.Status), love.string.Status, {
editor: love.component.MeetingMinutesStatusPresenter
})
@ReadOnly()
status!: MeetingStatus
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: number attachments?: number
@ -211,6 +224,15 @@ export class TMeetingMinutes extends TAttachedDoc implements MeetingMinutes {
@Prop(PropCollection(chunter.class.ChatMessage), activity.string.Messages) @Prop(PropCollection(chunter.class.ChatMessage), activity.string.Messages)
messages?: number messages?: number
@Prop(TypeTimestamp(), love.string.MeetingStart, { editor: view.component.TimestampPresenter })
@ReadOnly()
@Index(IndexKind.IndexedDsc)
declare createdOn: Timestamp
@Prop(TypeTimestamp(), love.string.MeetingEnd)
@ReadOnly()
meetingEnd?: Timestamp
} }
export default love export default love
@ -424,17 +446,17 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: love.class.Room, ofClass: love.class.Room,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput, props: { collection: 'messages' } } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: love.class.Office, ofClass: love.class.Office,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput, props: { collection: 'messages' } } }
}) })
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: { component: chunter.component.ChatMessageInput, props: { collection: 'messages' } } }
}) })
builder.mixin(love.class.MeetingMinutes, core.class.Class, activity.mixin.ActivityDoc, {}) builder.mixin(love.class.MeetingMinutes, core.class.Class, activity.mixin.ActivityDoc, {})
@ -477,10 +499,11 @@ export function createModel (builder: Builder): void {
descriptor: view.viewlet.Table, descriptor: view.viewlet.Table,
config: [ config: [
'', '',
{ key: 'status', presenter: love.component.MeetingMinutesStatusPresenter, label: love.string.Status },
'createdOn',
'meetingEnd',
{ key: 'messages', displayProps: { key: 'messages', suffix: true } }, { key: 'messages', displayProps: { key: 'messages', suffix: true } },
{ key: 'transcription', displayProps: { key: 'transcription', suffix: true } }, { key: 'transcription', displayProps: { key: 'transcription', suffix: true } }
'modifiedOn',
'modifiedBy'
], ],
configOptions: { configOptions: {
hiddenKeys: ['description'], hiddenKeys: ['description'],

View File

@ -16,7 +16,16 @@
import contact from '@hcengineering/contact' import contact from '@hcengineering/contact'
import { type Space, TxOperations, type Ref, makeCollaborativeDoc } 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, type Room } from '@hcengineering/love' import {
MeetingStatus,
RoomAccess,
RoomType,
createDefaultRooms,
isOffice,
loveId,
type Floor,
type Room
} from '@hcengineering/love'
import { import {
createDefaultSpace, createDefaultSpace,
migrateSpace, migrateSpace,
@ -142,6 +151,16 @@ export const loveOperation: MigrateOperation = {
} }
} }
} }
},
{
state: 'default-meeting-minutes-status',
func: async (client) => {
await client.update(
DOMAIN_MEETING_MINUTES,
{ status: { $exists: false } },
{ status: MeetingStatus.Finished }
)
}
} }
]) ])
}, },

View File

@ -159,7 +159,7 @@ function defineProduct (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: products.class.Product, ofClass: products.class.Product,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(products.class.Product, core.class.Class, view.mixin.ObjectEditor, { builder.mixin(products.class.Product, core.class.Class, view.mixin.ObjectEditor, {
@ -299,7 +299,7 @@ function defineProductVersion (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: products.class.ProductVersion, ofClass: products.class.ProductVersion,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(products.class.ProductVersion, core.class.Class, view.mixin.ObjectEditor, { builder.mixin(products.class.ProductVersion, core.class.Class, view.mixin.ObjectEditor, {

View File

@ -56,17 +56,17 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: recruit.class.Vacancy, ofClass: recruit.class.Vacancy,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: recruit.class.Applicant, ofClass: recruit.class.Applicant,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: recruit.class.Review, ofClass: recruit.class.Review,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(recruit.class.Vacancy, core.class.Class, workbench.mixin.SpaceView, { builder.mixin(recruit.class.Vacancy, core.class.Class, workbench.mixin.SpaceView, {

View File

@ -91,7 +91,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: survey.class.Survey, ofClass: survey.class.Survey,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc<Viewlet>( builder.createDoc<Viewlet>(

View File

@ -143,7 +143,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestProject, ofClass: testManagement.class.TestProject,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
defineTestSuite(builder) defineTestSuite(builder)
@ -218,7 +218,7 @@ function defineTestSuite (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestSuite, ofClass: testManagement.class.TestSuite,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(testManagement.class.TestSuite, core.class.Class, view.mixin.ObjectEditor, { builder.mixin(testManagement.class.TestSuite, core.class.Class, view.mixin.ObjectEditor, {
@ -283,7 +283,7 @@ function defineTestCase (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestCase, ofClass: testManagement.class.TestCase,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(testManagement.class.TestCase, core.class.Class, view.mixin.ObjectEditor, { builder.mixin(testManagement.class.TestCase, core.class.Class, view.mixin.ObjectEditor, {
@ -388,7 +388,7 @@ function defineTestRun (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestRun, ofClass: testManagement.class.TestRun,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.mixin(testManagement.class.TestRun, core.class.Class, view.mixin.ObjectEditor, { builder.mixin(testManagement.class.TestRun, core.class.Class, view.mixin.ObjectEditor, {

View File

@ -461,22 +461,22 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: tracker.class.Issue, ofClass: tracker.class.Issue,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: tracker.class.Milestone, ofClass: tracker.class.Milestone,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: tracker.class.Component, ofClass: tracker.class.Component,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: tracker.class.IssueTemplate, ofClass: tracker.class.IssueTemplate,
components: { input: chunter.component.ChatMessageInput } components: { input: { component: chunter.component.ChatMessageInput } }
}) })
defineViewlets(builder) defineViewlets(builder)

View File

@ -24,5 +24,11 @@
</script> </script>
{#if extension} {#if extension}
<Component is={extension.components[kind]} {props} on:close on:open on:submit /> <Component
is={extension.components[kind].component}
props={{ ...extension.components[kind].props, ...props }}
on:close
on:open
on:submit
/>
{/if} {/if}

View File

@ -30,7 +30,7 @@
$: attrViewletConfig = viewlet?.config?.[attributeModel.key] $: attrViewletConfig = viewlet?.config?.[attributeModel.key]
$: attributeIcon = attrViewletConfig?.icon ?? attributeModel.icon ?? IconEdit $: attributeIcon = attrViewletConfig?.icon ?? attributeModel.icon ?? IconEdit
$: isUnset = values.length > 0 && !values.some((value) => value !== null && value !== '') $: isUnset = values.length > 0 && !values.some((value) => value != null && value !== '')
$: isTextType = getIsTextType(attributeModel) $: isTextType = getIsTextType(attributeModel)
@ -42,7 +42,7 @@
</script> </script>
{#if isUnset} {#if isUnset}
<div class="row overflow-label"> <div class="unset row overflow-label">
<span class="mr-1"><Icon icon={attributeIcon} size="small" /></span> <span class="mr-1"><Icon icon={attributeIcon} size="small" /></span>
<Label label={activity.string.Unset} /> <Label label={activity.string.Unset} />
<span class="lower"><Label label={attributeModel.label} /></span> <span class="lower"><Label label={attributeModel.label} /></span>
@ -89,6 +89,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.25rem; gap: 0.25rem;
color: var(--global-primary-TextColor);
} }
.showMore { .showMore {

View File

@ -210,7 +210,7 @@ export type ActivityExtensionKind = 'input'
*/ */
export interface ActivityExtension extends Doc { export interface ActivityExtension extends Doc {
ofClass: Ref<Class<Doc>> ofClass: Ref<Class<Doc>>
components: Record<ActivityExtensionKind, AnyComponent> components: Record<ActivityExtensionKind, { component: AnyComponent, props?: Record<string, any> }>
} }
/** /**

View File

@ -72,6 +72,9 @@
"StartMeeting": "Start meeting", "StartMeeting": "Start meeting",
"Video": "Video", "Video": "Video",
"NoMeetingMinutes": "No meeting minutes", "NoMeetingMinutes": "No meeting minutes",
"JoinMeeting": "Join meeting" "JoinMeeting": "Join meeting",
"MeetingStart": "Meeting start",
"MeetingEnd": "Meeting end",
"Status": "Status"
} }
} }

View File

@ -72,6 +72,9 @@
"StartMeeting": "Iniciar reunión", "StartMeeting": "Iniciar reunión",
"Video": "Video", "Video": "Video",
"NoMeetingMinutes": "Sin minutos de reunión", "NoMeetingMinutes": "Sin minutos de reunión",
"JoinMeeting": "Unirse a la reunión" "JoinMeeting": "Unirse a la reunión",
"MeetingStart": "Inicio de la reunión",
"MeetingEnd": "Fin de la reunión",
"Status": "Estado"
} }
} }

View File

@ -72,6 +72,9 @@
"StartMeeting": "Démarrer la réunion", "StartMeeting": "Démarrer la réunion",
"Video": "Vidéo", "Video": "Vidéo",
"NoMeetingMinutes": "Pas de minutes de réunion", "NoMeetingMinutes": "Pas de minutes de réunion",
"JoinMeeting": "Rejoindre la réunion" "JoinMeeting": "Rejoindre la réunion",
"MeetingStart": "Début de la réunion",
"MeetingEnd": "Fin de la réunion",
"Status": "Statut"
} }
} }

View File

@ -72,6 +72,9 @@
"StartMeeting": "Inizia riunione", "StartMeeting": "Inizia riunione",
"Video": "Video", "Video": "Video",
"NoMeetingMinutes": "Nessun verbale della riunione", "NoMeetingMinutes": "Nessun verbale della riunione",
"JoinMeeting": "Unisciti alla riunione" "JoinMeeting": "Unisciti alla riunione",
"MeetingStart": "Inizio riunione",
"MeetingEnd": "Fine riunione",
"Status": "Stato"
} }
} }

View File

@ -72,6 +72,9 @@
"StartMeeting": "Iniciar reunião", "StartMeeting": "Iniciar reunião",
"Video": "Vídeo", "Video": "Vídeo",
"NoMeetingMinutes": "Sem minutos de reunião", "NoMeetingMinutes": "Sem minutos de reunião",
"JoinMeeting": "Participar na reunião" "JoinMeeting": "Participar na reunião",
"MeetingStart": "Início da reunião",
"MeetingEnd": "Fim da reunião",
"Status": "Estado"
} }
} }

View File

@ -72,6 +72,9 @@
"StartMeeting": "Начать встречу", "StartMeeting": "Начать встречу",
"Video": "Видео", "Video": "Видео",
"NoMeetingMinutes": "Нет результатов встреч", "NoMeetingMinutes": "Нет результатов встреч",
"JoinMeeting": "Присоединиться к встрече" "JoinMeeting": "Присоединиться к встрече",
"MeetingStart": "Начало встречи",
"MeetingEnd": "Конец встречи",
"Status": "Статус"
} }
} }

View File

@ -72,6 +72,9 @@
"StartMeeting": "开始会议", "StartMeeting": "开始会议",
"Video": "视频", "Video": "视频",
"NoMeetingMinutes": "无会议记录", "NoMeetingMinutes": "无会议记录",
"JoinMeeting": "加入会议" "JoinMeeting": "加入会议",
"MeetingStart": "会议开始",
"MeetingEnd": "会议结束",
"Status": "状态"
} }
} }

View File

@ -29,7 +29,8 @@
type CompAndProps, type CompAndProps,
IconMoreV, IconMoreV,
ButtonMenu, ButtonMenu,
DropdownIntlItem, IconMaximize DropdownIntlItem,
IconMaximize
} 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'
@ -268,7 +269,7 @@
direction: 'top' direction: 'top'
}} }}
kind={'secondary'} kind={'secondary'}
iconSize='medium' iconSize="medium"
size={'large'} size={'large'}
on:click={maximize} on:click={maximize}
/> />

View File

@ -19,11 +19,10 @@
import { createEventDispatcher, onMount } from 'svelte' import { createEventDispatcher, onMount } from 'svelte'
import { personByIdStore } from '@hcengineering/contact-resources' import { personByIdStore } from '@hcengineering/contact-resources'
import { IntlString } from '@hcengineering/platform' import { IntlString } from '@hcengineering/platform'
import { openDoc } from '@hcengineering/view-resources'
import love from '../plugin' import love from '../plugin'
import { getRoomName, tryConnect } from '../utils' import { getRoomName, tryConnect } from '../utils'
import { infos, invites, myInfo, myRequests, selectedRoomPlace, meetingMinutesStore } from '../stores' import { infos, invites, myInfo, myRequests, selectedRoomPlace, myOffice, currentRoom } from '../stores'
export let object: Room export let object: Room
export let readonly: boolean = false export let readonly: boolean = false
@ -60,12 +59,6 @@
) )
connecting = false connecting = false
selectedRoomPlace.set(undefined) selectedRoomPlace.set(undefined)
const meeting = $meetingMinutesStore
if (meeting !== undefined) {
await openDoc(client.getHierarchy(), meeting)
}
} }
let connectLabel: IntlString = love.string.StartMeeting let connectLabel: IntlString = love.string.StartMeeting
@ -88,7 +81,7 @@
focusIndex={1} focusIndex={1}
/> />
</div> </div>
{#if (object._id !== $myInfo?.room && $myInfo !== undefined)} {#if object._id !== $myOffice?._id && ($currentRoom?._id !== object._id || connecting)}
<ModernButton label={connectLabel} size="large" kind={'primary'} on:click={connect} loading={connecting} /> <ModernButton label={connectLabel} size="large" kind={'primary'} on:click={connect} loading={connecting} />
{/if} {/if}
</div> </div>

View File

@ -17,7 +17,14 @@
import { ObjectPresenter } from '@hcengineering/view-resources' import { ObjectPresenter } from '@hcengineering/view-resources'
export let object: MeetingMinutes export let object: MeetingMinutes
</script> </script>
<ObjectPresenter objectId={object.attachedTo} _class={object.attachedToClass} shouldShowAvatar={false} disabled props={{ type: 'text' }}/> <span class="label flex-row-center ml-3 no-word-wrap">
<ObjectPresenter
objectId={object.attachedTo}
_class={object.attachedToClass}
shouldShowAvatar={false}
disabled
props={{ type: 'text' }}
/>
</span>

View File

@ -15,22 +15,50 @@
<script lang="ts"> <script lang="ts">
import type { Class, Doc, Ref, Space } from '@hcengineering/core' import type { Class, Doc, Ref, Space } from '@hcengineering/core'
import { Label, Section } from '@hcengineering/ui' import { Label, Section } from '@hcengineering/ui'
import { Table } from '@hcengineering/view-resources' import { Table, ViewletsSettingButton } from '@hcengineering/view-resources'
import love from '@hcengineering/love' import { Viewlet, ViewletPreference } from '@hcengineering/view'
import love from '../plugin'
export let objectId: Ref<Doc> export let objectId: Ref<Doc>
export let space: Ref<Space> export let space: Ref<Space>
export let _class: Ref<Class<Doc>> export let _class: Ref<Class<Doc>>
export let readonly: boolean = false export let readonly: boolean = false
export let meetings: number export let meetings: number
let viewlet: Viewlet | undefined
let preference: ViewletPreference | undefined
let loading = true
</script> </script>
<Section label={love.string.MeetingMinutes} icon={love.icon.Cam}> <Section label={love.string.MeetingMinutes} icon={love.icon.Cam}>
<svelte:fragment slot="header">
<div class="flex-row-center gap-2 reverse">
<ViewletsSettingButton
viewletQuery={{ _id: love.viewlet.TableMeetingMinutes }}
kind={'tertiary'}
bind:viewlet
bind:loading
bind:preference
/>
</div>
</svelte:fragment>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
{#if meetings > 0} {#if meetings > 0}
<Table <Table
_class={love.class.MeetingMinutes} _class={love.class.MeetingMinutes}
config={['', 'transcription', 'messages']} config={preference?.config ?? [
'',
{
key: 'status',
label: love.string.Status,
presenter: love.component.MeetingMinutesStatusPresenter
},
'messages',
'createdOn',
'meetingEnd'
]}
query={{ attachedTo: objectId }} query={{ attachedTo: objectId }}
loadingProps={{ length: meetings }} loadingProps={{ length: meetings }}
{readonly} {readonly}

View File

@ -0,0 +1,45 @@
<!--
// 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 { MeetingMinutes, MeetingStatus } from '@hcengineering/love'
import { StateType, StateTag } from '@hcengineering/ui'
import { getEmbeddedLabel } from '@hcengineering/platform'
export let object: MeetingMinutes | undefined
export let value: MeetingStatus | undefined
export let attributeKey: string | undefined
const displayData = {
[MeetingStatus.Active]: {
label: getEmbeddedLabel('Active'),
type: StateType.Positive
},
[MeetingStatus.Finished]: {
label: getEmbeddedLabel('Finished'),
type: StateType.Regular
}
}
let status: MeetingStatus | undefined
$: status = value ?? object?.status
$: data = status !== undefined ? displayData[status] : undefined
</script>
{#if data}
<span class="flex-row-center" class:ml-3={attributeKey !== undefined}>
<StateTag type={data.type} label={data.label} />
</span>
{/if}

View File

@ -15,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import { Person, PersonAccount } from '@hcengineering/contact' import { Person, PersonAccount } from '@hcengineering/contact'
import { personByIdStore, UserInfo } from '@hcengineering/contact-resources' import { personByIdStore, UserInfo } from '@hcengineering/contact-resources'
import { IdMap, getCurrentAccount } from '@hcengineering/core' import { IdMap, getCurrentAccount, Ref, Class, Doc } from '@hcengineering/core'
import ui, { import ui, {
ModernButton, ModernButton,
SplitButton, SplitButton,
@ -40,8 +40,10 @@
roomAccessLabel roomAccessLabel
} from '@hcengineering/love' } from '@hcengineering/love'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { getObjectLinkFragment } from '@hcengineering/view-resources'
import { getClient } from '@hcengineering/presentation'
import love from '../plugin' import love from '../plugin'
import { currentRoom, infos, invites, myInfo, myOffice, myRequests } from '../stores' import { currentRoom, infos, invites, myInfo, myOffice, myRequests, meetingMinutesStore } from '../stores'
import { import {
getRoomName, getRoomName,
isCameraEnabled, isCameraEnabled,
@ -58,9 +60,11 @@
import CamSettingPopup from './CamSettingPopup.svelte' import CamSettingPopup from './CamSettingPopup.svelte'
import MicSettingPopup from './MicSettingPopup.svelte' import MicSettingPopup from './MicSettingPopup.svelte'
import RoomAccessPopup from './RoomAccessPopup.svelte' import RoomAccessPopup from './RoomAccessPopup.svelte'
import view from '@hcengineering/view'
export let room: Room export let room: Room
const client = getClient()
function getPerson (info: ParticipantInfo | undefined, employees: IdMap<Person>): Person | undefined { function getPerson (info: ParticipantInfo | undefined, employees: IdMap<Person>): Person | undefined {
if (info !== undefined) { if (info !== undefined) {
return employees.get(info.person) return employees.get(info.person)
@ -104,13 +108,21 @@
dispatch('close') dispatch('close')
} }
function back (): void { async function back (): Promise<void> {
closePanel() const meetingMinutes = $meetingMinutesStore
const loc = getCurrentLocation() if (meetingMinutes !== undefined) {
const hierarchy = client.getHierarchy()
const panelComponent = hierarchy.classHierarchyMixin(
meetingMinutes._class as Ref<Class<Doc>>,
view.mixin.ObjectPanel
)
const comp = panelComponent?.component ?? view.component.EditDoc
const loc = await getObjectLinkFragment(hierarchy, meetingMinutes, {}, comp)
loc.path[2] = loveId loc.path[2] = loveId
loc.path.length = 3 loc.path.length = 3
navigate(loc) navigate(loc)
} }
}
function micSettings (e: MouseEvent): void { function micSettings (e: MouseEvent): void {
showPopup(MicSettingPopup, {}, eventToHTMLElement(e)) showPopup(MicSettingPopup, {}, eventToHTMLElement(e))

View File

@ -60,7 +60,9 @@
$: if (sid != null && room !== undefined) { $: if (sid != null && room !== undefined) {
meetingQuery.query(love.class.MeetingMinutes, { sid, attachedTo: room._id }, async (res) => { meetingQuery.query(love.class.MeetingMinutes, { sid, attachedTo: room._id }, async (res) => {
meetingMinutes = res[0] meetingMinutes = res[0]
if (meetingMinutes) {
meetingMinutesStore.set(meetingMinutes) meetingMinutesStore.set(meetingMinutes)
}
isMeetingMinutesLoaded = true isMeetingMinutesLoaded = true
}) })
} else { } else {

View File

@ -21,6 +21,7 @@ import MeetingMinutesTable from './components/MeetingMinutesTable.svelte'
import PanelControlBar from './components/PanelControlBar.svelte' import PanelControlBar from './components/PanelControlBar.svelte'
import RoomPresenter from './components/RoomPresenter.svelte' import RoomPresenter from './components/RoomPresenter.svelte'
import MeetingMinutesDocEditor from './components/MeetingMinutesDocEditor.svelte' import MeetingMinutesDocEditor from './components/MeetingMinutesDocEditor.svelte'
import MeetingMinutesStatusPresenter from './components/MeetingMinutesStatusPresenter.svelte'
import { import {
copyGuestLink, copyGuestLink,
@ -55,7 +56,8 @@ export default async (): Promise<Resources> => ({
MeetingMinutesTable, MeetingMinutesTable,
PanelControlBar, PanelControlBar,
RoomPresenter, RoomPresenter,
MeetingMinutesDocEditor MeetingMinutesDocEditor,
MeetingMinutesStatusPresenter
}, },
function: { function: {
CreateMeeting: createMeeting, CreateMeeting: createMeeting,

View File

@ -33,7 +33,8 @@ export default mergeIds(loveId, love, {
MeetingMinutesTable: '' as AnyComponent, MeetingMinutesTable: '' as AnyComponent,
FloorView: '' as AnyComponent, FloorView: '' as AnyComponent,
PanelControlBar: '' as AnyComponent, PanelControlBar: '' as AnyComponent,
MeetingMinutesDocEditor: '' as AnyComponent MeetingMinutesDocEditor: '' as AnyComponent,
MeetingMinutesStatusPresenter: '' as AnyComponent
}, },
function: { function: {
CreateMeeting: '' as Resource<DocCreateFunction>, CreateMeeting: '' as Resource<DocCreateFunction>,

View File

@ -30,7 +30,8 @@ import {
RoomType, RoomType,
TranscriptionStatus, TranscriptionStatus,
type RoomMetadata, type RoomMetadata,
type MeetingMinutes type MeetingMinutes,
MeetingStatus
} 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, {
@ -39,7 +40,14 @@ import presentation, {
type DocCreatePhase, type DocCreatePhase,
getClient getClient
} from '@hcengineering/presentation' } from '@hcengineering/presentation'
import { type DropdownTextItem, getCurrentLocation, navigate, showPopup } from '@hcengineering/ui' import {
type DropdownTextItem,
getCurrentLocation,
navigate,
showPopup,
panelstore,
closePanel
} 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 {
@ -70,10 +78,11 @@ import {
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 chunter from '@hcengineering/chunter'
import { openDoc } from '@hcengineering/view-resources'
import { sendMessage } from './broadcast' import { sendMessage } from './broadcast'
import love from './plugin' import love from './plugin'
import { $myPreferences, meetingMinutesStore, currentRoom } from './stores' import { $myPreferences, currentRoom, meetingMinutesStore, selectedRoomPlace } from './stores'
import RoomSettingsPopup from './components/RoomSettingsPopup.svelte' import RoomSettingsPopup from './components/RoomSettingsPopup.svelte'
export const selectedCamId = 'selectedDevice_cam' export const selectedCamId = 'selectedDevice_cam'
@ -444,7 +453,6 @@ 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 })
@ -463,6 +471,22 @@ export async function leaveRoom (ownInfo: ParticipantInfo | undefined, ownOffice
} }
} }
await disconnect() await disconnect()
closeMeetingMinutes()
}
function closeMeetingMinutes (): void {
const loc = getCurrentLocation()
if (loc.path[2] === loveId) {
const meetingMinutes = get(meetingMinutesStore)
const panel = get(panelstore).panel
const { _id } = panel ?? {}
if (_id !== undefined && meetingMinutes !== undefined && _id === meetingMinutes._id) {
closePanel()
}
}
meetingMinutesStore.set(undefined)
} }
export async function setCam (value: boolean): Promise<void> { export async function setCam (value: boolean): Promise<void> {
@ -593,7 +617,7 @@ async function connectLK (currentPerson: Person, room: Room): Promise<void> {
]) ])
} }
async function createMeetingMinutes (room: Room): Promise<void> { async function openMeetingMinutes (room: Room): Promise<void> {
const client = getClient() const client = getClient()
const sid = await lk.getSid() const sid = await lk.getSid()
@ -601,7 +625,17 @@ async function createMeetingMinutes (room: Room): Promise<void> {
const doc = await client.findOne(love.class.MeetingMinutes, { sid }) const doc = await client.findOne(love.class.MeetingMinutes, { sid })
if (doc === undefined) { if (doc === undefined) {
const dateStr = new Date().toISOString().replace('T', ' ').slice(0, 19) const date = new Date()
.toLocaleDateString('en-GB', {
day: 'numeric',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false,
timeZone: 'UTC'
})
.replace(',', ' at')
const _id = generateId<MeetingMinutes>() const _id = generateId<MeetingMinutes>()
const newDoc: MeetingMinutes = { const newDoc: MeetingMinutes = {
_id, _id,
@ -611,24 +645,35 @@ async function createMeetingMinutes (room: Room): Promise<void> {
attachedToClass: room._class, attachedToClass: room._class,
collection: 'meetings', collection: 'meetings',
space: core.space.Workspace, space: core.space.Workspace,
title: room.name + ' ' + dateStr, title: `${room.name} ${date}`,
description: makeCollaborativeDoc(_id, 'description'), description: makeCollaborativeDoc(_id, 'description'),
status: MeetingStatus.Active,
modifiedBy: getCurrentAccount()._id, modifiedBy: getCurrentAccount()._id,
modifiedOn: Date.now() modifiedOn: Date.now()
} }
await client.addCollection( await client.addCollection(
love.class.MeetingMinutes, love.class.MeetingMinutes,
core.space.Workspace, core.space.Workspace,
room._id, room._id,
room._class, room._class,
'meetings', 'meetings',
{ sid, title: newDoc.title, description: newDoc.description }, { sid, title: newDoc.title, description: newDoc.description, status: newDoc.status },
_id _id
) )
meetingMinutesStore.set(newDoc) meetingMinutesStore.set(newDoc)
const loc = getCurrentLocation()
if (loc.path[2] === loveId) {
await openDoc(client.getHierarchy(), newDoc)
}
} else { } else {
meetingMinutesStore.set(doc) meetingMinutesStore.set(doc)
const loc = getCurrentLocation()
if (loc.path[2] === loveId) {
await openDoc(client.getHierarchy(), doc)
}
if (doc.status !== MeetingStatus.Active) {
void client.update(doc, { status: MeetingStatus.Active, meetingEnd: undefined })
}
} }
} }
} }
@ -643,7 +688,8 @@ 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) selectedRoomPlace.set(undefined)
await openMeetingMinutes(room)
} }
export const joinRequest: Ref<JoinRequest> | undefined = undefined export const joinRequest: Ref<JoinRequest> | undefined = undefined

View File

@ -1,6 +1,6 @@
import { Event } from '@hcengineering/calendar' import { Event } from '@hcengineering/calendar'
import { Person } from '@hcengineering/contact' import { Person } from '@hcengineering/contact'
import { AttachedDoc, Class, CollaborativeDoc, Doc, Mixin, Ref } from '@hcengineering/core' import { AttachedDoc, Class, CollaborativeDoc, Doc, Mixin, Ref, Timestamp } 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'
@ -105,6 +105,7 @@ export interface Room extends Doc {
description: CollaborativeDoc description: CollaborativeDoc
attachments?: number attachments?: number
meetings?: number meetings?: number
messages?: number
} }
export interface Office extends Room { export interface Office extends Room {
@ -158,12 +159,22 @@ export interface DevicesPreference extends Preference {
camEnabled: boolean camEnabled: boolean
} }
export enum MeetingStatus {
Active,
Finished
}
export interface MeetingMinutes extends AttachedDoc { export interface MeetingMinutes extends AttachedDoc {
sid: string sid: string
title: string title: string
description: CollaborativeDoc
status: MeetingStatus
meetingEnd?: Timestamp
transcription?: number transcription?: number
messages?: number messages?: number
description: CollaborativeDoc
attachments?: number attachments?: number
} }
@ -209,7 +220,10 @@ const love = plugin(loveId, {
StartMeeting: '' as IntlString, StartMeeting: '' as IntlString,
Video: '' as IntlString, Video: '' as IntlString,
NoMeetingMinutes: '' as IntlString, NoMeetingMinutes: '' as IntlString,
JoinMeeting: '' as IntlString JoinMeeting: '' as IntlString,
MeetingStart: '' as IntlString,
MeetingEnd: '' as IntlString,
Status: '' as IntlString
}, },
ids: { ids: {
MainFloor: '' as Ref<Floor>, MainFloor: '' as Ref<Floor>,

View File

@ -27,7 +27,9 @@
{#if kind === 'link'} {#if kind === 'link'}
<Button {kind} {size} {justify} {width}> <Button {kind} {size} {justify} {width}>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
{#if value != null}
<TimeSince {value} /> <TimeSince {value} />
{/if}
</svelte:fragment> </svelte:fragment>
</Button> </Button>
{:else} {:else}

View File

@ -43,7 +43,6 @@ import {
ListItemPresenter, ListItemPresenter,
ObjectEditor, ObjectEditor,
ObjectEditorFooter, ObjectEditorFooter,
ObjectPanelFooter,
ObjectEditorHeader, ObjectEditorHeader,
ObjectFactory, ObjectFactory,
ObjectIcon, ObjectIcon,

View File

@ -13,29 +13,30 @@
// limitations under the License. // limitations under the License.
// //
import contact, { Employee, Person, PersonAccount, formatName, getName } from '@hcengineering/contact' import contact, { Employee, formatName, getName, Person, PersonAccount } from '@hcengineering/contact'
import core, { import core, {
Account, Account,
concatLink,
Doc,
Ref, Ref,
Tx, Tx,
TxCUD,
TxCreateDoc, TxCreateDoc,
TxCUD,
TxMixin, TxMixin,
TxProcessor, TxProcessor,
TxUpdateDoc, TxUpdateDoc,
UserStatus, UserStatus
Doc,
concatLink
} from '@hcengineering/core' } from '@hcengineering/core'
import love, { import love, {
Invite, Invite,
isOffice,
JoinRequest, JoinRequest,
loveId,
MeetingMinutes, MeetingMinutes,
MeetingStatus,
ParticipantInfo, ParticipantInfo,
RequestStatus, RequestStatus,
RoomAccess, RoomAccess
isOffice,
loveId
} from '@hcengineering/love' } from '@hcengineering/love'
import notification from '@hcengineering/notification' import notification from '@hcengineering/notification'
import { getMetadata, translate } from '@hcengineering/platform' import { getMetadata, translate } from '@hcengineering/platform'
@ -240,6 +241,40 @@ async function setDefaultRoomAccess (info: ParticipantInfo, control: TriggerCont
return res return res
} }
async function finishMeetingMinutes (
info: ParticipantInfo,
control: TriggerControl,
tx: TxCUD<ParticipantInfo>
): Promise<Tx[]> {
const res: Tx[] = []
const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {})
const roomInfo = roomInfos.find((ri) => ri.persons.includes(info.person))
if (roomInfo === undefined) {
return res
}
const currentPersons = roomInfo.persons.filter((p) => p !== info.person)
if (currentPersons.length === 0) {
const meetingMinutes = await control.findAll(control.ctx, love.class.MeetingMinutes, {
attachedTo: roomInfo.room,
status: MeetingStatus.Active
})
for (const meeting of meetingMinutes) {
res.push(
control.txFactory.createTxUpdateDoc(meeting._class, meeting.space, meeting._id, {
status: MeetingStatus.Finished,
meetingEnd: tx.modifiedOn
})
)
}
}
return res
}
export async function OnParticipantInfo (txes: Tx[], control: TriggerControl): Promise<Tx[]> { export async function OnParticipantInfo (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
const result: Tx[] = [] const result: Tx[] = []
for (const tx of txes) { for (const tx of txes) {
@ -254,6 +289,7 @@ export async function OnParticipantInfo (txes: Tx[], control: TriggerControl): P
continue continue
} }
result.push(...(await setDefaultRoomAccess(removedInfo, control))) result.push(...(await setDefaultRoomAccess(removedInfo, control)))
result.push(...(await finishMeetingMinutes(removedInfo, control, actualTx)))
continue continue
} }
if (actualTx._class === core.class.TxUpdateDoc) { if (actualTx._class === core.class.TxUpdateDoc) {
@ -269,6 +305,7 @@ export async function OnParticipantInfo (txes: Tx[], control: TriggerControl): P
} }
result.push(...(await rejectJoinRequests(info, control))) result.push(...(await rejectJoinRequests(info, control)))
result.push(...(await setDefaultRoomAccess(info, control))) result.push(...(await setDefaultRoomAccess(info, control)))
result.push(...(await finishMeetingMinutes(info, control, actualTx)))
result.push(...(await roomJoinHandler(info, control))) result.push(...(await roomJoinHandler(info, control)))
} }
} }

View File

@ -949,7 +949,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(activity.class.ActivityExtension, core.space.Model, { builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: github.class.GithubPullRequest, ofClass: github.class.GithubPullRequest,
components: { components: {
input: chunter.component.ChatMessageInput input: { component: chunter.component.ChatMessageInput }
} }
}) })