mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Office: use pulse for invites and join requests (#9941)
Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
parent
5f3911d5c7
commit
ff4fdc228a
@@ -31,8 +31,6 @@ import {
|
||||
import {
|
||||
type DevicesPreference,
|
||||
type Floor,
|
||||
type Invite,
|
||||
type JoinRequest,
|
||||
loveId,
|
||||
type Meeting,
|
||||
type MeetingMinutes,
|
||||
@@ -40,7 +38,6 @@ import {
|
||||
type MeetingSchedule,
|
||||
type Office,
|
||||
type ParticipantInfo,
|
||||
type RequestStatus,
|
||||
type Room,
|
||||
type RoomAccess,
|
||||
type RoomInfo,
|
||||
@@ -163,31 +160,6 @@ export class TParticipantInfo extends TDoc implements ParticipantInfo {
|
||||
account!: AccountUuid | null
|
||||
}
|
||||
|
||||
@Model(love.class.JoinRequest, core.class.Doc, DOMAIN_TRANSIENT)
|
||||
export class TJoinRequest extends TDoc implements JoinRequest {
|
||||
@Prop(TypeRef(contact.class.Person), getEmbeddedLabel('From'))
|
||||
person!: Ref<Person>
|
||||
|
||||
@Prop(TypeRef(love.class.Room), love.string.Room)
|
||||
room!: Ref<Room>
|
||||
|
||||
status!: RequestStatus
|
||||
}
|
||||
|
||||
@Model(love.class.Invite, core.class.Doc, DOMAIN_TRANSIENT)
|
||||
export class TInvite extends TDoc implements Invite {
|
||||
@Prop(TypeRef(contact.class.Person), getEmbeddedLabel('From'))
|
||||
from!: Ref<Person>
|
||||
|
||||
@Prop(TypeRef(contact.class.Person), getEmbeddedLabel('Target'))
|
||||
target!: Ref<Person>
|
||||
|
||||
@Prop(TypeRef(love.class.Room), love.string.Room)
|
||||
room!: Ref<Room>
|
||||
|
||||
status!: RequestStatus
|
||||
}
|
||||
|
||||
@Model(love.class.DevicesPreference, preference.class.Preference)
|
||||
export class TDevicesPreference extends TPreference implements DevicesPreference {
|
||||
blurRadius!: number
|
||||
@@ -272,10 +244,8 @@ export function createModel (builder: Builder): void {
|
||||
TFloor,
|
||||
TOffice,
|
||||
TParticipantInfo,
|
||||
TJoinRequest,
|
||||
TDevicesPreference,
|
||||
TRoomInfo,
|
||||
TInvite,
|
||||
TMeeting,
|
||||
TMeetingMinutes,
|
||||
TMeetingSchedule
|
||||
@@ -388,36 +358,6 @@ export function createModel (builder: Builder): void {
|
||||
love.ids.LoveNotificationGroup
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: false,
|
||||
generated: false,
|
||||
label: love.string.InivitingLabel,
|
||||
group: love.ids.LoveNotificationGroup,
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: love.class.Invite,
|
||||
defaultEnabled: true
|
||||
},
|
||||
love.ids.InviteNotification
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: false,
|
||||
generated: false,
|
||||
label: love.string.KnockingLabel,
|
||||
group: love.ids.LoveNotificationGroup,
|
||||
txClasses: [],
|
||||
objectClass: love.class.JoinRequest,
|
||||
defaultEnabled: true
|
||||
},
|
||||
love.ids.KnockNotification
|
||||
)
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.SoundNotificationProvider,
|
||||
excludeIgnore: [love.ids.KnockNotification],
|
||||
@@ -514,11 +454,6 @@ export function createModel (builder: Builder): void {
|
||||
components: { input: { component: chunter.component.ChatMessageInput, props: { collection: 'messages' } } }
|
||||
})
|
||||
|
||||
builder.mixin(love.class.JoinRequest, core.class.Class, core.mixin.TxAccessLevel, {
|
||||
createAccessLevel: AccountRole.Guest,
|
||||
removeAccessLevel: AccountRole.Guest
|
||||
})
|
||||
|
||||
builder.mixin(love.class.ParticipantInfo, core.class.Class, core.mixin.TxAccessLevel, {
|
||||
createAccessLevel: AccountRole.Guest,
|
||||
updateAccessLevel: AccountRole.Guest
|
||||
|
||||
@@ -64,22 +64,6 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverLove.trigger.OnKnock,
|
||||
txMatch: {
|
||||
objectClass: love.class.JoinRequest,
|
||||
_class: core.class.TxCreateDoc
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverLove.trigger.OnInvite,
|
||||
txMatch: {
|
||||
objectClass: love.class.Invite,
|
||||
_class: core.class.TxCreateDoc
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(love.class.MeetingMinutes, core.class.Class, serverCore.mixin.SearchPresenter, {
|
||||
searchIcon: love.icon.MeetingMinutes,
|
||||
title: [['title']]
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"@hcengineering/theme": "^0.6.5",
|
||||
"@hcengineering/retry": "^0.6.0",
|
||||
"@hcengineering/hulylake-client": "^0.6.0",
|
||||
"@hcengineering/hulypulse-client": "^0.6.0",
|
||||
"fast-equals": "^5.2.2",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"svelte": "^4.2.20",
|
||||
|
||||
@@ -78,3 +78,4 @@ export * from './drawingCommand'
|
||||
export * from './drawingCommandsProcessor'
|
||||
export * from './link-preview'
|
||||
export * from './communication'
|
||||
export * from './pulse'
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
import { HulypulseClient } from '@hcengineering/hulypulse-client'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import presentation from './plugin'
|
||||
|
||||
let pulseclient: HulypulseClient | undefined
|
||||
|
||||
@@ -143,7 +143,7 @@ export function showPopup (
|
||||
id,
|
||||
close: closePopupOp,
|
||||
update: (props) => {
|
||||
updatePopup(id, props)
|
||||
updatePopup(id, { props })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"@hcengineering/emoji-resources": "^0.6.0",
|
||||
"@hcengineering/theme": "^0.6.5",
|
||||
"@hcengineering/account-client": "^0.6.0",
|
||||
"@hcengineering/hulypulse-client": "^0.6.0",
|
||||
"@livekit/krisp-noise-filter": "^0.3.0",
|
||||
"@livekit/track-processors": "^0.5.6",
|
||||
"livekit-client": "^2.15.6",
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
import { ActionIcon } from '@hcengineering/ui'
|
||||
import love from '../plugin'
|
||||
import { myInfo } from '../stores'
|
||||
import { joinMeeting, kick, sendInvites } from '../meetings'
|
||||
import { joinMeeting, kick } from '../meetings'
|
||||
import { sendInvites } from '../invites'
|
||||
|
||||
export let room: Room
|
||||
export let person: Ref<Person>
|
||||
@@ -30,7 +31,7 @@
|
||||
label={love.string.Invite}
|
||||
icon={love.icon.Invite}
|
||||
action={() => {
|
||||
void sendInvites([person])
|
||||
sendInvites([person])
|
||||
}}
|
||||
/>
|
||||
{#if room.access === RoomAccess.Knock}
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
import love from '../plugin'
|
||||
import { liveKitClient, lk } from '../utils'
|
||||
import { lkSessionConnected } from '../liveKitClient'
|
||||
import { subscribeInviteRequests, unsubscribeInviteRequests } from '../invites'
|
||||
import { Room } from '@hcengineering/love'
|
||||
import { subscribeJoinRequests, unsubscribeJoinRequests } from '../joinRequests'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { myInfo } from '../stores'
|
||||
|
||||
let parentElement: HTMLDivElement
|
||||
|
||||
@@ -33,13 +38,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
function subscribeRoomRequests (room: Ref<Room> | undefined): void {
|
||||
unsubscribeJoinRequests()
|
||||
.then(() => subscribeJoinRequests(room))
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
}
|
||||
|
||||
$: subscribeRoomRequests($myInfo?.room)
|
||||
|
||||
onMount(async () => {
|
||||
pushRootBarComponent('left', love.component.ControlExt, 20)
|
||||
lk.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)
|
||||
lk.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
|
||||
|
||||
await subscribeInviteRequests()
|
||||
})
|
||||
|
||||
onDestroy(async () => {
|
||||
await unsubscribeInviteRequests()
|
||||
lk.off(RoomEvent.TrackSubscribed, handleTrackSubscribed)
|
||||
lk.off(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
|
||||
if ($lkSessionConnected) {
|
||||
|
||||
@@ -14,29 +14,17 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { IdMap, Ref, toIdMap } from '@hcengineering/core'
|
||||
import { Invite, isOffice, JoinRequest, ParticipantInfo, RequestStatus, Room } from '@hcengineering/love'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
closePopup,
|
||||
eventToHTMLElement,
|
||||
Location,
|
||||
location,
|
||||
PopupResult,
|
||||
showPopup,
|
||||
closeTooltip
|
||||
} from '@hcengineering/ui'
|
||||
import { isOffice, ParticipantInfo, Room } from '@hcengineering/love'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { closePopup, eventToHTMLElement, Location, location, showPopup, closeTooltip } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import workbench from '@hcengineering/workbench'
|
||||
import { closeWidget, sidebarStore } from '@hcengineering/workbench-resources'
|
||||
|
||||
import love from '../../plugin'
|
||||
import { activeInvites, currentRoom, infos, myInfo, myInvites, myRequests, rooms } from '../../stores'
|
||||
import { currentRoom, infos, myInfo, rooms } from '../../stores'
|
||||
import { createMeetingWidget, getRoomName } from '../../utils'
|
||||
import ActiveInvitesPopup from './invites/ActiveInvitesPopup.svelte'
|
||||
import InvitePopup from './invites/InvitePopup.svelte'
|
||||
import PersonActionPopup from '../PersonActionPopup.svelte'
|
||||
import RequestPopup from './invites/RequestPopup.svelte'
|
||||
import RequestingPopup from './invites/RequestingPopup.svelte'
|
||||
import RoomPopup from '../RoomPopup.svelte'
|
||||
import RoomButton from '../RoomButton.svelte'
|
||||
import { leaveMeeting, currentMeetingRoom } from '../../meetings'
|
||||
@@ -70,56 +58,6 @@
|
||||
|
||||
$: activeRooms = getActiveRooms($rooms, $infos)
|
||||
|
||||
const query = createQuery()
|
||||
let requests: JoinRequest[] = []
|
||||
query.query(love.class.JoinRequest, { status: RequestStatus.Pending }, (res) => {
|
||||
requests = res
|
||||
})
|
||||
|
||||
let activeRequest: JoinRequest | undefined = undefined
|
||||
const joinRequestCategory = 'joinRequest'
|
||||
function checkRequests (requests: JoinRequest[], $myInfo: ParticipantInfo | undefined): void {
|
||||
if (activeRequest !== undefined) {
|
||||
// try to find active request, if it not exists close popup
|
||||
if (requests.find((r) => r._id === activeRequest?._id && r.room === $myInfo?.room) === undefined) {
|
||||
closePopup(joinRequestCategory)
|
||||
activeRequest = undefined
|
||||
}
|
||||
}
|
||||
if (activeRequest === undefined) {
|
||||
activeRequest = requests.find((r) => r.room === $myInfo?.room)
|
||||
if (activeRequest !== undefined) {
|
||||
showPopup(RequestPopup, { request: activeRequest }, undefined, undefined, undefined, {
|
||||
category: joinRequestCategory,
|
||||
overlay: false,
|
||||
fixed: true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const myJoinRequestCategory = 'MyJoinRequest'
|
||||
let myRequestsPopup: PopupResult | undefined = undefined
|
||||
|
||||
function checkMyRequests (requests: JoinRequest[]): void {
|
||||
if (requests.length > 0) {
|
||||
if (myRequestsPopup === undefined) {
|
||||
myRequestsPopup = showPopup(RequestingPopup, { request: requests[0] }, undefined, undefined, undefined, {
|
||||
category: myJoinRequestCategory,
|
||||
overlay: false,
|
||||
fixed: true
|
||||
})
|
||||
}
|
||||
} else if (myRequestsPopup !== undefined) {
|
||||
myRequestsPopup.close()
|
||||
myRequestsPopup = undefined
|
||||
}
|
||||
}
|
||||
|
||||
$: checkMyRequests($myRequests)
|
||||
|
||||
$: checkRequests(requests, $myInfo)
|
||||
|
||||
function openRoom (room: Room): (e: MouseEvent) => void {
|
||||
return (e: MouseEvent) => {
|
||||
closeTooltip()
|
||||
@@ -127,55 +65,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
let activeInvite: Invite | undefined = undefined
|
||||
const inviteCategory = 'inviteReq'
|
||||
function checkInvites (invites: Invite[]): void {
|
||||
if (activeInvite !== undefined) {
|
||||
// try to find active request, if it not exists close popup
|
||||
if (invites.find((r) => r._id === activeInvite?._id) === undefined) {
|
||||
closePopup(inviteCategory)
|
||||
activeInvite = undefined
|
||||
}
|
||||
}
|
||||
if (activeInvite === undefined) {
|
||||
activeInvite = invites[0]
|
||||
if (activeInvite !== undefined) {
|
||||
showPopup(InvitePopup, { invite: activeInvite }, undefined, undefined, undefined, {
|
||||
category: inviteCategory,
|
||||
overlay: false,
|
||||
fixed: true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: checkInvites($myInvites)
|
||||
|
||||
const myInvitesCategory = 'myInvites'
|
||||
|
||||
let myInvitesPopup: PopupResult | undefined = undefined
|
||||
|
||||
function checkActiveInvites (invites: Invite[]): void {
|
||||
if (invites.length > 0) {
|
||||
if (myInvitesPopup === undefined) {
|
||||
myInvitesPopup = showPopup(ActiveInvitesPopup, {}, undefined, undefined, undefined, {
|
||||
category: myInvitesCategory,
|
||||
overlay: false,
|
||||
fixed: true
|
||||
})
|
||||
}
|
||||
} else if (myInvitesPopup !== undefined) {
|
||||
myInvitesPopup.close()
|
||||
myInvitesPopup = undefined
|
||||
}
|
||||
}
|
||||
|
||||
$: reception = $rooms.find((f) => f._id === love.ids.Reception)
|
||||
|
||||
$: receptionParticipants = $infos.filter((p) => p.room === love.ids.Reception)
|
||||
|
||||
$: checkActiveInvites($activeInvites)
|
||||
|
||||
function checkActiveMeeting (loc: Location, meetingSessionConnected: boolean, room: Ref<Room> | undefined): void {
|
||||
const meetingWidgetState = $sidebarStore.widgetsState.get(love.ids.MeetingWidget)
|
||||
const isMeetingWidgetCreated = meetingWidgetState !== undefined
|
||||
@@ -211,9 +106,6 @@
|
||||
|
||||
onDestroy(() => {
|
||||
closePopup(myInvitesCategory)
|
||||
closePopup(inviteCategory)
|
||||
closePopup(joinRequestCategory)
|
||||
closePopup(myJoinRequestCategory)
|
||||
closeWidget(love.ids.MeetingWidget)
|
||||
})
|
||||
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
<script lang="ts">
|
||||
import { ButtonBaseSize, IconSize, ModernButton, showPopup } from '@hcengineering/ui'
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import { currentMeetingRoom, sendInvites } from '../../../meetings'
|
||||
import { currentMeetingRoom } from '../../../meetings'
|
||||
import love from '../../../plugin'
|
||||
import { SelectUsersPopup } from '@hcengineering/contact-resources'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { sendInvites } from '../../../invites'
|
||||
import { infos } from '../../../stores'
|
||||
|
||||
export let employee: Employee | undefined = undefined
|
||||
@@ -33,7 +34,7 @@
|
||||
|
||||
async function invite (): Promise<void> {
|
||||
if (employee !== undefined) {
|
||||
await sendInvites([employee._id])
|
||||
sendInvites([employee._id])
|
||||
} else {
|
||||
openSelectUsersPopup()
|
||||
}
|
||||
@@ -53,7 +54,7 @@
|
||||
'top',
|
||||
(result?: Ref<Employee>[]) => {
|
||||
if (result != null) {
|
||||
void sendInvites(result)
|
||||
sendInvites(result)
|
||||
}
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
+29
-5
@@ -13,17 +13,41 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@hcengineering/contact'
|
||||
import contact, { Person } from '@hcengineering/contact'
|
||||
import { CombineAvatars } from '@hcengineering/contact-resources'
|
||||
import { Button, Label } from '@hcengineering/ui'
|
||||
import love from '../../../plugin'
|
||||
import { cancelInvites } from '../../../meetings'
|
||||
import { activeInvites } from '../../../stores'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { onMount } from 'svelte'
|
||||
import {
|
||||
cancelInvites,
|
||||
closeInvitesPopup,
|
||||
inviteRequestSecondsToLive,
|
||||
subscribeInviteResponses,
|
||||
unsubscribeInviteResponses,
|
||||
updateInvites
|
||||
} from '../../../invites'
|
||||
|
||||
$: persons = $activeInvites.map((p) => p.target)
|
||||
export let persons: Array<Ref<Person>>
|
||||
export let meetingId: string
|
||||
|
||||
onMount(() => {
|
||||
void subscribeInviteResponses()
|
||||
void doUpdateInvites()
|
||||
const interval = setInterval(doUpdateInvites, (inviteRequestSecondsToLive - 2) * 1000)
|
||||
return () => {
|
||||
void unsubscribeInviteResponses()
|
||||
clearInterval(interval)
|
||||
void cancelInvites(meetingId)
|
||||
}
|
||||
})
|
||||
|
||||
async function doUpdateInvites (): Promise<void> {
|
||||
await updateInvites(persons, meetingId)
|
||||
}
|
||||
|
||||
async function cancel (): Promise<void> {
|
||||
await cancelInvites($activeInvites)
|
||||
closeInvitesPopup()
|
||||
}
|
||||
</script>
|
||||
|
||||
+7
-14
@@ -15,17 +15,16 @@
|
||||
<script lang="ts">
|
||||
import { formatName, Person } from '@hcengineering/contact'
|
||||
import { Avatar, getPersonByPersonRefCb } from '@hcengineering/contact-resources'
|
||||
import { playNotificationSound } from '@hcengineering/presentation'
|
||||
import { playSound } from '@hcengineering/presentation'
|
||||
import { Button, Label } from '@hcengineering/ui'
|
||||
import { Invite } from '@hcengineering/love'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
|
||||
import love from '../../../plugin'
|
||||
import { acceptInvite, rejectInvite } from '../../../meetings'
|
||||
|
||||
export let invite: Invite
|
||||
import { InviteRequest, responseToInviteRequest } from '../../../invites'
|
||||
|
||||
export let invite: InviteRequest
|
||||
let person: Person | undefined = undefined
|
||||
|
||||
$: getPersonByPersonRefCb(invite.from, (p) => {
|
||||
person = p ?? undefined
|
||||
})
|
||||
@@ -33,15 +32,15 @@
|
||||
let stopSound: (() => void) | null = null
|
||||
|
||||
async function accept (): Promise<void> {
|
||||
await acceptInvite(invite)
|
||||
await responseToInviteRequest(true)
|
||||
}
|
||||
|
||||
async function decline (): Promise<void> {
|
||||
await rejectInvite(invite)
|
||||
await responseToInviteRequest(false)
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
stopSound = await playNotificationSound(love.sound.Knock, love.class.Invite, true)
|
||||
stopSound = await playSound(love.sound.Knock, true)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
@@ -83,10 +82,4 @@
|
||||
color: var(--caption-color);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.roomTitle {
|
||||
color: var(--caption-color);
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
+27
-5
@@ -14,18 +14,40 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Button, Label } from '@hcengineering/ui'
|
||||
import { JoinRequest } from '@hcengineering/love'
|
||||
import love from '../../../plugin'
|
||||
import { rooms } from '../../../stores'
|
||||
import { getRoomLabel } from '../../../utils'
|
||||
import { cancelJoinRequest } from '../../../meetings'
|
||||
import {
|
||||
cancelJoinRequest,
|
||||
closeJoinRequestPopup,
|
||||
joinRequestSecondsToLive,
|
||||
subscribeJoinResponses,
|
||||
unsubscribeJoinResponses,
|
||||
updateJoinRequest
|
||||
} from '../../../joinRequests'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
export let request: JoinRequest
|
||||
export let meetingId: string
|
||||
|
||||
$: room = $rooms.find((p) => p._id === request.room)
|
||||
$: room = $rooms.find((p) => p._id === meetingId)
|
||||
|
||||
onMount(() => {
|
||||
void subscribeJoinResponses()
|
||||
void doUpdateRequest()
|
||||
const interval = setInterval(doUpdateRequest, (joinRequestSecondsToLive - 2) * 1000)
|
||||
return () => {
|
||||
void unsubscribeJoinResponses()
|
||||
clearInterval(interval)
|
||||
void cancelJoinRequest()
|
||||
}
|
||||
})
|
||||
|
||||
async function doUpdateRequest (): Promise<void> {
|
||||
await updateJoinRequest()
|
||||
}
|
||||
|
||||
async function cancel (): Promise<void> {
|
||||
await cancelJoinRequest(request)
|
||||
closeJoinRequestPopup()
|
||||
}
|
||||
</script>
|
||||
|
||||
+7
-8
@@ -15,30 +15,29 @@
|
||||
<script lang="ts">
|
||||
import { formatName } from '@hcengineering/contact'
|
||||
import { Avatar, getPersonByPersonRefStore } from '@hcengineering/contact-resources'
|
||||
import { playNotificationSound } from '@hcengineering/presentation'
|
||||
import { playSound } from '@hcengineering/presentation'
|
||||
import { Button, Label } from '@hcengineering/ui'
|
||||
import { JoinRequest } from '@hcengineering/love'
|
||||
import love from '../../../plugin'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { acceptJoinRequest, rejectJoinRequest } from '../../../meetings'
|
||||
import { responseToJoinRequest, JoinRequest } from '../../../joinRequests'
|
||||
|
||||
export let request: JoinRequest
|
||||
|
||||
$: personByRefStore = getPersonByPersonRefStore([request.person])
|
||||
$: person = $personByRefStore.get(request.person)
|
||||
$: personByRefStore = getPersonByPersonRefStore([request.from])
|
||||
$: person = $personByRefStore.get(request.from)
|
||||
|
||||
let stopSound: (() => void) | null = null
|
||||
|
||||
async function accept (): Promise<void> {
|
||||
await acceptJoinRequest(request)
|
||||
await responseToJoinRequest(request, true)
|
||||
}
|
||||
|
||||
async function decline (): Promise<void> {
|
||||
await rejectJoinRequest(request)
|
||||
await responseToJoinRequest(request, false)
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
stopSound = await playNotificationSound(love.sound.Knock, love.class.JoinRequest, true)
|
||||
stopSound = await playSound(love.sound.Knock, true)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
@@ -0,0 +1,234 @@
|
||||
//
|
||||
// Copyright © 2025 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.
|
||||
//
|
||||
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import { getCurrentEmployee, type Person } from '@hcengineering/contact'
|
||||
import { type PopupResult, showPopup } from '@hcengineering/ui'
|
||||
import { type UnsubscribeCallback } from '@hcengineering/hulypulse-client'
|
||||
import presentation, { createPulseClient } from '@hcengineering/presentation'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { get } from 'svelte/store'
|
||||
import { myInfo } from './stores'
|
||||
import love, { type Room } from '@hcengineering/love'
|
||||
import InviteRequestPopup from './components/meeting/invites/InviteRequestPopup.svelte'
|
||||
import InviteResponsePopup from './components/meeting/invites/InviteResponsePopup.svelte'
|
||||
import { joinOrCreateMeetingByInvite } from './meetings'
|
||||
|
||||
export const inviteRequestSecondsToLive = 5
|
||||
const responseSecondsToLive = 2
|
||||
|
||||
const pulsePrefix = 'love/invite'
|
||||
|
||||
export interface InviteRequest {
|
||||
from: Ref<Person>
|
||||
meetingId: string
|
||||
}
|
||||
|
||||
export interface InviteResponse {
|
||||
from: Ref<Person>
|
||||
meetingId: string
|
||||
accept: boolean
|
||||
}
|
||||
|
||||
const requestPopupCategory = 'inviteRequest'
|
||||
let unsubscribeResponse: UnsubscribeCallback | undefined
|
||||
let requestPopup: PopupResult | undefined
|
||||
let requestPersons: Array<Ref<Person>> | undefined
|
||||
|
||||
export async function subscribeInviteResponses (): Promise<void> {
|
||||
const client = await createPulseClient()
|
||||
if (client === undefined) return
|
||||
|
||||
const currentPerson = getCurrentEmployee()
|
||||
|
||||
unsubscribeResponse = await client.subscribe(
|
||||
`${getPulsePrefix()}/response/${currentPerson}/`,
|
||||
(key: string, inviteResponse: InviteResponse | undefined) => {
|
||||
void onInviteResponse(key, inviteResponse)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export async function unsubscribeInviteResponses (): Promise<void> {
|
||||
if (unsubscribeResponse !== undefined) {
|
||||
await unsubscribeResponse()
|
||||
}
|
||||
}
|
||||
|
||||
export function sendInvites (persons: Array<Ref<Person>>): void {
|
||||
closeInvitesPopup()
|
||||
requestPersons = persons
|
||||
const myParticipation = get(myInfo)
|
||||
const room = myParticipation?.room
|
||||
if (room === undefined || room === love.ids.Reception) return
|
||||
requestPopup = showPopup(InviteRequestPopup, { persons, meetingId: room }, undefined, undefined, undefined, {
|
||||
category: requestPopupCategory,
|
||||
overlay: false,
|
||||
fixed: true
|
||||
})
|
||||
}
|
||||
|
||||
export function closeInvitesPopup (): void {
|
||||
requestPopup?.close()
|
||||
requestPopup = undefined
|
||||
}
|
||||
|
||||
export async function updateInvites (persons: Array<Ref<Person>>, meetingId: string): Promise<void> {
|
||||
const client = await createPulseClient()
|
||||
const currentPerson = getCurrentEmployee()
|
||||
|
||||
if (client === undefined) return
|
||||
|
||||
try {
|
||||
for (const person of persons) {
|
||||
await client.put(
|
||||
`${getPulsePrefix()}/request/${person}/${currentPerson}/${meetingId}`,
|
||||
{ from: currentPerson, meetingId },
|
||||
inviteRequestSecondsToLive
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('failed to put invite info:', error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelInvites (meetingId: string): Promise<void> {
|
||||
const client = await createPulseClient()
|
||||
const currentPerson = getCurrentEmployee()
|
||||
|
||||
if (client === undefined || requestPersons === undefined) return
|
||||
try {
|
||||
for (const person of requestPersons) {
|
||||
await client.delete(`${getPulsePrefix()}/request/${person}/${currentPerson}/${meetingId}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('failed to delete invite info:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function onInviteResponse (_key: string, response: InviteResponse | undefined): Promise<void> {
|
||||
const client = await createPulseClient()
|
||||
if (client === undefined || response === undefined || requestPersons === undefined) return
|
||||
|
||||
const currentPerson = getCurrentEmployee()
|
||||
await client.delete(`${getPulsePrefix()}/request/${response.from}/${currentPerson}/${response.meetingId}`)
|
||||
await client.delete(`${getPulsePrefix()}/response/${currentPerson}/${response.from}/${response.meetingId}`)
|
||||
requestPersons = requestPersons.filter((p) => p !== response.from)
|
||||
if (requestPersons.length === 0) {
|
||||
closeInvitesPopup()
|
||||
} else {
|
||||
requestPopup?.update({ persons: requestPersons })
|
||||
}
|
||||
|
||||
if (!response.accept) return
|
||||
await joinOrCreateMeetingByInvite(response.meetingId as Ref<Room>)
|
||||
}
|
||||
|
||||
const responsePopupCategory = 'inviteResponse'
|
||||
let responsePopup: PopupResult | undefined
|
||||
let unsubscribeRequest: UnsubscribeCallback | undefined
|
||||
let activeRequestKey: string | undefined
|
||||
let activeRequestsMap: Map<string, InviteRequest | undefined>
|
||||
|
||||
export async function subscribeInviteRequests (): Promise<void> {
|
||||
const client = await createPulseClient()
|
||||
if (client === undefined) return
|
||||
|
||||
const currentPerson = getCurrentEmployee()
|
||||
|
||||
activeRequestsMap = new Map<string, InviteRequest>()
|
||||
unsubscribeRequest = await client.subscribe(`${getPulsePrefix()}/request/${currentPerson}/`, onInviteRequest)
|
||||
}
|
||||
|
||||
export async function unsubscribeInviteRequests (): Promise<void> {
|
||||
if (unsubscribeRequest !== undefined) {
|
||||
await unsubscribeRequest()
|
||||
}
|
||||
}
|
||||
|
||||
export async function responseToInviteRequest (accept: boolean): Promise<void> {
|
||||
if (activeRequestKey === undefined) return
|
||||
|
||||
const client = await createPulseClient()
|
||||
if (client === undefined) return
|
||||
|
||||
const activeInvite = activeRequestsMap.get(activeRequestKey)
|
||||
if (activeInvite === undefined) return
|
||||
|
||||
const currentPerson = getCurrentEmployee()
|
||||
await client.put(
|
||||
`${getPulsePrefix()}/response/${activeInvite.from}/${currentPerson}/${activeInvite.meetingId}`,
|
||||
{ accept, from: currentPerson, meetingId: activeInvite.meetingId },
|
||||
responseSecondsToLive
|
||||
)
|
||||
|
||||
responsePopup?.close()
|
||||
responsePopup = undefined
|
||||
if (activeRequestKey === undefined) return
|
||||
activeRequestsMap.set(activeRequestKey, undefined)
|
||||
activeRequestKey = undefined
|
||||
|
||||
if (!accept) return
|
||||
await joinOrCreateMeetingByInvite(activeInvite.meetingId as Ref<Room>)
|
||||
}
|
||||
|
||||
function onInviteRequest (key: string, request: InviteRequest | undefined): void {
|
||||
if (request === undefined) {
|
||||
activeRequestsMap.delete(key)
|
||||
if (activeRequestKey === key) {
|
||||
activeRequestKey = undefined
|
||||
}
|
||||
} else if (!activeRequestsMap.has(key)) {
|
||||
activeRequestsMap.set(key, request)
|
||||
}
|
||||
let keyChanged: boolean = false
|
||||
if (activeRequestKey === undefined && activeRequestsMap.size > 0) {
|
||||
const requests = Array.from(activeRequestsMap.entries())
|
||||
const unprocessedRequest = requests.find((v: [string, InviteRequest | undefined]) => v[1] !== undefined)
|
||||
if (unprocessedRequest !== undefined) {
|
||||
activeRequestKey = unprocessedRequest[0]
|
||||
keyChanged = true
|
||||
}
|
||||
}
|
||||
if (activeRequestKey === undefined) {
|
||||
responsePopup?.close()
|
||||
responsePopup = undefined
|
||||
return
|
||||
}
|
||||
|
||||
if (responsePopup === undefined) {
|
||||
responsePopup = showPopup(
|
||||
InviteResponsePopup,
|
||||
{ invite: activeRequestsMap.get(activeRequestKey) },
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
category: responsePopupCategory,
|
||||
overlay: false,
|
||||
fixed: true
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
if (keyChanged) {
|
||||
responsePopup?.update({ invite: activeRequestsMap.get(activeRequestKey) })
|
||||
}
|
||||
}
|
||||
|
||||
function getPulsePrefix (): string {
|
||||
const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? ''
|
||||
return `${workspace}/${pulsePrefix}`
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
//
|
||||
// Copyright © 2025 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.
|
||||
//
|
||||
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import { getCurrentEmployee, type Person } from '@hcengineering/contact'
|
||||
import { type PopupResult, showPopup } from '@hcengineering/ui'
|
||||
import { type UnsubscribeCallback } from '@hcengineering/hulypulse-client'
|
||||
import presentation, { createPulseClient } from '@hcengineering/presentation'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { type Room } from '@hcengineering/love'
|
||||
import { joinOrCreateMeetingByInvite } from './meetings'
|
||||
import JoinRequestPopup from './components/meeting/invites/JoinRequestPopup.svelte'
|
||||
import JoinResponsePopup from './components/meeting/invites/JoinResponsePopup.svelte'
|
||||
|
||||
export const joinRequestSecondsToLive = 5
|
||||
const responseSecondsToLive = 2
|
||||
|
||||
const pulsePrefix = 'love/join'
|
||||
|
||||
export interface JoinRequest {
|
||||
from: Ref<Person>
|
||||
meetingId: string
|
||||
}
|
||||
|
||||
export interface JoinResponse {
|
||||
meetingId: string
|
||||
accept: boolean
|
||||
}
|
||||
|
||||
const requestPopupCategory = 'joinRequest'
|
||||
let unsubscribeResponse: UnsubscribeCallback | undefined
|
||||
let requestPopup: PopupResult | undefined
|
||||
let requestMeetingId: string | undefined
|
||||
|
||||
export async function subscribeJoinResponses (): Promise<void> {
|
||||
const client = await createPulseClient()
|
||||
const currentPerson = getCurrentEmployee()
|
||||
|
||||
if (client === undefined) return
|
||||
|
||||
unsubscribeResponse = await client.subscribe(
|
||||
`${getPulsePrefix()}/response/${currentPerson}/`,
|
||||
(key: string, inviteResponse: JoinResponse | undefined) => {
|
||||
void onJoinResponse(key, inviteResponse)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export async function unsubscribeJoinResponses (): Promise<void> {
|
||||
if (unsubscribeResponse !== undefined) {
|
||||
await unsubscribeResponse()
|
||||
}
|
||||
}
|
||||
|
||||
export function sendJoinRequest (meetingId: string): void {
|
||||
closeJoinRequestPopup()
|
||||
requestMeetingId = meetingId
|
||||
requestPopup = showPopup(JoinRequestPopup, { meetingId }, undefined, undefined, undefined, {
|
||||
category: requestPopupCategory,
|
||||
overlay: false,
|
||||
fixed: true
|
||||
})
|
||||
}
|
||||
|
||||
export function closeJoinRequestPopup (): void {
|
||||
requestPopup?.close()
|
||||
requestPopup = undefined
|
||||
}
|
||||
|
||||
export async function updateJoinRequest (): Promise<void> {
|
||||
const client = await createPulseClient()
|
||||
const currentPerson = getCurrentEmployee()
|
||||
|
||||
if (client === undefined) return
|
||||
|
||||
try {
|
||||
await client.put(
|
||||
`${getPulsePrefix()}/request/${requestMeetingId}/${currentPerson}`,
|
||||
{ from: currentPerson, meetingId: requestMeetingId },
|
||||
joinRequestSecondsToLive
|
||||
)
|
||||
} catch (error) {
|
||||
console.warn('failed to put joinRequest:', error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelJoinRequest (): Promise<void> {
|
||||
const client = await createPulseClient()
|
||||
const currentPerson = getCurrentEmployee()
|
||||
|
||||
if (client === undefined) return
|
||||
try {
|
||||
await client.delete(`${getPulsePrefix()}/request/${requestMeetingId}/${currentPerson}`)
|
||||
} catch (error) {
|
||||
console.warn('failed to delete invite info:', error)
|
||||
}
|
||||
requestMeetingId = undefined
|
||||
}
|
||||
|
||||
async function onJoinResponse (_key: string, response: JoinResponse | undefined): Promise<void> {
|
||||
const client = await createPulseClient()
|
||||
if (
|
||||
client === undefined ||
|
||||
response === undefined ||
|
||||
requestMeetingId === undefined ||
|
||||
requestMeetingId !== response.meetingId
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentPerson = getCurrentEmployee()
|
||||
await client.delete(`${getPulsePrefix()}/request/${requestMeetingId}/${currentPerson}`)
|
||||
await client.delete(`${getPulsePrefix()}/response/${currentPerson}/${requestMeetingId}`)
|
||||
closeJoinRequestPopup()
|
||||
if (!response.accept) return
|
||||
await joinOrCreateMeetingByInvite(response.meetingId as Ref<Room>)
|
||||
}
|
||||
|
||||
const responsePopupCategory = 'joinResponse'
|
||||
let responsePopup: PopupResult | undefined
|
||||
let unsubscribeRequest: UnsubscribeCallback | undefined
|
||||
let activeRequestKey: string | undefined
|
||||
let activeRequestsMap: Map<string, JoinRequest | undefined> | undefined
|
||||
|
||||
export async function subscribeJoinRequests (meetingId: string | undefined): Promise<void> {
|
||||
if (meetingId === undefined) return
|
||||
|
||||
const client = await createPulseClient()
|
||||
if (client === undefined) return
|
||||
|
||||
activeRequestsMap = new Map<string, JoinRequest>()
|
||||
unsubscribeRequest = await client.subscribe(`${getPulsePrefix()}/request/${meetingId}/`, onJoinRequest)
|
||||
}
|
||||
|
||||
export async function unsubscribeJoinRequests (): Promise<void> {
|
||||
activeRequestsMap = undefined
|
||||
if (unsubscribeRequest !== undefined) {
|
||||
await unsubscribeRequest()
|
||||
}
|
||||
}
|
||||
|
||||
export async function responseToJoinRequest (joinRequest: JoinRequest, accept: boolean): Promise<void> {
|
||||
if (activeRequestKey === undefined) return
|
||||
|
||||
const client = await createPulseClient()
|
||||
if (client === undefined || activeRequestsMap === undefined) return
|
||||
|
||||
const activeRequest = activeRequestsMap.get(activeRequestKey)
|
||||
if (activeRequest === undefined) return
|
||||
|
||||
await client.put(
|
||||
`${getPulsePrefix()}/response/${joinRequest.from}/${joinRequest.meetingId}`,
|
||||
{ accept, meetingId: joinRequest.meetingId },
|
||||
responseSecondsToLive
|
||||
)
|
||||
|
||||
responsePopup?.close()
|
||||
responsePopup = undefined
|
||||
if (activeRequestKey === undefined) return
|
||||
activeRequestsMap.set(activeRequestKey, undefined)
|
||||
activeRequestKey = undefined
|
||||
|
||||
if (!accept) return
|
||||
await joinOrCreateMeetingByInvite(joinRequest.meetingId as Ref<Room>)
|
||||
}
|
||||
|
||||
function onJoinRequest (key: string, request: JoinRequest | undefined): void {
|
||||
if (activeRequestsMap === undefined) return
|
||||
if (request === undefined) {
|
||||
activeRequestsMap.delete(key)
|
||||
if (activeRequestKey === key) {
|
||||
activeRequestKey = undefined
|
||||
}
|
||||
} else if (!activeRequestsMap.has(key)) {
|
||||
activeRequestsMap.set(key, request)
|
||||
}
|
||||
let keyChanged: boolean = false
|
||||
if (activeRequestKey === undefined && activeRequestsMap.size > 0) {
|
||||
const requests = Array.from(activeRequestsMap.entries())
|
||||
const unprocessedRequest = requests.find((v: [string, JoinRequest | undefined]) => v[1] !== undefined)
|
||||
if (unprocessedRequest !== undefined) {
|
||||
activeRequestKey = unprocessedRequest[0]
|
||||
keyChanged = true
|
||||
}
|
||||
}
|
||||
if (activeRequestKey === undefined) {
|
||||
responsePopup?.close()
|
||||
responsePopup = undefined
|
||||
return
|
||||
}
|
||||
|
||||
if (responsePopup === undefined) {
|
||||
responsePopup = showPopup(
|
||||
JoinResponsePopup,
|
||||
{ request: activeRequestsMap.get(activeRequestKey) },
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
category: responsePopupCategory,
|
||||
overlay: false,
|
||||
fixed: true
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
if (keyChanged) {
|
||||
responsePopup?.update({ invite: activeRequestsMap.get(activeRequestKey) })
|
||||
}
|
||||
}
|
||||
|
||||
function getPulsePrefix (): string {
|
||||
const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? ''
|
||||
return `${workspace}/${pulsePrefix}`
|
||||
}
|
||||
@@ -1,16 +1,6 @@
|
||||
import core, { AccountRole, getCurrentAccount, type Ref } from '@hcengineering/core'
|
||||
import love, {
|
||||
getFreeRoomPlace,
|
||||
MeetingStatus,
|
||||
type Room,
|
||||
RoomType,
|
||||
isOffice,
|
||||
RoomAccess,
|
||||
RequestStatus,
|
||||
type JoinRequest,
|
||||
type Invite
|
||||
} from '@hcengineering/love'
|
||||
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import love, { getFreeRoomPlace, MeetingStatus, type Room, RoomType, isOffice, RoomAccess } from '@hcengineering/love'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
closeMeetingMinutes,
|
||||
getLiveKitEndpoint,
|
||||
@@ -25,10 +15,9 @@ import { infos, myInfo, myOffice, rooms } from './stores'
|
||||
import { getCurrentEmployee, type Person } from '@hcengineering/contact'
|
||||
import { getPersonByPersonRef } from '@hcengineering/contact-resources'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { sendJoinRequest, unsubscribeJoinRequests } from './joinRequests'
|
||||
|
||||
export let currentMeetingRoom: Ref<Room> | undefined
|
||||
const requestsQuery = createQuery(true)
|
||||
const invitesQuery = createQuery(true)
|
||||
|
||||
export async function createMeeting (room: Room): Promise<void> {
|
||||
if (room.access === RoomAccess.DND) return
|
||||
@@ -37,7 +26,7 @@ export async function createMeeting (room: Room): Promise<void> {
|
||||
const currentPerson = await getPersonByPersonRef(me)
|
||||
|
||||
if (isOffice(room) && room.person !== currentPerson?._id) {
|
||||
await sendJoinRequest(room)
|
||||
sendJoinRequest(room._id)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -79,6 +68,7 @@ export async function leaveMeeting (): Promise<void> {
|
||||
}
|
||||
}
|
||||
await liveKitClient.disconnect()
|
||||
await unsubscribeJoinRequests()
|
||||
closeMeetingMinutes()
|
||||
currentMeetingRoom = undefined
|
||||
}
|
||||
@@ -88,17 +78,18 @@ export async function joinMeeting (room: Room): Promise<void> {
|
||||
|
||||
const isGuest = getCurrentAccount().role === AccountRole.Guest
|
||||
if (room.access === RoomAccess.Knock || isOffice(room) || isGuest) {
|
||||
await sendJoinRequest(room)
|
||||
sendJoinRequest(room._id)
|
||||
return
|
||||
}
|
||||
|
||||
await connectToMeeting(room)
|
||||
}
|
||||
|
||||
export async function acceptInvite (invite: Invite): Promise<void> {
|
||||
export async function joinOrCreateMeetingByInvite (roomId: Ref<Room>): Promise<void> {
|
||||
if (currentMeetingRoom === roomId) return
|
||||
|
||||
const client = getClient()
|
||||
await client.update(invite, { status: RequestStatus.Approved })
|
||||
const room = getRoomById(invite.room)
|
||||
const room = getRoomById(roomId)
|
||||
|
||||
if (room === undefined) return
|
||||
|
||||
@@ -113,111 +104,6 @@ export async function acceptInvite (invite: Invite): Promise<void> {
|
||||
await connectToMeeting(room)
|
||||
}
|
||||
|
||||
export async function rejectInvite (invite: Invite): Promise<void> {
|
||||
const client = getClient()
|
||||
await client.update(invite, { status: RequestStatus.Rejected })
|
||||
}
|
||||
|
||||
export async function cancelInvites (invites: Invite[]): Promise<void> {
|
||||
const client = getClient()
|
||||
for (const invite of invites) {
|
||||
if (invite.status === RequestStatus.Pending) {
|
||||
await client.remove(invite)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendInvites (persons: Array<Ref<Person>>): Promise<void> {
|
||||
const myParticipation = get(myInfo)
|
||||
const room = myParticipation?.room
|
||||
if (room === undefined || room === love.ids.Reception) return
|
||||
const client = getClient()
|
||||
const me = getCurrentEmployee()
|
||||
const inviteIds: Array<Ref<Invite>> = []
|
||||
for (const person of persons) {
|
||||
inviteIds.push(
|
||||
await client.createDoc(love.class.Invite, core.space.Workspace, {
|
||||
target: person,
|
||||
room,
|
||||
status: RequestStatus.Pending,
|
||||
from: me
|
||||
})
|
||||
)
|
||||
}
|
||||
invitesQuery.query(love.class.Invite, { from: me, _id: { $in: inviteIds } }, (res) => {
|
||||
for (const invite of res) {
|
||||
if (invite === undefined || invite.status !== RequestStatus.Approved) {
|
||||
continue
|
||||
}
|
||||
invitesQuery.unsubscribe()
|
||||
const room = getRoomById(invite.room)
|
||||
if (room === undefined) return
|
||||
void connectToMeeting(room)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function acceptJoinRequest (request: JoinRequest): Promise<void> {
|
||||
const client = getClient()
|
||||
const room = getRoomById(request.room)
|
||||
|
||||
if (room === undefined) return
|
||||
|
||||
const meeting = await client.findOne(love.class.MeetingMinutes, {
|
||||
attachedTo: room._id,
|
||||
status: MeetingStatus.Active
|
||||
})
|
||||
if (meeting === undefined) {
|
||||
await createMeetingDocument(room)
|
||||
}
|
||||
|
||||
await client.update(request, { status: RequestStatus.Approved })
|
||||
await connectToMeeting(room)
|
||||
}
|
||||
|
||||
export async function rejectJoinRequest (request: JoinRequest): Promise<void> {
|
||||
const client = getClient()
|
||||
await client.update(request, { status: RequestStatus.Rejected })
|
||||
}
|
||||
|
||||
export async function cancelJoinRequest (request: JoinRequest): Promise<void> {
|
||||
if (request.status === RequestStatus.Pending) {
|
||||
const client = getClient()
|
||||
await client.remove(request)
|
||||
}
|
||||
}
|
||||
|
||||
async function sendJoinRequest (room: Room): Promise<void> {
|
||||
if (room.access === RoomAccess.DND) return
|
||||
|
||||
const me = getCurrentEmployee()
|
||||
const currentPerson = await getPersonByPersonRef(me)
|
||||
|
||||
if (currentPerson == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isOffice(room) && room.person === currentPerson._id) {
|
||||
return
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
const _id = await client.createDoc(love.class.JoinRequest, core.space.Workspace, {
|
||||
person: currentPerson._id,
|
||||
room: room._id,
|
||||
status: RequestStatus.Pending
|
||||
})
|
||||
requestsQuery.query(love.class.JoinRequest, { person: me, _id }, (res) => {
|
||||
const req = res[0]
|
||||
if (req === undefined) return
|
||||
if (req.status === RequestStatus.Pending) return
|
||||
requestsQuery.unsubscribe()
|
||||
if (req.status === RequestStatus.Approved) {
|
||||
void connectToMeeting(room)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function kick (person: Ref<Person>): Promise<void> {
|
||||
const client = getClient()
|
||||
const allRooms = get(rooms)
|
||||
|
||||
@@ -3,11 +3,8 @@ import { getCurrentEmployee } from '@hcengineering/contact'
|
||||
import { getPersonRefByPersonId } from '@hcengineering/contact-resources'
|
||||
import { type Ref } from '@hcengineering/core'
|
||||
import {
|
||||
RequestStatus,
|
||||
type DevicesPreference,
|
||||
type Floor,
|
||||
type Invite,
|
||||
type JoinRequest,
|
||||
type MeetingMinutes,
|
||||
type Office,
|
||||
type ParticipantInfo,
|
||||
@@ -43,16 +40,6 @@ export const activeFloor = derived([rooms, myInfo, myOffice], ([rooms, myInfo, m
|
||||
}
|
||||
return res ?? love.ids.MainFloor
|
||||
})
|
||||
export const myRequests = writable<JoinRequest[]>([])
|
||||
export const invites = writable<Invite[]>([])
|
||||
export const myInvites = derived([invites, myInfo], ([val, info]) => {
|
||||
const personId = getCurrentEmployee()
|
||||
return val.filter((p) => p.target === personId && info?.room !== p.room)
|
||||
})
|
||||
export const activeInvites = derived(invites, (val) => {
|
||||
const personId = getCurrentEmployee()
|
||||
return val.filter((p) => p.from === personId)
|
||||
})
|
||||
|
||||
export const myPreferences = writable<DevicesPreference | undefined>()
|
||||
export let $myPreferences: DevicesPreference | undefined
|
||||
@@ -79,9 +66,7 @@ const officeLoaded = writable(false)
|
||||
const query = createQuery(true)
|
||||
const statusQuery = createQuery(true)
|
||||
const floorsQuery = createQuery(true)
|
||||
const requestsQuery = createQuery(true)
|
||||
const preferencesQuery = createQuery(true)
|
||||
const invitesQuery = createQuery(true)
|
||||
|
||||
onClient(() => {
|
||||
const roomPromise = new Promise<void>((resolve) =>
|
||||
@@ -102,16 +87,6 @@ onClient(() => {
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
const requestPromise = new Promise<void>((resolve) =>
|
||||
requestsQuery.query(
|
||||
love.class.JoinRequest,
|
||||
{ person: getCurrentEmployee(), status: RequestStatus.Pending },
|
||||
(res) => {
|
||||
myRequests.set(res)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
)
|
||||
const preferencePromise = new Promise<void>((resolve) =>
|
||||
preferencesQuery.query(love.class.DevicesPreference, {}, (res) => {
|
||||
myPreferences.set(res[0])
|
||||
@@ -120,18 +95,9 @@ onClient(() => {
|
||||
})
|
||||
)
|
||||
|
||||
const invitesPromise = new Promise<void>((resolve) =>
|
||||
invitesQuery.query(love.class.Invite, { status: RequestStatus.Pending }, (res) => {
|
||||
invites.set(res)
|
||||
resolve()
|
||||
})
|
||||
)
|
||||
|
||||
void Promise.all([roomPromise, infoPromise, floorPromise, requestPromise, preferencePromise, invitesPromise]).then(
|
||||
() => {
|
||||
officeLoaded.set(true)
|
||||
}
|
||||
)
|
||||
void Promise.all([roomPromise, infoPromise, floorPromise, preferencePromise]).then(() => {
|
||||
officeLoaded.set(true)
|
||||
})
|
||||
})
|
||||
|
||||
export async function waitForOfficeLoaded (): Promise<void> {
|
||||
|
||||
@@ -21,7 +21,6 @@ import core, {
|
||||
import login from '@hcengineering/login'
|
||||
import {
|
||||
isOffice,
|
||||
type JoinRequest,
|
||||
LoveEvents,
|
||||
loveId,
|
||||
type Meeting,
|
||||
@@ -280,8 +279,6 @@ export async function navigateToMeetingMinutes (room: Room): Promise<void> {
|
||||
await navigateToOfficeDoc(room)
|
||||
}
|
||||
|
||||
export const joinRequest: Ref<JoinRequest> | undefined = undefined
|
||||
|
||||
export function calculateFloorSize (_rooms: Room[], _preview?: boolean): number {
|
||||
let fH: number = 5
|
||||
_rooms.forEach((room) => {
|
||||
|
||||
@@ -8,8 +8,6 @@ import { Widget } from '@hcengineering/workbench'
|
||||
import {
|
||||
DevicesPreference,
|
||||
Floor,
|
||||
Invite,
|
||||
JoinRequest,
|
||||
Meeting,
|
||||
MeetingMinutes,
|
||||
MeetingSchedule,
|
||||
@@ -28,10 +26,8 @@ const love = plugin(loveId, {
|
||||
Floor: '' as Ref<Class<Floor>>,
|
||||
Office: '' as Ref<Class<Office>>,
|
||||
ParticipantInfo: '' as Ref<Class<ParticipantInfo>>,
|
||||
JoinRequest: '' as Ref<Class<JoinRequest>>,
|
||||
DevicesPreference: '' as Ref<Class<DevicesPreference>>,
|
||||
RoomInfo: '' as Ref<Class<RoomInfo>>,
|
||||
Invite: '' as Ref<Class<Invite>>,
|
||||
MeetingMinutes: '' as Ref<Class<MeetingMinutes>>
|
||||
},
|
||||
mixin: {
|
||||
|
||||
@@ -127,25 +127,6 @@ export interface MeetingSchedule extends Schedule {
|
||||
room: Ref<Room>
|
||||
}
|
||||
|
||||
export enum RequestStatus {
|
||||
Pending,
|
||||
Approved,
|
||||
Rejected
|
||||
}
|
||||
|
||||
export interface JoinRequest extends Doc {
|
||||
person: Ref<Person>
|
||||
room: Ref<Room>
|
||||
status: RequestStatus
|
||||
}
|
||||
|
||||
export interface Invite extends Doc {
|
||||
from: Ref<Person>
|
||||
target: Ref<Person>
|
||||
room: Ref<Room>
|
||||
status: RequestStatus
|
||||
}
|
||||
|
||||
export interface DevicesPreference extends Preference {
|
||||
micEnabled: boolean
|
||||
noiseCancellation: boolean
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { HulypulseClient } from '@hcengineering/hulypulse-client'
|
||||
import { createPulseClient } from '../pulse'
|
||||
import { createPulseClient } from '@hcengineering/presentation'
|
||||
|
||||
let parentElement: HTMLDivElement
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import { getFollowee, publishData, followeeDataSubscribe, followeeDataUnsubscrib
|
||||
|
||||
export { Presence, PresenceAvatars }
|
||||
|
||||
export * from './pulse'
|
||||
export * from './presence'
|
||||
export * from './typing'
|
||||
export * from './types'
|
||||
|
||||
@@ -15,9 +15,7 @@ import { type Employee, type Person } from '@hcengineering/contact'
|
||||
import { type UnsubscribeCallback, type Callback } from '@hcengineering/hulypulse-client'
|
||||
import { type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
|
||||
import { createPulseClient } from './pulse'
|
||||
import presentation, { createPulseClient } from '@hcengineering/presentation'
|
||||
|
||||
export interface PresenceInfo {
|
||||
personId: Ref<Person>
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
import { type Employee, type Person } from '@hcengineering/contact'
|
||||
import { type UnsubscribeCallback, type Callback } from '@hcengineering/hulypulse-client'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import presentation, { createPulseClient } from '@hcengineering/presentation'
|
||||
import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { createPulseClient } from './pulse'
|
||||
|
||||
const typingDelaySeconds = 2
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import contact, { Employee, formatName, getName, Person } from '@hcengineering/contact'
|
||||
import contact, { Employee, getName, Person } from '@hcengineering/contact'
|
||||
import core, {
|
||||
type AccountUuid,
|
||||
combineAttributes,
|
||||
@@ -31,28 +31,18 @@ import core, {
|
||||
UserStatus
|
||||
} from '@hcengineering/core'
|
||||
import love, {
|
||||
Invite,
|
||||
isOffice,
|
||||
JoinRequest,
|
||||
loveId,
|
||||
MeetingMinutes,
|
||||
MeetingStatus,
|
||||
Office,
|
||||
ParticipantInfo,
|
||||
RequestStatus,
|
||||
Room,
|
||||
RoomAccess,
|
||||
RoomInfo
|
||||
} from '@hcengineering/love'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { getMetadata, translate } from '@hcengineering/platform'
|
||||
import { getSocialStrings } from '@hcengineering/server-contact'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import {
|
||||
createPushNotification,
|
||||
getNotificationProviderControl,
|
||||
isAllowed
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
import view from '@hcengineering/view'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
@@ -290,29 +280,6 @@ async function getRoomName (control: TriggerControl, roomId: Ref<Room>): Promise
|
||||
return room.name
|
||||
}
|
||||
|
||||
async function rejectJoinRequests (info: ParticipantInfo, control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {})
|
||||
const oldRoomInfo = roomInfos.find((ri) => ri.persons.includes(info.person))
|
||||
if (oldRoomInfo !== undefined) {
|
||||
const restPersons = oldRoomInfo.persons.filter((p) => p !== info.person)
|
||||
if (restPersons.length === 0) {
|
||||
const requests = await control.findAll(control.ctx, love.class.JoinRequest, {
|
||||
room: oldRoomInfo.room,
|
||||
status: RequestStatus.Pending
|
||||
})
|
||||
for (const request of requests) {
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(love.class.JoinRequest, core.space.Workspace, request._id, {
|
||||
status: RequestStatus.Rejected
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
async function setDefaultRoomAccess (info: ParticipantInfo, control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {})
|
||||
@@ -387,7 +354,6 @@ export async function OnParticipantInfo (txes: Tx[], control: TriggerControl): P
|
||||
if (info === undefined) {
|
||||
continue
|
||||
}
|
||||
result.push(...(await rejectJoinRequests(info, control)))
|
||||
result.push(...(await setDefaultRoomAccess(info, control)))
|
||||
result.push(...(await roomJoinHandler(info, control)))
|
||||
}
|
||||
@@ -395,117 +361,6 @@ export async function OnParticipantInfo (txes: Tx[], control: TriggerControl): P
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnKnock (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const actualTx = tx as TxCreateDoc<JoinRequest>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const request = TxProcessor.createDoc2Doc(actualTx)
|
||||
if (request.status === RequestStatus.Pending) {
|
||||
const roomInfo = (await control.findAll(control.ctx, love.class.RoomInfo, { room: request.room }))[0]
|
||||
if (roomInfo !== undefined) {
|
||||
const from = (await control.findAll(control.ctx, contact.class.Person, { _id: request.person }))[0]
|
||||
if (from === undefined) {
|
||||
continue
|
||||
}
|
||||
const type = await control.modelDb.findOne(notification.class.NotificationType, {
|
||||
_id: love.ids.KnockNotification
|
||||
})
|
||||
if (type === undefined) {
|
||||
continue
|
||||
}
|
||||
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
|
||||
_id: notification.providers.PushNotificationProvider
|
||||
})
|
||||
if (provider === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const notificationControl = await getNotificationProviderControl(control.ctx, control)
|
||||
for (const user of roomInfo.persons) {
|
||||
const socialStrings = await getSocialStrings(control, user)
|
||||
if (socialStrings.length === 0) continue
|
||||
if (isAllowed(control, socialStrings, type, provider, notificationControl)) {
|
||||
const path = [workbenchId, control.workspace.url, loveId]
|
||||
const title = await translate(love.string.KnockingLabel, {})
|
||||
const body = await translate(love.string.IsKnocking, {
|
||||
name: formatName(from.name, control.branding?.lastNameFirst)
|
||||
})
|
||||
const employee = await control.findAll(
|
||||
control.ctx,
|
||||
contact.mixin.Employee,
|
||||
{ _id: user as Ref<Employee> },
|
||||
{ limit: 1 }
|
||||
)
|
||||
const account = employee[0]?.personUuid
|
||||
if (account === undefined) continue
|
||||
|
||||
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
|
||||
user: account
|
||||
})
|
||||
await createPushNotification(control, account, title, body, request._id, subscriptions, from, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnInvite (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const actualTx = tx as TxCreateDoc<Invite>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const invite = TxProcessor.createDoc2Doc(actualTx)
|
||||
if (invite.status === RequestStatus.Pending) {
|
||||
const target = (
|
||||
await control.findAll(
|
||||
control.ctx,
|
||||
contact.mixin.Employee,
|
||||
{ _id: invite.target as Ref<Employee> },
|
||||
{ limit: 1 }
|
||||
)
|
||||
)[0]
|
||||
if (target === undefined) {
|
||||
continue
|
||||
}
|
||||
const from = (await control.findAll(control.ctx, contact.class.Person, { _id: invite.from }))[0]
|
||||
const type = await control.modelDb.findOne(notification.class.NotificationType, {
|
||||
_id: love.ids.InviteNotification
|
||||
})
|
||||
if (type === undefined) {
|
||||
continue
|
||||
}
|
||||
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
|
||||
_id: notification.providers.PushNotificationProvider
|
||||
})
|
||||
if (provider === undefined) {
|
||||
continue
|
||||
}
|
||||
const notificationControl = await getNotificationProviderControl(control.ctx, control)
|
||||
const socialStrings = await getSocialStrings(control, target._id)
|
||||
if (isAllowed(control, socialStrings, type, provider, notificationControl)) {
|
||||
const path = [workbenchId, control.workspace.url, loveId]
|
||||
const title = await translate(love.string.InivitingLabel, {})
|
||||
const body =
|
||||
from !== undefined
|
||||
? await translate(love.string.InvitingYou, {
|
||||
name: formatName(from.name, control.branding?.lastNameFirst)
|
||||
})
|
||||
: await translate(love.string.InivitingLabel, {})
|
||||
const account = target?.personUuid
|
||||
if (account === undefined) continue
|
||||
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
|
||||
user: account
|
||||
})
|
||||
await createPushNotification(control, account, title, body, invite._id, subscriptions, from, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export async function meetingMinutesHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const meetingMinutes = doc as MeetingMinutes
|
||||
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
|
||||
@@ -587,8 +442,6 @@ export default async () => ({
|
||||
OnEmployee,
|
||||
OnUserStatus,
|
||||
OnParticipantInfo,
|
||||
OnRoomInfo,
|
||||
OnKnock,
|
||||
OnInvite
|
||||
OnRoomInfo
|
||||
}
|
||||
})
|
||||
|
||||
@@ -21,7 +21,6 @@ export default plugin(serverLoveId, {
|
||||
OnUserStatus: '' as Resource<TriggerFunc>,
|
||||
OnParticipantInfo: '' as Resource<TriggerFunc>,
|
||||
OnRoomInfo: '' as Resource<TriggerFunc>,
|
||||
OnInvite: '' as Resource<TriggerFunc>,
|
||||
OnKnock: '' as Resource<TriggerFunc>
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user