mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-23 08:48:01 +00:00
Scheduled meetings (#6206)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
31f6fcbcf8
commit
88e2df5885
@ -46,6 +46,7 @@
|
|||||||
"@hcengineering/love": "^0.6.0",
|
"@hcengineering/love": "^0.6.0",
|
||||||
"@hcengineering/love-resources": "^0.6.0",
|
"@hcengineering/love-resources": "^0.6.0",
|
||||||
"@hcengineering/notification": "^0.6.23",
|
"@hcengineering/notification": "^0.6.23",
|
||||||
"@hcengineering/model-notification": "^0.6.0"
|
"@hcengineering/model-notification": "^0.6.0",
|
||||||
|
"@hcengineering/model-calendar": "^0.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,21 +15,13 @@
|
|||||||
|
|
||||||
import contact, { type Employee, type Person } from '@hcengineering/contact'
|
import contact, { type Employee, type Person } from '@hcengineering/contact'
|
||||||
import { AccountRole, DOMAIN_TRANSIENT, IndexKind, type Domain, type Ref } from '@hcengineering/core'
|
import { AccountRole, DOMAIN_TRANSIENT, IndexKind, type Domain, type Ref } from '@hcengineering/core'
|
||||||
import { Index, Model, Prop, TypeRef, type Builder } from '@hcengineering/model'
|
|
||||||
import core, { TDoc } from '@hcengineering/model-core'
|
|
||||||
import preference, { TPreference } from '@hcengineering/model-preference'
|
|
||||||
import presentation from '@hcengineering/model-presentation'
|
|
||||||
import view, { createAction } from '@hcengineering/model-view'
|
|
||||||
import notification from '@hcengineering/notification'
|
|
||||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
|
||||||
import setting from '@hcengineering/setting'
|
|
||||||
import workbench from '@hcengineering/workbench'
|
|
||||||
import {
|
import {
|
||||||
loveId,
|
loveId,
|
||||||
type DevicesPreference,
|
type DevicesPreference,
|
||||||
type Floor,
|
type Floor,
|
||||||
type Invite,
|
type Invite,
|
||||||
type JoinRequest,
|
type JoinRequest,
|
||||||
|
type Meeting,
|
||||||
type Office,
|
type Office,
|
||||||
type ParticipantInfo,
|
type ParticipantInfo,
|
||||||
type RequestStatus,
|
type RequestStatus,
|
||||||
@ -38,6 +30,16 @@ import {
|
|||||||
type RoomInfo,
|
type RoomInfo,
|
||||||
type RoomType
|
type RoomType
|
||||||
} from '@hcengineering/love'
|
} from '@hcengineering/love'
|
||||||
|
import { Index, Mixin, Model, Prop, TypeRef, type Builder } from '@hcengineering/model'
|
||||||
|
import calendar, { TEvent } from '@hcengineering/model-calendar'
|
||||||
|
import core, { TDoc } from '@hcengineering/model-core'
|
||||||
|
import preference, { TPreference } from '@hcengineering/model-preference'
|
||||||
|
import presentation from '@hcengineering/model-presentation'
|
||||||
|
import view, { createAction } from '@hcengineering/model-view'
|
||||||
|
import notification from '@hcengineering/notification'
|
||||||
|
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
|
import setting from '@hcengineering/setting'
|
||||||
|
import workbench from '@hcengineering/workbench'
|
||||||
import love from './plugin'
|
import love from './plugin'
|
||||||
|
|
||||||
export { loveId } from '@hcengineering/love'
|
export { loveId } from '@hcengineering/love'
|
||||||
@ -127,10 +129,25 @@ export class TRoomInfo extends TDoc implements RoomInfo {
|
|||||||
isOffice!: boolean
|
isOffice!: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mixin(love.mixin.Meeting, calendar.class.Event)
|
||||||
|
export class TMeeting extends TEvent implements Meeting {
|
||||||
|
room!: Ref<Room>
|
||||||
|
}
|
||||||
|
|
||||||
export default love
|
export default love
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(TRoom, TFloor, TOffice, TParticipantInfo, TJoinRequest, TDevicesPreference, TRoomInfo, TInvite)
|
builder.createModel(
|
||||||
|
TRoom,
|
||||||
|
TFloor,
|
||||||
|
TOffice,
|
||||||
|
TParticipantInfo,
|
||||||
|
TJoinRequest,
|
||||||
|
TDevicesPreference,
|
||||||
|
TRoomInfo,
|
||||||
|
TInvite,
|
||||||
|
TMeeting
|
||||||
|
)
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
workbench.class.Application,
|
workbench.class.Application,
|
||||||
@ -151,6 +168,19 @@ export function createModel (builder: Builder): void {
|
|||||||
component: love.component.WorkbenchExtension
|
component: love.component.WorkbenchExtension
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.createDoc(presentation.class.DocCreateExtension, core.space.Model, {
|
||||||
|
ofClass: calendar.class.Event,
|
||||||
|
apply: love.function.CreateMeeting,
|
||||||
|
components: {
|
||||||
|
body: love.component.MeetingData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
|
||||||
|
extension: calendar.extensions.EditEventExtensions,
|
||||||
|
component: love.component.EditMeetingData
|
||||||
|
})
|
||||||
|
|
||||||
builder.createDoc(
|
builder.createDoc(
|
||||||
setting.class.SettingsCategory,
|
setting.class.SettingsCategory,
|
||||||
core.space.Model,
|
core.space.Model,
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
export let manager: DocCreateExtensionManager
|
export let manager: DocCreateExtensionManager
|
||||||
export let kind: CreateExtensionKind
|
export let kind: CreateExtensionKind
|
||||||
export let props: Record<string, any> = {}
|
export let props: Record<string, any> = {}
|
||||||
export let space: Space | undefined
|
export let space: Space | undefined = undefined
|
||||||
|
|
||||||
$: extensions = manager.extensions
|
$: extensions = manager.extensions
|
||||||
|
|
||||||
|
@ -13,10 +13,15 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Calendar, RecurringRule, Visibility, generateEventId } from '@hcengineering/calendar'
|
import { Calendar, Event, ReccuringEvent, RecurringRule, Visibility, generateEventId } from '@hcengineering/calendar'
|
||||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||||
import { Class, Doc, Markup, Ref, getCurrentAccount } from '@hcengineering/core'
|
import core, { Class, Doc, Markup, Ref, Space, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||||
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
import presentation, {
|
||||||
|
createQuery,
|
||||||
|
DocCreateExtComponent,
|
||||||
|
DocCreateExtensionManager,
|
||||||
|
getClient
|
||||||
|
} from '@hcengineering/presentation'
|
||||||
import { EmptyMarkup } from '@hcengineering/text'
|
import { EmptyMarkup } from '@hcengineering/text'
|
||||||
import { StyledTextBox } from '@hcengineering/text-editor-resources'
|
import { StyledTextBox } from '@hcengineering/text-editor-resources'
|
||||||
import {
|
import {
|
||||||
@ -43,16 +48,21 @@
|
|||||||
import ReccurancePopup from './ReccurancePopup.svelte'
|
import ReccurancePopup from './ReccurancePopup.svelte'
|
||||||
import VisibilityEditor from './VisibilityEditor.svelte'
|
import VisibilityEditor from './VisibilityEditor.svelte'
|
||||||
|
|
||||||
|
const currentUser = getCurrentAccount() as PersonAccount
|
||||||
|
|
||||||
export let attachedTo: Ref<Doc> = calendar.ids.NoAttached
|
export let attachedTo: Ref<Doc> = calendar.ids.NoAttached
|
||||||
export let attachedToClass: Ref<Class<Doc>> = calendar.class.Event
|
export let attachedToClass: Ref<Class<Doc>> = calendar.class.Event
|
||||||
export let title: string = ''
|
export let title: string = ''
|
||||||
export let date: Date | undefined = undefined
|
export let date: Date | undefined = undefined
|
||||||
export let withTime = false
|
export let withTime = false
|
||||||
|
export let participants: Ref<Person>[] = [currentUser.person]
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const defaultDuration = 60 * 60 * 1000
|
const defaultDuration = 60 * 60 * 1000
|
||||||
const allDayDuration = 24 * 60 * 60 * 1000 - 1
|
const allDayDuration = 24 * 60 * 60 * 1000 - 1
|
||||||
|
|
||||||
|
const docCreateManager = DocCreateExtensionManager.create(calendar.class.Event)
|
||||||
|
|
||||||
let startDate =
|
let startDate =
|
||||||
date === undefined ? now.getTime() : withTime ? date.getTime() : date.setHours(now.getHours(), now.getMinutes())
|
date === undefined ? now.getTime() : withTime ? date.getTime() : date.setHours(now.getHours(), now.getMinutes())
|
||||||
const duration = defaultDuration
|
const duration = defaultDuration
|
||||||
@ -75,10 +85,14 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const spaceQ = createQuery()
|
||||||
|
let space: Space | undefined = undefined
|
||||||
|
spaceQ.query(core.class.Space, { _id: calendar.space.Calendar }, (res) => {
|
||||||
|
space = res[0]
|
||||||
|
})
|
||||||
|
|
||||||
let rules: RecurringRule[] = []
|
let rules: RecurringRule[] = []
|
||||||
|
|
||||||
const currentUser = getCurrentAccount() as PersonAccount
|
|
||||||
let participants: Ref<Person>[] = [currentUser.person]
|
|
||||||
let externalParticipants: string[] = []
|
let externalParticipants: string[] = []
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
@ -93,6 +107,7 @@
|
|||||||
if (startDate != null) date = startDate
|
if (startDate != null) date = startDate
|
||||||
if (date === undefined) return
|
if (date === undefined) return
|
||||||
if (title === '') return
|
if (title === '') return
|
||||||
|
const _id = generateId<Event>()
|
||||||
if (rules.length > 0) {
|
if (rules.length > 0) {
|
||||||
await client.addCollection(
|
await client.addCollection(
|
||||||
calendar.class.ReccuringEvent,
|
calendar.class.ReccuringEvent,
|
||||||
@ -119,25 +134,37 @@
|
|||||||
access: 'owner',
|
access: 'owner',
|
||||||
originalStartTime: allDay ? saveUTC(date) : date,
|
originalStartTime: allDay ? saveUTC(date) : date,
|
||||||
timeZone
|
timeZone
|
||||||
}
|
},
|
||||||
|
_id as Ref<ReccuringEvent>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
await client.addCollection(calendar.class.Event, calendar.space.Calendar, attachedTo, attachedToClass, 'events', {
|
await client.addCollection(
|
||||||
calendar: _calendar,
|
calendar.class.Event,
|
||||||
eventId: generateEventId(),
|
calendar.space.Calendar,
|
||||||
date: allDay ? saveUTC(date) : date,
|
attachedTo,
|
||||||
dueDate: allDay ? saveUTC(dueDate) : dueDate,
|
attachedToClass,
|
||||||
externalParticipants,
|
'events',
|
||||||
description,
|
{
|
||||||
visibility,
|
calendar: _calendar,
|
||||||
participants,
|
eventId: generateEventId(),
|
||||||
reminders,
|
date: allDay ? saveUTC(date) : date,
|
||||||
title,
|
dueDate: allDay ? saveUTC(dueDate) : dueDate,
|
||||||
location,
|
externalParticipants,
|
||||||
allDay,
|
description,
|
||||||
timeZone,
|
visibility,
|
||||||
access: 'owner'
|
participants,
|
||||||
})
|
reminders,
|
||||||
|
title,
|
||||||
|
location,
|
||||||
|
allDay,
|
||||||
|
timeZone,
|
||||||
|
access: 'owner'
|
||||||
|
},
|
||||||
|
_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (space !== undefined) {
|
||||||
|
await docCreateManager.commit(client, _id, space, {}, 'post')
|
||||||
}
|
}
|
||||||
dispatch('close')
|
dispatch('close')
|
||||||
}
|
}
|
||||||
@ -204,6 +231,9 @@
|
|||||||
<LocationEditor focusIndex={10010} bind:value={location} />
|
<LocationEditor focusIndex={10010} bind:value={location} />
|
||||||
<EventParticipants focusIndex={10011} bind:participants bind:externalParticipants />
|
<EventParticipants focusIndex={10011} bind:participants bind:externalParticipants />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<DocCreateExtComponent manager={docCreateManager} kind={'body'} />
|
||||||
|
</div>
|
||||||
<div class="block row gap-1-5">
|
<div class="block row gap-1-5">
|
||||||
<div class="top-icon">
|
<div class="top-icon">
|
||||||
<Icon icon={calendar.icon.Description} size={'small'} />
|
<Icon icon={calendar.icon.Description} size={'small'} />
|
||||||
@ -222,7 +252,7 @@
|
|||||||
<CalendarSelector bind:value={_calendar} focusIndex={10101} />
|
<CalendarSelector bind:value={_calendar} focusIndex={10101} />
|
||||||
<div class="flex-row-center flex-gap-1">
|
<div class="flex-row-center flex-gap-1">
|
||||||
<Icon icon={calendar.icon.Hidden} size={'small'} />
|
<Icon icon={calendar.icon.Hidden} size={'small'} />
|
||||||
<VisibilityEditor bind:value={visibility} kind={'tertiary'} focusIndex={10102} withoutIcon />
|
<VisibilityEditor bind:value={visibility} kind={'tertiary'} size={'small'} focusIndex={10102} withoutIcon />
|
||||||
</div>
|
</div>
|
||||||
<EventReminders bind:reminders focusIndex={10103} />
|
<EventReminders bind:reminders focusIndex={10103} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import { Event, ReccuringEvent, ReccuringInstance, RecurringRule } from '@hcengineering/calendar'
|
import { Event, ReccuringEvent, ReccuringInstance, RecurringRule } from '@hcengineering/calendar'
|
||||||
import { Person } from '@hcengineering/contact'
|
import { Person } from '@hcengineering/contact'
|
||||||
import { DocumentUpdate, Ref } from '@hcengineering/core'
|
import { DocumentUpdate, Ref } from '@hcengineering/core'
|
||||||
import presentation, { getClient } from '@hcengineering/presentation'
|
import presentation, { ComponentExtensions, getClient } from '@hcengineering/presentation'
|
||||||
import { StyledTextBox } from '@hcengineering/text-editor-resources'
|
import { StyledTextBox } from '@hcengineering/text-editor-resources'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -199,6 +199,7 @@
|
|||||||
<div class="block rightCropPadding">
|
<div class="block rightCropPadding">
|
||||||
<LocationEditor bind:value={location} focusIndex={10005} />
|
<LocationEditor bind:value={location} focusIndex={10005} />
|
||||||
<EventParticipants bind:participants bind:externalParticipants disabled={readOnly} focusIndex={10006} />
|
<EventParticipants bind:participants bind:externalParticipants disabled={readOnly} focusIndex={10006} />
|
||||||
|
<ComponentExtensions extension={calendar.extensions.EditEventExtensions} props={{ readOnly, value: object }} />
|
||||||
</div>
|
</div>
|
||||||
<div class="block row gap-1-5">
|
<div class="block row gap-1-5">
|
||||||
<div class="top-icon">
|
<div class="top-icon">
|
||||||
|
@ -49,6 +49,8 @@
|
|||||||
<Button
|
<Button
|
||||||
label={reminders.length > 0 ? calendar.string.AddReminder : calendar.string.Reminders}
|
label={reminders.length > 0 ? calendar.string.AddReminder : calendar.string.Reminders}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
padding={'0 .5rem'}
|
||||||
|
justify={'left'}
|
||||||
kind={'ghost'}
|
kind={'ghost'}
|
||||||
{focusIndex}
|
{focusIndex}
|
||||||
on:click={(e) => {
|
on:click={(e) => {
|
||||||
|
@ -17,7 +17,7 @@ import { NotificationType } from '@hcengineering/notification'
|
|||||||
import type { Asset, IntlString, Metadata, Plugin } from '@hcengineering/platform'
|
import type { Asset, IntlString, Metadata, Plugin } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
import type { Handler, IntegrationType } from '@hcengineering/setting'
|
import type { Handler, IntegrationType } from '@hcengineering/setting'
|
||||||
import { AnyComponent } from '@hcengineering/ui'
|
import { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -212,6 +212,9 @@ const calendarPlugin = plugin(calendarId, {
|
|||||||
metadata: {
|
metadata: {
|
||||||
CalendarServiceURL: '' as Metadata<string>
|
CalendarServiceURL: '' as Metadata<string>
|
||||||
},
|
},
|
||||||
|
extensions: {
|
||||||
|
EditEventExtensions: '' as ComponentExtensionId
|
||||||
|
},
|
||||||
ids: {
|
ids: {
|
||||||
ReminderNotification: '' as Ref<NotificationType>,
|
ReminderNotification: '' as Ref<NotificationType>,
|
||||||
NoAttached: '' as Ref<Event>
|
NoAttached: '' as Ref<Event>
|
||||||
|
@ -521,7 +521,7 @@ export async function getInviteLinkId (
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
|
||||||
|
|
||||||
const exp = expHours * 1000 * 60 * 60
|
const exp = expHours < 0 ? -1 : expHours * 1000 * 60 * 60
|
||||||
|
|
||||||
if (accountsUrl === undefined) {
|
if (accountsUrl === undefined) {
|
||||||
throw new Error('accounts url not specified')
|
throw new Error('accounts url not specified')
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
"FullscreenMode": "Full-screen mode",
|
"FullscreenMode": "Full-screen mode",
|
||||||
"ExitingFullscreenMode": "Exiting fullscreen mode",
|
"ExitingFullscreenMode": "Exiting fullscreen mode",
|
||||||
"Select": "Select",
|
"Select": "Select",
|
||||||
"ChooseShare": "Choose what to share"
|
"ChooseShare": "Choose what to share",
|
||||||
|
"CreateMeeting": "Create meeting"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
"FullscreenMode": "Modo de pantalla completa",
|
"FullscreenMode": "Modo de pantalla completa",
|
||||||
"ExitingFullscreenMode": "Salir del modo de pantalla completa",
|
"ExitingFullscreenMode": "Salir del modo de pantalla completa",
|
||||||
"Select": "Seleccionar",
|
"Select": "Seleccionar",
|
||||||
"ChooseShare": "Elija qué compartir"
|
"ChooseShare": "Elija qué compartir",
|
||||||
|
"CreateMeeting": "Crear reunión"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
"FullscreenMode": "Mode plein écran",
|
"FullscreenMode": "Mode plein écran",
|
||||||
"ExitingFullscreenMode": "Quitter le mode plein écran",
|
"ExitingFullscreenMode": "Quitter le mode plein écran",
|
||||||
"Select": "Sélectionner",
|
"Select": "Sélectionner",
|
||||||
"ChooseShare": "Choisissez ce que vous voulez partager"
|
"ChooseShare": "Choisissez ce que vous voulez partager",
|
||||||
|
"CreateMeeting": "Créer une réunion"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -59,6 +59,7 @@
|
|||||||
"FullscreenMode": "Modo de ecrã inteiro",
|
"FullscreenMode": "Modo de ecrã inteiro",
|
||||||
"ExitingFullscreenMode": "Saindo do modo de tela cheia",
|
"ExitingFullscreenMode": "Saindo do modo de tela cheia",
|
||||||
"Select": "Seleccione",
|
"Select": "Seleccione",
|
||||||
"ChooseShare": "Escolha o que partilhar"
|
"ChooseShare": "Escolha o que partilhar",
|
||||||
|
"CreateMeeting": "Criar reunião"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
"FullscreenMode": "Полноэкранный режим",
|
"FullscreenMode": "Полноэкранный режим",
|
||||||
"ExitingFullscreenMode": "Выход из полноэкранного режима",
|
"ExitingFullscreenMode": "Выход из полноэкранного режима",
|
||||||
"Select": "Выбрать",
|
"Select": "Выбрать",
|
||||||
"ChooseShare": "Выберите, чем вы хотите поделиться"
|
"ChooseShare": "Выберите, чем вы хотите поделиться",
|
||||||
|
"CreateMeeting": "Создать встречу"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
"FullscreenMode": "全屏模式",
|
"FullscreenMode": "全屏模式",
|
||||||
"ExitingFullscreenMode": "退出全屏模式",
|
"ExitingFullscreenMode": "退出全屏模式",
|
||||||
"Select": "选择",
|
"Select": "选择",
|
||||||
"ChooseShare": "选择共享内容"
|
"ChooseShare": "选择共享内容",
|
||||||
|
"CreateMeeting": "创建会议"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
"@hcengineering/platform": "^0.6.11",
|
"@hcengineering/platform": "^0.6.11",
|
||||||
"svelte": "^4.2.12",
|
"svelte": "^4.2.12",
|
||||||
"@hcengineering/ui": "^0.6.15",
|
"@hcengineering/ui": "^0.6.15",
|
||||||
|
"@hcengineering/calendar": "^0.6.24",
|
||||||
"@hcengineering/contact": "^0.6.24",
|
"@hcengineering/contact": "^0.6.24",
|
||||||
"@hcengineering/contact-resources": "^0.6.0",
|
"@hcengineering/contact-resources": "^0.6.0",
|
||||||
"@hcengineering/view-resources": "^0.6.0",
|
"@hcengineering/view-resources": "^0.6.0",
|
||||||
|
@ -122,10 +122,11 @@
|
|||||||
if (roomInfo !== undefined) {
|
if (roomInfo !== undefined) {
|
||||||
const navigateUrl = getCurrentLocation()
|
const navigateUrl = getCurrentLocation()
|
||||||
navigateUrl.query = {
|
navigateUrl.query = {
|
||||||
meetId: roomInfo._id
|
sessionId: roomInfo._id
|
||||||
}
|
}
|
||||||
|
|
||||||
const func = await getResource(login.function.GetInviteLink)
|
const func = await getResource(login.function.GetInviteLink)
|
||||||
return await func(24 * 30, '', -1, AccountRole.Guest, JSON.stringify(navigateUrl))
|
return await func(24, '', -1, AccountRole.Guest, encodeURIComponent(JSON.stringify(navigateUrl)))
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
37
plugins/love-resources/src/components/EditMeetingData.svelte
Normal file
37
plugins/love-resources/src/components/EditMeetingData.svelte
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Event } from '@hcengineering/calendar'
|
||||||
|
import love from '../plugin'
|
||||||
|
import RoomSelector from './RoomSelector.svelte'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { Ref } from '@hcengineering/core'
|
||||||
|
import { Room } from '@hcengineering/love'
|
||||||
|
|
||||||
|
export let value: Event
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
$: isMeeting = client.getHierarchy().hasMixin(value, love.mixin.Meeting)
|
||||||
|
$: meeting = isMeeting ? client.getHierarchy().as(value, love.mixin.Meeting) : null
|
||||||
|
|
||||||
|
async function changeRoom (val: Ref<Room>): Promise<void> {
|
||||||
|
await client.updateMixin(value._id, value._class, value.space, love.mixin.Meeting, { room: val })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if isMeeting && meeting}
|
||||||
|
<RoomSelector value={meeting?.room} on:change={(ev) => changeRoom(ev.detail)} />
|
||||||
|
{/if}
|
@ -16,12 +16,12 @@
|
|||||||
import { Contact, Person } from '@hcengineering/contact'
|
import { Contact, Person } from '@hcengineering/contact'
|
||||||
import { personByIdStore } from '@hcengineering/contact-resources'
|
import { personByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { Ref } from '@hcengineering/core'
|
import { Ref } from '@hcengineering/core'
|
||||||
import love, { Floor as FloorType, Office, Room, RoomInfo, isOffice } from '@hcengineering/love'
|
import love, { Floor as FloorType, Meeting, Office, Room, RoomInfo, isOffice } from '@hcengineering/love'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { deviceOptionsStore as deviceInfo, getCurrentLocation, navigate } from '@hcengineering/ui'
|
import { deviceOptionsStore as deviceInfo, getCurrentLocation, navigate } from '@hcengineering/ui'
|
||||||
import { onMount, onDestroy } from 'svelte'
|
import { onMount, onDestroy } from 'svelte'
|
||||||
import { activeFloor, floors, infos, invites, myInfo, myRequests, rooms } from '../stores'
|
import { activeFloor, floors, infos, invites, myInfo, myRequests, rooms } from '../stores'
|
||||||
import { tryConnect } from '../utils'
|
import { connectToMeeting, tryConnect } from '../utils'
|
||||||
import Floor from './Floor.svelte'
|
import Floor from './Floor.svelte'
|
||||||
import FloorConfigure from './FloorConfigure.svelte'
|
import FloorConfigure from './FloorConfigure.svelte'
|
||||||
import Floors from './Floors.svelte'
|
import Floors from './Floors.svelte'
|
||||||
@ -42,25 +42,31 @@
|
|||||||
$: $deviceInfo.replacedPanel = replacedPanel
|
$: $deviceInfo.replacedPanel = replacedPanel
|
||||||
onDestroy(() => ($deviceInfo.replacedPanel = undefined))
|
onDestroy(() => ($deviceInfo.replacedPanel = undefined))
|
||||||
|
|
||||||
|
async function connectToSession (sessionId: string): Promise<void> {
|
||||||
|
const client = getClient()
|
||||||
|
const info = await client.findOne(love.class.RoomInfo, { _id: sessionId as Ref<RoomInfo> })
|
||||||
|
if (info === undefined) return
|
||||||
|
const room = $rooms.find((p) => p._id === info.room)
|
||||||
|
if (room === undefined) return
|
||||||
|
tryConnect(
|
||||||
|
$personByIdStore,
|
||||||
|
$myInfo,
|
||||||
|
room,
|
||||||
|
$infos.filter((p) => p.room === room._id),
|
||||||
|
$myRequests,
|
||||||
|
$invites
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const loc = getCurrentLocation()
|
const loc = getCurrentLocation()
|
||||||
const { meetId, ...query } = loc.query ?? {}
|
const { sessionId, meetId, ...query } = loc.query ?? {}
|
||||||
if (meetId != null) {
|
loc.query = Object.keys(query).length === 0 ? undefined : query
|
||||||
loc.query = Object.keys(query).length === 0 ? undefined : query
|
navigate(loc, true)
|
||||||
navigate(loc, true)
|
if (sessionId != null) {
|
||||||
const client = getClient()
|
await connectToSession(sessionId)
|
||||||
const info = await client.findOne(love.class.RoomInfo, { _id: meetId as Ref<RoomInfo> })
|
} else if (meetId != null) {
|
||||||
if (info === undefined) return
|
await connectToMeeting($personByIdStore, $myInfo, $infos, $myRequests, $invites, $rooms, meetId)
|
||||||
const room = $rooms.find((p) => p._id === info.room)
|
|
||||||
if (room === undefined) return
|
|
||||||
tryConnect(
|
|
||||||
$personByIdStore,
|
|
||||||
$myInfo,
|
|
||||||
room,
|
|
||||||
$infos.filter((p) => p.room === room._id),
|
|
||||||
$myRequests,
|
|
||||||
$invites
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
56
plugins/love-resources/src/components/MeetingData.svelte
Normal file
56
plugins/love-resources/src/components/MeetingData.svelte
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Ref } from '@hcengineering/core'
|
||||||
|
import { Room } from '@hcengineering/love'
|
||||||
|
import { Button, CheckBox } from '@hcengineering/ui'
|
||||||
|
import { Writable } from 'svelte/store'
|
||||||
|
import love from '../plugin'
|
||||||
|
import RoomSelector from './RoomSelector.svelte'
|
||||||
|
|
||||||
|
export let state: Writable<Record<string, any>>
|
||||||
|
|
||||||
|
function changeRoom (val: Ref<Room>) {
|
||||||
|
$state.room = val
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeIsMeeting () {
|
||||||
|
$state.isMeeting = isMeeting
|
||||||
|
}
|
||||||
|
|
||||||
|
let isMeeting = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex-row-center gap-1-5 mt-1">
|
||||||
|
<CheckBox bind:checked={isMeeting} kind={'primary'} on:value={changeIsMeeting} />
|
||||||
|
<Button
|
||||||
|
label={love.string.CreateMeeting}
|
||||||
|
kind={'ghost'}
|
||||||
|
padding={'0 .5rem'}
|
||||||
|
justify={'left'}
|
||||||
|
on:click={() => {
|
||||||
|
isMeeting = !isMeeting
|
||||||
|
changeIsMeeting()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if isMeeting}
|
||||||
|
<RoomSelector
|
||||||
|
value={$state.room}
|
||||||
|
on:change={(ev) => {
|
||||||
|
changeRoom(ev.detail)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
64
plugins/love-resources/src/components/RoomSelector.svelte
Normal file
64
plugins/love-resources/src/components/RoomSelector.svelte
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Ref } from '@hcengineering/core'
|
||||||
|
import love, { isOffice, Room } from '@hcengineering/love'
|
||||||
|
import { Dropdown, Icon } from '@hcengineering/ui'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { rooms } from '../stores'
|
||||||
|
|
||||||
|
export let value: Ref<Room> | undefined
|
||||||
|
export let disabled: boolean = false
|
||||||
|
export let focusIndex = -1
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: items = $rooms
|
||||||
|
.filter((p) => !isOffice(p))
|
||||||
|
.map((p) => {
|
||||||
|
return {
|
||||||
|
_id: p._id,
|
||||||
|
label: p.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$: selected = value !== undefined ? items.find((p) => p._id === value) : undefined
|
||||||
|
|
||||||
|
function change (id: Ref<Room>) {
|
||||||
|
if (value !== id) {
|
||||||
|
dispatch('change', id)
|
||||||
|
value = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if items.length > 0}
|
||||||
|
<div class="flex-row-center flex-gap-1">
|
||||||
|
<Icon icon={love.icon.Mic} size={'small'} />
|
||||||
|
<Dropdown
|
||||||
|
kind={'ghost'}
|
||||||
|
size={'medium'}
|
||||||
|
placeholder={love.string.Room}
|
||||||
|
{items}
|
||||||
|
withSearch={false}
|
||||||
|
{selected}
|
||||||
|
{disabled}
|
||||||
|
{focusIndex}
|
||||||
|
on:selected={(e) => {
|
||||||
|
change(e.detail._id)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
@ -1,10 +1,12 @@
|
|||||||
import { type Resources } from '@hcengineering/platform'
|
import { type Resources } from '@hcengineering/platform'
|
||||||
import ControlExt from './components/ControlExt.svelte'
|
import ControlExt from './components/ControlExt.svelte'
|
||||||
|
import EditMeetingData from './components/EditMeetingData.svelte'
|
||||||
import Main from './components/Main.svelte'
|
import Main from './components/Main.svelte'
|
||||||
|
import MeetingData from './components/MeetingData.svelte'
|
||||||
|
import SelectScreenSourcePopup from './components/SelectScreenSourcePopup.svelte'
|
||||||
import Settings from './components/Settings.svelte'
|
import Settings from './components/Settings.svelte'
|
||||||
import WorkbenchExtension from './components/WorkbenchExtension.svelte'
|
import WorkbenchExtension from './components/WorkbenchExtension.svelte'
|
||||||
import SelectScreenSourcePopup from './components/SelectScreenSourcePopup.svelte'
|
import { createMeeting, toggleMic, toggleVideo } from './utils'
|
||||||
import { toggleMic, toggleVideo } from './utils'
|
|
||||||
|
|
||||||
export { setCustomCreateScreenTracks } from './utils'
|
export { setCustomCreateScreenTracks } from './utils'
|
||||||
|
|
||||||
@ -14,7 +16,12 @@ export default async (): Promise<Resources> => ({
|
|||||||
ControlExt,
|
ControlExt,
|
||||||
Settings,
|
Settings,
|
||||||
WorkbenchExtension,
|
WorkbenchExtension,
|
||||||
SelectScreenSourcePopup
|
SelectScreenSourcePopup,
|
||||||
|
MeetingData,
|
||||||
|
EditMeetingData
|
||||||
|
},
|
||||||
|
function: {
|
||||||
|
CreateMeeting: createMeeting
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
ToggleMic: toggleMic,
|
ToggleMic: toggleMic,
|
||||||
|
@ -13,15 +13,22 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { mergeIds, type IntlString } from '@hcengineering/platform'
|
|
||||||
import { type AnyComponent } from '@hcengineering/ui'
|
|
||||||
import love, { loveId } from '@hcengineering/love'
|
import love, { loveId } from '@hcengineering/love'
|
||||||
|
import { mergeIds, type IntlString, type Resource } from '@hcengineering/platform'
|
||||||
|
import { type DocCreateFunction } from '@hcengineering/presentation'
|
||||||
|
import { type AnyComponent } from '@hcengineering/ui'
|
||||||
|
|
||||||
export default mergeIds(loveId, love, {
|
export default mergeIds(loveId, love, {
|
||||||
component: {
|
component: {
|
||||||
ControlExt: '' as AnyComponent
|
ControlExt: '' as AnyComponent,
|
||||||
|
MeetingData: '' as AnyComponent,
|
||||||
|
EditMeetingData: '' as AnyComponent
|
||||||
|
},
|
||||||
|
function: {
|
||||||
|
CreateMeeting: '' as Resource<DocCreateFunction>
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
|
CreateMeeting: '' as IntlString,
|
||||||
LeaveRoom: '' as IntlString,
|
LeaveRoom: '' as IntlString,
|
||||||
LeaveRoomConfirmation: '' as IntlString,
|
LeaveRoomConfirmation: '' as IntlString,
|
||||||
Mute: '' as IntlString,
|
Mute: '' as IntlString,
|
||||||
|
@ -1,6 +1,17 @@
|
|||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
|
import calendar, { type Event } from '@hcengineering/calendar'
|
||||||
import contact, { getName, type Person, type PersonAccount } from '@hcengineering/contact'
|
import contact, { getName, type Person, type PersonAccount } from '@hcengineering/contact'
|
||||||
import core, { concatLink, getCurrentAccount, type IdMap, type Ref, type Space } from '@hcengineering/core'
|
import core, {
|
||||||
|
AccountRole,
|
||||||
|
concatLink,
|
||||||
|
getCurrentAccount,
|
||||||
|
type Data,
|
||||||
|
type IdMap,
|
||||||
|
type Ref,
|
||||||
|
type Space,
|
||||||
|
type TxOperations
|
||||||
|
} from '@hcengineering/core'
|
||||||
|
import login from '@hcengineering/login'
|
||||||
import {
|
import {
|
||||||
RequestStatus,
|
RequestStatus,
|
||||||
RoomAccess,
|
RoomAccess,
|
||||||
@ -9,12 +20,13 @@ import {
|
|||||||
loveId,
|
loveId,
|
||||||
type Invite,
|
type Invite,
|
||||||
type JoinRequest,
|
type JoinRequest,
|
||||||
|
type Meeting,
|
||||||
type Office,
|
type Office,
|
||||||
type ParticipantInfo,
|
type ParticipantInfo,
|
||||||
type Room
|
type Room
|
||||||
} from '@hcengineering/love'
|
} from '@hcengineering/love'
|
||||||
import { getEmbeddedLabel, getMetadata, type IntlString } from '@hcengineering/platform'
|
import { getEmbeddedLabel, getMetadata, getResource, type IntlString } from '@hcengineering/platform'
|
||||||
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
import presentation, { createQuery, getClient, type DocCreatePhase } from '@hcengineering/presentation'
|
||||||
import { getCurrentLocation, navigate, type DropdownTextItem } from '@hcengineering/ui'
|
import { getCurrentLocation, navigate, type DropdownTextItem } from '@hcengineering/ui'
|
||||||
import { KrispNoiseFilter, isKrispNoiseFilterSupported } from '@livekit/krisp-noise-filter'
|
import { KrispNoiseFilter, isKrispNoiseFilterSupported } 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'
|
||||||
@ -555,6 +567,33 @@ function checkPlace (room: Room, info: ParticipantInfo[], x: number, y: number):
|
|||||||
return !isOffice(room) && info.find((p) => p.x === x && p.y === y) === undefined
|
return !isOffice(room) && info.find((p) => p.x === x && p.y === y) === undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function connectToMeeting (
|
||||||
|
personByIdStore: IdMap<Person>,
|
||||||
|
currentInfo: ParticipantInfo | undefined,
|
||||||
|
info: ParticipantInfo[],
|
||||||
|
currentRequests: JoinRequest[],
|
||||||
|
currentInvites: Invite[],
|
||||||
|
rooms: Room[],
|
||||||
|
meetId: string
|
||||||
|
): Promise<void> {
|
||||||
|
const client = getClient()
|
||||||
|
const meeting = await client.findOne(love.mixin.Meeting, { _id: meetId as Ref<Meeting> })
|
||||||
|
if (meeting === undefined) return
|
||||||
|
const room = rooms.find((p) => p._id === meeting.room)
|
||||||
|
if (room === undefined) return
|
||||||
|
|
||||||
|
// check time (it should be 10 minutes before the meeting or active in roomInfo)
|
||||||
|
|
||||||
|
await tryConnect(
|
||||||
|
personByIdStore,
|
||||||
|
currentInfo,
|
||||||
|
room,
|
||||||
|
info.filter((p) => p.room === room._id),
|
||||||
|
currentRequests,
|
||||||
|
currentInvites
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export async function tryConnect (
|
export async function tryConnect (
|
||||||
personByIdStore: IdMap<Person>,
|
personByIdStore: IdMap<Person>,
|
||||||
currentInfo: ParticipantInfo | undefined,
|
currentInfo: ParticipantInfo | undefined,
|
||||||
@ -720,3 +759,27 @@ async function checkRecordAvailable (): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void checkRecordAvailable()
|
void checkRecordAvailable()
|
||||||
|
|
||||||
|
export async function createMeeting (
|
||||||
|
client: TxOperations,
|
||||||
|
_id: Ref<Event>,
|
||||||
|
space: Space,
|
||||||
|
data: Data<Event>,
|
||||||
|
store: Record<string, any>,
|
||||||
|
phase: DocCreatePhase
|
||||||
|
): Promise<void> {
|
||||||
|
if (phase === 'post' && store.room != null && store.isMeeting === true) {
|
||||||
|
await client.createMixin<Event, Meeting>(_id, calendar.class.Event, space._id, love.mixin.Meeting, {
|
||||||
|
room: store.room as Ref<Room>
|
||||||
|
})
|
||||||
|
const event = await client.findOne(calendar.class.Event, { _id })
|
||||||
|
if (event === undefined) return
|
||||||
|
const navigateUrl = getCurrentLocation()
|
||||||
|
navigateUrl.query = {
|
||||||
|
meetId: _id
|
||||||
|
}
|
||||||
|
const func = await getResource(login.function.GetInviteLink)
|
||||||
|
const link = await func(-1, '', -1, AccountRole.Guest, encodeURIComponent(JSON.stringify(navigateUrl)))
|
||||||
|
await client.update(event, { location: link })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"@hcengineering/preference": "^0.6.13",
|
"@hcengineering/preference": "^0.6.13",
|
||||||
"@hcengineering/notification": "^0.6.23",
|
"@hcengineering/notification": "^0.6.23",
|
||||||
"@hcengineering/ui": "^0.6.15",
|
"@hcengineering/ui": "^0.6.15",
|
||||||
|
"@hcengineering/calendar": "^0.6.24",
|
||||||
"@hcengineering/drive": "^0.6.0",
|
"@hcengineering/drive": "^0.6.0",
|
||||||
"@hcengineering/core": "^0.6.32",
|
"@hcengineering/core": "^0.6.32",
|
||||||
"@hcengineering/view": "^0.6.13"
|
"@hcengineering/view": "^0.6.13"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
import { Event } from '@hcengineering/calendar'
|
||||||
import { Person } from '@hcengineering/contact'
|
import { Person } from '@hcengineering/contact'
|
||||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
import { Class, 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'
|
||||||
@ -58,6 +59,10 @@ export interface RoomInfo extends Doc {
|
|||||||
isOffice: boolean
|
isOffice: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Meeting extends Event {
|
||||||
|
room: Ref<Room>
|
||||||
|
}
|
||||||
|
|
||||||
export enum RequestStatus {
|
export enum RequestStatus {
|
||||||
Pending,
|
Pending,
|
||||||
Approved,
|
Approved,
|
||||||
@ -97,6 +102,9 @@ const love = plugin(loveId, {
|
|||||||
RoomInfo: '' as Ref<Class<RoomInfo>>,
|
RoomInfo: '' as Ref<Class<RoomInfo>>,
|
||||||
Invite: '' as Ref<Class<Invite>>
|
Invite: '' as Ref<Class<Invite>>
|
||||||
},
|
},
|
||||||
|
mixin: {
|
||||||
|
Meeting: '' as Ref<Mixin<Meeting>>
|
||||||
|
},
|
||||||
action: {
|
action: {
|
||||||
ToggleMic: '' as Ref<Action>,
|
ToggleMic: '' as Ref<Action>,
|
||||||
ToggleVideo: '' as Ref<Action>
|
ToggleVideo: '' as Ref<Action>
|
||||||
|
@ -24,7 +24,6 @@ import contact, {
|
|||||||
Person,
|
Person,
|
||||||
PersonAccount
|
PersonAccount
|
||||||
} from '@hcengineering/contact'
|
} from '@hcengineering/contact'
|
||||||
import { buildStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage'
|
|
||||||
import core, {
|
import core, {
|
||||||
AccountRole,
|
AccountRole,
|
||||||
BaseWorkspaceInfo,
|
BaseWorkspaceInfo,
|
||||||
@ -40,30 +39,17 @@ import core, {
|
|||||||
Ref,
|
Ref,
|
||||||
roleOrder,
|
roleOrder,
|
||||||
systemAccountEmail,
|
systemAccountEmail,
|
||||||
|
Timestamp,
|
||||||
Tx,
|
Tx,
|
||||||
TxOperations,
|
TxOperations,
|
||||||
Version,
|
Version,
|
||||||
versionToString,
|
versionToString,
|
||||||
WorkspaceId,
|
WorkspaceId,
|
||||||
Timestamp,
|
|
||||||
WorkspaceIdWithUrl,
|
WorkspaceIdWithUrl,
|
||||||
type Branding
|
type Branding
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { consoleModelLogger, MigrateOperation, ModelLogger } from '@hcengineering/model'
|
import { consoleModelLogger, MigrateOperation, ModelLogger } from '@hcengineering/model'
|
||||||
import platform, { getMetadata, PlatformError, Severity, Status, translate } from '@hcengineering/platform'
|
import platform, { getMetadata, PlatformError, Severity, Status, translate } from '@hcengineering/platform'
|
||||||
import { decodeToken, generateToken } from '@hcengineering/server-token'
|
|
||||||
import toolPlugin, {
|
|
||||||
connect,
|
|
||||||
initializeWorkspace,
|
|
||||||
initModel,
|
|
||||||
updateModel,
|
|
||||||
prepareTools,
|
|
||||||
upgradeModel
|
|
||||||
} from '@hcengineering/server-tool'
|
|
||||||
import { pbkdf2Sync, randomBytes } from 'crypto'
|
|
||||||
import { Binary, Db, Filter, ObjectId, type MongoClient } from 'mongodb'
|
|
||||||
import fetch from 'node-fetch'
|
|
||||||
import otpGenerator from 'otp-generator'
|
|
||||||
import {
|
import {
|
||||||
DummyFullTextAdapter,
|
DummyFullTextAdapter,
|
||||||
Pipeline,
|
Pipeline,
|
||||||
@ -78,6 +64,20 @@ import {
|
|||||||
registerServerPlugins,
|
registerServerPlugins,
|
||||||
registerStringLoaders
|
registerStringLoaders
|
||||||
} from '@hcengineering/server-pipeline'
|
} from '@hcengineering/server-pipeline'
|
||||||
|
import { buildStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage'
|
||||||
|
import { decodeToken, generateToken } from '@hcengineering/server-token'
|
||||||
|
import toolPlugin, {
|
||||||
|
connect,
|
||||||
|
initializeWorkspace,
|
||||||
|
initModel,
|
||||||
|
prepareTools,
|
||||||
|
updateModel,
|
||||||
|
upgradeModel
|
||||||
|
} from '@hcengineering/server-tool'
|
||||||
|
import { pbkdf2Sync, randomBytes } from 'crypto'
|
||||||
|
import { Binary, Db, Filter, ObjectId, type MongoClient } from 'mongodb'
|
||||||
|
import fetch from 'node-fetch'
|
||||||
|
import otpGenerator from 'otp-generator'
|
||||||
|
|
||||||
import { accountPlugin } from './plugin'
|
import { accountPlugin } from './plugin'
|
||||||
|
|
||||||
@ -692,7 +692,7 @@ export async function checkInvite (ctx: MeasureContext, invite: Invite | null, e
|
|||||||
Analytics.handleError(new Error(`no invite or invite limit exceed ${email}`))
|
Analytics.handleError(new Error(`no invite or invite limit exceed ${email}`))
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||||
}
|
}
|
||||||
if (invite.exp < Date.now()) {
|
if (invite.exp !== -1 && invite.exp < Date.now()) {
|
||||||
ctx.error('invite', { email, state: 'link expired' })
|
ctx.error('invite', { email, state: 'link expired' })
|
||||||
Analytics.handleError(new Error(`invite link expired ${invite._id.toString()} ${email}`))
|
Analytics.handleError(new Error(`invite link expired ${invite._id.toString()} ${email}`))
|
||||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.ExpiredLink, {}))
|
throw new PlatformError(new Status(Severity.ERROR, platform.status.ExpiredLink, {}))
|
||||||
@ -1577,7 +1577,7 @@ export async function getInviteLink (
|
|||||||
ctx.info('Getting invite link', { workspace: workspace.name, emailMask, limit })
|
ctx.info('Getting invite link', { workspace: workspace.name, emailMask, limit })
|
||||||
const data: Omit<Invite, '_id'> = {
|
const data: Omit<Invite, '_id'> = {
|
||||||
workspace,
|
workspace,
|
||||||
exp: Date.now() + exp,
|
exp: exp < 0 ? -1 : Date.now() + exp,
|
||||||
emailMask,
|
emailMask,
|
||||||
limit,
|
limit,
|
||||||
role: role ?? AccountRole.User
|
role: role ?? AccountRole.User
|
||||||
|
@ -27,7 +27,10 @@ import core, {
|
|||||||
AttachedData,
|
AttachedData,
|
||||||
Client,
|
Client,
|
||||||
Data,
|
Data,
|
||||||
|
Doc,
|
||||||
|
DocData,
|
||||||
DocumentUpdate,
|
DocumentUpdate,
|
||||||
|
Mixin,
|
||||||
Ref,
|
Ref,
|
||||||
TxOperations,
|
TxOperations,
|
||||||
TxUpdateDoc,
|
TxUpdateDoc,
|
||||||
@ -42,10 +45,10 @@ import type { Collection, Db } from 'mongodb'
|
|||||||
import { encode64 } from './base64'
|
import { encode64 } from './base64'
|
||||||
import { CalendarController } from './calendarController'
|
import { CalendarController } from './calendarController'
|
||||||
import config from './config'
|
import config from './config'
|
||||||
|
import { RateLimiter } from './rateLimiter'
|
||||||
import type { CalendarHistory, EventHistory, EventWatch, ProjectCredentials, State, Token, User, Watch } from './types'
|
import type { CalendarHistory, EventHistory, EventWatch, ProjectCredentials, State, Token, User, Watch } from './types'
|
||||||
import { encodeReccuring, isToken, parseRecurrenceStrings } from './utils'
|
import { encodeReccuring, isToken, parseRecurrenceStrings } from './utils'
|
||||||
import type { WorkspaceClient } from './workspaceClient'
|
import type { WorkspaceClient } from './workspaceClient'
|
||||||
import { RateLimiter } from './rateLimiter'
|
|
||||||
|
|
||||||
const SCOPES = [
|
const SCOPES = [
|
||||||
'https://www.googleapis.com/auth/calendar.calendars.readonly',
|
'https://www.googleapis.com/auth/calendar.calendars.readonly',
|
||||||
@ -703,7 +706,7 @@ export class CalendarClient {
|
|||||||
}
|
}
|
||||||
const data: Partial<AttachedData<Event>> = await this.parseUpdateData(event)
|
const data: Partial<AttachedData<Event>> = await this.parseUpdateData(event)
|
||||||
if (event.recurringEventId != null) {
|
if (event.recurringEventId != null) {
|
||||||
const diff = this.getEventDiff<ReccuringInstance>(
|
const diff = this.getDiff<ReccuringInstance>(
|
||||||
{
|
{
|
||||||
...data,
|
...data,
|
||||||
recurringEventId: event.recurringEventId as Ref<ReccuringEvent>,
|
recurringEventId: event.recurringEventId as Ref<ReccuringEvent>,
|
||||||
@ -719,7 +722,7 @@ export class CalendarClient {
|
|||||||
} else {
|
} else {
|
||||||
if (event.recurrence != null) {
|
if (event.recurrence != null) {
|
||||||
const parseRule = parseRecurrenceStrings(event.recurrence)
|
const parseRule = parseRecurrenceStrings(event.recurrence)
|
||||||
const diff = this.getEventDiff<ReccuringEvent>(
|
const diff = this.getDiff<ReccuringEvent>(
|
||||||
{
|
{
|
||||||
...data,
|
...data,
|
||||||
rules: parseRule.rules,
|
rules: parseRule.rules,
|
||||||
@ -733,13 +736,70 @@ export class CalendarClient {
|
|||||||
await this.client.update(current, diff)
|
await this.client.update(current, diff)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const diff = this.getEventDiff(data, current)
|
const diff = this.getDiff(data, current)
|
||||||
if (Object.keys(diff).length > 0) {
|
if (Object.keys(diff).length > 0) {
|
||||||
console.log('UPDATE EVENT DIFF', JSON.stringify(diff), JSON.stringify(current))
|
console.log('UPDATE EVENT DIFF', JSON.stringify(diff), JSON.stringify(current))
|
||||||
await this.client.update(current, diff)
|
await this.client.update(current, diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await this.updateMixins(event, current)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateMixins (event: calendar_v3.Schema$Event, current: Event): Promise<void> {
|
||||||
|
const mixins = this.parseMixins(event)
|
||||||
|
if (mixins !== undefined) {
|
||||||
|
for (const mixin in mixins) {
|
||||||
|
const attr = mixins[mixin]
|
||||||
|
if (typeof attr === 'object' && Object.keys(attr).length > 0) {
|
||||||
|
if (this.client.getHierarchy().hasMixin(current, mixin as Ref<Mixin<Doc>>)) {
|
||||||
|
const diff = this.getDiff(attr, this.client.getHierarchy().as(current, mixin as Ref<Mixin<Doc>>))
|
||||||
|
if (Object.keys(diff).length > 0) {
|
||||||
|
await this.client.updateMixin(
|
||||||
|
current._id,
|
||||||
|
current._class,
|
||||||
|
calendar.space.Calendar,
|
||||||
|
mixin as Ref<Mixin<Doc>>,
|
||||||
|
diff
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await this.client.createMixin(
|
||||||
|
current._id,
|
||||||
|
current._class,
|
||||||
|
calendar.space.Calendar,
|
||||||
|
mixin as Ref<Mixin<Doc>>,
|
||||||
|
attr
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseMixins (event: calendar_v3.Schema$Event): Record<string, any> | undefined {
|
||||||
|
if (event.extendedProperties?.shared?.mixins !== undefined) {
|
||||||
|
const mixins = JSON.parse(event.extendedProperties.shared.mixins)
|
||||||
|
return mixins
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveMixins (event: calendar_v3.Schema$Event, _id: Ref<Event>): Promise<void> {
|
||||||
|
const mixins = this.parseMixins(event)
|
||||||
|
if (mixins !== undefined) {
|
||||||
|
for (const mixin in mixins) {
|
||||||
|
const attr = mixins[mixin]
|
||||||
|
if (typeof attr === 'object' && Object.keys(attr).length > 0) {
|
||||||
|
await this.client.createMixin(
|
||||||
|
_id,
|
||||||
|
calendar.class.Event,
|
||||||
|
calendar.space.Calendar,
|
||||||
|
mixin as Ref<Mixin<Doc>>,
|
||||||
|
attr
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveExtEvent (
|
private async saveExtEvent (
|
||||||
@ -767,6 +827,7 @@ export class CalendarClient {
|
|||||||
timeZone: event.start?.timeZone ?? event.end?.timeZone ?? 'Etc/GMT'
|
timeZone: event.start?.timeZone ?? event.end?.timeZone ?? 'Etc/GMT'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
await this.saveMixins(event, id)
|
||||||
console.log('SAVE INSTANCE', id, JSON.stringify(event))
|
console.log('SAVE INSTANCE', id, JSON.stringify(event))
|
||||||
} else if (event.status !== 'cancelled') {
|
} else if (event.status !== 'cancelled') {
|
||||||
if (event.recurrence != null) {
|
if (event.recurrence != null) {
|
||||||
@ -786,6 +847,7 @@ export class CalendarClient {
|
|||||||
timeZone: event.start?.timeZone ?? event.end?.timeZone ?? 'Etc/GMT'
|
timeZone: event.start?.timeZone ?? event.end?.timeZone ?? 'Etc/GMT'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
await this.saveMixins(event, id)
|
||||||
console.log('SAVE REC EVENT', id, JSON.stringify(event))
|
console.log('SAVE REC EVENT', id, JSON.stringify(event))
|
||||||
} else {
|
} else {
|
||||||
const id = await this.client.addCollection(
|
const id = await this.client.addCollection(
|
||||||
@ -796,12 +858,13 @@ export class CalendarClient {
|
|||||||
'events',
|
'events',
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
|
await this.saveMixins(event, id)
|
||||||
console.log('SAVE EVENT', id, JSON.stringify(event))
|
console.log('SAVE EVENT', id, JSON.stringify(event))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEventDiff<T extends Event>(data: Partial<AttachedData<T>>, current: T): Partial<AttachedData<T>> {
|
private getDiff<T extends Doc>(data: Partial<DocData<T>>, current: T): Partial<DocData<T>> {
|
||||||
const res = {}
|
const res = {}
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
if (!deepEqual((data as any)[key], (current as any)[key])) {
|
if (!deepEqual((data as any)[key], (current as any)[key])) {
|
||||||
@ -1120,6 +1183,24 @@ export class CalendarClient {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getMixinFields (event: Event): Record<string, any> {
|
||||||
|
const res = {}
|
||||||
|
const h = this.client.getHierarchy()
|
||||||
|
for (const [k, v] of Object.entries(event)) {
|
||||||
|
if (typeof v === 'object' && h.isMixin(k as Ref<Mixin<Doc>>)) {
|
||||||
|
for (const [key, value] of Object.entries(v)) {
|
||||||
|
if (value !== undefined) {
|
||||||
|
const obj = (res as any)[k] ?? {}
|
||||||
|
obj[key] = value
|
||||||
|
;(res as any)[k] = obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
private convertBody (event: Event): calendar_v3.Schema$Event {
|
private convertBody (event: Event): calendar_v3.Schema$Event {
|
||||||
const res: calendar_v3.Schema$Event = {
|
const res: calendar_v3.Schema$Event = {
|
||||||
start: convertDate(event.date, event.allDay, getTimezone(event)),
|
start: convertDate(event.date, event.allDay, getTimezone(event)),
|
||||||
@ -1141,6 +1222,16 @@ export class CalendarClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const mixin = this.getMixinFields(event)
|
||||||
|
if (Object.keys(mixin).length > 0) {
|
||||||
|
res.extendedProperties = {
|
||||||
|
...res.extendedProperties,
|
||||||
|
shared: {
|
||||||
|
...res.extendedProperties?.shared,
|
||||||
|
mixin: JSON.stringify(mixin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (event.reminders !== undefined) {
|
if (event.reminders !== undefined) {
|
||||||
res.reminders = {
|
res.reminders = {
|
||||||
useDefault: false,
|
useDefault: false,
|
||||||
|
Loading…
Reference in New Issue
Block a user