Allow kick participant from office ()

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2025-01-09 17:39:22 +05:00 committed by GitHub
parent 8c3dd61e71
commit 5924c4aef5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 164 additions and 74 deletions

View File

@ -89,4 +89,9 @@
<path d="M24 21C24 20.4477 24.4477 20 25 20C25.5523 20 26 20.4477 26 21V24H29C29.5523 24 30 24.4477 30 25C30 25.5523 29.5523 26 29 26H26V29C26 29.5523 25.5523 30 25 30C24.4477 30 24 29.5523 24 29V26H21C20.4477 26 20 25.5523 20 25C20 24.4477 20.4477 24 21 24H24V21Z" />
<path d="M13 20C9.13401 20 6 23.134 6 27V29C6 29.5523 6.44772 30 7 30C7.55228 30 8 29.5523 8 29V27C8 24.2386 10.2386 22 13 22H17C17.5523 22 18 21.5523 18 21C18 20.4477 17.5523 20 17 20H13Z" />
</symbol>
<symbol id="kick" viewBox="0 0 32 32">
<path fill-rule="evenodd" clip-rule="evenodd" d="M24 10C24 14.4183 20.4183 18 16 18C11.5817 18 8 14.4183 8 10C8 5.58172 11.5817 2 16 2C20.4183 2 24 5.58172 24 10ZM16 16C19.3137 16 22 13.3137 22 10C22 6.68629 19.3137 4 16 4C12.6863 4 10 6.68629 10 10C10 13.3137 12.6863 16 16 16Z" />
<path d="M21.7071 20.2931C21.3166 19.9026 20.6834 19.9026 20.2929 20.2931C19.9024 20.6837 19.9024 21.3168 20.2929 21.7074L23.5858 25.0002L20.2929 28.2931C19.9024 28.6837 19.9024 29.3168 20.2929 29.7074C20.6761 30.0906 21.2929 30.0977 21.6849 29.7289L25 26.4138L28.2929 29.7074C28.6834 30.0979 29.3166 30.0979 29.7071 29.7074C30.0976 29.3168 30.0976 28.6837 29.7071 28.2931L26.4142 25.0002L29.7071 21.7074C30.0976 21.3168 30.0976 20.6837 29.7071 20.2931C29.3166 19.9026 28.6834 19.9026 28.2929 20.2931L25 23.586L21.7071 20.2931Z" />
<path d="M13 20.0002C9.13401 20.0002 6 23.1343 6 27.0002V29.0002C6 29.5525 6.44772 30.0002 7 30.0002C7.55228 30.0002 8 29.5525 8 29.0002V27.0002C8 24.2388 10.2386 22.0002 13 22.0002H17C17.5523 22.0002 18 21.5525 18 21.0002C18 20.448 17.5523 20.0002 17 20.0002H13Z" />
</symbol>
</svg>

Before

(image error) Size: 14 KiB

After

(image error) Size: 15 KiB

View File

@ -75,10 +75,12 @@
"JoinMeeting": "Připojit se ke schůzce",
"MeetingStart": "Začátek schůzky",
"MeetingEnd": "Konec schůzky",
"EndMeeting": "Ukončit schůzku",
"Status": "Stav",
"Active": "Aktivní",
"Finished": "Dokončeno",
"StartWithRecording": "Začít s nahráváním",
"Language": "Jazyk"
"Language": "Jazyk",
"Kick": "Vyhodit"
}
}

View File

@ -75,10 +75,12 @@
"JoinMeeting": "Join meeting",
"MeetingStart": "Meeting start",
"MeetingEnd": "Meeting end",
"EndMeeting": "End meeting",
"Status": "Status",
"Active": "Active",
"Finished": "Finished",
"StartWithRecording": "Start with recording",
"Language": "Language"
"Language": "Language",
"Kick": "Kick"
}
}

View File

@ -75,10 +75,12 @@
"JoinMeeting": "Unirse a la reunión",
"MeetingStart": "Inicio de la reunión",
"MeetingEnd": "Fin de la reunión",
"EndMeeting": "Terminar reunión",
"Status": "Estado",
"Active": "Activo",
"Finished": "Terminado",
"StartWithRecording": "Iniciar con grabación",
"Language": "Idioma"
"Language": "Idioma",
"Kick": "Expulsar"
}
}

View File

@ -75,10 +75,12 @@
"JoinMeeting": "Rejoindre la réunion",
"MeetingStart": "Début de la réunion",
"MeetingEnd": "Fin de la réunion",
"EndMeeting": "Terminer la réunion",
"Status": "Statut",
"Active": "Actif",
"Finished": "Terminé",
"StartWithRecording": "Démarrer avec l'enregistrement",
"Language": "Langue"
"Language": "Langue",
"Kick": "Expulser"
}
}

View File

@ -75,10 +75,12 @@
"JoinMeeting": "Unisciti alla riunione",
"MeetingStart": "Inizio riunione",
"MeetingEnd": "Fine riunione",
"EndMeeting": "Termina riunione",
"Status": "Stato",
"Active": "Attivo",
"Finished": "Finito",
"StartWithRecording": "Inizia con la registrazione",
"Language": "Lingua"
"Language": "Lingua",
"Kick": "Espellere"
}
}

View File

@ -75,10 +75,12 @@
"JoinMeeting": "Participar na reunião",
"MeetingStart": "Início da reunião",
"MeetingEnd": "Fim da reunião",
"EndMeeting": "Terminar reunião",
"Status": "Estado",
"Active": "Ativo",
"Finished": "Finalizado",
"StartWithRecording": "Começar com gravação",
"Language": "Idioma"
"Language": "Idioma",
"Kick": "Expulsar"
}
}

View File

@ -75,10 +75,12 @@
"JoinMeeting": "Присоединиться к встрече",
"MeetingStart": "Начало встречи",
"MeetingEnd": "Конец встречи",
"EndMeeting": "Завершить встречу",
"Status": "Статус",
"Active": "Активно",
"Finished": "Завершено",
"StartWithRecording": "Начинать с записью",
"Language": "Язык"
"Language": "Язык",
"Kick": "Выгнать"
}
}

View File

@ -75,10 +75,12 @@
"JoinMeeting": "加入会议",
"MeetingStart": "会议开始",
"MeetingEnd": "会议结束",
"EndMeeting": "结束会议",
"Status": "状态",
"Active": "活动",
"Finished": "已完成",
"StartWithRecording": "开始录制",
"Language": "语言"
"Language": "语言",
"Kick": "踢出"
}
}

View File

@ -36,7 +36,8 @@ loadMetadata(love.icon, {
StopRecord: `${icons}#stopRecord`,
FullScreen: `${icons}#fullscreen`,
ExitFullScreen: `${icons}#exitfullscreen`,
Invite: `${icons}#invite`
Invite: `${icons}#invite`,
Kick: `${icons}#kick`
})
loadMetadata(love.sound, {
Knock: require('../assets/knock.wav')

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { personByIdStore } from '@hcengineering/contact-resources'
import { IdMap, Ref, toIdMap } from '@hcengineering/core'
import { getCurrentAccount, IdMap, Ref, toIdMap } from '@hcengineering/core'
import {
Invite,
isOffice,
@ -42,9 +42,11 @@
import love from '../plugin'
import { activeInvites, currentRoom, infos, myInfo, myInvites, myOffice, myRequests, rooms } from '../stores'
import {
connectRoom,
createMeetingVideoWidgetTab,
createMeetingWidget,
disconnect,
endMeeting,
getRoomName,
isCurrentInstanceConnected,
leaveRoom,
@ -57,6 +59,7 @@
import RequestingPopup from './RequestingPopup.svelte'
import RoomPopup from './RoomPopup.svelte'
import RoomButton from './RoomButton.svelte'
import { Person, PersonAccount } from '@hcengineering/contact'
const client = getClient()
@ -105,7 +108,7 @@
if (activeRequest === undefined) {
activeRequest = requests.find((r) => r.room === $myInfo?.room)
if (activeRequest !== undefined) {
showPopup(RequestPopup, { request: activeRequest }, myOfficeElement, undefined, undefined, {
showPopup(RequestPopup, { request: activeRequest }, undefined, undefined, undefined, {
category: joinRequestCategory,
overlay: false,
fixed: true
@ -120,7 +123,7 @@
function checkMyRequests (requests: JoinRequest[]): void {
if (requests.length > 0) {
if (myRequestsPopup === undefined) {
myRequestsPopup = showPopup(RequestingPopup, { request: requests[0] }, myOfficeElement, undefined, undefined, {
myRequestsPopup = showPopup(RequestingPopup, { request: requests[0] }, undefined, undefined, undefined, {
category: myJoinRequestCategory,
overlay: false,
fixed: true
@ -134,8 +137,6 @@
$: checkMyRequests($myRequests)
let myOfficeElement: HTMLDivElement
$: checkRequests(requests, $myInfo)
function openRoom (room: Room): (e: MouseEvent) => void {
@ -158,7 +159,7 @@
if (activeInvite === undefined) {
activeInvite = invites[0]
if (activeInvite !== undefined) {
showPopup(InvitePopup, { invite: activeInvite }, myOfficeElement, undefined, undefined, {
showPopup(InvitePopup, { invite: activeInvite }, undefined, undefined, undefined, {
category: inviteCategory,
overlay: false,
fixed: true
@ -173,21 +174,29 @@
infos: ParticipantInfo[],
myInfo: ParticipantInfo | undefined,
myOffice: Office | undefined,
personByIdStore: IdMap<Person>,
isConnected: boolean
): Promise<void> {
if (myOffice !== undefined) {
if (myInfo !== undefined && myInfo.room === myOffice._id) {
const filtered = infos.filter((p) => p.room === myOffice._id && p.person !== myInfo.person)
if (filtered.length === 0) {
if (isConnected) {
await disconnect()
}
if (myInfo !== undefined && myInfo.room === (myOffice?._id ?? love.ids.Reception)) {
if (myOffice === undefined) {
await disconnect()
return
}
const filtered = infos.filter((p) => p.room === myOffice._id && p.person !== myInfo.person)
if (filtered.length === 0) {
if (isConnected) {
await disconnect()
}
} else if (!isConnected) {
const me = getCurrentAccount() as PersonAccount
const myPerson = personByIdStore.get(me.person)
if (myPerson === undefined) return
await connectRoom(0, 0, myInfo, myPerson, myOffice)
}
}
}
$: checkOwnRoomConnection($infos, $myInfo, $myOffice, $isCurrentInstanceConnected)
$: checkOwnRoomConnection($infos, $myInfo, $myOffice, $personByIdStore, $isCurrentInstanceConnected)
const myInvitesCategory = 'myInvites'
@ -196,7 +205,7 @@
function checkActiveInvites (invites: Invite[]): void {
if (invites.length > 0) {
if (myInvitesPopup === undefined) {
myInvitesPopup = showPopup(ActiveInvitesPopup, { invites }, myOfficeElement, undefined, undefined, {
myInvitesPopup = showPopup(ActiveInvitesPopup, { invites }, undefined, undefined, undefined, {
category: myInvitesCategory,
overlay: false,
fixed: true
@ -277,7 +286,11 @@
const beforeUnloadListener = () => {
if ($myInfo !== undefined && $isCurrentInstanceConnected) {
leaveRoom($myInfo, $myOffice)
if ($myOffice !== undefined && $myInfo.room === $myOffice._id) {
endMeeting($myOffice, $rooms, $infos, $myInfo)
} else {
leaveRoom($myInfo, $myOffice)
}
}
}
@ -287,7 +300,7 @@
<div class="flex-row-center flex-gap-2">
{#if activeRooms.length > 0}
<!-- <div class="divider" />-->
{#each activeRooms as active, i}
{#each activeRooms as active}
<RoomButton
label={getRoomName(active, $personByIdStore)}
participants={active.participants}
@ -296,7 +309,7 @@
/>
{/each}
{/if}
{#if reception && receptionParticipants.length > 0}
{#if reception !== undefined && receptionParticipants.length > 0}
{#if activeRooms.length > 0}
<div class="divider" />
{/if}

View File

@ -1,20 +1,32 @@
<script lang="ts">
import { Person } from '@hcengineering/contact'
import { personByIdStore } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { Room, RoomAccess } from '@hcengineering/love'
import { isOffice, Room, RoomAccess } from '@hcengineering/love'
import { ActionIcon } from '@hcengineering/ui'
import love from '../plugin'
import { invite, tryConnect } from '../utils'
import { infos, invites, myInfo, myRequests } from '../stores'
import { personByIdStore } from '@hcengineering/contact-resources'
import { infos, invites, myInfo, myRequests, rooms } from '../stores'
import { invite, kick, tryConnect } from '../utils'
export let room: Room
export let person: Ref<Person>
$: isMyOffice = isOffice(room) && room.person === $myInfo?.person
$: info = $infos.filter((p) => p.room === room._id)
</script>
<div class="p-3 flex-gap-2 antiPopup">
{#if isMyOffice && person !== $myInfo?.person}
<ActionIcon
size={'small'}
label={love.string.Kick}
icon={love.icon.Kick}
action={() => {
kick(person, $rooms, $infos)
}}
/>
{/if}
{#if $myInfo?.room !== room._id}
<ActionIcon
size={'small'}

View File

@ -16,7 +16,7 @@
import { Person, PersonAccount } from '@hcengineering/contact'
import { personByIdStore, UserInfo } from '@hcengineering/contact-resources'
import { IdMap, getCurrentAccount, Ref, Class, Doc } from '@hcengineering/core'
import ui, {
import {
ModernButton,
SplitButton,
IconArrowLeft,
@ -44,8 +44,9 @@
import { getObjectLinkFragment } from '@hcengineering/view-resources'
import { getClient } from '@hcengineering/presentation'
import love from '../plugin'
import { currentRoom, infos, invites, myInfo, myOffice, myRequests, currentMeetingMinutes } from '../stores'
import { currentRoom, infos, invites, myInfo, myOffice, myRequests, currentMeetingMinutes, rooms } from '../stores'
import {
endMeeting,
getRoomName,
isCameraEnabled,
isConnected,
@ -75,7 +76,9 @@
let joined: boolean = false
$: joined = $myInfo?.room === room._id
$: allowLeave = $myInfo?.room !== ($myOffice?._id ?? love.ids.Reception)
$: isMyOffice = $myInfo?.room === $myOffice?._id
$: allowLeave = !isMyOffice && $myInfo?.room !== love.ids.Reception
let info: ParticipantInfo[] = []
$: info = $infos.filter((p) => p.room === room._id)
@ -104,6 +107,13 @@
dispatch('close')
}
async function end (): Promise<void> {
if (isOffice(room) && $myInfo !== undefined) {
await endMeeting(room, $rooms, $infos, $myInfo)
}
dispatch('close')
}
async function connect (): Promise<void> {
await tryConnect($personByIdStore, $myInfo, room, info, $myRequests, $invites)
dispatch('close')
@ -218,17 +228,27 @@
/>
</div>
{/if}
{#if $location.path[2] !== loveId || (joined && allowLeave) || !joined}
{#if $location.path[2] !== loveId || (joined && (allowLeave || isMyOffice)) || !joined}
<div class="btns flex-row-center flex-reverse flex-no-shrink w-full flex-gap-2">
{#if joined && allowLeave}
<ModernButton
label={love.string.LeaveRoom}
icon={love.icon.LeaveRoom}
size={'large'}
kind={'negative'}
on:click={leave}
/>
{:else if !joined}
{#if joined}
{#if allowLeave}
<ModernButton
label={love.string.LeaveRoom}
icon={love.icon.LeaveRoom}
size={'large'}
kind={'negative'}
on:click={leave}
/>
{:else if isMyOffice}
<ModernButton
label={love.string.EndMeeting}
icon={love.icon.LeaveRoom}
size={'large'}
kind={'negative'}
on:click={end}
/>
{/if}
{:else}
<ModernButton
icon={love.icon.EnterRoom}
label={love.string.EnterRoom}

View File

@ -89,7 +89,7 @@
e.stopPropagation()
e.preventDefault()
if (person !== undefined) {
if (room._id === $myInfo?.room || $myInfo === undefined) return
if ($myInfo === undefined) return
showPopup(PersonActionPopup, { room, person: person._id }, eventToHTMLElement(e))
} else {
await openRoom(x, y)

View File

@ -1,10 +1,9 @@
<script lang="ts">
import { pushRootBarComponent, rootBarExtensions } from '@hcengineering/ui'
import { pushRootBarComponent } from '@hcengineering/ui'
import { RemoteParticipant, RemoteTrack, RemoteTrackPublication, RoomEvent, Track } from 'livekit-client'
import { onDestroy, onMount } from 'svelte'
import love from '../plugin'
import { disconnect, isCurrentInstanceConnected, lk } from '../utils'
import { generateId } from '@hcengineering/core'
let parentElement: HTMLDivElement

View File

@ -1,19 +1,22 @@
import aiBot from '@hcengineering/ai-bot'
import { connectMeeting, disconnectMeeting } from '@hcengineering/ai-bot-resources'
import { Analytics } from '@hcengineering/analytics'
import calendar, { type Event, getAllEvents } from '@hcengineering/calendar'
import chunter from '@hcengineering/chunter'
import contact, { getName, type Person, type PersonAccount } from '@hcengineering/contact'
import { personByIdStore } from '@hcengineering/contact-resources'
import core, {
AccountRole,
concatLink,
type Data,
type Doc,
generateId,
getCurrentAccount,
type Hierarchy,
type IdMap,
type Ref,
type Space,
type TxOperations,
type Hierarchy,
type Doc
type TxOperations
} from '@hcengineering/core'
import login from '@hcengineering/login'
import {
@ -24,16 +27,16 @@ import {
LoveEvents,
loveId,
type Meeting,
type MeetingMinutes,
MeetingStatus,
type Office,
type ParticipantInfo,
RequestStatus,
type Room,
RoomAccess,
RoomType,
TranscriptionStatus,
type RoomMetadata,
type MeetingMinutes,
MeetingStatus
RoomType,
TranscriptionStatus
} from '@hcengineering/love'
import { getEmbeddedLabel, getMetadata, getResource, type IntlString } from '@hcengineering/platform'
import presentation, {
@ -43,18 +46,29 @@ import presentation, {
getClient
} from '@hcengineering/presentation'
import {
closePanel,
type DropdownTextItem,
getCurrentLocation,
navigate,
showPopup,
panelstore,
closePanel
showPopup
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import { getObjectLinkFragment } from '@hcengineering/view-resources'
import { type Widget, type WidgetTab } from '@hcengineering/workbench'
import {
currentWorkspaceStore,
openWidget,
openWidgetTab,
sidebarStore,
updateWidgetState
} from '@hcengineering/workbench-resources'
import { isKrispNoiseFilterSupported, KrispNoiseFilter } from '@livekit/krisp-noise-filter'
import { BackgroundBlur, type BackgroundOptions, type ProcessorWrapper } from '@livekit/track-processors'
import {
type AudioCaptureOptions,
ConnectionState,
Room as LKRoom,
LocalAudioTrack,
type LocalTrack,
type LocalTrackPublication,
@ -62,30 +76,16 @@ import {
type RemoteParticipant,
type RemoteTrack,
type RemoteTrackPublication,
Room as LKRoom,
RoomEvent,
Track,
type VideoCaptureOptions
} from 'livekit-client'
import { get, writable } from 'svelte/store'
import aiBot from '@hcengineering/ai-bot'
import { connectMeeting, disconnectMeeting } from '@hcengineering/ai-bot-resources'
import {
openWidget,
sidebarStore,
updateWidgetState,
currentWorkspaceStore,
openWidgetTab
} from '@hcengineering/workbench-resources'
import { type Widget, type WidgetTab } from '@hcengineering/workbench'
import view from '@hcengineering/view'
import chunter from '@hcengineering/chunter'
import { getObjectLinkFragment } from '@hcengineering/view-resources'
import { sendMessage } from './broadcast'
import love from './plugin'
import { $myPreferences, currentRoom, currentMeetingMinutes, selectedRoomPlace, myOffice } from './stores'
import RoomSettingsPopup from './components/RoomSettingsPopup.svelte'
import love from './plugin'
import { $myPreferences, currentMeetingMinutes, currentRoom, myOffice, selectedRoomPlace } from './stores'
export const selectedCamId = 'selectedDevice_cam'
export const selectedMicId = 'selectedDevice_mic'
@ -860,6 +860,27 @@ export async function tryConnect (
}
}
export async function endMeeting (
room: Office,
rooms: Room[],
infos: ParticipantInfo[],
currentInfo: ParticipantInfo
): Promise<void> {
const roomInfos = infos.filter((p) => p.room === room._id && room.person !== p.person)
for (const roomInfo of roomInfos) {
await kick(roomInfo.person, rooms, infos)
}
await leaveRoom(currentInfo, room)
}
export async function kick (person: Ref<Person>, rooms: Room[], infos: ParticipantInfo[]): Promise<void> {
const personInfo = infos.find((p) => p.person === person)
if (personInfo === undefined) return
const personOffice = rooms.find((r) => isOffice(r) && r.person === personInfo.person)
const client = getClient()
await client.update(personInfo, { room: personOffice?._id ?? love.ids.Reception, x: 0, y: 0 })
}
export async function invite (person: Ref<Person>, room: Ref<Room> | undefined): Promise<void> {
if (room === undefined || room === love.ids.Reception) return
const client = getClient()

View File

@ -225,7 +225,9 @@ const love = plugin(loveId, {
Status: '' as IntlString,
Active: '' as IntlString,
Finished: '' as IntlString,
StartWithRecording: '' as IntlString
StartWithRecording: '' as IntlString,
Kick: '' as IntlString,
EndMeeting: '' as IntlString
},
ids: {
MainFloor: '' as Ref<Floor>,
@ -254,7 +256,8 @@ const love = plugin(loveId, {
StopRecord: '' as Asset,
FullScreen: '' as Asset,
ExitFullScreen: '' as Asset,
Invite: '' as Asset
Invite: '' as Asset,
Kick: '' as Asset
},
sound: {
Knock: '' as Asset