From 9442f529f4d999262e9e0b0c6a9873c262877c33 Mon Sep 17 00:00:00 2001 From: Stefan Otto Date: Mon, 30 Mar 2020 22:42:40 +0200 Subject: [PATCH 01/22] clean up; first testing --- app/src/RoomClient.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index fac58da..6e8d08e 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -959,22 +959,6 @@ export default class RoomClient } } - async getAudioTrack() - { - await navigator.mediaDevices.getUserMedia( - { - audio : true, video : false - }); - } - - async getVideoTrack() - { - await navigator.mediaDevices.getUserMedia( - { - audio : false, video : true - }); - } - async changeAudioDevice(deviceId) { logger.debug('changeAudioDevice() [deviceId: %s]', deviceId); @@ -1012,10 +996,18 @@ export default class RoomClient { audio : { - deviceId : { exact: device.deviceId } + deviceId : { exact: device.deviceId }, + sampleRate : 48000, + channelCount : 1, + volume : 1.0, + autoGainControl : true, + echoCancellation : false, + noiseSuppression : false, + sampleSize : 16 } - }); - + } + ); + logger.debug('Constraints: %o', stream.getAudioTracks()[0].getConstraints()); const track = stream.getAudioTracks()[0]; if (this._micProducer) @@ -2687,10 +2679,18 @@ export default class RoomClient const stream = await navigator.mediaDevices.getUserMedia( { audio : { - deviceId : { exact: deviceId } + deviceId : { exact: deviceId }, + sampleRate : 48000, + channelCount : 1, + volume : 1.0, + autoGainControl : true, + echoCancellation : false, + noiseSuppression : false, + sampleSize : 16 } } ); + logger.debug('Constraints: %o', stream.getAudioTracks()[0].getConstraints()); track = stream.getAudioTracks()[0]; From 1469f6c5fbbfe8b922ab715539124a400f361b8a Mon Sep 17 00:00:00 2001 From: Stefan Otto Date: Thu, 9 Apr 2020 17:59:29 +0200 Subject: [PATCH 02/22] cleanup calling hark --- app/src/RoomClient.js | 152 ++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 94 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 6e8d08e..0d0bd3a 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -959,6 +959,60 @@ export default class RoomClient } } + disconnectLocalHark() { + logger.debug('disconnectLocalHark() | Stopping harkStream.'); + if (this._harkStream != null) { + this._harkStream.getAudioTracks()[0].stop(); + this._harkStream = null; + } + + if (this._hark != null) { + logger.debug('disconnectLocalHark() Stopping hark.'); + this._hark.stop(); + } + } + + connectLocalHark(track) { + logger.debug('connectLocalHark() | Track:%o', track); + this._harkStream = new MediaStream(); + + this._harkStream.addTrack(track.clone()); + this._harkStream.getAudioTracks()[0].enabled = true; + + if (!this._harkStream.getAudioTracks()[0]) + throw new Error('getMicStream():something went wrong with hark'); + + this._hark = hark(this._harkStream, { play: false }); + + // eslint-disable-next-line no-unused-vars + this._hark.on('volume_change', (dBs, threshold) => { + // The exact formula to convert from dBs (-100..0) to linear (0..1) is: + // Math.pow(10, dBs / 20) + // However it does not produce a visually useful output, so let exagerate + // it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to + // minimize component renderings. + let volume = Math.round(Math.pow(10, dBs / 85) * 10); + + if (volume === 1) + volume = 0; + + volume = Math.round(volume); + + if (this._micProducer && volume !== this._micProducer.volume) { + this._micProducer.volume = volume; + + store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume)); + } + }); + this._hark.on('speaking', function () { + store.dispatch(meActions.setIsSpeaking(true)); + }); + this._hark.on('stopped_speaking', function () { + store.dispatch(meActions.setIsSpeaking(false)); + }); + } + + async changeAudioDevice(deviceId) { logger.debug('changeAudioDevice() [deviceId: %s]', deviceId); @@ -967,7 +1021,7 @@ export default class RoomClient meActions.setAudioInProgress(true)); try - { + { const device = this._audioDevices[deviceId]; if (!device) @@ -977,15 +1031,7 @@ export default class RoomClient 'changeAudioDevice() | new selected webcam [device:%o]', device); - if (this._hark != null) - this._hark.stop(); - - if (this._harkStream != null) - { - logger.debug('Stopping hark.'); - this._harkStream.getAudioTracks()[0].stop(); - this._harkStream = null; - } + this.disconnectLocalHark(); if (this._micProducer && this._micProducer.track) this._micProducer.track.stop(); @@ -1015,47 +1061,8 @@ export default class RoomClient if (this._micProducer) this._micProducer.volume = 0; + this.connectLocalHark(track); - this._harkStream = new MediaStream(); - - this._harkStream.addTrack(track.clone()); - this._harkStream.getAudioTracks()[0].enabled = true; - - if (!this._harkStream.getAudioTracks()[0]) - throw new Error('changeAudioDevice(): given stream has no audio track'); - - this._hark = hark(this._harkStream, { play: false }); - - // eslint-disable-next-line no-unused-vars - this._hark.on('volume_change', (dBs, threshold) => - { - // The exact formula to convert from dBs (-100..0) to linear (0..1) is: - // Math.pow(10, dBs / 20) - // However it does not produce a visually useful output, so let exagerate - // it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to - // minimize component renderings. - let volume = Math.round(Math.pow(10, dBs / 85) * 10); - - if (volume === 1) - volume = 0; - - volume = Math.round(volume); - - if (this._micProducer && volume !== this._micProducer.volume) - { - this._micProducer.volume = volume; - - store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume)); - } - }); - this._hark.on('speaking', function() - { - store.dispatch(meActions.setIsSpeaking(true)); - }); - this._hark.on('stopped_speaking', function() - { - store.dispatch(meActions.setIsSpeaking(false)); - }); if (this._micProducer && this._micProducer.id) store.dispatch( producerActions.setProducerTrack(this._micProducer.id, track)); @@ -2745,51 +2752,8 @@ export default class RoomClient this._micProducer.volume = 0; - if (this._hark != null) - this._hark.stop(); + this.connectLocalHark(track); - if (this._harkStream != null) - this._harkStream.getAudioTracks()[0].stop(); - - this._harkStream = new MediaStream(); - - this._harkStream.addTrack(track.clone()); - - if (!this._harkStream.getAudioTracks()[0]) - throw new Error('enableMic(): given stream has no audio track'); - - this._hark = hark(this._harkStream, { play: false }); - - // eslint-disable-next-line no-unused-vars - this._hark.on('volume_change', (dBs, threshold) => - { - // The exact formula to convert from dBs (-100..0) to linear (0..1) is: - // Math.pow(10, dBs / 20) - // However it does not produce a visually useful output, so let exagerate - // it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to - // minimize component renderings. - let volume = Math.round(Math.pow(10, dBs / 85) * 10); - - if (volume === 1) - volume = 0; - - volume = Math.round(volume); - - if (this._micProducer && volume !== this._micProducer.volume) - { - this._micProducer.volume = volume; - - store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume)); - } - }); - this._hark.on('speaking', function() - { - store.dispatch(meActions.setIsSpeaking(true)); - }); - this._hark.on('stopped_speaking', function() - { - store.dispatch(meActions.setIsSpeaking(false)); - }); } catch (error) { From 417788325b3f0ec8aa3245397e25e9ddf97fe911 Mon Sep 17 00:00:00 2001 From: Luca Date: Tue, 5 May 2020 18:21:18 +0200 Subject: [PATCH 03/22] Update italian translation --- app/src/translations/it.json | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 2c2e01a..284c40e 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -49,22 +49,22 @@ "room.spotlights": "Partecipanti in Evidenza", "room.passive": "Participanti Passivi", "room.videoPaused": "Il video è in pausa", - "room.muteAll": null, - "room.stopAllVideo": null, - "room.closeMeeting": null, - "room.clearChat": null, - "room.clearFileSharing": null, - "room.speechUnsupported": null, - "room.moderatoractions": null, - "room.raisedHand": null, - "room.loweredHand": null, - "room.extraVideo": null, + "room.muteAll": "Muta tutti", + "room.stopAllVideo": "Ferma tutti i video", + "room.closeMeeting": "Termina meeting", + "room.clearChat": "Pulisci chat", + "room.clearFileSharing": "Pulisci file sharing", + "room.speechUnsupported": "Il tuo browser non supporta il riconoscimento vocale", + "room.moderatoractions": "Azioni moderatore", + "room.raisedHand": "{displayName} ha alzato la mano", + "room.loweredHand": "{displayName} ha abbassato la mano", + "room.extraVideo": "Video extra", "room.overRoomLimit": null, - "me.mutedPTT": null, + "me.mutedPTT": "Sei mutato, tieni premuto SPAZIO per parlare", - "roles.gotRole": null, - "roles.lostRole": null, + "roles.gotRole": "Hai ottenuto il ruolo: {role}", + "roles.lostRole": "Hai perso il ruolo: {role}", "tooltip.login": "Log in", "tooltip.logout": "Log out", @@ -76,9 +76,9 @@ "tooltip.lobby": "Mostra lobby", "tooltip.settings": "Mostra impostazioni", "tooltip.participants": "Mostra partecipanti", - "tooltip.muteParticipant": null, - "tooltip.muteParticipantVideo": null, - "tooltip.raisedHand": null, + "tooltip.muteParticipant": "Muta partecipante", + "tooltip.muteParticipantVideo": "Ferma video partecipante", + "tooltip.raisedHand": "Mano alzata", "label.roomName": "Nome della stanza", "label.chooseRoomButton": "Continua", @@ -103,11 +103,11 @@ "label.veryHigh": "Molto alta (FHD)", "label.ultra": "Ultra (UHD)", "label.close": "Chiudi", - "label.media": null, - "label.appearence": null, - "label.advanced": null, - "label.addVideo": null, - "label.promoteAllPeers": null, + "label.media": "Media", + "label.appearence": "Aspetto", + "label.advanced": "Avanzate", + "label.addVideo": "Aggiungi video", + "label.promoteAllPeers": "Promuovi tutti", "settings.settings": "Impostazioni", "settings.camera": "Videocamera", @@ -125,8 +125,8 @@ "settings.advancedMode": "Modalità avanzata", "settings.permanentTopBar": "Barra superiore permanente", "settings.lastn": "Numero di video visibili", - "settings.hiddenControls": null, - "settings.notificationSounds": null, + "settings.hiddenControls": "Controlli media nascosti", + "settings.notificationSounds": "Suoni di notifica", "filesharing.saveFileError": "Impossibile salvare file", "filesharing.startingFileShare": "Tentativo di condivisione file", @@ -168,8 +168,8 @@ "devices.cameraDisconnected": "Videocamera scollegata", "devices.cameraError": "Errore con l'accesso alla videocamera", - "moderator.clearChat": null, - "moderator.clearFiles": null, - "moderator.muteAudio": null, - "moderator.muteVideo": null + "moderator.clearChat": "Il moderatore ha pulito la chat", + "moderator.clearFiles": "Il moderatore ha pulito i file", + "moderator.muteAudio": "Il moderatore ha mutato il tuo audio", + "moderator.muteVideo": "Il moderatore ha fermato il tuo video" } From 3710c3b3ace21912de4b31ed0d1e0404ac2733b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 20:08:11 +0200 Subject: [PATCH 04/22] Add tooltips to participant list, fixes #299 --- .../MeetingDrawer/ParticipantList/ListMe.js | 35 ++-- .../MeetingDrawer/ParticipantList/ListPeer.js | 163 +++++++++++------- app/src/translations/cn.json | 1 + app/src/translations/cs.json | 1 + app/src/translations/de.json | 1 + app/src/translations/dk.json | 1 + app/src/translations/el.json | 1 + app/src/translations/en.json | 1 + app/src/translations/es.json | 1 + app/src/translations/fr.json | 1 + app/src/translations/hr.json | 1 + app/src/translations/hu.json | 1 + app/src/translations/it.json | 1 + app/src/translations/lv.json | 1 + app/src/translations/nb.json | 1 + app/src/translations/pl.json | 1 + app/src/translations/pt.json | 1 + app/src/translations/ro.json | 1 + app/src/translations/tr.json | 1 + app/src/translations/uk.json | 1 + 20 files changed, 138 insertions(+), 78 deletions(-) diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js index a32412f..d230db2 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import * as appPropTypes from '../../appPropTypes'; import { useIntl } from 'react-intl'; import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; import PanIcon from '@material-ui/icons/PanTool'; import EmptyAvatar from '../../../images/avatar-empty.jpeg'; @@ -58,23 +59,31 @@ const ListMe = (props) =>
{settings.displayName}
- - { - e.stopPropagation(); - - roomClient.setRaisedHand(!me.raisedHand); - }} + placement='bottom' > - - + + { + e.stopPropagation(); + + roomClient.setRaisedHand(!me.raisedHand); + }} + > + + + ); }; diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js index 914ae04..1aa70a1 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js @@ -7,6 +7,7 @@ import * as appPropTypes from '../../appPropTypes'; import { withRoomContext } from '../../../RoomContext'; import { useIntl } from 'react-intl'; import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; import VideocamIcon from '@material-ui/icons/Videocam'; import VideocamOffIcon from '@material-ui/icons/VideocamOff'; import VolumeUpIcon from '@material-ui/icons/VolumeUp'; @@ -99,90 +100,122 @@ const ListPeer = (props) => } { screenConsumer && - - { - e.stopPropagation(); - - screenVisible ? - roomClient.modifyPeerConsumer(peer.id, 'screen', true) : - roomClient.modifyPeerConsumer(peer.id, 'screen', false); - }} + placement='bottom' > - { screenVisible ? - - : - - } - + + { + e.stopPropagation(); + + screenVisible ? + roomClient.modifyPeerConsumer(peer.id, 'screen', true) : + roomClient.modifyPeerConsumer(peer.id, 'screen', false); + }} + > + { screenVisible ? + + : + + } + + } - - { - e.stopPropagation(); - - webcamEnabled ? - roomClient.modifyPeerConsumer(peer.id, 'webcam', true) : - roomClient.modifyPeerConsumer(peer.id, 'webcam', false); - }} + placement='bottom' > - { webcamEnabled ? - - : - - } - - - { - e.stopPropagation(); - - micEnabled ? - roomClient.modifyPeerConsumer(peer.id, 'mic', true) : - roomClient.modifyPeerConsumer(peer.id, 'mic', false); - }} - > - { micEnabled ? - - : - - } - - { isModerator && { e.stopPropagation(); - roomClient.kickPeer(peer.id); + webcamEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'webcam', true) : + roomClient.modifyPeerConsumer(peer.id, 'webcam', false); }} > - + { webcamEnabled ? + + : + + } + + + + { + e.stopPropagation(); + + micEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'mic', true) : + roomClient.modifyPeerConsumer(peer.id, 'mic', false); + }} + > + { micEnabled ? + + : + + } + + + { isModerator && + + + { + e.stopPropagation(); + + roomClient.kickPeer(peer.id); + }} + > + + + } {children} diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index e3b5597..3ead544 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "房间名称", "label.chooseRoomButton": "继续", diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index fd3cc3c..cd16197 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -79,6 +79,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Jméno místnosti", "label.chooseRoomButton": "Pokračovat", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index 8a119ad..b5015d7 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Name des Raums", "label.chooseRoomButton": "Weiter", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 40e2698..9f99a55 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Værelsesnavn", "label.chooseRoomButton": "Fortsæt", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 2600ab2..47d5fe0 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Όνομα δωματίου", "label.chooseRoomButton": "Συνέχεια", diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 19c46b4..c7447fe 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": "Mute participant", "tooltip.muteParticipantVideo": "Mute participant video", "tooltip.raisedHand": "Raise hand", + "tooltip.muteScreenSharing": "Mute participant share", "label.roomName": "Room name", "label.chooseRoomButton": "Continue", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index 4c8abea..a630e15 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Nombre de la sala", "label.chooseRoomButton": "Continuar", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index 9148655..d36e238 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Nom de la salle", "label.chooseRoomButton": "Continuer", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index 1f5ce55..68f8418 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": "Utišaj sudionika", "tooltip.muteParticipantVideo": "Ne primaj video sudionika", "tooltip.raisedHand": "Podigni ruku", + "tooltip.muteScreenSharing": null, "label.roomName": "Naziv sobe", "label.chooseRoomButton": "Nastavi", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index e185cfb..7c2ce89 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": "Résztvevő némítása", "tooltip.muteParticipantVideo": "Résztvevő video némítása", "tooltip.raisedHand": "Jelentkezés", + "tooltip.muteScreenSharing": null, "label.roomName": "Konferencia", "label.chooseRoomButton": "Tovább", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 284c40e..b088fa9 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -79,6 +79,7 @@ "tooltip.muteParticipant": "Muta partecipante", "tooltip.muteParticipantVideo": "Ferma video partecipante", "tooltip.raisedHand": "Mano alzata", + "tooltip.muteScreenSharing": null, "label.roomName": "Nome della stanza", "label.chooseRoomButton": "Continua", diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index dce2204..e981b35 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -79,6 +79,7 @@ "tooltip.muteParticipant": "Noklusināt dalībnieku", "tooltip.muteParticipantVideo": "Atslēgt dalībnieka video", "tooltip.raisedHand": "Pacelt roku", + "tooltip.muteScreenSharing": null, "label.roomName": "Sapulces telpas nosaukums (ID)", "label.chooseRoomButton": "Turpināt", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 2965f59..77c4ef5 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": "Demp deltaker", "tooltip.muteParticipantVideo": "Demp deltakervideo", "tooltip.raisedHand": "Rekk opp hånden", + "tooltip.muteScreenSharing": "Demp deltaker skjermdeling", "label.roomName": "Møtenavn", "label.chooseRoomButton": "Fortsett", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 14f5146..ffb41ec 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Nazwa konferencji", "label.chooseRoomButton": "Kontynuuj", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 89a8da1..3861c01 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Nome da sala", "label.chooseRoomButton": "Continuar", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 49936b0..ca906de 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Numele camerei", "label.chooseRoomButton": "Continuare", diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index 3fb115f..b53d1ea 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Oda adı", "label.chooseRoomButton": "Devam", diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index 6c15f7a..d9ceaa8 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -80,6 +80,7 @@ "tooltip.muteParticipant": null, "tooltip.muteParticipantVideo": null, "tooltip.raisedHand": null, + "tooltip.muteScreenSharing": null, "label.roomName": "Назва кімнати", "label.chooseRoomButton": "Продовжити", From c02c8b1d673f18a0cfd23e1de971fe0f874f4386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 21:27:45 +0200 Subject: [PATCH 05/22] Missing translation string --- app/src/RoomClient.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 8a502be..beea13b 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -2811,7 +2811,9 @@ export default class RoomClient { text : intl.formatMessage({ id : 'roles.gotRole', - defaultMessage : `You got the role: ${role}` + defaultMessage : 'You got the role: {role}' + }, { + role }) })); } From 3c7afd20667f88e5ea1799b8049259f089abcccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 21:31:21 +0200 Subject: [PATCH 06/22] Make buttons in AppBar fit on narrow screens, fixes #279 --- app/src/components/Controls/TopBar.js | 326 ++++++++++++++++++++++---- app/src/translations/cn.json | 1 + app/src/translations/cs.json | 1 + app/src/translations/de.json | 1 + app/src/translations/dk.json | 1 + app/src/translations/el.json | 1 + app/src/translations/en.json | 1 + app/src/translations/es.json | 1 + app/src/translations/fr.json | 1 + app/src/translations/hr.json | 1 + app/src/translations/hu.json | 1 + app/src/translations/it.json | 1 + app/src/translations/lv.json | 1 + app/src/translations/nb.json | 1 + app/src/translations/pl.json | 1 + app/src/translations/pt.json | 1 + app/src/translations/ro.json | 1 + app/src/translations/tr.json | 1 + app/src/translations/uk.json | 1 + 19 files changed, 300 insertions(+), 44 deletions(-) diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index cbe4bee..6422788 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -16,11 +16,13 @@ import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import MenuItem from '@material-ui/core/MenuItem'; import Menu from '@material-ui/core/Menu'; +import Popover from '@material-ui/core/Popover'; import Typography from '@material-ui/core/Typography'; import IconButton from '@material-ui/core/IconButton'; import MenuIcon from '@material-ui/icons/Menu'; import Avatar from '@material-ui/core/Avatar'; import Badge from '@material-ui/core/Badge'; +import Paper from '@material-ui/core/Paper'; import ExtensionIcon from '@material-ui/icons/Extension'; import AccountCircle from '@material-ui/icons/AccountCircle'; import FullScreenIcon from '@material-ui/icons/Fullscreen'; @@ -33,6 +35,7 @@ import LockOpenIcon from '@material-ui/icons/LockOpen'; import VideoCallIcon from '@material-ui/icons/VideoCall'; import Button from '@material-ui/core/Button'; import Tooltip from '@material-ui/core/Tooltip'; +import MoreIcon from '@material-ui/icons/MoreVert'; const styles = (theme) => ({ @@ -77,9 +80,17 @@ const styles = (theme) => display : 'block' } }, - actionButtons : - { - display : 'flex' + sectionDesktop : { + display : 'none', + [theme.breakpoints.up('md')] : { + display : 'flex' + } + }, + sectionMobile : { + display : 'flex', + [theme.breakpoints.up('md')] : { + display : 'none' + } }, actionButton : { @@ -96,7 +107,7 @@ const styles = (theme) => }, moreAction : { - margin : theme.spacing(0, 0, 0, 1) + margin : theme.spacing(0.5, 0, 0.5, 1.5) } }); @@ -135,16 +146,36 @@ const TopBar = (props) => { const intl = useIntl(); - const [ moreActionsElement, setMoreActionsElement ] = useState(null); + const [ mobileMoreAnchorEl, setMobileMoreAnchorEl ] = useState(null); + const [ anchorEl, setAnchorEl ] = useState(null); + const [ currentMenu, setCurrentMenu ] = useState(null); - const handleMoreActionsOpen = (event) => + const handleExited = () => { - setMoreActionsElement(event.currentTarget); + setCurrentMenu(null); }; - const handleMoreActionsClose = () => + const handleMobileMenuOpen = (event) => { - setMoreActionsElement(null); + setMobileMoreAnchorEl(event.currentTarget); + }; + + const handleMobileMenuClose = () => + { + setMobileMoreAnchorEl(null); + }; + + const handleMenuOpen = (event, menu) => + { + setAnchorEl(event.currentTarget); + setCurrentMenu(menu); + }; + + const handleMenuClose = () => + { + setAnchorEl(null); + + handleMobileMenuClose(); }; const { @@ -171,7 +202,8 @@ const TopBar = (props) => classes } = props; - const isMoreActionsMenuOpen = Boolean(moreActionsElement); + const isMenuOpen = Boolean(anchorEl); + const isMobileMenuOpen = Boolean(mobileMoreAnchorEl); const lockTooltip = room.locked ? intl.formatMessage({ @@ -239,10 +271,15 @@ const TopBar = (props) => { window.config.title ? window.config.title : 'Multiparty meeting' }
-
+
handleMenuOpen(event, 'moreActions')} color='inherit' > @@ -386,52 +423,253 @@ const TopBar = (props) => } -
-
+
+ + + +
+
+ - + { currentMenu === 'moreActions' && + + + { + handleMenuClose(); + setExtraVideoOpen(!room.extraVideoOpen); + }} + > + +

+ +

+
+
+ } + + + { loginEnabled && + + { + handleMenuClose(); + loggedIn ? roomClient.logout() : roomClient.login(); + }} + > + { myPicture ? + + : + + } + { loggedIn ? +

+ +

+ : +

+ +

+ } +
+ } { - handleMoreActionsClose(); - setExtraVideoOpen(!room.extraVideoOpen); + handleMenuClose(); + + if (room.locked) + { + roomClient.unlockRoom(); + } + else + { + roomClient.lockRoom(); + } }} > - + { room.locked ? + + : + + } + { room.locked ? +

+ +

+ : +

+ +

+ } +
+ + { + handleMenuClose(); + setSettingsOpen(!room.settingsOpen); + }} + > +

+

+
+ { lobbyPeers.length > 0 && + + { + handleMenuClose(); + setLockDialogOpen(!room.lockDialogOpen); + }} + > + + + +

+ +

+
+ } + + { + handleMenuClose(); + openUsersTab(); + }} + > + + + +

+ +

+
+ { fullscreenEnabled && + + { + handleMenuClose(); + onFullscreen(); + }} + > + { fullscreen ? + + : + + } +

+ +

+
+ } + handleMenuOpen(event, 'moreActions')} + > + +

+

diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index 3ead544..097e9b7 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "设置", "settings.camera": "视频设备", diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index cd16197..66e72b2 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -109,6 +109,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Nastavení", "settings.camera": "Kamera", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index b5015d7..6d72d54 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Einstellungen", "settings.camera": "Kamera", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 9f99a55..351907c 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Indstillinger", "settings.camera": "Kamera", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 47d5fe0..8f34eb1 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Ρυθμίσεις", "settings.camera": "Κάμερα", diff --git a/app/src/translations/en.json b/app/src/translations/en.json index c7447fe..660585d 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -110,6 +110,7 @@ "label.advanced": "Advanced", "label.addVideo": "Add video", "label.promoteAllPeers": "Promote all", + "label.moreActions": "More actions", "settings.settings": "Settings", "settings.camera": "Camera", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index a630e15..f685b8f 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Ajustes", "settings.camera": "Cámara", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index d36e238..d1a67cf 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Paramètres", "settings.camera": "Caméra", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index 68f8418..f08f516 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -110,6 +110,7 @@ "label.advanced": "Napredno", "label.addVideo": "Dodaj video", "label.promoteAllPeers": "Promoviraj sve", + "label.moreActions": null, "settings.settings": "Postavke", "settings.camera": "Kamera", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index 7c2ce89..0d1f419 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -110,6 +110,7 @@ "label.advanced": "Részletek", "label.addVideo": "Videó hozzáadása", "label.promoteAllPeers": "Mindenkit beengedek", + "label.moreActions": null, "settings.settings": "Beállítások", "settings.camera": "Kamera", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index b088fa9..472ce91 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -109,6 +109,7 @@ "label.advanced": "Avanzate", "label.addVideo": "Aggiungi video", "label.promoteAllPeers": "Promuovi tutti", + "label.moreActions": null, "settings.settings": "Impostazioni", "settings.camera": "Videocamera", diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index e981b35..dd8fac4 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -107,6 +107,7 @@ "label.appearence": "Izskats", "label.advanced": "Advancēts", "label.addVideo": "Pievienot video", + "label.moreActions": null, "settings.settings": "Iestatījumi", "settings.camera": "Kamera", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 77c4ef5..58e3b08 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -110,6 +110,7 @@ "label.advanced": "Avansert", "label.addVideo": "Legg til video", "label.promoteAllPeers": "Slipp inn alle", + "label.moreActions": "Flere handlinger", "settings.settings": "Innstillinger", "settings.camera": "Kamera", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index ffb41ec..923de79 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Ustawienia", "settings.camera": "Kamera", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index 3861c01..b863098 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Definições", "settings.camera": "Camera", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index ca906de..624c231 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Setări", "settings.camera": "Cameră video", diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index b53d1ea..8f9c29f 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Ayarlar", "settings.camera": "Kamera", diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index d9ceaa8..db4a082 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -110,6 +110,7 @@ "label.advanced": null, "label.addVideo": null, "label.promoteAllPeers": null, + "label.moreActions": null, "settings.settings": "Налаштування", "settings.camera": "Камера", From 2acd35d32b76f17004d916ea4441fe450a871ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 5 May 2020 21:41:23 +0200 Subject: [PATCH 07/22] Fixes in hungarian translation --- app/src/translations/hu.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index 0d1f419..c3dd780 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -1,24 +1,24 @@ { "socket.disconnected": "A kapcsolat lebomlott", "socket.reconnecting": "A kapcsolat lebomlott, újrapróbálkozás", - "socket.reconnected": "Sikeres újarkapcsolódás", + "socket.reconnected": "Sikeres újrakapcsolódás", "socket.requestError": "Sikertelen szerver lekérés", "room.chooseRoom": "Válaszd ki a konferenciaszobát", "room.cookieConsent": "Ez a weblap a felhasználói élmény fokozása miatt sütiket használ", "room.consentUnderstand": "Megértettem", - "room.joined": "Csatlakozátál a konferenciához", + "room.joined": "Csatlakoztál a konferenciához", "room.cantJoin": "Sikertelen csatlakozás a konferenciához", "room.youLocked": "A konferenciába való belépés letiltva", - "room.cantLock": "Sikertelen a konferenciaba való belépés letiltása", + "room.cantLock": "Sikertelen a konferenciába való belépés letiltása", "room.youUnLocked": "A konferenciába való belépés engedélyezve", "room.cantUnLock": "Sikertelen a konferenciába való belépés engedélyezése", "room.locked": "A konferenciába való belépés letiltva", "room.unlocked": "A konferenciába való belépés engedélyezve", "room.newLobbyPeer": "Új részvevő lépett be a konferencia előszobájába", "room.lobbyPeerLeft": "A konferencia előszobájából a részvevő távozott", - "room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő meváltoztatta a nevét: {displayName}", - "room.lobbyPeerChangedPicture": "Az előszobai résztvevő meváltoztatta a képét", + "room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő megváltoztatta a nevét: {displayName}", + "room.lobbyPeerChangedPicture": "Az előszobai résztvevő megváltoztatta a képét", "room.setAccessCode": "A konferencia hozzáférési kódja megváltozott", "room.accessCodeOn": "A konferencia hozzáférési kódja aktiválva", "room.accessCodeOff": "A konferencia hozzáférési kódka deaktiválva", @@ -39,7 +39,7 @@ "room.audioOnly": "csak Hang", "room.audioVideo": "Hang és Videó", "room.youAreReady": "Ok, kész vagy", - "room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferecnia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.", + "room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferencia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.", "room.locketWait": "Az automatikus belépés tiltva van - Várj amíg valaki beenged ...", "room.lobbyAdministration": "Előszoba adminisztráció", "room.peersInLobby": "Résztvevők az előszobában", @@ -54,7 +54,7 @@ "room.closeMeeting": "Konferencia lebontása", "room.clearChat": "Chat történelem kiürítése", "room.clearFileSharing": "File megosztás kiürítése", - "room.speechUnsupported": "A böngésződ nem támogatja a hangszint", + "room.speechUnsupported": "A böngésződ nem támogatja a hangfelismerést", "room.moderatoractions": "Moderátor funkciók", "room.raisedHand": "{displayName} jelentkezik", "room.loweredHand": "{displayName} leeresztette a kezét", @@ -68,7 +68,7 @@ "tooltip.login": "Belépés", "tooltip.logout": "Kilépés", - "tooltip.admitFromLobby": "Beenegdem az előszobából", + "tooltip.admitFromLobby": "Beengedem az előszobából", "tooltip.lockRoom": "A konferenciába való belépés letiltása", "tooltip.unLockRoom": "konferenciába való belépés engedélyezése", "tooltip.enterFullscreen": "Teljes képernyős mód", @@ -78,9 +78,9 @@ "tooltip.participants": "Résztvevők", "tooltip.kickParticipant": "Résztvevő kirúgása", "tooltip.muteParticipant": "Résztvevő némítása", - "tooltip.muteParticipantVideo": "Résztvevő video némítása", + "tooltip.muteParticipantVideo": "Résztvevő videóstreamének némítása", "tooltip.raisedHand": "Jelentkezés", - "tooltip.muteScreenSharing": null, + "tooltip.muteScreenSharing": "Képernyőmegosztás szüneteltetése", "label.roomName": "Konferencia", "label.chooseRoomButton": "Tovább", @@ -110,7 +110,7 @@ "label.advanced": "Részletek", "label.addVideo": "Videó hozzáadása", "label.promoteAllPeers": "Mindenkit beengedek", - "label.moreActions": null, + "label.moreActions": "További műveletek", "settings.settings": "Beállítások", "settings.camera": "Kamera", @@ -129,12 +129,12 @@ "settings.permanentTopBar": "Állandó felső sáv", "settings.lastn": "A látható videók száma", "settings.hiddenControls": "Média Gombok automatikus elrejtése", - "settings.notificationSounds": "Értesítések hangjelzjéssel", + "settings.notificationSounds": "Értesítések hangjelzéssel", "filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.startingFileShare": "Fájl megosztása", "filesharing.successfulFileShare": "A fájl sikeresen megosztva", - "filesharing.unableToShare": "Sikereteln fájl megosztás", + "filesharing.unableToShare": "Sikertelen fájl megosztás", "filesharing.error": "Hiba a fájlmegosztás során", "filesharing.finished": "A fájl letöltés befejeződött", "filesharing.save": "Mentés", @@ -144,7 +144,7 @@ "devices.devicesChanged": "Az eszközei megváltoztak, konfiguráld őket be a beállítások menüben", - "device.audioUnsupported": "A hnag nem támogatott", + "device.audioUnsupported": "A hang nem támogatott", "device.activateAudio": "Hang aktiválása", "device.muteAudio": "Hang némítása", "device.unMuteAudio": "Hang némítás kikapcsolása", @@ -155,9 +155,9 @@ "device.screenSharingUnsupported": "A képernyő megosztás nem támogatott", "device.startScreenSharing": "Képernyőmegosztás indítása", - "device.stopScreenSharing": "Képernyőmegosztás leáłłítása", + "device.stopScreenSharing": "Képernyőmegosztás leállítása", - "devices.microphoneDisconnected": "Microphone kapcsolat bontva", + "devices.microphoneDisconnected": "Mikrofon kapcsolat bontva", "devices.microphoneError": "Hiba történt a mikrofon hangeszköz elérése közben", "devices.microphoneMute": "A mikrofon némítva lett", "devices.microphoneUnMute": "A mikrofon némítása ki lett kapocsolva", From ee6409b2b3cc979d7d34d93aff18c7d719193a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Tue, 5 May 2020 22:20:21 +0200 Subject: [PATCH 08/22] Hide quality indicator if the quality has top score. --- app/src/components/VideoContainers/VideoView.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index fd84bae..9afcd3b 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -215,16 +215,16 @@ class VideoView extends React.PureComponent case 7: case 8: + case 9: { quality = ; break; } - case 9: case 10: { - quality = ; + quality = null; // ; break; } @@ -261,7 +261,7 @@ class VideoView extends React.PureComponent

{videoWidth}x{videoHeight}

}
- { !isMe && + { !isMe &&
{ quality From a2d11121d3593ee08722be50b8381cf1ccedef83 Mon Sep 17 00:00:00 2001 From: Stefan Otto Date: Tue, 5 May 2020 22:28:46 +0200 Subject: [PATCH 09/22] next iteration state + audio settings --- app/package.json | 1 + app/public/config/config.example.js | 16 ++++- app/src/RoomClient.js | 25 +++++--- app/src/actions/settingsActions.js | 39 +++++++++++++ app/src/components/Settings/Settings.js | 52 +++++++++++++++++ app/src/reducers/settings.js | 77 +++++++++++++++++++++++++ 6 files changed, 198 insertions(+), 12 deletions(-) diff --git a/app/package.json b/app/package.json index b58d66a..d7cff17 100644 --- a/app/package.json +++ b/app/package.json @@ -11,6 +11,7 @@ "@material-ui/core": "^4.5.1", "@material-ui/icons": "^4.5.1", "bowser": "^2.7.0", + "classnames": "^2.2.6", "dompurify": "^2.0.7", "domready": "^1.0.8", "end-of-stream": "1.4.0", diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index 9aaff4b..f64b139 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -31,9 +31,19 @@ var config = { tcp : true }, - lastN : 4, - mobileLastN : 1, - background : 'images/background.jpg', + lastN : 4, + mobileLastN : 1, + defaultAudio : + { + sampleRate : 48000, + channelCount : 1, + volume : 1.0, + autoGainControl : true, + echoCancellation : true, + noiseSuppression : true, + sampleSize : 16 + }, + background : 'images/background.jpg', // Add file and uncomment for adding logo to appbar // logo : 'images/logo.svg', title : 'Multiparty meeting', diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 0d0bd3a..de3e177 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -30,6 +30,7 @@ let requestTimeout, transportOptions, lastN, mobileLastN, + defaultAudio, defaultResolution; if (process.env.NODE_ENV !== 'test') @@ -39,6 +40,7 @@ if (process.env.NODE_ENV !== 'test') transportOptions, lastN, mobileLastN, + defaultAudio, defaultResolution } = window.config); } @@ -203,6 +205,9 @@ export default class RoomClient if (defaultResolution) store.dispatch(settingsActions.setVideoResolution(defaultResolution)); + if (defaultAudio) + store.dispatch(settingsActions.setDefaultAudio(defaultAudio)); + // Max spotlights if (device.bowser.getPlatformType() === 'desktop') this._maxSpotlights = lastN; @@ -1036,23 +1041,24 @@ export default class RoomClient if (this._micProducer && this._micProducer.track) this._micProducer.track.stop(); - logger.debug('changeAudioDevice() | calling getUserMedia()'); + logger.debug('changeAudioDevice() | calling getUserMedia() %o', store.getState().settings); const stream = await navigator.mediaDevices.getUserMedia( { audio : { - deviceId : { exact: device.deviceId }, - sampleRate : 48000, - channelCount : 1, - volume : 1.0, - autoGainControl : true, - echoCancellation : false, - noiseSuppression : false, - sampleSize : 16 + deviceId : { exact: device.deviceId }, + sampleRate : store.getState().settings.sampleRate, + channelCount : store.getState().settings.channelCount, + volume : store.getState().settings.volume, + autoGainControl : store.getState().settings.autoGainControl, + echoCancellation : store.getState().settings.echoCancellation, + noiseSuppression : store.getState().settings.noiseSuppression, + sampleSize : store.getState().settings.sampleSize } } ); + logger.debug('Constraints: %o', stream.getAudioTracks()[0].getConstraints()); const track = stream.getAudioTracks()[0]; @@ -2697,6 +2703,7 @@ export default class RoomClient } } ); + logger.debug('Constraints: %o', stream.getAudioTracks()[0].getConstraints()); track = stream.getAudioTracks()[0]; diff --git a/app/src/actions/settingsActions.js b/app/src/actions/settingsActions.js index 79b5ef2..68b4257 100644 --- a/app/src/actions/settingsActions.js +++ b/app/src/actions/settingsActions.js @@ -32,6 +32,45 @@ export const togglePermanentTopBar = () => type : 'TOGGLE_PERMANENT_TOPBAR' }); +export const setEchoCancellation = (echoCancellation) => + ({ + type : 'SET_ECHO_CANCELLATION', + payload : { echoCancellation } + }); + +export const setAutoGainControl = (autoGainControl) => + ({ + type : 'SET_AUTO_GAIN_CONTROL', + payload : { autoGainControl } + }); + +export const setNoiseSuppression = (noiseSuppression) => + ({ + type : 'SET_NOISE_SUPPRESSION', + payload : { noiseSuppression } + }); + +export const setDefaultAudio = (defaultAudio) => + ({ + type : 'SET_DEFAULT_AUDIO', + payload : { defaultAudio } + }); + +export const toggleEchoCancellation = () => + ({ + type : 'TOGGLE_ECHO_CANCELLATION' + }); + +export const toggleAutoGainControl = () => + ({ + type : 'TOGGLE_AUTO_GAIN_CONTROL' + }); + +export const toggleNoiseSuppression = () => + ({ + type : 'TOGGLE_NOISE_SUPPRESSION' + }); + export const setLastN = (lastN) => ({ type : 'SET_LAST_N', diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index 91ba0db..3c56be4 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -60,6 +60,9 @@ const Settings = ({ settings, onToggleAdvancedMode, onTogglePermanentTopBar, + setEchoCancellation, + setAutoGainControl, + setNoiseSuppression, handleCloseSettings, handleChangeMode, classes @@ -329,6 +332,49 @@ const Settings = ({ + + { + setEchoCancellation(event.target.checked); + roomClient.changeAudioDevice(settings.selectedAudioDevice); + }} + />} + label={intl.formatMessage({ + id : 'settings.echoCancellation', + defaultMessage : 'Echo Cancellation' + })} + /> + { + setAutoGainControl(event.target.checked); + roomClient.changeAudioDevice(settings.selectedAudioDevice); + }} + />} + label={intl.formatMessage({ + id: 'settings.autoGainControl', + defaultMessage: 'Auto Gain Control' + })} + /> + { + setNoiseSuppression(event.target.checked); + roomClient.changeAudioDevice(settings.selectedAudioDevice); + }} + />} + label={intl.formatMessage({ + id: 'settings.noiseSuppression', + defaultMessage: 'Noise Suppression' + })} + /> } @@ -359,6 +405,9 @@ Settings.propTypes = settings : PropTypes.object.isRequired, onToggleAdvancedMode : PropTypes.func.isRequired, onTogglePermanentTopBar : PropTypes.func.isRequired, + setEchoCancellation : PropTypes.func.isRequired, + setAutoGainControl : PropTypes.func.isRequired, + setNoiseSuppression : PropTypes.func.isRequired, handleChangeMode : PropTypes.func.isRequired, handleCloseSettings : PropTypes.func.isRequired, classes : PropTypes.object.isRequired @@ -376,6 +425,9 @@ const mapStateToProps = (state) => const mapDispatchToProps = { onToggleAdvancedMode : settingsActions.toggleAdvancedMode, onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, + setEchoCancellation : settingsActions.setEchoCancellation, + setAutoGainControl : settingsActions.toggleAutoGainControl, + setNoiseSuppression : settingsActions.toggleNoiseSuppression, handleChangeMode : roomActions.setDisplayMode, handleCloseSettings : roomActions.setSettingsOpen }; diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js index 21d59db..60f05fd 100644 --- a/app/src/reducers/settings.js +++ b/app/src/reducers/settings.js @@ -4,6 +4,13 @@ const initialState = selectedWebcam : null, selectedAudioDevice : null, advancedMode : false, + sampleRate : 48000, + channelCount : 1, + volume : 1.0, + autoGainControl : true, + echoCancellation : true, + noiseSuppression : true, + sampleSize : 16, resolution : 'medium', // low, medium, high, veryhigh, ultra lastN : 4, permanentTopBar : true @@ -37,6 +44,76 @@ const settings = (state = initialState, action) => return { ...state, advancedMode }; } + case 'SET_SAMPLE_RATE': + { + const { sampleRate } = action.payload; + + return { ...state, sampleRate }; + } + + case 'SET_CHANNEL_COUNT': + { + const { channelCount } = action.payload; + + return { ...state, channelCount }; + } + + case 'SET_VOLUME': + { + const { volume } = action.payload; + + return { ...state, volume }; + } + + case 'SET_AUTO_GAIN_CONTROL': + { + const { autoGainControl } = action.payload; + + return { ...state, autoGainControl }; + } + + case 'SET_ECHO_CANCELLATION': + { + const { echoCancellation } = action.payload; + + return { ...state, echoCancellation }; + } + + case 'SET_NOISE_SUPPRESSION': + { + const { noiseSuppression } = action.payload; + + return { ...state, noiseSuppression }; + } + + case 'TOGGLE_AUTO_GAIN_CONTROL': + { + const autoGainControl = !state.autoGainControl; + + return { ...state, autoGainControl }; + } + + case 'TOGGLE_ECHO_CANCELLATION': + { + const echoCancellation = !state.echoCancellation; + + return { ...state, echoCancellation }; + } + + case 'TOGGLE_NOISE_SUPPRESSION': + { + const noiseSuppression = !state.noiseSuppression; + + return { ...state, noiseSuppression }; + } + + case 'SET_SAMPLE_SIZE': + { + const { sampleSize } = action.payload; + + return { ...state, sampleSize }; + } + case 'SET_LAST_N': { const { lastN } = action.payload; From fa5f4f02a606e9d5ceceba65114a740388fa7435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9sz=C3=A1ros=20Mih=C3=A1ly?= Date: Tue, 5 May 2020 23:23:52 +0200 Subject: [PATCH 10/22] Use full room and peer object for the statusLogger --- server/config/config.example.js | 8 ++++---- server/server.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/config/config.example.js b/server/config/config.example.js index 9d27f11..dda9e2f 100644 --- a/server/config/config.example.js +++ b/server/config/config.example.js @@ -96,8 +96,8 @@ module.exports = this._queue = new AwaitQueue(); } - // rooms: number of rooms - // peers: number of peers + // rooms: rooms object + // peers: peers object // eslint-disable-next-line no-unused-vars async log({ rooms, peers }) { @@ -106,9 +106,9 @@ module.exports = // Do your logging in here, use queue to keep correct order // eslint-disable-next-line no-console - console.log('Number of rooms: ', rooms); + console.log('Number of rooms: ', rooms.size); // eslint-disable-next-line no-console - console.log('Number of peers: ', peers); + console.log('Number of peers: ', peers.size); }) .catch((error) => { diff --git a/server/server.js b/server/server.js index 42112fe..5820413 100755 --- a/server/server.js +++ b/server/server.js @@ -197,8 +197,8 @@ function statusLog() if (statusLogger) { statusLogger.log({ - rooms : rooms.size, - peers : peers.size + rooms : rooms, + peers : peers }); } } From da6c9d3ecf4005204020fbad932b5a7e61e12f3b Mon Sep 17 00:00:00 2001 From: Stefan Otto Date: Tue, 5 May 2020 23:41:27 +0200 Subject: [PATCH 11/22] fix settings.js --- app/src/reducers/settings.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js index 60f05fd..79d2bc3 100644 --- a/app/src/reducers/settings.js +++ b/app/src/reducers/settings.js @@ -86,6 +86,13 @@ const settings = (state = initialState, action) => return { ...state, noiseSuppression }; } + case 'SET_DEFAULT_AUDIO': + { + const { audio } = action.payload; + + return { ...state, audio }; + } + case 'TOGGLE_AUTO_GAIN_CONTROL': { const autoGainControl = !state.autoGainControl; From 136037d83f60261234ecaf8f90710a44b7814c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 6 May 2020 01:40:08 +0200 Subject: [PATCH 12/22] Simplify participantlist and order participants based on status. Raise hand queue, and moderator can remove raised hand. Fixes #146, #278 --- app/src/RoomClient.js | 27 +++++++ app/src/actions/peerActions.js | 6 ++ .../MeetingDrawer/ParticipantList/ListMe.js | 11 ++- .../MeetingDrawer/ParticipantList/ListPeer.js | 48 ++++++++++-- .../ParticipantList/ParticipantList.js | 73 ++++++++----------- app/src/components/Selectors.js | 30 +++++++- app/src/reducers/peers.js | 7 ++ server/lib/Room.js | 23 ++++++ 8 files changed, 168 insertions(+), 57 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index beea13b..1c93794 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -1533,6 +1533,26 @@ export default class RoomClient } } + async lowerPeerHand(peerId) + { + logger.debug('lowerPeerHand() [peerId:"%s"]', peerId); + + store.dispatch( + peerActions.setPeerRaisedHandInProgress(peerId, true)); + + try + { + await this.sendRequest('moderator:lowerHand', { peerId }); + } + catch (error) + { + logger.error('lowerPeerHand() | [error:"%o"]', error); + } + + store.dispatch( + peerActions.setPeerRaisedHandInProgress(peerId, false)); + } + async setRaisedHand(raisedHand) { logger.debug('setRaisedHand: ', raisedHand); @@ -2534,6 +2554,13 @@ export default class RoomClient break; } + case 'moderator:lowerHand': + { + this.setRaisedHand(false); + + break; + } + case 'gotRole': { const { peerId, role } = notification.data; diff --git a/app/src/actions/peerActions.js b/app/src/actions/peerActions.js index fee30a5..414b744 100644 --- a/app/src/actions/peerActions.js +++ b/app/src/actions/peerActions.js @@ -40,6 +40,12 @@ export const setPeerRaisedHand = (peerId, raisedHand, raisedHandTimestamp) => payload : { peerId, raisedHand, raisedHandTimestamp } }); +export const setPeerRaisedHandInProgress = (peerId, flag) => + ({ + type : 'SET_PEER_RAISED_HAND_IN_PROGRESS', + payload : { peerId, flag } + }); + export const setPeerPicture = (peerId, picture) => ({ type : 'SET_PEER_PICTURE', diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js index d230db2..33873d2 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js @@ -2,6 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; +import classnames from 'classnames'; import PropTypes from 'prop-types'; import * as appPropTypes from '../../appPropTypes'; import { useIntl } from 'react-intl'; @@ -23,7 +24,7 @@ const styles = (theme) => { borderRadius : '50%', height : '2rem', - marginTop : theme.spacing(1) + marginTop : theme.spacing(0.5) }, peerInfo : { @@ -33,6 +34,10 @@ const styles = (theme) => flexGrow : 1, alignItems : 'center' }, + buttons : + { + padding : theme.spacing(1) + }, green : { color : 'rgba(0, 153, 0, 1)' @@ -71,7 +76,9 @@ const ListMe = (props) => id : 'tooltip.raisedHand', defaultMessage : 'Raise hand' })} - className={me.raisedHand ? classes.green : null} + className={ + classnames(me.raisedHand ? classes.green : null, classes.buttons) + } disabled={me.raisedHandInProgress} color='primary' onClick={(e) => diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js index 1aa70a1..d8b3fb3 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import * as appPropTypes from '../../appPropTypes'; import { withRoomContext } from '../../../RoomContext'; import { useIntl } from 'react-intl'; +import { green } from '@material-ui/core/colors'; import IconButton from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; import VideocamIcon from '@material-ui/icons/Videocam'; @@ -17,6 +18,7 @@ import ScreenOffIcon from '@material-ui/icons/StopScreenShare'; import ExitIcon from '@material-ui/icons/ExitToApp'; import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import PanIcon from '@material-ui/icons/PanTool'; +import RecordVoiceOverIcon from '@material-ui/icons/RecordVoiceOver'; const styles = (theme) => ({ @@ -31,7 +33,7 @@ const styles = (theme) => { borderRadius : '50%', height : '2rem', - marginTop : theme.spacing(1) + marginTop : theme.spacing(0.5) }, peerInfo : { @@ -44,11 +46,16 @@ const styles = (theme) => indicators : { display : 'flex', - padding : theme.spacing(1.5) + padding : theme.spacing(1) + }, + buttons : + { + padding : theme.spacing(1) }, green : { - color : 'rgba(0, 153, 0, 1)' + color : 'rgba(0, 153, 0, 1)', + marginLeft : theme.spacing(2) } }); @@ -59,6 +66,7 @@ const ListPeer = (props) => const { roomClient, isModerator, + spotlight, peer, micConsumer, webcamConsumer, @@ -94,11 +102,30 @@ const ListPeer = (props) =>
{peer.displayName}
-
- { peer.raisedHand && - - } -
+ { peer.raisedHand && + + { + e.stopPropagation(); + + roomClient.lowerPeerHand(peer.id); + }} + > + + + } + { spotlight && + + + + } { screenConsumer && })} color={screenVisible ? 'primary' : 'secondary'} disabled={peer.peerScreenInProgress} + className={classes.buttons} onClick={(e) => { e.stopPropagation(); @@ -145,6 +173,7 @@ const ListPeer = (props) => })} color={webcamEnabled ? 'primary' : 'secondary'} disabled={peer.peerVideoInProgress} + className={classes.buttons} onClick={(e) => { e.stopPropagation(); @@ -175,6 +204,7 @@ const ListPeer = (props) => })} color={micEnabled ? 'primary' : 'secondary'} disabled={peer.peerAudioInProgress} + className={classes.buttons} onClick={(e) => { e.stopPropagation(); @@ -205,6 +235,7 @@ const ListPeer = (props) => defaultMessage : 'Kick out participant' })} disabled={peer.peerKickInProgress} + className={classes.buttons} color='secondary' onClick={(e) => { @@ -227,6 +258,7 @@ ListPeer.propTypes = roomClient : PropTypes.any.isRequired, advancedMode : PropTypes.bool, isModerator : PropTypes.bool, + spotlight : PropTypes.bool, peer : appPropTypes.Peer.isRequired, micConsumer : appPropTypes.Consumer, webcamConsumer : appPropTypes.Consumer, diff --git a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js index af35dbd..411c745 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js @@ -1,10 +1,9 @@ import React from 'react'; import { connect } from 'react-redux'; import { - passivePeersSelector, - spotlightSortedPeersSelector + participantListSelector } from '../../Selectors'; -import classNames from 'classnames'; +import classnames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; import PropTypes from 'prop-types'; @@ -76,9 +75,9 @@ class ParticipantList extends React.PureComponent roomClient, advancedMode, isModerator, - passivePeers, + participants, + spotlights, selectedPeerId, - spotlightPeers, classes } = this.props; @@ -107,48 +106,34 @@ class ParticipantList extends React.PureComponent
  • - { spotlightPeers.map((peer) => ( + { participants.map((peer) => (
  • roomClient.setSelectedPeer(peer.id)} > - - - -
  • - ))} -
-
    -
  • - -
  • - { passivePeers.map((peer) => ( -
  • roomClient.setSelectedPeer(peer.id)} - > - + { spotlights.includes(peer.id) ? + + + + : + + }
  • ))}
@@ -162,9 +147,9 @@ ParticipantList.propTypes = roomClient : PropTypes.any.isRequired, advancedMode : PropTypes.bool, isModerator : PropTypes.bool, - passivePeers : PropTypes.array, + participants : PropTypes.array, + spotlights : PropTypes.array, selectedPeerId : PropTypes.string, - spotlightPeers : PropTypes.array, classes : PropTypes.object.isRequired }; @@ -174,9 +159,9 @@ const mapStateToProps = (state) => isModerator : state.me.roles.some((role) => state.room.permissionsFromRoles.MODERATE_ROOM.includes(role)), - passivePeers : passivePeersSelector(state), - selectedPeerId : state.room.selectedPeerId, - spotlightPeers : spotlightSortedPeersSelector(state) + participants : participantListSelector(state), + spotlights : state.room.spotlights, + selectedPeerId : state.room.selectedPeerId }; }; diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js index fd22aff..b8e5e41 100644 --- a/app/src/components/Selectors.js +++ b/app/src/components/Selectors.js @@ -12,7 +12,8 @@ const peersKeySelector = createSelector( peersSelector, (peers) => Object.keys(peers) ); -const peersValueSelector = createSelector( + +export const peersValueSelector = createSelector( peersSelector, (peers) => Object.values(peers) ); @@ -113,8 +114,31 @@ export const spotlightPeersSelector = createSelector( export const spotlightSortedPeersSelector = createSelector( spotlightsSelector, peersValueSelector, - (spotlights, peers) => peers.filter((peer) => spotlights.includes(peer.id)) - .sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) + (spotlights, peers) => + peers.filter((peer) => spotlights.includes(peer.id) && !peer.raisedHand) + .sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) +); + +const raisedHandSortedPeers = createSelector( + peersValueSelector, + (peers) => peers.filter((peer) => peer.raisedHand) + .sort((a, b) => a.raisedHandTimestamp - b.raisedHandTimestamp) +); + +const peersSortedSelector = createSelector( + spotlightsSelector, + peersValueSelector, + (spotlights, peers) => + peers.filter((peer) => !spotlights.includes(peer.id) && !peer.raisedHand) + .sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) +); + +export const participantListSelector = createSelector( + raisedHandSortedPeers, + spotlightSortedPeersSelector, + peersSortedSelector, + (raisedHands, spotlights, peers) => + [ ...raisedHands, ...spotlights, ...peers ] ); export const peersLengthSelector = createSelector( diff --git a/app/src/reducers/peers.js b/app/src/reducers/peers.js index 4c8bee1..3e2c6a0 100644 --- a/app/src/reducers/peers.js +++ b/app/src/reducers/peers.js @@ -26,6 +26,12 @@ const peer = (state = {}, action) => raisedHand : action.payload.raisedHand, raisedHandTimestamp : action.payload.raisedHandTimestamp }; + + case 'SET_PEER_RAISED_HAND_IN_PROGRESS': + return { + ...state, + raisedHandInProgress : action.payload.flag + }; case 'ADD_CONSUMER': { @@ -91,6 +97,7 @@ const peers = (state = {}, action) => case 'SET_PEER_AUDIO_IN_PROGRESS': case 'SET_PEER_SCREEN_IN_PROGRESS': case 'SET_PEER_RAISED_HAND': + case 'SET_PEER_RAISED_HAND_IN_PROGRESS': case 'SET_PEER_PICTURE': case 'ADD_CONSUMER': case 'ADD_PEER_ROLE': diff --git a/server/lib/Room.js b/server/lib/Room.js index 0f89b4d..1b207fa 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -1430,6 +1430,29 @@ class Room extends EventEmitter break; } + case 'moderator:lowerHand': + { + if ( + !peer.roles.some( + (role) => permissionsFromRoles.MODERATE_ROOM.includes(role) + ) + ) + throw new Error('peer not authorized'); + + const { peerId } = request.data; + + const lowerPeer = this._peers[peerId]; + + if (!lowerPeer) + throw new Error(`peer with id "${peerId}" not found`); + + this._notification(lowerPeer.socket, 'moderator:lowerHand'); + + cb(); + + break; + } + default: { logger.error('unknown request.method "%s"', request.method); From aca3499afb8d10091bc7cc72e08ce240b322af2d Mon Sep 17 00:00:00 2001 From: Stefan Otto Date: Wed, 6 May 2020 02:33:37 +0200 Subject: [PATCH 13/22] merge from develop --- CHANGELOG.md | 32 +- CONTRIBUTING.md | 1 + HAproxy.md | 101 ++ LICENSE.md | 21 + LTI/LTI.md | 61 ++ LTI/lti1.png | Bin 0 -> 87852 bytes LTI/lti2.png | Bin 0 -> 59763 bytes LTI/lti3.png | Bin 0 -> 118045 bytes LTI/lti4.png | Bin 0 -> 136597 bytes README.md | 23 +- app/package.json | 17 +- app/public/config/config.example.js | 34 +- app/public/privacy/privacy.html | 13 + app/src/RoomClient.js | 893 +++++++++++++++--- app/src/ScreenShare.js | 5 +- app/src/__tests__/Room.spec.js | 17 +- app/src/__tests__/RoomClient.spec.js | 2 +- app/src/actions/chatActions.js | 5 + app/src/actions/fileActions.js | 5 + app/src/actions/meActions.js | 25 +- app/src/actions/peerActions.js | 6 +- app/src/actions/roomActions.js | 54 +- app/src/actions/settingsActions.js | 16 + .../AccessControl/LockDialog/ListLobbyPeer.js | 97 +- .../AccessControl/LockDialog/LockDialog.js | 94 +- app/src/components/App.js | 2 +- app/src/components/ChooseRoom.js | 4 +- app/src/components/Containers/Me.js | 481 ++++++++-- app/src/components/Containers/Peer.js | 424 +++++++-- app/src/components/Containers/SpeakerPeer.js | 59 +- app/src/components/Controls/ExtraVideo.js | 167 ++++ app/src/components/Controls/MobileControls.js | 172 ---- app/src/components/Controls/TopBar.js | 692 ++++++++++---- app/src/components/JoinDialog.js | 62 +- app/src/components/MeetingDrawer/Chat/Chat.js | 2 + .../MeetingDrawer/Chat/ChatInput.js | 11 +- .../MeetingDrawer/Chat/ChatModerator.js | 100 ++ .../components/MeetingDrawer/Chat/Message.js | 14 +- .../MeetingDrawer/FileSharing/FileSharing.js | 103 +- .../FileSharing/FileSharingModerator.js | 100 ++ .../components/MeetingDrawer/MeetingDrawer.js | 32 +- .../MeetingDrawer/ParticipantList/ListMe.js | 107 +-- .../ParticipantList/ListModerator.js | 12 +- .../MeetingDrawer/ParticipantList/ListPeer.js | 169 ++-- .../ParticipantList/ParticipantList.js | 39 +- app/src/components/MeetingViews/Filmstrip.js | 121 ++- app/src/components/PeerAudio/AudioPeers.js | 17 +- app/src/components/PeerAudio/PeerAudio.js | 33 +- app/src/components/Room.js | 27 +- app/src/components/Selectors.js | 79 +- .../components/Settings/AdvancedSettings.js | 125 +++ .../components/Settings/AppearenceSettings.js | 143 +++ app/src/components/Settings/MediaSettings.js | 341 +++++++ app/src/components/Settings/Settings.js | 426 ++------- .../VideoContainers/FullScreenView.js | 25 +- .../components/VideoContainers/FullView.js | 9 +- .../components/VideoContainers/VideoView.js | 208 +++- app/src/components/VideoWindow/VideoWindow.js | 25 +- app/src/components/appPropTypes.js | 6 +- app/src/deviceInfo.js | 8 +- app/src/images/icon-hand-black.svg | 26 - app/src/images/icon-hand-white.svg | 26 - app/src/index.js | 16 +- app/src/reducers/chat.js | 5 + app/src/reducers/files.js | 3 + app/src/reducers/lobbyPeers.js | 2 +- app/src/reducers/me.js | 31 +- app/src/reducers/peers.js | 10 +- app/src/reducers/room.js | 107 ++- app/src/reducers/settings.js | 26 +- app/src/reducers/userRoles.js | 4 - app/src/translations/cn.json | 37 +- app/src/translations/{cz.json => cs.json} | 41 +- app/src/translations/de.json | 149 +-- app/src/translations/dk.json | 37 +- app/src/translations/el.json | 37 +- app/src/translations/en.json | 37 +- app/src/translations/es.json | 37 +- app/src/translations/fr.json | 37 +- app/src/translations/hr.json | 71 +- app/src/translations/hu.json | 87 +- app/src/translations/it.json | 47 +- app/src/translations/lv.json | 172 ++++ app/src/translations/nb.json | 37 +- app/src/translations/pl.json | 41 +- app/src/translations/pt.json | 37 +- app/src/translations/ro.json | 37 +- app/src/translations/tr.json | 170 ++++ app/src/translations/uk.json | 178 ++++ munin/mm-plugin | 2 +- prom.md | 55 ++ server/config/config.example.js | 125 ++- server/lib/Lobby.js | 7 +- server/lib/Peer.js | 76 +- server/lib/Room.js | 590 +++++++++--- server/lib/promExporter.js | 284 ++++++ server/package.json | 15 +- server/server.js | 168 +++- server/userRoles.js | 13 +- 99 files changed, 6709 insertions(+), 2038 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 HAproxy.md create mode 100644 LICENSE.md create mode 100644 LTI/LTI.md create mode 100644 LTI/lti1.png create mode 100644 LTI/lti2.png create mode 100644 LTI/lti3.png create mode 100644 LTI/lti4.png create mode 100644 app/public/privacy/privacy.html create mode 100644 app/src/components/Controls/ExtraVideo.js delete mode 100644 app/src/components/Controls/MobileControls.js create mode 100644 app/src/components/MeetingDrawer/Chat/ChatModerator.js create mode 100644 app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js create mode 100644 app/src/components/Settings/AdvancedSettings.js create mode 100644 app/src/components/Settings/AppearenceSettings.js create mode 100644 app/src/components/Settings/MediaSettings.js delete mode 100644 app/src/images/icon-hand-black.svg delete mode 100644 app/src/images/icon-hand-white.svg delete mode 100644 app/src/reducers/userRoles.js rename app/src/translations/{cz.json => cs.json} (85%) create mode 100644 app/src/translations/lv.json create mode 100644 app/src/translations/tr.json create mode 100644 app/src/translations/uk.json create mode 100644 prom.md create mode 100644 server/lib/promExporter.js diff --git a/CHANGELOG.md b/CHANGELOG.md index bda55dd..471b71f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,32 @@ # Changelog +## 3.2.1 + +* Fix: permananent top bar by default +* Fix: `httpOnly` mode https redirect +* Add some extra checks for video stream and track +* Add Italian translation +* Add Czech translation +* Add new server option `trustProxy` for load balancing http only use case +* Add HAproxy load balance example +* Add LTI LMS integration documentation +* Fix spacing of leave button +* Fix for sharing same file multiple times + ## 3.2 * Add munin plugin -* Add muted=true search param to disble audio by deffault +* Add `muted=true` search param to disable audio by default * Modify webtorrent tracker * Add key shortcut `space` for audio mute * Add key shortcut `v` for video mute * Add user configurable LastN -* Add option to sticky top bar (sticky by default) -* update mediasoup server -* Add simulcast options to app config (disabled by default) -* Add stats option to get counts of rooms and peers -* Add httpOnly option for loadbalancer backend setups +* Add option to permananent top bar (permanent by default) +* Update mediasoup server +* Add `simulcast` options to app config (disabled by default) +* Add `stats` option to get counts of rooms and peers +* Add `httpOnly` option for loadbalancer backend setups * LTI integration for LMS systems like moodle -* Add muted=false search parameter * Add translations (12+1 languages) * Add support IPv6 * Many other fixes and refactorings @@ -33,10 +45,10 @@ * Updated to mediasoup v3 * Replace lib "passport-datporten" with "openid-client" (a general OIDC certified client) - - OpenID Connect discovery - - Auth code flow + * OpenID Connect discovery + * Auth code flow * Add spdy http2 support. - - Notice it does not supports node 11.x + * Notice it does not supports node 11.x * Updated to Material UI v4 ## 2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d5c8efc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +Source code contributions should pass static code analysis as performed by `npm run lint` in `server` and `app` respectively. diff --git a/HAproxy.md b/HAproxy.md new file mode 100644 index 0000000..485e11e --- /dev/null +++ b/HAproxy.md @@ -0,0 +1,101 @@ +# Howto deploy a (room based) load balanced cluster + +This example will show how to setup an HA proxy to provide load balancing between several +multiparty-meeting servers. + +## IP and DNS + +In this basic example we use the following names and ips: + +### Backend + +* `mm1.example.com` <=> `192.0.2.1` +* `mm2.example.com` <=> `192.0.2.2` +* `mm3.example.com` <=> `192.0.2.3` + +### Redis + +* `redis.example.com` <=> `192.0.2.4` + +### Load balancer HAproxy + +* `meet.example.com` <=> `192.0.2.5` + +## Deploy multiple multiparty-meeting servers + +This is most easily done using Ansible (see below), but can be done +in any way you choose (manual, Docker, Ansible). + +Read more here: [mm-ansible](https://github.com/misi/mm-ansible) +[![asciicast](https://asciinema.org/a/311365.svg)](https://asciinema.org/a/311365) + +## Setup Redis for central HTTP session store + +### Use one Redis for all multiparty-meeting servers + +* Deploy a Redis cluster for all instances. + * We will use in our actual example `192.0.2.4` as redis HA cluster ip. It is out of scope howto deploy it. + +OR + +* For testing you can use Redis from one the multiparty-meeting servers. e.g. If you plan only for testing on your first multiparty-meeting server. + * Configure Redis `redis.conf` to not only bind to your loopback but also to your global ip address too: + + ``` plaintext + bind 192.0.2.1 + ``` + + This example sets this to `192.0.2.1`, change this according to your local installation. + + * Change your firewall config to allow incoming Redis. Example (depends on the type of firewall): + + ``` plaintext + chain INPUT { + policy DROP; + + saddr mm2.example.com proto tcp dport 6379 ACCEPT; + saddr mm3.example.com proto tcp dport 6379 ACCEPT; + } + ``` + + * **Set a password, or if you don't (like in this basic example) take care to set strict firewall rules** + +## Configure multiparty-meeting servers + +### Server config + +mm/configs/server/config.js + +``` js +redisOptions : { host: '192.0.2.4'}, +listeningPort: 80, +httpOnly: true, +trustProxy : ['192.0.2.5'], +``` + +## Deploy HA proxy + +* Configure certificate / letsencrypt for `meet.example.com` + * In this example we put a complete chain and private key in /root/certificate.pem. +* Install and setup haproxy + + `apt install haproxy` + +* Add to /etc/haproxy/haproxy.cfg config + + ``` plaintext + backend multipartymeeting + balance url_param roomId + hash-type consistent + + server mm1 192.0.2.1:80 check maxconn 20 verify none + server mm2 192.0.2.2:80 check maxconn 20 verify none + server mm3 192.0.2.3:80 check maxconn 20 verify none + + frontend meet.example.com + bind 192.0.2.5:80 + bind 192.0.2.5:443 ssl crt /root/certificate.pem + http-request redirect scheme https unless { ssl_fc } + reqadd X-Forwarded-Proto:\ https + default_backend multipartymeeting + ``` diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b3d9d19 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 GÉANT Association + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LTI/LTI.md b/LTI/LTI.md new file mode 100644 index 0000000..15b6e6e --- /dev/null +++ b/LTI/LTI.md @@ -0,0 +1,61 @@ +# Learning Tools Interoperability (LTI) + +## LTI + +Read more about IMS Global defined interface for tools like our VideoConference system integration with Learning Management Systems(LMS) (e.g. moodle). +See: [IMS Global Learning Tool Interoperability](https://www.imsglobal.org/activity/learning-tools-interoperability) + +We implemented LTI interface version 1.0/1.1 + +### Server config auth section LTI settings + +Set in server configuration a random key and secret + +``` json +auth : + { + lti : + { + consumerKey : 'key', + consumerSecret : 'secret' + }, + } +``` + +### Configure your LMS system with secret and key settings above + +#### Auth tool URL + +Set tool URL to your server with path /auth/lti + +``` url +https://mm.example.com/auth/lti +``` + +#### In moodle find external tool plugin setting and external tool action + +See: [moodle external tool settings](https://docs.moodle.org/38/en/External_tool_settings) + +#### Add and activity + +![Add external tool](lti1.png) + +#### Setup Activity + +##### Activity setup basic form + +Open fully the settings **Click on show more!!** +![Add external tool config](lti2.png) + +##### Empty full form + +![Opened external tool config](lti3.png) + +##### Filled out form + +![Filled out external tool config](lti4.png) + +## moodle plugin + +Alternatively you can use multipartymeeting moodle plugin: +[https://github.com/misi/moodle-mod_multipartymeeting](https://github.com/misi/moodle-mod_multipartymeeting) diff --git a/LTI/lti1.png b/LTI/lti1.png new file mode 100644 index 0000000000000000000000000000000000000000..cc420c49bb09244b2d53eda790262ebe77176237 GIT binary patch literal 87852 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

!#|bgz{Y$vcmJ`op;t|fg-=D zLdb5^%(~R#`pq|3jQl$Y-EwBSJg!MUrdeqMxQVV1W_UD*^9UCGQwz{%e5qcF0A3~o zjWVdZsk1$PiUKXLy|Ro?uk!Wl_hjc^YzUzM%<+nmBA9^Vc7ArMsg!B9wQH~&DKFI0>8N!Ehm+W&S)kiJJ`xL6YA02wY@lmTm<~Kn6-dZnh*baOI zG$0?D;A5sb3GtaR`A02CiG$C1dxO5^l$*cmd?EVoT|35GJ~Cp5T1U5rKs*M6g8&hb z>1Zr=Sm{6DmjabgIrROXG!+ z5@>%3VsfqCZW4aE&t3U;kFfkJ01?6szs2mtSzwq9$l$yeRPyLgATy}j_Ihm)(Oqii z&&0g0zFLFZw`r=7wsKH_>d5!Jjv)mo&6x`9gPJ=@>SKJ{J zEGan&I8Av0sN1$f+gVxCWPyo^$w!Xo5u&p8xep)Ky=J_>x`y(ryQU^h zlOIGjM5d-t2j~8fXnF~{kT_7jf&Ewl!vm+Db_R%NqayR0&*Eb_^X?O^1}k@eM;So7 z5z{fCppMp!y=9ENRUct6-f3t)p3($%5Y1+2b}aQn+aB`wE`;E!Y@w22k2FJm(jig1Iy*b#W0R7uM3{aL52nEJ;u;#Xc(VG&%*KZ& znUWIb=!%y*^!$(0(xH`3L_|XWb0aI!a8)fu;3GoFTUsV6v&Gy!0qJ1v2;X%N2o?KJ zBIM6=1$w+6w~9rq56gP66N4{!g>sISmICL>SOElr+-9 zb`WJSQ&22AT7C)`=-mYaL{ zP^Rmpk+Creud{`8XL({o(N#>s)RUmtKh1|mrozPLM%oAeqEP=LW!TK&0m)ftr@2@< zeq0-M1T`?SckjT$ZTAUeD!qW@N>O3Rv{bjv{uH4G>1i#Y&kzAoK_4`w;E%$r?u4Et zxZtX-A~nAdzkq4&nrVQu$~Uf&>!5H; zW-UjXaZ&$%d+yJqzK+#oY>Znraf;$Z{pOjy(M%ZNhb*gc~%&xwD!MD%Xg8cV9}Wo7chQl zZBtIj7)(v?+?Vu{$Itys^LG~AOuQ&U76@`3UGj#A?eB#x$zsM&v;HcJTBizrG2lc`n zGt2w79)42gnM1!|O%igb6LT|uk9L3DJelGDH!DTlp8J3ZpO^mhTEJfb)-z+!gM zGVsOX$#lSzE{2Y-$cIH&R;QKAL7N45-VV`fkyx*kshqzA%vQ+!eN* zU+gfu{Ym0Xd{JL;gkh`Iv^1U~{qM@Vi1^=-U>ePLuS{npWQ@D8${8uADx~62V!9wr z^@Rv&t34TFI)VZUtiQ|uzJ-LW4_9z7JCAK8d|Fw#2O6D#{-0I{#FCOXS>53N95*z% zVPy8ETX`)`|1T@lFnZVeMz0U1(wWTuel7d=XZr0Jgy(I-Cz`^uAAPy)Ab77o$%hW% z%;s!gAH_`;_?dC5Yv%cJrHikh3h>go+3E>$Y2ctj`DMHr3vC=!Y>-f~&=Jkf!h-7V z`sGVfQWC;qqxu6V{Q%{HbqoA@^}hQJKAPp$$iDQiC06>zNOY3}K0KX@Gb+eT`y)N|fiBvT{*!av_uue1RPc zk|YlvyU4R57|$J?Sb`;zx(q?w{+x2Bb!ZU$ZHO13Ce+VEiT$EUMMv+_We26=uWF%~ zy@Ov9gTCdsw`y(I##y%0vDXM-ZLN9d;sp5=m4scU|d0r|_m9I*+6AfIpHV5r%l7b=0U?u_E($n#}ubn^N&~sRH&t9O= zd$UdNK3t>Fz7E#)Tzf5$1S1~sk;?MPj`tN8yBsG73&!Q;$-gxG41>U(tKowE7fc#z z*XHbWwPM{S!*f{!osPOcsx44Eaoa^V>0b5S=Xai6^t3VnrBH@O(Ykf(bwq#}e@mNs z5nsIIoe`*C*3>xuF6$CPJbAl2IApbeek+V`{4&EmDik9qEn)v0R(J#89zA_~PvyB~ z^Q?JAMLFUDI8r9wp*CQUykSxTZSEsHiQZ}<;7O|)X?2_8!2^PLN`R1lgqL;G-##+j zQ=EK%-=$Vi2qspBp5~J6?@|v51pp@R{RZJaab9fCbL1gL4%LAgvgIxKWCsPDBf;L4 zmuFSXGhTz-X|Io_p?B@ej+p@?Hgsb4Wgee|N|pI00o|UmY!7EM?ihFJ)(Z8PKz{mB zCu=-+t=PrLgw&^A5X^AtVzR-fb6VeXId4$5a_z(sp!`zC7 ztTAt#7R%*8Qbzvnh)=`48ZtW-z& z&H~jCCrpRH@~KlRH1n5vei?D~L%UYq9r~~;(+24wqwBJD-2rt9kJ-RAbL*$g6n0gX zrrTr6;f4`o@aQ03s47v$Jlgonr881u+5*=OQIxb|ltDW?H(y0Y7Mc#7?Ci#X z=WI9#Al_VQkABCY9{Uq>y?psn49mgDXn?-ZEzKsyq+L;7Wb9+jrSK5fUc>3H+t+G_1z2K26n-10#d(-qcO}8yH!FSi`e+zS?XnnZfgfsLjwG5VM9eSBwjBs zA;k>dar=OT$9j3eP+L1ADTz({N%c7f ztUSLrr{I!(HU}q)f5W}$l?cceQC6l36qA#ackyahN4QHQP2S!I)qHvUD_KPM$=|!R zxfKPC4}pPGe#PnY7c&i(r{(12E+M?R8vlC>D5rk|CFzrp!>?{FDqjhSu7;y`p#|m)>?Sb@3UzcU6```DiDT2|9SxPZj2n0a?hKKq?!vAzfd3bH>Aq$+Bg6U2OgX7;jmX_Mb z*tdbIb<=7w5oMEdrHYI+QV_$qm zTpSE7sVxGn-vu!=xgr;g@3gnKx3WAQxuT$?IDHS4e$Th(X=wJ)&!evgo*^B*z0$nO zB8+u~i%k@7Q3|Ol+OT!aHY~Jgc(5=sBHaI_p9~(O9lfN5$aOBSfOoJY)P!Vz=CvEI zt3yV;5i50I`49h=eA0)^F0$7{Ei?bTd)#-KoYT;r=3zdG>$0ZfwtE;veYa?O$12a* z{#DLPa(u5h_I=2bA^2Pq-j85Y4nisA4}OK6gZzorPPpIsvHE2H;hh>C8wH=Xg2Jm? zh=mSK3yYrvJv|Sff6Gq%yDRH4K6!7MUG_01@uodb+>1ki0=I;5b!NPqB~HG+g!L3D z_c!JWfXmed2mG?o@W9`}Hyzg5Yh;V3V!l6*nd2 zbhnsSWj&igj&QB0m7>B$C<{h^q=X+tM0~%PD)FhEHGh3rUM40hA>qs}N5O-*$EO^Z z>mTVl7sjd!ft<(3>(2x+B6cKS5+HsKnIMH7l9Q7qg+f}Rs-Du~mz9-Vl_r2Vs!Rz1 zK^Mn^vp0tjzFTRKC-n*Q-p6wZy}Y6Sq;2RV{vHu=k>WJ=Z&VITxX&Qs0ghMc;4s9z zZVVNXAb@w;vZnknwK}UHFK@g*p?7!bC7tTi&G{E_lAtHeQmJxP`1LY3Llek zdu05xf+R^;iB_VxD{+TGd^ zX)3@{oXn2I$s-EpF}{iHoaJ>8>yAVH1*4HvSGN!Dd6_@qbWw2Q0&>^6;sA7iz!~t| zx0&Rat1bx~De(@HtO}a+8vxFOB0i=EA9U>hxBty}Qe(IBSe+z5 zOhM3F^~n?e5~s07U+A37fLZ@c(sp}iZqAv0CpE|F+2w^5KF%A;mit+7#^;^0)fSxUh;QyKN5MWn6%+0MRQk!iSmucz6zAcWoP`#0rw1|O#BE-?owQ!X_yv<7&r?2Bp zjs`CXi-w53sBT?vXnxzP@V+dmrljJBMK`4&`&E>ruyh-9gQ}a|7lsr>yiRlZK|!6T z2Pi%wl#}M2jq?Q~6$DFHHtcS|D90V&9yv&IkC2rrl_ACBv187sTzR=T!@$x&Q z8D^=V$tAp%u(X*NEhvc)hS0;#u@W*N7X-r2es^p3zFea92;s)+?Cz1RkS=8u?|CyO z5epgT2AO7i0HwFKtuO z78EgiuLUY(tc|9a946?RUI?D;hRRMuV@*8d1~c9Fe|njUk`)7d{*-_}R!rYl*Oj1O z{wW9QieqU^;7(8a>OR$DPrb5V#!9`YiQSuUm?yq!+IA3U6rE)MXa8n@p)=*hkoTXj zj{2mw&(8+3h75Br(LNsFQ-?`*V#UrHHL*sverx)1S`)>^@#V-Zlz@W5Mh*uD$C>-u zHFw0)&}J6ou?!9kp+bx9*_#zfE{KJmqSzda&}*Ls?h@>b2t5sT<)MLmwQTQdlJ`Sr zk%AFDOp_28a^HYbm?{Xy6<5--}M4xi{ z(S;#6k>_}+H-#yd3q@opuDsf9Us9@4S5cFc-P&WW#0u`1wUJ6W#?axB(Om8753NOR zaf!7;mMPFgJ34k|NB0k9Yfh!OA3}`h&W8`K_QuZZeQ1a-h>Tu4vcd3s(>$A_Q4rVu zJG6+9NjIzOgcqdCWsoUiqKYWk zNv80$%(hr5{OD<@C5SN-NF0V(a^I2E^^(wr{jPJSD zKa<5576^|XFLgvCzTWTRFOm2oIp7F;veUd|+)1`m_6{b-Ae?2Y{bF3)xQ3AT&njLM zgKk2iNEcUE<|yt`l0!fnFfg#rW}kX+tazai=iCbvO&S~u#0t&M!;6;xZWFNfwOkS)E!akWCf z-xxs|*2WzR44_Vf|obLMSshP?lPB}_y*`$y0F zsO5IN({B~lxH3EcXXk6t6%{9V z>9Sc?>YA7P?hjZgDW~`EI5^|tzIlUE1fPYfii&wN72CrMpkE#AwGKxqT3R?w&UzI4 zCnqPTG<)-VY}MtxOTom%Bt5QjH6)IgohldH>A9#rdVK0+wco5>nOmEfcaq(uHXXST zs3Ul|HrktO6>D`n6w*fo99~_{GuQ%E2z`B)~3i3nH{u`shTKiEB? z$>6iKTY~K zqyto)68(ON1z-GRd7&_($j($*UHt|^I_-Cmg)IP0Fdtmi)X>PBN-Zing<*+HcgNSq zsP;&Erd3Bm^P%5n4Eq7X>rY z#cD<+&M%EeWIHQFh*Ghzcufz~q=ulaoZ0S=6yoR;h?A1W9@{Twt5@peezLdZ5$=pl zXX>e)QY~EUDlO25++RV-ZWk6W>KM_9;+*I24%R!DjKy#Zf&v4L%b(G-wRGI!^Psta zWbH5AM2Oere&u}qrVcM~l_d!fdc;m=rFa3)Td$%#ropwH8mQH1U({4rpKf;9G?lT+ z=@MP9;Ij&NOAV){h0wKvrYq@%I7A*`H`8%2NF~V^IXTSP@jfb+>00X{xh5wU!dv(x zgiN@=V}7OX_2lki7n>&;Mw0w@r@CLiM7?)o`*1El7ILi=$}~zA9!5P)trohF*ksFn zoT`v1g)fN~x>wp8)*NET69=fppHBt4I7>DL20Kj)9-oPs{G!-8n4;I>_O5LRX~zht zhVYj=(5TotM zS|MI@NT#st<@UAx?Uw0!TOY&Pi#L(d#M~;Lgo&qtE}L+#fdmC|o8mtArJPO?MOxKw ztZkOM&Z5ic`^by*@OL<$cEumXmL9B+H0rVDHfUGlIxE*YS`czDFklKdxl81zGsgQJ z&2ExfsjI7R>#{g7;RiI_G@w^*e&V4gB4S|hS(t1<6U5m(XB;S1zg4i zDO?C}zh2mO!9ssRgR5QbbaV6fz*DQ#!rIHkkUm{PBGkHa`&xIGvi@aj;s%O=IY_37ZxW7tRZL~P=NGo&1vrjluTRpFxwefH!XR`7UDN>Py#UD!lT*efVMLhIj| zNd=$N(eU%%U%&7z-B}O&8HE|wW14o5Se!%8A}jNLk(cKOZwZ#YJ@{nFs_kc1RUP@! z26kG^i={-DG_);ZU#i6wCx(Uj5KL{?W6aDNEWLjbLy;~U&4~X&XmgTm^ViPq?nLEf z-S|k7SB;RbKlT)P&H7@hR0QLxI(;~}os}0pxyjah|H0Bq@#;^isaL$?8B=mPMJ~(1 z@s%|vnKt*>(0!B4UT%MWY3CQ$KAo#pf`x!s7^ftk5=dbxaM(^B*u}yA-lxrs4dZ~5`{54oKKol`_)OBsl%Y4Xtt+RF0oi27RL~p+X1<`x%K|o$`+<$ zB{EwrxAAJ@ExYuv$k>kxnx0k4I;?QtMK%1557Be{E)P!LEpv!dhXVa^xZ)8R-;$bI zcSpw>tV+pFYiIkBrd49QIv<8hQl%EVY+fGDzWJIUVZ``N!R(1=e11uVPDAALRD?*9 z0tunYg36!+hyTJ}jdRCulWwlmw3%kSiHXY~GmZD&0dLkMuC9tBU;g!jv#UpORZ z>ff|1nD71ltxGM8E3dt}x~QYqJl&yCzC|H;nG?+T$W#{jnxeQ%;7!4=5^I0_@GA13 zJFlkRLLQ$J?~CZI>sL{FrRD$r0giwBfs|^E?~~8bA<90q<*W9m+t(W;f_fFMpR~bY z-e2DNeh<^<$h4)iz5dYAhAOti5^`MY;@`fF$PztkK4!@c^%L>~WJqDJbbT+p*Mo>n! ztLCWl!#DKrJB_UZuHYQ~EDH!bSR13@GqHZv!hKle^bxOsz%mBG<25r^^8Q&d`Z@b(+I>&% zdGN7vnP~Vnw;fjVd!*?K>b3NA6XMy=)RSZnkf#XE2*m3hEKOQ9AG`+w+9Qjh9G&tR z%Z?1|i3;oCA`%2{*k^#$fEWBRh_|uCcu}y6CGWdht^RCUe!k-U;A?2;+1f=e4l}V6hSU1x!IHC=_JdH_?B*Rbb6fmXeqSUtionWXef88Zgukc zvo$>YZ-qha-Z~3&dn1~Bjm#UrSfj; zA{Th22kF>eYbgbJ`Q#+=X|-$pi<8x0BJW5aPXCa%Tu$$67njrOXydP6zw#OUkK{!R zsjgp*(u@MY9RXGXB+=?m-i0)@0|(~V!zVqxIc_AWIXDs0&>i(eHlf-W*f~~FQ7OX; z7ln|aN5#c9D$Mv9i#4E`sqc)Ai80Sgt!1Y9{GL8lau**ca^C!*QD3AfZ;4Ugj%@$E z93{J2BP5h=&~;s$`p1ot?jdIj7nk1MMFHl>pGWSSZi4K5GGqB8@qCUIo{PcN)zwTP zCN?(6uV#ubP@Q<=QZW}IyYN!L`TW4Qj z@qb1$$Ou*>zm$k|)TZR~a>m=*K6Ee6Hj9Qn8>i&z880iCs#3PMo+`)l-W{wFtR%*2 z3NOo2oylFnw`g+%Q`lzIjsytKx zrsNnG;kFlYc{?H{4BX%Cqm_y{7hrek00V^@EDV&_HJ&HHTfIplWOvj8nqzj$C3)<3 zyYO>?k&Kww$8@~&8y{Y_BVN}}W044$whd4$G99({77+M9Qn5BPEc|{j~L8?)l^QBK zVA-}Xn9=`bv&QjNzV{TsKNQsN2OEKvPM*_mj$`^OZY83Bzn8_kHd1=2V{Q)JX)f5S zb8?<}P0vtp&!ph2ad2?BPvv^Xd<|y2I#Yi&^;2nDL^lozznAKO6A_V#qrRD0bbLG$ zLMx!sjc?(jLdG0eRCsCBO%DsWo;b9ZywaseY!2ByM)}#XEDS+H8AU}M6BAYoduE7N zERUE@wpeg8zBJD>Yge_jwe3_FuRlR9H#7BfDQhilt*u~hfez!nXYfUXi~KJD_A@c+ z3>d_Miwblt8A>yk@D~DXy6kOj%}Vy6*aiVzv@inEP2o|ax40qLd+lJ`r3%`*+IRup z`wo9&L>TeBhAUrMNx7^hi%m8+$X*cF!R9dakt9bU1DTEOxJzeZk{&f&czY~GB~NS8 zatb!k8SR}HX`k}*SzpAzHGjIAB&l&bOsbclr9Xs#(YHH2L&nX)3b?-MJOmK zyt1{mwX^$--1dvUeG_|t51x!QBhq+aE6c7c{9s^c$eaJz{mf;iVO#fb(|e=Cj6NI# zp_7o{J$_`NO^NQ?n_(z{$e-Wt=;%PUfUG&$nHJ_dyO^WJ7Jp^fBVYC4BwsX%GEBH_ z^^FMSg@wl#U1fX>+RfjIn9eefLVLO|>>i(}@mY`a$oPQPjQ^&wpEO29Ce2u_@XYiy zo8yYonUbyT_6Gt64TJbjV^!s$Kg|w&p5W03xiRuZO4N<4j0@6h)dQS}?^#*1uXwxz z&Z`O0{t5A9f|<)&+_)_s-LxM}B2avKMq~WEA>s`Qo?Ov8D=Vn?$FdF>gYrY);UXNL z+WLE5<^zHo<}Vd)AmqPu+Dp;JT9`<3cbOJSrhe}KW+ZVV8j(}`BbPau6Biy(S>2H+ zm-q9Td@+R>m%l@vfNup_U`$Y8VVL}bj@Higr4>oaTfl!s{0X3X$jb|OfXAvay6-mZ z;=dem>70_W}J$uIEfoq z&MU6%**hL4^-5#fkypD!)WHs=Q55-8g4KhRRJ$o>Ol9m? zQ@$hFXsOL?AKk@5j!n8pcfquD9IN&3mEu#JyJtJ?LT*ZI(H&^J3%Tm0I)C*5X2Q}| zNjC6dZ+EdSe4C8)L&GQ^uP>ESL?QNnDIiT7CUZA_thk}4%5@j}F3;Bsm%XK425&6~ z^41@-BlGEk>5;>h!}%{1F90x>;-kL?$j5%L*IdTr6wJ~Q-%@vm)Nf}AD3 z_L@3)hX>TY|I=OSz>$%WVHV(sERp{}ldYuuWptGP^CTx}xHQ-F2)~N*m*R#9E#{%K zXCMMJ;(wg*^WD*O{mhsb zK^kmjy-;sV3i1?CvBT!2L?14j4hGD9%X>ba*ZK+4($JKqO6cgWnmaj_C}cF~UWGIa z7!Km0%e`oNh`*nlks^v`eV=oT4-QQ^0B^D7n*vAupgf0RRJ&21F)8l#t1!H-^sd}m zf8*%!I+pEPnjhAklWswR;Jdt^5422&e#&J~3rba7kc?UW&QxA0j9!igvb%i@A@YbZ zKHJ7dw-H4L!@W1qw+6@Z&7gZgAPODvw$J8?>5_GGva&2_6xG!B`iip9hO!<9&#NRB zP+k~r)kEqb<*l#yu;SceYA`jVf@yY*j?sG~&-j+PebUiR5$aGdZtruCyp9ndNte?m z+moOa0RVRRPV5c3FpNwZahz?m;sN-80>t_(uOYh=AH$;bTmF8^%HrjIw7Wr{mGjy< zZr-xM$WVgB&Wg}yA%`-`20cFqA(n@Vmex;%@(%+OTmaaiOr(|F?7-T_aYjQ!O^t?* z&VU#laa5~dw(H*ak0ay%GP1|qErgqIMVDGW=~E<8?JM)mLCYuLjVuvDUTD&-5}B=2 zebUZyx+CEB`T5*Ra|V0Hw_f{JP#?c}bCNG?Uu@@)({`B{nT0n=>W^%TBJ_&z4VqX> zjefvGSa#|_b;|+ANsl<H$OS5YuMt6!rrzEMP>kV1IV9S zR%Y}&#*yj8dpJ#sdreyR0-tgeUuIh=yV{zI3UvPr?JRg16^n}Q==dvBb$-5)1>h$g zhWmGN#A^qE2wuaJOSki@=vr+yji#Nh)RJ=_2$63pAz zuP%BCQ`9&XTD-$XY>k-YM1paQ_imbK(gQ|%jF92Uief`6Chva#eSm#K$c1wiGNYsy z)_S6$vuvH;fjynK+%-z_cR|W(6>njHm_f?t=V7%|Kg@twex5Vt3^eo;cT23Gr-1>2 zkh9Kaf5K@M5Az1oJA$@51AbDGz_JF@|B*E=aAMeJJO47byddJ{tFlKc`rmfa+2 zdNc$^cw2tH`Fx*`byHobq4^CIAHBv81TpSUVZ(PWl|AbqMf|aBN7r-`5fMqzNFd+n zkyjutow_5!7+zxZRK-a)_feqVBF4+k~Tn)K%yQk8*F-vtibj(@Y z%K25kFm$L9Inyv47_W8?>0YD=5PGja?Bm-D?|5)PiJN|VXRD>%eNE3MF+6;t#45I` z`B*1`96}&;_t#St)6;s1Sajlq@Z4Wb>Cp9?LpVUEH4-@>ezSdu`BSyy<&Qgg>;{ZY zA4z7Xr+p|Od}AMT<>*S#Qy{1k`#(=rfJYIO6+Jzagn`?rexg3AYr_SGg=An5tj8W3 z_rgCzNq27r4S_>(u{^!8@CO1(&QCmxfwc!#2ZUk!XUBm>r-#*5RbEkiXb5OwNyEQ< z!C#zPIJkk)(b-OVv=4S6cUj~@fD)Ow&@Ce^IXIYM#wmEOe%or&wlcIfn6aI?*_oCrjP-FoA$wbBC{pdU2hFJp4Q}z)GH|>vA7SrBWfR*>+o&j#yv!l`R^6L<8*WqWR?dwM_Y>xpBfu0I~D`3=yZO<9|(8zk#)oB3gtnbeTkwHdc;PH<8QxatSqouaNK(ebadH zEuW)EkxD}9TNHFiun88PztF3hb$R-5ZTG#k$z9CO+IQYUeGfe#9%xpho8GD_i;i|X_yi}Bedsz+ zk~ez`wBFdloOF`bW-R%T+{ejkjC`~xPE%N|#t5Olc1qz4sC2R#ZutYJm!nnvlrC&}Pt2r#zgfmR zZl6ckbZINHu@SNLH-t4Sr35EorzvKHNgDe%E*2D%C(IL+tL^CcC|X(w-_vFI{R@CY zl%^1)l5~FqTt_4S0Jt)a<9!saqx2GoK679_+4{LuPaXQW^85718|3>d$}b_NThze7 z^GW?y(vt{g9iTDV%?!Vw4$U`SuK*@!ToCW?9L=GdV2_Q6miF4-c^R@X;ba*?`R@=G zstBy{7A|5+Mn~nt#MiE!Lag{wQC7*DD9TimFtF4rnXwRU4D>~qziH@b zA#fK)*CeF&AFMjN+~e|uQu`~?x?z4e8l<2Ithw7H#SHX2hlM7PwjmeCm(!0Ie{+a} zThMmFj@K>jaCf`4tquLPL`WaMgK1)X7$yRo}GjmIdiv@{D zIm~9>KIyIHO#Su`oGfeVZ=7r@%$pRD)%Q)(vAnB)l9qhmC8u{|SV_WcFDjuLbz8WK zB2J8&NxkY7$nYzjHno44CN0t1=W`1|2p1V z|9iX#*eXJ==c^BIc{vn}e3-wLv(*Rv{KOwRZ?+M9=uzho5FG!V9`hsjpgiO0^tZ{{ z&E;up6B7yQ(7`-yGIqYQhjK-vxx&_NGuczF{{dcu1gT$5=DQ_32t5N)@}61>gu}}4 zw(@*d+82+J^zLf+L%6O=^vVi$c8NoW-KQwkL~lgSe8DJd|CAFAO%CSSqs3NPOpxkB z^RC`ZX*kK#ouh&zRLpXACRv>sH-MeJ&>x#3z?<{YqsAd@)>2kgZ`3~B+89Zi+y7xa zSFUxQFtl^L;(2Rp-^SeB!jUjD%f-#DBbv-@?I-{OjIW{ZB?vnQpWGZt%g*u~9C^TX zn%B53CK~D%K<43_m?qF`U3O7cFfu69s2(J{2);(Ljd8a>h#BGiNX*+D+}Z+g*;R|# z_ou18VX>>GD}ps8_R$AlS%_wEN|%iX49(ES#-@E1g8Y_ykFiCN0MoVg(UM0T9QEFm zkj%5(K_L?w_gR!W)QDXjGQ{9+|2=O>>SW?w3tri#7?^KS_S-o5$~_ll0n`Q}tN|-A zAaObYm6=l&j{>8?1(9NQbxN&JjSq7h4f_?`4e)gp<>!xi2E*6GMUze0>Cr?d3Jv}a|D)4u| zQ2F~N2F#3?hX+!u69DmZG@`mSx;9c=Ki1wp)^5?AOXfcW`yP}f$f20t#dyvbb%Oy! zU#ZZC8x)lNEB^PE3IpVnV7KV|6W~?VJZ-$dM!v=Y6~5Ec%i{-R6l_Ay8_vLRfiZZY zy!PS0jJi&PYI-X!o;t4g@e1DC9Vrw|RVde=UC0{BTRjvr33Q0*!CU0!+gzUqzeB7^ z-60?!!0vi_j69zht;!1tXq6Rrj?~v9R+N5!`CRw<_1?#iFrIvaLb_?bz42HMmVZOI z6HlAw*c=)jbcQIVOA}&us#b3+pC6e)7%+XX@Z@#E>yuk_4-1 zd_)Gjz_joG$3!azV8-m5n566;Gy%^X939EH9KL(4qN4AqV&0NW( z4VQcgw+PJQAyFgbzMIBCO9NDVZZqQ7M4o&&!@w*noehg#-FR(w_g+TJf`Fv zDRY&I<+6H;?R{x;FBw2R*#4s)FfvxSLw5htx5>!N}BZbbo z;O&%!gi&$<*J6AO?^VpC}|M2nFW|9*N!#U02rqXy-4 zDAZ|4r0@DwNO&Uz$8ut*JL#b~z06Xgzf#xL)s-4xg^q0N^m!;=V%)&Q#QPm!I*6Z~ z@rKbTKW7A%rZo8nP%{D|fBo(7uo^)cuA-RHLvdO($h?NYF*sVdq?Dv2D1wZP=1#4V zXW+5t@#W!L(WJTR6Z`4h3_9GC5nbkI6jIGWr0EY?Jx?6ce`bGr{!<~`==4xRKtLc( zHvU5*Ju|aTfkETx)*UEUgdSal7u>Raa^C*yO`L>ThyosYD&3rbq`SHrrS*4jdzpIZ zb?sultUaJAwJ|*pf8-}=MpI`{5LfeqZTl9UpctnKf!|IkL7*uV0f(dIa$J_xt2#E+UY{G z(2t=1*N5e$ajyI4Kl`hsBe6G_Jh1<4yA0*&+z)$xmD=`b#SAv)-Gxr*Q2_I$zz%@S zQ-pNx+NdO(nJ^U-lhsT^00`iLLT|**6+6+FMtv~`bGRqH@4!U$jsEbAxG*d(|A8(x zFC8QBa^`6JN_8#b^_Vj*m>PR6)zy5P`vPTt04*-R=2&r-DqFNlOiXb&LS($y0x&Oy zmU*z#X^&upTWpke)O_CV_~a%j2YVrFcpJdlBcB_^s0&`)2$yVJXV&ZLUjhZ~>`pC=&LUFg^tO>O_x zC?)Rbh)KuS~IjtBT4& z%>Jre;?YgSKyyq|eQ}1=LKpUL*Q}-TYp@E}e1Xkg6C&xIExAqY=OYX|(m#D(lxsD@ z! zAy7&q_aNskkNv?~sbbBh@T*rX!`qbyYgXo`FQ1e-o#Wg)Ine!@Sur{|c%Wt3@ht!k zTl^9QQCxhMGkyC-==7q2;oZAyI261n2$&v$8;?sk|BV}uA76pO5xl)MtL+*`q7)}8 z94GS43ez;=C1Hw7O`YN-E`H&4Ag!%^9-x?BEai|1y(`291_*kxb1mSH#l#=-=vInZ zxh>E5L7E+?P#Y1N|1#7~<3RaO9J%2tHZa86948-13kod3Wsg3anQtebs9YZ=_ffq} zR_zw7bb`6Mv8LEuj4}fb*|_(yq?V&&G4j(bD!H&28s7PUVa32l)=#02c)-BoZ0l6< ziuqvdIUcw5WZxIN`0r%(yUoJd#e+4{TB^5EIp5$1d#jI z{zzTLkzcmAJEJR+7TP`!O%BjMWOX>9G)+I6l#2qo_PH|M1@#-@slLu{fcDc4UeVjU zKn2BM0-L7>1TwliuY*n_vG&yq&+1-u_sJHYC%vHEh~u?F{wwKV9s@J8da<$gu97~n z(^BGP=PjFthPML1Cqpa^6wGF|sY0;2#zYyRiG>9w!mFkRDUQ7bj2XK_a-mp(zZhzI zc5M^b085+Aq996QD6T>+eD?EAGO0E7Es{Uao0ig zGjX_mVCP_ohVVtd36lh?m8P`*<#_&6R1t=EDsVseKlrJX`L#|&3;2`c^%G=>we_{C z>MEGSgo_p!7l4B`5anK}?(+Z`l#SOfqtjy$r!O0hk@R6+j4=y$5*#Cr?bx`}suBystMGpqeX=`cCnaDh6#|w~D zIM4stmMK2V1`qo&CJ_=dvb9A=@Wl(wSK3_LTQxyui$F=45X_sRM9+J-D>;$y6%UtO zg3!bdrg88F+n)8P5n;7I34t_kdL|}&XXjX6&pQa{P6LI`0>$X(ZOf&ISOB*f*nBNQ z$#Zymypvae z{s9Dun-2{I!>+2UOPijawG*T6;CVx1tF35lnI$MFsy%Gsvsc6-rj_{TbG7=^TzglwW ze~em-j$n`0(o(<`Fc%ZqoP?Qjwd*Q&f+$cxyn3^UqNSPX-QdpKG?q<_h;j1PY&g)x zCX3>}zXt=PYlrJOnVEeDMn^qVrPdQ(TeX+1Mn6vc{U7V%m_A<;(*F;fgqe9*kzwCp zFRzl5r$ppi4ac9KbJ>jRmEl6Dmr!B|CQvGDebp+zJuC*=Fnj?HJF*jAVA35#dK%>P zJcWLc(HJ^^FUIgzxXzCP_e*+vGQMS3#UZXf$>b)GM$O@TQV4VjR)E`D=IsD3HZ#jm z1fuBQ1d8!o{PROZsMvs6a<5;1h8QJ2F|qzVgCY-FBzb?Vedwb;bJ+7(t7nwAkUjzz z>nKyBg#$(FK2bc;(g(j)b@$gM+q+W&BV*;DVTkSQa5yd3W;ge+ZiQENj|McT#isUZymE%*B;G)_8OyY=eMEYgU&Q_w%knR#$O))$5Ip~E6E8}7O_OlrZROpEaT#Q^=?c{B zRu8NYWw*WiTbDVY`iyTI85{(IV3!>vR|h?StJK!0p_7=Z|EJaJwAOxx}iA(@JWZU`Oj zlP`?gRi+13$C9Z(3Tt&+mh>?ElC_g_N^BB^Eb4#xV^KV(#s#hHyPIUt9+8HBm*F7B z`rq1OAjpI<%xPgeH~x6p`5qlmoq*42Gq2rw?mMUy<}3}|#m8S-n}axqLS#U-T8Ytf z#WvW5KK=AflRgJb6T0n-&FRFg?jFaLfft2y>BqZ8o;}_;OPoob50@Pj1tlkGuO`Wx z;*0Z!^l1rtFfzuTIc7ht%j<0S81Buo-^F@qWW=ccgXU4CX;*+l0B#ox38zV43$Oix zY1>C)wq)s_OB~!Q%Ef;+1_P|SoSH)@R1@PNzi@B4U(-Tb*@Hohy0Vwr3A?ym*%y3gSwiZb<`^V_i73>$d_eXp($Sj#4)fvR#8&F$Iv%0trxkQTi}8k9-d5BHCcdknOM0_mnaGG?%+ zM_x6106HMle_#b(Ag^&vxcPHd9tAqF`Qukoqb&n>j z<#&kUONHkt5?TDt8&}cx^}QF&=a)3XFNIlD?^khWkHCoTIY;WcuuFbX(Z{DL<(1`D zSeipgo~K;QRsh_eIJF(#CGBdsF{_fJ1H9;;EVXMX5(Htb(0y&hIIeDA>jO5#qXr+i zbkb%;)u%X0N=o@^9bfC}wl!vx5qiJ?o8Dl?|4^1!IZ$?2R%ddyUc!s^79eirh&nx1 zP+QcJ2;0_I6ptpHx$U_Sd3#r3wzs$MZiVWCgJ%7hKa5E{GNG!()EyN|Re}k0@7=Wi ztM&OGFDLGW%4qNwTOHJ`rAkG<6`jTrK06mLG5?+Uy`S4l&HMewhq4k91Px?L55r@W zt*2_v_gf04Hsthi8@hFFtj=;tN3mYCv(#E9pi&TpzTl6Gi`$AWgHlqvQfTejUee+E zfTl0sk6Z6?@KPL&ZU>0vR;Oq&ce_Mo%C{7w;bXWqU2Xt@JGa_UitYfwoP7_gM+RsQ z7&V_qcj1@ZODo2TgbMray$8}G^amz1q>G>oEZTZFXE=SlO9Z|M*D;pPYsi4ioJjZo zFnzX-Jl6z;qnZZZ?|DBn9540fC37`pnTC!WEG#T&wE|ufriUywUbmdzJf|r!Vw^Li z0{$8FNzh;W>JJ-PAm@&nz$;083f=a=&<%^MRdJ7KB@6^UeYn29N26MlD4!w%l-PCgp)a7DEM0s8C9quG$K`*PMv_uo6CBl(iRQ>mTDuQ6Z_OWpK5 zk6<j3;``L->Ttnq?HoB^Q)kXc zM@~#idDvH>AOp?0nUFr`6Bye8f1_CWjv%&|Sl35*#UR!e$(2>S%O zW2w1WHmw93e7~x{@K!6D7gqa=Lw-`nYI#Ubwu*i}<|wQN0IR#{rBMC+@5l100~CZ- z$2cU&!APlLPtWv`*LUyn_nW<0+%;ug+j#d&#lcsOB4a+N2GqJT09MjVWtiR> zrM!3Msu%+fXFTQ(U{;xu2H$$Ije(pg`#!nQXwvnJgL#(q%r{7ueAg9CdUq#iR1y%H z`ln!e$8HIM5$w^~4^94sV}9wzbl8$70W@zRc z!eFk8l9-s}Zn;*CdFSYK%2OOtHuJ{J-+(`vcwpi9CA3(KDPS%V;Y%l+JW(VPtxV>- z?!1{gL-3pS#*OEr7Z&ZSSW1eeKl#y-M8@^4|I?%iA+M}$WntmY%w^oyaN}LF_m$V6 zZUkAOH3lZXfpR@uz1z z`=`FW@2-3WS}--cxvJ9Krb#!X@+I;(YNT`g2VjE(floj{CU`M6?R(8j@GdC5hFiw} zOY3J2){tL8{6wrb`WWQb=KoGvA9`r4_h3(A@|Gqa%UdV$AjFS5etwO81;+^`Rb3*> zMHd@vqhrgat+(FNQHL&hR&Lo$udc>NhyMoUmiov@{kxl;EggIN+g)v4I@`N_A5`4pCprlJU>zJ~cBtgs5Af%3Y}g2^-ulAc%zt%Oi#Mzo3D4c~4drSKW_4)YRDh z%eXA#aMRtbY;0uYO5I6uR)E+H6qZ&d|2t{DWkb-N0Hd*9n8MZF^~cB{g7vF+EyOjX zt=;)9?>@yF|F}o}d;0G|#?@cHlD||spP!ups~v$5h~)lNC%+U@Rl3rT{rMP z4JFm;e8R8@f9HnxOUhd@M<++XhQ5a%&_ZV~ zZvo4EB{d}jA0y~#Rz}WmSt??GvdeWb0|j;;5g4OrF{_5)>&5@L);eE0IKq}&b7dK{^+9CmU{>E%ZUrvxdc?xyWVMnrH@CZc zWnbS9iREP%mI&E@aL(1}ksMcxoSQa?KL_#rfFQ1q6$bv)Ly~`KH|P2bDP4y5ItH?3+tA(^;aUS#Pb{}}4(n8=1Q7mRLTz4j14b$}B4YZp_I;n)O(r z8U$l5m~0{Pb8;B6GPh=-omjHXu!!thCW-D&AOQLNO;T>%YA@|{H6riJj_hpU*Pw{V zT5k+)T}N!aviCk(OoTo}GIghKzC9@OQ>zh-DNgl}PY<8(R#S`CHgsj3^G^ zSQ!!<>+AoVw*$3X*AekQKaE;kL0R)_^4iJ$-d3)tHdOJCPfL;fbr*|&C69|#zYF5J zi@gY%D?%1^!n->%fP@jWM=ay7;^8+e?)pBdQ9s%Ju$eB+%)l^KU{Ca}o`l_{=Szlg zm*!SjiXj7*MISAYIZ^|OIup3PVQE&Pr!{iOmu35pO!*S4v8y3>*|x`8r#{C1S)T6w zF7@`O-qG<1)*ZexproPG59Gq9oJ#hV`>8ST-B|*4GldV<`|oqJ*tBeg)=QuVHh$KB z@^0m0h)^Hvapke994lKDG-9eueTGX>Q=hzT<{eEm1shDk*v3UB$G?V+!nd7-%KH4ugZY_ZJEAzqhtliDR8- z5dQ%b2c+Hu*pA@I_t!}2)^+d$WlB@XV8w#5l4L6GrD}!}%Io;}_`tw+_g!-26o){l z4Ih_~kS-IiH~XQOBAR-Oo@Sx@;f73-JPa7%Shubh6co@))26*|QTQNJH@w%UAtNZ@qFKtB`n!UnWtxrM`!KB=>ih9tL$oi^N zg}q6oZ#~!yadB~xj^`r~R@Tz0GHCp8IQUBR43|3|`1imcJkb#;X|h zohzbH2;9?~!%tSokW3KH$;mA$c6KmbH)_g9ymkxCUr}~_+;xv3BBa2a4QS_fI)9MKe&=QhyZOX%9JpOQ;;;!s7*hJA5M3T zHu~`#sJNd8&AY)G_WrpKu%eLsWJY$2!R*bN(_m@3+waA2L!%NBLSthg0~xNK&|P+i zC3!^Mb;)vtD;T0+oT#Ai8BPZL5GfU&SwElq0x$Uo{UK}0SdZ{ZH>^_UQ+087R^GU~Gc4Z~=VPa_L z3u;$(BV%ENNaCG6~%uH@XcqHsL<&G<@US`{8jCelOWTs zaWn)F`=xmcx#hK9mf*2s=WRd5uZT1B>~Aw2_iIyYdAI z48LD=V>fp0L~T%(na{LYR!x8!I`Ay};d5i^7tb>(xYX2(O>H7Hgf8K*mB}TQWjv^e zc-h3AiBi;){d(@(6}Zm(4jm+^3a!(x2+xaN*B7|%>@7!FO@po}gko_Q?M-yQch#$H z1yFu~_d&;UKjIHi)%K=!X%rrYv};$mgocN|lp42a^I?wXUtC;Nt#W&G-b`_Jeunrn zw+!C#%wThgbLLhT5SAW%e$q~={YZ-!o);FqDs4=(V(gswo1sP1!wbejmL*j1#NlHA zga{P&WrvE4jEC{E6vNJ}+WO`iBKuYr2BKe$O-ukyJrloGpr)m(E3ChT|1wVW1xpMc zJ?FHOXkiYj2n6ebUDocqJN*2bh(Eh%{LY&JLir)hvsQuc&(`N!d6NJYRDc=5QU?jp z119s$c5?;Yoj2eALO`JJv2llKxMZ2EpB@ts2nxHd?1A-0M<)pcM$(2pM|_=923pA1ort$Y@KlTagU8)OiY{Q= zLA<~oT{PkkDs(fm<&7z3k4ilJ zv)DZR#M0O;4R)}&w0)KTMiBp1P2z!P%p%tw%L z@^*?Wh6YDQZXjwlrY`08^y4+9tvj+ce0P&1d%4Mf-{SXaI#6&lw&rdiIOkt4 zuQp{Ks^#MSR#!VKt}VKGx_OM|ruFyj83K4d3@%q!#R(%ixFrXN0;F}kxgVhhhymwE za#Nf7!C=-G`S5tky+zK?tsNcT6$Chz`;_T#J^Mheg164u4a?kLTz3fj1?qvUwKecU zQG9>`fIwW9$Vke{K9k1ypD2{gfN^KZ!S;cNC6{$nep(x|v(=WZ1{i+0palH6 zWE!r}Xiz}Nq_w5KItct>NX``YoZ&{uQwVo;>r#~Tj*MhfRM2^>F8rI#iMfJZTSE0| z18zWk&id%@tSD&)naV{LEdi%9Zpagz-B!s_>zcRwho8y6xwZrYlF>0@5BcMdeSCuA zY%(Pk+bSJ4;=;l-+ZS1)xEH0@MnDR~!qN?@5|r0oHn+h)hwEOTzP_@uevi}EU!s5( zP8q6jNml_KZk)*G(=;J|)?(iraUjb|O2U}W8OA_h{tn3pHXwhqOrjSo7jv*J#v20X zdulc|HY((w>8^PGvd_l+de+uLTt*$E`=osv!eIU>nzVJeOJseYhKH{tGdFko70(e} z2Nb$o#S=wzU~d9^yT(RZ#HYif?XHPS7yU-?mVisC#<-53j8{))I#r|X_7v0spW5eP z6n97U_;Q4QGoSABWr!RG)!SQ?Z)xyfJXZQLX<5skx-{~*+s#BUywE_9pnwWxC4=(A zcgo>>Dkn670z~ps{LQOqRB>pJH85zYUV3|HZ_LLs-bO8{bQKg5ip|(OmToa|Ha1z9 z>Wt;Kp-xFQOit-a5K`8idbQTj`k}2Y|Gi9XeZAaWsh$5t+gm_&)ot&>TM%hMK~hqs zOArv05Rq<_ZjkP75ClY05s{LT?(R@2K}tZ7RJuE*?!@<;bN}a@cii!f?~co0hy#i3 z{_VBaoX>n>a}R5rWI{>jui>({*U`$@mDuP+ZB5O1`z=?gEzGx=%ZsI{IXOAFDdZ2o z=L;lyrphNxT#2t_n9t9hwi%}+r6@mR~V@M8!TrwI;$1*gre19e$UnZB=U-5;{`kJ$`!N6~mVKePF;;1^Y0f z*IJ&YmF03F!(_*Z%J$s`8M=Je-Bbr&k9_8-?L$bt)~vFnA-a0?DyQvF>Vjz(qpQ_( z>ei*Ac_lhqb6xR*f(?0c&OopqeB{G7>Le!qxy*QM`1WIgrG|rptu5ZiOoJaMyp<1H z*I=Muy!hH~_4iI}>2StMwlZBl9_BbPv5;+YVPI-#SXc~@H1!SNiPEO@FFJVRnuZj? zIJZ*CS9^cW-_y&aN3+Qw7J9oney@$}rkM;LaY5rj3k5IGR0n^)zbX_|qyNZ$} z1R=R?a8S|CfNSG=0}Ji9k=~~}zcxGUttXvbT=29w@DSU*F<_n5IxQNYA|$i(mG_(@ zwwm7+7xxYBJa%O381GtzX2SGtnArBt4uUuqttdQsf`(L`{Y(idB?x(nf{^eVZAAH@ zT*F;b*I*iJYHI5H2qO-CYq`w+zUtcqbfq;J8ATh}m&zm0Fjl`${&qxtXsGfrO^8Cv z;|j(>6a|LgeYb;5ny7Sn-rgEuT%~e`J)?eMEk1A8nb!Vd#Yn`_&b-dOo*(D?g6w=K zrfj-R0B-gbPeEOa3k)}cfIHz?C6x^JqTLj)hTFihB$ZhFGKDaEhRko$5ZTa(hwScd z?t5E1mR^HEc!((L?4U^#Ka8N9Equ^a{NeM$Bk)TuT2|-sIpo%(Lm&*G%@LKKELYup z8T*X*#?_ZNI1^sa`U(vwl5C82tZ~F+M4-$qcXDEV%Bx3`T+4NrkB?1*#P#OlaGVp( zgbpfjk73$JIX?>)xc(Uf{r%J1Xj$~I4`RG_+)-26V?}uezJ+%aT$YpGWE4(xJ;DqS zk)iSbs4$fLKK)uiR91~tlnz#a@t@l@CjzZP+oz>RUrO8d9X$A}Uwz?c!ZhMsj0*DU zFyh<}2Sv$G$OX9}(bDYpNWZV#WSr4O zlGxvIp2RG)uTtF8c&b%vJXC)aqq5VbXJqtz+z;bXYDOy^L57{Tw;&mYWKNnI|6-wfjQww%&Ns7*zZ#w@U$m|V?DA`svzLt}PNH06 zLR^OaL7%U_g6>&C>&OUEVytt3;a}_Qy~lT@Y0>%)IAeWv zHI*Jm#>YOOT~>3hNLSM2&YQei#jY&r=5ltwXzk0y+=G^TT0pu^hpN&Z_Aif z?+e@qGtZr^L1v^WmdgZ!#{v87Z2pi_LzZ~E#(O%$osa&te9Nl5Fg|pdws%3v(A)X> z2D{MN`H<1q@6)IJ(HqhIs-T{%)^cQHVinN-to0_X{xdju0u=*@SS`{q8vR+{iNjOW zS!aYq?%`=c)MXUwz1yMcpK9VV-S*>eT}6;bsxjRhOM3l@8S>)jnxmkmQmb{&--=a) z;IWSl^g`=QNYh9t+E+Cl9UZ3XgzFT-(-t~Qf&@t1&DRxmuXYRd4~kL%!aDiJ)F2=u zE32BXRc!l3sak7o@t2_8@m;Kd689&)9m#sxG9CI3-!Gw~BEyyylkHcyP>mh#TDrJY zHm(UNFq%Xb+L*dlO)lKNN!tg8ly4e^!ZIq|pC~A7m^&I+ZhjJ;-GPGJY*DpU_BiT30nYP;F<7V=^$#pDO<&E1opdI>CO`*dkGFx38F&6uEngTM4$ zUHI$kZuNph56noUlcC}}SIY?#pF_|1tk2k>I~JzaK+!(`KH;*9HRs>=%v7>AUoJ>9 zQ1}gu6Ld9DE}|UHl*MB|6C#Cw-qJ2yj-m{Q9J44)wo=EybhVCRl#5k z5K3Oa{ivlyD4cw+@)jx_h7|93IX6SQj;Xy+GabvOQXLX;E1(fra> z-up`rBFBsGwwYG2CuP3vX`;81P}LkgIok71{WOa5eW_3-#kirqYl0@SFzSP08e_=tE(YhU0+2Weuo<@z|4e&J!xb$ zUY(KQ{PDP&oF{O1d9<>qs3^6>9-Im5J$6x$gnK@}B)%!e%Cyef4;5;E{#7)%tbK(r zbnx}rjHIWCpGbioXDstu_T_=%8wfR61?&nFW}G{pD#n0~?Of&Q=74GdLcV#)~mtAp(7b-h2nVDjRt2A5pf+1V)yOlEx& zK3;u;^GGH9C9WoC-^YhR@`-|mrtKS>dt&j*kG1ODS8Lux(Z75dVkhghAn^@M-oQm- zLGctF;vzhLzp2SCR`KaYsUou-G#KZ#=}u^;xHpouv5lGpH~NVvt)i!hfv3onc>PUI z{juRnn_T<#iLS35=BAd{SJx|nKS@E6XefS}Ao%%z)*cAbZnPB=!+x~8TIwKv6Pz6x z8J!&Lg2P`XP;f8{(p-V)fwsvSFw`(VihW}wEgjPAk8ZczV*UWRN>ps-;Cc{jC<#Vr zDde9oAlIU!m!6RT_zZCYi{3L)yvn7KA_ba@M#u7Kru^6Lg&&QVo%lnE}(^ymWqq>2nhnvcoC<`x$(APagw zQgU*z5IsvvM{a?^uU~N>dv8ufN2fr2Z@hM2OFJPyj~_wALP>NK)P{R|eaT-uy{#X& z;rf)X(nk7eNfX*OY;PI_=_rYE>!X>xNODYOTW1XW^3p-oGgd}L9zT9HmI7QmS;w=F z4~b3ai8`UsxlF;SRxjhEECfF1U4zjroGA(nr`|WXN2|Gsng)A+U$$}yNVzF z#l_7-6vX$!XJq@%9RYzIKM7i<87u_xqm~qpc=~h_064(&KL0XR(P^6}CnZf8ssTct zRDOQ$XWZtJzbOtr?CS)&KbGG8w!7Ph4QODxQlB{QZqwU04CD_eScrxK>F8vf2%spi zj3tArfL(cffHtMQcSBpMBHK*bK*Q#O7H!@wP)YRMlzP zE$i7C7{`G8PLq%C#dAxnrc-l7vp!z_eHf30C?TRNIDp(H-gZeT?xt}dF z!>nNZxmiL|QiP6lTs(9Tm{Gyx2TQvaED+Fi{w_?7p;O8>yAvxZbT**$CN~#5Rk$}R zu+@k1=|vG5KkADp7g&=%a=>#sh-w#Imz~vXdGY*zGh{QS!29aL=j4Xd#5zdP=*8OE z-7RJrsDm{4%5OPk#3u7lsOR^9tg1}FysacX zB!*SH&PmfqS(yOb;5uhPXHkFu{?^vEZk;P8!oa{XRwGctji&PFJmyaXMbEcy{cxJt zM(LNky1U+YJy@8{RYiQcOuG5``Fl<8j0v->2hS+m9TDJP)aM|%emP#4+Vq7%?7PQL zRMR*MCaZ$eEv>B^oKG$xKS#=!!68S4ihBT+n_1Bs!V*ZZxJM8;e8bmw&ce>X zR^B=w;66O=v*8OVCF$Ql390u>^|VSuTif5uvL4Nr`)AJJ*iyukgTrU?=POX#%1UVA z{;|*X+~eDNb2upqJ3d}VsSnlG)~r4DT3B^R7S`tYKl$v0ua2q|VqszFu)dO5aD=oY zR=Hw^>E-FDx86_3nv?EY$~EPYGOHATj)urs4kcQP!o`-|=w&TNf9uq5Da8&Oy z&P6xC+1ka$#oH$r2o$|J^5aL?*8ULwqqN1tu@K2UT*fkIR4Ba~9k{ddyY;IRWX zOzksfjYU5)9$vx|qX1*W`GR-2G|Or%1Uon4yR6gEEUjv@EKDU?N0uHv!yYNv*nWtQ z6`-4AjqxFxP{>W9H{C*RdTMIRty%aDBNN+YES*IAt!8x8Mtg9}<6qnwIzhi@XmEK2 z*l0H?@6T{2H*Q(#=ghlJf+3sXgQdY`JQEB0 z8T&&BgX!D9qac^WSIcIEiu_bp>>JQE&ide^qx7vq7-ObUQk!DZr1$4HB}zYP;QC+Q z+cT}Ij{QM=?4&Z6`ve1ifqqXz)xGNKXWR3mPV3)?hh6uM13(dTvw3yU=ggorCN(5X z)^TQ!J@*32;T#7K4_1PZ&7w``wHK+m`Bt{QQg@DnfXpeWQuF*(H%ZBZj4pJ7JyawdbfTwI zw=RADd9>c}`RdP0IoQJ7`&&CKOiVeRN6u?2x)7C6u7Y+*+&}nT#R~_WcZ1pFP2(hk zj}R-T_g{B;OSUG`&(AL|jtuPCByRrk!2wc5uqc#Iv?rye$|(xe7~r}I7yi3Ziwzxl z7au=*yl!zsg$5|6eRbAZ69+FZDzb6tr(J7EFtul2J!bVvQ9Oc5m$1qgg2`Q10v%J#cfXB_t|S zX=;D%HV@Cp@sSGdGnk<-p(u1+;4(huv@!>ms9zt_{#1cZ4U)uHDJj`}|L>^^BO@c0XrJhnSGNcQE<)nAJj4a`vO^&fOm918Sc76-Mm$ zoAhX4eXaM9*G3RuU;tHDqaZ9_SrvkCp@3!bjA8@H(0#8XJvSNO@ORF?o7}!|efc06 z^@iYU;=RgkiMeL8>6=)a;p*tn?x3Qe^k%6R>f{3^$68*hmbVnAu@fhjw)&$U8^Ny> zdO-UYT2@b{S-CZBMA*wqpu%01L614WE!~VDFWCm0df&C&9!e|ku`CryiB!5}#C$sz;Xl<= zQWjH`Y%=1!cczy!J$&=YfO7t|1#KZXdIyW*MJ$Sn*H{R#u(7Zq2NP@^=s*%LO!06p zUlRoDqm|Zh?D=SQ9ZZ|IqDyn>$5z$c4a)nxZ|;wdyK*)TY{4QcdX7*W+}L&P7adnw z5oFwVqPO>jPMU{V8Q}|NVFtfK_lMMxc>Ue%yG(>HW9p`+{KOyUyiZKC&@KGvRex!e zuL>I67BoEFH$;qttF}U;;9}sOT2*?+$X>gpza!065G#dpIq35}TBSO==%*@5%raT) zHdz-C@YjZym9?Clgyi_XortMg^SFl(4>vbvz$b8-ooxSFR)giF26YQ4BkyUHG_nv(jDBOi| z-U*?u9umSTm3lu1s6u_?HVtFg99L*4saUJZ_X@O#Qx>;4lm79Pu$~K$ttvHg?T?R- zc+gk3orTTUx1>Qv2ycChkwAz;5ZdVdHR||^2;2R&@oeQxxZ>cJsku30qL9H>m@(c9 z%n=_arqb0p4U|gfA0D;AnjEgztYqWS<}A=o5_BLMeIwx1@nM%H3mA|C$ko={{#p^)VQzT>1Yj1q1lD9?_GO?w6x+1*+= zvj_U}Ri!cUFS>aO#mIawE?FCS`|>S|`c-1$Ag}wL2OG0OY-$nuaSmM}Yp1dvR%BO- zCHYQ%eGbCY8Jp&{;XvBg2GPgzez&%M@>sROtm3CYIpjC1e3qBd8dwvT^Ip!H3`|^} z!^h*19J+K_wsPUaE{MS?C}87wONNIK)*6>GV#Mdx1aJbW-i^WWzUW1~hLn0a;IU|$ zgBu+_hE#2J^pb4>CQBGAvDudBBBT6yQ5ubv&kD8IgM`g@cI2TFz=~{E{ zh5r~HmN@%%gL}EXxg_o=Y?YG=6&8G?b3FDH%q5n48a{U^kPOSxPjyEcsyB&3z;+He z2k_#U-HC%d%P=u=LDQSE8JVhi%f`8B4|~0~W`ecY#EJbt?96@VjtnWE@_VKz?iVPn zmy9Rc2Wp(p`(Hh1cFo>O>?~4|R7xu&}iBvc(`D4UmOv7=A0^xbK>AXlrY0Lc(>FXTED? za6_RU&cE{La5I^%_ybp47>P0}Nq^z)#75hji&-*saCCxGsXLXjmX@Kaq5wE& zUqIOxJXU;Tb|(03^L75$j=y(EFZthge{=TCKM2tKbl}6Tl%}Q@ZODHAo@ili?y~mc zv5nJmJax3xXIdVC?oM$+EHFs;nH;^KV$b?RSwP^G#A9}v>5<)o=NALCJ{obVvgiiB zPk0sgzJcAkQHBW~O3cd|>Pi~G;^%kd(%yZ^vHZwtROR@%x@MMp=O@NlU#-g{m!h&` z*qeXjrjV+B%X)17t$6y{wQJxrEXLf@pijMaf2Wp=9K z`Cw#~vH4w#+xBl8#~?x6b|YYOv4_eGYp4Prs&8Da9L%>7QE_&Ly`|&JG~*X~HM+b^si3~8w|Vb%1U{seaIzTm&uwfFw7rJ( zB>1JM$V8nR0qHdg0tzR{7x?`<(2!ovya1z=@|A=_Onx=`xVHB8b`pY63VIIIdsNb+ zL2^47Qh8LJl~z2+c98+UKQRC6{%})1p8u)Orwg;Gcj)_%^AAd3yMSGSgsc>c@+5d- zZW@wS0Hh}4R0qI6pBX&In7Dpn_$@d)QdZtKAA2oJS1&fmo5jWt;q*Zt$?6x!BDTNd z;s}cLr)?g3=A$JCUAj#1i`Vh_q-~?3!#w(g&jwOvYd|gbJ6fo$;L6q${r`j+!UMq* zRbCzs5k~L4yb$~PJkHF{*`QeFh3Hf-jsKI5k74!E4z(Wp*4i~7Nf0|c9Pjv|*udI< zJvX%G9eM<1O?6F)8XX~4%b8E*$^d={1L49ruXV2G9DmpdcO2M zC>0p&&kY)@_vjgH`O%Ssis&`J_=Oe_fQyS;y}!oC#}6fMl*ET3ke*Jrc@<6HY5zXv zZ*9%yc6bO-7`?pMWvbJ=?0EIgR{GJo;g?BSKR}UalRGh$@lp2sZ|}3?vQc58R}wR7 zYV{0mdk5au^7Oo%>f*Pa$-e~kpvioZ?C0R ztbA^7mw1V9~4aWUV#v!XENjZ1%t7NbOnW-}y- z{gN3m{zXZCcNXR&AnyVsd%e3hJx}WBcH`)%8UvxhJzCITM8lTyh_-LK&hxC#us6cp z-F-Ol9c#<$vwgSJTets$DOfF)m7{l9TgW(YdK*>7R4N-V<>bOch%Qk|@>dvZzo;DY z1;Us9Xv43+p3(50Hg7CPMxZonXsXg*eaRuYv>#VRk@=uvj?&S=N#K>I6vNe5PVJ!D z)2y^%KRU8u3wmhtYy9!a{?S+Si4zS0(gGeLX{QTg#3=&%?Wsz;Ec`!IRO>przW&tE zC>}O;L`C)W{q3&5JNgnf1DseE>!9w(OKqzpS#TmXIcK#*ZmpfV=~4WM+@{4fk?Q#I zL%p@IefaJ~%2|<+as(+s@G3Vk%+%}ZFD2ZKJ^ql~+x$E@k;HX(q})F!931e(#e?8~ z6!6HhV0zo+^c;oE-VDZsC6D-i`jn|zVI>_J!R&qN=`j-?PLaQ~bP#{z)5s?N!}iRd z|Bb_xzxnoOWU>}p1(q+|#VpEiGlP!a%3kSUv{*LhZm!sRrJ<`!m0M7gQ4?MKe7{hB zs-k9B7C{bNJq}$xFfaQ3iON!mumM4>Hl`Xt#@aax>PmegkKXDy181;~fGF;{6 zJ}n*0o(umR4a>gk>sK;z^7XMVPDPcKMI7wxKHD38H)a*{_EK*}dE=IC%hdzCq}3@$ zVOhh=%_GoNo8)jJ)g8txzfJifK8}CbabCkYwcM@I1&o26S2fsgee3V1Sn=4}E>JO{ zSC3az`@{(cJt8Uk;Bj$6Vq*P(t-WJ1Z^K;r8rKCF`h9D#F=E6I*m}~r z*Nu(7PSrvLG3icla&!cSTfK*ypnHOF&>GUOkixk;_X@ zn1Owa-YEP+r0?u#{Q< zGUzC|{M18yv$Gd-qM3=<)R|&B1q8Lr2G;*pjvv!>=Co|YI9q>?) zF)eMFxvc1|BZ#eI(Ih!>#0h_jUj6pyw_9ZH|Gc#xYXe7au(?5tWySIVy5C{^`8L{^ zPINr-l(aO|^F+Bse&>XH^Fx%*&OI+*_CCvl7wXJ=)8McqT@5xy4!dt}I*GgW{Z)m{ zs6$j$hzRR`yMDWZ0D0_-csM1Or_jPIzEQBieuW$A4_DSH_}`PvBtI<;V<`l}RzQm%llTPS=BtQ^_8<%DG4lNH}~Z2w6`#0D149rkv1 z-UkcCQaY}I)HD6JJ0@-3G$uJ0<72Q*PH17w2w^0^xUE6%9NYb$!?pkG>D>7bXwRIQ zN(XQ3Pnq&Q*Lf*4WG0Jp{XeWG^lrP3?7lPy#v^ zl)R7yTbQ34f7fiQj?aPCWkrI|&0r)nR+(OYkF1vpv7I)a`*lOI69@tI&e|Pc2$=o+ z`kHUjF2*b>*F)MdqW+_DuLUM}kuOv<9&3ftu`#ht6y46#ooomQ2!IKZG!g5{crNsW zxFN$kciE{F8Ne&T-(RIbo!IX!X}Q&C;+WR8k5vI#m3|^AMw1=E2qJ0rhD5?)C01 zC`A(QR-4amK|bO2d-hcui^_M>Dq~cQRfv{Ez`XnSfvf&ggJ5vJ1RR0s&!-N5A7o*J ziQ27O>CeCx<&gX5$nG-aJHsTgqocbZKVO#5?fQdA?c2rjN!RqBPGPmOiq>Cyh<;W? z<2b#heXrKBSd!oQzQOie*7#3CA+a=*mu%0+8h3#OQ;?Av7aI$h=J;4gXXgdvzbmV3 zlby}2j%qJ)wiv+RHTf#NAlj zM?v0k$hy{*B#c(3bE^OJbCV0d9EUbdlsfSeehN%cAw(S)(R-6LnV)bS>}80SA3)IT zjZEsu2-F9WAJW=NN^AgEdL_~P%ND?o6b$lC;0+_bt*v6wp+rP;mKA0?-asH)HhtRi zs|Zf+C*R(cs6pgzo!xc@yu73_7W(?(xz92(gLFWq5TJDdv9-6?D&yFY_*TrlECR&_ z=wl^H^~OX7G->|DC%N?3Jc_7uCN2NcVa)l+ddqIC%8d&;u6kIO{3rJ2BeP(Q4ROU+ zlHkz)i_xL2{nA~>hP@C-QO4N=L+EFlCkM_xP6V*MG^n6d;$#0-rKoqcVHo?hh^=F{RU{wv0mt3VqpFHrL5M7Of5 zPB1b2I7o0HrkfhSh|>;j8eRz&nJB7}rQ*5j0iw=?{XkIo17hj&4ZaNv0g z2UosEy))>IZ6@k|R5P~@3^)#UT*HoGq4*`Fp&G*p6o`MGPL7h2jBkgaSWgum&R~IB zc$TxcS^hmC2M&&wwie6RuIhF11dY$sx|6V>oQOq`3K(lCN4WwOLM0p9%hT|IIM;p~f8&rNe@?YF?+L9FvAS5INZZPsU##+yBf%_|lA9$HmYlWY($G~F$ z;~&bSG1NW1S*2A_a)uzur~W;}D+RCE@S|toe%(Of;zRL7q(bA~l$nR+<3|v#7Xuajd%Zk9-5rEf5|n zDk^FW>1b-29G^abNG|=~q3R2}1KCl)=(V34kOVd{;`{$|6cBX+Cngx^M+t#}{_Nr? zdZ!_ZV{=(Qdbjj@NjllM4+(l}W7Ub`ob+Y()jj)=Z2RqvnQ8L-_wR+N@7&f;rRsqb z@j&7#qPO)SN&GO(GQ<*YpqYzcFt(YwWmW+TG3HX-EdwAl#hB}vew})^+&5xUp`M~3 zk$tr!`kf3y;lq*5s6758{)>_G>Bq-y#gaK$nHtQ!wcgPBN!_eid}2(#+zs(9$wfs$ zi4@O-fn%EqV_!Cdt21A7kj6I9)5WEe}4RL7a_blXr6?Hm!LWKyg z*AqZT7g-qOtV0aAw$9ee5Nt}tYfP zv}+SbrP43dK5Q)~(QY*^P`mv0n3fh67PfA^J1%mrYb_Wr z?kI+a88G=TMk43bqXv-?@X=3Ecyqt&MwFAg&-_CrvMv)6Qh?y|^Q^EbPt>>W^kmdN zBu_%E(prlyV+OsC@XhecvD*uM(-0#9&?LO2_S5wB^&yBT)B5-W9xJL}K+;gol(RB7 z2hT*fY8Im0Fjfj)XPhSyHSB^7b;r2TQqbc~5;hcjR9b6W));Bk3VO}4R4{rhy^&mg zD6-$uf_Hr(H-*8_aQdkf)Ii6bLl zGS6}6uQro-NR1C4H!X7EbAY&S#n&PTtWrG|Zc2o~xyx7?pGS2!nG&AKZG{Eb7Z{YD z4?Pk`GzL*~X(A(LEv4+`zRRvq6`Tp<8@a5!m(QQs9!QN14Yk-j+LI#@@;+;*U)ALN zUQl5B~P>vm6%n~>G#){ z$v;PGYJ#KaR8<HB>_6~ja04<+ylZry&iCi`hp3Z8Hxkp=k0SYHz!_)Mq_f01JN*d3^2w+Sc z7584y{A(3}>fZw_h3Bycxu%x18tkL98oYnJyhbmd7*KXCiHRWp@u_p&H0WrL zb7Z_u%DX&P^${D{Gw0 ziSC3hKb(S3=H^wN=histm3xj`!Gd1y;+7LA|BN9Ws_X$m^4}rC+7v#^d+iHZt0qdX z!L{is9%XYraW2{g3AN^ou3x|C!*2nO6JU*ljy!&>l7pTDRrPs^{%#?wG*fS$>_b&?`JGnU2Iv$o7|8QBKaQHVSd=_{bj0x}7{i~TkJx`8*A!?xU``TFO z691sv#zIPbF7r9HePf@z+G5`tx8(MXvXXk%atZBsnElV&m^-VzM#skmY(L)bTJ<_y zx|VUVvhhym=PtkeE7o4BOg^^;6m70RmTT2^tXU&(2?j!)FZBWuHhn07@(d>T?|y_- zQ7T=$()SI9=uf@@1CD*yo$GFQn9_%$IYR#fAv^)#D8ZKk6O|2SJy}rv5E2WqGqJ6i zTjaiHnVqq-Tx1a2w;tay`wjV`oo8#wAjJmGdD=7|f(Qr*2c!6PXmn&15LDiI`0$c@7Z3L! z3ptkv^XRC%{@}|P~s_K~5KC$mOxZB}I@b`V^#XVkE6`|NADu1(8+Gh>iggz@% z{M$HTLhnjabUti*2UHLNd=26a?|Iq>L>ZC(FoYAq8*H4Zt7{u9QA*UQzlHLwYQ-D3 zPm51QRaKRR$buEwTF=PM&u?fDbT`Z`ER0wd(wI__yBsWP4qUD1sQyA5U6zEW9a}rL zlO>EIe8s{H)TVXuHFn#^(Cc^@8p1&aq?*obvp5T0eKJr3rVYKGGcH!M{A4tBslok3 z<5nTu5@#W1!r(Zvcw4T9?5qP{Oawu|m2zmP(c^n|LADtCi<`8mt}gLEl6ORKji0Mj z?5914H4E?m2EVeF|G6dFjdlBii3__GQ zYw*9t8n)|kfDHvFxaM$iK@Sv6lv<9oH@*k<99wrcycgfTr8r_(?x@$xd~U!7gPUOOI)o|eHd%3bp4|$HzE4f zJwk)8QyVY0pg7{Txxx@7$*fbqFxI8B{1Oax8of`5*tA{*Jb(Ti=lb0j%Lf#(q6Kbm zM_I1qvN%_}ZD&M8u;)f4zKYg%>eKCZPVDRBz_=I#hcuNV<*88klT#87hd>2}-;E7k zeKahh?t2TjE!!i%^n2@<$_C+3hMpFc{IG0% zb;J15*E!o^-~;-M`}D@`vAq+*vl@qv$FxMH0t5MPB(?Q%eSMa^h(2fR(n-4d`#Cmi zMs~80-JdqnF?#B@UFj3FWU_b!y`?4*-}eUvJ39W3^QD;0F)E*v*lC&#M`&g_u72S9!1KF)Z(67 z`-BHXKW6gs6jG&&%At?^@&T?HvNrD$sr*9<6J_=fDa`GPjG{dJ@~2dga;Eg0y5Epd zEm0!l`(IKRQB_r5c6KUqTqwu!CQlA@WupYMWohuK%slZ$;mV6fxoo0 z8;0M9^vrw17D7wNVTW0dKuOkPfe^Wh!E1%~TwGOeRah!W$D0u9Q+o={iPjw=|;brNDE$T&$DqCUTfnDPLJ8qO7IJB7F`D~tGf`+29&yV|lp2%+L0lB4l`v8R8e)1M?mn*xSNR`Yf7!mzK<3f78q?>;GXTHnQiV zY<)31+ZO4ia0rXdsSPb7{vSgVu&?g#PwO*XKm%gf;NT<3#e4;xUBJMAH`oMsP_%o{ z5D53m-F-Q*#wT7McTX8)@U0$XUU_V zw_gm4-2CVFCMU|X9?Sn)SWti@Y>f^FyVf5Iny3YAFqD?>z4j@F4SZ1`(##@F!v`mI zt{)HdGc(E?=hFZI{NQG&Zvpv^-j)`80ppl&Eg?M510|m-RWKe^-jddk;!F1N*)_yJ z+CS3~xOM*oAK)eLk?jEyj2VIpF+}GI9>BGrd;D*m&EJj!R%I0JHY286EhKC=zdc2S zfAwd^MU@Bs#p-#CqN%=x?!@G8e(I8JnIu2N1|8<$ue}lUL$z$G!A1?#b6iyY!Fh*9 zi3tBgKl74lq~1toyAk!PU{bZVRrXr$DhkMNA|s}O1N_FUF>eyTM(>vaBD|NMkVG#} zA>e#`+ysW#Iiux?_k8pr*CEaO`aS4~Iyx#5-)j#(mOD8S(91S>(YuH>)kgK76WL%E za1boi6I+?Qvfg+Bf!Zx9LoQ^zmff~*%8tONCMq5qiIhr*mh9Z#dmzFPVzeIguuw;R zIU=|!escG_&ko5Qbw#_|8ulQSk&nu>9H->7dGHf155#H`k0>wTJFkp!+rxLZ${s8i z9+@V)hj4U#7WVKLR^K-joTNejrcJ?+_klJ{_)J;W& z0zqzw;jlBXH1nM8|4#uf$A2kjV6?$+9OuATEgOaVd^nSPW~{npFJ}}TTb>f zYu7(?H)-)_@f@)>OiZ=}NB`bTKul_-aW+Z{{sY)necyi7JMNL>qkV-D1_V=(iT3v5 zS3Vkkb>G<7I4ms8Wo-udwVG=0VqzNI4i;Vo)oMy-G~p6rpb6iAr`lMn)*75WmY4fq zdqE-vh6vLB zyHIFVYZcVlG-d{gKi(;bDb#T)D!cD6mzS3IP&)oBNkZ@zA(qK0XX0OS+Y9saN)Wg< zXE9_0)E0>Jss~texSmN$IqKkd82N;bjw)Ay)7n_@N4=jF82x#fne$UCo>N8uPt&6y z{LU+`zoycETqxDx3@|<_dk#~~oo;Oy=@U%tmm5*N4VHZ3#Lct_8aXnle!V+++8R4I z*uwWN7klhId7{b{76xR34_9+1b~{YJ`_8tMi!C>btjYm#_pDw1(#2F9u+^+*VqkFP zu4y}+3V7C0{P!IJ(buH;e^Db$ArYef-_^L4kH=x&)VpEZ*k>pqnwpq6&fs&#d(Gw! z^POq?r2CA+rR?Ft$j1i{Iuto$zt%X+A0PWcYl65Ehm6wlCx)9(6nn+T=PrZTm|_Js zi}1^P9@A5O{#kZh<`weNe~q>-qR_;AHv1xI$unv*^CI>_Dc|)9u*%Whrz)E;^=Wk7 z|Mc3vR9< zFT!V;X`Oo}26mXCLrCuz+8Dz;B!l^4qz!T|*z-(mddBiciTk$|!v9gq8$)D{>PMX^ zmn11G>!GZeQdnp+Svvn!H$ziH!;|O2)2BY`A(Ohtb!sWXU#rFz`!i~)y_<7h%-_0| zqW=qYGCuqPaCwc0Cstn|+-^3VnmaaY32&m>_M2DA%F5JgJX=ieO`{=2f{uoTI`8Sj z=@R+vELYx94cTA>NQ(kBVIEyD!1Ioi=V|peu|cW}r%A?g1FeP)7_-R zm%gcW*#yR%Vq$@DXzfNNph@u=Dr9#rlRH~)EUj3}17t)6ymCsjva+^TN2^PHc5PkS zqCaR2yiC>uE#T&KYfdNBA)~ZuSXfE4kn}_83~U21X*_WZ1fKQebr4lEGB9LiXJ7am z(=tqHh#SGb2r&ot)z?pS*L)*A591ODTMb31sLdtlmmfE>M| zJu9s(f*nf>qFABMZ#vvYG46gbTz8Akxo0i04+8;vz+a3ZZ-AN#d^h)ytdO~Ft-ymD6$!*81wy7GUDT^DR0}cO zxvD9dnN|@aeJTF4Jp}aU{nNQZEbx81vA*>)cl8@L9%@M*`_S)VLuytq3F9~<85=3bxZZ9XNr5Oqf zBV@ZvgSq7vLlx=HL?KVKx7Uo6$-*j3_Sqnz=JaB)njf{qR`#2F)h-th$c3}yInpli z=~km#RC1iuC4le-h!lwO`y{EHk@5_NW8kp>+)D5J4r|B2oI!kn7DhSCd$m7VIwf?A zoUg;?gDCBnj)vW%3W*9v&6u2A@$^&WD&S6-N-6>E4Yb`!6ZO6GJX)lCdjckBWVW1q zw>Z$K#J5GD1^d`~KZL^Q9b( zX;_$(2}n6W>-@PpT$q|8Rt7IG&o@=%AEnnol2EUP%RF#vw5(E&xoQy zmHfV`-r4#o9+G@Ks(rX;WyV-2zpnFVM5Rl$9%?ZZk|b1Fxt^^fBK_LY(b2%rxj?O& z=}4vf421lzL!k#90)ihnEVpjRLaqiGud|GW%+7j7a*?f}>+{?Y?1(mk;H4NA9q>lq z$;)D3@r){WnvaVcNd$&(fp&zvghKl@89IlSt*7TqI3B~ffiF?e!=oc(Rk3ad65l|I zTRi#|W(`UFUah^6zou8ip1kp5riY!Ju6TItE6W|DdYD~Fu`+%7Z{b4K@4@o04XSz% zRY!E`P8j7v(-miTD@-b#4w#UTi0jy{_t*$pzrZkkpt!TLu;gXsX_c7juj|w!NKp7q z@J|o?s255(>+|zx3d}?FuSuqV18X7BYhMZY^zozMUDNb}f&%w6tAc16H$eZ2<>m5g?*_~X~LNH={%c`EoN2Hvd z2a;E4=G548bCuRh{?+@v=QWX=38ye^ecPPt(l(!&IN7emKnNzcH@2nu+DK1$zUT*V)`BOleV)g*eVV}Ma@zaq=EVm%I5;x0>iK-xM~&c? z%*Hyq3)Y&(COa#-sGn$g(w?#FG_3qJ^aARd{*`#1prE9?Z)c`guhrbXj{IDmg(-f( zTU#!!{i{4E^yVdS}Q1kCKiG0U%{n``q3yq5u_cwJd2J2)rFCSk~c#y8ffQ~-Yx!)Goi55O(FBQ7JzWJr}Yj^ioO1HJA#?B_z6E#oN z)Ld6KQVhjYMR`)i%@*lwnAq8EcH^ALhw=|s5Bk8*)?YD*`Dhq--@4?w|4X#EI*X}r zply`8P5%zWT`JV5pNl!%b{4h1=fG2woSeK=N~dYMbt5PB37yZ=Qsd)hJ&wz3qZTAD zvr;#s2X|>=ONmqa7vJBPO6@0Q9dS%04V%Nxdyc}Kk}K{Xgt-s@uw}WT+3D?DUw;zd zK)m2c79h1+tEVL*GWCX08K$TqEwmY6xTT4XxR+N1gc#58NLgC0ytr}$tQ^5>F8eVw zd2e0#ZH0Y!g6dg0IeB>_x{Qy0AFybaWZ9*O?FuaGHrP82@TRp?OkMdL)TLWwJG$4- zrp>Ra_(p*tf#>NKdk7H$IbFn&vrKt;?cIAliCiA*4JWxnd1(sSAhlM`JpQ@3_$oH~ z$!&)t=T#=2Y{Rz7NnJtNx_f0dmDZmFs~wD0EyaSIS5-tsaVP{|i>32e4i|xT!bmXh zjFvKDDCa3QVoG`S7}UV2w3x_(6Mrh+74jn!VJ;u=nn4!EYlm_MEg(jV-)UiE@R@%z zDn;e`@ond??>?ngeonE&n~Fc=y%ubVB#ZA`GvZNQbtB`oe%#nVkL+M#kCwTGh^5^w zNWY%ayY#H)CzmMfE$418pTo>zND?h)^ zckiOAT@D2w-buGXZ!~@S)ZlB}#>C{X(BU4LY*h)i1+N0mWBZ1dw1Y<)^6N3m^Ti{i zcWa}t2e`C1hK&rYqM*RL5FrB7)ePRJ)X0(ZmP&JC$o_WsXYac3@Ni%gO`l_qhN(Wi z;>}kcy!+GFjfq&uTw#7Z#Dx&?6rSoHCP%5zt`!QEEVjo1GdiUbIvm#+$1En-@bLp7UKAz+3a$0Vr5XE(#NK1_D6!D zYe5fDDKk&wk017%?D&&edT6|q<}o@=im(>rl5`~|WaEP8j7=JSaMO?FwaQDOZH6Ty zflr@8mrK9MO3A)hEX~frsjP&A5+xxic@f>s7!JjEI~Z0YiF(&~SV#SE&X(`0WRV@~ z{37Py829pIjZ-2jRU%zUKtLdhm3O2Zc%7NI#HBwe@%z(UyIy&po_=M7%R`{r{t$;k zcBwYmEGkBVm34|o!_or(bf7eA>Pi&DpD~|}1vh+tXHyZUtt$*RQd;y6#_KkHA3uc) zq2|oOWlXQ&M>+L1%_wGXQ5@1|s)@RqgHB!&#`tvhnwpAV6UnUj zRU?B?soZ81*B!Qt^J6IeGVq z+hox!qs%0*SfWMg>B8asR>{bvD`NPCB9#~p55u$@YZLEfgq)L|bee3B9h`h&#B8BQ z%3QvzxBR59zIc>-vZjNF{M{+@@Yl9zwTb>rNB+jiwg;!GI#$a{IfE@C03slsDEb@u zPAX`ooq#e`w(1C!Ys-gJ2_dP9rk7?6jpHL8qhTP|3B34`O8a?pkjN4E+`fu>=}+6l zNc7|pqukN&g@yKcd#n%2L6?ip%5+{C20^N`WIROFAKoqB&GHXK6?b{4O zUI|%)=CN$gy#IW=5E<5)c;q=l2GAXyqRDKLq>u}0&cmNn$G(Lym;ab60C=B8nbxc> zdW6zO$9N@BA~ZBE`jO_0W$Ea1cseW3 z=IJ=xr8TSGJt}x|=!_X3UoHnscX%ASl6LRKaZ6aWi|CM*M-Q%XU64ByazJ4;@Z?Tf z)dS_{&j}EBT-q!z+f?nh?^l$-)0$1**mwwEtx-wf^qgYjXH8cqm$S zaoiyO2->Gl-^poLpWTGRE8WQ=>npR$6>E=aQ$|pDQEkjDQA4*)G_&4+yJk2 zTCzzS69cq9m)3`M=_5Ub%nh|G@Vrco6Y-}n=5FcMUemag(;0utAEE1S8Lh}1y}P^1 zYF1jBXHe>Hx_T3?8-Rhp_^C-M6s9-7ef##Lpk0VtIbY$+nC7MPFJZ4;#<(LlPXO2} z$%}Z+Q1F;4ETn0i(s+GqJO~IHbYf9R=6r@n9I|@9KT2DOLZw#pgIJn({PB1#@&4rk zQF^n9FBtIAAYj5LySLbvo>Qw!$seZkmMl$)`3FWiU$=fTOW8zGh#mZuk9H1EbpG(Y z4J1O9nUwq8!>5^2y6YZi8}XNG>UOOT`l?TNpO)d**{FPpER~qVi^D6Jd%UN9B6&CX zjN)ath0{sPz57-$1b&(8#Zkv~iNi*_!fZhnu8$7=-HztkUKiBy$n~0+X@9}E4i8JQ z#3*ahqws2NU;_br(XKYtJT=CaW>fCvkQ>|-PBX{VuyuB|mGR&w-MtoKA2E&X`y87> znmoP7pUHku-E|GNTB*DYQ$G6SOq$N2+0CfdpZJgQQb)NK&M{i@v}Wn}AH|Mv^0JKy zREIQO78Iu4QP9$QcX}}2zPb=IMDIcF!c(n={|ZRPB%!ku$|fz7V?q(uW_w5DF(I*k z4-YMF1L}riZY+PHcF`ZjTo&U%7i}f0Vb|IKaC%U&-y+%wJ{J5=s-yXkfrQ0cIBxT= zUDI-zwRJw+cTej(TEp;&QNQ(nH%~$?xY&g`k5ZIW{F?TOFIwp$B10C+SZKd#*-`du z@B}}nveFgw&@CQfaDVBd8Qy)820PE2=WrFFuK7cIaG;yq@L_!Km?bhNWn`7|JzL-6 zo4o*zu+PcSMz2BSvkJUGwfcMw$uG81^KmlVRr@!S<+{7tZt#834cc9^8LtVZB&HAO zXg)d&Bx3n}DX+cYzCIi1-FRwvVJ41{J(C32k_YbYC5$_ag8YMg2C;AZd=pmD5EJ6+ z5p_D5Yf0M>B7nnin_>Ten~u!t&v@2sS}C`EVB)=2!;fv9xLA)0`pYcF$Pn9GsDQUO zygaMPDAcOYBT(p9%97{^qa@$TXCA{XoC)_+${HvfG9$F$)~sx^mtja4_as7GJ!v>k z1OEA%i)jRO$pY$S*sX_Z+j$q=JashV-|#W0Ei-P&qwe)LhSgb`~6Wu50TG#r^ zl(|0?AX`%d(GvJ2mujs6)>DEUa+9cF)pb9-N@61L}$IThB06aRb`(skIc(o#@ z+0pLAC>(f9da5P6zc2FIDiCs^g$>-FtnT?`t^kFsw(-ETP^y>l@)JDNdZ#^ZrtgEX;ljdITmNR8X}@7TalvW`TLBMFuE7}l7RFI0$e=U-@BP>omc z-oAVH;lm5a%q$6{8Sl3^ND;6X_V+(0t7CdUQY=l#ZHv25v&6|lErObh-+qB@mf^-S zz}nl`@7mhh^4YC^|4!WKvO2Eq}JIj3wLJ5WoRm z9nDeKSGsCP)sq|FhR?zh_U{RdG^vFk*YzDB17kfRu~{Fs(ZPOXbYW}XQ$$x6CYc;9 zf3J7LII7I&urep%S41jYe-+qK>m7Yck80MsUvPj0DG(~8(t{=2roE=>GQiva_B(z4 zWU-^`Fu>q6XBw;`Wzyq+4qIJJ)KJL7*v3S>22t5WMFoQ@#)1i}@e&?`rY-9aqnyWn z+&t~3Zh@EDwQm_-eMJ;784nBL3I?P!d1@@zE=fyEN9NtV&1VV&oGl{+((8PDd{8m3 zb}@hfB5>gVl-vx-)tCO~uo~t98Aw83 zn?&6IA@;jt{0U~D)N<~`TsQr_PV4knLRkb3-@)PVcqjhDheu$9l-kymePdXaIQSfK z_Qln}n>*IPSqQ53u-|HA9V^FfSdHhxY8V}L@5%)i+HSg`3vq&G^vH&5%?IFGPJ0cD z+AZn0jRIeN&6gb1~s412e?(anIK44nV)o7YkQ+n@dCFKFkb zYN76Oo9Fi@r7Soau{6aq-iQ>i61fxT62bR*1;d{#mos?vbkMvE`2`)=r6o<1JaN3# z(~yY!0l-WY)nYapzZF=+FHtb23cYZ39eSLB2CF zp?&uLKLGmn?%lfw7DEFVS@`ne>#)ShFl_g(;EGsLH7eLftP7{hC|{=J>ELVpyE!}Y zBroUdpGw ztbz0b;$6DW_=n%gWhj?KJE~tuK4*;{yJpDQZd6!a?tD5PGtH>JA~iz8&#=8t089Q) z3N3g`LzKSxH4{;*iDJ!${p+U#xCXk0>=0W(?v^EASStK#S8!aA_z zC{J^zcj*m-EVkLs)>p~2UO}14j9ybg%EO`iA)cqld%9j2(quWPrEoJpprZo{Wb{8~ z@cR5Pi_|DL;`O4^f(~^m_j@()Uv+`R z#Qb2nJwnrz>*P_%n4g1bdREN(9M`SJ${!rX#l)n>Bc6cm;+=iK)HV*5{=GnTlqRt=l($Us57|)1VGP$Slu% z0hC!o^Qv-lO~&Y53pEm{+!Hz?)|TX`TdLF^ebs# zddpLW-4J6n23g(q#9zDC%e14#cC{rX{R8OF@IK3*C))r@lE8Zvs zrQYy}f^YByva7lJA5OORd3@fgb;AFak*0)tc=b6!!Zu4AFZ22JAB`gkhMm9}2Lkqg zC8e`xNofxgB&BL<{C%p}k0M3ww+7asC{L&4cU%ED6Bc5U)=e#JuaYwu z(mb;yB1aNxMJwV~tgkBi;?W~5tuLP{UBCS;%J3FRPE1@_S>e(<;zKGaNva)&tpke z=U}Vz*jpbqa{^ip0O3F;)-n$#O5ir%_wM0gLq9w^QI4tnz~-l2)-(^=%>MtSmx!UU z@hIfbn~e>IZU%~D>*IBUlat~T}Da&cMA1gy7sZe#j%V z>OXLD_r>ym<;m#^bdG0r`t#ro;qF>V`_S1FGwOu@`}nyN_M)RZl%LQGtcxTxLmg97 zEiI$XBUQy6-DnXJ&mE1qAy!oCU;&HT)!+i1`f!aB=h-i10H$V?Kd#d^Y66TKVXWnN zO>q>j zzcUNONm_4-d5!?~iA4NLiN-x-!)A16CSAIT3F@6qpev$UU4!WObJa*b>Xg#kF!FBe0=M_w!H&85OC*Dd+yz7aVX}B!h?| zCEn|9ls{sNtv2e%L4MX5WGJN$tYE-Hp_-BeA4yLKWy@bHp36aeANox1F{~6AWXom1 z+seAd@mv0tppR^vzD6EPH1!v;otew3J>11^h;4kedlCOZsb8}p{J%y*maW2UGx?=f zf6%-r4{(}LKwK~A=~~zh#+I}zf=@`j%af>mI}!mRkK$aHHtREPcHXf7A{0YBYXC=l zZt?xB1|J7w;$#GBkbL;t*I1A$>9Bjj5oho-#0gh z&aS(cr@h@J7EuGJZSz@#IjW2*{j&umKo#!y|l|2Mt%N% z{3^gne2zB70b>R20JP7EnV#M>UC%sC0@JaVUv+Z&qnnkmg@72`dc4x^_`vBSQ6J&2 z?R^lNfxC&>DtZq}Ns^LnHjMJx)pm`8gXteWDBL64b`{K0ao_(zTJ>DV%FM4b0sQMb zySlJ^;TELiIi3w9y&Pl!&Q|b`3I#!3@jMH3RniB|G45W_>ebRpsECb)`0}wHo#GN4 z+v|@1BX@b-u;83DGyowyc>Bf@qeC-A^iW9 z{|ki|^k(WBi@4a#Jvl>C^nGz!k&I%vp+t61qbFTj-J_nzk)rk{t5~|qdyj8$fzIrK z$p*KJ?J+;(>(|%k<=(uCTZX&4z&=NIXt8ahwzIzI{flw62>=__X|&PtqD+0-z>2;|I0eZB^H((`(S%Q(|g+Q zQCB{HS1n*U9SwrovzC0Xtth{uP(ohoEMtVl2>#={^&f7QPJIA`GuTITYgKr@cRf{{ z@j||LwkL9eS}s{$K;J$s%24fTxI^|#$n&sR~%aw`2tj25QaT@ZuAMu`vb*94e({Si_{NfzqpT1Pe zf+~8$Huj^H!CAODJcAQ&6MF8!h%_ga{Ay?gB+WN_QO{mi2@w?mhY6%~6P<}?S_ zsQ5hxY>%2hU`dm+B1oB*_EdEiopzQ|GN0RWPGqCx`)=bB1N~F(8YD+8$L5r{(eS?c zgl#~_>g-rq&xSV@0)cF_l+k$OO(rEITa5!~<1VwGA5Ic^_Ye10D?R5& z3v$$;H2}6v7z?%0BZptHk24B1>zBS4&AJQS)8X zlH%#UKA!u7f4t}Up6-*wLN-m*(?zeSYsbgx?S7fJMyjejToN>VizQH7Sun{+IaDn^ z&^s%N_t5D;rhsNb4jqQ$n>0R-Haz~OX|8zp{)b8#mlUeiF_hk7&z zO!5mDPPw_vr>qNF!phfvJ}}s=SxX$IBtJRmq$D@V7#n7z=5usiJCI!)^;E;$apV^g zn%&G@>i3T4U0B9YigX_Ak2agy?**QC*y3wI3hnRzM7H=L|IeuWk6vTto14MVHAAIV zI`DorIIN(YgEJ_;{r1I`J$Hn|^-(lV41)Mh$ z@(`mc8L_OcCwt=H*u=Iy?!-6-_zy~ey>EKEpv>!*~D2C>-SykeN>`_D|)!AYqvy=^;U&trrT2ncDE?B z4IkS(i5+(#&=8rI7UvpEQ|>H*#1T%n`m9(K!<$>-_@TEoyHjqQ5O>G+o0gX4c<0Sa zF|wjw0*GFjHod}n@Y~y;zf07LiuPU$_tar4KK8>BObOgnwbH5x@gRa*03XUnQNP(V zRIHv3>U>mT?oJW7f|z{l=FMC;vy$}=6WF2kuO$lj9>=X$gGwDQ_G(=!>1`6Nz(Ti% zY7}wvWoFBMuy`qz?z}YcI9=&R5IwHPA!$2V#0?R}JT(D+KGCBupT3c(rLR;nH zz(nDK=VZ-8uAIEgQR$@QB$_71{MQ;VPpWn=PnaQ9K4@Q;RrLGniB$m^#dz2;m;FKt zD65%NnUTVBvR9Ztn9fY%F1heQox`FA7Xn4nLh_eHZg9ZQ0?w~s4tV!3fGxjnNtgif zL3abuO9d;C92Vte=0EdOpl(=l3{X%_aP^qJOP2j+#GS6v5br)iwA9L%i+HJfU6yp` z>+chx7M1<6HSy>IB)$>7BtpHuO+femesQo!&etoY`p&>?D`5CRy=YJ8lZ`4UV_!w# zdC?0W5N#P7Ja60YaQPxicB;jws)@1LNlAc^5smkwIh1_m3n9F{uvDck9tw}{9ay}a z6=44JO3Hurlbv4nlGi;x(ejVBzgT}0zx9yq)lS*|J0-62xq4Z{W|r8Oa50HcxApKI zLppX={rYde=n=7eUcIc@(#VcOn8`oJzWw{TpvSVdyw{~2_v2=Kaz^!Rv2xUK&tg_6y>Bi{X!9H}zeYghEQ_d~5XJZ<}PAeS*9&~uA^?|#8m}0^2ZU4dD5AlgP7Vzh@ zBwlO>ed*FsK5<9e-43Ad{`tAn1ipq2j3xrcIA5rG`HR!B3?WPyRV%Gas*YAbcGT)WOtFNdsC`cvOAZrYoI?3sa#-%Y_&Pjq-{dy1> zx=j%vD;O)c4D%pTSE45kZJwUigX6m+hgli60gqVleyMZEWIt!v#%zNRDs!)kV%Fg4 z81Pz~Lm{!i2nmTt+?bu6#Rjpa@#)F2dcKYuW~)r&-k;%m8x7N+xYfE)3k;ZqIpsZ@ zWtW{MNrdlYEURuhwZ%leKw~IV&p&eN4Yi;pJYlORZo>&l2cO=@#0a|`@o{hz&Sc>b zQWTC@&bZG1AO)j(hcg%7I;-z*A499F&S{0rE<`Rv{OQxGL5Hz=AD_7{>ZPTn)3NGU zzA~rw--{(Ctzndu#6_dMZDuWRZn#ysA%#vW<)9T#)g=fUFRDHD1vIvmum{h9|2P}6 z3spfJV6l2_ac@8NJcm#{^wytoF?jOQU;nfQ7jbr;>DDSs;_{hJuI5BIT)uvUC7oDZ z)oxi?SwjI20Wg8838jd)U>M55))71s$z!7! z&`eA%gq;vVqW}-_l}g_wT&dk_prZ)GK^{JYDgRc8XT$>D;-sV?MCiAs-)Yfti?eIm zm@Ik1z;no>49@JX79)o%G=kUBMC>r8;$Ex5>|}4xnS|Nc;Go*xKT?9d&Ctj=doWup zjFNas2FKu}W*>PQ7niA)6Lu&hn|Od0qGXL~1M8xyKRK7q9%=-!60xu{VNBt1n<;H0 zAq)Z3fJUh3wM%yd7^_1B&C8(8y`&YkTfJWA zVCRskyd=_nJi><^+I;^+v0xb6S4Ha*a?g1`Q$bA)2kHMT%Mx2(nCaW@lt0)xKydc> zBMrXZ({Rk4x}^hezvU`Hq((})#WsCfMRRcx|gBRw02TT_lhc9GQSwIut+0sA&@TU{dNv9 z?uwiK^G7d(#|vlWt(C)KhGxYvs{zsn{)(41lybAmA@}K^qzxIxhD3 zHBNuZ$UyVE^$q4tR!oIoKq3y{#IdMB$YLEIgKdcef+SNoO z*?gFb3H(l4j~@LxGuyWw&m1(TrlP95&d)6rKR7K{IY3)p1VPAynZ-v_Sc5JpW;k~*Z5(b;a)Sk zooN!1Y#Pa5r@eRbCuhn3wa&y>Z@jjr*9U51-n@BJ`K-TDf8VbDvjt62!a+98wQI-I<#seY zW*l3WexJV)Hh&qSrKRPG+n}Tz74zURtBp-guAQ{efHww|(Nja`H@`&G$fJ_laHu&kw9b+~Z z3`#8H%PK2{Tdr$(@}g8AlLz!}DBj1)%KRcC8tUr3?d{$dXh?spvV2Lm2e~l`{YOg5 zOitl4qc9}_oyc&ki_mnlvC9 z_)pYr$(%(&?F>&S8Z^7f90`QVg zFNGn~9L(9u@$Lod6U9z+lr>(&M(D$rl?#8NIc9&7It71{HS5paeIG>N5Zh+_1cMQO z%svW(8XFXwh&l!Twt(w(31UQ69-)2b{b(Is6Av7ZUMnf|49(4(*Kgju%bcE0zmLJx zdHWD5EVDzvlBTD7xDz5#gtNlV6aK5{?3I<94xF59Y-}2cgXvoL4ub2^=5T=q^1}yB zZEbDj8=O#F01oc9eL}Dw&M!7PNl?z7s3Lwppr09qOg&aoDk&|^pPNVPA-1@aMG!6z zi7#LF7+0EAd$Ws)O-xMG8V%<+%&d*$d#pL))_UTLhV4BDbJVPfXtyn|XhU!B<^y{1 z-c)g~?HNLK^#j?nXT`96ki;i|#T=Xzw`bsSnZXHa;FeG+kFBuv#whW& zw8hh>-@LHuDk@$6)20maa&niwy);!-!;_N6kA(`lV%gKOfApkOoeQ`xm#r1YW9r+} zx#TcXWZr0*y}Z$`A*FkfiY~yq z%A51nmb&7_c zY?Rd&d!UJze;MLS{eiA7q=|3+!Sfx*>i4)bjnHocbHAodkmq#i?rbx@v*UA@}A`BSWqwqN&0(_jaFHOQA_(v5BJ@NF^2zz z99?LOYEw(}#!OUn$1yP}F)?LI`5jc+o-BK5v!)~`zYj@IH(O934>;HexOxqJSE630 z<6}}?>Yg}^9hWAZ!2-?F%EO=K337++-!>=*yBa0Vm^Mf?oLf&NdS?O zFQ65{5vMH2s+RiH$|^2_Z|}rkP*qh`?k+n!J2UfqR&+*lYwP=vFF856xt!z|@l6~P z2-P!FjR7{@qYI^lItTYfkLI!zg9D-pfh~`8XLQB0Yu{}sXQ=c%t0^VFySuCLvq}1B zf3FU^MVQ>}&oZq8_dG3fvR;zprc*<9-*f5^*j#e*{{DVf0?ee*-)bSJ2i~1%*2Dr6 zf6RU2lzGkg$Cu|{A+4;^O6npbqh9w#@}Jw8%x3;R@q`7ICCHWn&i=u$Hnz|QfOEOtQp_`j2NDBe|L0TAnt%6f)2Ma z&dH2I;QTyoGelpOp?X6@($Y+yKmUR}+S|Uol5yntR8vz%=5>f5W~Y6D{)Xzh9=EvH zTJ@w2B9w{^%lM$L(Nw%AFCwMNq{dhGGzkrU3Jjp66?ShAmX%5rhyFm)iG;S}Yuwgq z>cT=APX;lU_i+B#mCv!Tuvi^+>Xww06nm`cz|6Q=SXvehlXIU+7cvvOpYQ(6rDR~> zvs}E#LccVgNidf5eIO>5FCTz9Nb{f*#=GI0izcfS1JSE%H@bSdy9Jx53lm0~cw|T~ zeSc_fE_W^FV-Dg_RFG?9`qj3e)BKa4BL}-NJDcL3w`2S;b6|G9aAP2*r|(rc%0j!h%o|M-Z;LBEL0a6pQ;N_%!uE!n7H z$$9@r-&phhWi=}wx2!78aiM#5X5aXJrTdo&v%uuk|3-A@x$2!kivWwhH>n3aT}^vD z-|7+_T}onNqSK_;Vj|qI+&fe&QE)e<(#eR^%E4hBL^r)W6Q52mB+C0VutdLVd=(-m zr?53v<2J%8EwXD{I3zBvBI#8XyLdgL!Ee~UVmtaylEBq4YGUSvPXTq6%HzGg#eogJ zF*&NhN@ZuxY8D#9ANf$8=F%pq+xdAg!>piS#f8AL=YY>bChDyYgJmNk_V%%>q;)VB z6{5~Zs{b5+19Xt})J_8SHW!;38$m3asUkiMKG>A|;*u5Q>+5S@F@co+N9AOBN>rM1 zwZCY+bcypX9uv(xouq`f1KquL17E!NM3~V_D+oje7J9hSzhRCNb{=Y^4D!K>wg4~1_>TXvrRDrg0Im&S9b6=q6y%m8kXvY^ zB5L+foJt|DwlCkr>?$Vv)*Z8~4ZCGLutWcl4VIOW(JwZV^dAd;w~>wc7N+^*uRf7J zn&`2yF)B9oUYv(|RuFltuZ;~#ft+q^qI;&^qd+S#wRL?){^qjD&Hv&Vn6o z<0|&J%F0R}o?-cB1X9{8yjo#S4c#}bR7g9@<2>)1EYuFvLS207W12K zfCSyi{GsI$cj?aBGZvo(%V%O)7VqTM(x`E(lPl_t_ zz#SZ8)~%#GRtK!IXZs(li#x~{#B;~qD87hXe^;u?_(bQc<|Fpz76Oic7rn%(oM|}7 zI$mdBXjlQ6^qh>OKdSZJg07mE$Q%vY$jN=qdWIG%TtqeY;>hkn0w>Fz;}&w*vsDs` zb<38syApU!>Op*Y?(EFU47``@+&ORx!NH2Q)Fxtn2dN=C_skpJR|hKWN2*`!e$0Rq z=^zq41=-ozruE)W1kZT?OoqEdOp%$CcBl;j-ya{zb6nwb8>Q~bL0_sGCb+oUxlD@ z0LbyLH?Py*21|QionJ91`F@H6)JZ@1kUu1+qoZ4*;T96YEcX~EBP%v~kB->S2s%s% z;vgCC-nH~@8gp}T#X4&8?OxKTzaDp&*{b_;TW!2h|7e(!cGAlFXgMEYV%TD1{Arxg z$aJKmQR{%5G$WGYmj{0DdQ!g!Ihes~#ne+qA1?)sr__w4)ClGszSDA(v_y5x8u~D5 z3e5epH*a?l#0G#F<(jeW{a?SXbms~hzYy2!K4P(pyFoEt_kSD%7ee4Z-j4 z@s=%j-#CRUWG7zw=~Fkre(Pl`Uy%;$MxQkKX8!cIM$aEd&?#rG?LntG^oze-cb;z4 zO*E5V5jy`cX*WJ?q0F>RMn=}{!t8R#^)-B0kN17~3l$j(%2~TY|qJv$s z)TbEzgbn066KUhC?o&rM?}raYQN0)X4Wd6ieeZJ8Gi__$Q)4B&m)ytwHr%2g2PL$?L5*?1=%%(Ud0+60f zS=iy|{UsXSQ>;3u2P`Z!q_HvZ!0MChXhqnl$U@svL0C{p$?i}`%sC3$Ti32}FfT7# zYd5BS9Uqk9y?EfyU&gunnp+a=x7PJR8&+P+bhUN%C!d@5CdMWt2&CZqxK&}y`?vnq z9GHG-+8@IpVD{u?EU-qy=NA0X4yC# zXJ_XuU3+M6lyCi2qS=^hevl}@);;tPqTJxhqINk;OIPKujJp!zbP0M~+HJq~>!l1w zD_+gSJ~1aX|Gl|aUdkJ*hGHHSxn^-&0|Wd~cf_{B(Q-PxqrF{I*aHy;iV74Se@oa~ zY+a_TS=-mQm-lvz_Qx{d-};mT8%DeJn8J@SXqt!0_G>iHTF;S@g@qVT*Gl@iL&iQe zMnps`ldjhvT^cPmHxK!+Cl}+Hd@w!a=`^|6kfpewpcu)};6)4sU^*>r+E7}WG7s=G zc^_Hin}>#U#&0a+eV}01K7RZdI~{7aJa6}2w_x;M2Vg{8m)4HSq1f$Fk?rYAc>nir zMx+I-tu?%`N89wgU1w7=@=pX~FN zHzO_dN7ff8Tq2E2O*#2yj@4JT42n?IBau~Gh*mxB`)^)~G>db)=x0nZYG!t5Rlvf+ zf_dPx&z`esodj$=hE2m7&#x_4={%X)fiy6ZGUZKh zYzU!cNCTx>9<662T|?-tzYq7{8tf~4kZU*rO0oBS4p$$lsyc>V6CF}il-`WP{j@zm zNVOtxC;k`g#}Xx#kFi~Q7FrW%I?6m++N2l}=6TC!#{;szsU@?*n|mU9tw&>NOYb78 zoF{Xx&(QY4iyhKTj7}Qt20>;A#8-azz}4vYH#qO*Zjq6p#+HsTw@VdC*LB}^ib@2q z{-BU?b-W6)XXClcmoMvFl&tmYa(R;V-LmnHe8idtRvv!9l`i4>;>8R1LT_T=H#qbm zD60@SvXI9FU*a-kYI<6)&?sPczNNfe5Wa_!Zw6@Ix58eY26qV*+3H7Ixcz;1P(tvT z`$wmKlz2Mpr=+A51IKN_vY|I`Ew7FieMjUzuq6mA%%k0n%${oY?M;}#U1ZroomF)I zKU?&DbFymPMmf?R;55yPH#l_KSx9=Qt&(eO4NYoXg*e2Fs_$k^(@LEjy2viUyngzA^;X?h=x=}E#WGIpo{RV=!0)HeM6`#&r~n9kt> zfKQkQnzKr*>Aoa9-Tf#X8(`V$zv~lvt@4UhSD=Jtb;wk;i(3byO2(vGQLn#FB1(I) zd?)E>d+>!^p@L&PqX=oS^#?c0UV&)=r!jOW(QxeqXnCnbkJiDD#uml-V$jTeZJ zlqKK3ebuUQ2Nmk*8ms1Vg%j?| z(&jgPlsa#{b8cjMXC?Whmv_OiJ!Kv!`((#UnO(74(~{$^3-yFwtA$GiGwW&)(({)~K>d7YgI_yi|4D`1E zv%Eghq9S+g+O@!RQDXv<2FZ*DTOwf7bF@6EB&|+l?_!Tqh>%-6NE2_4m1gkFoblU9ejVV(D^ zd$_o`elkeykC%B7U!66r7u)t1He@RUXTx#s+&NH>69K?sMq>^yGzABY6@~`a)ExB~ zSLqj;_<(I>Vyq{kO5td!rv=p9@h3H^awEmY5Qxoasr)4Yx3azueW05`Y;e&At%Di* z2Ty-CD>2VEIOtPFwOU(S>**l~HP>51EEpN2U6}9Qbq9A4#VEaogqaWL>kip_7>qR$ z$gXTmY-Z^d0KP?vfsa|G%@;Ej5grcUZ{_w5b@VyRx{clJ)|Mw=B%LO`zU1bvudj>P z4}Bw<16Bj=8gcTmvjb?N!C0SCNmVuZm_PyOgU(L-jYPiWbD*e0hBHdP=$joH8p=az z7&lmg^$oTp-|mz&WrPf`2?6|UZJl%Md+f1ZOTBz_y(Zxxsbk45{P-{{CRhDkup;2N zr1<&s6{EI5x$;;;rOstdi~Ef>F1 z%rI{BS4UUGVHVh;s+^D|9+RGwe0+Sg5-SZf=XUycEZEf3--m}!RX$T3YYGPOoZB>i zKW1kKu=BUY{0-y)e-{jWaF-W*?dDB))Xa3ZQ0JDOQb&iBAJ)1UYdiE{uTs*NnZP+& zGoFI`yUpaivT~PZSV%?mj}$8|alP^q0iM>Of7?%wTTWGDq`L7Fns2bSwj$M% zbofSlAlDBL4lbd32y5#u6f!dE8KQ2--B9Y!T&A`Ah(YL3H?lJgkuFIm?A&R_NJS-p zsk2LRpe9z{cRNvdGT2(46Om_hmsQuw@)N}R`$xN9am1VSI^;w_E}Xn@EtWwuky04q zBS!T(Y&48ULt7A9mQ)d^>u54n0*wUq`@QW!_^mzp7*+=3Dkr$-rje>+VMT>53rd+@ zQrvxatx&H3v%e3#^&o=z;z+^Qn>TNEC!RDFPfG#*ajM>flZoj^>qM=YB=!seH=J*{ z2WTbbvU@|eVpXe(mfFRHfr3IYtrkC zf#4Q?gd=kM8{}$V8$~@m+?Lq_V9jB`wgd;PXg3q3P+SzI$9i$44QP6Se2mho^C1FR6oIr&DO)(vyD!^gxaoL~prE z-s^9WoBme|ASTud>gts%S2z=RaefP|$Sg%bAA%l^ z%EaVp?1I7@d)3u_E`QQuK?xP;Z;j1u`t7d(7zla--QC@(ske76@S&l-cv z=`a1Vh8?Jy`Q({TvobO=aj(46xB8-#;R4C!$hzokL%~e7TLdLXZB@M==BKU^?S%`hU01h$E5O;-i!v z;bQYDpRRZS|NfTt{*ahx3zAW#Dy>%)sIv|LU@EOV`}e;$Dj$9%^6zt!T6wTje_qW> zwqF#YYjcRqf8UYip_yzI9xx$TLh2>D!i(hzST{b%)yx^i-FtE#;0?d>hjjc+v+ zd{o&4e#;(=>cxrc7F75lq$quw8s>oFAn*wDH@3AWf7Fvv%u?t1^Vk6{31-XvL>Kau zio%m1<1Wv%6QdGkfu{|vkdq3_;qp~!5#O?5x-D|)n+N?1Y;&D?ict&{xA^Oi>6|f= z$%v$?Ss$h){%XDd62u&RXuBS1RHWXaP^1_37+Y-SWK@Os?8E|-0v(!Uv7Uk$8&o8{ zTytOF4p#caaiBUH+3`$Dywar6)nkofFzyWe7PdzeN+fl&-W;#lT$q!E<6K&22xPI^ zK?*@|;;s$GURUFg;lb=ez$!Yf=2D=xdc=vnNgsv|%57R6h(QDLI->#S7A~CdqvADY z@ZWuZcyfA8+8_e-6 zN^U@|F){0M=-+zqX}g{ju0afZ58%sU=mG{qew$P8YIJ8@s(!JV-+l&F`sO5iL%wd_ zKrYspd0w;95nxiF2H|3+V3ZOIL=R!L8=Xd)W#t-EBzDI@b)k@go8@KvAGy#62qh(@ zA9ibVqGEmT)&p_To}?q~_|AM?4$gP{R?1L;z>R%#>%0No!5noCI|$598y8`e>fc{gQY<#9%|#Q6Jo5adUDP)wcO0k z#U8P~l9G~DwZ)+I+Qs0s^#<<$Uv}3oARthL(i2HICt2$ofNgS@mA!+wLQGn=bap<0 zjCY&JSw4F(%xaj1W> zlMwYk{@N}gE-tR2f%qAIe|_}Gsw?&skO)A2RP&kJu2EgPCb(36=uHWlh!?6~@ZDcI zZmr;?6LS2N3cy;SL8i=B!hI0|s_3!m2Wo1Lj*bV`q;&NLs}8#p|DN-#?sFTP(IVq2 z6JfYdW>^9bvBrOi0;zTq*jc3#f8w!!I& zKL4a(xW+u&bxE1o6=U?tH8DQkb8UZoqvxWqcZuc6AIEDCmSBZ|63IV`p-U?dmWu>w zt-QP()~c~FGhCpwpujv(B3s9|&^TV>kp&2y5D8zrcW*sugReW>W&I#uXuZ~B6;#1m zWjSG?CA+0J3KXII{9wJ|No6VU@qJKR%~4V&VS771%N)64rq*~&%A3dg`)6jH$KUIA zvXtH%n{9djU)x$xkEpL<#6Ru(FlWdC^6~cHrYBCj1K)z9Mv6^xbkt9*{Zk^%F41rw z;bWqi2jCjpiHT#y4aaxca?2eCkL#3>At4QE)7`Yvy@{fJdn2309`=Z1pW`OQD22cS zdHAbs#+~tKm79CZ-FV-hRk8~Hj#hR;FCHSReBCF@AkcVWQil4&6+5rvKdHo9*W1s} z&)1jQdc2$_fAU+$haYdG=tVyt^6V-<{jaTzDK@RM7k^8n`QJr%KaPLnDR8v4@BTYY z{C_9>ldMq7I+1;Vpw99*PHqLPb&rECT3bs?Qc_a9+dX|mgENSMJ;~m-;f5~M; z3w(8Tm0rw$aVV+GDEv1u1oG?$vj}`omSPlfrd(c*HImV|EJ!8NX`fLdZwf2)MglVW^%YEfzm1G2QsDdfo=h2_%IX*s<168AZO|| zRN_FoLDC8?PWce=k(HiJ3LvvKrtfSv;IKII+Z$TBGqHAqaoV6ix-R0pwb$!cOrES$ z`~*dg4U(MMf?H1q0SU%WX_kMZ!H%!D(1^yQ-|SORi~ z(FKs>&K{=5!jOZ*ju@)k-c(V)UhGPVPZ<>SN@*woR-#mT{21@wJxWG_6UOYk@N~Tl zT5K6qYzQDM*_|=UcFnGjvbMKpX5PIJni0r%@Z-&gAo8_1Zf&F06R^CLN=E^Hezfh> zx7dJ=o%Rq=+u_Inf3=E6NC%pKLHusNkdV~pzn6qAY;Mw1Q^%{E1>L=3Q@5_pkPtYL zl+teK(<{&yZ7gQEcI&uh>;kxJx4@&-B8KVMPCx86g+Ebt+f)ivK9^T;l_588-3n=! zZqtb6M7{XlR+swbO-OS~o7eU*c4Sk0UDz@8L7?M|qJS{}wP-gKLf_L$J@17VGp!xNxmtDe3ov`S6s;_7~zIGn&->WZ1L@$hB7Mnxy? zw04)p=MJ-8d^`l}t0GrSdTz-)9Z3&7GZEa`W>GWkb2S1q_nDso4X4v&fW-S1wRS7fm*3q>80wCq+kF z*xOH})nTX7tqeJI?qy_0yn6L2Q8Pu;cW=VM?6=VTjT`W1>}S@(IAZ2VLRFp%szn$!x<6Tu=SI`JJc&BkD>Mh5 zCGKg9#o^5{7~-^#?Yigo&$d4@YcslIUS}=$@Q4TiivZWO?au;{`yyLc31S?$YbcL- z5-bvwam*kGC&SEI1SgJOUSdXXy*xSP|F40PckbE$*l7f4Kd|3dQ3;!q4Gw9?BdO`p zlF83fK&((RuCmEp_tDGw&#<6XtS(0@O}jUa`$_yt1iGCCaKxALt_~=#Cqg zhr|gupomVFa&z7tuuAR(*FbXprG2}rFCf)B^e?}=U1T)O!CXq5wNUHH?$T$45@ms? zMegp#T$VrSpS76j^FoLzGW_@N8&n=*^Q2YH%bc5lL??mU-0<_;qM`^jcU4s#o$`U@ z2qPi30D<1DC$eg=i%VTS$-7N9=7EHT@T5u4jY8cr1;D{4l?4Krp&l7InB$)UyLnM` zLXq(b?YvhYxW&=euQ^0TryKpShoRaFjmMwe^Rxif2})|~IOSmgAn;PO|C;$uvX2#7 zgxS*3aldIu=?#(|#EZ%WHuWGW|C{)Knnzks0X1g5#>;lJ(oma1QnG=oq1Js%r^tR- z9*Xw?2y1hu4q(XC+nfghb)(;ywkIbh7Da-7hL03Gts4pi;-8J9V{&@|l~fa?f{_kP zL4x;(hRT6Zl!D;^GhPQP(9gvO{i!R>1biLq{^ZF|+Ga`rb9N*r8vj+Y{2#C3ziBTH zj-mWJv6I{>n1tjzXA>d9e&Wh$A$D{DWpFe2)lHUCeuBr@F)(sD-%U0pjlH zwb3dB*&DJOrQ{@dxON_cGIn~ca>_aoPxH)KAm*KS__!tUp> zrPI{Hf(4Z0^Q}uHnkK~6+{Bk8*(Q}L39NNqBD=eM?Ai)>7tAWwGOMZ%KL7SP_fdzs zz=>j0DW}OwJ56}tIKP$u!3NMRg)HkB8BGjGCu|;!Q_(P*HyBksxg+gbq>Tas+C#Fx zL)x7)BmoIUhgn5+PS`SDA+}FV`ER8fCU0fIZ-Xn~?z_xA$973LJ(%9`$cX0*%uR5! zEi&aQCG9TiV@p!g`cU@z?4vgflK$ny@2}oA%Xx`ZU07(Hn4AoI{9I=Ue{>0PoE8=l z!Kq#AzCF`$+&FDK=+IZUJp*PbfXo@hH)qq@%eNmqJ=PEa8a_b*0XiVJ0;C-CXRW+= zZu1iGslgPCaqOnG=5VP7-vh2z8*&oSP%#gV`IRRX5wwK;FrnC z30AH9w|ICqCo87Qy6TjdI+I($b4e76hLI6=8}QE=lBm&4)4J}2t|{#1OalN|e%Ba^ zQDo#y4Sl9>s!vJZIPM{w} z`gzSx`RqkW_?@u_S6ELUFGCt@ys*8|j1dNPfrJ&_)_UyL_W-H7dNB{Y)p8*yC@GVw zp3bcaIgr7zGs(J~$Jljo)Tobmy%+V|0_n^8^OGfa_A-wRClu1yp;^kxzV%R|3i)r6 z&-+Gw`cX<+ou{~%UZE+DoN^bvcAJ`-`o-L9R}MZtmtbr!(>8_kXr)@jEuIB91L$J* zzae9akBkH>vr+;oLVnPAi8*pj!g<;vdx7;J8*^cI zh@ycq+yjlKlN*o3#KpaK(I6Y#Gp%)}qKI-B{VVRcKO;blI4Zj3>>L?+qt0_TU$0_q zRlr85!6aYbugp*zgx@QhlcA5(CLlw3rp6i?Fj?FIdtM%Pds^f1+H?D%@=H!gz8)}OR^7AL3G?gnv+>(Nne`ML5o}TiL zl1Mw_1|;#1-W+>+dLkSkmX-scpO+!&7uO^k9AI2&Yhh=X)L;dgSAtd!oCRRaBHPi~ zx^iwi;w+MGW4K5W#WM{EjvI7z1$L{8D_^gJ=9^q|N5|@TW=%*lTeCUb1d1?(Q#^r}aK<2MhGlyVce^K79zxRofW zQ^3N+^h8dI0h0%sJX!!2GBHy|Q!X#xK)b?8H1Db^$E#i;AHJQ_)|T7WR-moDG_LUA z;(}K8AJ+I~u^;)KNS)`lXOXteJ}SfBUQ2rrd|n6Ws*9CFY>a>EopyOae{o%}$htS( z?RY;aHugEC-EgV=)~3VIi#)TUiC410_SHI7m6gtYp8{uK9!s6WDOJwg$4;+6-$z+Z z$KAcg^@4-s;^L$|5f^=!8H1!4*nJg&6dQA| z-&sJ=tO_#-x>b<`w|XEwOct`YJsI{W8vJT;*6M-~o1!9sh)^O)c7TFHYej|jeO>Zua0`vzq>YO(LSO1q}2j7NFMC>SQ7vzjP%dJJZ7_X9W*`R3LM z1<`tt2Mi1hBA-ZVROIV|1}D(2q^70DLWM_!Ssb0px`)oZoYyPXhjj;!fL0`7SFr(p z(rIF6k{=tex6CWT^XrahZ|u}AK@R92d;m2gu5o7>?k61bxUSMkBTXt`Z9fTkA2Xb*CVd0r?F3-SZ5%65l&l~*uqzP1dipxIG^Or7#F-+HS8y=%h z&XUQ>$`X2UQ1lT>JD2bQcAwBKNb=NmS??sx(#e|bM_UrK;0^8CZb{vRtt2o!^H%JmWxM|t~qiSCLwTI zD6lOv2-dLDzom+bZGU@GL{QLiku9UKdQSjIw3Wax1kd4SRTYjr@t7beqCdKwtJcwFR)pkG`uW1L^};f%y@!!bY-4EX4>wQB z-zo9aZf#LUj>k-$Ck4^_OH$tWK4Z@6`kwchSz1ZSJL2LWfA=pQntcnrMKVjIRQW+d z@<9=wdm27%NvY(1oeKE*fBYaRmxz*k-;u(3~W=O8pECser@u1&hMI&XRe2#m%7w$im0v7*KRI6$v8P` zn+JL=dp?fsw7RXS2J!9DaZnNJ>FMd_>x@|5A9tFlUs|hhh+z%P+I&b-FEuc<#_g3w z5!A|>V`IzFl|t8G+EM&S$&LHm@A7@6dQ<@V4AATF^t$D5R$bH$$gMh%r@?UWfTa%L z_N}_&UA)|=C}F*6_xRdD}dMvZOQS5ZxDA zdzQN5m)CiKjGp*_zy{Ql@zm6yUE01&86b$JvI?!4mh#i%F<;(T+^?tWUYa@cQ1*Yu z9F8AhJ}&hDNtTf+n(>te16%I)AIC;`kfss!3j$CO85=0vMj4L2JEnxgu}?Dt2K0Sz=WPiD3) z*SrRS-d(_N83E@`q@kh@XvaQ2=nqWdYXC#WYyWBig2GgAo=(^ebn&SiPb9I6ouqH) zf14=ahB#l3$x+tJbC^gB++2J5iH*5*%X5)9R=573C?8KTYf~G1%W`Gw>Jv&${YQLP zH%z#$*Nqfc=;@DEtlQ8CTV`5XJk@?5^7*BE!gu$R6YnXPziBn2eO_X5EsWT7zb#SZ zo&@i^QUB#BV*@?2o_D94{!nMa9*c~Pk5{;&S7@+{zjVhMk>oZb-5Xlo{5ElMaZx}+ zn)~gCloav#mV`+0gG9o?G~3(0FD?stNGP%S8ygwPg9nj~8C+5kZJ8>^B^2(8$oL3Qz0Bb8|tjgXQjBF#YQW9C6+FshOGC zumf3$H*{{T{YUBRkNubQa$DM@_6em%N-kboQ{{om{#oVCqN4IXELP@R9n(dRd@=F= z87O~ZyWS1S(a7kxshZ|g2b_6v^~T3^@lBonff~2m_8%03IdQSE`%`vp?^PzfwolgD zN6~1s4gT=ilP6EwuFx}Y0?F#3`v?vY=eEsOIcB_(x3^`1(h&S!e8 z>)lA;7vLW$G}M;Q18;MJ4Iar>Sri0=$8GeUi9&uOtz2%;^!VIHHm=I#MoPn&Sy^+m z+pZvqJF+*L)KjDa0xp@9xXPcdGcjgw&jF&`)ifxe_x7qiYb!mHmGiBpj$UK^r$v-= z%{!*{|4@ccos4Y)2Z0XS0oKUxnwlCr^+jpn@X)+SP?CURqybY6@2;v#?`@VLhjQvlhD(Es)rkdFki@v_@Y1jQukuvf zh+ZCQF5{5t|8U2XwM7DOC1vG}zD7dpzN&`CB_G-2&r* zfl>RYX13_eKo}TNzbf;{;*osU+Aw@$G_U;`^TkV-pxYeGyc-=IJ$bg>9UT{R^DSRR z|0O&;@v6vR^GkKh5zCF-o`IOskMm(&ov`EwP%G3yMu^ z-(KzP>^wcUM`y9fY>jO&J*5=GrZ3Uxh!#PDAzvZ8fklezz@cNV{;WasE{b!q!k;ZRRr=}2X z>b5h5t99D})w@SM?IJVB7RTXx2q^+@kyBA`gy=$S zyNip&fv54&5oSchB$fPfO>M9eR(EIny(Nv#{rX;_F%V* zMxp+};kF`(A%s_dfxnh8R_rvX$Or|)3f7>*hdn_Cc-W5?S5GvW)={*Di>`$<0Vz{P z;PN<-GmiFj9-9`lgR0aGr5+H0b^7Kl?pw6`D51=~rhrsx5y#c;nEET$iO zkAcJ<&3!gUr{vg$wpHb6YZ^&e_wy#Xdn(I}PSAOOe&&9>lcf>!@tOvM>xPxSDIkMB z2aS^pFBh*0Sf32PD-!Qd_rp8wsn*JmIhy|fjdM1y&8d?4az(5&7`v+{q~qWz$Fgl# zHid_|D5UU@&s(g7$_fv^ykXswj5(Nu1YbSIZX-cNMtVSee2z-3x@6898XD5k>fN}0 z9r&t_1b3y3v$Hc8#9y*Ofc)`Y&(0VDkvWe_571NaJ)XwS)H#l$#XQzFDpfKD)d@#l z&k$mJ;a~=~ZmW?^gck;4Th+9y8~Ae);GzsKXM6iKH4$W~>yid$ZJ5p#@6yH~a7aP3 z19K>DJPD#ZHC!N>r2?6!PD~q)bGBa`5Vgthn8pyLW@lwV1io@p@WuB=oc~G77_#m? z)HGliBZ5c04&zbM&=6nweil1zlR+VM_+py!iu6Wb0gN!(k((a40EZP=zDn`j{0_(- z-}zTP_Kgy_+;+P_yYYGaNBn^Kj3*T%mHN7-*QIHfonAl2jgegaDxS2p;U3@ZH&N#p zJq%j1-$Y6{??eYPxXylWU){t`5f{1*y=MHITJ%i>z#ui#+y3pSn)MO76k+H6Lv2LF z(cxg6bWy%(fi)7oDR%g^iP`Y*Nn3`0GZR^FlK6HEqAflyZnFW!5X>OB66wc~h~69m z8TeVGlq0OMF8FGo5nz24Gw^Hckdej3p`Lt!7w`s(F(~rtPa`T*&tZ=n2pk6wyU*JA zTo#fsCMIPxU%rf~jeKjY(P@~Io0~geHo8p_y7T0TMgPHcUAkK;qqi& zl=RU?_jp-XXQz=c#B*vktJZz(D(za*9B<%Zf38YAPeF<<>eHjM=Qhgp7vq;W^=4cU zgcw#0B^9-4NQUI7zAA9c8;lcRL(0LdGKKW9pfvoGTkaK9YM^V8kq{@cao7CO4Js#B zz^Gne;xeuD*Cs!lZHkh>w`WB!s4>iOnK#zYKfSY;0&=F1!w8r z>({RVSU7+yvS#DKA5$9G3PY9ce59>LMDdYvOd z9-ghmOtv(M{VyJVtSqeQUR$E5G{0V>(^L#Bsdd~Yt>niKPOrLI{2s>yk^$q};?m&j z0cl*7z9F6By`JV(T2^+aP3@LQBaygOBr&_1n|AD{ADxyKfK98?cNWDgX0Cg#PdlJG zb&DXcbPF5MnU_@Zm%g25YA_T}1^6%j`y`)!$-GbMP>mBFjTfAoWzE5K%{m0*-g07B;_&s_w zH#|x}LFx{q*7q|r>-`t$Af`T>%uzz?Fv{B0f7Nj6a+-2i4aA=f`If+ z6r?xlHHi)=C?X;PN>!0AMCmOAMY@3WPUsL?sDVI2?v9KzbMCs|`ObImch5Olvt|h- z`L*}m<$0dHxA`cq|9AfaIZ^FxkL8OwED+!OgLhTQMDuLEDC<=`XTC0zQ z34TaSs;sCyar|3H^>;m-MSU?TM4GtzrFf=U#{Wg%+l?5nMI<++a(5MXEDH~hycGzM z8@ckBd8bQlhu@4Xv536WsDRjz!+8rD!zdyk)Q_ zB^NdpC~24LOYe1Ml~Adr#iZ^Wrv#R-sRCU19ueHZgF}l83rNmo@?gNRPREcw`Ca?7 zIUz7uq#VFb<17h~%idaBi*8ZlZqBeBXZZ?Cch$Lh#7(GcAfO#uH*C<{nR)NErDhM4 zduV4-l|1WvAWcY*n@|%JrcqdLLw63uE&BYIYXRc6-9V&EV9+DAgDMIa6r@1IA6_1v zE28aW2HmV6`8__JT5TuD1buJpOX1<&Nf*GUqUl9rlRdKqQ$uwkjg5_t%?0xaQT#_W zQh%HA;j*NrcS_Cy95ti79|Z+fo(8Er>5=DoL(b01%36_I^(-s(QAF=SVjXm<$o8-m zE&M1d=I1RNM1+KF3FJtgu6+BxtWTfrfBEB4eU!v*-U~LZXPe`qq7KSiH5aIlw)B>i zu*lzZURV?@J!ie}N;_RQGBO-nQ)8_)YU>F(m7$SQFYMfEYk6PlC|j(qB0aEy3!|?a z;BF7CU)V$pdQ0?Mmh1Zq?z+R;k?iyCcz{}S#2a_$2uFJmTgCj(f=fAZBC*G@Bf~T& zGghW_dDe%*$QtUIc|jg`v6&mpNS;bMsN>dfJONKgc_pV5&KozIzqO6$aAZEdwB z%kMGbJ_GMCSgGJAPl!#pp04Ehl2DRgYWI&r0aQg&Nvfw^_aEE$wqJyd3`B^iMh(b5 zu%31XyLh2Hmsahar$IqxW@hPnJ|E}?2UI_x%p@2Bm$C>|v?dKgAsTq@v{}I|8}eej zt-Yp_y;>6U=~yTilAb1x2guTV6^B1|xu|h^%WIepDKWi_jOW>4t5kr6=g+dRh}qcf z1}gN?4cpj=$jHc7uT0SO__~|QYOqgE26MvhF?K1(q{m%&X?a|LUTWE%sNKyQ8uRL~ z{Har?KAMujPejvE^>dLXsKwZ(5LwEtzK<8HHfVw9V$u=)g%k9WlP9Uv^0lmm1(vV1 z;fT@k@c@>ChcNnP@aT%d?dGm3_f9n8Cz~XsnjD`72Mdb|8K!^E`FO^kMc29mI(T%k z(S|=(&sVEELg%qjMY@!btZdp!XbFwnGbJGcnyJCBk64oh;(Zq!8KF?t)8X28%EzDS zXG4S~ZadkYVOTL8&L87e#jrRT(bOGVB-&+2s2`tEJ({YQO5Z0I6s!(mOxC;DhTRFd zYP;@c;QpofNO6kkpqHJNi3wU!8PeBXyy6*YRX4QBv>fYfi^f9YTDpvf9gnqsu8o_Y zx3=u=3?+*(gD{6@e|$yC2T}j{gGJu?rgK|%M@pzy^SqzhMf6-u;i8G}Xs+RGLB4fZ zW$3YquP%)?{nNUJdXpAy&>ZH*F5Y~NyV8YTQ#uRdh@9YK8J|)nm~Uw%C;^z~(qUti zW978#ooa>wt1xI?KtlcO7_;ogYtc#-&(-u6~mldt}MolclRoH5DD zxpdAu@rj^qZ^~U*Xd|ZyN~#drh!#;NpJsJf*5>06Q_7dvchApv?Vr>YgBmwwIZhe5 z=Kh=;!{^nwI5;`a`!w`M)JLEARz=UT(GIH&=EFcvZqr(Q>-u%8-cvIEa}e%@76bDu zfH`m8cXNXpK+fKe+K#b%&)t_FjlVMpwm8%3w2%-6{jE;l%_iTWZAj9wrusSpJ*V!7 zTki*r0u80C7xGSkN!qD;cetU%4bK)lhR#h7i;d;7{tgAWR?`GLeQmbBwl=L~{AAW( z($Ebn0K&8#OLy~X&GM*gXk=Sdb!@q5J#!S&kwV*#p7p_4epX; z*Gnnn3D~UzaXpSVR|#p0i;Goi)HLV(vHB>Bwp5(h-e(`gLye`PZN^m=$LfXf{U;YX za~|*KBu_M%`K?rb`=({GVQ}O74tz2AwNa08V1hFAvnw@Q8INzW#&OG?gp&3CfLIy# z6#axsgBhIntmin6xs*Ow6>v=I{=^G2N8A+sIj6UUl6QAML>De0conL%#)qD*>!fu; z52Q~rjVNZu;sqOcjT=2TxRdA8d^kWbUj6#XvTGyf6A(vRlC;wy z_~c1a^S=)?pqVkMdl)FZmH0f?_uv7^ze#@C}4pvJp7II0|81LY{Dkaqj-mpy)!(Pu|>LsK1t_b);B7zE=kyDd{4@XiHQLd zok_AmqtT;t78&q(4j+alb4iJc%jtgbga*`}o)$%pvkPtcl)LU+n=>2Rt>wYmWIDl4 z_neFCNh{uyR(t7G=A#?`|g6>$UGpti_-7xy`8KY zo696|3OITwf$(QxV`Hmvgl-Oi`bexUWrG)m@-T>Ka^<70Qx9#dD2Ovw*3&M`wz9nH z4bGGS1Oq_|l%GN+|4A6P$)h(p9~R@0Kk2_F#s43Fb)vLr@0JIX^P{h<+l+t0{M5YH zkl;;bg8hfr1EeQ#jf~f2*U&$UsT9~Kn@Z9DieEj!f=7DGaYny1t4O)iy)9icy-1mH zeu3`f|D^MaT%Clc^S*Do%SDb(&3?3(ZFGF}#K{xl_RSwW;`@U+#JdN;emd8w%KFS^ z)rmyEyUsYlypwQ(m$fY#qi9PlYRSiw_icZz|tZuDdCifBeLWIN#bDyz439^^vfUMWmiG&cB#f5G_dZGTL zHv1Xqu~CW+BfF;1a#J^zRl2H~d44`VbH?LKTqpM}?%g9t85t(wqoZEE>ilq{MZ&(@ zo!BhRE5_@8`}S=qry>7-9HU5U?dMeL9v&WMk{x+*MA*=OSr19C2#S0a9xm?Cn)2S#-EVs6{SZ7N z1=XpxbX}*@(kBXk$KTHgl`M$Z{~XR_OIZ?Q6WHm=o67g(0s3ptDgPQ!s6aL)xb17z z{2^z~oZ82GX}MLCRkp6~>MmYMNrBU;?uurteG7|w<#+Ag{e)A$W^Ij)^~htEgJEcE z=p?(3_b^=)b>q6;lqhW>SE+NrKtk%L4UB#Yk$>9D?2`h#Vq&QpBF%ugy3h2e#mmY1 zzjzgMSYGPRkGDl`Q@W)>KoT!lv-a&-fMYy*#JMgPm($uxnC5x&KBc)d{_FeqEbV++O*xs0!;@P4p@a^fndM4ZvqR6&T4uaSd;iB8|e zP{irf)2Y^{=jZWj`=1CFwPk7w$nm0~zy!f;q9Zr`Y&;qVGrGnTwI(5 zP_owjy@flud~Y)rLMn*n%_@CgM@4mix?2utIgZvK^X3ivwPQ{SqfTiP5iv12#`zBM z=ml#FCFm5<{!LUsM6cjZSs9JrL)f6is_DHVEG+;ASg+D7=&xE|IrJ65Viw4SB$NPH zN|#Gz2u}x?hJu3q)J&yOeF+O;?U38*0*q6|JZ9YgW-O8bH(7OUGQ9)snejXR_tU4v zWQqsXuko}2_I*7-cBNNH`n1=QPlAiHb4yEecaDAb`mE1K8CBN4wdJ1kCr`RBO*B1H zd<5V%Vxc<0rX!72zSF6S3yB&H7l`v5dU2J>zAeo>@g=$#L&vSC-n!|#Sg-C?=*2F6 z&ZEmbvLCl@cmH9yWmk2smYDqLMaC0cEjO)|o%`1V`^J}}Lg~f+_PV%>xoO3W?ef5C zoc4diN&m+j{)wcLl9D1@#{Gj}i0wbi8~(?FflZ&8_9ap?N!w%hmQNKE{i!N$k#MFx z#~_;MIfZ!o+k@c2NXV4l8vMHtkiCiOV@X-3!sf&!sKO(CdoNvAEfy2w_7exwVOEYe0G=FtQL7luW-=<^!fmQ8O7$ z{}!fJ|9XBHTolKlL)nz6t(`GX>*|m(%PkYz(-&{n3UY8Jo;=q6p3J15+NSrj7)YTo zLl|!j6m(sLFkcmPchUzQCuf+cO%Hba&5)U5-I&?lY|Ge-ZkhPON~{2;CBrt-=FGkU zY=ou;IUdW_V5E6iT171o*x$pE^^HQMsM;Y~bgNXB`HYS5PkH|Pj`j$G);s8KxUrMl zcfGGkNatg9ZCZ9?zfZhx^AlhYw2|E-7d<8q%j;#`bK14Qc_&?)`>QNL*%&q;=KNIeQAH}OcpWLxV_NbtG%pwkX+r6GJBJ8tA6LOzsg_$2p28BzuqeHfadc0ubrM}bF+VOm@_-A z;KFH%ysN<@LB$rx3l0v}UY=uBthlLUfeP=}$Kg0zKH3}BIO+hi@$V=cL`5A@ksx5* zptb_rAL<7ll$4~38_x7G$g^uobQwn=&RuTx{@;=!z|oUb;!O+;IK15T6`(!to;`cU z8e;QJN1rYmr+J1q@JOK6p2-!@m^i@_Xt5oSi!||7zx9kDxiing#TkvBQGtu{u6)WO zU;nLOxaZy@mZ!B)iS>%wE1ZA!Uz;KL&x>?+ak;t+G)b(&U~YN+!3qe9Xp7we@u8Pj zmqBo7h6H#J-!zq|3x(uKA^s}SXP)ydjdI0nY2dv|NLxE=M_*AV=`U)W#B?1AXN69q z&DDw9G$Y40NRv@Rx%6v4#TEW6rs>5G>W|9`%Th%vo>bS@@8t#6zP3~9)2+ar7TzOf z<#LQSB3b0C0S>X+Q~{ty8l^2`^$iT%S7cWLtd;3Un459UW~m-nPZ?-2zJ- z&8cF_MK{Cew5%LChiSP;dul9NH2KyP*mvns{`dctofxHl+^j10>{2V2_e^^hDl$`M=_0Z9Jf3Q!QPCFb-bKUb z_p1kwZax(|K|>|+S39ys>q0J#G4w0g+2)^jVvEFQ zNVo4KT$&$g0UWz%W{i_R+4!Q{bW3{2!vHREm;(TX?YfTko9~WpkN8YaN&y*7L4mWf zw|94Of(e)JQYfpilhfyX&`pHIdf2z5_BaW|W!X^{uKV0oZ(5ez$RE%2$(j6-+u=ZS zC>kXLWW)0HInJYac$OZtq@U%gpDn?qhFC9o+T=cw+k;}3E74)^S?L`eycSiC9Y>2^U z(+V@@K9BJXNwwFjqZuB(T-NxZG~0Ne!3rUf!U>zgu@TjTWDBM6u5>^KIE#us4 zdmKy7xmG)M7-5F9%dgo@GH-Ts==^HOPN{FYcFetI`#5MV!nQHVW+{inRmHnbf-{Pb zk4F}&L77Ibt}!^?WYkK(Z6n&pSDZNYlL-p;B!;^*NZAiOKtY1({bTDkH7WAzpHM3^-mV0ZlPAX>JY0v=8k$z5KZQpHJ>E{&)I^%NqC;kTM%fPU^O=038Nehx z@^)L4q^-p21V6L%{U5j$*#dSswV#Xb_bA50+BKM?sTwucbq$`(%DbIhQkJv5%5<;g zS=vgp1j<`6fV;;7E0w8P?5?WnuxqdR)P&yZaWeIB=LIAsNgK+kR{;ww@~Wz;yLkC!Glnz_41jDDkj%G_ZWlJZ zz$1Cr6!aLF7-`!8?mj$qGX3+gz%lpgE68V@m|6W~y2XBApu#~G^-?e%NJCvQvylt@ z{QS-=M6Z#7VIk-DSy@2#HJdB9G)K`g)i&*4XB1<#yiBZXhJvGJ4Iu+>Qm;!qZpEAK z$DCd&b}-^&O-+W?lj*uA({aR>;*Qnbhq8gifRbJdrOM~B-bP+CZhMc2#Nh9I5kYy= zK5L}M&3DMcP4ehnh=))?$Dy&IVZ-bvjtf32KzM*+tsE)bofPRHW)nId320E z1Quzena)xa(8F*HA%l}>infxsHw5+ayNZ`)#nE3Q<1u2HcaY^0RQai63%eVs`<-2b|>@HB~#^BDOm>42`Q`V7&nUw%cu8cbv2^-_PYt zja})@cO_=QM$Aypu6%hy8R8rFh7&*YTP7=kdeZ#%CO5gLbHIuJnc zt}+H(XtF)G4kQ_wg|m-A!N-G|D`ok5NKJOJHXR%6Ih!iDS#R+mqxr}S0%T6O2`ZRd zgpBXEo=B7VmNad4cA{~yD>u2?uXv{0(r?AYWt0_SQHHgEmh~JKl2Yr>G{KBmjc5da z|K1RL5iJX4j_%V3th+u}kwf{e9V;M$Hq$it>PG_F2pD;#x?o;X|uPM)6lXUmhh;(s@*{V&AU8L9c5gu_RUGw%@(9vNX2GKiAI|72|$@yHx^Hqg-_PtP3)pwl`b+_C8@RZgCj;r8rQhs|T8uis@Li3YIt ze1kNmN)BV&_HODqYQw!|R9D2LMa~V6rTQn}$0o+j;%h#!dej|^Z zP%Iww^{Zu9LR_vzVRF$3J2*;aS!hCF>%6`29bvn9u+s0{9$M#=TB;_H^85+sFo1)J2c! zAE;snUVkv!N(OP98>V;f4sM0ks{MtQA5KU}kpIBM^nut4lrV5!z{zk)SJH{gKIb4s_KDb*;kULHXkH*nfdRE?U+wqvi*#-J;#Y)#Nv7j)XQ1Es$V;HgzN0WxdCOqa*Xv!x|c5*h)85YVyn z5epFX#c#4LKzZGrt44b2(2~?xK+E*0@?DyTi)-h2cZo_p7wxq;7Qm9lc_GEL2qcJZ z#9nJyv`VFNlN0<*dwD@2wI1z=XDCOoaWdLYKS2W^yHew*Ng!_bA>mdYuV*Acbix3i zlWS^j?9a{y0fECda-WQdh(kt$wKbcs3g4b zG7gfDHHXO+(8c$skE;SwvbI-eGhnkzm9Rx0vL960#VaA`-r>Cc__B!%0aa=g-KR0-dCj?FR1KY7}=PZ(9 z1H;qgcaff<@$aFdjArIq$Fj^!!PQ-N2j5qOR^oZwJV74Q=`y|1_qO*KBQ$f>|c3I=?VclV%*QJUoLhf_NJN# zjq#wUH`LYVAv_nix{Y6G1AT|*kpGhv#8R~f0YiANpUq=gzc$j+C|WpI;_Wu|oJ(?p zGRS{ZS($OAnZ_599`)gDNOEJ4$oe>c2(#_$zB}DZ>>wUgyK$p%ISB(@gPmX6j5l2eOYn$fbPprUoV0ZW`gHPzAM?jqgP#xhfz@GT-*!78 zbd2;=o(w5&e)#)o0y^^5YtO~t4uKOVbv&RG4ZJR2cuBB3S?J{j+gC_L#2ro$iLQCn z<2Gfm3CeHTqAvJvDUtp^`@H^N(o*~#RsJQ_V8|;vKfQ5b)A88NVhnd59(nrDm~r>K zV#SFz)To*1ob(gc_*h6 zXzuO!_V+i)$cyM(Py>Se$w|TOzz2>0*^}qLRG#s_h+`Rw4Rk)38h}be<3qUet>#}c z>IXg?m;CNffLd_?zx5-zqaix^@gToR(ZI{P^`&kKQGmPa()rC*)bm{UG+rMR*^eTP zpl*w#qzZs@SIS*702s+C@pATSOXH72CLq)-cECvs3+H-IG3Yqp>dxoArk;fIydV5K zG4%2r(6^sglN|+H$p{%onn6-0yH4H9EVs}(u_7lmRkD}6klsHj%=+`w1C5=&Bs{yc z^F0_)##2f|O7p~$G1g7K{8Y{-q9hj&)*#%#2>s>XJ)&G!5ai z!hWPJH_wU6e#gS9(n_K1w5 z#44YgUMb@U#!24R`lS3Z<<#%zuX-V-!@RC396S8x35A~N`@pi@WL@8<)YHilJ31rd z{Jj9`j+ZCbE!v{oDlr?03+nFfS(7QNl`h%+|n{yHNArq zntUwUMBgDzqBr($+ljugdQ09kZhy=(g!sMHlm|(|IA0i#Yuk!b!?PYp2Ua%D6!-I9 z!v6;1KVDx^Q&v@d{_NSl-MgEtE^IHlU;Y!bKXJ39H&OGtT=`^MMp(M3BbGI^HD~F{ zRYalliBvT1qC;k4cnA>v#eAl@1{q9NdW6w%jeG|IQhzq78XDG{4k!7FHA$5Z+~t4Y z;h2-3@7R(S9u{WiOHKXpDb8_eCTvP+>b;p9w_NR=v#sRxwi4?BXpHGW>ci8^zQA09 z@2jK^2?@o>u+78oD%C*!X5#Za&%%vlk&foFmy4CfaW}f_fa9f6Fw7Zr>@Edu0$t~M==88RrAuPl}G4F3@ z59oio?{uYS)49XLqu*1cZ3Od4OM8_+-p|WRv43nKNc*zwfRn3hwKm>+#vs`N=geAH zP>f|lni`xoDJj4sg?T|F`TG%ZC0^vvFws>4JL2FI~$FRpM+1BqckH zw$(<8TCPovemH(WKlh~bNxv% zJAD{aaVaw0q|~S@XtzKxK2D0AP^#IP5Gf-qJ(>m^fweu|5$dC%OU#ac;kEOg6=xN9 z!$2q|ZaLFqE7&L`m`+?@`DUdy+V@fdth#IjzJRXl#0*eFljgk0c! zspYKJ=tWmpYf>^YprXJWcmpj^Nj-Vv(kEqPd@9n!w@2xuqf)|N?~fKSF6Dz&M3>*qzd;Xr@(qsB7?8pdX{^%W%S zyKFvwtBhS;`F3OfWXGXUXgelU!Sv+{-@(r(&q>VsYt+=#ypeNh&lu?$5lhnhr3FaJ z#;~d*nEQ5oTO%$%-W^tIlymP* zv&)l%qIjc{)xKA+K$s)AEp2_xmrP7~uwywmQlY|b6*?_e*au8xuBMXQtj( zh!Wrbaf0YI`}L7=@yc@!v7DTsx~lSPHsVpAGGl#KCUmlumOJ_<@V1R6xdwfouE_fM ztZOE#f|XuDl9*$t^_Xp(+s~NH;ma(HUB81p1RW|^9Lky&$XN-Pfgc$o_tcm}MuX69 zE-u3FEjRrnT*StPT~!E=Ba!<%jGOIAQ(T050}~L6o;_>L$${pd(if$)()4(xQf36M zBDlp(ttigsDxx>8U*crV{!(9`>k;CRw0tvMFhW$9+&o%WWu_KWeE;3OCkKbCp2G}S zTbMA@fGqGF97=>idoa(mIl;GZ;H?A{tw5UxS+|RW7m5B*;xl%k;l5l!Ut$pb-L)ESFxc=`n%^p_0ZwI zf3$r*yYo-sDqQaaH{L&4Lipj6$ghYEerSL6CkY4tE-QNR_qXY>{YfXnAGS>W66V6c z?->7L>)?lvd;c>I5)_-r>6x_{i8UzNURKoE-ECoRu3xmbtjMa>-TP`%V)2%bK|1N} z#C;L7zkL#6B6sg(zo_i7RgcXl)Pzj2#d+D6w95*`F-pCN4+ zjq5^9(xr~StkIUYmpc)E?e+h*H{?Hd*GEZ_gku+-ms&9NWVid*>o4vX(oWYezB259 zL&np})j&U&>uS3iKNp^U35Qx=@+sbxnZ>Offt_0-qi8c*BOHEhn7RBFk6K4yCw8DO z6xfEy!Kzlde#tgDPiSVydBvgjp7!-`4EDj*C+{Uu*vQinq5r z^^3N;5M7H*P=r8Pr)HS3TmEpHx4)a4o3JpSH|}m@DXPWt`$gX+J^Ti#Vcm(GULR#p zq+fFTc}3+L&FKT}iMGisoY8pAlRqUIu=+l!Dz4;@frK4rLT`=4P?j;U5nWwfjq>hq z;N^~{_)GG=Z%T=lw9m3@`a?$;38gC>lIZuU$wD6@wu&9yConu*f4aN78@e#&8W?M3 z{ECN6^b{irMmX=~RQ{Neo_sb@{gZtB0nGF1+KWwFIfB>HZ_sx$*_^-k;~MPf%C&d= z*APc9d_dn>td+~PS(Th<&FBb0&|gAl;$xqD75$U*Nr6pfP$7I$Wur(vTc^K`7L{iw zW7`l?dr7%mH*YUdBk?gNqi8D+6Sq6GwvjK)dpiSS4-~z;xBbgIZJSly6)e4*lg7*~ z_mX~9=nt}j0#@>SjO=)u#25H7D(4O+s@2>Z|8GW%RBGLCYK{bt9q3o-a`b#fuXdH# z|8=~of*+~^izc%a*7OjYN6wrbAJ)ZsO$`8}6(@svcX^SSoBSja8*k!R5Fa&3oS7od z6x**qzZA4kO_3bKc)Cs5 zJxtuN2A>06rlHRYM{v?*Hizyk?c@$N=ziHIJMCm)84f>quqTfDS_l_7 zltx&>rhK~EnOgHs%pZ1j9}4|auB3n^SUEZplC*^c1gch7HQ(KpB~0`PG)C;P0e=IY zihbW>aB5MK`%7TZ)+~fxJW>U4kJ#z7i}A6w&#NlkNG+oL{N)|V2DlQg5hu#v!^rj1r z?KuihB3GvS*qRfo0=5yvRZTaZ!WQ`X5`Fw<_+EbP`P1ugr0JQN`jX@G(}fr{s=NX^ zMwUNC#WwM^Gs$G7AIlRG94#?i{5b(};DC&b;eoEtplFE_Mza4}rG_S>M8&(hv zZB`d-bw$#3a-Rk=0=;A1%bs?86lplsn?i2 z-XN-fBy(S#=7F62f93(f2fkwWvZ<|7mCxgoe2>#8<3H3YkM4VHjZ~ZMmezt0+BKDA zxQ#EtltXgQL`(MiaAvIdcoh`chxB6{zy0`9IrP2DrSy%2+1Ru+p)tl`jLOUPN4lqv z@LptUo7J`8qe;(FwLX7MsJQlq(q~UW^;FgyLhSi%p+ejjrSR>}*?m0A#8yYkz`&b{ z=P-(Y-na*BkGD4%=<>z#Cce}R2kXmfR3j3uMvd*>4L!m!jFeQ;NB~AGu`MRUNoagJXc;R-U>z6C` z&jz0cdU|_4;}*N60Cmzajk&s+$Dds1qIV}%JlwV>8bx26z#ojXd_%Zof$|7#65~7A zo)~rz`o9}wYz!fWXQsI?UaN}vDcNLPuF26O)s70sH@Afq{Q_JybG&x}1m!+7w2If3PD{WOXSyMxU z+an`V*Zja6iD8+Ra!<%M!^)Jnxkuws=tbH2;vg9(4Tax0#V>FTkBl5`P162{DY<)i zdBa*Q`E_>orGqgeYF@~9zJ*zZlVBDgul5~p==*@c>SPRM9EK*Tx2hmk5JVIdMxYBE z`1zo{eNshoao**baG!gxog-rcWNKPm>`Bmnk#A^hEXr07_=mX8&O^qkHxGtt+wwum z9680G+pUU~jKSakoQKS~gr&KT1SV8L*Lu|)Yo5uO(Y6mxms?IgRN>fpG$hQcl2X6& zjil<8|9xfTwpd(L5eVX_n>=lIv6sMm}6%L)sZM6Xf8 zJfO|k=l6Ma>(V3L<$`Kz9`;|lH4%mTgK}1W=P%b2(|%hX%q<`*Ypmct+rL<_m61!W zr?^^2PKXO(3`_2frioh~Ja~vvMf~a0>+D4tN4)ez`9lc_iAE_rC2qCPe8Tl*M*?(= zm7ul#Piy05#2I!D4uiLd`Hl=tABbH8LXP2lBu7UhgAfj#!JLxDMmJAiE0vLwnp{53 z&U}uWi#%}Uap2Jq|IdMJ39^4CW3{Jb>pzZ^-#I(7n3|A~U=n{0GaGZH-oc-%jSQ-ne#=4?)fsg04}V`J9OhqekVkeRyn)twyO4 z@AUj~tI0F+&EkT<>g6d(=lz?horHwoftrSfTJdRM?kP*RzqDv7ynMNF#&ez7$;D;= zR5Nj0I(v*JWvFmO#3XG*>5(}7y!U9huENMzeRSa@rNo|G@Q=Nv7)7{b3^+qYOzb+o zOHE-6W|coyiRR&Y+rhbGKW(IWRA`6a6=|mr>g7B5vb?79jQHuDrJ4#iBi&Q=zGM#w zow^{ZRK6hGNe5N6` zxbm*ok$BbOybQ&A`m43dksB*{nidvgubmD#J8vM4opbre!fJ}efQWSAOk{f70*g&HM@I!%Q+YYRyx%rdp6?@_kC`3z7oiaO#I5zvsFB(vy^B$hh@5U_G64a9>$C>d($#@EKf$9f~ka^Qn)W?eXHL(4GYN43P(7b z*WIGoG()P3U!9C@v+ekwbKSf(>coWE?pR!)k7HlhAWHTLB z1vd5>k7RdcWhIr;*JosWGrR~Y>#Ici1a;F6wIz*&_RVRxB+oQA-;>hw!UumxVSVYZ%6I_NfTo%N=rPf@Bu)TlV5MC|wvc&<~^W?SpaU_g8_MMnh2Uo-sZ_`)o&NS70y5o9~ z=hkk`R4tcrUr84-Gr*Yup6Gpi?c(R{7QoE@!A>rzh4?I+wlKVcyhX8?%9~kd=7#P0 zvOZdZ{o-oEw{I?mk4Kw7qdw(FcErS^`-@3sQPrp5&}tzIaBN%;JtGn+5~sH4i0{JQ zWF=K7mxpj6;$C=42np3CYNh^oK11-D+K)F7-+KX{kukI6Sjr)JcW6}T20Q!3(eQ*z zL26YE4Gx3U!j@8+h{bTyjEs9>ggX?qw_V}E;Zap02h_V#_T=#W8EKRST`@0GIkZJ+ zzV44l!vg3g;95zRs`;*>ze17sWDjrp4$t{s*wAHPu0pI4D}V8IJAi}&eA?-rhADr& z^91k?5wBums9)alLML<)kwlK27qp&oO4x%j2nuxF)w;Q#uw>R_n2`*W8BZbHN)+UBAs5^sWf4%)OdYhh!*7r3CQ)PWNs53ntF|n~W zZ6BLRx&8JAxjC-LUvI7Z{hyeEM1;eyKWy2;{ND&}fVA_WfDDF`mS_ktK}LqXj!wi+ z#6ZbDS@NF2PZ%NBCbr=YQDn>1&>bx5#k*_?%N~E)CQ-<^6aP8T$bSq8_}>6|rqA>}(09FP9L}-f zg&vuh&;txO>0vMdy||oZUVGTdEW{wow<1LiVcULTF5e5GU&Qw7`OoDqN&3($)14Ee z+EBrCFi|a5t;vnYztcuLBxbI9K;#lLyWZvBXuB%usT#)ih5GB01CAr4`HkQvpa(kA z^!atg$lq$z5YhLiJ*ThnF!#)_SDH37JVF!9K08@F{|gcLW4qZ{<0fGZUu5E`xB_!g z?|m@AVz|$De4JfhWX^^urKH}%V(UfHBRTw+J5uaCj$?Pg8f56C>!ioG{CxtCbX_U+ zkWI!T{f9NuUSaGJFwg3etpS1^V^ck+zE#E0o3hrH){){9j~3$786O#W{rvwqi@ri} zo%^I#;!9idE~m9>sW~DcHwQNwSUqGu7}sCQU-himlc<3$h1wl`OX@iCX*JZ@0T_eC z_&gvAKR+f*9vry4ye(RnoT`oZM1k84)>LU@5uy6;vRC6AZyHthUKs0&e@8Lj!XT*8-HY_e+n{M&Af_8Y8sEh&@`}SI_>74cl^9F&`5Zx^P65 z*WrWv#@7nw9s8Sz%Ro>>i|^NZ`WLH3?3QkoYHlevTME3(mvv{*lrfv}Wnk+(*8bvP z*;X1`JzbxjA|)6e9B5SRj{5c~WM*cDN77E;(_b}t>TvTrr^{Cpp@`-@#2gFIsHL;h z=*C+1Wg!$~#`@sf$;%^<;2sTvtojvh7<{+ghDtrVW?);b9cC)clLLY`NtQoflhjxA}z-!OOs%Dz=p4-enLPN}k-iGhJnE>kC6U=Ckf zu4@(fOKqdIHL)h&5ki(Hf!SeU)Lg%qH6^AGhvQM2#aO6FYo( z=`fp;(izFVi*^7u1^5N@6?-5)DhssCDJg&&Px0RPtZYcMpt!gSBZ+uolD&{$p{$c> z;BpcQ?8W9Ix9<&-mccKQqy6NLS(z>R(?(-rVitJbwoEA~*y~C)QtnYxKbfFFO`oov zaqjok3`pg}GkV7Ak@x53LVxSqT!HcP(!ZAAT!`sIYSO)$m+#)a;|Lz>N}lYL^R!;A z<&yEhNDMAQG$To?{wE2y5D()-oCY3%4OxO&-D{b$J_(X`RMZqti-oU;x!dI!|U;-eeqQ;zbN4WMU4*>m3IM> zd>UU`7X4Os1Xg#>A5sbNw?6jcwnDp-;y4GkWs1N3BhG0tBCz+bY^>^nW4A(j6ChnZ z`J|&kl`ZCb6s-BZX-X}o(6;z;c0_6J6yUSVhZk*gnPtoV<_x951W?5qH}>sa`s=DM zZ!&_9xZ$!r9UmkIR6$aXe@)H(ylOy-gOp;O%tO69d&#Wn&e><22q*AYW|hHg2mZ>K zWbV(sAo{C|ak_frl%djJByj|L)d67r+1^6KAN?j%eOEJdNQnFQ^;d5uzKo5PbzA!m zWS+OTXX{6G+~q|dAMGQdFz}E=2$ubZtdj<`U{n~H%$Uh4_@&8XQ|cJ=8E_<2vuzS^fR{Er3J8fxyxR6xCHo zx(kdLK%=%ab z8rQj{8+Dxv7t-Ei(=wUcFPmU0;d|EVx;@^&R_BcP%)Ocg)&x1)NBx#E!Wiv?R>WiM zF=>mKK&^Cx#An5Z@32@cF*-cRpUa`5*U^CUafJ-7@{psAs@eZ!x$}mrLh@ai)u5xQI3zQC&B>M? zx*g3SOeq7zP#F0A!joL~r5sF7c6RomP{IEGneIGQU@$NF%)-iK6E$}C@R(}D^+<*Z zgbOY|b$$nFBKXh=*EQf_D&wTQPV%ZJYUFz6cD)ikDPems-(9YXb+5W$?hSl*?MT?HV%DzwC_}FwV^Eua_ zI>s0~1xnrx%xAvx?I;q8kk@7b2pGEdWAYwmScw(GX-jEw@g zw2;o6tGn}4Stn<}6QF*{vnUm)$I-N165$x^KKPPK2aCOC% zmHAB)a(eNUC;{^C4&H*;z%#p%^RA0v`oreu3!g%w4nb^&PBcO-Q+Xlg2<6mA(O>Av z12!cv?H~<6k#`A*i`(04JqvVYn&~Xmyaw~Z+6lknn7zH(2s21w21SyYqv^{MLT@j@Y7Q1~>x~)t+sGkQ}~;OU&C2N)|uV zASG?u|3`K20Tg8xZH+qPsE7ejK#?RUf>&BxjW*l5?h2 zqNFBgx>Y38)q_Fil4wOXsI411$(%sY0j zv$EcmhHcauh$*>KL>;HYVss+y>sg&dn{XgLyOi&ko0@vA^b7n=GGeYR`$$Of=+(p` z)>tQ$Ptl06zp;?#F+}QYTmZJrPNuE zySnt3;2iGZyh_@h8Vf6{xQTDEv%Wf14zSUq4Wa=12mYbGwUsnXdt;^r`i$yLPcC*d zugV*bbaMYNEKZV{ibhjn}BxYlZ>F0GR80tOvdvzMrQMSHPVF zL{^3CY!6&77a#$-xb$gK|Di1h@5l_U+L3*Qwr?N+0*Rt7 zbx+e@Hn~eew5qQ(K)x`o_@nAF$^rs!h+Bv&`m(!_n&|aBBK^^& z3K3CzCg#_!n`1~;Zzg8(&SZ+djMAq@t+gKG9!PHye%q1YrKV7VwDiJ)wTx+Vr#@KD zZ+m$~Mnt3rC?tuQz|(*s-qK3#&e5;T+0T)EdV`?;-a9-V9&5m zeI_Paove6B7rP zS!D-XEnIpGEN^aRDDf2Ltp;U+?%hw&?CeyUYKOHa?C(q&z8pnX%SuT@b%`0A&pXT+ z9H@()d?^BBv^V^@A)j9K-~!(I$iTA^00kKZg-T!xklM{*w5cGa=*hk-KvC(n*I8|c zOqb?k{ywjk3pE|H@$Sp8fEmZCm5C-cki`MdscE~iS0)aX9HR|k5JNrhSOwyz3?6p3 zf6Mb9DkH#QXa9~!`nQ}dcJ9J)>I!p@|#7n!@~87(!w=rfFqa4|xSbuJvlsY{s| z7lP*By^~4vs7uI zXOP{erdUN4{*(T+rSFvkqC^JBr)&s*go4-H9ccn_oZq@L7RiH6O{ZthT<`jh!~6&cPI1 zWnpRDwrq*wkY$EOax+-KW_-0DP_PwPr@%=XA5o?V6sAECKFh?YrpF)U=SMK4SZY`e zyl0?wVTOgMi~@EZ@PdknmC4pG}Z2ZvD(DQ%gosW(u?S7 zp2548rAY>Mw-DcqVF%vf#LfTa7OB|(lS4hOrq-M6swVSk;Jx?iUsDHWose&~Ko>e&DK(YIDI zXt3HHJ_45)yc6M)t=@VFUNR3OL38G4_sH6X2Ui+`tfuQ||ANzijWc9mUu z{{-=MnoT5XOmRxnM66t#=ni8LX(6YWun<78p)LhQiVhffS8k^=3JM7Uzq&TvY0)jR zRcC%g!_?|PNAvLl<;IQxC_)p(BA#r|T!($v$D_iohh7%$-fK~%!kE!wKoKrhT-8XsVA=j zr0+B~qzHLkVJZZoGR&4FI!N$@N|Rodr{G{L$R0S3wMX)3<-Hl7nr%&~B&!4n3c6@! z)ww7w271Gv9O|mjh0HfEtwRp(y0#BZZsZ ze0guHfLl4SInj@eb;cT9#Wh%pu*O_vPM5D{W8D&|h7#%W`}fG`^r-7pX-f6P{%l)|XiOHE(?Gznf)(@Z^a}JN%+;9(TIjICS79F}DA7;A@FBW+8J? z?3(cDV+`!Tp*}nz!Afj=O56suXavG8FVGI%Ezb)LRTe91O!NcIi>DZ&I6TFh|0^EB zXMI0k)sIxbc6 zE#tHxFcvq|kny|#FYtgoabv{kKi~@!Ybz6xMpK>?GG%uTmTQiLN(y3)GMZDDi8)D1 zs?Ty`q6rvNw$XT+^854sqMXJ%-$99vsfw!cnqCY(sUM@nq>`=Gm#ur3=Ao69Fg_ul zT_fiSzNrI_a_qutP?3=Ti-h%!G9lm zMr{6<-uZu$lG)gN_Gt0~ZZbRC_O}9VX#JoI{n_4MC{04YviRBF_kVB_^7%gAq8HzO6mpt(f(9Se6g)@N6#v)Syx;P2 z5ts0@b2l{oRgWFoPGGM()tqNgdjT-wGiTm)KThuDg1#muUyrKLy(6plILum~KD~)H zFaWv2;&^d2jHIw?z;p&Qaaz(jbuh-IU1;19|Ho-Yp&1yl$*=83<{_{FlR;5N#{Y~ z>f8YK$GFEPy`cMow)Gewei!_`u05XF*E?QNTr*RIdd>}n_F~d^_q?^NPtk2z+8PUO z&G_DWTT9FGMwVv2Eq#<66|KwnavAx7J9oJBZ(#;AN7iQc?R$TI-F|rs89q((4NlE{ z>MdPWhErEP+w4^Od6ye?pz?h5jRQ1J`Ic066vKb%ejb;Oz^Aj?-VF%&^zgjcTyGYp zlbiw)&Gl}9${8*rV=WO4okd7_EDXyC^qTF9m-pg&Pn5eI{Lw?H?>g;%RLb1qq2l15 zAI&-{m2tg>BK$&~7z|3BrW0{o?lNLQ1_p;xghO);4TCgn&F6?T$c-@ijAH%D*O!@{ za8?P_$4<{IElp3Wfby&CmyrKeP)CHn$T3hL)AC!hjS1@O%R@#5OyLCtKuDWmmFMJV zn7ZON0QhHL-MPq7|6M7OGW_G6Q;PXcPoJK&>}@UiJ_(aG86De;3X<;%-)m+k{K3Dv z%w|}T^lNp-7Jb|kKq&)=3>nmRn8G#CMOKQnhSN>@gAOJrAR$3F@3*@C5AlTE*%pes zVc-j&^te~7Z?r92cH&uv;_nH(r>`-*`t^FtiNH(2X=Vy;$=^ENphMNpuLDzdh`XjC z$7v4VSOmoIGdH%QP~54awyzWz&-l( z85)PB8vN87vSTAd+EcYG2UmYt`Qa~a$IoLsBmgi8Q3$t&S(JUXZG3)?&RGi~g;W*V zlSgX}Qs!4-tJb6Xf7SDyvt2v*3y1E=;2FL+kiwwVKXgkd&@I8U`S~@gF9XIPpKx#z zPyrEJGniU~o9vx@(+V9{EMp4L%2A)U1tY(9y_WQQYYEfm(B~~m$rKrije4OGl)lV5 zjhM!$Z){SB`}l{(p7vP&`e`>LAsG2f9M@fW#qM9~I5&olU)TDc%GCa}6^gG0N@7mt zgfeSBP0&i=JiezL-Ixz-0jX7#@z09&r=X0RBMN%v>l0VbA7-HFo^`x*Cj@WWUUB#s zSsa>~-ONWR-{q=CYh!w!gU*qID{%X7rJ3F|0&$y2@IOtw=3;=G?WGbGMd?=*SZ$$I zF1>*HGtlFHU0sfXBLg^vdAgC6LBF7|=BCmU*fU)P&$ENtKxht#j+i^KHqg*BU14%g zXSX-Jo>d`!EO%?3&q_UC`;%I_YLYO52}ZpDCD^u4b+GCFb@A>87$nlYbIPVxL+-u< z?89So(Xf~sW<_zl9#bAmHRDVb`aquYmz7~(ktdE+ako>T*2WIcxSSl17I z)zX*ce!IWQi|rJIo_EB5QVMm55WbiB|Ez=}bEqXq z@-6g4FY z1-?DeQj|Stx4kfqm|czP>KZJxx37FUTv1syfL~1Lq#qd_p5%9QBD#$_FI*6?ZxwK| z1VhM?uF`{NWZ7?*M-%}rL-W^7x5?#NksxF)W_!%= z+(~_tP=z@DRO@t0xZ(#daXwbX((=Pwt1fwn>=+Hzx5FGSC@46;(48+e$_NNJrQ~In zEN;E3^|sgPgSKPpY?NT?y0@4h^n#l|L8o+{k`C8J+L_`Eczrl(BTie0Q zf0f^yO-8P4Y$(dfwQepCubmsOJNppef?BuwE(r!z*Gs1&uk&4*ZFUBAo^e=`X?^XX0UNYrK%qf0 zRcnj~(kCnH>0*0I0sDy!{-_#4|a*$vZozkW+KKzgUr zR2%hD!*79dOBsHP&qX44X|5W6Yj$48=S7I;`nG*M>0wZ~tzXcpRnjl-p=#C2?XHs# z(--sLOFzxX*Ocq%mv&jhQ6V_U!vbC?voo>ZA!m~ts+rX&t2qIS2Q}8|H=^q1MvB8Q zrSlf-mEIFm@W;~}Rw2@{Suydt z#v(?z62R$xyB8%H&`QPONyG|JP=v?eab}%yOCHP1n~JnKIh=mva7!cuhK4F6>hGeD zSLl{Los8izG^q79L4P)klzZgq`MHNG8!YHmE+Xnq#N^T4a@_vXn^`9&oVJs$FMYRp zo#xthQ?4$gxO1shtv?b+LJ5<&H;=0uN6Q3N+!H0E6B7Asl_;0~LHjMP+{v=mdu#a$d@yv}q!=|+;@vsXns3#dcy4Xb5-lg18IY;cs zSQQonHx6u#f!Wf`RUK|=nJFVCf&KvtewbcsFW`Th%|1OSA(s92(sn{xY<(TDyQi3> z``M#9IyyG!&QRi(GiQr^$+M%mEc)Km^_{1o>IdOBsQb_H?AY{dUza2~d)ANq`$8oW zu>8I0a39t8#mg};Cz7XKXBm=}HA@&rv&d7Pll)_>b}(&{tlV}FQ%$^WsS+Pc!laZU zsx{WJfykjKCN|c#b{Ay8R+}|`@E0j0MGz~-KVpxwM_I1m1jS2iuWGDre~GXa z-&_-QtpH#~U#QFus33b6fu*GC4uy`U5Qg+&KQR-&>RVfbJCMFdC%&LtaQnBc*P1rM zN5xMLPt~f8EKIN}Ur~t5xXLe;9 zk16o}*667uun2uG;iU+>ML|cDJn4Dog;i*bs3l2^F8c<&3{4ht`;!t)!X)JMnAMl$ z#EB)$-rcJoC?q4fH9&(?feg0yEneeJebc>iWLd z_Of2TUZ@dd_H!2lr?XYMDfNn1AiF4@<|)$uSZt2+BL@tuk72$$9~FJds@baXFDD=P zxVIUPw!j-%g207|F!A*zMp}3$1tkSth?YqXZ^;12W4WT}(J|NWM;@-C@`+4}HzYLl zG{9@|^|+l?Sw?n^4{t;@EyDr>yKPH4qOvWcT1U7HHFBAKfs|9-NBd=%r14dl%&Z^P zc9yg>G~=}{;=tsl>YG~R*yQPHaA@@`YQBH?896P%4QdSlk5)u>|N+EgAPDR*-AbIB|1TUDaYeYA(N5!cp#745+KwB%3K5=xs zRbsc5kvr!BURWDz$o;O1QAgy*wvFA0iYQ!)=#-U~evxn0Ci@%6-x=_`uIuw_a~d;- z=eQYmCuUMumBFq@SK9PPpX`K#=3mx~aHR~RmGKXy`9IS#NyFd;+?lfxuQl@?#Hgff zZ*P|+Rq@+)-`O>yHP8Gk8NjNXk}*q7MbCvd4O08|`9YqTD9WJbUhnJJNJ?5-Sm3Ax zEfST6Mj4V7oZE=)Zp)XUE!k;lcb51v*ka)*0nb7*)!PKz&PbU>sdHnGQ+D)mCVso| zJk_is!(T|GVX|^O#+0XqLUKr3cWbs^iloqZV?;cZ9)XlneQi0^a$PxD3?@f;t$&HDh zq%4kJxx%uuFiPC*(8V^Xhkx*jO)rdlZ6@sa)Xdi^(ws|TpMifQzz-pmA|W|HW?_cOfTv!dGpJ>fZa6L zt_(?c^<9@)J}W_UW8)h(Qwt3U^th6+eS?Y} zDvKWV(7ty24V)rVA$R(w(JB=(d&Go{fp7_&qd%e1pFWAnI~^=!I0GG#h+Z&8O+2Z^ z9+fbd&mb_HS-VGOJyK)?dJ#CI1~fJEC8hc;OUd6orikfpLpZrOMC?}I5=O-eJz8kE zia&Z9U!~J;Ec3F)dv9~~5fY9xcPHfhV5U~6z2$((7H?sMD@ z-2xU<<272NC_HG~c!BTzV5!4h(dM}?a0E1!Ts2`;V1UnB6_P+o-NJaD61P6=aN69hB$rx$@&y{gmD2<3rMODgOL*!^faKqx z>T0M}(Mxls*@L#)I@*!!8bx@r+|e?Ns0?$Zw6qNC;fl=|!7SSQM23AjZQJ{y5lVI_p3HcqQ+{{fnp3H%*c-qK z+o7wd&Qk(7Nxy*A<+mHzYThrz+bqL>ZnN+&&u_u1YHBx^{MHx7YMZUb8sG-n;l1>y z{u!HDuMf&S`0+#k$!2!{MNdH}!@(DU5%rIC$^W2kF!p@avvxlbFN>Kohul=yC@o7z zezwFPcx6>tX!l#<_V&oYKz1pu-&%tFDj#@CzI&T}=ttvNJ1^7qFF$|O@`HlhcOo>! zv22uIVEAae{GHMfu{qowK%p6(4l4XZhfI$z{X+!2`nP+F+rOr<{#(CsR_Yu`4u7kt zh~alUzWt}j>62ZaQ|LYA-nvL8Wy1>vuaz_2tNjMR!2XvZTk=23&;Qo9@h{crf8nFv zSaB--01YkjDM4X*Tvs4pPrDX3_>!uXwu;RQkLyhXn^(9)roAywmMmPV?RRkS1PmKo z_pcZK0G)%$-7q(I_cDl8w&=~M^V$;1@_W+mj@X(fi#f#aKZs6-Uuy-tHpe0| zm1wJ8h8aT$n!{A*mk8FS0E$aZVJY{s&2no!wocOv@h<4czLo#^9JE9Nr~t*~0KCwd z^OOsM?um{wj8C>;gmZIo%mACs{#-)hMJ~3NO{=UO;kZhy;#o$I)|#ihmEUGN%_!)J z+qOqhBzNfY#^}uTWsO4;8k5vf5Y~J5wWz${`Z-^9QCS3BC7*F;WXQpE!}u9m+QBy# zZCs-@>Gd^ktDMQ=)2o$gWVrkkA?NL8hVit`akMUU2yz}@p&apawx*I~qa;Nc`4Djv zZ+O#mW0YpOle*g_1G3Md2t%o(4fd%#Nusj|Jj7qO!3Le44>KeDIS*Do600zNH-E$-dO{wni;BqcQ30(i zbrs9O>w3f@$mE9sPA5*qwUUUVCY$%-3e7CTyPQx=}?<7~~;a zz?gqZj2q-msz&UM%uOOkh9?IQC#uVAMryo6oKYHW(F?FH5(>4>;?VazXbigGDOa^n@k8k7qk!6Kt(N)9R zy3oa&AeSVV8X5{ykF8_zqpMM;;>&buZR?fKHoI73G>LJ3`v5R%!g%T5rT^c7_=qokX^YpyKvO#}s z8`^77Vrk@P0(EjsNJ%~ZxRd((aI;k!wM<9rLFcB09Vc3WlyZnvAFozCKfghcjA#m* z?Rz_pDxPwl>6z%BU`-gvhsNU`$=m5C^p7%03%xg1Kfd|Oj}G3;0(Ew=X{iC;oYf<>3DEw`6J!9xOsH9>}GiP^>n$Zg^_tiz2}I`nA452Mj& zZW+D(4a01F1jfX0bruop{jF4OWQ;<*Pu?^`(R6~Eo!54(B8JC+lu0vBZxV|gFO?tU zv(hTJ!Z_~~P5PgvC%Ah2R0DaQtl9Ue(l5gfMG2Q1 zYEL^2Oq-Idin!ON9tp+jjD47`eqEOS+RVyX|4`gYZ`z6zlIWZQw^rI`*NKXB;;?_H zwyv(z(umt|tA?1z)(IvCaW~aG>o|$SyAVo|WHuXY7F~CW)SC_8z0;rF6_JSoIglnv zI8TZRHlFW_Vv;xEI}1}oq{Yp-z7KJWb#P3>`LsR{7_ZahRa*-T8benPpV{Y6Mp7p4c(Jyvu5S%#m+?Hq{u>X_9`4Q@ z>IJu}1|@d~#4p}i=wiqR!X*4hA!n2K1Z(xQyw9uCYPjiemYWamy&|%A1Sqx#|Q}v3s{Uv zLzD4U8xmBHku9EJf|%0N3?d8H-w<6eUOc|bbb>d;mVqh%S237$1JKg(&cTl?qb#j|Z%xw)#TN{nHjsy#P_gsGB6O-8az{Kv-i z((NDy)qQKhyTJt&g;d(pzok}G;Q=q|N1l(Vsda43SMk|ZEzby{B{&hjkGd?$#jroJ z{2=16$`_iK>YgY>9F3xqdkXe!+C zFV-c9uN`|8-lu*Jj^uvO)6nn(xV89b-SQ=E9UW?W`|d@}&*S6m?F&;_q~RGj9gd!+ zw_nUZ;eL8he-o}Zz!=e-@d8h7V~Wz{v-;=cWAKngC)T&~h%QWSYRNu>LR4aRkB{oMRGEgCa_1TA>wt?<)ZvQeS)>M z7nnjy#V&?e5#e4mq=0G$4$vuA!qG#|+5xws6SrU84vdftxL~NITe*%n+Z?jkKUm+; zVA{Lef91+cP}?07^?a|Sng3uV!7Os=Q+-5J2>(q!K9zV+wdw)rJpBM}(v%FqCJ1D_ zRL@E%3=b#&{0U81iC~Q{tTK<5>k*4Y=Y!Q3i!a1*_mwm^Ha#l2+F|Iw!Fg3}1Yz6x z{&O%qkg$?U2*C`rv>d(#Wo12!W>!`lz5zk<)mKIR=edPhSkm>UZK0`mobB>X9Bcuk zg;Tqv0qhsZoV<&sZ&q)uFGP2jZhHg_oVqCL9KkV&?2#DP9jY_YD(6m;i{f0l_M>|X zGcYUgcxLa2=WVwIY)Ei$vM3@WHB2u3g0O9l&;wD|<%g{2SkIb7d{Mh%<#O%j&0aAD zMMWw}z;a2L+S|E$+OLe`3=9k`cK!X3#U-4HsU7=Q^N<+RetG6-Ur-(v64XoZSn-pO zA!8IZ*ct^0jEWHVoqLwodylE$hQ{XOOizckt#o>D`oj9|(U*PiDdGp;?ttHV;#t)I zV;ej%NntbiY;=7~Lfo-&QNvr{0}=Alf~ zLI0;Q3p^R0AUBXQ-*Y37^y5FuGxZ>s0|H!( zjAUhryK&UxH_d;1tG_E5(337N>knsBKQb?Rmj5P~?Ai>Z87SoG@y9<;C%D--P^l1)1BI=rynKv$W_|6IH-6X)x;lTV>`+_`%_Ey_XB>i z7TmZuszU0Idfd7-#3;UEE-jswnku!&IHI1b>bATLIPf!%tutrOA{M%_&}K)osd(lL zw%jx)vTK$+MvJ(mvC05tz`*bNCM0Adb|oTB(8Sod+!k~F>2_DaascosRP5@@nSm3A zb)?vF#GB2YxX#2pcslifCn>m#yc@=d^f)(AZSE4qezn(Tj{E8j_74hR1}Gopxw1NG zLlh3V&H{8YTx}e7Mx9jT*pv z2rBM6sA|p2gY*2>ZN%>0g<2h^?Wd=7ay$k*!D2;8VR!IyoUPFk;V^85-0B4nYn!-= z9QBJbz45!y^$b-xSv`FCbqj;AGcsBMNjrXTQ?Qzn=GsRo5|#E2jV#{rVsquNMNvrx zznllfRZu=Dr-+_9L54ChkLfM}a0r4s(N#{_KD9o@L2b`{>b90JTNkj8Y##OJxD~^^ zD>!;ZbE@1}cbWk|bUL~P^Nz%gQp@CXP)gwwU}ty#JfsqO`m`OoPSpAPj~c(A-kgNlb{d+gSw5G6^-_))nY(xG z&~w;g7sSMiQ#ZylLSGNqjp5qD^{AUB#wO-U39kk0lF#uNay_UrhMIhdERN6I+fuI8*WLgwkIRzy%&^NxsFD20 zgj3wZm*a>M828-mXZIpyUUhc99ks10;)WjM9}a5zUKZDb07nQe2mp86RAD?{!rw z*?Wxf_UpiRN^!W8KfBbgm%eT7D<_uI3rb6Kfj^9=l7~m(G`*nZ%+sISYYX{L+uAxx z`jN8XN+&f>y_)7%6e2Hvss~@)M5i1*w0&mfG>4~LjX1XW)BauzUnwX5e+8M&r)Okb zsQM-2IfN7cp9QG=yheu({TCpZ|EAyAhoNrg_00|Wlbw~6q5?|T??l2mwudu+{{08O z%CAtQAa^WAQ2kc@vRf&rfDhW?b6)|s^ye{0@-3GTK=gguaBHK=d?J0$8W~u z8*D6&B0F82$~~649GK|GZ|Jbls?AC@+eGxom7=7|=C21NVi@A|_)2$&2XbSCoomqL zOXGoO@wGKLA*U+ACl9w9g9INOsGirZ5Jsok!t8#l;}tYSv}bA08UX8yEY{Wl1THccs2F$BUsz z9W|{;PfL5vNb*MrE%2e%IRypeSy^)x6*JGC37a$vIV~;rl;^)am>+qD8oS$W_mYZ3 zCr4Xlwivt*pu7M)V|OKbubtOw_YJK)y_7;Wp*(U$2S-^z>V*L!<0d-^LSHJhT^=Y zY+xWTQNxv9+2r4=-B0aq%t8-so;3~jp}!yY;m^^%}koW{ACiMJ= zMR$=Yp^xoyW!I={-8WBqQMoT{uMAaGzy(w~F92%8nh^jr7~JtqrGg_?lfSXZh?Igx8;V_h7P)`|Ar4U6AhTYvM_AO3)D)sGm^ z8%f;_jg4h1a*SR#v>HL({FCtor_MFIVLN%|OUwijr(OIoHCOE5JGYzX%%?9MgQ$nc z`S_@057gr*;2^^dOkPr7EpqOE>6EQiSy>r+ewK=gKG($R3TZ?~LOCUerfEJZ52%Up zY6Jpw85Xj0*y?hbqDM zPd+BygP=%xMYqf*U#)tR(f{;GhM7XAJ!e$7foPSBu>1DI;wvH3JlflY=ncAeMEwtO zJJ0}tZT`$asXXAzPKFX*i*Wbb@I+F7RbrrxYUwJilP7N8q`8OwK(4-h@gxoxQW{k{ zcu$#08WI?kQUo2Qm7b9^L%>uyDmhuSO2DQ^o8+5%#WGA}A}}SDki;ZOa>>Zl)>h24 z>!X5)gE0@>MQnYi2R~o)@+J zk=_<*W`ZD~%ULnrl)yFu0Wm`=g+vi%lT^E}koy4Cuq+Ij8#W3B~<4v`GXN=vA- zKu!ZBN6Mz&k$Fi84cRK43erFn^rl;jFDb9~)I7M!KK{jeSjL@J$Wk`#vqPftTkD_O$M-m7lDQ4W%Xd5h8jtC zfdn2b3nMvnGxUZ*$?@XUg&fTGUPQBUYcy9x#$~S;Gj1`K(g3NiUr@`|R172F9StQ! zmq2|3wxA>a@y)g+DUcA@aP(+~Fx}s0!Lr2p?VW_DldYCwqr=0Ejg7~d0JOZq%8Jm( za&%~cX2X3G=ccDuoV@U7n%|xZu^STAYS+M@8 zwN;Mf+xPFQsJH3l%vboQ@GR>z@22(HWi_*!LQ`gDX0F-eZe>mSjM08*xreRqJGn}$ z;p$pdf4~zl`aVxVc6*OdYHCTq9ptfb zn~|4US;@BOqH;9D=&siU1QkoZJum89<4p4)={`|Kwd5lHi-B=muHmU}1lLWqn;mS} z-h`{7TX`l_ev*Yxhl^m_SQuTZ_H1nR+@TZQ-LixEuSKVh_VyC8bdTHRj&`&*4{|T_ z^J}KL#PNQ4HHDLp%*Yt=&maQ4V0SF;bT^SZ${JI6Q%tPxaX0f^BxMBBW#zZUQ#U>K ze9!Ul`hU589sbFZmHnQ=_<5(=`@kr8}C;p#Cd|C0iAH7}hQ7E}|Ocx&Wgh@z6bLlP$8dq%J-nx^wI$64wF!SsXLKy|& zqP*kQ#H&BE(_zgnUq8Q%x0rqE;-JPJ_3hg?89lu;L|LUZu(C>PEq@Y3L6QM!yp`7J z^`is=k71pH;?mXQ_?Q8QNDzsWe9kpZrHBNJ;i0r?g$7k&=D?79t9N7c8I(a(9Kw_6c3(RBa$4$Txf&MwykrGpz z3}zu{Q@en$fp!P*W6@mn5f>{y;%y&F}^-KQ%}#lo_hI zbjvNsI3K)gfO!nass!z6C75Znw6GoX*t1Sa{C#IFhD+CMVkse!^c)QMtf33lvf4jy z^gApGX}VGEx{vwF`W!ozqo17-ehqRI-iL>)fF7b(m&*$BRv1J`Jju_$-;-bq$Fg=_-5LCtol)u0P@b9SH<B4;u??7%pLP$SVa)~8>j?;AF&8q@+)C~Sv64oji2dhih?>qdNWISnC8m&F zC#U%|*)oZ||0XtrAX1BLej`|HHq$Fk8x?#(@N-YkQ}myH-@Z|VhKAO9Y%Fxw>UMGr z8b&Xw+DtULI?bXTpjUS=>%d*ydLf@8c9p%~!Oo6J+VS=?!@<1xe9GwPMle;%RlnMc z^?mVT7gHJGhd+4iOU^%H?)D2F;1^mWnxOs#3(`#1Mx+7Fcs=mgH)yq-A{gTp!ZWPN}K+BJW@_xb7nGX*bMF z)7m4NyJY@e#UIc4Z~dJ}2mhxfsBoW(J@_8-f3Z^if0?lUzy5aHh`+JB(#JiB=3m^u OU$Qbv(s_~(|NK9;wfI{A literal 0 HcmV?d00001 diff --git a/LTI/lti3.png b/LTI/lti3.png new file mode 100644 index 0000000000000000000000000000000000000000..3b16d35189c4254d56ec8cc4cd2b09a9ec2f67c3 GIT binary patch literal 118045 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ℑyXnAz;t^{<8t)oo4;M#IGX9$<)Zt0-mzddYxi5q z#(oXk^GDz620FH1{(PxioX!EeOFa52=j#$CY!Olw>U-q%Y42;_w_r(8ZZe%ga!$3_ z

Pr*&ERhtY>Y@fHp%!>W>5JBoSTafKUp%=`< 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 z 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", @@ -12,14 +12,15 @@ "@material-ui/icons": "^4.5.1", "bowser": "^2.7.0", "classnames": "^2.2.6", + "create-torrent": "^4.4.1", "dompurify": "^2.0.7", "domready": "^1.0.8", - "end-of-stream": "1.4.0", + "end-of-stream": "1.4.1", "file-saver": "^2.0.2", "hark": "^1.2.3", "is-electron": "^2.2.0", "marked": "^0.8.0", - "mediasoup-client": "^3.5.4", + "mediasoup-client": "^3.6.4", "notistack": "^0.9.5", "prop-types": "^15.7.2", "random-string": "^0.2.0", @@ -29,7 +30,8 @@ "react-intl": "^3.4.0", "react-redux": "^7.1.1", "react-router-dom": "^5.1.2", - "react-scripts": "^3.3.0", + "react-scripts": "3.4.1", + "react-wakelock-react16": "0.0.7", "redux": "^4.0.4", "redux-logger": "^3.0.6", "redux-persist": "^6.0.0", @@ -38,7 +40,7 @@ "riek": "^1.1.0", "socket.io-client": "^2.3.0", "source-map-explorer": "^2.1.0", - "webtorrent": "^0.107.16" + "webtorrent": "^0.107.17" }, "scripts": { "analyze": "source-map-explorer build/static/js/*", @@ -58,11 +60,8 @@ ], "devDependencies": { "electron": "^7.1.1", - "eslint": "^6.5.1", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-react": "^7.16.0", + "eslint-plugin-react": "^7.19.0", "foreman": "^3.0.1", - "jest": "^24.9.0", "redux-mock-store": "^1.5.3" } } diff --git a/app/public/config/config.example.js b/app/public/config/config.example.js index f64b139..ca7a213 100644 --- a/app/public/config/config.example.js +++ b/app/public/config/config.example.js @@ -1,9 +1,9 @@ // eslint-disable-next-line var config = { - loginEnabled : false, - developmentPort : 3443, - productionPort : 443, + loginEnabled : false, + developmentPort : 3443, + productionPort : 443, /** * If defaultResolution is set, it will override user settings when joining: @@ -25,29 +25,45 @@ var config = { scaleResolutionDownBy: 2 }, { scaleResolutionDownBy: 1 } ], + + /** + * White listing browsers that support audio output device selection. + * It is not yet fully implemented in Firefox. + * See: https://bugzilla.mozilla.org/show_bug.cgi?id=1498512 + */ + audioOutputSupportedBrowsers : + [ + 'chrome', + 'opera' + ], // Socket.io request timeout requestTimeout : 10000, transportOptions : { tcp : true }, - lastN : 4, - mobileLastN : 1, defaultAudio : { sampleRate : 48000, channelCount : 1, volume : 1.0, - autoGainControl : true, + autoGainControl : false, echoCancellation : true, noiseSuppression : true, sampleSize : 16 }, - background : 'images/background.jpg', + background : 'images/background.jpg', + defaultLayout : 'democratic', // democratic, filmstrip + lastN : 4, + mobileLastN : 1, + // Highest number of speakers user can select + maxLastN : 5, + // If truthy, users can NOT change number of speakers visible + lockLastN : false, // Add file and uncomment for adding logo to appbar // logo : 'images/logo.svg', - title : 'Multiparty meeting', - theme : + title : 'Multiparty meeting', + theme : { palette : { diff --git a/app/public/privacy/privacy.html b/app/public/privacy/privacy.html new file mode 100644 index 0000000..89b4959 --- /dev/null +++ b/app/public/privacy/privacy.html @@ -0,0 +1,13 @@ + + + + + + Pleaceholder for Privacy Statetment/Policy, AUP + + +

Privacy Statement

+

Privacy Policy

+

Acceptable use policy (AUP)

+ + \ No newline at end of file diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index de3e177..684a0e3 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -14,6 +14,8 @@ import * as consumerActions from './actions/consumerActions'; import * as producerActions from './actions/producerActions'; import * as notificationActions from './actions/notificationActions'; +let createTorrent; + let WebTorrent; let saveAs; @@ -128,8 +130,6 @@ export default class RoomClient peerId, accessCode, device, - useSimulcast, - useSharingSimulcast, produce, forceTcp, displayName, @@ -142,8 +142,8 @@ export default class RoomClient throw new Error('Missing device'); logger.debug( - 'constructor() [peerId: "%s", device: "%s", useSimulcast: "%s", produce: "%s", forceTcp: "%s", displayName ""]', - peerId, device.flag, useSimulcast, produce, forceTcp, displayName); + 'constructor() [peerId: "%s", device: "%s", produce: "%s", forceTcp: "%s", displayName ""]', + peerId, device.flag, produce, forceTcp, displayName); this._signalingUrl = null; @@ -153,24 +153,26 @@ export default class RoomClient // Whether we should produce. this._produce = produce; - // Wheter we force TCP + // Whether we force TCP this._forceTcp = forceTcp; // Use displayName if (displayName) store.dispatch(settingsActions.setDisplayName(displayName)); + this._tracker = 'wss://tracker.lab.vvc.niif.hu:443'; + // Torrent support this._torrentSupport = null; // Whether simulcast should be used. - this._useSimulcast = useSimulcast; + this._useSimulcast = false; if ('simulcast' in window.config) this._useSimulcast = window.config.simulcast; // Whether simulcast should be used for sharing - this._useSharingSimulcast = useSharingSimulcast; + this._useSharingSimulcast = false; if ('simulcastSharing' in window.config) this._useSharingSimulcast = window.config.simulcastSharing; @@ -199,6 +201,9 @@ export default class RoomClient // @type {mediasoupClient.Device} this._mediasoupDevice = null; + // Put the browser info into state + store.dispatch(meActions.setBrowser(device)); + // Our WebTorrent client this._webTorrent = null; @@ -209,13 +214,10 @@ export default class RoomClient store.dispatch(settingsActions.setDefaultAudio(defaultAudio)); // Max spotlights - if (device.bowser.getPlatformType() === 'desktop') + if (device.platform === 'desktop') this._maxSpotlights = lastN; else - { this._maxSpotlights = mobileLastN; - store.dispatch(meActions.setIsMobile()); - } store.dispatch( settingsActions.setLastN(this._maxSpotlights)); @@ -241,12 +243,17 @@ export default class RoomClient // Local webcam mediasoup Producer. this._webcamProducer = null; + // Extra videos being produced + this._extraVideoProducers = new Map(); + // Map of webcam MediaDeviceInfos indexed by deviceId. // @type {Map} this._webcams = {}; this._audioDevices = {}; + this._audioOutputDevices = {}; + // mediasoup Consumers. // @type {Map} this._consumers = new Map(); @@ -285,7 +292,7 @@ export default class RoomClient _startKeyListener() { - // Add keypress event listner on document + // Add keydown event listener on document document.addEventListener('keydown', (event) => { if (event.repeat) return; @@ -363,7 +370,7 @@ export default class RoomClient store.dispatch(requestActions.notify( { text : intl.formatMessage({ - id : 'devices.microPhoneMute', + id : 'devices.microphoneMute', defaultMessage : 'Muted your microphone' }) })); @@ -375,7 +382,7 @@ export default class RoomClient store.dispatch(requestActions.notify( { text : intl.formatMessage({ - id : 'devices.microPhoneUnMute', + id : 'devices.microphoneUnMute', defaultMessage : 'Unmuted your microphone' }) })); @@ -459,6 +466,7 @@ export default class RoomClient await this._updateAudioDevices(); await this._updateWebcams(); + await this._updateAudioOutputDevices(); store.dispatch(requestActions.notify( { @@ -470,9 +478,9 @@ export default class RoomClient }); } - login() + login(roomId = this._roomId) { - const url = `/auth/login?peerId=${this._peerId}&roomId=${this._roomId}`; + const url = `/auth/login?peerId=${this._peerId}&roomId=${roomId}`; window.open(url, 'loginWindow'); } @@ -506,6 +514,8 @@ export default class RoomClient { logger.debug('receiveLogoutChildWindow()'); + store.dispatch(meActions.setPicture(null)); + store.dispatch(meActions.loggedIn(false)); store.dispatch(requestActions.notify( @@ -519,22 +529,22 @@ export default class RoomClient _soundNotification() { - const alertPromise = this._soundAlert.play(); + const { notificationSounds } = store.getState().settings; - if (alertPromise !== undefined) + if (notificationSounds) { - alertPromise - .then() - .catch((error) => - { - logger.error('_soundAlert.play() | failed: %o', error); - }); - } - } + const alertPromise = this._soundAlert.play(); - notify(text) - { - store.dispatch(requestActions.notify({ text: text })); + if (alertPromise !== undefined) + { + alertPromise + .then() + .catch((error) => + { + logger.error('_soundAlert.play() | failed: %o', error); + }); + } + } } timeoutCallback(callback) @@ -627,7 +637,7 @@ export default class RoomClient type : 'error', text : intl.formatMessage({ id : 'room.changeDisplayNameError', - defaultMessage : 'An error occured while changing your display name' + defaultMessage : 'An error occurred while changing your display name' }) })); } @@ -682,7 +692,7 @@ export default class RoomClient { if (err) { - return store.dispatch(requestActions.notify( + store.dispatch(requestActions.notify( { type : 'error', text : intl.formatMessage({ @@ -690,6 +700,8 @@ export default class RoomClient defaultMessage : 'Unable to save file' }) })); + + return; } saveAs(blob, file.name); @@ -706,7 +718,9 @@ export default class RoomClient if (existingTorrent) { // Never add duplicate torrents, use the existing one instead. - return this._handleTorrent(existingTorrent); + this._handleTorrent(existingTorrent); + + return; } this._webTorrent.add(magnetUri, this._handleTorrent); @@ -718,11 +732,13 @@ export default class RoomClient // same file was sent multiple times. if (torrent.progress === 1) { - return store.dispatch( + store.dispatch( fileActions.setFileDone( torrent.magnetURI, torrent.files )); + + return; } let lastMove = 0; @@ -761,10 +777,25 @@ export default class RoomClient }) })); - this._webTorrent.seed( - files, - { announceList: [ [ 'wss://tracker.lab.vvc.niif.hu:443' ] ] }, - (torrent) => + createTorrent(files, (err, torrent) => + { + if (err) + { + store.dispatch(requestActions.notify( + { + type : 'error', + text : intl.formatMessage({ + id : 'filesharing.unableToShare', + defaultMessage : 'Unable to share file' + }) + })); + + return; + } + + const existingTorrent = this._webTorrent.get(torrent); + + if (existingTorrent) { store.dispatch(requestActions.notify( { @@ -776,11 +807,35 @@ export default class RoomClient store.dispatch(fileActions.addFile( this._peerId, - torrent.magnetURI + existingTorrent.magnetURI )); - this._sendFile(torrent.magnetURI); - }); + this._sendFile(existingTorrent.magnetURI); + + return; + } + + this._webTorrent.seed( + files, + { announceList: [ [ this._tracker ] ] }, + (newTorrent) => + { + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'filesharing.successfulFileShare', + defaultMessage : 'File successfully shared' + }) + })); + + store.dispatch(fileActions.addFile( + this._peerId, + newTorrent.magnetURI + )); + + this._sendFile(newTorrent.magnetURI); + }); + }); } // { file, name, picture } @@ -807,62 +862,6 @@ export default class RoomClient } } - async getServerHistory() - { - logger.debug('getServerHistory()'); - - try - { - const { - chatHistory, - fileHistory, - lastNHistory, - locked, - lobbyPeers, - accessCode - } = await this.sendRequest('serverHistory'); - - (chatHistory.length > 0) && store.dispatch( - chatActions.addChatHistory(chatHistory)); - - (fileHistory.length > 0) && store.dispatch( - fileActions.addFileHistory(fileHistory)); - - if (lastNHistory.length > 0) - { - logger.debug('Got lastNHistory'); - - // Remove our self from list - const index = lastNHistory.indexOf(this._peerId); - - lastNHistory.splice(index, 1); - - this._spotlights.addSpeakerList(lastNHistory); - } - - locked ? - store.dispatch(roomActions.setRoomLocked()) : - store.dispatch(roomActions.setRoomUnLocked()); - - (lobbyPeers.length > 0) && lobbyPeers.forEach((peer) => - { - store.dispatch( - lobbyPeerActions.addLobbyPeer(peer.peerId)); - store.dispatch( - lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName)); - store.dispatch( - lobbyPeerActions.setLobbyPeerPicture(peer.picture)); - }); - - (accessCode != null) && store.dispatch( - roomActions.setAccessCode(accessCode)); - } - catch (error) - { - logger.error('getServerHistory() | failed: %o', error); - } - } - async muteMic() { logger.debug('muteMic()'); @@ -1086,6 +1085,37 @@ export default class RoomClient meActions.setAudioInProgress(false)); } + async changeAudioOutputDevice(deviceId) + { + logger.debug('changeAudioOutputDevice() [deviceId: %s]', deviceId); + + store.dispatch( + meActions.setAudioOutputInProgress(true)); + + try + { + const device = this._audioOutputDevices[deviceId]; + + if (!device) + throw new Error('Selected audio output device no longer avaibale'); + + logger.debug( + 'changeAudioOutputDevice() | new selected [audio output device:%o]', + device); + + store.dispatch(settingsActions.setSelectedAudioOutputDevice(deviceId)); + + await this._updateAudioOutputDevices(); + } + catch (error) + { + logger.error('changeAudioOutputDevice() failed: %o', error); + } + + store.dispatch( + meActions.setAudioOutputInProgress(false)); + } + async changeVideoResolution(resolution) { logger.debug('changeVideoResolution() [resolution: %s]', resolution); @@ -1114,14 +1144,41 @@ export default class RoomClient ...VIDEO_CONSTRAINS[resolution] } }); + + if (stream) + { + const track = stream.getVideoTracks()[0]; - const track = stream.getVideoTracks()[0]; - - await this._webcamProducer.replaceTrack({ track }); - - store.dispatch( - producerActions.setProducerTrack(this._webcamProducer.id, track)); + if (track) + { + if (this._webcamProducer) + { + await this._webcamProducer.replaceTrack({ track }); + } + else + { + this._webcamProducer = await this._sendTransport.produce({ + track, + appData : + { + source : 'webcam' + } + }); + } + store.dispatch( + producerActions.setProducerTrack(this._webcamProducer.id, track)); + } + else + { + logger.warn('getVideoTracks Error: First Video Track is null'); + } + + } + else + { + logger.warn('getUserMedia Error: Stream is null!'); + } store.dispatch(settingsActions.setSelectedWebcamDevice(deviceId)); store.dispatch(settingsActions.setVideoResolution(resolution)); @@ -1174,11 +1231,24 @@ export default class RoomClient if (track) { - await this._webcamProducer.replaceTrack({ track }); - + if (this._webcamProducer) + { + await this._webcamProducer.replaceTrack({ track }); + } + else + { + this._webcamProducer = await this._sendTransport.produce({ + track, + appData : + { + source : 'webcam' + } + }); + } + store.dispatch( producerActions.setProducerTrack(this._webcamProducer.id, track)); - + } else { @@ -1214,6 +1284,26 @@ export default class RoomClient roomActions.setSelectedPeer(peerId)); } + async promoteAllLobbyPeers() + { + logger.debug('promoteAllLobbyPeers()'); + + store.dispatch( + roomActions.setLobbyPeersPromotionInProgress(true)); + + try + { + await this.sendRequest('promoteAllPeers'); + } + catch (error) + { + logger.error('promoteAllLobbyPeers() [error:"%o"]', error); + } + + store.dispatch( + roomActions.setLobbyPeersPromotionInProgress(false)); + } + async promoteLobbyPeer(peerId) { logger.debug('promoteLobbyPeer() [peerId:"%s"]', peerId); @@ -1234,6 +1324,50 @@ export default class RoomClient lobbyPeerActions.setLobbyPeerPromotionInProgress(peerId, false)); } + async clearChat() + { + logger.debug('clearChat()'); + + store.dispatch( + roomActions.setClearChatInProgress(true)); + + try + { + await this.sendRequest('moderator:clearChat'); + + store.dispatch(chatActions.clearChat()); + } + catch (error) + { + logger.error('clearChat() failed: %o', error); + } + + store.dispatch( + roomActions.setClearChatInProgress(false)); + } + + async clearFileSharing() + { + logger.debug('clearFileSharing()'); + + store.dispatch( + roomActions.setClearFileSharingInProgress(true)); + + try + { + await this.sendRequest('moderator:clearFileSharing'); + + store.dispatch(fileActions.clearFiles()); + } + catch (error) + { + logger.error('clearFileSharing() failed: %o', error); + } + + store.dispatch( + roomActions.setClearFileSharingInProgress(false)); + } + async kickPeer(peerId) { logger.debug('kickPeer() [peerId:"%s"]', peerId); @@ -1409,30 +1543,30 @@ export default class RoomClient } } - async sendRaiseHandState(state) + async setRaisedHand(raisedHand) { - logger.debug('sendRaiseHandState: ', state); + logger.debug('setRaisedHand: ', raisedHand); store.dispatch( - meActions.setMyRaiseHandStateInProgress(true)); + meActions.setRaisedHandInProgress(true)); try { - await this.sendRequest('raiseHand', { raiseHandState: state }); + await this.sendRequest('raisedHand', { raisedHand }); store.dispatch( - meActions.setMyRaiseHandState(state)); + meActions.setRaisedHand(raisedHand)); } catch (error) { - logger.error('sendRaiseHandState() | failed: %o', error); + logger.error('setRaisedHand() | [error:"%o"]', error); // We need to refresh the component for it to render changed state - store.dispatch(meActions.setMyRaiseHandState(!state)); + store.dispatch(meActions.setRaisedHand(!raisedHand)); } store.dispatch( - meActions.setMyRaiseHandStateInProgress(false)); + meActions.setRaisedHandInProgress(false)); } async setMaxSendingSpatialLayer(spatialLayer) @@ -1506,6 +1640,13 @@ export default class RoomClient async _loadDynamicImports() { + ({ default: createTorrent } = await import( + + /* webpackPrefetch: true */ + /* webpackChunkName: "createtorrent" */ + 'create-torrent' + )); + ({ default: WebTorrent } = await import( /* webpackPrefetch: true */ @@ -1600,6 +1741,50 @@ export default class RoomClient }) })); + if (this._screenSharingProducer) + { + this._screenSharingProducer.close(); + + store.dispatch( + producerActions.removeProducer(this._screenSharingProducer.id)); + + this._screenSharingProducer = null; + } + + if (this._webcamProducer) + { + this._webcamProducer.close(); + + store.dispatch( + producerActions.removeProducer(this._webcamProducer.id)); + + this._webcamProducer = null; + } + + if (this._micProducer) + { + this._micProducer.close(); + + store.dispatch( + producerActions.removeProducer(this._micProducer.id)); + + this._micProducer = null; + } + + if (this._sendTransport) + { + this._sendTransport.close(); + + this._sendTransport = null; + } + + if (this._recvTransport) + { + this._recvTransport.close(); + + this._recvTransport = null; + } + store.dispatch(roomActions.setRoomState('connecting')); }); @@ -1728,7 +1913,7 @@ export default class RoomClient { // The exact formula to convert from dBs (-100..0) to linear (0..1) is: // Math.pow(10, dBs / 20) - // However it does not produce a visually useful output, so let exagerate + // However it does not produce a visually useful output, so let exaggerate // it a bit. Also, let convert it from 0..1 to 0..10 and avoid value 1 to // minimize component renderings. let volume = Math.round(Math.pow(10, dBs / 85) * 10); @@ -1788,6 +1973,13 @@ export default class RoomClient break; } + case 'overRoomLimit': + { + store.dispatch(roomActions.setOverRoomLimit(true)); + + break; + } + case 'roomReady': { const { turnServers } = notification.data; @@ -1801,6 +1993,13 @@ export default class RoomClient break; } + + case 'roomBack': + { + await this._joinRoom({ joinVideo }); + + break; + } case 'lockRoom': { @@ -1842,6 +2041,8 @@ export default class RoomClient lobbyPeerActions.addLobbyPeer(peerId)); store.dispatch( roomActions.setToolbarsVisible(true)); + + this._soundNotification(); store.dispatch(requestActions.notify( { @@ -1853,6 +2054,43 @@ export default class RoomClient break; } + + case 'parkedPeers': + { + const { lobbyPeers } = notification.data; + + if (lobbyPeers.length > 0) + { + lobbyPeers.forEach((peer) => + { + store.dispatch( + lobbyPeerActions.addLobbyPeer(peer.peerId)); + store.dispatch( + lobbyPeerActions.setLobbyPeerDisplayName( + peer.displayName, + peer.peerId + ) + ); + store.dispatch( + lobbyPeerActions.setLobbyPeerPicture(peer.picture)); + }); + + store.dispatch( + roomActions.setToolbarsVisible(true)); + + this._soundNotification(); + + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'room.newLobbyPeer', + defaultMessage : 'New participant entered the lobby' + }) + })); + } + + break; + } case 'lobby:peerClosed': { @@ -2012,6 +2250,58 @@ export default class RoomClient break; } + case 'raisedHand': + { + const { + peerId, + raisedHand, + raisedHandTimestamp + } = notification.data; + + store.dispatch( + peerActions.setPeerRaisedHand( + peerId, + raisedHand, + raisedHandTimestamp + ) + ); + + const { displayName } = store.getState().peers[peerId]; + + let text; + + if (raisedHand) + { + text = intl.formatMessage({ + id : 'room.raisedHand', + defaultMessage : '{displayName} raised their hand' + }, { + displayName + }); + } + else + { + text = intl.formatMessage({ + id : 'room.loweredHand', + defaultMessage : '{displayName} put their hand down' + }, { + displayName + }); + } + + if (displayName) + { + store.dispatch(requestActions.notify( + { + text + })); + } + + this._soundNotification(); + + break; + } + case 'chatMessage': { const { peerId, chatMessage } = notification.data; @@ -2033,6 +2323,21 @@ export default class RoomClient break; } + case 'moderator:clearChat': + { + store.dispatch(chatActions.clearChat()); + + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'moderator.clearChat', + defaultMessage : 'Moderator cleared the chat' + }) + })); + + break; + } + case 'sendFile': { const { peerId, magnetUri } = notification.data; @@ -2061,6 +2366,21 @@ export default class RoomClient break; } + case 'moderator:clearFileSharing': + { + store.dispatch(fileActions.clearFiles()); + + store.dispatch(requestActions.notify( + { + text : intl.formatMessage({ + id : 'moderator.clearFiles', + defaultMessage : 'Moderator cleared the files' + }) + })); + + break; + } + case 'producerScore': { const { producerId, score } = notification.data; @@ -2078,6 +2398,8 @@ export default class RoomClient store.dispatch( peerActions.addPeer({ id, displayName, picture, roles, consumers: [] })); + this._soundNotification(); + store.dispatch(requestActions.notify( { text : intl.formatMessage({ @@ -2187,8 +2509,8 @@ export default class RoomClient store.dispatch(requestActions.notify( { text : intl.formatMessage({ - id : 'moderator.mute', - defaultMessage : 'Moderator muted your microphone' + id : 'moderator.muteAudio', + defaultMessage : 'Moderator muted your audio' }) })); } @@ -2206,7 +2528,7 @@ export default class RoomClient store.dispatch(requestActions.notify( { text : intl.formatMessage({ - id : 'moderator.mute', + id : 'moderator.muteVideo', defaultMessage : 'Moderator stopped your video' }) })); @@ -2234,7 +2556,9 @@ export default class RoomClient { text : intl.formatMessage({ id : 'roles.gotRole', - defaultMessage : `You got the role: ${role}` + defaultMessage : 'You got the role: {role}' + }, { + role }) })); } @@ -2256,7 +2580,9 @@ export default class RoomClient { text : intl.formatMessage({ id : 'roles.lostRole', - defaultMessage : `You lost the role: ${role}` + defaultMessage : 'You lost the role: {role}' + }, { + role }) })); } @@ -2294,10 +2620,8 @@ export default class RoomClient { logger.debug('_joinRoom()'); - const { - displayName, - picture - } = store.getState().settings; + const { displayName } = store.getState().settings; + const { picture } = store.getState().me; try { @@ -2310,7 +2634,7 @@ export default class RoomClient } } }); - + this._webTorrent.on('error', (error) => { logger.error('Filesharing [error:"%o"]', error); @@ -2318,7 +2642,10 @@ export default class RoomClient store.dispatch(requestActions.notify( { type : 'error', - text : intl.formatMessage({ id: 'filesharing.error', defaultMessage: 'There was a filesharing error' }) + text : intl.formatMessage({ + id : 'filesharing.error', + defaultMessage : 'There was a filesharing error' + }) })); }); @@ -2327,6 +2654,9 @@ export default class RoomClient const routerRtpCapabilities = await this.sendRequest('getRouterRtpCapabilities'); + routerRtpCapabilities.headerExtensions = routerRtpCapabilities.headerExtensions + .filter((ext) => ext.uri !== 'urn:3gpp:video-orientation'); + await this._mediasoupDevice.load({ routerRtpCapabilities }); if (this._produce) @@ -2354,7 +2684,7 @@ export default class RoomClient dtlsParameters, iceServers : this._turnServers, // TODO: Fix for issue #72 - iceTransportPolicy : this._device.flag === 'firefox' ? 'relay' : undefined, + iceTransportPolicy : this._device.flag === 'firefox' && this._turnServers ? 'relay' : undefined, proprietaryConstraints : PC_PROPRIETARY_CONSTRAINTS }); @@ -2418,7 +2748,7 @@ export default class RoomClient dtlsParameters, iceServers : this._turnServers, // TODO: Fix for issue #72 - iceTransportPolicy : this._device.flag === 'firefox' ? 'relay' : undefined + iceTransportPolicy : this._device.flag === 'firefox' && this._turnServers ? 'relay' : undefined }); this._recvTransport.on( @@ -2444,7 +2774,20 @@ export default class RoomClient canShareFiles : this._torrentSupport })); - const { roles, peers } = await this.sendRequest( + const { + authenticated, + roles, + peers, + tracker, + permissionsFromRoles, + userRoles, + chatHistory, + fileHistory, + lastNHistory, + locked, + lobbyPeers, + accessCode + } = await this.sendRequest( 'join', { displayName : displayName, @@ -2452,7 +2795,19 @@ export default class RoomClient rtpCapabilities : this._mediasoupDevice.rtpCapabilities }); - logger.debug('_joinRoom() joined [peers:"%o", roles:"%o"]', peers, roles); + logger.debug( + '_joinRoom() joined [authenticated:"%s", peers:"%o", roles:"%o"]', + authenticated, + peers, + roles + ); + + tracker && (this._tracker = tracker); + + store.dispatch(meActions.loggedIn(authenticated)); + + store.dispatch(roomActions.setUserRoles(userRoles)); + store.dispatch(roomActions.setPermissionsFromRoles(permissionsFromRoles)); const myRoles = store.getState().me.roles; @@ -2466,7 +2821,9 @@ export default class RoomClient { text : intl.formatMessage({ id : 'roles.gotRole', - defaultMessage : `You got the role: ${role}` + defaultMessage : 'You got the role: {role}' + }, { + role }) })); } @@ -2486,7 +2843,39 @@ export default class RoomClient this.updateSpotlights(spotlights); }); - // Don't produce if explicitely requested to not to do it. + (chatHistory.length > 0) && store.dispatch( + chatActions.addChatHistory(chatHistory)); + + (fileHistory.length > 0) && store.dispatch( + fileActions.addFileHistory(fileHistory)); + + if (lastNHistory.length > 0) + { + logger.debug('_joinRoom() | got lastN history'); + + this._spotlights.addSpeakerList( + lastNHistory.filter((peerId) => peerId !== this._peerId) + ); + } + + locked ? + store.dispatch(roomActions.setRoomLocked()) : + store.dispatch(roomActions.setRoomUnLocked()); + + (lobbyPeers.length > 0) && lobbyPeers.forEach((peer) => + { + store.dispatch( + lobbyPeerActions.addLobbyPeer(peer.peerId)); + store.dispatch( + lobbyPeerActions.setLobbyPeerDisplayName(peer.displayName, peer.peerId)); + store.dispatch( + lobbyPeerActions.setLobbyPeerPicture(peer.picture)); + }); + + (accessCode != null) && store.dispatch( + roomActions.setAccessCode(accessCode)); + + // Don't produce if explicitly requested to not to do it. if (this._produce) { if (this._mediasoupDevice.canProduce('audio')) @@ -2500,14 +2889,25 @@ export default class RoomClient if (joinVideo && this._mediasoupDevice.canProduce('video')) this.enableWebcam(); } + + await this._updateAudioOutputDevices(); + const { selectedAudioOutputDevice } = store.getState().settings; + + if (!selectedAudioOutputDevice && this._audioOutputDevices !== {}) + { + store.dispatch( + settingsActions.setSelectedAudioOutputDevice( + Object.keys(this._audioOutputDevices)[0] + ) + ); + } + store.dispatch(roomActions.setRoomState('connected')); - // Clean all the existing notifcations. + // Clean all the existing notifications. store.dispatch(notificationActions.removeAllNotifications()); - this.getServerHistory(); - store.dispatch(requestActions.notify( { text : intl.formatMessage({ @@ -2657,6 +3057,159 @@ export default class RoomClient } } + async addExtraVideo(videoDeviceId) + { + logger.debug( + 'addExtraVideo() [videoDeviceId:"%s"]', + videoDeviceId + ); + + store.dispatch( + roomActions.setExtraVideoOpen(false)); + + if (!this._mediasoupDevice.canProduce('video')) + { + logger.error('enableWebcam() | cannot produce video'); + + return; + } + + let track; + + store.dispatch( + meActions.setWebcamInProgress(true)); + + try + { + const device = this._webcams[videoDeviceId]; + const resolution = store.getState().settings.resolution; + + if (!device) + throw new Error('no webcam devices'); + + logger.debug( + 'addExtraVideo() | new selected webcam [device:%o]', + device); + + logger.debug('_setWebcamProducer() | calling getUserMedia()'); + + const stream = await navigator.mediaDevices.getUserMedia( + { + video : + { + deviceId : { ideal: videoDeviceId }, + ...VIDEO_CONSTRAINS[resolution] + } + }); + + track = stream.getVideoTracks()[0]; + + let producer; + + if (this._useSimulcast) + { + // If VP9 is the only available video codec then use SVC. + const firstVideoCodec = this._mediasoupDevice + .rtpCapabilities + .codecs + .find((c) => c.kind === 'video'); + + let encodings; + + if (firstVideoCodec.mimeType.toLowerCase() === 'video/vp9') + encodings = VIDEO_KSVC_ENCODINGS; + else if ('simulcastEncodings' in window.config) + encodings = window.config.simulcastEncodings; + else + encodings = VIDEO_SIMULCAST_ENCODINGS; + + producer = await this._sendTransport.produce( + { + track, + encodings, + codecOptions : + { + videoGoogleStartBitrate : 1000 + }, + appData : + { + source : 'extravideo' + } + }); + } + else + { + producer = await this._sendTransport.produce({ + track, + appData : + { + source : 'extravideo' + } + }); + } + + this._extraVideoProducers.set(producer.id, producer); + + store.dispatch(producerActions.addProducer( + { + id : producer.id, + deviceLabel : device.label, + source : 'extravideo', + paused : producer.paused, + track : producer.track, + rtpParameters : producer.rtpParameters, + codec : producer.rtpParameters.codecs[0].mimeType.split('/')[1] + })); + + // store.dispatch(settingsActions.setSelectedWebcamDevice(deviceId)); + + await this._updateWebcams(); + + producer.on('transportclose', () => + { + this._extraVideoProducers.delete(producer.id); + + producer = null; + }); + + producer.on('trackended', () => + { + store.dispatch(requestActions.notify( + { + type : 'error', + text : intl.formatMessage({ + id : 'devices.cameraDisconnected', + defaultMessage : 'Camera disconnected' + }) + })); + + this.disableExtraVideo(producer.id) + .catch(() => {}); + }); + + logger.debug('addExtraVideo() succeeded'); + } + catch (error) + { + logger.error('addExtraVideo() failed:%o', error); + + store.dispatch(requestActions.notify( + { + type : 'error', + text : intl.formatMessage({ + id : 'devices.cameraError', + defaultMessage : 'An error occurred while accessing your camera' + }) + })); + + if (track) + track.stop(); + } + + store.dispatch( + meActions.setWebcamInProgress(false)); + } + async enableMic() { if (this._micProducer) @@ -2692,7 +3245,7 @@ export default class RoomClient const stream = await navigator.mediaDevices.getUserMedia( { audio : { - deviceId : { exact: deviceId }, + deviceId : { ideal: deviceId }, sampleRate : 48000, channelCount : 1, volume : 1.0, @@ -2771,7 +3324,7 @@ export default class RoomClient type : 'error', text : intl.formatMessage({ id : 'devices.microphoneError', - defaultMessage : 'An error occured while accessing your microphone' + defaultMessage : 'An error occurred while accessing your microphone' }) })); @@ -2938,7 +3491,7 @@ export default class RoomClient type : 'error', text : intl.formatMessage({ id : 'devices.screenSharingError', - defaultMessage : 'An error occured while accessing your screen' + defaultMessage : 'An error occurred while accessing your screen' }) })); @@ -3016,7 +3569,7 @@ export default class RoomClient { video : { - deviceId : { exact: deviceId }, + deviceId : { ideal: deviceId }, ...VIDEO_CONSTRAINS[resolution] } }); @@ -3111,7 +3664,7 @@ export default class RoomClient type : 'error', text : intl.formatMessage({ id : 'devices.cameraError', - defaultMessage : 'An error occured while accessing your camera' + defaultMessage : 'An error occurred while accessing your camera' }) })); @@ -3123,6 +3676,37 @@ export default class RoomClient meActions.setWebcamInProgress(false)); } + async disableExtraVideo(id) + { + logger.debug('disableExtraVideo()'); + + const producer = this._extraVideoProducers.get(id); + + if (!producer) + return; + + store.dispatch(meActions.setWebcamInProgress(true)); + + producer.close(); + + store.dispatch( + producerActions.removeProducer(id)); + + try + { + await this.sendRequest( + 'closeProducer', { producerId: id }); + } + catch (error) + { + logger.error('disableWebcam() [error:"%o"]', error); + } + + this._extraVideoProducers.delete(id); + + store.dispatch(meActions.setWebcamInProgress(false)); + } + async disableWebcam() { logger.debug('disableWebcam()'); @@ -3265,4 +3849,35 @@ export default class RoomClient logger.error('_getWebcamDeviceId() failed:%o', error); } } + + async _updateAudioOutputDevices() + { + logger.debug('_updateAudioOutputDevices()'); + + // Reset the list. + this._audioOutputDevices = {}; + + try + { + logger.debug('_updateAudioOutputDevices() | calling enumerateDevices()'); + + const devices = await navigator.mediaDevices.enumerateDevices(); + + for (const device of devices) + { + if (device.kind !== 'audiooutput') + continue; + + this._audioOutputDevices[device.deviceId] = device; + } + + store.dispatch( + meActions.setAudioOutputDevices(this._audioOutputDevices)); + } + catch (error) + { + logger.error('_updateAudioOutputDevices() failed:%o', error); + } + } + } diff --git a/app/src/ScreenShare.js b/app/src/ScreenShare.js index 180fe2a..2ff1bcc 100644 --- a/app/src/ScreenShare.js +++ b/app/src/ScreenShare.js @@ -225,10 +225,7 @@ export default class ScreenShare return new DisplayMediaScreenShare(); } case 'chrome': - { - return new DisplayMediaScreenShare(); - } - case 'msedge': + case 'edge': { return new DisplayMediaScreenShare(); } diff --git a/app/src/__tests__/Room.spec.js b/app/src/__tests__/Room.spec.js index a866e06..bd62322 100644 --- a/app/src/__tests__/Room.spec.js +++ b/app/src/__tests__/Room.spec.js @@ -31,6 +31,8 @@ beforeEach(() => me : { audioDevices : null, audioInProgress : false, + audioOutputDevices : null, + audioOutputInProgress : false, canSendMic : false, canSendWebcam : false, canShareFiles : false, @@ -40,8 +42,8 @@ beforeEach(() => loggedIn : false, loginEnabled : true, picture : null, - raiseHand : false, - raiseHandInProgress : false, + raisedHand : false, + raisedHandInProgress : false, screenShareInProgress : false, webcamDevices : null, webcamInProgress : false @@ -72,11 +74,12 @@ beforeEach(() => windowConsumer : null }, settings : { - advancedMode : true, - displayName : 'Jest Tester', - resolution : 'ultra', - selectedAudioDevice : 'default', - selectedWebcam : 'soifjsiajosjfoi' + advancedMode : true, + displayName : 'Jest Tester', + resolution : 'ultra', + selectedAudioDevice : 'default', + selectedAudioOutputDevice : 'default', + selectedWebcam : 'soifjsiajosjfoi' }, toolarea : { currentToolTab : 'chat', diff --git a/app/src/__tests__/RoomClient.spec.js b/app/src/__tests__/RoomClient.spec.js index 086e6a5..5e1191c 100644 --- a/app/src/__tests__/RoomClient.spec.js +++ b/app/src/__tests__/RoomClient.spec.js @@ -1,6 +1,6 @@ import RoomClient from '../RoomClient'; -describe('new RoomClient() without paramaters throws Error', () => +describe('new RoomClient() without parameters throws Error', () => { test('Matches the snapshot', () => { diff --git a/app/src/actions/chatActions.js b/app/src/actions/chatActions.js index c38d92d..f7b0cf3 100644 --- a/app/src/actions/chatActions.js +++ b/app/src/actions/chatActions.js @@ -14,4 +14,9 @@ export const addChatHistory = (chatHistory) => ({ type : 'ADD_CHAT_HISTORY', payload : { chatHistory } + }); + +export const clearChat = () => + ({ + type : 'CLEAR_CHAT' }); \ No newline at end of file diff --git a/app/src/actions/fileActions.js b/app/src/actions/fileActions.js index f9277d6..b5d90e5 100644 --- a/app/src/actions/fileActions.js +++ b/app/src/actions/fileActions.js @@ -32,4 +32,9 @@ export const setFileDone = (magnetUri, sharedFiles) => ({ type : 'SET_FILE_DONE', payload : { magnetUri, sharedFiles } + }); + +export const clearFiles = () => + ({ + type : 'CLEAR_FILES' }); \ No newline at end of file diff --git a/app/src/actions/meActions.js b/app/src/actions/meActions.js index fc72592..7fb34ea 100644 --- a/app/src/actions/meActions.js +++ b/app/src/actions/meActions.js @@ -4,9 +4,10 @@ export const setMe = ({ peerId, loginEnabled }) => payload : { peerId, loginEnabled } }); -export const setIsMobile = () => +export const setBrowser = (browser) => ({ - type : 'SET_IS_MOBILE' + type : 'SET_BROWSER', + payload : { browser } }); export const loggedIn = (flag) => @@ -50,15 +51,21 @@ export const setAudioDevices = (devices) => payload : { devices } }); +export const setAudioOutputDevices = (devices) => + ({ + type : 'SET_AUDIO_OUTPUT_DEVICES', + payload : { devices } + }); + export const setWebcamDevices = (devices) => ({ type : 'SET_WEBCAM_DEVICES', payload : { devices } }); -export const setMyRaiseHandState = (flag) => +export const setRaisedHand = (flag) => ({ - type : 'SET_MY_RAISE_HAND_STATE', + type : 'SET_RAISED_HAND', payload : { flag } }); @@ -67,6 +74,12 @@ export const setAudioInProgress = (flag) => type : 'SET_AUDIO_IN_PROGRESS', payload : { flag } }); + +export const setAudioOutputInProgress = (flag) => + ({ + type : 'SET_AUDIO_OUTPUT_IN_PROGRESS', + payload : { flag } + }); export const setWebcamInProgress = (flag) => ({ @@ -80,9 +93,9 @@ export const setScreenShareInProgress = (flag) => payload : { flag } }); -export const setMyRaiseHandStateInProgress = (flag) => +export const setRaisedHandInProgress = (flag) => ({ - type : 'SET_MY_RAISE_HAND_STATE_IN_PROGRESS', + type : 'SET_RAISED_HAND_IN_PROGRESS', payload : { flag } }); diff --git a/app/src/actions/peerActions.js b/app/src/actions/peerActions.js index dc41568..fee30a5 100644 --- a/app/src/actions/peerActions.js +++ b/app/src/actions/peerActions.js @@ -34,10 +34,10 @@ export const setPeerScreenInProgress = (peerId, flag) => payload : { peerId, flag } }); -export const setPeerRaiseHandState = (peerId, raiseHandState) => +export const setPeerRaisedHand = (peerId, raisedHand, raisedHandTimestamp) => ({ - type : 'SET_PEER_RAISE_HAND_STATE', - payload : { peerId, raiseHandState } + type : 'SET_PEER_RAISED_HAND', + payload : { peerId, raisedHand, raisedHandTimestamp } }); export const setPeerPicture = (peerId, picture) => diff --git a/app/src/actions/roomActions.js b/app/src/actions/roomActions.js index 6003b9e..b90bf1b 100644 --- a/app/src/actions/roomActions.js +++ b/app/src/actions/roomActions.js @@ -40,6 +40,12 @@ export const setSignInRequired = (signInRequired) => payload : { signInRequired } }); +export const setOverRoomLimit = (overRoomLimit) => + ({ + type : 'SET_OVER_ROOM_LIMIT', + payload : { overRoomLimit } + }); + export const setAccessCode = (accessCode) => ({ type : 'SET_ACCESS_CODE', @@ -52,13 +58,25 @@ export const setJoinByAccessCode = (joinByAccessCode) => payload : { joinByAccessCode } }); -export const setSettingsOpen = ({ settingsOpen }) => +export const setSettingsOpen = (settingsOpen) => ({ type : 'SET_SETTINGS_OPEN', payload : { settingsOpen } }); -export const setLockDialogOpen = ({ lockDialogOpen }) => +export const setExtraVideoOpen = (extraVideoOpen) => + ({ + type : 'SET_EXTRA_VIDEO_OPEN', + payload : { extraVideoOpen } + }); + +export const setSettingsTab = (tab) => + ({ + type : 'SET_SETTINGS_TAB', + payload : { tab } + }); + +export const setLockDialogOpen = (lockDialogOpen) => ({ type : 'SET_LOCK_DIALOG_OPEN', payload : { lockDialogOpen } @@ -111,6 +129,12 @@ export const toggleConsumerFullscreen = (consumerId) => payload : { consumerId } }); +export const setLobbyPeersPromotionInProgress = (flag) => + ({ + type : 'SET_LOBBY_PEERS_PROMOTION_IN_PROGRESS', + payload : { flag } + }); + export const setMuteAllInProgress = (flag) => ({ type : 'MUTE_ALL_IN_PROGRESS', @@ -127,4 +151,28 @@ export const setCloseMeetingInProgress = (flag) => ({ type : 'CLOSE_MEETING_IN_PROGRESS', payload : { flag } - }); \ No newline at end of file + }); + +export const setClearChatInProgress = (flag) => + ({ + type : 'CLEAR_CHAT_IN_PROGRESS', + payload : { flag } + }); + +export const setClearFileSharingInProgress = (flag) => + ({ + type : 'CLEAR_FILE_SHARING_IN_PROGRESS', + payload : { flag } + }); + +export const setUserRoles = (userRoles) => + ({ + type : 'SET_USER_ROLES', + payload : { userRoles } + }); + +export const setPermissionsFromRoles = (permissionsFromRoles) => + ({ + type : 'SET_PERMISSIONS_FROM_ROLES', + payload : { permissionsFromRoles } + }); diff --git a/app/src/actions/settingsActions.js b/app/src/actions/settingsActions.js index 68b4257..654600e 100644 --- a/app/src/actions/settingsActions.js +++ b/app/src/actions/settingsActions.js @@ -4,6 +4,12 @@ export const setSelectedAudioDevice = (deviceId) => payload : { deviceId } }); +export const setSelectedAudioOutputDevice = (deviceId) => + ({ + type : 'CHANGE_AUDIO_OUTPUT_DEVICE', + payload : { deviceId } + }); + export const setSelectedWebcamDevice = (deviceId) => ({ type : 'CHANGE_WEBCAM', @@ -71,6 +77,16 @@ export const toggleNoiseSuppression = () => type : 'TOGGLE_NOISE_SUPPRESSION' }); +export const toggleHiddenControls = () => + ({ + type : 'TOGGLE_HIDDEN_CONTROLS' + }); + +export const toggleNotificationSounds = () => + ({ + type : 'TOGGLE_NOTIFICATION_SOUNDS' + }); + export const setLastN = (lastN) => ({ type : 'SET_LAST_N', diff --git a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js index 94d04d2..9e73e82 100644 --- a/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js +++ b/app/src/components/AccessControl/LockDialog/ListLobbyPeer.js @@ -7,72 +7,16 @@ import { withRoomContext } from '../../../RoomContext'; import { useIntl } from 'react-intl'; import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; -import ListItemIcon from '@material-ui/core/ListItemIcon'; +import IconButton from '@material-ui/core/IconButton'; import ListItemAvatar from '@material-ui/core/ListItemAvatar'; import Avatar from '@material-ui/core/Avatar'; import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import PromoteIcon from '@material-ui/icons/OpenInBrowser'; import Tooltip from '@material-ui/core/Tooltip'; -const styles = (theme) => +const styles = () => ({ root : - { - padding : theme.spacing(1), - width : '100%', - overflow : 'hidden', - cursor : 'auto', - display : 'flex' - }, - avatar : - { - borderRadius : '50%', - height : '2rem' - }, - peerInfo : - { - fontSize : '1rem', - border : 'none', - display : 'flex', - paddingLeft : theme.spacing(1), - flexGrow : 1, - alignItems : 'center' - }, - controls : - { - float : 'right', - display : 'flex', - flexDirection : 'row', - justifyContent : 'flex-start', - alignItems : 'center' - }, - button : - { - flex : '0 0 auto', - margin : '0.3rem', - borderRadius : 2, - backgroundColor : 'rgba(0, 0, 0, 0.5)', - cursor : 'pointer', - transitionProperty : 'opacity, background-color', - transitionDuration : '0.15s', - width : 'var(--media-control-button-size)', - height : 'var(--media-control-button-size)', - opacity : 0.85, - '&:hover' : - { - opacity : 1 - }, - '&.disabled' : - { - pointerEvents : 'none', - backgroundColor : 'var(--media-control-botton-disabled)' - }, - '&.promote' : - { - backgroundColor : 'var(--media-control-botton-on)' - } - }, - ListItem : { alignItems : 'center' } @@ -83,6 +27,8 @@ const ListLobbyPeer = (props) => const { roomClient, peer, + promotionInProgress, + canPromote, classes } = props; @@ -92,7 +38,7 @@ const ListLobbyPeer = (props) => return ( defaultMessage : 'Click to let them in' })} > - { e.stopPropagation(); @@ -120,7 +69,7 @@ const ListLobbyPeer = (props) => }} > - +
); @@ -128,16 +77,22 @@ const ListLobbyPeer = (props) => ListLobbyPeer.propTypes = { - roomClient : PropTypes.any.isRequired, - advancedMode : PropTypes.bool, - peer : PropTypes.object.isRequired, - classes : PropTypes.object.isRequired + roomClient : PropTypes.any.isRequired, + advancedMode : PropTypes.bool, + peer : PropTypes.object.isRequired, + promotionInProgress : PropTypes.bool.isRequired, + canPromote : PropTypes.bool.isRequired, + classes : PropTypes.object.isRequired }; const mapStateToProps = (state, { id }) => { return { - peer : state.lobbyPeers[id] + peer : state.lobbyPeers[id], + promotionInProgress : state.room.lobbyPeersPromotionInProgress, + canPromote : + state.me.roles.some((role) => + state.room.permissionsFromRoles.PROMOTE_PEER.includes(role)) }; }; @@ -149,6 +104,10 @@ export default withRoomContext(connect( areStatesEqual : (next, prev) => { return ( + prev.room.permissionsFromRoles === next.room.permissionsFromRoles && + prev.room.lobbyPeersPromotionInProgress === + next.room.lobbyPeersPromotionInProgress && + prev.me.roles === next.me.roles && prev.lobbyPeers === next.lobbyPeers ); } diff --git a/app/src/components/AccessControl/LockDialog/LockDialog.js b/app/src/components/AccessControl/LockDialog/LockDialog.js index 57dd0ea..4d6cd24 100644 --- a/app/src/components/AccessControl/LockDialog/LockDialog.js +++ b/app/src/components/AccessControl/LockDialog/LockDialog.js @@ -15,14 +15,6 @@ import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; import Button from '@material-ui/core/Button'; -// import FormLabel from '@material-ui/core/FormLabel'; -// import FormControl from '@material-ui/core/FormControl'; -// import FormGroup from '@material-ui/core/FormGroup'; -// import FormControlLabel from '@material-ui/core/FormControlLabel'; -// import Checkbox from '@material-ui/core/Checkbox'; -// import InputLabel from '@material-ui/core/InputLabel'; -// import OutlinedInput from '@material-ui/core/OutlinedInput'; -// import Switch from '@material-ui/core/Switch'; import List from '@material-ui/core/List'; import ListSubheader from '@material-ui/core/ListSubheader'; import ListLobbyPeer from './ListLobbyPeer'; @@ -59,11 +51,11 @@ const styles = (theme) => }); const LockDialog = ({ - // roomClient, + roomClient, room, handleCloseLockDialog, - // handleAccessCode, lobbyPeers, + canPromote, classes }) => { @@ -71,7 +63,7 @@ const LockDialog = ({ handleCloseLockDialog({ lockDialogOpen: false })} + onClose={() => handleCloseLockDialog(false)} classes={{ paper : classes.dialogPaper }} @@ -82,54 +74,6 @@ const LockDialog = ({ defaultMessage='Lobby administration' /> - {/* - - Room lock - - - { - if (room.locked) - { - roomClient.unlockRoom(); - } - else - { - roomClient.lockRoom(); - } - }} - />} - label='Lock' - /> - TODO: access code - roomClient.setJoinByAccessCode(event.target.checked) - } - />} - label='Join by Access code' - /> - - handleAccessCode(event.target.value)} - > - - - - - - */} { lobbyPeers.length > 0 ? } - + + + + ); +}; + +ExtraVideo.propTypes = +{ + roomClient : PropTypes.object.isRequired, + extraVideoOpen : PropTypes.bool.isRequired, + webcamDevices : PropTypes.object, + handleCloseExtraVideo : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + webcamDevices : state.me.webcamDevices, + extraVideoOpen : state.room.extraVideoOpen + }); + +const mapDispatchToProps = { + handleCloseExtraVideo : roomActions.setExtraVideoOpen +}; + +export default withRoomContext(connect( + mapStateToProps, + mapDispatchToProps, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.me.webcamDevices === next.me.webcamDevices && + prev.room.extraVideoOpen === next.room.extraVideoOpen + ); + } + } +)(withStyles(styles)(ExtraVideo))); \ No newline at end of file diff --git a/app/src/components/Controls/MobileControls.js b/app/src/components/Controls/MobileControls.js deleted file mode 100644 index 48ab33e..0000000 --- a/app/src/components/Controls/MobileControls.js +++ /dev/null @@ -1,172 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { meProducersSelector } from '../Selectors'; -import { withStyles } from '@material-ui/core/styles'; -import * as appPropTypes from '../appPropTypes'; -import { withRoomContext } from '../../RoomContext'; -import Fab from '@material-ui/core/Fab'; -import Tooltip from '@material-ui/core/Tooltip'; -import MicIcon from '@material-ui/icons/Mic'; -import MicOffIcon from '@material-ui/icons/MicOff'; -import VideoIcon from '@material-ui/icons/Videocam'; -import VideoOffIcon from '@material-ui/icons/VideocamOff'; - -const styles = (theme) => - ({ - root : - { - position : 'fixed', - zIndex : 500, - display : 'flex', - flexDirection : 'row', - bottom : '0.5em', - left : '50%', - transform : 'translate(-50%, -0%)' - }, - fab : - { - margin : theme.spacing(1) - } - }); - -const MobileControls = (props) => -{ - const { - roomClient, - me, - micProducer, - webcamProducer, - classes - } = props; - - let micState; - - let micTip; - - if (!me.canSendMic) - { - micState = 'unsupported'; - micTip = 'Audio unsupported'; - } - else if (!micProducer) - { - micState = 'off'; - micTip = 'Activate audio'; - } - else if (!micProducer.locallyPaused && !micProducer.remotelyPaused) - { - micState = 'on'; - micTip = 'Mute audio'; - } - else - { - micState = 'muted'; - micTip = 'Unmute audio'; - } - - let webcamState; - - let webcamTip; - - if (!me.canSendWebcam) - { - webcamState = 'unsupported'; - webcamTip = 'Video unsupported'; - } - else if (webcamProducer) - { - webcamState = 'on'; - webcamTip = 'Stop video'; - } - else - { - webcamState = 'off'; - webcamTip = 'Start video'; - } - - return ( -
- -
- - { - if (micState === 'off') - roomClient.enableMic(); - else if (micState === 'on') - roomClient.muteMic(); - else - roomClient.unmuteMic(); - }} - > - { micState === 'on' ? - - : - - } - -
-
- -
- - { - webcamState === 'on' ? - roomClient.disableWebcam() : - roomClient.enableWebcam(); - }} - > - { webcamState === 'on' ? - - : - - } - -
-
-
- ); -}; - -MobileControls.propTypes = -{ - roomClient : PropTypes.any.isRequired, - me : appPropTypes.Me.isRequired, - micProducer : appPropTypes.Producer, - webcamProducer : appPropTypes.Producer, - classes : PropTypes.object.isRequired, - theme : PropTypes.object.isRequired -}; - -const mapStateToProps = (state) => - ({ - ...meProducersSelector(state), - me : state.me - }); - -export default withRoomContext(connect( - mapStateToProps, - null, - null, - { - areStatesEqual : (next, prev) => - { - return ( - prev.producers === next.producers && - prev.me === next.me - ); - } - } -)(withStyles(styles, { withTheme: true })(MobileControls))); \ No newline at end of file diff --git a/app/src/components/Controls/TopBar.js b/app/src/components/Controls/TopBar.js index 50a20c0..6422788 100644 --- a/app/src/components/Controls/TopBar.js +++ b/app/src/components/Controls/TopBar.js @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { useState } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { lobbyPeersKeySelector, - peersLengthSelector + peersLengthSelector, + raisedHandsSelector } from '../Selectors'; import * as appPropTypes from '../appPropTypes'; import { withRoomContext } from '../../RoomContext'; @@ -13,11 +14,16 @@ import * as toolareaActions from '../../actions/toolareaActions'; import { useIntl, FormattedMessage } from 'react-intl'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; +import MenuItem from '@material-ui/core/MenuItem'; +import Menu from '@material-ui/core/Menu'; +import Popover from '@material-ui/core/Popover'; import Typography from '@material-ui/core/Typography'; import IconButton from '@material-ui/core/IconButton'; import MenuIcon from '@material-ui/icons/Menu'; import Avatar from '@material-ui/core/Avatar'; import Badge from '@material-ui/core/Badge'; +import Paper from '@material-ui/core/Paper'; +import ExtensionIcon from '@material-ui/icons/Extension'; import AccountCircle from '@material-ui/icons/AccountCircle'; import FullScreenIcon from '@material-ui/icons/Fullscreen'; import FullScreenExitIcon from '@material-ui/icons/FullscreenExit'; @@ -26,8 +32,10 @@ import SecurityIcon from '@material-ui/icons/Security'; import PeopleIcon from '@material-ui/icons/People'; import LockIcon from '@material-ui/icons/Lock'; import LockOpenIcon from '@material-ui/icons/LockOpen'; +import VideoCallIcon from '@material-ui/icons/VideoCall'; import Button from '@material-ui/core/Button'; import Tooltip from '@material-ui/core/Tooltip'; +import MoreIcon from '@material-ui/icons/MoreVert'; const styles = (theme) => ({ @@ -72,14 +80,34 @@ const styles = (theme) => display : 'block' } }, - actionButtons : - { - display : 'flex' + sectionDesktop : { + display : 'none', + [theme.breakpoints.up('md')] : { + display : 'flex' + } + }, + sectionMobile : { + display : 'flex', + [theme.breakpoints.up('md')] : { + display : 'none' + } }, actionButton : { - margin : theme.spacing(1), - padding : 0 + margin : theme.spacing(1, 0), + padding : theme.spacing(0, 1) + }, + disabledButton : + { + margin : theme.spacing(1, 0) + }, + green : + { + color : 'rgba(0, 153, 0, 1)' + }, + moreAction : + { + margin : theme.spacing(0.5, 0, 0.5, 1.5) } }); @@ -118,6 +146,38 @@ const TopBar = (props) => { const intl = useIntl(); + const [ mobileMoreAnchorEl, setMobileMoreAnchorEl ] = useState(null); + const [ anchorEl, setAnchorEl ] = useState(null); + const [ currentMenu, setCurrentMenu ] = useState(null); + + const handleExited = () => + { + setCurrentMenu(null); + }; + + const handleMobileMenuOpen = (event) => + { + setMobileMoreAnchorEl(event.currentTarget); + }; + + const handleMobileMenuClose = () => + { + setMobileMoreAnchorEl(null); + }; + + const handleMenuOpen = (event, menu) => + { + setAnchorEl(event.currentTarget); + setCurrentMenu(menu); + }; + + const handleMenuClose = () => + { + setAnchorEl(null); + + handleMobileMenuClose(); + }; + const { roomClient, room, @@ -131,13 +191,20 @@ const TopBar = (props) => fullscreen, onFullscreen, setSettingsOpen, + setExtraVideoOpen, setLockDialogOpen, toggleToolArea, openUsersTab, unread, + canProduceExtraVideo, + canLock, + canPromote, classes } = props; + const isMenuOpen = Boolean(anchorEl); + const isMobileMenuOpen = Boolean(mobileMoreAnchorEl); + const lockTooltip = room.locked ? intl.formatMessage({ id : 'tooltip.unLockRoom', @@ -172,170 +239,200 @@ const TopBar = (props) => }); return ( - - - toggleToolArea()} - > - - - - - { window.config && window.config.logo && Logo } - - { window.config && window.config.title ? window.config.title : 'Multiparty meeting' } - -
-
- { fullscreenEnabled && - - - { fullscreen ? - - : - - } - - - } - + + + toggleToolArea()} > + + + + { window.config.logo && Logo } + + { window.config.title ? window.config.title : 'Multiparty meeting' } + +
+
+ handleMenuOpen(event, 'moreActions')} + color='inherit' + > + + + { fullscreenEnabled && + + + { fullscreen ? + + : + + } + + + } + openUsersTab()} > - openUsersTab()} > - - - - - - + + + + + setSettingsOpen(!room.settingsOpen)} - > - - - - - - { - if (room.locked) - { - roomClient.unlockRoom(); - } - else - { - roomClient.lockRoom(); - } - }} - > - { room.locked ? - - : - - } - - - { lobbyPeers.length > 0 && - setLockDialogOpen(!room.lockDialogOpen)} - > - - - - - - } - { loginEnabled && - - - { - loggedIn ? roomClient.logout() : roomClient.login(); - }} + onClick={() => setSettingsOpen(!room.settingsOpen)} > - { myPicture ? - - : - - } + - } + + + + { + if (room.locked) + { + roomClient.unlockRoom(); + } + else + { + roomClient.lockRoom(); + } + }} + > + { room.locked ? + + : + + } + + + + { lobbyPeers.length > 0 && + + + setLockDialogOpen(!room.lockDialogOpen)} + > + + + + + + + } + { loginEnabled && + + + { + loggedIn ? roomClient.logout() : roomClient.login(); + }} + > + { myPicture ? + + : + + } + + + } +
+
+ + + +
-
- - + + + + { currentMenu === 'moreActions' && + + + { + handleMenuClose(); + setExtraVideoOpen(!room.extraVideoOpen); + }} + > + +

+ +

+
+
+ } +
+ + { loginEnabled && + + { + handleMenuClose(); + loggedIn ? roomClient.logout() : roomClient.login(); + }} + > + { myPicture ? + + : + + } + { loggedIn ? +

+ +

+ : +

+ +

+ } +
+ } + + { + handleMenuClose(); + + if (room.locked) + { + roomClient.unlockRoom(); + } + else + { + roomClient.lockRoom(); + } + }} + > + { room.locked ? + + : + + } + { room.locked ? +

+ +

+ : +

+ +

+ } +
+ + { + handleMenuClose(); + setSettingsOpen(!room.settingsOpen); + }} + > + +

+ +

+
+ { lobbyPeers.length > 0 && + + { + handleMenuClose(); + setLockDialogOpen(!room.lockDialogOpen); + }} + > + + + +

+ +

+
+ } + + { + handleMenuClose(); + openUsersTab(); + }} + > + + + +

+ +

+
+ { fullscreenEnabled && + + { + handleMenuClose(); + onFullscreen(); + }} + > + { fullscreen ? + + : + + } +

+ +

+
+ } + handleMenuOpen(event, 'moreActions')} + > + +

+ +

+
+
+ ); }; TopBar.propTypes = { - roomClient : PropTypes.object.isRequired, - room : appPropTypes.Room.isRequired, - peersLength : PropTypes.number, - lobbyPeers : PropTypes.array, - permanentTopBar : PropTypes.bool, - myPicture : PropTypes.string, - loggedIn : PropTypes.bool.isRequired, - loginEnabled : PropTypes.bool.isRequired, - fullscreenEnabled : PropTypes.bool, - fullscreen : PropTypes.bool, - onFullscreen : PropTypes.func.isRequired, - setToolbarsVisible : PropTypes.func.isRequired, - setSettingsOpen : PropTypes.func.isRequired, - setLockDialogOpen : PropTypes.func.isRequired, - toggleToolArea : PropTypes.func.isRequired, - openUsersTab : PropTypes.func.isRequired, - unread : PropTypes.number.isRequired, - classes : PropTypes.object.isRequired, - theme : PropTypes.object.isRequired + roomClient : PropTypes.object.isRequired, + room : appPropTypes.Room.isRequired, + peersLength : PropTypes.number, + lobbyPeers : PropTypes.array, + permanentTopBar : PropTypes.bool, + myPicture : PropTypes.string, + loggedIn : PropTypes.bool.isRequired, + loginEnabled : PropTypes.bool.isRequired, + fullscreenEnabled : PropTypes.bool, + fullscreen : PropTypes.bool, + onFullscreen : PropTypes.func.isRequired, + setToolbarsVisible : PropTypes.func.isRequired, + setSettingsOpen : PropTypes.func.isRequired, + setExtraVideoOpen : PropTypes.func.isRequired, + setLockDialogOpen : PropTypes.func.isRequired, + toggleToolArea : PropTypes.func.isRequired, + openUsersTab : PropTypes.func.isRequired, + unread : PropTypes.number.isRequired, + canProduceExtraVideo : PropTypes.bool.isRequired, + canLock : PropTypes.bool.isRequired, + canPromote : PropTypes.bool.isRequired, + classes : PropTypes.object.isRequired, + theme : PropTypes.object.isRequired }; const mapStateToProps = (state) => @@ -391,7 +715,16 @@ const mapStateToProps = (state) => loginEnabled : state.me.loginEnabled, myPicture : state.me.picture, unread : state.toolarea.unreadMessages + - state.toolarea.unreadFiles + state.toolarea.unreadFiles + raisedHandsSelector(state), + canProduceExtraVideo : + state.me.roles.some((role) => + state.room.permissionsFromRoles.EXTRA_VIDEO.includes(role)), + canLock : + state.me.roles.some((role) => + state.room.permissionsFromRoles.CHANGE_ROOM_LOCK.includes(role)), + canPromote : + state.me.roles.some((role) => + state.room.permissionsFromRoles.PROMOTE_PEER.includes(role)) }); const mapDispatchToProps = (dispatch) => @@ -402,11 +735,15 @@ const mapDispatchToProps = (dispatch) => }, setSettingsOpen : (settingsOpen) => { - dispatch(roomActions.setSettingsOpen({ settingsOpen })); + dispatch(roomActions.setSettingsOpen(settingsOpen)); + }, + setExtraVideoOpen : (extraVideoOpen) => + { + dispatch(roomActions.setExtraVideoOpen(extraVideoOpen)); }, setLockDialogOpen : (lockDialogOpen) => { - dispatch(roomActions.setLockDialogOpen({ lockDialogOpen })); + dispatch(roomActions.setLockDialogOpen(lockDialogOpen)); }, toggleToolArea : () => { @@ -434,9 +771,10 @@ export default withRoomContext(connect( prev.me.loggedIn === next.me.loggedIn && prev.me.loginEnabled === next.me.loginEnabled && prev.me.picture === next.me.picture && + prev.me.roles === next.me.roles && prev.toolarea.unreadMessages === next.toolarea.unreadMessages && prev.toolarea.unreadFiles === next.toolarea.unreadFiles ); } } -)(withStyles(styles, { withTheme: true })(TopBar))); \ No newline at end of file +)(withStyles(styles, { withTheme: true })(TopBar))); diff --git a/app/src/components/JoinDialog.js b/app/src/components/JoinDialog.js index 814a18d..3d2596f 100644 --- a/app/src/components/JoinDialog.js +++ b/app/src/components/JoinDialog.js @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../RoomContext'; +import classnames from 'classnames'; import isElectron from 'is-electron'; import * as settingsActions from '../actions/settingsActions'; import PropTypes from 'prop-types'; @@ -82,6 +83,10 @@ const styles = (theme) => green : { color : 'rgba(0, 153, 0, 1)' + }, + red : + { + color : 'rgba(153, 0, 0, 1)' } }); @@ -103,7 +108,7 @@ const DialogTitle = withStyles(styles)((props) => }; }, []); - const { children, classes, myPicture, onLogin, ...other } = props; + const { children, classes, myPicture, onLogin, loggedIn, ...other } = props; const handleTooltipClose = () => { @@ -115,19 +120,27 @@ const DialogTitle = withStyles(styles)((props) => setOpen(true); }; + const loginTooltip = loggedIn ? + intl.formatMessage({ + id : 'tooltip.logout', + defaultMessage : 'Log out' + }) + : + intl.formatMessage({ + id : 'tooltip.login', + defaultMessage : 'Log in' + }); + return ( - { window.config && window.config.logo && Logo } + { window.config.logo && Logo } {children} - { window.config && window.config.loginEnabled && + { window.config.loginEnabled && { myPicture ? : - + } @@ -207,12 +222,13 @@ const JoinDialog = ({ > + onLogin={() => { - loggedIn ? roomClient.logout() : roomClient.login(); + loggedIn ? roomClient.logout() : roomClient.login(roomId); }} + loggedIn={loggedIn} > - { window.config && window.config.title ? window.config.title : 'Multiparty meeting' } + { window.config.title ? window.config.title : 'Multiparty meeting' }
@@ -269,6 +285,16 @@ const JoinDialog = ({ }} fullWidth /> + {!room.inLobby && room.overRoomLimit && + + + + } @@ -307,6 +333,7 @@ const JoinDialog = ({ className={classes.green} gutterBottom variant='h6' + style={{ fontWeight: '600' }} align='center' > { room.signInRequired ? - + : - + return ( + diff --git a/app/src/components/MeetingDrawer/Chat/ChatInput.js b/app/src/components/MeetingDrawer/Chat/ChatInput.js index 5be44e9..480bb26 100644 --- a/app/src/components/MeetingDrawer/Chat/ChatInput.js +++ b/app/src/components/MeetingDrawer/Chat/ChatInput.js @@ -54,6 +54,7 @@ const ChatInput = (props) => roomClient, displayName, picture, + canChat, classes } = props; @@ -66,6 +67,7 @@ const ChatInput = (props) => defaultMessage : 'Enter chat message...' })} value={message || ''} + disabled={!canChat} onChange={handleChange} onKeyPress={(ev) => { @@ -89,6 +91,7 @@ const ChatInput = (props) => color='primary' className={classes.iconButton} aria-label='Send' + disabled={!canChat} onClick={() => { if (message && message !== '') @@ -112,13 +115,17 @@ ChatInput.propTypes = roomClient : PropTypes.object.isRequired, displayName : PropTypes.string, picture : PropTypes.string, + canChat : PropTypes.bool.isRequired, classes : PropTypes.object.isRequired }; const mapStateToProps = (state) => ({ displayName : state.settings.displayName, - picture : state.me.picture + picture : state.me.picture, + canChat : + state.me.roles.some((role) => + state.room.permissionsFromRoles.SEND_CHAT.includes(role)) }); export default withRoomContext( @@ -130,6 +137,8 @@ export default withRoomContext( areStatesEqual : (next, prev) => { return ( + prev.room.permissionsFromRoles === next.room.permissionsFromRoles && + prev.me.roles === next.me.roles && prev.settings.displayName === next.settings.displayName && prev.me.picture === next.me.picture ); diff --git a/app/src/components/MeetingDrawer/Chat/ChatModerator.js b/app/src/components/MeetingDrawer/Chat/ChatModerator.js new file mode 100644 index 0000000..a35675b --- /dev/null +++ b/app/src/components/MeetingDrawer/Chat/ChatModerator.js @@ -0,0 +1,100 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { withRoomContext } from '../../../RoomContext'; +import { withStyles } from '@material-ui/core/styles'; +import { useIntl, FormattedMessage } from 'react-intl'; +import Button from '@material-ui/core/Button'; + +const styles = (theme) => + ({ + root : + { + display : 'flex', + padding : theme.spacing(1), + boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)', + backgroundColor : 'rgba(255, 255, 255, 1)' + }, + listheader : + { + padding : theme.spacing(1), + fontWeight : 'bolder' + }, + actionButton : + { + marginLeft : 'auto' + } + }); + +const ChatModerator = (props) => +{ + const intl = useIntl(); + + const { + roomClient, + isChatModerator, + room, + classes + } = props; + + if (!isChatModerator) + return null; + + return ( +
    +
  • + +
  • + +
+ ); +}; + +ChatModerator.propTypes = +{ + roomClient : PropTypes.any.isRequired, + isChatModerator : PropTypes.bool, + room : PropTypes.object, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + isChatModerator : + state.me.roles.some((role) => + state.room.permissionsFromRoles.MODERATE_CHAT.includes(role)), + room : state.room + }); + +export default withRoomContext(connect( + mapStateToProps, + null, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room === next.room && + prev.me === next.me + ); + } + } +)(withStyles(styles)(ChatModerator))); \ No newline at end of file diff --git a/app/src/components/MeetingDrawer/Chat/Message.js b/app/src/components/MeetingDrawer/Chat/Message.js index a60c245..b770689 100644 --- a/app/src/components/MeetingDrawer/Chat/Message.js +++ b/app/src/components/MeetingDrawer/Chat/Message.js @@ -6,6 +6,7 @@ import DOMPurify from 'dompurify'; import marked from 'marked'; import Paper from '@material-ui/core/Paper'; import Typography from '@material-ui/core/Typography'; +import { useIntl } from 'react-intl'; const linkRenderer = new marked.Renderer(); @@ -55,6 +56,8 @@ const styles = (theme) => const Message = (props) => { + const intl = useIntl(); + const { self, picture, @@ -88,7 +91,16 @@ const Message = (props) => } ) }} /> - {self ? 'Me' : name} - {time} + + { self ? + intl.formatMessage({ + id : 'room.me', + defaultMessage : 'Me' + }) + : + name + } - {time} +
); diff --git a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js index 8713134..78ba569 100644 --- a/app/src/components/MeetingDrawer/FileSharing/FileSharing.js +++ b/app/src/components/MeetingDrawer/FileSharing/FileSharing.js @@ -5,6 +5,7 @@ import { withStyles } from '@material-ui/core/styles'; import { withRoomContext } from '../../../RoomContext'; import { useIntl } from 'react-intl'; import FileList from './FileList'; +import FileSharingModerator from './FileSharingModerator'; import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; @@ -24,6 +25,10 @@ const styles = (theme) => button : { margin : theme.spacing(1) + }, + shareButtonsWrapper : + { + display : 'flex' } }); @@ -35,12 +40,14 @@ const FileSharing = (props) => { if (event.target.files.length > 0) { - props.roomClient.shareFiles(event.target.files); + await props.roomClient.shareFiles(event.target.files); } }; const { canShareFiles, + browser, + canShare, classes } = props; @@ -55,25 +62,61 @@ const FileSharing = (props) => defaultMessage : 'File sharing not supported' }); + const buttonGalleryDescription = canShareFiles ? + intl.formatMessage({ + id : 'label.shareGalleryFile', + defaultMessage : 'Share image' + }) + : + intl.formatMessage({ + id : 'label.fileSharingUnsupported', + defaultMessage : 'File sharing not supported' + }); + return ( - - - + +
+ (e.target.value = null)} + id='share-files-button' + /> + + + { + (browser.platform === 'mobile') && canShareFiles && canShare && + } +
); @@ -81,8 +124,10 @@ const FileSharing = (props) => FileSharing.propTypes = { roomClient : PropTypes.any.isRequired, + browser : PropTypes.object.isRequired, canShareFiles : PropTypes.bool.isRequired, tabOpen : PropTypes.bool.isRequired, + canShare : PropTypes.bool.isRequired, classes : PropTypes.object.isRequired }; @@ -90,10 +135,28 @@ const mapStateToProps = (state) => { return { canShareFiles : state.me.canShareFiles, - tabOpen : state.toolarea.currentToolTab === 'files' + browser : state.me.browser, + tabOpen : state.toolarea.currentToolTab === 'files', + canShare : + state.me.roles.some((role) => + state.room.permissionsFromRoles.SHARE_FILE.includes(role)) }; }; export default withRoomContext(connect( - mapStateToProps + mapStateToProps, + null, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room.permissionsFromRoles === next.room.permissionsFromRoles && + prev.me.browser === next.me.browser && + prev.me.roles === next.me.roles && + prev.me.canShareFiles === next.me.canShareFiles && + prev.toolarea.currentToolTab === next.toolarea.currentToolTab + ); + } + } )(withStyles(styles)(FileSharing))); diff --git a/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js b/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js new file mode 100644 index 0000000..e38e54c --- /dev/null +++ b/app/src/components/MeetingDrawer/FileSharing/FileSharingModerator.js @@ -0,0 +1,100 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { withRoomContext } from '../../../RoomContext'; +import { withStyles } from '@material-ui/core/styles'; +import { useIntl, FormattedMessage } from 'react-intl'; +import Button from '@material-ui/core/Button'; + +const styles = (theme) => + ({ + root : + { + display : 'flex', + padding : theme.spacing(1), + boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)', + backgroundColor : 'rgba(255, 255, 255, 1)' + }, + listheader : + { + padding : theme.spacing(1), + fontWeight : 'bolder' + }, + actionButton : + { + marginLeft : 'auto' + } + }); + +const FileSharingModerator = (props) => +{ + const intl = useIntl(); + + const { + roomClient, + isFileSharingModerator, + room, + classes + } = props; + + if (!isFileSharingModerator) + return null; + + return ( +
    +
  • + +
  • + +
+ ); +}; + +FileSharingModerator.propTypes = +{ + roomClient : PropTypes.any.isRequired, + isFileSharingModerator : PropTypes.bool, + room : PropTypes.object, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + isFileSharingModerator : + state.me.roles.some((role) => + state.room.permissionsFromRoles.MODERATE_FILES.includes(role)), + room : state.room + }); + +export default withRoomContext(connect( + mapStateToProps, + null, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room === next.room && + prev.me === next.me + ); + } + } +)(withStyles(styles)(FileSharingModerator))); \ No newline at end of file diff --git a/app/src/components/MeetingDrawer/MeetingDrawer.js b/app/src/components/MeetingDrawer/MeetingDrawer.js index 0f81cde..4ac0831 100644 --- a/app/src/components/MeetingDrawer/MeetingDrawer.js +++ b/app/src/components/MeetingDrawer/MeetingDrawer.js @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; +import { raisedHandsSelector } from '../Selectors'; import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; import * as toolareaActions from '../../actions/toolareaActions'; @@ -51,6 +52,7 @@ const MeetingDrawer = (props) => currentToolTab, unreadMessages, unreadFiles, + raisedHands, closeDrawer, setToolTab, classes, @@ -93,10 +95,14 @@ const MeetingDrawer = (props) => } /> + {intl.formatMessage({ + id : 'label.participants', + defaultMessage : 'Participants' + })} + + } /> @@ -116,16 +122,21 @@ MeetingDrawer.propTypes = setToolTab : PropTypes.func.isRequired, unreadMessages : PropTypes.number.isRequired, unreadFiles : PropTypes.number.isRequired, + raisedHands : PropTypes.number.isRequired, closeDrawer : PropTypes.func.isRequired, classes : PropTypes.object.isRequired, theme : PropTypes.object.isRequired }; -const mapStateToProps = (state) => ({ - currentToolTab : state.toolarea.currentToolTab, - unreadMessages : state.toolarea.unreadMessages, - unreadFiles : state.toolarea.unreadFiles -}); +const mapStateToProps = (state) => +{ + return { + currentToolTab : state.toolarea.currentToolTab, + unreadMessages : state.toolarea.unreadMessages, + unreadFiles : state.toolarea.unreadFiles, + raisedHands : raisedHandsSelector(state) + }; +}; const mapDispatchToProps = { setToolTab : toolareaActions.setToolTab @@ -141,7 +152,8 @@ export default connect( return ( prev.toolarea.currentToolTab === next.toolarea.currentToolTab && prev.toolarea.unreadMessages === next.toolarea.unreadMessages && - prev.toolarea.unreadFiles === next.toolarea.unreadFiles + prev.toolarea.unreadFiles === next.toolarea.unreadFiles && + prev.peers === next.peers ); } } diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js index 304cbb9..d230db2 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListMe.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListMe.js @@ -1,79 +1,50 @@ import React from 'react'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; -import classnames from 'classnames'; +import { withRoomContext } from '../../../RoomContext'; import PropTypes from 'prop-types'; import * as appPropTypes from '../../appPropTypes'; +import { useIntl } from 'react-intl'; +import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; +import PanIcon from '@material-ui/icons/PanTool'; import EmptyAvatar from '../../../images/avatar-empty.jpeg'; -import HandIcon from '../../../images/icon-hand-white.svg'; const styles = (theme) => ({ root : { - padding : theme.spacing(1), width : '100%', overflow : 'hidden', cursor : 'auto', display : 'flex' }, - listPeer : - { - display : 'flex' - }, avatar : { borderRadius : '50%', - height : '2rem' + height : '2rem', + marginTop : theme.spacing(1) }, peerInfo : { fontSize : '1rem', - border : 'none', display : 'flex', paddingLeft : theme.spacing(1), flexGrow : 1, alignItems : 'center' }, - indicators : + green : { - left : 0, - top : 0, - display : 'flex', - flexDirection : 'row', - justifyContent : 'flex-start', - alignItems : 'center', - transition : 'opacity 0.3s' - }, - icon : - { - flex : '0 0 auto', - margin : '0.3rem', - borderRadius : 2, - backgroundPosition : 'center', - backgroundSize : '75%', - backgroundRepeat : 'no-repeat', - backgroundColor : 'rgba(0, 0, 0, 0.5)', - transitionProperty : 'opacity, background-color', - transitionDuration : '0.15s', - width : 'var(--media-control-button-size)', - height : 'var(--media-control-button-size)', - opacity : 0.85, - '&:hover' : - { - opacity : 1 - }, - '&.raise-hand' : - { - backgroundImage : `url(${HandIcon})`, - opacity : 1 - } + color : 'rgba(0, 153, 0, 1)' } }); const ListMe = (props) => { + const intl = useIntl(); + const { + roomClient, me, settings, classes @@ -82,29 +53,47 @@ const ListMe = (props) => const picture = me.picture || EmptyAvatar; return ( -
  • -
    - My avatar +
    + My avatar -
    - {settings.displayName} -
    - -
    - { me.raisedHand && -
    - } -
    +
    + {settings.displayName}
    -
  • + + + { + e.stopPropagation(); + + roomClient.setRaisedHand(!me.raisedHand); + }} + > + + + +
    ); }; ListMe.propTypes = { - me : appPropTypes.Me.isRequired, - settings : PropTypes.object.isRequired, - classes : PropTypes.object.isRequired + roomClient : PropTypes.object.isRequired, + me : appPropTypes.Me.isRequired, + settings : PropTypes.object.isRequired, + classes : PropTypes.object.isRequired }; const mapStateToProps = (state) => ({ @@ -112,7 +101,7 @@ const mapStateToProps = (state) => ({ settings : state.settings }); -export default connect( +export default withRoomContext(connect( mapStateToProps, null, null, @@ -125,4 +114,4 @@ export default connect( ); } } -)(withStyles(styles)(ListMe)); +)(withStyles(styles)(ListMe))); diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js b/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js index 1c711a1..c10506a 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListModerator.js @@ -10,14 +10,7 @@ const styles = (theme) => ({ root : { - padding : theme.spacing(1), - width : '100%', - overflow : 'hidden', - cursor : 'auto', - display : 'flex' - }, - actionButtons : - { + padding : theme.spacing(1), display : 'flex' }, divider : @@ -43,7 +36,6 @@ const ListModerator = (props) => id : 'room.muteAll', defaultMessage : 'Mute all' })} - className={classes.actionButton} variant='contained' color='secondary' disabled={room.muteAllInProgress} @@ -60,7 +52,6 @@ const ListModerator = (props) => id : 'room.stopAllVideo', defaultMessage : 'Stop all video' })} - className={classes.actionButton} variant='contained' color='secondary' disabled={room.stopAllVideoInProgress} @@ -77,7 +68,6 @@ const ListModerator = (props) => id : 'room.closeMeeting', defaultMessage : 'Close meeting' })} - className={classes.actionButton} variant='contained' color='secondary' disabled={room.closeMeetingInProgress} diff --git a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js index 7fd0383..1aa70a1 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ListPeer.js @@ -3,42 +3,39 @@ import { connect } from 'react-redux'; import { makePeerConsumerSelector } from '../../Selectors'; import { withStyles } from '@material-ui/core/styles'; import PropTypes from 'prop-types'; -import classnames from 'classnames'; import * as appPropTypes from '../../appPropTypes'; import { withRoomContext } from '../../../RoomContext'; import { useIntl } from 'react-intl'; import IconButton from '@material-ui/core/IconButton'; -import MicIcon from '@material-ui/icons/Mic'; -import MicOffIcon from '@material-ui/icons/MicOff'; +import Tooltip from '@material-ui/core/Tooltip'; +import VideocamIcon from '@material-ui/icons/Videocam'; +import VideocamOffIcon from '@material-ui/icons/VideocamOff'; +import VolumeUpIcon from '@material-ui/icons/VolumeUp'; +import VolumeOffIcon from '@material-ui/icons/VolumeOff'; import ScreenIcon from '@material-ui/icons/ScreenShare'; import ScreenOffIcon from '@material-ui/icons/StopScreenShare'; import ExitIcon from '@material-ui/icons/ExitToApp'; import EmptyAvatar from '../../../images/avatar-empty.jpeg'; -import HandIcon from '../../../images/icon-hand-white.svg'; +import PanIcon from '@material-ui/icons/PanTool'; const styles = (theme) => ({ root : { - padding : theme.spacing(1), width : '100%', overflow : 'hidden', cursor : 'auto', display : 'flex' }, - listPeer : - { - display : 'flex' - }, avatar : { borderRadius : '50%', - height : '2rem' + height : '2rem', + marginTop : theme.spacing(1) }, peerInfo : { fontSize : '1rem', - border : 'none', display : 'flex', paddingLeft : theme.spacing(1), flexGrow : 1, @@ -46,52 +43,12 @@ const styles = (theme) => }, indicators : { - left : 0, - top : 0, - display : 'flex', - flexDirection : 'row', - justifyContent : 'flex-start', - alignItems : 'center', - transition : 'opacity 0.3s' + display : 'flex', + padding : theme.spacing(1.5) }, - icon : + green : { - flex : '0 0 auto', - margin : '0.3rem', - borderRadius : 2, - backgroundPosition : 'center', - backgroundSize : '75%', - backgroundRepeat : 'no-repeat', - backgroundColor : 'rgba(0, 0, 0, 0.5)', - transitionProperty : 'opacity, background-color', - transitionDuration : '0.15s', - width : 'var(--media-control-button-size)', - height : 'var(--media-control-button-size)', - opacity : 0.85, - '&:hover' : - { - opacity : 1 - }, - '&.on' : - { - opacity : 1 - }, - '&.off' : - { - opacity : 0.2 - }, - '&.raise-hand' : - { - backgroundImage : `url(${HandIcon})` - } - }, - controls : - { - float : 'right', - display : 'flex', - flexDirection : 'row', - justifyContent : 'flex-start', - alignItems : 'center' + color : 'rgba(0, 153, 0, 1)' } }); @@ -104,11 +61,18 @@ const ListPeer = (props) => isModerator, peer, micConsumer, + webcamConsumer, screenConsumer, children, classes } = props; + const webcamEnabled = ( + Boolean(webcamConsumer) && + !webcamConsumer.locallyPaused && + !webcamConsumer.remotelyPaused + ); + const micEnabled = ( Boolean(micConsumer) && !micConsumer.locallyPaused && @@ -131,21 +95,18 @@ const ListPeer = (props) => {peer.displayName}
    - { peer.raiseHandState && -
    + { peer.raisedHand && + }
    - {children} -
    - { screenConsumer && + { screenConsumer && + })} color={screenVisible ? 'primary' : 'secondary'} disabled={peer.peerScreenInProgress} - onClick={() => + onClick={(e) => { + e.stopPropagation(); + screenVisible ? roomClient.modifyPeerConsumer(peer.id, 'screen', true) : roomClient.modifyPeerConsumer(peer.id, 'screen', false); @@ -166,7 +129,45 @@ const ListPeer = (props) => } - } + + } + + + { + e.stopPropagation(); + + webcamEnabled ? + roomClient.modifyPeerConsumer(peer.id, 'webcam', true) : + roomClient.modifyPeerConsumer(peer.id, 'webcam', false); + }} + > + { webcamEnabled ? + + : + + } + + + })} color={micEnabled ? 'primary' : 'secondary'} disabled={peer.peerAudioInProgress} - onClick={() => + onClick={(e) => { + e.stopPropagation(); + micEnabled ? roomClient.modifyPeerConsumer(peer.id, 'mic', true) : roomClient.modifyPeerConsumer(peer.id, 'mic', false); }} > { micEnabled ? - + : - + } - { isModerator && + + { isModerator && + + color='secondary' + onClick={(e) => { + e.stopPropagation(); + roomClient.kickPeer(peer.id); }} > - } -
    + + } + {children}
    ); }; diff --git a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js index dbf5491..af35dbd 100644 --- a/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js +++ b/app/src/components/MeetingDrawer/ParticipantList/ParticipantList.js @@ -2,7 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { passivePeersSelector, - spotlightPeersSelector + spotlightSortedPeersSelector } from '../../Selectors'; import classNames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; @@ -13,7 +13,6 @@ import ListPeer from './ListPeer'; import ListMe from './ListMe'; import ListModerator from './ListModerator'; import Volume from '../../Containers/Volume'; -import * as userRoles from '../../../reducers/userRoles'; const styles = (theme) => ({ @@ -32,12 +31,10 @@ const styles = (theme) => }, listheader : { - padding : theme.spacing(1), fontWeight : 'bolder' }, listItem : { - padding : theme.spacing(1), width : '100%', overflow : 'hidden', cursor : 'pointer', @@ -114,16 +111,20 @@ class ParticipantList extends React.PureComponent defaultMessage='Participants in Spotlight' /> - { spotlightPeers.map((peerId) => ( + { spotlightPeers.map((peer) => (
  • roomClient.setSelectedPeer(peerId)} + onClick={() => roomClient.setSelectedPeer(peer.id)} > - - + +
  • ))} @@ -135,16 +136,16 @@ class ParticipantList extends React.PureComponent defaultMessage='Passive Participants' /> - { passivePeers.map((peerId) => ( + { passivePeers.map((peer) => (
  • roomClient.setSelectedPeer(peerId)} + onClick={() => roomClient.setSelectedPeer(peer.id)} > @@ -170,11 +171,12 @@ ParticipantList.propTypes = const mapStateToProps = (state) => { return { - isModerator : state.me.roles.includes(userRoles.MODERATOR) || - state.me.roles.includes(userRoles.ADMIN), + isModerator : + state.me.roles.some((role) => + state.room.permissionsFromRoles.MODERATE_ROOM.includes(role)), passivePeers : passivePeersSelector(state), selectedPeerId : state.room.selectedPeerId, - spotlightPeers : spotlightPeersSelector(state) + spotlightPeers : spotlightSortedPeersSelector(state) }; }; @@ -186,6 +188,7 @@ const ParticipantListContainer = withRoomContext(connect( areStatesEqual : (next, prev) => { return ( + prev.room.permissionsFromRoles === next.room.permissionsFromRoles && prev.me.roles === next.me.roles && prev.peers === next.peers && prev.room.spotlights === next.room.spotlights && diff --git a/app/src/components/MeetingViews/Filmstrip.js b/app/src/components/MeetingViews/Filmstrip.js index 2ed11c6..d1cfba6 100644 --- a/app/src/components/MeetingViews/Filmstrip.js +++ b/app/src/components/MeetingViews/Filmstrip.js @@ -12,6 +12,12 @@ import Peer from '../Containers/Peer'; import SpeakerPeer from '../Containers/SpeakerPeer'; import Grid from '@material-ui/core/Grid'; +const RATIO = 1.334; +const PADDING_V = 40; +const PADDING_H = 0; +const FILMSTRING_PADDING_V = 10; +const FILMSTRING_PADDING_H = 0; + const styles = () => ({ root : @@ -20,24 +26,22 @@ const styles = () => width : '100%', display : 'grid', gridTemplateColumns : '1fr', - gridTemplateRows : '1.6fr minmax(0, 0.4fr)' + gridTemplateRows : '1fr 0.25fr' }, speaker : { - gridArea : '1 / 1 / 2 / 2', + gridArea : '1 / 1 / 1 / 1', display : 'flex', justifyContent : 'center', - alignItems : 'center', - paddingTop : 40 + alignItems : 'center' }, filmStrip : { - gridArea : '2 / 1 / 3 / 2' + gridArea : '2 / 1 / 2 / 1' }, filmItem : { display : 'flex', - marginLeft : '6px', border : 'var(--peer-border)', '&.selected' : { @@ -45,8 +49,18 @@ const styles = () => }, '&.active' : { - opacity : '0.6' + borderColor : 'var(--selected-peer-border-color)' } + }, + hiddenToolBar : + { + paddingTop : 0, + transition : 'padding .5s' + }, + showingToolBar : + { + paddingTop : 60, + transition : 'padding .5s' } }); @@ -58,6 +72,8 @@ class Filmstrip extends React.PureComponent this.resizeTimeout = null; + this.rootContainer = React.createRef(); + this.activePeerContainer = React.createRef(); this.filmStripContainer = React.createRef(); @@ -105,24 +121,38 @@ class Filmstrip extends React.PureComponent { const newState = {}; + const root = this.rootContainer.current; + + if (!root) + return; + + const availableWidth = root.clientWidth; + // Grid is: + // 4/5 speaker + // 1/5 filmstrip + const availableSpeakerHeight = (root.clientHeight * 0.8) - + (this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING_V : PADDING_H); + + const availableFilmstripHeight = root.clientHeight * 0.2; + const speaker = this.activePeerContainer.current; if (speaker) { - let speakerWidth = (speaker.clientWidth - 100); + let speakerWidth = (availableWidth - PADDING_H); - let speakerHeight = (speakerWidth / 4) * 3; + let speakerHeight = speakerWidth / RATIO; if (this.isSharingCamera(this.getActivePeerId())) { speakerWidth /= 2; - speakerHeight = (speakerWidth / 4) * 3; + speakerHeight = speakerWidth / RATIO; } - if (speakerHeight > (speaker.clientHeight - 60)) + if (speakerHeight > (availableSpeakerHeight - PADDING_V)) { - speakerHeight = (speaker.clientHeight - 60); - speakerWidth = (speakerHeight / 3) * 4; + speakerHeight = (availableSpeakerHeight - PADDING_V); + speakerWidth = speakerHeight * RATIO; } newState.speakerWidth = speakerWidth; @@ -133,14 +163,18 @@ class Filmstrip extends React.PureComponent if (filmStrip) { - let filmStripHeight = filmStrip.clientHeight - 10; + let filmStripHeight = availableFilmstripHeight - FILMSTRING_PADDING_V; - let filmStripWidth = (filmStripHeight / 3) * 4; + let filmStripWidth = filmStripHeight * RATIO; - if (filmStripWidth * this.props.boxes > (filmStrip.clientWidth - 50)) + if ( + (filmStripWidth * this.props.boxes) > + (availableWidth - FILMSTRING_PADDING_H) + ) { - filmStripWidth = (filmStrip.clientWidth - 50) / this.props.boxes; - filmStripHeight = (filmStripWidth / 4) * 3; + filmStripWidth = (availableWidth - FILMSTRING_PADDING_H) / + this.props.boxes; + filmStripHeight = filmStripWidth / RATIO; } newState.filmStripWidth = filmStripWidth; @@ -172,27 +206,21 @@ class Filmstrip extends React.PureComponent window.removeEventListener('resize', this.updateDimensions); } - componentWillUpdate(nextProps) - { - if (nextProps !== this.props) - { - if ( - nextProps.activeSpeakerId != null && - nextProps.activeSpeakerId !== this.props.myId - ) - { - // eslint-disable-next-line react/no-did-update-set-state - this.setState({ - lastSpeaker : nextProps.activeSpeakerId - }); - } - } - } - componentDidUpdate(prevProps) { if (prevProps !== this.props) { + if ( + this.props.activeSpeakerId != null && + this.props.activeSpeakerId !== this.props.myId + ) + { + // eslint-disable-next-line react/no-did-update-set-state + this.setState({ + lastSpeaker : this.props.activeSpeakerId + }); + } + this.updateDimensions(); } } @@ -205,6 +233,8 @@ class Filmstrip extends React.PureComponent myId, advancedMode, spotlights, + toolbarsVisible, + permanentTopBar, classes } = this.props; @@ -223,7 +253,14 @@ class Filmstrip extends React.PureComponent }; return ( -
    +
    { peers[activePeerId] &&
    @@ -268,7 +305,7 @@ class Filmstrip extends React.PureComponent advancedMode={advancedMode} id={peerId} style={peerStyle} - smallButtons + smallContainer />
    @@ -296,6 +333,8 @@ Filmstrip.propTypes = { selectedPeerId : PropTypes.string, spotlights : PropTypes.array.isRequired, boxes : PropTypes.number, + toolbarsVisible : PropTypes.bool.isRequired, + permanentTopBar : PropTypes.bool, classes : PropTypes.object.isRequired }; @@ -308,7 +347,9 @@ const mapStateToProps = (state) => consumers : state.consumers, myId : state.me.id, spotlights : state.room.spotlights, - boxes : videoBoxesSelector(state) + boxes : videoBoxesSelector(state), + toolbarsVisible : state.room.toolbarsVisible, + permanentTopBar : state.settings.permanentTopBar }; }; @@ -322,6 +363,8 @@ export default withRoomContext(connect( return ( prev.room.activeSpeakerId === next.room.activeSpeakerId && prev.room.selectedPeerId === next.room.selectedPeerId && + prev.room.toolbarsVisible === next.room.toolbarsVisible && + prev.settings.permanentTopBar === next.settings.permanentTopBar && prev.peers === next.peers && prev.consumers === next.consumers && prev.room.spotlights === next.room.spotlights && diff --git a/app/src/components/PeerAudio/AudioPeers.js b/app/src/components/PeerAudio/AudioPeers.js index 671f4a2..6e9921e 100644 --- a/app/src/components/PeerAudio/AudioPeers.js +++ b/app/src/components/PeerAudio/AudioPeers.js @@ -1,13 +1,14 @@ import React from 'react'; import { connect } from 'react-redux'; -import { micConsumerSelector } from '../Selectors'; +import { passiveMicConsumerSelector } from '../Selectors'; import PropTypes from 'prop-types'; import PeerAudio from './PeerAudio'; const AudioPeers = (props) => { const { - micConsumers + micConsumers, + audioOutputDevice } = props; return ( @@ -19,6 +20,7 @@ const AudioPeers = (props) => ); }) @@ -29,12 +31,14 @@ const AudioPeers = (props) => AudioPeers.propTypes = { - micConsumers : PropTypes.array + micConsumers : PropTypes.array, + audioOutputDevice : PropTypes.string }; const mapStateToProps = (state) => ({ - micConsumers : micConsumerSelector(state) + micConsumers : passiveMicConsumerSelector(state), + audioOutputDevice : state.settings.selectedAudioOutputDevice }); const AudioPeersContainer = connect( @@ -45,7 +49,10 @@ const AudioPeersContainer = connect( areStatesEqual : (next, prev) => { return ( - prev.consumers === next.consumers + prev.consumers === next.consumers && + prev.room.spotlights === next.room.spotlights && + prev.settings.selectedAudioOutputDevice === + next.settings.selectedAudioOutputDevice ); } } diff --git a/app/src/components/PeerAudio/PeerAudio.js b/app/src/components/PeerAudio/PeerAudio.js index 38d7faf..b4be0f7 100644 --- a/app/src/components/PeerAudio/PeerAudio.js +++ b/app/src/components/PeerAudio/PeerAudio.js @@ -10,6 +10,7 @@ export default class PeerAudio extends React.PureComponent // Latest received audio track. // @type {MediaStreamTrack} this._audioTrack = null; + this._audioOutputDevice = null; } render() @@ -24,17 +25,21 @@ export default class PeerAudio extends React.PureComponent componentDidMount() { - const { audioTrack } = this.props; + const { audioTrack, audioOutputDevice } = this.props; this._setTrack(audioTrack); + this._setOutputDevice(audioOutputDevice); } - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) + componentDidUpdate(prevProps) { - const { audioTrack } = nextProps; - - this._setTrack(audioTrack); + if (prevProps !== this.props) + { + const { audioTrack, audioOutputDevice } = this.props; + + this._setTrack(audioTrack); + this._setOutputDevice(audioOutputDevice); + } } _setTrack(audioTrack) @@ -60,9 +65,23 @@ export default class PeerAudio extends React.PureComponent audio.srcObject = null; } } + + _setOutputDevice(audioOutputDevice) + { + if (this._audioOutputDevice === audioOutputDevice) + return; + + this._audioOutputDevice = audioOutputDevice; + + const { audio } = this.refs; + + if (audioOutputDevice && typeof audio.setSinkId === 'function') + audio.setSinkId(audioOutputDevice); + } } PeerAudio.propTypes = { - audioTrack : PropTypes.any + audioTrack : PropTypes.any, + audioOutputDevice : PropTypes.string }; diff --git a/app/src/components/Room.js b/app/src/components/Room.js index aef3987..cdda66f 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -23,7 +23,8 @@ import VideoWindow from './VideoWindow/VideoWindow'; import LockDialog from './AccessControl/LockDialog/LockDialog'; import Settings from './Settings/Settings'; import TopBar from './Controls/TopBar'; -import MobileControls from './Controls/MobileControls'; +import WakeLock from 'react-wakelock-react16'; +import ExtraVideo from './Controls/ExtraVideo'; const TIMEOUT = 5 * 1000; @@ -139,9 +140,9 @@ class Room extends React.PureComponent { const { room, + browser, advancedMode, toolAreaOpen, - isMobile, toggleToolArea, classes, theme @@ -204,12 +205,12 @@ class Room extends React.PureComponent - - - { isMobile && - + { browser.platform === 'mobile' && browser.os !== 'ios' && + } + + { room.lockDialogOpen && } @@ -217,6 +218,10 @@ class Room extends React.PureComponent { room.settingsOpen && } + + { room.extraVideoOpen && + + }
    ); } @@ -225,10 +230,10 @@ class Room extends React.PureComponent Room.propTypes = { room : appPropTypes.Room.isRequired, + browser : PropTypes.object.isRequired, advancedMode : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired, setToolbarsVisible : PropTypes.func.isRequired, - isMobile : PropTypes.bool, toggleToolArea : PropTypes.func.isRequired, classes : PropTypes.object.isRequired, theme : PropTypes.object.isRequired @@ -237,9 +242,9 @@ Room.propTypes = const mapStateToProps = (state) => ({ room : state.room, + browser : state.me.browser, advancedMode : state.settings.advancedMode, - toolAreaOpen : state.toolarea.toolAreaOpen, - isMobile : state.me.isMobile + toolAreaOpen : state.toolarea.toolAreaOpen }); const mapDispatchToProps = (dispatch) => @@ -263,9 +268,9 @@ export default connect( { return ( prev.room === next.room && + prev.me.browser === next.me.browser && prev.settings.advancedMode === next.settings.advancedMode && - prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen && - prev.me.isMobile === next.me.isMobile + prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen ); } } diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js index 7a9cbfc..fd22aff 100644 --- a/app/src/components/Selectors.js +++ b/app/src/components/Selectors.js @@ -12,6 +12,10 @@ const peersKeySelector = createSelector( peersSelector, (peers) => Object.keys(peers) ); +const peersValueSelector = createSelector( + peersSelector, + (peers) => Object.values(peers) +); export const lobbyPeersKeySelector = createSelector( lobbyPeersSelector, @@ -33,6 +37,11 @@ export const screenProducersSelector = createSelector( (producers) => Object.values(producers).filter((producer) => producer.source === 'screen') ); +export const extraVideoProducersSelector = createSelector( + producersSelect, + (producers) => Object.values(producers).filter((producer) => producer.source === 'extravideo') +); + export const micProducerSelector = createSelector( producersSelect, (producers) => Object.values(producers).find((producer) => producer.source === 'mic') @@ -63,6 +72,33 @@ export const screenConsumerSelector = createSelector( (consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen') ); +export const spotlightScreenConsumerSelector = createSelector( + spotlightsSelector, + consumersSelect, + (spotlights, consumers) => + Object.values(consumers).filter( + (consumer) => consumer.source === 'screen' && spotlights.includes(consumer.peerId) + ) +); + +export const spotlightExtraVideoConsumerSelector = createSelector( + spotlightsSelector, + consumersSelect, + (spotlights, consumers) => + Object.values(consumers).filter( + (consumer) => consumer.source === 'extravideo' && spotlights.includes(consumer.peerId) + ) +); + +export const passiveMicConsumerSelector = createSelector( + spotlightsSelector, + consumersSelect, + (spotlights, consumers) => + Object.values(consumers).filter( + (consumer) => consumer.source === 'mic' && !spotlights.includes(consumer.peerId) + ) +); + export const spotlightsLengthSelector = createSelector( spotlightsSelector, (spotlights) => spotlights.length @@ -74,35 +110,60 @@ export const spotlightPeersSelector = createSelector( (spotlights, peers) => peers.filter((peerId) => spotlights.includes(peerId)) ); +export const spotlightSortedPeersSelector = createSelector( + spotlightsSelector, + peersValueSelector, + (spotlights, peers) => peers.filter((peer) => spotlights.includes(peer.id)) + .sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) +); + export const peersLengthSelector = createSelector( peersSelector, (peers) => Object.values(peers).length ); export const passivePeersSelector = createSelector( - peersKeySelector, + peersValueSelector, spotlightsSelector, - (peers, spotlights) => peers.filter((peerId) => !spotlights.includes(peerId)) + (peers, spotlights) => peers.filter((peer) => !spotlights.includes(peer.id)) + .sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))) +); + +export const raisedHandsSelector = createSelector( + peersValueSelector, + (peers) => peers.reduce((a, b) => (a + (b.raisedHand ? 1 : 0)), 0) ); export const videoBoxesSelector = createSelector( spotlightsLengthSelector, screenProducersSelector, - screenConsumerSelector, - (spotlightsLength, screenProducers, screenConsumers) => - spotlightsLength + 1 + screenProducers.length + screenConsumers.length + spotlightScreenConsumerSelector, + extraVideoProducersSelector, + spotlightExtraVideoConsumerSelector, + ( + spotlightsLength, + screenProducers, + screenConsumers, + extraVideoProducers, + extraVideoConsumers + ) => + spotlightsLength + 1 + screenProducers.length + + screenConsumers.length + extraVideoProducers.length + + extraVideoConsumers.length ); export const meProducersSelector = createSelector( micProducerSelector, webcamProducerSelector, screenProducerSelector, - (micProducer, webcamProducer, screenProducer) => + extraVideoProducersSelector, + (micProducer, webcamProducer, screenProducer, extraVideoProducers) => { return { micProducer, webcamProducer, - screenProducer + screenProducer, + extraVideoProducers }; } ); @@ -125,8 +186,10 @@ export const makePeerConsumerSelector = () => consumersArray.find((consumer) => consumer.source === 'webcam'); const screenConsumer = consumersArray.find((consumer) => consumer.source === 'screen'); + const extraVideoConsumers = + consumersArray.filter((consumer) => consumer.source === 'extravideo'); - return { micConsumer, webcamConsumer, screenConsumer }; + return { micConsumer, webcamConsumer, screenConsumer, extraVideoConsumers }; } ); }; diff --git a/app/src/components/Settings/AdvancedSettings.js b/app/src/components/Settings/AdvancedSettings.js new file mode 100644 index 0000000..520dc0f --- /dev/null +++ b/app/src/components/Settings/AdvancedSettings.js @@ -0,0 +1,125 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { withStyles } from '@material-ui/core/styles'; +import { withRoomContext } from '../../RoomContext'; +import * as settingsActions from '../../actions/settingsActions'; +import PropTypes from 'prop-types'; +import { useIntl, FormattedMessage } from 'react-intl'; +import MenuItem from '@material-ui/core/MenuItem'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import FormControl from '@material-ui/core/FormControl'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Select from '@material-ui/core/Select'; +import Checkbox from '@material-ui/core/Checkbox'; + +const styles = (theme) => + ({ + setting : + { + padding : theme.spacing(2) + }, + formControl : + { + display : 'flex' + } + }); + +const AdvancedSettings = ({ + roomClient, + settings, + onToggleAdvancedMode, + onToggleNotificationSounds, + classes +}) => +{ + const intl = useIntl(); + + return ( + + } + label={intl.formatMessage({ + id : 'settings.advancedMode', + defaultMessage : 'Advanced mode' + })} + /> + } + label={intl.formatMessage({ + id : 'settings.notificationSounds', + defaultMessage : 'Notification sounds' + })} + /> + { !window.config.lockLastN && +
    + + + + + + +
    + } +
    + ); +}; + +AdvancedSettings.propTypes = +{ + roomClient : PropTypes.any.isRequired, + settings : PropTypes.object.isRequired, + onToggleAdvancedMode : PropTypes.func.isRequired, + onToggleNotificationSounds : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + settings : state.settings + }); + +const mapDispatchToProps = { + onToggleAdvancedMode : settingsActions.toggleAdvancedMode, + onToggleNotificationSounds : settingsActions.toggleNotificationSounds +}; + +export default withRoomContext(connect( + mapStateToProps, + mapDispatchToProps, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.settings === next.settings + ); + } + } +)(withStyles(styles)(AdvancedSettings))); \ No newline at end of file diff --git a/app/src/components/Settings/AppearenceSettings.js b/app/src/components/Settings/AppearenceSettings.js new file mode 100644 index 0000000..705b2f6 --- /dev/null +++ b/app/src/components/Settings/AppearenceSettings.js @@ -0,0 +1,143 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import * as appPropTypes from '../appPropTypes'; +import { withStyles } from '@material-ui/core/styles'; +import * as roomActions from '../../actions/roomActions'; +import * as settingsActions from '../../actions/settingsActions'; +import PropTypes from 'prop-types'; +import { useIntl, FormattedMessage } from 'react-intl'; +import MenuItem from '@material-ui/core/MenuItem'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import FormControl from '@material-ui/core/FormControl'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Select from '@material-ui/core/Select'; +import Checkbox from '@material-ui/core/Checkbox'; + +const styles = (theme) => + ({ + setting : + { + padding : theme.spacing(2) + }, + formControl : + { + display : 'flex' + } + }); + +const AppearenceSettings = ({ + room, + settings, + onTogglePermanentTopBar, + onToggleHiddenControls, + handleChangeMode, + classes +}) => +{ + const intl = useIntl(); + + const modes = [ { + value : 'democratic', + label : intl.formatMessage({ + id : 'label.democratic', + defaultMessage : 'Democratic view' + }) + }, { + value : 'filmstrip', + label : intl.formatMessage({ + id : 'label.filmstrip', + defaultMessage : 'Filmstrip view' + }) + } ]; + + return ( + +
    + + + + + + +
    + } + label={intl.formatMessage({ + id : 'settings.permanentTopBar', + defaultMessage : 'Permanent top bar' + })} + /> + } + label={intl.formatMessage({ + id : 'settings.hiddenControls', + defaultMessage : 'Hidden media controls' + })} + /> +
    + ); +}; + +AppearenceSettings.propTypes = +{ + room : appPropTypes.Room.isRequired, + settings : PropTypes.object.isRequired, + onTogglePermanentTopBar : PropTypes.func.isRequired, + onToggleHiddenControls : PropTypes.func.isRequired, + handleChangeMode : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => + ({ + room : state.room, + settings : state.settings + }); + +const mapDispatchToProps = { + onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, + onToggleHiddenControls : settingsActions.toggleHiddenControls, + handleChangeMode : roomActions.setDisplayMode +}; + +export default connect( + mapStateToProps, + mapDispatchToProps, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.room === next.room && + prev.settings === next.settings + ); + } + } +)(withStyles(styles)(AppearenceSettings)); \ No newline at end of file diff --git a/app/src/components/Settings/MediaSettings.js b/app/src/components/Settings/MediaSettings.js new file mode 100644 index 0000000..392df88 --- /dev/null +++ b/app/src/components/Settings/MediaSettings.js @@ -0,0 +1,341 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import * as appPropTypes from '../appPropTypes'; +import { withStyles } from '@material-ui/core/styles'; +import { withRoomContext } from '../../RoomContext'; +import * as settingsActions from '../../actions/settingsActions'; +import PropTypes from 'prop-types'; +import { useIntl, FormattedMessage } from 'react-intl'; +import MenuItem from '@material-ui/core/MenuItem'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import FormControl from '@material-ui/core/FormControl'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Select from '@material-ui/core/Select'; +import Checkbox from '@material-ui/core/Checkbox'; + +const styles = (theme) => + ({ + setting : + { + padding : theme.spacing(2) + }, + formControl : + { + display : 'flex' + } + }); + +const MediaSettings = ({ + setEchoCancellation, + setAutoGainControl, + setNoiseSuppression, + roomClient, + me, + settings, + classes +}) => +{ + const intl = useIntl(); + + const resolutions = [ { + value : 'low', + label : intl.formatMessage({ + id : 'label.low', + defaultMessage : 'Low' + }) + }, + { + value : 'medium', + label : intl.formatMessage({ + id : 'label.medium', + defaultMessage : 'Medium' + }) + }, + { + value : 'high', + label : intl.formatMessage({ + id : 'label.high', + defaultMessage : 'High (HD)' + }) + }, + { + value : 'veryhigh', + label : intl.formatMessage({ + id : 'label.veryHigh', + defaultMessage : 'Very high (FHD)' + }) + }, + { + value : 'ultra', + label : intl.formatMessage({ + id : 'label.ultra', + defaultMessage : 'Ultra (UHD)' + }) + } ]; + + let webcams; + + if (me.webcamDevices) + webcams = Object.values(me.webcamDevices); + else + webcams = []; + + let audioDevices; + + if (me.audioDevices) + audioDevices = Object.values(me.audioDevices); + else + audioDevices = []; + + let audioOutputDevices; + + if (me.audioOutputDevices) + audioOutputDevices = Object.values(me.audioOutputDevices); + else + audioOutputDevices = []; + + return ( + +
    + + + + { webcams.length > 0 ? + intl.formatMessage({ + id : 'settings.selectCamera', + defaultMessage : 'Select video device' + }) + : + intl.formatMessage({ + id : 'settings.cantSelectCamera', + defaultMessage : 'Unable to select video device' + }) + } + + +
    +
    + + + + { audioDevices.length > 0 ? + intl.formatMessage({ + id : 'settings.selectAudio', + defaultMessage : 'Select audio device' + }) + : + intl.formatMessage({ + id : 'settings.cantSelectAudio', + defaultMessage : 'Unable to select audio device' + }) + } + + +
    + { 'audioOutputSupportedBrowsers' in window.config && + window.config.audioOutputSupportedBrowsers.includes(me.browser.name) && +
    + + + + { audioOutputDevices.length > 0 ? + intl.formatMessage({ + id : 'settings.selectAudioOutput', + defaultMessage : 'Select audio output device' + }) + : + intl.formatMessage({ + id : 'settings.cantSelectAudioOutput', + defaultMessage : 'Unable to select audio output device' + }) + } + + +
    + } +
    + + + + + + + { + setEchoCancellation(event.target.checked); + roomClient.changeAudioDevice(settings.selectedAudioDevice); + }} + />} + label={intl.formatMessage({ + id : 'settings.echoCancellation', + defaultMessage : 'Echo Cancellation' + })} + /> + { + setAutoGainControl(event.target.checked); + roomClient.changeAudioDevice(settings.selectedAudioDevice); + }} + />} + label={intl.formatMessage({ + id : 'settings.autoGainControl', + defaultMessage : 'Auto Gain Control' + })} + /> + { + setNoiseSuppression(event.target.checked); + roomClient.changeAudioDevice(settings.selectedAudioDevice); + }} + />} + label={intl.formatMessage({ + id : 'settings.noiseSuppression', + defaultMessage : 'Noise Suppression' + })} + /> + +
    + ); +}; + +MediaSettings.propTypes = +{ + roomClient : PropTypes.any.isRequired, + setEchoCancellation : PropTypes.func.isRequired, + setAutoGainControl : PropTypes.func.isRequired, + setNoiseSuppression : PropTypes.func.isRequired, + me : appPropTypes.Me.isRequired, + settings : PropTypes.object.isRequired, + classes : PropTypes.object.isRequired +}; + +const mapStateToProps = (state) => +{ + return { + me : state.me, + settings : state.settings + }; +}; + +const mapDispatchToProps = { + setEchoCancellation : settingsActions.setEchoCancellation, + setAutoGainControl : settingsActions.toggleAutoGainControl, + setNoiseSuppression : settingsActions.toggleNoiseSuppression +}; + +export default withRoomContext(connect( + mapStateToProps, + mapDispatchToProps, + null, + { + areStatesEqual : (next, prev) => + { + return ( + prev.me === next.me && + prev.settings === next.settings + ); + } + } +)(withStyles(styles)(MediaSettings))); \ No newline at end of file diff --git a/app/src/components/Settings/Settings.js b/app/src/components/Settings/Settings.js index 3c56be4..6633829 100644 --- a/app/src/components/Settings/Settings.js +++ b/app/src/components/Settings/Settings.js @@ -1,22 +1,25 @@ import React from 'react'; import { connect } from 'react-redux'; -import * as appPropTypes from '../appPropTypes'; import { withStyles } from '@material-ui/core/styles'; -import { withRoomContext } from '../../RoomContext'; import * as roomActions from '../../actions/roomActions'; -import * as settingsActions from '../../actions/settingsActions'; import PropTypes from 'prop-types'; import { useIntl, FormattedMessage } from 'react-intl'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; +import MediaSettings from './MediaSettings'; +import AppearenceSettings from './AppearenceSettings'; +import AdvancedSettings from './AdvancedSettings'; import Dialog from '@material-ui/core/Dialog'; import DialogTitle from '@material-ui/core/DialogTitle'; import DialogActions from '@material-ui/core/DialogActions'; import Button from '@material-ui/core/Button'; -import MenuItem from '@material-ui/core/MenuItem'; -import FormHelperText from '@material-ui/core/FormHelperText'; -import FormControl from '@material-ui/core/FormControl'; -import FormControlLabel from '@material-ui/core/FormControlLabel'; -import Select from '@material-ui/core/Select'; -import Checkbox from '@material-ui/core/Checkbox'; + +const tabs = +[ + 'media', + 'appearence', + 'advanced' +]; const styles = (theme) => ({ @@ -43,102 +46,27 @@ const styles = (theme) => width : '90vw' } }, - setting : + tabsHeader : { - padding : theme.spacing(2) - }, - formControl : - { - display : 'flex' + flexGrow : 1 } }); const Settings = ({ - roomClient, - room, - me, - settings, - onToggleAdvancedMode, - onTogglePermanentTopBar, - setEchoCancellation, - setAutoGainControl, - setNoiseSuppression, + currentSettingsTab, + settingsOpen, handleCloseSettings, - handleChangeMode, + setSettingsTab, classes }) => { const intl = useIntl(); - const modes = [ { - value : 'democratic', - label : intl.formatMessage({ - id : 'label.democratic', - defaultMessage : 'Democratic view' - }) - }, { - value : 'filmstrip', - label : intl.formatMessage({ - id : 'label.filmstrip', - defaultMessage : 'Filmstrip view' - }) - } ]; - - const resolutions = [ { - value : 'low', - label : intl.formatMessage({ - id : 'label.low', - defaultMessage : 'Low' - }) - }, - { - value : 'medium', - label : intl.formatMessage({ - id : 'label.medium', - defaultMessage : 'Medium' - }) - }, - { - value : 'high', - label : intl.formatMessage({ - id : 'label.high', - defaultMessage : 'High (HD)' - }) - }, - { - value : 'veryhigh', - label : intl.formatMessage({ - id : 'label.veryHigh', - defaultMessage : 'Very high (FHD)' - }) - }, - { - value : 'ultra', - label : intl.formatMessage({ - id : 'label.ultra', - defaultMessage : 'Ultra (UHD)' - }) - } ]; - - let webcams; - - if (me.webcamDevices) - webcams = Object.values(me.webcamDevices); - else - webcams = []; - - let audioDevices; - - if (me.audioDevices) - audioDevices = Object.values(me.audioDevices); - else - audioDevices = []; - return ( handleCloseSettings({ settingsOpen: false })} + open={settingsOpen} + onClose={() => handleCloseSettings(false)} classes={{ paper : classes.dialogPaper }} @@ -149,244 +77,40 @@ const Settings = ({ defaultMessage='Settings' /> -
    - - - - { webcams.length > 0 ? - intl.formatMessage({ - id : 'settings.selectCamera', - defaultMessage : 'Select video device' - }) - : - intl.formatMessage({ - id : 'settings.cantSelectCamera', - defaultMessage : 'Unable to select video device' - }) - } - - -
    -
    - - - - { audioDevices.length > 0 ? - intl.formatMessage({ - id : 'settings.selectAudio', - defaultMessage : 'Select audio device' - }) - : - intl.formatMessage({ - id : 'settings.cantSelectAudio', - defaultMessage : 'Unable to select audio device' - }) - } - - -
    -
    - - - - - - -
    -
    - - - - - - -
    - } - label={intl.formatMessage({ - id : 'settings.advancedMode', - defaultMessage : 'Advanced mode' - })} - /> - { settings.advancedMode && - -
    - - - - - - -
    - - { - setEchoCancellation(event.target.checked); - roomClient.changeAudioDevice(settings.selectedAudioDevice); - }} - />} - label={intl.formatMessage({ - id : 'settings.echoCancellation', - defaultMessage : 'Echo Cancellation' - })} - /> - { - setAutoGainControl(event.target.checked); - roomClient.changeAudioDevice(settings.selectedAudioDevice); - }} - />} - label={intl.formatMessage({ - id: 'settings.autoGainControl', - defaultMessage: 'Auto Gain Control' - })} - /> - { - setNoiseSuppression(event.target.checked); - roomClient.changeAudioDevice(settings.selectedAudioDevice); - }} - />} - label={intl.formatMessage({ - id: 'settings.noiseSuppression', - defaultMessage: 'Noise Suppression' - })} - /> - } - label={intl.formatMessage({ - id : 'settings.permanentTopBar', - defaultMessage : 'Permanent top bar' - })} - /> -
    - } + setSettingsTab(tabs[value])} + indicatorColor='primary' + textColor='primary' + variant='fullWidth' + > + + + + + {currentSettingsTab === 'media' && } + {currentSettingsTab === 'appearence' && } + {currentSettingsTab === 'advanced' && } -
  • - { participants.map((peer) => ( -
  • roomClient.setSelectedPeer(peer.id)} - > - { spotlights.includes(peer.id) ? - + { participants.map((peer) => ( + +
  • roomClient.setSelectedPeer(peer.id)} > - - - : - - } -
  • - ))} + { spotlights.includes(peer.id) ? + + + + : + + } + + + ))} +
    ); From 45a89b9f1acb9f7a7db87fa8064f172d989ea89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 6 May 2020 13:50:29 +0200 Subject: [PATCH 16/22] Only keep one self destruct timeout, ref #255 --- server/lib/Room.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index 1b207fa..69e3e31 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -117,6 +117,8 @@ class Room extends EventEmitter this._peers = {}; + this._selfDestructTimeout = null; + // Array of mediasoup Router instances. this._mediasoupRouters = mediasoupRouters; @@ -146,6 +148,11 @@ class Room extends EventEmitter this._closed = true; + if (this._selfDestructTimeout) + clearTimeout(this._selfDestructTimeout); + + this._selfDestructTimeout = null; + this._chatHistory = null; this._fileHistory = null; @@ -411,7 +418,10 @@ class Room extends EventEmitter { logger.debug('selfDestructCountdown() started'); - setTimeout(() => + if (this._selfDestructTimeout) + clearTimeout(this._selfDestructTimeout); + + this._selfDestructTimeout = setTimeout(() => { if (this._closed) return; From 26874877ba5ea4df4db57898006a3541b848cad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 6 May 2020 14:03:47 +0200 Subject: [PATCH 17/22] Provide roomId to logout for load balanced scenarios, fixes #275 --- app/src/RoomClient.js | 4 ++-- app/src/components/JoinDialog.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 1c93794..9f7dd10 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -475,9 +475,9 @@ export default class RoomClient window.open(url, 'loginWindow'); } - logout() + logout(roomId = this._roomId) { - window.open('/auth/logout', 'logoutWindow'); + window.open(`/auth/logout?peerId=${this._peerId}&roomId=${roomId}`, 'logoutWindow'); } receiveLoginChildWindow(data) diff --git a/app/src/components/JoinDialog.js b/app/src/components/JoinDialog.js index 3d2596f..0ad8ef8 100644 --- a/app/src/components/JoinDialog.js +++ b/app/src/components/JoinDialog.js @@ -224,7 +224,7 @@ const JoinDialog = ({ myPicture={myPicture} onLogin={() => { - loggedIn ? roomClient.logout() : roomClient.login(roomId); + loggedIn ? roomClient.logout(roomId) : roomClient.login(roomId); }} loggedIn={loggedIn} > From 3f1d102c5901e95c6d0e6d339b3bade5989d3571 Mon Sep 17 00:00:00 2001 From: Stefan Otto Date: Wed, 6 May 2020 18:47:14 +0200 Subject: [PATCH 18/22] fix my errors in gigantic merge + linting --- app/src/RoomClient.js | 99 ++++---------------- app/src/components/Selectors.js | 4 - app/src/components/Settings/MediaSettings.js | 9 +- 3 files changed, 22 insertions(+), 90 deletions(-) diff --git a/app/src/RoomClient.js b/app/src/RoomClient.js index 7fd9cb5..df276ae 100644 --- a/app/src/RoomClient.js +++ b/app/src/RoomClient.js @@ -953,20 +953,24 @@ export default class RoomClient } } - disconnectLocalHark() { + disconnectLocalHark() + { logger.debug('disconnectLocalHark() | Stopping harkStream.'); - if (this._harkStream != null) { + if (this._harkStream != null) + { this._harkStream.getAudioTracks()[0].stop(); this._harkStream = null; } - if (this._hark != null) { + if (this._hark != null) + { logger.debug('disconnectLocalHark() Stopping hark.'); this._hark.stop(); } } - connectLocalHark(track) { + connectLocalHark(track) + { logger.debug('connectLocalHark() | Track:%o', track); this._harkStream = new MediaStream(); @@ -979,7 +983,8 @@ export default class RoomClient this._hark = hark(this._harkStream, { play: false }); // eslint-disable-next-line no-unused-vars - this._hark.on('volume_change', (dBs, threshold) => { + this._hark.on('volume_change', (dBs, threshold) => + { // The exact formula to convert from dBs (-100..0) to linear (0..1) is: // Math.pow(10, dBs / 20) // However it does not produce a visually useful output, so let exagerate @@ -992,21 +997,23 @@ export default class RoomClient volume = Math.round(volume); - if (this._micProducer && volume !== this._micProducer.volume) { + if (this._micProducer && volume !== this._micProducer.volume) + { this._micProducer.volume = volume; store.dispatch(peerVolumeActions.setPeerVolume(this._peerId, volume)); } }); - this._hark.on('speaking', function () { + this._hark.on('speaking', function() + { store.dispatch(meActions.setIsSpeaking(true)); }); - this._hark.on('stopped_speaking', function () { + this._hark.on('stopped_speaking', function() + { store.dispatch(meActions.setIsSpeaking(false)); }); } - async changeAudioDevice(deviceId) { logger.debug('changeAudioDevice() [deviceId: %s]', deviceId); @@ -1106,37 +1113,6 @@ export default class RoomClient meActions.setAudioOutputInProgress(false)); } - async changeAudioOutputDevice(deviceId) - { - logger.debug('changeAudioOutputDevice() [deviceId: %s]', deviceId); - - store.dispatch( - meActions.setAudioOutputInProgress(true)); - - try - { - const device = this._audioOutputDevices[deviceId]; - - if (!device) - throw new Error('Selected audio output device no longer avaibale'); - - logger.debug( - 'changeAudioOutputDevice() | new selected [audio output device:%o]', - device); - - store.dispatch(settingsActions.setSelectedAudioOutputDevice(deviceId)); - - await this._updateAudioOutputDevices(); - } - catch (error) - { - logger.error('changeAudioOutputDevice() failed: %o', error); - } - - store.dispatch( - meActions.setAudioOutputInProgress(false)); - } - async changeVideoResolution(resolution) { logger.debug('changeVideoResolution() [resolution: %s]', resolution); @@ -1826,49 +1802,6 @@ export default class RoomClient this._recvTransport = null; } - store.dispatch(roomActions.setRoomState('connecting')); - }); - - store.dispatch( - producerActions.removeProducer(this._screenSharingProducer.id)); - - this._screenSharingProducer = null; - } - - if (this._webcamProducer) - { - this._webcamProducer.close(); - - store.dispatch( - producerActions.removeProducer(this._webcamProducer.id)); - - this._webcamProducer = null; - } - - if (this._micProducer) - { - this._micProducer.close(); - - store.dispatch( - producerActions.removeProducer(this._micProducer.id)); - - this._micProducer = null; - } - - if (this._sendTransport) - { - this._sendTransport.close(); - - this._sendTransport = null; - } - - if (this._recvTransport) - { - this._recvTransport.close(); - - this._recvTransport = null; - } - store.dispatch(roomActions.setRoomState('connecting')); }); diff --git a/app/src/components/Selectors.js b/app/src/components/Selectors.js index 8f59d5d..b8e5e41 100644 --- a/app/src/components/Selectors.js +++ b/app/src/components/Selectors.js @@ -12,10 +12,6 @@ const peersKeySelector = createSelector( peersSelector, (peers) => Object.keys(peers) ); -const peersValueSelector = createSelector( - peersSelector, - (peers) => Object.values(peers) -); export const peersValueSelector = createSelector( peersSelector, diff --git a/app/src/components/Settings/MediaSettings.js b/app/src/components/Settings/MediaSettings.js index 392df88..d215181 100644 --- a/app/src/components/Settings/MediaSettings.js +++ b/app/src/components/Settings/MediaSettings.js @@ -257,7 +257,8 @@ const MediaSettings = ({ className={classes.setting} control={ { + (event) => + { setEchoCancellation(event.target.checked); roomClient.changeAudioDevice(settings.selectedAudioDevice); }} @@ -271,7 +272,8 @@ const MediaSettings = ({ className={classes.setting} control={ { + (event) => + { setAutoGainControl(event.target.checked); roomClient.changeAudioDevice(settings.selectedAudioDevice); }} @@ -285,7 +287,8 @@ const MediaSettings = ({ className={classes.setting} control={ { + (event) => + { setNoiseSuppression(event.target.checked); roomClient.changeAudioDevice(settings.selectedAudioDevice); }} From 1cbf2d5b38badd9d9305b2087a80164a25356d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 6 May 2020 21:05:34 +0200 Subject: [PATCH 19/22] Remove unused imports, fixes #305 --- app/src/components/VideoContainers/VideoView.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/components/VideoContainers/VideoView.js b/app/src/components/VideoContainers/VideoView.js index 9afcd3b..f0e407b 100644 --- a/app/src/components/VideoContainers/VideoView.js +++ b/app/src/components/VideoContainers/VideoView.js @@ -4,13 +4,12 @@ import classnames from 'classnames'; import { withStyles } from '@material-ui/core/styles'; import EditableInput from '../Controls/EditableInput'; import Logger from '../../Logger'; -import { green, yellow, orange, red } from '@material-ui/core/colors'; +import { yellow, orange, red } from '@material-ui/core/colors'; import SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff'; import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar'; import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar'; import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar'; import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar'; -import SignalCellularAltIcon from '@material-ui/icons/SignalCellularAlt'; const logger = new Logger('VideoView'); @@ -162,8 +161,6 @@ class VideoView extends React.PureComponent videoMultiLayer, audioScore, videoScore, - // consumerSpatialLayers, - // consumerTemporalLayers, consumerCurrentSpatialLayer, consumerCurrentTemporalLayer, consumerPreferredSpatialLayer, @@ -224,7 +221,7 @@ class VideoView extends React.PureComponent case 10: { - quality = null; // ; + quality = null; break; } From c1aa62d22cccd5a185e2393c73194c4c24d258bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 6 May 2020 22:22:57 +0200 Subject: [PATCH 20/22] Don't send lobbypeers to client if they don't have PROMOTE_PEER role, fixes #208 --- server/lib/Room.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/lib/Room.js b/server/lib/Room.js index 69e3e31..02ba41c 100644 --- a/server/lib/Room.js +++ b/server/lib/Room.js @@ -669,7 +669,14 @@ class Room extends EventEmitter .filter((joinedPeer) => joinedPeer.id !== peer.id) .map((joinedPeer) => (joinedPeer.peerInfo)); - const lobbyPeers = this._lobby.peerList(); + let lobbyPeers = []; + + if ( // Allowed to promote peers, notify about lobbypeers + peer.roles.some((role) => + permissionsFromRoles.PROMOTE_PEER.includes(role) + ) + ) + lobbyPeers = this._lobby.peerList(); cb(null, { roles : peer.roles, From 457d679382caffcd20f71232adff7897b8290377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Wed, 6 May 2020 23:08:51 +0200 Subject: [PATCH 21/22] Setting to disable notifications, fixes #306 --- app/src/actions/settingsActions.js | 5 ++++ app/src/components/Room.js | 16 ++++++---- .../components/Settings/AppearenceSettings.js | 29 +++++++++++++------ app/src/reducers/settings.js | 8 +++++ app/src/translations/cn.json | 1 + app/src/translations/cs.json | 1 + app/src/translations/de.json | 1 + app/src/translations/dk.json | 1 + app/src/translations/el.json | 1 + app/src/translations/en.json | 1 + app/src/translations/es.json | 1 + app/src/translations/fr.json | 1 + app/src/translations/hr.json | 1 + app/src/translations/hu.json | 1 + app/src/translations/it.json | 1 + app/src/translations/lv.json | 1 + app/src/translations/nb.json | 1 + app/src/translations/pl.json | 1 + app/src/translations/pt.json | 1 + app/src/translations/ro.json | 1 + app/src/translations/tr.json | 1 + app/src/translations/uk.json | 1 + 22 files changed, 62 insertions(+), 14 deletions(-) diff --git a/app/src/actions/settingsActions.js b/app/src/actions/settingsActions.js index 5416b02..63c12bf 100644 --- a/app/src/actions/settingsActions.js +++ b/app/src/actions/settingsActions.js @@ -38,6 +38,11 @@ export const togglePermanentTopBar = () => type : 'TOGGLE_PERMANENT_TOPBAR' }); +export const toggleShowNotifications = () => + ({ + type : 'TOGGLE_SHOW_NOTIFICATIONS' + }); + export const setEchoCancellation = (echoCancellation) => ({ type : 'SET_ECHO_CANCELLATION', diff --git a/app/src/components/Room.js b/app/src/components/Room.js index cdda66f..7562ee4 100644 --- a/app/src/components/Room.js +++ b/app/src/components/Room.js @@ -142,6 +142,7 @@ class Room extends React.PureComponent room, browser, advancedMode, + showNotifications, toolAreaOpen, toggleToolArea, classes, @@ -178,7 +179,9 @@ class Room extends React.PureComponent - + { showNotifications && + + } @@ -232,6 +235,7 @@ Room.propTypes = room : appPropTypes.Room.isRequired, browser : PropTypes.object.isRequired, advancedMode : PropTypes.bool.isRequired, + showNotifications : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired, setToolbarsVisible : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired, @@ -241,10 +245,11 @@ Room.propTypes = const mapStateToProps = (state) => ({ - room : state.room, - browser : state.me.browser, - advancedMode : state.settings.advancedMode, - toolAreaOpen : state.toolarea.toolAreaOpen + room : state.room, + browser : state.me.browser, + advancedMode : state.settings.advancedMode, + showNotifications : state.settings.showNotifications, + toolAreaOpen : state.toolarea.toolAreaOpen }); const mapDispatchToProps = (dispatch) => @@ -270,6 +275,7 @@ export default connect( prev.room === next.room && prev.me.browser === next.me.browser && prev.settings.advancedMode === next.settings.advancedMode && + prev.settings.showNotifications === next.settings.showNotifications && prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen ); } diff --git a/app/src/components/Settings/AppearenceSettings.js b/app/src/components/Settings/AppearenceSettings.js index 705b2f6..a34a5e1 100644 --- a/app/src/components/Settings/AppearenceSettings.js +++ b/app/src/components/Settings/AppearenceSettings.js @@ -30,6 +30,7 @@ const AppearenceSettings = ({ settings, onTogglePermanentTopBar, onToggleHiddenControls, + onToggleShowNotifications, handleChangeMode, classes }) => @@ -101,18 +102,27 @@ const AppearenceSettings = ({ defaultMessage : 'Hidden media controls' })} /> + } + label={intl.formatMessage({ + id : 'settings.showNotifications', + defaultMessage : 'Show notifications' + })} + /> ); }; AppearenceSettings.propTypes = { - room : appPropTypes.Room.isRequired, - settings : PropTypes.object.isRequired, - onTogglePermanentTopBar : PropTypes.func.isRequired, - onToggleHiddenControls : PropTypes.func.isRequired, - handleChangeMode : PropTypes.func.isRequired, - classes : PropTypes.object.isRequired + room : appPropTypes.Room.isRequired, + settings : PropTypes.object.isRequired, + onTogglePermanentTopBar : PropTypes.func.isRequired, + onToggleHiddenControls : PropTypes.func.isRequired, + onToggleShowNotifications : PropTypes.func.isRequired, + handleChangeMode : PropTypes.func.isRequired, + classes : PropTypes.object.isRequired }; const mapStateToProps = (state) => @@ -122,9 +132,10 @@ const mapStateToProps = (state) => }); const mapDispatchToProps = { - onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, - onToggleHiddenControls : settingsActions.toggleHiddenControls, - handleChangeMode : roomActions.setDisplayMode + onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, + onToggleHiddenControls : settingsActions.toggleHiddenControls, + onToggleShowNotifications : settingsActions.toggleShowNotifications, + handleChangeMode : roomActions.setDisplayMode }; export default connect( diff --git a/app/src/reducers/settings.js b/app/src/reducers/settings.js index da96ecc..7186fdc 100644 --- a/app/src/reducers/settings.js +++ b/app/src/reducers/settings.js @@ -16,6 +16,7 @@ const initialState = lastN : 4, permanentTopBar : true, hiddenControls : false, + showNotifications : true, notificationSounds : true, ...window.config.defaultAudio }; @@ -158,6 +159,13 @@ const settings = (state = initialState, action) => return { ...state, notificationSounds }; } + case 'TOGGLE_SHOW_NOTIFICATIONS': + { + const showNotifications = !state.showNotifications; + + return { ...state, showNotifications }; + } + case 'SET_VIDEO_RESOLUTION': { const { resolution } = action.payload; diff --git a/app/src/translations/cn.json b/app/src/translations/cn.json index 097e9b7..26724a4 100644 --- a/app/src/translations/cn.json +++ b/app/src/translations/cn.json @@ -130,6 +130,7 @@ "settings.lastn": "可见视频数量", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "无法保存文件", "filesharing.startingFileShare": "正在尝试共享文件", diff --git a/app/src/translations/cs.json b/app/src/translations/cs.json index 66e72b2..b80ab14 100644 --- a/app/src/translations/cs.json +++ b/app/src/translations/cs.json @@ -129,6 +129,7 @@ "settings.lastn": null, "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Není možné uložit soubor", "filesharing.startingFileShare": "Pokouším se sdílet soubor", diff --git a/app/src/translations/de.json b/app/src/translations/de.json index 6d72d54..3eabb28 100644 --- a/app/src/translations/de.json +++ b/app/src/translations/de.json @@ -130,6 +130,7 @@ "settings.lastn": "Anzahl der sichtbaren Videos", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Fehler beim Speichern der Datei", "filesharing.startingFileShare": "Starte Teilen der Datei", diff --git a/app/src/translations/dk.json b/app/src/translations/dk.json index 351907c..cab3586 100644 --- a/app/src/translations/dk.json +++ b/app/src/translations/dk.json @@ -130,6 +130,7 @@ "settings.lastn": "Antal synlige videoer", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Kan ikke gemme fil", "filesharing.startingFileShare": "Forsøger at dele filen", diff --git a/app/src/translations/el.json b/app/src/translations/el.json index 8f34eb1..5a83427 100644 --- a/app/src/translations/el.json +++ b/app/src/translations/el.json @@ -130,6 +130,7 @@ "settings.lastn": "Αριθμός ορατών βίντεο", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου", "filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου", diff --git a/app/src/translations/en.json b/app/src/translations/en.json index 660585d..2b40a30 100644 --- a/app/src/translations/en.json +++ b/app/src/translations/en.json @@ -130,6 +130,7 @@ "settings.lastn": "Number of visible videos", "settings.hiddenControls": "Hidden media controls", "settings.notificationSounds": "Notification sounds", + "settings.showNotifications": "Show notifications", "filesharing.saveFileError": "Unable to save file", "filesharing.startingFileShare": "Attempting to share file", diff --git a/app/src/translations/es.json b/app/src/translations/es.json index f685b8f..63c2f7d 100644 --- a/app/src/translations/es.json +++ b/app/src/translations/es.json @@ -130,6 +130,7 @@ "settings.lastn": "Cantidad de videos visibles", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "No ha sido posible guardar el fichero", "filesharing.startingFileShare": "Intentando compartir el fichero", diff --git a/app/src/translations/fr.json b/app/src/translations/fr.json index d1a67cf..4471861 100644 --- a/app/src/translations/fr.json +++ b/app/src/translations/fr.json @@ -130,6 +130,7 @@ "settings.lastn": "Nombre de vidéos visibles", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Impossible d'enregistrer le fichier", "filesharing.startingFileShare": "Début du transfert de fichier", diff --git a/app/src/translations/hr.json b/app/src/translations/hr.json index f08f516..682dfbe 100644 --- a/app/src/translations/hr.json +++ b/app/src/translations/hr.json @@ -130,6 +130,7 @@ "settings.lastn": "Broj vidljivih videozapisa", "settings.hiddenControls": "Skrivene kontrole medija", "settings.notificationSounds": "Zvuk obavijesti", + "settings.showNotifications": null, "filesharing.saveFileError": "Nije moguće spremiti datoteku", "filesharing.startingFileShare": "Pokušaj dijeljenja datoteke", diff --git a/app/src/translations/hu.json b/app/src/translations/hu.json index c3dd780..262f117 100644 --- a/app/src/translations/hu.json +++ b/app/src/translations/hu.json @@ -130,6 +130,7 @@ "settings.lastn": "A látható videók száma", "settings.hiddenControls": "Média Gombok automatikus elrejtése", "settings.notificationSounds": "Értesítések hangjelzéssel", + "settings.showNotifications": null, "filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.startingFileShare": "Fájl megosztása", diff --git a/app/src/translations/it.json b/app/src/translations/it.json index 472ce91..27b3194 100644 --- a/app/src/translations/it.json +++ b/app/src/translations/it.json @@ -129,6 +129,7 @@ "settings.lastn": "Numero di video visibili", "settings.hiddenControls": "Controlli media nascosti", "settings.notificationSounds": "Suoni di notifica", + "settings.showNotifications": null, "filesharing.saveFileError": "Impossibile salvare file", "filesharing.startingFileShare": "Tentativo di condivisione file", diff --git a/app/src/translations/lv.json b/app/src/translations/lv.json index dd8fac4..d355c84 100644 --- a/app/src/translations/lv.json +++ b/app/src/translations/lv.json @@ -124,6 +124,7 @@ "settings.lastn": "Jums redzamo video/kameru skaits", "settings.hiddenControls": "Slēpto mediju vadība", "settings.notificationSounds": "Paziņojumu skaņas", + "settings.showNotifications": null, "filesharing.saveFileError": "Nav iespējams saglabāt failu", "filesharing.startingFileShare": "Tiek mēģināts kopīgot failu", diff --git a/app/src/translations/nb.json b/app/src/translations/nb.json index 58e3b08..59edd6c 100644 --- a/app/src/translations/nb.json +++ b/app/src/translations/nb.json @@ -130,6 +130,7 @@ "settings.lastn": "Antall videoer synlig", "settings.hiddenControls": "Skjul media knapper", "settings.notificationSounds": "Varslingslyder", + "settings.showNotifications": "Vis varslinger", "filesharing.saveFileError": "Klarte ikke å lagre fil", "filesharing.startingFileShare": "Starter fildeling", diff --git a/app/src/translations/pl.json b/app/src/translations/pl.json index 923de79..1413c73 100644 --- a/app/src/translations/pl.json +++ b/app/src/translations/pl.json @@ -130,6 +130,7 @@ "settings.lastn": "Liczba widocznych uczestników (zdalnych)", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Nie można zapisać pliku", "filesharing.startingFileShare": "Próba udostępnienia pliku", diff --git a/app/src/translations/pt.json b/app/src/translations/pt.json index b863098..3b0a078 100644 --- a/app/src/translations/pt.json +++ b/app/src/translations/pt.json @@ -130,6 +130,7 @@ "settings.lastn": "Número de vídeos visíveis", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Impossível de gravar o ficheiro", "filesharing.startingFileShare": "Tentando partilha de ficheiro", diff --git a/app/src/translations/ro.json b/app/src/translations/ro.json index 624c231..e666ddf 100644 --- a/app/src/translations/ro.json +++ b/app/src/translations/ro.json @@ -130,6 +130,7 @@ "settings.lastn": "Numărul de videoclipuri vizibile", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat", "filesharing.startingFileShare": "Partajarea fișierului", diff --git a/app/src/translations/tr.json b/app/src/translations/tr.json index 8f9c29f..16aaae0 100644 --- a/app/src/translations/tr.json +++ b/app/src/translations/tr.json @@ -127,6 +127,7 @@ "settings.lastn": "İzlenebilir video sayısı", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Dosya kaydedilemiyor", "filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor", diff --git a/app/src/translations/uk.json b/app/src/translations/uk.json index db4a082..fe9dd52 100644 --- a/app/src/translations/uk.json +++ b/app/src/translations/uk.json @@ -130,6 +130,7 @@ "settings.lastn": "Кількість видимих ​​відео", "settings.hiddenControls": null, "settings.notificationSounds": null, + "settings.showNotifications": null, "filesharing.saveFileError": "Неможливо зберегти файл", "filesharing.startingFileShare": "Спроба поділитися файлом", From 7e9160927684e85d73ab41dcf8359b1ce7f42617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 7 May 2020 12:20:24 +0200 Subject: [PATCH 22/22] Have a global try-catch in the server --- server/package.json | 2 +- server/server.js | 101 ++++++++++++++++++++------------------------ 2 files changed, 46 insertions(+), 57 deletions(-) diff --git a/server/package.json b/server/package.json index ef96f36..d407d03 100644 --- a/server/package.json +++ b/server/package.json @@ -7,7 +7,7 @@ "license": "MIT", "main": "lib/index.js", "scripts": { - "start": "DEBUG=${DEBUG:='*mediasoup* *INFO* *WARN* *ERROR*'} INTERACTIVE=${INTERACTIVE:='true'} node server.js", + "start": "node server.js", "connect": "node connect.js", "lint": "eslint -c .eslintrc.json --ext .js *.js lib/" }, diff --git a/server/server.js b/server/server.js index 5820413..fcde0b1 100755 --- a/server/server.js +++ b/server/server.js @@ -127,69 +127,58 @@ let oidcStrategy; async function run() { - // Open the interactive server. - await interactiveServer(rooms, peers); - - // start Prometheus exporter - if (config.prometheus) + try { - await promExporter(rooms, peers, config.prometheus); - } + // Open the interactive server. + await interactiveServer(rooms, peers); - if (typeof(config.auth) === 'undefined') - { - logger.warn('Auth is not configured properly!'); - } - else - { - await setupAuth(); - } - - // Run a mediasoup Worker. - await runMediasoupWorkers(); - - // Run HTTPS server. - await runHttpsServer(); - - // Run WebSocketServer. - await runWebSocketServer(); - - // eslint-disable-next-line no-unused-vars - function errorHandler(err, req, res, next) - { - const trackingId = uuidv4(); - - res.status(500).send( - `

    Internal Server Error

    -

    If you report this error, please also report this - tracking ID which makes it possible to locate your session - in the logs which are available to the system administrator: - ${trackingId}

    ` - ); - logger.error( - 'Express error handler dump with tracking ID: %s, error dump: %o', - trackingId, err); - } - - app.use(errorHandler); - - // Log rooms status every 30 seconds. - setInterval(() => - { - for (const room of rooms.values()) + // start Prometheus exporter + if (config.prometheus) { - room.logStatus(); + await promExporter(rooms, peers, config.prometheus); } - }, 120000); - // check for deserted rooms - setInterval(() => - { - for (const room of rooms.values()) + if (typeof(config.auth) === 'undefined') { - room.checkEmpty(); + logger.warn('Auth is not configured properly!'); } - }, 10000); + else + { + await setupAuth(); + } + + // Run a mediasoup Worker. + await runMediasoupWorkers(); + + // Run HTTPS server. + await runHttpsServer(); + + // Run WebSocketServer. + await runWebSocketServer(); + + const errorHandler = (err, req, res, next) => + { + const trackingId = uuidv4(); + + res.status(500).send( + `

    Internal Server Error

    +

    If you report this error, please also report this + tracking ID which makes it possible to locate your session + in the logs which are available to the system administrator: + ${trackingId}

    ` + ); + logger.error( + 'Express error handler dump with tracking ID: %s, error dump: %o', + trackingId, err); + }; + + // eslint-disable-next-line no-unused-vars + app.use(errorHandler); + } + catch (error) + { + logger.error('run() [error:"%o"]', error); + } } function statusLog()