mirror of
https://github.com/nextcloud/spreed.git
synced 2025-12-18 05:20:50 +01:00
feat(call): add bottom bar in call and move controls there
Signed-off-by: Dorra Jaouad <dorra.jaoued7@gmail.com>
This commit is contained in:
parent
d77e6f4994
commit
3ebf2c4c73
6 changed files with 198 additions and 172 deletions
181
src/components/CallView/BottomBar.vue
Normal file
181
src/components/CallView/BottomBar.vue
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
|
||||
import { computed, watch } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import IconHandBackLeft from 'vue-material-design-icons/HandBackLeft.vue'
|
||||
import CallButton from '../TopBar/CallButton.vue'
|
||||
import ReactionMenu from '../TopBar/ReactionMenu.vue'
|
||||
import TopBarMediaControls from '../TopBar/TopBarMediaControls.vue'
|
||||
import { useGetToken } from '../../composables/useGetToken.ts'
|
||||
import { CONVERSATION, PARTICIPANT } from '../../constants.ts'
|
||||
import { getTalkConfig } from '../../services/CapabilitiesManager.ts'
|
||||
import { useActorStore } from '../../stores/actor.ts'
|
||||
import { useBreakoutRoomsStore } from '../../stores/breakoutRooms.ts'
|
||||
import { localCallParticipantModel, localMediaModel } from '../../utils/webrtc/index.js'
|
||||
|
||||
const { isSidebar = false } = defineProps<{
|
||||
isSidebar: boolean
|
||||
}>()
|
||||
const AUTO_LOWER_HAND_THRESHOLD = 3000
|
||||
const disableKeyboardShortcuts = OCP.Accessibility.disableKeyboardShortcuts()
|
||||
|
||||
const store = useStore()
|
||||
const token = useGetToken()
|
||||
const actorStore = useActorStore()
|
||||
const breakoutRoomsStore = useBreakoutRoomsStore()
|
||||
|
||||
const conversation = computed(() => {
|
||||
return store.getters.conversation(token.value) || store.getters.dummyConversation
|
||||
})
|
||||
|
||||
const supportedReactions = computed(() => getTalkConfig(token.value, 'call', 'supported-reactions') || [])
|
||||
|
||||
const hasReactionSupport = computed(() => supportedReactions.value && supportedReactions.value.length > 0)
|
||||
|
||||
const canModerate = computed(() => [PARTICIPANT.TYPE.OWNER, PARTICIPANT.TYPE.MODERATOR, PARTICIPANT.TYPE.GUEST_MODERATOR]
|
||||
.includes(conversation.value.participantType))
|
||||
|
||||
const isHandRaised = computed(() => localMediaModel.attributes.raisedHand.state === true)
|
||||
|
||||
const raiseHandButtonLabel = computed(() => {
|
||||
if (!isHandRaised.value) {
|
||||
return disableKeyboardShortcuts
|
||||
? t('spreed', 'Raise hand')
|
||||
: t('spreed', 'Raise hand (R)')
|
||||
}
|
||||
return disableKeyboardShortcuts
|
||||
? t('spreed', 'Lower hand')
|
||||
: t('spreed', 'Lower hand (R)')
|
||||
})
|
||||
|
||||
const userIsInBreakoutRoomAndInCall = computed(() => conversation.value.objectType === CONVERSATION.OBJECT_TYPE.BREAKOUT_ROOM)
|
||||
|
||||
let lowerHandDelay = AUTO_LOWER_HAND_THRESHOLD
|
||||
let speakingTimestamp: number | null = null
|
||||
let lowerHandTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
// Hand raising functionality
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function toggleHandRaised() {
|
||||
const newState = !isHandRaised.value
|
||||
localMediaModel.toggleHandRaised(newState)
|
||||
store.dispatch('setParticipantHandRaised', {
|
||||
sessionId: actorStore.sessionId,
|
||||
raisedHand: localMediaModel.attributes.raisedHand,
|
||||
})
|
||||
|
||||
// Handle breakout room assistance requests
|
||||
if (userIsInBreakoutRoomAndInCall.value && !canModerate.value) {
|
||||
const hasRaisedHands = Object.keys(store.getters.participantRaisedHandList)
|
||||
.filter((sessionId) => sessionId !== actorStore.sessionId)
|
||||
.length !== 0
|
||||
|
||||
if (hasRaisedHands) {
|
||||
return // Assistance is already requested by someone in the room
|
||||
}
|
||||
|
||||
const hasAssistanceRequested = conversation.value.breakoutRoomStatus === CONVERSATION.BREAKOUT_ROOM_STATUS.STATUS_ASSISTANCE_REQUESTED
|
||||
if (newState && !hasAssistanceRequested) {
|
||||
breakoutRoomsStore.requestAssistance(token.value)
|
||||
} else if (!newState && hasAssistanceRequested) {
|
||||
breakoutRoomsStore.dismissRequestAssistance(token.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-lower hand when speaking
|
||||
watch(() => localMediaModel.attributes.speaking, (speaking) => {
|
||||
if (lowerHandTimeout !== null && !speaking) {
|
||||
lowerHandDelay = Math.max(0, lowerHandDelay - (Date.now() - speakingTimestamp!))
|
||||
clearTimeout(lowerHandTimeout)
|
||||
lowerHandTimeout = null
|
||||
return
|
||||
}
|
||||
|
||||
// User is not speaking OR timeout is already running OR hand is not raised
|
||||
if (!speaking || lowerHandTimeout !== null || !isHandRaised.value) {
|
||||
return
|
||||
}
|
||||
|
||||
speakingTimestamp = Date.now()
|
||||
lowerHandTimeout = setTimeout(() => {
|
||||
lowerHandTimeout = null
|
||||
speakingTimestamp = null
|
||||
lowerHandDelay = AUTO_LOWER_HAND_THRESHOLD
|
||||
|
||||
if (isHandRaised.value) {
|
||||
toggleHandRaised()
|
||||
}
|
||||
}, lowerHandDelay)
|
||||
})
|
||||
|
||||
// Keyboard shortcuts
|
||||
useHotKey('r', toggleHandRaised)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bottom-bar" data-theme-dark>
|
||||
<!-- fullscreen and grid view buttons -->
|
||||
|
||||
<div class="bottom-bar-call-controls">
|
||||
<!-- Local media controls -->
|
||||
<TopBarMediaControls
|
||||
:token="token"
|
||||
:model="localMediaModel"
|
||||
:is-sidebar="isSidebar"
|
||||
:local-call-participant-model="localCallParticipantModel" />
|
||||
|
||||
<!-- Reactions menu -->
|
||||
<ReactionMenu v-if="hasReactionSupport"
|
||||
:token="token"
|
||||
:supported-reactions="supportedReactions"
|
||||
:local-call-participant-model="localCallParticipantModel" />
|
||||
|
||||
<NcButton
|
||||
:title="raiseHandButtonLabel"
|
||||
:aria-label="raiseHandButtonLabel"
|
||||
:variant="isHandRaised ? 'secondary' : 'tertiary'"
|
||||
@click="toggleHandRaised">
|
||||
<!-- The following icon is much bigger than all the others
|
||||
so we reduce its size -->
|
||||
<template #icon>
|
||||
<IconHandBackLeft :size="16" />
|
||||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
|
||||
<CallButton shrink-on-mobile
|
||||
:hide-text="isSidebar"
|
||||
:is-screensharing="!!localMediaModel.attributes.localScreen" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bottom-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
inset-inline: 0;
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 calc(var(--default-grid-baseline) * 2);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.bottom-bar-call-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap: var(--default-grid-baseline);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -127,6 +127,8 @@
|
|||
:is-sidebar="isSidebar"
|
||||
@click-video="handleClickLocalVideo" />
|
||||
</div>
|
||||
|
||||
<BottomBar :is-sidebar="isSidebar" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -138,6 +140,7 @@ import { loadState } from '@nextcloud/initial-state'
|
|||
import { t } from '@nextcloud/l10n'
|
||||
import debounce from 'debounce'
|
||||
import { provide, ref } from 'vue'
|
||||
import BottomBar from './BottomBar.vue'
|
||||
import Grid from './Grid/Grid.vue'
|
||||
import EmptyCallView from './shared/EmptyCallView.vue'
|
||||
import LocalVideo from './shared/LocalVideo.vue'
|
||||
|
|
@ -167,6 +170,7 @@ export default {
|
|||
name: 'CallView',
|
||||
|
||||
components: {
|
||||
BottomBar,
|
||||
EmptyCallView,
|
||||
Grid,
|
||||
LocalVideo,
|
||||
|
|
@ -833,6 +837,8 @@ export default {
|
|||
// Default value has changed since v29.0.4: 'blur(25px)' => 'none'
|
||||
backdrop-filter: var(--filter-background-blur);
|
||||
--grid-gap: calc(var(--default-grid-baseline) * 2);
|
||||
--top-bar-height: 51px;
|
||||
--bottom-bar-height: 56px;
|
||||
|
||||
&.call-container__blurred {
|
||||
backdrop-filter: blur(25px);
|
||||
|
|
@ -845,8 +851,9 @@ export default {
|
|||
#videos {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: calc(100% - 51px);
|
||||
top: 51px; // TopBar height
|
||||
height: calc(100% - (var(--top-bar-height) + var(--bottom-bar-height)));
|
||||
top: var(--top-bar-height);
|
||||
bottom: var(--bottom-bar-height);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
|
@ -894,6 +901,8 @@ export default {
|
|||
&--sidebar {
|
||||
width: 150px;
|
||||
height: 100px;
|
||||
bottom: var(--bottom-bar-height);
|
||||
margin: var(--default-grid-baseline);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -142,27 +142,13 @@
|
|||
<ExtendOneToOneDialog v-else-if="!isSidebar && canExtendOneToOneConversation"
|
||||
:token="token" />
|
||||
|
||||
<!-- Reactions menu -->
|
||||
<ReactionMenu v-if="isInCall && hasReactionSupport"
|
||||
:token="token"
|
||||
:supported-reactions="supportedReactions"
|
||||
:local-call-participant-model="localCallParticipantModel" />
|
||||
|
||||
<!-- Local media controls -->
|
||||
<TopBarMediaControls v-if="isInCall"
|
||||
:token="token"
|
||||
:model="localMediaModel"
|
||||
:is-sidebar="isSidebar"
|
||||
:local-call-participant-model="localCallParticipantModel" />
|
||||
|
||||
<!-- TopBar menu -->
|
||||
<TopBarMenu :token="token"
|
||||
:show-actions="!isSidebar"
|
||||
:is-sidebar="isSidebar"
|
||||
:model="localMediaModel"
|
||||
@open-breakout-rooms-editor="showBreakoutRoomsEditor = true" />
|
||||
|
||||
<CallButton shrink-on-mobile :hide-text="isSidebar" :is-screensharing="!!localMediaModel.attributes.localScreen" />
|
||||
<CallButton v-if="!isInCall" shrink-on-mobile />
|
||||
|
||||
<!-- Breakout rooms editor -->
|
||||
<BreakoutRoomsEditor v-if="showBreakoutRoomsEditor"
|
||||
|
|
@ -194,9 +180,7 @@ import ConversationIcon from '../ConversationIcon.vue'
|
|||
import ExtendOneToOneDialog from '../ExtendOneToOneDialog.vue'
|
||||
import CallButton from './CallButton.vue'
|
||||
import CallTime from './CallTime.vue'
|
||||
import ReactionMenu from './ReactionMenu.vue'
|
||||
import TasksCounter from './TasksCounter.vue'
|
||||
import TopBarMediaControls from './TopBarMediaControls.vue'
|
||||
import TopBarMenu from './TopBarMenu.vue'
|
||||
import { useGetThreadId } from '../../composables/useGetThreadId.ts'
|
||||
import { useGetToken } from '../../composables/useGetToken.ts'
|
||||
|
|
@ -209,7 +193,6 @@ import { useSidebarStore } from '../../stores/sidebar.ts'
|
|||
import { getDisplayNameWithFallback } from '../../utils/getDisplayName.ts'
|
||||
import { parseToSimpleMessage } from '../../utils/textParse.ts'
|
||||
import { getStatusMessage } from '../../utils/userStatus.ts'
|
||||
import { localCallParticipantModel, localMediaModel } from '../../utils/webrtc/index.js'
|
||||
|
||||
const canStartConversations = getTalkConfig('local', 'conversations', 'can-create')
|
||||
const supportConversationCreationAll = hasTalkFeature('local', 'conversation-creation-all')
|
||||
|
|
@ -240,7 +223,6 @@ export default {
|
|||
CallTime,
|
||||
ConversationIcon,
|
||||
ExtendOneToOneDialog,
|
||||
TopBarMediaControls,
|
||||
NcActionButton,
|
||||
NcActions,
|
||||
NcButton,
|
||||
|
|
@ -248,7 +230,6 @@ export default {
|
|||
NcRichText,
|
||||
TopBarMenu,
|
||||
TasksCounter,
|
||||
ReactionMenu,
|
||||
// Icons
|
||||
IconAccountMultipleOutline,
|
||||
IconAccountMultiplePlusOutline,
|
||||
|
|
@ -275,8 +256,6 @@ export default {
|
|||
return {
|
||||
AVATAR,
|
||||
PARTICIPANT,
|
||||
localCallParticipantModel,
|
||||
localMediaModel,
|
||||
groupwareStore: useGroupwareStore(),
|
||||
sidebarStore: useSidebarStore(),
|
||||
actorStore: useActorStore(),
|
||||
|
|
@ -371,14 +350,6 @@ export default {
|
|||
return n('spreed', '%n participant in call', '%n participants in call', this.$store.getters.participantsInCall(this.token))
|
||||
},
|
||||
|
||||
supportedReactions() {
|
||||
return getTalkConfig(this.token, 'call', 'supported-reactions')
|
||||
},
|
||||
|
||||
hasReactionSupport() {
|
||||
return this.isInCall && this.supportedReactions?.length > 0
|
||||
},
|
||||
|
||||
showCalendarEvents() {
|
||||
return this.getUserId && !this.isInCall && !this.isSidebar
|
||||
&& this.conversation.type !== CONVERSATION.TYPE.NOTE_TO_SELF
|
||||
|
|
|
|||
|
|
@ -5,20 +5,8 @@
|
|||
|
||||
<template>
|
||||
<div class="top-bar-menu">
|
||||
<TransitionExpand v-if="isInCall" :show="isHandRaised" direction="horizontal">
|
||||
<NcButton :title="raiseHandButtonLabel"
|
||||
:aria-label="raiseHandButtonLabel"
|
||||
variant="tertiary"
|
||||
@click.stop="toggleHandRaised">
|
||||
<template #icon>
|
||||
<!-- The following icon is much bigger than all the others
|
||||
so we reduce its size -->
|
||||
<IconHandBackLeft :size="18" />
|
||||
</template>
|
||||
</NcButton>
|
||||
</TransitionExpand>
|
||||
|
||||
<NcActions v-if="!isSidebar"
|
||||
force-menu
|
||||
:title="t('spreed', 'Conversation actions')"
|
||||
:aria-label="t('spreed', 'Conversation actions')"
|
||||
variant="tertiary">
|
||||
|
|
@ -28,17 +16,6 @@
|
|||
</template>
|
||||
|
||||
<template v-if="showActions && isInCall">
|
||||
<!-- Raise hand -->
|
||||
<NcActionButton close-after-click
|
||||
@click="toggleHandRaised">
|
||||
<!-- The following icon is much bigger than all the others
|
||||
so we reduce its size -->
|
||||
<template #icon>
|
||||
<IconHandBackLeft :size="16" />
|
||||
</template>
|
||||
{{ raiseHandButtonLabel }}
|
||||
</NcActionButton>
|
||||
|
||||
<!-- Moderator actions -->
|
||||
<template v-if="!isOneToOneConversation && canFullModerate">
|
||||
<NcActionButton close-after-click
|
||||
|
|
@ -159,7 +136,6 @@ import NcActionButton from '@nextcloud/vue/components/NcActionButton'
|
|||
import NcActionLink from '@nextcloud/vue/components/NcActionLink'
|
||||
import NcActions from '@nextcloud/vue/components/NcActions'
|
||||
import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
|
||||
import IconCog from 'vue-material-design-icons/Cog.vue'
|
||||
|
|
@ -168,14 +144,12 @@ import IconDotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'
|
|||
import IconFile from 'vue-material-design-icons/File.vue'
|
||||
import IconFullscreen from 'vue-material-design-icons/Fullscreen.vue'
|
||||
import IconFullscreenExit from 'vue-material-design-icons/FullscreenExit.vue'
|
||||
import IconHandBackLeft from 'vue-material-design-icons/HandBackLeft.vue'
|
||||
import IconMicrophoneOff from 'vue-material-design-icons/MicrophoneOff.vue'
|
||||
import IconRecordCircle from 'vue-material-design-icons/RecordCircle.vue'
|
||||
import IconStop from 'vue-material-design-icons/Stop.vue'
|
||||
import IconVideo from 'vue-material-design-icons/Video.vue'
|
||||
import IconViewGallery from 'vue-material-design-icons/ViewGallery.vue'
|
||||
import IconViewGrid from 'vue-material-design-icons/ViewGrid.vue'
|
||||
import TransitionExpand from '../MediaSettings/TransitionExpand.vue'
|
||||
import IconFileDownload from '../../../img/material-icons/file-download.svg?raw'
|
||||
import {
|
||||
disableFullscreen,
|
||||
|
|
@ -185,25 +159,18 @@ import {
|
|||
import { useIsInCall } from '../../composables/useIsInCall.js'
|
||||
import { CALL, CONVERSATION, PARTICIPANT } from '../../constants.ts'
|
||||
import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManager.ts'
|
||||
import { useActorStore } from '../../stores/actor.ts'
|
||||
import { useBreakoutRoomsStore } from '../../stores/breakoutRooms.ts'
|
||||
import { useCallViewStore } from '../../stores/callView.ts'
|
||||
import { generateAbsoluteUrl } from '../../utils/handleUrl.ts'
|
||||
import { callParticipantCollection } from '../../utils/webrtc/index.js'
|
||||
|
||||
const AUTO_LOWER_HAND_THRESHOLD = 3000
|
||||
const disableKeyboardShortcuts = OCP.Accessibility.disableKeyboardShortcuts()
|
||||
|
||||
export default {
|
||||
name: 'TopBarMenu',
|
||||
|
||||
components: {
|
||||
TransitionExpand,
|
||||
NcActionButton,
|
||||
NcActionLink,
|
||||
NcActionSeparator,
|
||||
NcActions,
|
||||
NcButton,
|
||||
NcLoadingIcon,
|
||||
NcIconSvgWrapper,
|
||||
// Icons
|
||||
|
|
@ -213,7 +180,6 @@ export default {
|
|||
IconFile,
|
||||
IconFullscreen,
|
||||
IconFullscreenExit,
|
||||
IconHandBackLeft,
|
||||
IconMicrophoneOff,
|
||||
IconRecordCircle,
|
||||
IconStop,
|
||||
|
|
@ -231,14 +197,6 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* The local media model
|
||||
*/
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
showActions: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
|
@ -260,18 +218,13 @@ export default {
|
|||
IconFileDownload,
|
||||
isInCall: useIsInCall(),
|
||||
isFullscreen: useDocumentFullscreen(),
|
||||
breakoutRoomsStore: useBreakoutRoomsStore(),
|
||||
callViewStore: useCallViewStore(),
|
||||
actorStore: useActorStore(),
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
boundaryElement: document.querySelector('.main-view'),
|
||||
lowerHandTimeout: null,
|
||||
speakingTimestamp: null,
|
||||
lowerHandDelay: AUTO_LOWER_HAND_THRESHOLD,
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -311,29 +264,6 @@ export default {
|
|||
return this.callViewStore.isGrid
|
||||
},
|
||||
|
||||
isVirtualBackgroundAvailable() {
|
||||
return this.model.attributes.virtualBackgroundAvailable
|
||||
},
|
||||
|
||||
isVirtualBackgroundEnabled() {
|
||||
return this.model.attributes.virtualBackgroundEnabled
|
||||
},
|
||||
|
||||
isHandRaised() {
|
||||
return this.model.attributes.raisedHand?.state === true
|
||||
},
|
||||
|
||||
raiseHandButtonLabel() {
|
||||
if (!this.isHandRaised) {
|
||||
return disableKeyboardShortcuts
|
||||
? t('spreed', 'Raise hand')
|
||||
: t('spreed', 'Raise hand (R)')
|
||||
}
|
||||
return disableKeyboardShortcuts
|
||||
? t('spreed', 'Lower hand')
|
||||
: t('spreed', 'Lower hand (R)')
|
||||
},
|
||||
|
||||
participantType() {
|
||||
return this.conversation.participantType
|
||||
},
|
||||
|
|
@ -373,13 +303,6 @@ export default {
|
|||
|| this.conversation.callRecording === CALL.RECORDING.AUDIO
|
||||
},
|
||||
|
||||
// True if current conversation is a breakout room and the breakout room has started
|
||||
// And a call is in progress
|
||||
userIsInBreakoutRoomAndInCall() {
|
||||
return this.conversation.objectType === CONVERSATION.OBJECT_TYPE.BREAKOUT_ROOM
|
||||
&& this.isInCall
|
||||
},
|
||||
|
||||
showCallLayoutSwitch() {
|
||||
return !this.callViewStore.isEmptyCallView
|
||||
},
|
||||
|
|
@ -393,37 +316,7 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
'model.attributes.speaking'(speaking) {
|
||||
// user stops speaking in lowerHandTimeout
|
||||
if (this.lowerHandTimeout !== null && !speaking) {
|
||||
this.lowerHandDelay = Math.max(0, this.lowerHandDelay - (Date.now() - this.speakingTimestamp))
|
||||
clearTimeout(this.lowerHandTimeout)
|
||||
this.lowerHandTimeout = null
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// user is not speaking OR timeout is already running OR hand is not raised
|
||||
if (!speaking || this.lowerHandTimeout !== null || !this.isHandRaised) {
|
||||
return
|
||||
}
|
||||
|
||||
this.speakingTimestamp = Date.now()
|
||||
this.lowerHandTimeout = setTimeout(() => {
|
||||
this.lowerHandTimeout = null
|
||||
this.speakingTimestamp = null
|
||||
this.lowerHandDelay = AUTO_LOWER_HAND_THRESHOLD
|
||||
|
||||
if (this.isHandRaised) {
|
||||
this.toggleHandRaised()
|
||||
}
|
||||
}, this.lowerHandDelay)
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
useHotKey('r', this.toggleHandRaised)
|
||||
useHotKey('f', this.toggleFullscreen)
|
||||
},
|
||||
|
||||
|
|
@ -467,37 +360,6 @@ export default {
|
|||
emit('talk:media-settings:show')
|
||||
},
|
||||
|
||||
toggleHandRaised() {
|
||||
if (!this.isInCall) {
|
||||
return
|
||||
}
|
||||
const newState = !this.isHandRaised
|
||||
this.model.toggleHandRaised(newState)
|
||||
this.$store.dispatch(
|
||||
'setParticipantHandRaised',
|
||||
{
|
||||
sessionId: this.actorStore.sessionId,
|
||||
raisedHand: this.model.attributes.raisedHand,
|
||||
},
|
||||
)
|
||||
// If the current conversation is a break-out room and the user is not a moderator,
|
||||
// also send request for assistance to the moderators.
|
||||
if (this.userIsInBreakoutRoomAndInCall && !this.canModerate) {
|
||||
const hasRaisedHands = Object.keys(this.$store.getters.participantRaisedHandList)
|
||||
.filter((sessionId) => sessionId !== this.actorStore.sessionId)
|
||||
.length !== 0
|
||||
if (hasRaisedHands) {
|
||||
return // Assistance is already requested by someone in the room
|
||||
}
|
||||
const hasAssistanceRequested = this.conversation.breakoutRoomStatus === CONVERSATION.BREAKOUT_ROOM_STATUS.STATUS_ASSISTANCE_REQUESTED
|
||||
if (newState && !hasAssistanceRequested) {
|
||||
this.breakoutRoomsStore.requestAssistance(this.token)
|
||||
} else if (!newState && hasAssistanceRequested) {
|
||||
this.breakoutRoomsStore.dismissRequestAssistance(this.token)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
openConversationSettings() {
|
||||
emit('show-conversation-settings', { token: this.token })
|
||||
},
|
||||
|
|
|
|||
3
src/env.d.ts
vendored
3
src/env.d.ts
vendored
|
|
@ -20,6 +20,9 @@ declare global {
|
|||
AppConfig: {
|
||||
setValue: (app: string, key: string, value: string | number | boolean, options?: { success?: () => void, error?: () => void }) => void
|
||||
}
|
||||
Accessibility: {
|
||||
disableKeyboardShortcuts: () => boolean
|
||||
}
|
||||
}
|
||||
|
||||
const OC: {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export default function LocalMediaModel() {
|
|||
virtualBackgroundUrl: null,
|
||||
localScreen: null,
|
||||
token: '',
|
||||
raisedHand: false,
|
||||
raisedHand: { state: false, timestamp: Date.now() },
|
||||
})
|
||||
|
||||
this._handleLocalStreamRequestedBound = this._handleLocalStreamRequested.bind(this)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue