Changeset View
Changeset View
Standalone View
Standalone View
src/resources/vue/Meet/Room.vue
Show All 33 Lines | <div id="meet-component"> | ||||
<svg-icon icon="comment"></svg-icon> | <svg-icon icon="comment"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn link-fullscreen closed hidden" @click="switchFullscreen" :title="$t('meet.menu-fullscreen')"> | <button class="btn link-fullscreen closed hidden" @click="switchFullscreen" :title="$t('meet.menu-fullscreen')"> | ||||
<svg-icon icon="expand"></svg-icon> | <svg-icon icon="expand"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn link-fullscreen open hidden" @click="switchFullscreen" :title="$t('meet.menu-fullscreen-exit')"> | <button class="btn link-fullscreen open hidden" @click="switchFullscreen" :title="$t('meet.menu-fullscreen-exit')"> | ||||
<svg-icon icon="compress"></svg-icon> | <svg-icon icon="compress"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn link-options" v-if="isRoomOwner()" @click="roomOptions" :title="$t('meet.options')"> | <button class="btn link-options" v-if="isRoomOwner()" @click="$refs.optionsDialog.show()" :title="$t('meet.options')"> | ||||
<svg-icon icon="gear"></svg-icon> | <svg-icon icon="gear"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn link-logout" @click="logout" :title="$t('meet.menu-leave')"> | <button class="btn link-logout" @click="logout" :title="$t('meet.menu-leave')"> | ||||
<svg-icon icon="power-off"></svg-icon> | <svg-icon icon="power-off"></svg-icon> | ||||
</button> | </button> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
▲ Show 20 Lines • Show All 65 Lines • ▼ Show 20 Lines | <div id="meet-component"> | ||||
<div class="chat-input m-2"> | <div class="chat-input m-2"> | ||||
<textarea class="form-control" rows="1"></textarea> | <textarea class="form-control" rows="1"></textarea> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<logon-form id="meet-auth" class="hidden" :dashboard="false" @success="authSuccess"></logon-form> | <logon-form id="meet-auth" class="hidden" :dashboard="false" @success="authSuccess"></logon-form> | ||||
<div id="leave-dialog" class="modal" tabindex="-1" role="dialog"> | <modal-dialog id="leave-dialog" ref="leaveDialog" :title="$t('meet.leave-title')"> | ||||
<div class="modal-dialog" role="document"> | |||||
<div class="modal-content"> | |||||
<div class="modal-header"> | |||||
<h5 class="modal-title">{{ $t('meet.leave-title') }}</h5> | |||||
<btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn> | |||||
</div> | |||||
<div class="modal-body"> | |||||
<p>{{ $t('meet.leave-body') }}</p> | <p>{{ $t('meet.leave-body') }}</p> | ||||
</div> | </modal-dialog> | ||||
<div class="modal-footer"> | |||||
<btn class="btn-danger modal-action" data-bs-dismiss="modal">{{ $t('btn.close') }}</btn> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div id="media-setup-dialog" class="modal" tabindex="-1" role="dialog"> | <modal-dialog id="media-setup-dialog" ref="setupDialog" :title="$t('meet.media-title')"> | ||||
<div class="modal-dialog" role="document"> | |||||
<div class="modal-content"> | |||||
<div class="modal-header"> | |||||
<h5 class="modal-title">{{ $t('meet.media-title') }}</h5> | |||||
<btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn> | |||||
</div> | |||||
<div class="modal-body"> | |||||
<form class="media-setup-form"> | <form class="media-setup-form"> | ||||
<div class="media-setup-preview"></div> | <div class="media-setup-preview"></div> | ||||
<div class="input-group mt-2"> | <div class="input-group mt-2"> | ||||
<label for="setup-mic" class="input-group-text mb-0" :title="$t('meet.mic')"> | <label for="setup-mic" class="input-group-text mb-0" :title="$t('meet.mic')"> | ||||
<svg-icon icon="microphone"></svg-icon> | <svg-icon icon="microphone"></svg-icon> | ||||
</label> | </label> | ||||
<select class="form-select" id="setup-mic" v-model="microphone" @change="setupMicrophoneChange"> | <select class="form-select" id="setup-mic" v-model="microphone" @change="setupMicrophoneChange"> | ||||
<option value="">{{ $t('form.none') }}</option> | <option value="">{{ $t('form.none') }}</option> | ||||
<option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option> | <option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option> | ||||
</select> | </select> | ||||
</div> | </div> | ||||
<div class="input-group mt-2"> | <div class="input-group mt-2"> | ||||
<label for="setup-cam" class="input-group-text mb-0" :title="$t('meet.cam')"> | <label for="setup-cam" class="input-group-text mb-0" :title="$t('meet.cam')"> | ||||
<svg-icon icon="video"></svg-icon> | <svg-icon icon="video"></svg-icon> | ||||
</label> | </label> | ||||
<select class="form-select" id="setup-cam" v-model="camera" @change="setupCameraChange"> | <select class="form-select" id="setup-cam" v-model="camera" @change="setupCameraChange"> | ||||
<option value="">{{ $t('form.none') }}</option> | <option value="">{{ $t('form.none') }}</option> | ||||
<option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option> | <option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option> | ||||
</select> | </select> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
</div> | </modal-dialog> | ||||
<div class="modal-footer"> | |||||
<btn class="btn-secondary modal-action" data-bs-dismiss="modal">{{ $t('btn.close') }}</btn> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<room-options v-if="session.config" :config="session.config" :room="room" @config-update="configUpdate"></room-options> | <room-options v-if="session.config" :config="session.config" :room="room" @config-update="configUpdate" ref="optionsDialog"></room-options> | ||||
<room-stats ref="roomStatsDialog" :stats="stats" :room="room"></room-stats> | <room-stats ref="statsDialog" :room="room"></room-stats> | ||||
</div> | </div> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import { Modal, Dropdown } from 'bootstrap' | import { Dropdown } from 'bootstrap' | ||||
import { Media } from '../../js/meet/media.js' | import { Media } from '../../js/meet/media.js' | ||||
import { Room as Meet } from '../../js/meet/room.js' | import { Room as Meet } from '../../js/meet/room.js' | ||||
import { Roles } from '../../js/meet/constants.js' | import { Roles } from '../../js/meet/constants.js' | ||||
import ModalDialog from '../Widgets/ModalDialog' | |||||
import StatusMessage from '../Widgets/StatusMessage' | import StatusMessage from '../Widgets/StatusMessage' | ||||
import LogonForm from '../Login' | import LogonForm from '../Login' | ||||
import RoomOptions from './RoomOptions' | import RoomOptions from './RoomOptions' | ||||
import RoomStats from './RoomStats' | import RoomStats from './RoomStats' | ||||
import { library } from '@fortawesome/fontawesome-svg-core' | import { library } from '@fortawesome/fontawesome-svg-core' | ||||
library.add( | library.add( | ||||
Show All 13 Lines | library.add( | ||||
require('@fortawesome/free-solid-svg-icons/faUser').definition, | require('@fortawesome/free-solid-svg-icons/faUser').definition, | ||||
require('@fortawesome/free-solid-svg-icons/faUsers').definition, | require('@fortawesome/free-solid-svg-icons/faUsers').definition, | ||||
require('@fortawesome/free-solid-svg-icons/faVideo').definition, | require('@fortawesome/free-solid-svg-icons/faVideo').definition, | ||||
require('@fortawesome/free-solid-svg-icons/faVideoSlash').definition, | require('@fortawesome/free-solid-svg-icons/faVideoSlash').definition, | ||||
require('@fortawesome/free-solid-svg-icons/faVolumeMute').definition, | require('@fortawesome/free-solid-svg-icons/faVolumeMute').definition, | ||||
) | ) | ||||
let roomRequest | let roomRequest | ||||
let statsRequest | |||||
const authHeader = 'X-Meet-Auth-Token' | const authHeader = 'X-Meet-Auth-Token' | ||||
export default { | export default { | ||||
components: { | components: { | ||||
LogonForm, | LogonForm, | ||||
ModalDialog, | |||||
RoomOptions, | RoomOptions, | ||||
RoomStats, | RoomStats, | ||||
StatusMessage | StatusMessage | ||||
}, | }, | ||||
data() { | data() { | ||||
return { | return { | ||||
setup: { | setup: { | ||||
cameras: [], | cameras: [], | ||||
Show All 21 Lines | export default { | ||||
325: 'meet.status-325', | 325: 'meet.status-325', | ||||
326: 'meet.status-326', | 326: 'meet.status-326', | ||||
327: 'meet.status-327', | 327: 'meet.status-327', | ||||
404: 'meet.status-404', | 404: 'meet.status-404', | ||||
429: 'meet.status-429', | 429: 'meet.status-429', | ||||
500: 'meet.status-500' | 500: 'meet.status-500' | ||||
}, | }, | ||||
session: {}, | session: {}, | ||||
stats: {}, | |||||
audioActive: false, | audioActive: false, | ||||
videoActive: false, | videoActive: false, | ||||
chatActive: false, | chatActive: false, | ||||
handRaised: false, | handRaised: false, | ||||
screenActive: false | screenActive: false | ||||
} | } | ||||
}, | }, | ||||
mounted() { | mounted() { | ||||
this.room = this.$route.params.room | this.room = this.$route.params.room | ||||
// Initialize Meet client and do some basic checks | // Initialize Meet client and do some basic checks | ||||
this.meet = new Meet($('#meet-session')[0]); | this.meet = new Meet($('#meet-session')[0]); | ||||
this.canShareScreen = this.meet.isScreenSharingSupported() | this.canShareScreen = this.meet.isScreenSharingSupported() | ||||
// Check the room and init the session | // Check the room and init the session | ||||
this.initSession() | this.initSession() | ||||
// Setup the room UI | // Setup the room UI | ||||
this.setupSession() | this.setupSession() | ||||
// Configure dialog events | // Configure dialog events | ||||
$('#leave-dialog')[0].addEventListener('hide.bs.modal', () => { | this.$refs.leaveDialog.events({ | ||||
hide: () => { | |||||
// FIXME: Where exactly the user should land? Currently he'll land | // FIXME: Where exactly the user should land? Currently he'll land | ||||
// on dashboard (if he's logged in) or login form (if he's not). | // on dashboard (if he's logged in) or login form (if he's not). | ||||
this.$router.push({ name: 'dashboard' }) | this.$router.push({ name: 'dashboard' }) | ||||
} | |||||
}) | }) | ||||
const dialog = $('#media-setup-dialog')[0] | this.$refs.setupDialog.events({ | ||||
dialog.addEventListener('show.bs.modal', () => { this.setupSession() }) | show: () => { this.setupSession() }, | ||||
dialog.addEventListener('hide.bs.modal', () => { this.meet.setupStop() }) | hide: () => { this.meet.setupStop() } | ||||
}) | |||||
this.roomStatsDialog = new Modal('#room-stats-dialog') | |||||
}, | }, | ||||
beforeDestroy() { | beforeDestroy() { | ||||
clearTimeout(roomRequest) | clearTimeout(roomRequest) | ||||
clearInterval(statsRequest) | |||||
$('#app').removeClass('meet') | $('#app').removeClass('meet') | ||||
if (this.meet) { | if (this.meet) { | ||||
this.meet.leaveRoom() | this.meet.leaveRoom() | ||||
} | } | ||||
delete axios.defaults.headers.common[authHeader] | delete axios.defaults.headers.common[authHeader] | ||||
$(document.body).off('keydown.meet') | $(document.body).off('keydown.meet') | ||||
}, | }, | ||||
methods: { | methods: { | ||||
authSuccess() { | authSuccess() { | ||||
// The user authentication succeeded, we still don't know it's really the room owner | // The user authentication succeeded, we still don't know it's really the room owner | ||||
this.initSession() | this.initSession() | ||||
$('#meet-setup').removeClass('hidden') | $('#meet-setup').removeClass('hidden') | ||||
$('#meet-auth').addClass('hidden') | $('#meet-auth').addClass('hidden') | ||||
}, | }, | ||||
configUpdate(config) { | configUpdate(config) { | ||||
this.session.config = Object.assign({}, this.session.config, config) | this.session.config = Object.assign({}, this.session.config, config) | ||||
}, | }, | ||||
async refreshStats() { | |||||
let stats = await this.meet.getStats() | |||||
this.stats = stats | |||||
}, | |||||
initSession(init) { | initSession(init) { | ||||
const button = $('#join-button').prop('disabled', true) | const button = $('#join-button').prop('disabled', true) | ||||
const post = { | const post = { | ||||
password: this.password, | password: this.password, | ||||
nickname: this.nickname, | nickname: this.nickname, | ||||
screenShare: this.canShareScreen ? 1 : 0, | screenShare: this.canShareScreen ? 1 : 0, | ||||
init: init ? 1 : 0, | init: init ? 1 : 0, | ||||
▲ Show 20 Lines • Show All 116 Lines • ▼ Show 20 Lines | export default { | ||||
} | } | ||||
if (this.roomState != 'ready' && !this.session.token) { | if (this.roomState != 'ready' && !this.session.token) { | ||||
this.initSession(true) | this.initSession(true) | ||||
return | return | ||||
} | } | ||||
clearTimeout(roomRequest) | clearTimeout(roomRequest) | ||||
clearInterval(statsRequest) | |||||
this.session.nickname = this.nickname | this.session.nickname = this.nickname | ||||
this.session.languages = this.languages | this.session.languages = this.languages | ||||
this.session.menuElement = $('#meet-session-menu')[0] | this.session.menuElement = $('#meet-session-menu')[0] | ||||
this.session.chatElement = $('#meet-chat')[0] | this.session.chatElement = $('#meet-chat')[0] | ||||
this.session.queueElement = $('#meet-queue')[0] | this.session.queueElement = $('#meet-queue')[0] | ||||
this.session.counterElement = $('#meet-counter span')[0] | this.session.counterElement = $('#meet-counter span')[0] | ||||
this.session.translate = (label, args) => this.$t(label, args) | this.session.translate = (label, args) => this.$t(label, args) | ||||
this.session.toast = this.$toast | this.session.toast = this.$toast | ||||
this.session.onSuccess = () => { | this.session.onSuccess = () => { | ||||
$('#app').addClass('meet') | $('#app').addClass('meet') | ||||
$('#meet-setup').addClass('hidden') | $('#meet-setup').addClass('hidden') | ||||
$('#meet-session-toolbar,#meet-session-layout').removeClass('hidden') | $('#meet-session-toolbar,#meet-session-layout').removeClass('hidden') | ||||
} | } | ||||
this.session.onError = () => { | this.session.onError = () => { | ||||
this.roomState = 500 | this.roomState = 500 | ||||
} | } | ||||
this.session.onDestroy = event => { | this.session.onDestroy = event => { | ||||
// TODO: Display different message for every other reason | // TODO: Display different message for every other reason | ||||
if (event.reason == 'session-closed' && !this.isRoomOwner()) { | if (event.reason == 'session-closed' && !this.isRoomOwner()) { | ||||
new Modal('#leave-dialog').show() | this.$refs.leaveDialog.show() | ||||
} | } | ||||
} | } | ||||
this.session.onUpdate = data => { this.updateSession(data) } | this.session.onUpdate = data => { this.updateSession(data) } | ||||
this.session.onMediaSetup = () => { this.setupMedia() } | this.session.onMediaSetup = () => { this.setupMedia() } | ||||
this.meet.joinRoom(this.session) | this.meet.joinRoom(this.session) | ||||
this.refreshStats() | |||||
this.keyboardShortcuts() | this.keyboardShortcuts() | ||||
}, | }, | ||||
keyboardShortcuts() { | keyboardShortcuts() { | ||||
$(document.body).on('keydown.meet', e => { | $(document.body).on('keydown.meet', e => { | ||||
if ($(e.target).is('select,input,textarea')) { | if ($(e.target).is('select,input,textarea')) { | ||||
return | return | ||||
} | } | ||||
// Self-Mute with 'm' key | // Self-Mute with 'm' key | ||||
if (e.key == 'm' || e.key == 'M') { | if (e.key == 'm' || e.key == 'M') { | ||||
if ($('#meet-session-menu').find('.link-audio:not(:disabled)').length) { | if ($('#meet-session-menu').find('.link-audio:not(:disabled)').length) { | ||||
this.switchSound() | this.switchSound() | ||||
} | } | ||||
} | } | ||||
// Show stats with '?' key | // Show stats with '?' key | ||||
if (e.key == '?') { | if (e.key == '?') { | ||||
this.roomStats() | this.$refs.statsDialog.toggle(this.meet) | ||||
} | } | ||||
}) | }) | ||||
}, | }, | ||||
logout() { | logout() { | ||||
this.meet.leaveRoom(true) | this.meet.leaveRoom(true) | ||||
this.meet = null | this.meet = null | ||||
this.$router.push({ name: 'dashboard' }) | this.$router.push({ name: 'dashboard' }) | ||||
}, | }, | ||||
Show All 20 Lines | export default { | ||||
localStorage.setItem(key, this.reqId) | localStorage.setItem(key, this.reqId) | ||||
} | } | ||||
return this.reqId | return this.reqId | ||||
}, | }, | ||||
roomOptions() { | roomOptions() { | ||||
new Modal('#room-options-dialog').show() | new Modal('#room-options-dialog').show() | ||||
}, | }, | ||||
roomStats() { | |||||
clearInterval(statsRequest) | |||||
if (this.roomStatsDialog.visible) { | |||||
this.roomStatsDialog.hide() | |||||
} else { | |||||
this.refreshStats() | |||||
statsRequest = setInterval(() => { | |||||
this.refreshStats() | |||||
}, 3000) | |||||
this.roomStatsDialog.show() | |||||
} | |||||
}, | |||||
setupMedia() { | setupMedia() { | ||||
const dialog = $('#media-setup-dialog')[0] | const dialog = $('#media-setup-dialog')[0] | ||||
if (!$('video', dialog).length) { | if (!$('video', dialog).length) { | ||||
$('#meet-setup').find('video,div.volume').appendTo($('.media-setup-preview', dialog)) | $('#meet-setup').find('video,div.volume').appendTo($('.media-setup-preview', dialog)) | ||||
} | } | ||||
new Modal(dialog).show() | this.$refs.setupDialog.show() | ||||
}, | }, | ||||
async setupSession() { | async setupSession() { | ||||
this.meet.setupStart({ | this.meet.setupStart({ | ||||
videoElement: $('#meet-setup video')[0] || $('#media-setup-dialog video')[0], | videoElement: $('#meet-setup video')[0] || $('#media-setup-dialog video')[0], | ||||
volumeElement: $('#meet-setup .volume')[0] || $('#media-setup-dialog .volume')[0], | volumeElement: $('#meet-setup .volume')[0] || $('#media-setup-dialog .volume')[0], | ||||
onSuccess: setup => { | onSuccess: setup => { | ||||
this.setup = setup | this.setup = setup | ||||
this.microphone = setup.audioSource | this.microphone = setup.audioSource | ||||
▲ Show 20 Lines • Show All 79 Lines • Show Last 20 Lines |