diff --git a/README.md b/README.md index 58c1f11302..a08769d014 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ BigBlueButton supports real-time sharing of audio, video, slides (with whiteboar Presenters can record and playback content for later sharing with others. -We designed BigBlueButton for online learning (though it can be used for many [other applications](http://www.c4isrnet.com/story/military-tech/disa/2015/02/11/disa-to-save-12m-defense-collaboration-services/23238997/)). The educational use cases for BigBlueButton are +We designed BigBlueButton for online learning, (though it can be used for many [other applications](https://www.c4isrnet.com/it-networks/2015/02/11/disa-to-replace-dco-with-new-collaboration-services-tool/) as well). The educational use cases for BigBlueButton are * Online tutoring (one-to-one) * Flipped classrooms (recording content ahead of your session) * Group collaboration (many-to-many) * Online classes (one-to-many) -You can install on a Ubuntu 18.04 64-bit server. We provide [bbb-install.sh](https://github.com/bigbluebutton/bbb-install) to let you have a server up and running within 30 minutes (or your money back 😉). +You can install on a Ubuntu 18.04 64-bit server. We provide [bbb-install.sh](https://github.com/bigbluebutton/bbb-install) to let you have a server up and running within 30 minutes (or your money back 😉). For full technical documentation BigBlueButton -- including architecture, features, API, and GreenLight (the default front-end) -- see [https://docs.bigbluebutton.org/](https://docs.bigbluebutton.org/). diff --git a/SECURITY.md b/SECURITY.md index cde70512b7..1dc6beba65 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,7 +12,7 @@ We actively support BigBlueButton through the community forums and through secur We have released 2.3 to the community and all our support efforts are now transitioned to 2.3. Also, BigBlueButton 2.2 is running on Ubuntu 16.04 which is now end of life. -As such, we highly recommend that all administrators deploy 2.3 going forward as it is built upon Ubuntu 18.04. You'll find [many improvements](/2.3/new.html) in this newer version. +As such, we highly recommend that all administrators deploy 2.3 going forward as it is built upon Ubuntu 18.04. You'll find [many improvements](https://docs.bigbluebutton.org/2.3/new.html) in this newer version. ## Reporting a Vulnerability diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/LearningDashboardActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/LearningDashboardActor.scala index f5f1d5ce93..e82ba50da4 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/LearningDashboardActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/LearningDashboardActor.scala @@ -1,6 +1,7 @@ package org.bigbluebutton.endpoint.redis import akka.actor.{Actor, ActorLogging, ActorSystem, Props} +import org.bigbluebutton.common2.domain.PresentationVO import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.util.JsonUtil import org.bigbluebutton.core.OutMessageGateway @@ -23,6 +24,7 @@ case class Meeting( users: Map[String, User] = Map(), polls: Map[String, Poll] = Map(), screenshares: Vector[Screenshare] = Vector(), + presentationSlides: Vector[PresentationSlide] = Vector(), createdOn: Long = System.currentTimeMillis(), endedOn: Long = 0, ) @@ -72,6 +74,12 @@ case class Screenshare( stoppedOn: Long = 0, ) +case class PresentationSlide( + presentationId: String, + pageNum: Long, + setOn: Long = System.currentTimeMillis(), +) + object LearningDashboardActor { def props( @@ -92,6 +100,7 @@ class LearningDashboardActor( private var meetings: Map[String, Meeting] = Map() private var meetingsLastJsonHash : Map[String,String] = Map() + private var meetingPresentations : Map[String,Map[String,PresentationVO]] = Map() system.scheduler.schedule(10.seconds, 10.seconds, self, SendPeriodicReport) @@ -108,6 +117,12 @@ class LearningDashboardActor( // Chat case m: GroupChatMessageBroadcastEvtMsg => handleGroupChatMessageBroadcastEvtMsg(m) + // Presentation + case m: PresentationConversionCompletedEvtMsg => handlePresentationConversionCompletedEvtMsg(m) + case m: SetCurrentPageEvtMsg => handleSetCurrentPageEvtMsg(m) + case m: RemovePresentationEvtMsg => handleRemovePresentationEvtMsg(m) + case m: SetCurrentPresentationEvtMsg => handleSetCurrentPresentationEvtMsg(m) + // User case m: UserJoinedMeetingEvtMsg => handleUserJoinedMeetingEvtMsg(m) case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m) @@ -151,6 +166,77 @@ class LearningDashboardActor( } } + private def handlePresentationConversionCompletedEvtMsg(msg: PresentationConversionCompletedEvtMsg) { + for { + meeting <- meetings.values.find(m => m.intId == msg.header.meetingId) + } yield { + val updatedPresentations = meetingPresentations.get(meeting.intId).getOrElse(Map()) + (msg.body.presentation.id -> msg.body.presentation) + meetingPresentations += (meeting.intId -> updatedPresentations) + if(msg.body.presentation.current == true) { + for { + page <- msg.body.presentation.pages.find(p => p.current == true) + } yield { + this.setPresentationSlide(meeting.intId, msg.body.presentation.id,page.num) + } + } + } + } + + private def handleSetCurrentPageEvtMsg(msg: SetCurrentPageEvtMsg) { + for { + meeting <- meetings.values.find(m => m.intId == msg.header.meetingId) + presentations <- meetingPresentations.get(meeting.intId) + presentation <- presentations.get(msg.body.presentationId) + page <- presentation.pages.find(p => p.id == msg.body.pageId) + } yield { + this.setPresentationSlide(meeting.intId, msg.body.presentationId,page.num) + } + } + + private def handleRemovePresentationEvtMsg(msg: RemovePresentationEvtMsg) { + for { + meeting <- meetings.values.find(m => m.intId == msg.header.meetingId) + } yield { + if(meeting.presentationSlides.last.presentationId == msg.body.presentationId) { + this.setPresentationSlide(meeting.intId, "",0) + } + } + } + + private def handleSetCurrentPresentationEvtMsg(msg: SetCurrentPresentationEvtMsg) { + for { + meeting <- meetings.values.find(m => m.intId == msg.header.meetingId) + } yield { + val presPreviousSlides: Vector[PresentationSlide] = meeting.presentationSlides.filter(p => p.presentationId == msg.body.presentationId); + if(presPreviousSlides.length > 0) { + //Set last page showed for this presentation + this.setPresentationSlide(meeting.intId, msg.body.presentationId,presPreviousSlides.last.pageNum) + } else { + //If none page was showed yet, set the current page (page 1 by default) + for { + presentations <- meetingPresentations.get(meeting.intId) + presentation <- presentations.get(msg.body.presentationId) + page <- presentation.pages.find(s => s.current == true) + } yield { + this.setPresentationSlide(meeting.intId, msg.body.presentationId,page.num) + } + } + } + } + + private def setPresentationSlide(meetingId: String, presentationId: String, pageNum: Long) { + for { + meeting <- meetings.values.find(m => m.intId == meetingId) + } yield { + if (meeting.presentationSlides.length == 0 || + meeting.presentationSlides.last.presentationId != presentationId || + meeting.presentationSlides.last.pageNum != pageNum) { + val updatedMeeting = meeting.copy(presentationSlides = meeting.presentationSlides :+ PresentationSlide(presentationId, pageNum)) + meetings += (updatedMeeting.intId -> updatedMeeting) + } + } + } + private def handleUserJoinedMeetingEvtMsg(msg: UserJoinedMeetingEvtMsg): Unit = { for { meeting <- meetings.values.find(m => m.intId == msg.header.meetingId) @@ -403,6 +489,7 @@ class LearningDashboardActor( //Send report one last time sendReport(updatedMeeting) + meetingPresentations = meetingPresentations.-(updatedMeeting.intId) meetings = meetings.-(updatedMeeting.intId) log.info(" removed for meeting {}.",updatedMeeting.intId) } @@ -426,7 +513,7 @@ class LearningDashboardActor( meetingsLastJsonHash += (meeting.intId -> activityJsonHash) - log.info("Activity Report sent for meeting {}",meeting.intId) + log.info("Learning Dashboard data sent for meeting {}",meeting.intId) } } diff --git a/bbb-learning-dashboard/src/App.css b/bbb-learning-dashboard/src/App.css index 4328cf33cd..c240bf3d88 100644 --- a/bbb-learning-dashboard/src/App.css +++ b/bbb-learning-dashboard/src/App.css @@ -1,42 +1,3 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - .col-text-left { text-align: left; } diff --git a/bbb-learning-dashboard/src/App.js b/bbb-learning-dashboard/src/App.js index 9c4262c575..7a680b25c1 100644 --- a/bbb-learning-dashboard/src/App.js +++ b/bbb-learning-dashboard/src/App.js @@ -1,12 +1,16 @@ import React from 'react'; import './App.css'; import './bbb-icons.css'; -import { FormattedMessage, FormattedDate, injectIntl } from 'react-intl'; +import { + FormattedMessage, FormattedDate, injectIntl, FormattedTime, +} from 'react-intl'; +import { emojiConfigs } from './services/EmojiService'; import Card from './components/Card'; import UsersTable from './components/UsersTable'; import StatusTable from './components/StatusTable'; import PollsTable from './components/PollsTable'; import ErrorMessage from './components/ErrorMessage'; +import { makeUserCSVData, tsToHHmmss } from './services/UserService'; class App extends React.Component { constructor(props) { @@ -19,6 +23,7 @@ class App extends React.Component { learningDashboardAccessToken: '', ldAccessTokenCopied: false, sessionToken: '', + lastUpdated: null, }; } @@ -29,6 +34,34 @@ class App extends React.Component { }, 10000); } + handleSaveSessionData(e) { + const { target: downloadButton } = e; + const { intl } = this.props; + const { activitiesJson } = this.state; + const { + name: meetingName, createdOn, users, polls, + } = activitiesJson; + const link = document.createElement('a'); + const data = makeUserCSVData(users, polls, intl); + const filename = `LearningDashboard_${meetingName}_${new Date(createdOn).toISOString().substr(0, 10)}.csv`.replace(/ /g, '-'); + + downloadButton.setAttribute('disabled', 'true'); + downloadButton.style.cursor = 'not-allowed'; + link.setAttribute('href', `data:application/octet-stream,${encodeURIComponent(data)}`); + link.setAttribute('download', filename); + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + downloadButton.innerHTML = intl.formatMessage({ id: 'app.learningDashboard.sessionDataDownloadedLabel', defaultMessage: 'Downloaded!' }); + setTimeout(() => { + downloadButton.innerHTML = intl.formatMessage({ id: 'app.learningDashboard.downloadSessionDataLabel', defaultMessage: 'Download Session Data' }); + downloadButton.removeAttribute('disabled'); + downloadButton.style.cursor = 'pointer'; + downloadButton.focus(); + }, 3000); + document.body.removeChild(link); + } + setDashboardParams() { let learningDashboardAccessToken = ''; let meetingId = ''; @@ -67,6 +100,39 @@ class App extends React.Component { this.fetchActivitiesJson); } + fetchMostUsedEmojis() { + const { activitiesJson } = this.state; + if (!activitiesJson) { return []; } + + // Icon elements + const emojis = [...Object.keys(emojiConfigs)]; + const icons = {}; + emojis.forEach((emoji) => { + icons[emoji] = (); + }); + + // Count each emoji + const emojiCount = {}; + emojis.forEach((emoji) => { + emojiCount[emoji] = 0; + }); + const allEmojisUsed = Object + .values(activitiesJson.users || {}) + .map((user) => user.emojis || []) + .flat(1); + allEmojisUsed.forEach((emoji) => { + emojiCount[emoji.name] += 1; + }); + + // Get the three most used + const mostUsedEmojis = Object + .entries(emojiCount) + .sort(([, countA], [, countB]) => countA - countB) + .reverse() + .slice(0, 3); + return mostUsedEmojis.map(([emoji]) => icons[emoji]); + } + fetchActivitiesJson() { const { learningDashboardAccessToken, meetingId, sessionToken } = this.state; @@ -74,7 +140,7 @@ class App extends React.Component { fetch(`${meetingId}/${learningDashboardAccessToken}/learning_dashboard_data.json`) .then((response) => response.json()) .then((json) => { - this.setState({ activitiesJson: json, loading: false }); + this.setState({ activitiesJson: json, loading: false, lastUpdated: Date.now() }); document.title = `Learning Dashboard - ${json.name}`; }).catch(() => { this.setState({ loading: false }); @@ -86,7 +152,7 @@ class App extends React.Component { .then((json) => { if (json.response.returncode === 'SUCCESS') { const jsonData = JSON.parse(json.response.data); - this.setState({ activitiesJson: jsonData, loading: false }); + this.setState({ activitiesJson: jsonData, loading: false, lastUpdated: Date.now() }); document.title = `Learning Dashboard - ${jsonData.name}`; } else { // When meeting is ended the sessionToken stop working, check for new cookies @@ -119,25 +185,21 @@ class App extends React.Component { render() { const { - activitiesJson, tab, sessionToken, loading, + activitiesJson, tab, sessionToken, loading, lastUpdated, learningDashboardAccessToken, ldAccessTokenCopied, } = this.state; const { intl } = this.props; document.title = `${intl.formatMessage({ id: 'app.learningDashboard.dashboardTitle', defaultMessage: 'Learning Dashboard' })} - ${activitiesJson.name}`; - function totalOfRaiseHand() { + function totalOfEmojis() { if (activitiesJson && activitiesJson.users) { return Object.values(activitiesJson.users) - .reduce((prevVal, elem) => prevVal + elem.emojis.filter((emoji) => emoji.name === 'raiseHand').length, 0); + .reduce((prevVal, elem) => prevVal + elem.emojis.length, 0); } return 0; } - function tsToHHmmss(ts) { - return (new Date(ts).toISOString().substr(11, 8)); - } - function totalOfActivity() { const minTime = Object.values(activitiesJson.users || {}).reduce((prevVal, elem) => { if (prevVal === 0 || elem.registeredOn < prevVal) return elem.registeredOn; @@ -257,7 +319,7 @@ class App extends React.Component { ) : null -} + }
{activitiesJson.name || ''} @@ -309,7 +371,7 @@ class App extends React.Component { } number={Object.values(activitiesJson.users || {}) .filter((u) => activitiesJson.endedOn > 0 || u.leftOn === 0).length} - cardClass="border-pink-500" + cardClass={tab === 'overview' ? 'border-pink-500' : 'hover:border-pink-500'} iconClass="bg-pink-50 text-pink-500" onClick={() => { this.setState({ tab: 'overview' }); @@ -331,52 +393,6 @@ class App extends React.Component { - - + + -

+

{ tab === 'overview' || tab === 'overview_activityscore' ? : null } { tab === 'status_timeline' - ? + ? : null } { tab === 'polling' ? @@ -441,6 +490,44 @@ class App extends React.Component { : null } +
+
+
+

+ { + lastUpdated && ( + <> + +   + +   + + + ) + } +

+
+ +
); } diff --git a/bbb-learning-dashboard/src/bbb-icons.css b/bbb-learning-dashboard/src/bbb-icons.css index 207ca1178b..fd2e1a725f 100755 --- a/bbb-learning-dashboard/src/bbb-icons.css +++ b/bbb-learning-dashboard/src/bbb-icons.css @@ -24,6 +24,15 @@ -moz-osx-font-smoothing: grayscale; } +.bbb-icon-card { + font-size: 1.25rem; + font-weight: bold; +} + +.bbb-icon-timeline { + font-weight: bold; +} + .icon-bbb-screenshare-fullscreen:before { content: "\e92a"; } diff --git a/bbb-learning-dashboard/src/components/Card.jsx b/bbb-learning-dashboard/src/components/Card.jsx index da3aa404e5..a81950b609 100644 --- a/bbb-learning-dashboard/src/components/Card.jsx +++ b/bbb-learning-dashboard/src/components/Card.jsx @@ -5,8 +5,36 @@ function Card(props) { number, name, children, iconClass, cardClass, } = props; + let icons; + + try { + React.Children.only(children); + icons = ( +
+ { children } +
+ ); + } catch (e) { + icons = ( +
+ { + React.Children.map(children, (child, index) => { + let offset = 4 / (index + 1); + offset = index === (React.Children.count(children) - 1) ? 0 : offset; + + return ( +
+ { child } +
+ ); + }) + } +
+ ); + } + return ( -
+

{ number } @@ -15,9 +43,7 @@ function Card(props) { { name }

-
- { children } -
+ {icons}
); } diff --git a/bbb-learning-dashboard/src/components/PollsTable.jsx b/bbb-learning-dashboard/src/components/PollsTable.jsx index fb4d34cb14..fa1efc9500 100644 --- a/bbb-learning-dashboard/src/components/PollsTable.jsx +++ b/bbb-learning-dashboard/src/components/PollsTable.jsx @@ -14,6 +14,41 @@ class PollsTable extends React.Component { return ''; } + if (typeof polls === 'object' && Object.values(polls).length === 0) { + return ( +
+
+ + + +
+

+ +

+

+ +

+
+ ); + } + return ( diff --git a/bbb-learning-dashboard/src/components/StatusTable.jsx b/bbb-learning-dashboard/src/components/StatusTable.jsx index c6af05b0e0..bc35f2e78f 100644 --- a/bbb-learning-dashboard/src/components/StatusTable.jsx +++ b/bbb-learning-dashboard/src/components/StatusTable.jsx @@ -1,9 +1,26 @@ import React from 'react'; import { FormattedMessage, injectIntl } from 'react-intl'; -import { getUserEmojisSummary, emojiConfigs } from '../services/EmojiService'; +import { emojiConfigs, filterUserEmojis } from '../services/EmojiService'; import UserAvatar from './UserAvatar'; class StatusTable extends React.Component { + componentDidMount() { + // This code is needed to prevent the emoji in the first cell + // after the username from overflowing + const emojis = document.getElementsByClassName('emojiOnFirstCell'); + for (let i = 0; i < emojis.length; i += 1) { + const emojiStyle = window.getComputedStyle(emojis[i]); + let offsetLeft = emojiStyle + .left + .replace(/px/g, '') + .trim(); + offsetLeft = Number(offsetLeft); + if (offsetLeft < 0) { + emojis[i].style.offsetLeft = '0px'; + } + } + } + render() { const spanMinutes = 10 * 60000; // 10 minutes default const { allUsers, intl } = this.props; @@ -32,7 +49,7 @@ class StatusTable extends React.Component {
- ); }) } diff --git a/bbb-learning-dashboard/src/components/UsersTable.jsx b/bbb-learning-dashboard/src/components/UsersTable.jsx index c900b91c89..58b9101eee 100644 --- a/bbb-learning-dashboard/src/components/UsersTable.jsx +++ b/bbb-learning-dashboard/src/components/UsersTable.jsx @@ -3,6 +3,7 @@ import { FormattedMessage, FormattedDate, FormattedNumber, injectIntl, } from 'react-intl'; import { getUserEmojisSummary, emojiConfigs } from '../services/EmojiService'; +import { getActivityScore, getSumOfTime, tsToHHmmss } from '../services/UserService'; import UserAvatar from './UserAvatar'; class UsersTable extends React.Component { @@ -30,57 +31,6 @@ class UsersTable extends React.Component { const { activityscoreOrder } = this.state; - function getSumOfTime(eventsArr) { - return eventsArr.reduce((prevVal, elem) => { - if (elem.stoppedOn > 0) return prevVal + (elem.stoppedOn - elem.startedOn); - return prevVal + (new Date().getTime() - elem.startedOn); - }, 0); - } - - function getActivityScore(user) { - if (user.isModerator) return 0; - - const allUsersArr = Object.values(allUsers || {}).filter((currUser) => !currUser.isModerator); - let userPoints = 0; - - // Calculate points of Talking - const usersTalkTime = allUsersArr.map((currUser) => currUser.talk.totalTime); - const maxTalkTime = Math.max(...usersTalkTime); - if (maxTalkTime > 0) { - userPoints += (user.talk.totalTime / maxTalkTime) * 2; - } - - // Calculate points of Chatting - const usersTotalOfMessages = allUsersArr.map((currUser) => currUser.totalOfMessages); - const maxMessages = Math.max(...usersTotalOfMessages); - if (maxMessages > 0) { - userPoints += (user.totalOfMessages / maxMessages) * 2; - } - - // Calculate points of Raise hand - const usersRaiseHand = allUsersArr.map((currUser) => currUser.emojis.filter((emoji) => emoji.name === 'raiseHand').length); - const maxRaiseHand = Math.max(...usersRaiseHand); - const userRaiseHand = user.emojis.filter((emoji) => emoji.name === 'raiseHand').length; - if (maxRaiseHand > 0) { - userPoints += (userRaiseHand / maxRaiseHand) * 2; - } - - // Calculate points of Emojis - const usersEmojis = allUsersArr.map((currUser) => currUser.emojis.filter((emoji) => emoji.name !== 'raiseHand').length); - const maxEmojis = Math.max(...usersEmojis); - const userEmojis = user.emojis.filter((emoji) => emoji.name !== 'raiseHand').length; - if (maxEmojis > 0) { - userPoints += (userEmojis / maxEmojis) * 2; - } - - // Calculate points of Polls - if (totalOfPolls > 0) { - userPoints += (Object.values(user.answers || {}).length / totalOfPolls) * 2; - } - - return userPoints; - } - const usersEmojisSummary = {}; Object.values(allUsers || {}).forEach((user) => { usersEmojisSummary[user.intId] = getUserEmojisSummary(user, 'raiseHand'); @@ -91,13 +41,9 @@ class UsersTable extends React.Component { return Math.ceil((totalUserOnlineTime / totalOfActivityTime) * 100); } - function tsToHHmmss(ts) { - return (new Date(ts).toISOString().substr(11, 8)); - } - const usersActivityScore = {}; Object.values(allUsers || {}).forEach((user) => { - usersActivityScore[user.intId] = getActivityScore(user); + usersActivityScore[user.intId] = getActivityScore(user, allUsers, totalOfPolls); }); return ( @@ -187,49 +133,48 @@ class UsersTable extends React.Component { if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; return 0; }) - .map((user) => ( - - + - + - + + + + - - - + { - Object.keys(usersEmojisSummary[user.intId] || {}).map((emoji) => ( -
- -   - { usersEmojisSummary[user.intId][emoji] } -   - -
- )) - } - - - { !user.isModerator ? ( - ) : - - )) + + + ); + }) ) : (
+ ( -
- +
+
@@ -71,81 +88,97 @@ class StatusTable extends React.Component {
{ periods.map((period) => { - const userEmojisInPeriod = getUserEmojisSummary(user, + const userEmojisInPeriod = filterUserEmojis(user, null, period, period + spanMinutes); + const { registeredOn, leftOn } = user; + const boundaryLeft = period; + const boundaryRight = period + spanMinutes - 1; return ( -
+ { - user.registeredOn > period && user.registeredOn < period + spanMinutes + (registeredOn >= boundaryLeft && registeredOn <= boundaryRight) + || (leftOn >= boundaryLeft && leftOn <= boundaryRight) + || (boundaryLeft > registeredOn && boundaryRight < leftOn) + || (boundaryLeft >= registeredOn && leftOn === 0) ? ( - - - = boundaryLeft + && registeredOn <= boundaryRight ? 'rounded-l' : ''; + let roundedRight = leftOn > boundaryLeft + && leftOn < boundaryRight ? 'rounded-r' : ''; + let offsetLeft = 0; + let offsetRight = 0; + if (registeredOn >= boundaryLeft && registeredOn <= boundaryRight) { + offsetLeft = ((registeredOn - boundaryLeft) * 100) / spanMinutes; + } + if (leftOn >= boundaryLeft && leftOn <= boundaryRight) { + offsetRight = ((boundaryRight - leftOn) * 100) / spanMinutes; + } + let width = ''; + if (offsetLeft === 0 && offsetRight >= 99) { + width = 'w-1.5'; + } + if (offsetRight === 0 && offsetLeft >= 99) { + width = 'w-1.5'; + } + if (offsetLeft && offsetRight) { + const variation = offsetLeft - offsetRight; + if ( + variation > -1 && variation < 1 + ) { + width = 'w-1.5'; + } + } + const isRTL = document.dir === 'rtl'; + if (isRTL) { + const aux = roundedRight; + + if (roundedLeft !== '') roundedRight = 'rounded-r'; + else roundedRight = ''; + + if (aux !== '') roundedLeft = 'rounded-l'; + else roundedLeft = ''; + } + // height / 2 + const redress = '(0.375rem / 2)'; + return ( +
- - + ); + })() ) : null } - { Object.keys(userEmojisInPeriod) - .map((emoji) => ( -
- -   - { userEmojisInPeriod[emoji] } -   - + { userEmojisInPeriod.map((emoji) => { + const offset = ((emoji.sentOn - period) * 100) / spanMinutes; + const origin = document.dir === 'rtl' ? 'right' : 'left'; + const onFirstCell = period === firstRegisteredOnTime; + // font-size / 2 + padding right/left + border-width + const redress = '(0.875rem / 2 + 0.25rem + 2px)'; + return ( +
+
- )) } - { - user.leftOn > period && user.leftOn < period + spanMinutes - ? ( - - - - - - ) : null - } + ); + })}
-
- {/* */} - - -     -
-

- {user.name} -

-

- - - - { + const opacity = user.leftOn > 0 ? 'opacity-75' : ''; + return ( +

+
+ + +     +
+

+ {user.name} +

+

+ + + + +

+ { user.leftOn > 0 ? (

@@ -259,154 +204,154 @@ class UsersTable extends React.Component {

) : null - } -
-
- - - -   - { tsToHHmmss( - (user.leftOn > 0 - ? user.leftOn - : (new Date()).getTime()) - user.registeredOn, - ) } -
-
+ } +
+
+ + + +   + { tsToHHmmss( + (user.leftOn > 0 + ? user.leftOn + : (new Date()).getTime()) - user.registeredOn, + ) } +
-
-
- { user.talk.totalTime > 0 - ? ( - - - +
+
+
+ { user.talk.totalTime > 0 + ? ( + + + + +   + { tsToHHmmss(user.talk.totalTime) } + + ) : null } + + { getSumOfTime(user.webcams) > 0 + ? ( + + + + +   + { tsToHHmmss(getSumOfTime(user.webcams)) } + + ) : null } + + { user.totalOfMessages > 0 + ? ( + + + + +   + {user.totalOfMessages} + + ) : null } + + { + Object.keys(usersEmojisSummary[user.intId] || {}).map((emoji) => ( +
+ +   + { usersEmojisSummary[user.intId][emoji] } +   + - -   - { tsToHHmmss(user.talk.totalTime) } - - ) : null } -
- { getSumOfTime(user.webcams) > 0 - ? ( - - - - -   - { tsToHHmmss(getSumOfTime(user.webcams)) } - - ) : null } - - { user.totalOfMessages > 0 - ? ( - - - - -   - {user.totalOfMessages} - - ) : null } - + + )) + } + + { user.emojis.filter((emoji) => emoji.name === 'raiseHand').length > 0 + ? ( + + + + +   + {user.emojis.filter((emoji) => emoji.name === 'raiseHand').length} + + ) : null } + - { user.emojis.filter((emoji) => emoji.name === 'raiseHand').length > 0 - ? ( - - - - -   - {user.emojis.filter((emoji) => emoji.name === 'raiseHand').length} - - ) : null } - + 0 ? '#A7F3D0' : '#e4e4e7'} /> 2 ? '#6EE7B7' : '#e4e4e7'} /> @@ -422,23 +367,24 @@ class UsersTable extends React.Component { } - - { - user.leftOn > 0 - ? ( - - - - ) - : ( - - - - ) - } -
+ { + user.leftOn > 0 + ? ( + + + + ) + : ( + + + + ) + } +
diff --git a/bbb-learning-dashboard/src/index.css b/bbb-learning-dashboard/src/index.css index a3319c71b3..4949bc1fe1 100644 --- a/bbb-learning-dashboard/src/index.css +++ b/bbb-learning-dashboard/src/index.css @@ -1,4 +1,34 @@ /* ./src/index.css */ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +@layer utilities { + .bg-inherit { + background-color: inherit; + } +} + +.translate-x-0 { + --tw-translate-x: 0px; +} + +.translate-x-2 { + --tw-translate-x: 0.5rem; +} + +.translate-x-4 { + --tw-translate-x: 1rem; +} + +[dir="rtl"] .translate-x-0 { + --tw-translate-x: 0px; +} + +[dir="rtl"] .translate-x-2 { + --tw-translate-x: -0.5rem; +} + +[dir="rtl"] .translate-x-4 { + --tw-translate-x: -1rem; +} diff --git a/bbb-learning-dashboard/src/services/EmojiService.js b/bbb-learning-dashboard/src/services/EmojiService.js index b64fdc98ff..20fdd737ce 100644 --- a/bbb-learning-dashboard/src/services/EmojiService.js +++ b/bbb-learning-dashboard/src/services/EmojiService.js @@ -60,3 +60,15 @@ export function getUserEmojisSummary(user, skipNames = null, start = null, end = }); return userEmojis; } + +export function filterUserEmojis(user, skipNames = null, start = null, end = null) { + const userEmojis = []; + user.emojis.forEach((emoji) => { + if (typeof emojiConfigs[emoji.name] === 'undefined') return; + if (skipNames != null && skipNames.split(',').indexOf(emoji.name) > -1) return; + if (start != null && emoji.sentOn < start) return; + if (end != null && emoji.sentOn > end) return; + userEmojis.push(emoji); + }); + return userEmojis; +} diff --git a/bbb-learning-dashboard/src/services/UserService.js b/bbb-learning-dashboard/src/services/UserService.js new file mode 100644 index 0000000000..84dc8a4c94 --- /dev/null +++ b/bbb-learning-dashboard/src/services/UserService.js @@ -0,0 +1,202 @@ +import { emojiConfigs, filterUserEmojis } from './EmojiService'; + +export function getActivityScore(user, allUsers, totalOfPolls) { + if (user.isModerator) return 0; + + const allUsersArr = Object.values(allUsers || {}).filter((currUser) => !currUser.isModerator); + let userPoints = 0; + + // Calculate points of Talking + const usersTalkTime = allUsersArr.map((currUser) => currUser.talk.totalTime); + const maxTalkTime = Math.max(...usersTalkTime); + if (maxTalkTime > 0) { + userPoints += (user.talk.totalTime / maxTalkTime) * 2; + } + + // Calculate points of Chatting + const usersTotalOfMessages = allUsersArr.map((currUser) => currUser.totalOfMessages); + const maxMessages = Math.max(...usersTotalOfMessages); + if (maxMessages > 0) { + userPoints += (user.totalOfMessages / maxMessages) * 2; + } + + // Calculate points of Raise hand + const usersRaiseHand = allUsersArr.map((currUser) => currUser.emojis.filter((emoji) => emoji.name === 'raiseHand').length); + const maxRaiseHand = Math.max(...usersRaiseHand); + const userRaiseHand = user.emojis.filter((emoji) => emoji.name === 'raiseHand').length; + if (maxRaiseHand > 0) { + userPoints += (userRaiseHand / maxRaiseHand) * 2; + } + + // Calculate points of Emojis + const usersEmojis = allUsersArr.map((currUser) => currUser.emojis.filter((emoji) => emoji.name !== 'raiseHand').length); + const maxEmojis = Math.max(...usersEmojis); + const userEmojis = user.emojis.filter((emoji) => emoji.name !== 'raiseHand').length; + if (maxEmojis > 0) { + userPoints += (userEmojis / maxEmojis) * 2; + } + + // Calculate points of Polls + if (totalOfPolls > 0) { + userPoints += (Object.values(user.answers || {}).length / totalOfPolls) * 2; + } + + return userPoints; +} + +export function getSumOfTime(eventsArr) { + return eventsArr.reduce((prevVal, elem) => { + if (elem.stoppedOn > 0) return prevVal + (elem.stoppedOn - elem.startedOn); + return prevVal + (new Date().getTime() - elem.startedOn); + }, 0); +} + +export function tsToHHmmss(ts) { + return (new Date(ts).toISOString().substr(11, 8)); +} + +const tableHeaderFields = [ + { + id: 'name', + defaultMessage: 'Name', + }, + { + id: 'moderator', + defaultMessage: 'Moderator', + }, + { + id: 'activityScore', + defaultMessage: 'Activity Score', + }, + { + id: 'colTalk', + defaultMessage: 'Talk Time', + }, + { + id: 'colWebcam', + defaultMessage: 'Webcam Time', + }, + { + id: 'colMessages', + defaultMessage: 'Messages', + }, + { + id: 'colEmojis', + defaultMessage: 'Emojis', + }, + { + id: 'pollVotes', + defaultMessage: 'Poll Votes', + }, + { + id: 'colRaiseHands', + defaultMessage: 'Raise Hands', + }, + { + id: 'join', + defaultMessage: 'Join', + }, + { + id: 'left', + defaultMessage: 'Left', + }, + { + id: 'duration', + defaultMessage: 'Duration', + }, +]; + +export function makeUserCSVData(users, polls, intl) { + const userRecords = {}; + const userValues = Object.values(users || {}); + const pollValues = Object.values(polls || {}); + const skipEmojis = Object + .keys(emojiConfigs) + .filter((emoji) => emoji !== 'raiseHand') + .join(','); + + for (let i = 0; i < userValues.length; i += 1) { + const user = userValues[i]; + const webcam = getSumOfTime(user.webcams); + const duration = user.leftOn > 0 + ? user.leftOn - user.registeredOn + : (new Date()).getTime() - user.registeredOn; + + const userData = { + name: user.name, + moderator: user.isModerator.toString().toUpperCase(), + activityScore: intl.formatNumber( + getActivityScore(user, userValues, Object.values(polls || {}).length), + { + minimumFractionDigits: 0, + maximumFractionDigits: 1, + }, + ), + talk: user.talk.totalTime > 0 ? tsToHHmmss(user.talk.totalTime) : '-', + webcam: webcam > 0 ? tsToHHmmss(webcam) : '-', + messages: user.totalOfMessages, + raiseHand: filterUserEmojis(user, 'raiseHand').length, + answers: Object.keys(user.answers).length, + emojis: filterUserEmojis(user, skipEmojis).length, + registeredOn: intl.formatDate(user.registeredOn, { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }), + leftOn: intl.formatDate(user.leftOn, { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }), + duration: tsToHHmmss(duration), + }; + + for (let j = 0; j < pollValues.length; j += 1) { + userData[`Poll_${j}`] = user.answers[pollValues[j].pollId] || '-'; + } + + const userFields = Object + .values(userData) + .map((data) => `"${data}"`); + + userRecords[user.intId] = userFields.join(','); + } + + const tableHeaderFieldsTranslated = tableHeaderFields + .map(({ id, defaultMessage }) => intl.formatMessage({ + id: `app.learningDashboard.usersTable.${id}`, + defaultMessage, + })); + + let header = tableHeaderFieldsTranslated.join(','); + let anonymousRecord = `"${intl.formatMessage({ + id: 'app.learningDashboard.pollsTable.anonymousRowName', + defaultMessage: 'Anonymous', + })}"`; + + // Skip the fields for the anonymous record + for (let k = 0; k < header.split(',').length - 1; k += 1) { + // Empty fields + anonymousRecord += ',""'; + } + + for (let i = 0; i < pollValues.length; i += 1) { + // Add the poll question headers + header += `,${pollValues[i].question || `Poll ${i + 1}`}`; + + // Add the anonymous answers + anonymousRecord += `,"${pollValues[i].anonymousAnswers.join('\r\n')}"`; + } + userRecords.Anonymous = anonymousRecord; + + return [ + header, + Object.values(userRecords).join('\r\n'), + ].join('\r\n'); +} diff --git a/bbb-learning-dashboard/tailwind.config.js b/bbb-learning-dashboard/tailwind.config.js index 7b39079d2d..664d9719fa 100644 --- a/bbb-learning-dashboard/tailwind.config.js +++ b/bbb-learning-dashboard/tailwind.config.js @@ -8,4 +8,4 @@ module.exports = { extend: {}, }, plugins: [], -} +}; diff --git a/bigbluebutton-html5/client/collection-mirror-initializer.js b/bigbluebutton-html5/client/collection-mirror-initializer.js new file mode 100644 index 0000000000..232381e665 --- /dev/null +++ b/bigbluebutton-html5/client/collection-mirror-initializer.js @@ -0,0 +1,92 @@ +import AbstractCollection from '/imports/ui/services/LocalCollectionSynchronizer/LocalCollectionSynchronizer'; + +// Collections +import Presentations from '/imports/api/presentations'; +import PresentationPods from '/imports/api/presentation-pods'; +import PresentationUploadToken from '/imports/api/presentation-upload-token'; +import Screenshare from '/imports/api/screenshare'; +import UserInfos from '/imports/api/users-infos'; +import Polls, { CurrentPoll } from '/imports/api/polls'; +import UsersPersistentData from '/imports/api/users-persistent-data'; +import UserSettings from '/imports/api/users-settings'; +import VideoStreams from '/imports/api/video-streams'; +import VoiceUsers from '/imports/api/voice-users'; +import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user'; +import Note from '/imports/api/note'; +import GroupChat from '/imports/api/group-chat'; +import ConnectionStatus from '/imports/api/connection-status'; +import Captions from '/imports/api/captions'; +import AuthTokenValidation from '/imports/api/auth-token-validation'; +import Annotations from '/imports/api/annotations'; +import Breakouts from '/imports/api/breakouts'; +import guestUsers from '/imports/api/guest-users'; +import Meetings, { RecordMeetings, ExternalVideoMeetings, MeetingTimeRemaining } from '/imports/api/meetings'; +import { UsersTyping } from '/imports/api/group-chat-msg'; +import Users, { CurrentUser } from '/imports/api/users'; +import { Slides, SlidePositions } from '/imports/api/slides'; + +// Custom Publishers +export const localCurrentPollSync = new AbstractCollection(CurrentPoll, CurrentPoll); +export const localCurrentUserSync = new AbstractCollection(CurrentUser, CurrentUser); +export const localSlidesSync = new AbstractCollection(Slides, Slides); +export const localSlidePositionsSync = new AbstractCollection(SlidePositions, SlidePositions); +export const localPollsSync = new AbstractCollection(Polls, Polls); +export const localPresentationsSync = new AbstractCollection(Presentations, Presentations); +export const localPresentationPodsSync = new AbstractCollection(PresentationPods, PresentationPods); +export const localPresentationUploadTokenSync = new AbstractCollection(PresentationUploadToken, PresentationUploadToken); +export const localScreenshareSync = new AbstractCollection(Screenshare, Screenshare); +export const localUserInfosSync = new AbstractCollection(UserInfos, UserInfos); +export const localUsersPersistentDataSync = new AbstractCollection(UsersPersistentData, UsersPersistentData); +export const localUserSettingsSync = new AbstractCollection(UserSettings, UserSettings); +export const localVideoStreamsSync = new AbstractCollection(VideoStreams, VideoStreams); +export const localVoiceUsersSync = new AbstractCollection(VoiceUsers, VoiceUsers); +export const localWhiteboardMultiUserSync = new AbstractCollection(WhiteboardMultiUser, WhiteboardMultiUser); +export const localNoteSync = new AbstractCollection(Note, Note); +export const localGroupChatSync = new AbstractCollection(GroupChat, GroupChat); +export const localConnectionStatusSync = new AbstractCollection(ConnectionStatus, ConnectionStatus); +export const localCaptionsSync = new AbstractCollection(Captions, Captions); +export const localAuthTokenValidationSync = new AbstractCollection(AuthTokenValidation, AuthTokenValidation); +export const localAnnotationsSync = new AbstractCollection(Annotations, Annotations); +export const localRecordMeetingsSync = new AbstractCollection(RecordMeetings, RecordMeetings); +export const localExternalVideoMeetingsSync = new AbstractCollection(ExternalVideoMeetings, ExternalVideoMeetings); +export const localMeetingTimeRemainingSync = new AbstractCollection(MeetingTimeRemaining, MeetingTimeRemaining); +export const localUsersTypingSync = new AbstractCollection(UsersTyping, UsersTyping); +export const localBreakoutsSync = new AbstractCollection(Breakouts, Breakouts); +export const localGuestUsersSync = new AbstractCollection(guestUsers, guestUsers); +export const localMeetingsSync = new AbstractCollection(Meetings, Meetings); +export const localUsersSync = new AbstractCollection(Users, Users); + +const collectionMirrorInitializer = () => { + localCurrentPollSync.setupListeners(); + localCurrentUserSync.setupListeners(); + localSlidesSync.setupListeners(); + localSlidePositionsSync.setupListeners(); + localPollsSync.setupListeners(); + localPresentationsSync.setupListeners(); + localPresentationPodsSync.setupListeners(); + localPresentationUploadTokenSync.setupListeners(); + localScreenshareSync.setupListeners(); + localUserInfosSync.setupListeners(); + localUsersPersistentDataSync.setupListeners(); + localUserSettingsSync.setupListeners(); + localVideoStreamsSync.setupListeners(); + localVoiceUsersSync.setupListeners(); + localWhiteboardMultiUserSync.setupListeners(); + localNoteSync.setupListeners(); + localGroupChatSync.setupListeners(); + localConnectionStatusSync.setupListeners(); + localCaptionsSync.setupListeners(); + localAuthTokenValidationSync.setupListeners(); + localAnnotationsSync.setupListeners(); + localRecordMeetingsSync.setupListeners(); + localExternalVideoMeetingsSync.setupListeners(); + localMeetingTimeRemainingSync.setupListeners(); + localUsersTypingSync.setupListeners(); + localBreakoutsSync.setupListeners(); + localGuestUsersSync.setupListeners(); + localMeetingsSync.setupListeners(); + localUsersSync.setupListeners(); +}; + +export default collectionMirrorInitializer; +// const localUsersSync = new AbstractCollection(CurrentUser, CurrentUser); diff --git a/bigbluebutton-html5/client/main.jsx b/bigbluebutton-html5/client/main.jsx index dca29dce58..05eabd656e 100755 --- a/bigbluebutton-html5/client/main.jsx +++ b/bigbluebutton-html5/client/main.jsx @@ -17,8 +17,6 @@ */ /* eslint no-unused-vars: 0 */ -import '../imports/ui/services/collection-hooks/collection-hooks'; - import React from 'react'; import { Meteor } from 'meteor/meteor'; import { render } from 'react-dom'; @@ -32,16 +30,15 @@ import ContextProviders from '/imports/ui/components/context-providers/component import ChatAdapter from '/imports/ui/components/components-data/chat-context/adapter'; import UsersAdapter from '/imports/ui/components/components-data/users-context/adapter'; import GroupChatAdapter from '/imports/ui/components/components-data/group-chat-context/adapter'; +import { liveDataEventBrokerInitializer } from '/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker'; -import '/imports/ui/local-collections/meetings-collection/meetings'; -import '/imports/ui/local-collections/breakouts-collection/breakouts'; -import '/imports/ui/local-collections/guest-users-collection/guest-users'; -import '/imports/ui/local-collections/users-collection/users'; +import collectionMirrorInitializer from './collection-mirror-initializer'; import('/imports/api/audio/client/bridge/bridge-whitelist').catch(() => { // bridge loading }); - +collectionMirrorInitializer(); +liveDataEventBrokerInitializer(); Meteor.startup(() => { // Logs all uncaught exceptions to the client logger window.addEventListener('error', (e) => { diff --git a/bigbluebutton-html5/imports/api/annotations/index.js b/bigbluebutton-html5/imports/api/annotations/index.js index 12e80e9ca4..ee550d0bb4 100755 --- a/bigbluebutton-html5/imports/api/annotations/index.js +++ b/bigbluebutton-html5/imports/api/annotations/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const Annotations = new Mongo.Collection('annotations'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const Annotations = new Mongo.Collection('annotations', collectionOptions); if (Meteor.isServer) { // types of queries for the annotations (Total): diff --git a/bigbluebutton-html5/imports/api/auth-token-validation/index.js b/bigbluebutton-html5/imports/api/auth-token-validation/index.js index b7186e60a9..0feacc2fb4 100644 --- a/bigbluebutton-html5/imports/api/auth-token-validation/index.js +++ b/bigbluebutton-html5/imports/api/auth-token-validation/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const AuthTokenValidation = new Mongo.Collection('auth-token-validation'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const AuthTokenValidation = new Mongo.Collection('auth-token-validation', collectionOptions); if (Meteor.isServer) { AuthTokenValidation._ensureIndex({ meetingId: 1, userId: 1 }); diff --git a/bigbluebutton-html5/imports/api/breakouts/index.js b/bigbluebutton-html5/imports/api/breakouts/index.js index 41c6ed54e4..d0c53512d6 100644 --- a/bigbluebutton-html5/imports/api/breakouts/index.js +++ b/bigbluebutton-html5/imports/api/breakouts/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const Breakouts = new Mongo.Collection('breakouts'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const Breakouts = new Mongo.Collection('breakouts', collectionOptions); if (Meteor.isServer) { // types of queries for the breakouts: diff --git a/bigbluebutton-html5/imports/api/captions/index.js b/bigbluebutton-html5/imports/api/captions/index.js index 85652495a2..859139869a 100644 --- a/bigbluebutton-html5/imports/api/captions/index.js +++ b/bigbluebutton-html5/imports/api/captions/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const Captions = new Mongo.Collection('captions'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const Captions = new Mongo.Collection('captions', collectionOptions); if (Meteor.isServer) { Captions._ensureIndex({ meetingId: 1, padId: 1 }); diff --git a/bigbluebutton-html5/imports/api/connection-status/index.js b/bigbluebutton-html5/imports/api/connection-status/index.js index d68450f84e..4e1b811805 100644 --- a/bigbluebutton-html5/imports/api/connection-status/index.js +++ b/bigbluebutton-html5/imports/api/connection-status/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const ConnectionStatus = new Mongo.Collection('connection-status'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const ConnectionStatus = new Mongo.Collection('connection-status', collectionOptions); if (Meteor.isServer) { ConnectionStatus._ensureIndex({ meetingId: 1, userId: 1 }); diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/index.js b/bigbluebutton-html5/imports/api/group-chat-msg/index.js index 37803a12ae..42b006cb76 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/index.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/index.js @@ -1,7 +1,11 @@ import { Meteor } from 'meteor/meteor'; +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + const GroupChatMsg = new Mongo.Collection('group-chat-msg'); -const UsersTyping = new Mongo.Collection('users-typing'); +const UsersTyping = new Mongo.Collection('users-typing', collectionOptions); if (Meteor.isServer) { GroupChatMsg._ensureIndex({ meetingId: 1, chatId: 1 }); diff --git a/bigbluebutton-html5/imports/api/group-chat/index.js b/bigbluebutton-html5/imports/api/group-chat/index.js index f26082d0a5..05381f370e 100644 --- a/bigbluebutton-html5/imports/api/group-chat/index.js +++ b/bigbluebutton-html5/imports/api/group-chat/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const GroupChat = new Mongo.Collection('group-chat'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const GroupChat = new Mongo.Collection('group-chat', collectionOptions); if (Meteor.isServer) { GroupChat._ensureIndex({ diff --git a/bigbluebutton-html5/imports/api/guest-users/index.js b/bigbluebutton-html5/imports/api/guest-users/index.js index 44ca2ae9c2..798317c4cf 100644 --- a/bigbluebutton-html5/imports/api/guest-users/index.js +++ b/bigbluebutton-html5/imports/api/guest-users/index.js @@ -1,5 +1,9 @@ import { Mongo } from 'meteor/mongo'; -const GuestUsers = new Mongo.Collection('guestUsers'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const GuestUsers = new Mongo.Collection('guestUsers', collectionOptions); export default GuestUsers; diff --git a/bigbluebutton-html5/imports/api/meetings/index.js b/bigbluebutton-html5/imports/api/meetings/index.js index d3c20af68d..0e42b89333 100644 --- a/bigbluebutton-html5/imports/api/meetings/index.js +++ b/bigbluebutton-html5/imports/api/meetings/index.js @@ -1,9 +1,13 @@ import { Meteor } from 'meteor/meteor'; -const Meetings = new Mongo.Collection('meetings'); -const RecordMeetings = new Mongo.Collection('record-meetings'); -const ExternalVideoMeetings = new Mongo.Collection('external-video-meetings'); -const MeetingTimeRemaining = new Mongo.Collection('meeting-time-remaining'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const Meetings = new Mongo.Collection('meetings', collectionOptions); +const RecordMeetings = new Mongo.Collection('record-meetings', collectionOptions); +const ExternalVideoMeetings = new Mongo.Collection('external-video-meetings', collectionOptions); +const MeetingTimeRemaining = new Mongo.Collection('meeting-time-remaining', collectionOptions); if (Meteor.isServer) { // types of queries for the meetings: diff --git a/bigbluebutton-html5/imports/api/note/index.js b/bigbluebutton-html5/imports/api/note/index.js index be3e2877a0..27ed4cec27 100644 --- a/bigbluebutton-html5/imports/api/note/index.js +++ b/bigbluebutton-html5/imports/api/note/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const Note = new Mongo.Collection('note'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const Note = new Mongo.Collection('note', collectionOptions); if (Meteor.isServer) { Note._ensureIndex({ meetingId: 1, noteId: 1 }); diff --git a/bigbluebutton-html5/imports/api/polls/index.js b/bigbluebutton-html5/imports/api/polls/index.js index 9be6cb854a..ac7fd05e73 100644 --- a/bigbluebutton-html5/imports/api/polls/index.js +++ b/bigbluebutton-html5/imports/api/polls/index.js @@ -1,7 +1,11 @@ import { Meteor } from 'meteor/meteor'; -const Polls = new Mongo.Collection('polls'); -export const CurrentPoll = new Mongo.Collection('current-poll'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const Polls = new Mongo.Collection('polls', collectionOptions); +export const CurrentPoll = new Mongo.Collection('current-poll', { connection: null }); if (Meteor.isServer) { // We can have just one active poll per meeting diff --git a/bigbluebutton-html5/imports/api/presentation-pods/index.js b/bigbluebutton-html5/imports/api/presentation-pods/index.js index 675d84e42c..14ced0fc9f 100644 --- a/bigbluebutton-html5/imports/api/presentation-pods/index.js +++ b/bigbluebutton-html5/imports/api/presentation-pods/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const PresentationPods = new Mongo.Collection('presentation-pods'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const PresentationPods = new Mongo.Collection('presentation-pods', collectionOptions); if (Meteor.isServer) { // types of queries for the presentation pods: diff --git a/bigbluebutton-html5/imports/api/presentation-upload-token/index.js b/bigbluebutton-html5/imports/api/presentation-upload-token/index.js index 11fdf5f478..4800f0a928 100644 --- a/bigbluebutton-html5/imports/api/presentation-upload-token/index.js +++ b/bigbluebutton-html5/imports/api/presentation-upload-token/index.js @@ -1,3 +1,7 @@ -const PresentationUploadToken = new Mongo.Collection('presentation-upload-token'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const PresentationUploadToken = new Mongo.Collection('presentation-upload-token', collectionOptions); export default PresentationUploadToken; diff --git a/bigbluebutton-html5/imports/api/presentations/index.js b/bigbluebutton-html5/imports/api/presentations/index.js index 0cd34458d4..d2c4f90ce2 100644 --- a/bigbluebutton-html5/imports/api/presentations/index.js +++ b/bigbluebutton-html5/imports/api/presentations/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const Presentations = new Mongo.Collection('presentations'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const Presentations = new Mongo.Collection('presentations', collectionOptions); if (Meteor.isServer) { // types of queries for the presentations: diff --git a/bigbluebutton-html5/imports/api/screenshare/index.js b/bigbluebutton-html5/imports/api/screenshare/index.js index 10d5c956d0..a24677805d 100644 --- a/bigbluebutton-html5/imports/api/screenshare/index.js +++ b/bigbluebutton-html5/imports/api/screenshare/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const Screenshare = new Mongo.Collection('screenshare'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const Screenshare = new Mongo.Collection('screenshare', collectionOptions); if (Meteor.isServer) { // types of queries for the screenshare: diff --git a/bigbluebutton-html5/imports/api/slides/index.js b/bigbluebutton-html5/imports/api/slides/index.js index 29b8f27c74..f35508d063 100755 --- a/bigbluebutton-html5/imports/api/slides/index.js +++ b/bigbluebutton-html5/imports/api/slides/index.js @@ -1,7 +1,11 @@ import { Meteor } from 'meteor/meteor'; -const Slides = new Mongo.Collection('slides'); -const SlidePositions = new Mongo.Collection('slide-positions'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const Slides = new Mongo.Collection('slides', collectionOptions); +const SlidePositions = new Mongo.Collection('slide-positions', collectionOptions); if (Meteor.isServer) { // types of queries for the slides: diff --git a/bigbluebutton-html5/imports/api/users-infos/index.js b/bigbluebutton-html5/imports/api/users-infos/index.js index 1b5d71ed25..414714f95c 100644 --- a/bigbluebutton-html5/imports/api/users-infos/index.js +++ b/bigbluebutton-html5/imports/api/users-infos/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const UserInfos = new Mongo.Collection('users-infos'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const UserInfos = new Mongo.Collection('users-infos', collectionOptions); if (Meteor.isServer) { UserInfos._ensureIndex({ meetingId: 1, userId: 1 }); diff --git a/bigbluebutton-html5/imports/api/users-persistent-data/index.js b/bigbluebutton-html5/imports/api/users-persistent-data/index.js index ab5a6d5337..718557757d 100644 --- a/bigbluebutton-html5/imports/api/users-persistent-data/index.js +++ b/bigbluebutton-html5/imports/api/users-persistent-data/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const UsersPersistentData = new Mongo.Collection('users-persistent-data'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const UsersPersistentData = new Mongo.Collection('users-persistent-data', collectionOptions); if (Meteor.isServer) { UsersPersistentData._ensureIndex({ meetingId: 1, userId: 1 }); diff --git a/bigbluebutton-html5/imports/api/users-settings/index.js b/bigbluebutton-html5/imports/api/users-settings/index.js index fc9e49a53e..8e18a21805 100644 --- a/bigbluebutton-html5/imports/api/users-settings/index.js +++ b/bigbluebutton-html5/imports/api/users-settings/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const UserSettings = new Mongo.Collection('users-settings'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const UserSettings = new Mongo.Collection('users-settings', collectionOptions); if (Meteor.isServer) { UserSettings._ensureIndex({ diff --git a/bigbluebutton-html5/imports/api/users/index.js b/bigbluebutton-html5/imports/api/users/index.js index 0df489b279..fcd49fe108 100644 --- a/bigbluebutton-html5/imports/api/users/index.js +++ b/bigbluebutton-html5/imports/api/users/index.js @@ -1,13 +1,17 @@ import { Meteor } from 'meteor/meteor'; -const Users = new Mongo.Collection('users'); -export const CurrentUser = new Mongo.Collection('current-user'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const Users = new Mongo.Collection('users', collectionOptions); +export const CurrentUser = new Mongo.Collection('current-user', { connection: null }); if (Meteor.isServer) { // types of queries for the users: // 1. meetingId // 2. meetingId, userId - + // { connection: Meteor.isClient ? null : true } Users._ensureIndex({ meetingId: 1, userId: 1 }); } diff --git a/bigbluebutton-html5/imports/api/video-streams/index.js b/bigbluebutton-html5/imports/api/video-streams/index.js index eaf2e253b6..465316a2d1 100644 --- a/bigbluebutton-html5/imports/api/video-streams/index.js +++ b/bigbluebutton-html5/imports/api/video-streams/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const VideoStreams = new Mongo.Collection('video-streams'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const VideoStreams = new Mongo.Collection('video-streams', collectionOptions); if (Meteor.isServer) { // types of queries for the video users: diff --git a/bigbluebutton-html5/imports/api/voice-users/index.js b/bigbluebutton-html5/imports/api/voice-users/index.js index 918050a46d..372109ff1c 100644 --- a/bigbluebutton-html5/imports/api/voice-users/index.js +++ b/bigbluebutton-html5/imports/api/voice-users/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const VoiceUsers = new Mongo.Collection('voiceUsers'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const VoiceUsers = new Mongo.Collection('voiceUsers', collectionOptions); if (Meteor.isServer) { // types of queries for the voice users: diff --git a/bigbluebutton-html5/imports/api/whiteboard-multi-user/index.js b/bigbluebutton-html5/imports/api/whiteboard-multi-user/index.js index 098583539e..1ab393129e 100644 --- a/bigbluebutton-html5/imports/api/whiteboard-multi-user/index.js +++ b/bigbluebutton-html5/imports/api/whiteboard-multi-user/index.js @@ -1,6 +1,10 @@ import { Meteor } from 'meteor/meteor'; -const WhiteboardMultiUser = new Mongo.Collection('whiteboard-multi-user'); +const collectionOptions = Meteor.isClient ? { + connection: null, +} : {}; + +const WhiteboardMultiUser = new Mongo.Collection('whiteboard-multi-user', collectionOptions); if (Meteor.isServer) { // types of queries for the whiteboard-multi-user: diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx index 9264718cb7..4199139e63 100755 --- a/bigbluebutton-html5/imports/startup/client/base.jsx +++ b/bigbluebutton-html5/imports/startup/client/base.jsx @@ -8,14 +8,14 @@ import MeetingEnded from '/imports/ui/components/meeting-ended/component'; import LoadingScreen from '/imports/ui/components/loading-screen/component'; import Settings from '/imports/ui/services/settings'; import logger from '/imports/startup/client/logger'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import { Session } from 'meteor/session'; import { FormattedMessage } from 'react-intl'; import { Meteor } from 'meteor/meteor'; import { RecordMeetings } from '../../api/meetings'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import AppService from '/imports/ui/components/app/service'; -import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts'; +import Breakouts from '/imports/api/breakouts'; import AudioService from '/imports/ui/components/audio/service'; import { notify } from '/imports/ui/services/notification'; import deviceInfo from '/imports/utils/deviceInfo'; @@ -218,6 +218,7 @@ class Base extends Component { if (approved && loading) this.updateLoadingState(false); if (prevProps.ejected || ejected) { + console.log(' if (prevProps.ejected || ejected) {'); Session.set('codeError', '403'); Session.set('isMeetingEnded', true); } @@ -227,6 +228,10 @@ class Base extends Component { this.setMeetingExisted(false); } + if ((prevProps.isMeteorConnected !== isMeteorConnected) && !isMeteorConnected) { + Session.set('globalIgnoreDeletes', true); + } + const enabled = HTML.classList.contains('animationsEnabled'); const disabled = HTML.classList.contains('animationsDisabled'); diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/captions/reader-menu/styles.js b/bigbluebutton-html5/imports/ui/components/actions-bar/captions/reader-menu/styles.js index 54a2565589..cffdface86 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/captions/reader-menu/styles.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/captions/reader-menu/styles.js @@ -2,7 +2,7 @@ import styled from 'styled-components'; import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints'; import Modal from '/imports/ui/components/modal/simple/component'; import { - colorBackground, + colorGrayDark, colorWhite, colorGrayLabel, colorGrayLight, @@ -16,7 +16,7 @@ const ReaderMenuModal = styled(Modal)` const Title = styled.header` display: block; - color: ${colorBackground}; + color: ${colorGrayDark}; font-size: 1.4rem; text-align: center; `; diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx index 547f474a6a..ae0625ad6f 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx @@ -29,6 +29,8 @@ const ActionsBarContainer = (props) => { const currentUser = { userId: Auth.userID, emoji: users[Auth.meetingID][Auth.userID].emoji }; + const amIPresenter = users[Auth.meetingID][Auth.userID].presenter; + return ( { currentUser, layoutContextDispatch, actionsBarStyle, + amIPresenter, } } /> @@ -49,11 +52,10 @@ const RAISE_HAND_BUTTON_ENABLED = Meteor.settings.public.app.raiseHandActionButt const OLD_MINIMIZE_BUTTON_ENABLED = Meteor.settings.public.presentation.oldMinimizeButton; export default withTracker(() => ({ - amIPresenter: Service.amIPresenter(), amIModerator: Service.amIModerator(), stopExternalVideoShare: ExternalVideoService.stopWatching, enableVideo: getFromUserSettings('bbb_enable_video', Meteor.settings.public.kurento.enableVideo), - isLayoutSwapped: getSwapLayout()&& shouldEnableSwapLayout(), + isLayoutSwapped: getSwapLayout() && shouldEnableSwapLayout(), toggleSwapLayout: MediaService.toggleSwapLayout, handleTakePresenter: Service.takePresenterRole, currentSlidHasContent: PresentationService.currentSlidHasContent(), diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/component.jsx index 86e19e009e..8af753071d 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/component.jsx @@ -183,7 +183,7 @@ const ScreenshareButton = ({ if (isSafari && !ScreenshareBridgeService.HAS_DISPLAY_MEDIA) { renderScreenshareUnavailableModal(); } else { - shareScreen(handleFailure); + shareScreen(amIPresenter, handleFailure); } }} id={isVideoBroadcasting ? 'unshare-screen-button' : 'share-screen-button'} diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/styles.js b/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/styles.js index 10a3bd33e2..2ee465ac81 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/styles.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/styles.js @@ -1,7 +1,7 @@ import styled from 'styled-components'; import Modal from '/imports/ui/components/modal/simple/component'; import Button from '/imports/ui/components/button/component'; -import { colorBackground, colorWhite } from '/imports/ui/stylesheets/styled-components/palette'; +import { colorGrayDark, colorWhite } from '/imports/ui/stylesheets/styled-components/palette'; import { jumboPaddingY, minModalHeight, @@ -19,7 +19,7 @@ const ScreenShareModal = styled(Modal)` const Title = styled.h3` font-weight: ${headingsFontWeight}; font-size: ${fontSizeLarge}; - color: ${colorBackground}; + color: ${colorGrayDark}; white-space: normal; padding-bottom: ${mdPaddingX}; `; diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js index d6f784b1b9..2f295970af 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/service.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/service.js @@ -1,8 +1,8 @@ import Auth from '/imports/ui/services/auth'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import { makeCall } from '/imports/ui/services/api'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; -import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts'; +import Meetings from '/imports/api/meetings'; +import Breakouts from '/imports/api/breakouts'; import { getVideoUrl } from '/imports/ui/components/external-video-player/service'; const USER_CONFIG = Meteor.settings.public.user; @@ -13,27 +13,16 @@ const getBreakouts = () => Breakouts.find({ parentMeetingId: Auth.meetingID }) .fetch() .sort((a, b) => a.sequence - b.sequence); -const currentBreakoutUsers = user => !Breakouts.findOne({ +const currentBreakoutUsers = (user) => !Breakouts.findOne({ 'joinedUsers.userId': new RegExp(`^${user.userId}`), }); -const filterBreakoutUsers = filter => users => users.filter(filter); +const filterBreakoutUsers = (filter) => (users) => users.filter(filter); const getUsersNotAssigned = filterBreakoutUsers(currentBreakoutUsers); const takePresenterRole = () => makeCall('assignPresenter', Auth.userID); -const amIPresenter = () => { - const currentUser = Users.findOne({ userId: Auth.userID }, - { fields: { presenter: 1 } }); - - if (!currentUser) { - return false; - } - - return currentUser.presenter; -}; - const amIModerator = () => { const currentUser = Users.findOne({ userId: Auth.userID }, { fields: { role: 1 } }); @@ -45,11 +34,9 @@ const amIModerator = () => { return currentUser.role === ROLE_MODERATOR; }; -const isMe = intId => intId === Auth.userID; - +const isMe = (intId) => intId === Auth.userID; export default { - amIPresenter, amIModerator, isMe, currentUser: () => Users.findOne({ meetingId: Auth.meetingID, userId: Auth.userID }, diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx index 3509d7e217..88c07f6ba0 100755 --- a/bigbluebutton-html5/imports/ui/components/app/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx @@ -3,8 +3,8 @@ import { withTracker } from 'meteor/react-meteor-data'; import { defineMessages, injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; import Auth from '/imports/ui/services/auth'; -import Users from '/imports/ui/local-collections/users-collection/users'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Users from '/imports/api/users'; +import Meetings from '/imports/api/meetings'; import { notify } from '/imports/ui/services/notification'; import CaptionsContainer from '/imports/ui/components/captions/container'; import CaptionsService from '/imports/ui/components/captions/service'; @@ -13,11 +13,11 @@ import deviceInfo from '/imports/utils/deviceInfo'; import UserInfos from '/imports/api/users-infos'; import Settings from '/imports/ui/services/settings'; import MediaService from '/imports/ui/components/media/service'; -import { - layoutSelect, - layoutSelectInput, - layoutSelectOutput, - layoutDispatch +import { + layoutSelect, + layoutSelectInput, + layoutSelectOutput, + layoutDispatch, } from '../layout/context'; import { @@ -119,10 +119,6 @@ const currentUserEmoji = (currentUser) => (currentUser ); export default injectIntl(withModalMounter(withTracker(({ intl, baseControls }) => { - if (Auth.connectionID !== Meteor.connection._lastSessionId) { - endMeeting('403'); - } - Users.find({ userId: Auth.userID, meetingId: Auth.meetingID }).observe({ removed() { endMeeting('403'); @@ -168,7 +164,7 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls }) const { viewScreenshare } = Settings.dataSaving; const shouldShowExternalVideo = MediaService.shouldShowExternalVideo(); const shouldShowScreenshare = MediaService.shouldShowScreenshare() - && (viewScreenshare || MediaService.isUserPresenter()); + && (viewScreenshare || currentUser?.presenter); let customStyleUrl = getFromUserSettings('bbb_custom_style_url', false); if (!customStyleUrl && CUSTOM_STYLE_URL) { diff --git a/bigbluebutton-html5/imports/ui/components/app/service.js b/bigbluebutton-html5/imports/ui/components/app/service.js index 31505c0918..1de523d006 100644 --- a/bigbluebutton-html5/imports/ui/components/app/service.js +++ b/bigbluebutton-html5/imports/ui/components/app/service.js @@ -1,5 +1,5 @@ -import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Breakouts from '/imports/api/breakouts'; +import Meetings from '/imports/api/meetings'; import Settings from '/imports/ui/services/settings'; import Auth from '/imports/ui/services/auth/index'; import deviceInfo from '/imports/utils/deviceInfo'; diff --git a/bigbluebutton-html5/imports/ui/components/app/styles.js b/bigbluebutton-html5/imports/ui/components/app/styles.js index 321d2fec9d..6957019541 100644 --- a/bigbluebutton-html5/imports/ui/components/app/styles.js +++ b/bigbluebutton-html5/imports/ui/components/app/styles.js @@ -1,6 +1,7 @@ import styled from 'styled-components'; import { barsPadding } from '/imports/ui/stylesheets/styled-components/general'; import { FlexColumn } from '/imports/ui/stylesheets/styled-components/placeholders'; +import { colorBackground } from '/imports/ui/stylesheets/styled-components/palette'; const CaptionsWrapper = styled.div` height: auto; @@ -17,7 +18,7 @@ const ActionsBar = styled.section` `; const Layout = styled(FlexColumn)` - background-color: #06172a; + background-color: ${colorBackground}; `; export default { diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx index 485b085461..9d216ada0d 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx @@ -4,7 +4,7 @@ import { withModalMounter } from '/imports/ui/components/modal/service'; import browserInfo from '/imports/utils/browserInfo'; import getFromUserSettings from '/imports/ui/services/users-settings'; import AudioModal from './component'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import lockContextContainer from '/imports/ui/components/lock-viewers/context/container'; import AudioError from '/imports/ui/services/audio-manager/error-codes'; diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.js b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.js index b4221ae359..d5e165fd1a 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.js +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.js @@ -4,7 +4,7 @@ import Modal from '/imports/ui/components/modal/simple/component'; import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints'; import { colorPrimary, - colorBackground, + colorGrayDark, } from '/imports/ui/stylesheets/styled-components/palette'; import { mdPaddingY, @@ -113,7 +113,7 @@ const Title = styled.h2` text-align: center; font-weight: 400; font-size: 1.3rem; - color: ${colorBackground}; + color: ${colorGrayDark}; white-space: normal; @media ${smallOnly} { diff --git a/bigbluebutton-html5/imports/ui/components/audio/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/container.jsx index fbc023ba62..c40a2e7d20 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/container.jsx @@ -5,7 +5,7 @@ import { Session } from 'meteor/session'; import { withModalMounter } from '/imports/ui/components/modal/service'; import { injectIntl, defineMessages } from 'react-intl'; import _ from 'lodash'; -import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts'; +import Breakouts from '/imports/api/breakouts'; import AppService from '/imports/ui/components/app/service'; import { notify } from '/imports/ui/services/notification'; import getFromUserSettings from '/imports/ui/services/users-settings'; diff --git a/bigbluebutton-html5/imports/ui/components/audio/service.js b/bigbluebutton-html5/imports/ui/components/audio/service.js index c13193eaa4..19c9e1330c 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/service.js +++ b/bigbluebutton-html5/imports/ui/components/audio/service.js @@ -1,8 +1,8 @@ -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; import { debounce, throttle } from 'lodash'; import AudioManager from '/imports/ui/services/audio-manager'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import { makeCall } from '/imports/ui/services/api'; import VoiceUsers from '/imports/api/voice-users'; import logger from '/imports/startup/client/logger'; diff --git a/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx b/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx index ce615f99ae..2cc93ea802 100644 --- a/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx @@ -17,7 +17,7 @@ class AuthenticatedHandler extends Component { } static updateStatus(status, lastStatus) { - return status.retryCount > 0 && lastStatus !== STATUS_CONNECTING ? status.status : lastStatus; + return lastStatus !== STATUS_CONNECTING ? status.status : lastStatus; } static addReconnectObservable() { @@ -27,6 +27,7 @@ class AuthenticatedHandler extends Component { lastStatus = AuthenticatedHandler.updateStatus(Meteor.status(), lastStatus); if (AuthenticatedHandler.shouldAuthenticate(Meteor.status(), lastStatus)) { + Session.set('userWillAuth', true); Auth.authenticate(true); lastStatus = Meteor.status().status; } diff --git a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx index 6a5ab7cc97..5e30a02101 100755 --- a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/component.jsx @@ -7,7 +7,6 @@ import PropTypes from 'prop-types'; import AudioService from '../audio/service'; import VideoService from '../video-provider/service'; import { screenshareHasEnded } from '/imports/ui/components/screenshare/service'; -import UserListService from '/imports/ui/components/user-list/service'; import Styled from './styles'; const intlMessages = defineMessages({ @@ -102,6 +101,7 @@ class BreakoutJoinConfirmation extends Component { isFreeJoin, voiceUserJoined, requestJoinURL, + amIPresenter, } = this.props; const { selectValue } = this.state; @@ -122,7 +122,7 @@ class BreakoutJoinConfirmation extends Component { VideoService.storeDeviceIds(); VideoService.exitVideo(); - if (UserListService.amIPresenter()) screenshareHasEnded(); + if (amIPresenter) screenshareHasEnded(); if (url === '') { logger.error({ logCode: 'breakoutjoinconfirmation_redirecting_to_url', diff --git a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx index 6803bcb00b..2bacb867dd 100755 --- a/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-join-confirmation/container.jsx @@ -1,17 +1,23 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; -import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts'; +import Breakouts from '/imports/api/breakouts'; import Auth from '/imports/ui/services/auth'; import { makeCall } from '/imports/ui/services/api'; import breakoutService from '/imports/ui/components/breakout-room/service'; import AudioManager from '/imports/ui/services/audio-manager'; import BreakoutJoinConfirmationComponent from './component'; +import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; -const BreakoutJoinConfirmationContrainer = (props) => ( - { + const usingUsersContext = useContext(UsersContext); + const { users } = usingUsersContext; + const amIPresenter = users[Auth.meetingID][Auth.userID].presenter; + + return -); +}; const getURL = (breakoutId) => { const currentUserId = Auth.userID; diff --git a/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx index 8ff607566f..16cd82e013 100644 --- a/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-room/component.jsx @@ -10,7 +10,6 @@ import BreakoutRoomContainer from './breakout-remaining-time/container'; import VideoService from '/imports/ui/components/video-provider/service'; import { PANELS, ACTIONS } from '../layout/enums'; import { screenshareHasEnded } from '/imports/ui/components/screenshare/service'; -import UserListService from '/imports/ui/components/user-list/service'; import AudioManager from '/imports/ui/services/audio-manager'; import Settings from '/imports/ui/services/settings'; @@ -249,6 +248,7 @@ class BreakoutRoom extends PureComponent { const { isMicrophoneUser, amIModerator, + amIPresenter, intl, isUserInBreakoutRoom, exitAudio, @@ -323,7 +323,7 @@ class BreakoutRoom extends PureComponent { }, 'joining breakout room closed audio in the main room'); VideoService.storeDeviceIds(); VideoService.exitVideo(); - if (UserListService.amIPresenter()) screenshareHasEnded(); + if (amIPresenter) screenshareHasEnded(); }} disabled={disable} /> diff --git a/bigbluebutton-html5/imports/ui/components/breakout-room/container.jsx b/bigbluebutton-html5/imports/ui/components/breakout-room/container.jsx index 34481c2202..e6ec3cdb11 100644 --- a/bigbluebutton-html5/imports/ui/components/breakout-room/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-room/container.jsx @@ -1,15 +1,23 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import AudioService from '/imports/ui/components/audio/service'; import AudioManager from '/imports/ui/services/audio-manager'; import BreakoutComponent from './component'; import Service from './service'; import { layoutDispatch } from '../layout/context'; +import Auth from '/imports/ui/services/auth'; +import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; const BreakoutContainer = (props) => { const layoutContextDispatch = layoutDispatch(); + const usingUsersContext = useContext(UsersContext); + const { users } = usingUsersContext; + const amIPresenter = users[Auth.meetingID][Auth.userID].presenter; - return ; + return ; }; export default withTracker((props) => { diff --git a/bigbluebutton-html5/imports/ui/components/breakout-room/service.js b/bigbluebutton-html5/imports/ui/components/breakout-room/service.js index 5d3d33c06e..1313b4293e 100644 --- a/bigbluebutton-html5/imports/ui/components/breakout-room/service.js +++ b/bigbluebutton-html5/imports/ui/components/breakout-room/service.js @@ -1,9 +1,9 @@ -import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts'; +import Breakouts from '/imports/api/breakouts'; import { MeetingTimeRemaining } from '/imports/api/meetings'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import { makeCall } from '/imports/ui/services/api'; import Auth from '/imports/ui/services/auth'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import UserListService from '/imports/ui/components/user-list/service'; import fp from 'lodash/fp'; diff --git a/bigbluebutton-html5/imports/ui/components/button/base/component.jsx b/bigbluebutton-html5/imports/ui/components/button/base/component.jsx index cad78aacf3..ed862ceea4 100644 --- a/bigbluebutton-html5/imports/ui/components/button/base/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/button/base/component.jsx @@ -161,6 +161,7 @@ export default class ButtonBase extends React.Component { 'animations', 'small', 'full', + 'iconRight', ]; return ( diff --git a/bigbluebutton-html5/imports/ui/components/button/component.jsx b/bigbluebutton-html5/imports/ui/components/button/component.jsx index 6e7665c045..659e05a9a0 100755 --- a/bigbluebutton-html5/imports/ui/components/button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/button/component.jsx @@ -155,11 +155,6 @@ export default class Button extends BaseButton { const remainingProps = this._cleanProps(otherProps); - /* TODO: We can change this and make the button with flexbox to avoid html - changes */ - const renderLeftFuncName = !iconRight ? 'renderIcon' : 'renderLabel'; - const renderRightFuncName = !iconRight ? 'renderLabel' : 'renderIcon'; - return ( - {this[renderLeftFuncName]()} - {this[renderRightFuncName]()} + {this.renderIcon()} + {this.renderLabel()} ); } diff --git a/bigbluebutton-html5/imports/ui/components/button/styles.js b/bigbluebutton-html5/imports/ui/components/button/styles.js index 682ac3e808..1ce5df8460 100644 --- a/bigbluebutton-html5/imports/ui/components/button/styles.js +++ b/bigbluebutton-html5/imports/ui/components/button/styles.js @@ -599,9 +599,55 @@ const ButtonSpan = styled.span` `; const Button = styled(BaseButton)` + border: none; + overflow: visible; + display: inline-block; + border-radius: ${borderSize}; font-weight: ${btnFontWeight}; line-height: 1; text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + user-select: none; + + &:-moz-focusring { + outline: none; + } + + &:hover, + &:focus { + outline: transparent; + outline-style: dotted; + outline-width: ${borderSize}; + text-decoration: none; + } + + &:active, + &:focus { + outline: transparent; + outline-width: ${borderSize}; + outline-style: solid; + } + + &:active { + background-image: none; + } + + &[aria-disabled="true"] { + cursor: not-allowed; + opacity: .65; + box-shadow: none; + } + + &, + &:active { + &:focus { + span:first-of-type::before { + border-radius: ${borderSize}; + } + } + } ${({ size }) => size === 'sm' && ` font-size: calc(${fontSizeSmall} * .85); @@ -967,6 +1013,11 @@ const Button = styled(BaseButton)` display: block; width: 100%; `} + + ${({ iconRight }) => iconRight && ` + display: flex; + flex-direction: row-reverse; + `} `; export default { diff --git a/bigbluebutton-html5/imports/ui/components/captions/service.js b/bigbluebutton-html5/imports/ui/components/captions/service.js index f56cb79bd6..f503836cbd 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/service.js +++ b/bigbluebutton-html5/imports/ui/components/captions/service.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import Captions from '/imports/api/captions'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; import { makeCall } from '/imports/ui/services/api'; import { Meteor } from 'meteor/meteor'; diff --git a/bigbluebutton-html5/imports/ui/components/captions/writer-menu/styles.js b/bigbluebutton-html5/imports/ui/components/captions/writer-menu/styles.js index c1be98766c..f0c4468188 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/writer-menu/styles.js +++ b/bigbluebutton-html5/imports/ui/components/captions/writer-menu/styles.js @@ -4,7 +4,7 @@ import { borderSize, borderSizeLarge } from '/imports/ui/stylesheets/styled-comp import { colorWhite, colorLink, - colorBackground, + colorGrayDark, colorGrayLighter, colorGrayLabel, colorPrimary, @@ -29,7 +29,7 @@ const Title = styled(HeaderElipsis)` text-align: center; font-weight: 400; font-size: 1.3rem; - color: ${colorBackground}; + color: ${colorGrayDark}; white-space: normal; flex: 1; margin: 0; diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx index 0496e4716d..c822a3a8ee 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-dropdown/container.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import Auth from '/imports/ui/services/auth'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; import ChatDropdown from './component'; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/container.jsx index b48fe65c65..5a3a617cd8 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/container.jsx @@ -2,8 +2,8 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import Auth from '/imports/ui/services/auth'; import { UsersTyping } from '/imports/api/group-chat-msg'; -import Users from '/imports/ui/local-collections/users-collection/users'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Users from '/imports/api/users'; +import Meetings from '/imports/api/meetings'; import TypingIndicator from './component'; const CHAT_CONFIG = Meteor.settings.public.chat; diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 10b0e14391..bd426b6277 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -1,5 +1,5 @@ import Users from '/imports/api/users'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import GroupChat from '/imports/api/group-chat'; import Auth from '/imports/ui/services/auth'; import UnreadMessages from '/imports/ui/services/unread-messages'; diff --git a/bigbluebutton-html5/imports/ui/components/components-data/chat-context/adapter.jsx b/bigbluebutton-html5/imports/ui/components/components-data/chat-context/adapter.jsx index f6656aa2e8..a7d9a65b69 100644 --- a/bigbluebutton-html5/imports/ui/components/components-data/chat-context/adapter.jsx +++ b/bigbluebutton-html5/imports/ui/components/components-data/chat-context/adapter.jsx @@ -5,7 +5,7 @@ import { UsersContext } from '../users-context/context'; import { makeCall } from '/imports/ui/services/api'; import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger'; import Auth from '/imports/ui/services/auth'; -import CollectionEventsBroker from '/imports/ui/services/collection-hooks-callbacks/collection-hooks-callbacks'; +import CollectionEventsBroker from '/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker'; let prevUserData = {}; let currentUserData = {}; diff --git a/bigbluebutton-html5/imports/ui/components/components-data/chat-context/context.jsx b/bigbluebutton-html5/imports/ui/components/components-data/chat-context/context.jsx index ba859023f8..738fcce96a 100644 --- a/bigbluebutton-html5/imports/ui/components/components-data/chat-context/context.jsx +++ b/bigbluebutton-html5/imports/ui/components/components-data/chat-context/context.jsx @@ -3,7 +3,7 @@ import React, { useReducer, } from 'react'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; import Storage from '/imports/ui/services/storage/session'; import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger'; diff --git a/bigbluebutton-html5/imports/ui/components/components-data/users-context/adapter.jsx b/bigbluebutton-html5/imports/ui/components/components-data/users-context/adapter.jsx index 3328818731..dfa32a6eb3 100644 --- a/bigbluebutton-html5/imports/ui/components/components-data/users-context/adapter.jsx +++ b/bigbluebutton-html5/imports/ui/components/components-data/users-context/adapter.jsx @@ -1,6 +1,6 @@ import { useContext, useEffect } from 'react'; import { CurrentUser } from '/imports/api/users'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import UsersPersistentData from '/imports/api/users-persistent-data'; import { UsersContext, ACTIONS } from './context'; import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger'; diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/icon/styles.js b/bigbluebutton-html5/imports/ui/components/connection-status/icon/styles.js index 42dee3c39c..81714a5160 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-status/icon/styles.js +++ b/bigbluebutton-html5/imports/ui/components/connection-status/icon/styles.js @@ -46,6 +46,7 @@ const SignalBars = styled.div` const Bar = styled.div` width: 20%; + border-radius: .46875em; `; const FirstBar = styled(Bar)` diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx b/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx index 6fef3010e7..981c8b5073 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx @@ -6,6 +6,7 @@ import Icon from '/imports/ui/components/connection-status/icon/component'; import Switch from '/imports/ui/components/switch/component'; import Service from '../service'; import Styled from './styles'; +import ConnectionStatusHelper from '../status-helper/container'; const NETWORK_MONITORING_INTERVAL_MS = 2000; const MIN_TIMEOUT = 3000; @@ -91,6 +92,42 @@ const intlMessages = defineMessages({ id: 'app.connection-status.lostPackets', description: 'Number of lost packets', }, + audioUploadRate: { + id: 'app.connection-status.audioUploadRate', + description: 'Label for audio current upload rate', + }, + audioDownloadRate: { + id: 'app.connection-status.audioDownloadRate', + description: 'Label for audio current download rate', + }, + videoUploadRate: { + id: 'app.connection-status.videoUploadRate', + description: 'Label for video current upload rate', + }, + videoDownloadRate: { + id: 'app.connection-status.videoDownloadRate', + description: 'Label for video current download rate', + }, + connectionStats: { + id: 'app.connection-status.connectionStats', + description: 'Label for Connection Stats tab', + }, + myLogs: { + id: 'app.connection-status.myLogs', + description: 'Label for My Logs tab', + }, + sessionLogs: { + id: 'app.connection-status.sessionLogs', + description: 'Label for Session Logs tab', + }, + next: { + id: 'app.connection-status.next', + description: 'Label for the next page of the connection stats tab', + }, + prev: { + id: 'app.connection-status.prev', + description: 'Label for the previous page of the connection stats tab', + }, }); const propTypes = { @@ -121,6 +158,8 @@ class ConnectionStatusComponent extends PureComponent { this.help = Service.getHelp(); this.state = { + selectedTab: '1', + dataPage: '1', dataSaving: props.dataSaving, hasNetworkData: false, networkData: { @@ -142,9 +181,10 @@ class ConnectionStatusComponent extends PureComponent { }; this.displaySettingsStatus = this.displaySettingsStatus.bind(this); this.rateInterval = null; - - this.audioLabel = (intl.formatMessage(intlMessages.audioLabel)).charAt(0); - this.videoLabel = (intl.formatMessage(intlMessages.videoLabel)).charAt(0); + this.audioUploadLabel = intl.formatMessage(intlMessages.audioUploadRate); + this.audioDownloadLabel = intl.formatMessage(intlMessages.audioDownloadRate); + this.videoUploadLabel = intl.formatMessage(intlMessages.videoUploadRate); + this.videoDownloadLabel = intl.formatMessage(intlMessages.videoDownloadRate); } async componentDidMount() { @@ -222,13 +262,13 @@ class ConnectionStatusComponent extends PureComponent { const { intl } = this.props; return ( - + - + {intl.formatMessage(intlMessages.empty)} - + ); @@ -280,15 +320,23 @@ class ConnectionStatusComponent extends PureComponent { intl, } = this.props; + const { selectedTab } = this.state; + if (isConnectionStatusEmpty(connectionStatus)) return this.renderEmpty(); - return connectionStatus.map((conn, index) => { + let connections = connectionStatus; + if (selectedTab === '2') { + connections = connections.filter(conn => conn.you); + if (isConnectionStatusEmpty(connections)) return this.renderEmpty(); + } + + return connections.map((conn, index) => { const dateTime = new Date(conn.timestamp); return ( @@ -411,13 +459,15 @@ class ConnectionStatusComponent extends PureComponent { } const { - audioLabel, - videoLabel, + audioUploadLabel, + audioDownloadLabel, + videoUploadLabel, + videoDownloadLabel, } = this; - const { intl } = this.props; + const { intl, closeModal } = this.props; - const { networkData } = this.state; + const { networkData, dataSaving, dataPage } = this.state; const { audioCurrentUploadRate, @@ -447,29 +497,103 @@ class ConnectionStatusComponent extends PureComponent { } } + function handlePaginationClick(action) { + if (action === 'next') { + this.setState({ dataPage: '2' }); + } + else { + this.setState({ dataPage: '1' }); + } + } + return ( - - {`↑${audioLabel}: ${audioCurrentUploadRate} k`} - - - {`↓${audioLabel}: ${audioCurrentDownloadRate} k`} - - - {`↑${videoLabel}: ${videoCurrentUploadRate} k`} - - - {`↓${videoLabel}: ${videoCurrentDownloadRate} k`} - - - {`${intl.formatMessage(intlMessages.jitter)}: ${jitter} ms`} - - - {`${intl.formatMessage(intlMessages.lostPackets)}: ${packetsLost}`} - - - {`${intl.formatMessage(intlMessages.usingTurn)}: ${isUsingTurn}`} - + + + + + + + + + closeModal(dataSaving, intl)} /> + + + + +
{`${audioUploadLabel}`}
+
{`${audioCurrentUploadRate}k ↑`}
+
+ +
{`${videoUploadLabel}`}
+
{`${videoCurrentUploadRate}k ↑`}
+
+ +
{`${intl.formatMessage(intlMessages.jitter)}`}
+
{`${jitter} ms`}
+
+ +
{`${intl.formatMessage(intlMessages.usingTurn)}`}
+
{`${isUsingTurn}`}
+
+
+ + + +
{`${audioDownloadLabel}`}
+
{`${audioCurrentDownloadRate}k ↓`}
+
+ +
{`${videoDownloadLabel}`}
+
{`${videoCurrentDownloadRate}k ↓`}
+
+ +
{`${intl.formatMessage(intlMessages.lostPackets)}`}
+
{`${packetsLost}`}
+
+ +
Content Hidden
+
0
+
+
+
+ + + + + + +
); } @@ -503,13 +627,69 @@ class ConnectionStatusComponent extends PureComponent { ); } + /** + * The navigation bar. + * @returns {Object} The component to be renderized. + */ + renderNavigation() { + const { intl } = this.props; + + const handleTabClick = (event) => { + const activeTabElement = document.querySelector('.activeConnectionStatusTab'); + const { target } = event; + + if (activeTabElement) { + activeTabElement.classList.remove('activeConnectionStatusTab'); + } + + target.classList.add('activeConnectionStatusTab'); + this.setState({ + selectedTab: target.dataset.tab, + }); + } + + return ( + +
+ {intl.formatMessage(intlMessages.connectionStats)} +
+
+ {intl.formatMessage(intlMessages.myLogs)} +
+ {Service.isModerator() + && ( +
+ {intl.formatMessage(intlMessages.sessionLogs)} +
+ ) + } +
+ ); + } + render() { const { closeModal, intl, } = this.props; - const { dataSaving } = this.state; + const { dataSaving, selectedTab } = this.state; return ( - - {intl.formatMessage(intlMessages.description)} - {' '} - {this.help - && ( - - {`(${intl.formatMessage(intlMessages.more)})`} - - ) + {this.renderNavigation()} + + + {selectedTab === '1' + ? this.renderNetworkData() + : this.renderConnections() + } + + {selectedTab === '1' && + this.renderCopyDataButton() } - - {this.renderNetworkData()} - {this.renderCopyDataButton()} - {this.renderDataSaving()} - - - {this.renderConnections()} - - + ); diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.js b/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.js index b992850a33..09248ba583 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.js +++ b/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.js @@ -5,28 +5,37 @@ import { colorGray, colorGrayDark, colorGrayLabel, + colorGrayLightest, + colorPrimary, } from '/imports/ui/stylesheets/styled-components/palette'; import { smPaddingX, smPaddingY, lgPaddingY, lgPaddingX, - modalMargin, titlePositionLeft, + mdPaddingX, + borderSizeLarge, + jumboPaddingY, } from '/imports/ui/stylesheets/styled-components/general'; import { fontSizeSmall, - fontSizeLarge, + fontSizeXL, } from '/imports/ui/stylesheets/styled-components/typography'; -import { hasPhoneDimentions } from '/imports/ui/stylesheets/styled-components/breakpoints'; +import { + hasPhoneDimentions, + mediumDown, + hasPhoneWidth, +} from '/imports/ui/stylesheets/styled-components/breakpoints'; const Item = styled.div` display: flex; width: 100%; height: 4rem; + border-bottom: 1px solid ${colorGrayLightest}; - ${({ even }) => even && ` - background-color: ${colorOffWhite}; + ${({ last }) => last && ` + border: none; `} `; @@ -37,11 +46,19 @@ const Left = styled.div` `; const Name = styled.div` - display: grid; - width: 100%; + display: flex; + width: 27.5%; height: 100%; align-items: center; - justify-content: center; + justify-content: flex-start; + + @media ${hasPhoneDimentions} { + width: 100%; + } +`; + +const FullName = styled(Name)` + width: 100%; `; const Text = styled.div` @@ -51,8 +68,13 @@ const Text = styled.div` text-overflow: ellipsis; ${({ offline }) => offline && ` - font-style: italic; + font-style: italic; `} + + [dir="rtl"] & { + padding: 0; + padding-right: .5rem; + } `; const ToggleLabel = styled.span` @@ -65,7 +87,6 @@ const ToggleLabel = styled.span` const Avatar = styled.div` display: flex; - width: 4rem; height: 100%; justify-content: center; align-items: center; @@ -87,6 +108,7 @@ const Time = styled.div` align-items: center; width: 100%; height: 100%; + justify-content: flex-end; `; const DataSaving = styled.div` @@ -149,30 +171,46 @@ const Label = styled.span` const NetworkDataContainer = styled.div` width: 100%; + height: 100%; display: flex; - background-color: ${colorOffWhite}; + + @media ${mediumDown} { + justify-content: space-between; + } `; const NetworkData = styled.div` - float: left; font-size: ${fontSizeSmall}; - margin-left: ${smPaddingX}; + + ${({ invisible }) => invisible && ` + visibility: hidden; + `} + + & :first-child { + font-weight: 600; + } `; const CopyContainer = styled.div` width: 100%; + display: flex; + justify-content: flex-end; + border: none; + border-top: 1px solid ${colorOffWhite}; + padding: ${jumboPaddingY} 0 0; `; const ConnectionStatusModal = styled(Modal)` - padding: ${smPaddingY}; + padding: 1.5rem; + border-radius: 7.5px; + + @media ${hasPhoneDimentions} { + padding: 1rem; + } `; const Container = styled.div` - margin: 0 ${modalMargin} ${lgPaddingX}; - - @media ${hasPhoneDimentions} { - margin: 0 1rem; - } + padding: 0 calc(${mdPaddingX} / 2 + ${borderSizeLarge}); `; const Header = styled.div` @@ -184,16 +222,14 @@ const Header = styled.div` `; const Title = styled.h2` - left: ${titlePositionLeft}; - right: auto; color: ${colorGrayDark}; - font-weight: bold; - font-size: ${fontSizeLarge}; - text-align: center; + font-weight: 500; + font-size: ${fontSizeXL}; + text-align: left; + margin: 0; [dir="rtl"] & { - left: auto; - right: ${titlePositionLeft}; + text-align: right; } `; @@ -219,14 +255,11 @@ const Status = styled.div` `; const Copy = styled.span` - float: right; - text-decoration: underline; cursor: pointer; - margin-right: ${smPaddingX}; + color: ${colorPrimary}; - [dir="rtl"] & { - margin-left: ${smPaddingX}; - float: left; + &:hover { + text-decoration: underline; } ${({ disabled }) => disabled && ` @@ -234,6 +267,175 @@ const Copy = styled.span` `} `; +const Helper = styled.div` + width: 12.5rem; + height: 100%; + border-radius: .5rem; + background-color: ${colorOffWhite}; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + @media ${mediumDown} { + ${({ page }) => page === '1' + ? 'display: flex;' + : 'display: none;'} + } +`; + +const NetworkDataContent = styled.div` + margin: 0; + display: flex; + justify-content: space-around; + flex-grow: 1; + + @media ${mediumDown} { + ${({ page }) => page === '2' + ? 'display: flex;' + : 'display: none;'} + } +`; + +const DataColumn = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + + @media ${hasPhoneWidth} { + flex-grow: 1; + } +`; + +const Main = styled.div` + height: 19.5rem; + display: flex; + flex-direction: column; +`; + +const Body = styled.div` + padding: ${jumboPaddingY} 0; + margin: 0; + flex-grow: 1; + overflow: auto; + position: relative; +`; + +const Navigation = styled.div` + display: flex; + border: none; + border-bottom: 1px solid ${colorOffWhite}; + user-select: none; + overflow-y: auto; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + + & :not(:last-child) { + margin: 0; + margin-right: ${lgPaddingX}; + } + + .activeConnectionStatusTab { + border: none; + border-bottom: 2px solid ${colorPrimary}; + color: ${colorPrimary}; + } + + & * { + cursor: pointer; + white-space: nowrap; + } + + [dir="rtl"] & { + & :not(:last-child) { + margin: 0; + margin-left: ${lgPaddingX}; + } + } +`; + +const Prev = styled.div` + display: none; + margin: 0 .5rem 0 .25rem; + + @media ${mediumDown} { + display: flex; + flex-direction: column; + justify-content: center; + } + + @media ${hasPhoneWidth} { + margin: 0; + } +`; + +const Next = styled(Prev)` + margin: 0 .25rem 0 .5rem; + + @media ${hasPhoneWidth} { + margin: 0; + } +`; + +const Button = styled.button` + flex: 0; + margin: 0; + padding: 0; + border: none; + background: none; + color: inherit; + border-radius: 50%; + cursor: pointer; + + &:disabled { + cursor: not-allowed; + opacity: .5; + } + + &:hover { + opacity: .75; + } + + &:focus { + outline: none; + } + + @media ${hasPhoneWidth} { + position: absolute; + bottom: 0; + padding: .25rem; + } +`; + +const ButtonLeft = styled(Button)` + left: calc(50% - 2rem); + + [dir="rtl"] & { + left: calc(50%); + } +`; + +const ButtonRight = styled(Button)` + right: calc(50% - 2rem); + + [dir="rtl"] & { + right: calc(50%); + } +`; + +const Chevron = styled.svg` + display: flex; + width: 1rem; + height: 1rem; + + [dir="rtl"] & { + transform: rotate(180deg); + } +`; + export default { Item, Left, @@ -262,4 +464,16 @@ export default { Wrapper, Status, Copy, + Helper, + NetworkDataContent, + Main, + Body, + Navigation, + FullName, + DataColumn, + Prev, + Next, + ButtonLeft, + ButtonRight, + Chevron, }; diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/service.js b/bigbluebutton-html5/imports/ui/components/connection-status/service.js index d0a9bc5f1c..62a461630c 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-status/service.js +++ b/bigbluebutton-html5/imports/ui/components/connection-status/service.js @@ -1,6 +1,6 @@ import { defineMessages } from 'react-intl'; import ConnectionStatus from '/imports/api/connection-status'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import UsersPersistentData from '/imports/api/users-persistent-data'; import Auth from '/imports/ui/services/auth'; import Settings from '/imports/ui/services/settings'; @@ -570,6 +570,7 @@ const calculateBitsPerSecondFromMultipleData = (currentData, previousData) => { }; export default { + isModerator, getConnectionStatus, getStats, getHelp, diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/status-helper/component.jsx b/bigbluebutton-html5/imports/ui/components/connection-status/status-helper/component.jsx new file mode 100644 index 0000000000..945f42caf8 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/connection-status/status-helper/component.jsx @@ -0,0 +1,89 @@ +import React, { Fragment, PureComponent } from 'react'; +import { defineMessages, injectIntl } from 'react-intl'; +import Styled from './styles'; +import { withModalMounter } from '/imports/ui/components/modal/service'; +import Icon from '/imports/ui/components/connection-status/icon/component'; +import SettingsMenuContainer from '/imports/ui/components/settings/container'; + +const intlMessages = defineMessages({ + label: { + id: 'app.connection-status.label', + description: 'Connection status label', + }, + settings: { + id: 'app.connection-status.settings', + description: 'Connection settings label', + }, +}); + +class ConnectionStatusIcon extends PureComponent { + renderIcon(level = 'normal') { + return( + + + + ); + } + + openAdjustSettings() { + const { + closeModal, + mountModal, + } = this.props; + + closeModal(); + mountModal(); + } + + render() { + const { + intl, + stats, + } = this.props; + + let color; + switch (stats) { + case 'warning': + color = 'success'; + break; + case 'danger': + color = 'warning'; + break; + case 'critical': + color = 'danger'; + break; + default: + color = 'success'; + } + + const level = stats ? stats : 'normal'; + + return ( + + + {this.renderIcon(level)} + + + {intl.formatMessage(intlMessages.label)} + + {level === 'critical' || level === 'danger' + && ( +
+ + {intl.formatMessage(intlMessages.settings)} + +
+ ) + } +
+ ); + } +} + +export default withModalMounter(injectIntl(ConnectionStatusIcon)); diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/status-helper/container.jsx b/bigbluebutton-html5/imports/ui/components/connection-status/status-helper/container.jsx new file mode 100644 index 0000000000..c35110cbe8 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/connection-status/status-helper/container.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { withTracker } from 'meteor/react-meteor-data'; +import ConnectionStatusService from '../service'; +import ConnectionStatusIconComponent from './component'; + +const connectionStatusIconContainer = props => ; + +export default withTracker(() => { + return { + stats: ConnectionStatusService.getStats(), + }; +})(connectionStatusIconContainer); diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/status-helper/styles.js b/bigbluebutton-html5/imports/ui/components/connection-status/status-helper/styles.js new file mode 100644 index 0000000000..d64c2a8cc2 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/connection-status/status-helper/styles.js @@ -0,0 +1,45 @@ +import styled from 'styled-components'; +import { + colorPrimary, + colorSuccess, + colorWarning, + colorDanger, +} from '/imports/ui/stylesheets/styled-components/palette'; + +const StatusIconWrapper = styled.div` + border-radius: 50%; + padding: 1.5rem; + + ${(color) => { + let bgColor = colorSuccess; + backgroundColor = color === 'warning' ? colorWarning : bgColor; + backgroundColor = color === 'danger' ? colorDanger : bgColor; + return `background-color: ${bgColor};` + }} +`; + +const IconWrapper = styled.div` + width: 2.25rem; + height: 2.25rem; +`; + +const Label = styled.div` + font-weight: 600; + margin: .25rem 0 .5rem; +`; + +const Settings = styled.span` + color: ${colorPrimary}; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +`; + +export default { + StatusIconWrapper, + IconWrapper, + Label, + Settings, +}; diff --git a/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/service.js b/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/service.js index 65fb2aa5ea..5817cdab2c 100644 --- a/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/service.js +++ b/bigbluebutton-html5/imports/ui/components/end-meeting-confirmation/service.js @@ -1,6 +1,6 @@ -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; const getMeetingTitle = () => { const meeting = Meetings.findOne({ diff --git a/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx b/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx index 41b3f57797..18d1902973 100755 --- a/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx @@ -24,6 +24,7 @@ class JoinHandler extends Component { this.state = { joined: false, + hasAlreadyJoined: false, }; } @@ -38,8 +39,8 @@ class JoinHandler extends Component { connected, status, } = Meteor.status(); - - if (status === 'connecting') { + const { hasAlreadyJoined } = this.state; + if (status === 'connecting' && !hasAlreadyJoined) { this.setState({ joined: false }); } @@ -83,6 +84,7 @@ class JoinHandler extends Component { } async fetchToken() { + const { hasAlreadyJoined } = this.state; if (!this._isMounted) return; const urlParams = new URLSearchParams(window.location.search); @@ -94,7 +96,9 @@ class JoinHandler extends Component { } // Old credentials stored in memory were being used when joining a new meeting - Auth.clearCredentials(); + if (!hasAlreadyJoined) { + Auth.clearCredentials(); + } const logUserInfo = () => { const userInfo = window.navigator; @@ -215,7 +219,10 @@ class JoinHandler extends Component { }, }, 'User faced an error on main.joinRouteHandler.'); } - this.setState({ joined: true }); + this.setState({ + joined: true, + hasAlreadyJoined: true, + }); } render() { diff --git a/bigbluebutton-html5/imports/ui/components/learning-dashboard/service.js b/bigbluebutton-html5/imports/ui/components/learning-dashboard/service.js index b5e9281973..4b0c9b05b4 100644 --- a/bigbluebutton-html5/imports/ui/components/learning-dashboard/service.js +++ b/bigbluebutton-html5/imports/ui/components/learning-dashboard/service.js @@ -1,6 +1,6 @@ import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; -import Meetings from '../../../api/meetings'; +import Meetings from '/imports/api/meetings'; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; diff --git a/bigbluebutton-html5/imports/ui/components/lock-viewers/container.jsx b/bigbluebutton-html5/imports/ui/components/lock-viewers/container.jsx index 3bd5568d92..a7146fc523 100755 --- a/bigbluebutton-html5/imports/ui/components/lock-viewers/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/lock-viewers/container.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import { withModalMounter } from '/imports/ui/components/modal/service'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import LockViewersComponent from './component'; import { updateLockSettings, updateWebcamsOnlyForModerator } from './service'; diff --git a/bigbluebutton-html5/imports/ui/components/lock-viewers/context/container.jsx b/bigbluebutton-html5/imports/ui/components/lock-viewers/context/container.jsx index 4de7e2cacc..0f33d1620c 100644 --- a/bigbluebutton-html5/imports/ui/components/lock-viewers/context/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/lock-viewers/context/container.jsx @@ -1,5 +1,5 @@ import { withTracker } from 'meteor/react-meteor-data'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import { LockStruct } from './context'; import { withUsersConsumer } from '/imports/ui/components/components-data/users-context/context'; diff --git a/bigbluebutton-html5/imports/ui/components/lock-viewers/notify/container.jsx b/bigbluebutton-html5/imports/ui/components/lock-viewers/notify/container.jsx index 509fcb1c64..9363621a94 100644 --- a/bigbluebutton-html5/imports/ui/components/lock-viewers/notify/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/lock-viewers/notify/container.jsx @@ -1,5 +1,5 @@ import { withTracker } from 'meteor/react-meteor-data'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import LockViewersNotifyComponent from './component'; diff --git a/bigbluebutton-html5/imports/ui/components/media/service.js b/bigbluebutton-html5/imports/ui/components/media/service.js index 5f91e1caab..794117dda0 100755 --- a/bigbluebutton-html5/imports/ui/components/media/service.js +++ b/bigbluebutton-html5/imports/ui/components/media/service.js @@ -1,8 +1,6 @@ import Presentations from '/imports/api/presentations'; import { isVideoBroadcasting } from '/imports/ui/components/screenshare/service'; import { getVideoUrl } from '/imports/ui/components/external-video-player/service'; -import Auth from '/imports/ui/services/auth'; -import Users from '/imports/ui/local-collections/users-collection/users'; import Settings from '/imports/ui/services/settings'; import getFromUserSettings from '/imports/ui/services/users-settings'; import { ACTIONS } from '../layout/enums'; @@ -21,9 +19,6 @@ const getPresentationInfo = () => { }; }; -const isUserPresenter = () => Users.findOne({ userId: Auth.userID }, - { fields: { presenter: 1 } }).presenter; - function shouldShowWhiteboard() { return true; } @@ -91,7 +86,6 @@ export default { shouldShowScreenshare, shouldShowExternalVideo, shouldShowOverlay, - isUserPresenter, isVideoBroadcasting, toggleSwapLayout, shouldEnableSwapLayout, diff --git a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx index 2f9f016e5e..a5e52d20ee 100755 --- a/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/meeting-ended/component.jsx @@ -10,8 +10,8 @@ import logoutRouteHandler from '/imports/utils/logoutRouteHandler'; import Rating from './rating/component'; import Styled from './styles'; import logger from '/imports/startup/client/logger'; -import Users from '/imports/ui/local-collections/users-collection/users'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Users from '/imports/api/users'; +import Meetings from '/imports/api/meetings'; import AudioManager from '/imports/ui/services/audio-manager'; import { meetingIsBreakout } from '/imports/ui/components/app/service'; diff --git a/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx b/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx index 959dc020e7..ecbd6bc9ce 100644 --- a/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Meetings from '/imports/api/meetings'; +import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; import { withModalMounter } from '/imports/ui/components/modal/service'; import { makeCall } from '/imports/ui/services/api'; diff --git a/bigbluebutton-html5/imports/ui/components/modal/remove-user/component.jsx b/bigbluebutton-html5/imports/ui/components/modal/remove-user/component.jsx index 165fd90230..bd4884baed 100644 --- a/bigbluebutton-html5/imports/ui/components/modal/remove-user/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/modal/remove-user/component.jsx @@ -69,7 +69,7 @@ class RemoveUserModal extends Component { - { diff --git a/bigbluebutton-html5/imports/ui/components/modal/remove-user/styles.js b/bigbluebutton-html5/imports/ui/components/modal/remove-user/styles.js index d0cd41f9aa..3efea06b18 100644 --- a/bigbluebutton-html5/imports/ui/components/modal/remove-user/styles.js +++ b/bigbluebutton-html5/imports/ui/components/modal/remove-user/styles.js @@ -69,7 +69,7 @@ const Footer = styled.div` display:flex; `; -const ConfirmButtom = styled(Button)` +const ConfirmButton = styled(Button)` padding-right: ${jumboPaddingY}; padding-left: ${jumboPaddingY}; margin: 0 ${smPaddingX} 0 0; @@ -79,7 +79,7 @@ const ConfirmButtom = styled(Button)` } `; -const DismissButtom = styled(ConfirmButtom)` +const DismissButton = styled(ConfirmButton)` box-shadow: 0 0 0 1px ${colorGray}; `; @@ -91,6 +91,6 @@ export default { Description, BanUserCheckBox, Footer, - ConfirmButtom, - DismissButtom, + ConfirmButton, + DismissButton, }; diff --git a/bigbluebutton-html5/imports/ui/components/modal/simple/component.jsx b/bigbluebutton-html5/imports/ui/components/modal/simple/component.jsx index 58a56ed6b7..fae17916a6 100755 --- a/bigbluebutton-html5/imports/ui/components/modal/simple/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/modal/simple/component.jsx @@ -81,7 +81,7 @@ class ModalSimple extends Component { {...otherProps} > - {title} + {title} {shouldShowCloseButton ? ( hasLeftMargin && ` + margin-left: 35px; + `} `; const DismissButton = styled(Button)` @@ -61,7 +65,7 @@ const Content = styled.div` overflow: auto; color: ${colorText}; font-weight: normal; - padding: ${lineHeightComputed} 0; + padding: 0; `; export default { diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx index 3b1ba1b332..ef299cdb53 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/container.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import { Meteor } from 'meteor/meteor'; import { withTracker } from 'meteor/react-meteor-data'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import getFromUserSettings from '/imports/ui/services/users-settings'; import userListService from '/imports/ui/components/user-list/service'; diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/service.js b/bigbluebutton-html5/imports/ui/components/nav-bar/service.js index 6b36c1da2e..6857f00219 100755 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/service.js +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/service.js @@ -1,6 +1,6 @@ import Auth from '/imports/ui/services/auth'; import { makeCall } from '/imports/ui/services/api'; -import RecordMeetings from '/imports/api/meetings'; +import Meetings from '/imports/api/meetings'; const processOutsideToggleRecording = (e) => { switch (e.data) { @@ -9,7 +9,7 @@ const processOutsideToggleRecording = (e) => { break; } case 'c_recording_status': { - const recordingState = (RecordMeetings.findOne({ meetingId: Auth.meetingID })).recording; + const recordingState = (Meetings.findOne({ meetingId: Auth.meetingID })).recording; const recordingMessage = recordingState ? 'recordingStarted' : 'recordingStopped'; this.window.parent.postMessage({ response: recordingMessage }, '*'); break; diff --git a/bigbluebutton-html5/imports/ui/components/note/service.js b/bigbluebutton-html5/imports/ui/components/note/service.js index 8f1721a649..38740b60eb 100644 --- a/bigbluebutton-html5/imports/ui/components/note/service.js +++ b/bigbluebutton-html5/imports/ui/components/note/service.js @@ -1,5 +1,5 @@ -import Users from '/imports/ui/local-collections/users-collection/users'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Users from '/imports/api/users'; +import Meetings from '/imports/api/meetings'; import Note from '/imports/api/note'; import { makeCall } from '/imports/ui/services/api'; import Auth from '/imports/ui/services/auth'; diff --git a/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx index 38a04e9507..271b32145e 100644 --- a/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/notifications-bar/container.jsx @@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import _ from 'lodash'; import Auth from '/imports/ui/services/auth'; import { MeetingTimeRemaining } from '/imports/api/meetings'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import BreakoutRemainingTime from '/imports/ui/components/breakout-room/breakout-remaining-time/container'; import Styled from './styles'; import { layoutSelectInput, layoutDispatch } from '../layout/context'; diff --git a/bigbluebutton-html5/imports/ui/components/poll/container.jsx b/bigbluebutton-html5/imports/ui/components/poll/container.jsx index c6ad1ce479..1ceedb08ec 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/poll/container.jsx @@ -18,6 +18,7 @@ const PollContainer = ({ ...props }) => { const usingUsersContext = useContext(UsersContext); const { users } = usingUsersContext; + const amIPresenter = users[Auth.meetingID][Auth.userID].presenter; const usernames = {}; @@ -25,7 +26,13 @@ const PollContainer = ({ ...props }) => { usernames[user.userId] = { userId: user.userId, name: user.name }; }); - return ; + return ( + + ); }; export default withTracker(() => { @@ -50,7 +57,6 @@ export default withTracker(() => { return { currentSlide, - amIPresenter: Service.amIPresenter(), pollTypes, startPoll, startCustomPoll, diff --git a/bigbluebutton-html5/imports/ui/components/poll/service.js b/bigbluebutton-html5/imports/ui/components/poll/service.js index f3f942321a..4586d83cc0 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/service.js +++ b/bigbluebutton-html5/imports/ui/components/poll/service.js @@ -1,4 +1,3 @@ -import Users from '/imports/ui/local-collections/users-collection/users'; import Auth from '/imports/ui/services/auth'; import { CurrentPoll } from '/imports/api/polls'; import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer'; @@ -212,10 +211,6 @@ const checkPollType = ( }; export default { - amIPresenter: () => Users.findOne( - { userId: Auth.userID }, - { fields: { presenter: 1 } }, - ).presenter, pollTypes, currentPoll: () => CurrentPoll.findOne({ meetingId: Auth.meetingID }), pollAnswerIds, diff --git a/bigbluebutton-html5/imports/ui/components/polling/container.jsx b/bigbluebutton-html5/imports/ui/components/polling/container.jsx index a7c158288a..d9b8838b76 100644 --- a/bigbluebutton-html5/imports/ui/components/polling/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/polling/container.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withTracker } from 'meteor/react-meteor-data'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; import PollingService from './service'; import PollService from '/imports/ui/components/poll/service'; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index a0d7692d7a..0f03081ba0 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -20,6 +20,7 @@ import Icon from '/imports/ui/components/icon/component'; import PollingContainer from '/imports/ui/components/polling/container'; import { ACTIONS, LAYOUT_TYPE } from '../layout/enums'; import DEFAULT_VALUES from '../layout/defaultValues'; +import { colorBackground } from '/imports/ui/stylesheets/styled-components/palette'; const intlMessages = defineMessages({ presentationLabel: { @@ -186,7 +187,7 @@ class Presentation extends PureComponent { this.currentPresentationToastId = toast(this.renderCurrentPresentationToast(), { onClose: () => { this.currentPresentationToastId = null; }, autoClose: shouldCloseToast, - className: "actionToast", + className: 'actionToast', }); } } @@ -693,13 +694,14 @@ class Presentation extends PureComponent { } renderWhiteboardToolbar(svgDimensions) { - const { currentSlide } = this.props; + const { currentSlide, userIsPresenter } = this.props; if (!this.isPresentationAccessible()) return null; return ( ); } @@ -854,7 +856,7 @@ class Presentation extends PureComponent { width: presentationBounds.width, height: presentationBounds.height, zIndex: fullscreenContext ? presentationBounds.zIndex : undefined, - backgroundColor: '#06172A', + backgroundColor: colorBackground, }} > {isFullscreen && } diff --git a/bigbluebutton-html5/imports/ui/components/presentation/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/container.jsx index 469314178e..23ffed4dd9 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/container.jsx @@ -11,7 +11,7 @@ import Presentation from '/imports/ui/components/presentation/component'; import PresentationToolbarService from './presentation-toolbar/service'; import { UsersContext } from '../components-data/users-context/context'; import Auth from '/imports/ui/services/auth'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import getFromUserSettings from '/imports/ui/services/users-settings'; import { layoutSelect, @@ -25,7 +25,7 @@ import { DEVICE_TYPE } from '../layout/enums'; const ROLE_VIEWER = Meteor.settings.public.user.role_viewer; const PresentationContainer = ({ presentationPodIds, mountPresentation, ...props }) => { - const { layoutSwapped, podId } = props; + const { layoutSwapped } = props; const cameraDock = layoutSelectInput((i) => i.cameraDock); const presentation = layoutSelectOutput((i) => i.presentation); @@ -44,8 +44,7 @@ const PresentationContainer = ({ presentationPodIds, mountPresentation, ...props const usingUsersContext = useContext(UsersContext); const { users } = usingUsersContext; const currentUser = users[Auth.meetingID][Auth.userID]; - - const userIsPresenter = (podId === 'DEFAULT_PRESENTATION_POD') ? currentUser.presenter : props.isPresenter; + const userIsPresenter = currentUser.presenter; return ( { currentSlide, slidePosition, downloadPresentationUri: PresentationService.downloadPresentationUri(podId), - isPresenter: PresentationService.isPresenter(podId), multiUser: WhiteboardService.hasMultiUserAccess(currentSlide && currentSlide.id, Auth.userID) && !layoutSwapped, presentationIsDownloadable, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/cursor/service.js b/bigbluebutton-html5/imports/ui/components/presentation/cursor/service.js index 5ef536974a..bdc1714b49 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/cursor/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/cursor/service.js @@ -1,5 +1,5 @@ import Cursor from '/imports/ui/components/cursor/service'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; const getCurrentCursor = (cursorId) => { const cursor = Cursor.findOne({ _id: cursorId }); diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx index 8142766f6c..d68647a6c7 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/container.jsx @@ -1,21 +1,24 @@ -import React from 'react'; +import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { withTracker } from 'meteor/react-meteor-data'; import PresentationService from '/imports/ui/components/presentation/service'; import MediaService from '/imports/ui/components/media/service'; -import Service from '/imports/ui/components/actions-bar/service'; import PollService from '/imports/ui/components/poll/service'; import { makeCall } from '/imports/ui/services/api'; import PresentationToolbar from './component'; import PresentationToolbarService from './service'; +import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; +import Auth from '/imports/ui/services/auth'; const POLLING_ENABLED = Meteor.settings.public.poll.enabled; const PresentationToolbarContainer = (props) => { - const { - userIsPresenter, - layoutSwapped, - } = props; + const usingUsersContext = useContext(UsersContext); + const { users } = usingUsersContext; + const currentUser = users[Auth.meetingID][Auth.userID]; + const userIsPresenter = currentUser.presenter; + + const { layoutSwapped } = props; if (userIsPresenter && !layoutSwapped) { // Only show controls if user is presenter and layout isn't swapped @@ -23,6 +26,7 @@ const PresentationToolbarContainer = (props) => { return ( ); } @@ -44,9 +48,7 @@ export default withTracker((params) => { }; return { - amIPresenter: Service.amIPresenter(), layoutSwapped: MediaService.getSwapLayout() && MediaService.shouldEnableSwapLayout(), - userIsPresenter: PresentationService.isPresenter(podId), numberOfSlides: PresentationToolbarService.getNumberOfSlides(podId, presentationId), nextSlide: PresentationToolbarService.nextSlide, previousSlide: PresentationToolbarService.previousSlide, @@ -65,9 +67,6 @@ PresentationToolbarContainer.propTypes = { zoom: PropTypes.number.isRequired, zoomChanger: PropTypes.func.isRequired, - // Is the user a presenter - userIsPresenter: PropTypes.bool.isRequired, - // Total number of slides in this presentation numberOfSlides: PropTypes.number.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx index 7796b9f7b3..699731ee2b 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx @@ -1,30 +1,35 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { Meteor } from 'meteor/meteor'; import { withTracker } from 'meteor/react-meteor-data'; import ErrorBoundary from '/imports/ui/components/error-boundary/component'; import FallbackModal from '/imports/ui/components/fallback-errors/fallback-modal/component'; import Service from './service'; -import PresentationService from '../service'; import PresentationUploader from './component'; +import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; +import Auth from '/imports/ui/services/auth'; const PRESENTATION_CONFIG = Meteor.settings.public.presentation; -const PresentationUploaderContainer = (props) => ( - props.isPresenter - && ( +const PresentationUploaderContainer = (props) => { + const usingUsersContext = useContext(UsersContext); + const { users } = usingUsersContext; + const currentUser = users[Auth.meetingID][Auth.userID]; + const userIsPresenter = currentUser.presenter; + + return userIsPresenter && ( }> - + - ) -); + ); +}; export default withTracker(() => { const currentPresentations = Service.getPresentations(); const { - dispatchDisableDownloadable, - dispatchEnableDownloadable, - dispatchTogglePresentationDownloadable, - } = Service; + dispatchDisableDownloadable, + dispatchEnableDownloadable, + dispatchTogglePresentationDownloadable, + } = Service; return { presentations: currentPresentations, @@ -41,6 +46,5 @@ export default withTracker(() => { dispatchTogglePresentationDownloadable, isOpen: Session.get('showUploadPresentationView') || false, selectedToBeNextCurrent: Session.get('selectedToBeNextCurrent') || null, - isPresenter: PresentationService.isPresenter('DEFAULT_PRESENTATION_POD'), }; })(PresentationUploaderContainer); diff --git a/bigbluebutton-html5/imports/ui/components/presentation/service.js b/bigbluebutton-html5/imports/ui/components/presentation/service.js index df6a9180d7..c8fff960cc 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/service.js @@ -1,7 +1,5 @@ -import PresentationPods from '/imports/api/presentation-pods'; import Presentations from '/imports/api/presentations'; import { Slides, SlidePositions } from '/imports/api/slides'; -import Auth from '/imports/ui/services/auth'; import PollService from '/imports/ui/components/poll/service'; const POLL_SETTINGS = Meteor.settings.public.poll; @@ -169,19 +167,9 @@ const parseCurrentSlideContent = (yesValue, noValue, abstentionValue, trueValue, }; }; -const isPresenter = (podId) => { - const selector = { - meetingId: Auth.meetingID, - podId, - }; - const pod = PresentationPods.findOne(selector); - return pod?.currentPresenterId === Auth.userID; -}; - export default { getCurrentSlide, getSlidePosition, - isPresenter, isPresentationDownloadable, downloadPresentationUri, currentSlidHasContent, diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx b/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx index 2ba41a11c9..5ecc64d491 100755 --- a/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx @@ -89,9 +89,10 @@ class ScreenshareComponent extends React.Component { layoutContextDispatch, intl, hidePresentation, + isPresenter, } = this.props; - screenshareHasStarted(); + screenshareHasStarted(isPresenter); // Autoplay failure handling window.addEventListener('screensharePlayFailed', this.handlePlayElementFailed); // Stream health state tracker to propagate UI changes on reconnections diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx b/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx index ec2e1041b6..a03fdf7a55 100755 --- a/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx @@ -1,6 +1,5 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; -import Users from '/imports/api/users/'; import Auth from '/imports/ui/services/auth'; import MediaService, { getSwapLayout, @@ -13,6 +12,7 @@ import { import ScreenshareComponent from './component'; import { layoutSelect, layoutSelectOutput, layoutDispatch } from '../layout/context'; import getFromUserSettings from '/imports/ui/services/users-settings'; +import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; const ScreenshareContainer = (props) => { const screenShare = layoutSelectOutput((i) => i.screenShare); @@ -23,6 +23,11 @@ const ScreenshareContainer = (props) => { const fullscreenElementId = 'Screenshare'; const fullscreenContext = (element === fullscreenElementId); + const usingUsersContext = useContext(UsersContext); + const { users } = usingUsersContext; + const currentUser = users[Auth.meetingID][Auth.userID]; + const isPresenter = currentUser.presenter; + if (isVideoBroadcasting()) { return ( { ...screenShare, fullscreenContext, fullscreenElementId, + isPresenter, } } /> @@ -43,14 +49,10 @@ const ScreenshareContainer = (props) => { const LAYOUT_CONFIG = Meteor.settings.public.layout; -export default withTracker(() => { - const user = Users.findOne({ userId: Auth.userID }, { fields: { presenter: 1 } }); - return { - isGloballyBroadcasting: isGloballyBroadcasting(), - isPresenter: user.presenter, - getSwapLayout, - shouldEnableSwapLayout, - toggleSwapLayout: MediaService.toggleSwapLayout, - hidePresentation: getFromUserSettings('bbb_hide_presentation', LAYOUT_CONFIG.hidePresentation), - }; -})(ScreenshareContainer); +export default withTracker(() => ({ + isGloballyBroadcasting: isGloballyBroadcasting(), + getSwapLayout, + shouldEnableSwapLayout, + toggleSwapLayout: MediaService.toggleSwapLayout, + hidePresentation: getFromUserSettings('bbb_hide_presentation', LAYOUT_CONFIG.hidePresentation), +}))(ScreenshareContainer); diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/service.js b/bigbluebutton-html5/imports/ui/components/screenshare/service.js index 27fe34d76f..2a43842644 100644 --- a/bigbluebutton-html5/imports/ui/components/screenshare/service.js +++ b/bigbluebutton-html5/imports/ui/components/screenshare/service.js @@ -4,9 +4,8 @@ import BridgeService from '/imports/api/screenshare/client/bridge/service'; import Settings from '/imports/ui/services/settings'; import logger from '/imports/startup/client/logger'; import { stopWatching } from '/imports/ui/components/external-video-player/service'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; -import UserListService from '/imports/ui/components/user-list/service'; import AudioService from '/imports/ui/components/audio/service'; import { Meteor } from "meteor/meteor"; import MediaStreamUtils from '/imports/utils/media-stream-utils'; @@ -97,14 +96,14 @@ const attachLocalPreviewStream = (mediaElement) => { } } -const screenshareHasStarted = () => { +const screenshareHasStarted = (isPresenter) => { // Presenter's screen preview is local, so skip - if (!UserListService.amIPresenter()) { + if (!isPresenter) { viewScreenshare(); } }; -const shareScreen = async (onFail) => { +const shareScreen = async (isPresenter, onFail) => { // stop external video share if running const meeting = Meetings.findOne({ meetingId: Auth.meetingID }); @@ -114,7 +113,7 @@ const shareScreen = async (onFail) => { try { const stream = await BridgeService.getScreenStream(); - if(!UserListService.isUserPresenter(Auth.userID)) return MediaStreamUtils.stopMediaStreamTracks(stream); + if(!isPresenter) return MediaStreamUtils.stopMediaStreamTracks(stream); await KurentoBridge.share(stream, onFail); setSharingScreen(true); } catch (error) { diff --git a/bigbluebutton-html5/imports/ui/components/settings/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/component.jsx index 657e3d254b..a5303aa018 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/component.jsx @@ -106,7 +106,7 @@ class Settings extends Component { super(props); const { - dataSaving, application, + dataSaving, application, selectedTab } = props; this.state = { @@ -118,7 +118,9 @@ class Settings extends Component { dataSaving: _.clone(dataSaving), application: _.clone(application), }, - selectedTab: 0, + selectedTab: _.isFinite(selectedTab) && selectedTab >=0 && selectedTab <= 2 + ? selectedTab + : 0, }; this.updateSettings = props.updateSettings; diff --git a/bigbluebutton-html5/imports/ui/components/settings/container.jsx b/bigbluebutton-html5/imports/ui/components/settings/container.jsx index 7e0668e9b6..4b569d46e3 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/container.jsx @@ -17,7 +17,8 @@ const SettingsContainer = (props) => { return ; }; -export default withTracker(() => ({ +export default withTracker((props) => ({ + ...props, audio: SettingsService.audio, dataSaving: SettingsService.dataSaving, application: SettingsService.application, diff --git a/bigbluebutton-html5/imports/ui/components/settings/service.js b/bigbluebutton-html5/imports/ui/components/settings/service.js index 355fc2f4e3..19c434dda8 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/service.js +++ b/bigbluebutton-html5/imports/ui/components/settings/service.js @@ -1,4 +1,4 @@ -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; import Settings from '/imports/ui/services/settings'; import { notify } from '/imports/ui/services/notification'; diff --git a/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx b/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx index fa92ef1a3f..dd88d4d770 100644 --- a/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx @@ -82,6 +82,7 @@ const SidebarContent = (props) => { }; const smallSidebar = width < (maxWidth / 2); + const pollDisplay = sidebarContentPanel === PANELS.POLL ? 'inherit' : 'none'; return ( { {sidebarContentPanel === PANELS.CHAT && } {sidebarContentPanel === PANELS.SHARED_NOTES && } {sidebarContentPanel === PANELS.CAPTIONS && } - {sidebarContentPanel === PANELS.POLL - && ( - - - - )} {sidebarContentPanel === PANELS.BREAKOUT && } {sidebarContentPanel === PANELS.WAITING_USERS && } + + + ); }; diff --git a/bigbluebutton-html5/imports/ui/components/status-notifier/container.jsx b/bigbluebutton-html5/imports/ui/components/status-notifier/container.jsx index e438f7242c..96a3aee273 100644 --- a/bigbluebutton-html5/imports/ui/components/status-notifier/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/status-notifier/container.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import Auth from '/imports/ui/services/auth'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import Settings from '/imports/ui/services/settings'; import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; import { makeCall } from '/imports/ui/services/api'; diff --git a/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx b/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx index ad3894487e..1f72ec79a9 100755 --- a/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx @@ -4,12 +4,15 @@ import Auth from '/imports/ui/services/auth'; import logger from '/imports/startup/client/logger'; import GroupChat from '/imports/api/group-chat'; import Annotations from '/imports/api/annotations'; -import Users, { localUsersSync } from '/imports/ui/local-collections/users-collection/users'; -import { localBreakoutsSync } from '/imports/ui/local-collections/breakouts-collection/breakouts'; -import { localGuestUsersSync } from '/imports/ui/local-collections/guest-users-collection/guest-users'; -import { localMeetingsSync } from '/imports/ui/local-collections/meetings-collection/meetings'; +import Users from '/imports/api/users'; import AnnotationsTextService from '/imports/ui/components/whiteboard/annotations/text/service'; import { Annotations as AnnotationsLocal } from '/imports/ui/components/whiteboard/service'; +import { + localBreakoutsSync, + localGuestUsersSync, + localMeetingsSync, + localUsersSync, +} from '/client/collection-mirror-initializer'; import SubscriptionRegistry, { subscriptionReactivity } from '../../services/subscription-registry/subscriptionRegistry'; const CHAT_CONFIG = Meteor.settings.public.chat; @@ -37,6 +40,7 @@ class Subscriptions extends Component { Session.set('subscriptionsReady', true); const event = new Event(EVENT_NAME_SUBSCRIPTION_READY); window.dispatchEvent(event); + Session.set('globalIgnoreDeletes', false); } } @@ -49,6 +53,12 @@ class Subscriptions extends Component { export default withTracker(() => { const { credentials } = Auth; const { meetingId, requesterUserId } = credentials; + const userWillAuth = Session.get('userWillAuth'); + // This if exist because when a unauth user try to subscribe to a publisher + // it returns a empty collection to the subscription + // and not rerun when the user is authenticated + if (userWillAuth) return {}; + if (Session.get('codeError')) { return { subscriptionsReady: true, @@ -90,11 +100,11 @@ export default withTracker(() => { if (oldRole === 'VIEWER' && currentUser?.role === 'MODERATOR') { // let this withTracker re-execute when a subscription is stopped subscriptionReactivity.depend(); - // Prevent data being removed by subscription stop localBreakoutsSync.setIgnoreDeletes(true); localGuestUsersSync.setIgnoreDeletes(true); localMeetingsSync.setIgnoreDeletes(true); localUsersSync.setIgnoreDeletes(true); + // Prevent data being removed by subscription stop // stop role dependent subscriptions [ SubscriptionRegistry.getSubscription('meetings'), diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js index 20882610e8..bf313653b4 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/service.js +++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js @@ -1,8 +1,8 @@ -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import VoiceUsers from '/imports/api/voice-users'; import GroupChat from '/imports/api/group-chat'; -import Breakouts from '/imports/ui/local-collections/breakouts-collection/breakouts'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Breakouts from '/imports/api/breakouts'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import Storage from '/imports/ui/services/storage/session'; import { EMOJI_STATUSES } from '/imports/utils/statuses'; @@ -595,8 +595,6 @@ const isUserPresenter = (userId) => { return user ? user.presenter : false; }; -const amIPresenter = () => isUserPresenter(Auth.userID); - export const getUserNamesLink = (docTitle, fnSortedLabel, lnSortedLabel) => { const mimeType = 'text/plain'; const userNamesObj = getUsers() @@ -666,7 +664,6 @@ export default { requestUserInformation, focusFirstDropDownItem, isUserPresenter, - amIPresenter, getUsersProp, getUserCount, sortUsersByCurrent, diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx index 057adcee80..394bc20634 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/container.jsx @@ -4,7 +4,7 @@ import { Session } from 'meteor/session'; import Auth from '/imports/ui/services/auth'; import Storage from '/imports/ui/services/storage/session'; import UserContent from './component'; -import GuestUsers from '/imports/ui/local-collections/guest-users-collection/guest-users'; +import GuestUsers from '/imports/api/guest-users'; import { layoutSelectInput, layoutDispatch } from '../../layout/context'; import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; import WaitingUsersService from '/imports/ui/components/waiting-users/service'; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx index 6903609851..77bbc191f2 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import BreakoutService from '/imports/ui/components/breakout-room/service'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import UserListItem from './component'; import UserListService from '/imports/ui/components/user-list/service'; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx index 26ca5d0dea..26df63fea7 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/container.jsx @@ -1,7 +1,7 @@ import { withTracker } from 'meteor/react-meteor-data'; import PropTypes from 'prop-types'; import Auth from '/imports/ui/services/auth'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import ActionsBarService from '/imports/ui/components/actions-bar/service'; import LearningDashboardService from '/imports/ui/components/learning-dashboard/service'; import UserListService from '/imports/ui/components/user-list/service'; diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx b/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx index 820ddbd0ba..f248859d5d 100755 --- a/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx @@ -1,8 +1,8 @@ import React from 'react'; import { withModalMounter } from '/imports/ui/components/modal/service'; import { withTracker } from 'meteor/react-meteor-data'; -import Users from '/imports/ui/local-collections/users-collection/users'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Users from '/imports/api/users'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import Service from './service'; import VideoPreview from './component'; diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/styles.js b/bigbluebutton-html5/imports/ui/components/video-preview/styles.js index 2ee187e6ab..3b1282adf3 100644 --- a/bigbluebutton-html5/imports/ui/components/video-preview/styles.js +++ b/bigbluebutton-html5/imports/ui/components/video-preview/styles.js @@ -1,4 +1,4 @@ -import styled, { css, keyframes } from 'styled-components' +import styled, { css, keyframes } from 'styled-components'; import { borderSize, borderSizeLarge, @@ -7,7 +7,7 @@ import { colorGrayLabel, colorWhite, colorGrayLighter, - colorBackground, + colorGrayDark, colorPrimary, colorText, } from '/imports/ui/stylesheets/styled-components/palette'; @@ -117,7 +117,7 @@ const BrowserWarning = styled.p` const Title = styled.div` display: block; - color: ${colorBackground}; + color: ${colorGrayDark}; font-size: 1.4rem; text-align: center; `; @@ -175,7 +175,7 @@ const ellipsis = keyframes` to { width: 1.5em; } -` +`; const FetchingAnimation = styled.span` margin: auto; @@ -226,4 +226,4 @@ export default { VideoPreviewModal, FetchingAnimation, VideoPreview, -}; \ No newline at end of file +}; diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/many-users-notify/container.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/many-users-notify/container.jsx index 1320a74dcb..05f3de23bc 100644 --- a/bigbluebutton-html5/imports/ui/components/video-provider/many-users-notify/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/many-users-notify/container.jsx @@ -1,5 +1,5 @@ import { withTracker } from 'meteor/react-meteor-data'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import Users from '/imports/api/users/'; import VideoStreams from '/imports/api/video-streams'; diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js index e380b05ce1..46677d33e3 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js @@ -2,8 +2,8 @@ import { Tracker } from 'meteor/tracker'; import { Session } from 'meteor/session'; import Settings from '/imports/ui/services/settings'; import Auth from '/imports/ui/services/auth'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Meetings from '/imports/api/meetings'; +import Users from '/imports/api/users'; import VideoStreams from '/imports/api/video-streams'; import UserListService from '/imports/ui/components/user-list/service'; import { makeCall } from '/imports/ui/services/api'; diff --git a/bigbluebutton-html5/imports/ui/components/waiting-users/alert/container.jsx b/bigbluebutton-html5/imports/ui/components/waiting-users/alert/container.jsx index 7a768386ee..83cb59dafb 100644 --- a/bigbluebutton-html5/imports/ui/components/waiting-users/alert/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/waiting-users/alert/container.jsx @@ -1,7 +1,7 @@ import React, { useContext } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import Auth from '/imports/ui/services/auth'; -import GuestUsers from '/imports/ui/local-collections/guest-users-collection/guest-users'; +import GuestUsers from '/imports/api/guest-users'; import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; import WaitingComponent from './component'; import { layoutSelectInput, layoutDispatch } from '../../layout/context'; diff --git a/bigbluebutton-html5/imports/ui/components/waiting-users/container.jsx b/bigbluebutton-html5/imports/ui/components/waiting-users/container.jsx index 4b5eff4610..8d8bef9efd 100644 --- a/bigbluebutton-html5/imports/ui/components/waiting-users/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/waiting-users/container.jsx @@ -1,8 +1,8 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import Auth from '/imports/ui/services/auth'; -import GuestUsers from '/imports/ui/local-collections/guest-users-collection/guest-users'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import GuestUsers from '/imports/api/guest-users'; +import Meetings from '/imports/api/meetings'; import Service from './service'; import WaitingComponent from './component'; import { layoutDispatch } from '../layout/context'; diff --git a/bigbluebutton-html5/imports/ui/components/waiting-users/service.js b/bigbluebutton-html5/imports/ui/components/waiting-users/service.js index 1c90d027dd..2afd33db27 100644 --- a/bigbluebutton-html5/imports/ui/components/waiting-users/service.js +++ b/bigbluebutton-html5/imports/ui/components/waiting-users/service.js @@ -1,4 +1,4 @@ -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; import Auth from '/imports/ui/services/auth'; import { makeCall } from '/imports/ui/services/api'; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx index 22b03c0c0d..259fd9f2f7 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotation-factory/reactive-annotation/container.jsx @@ -5,7 +5,7 @@ import MediaService, { getSwapLayout, shouldEnableSwapLayout } from '/imports/ui import ReactiveAnnotationService from './service'; import ReactiveAnnotation from './component'; import Auth from '/imports/ui/services/auth'; -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import getFromUserSettings from '/imports/ui/services/users-settings'; const ROLE_VIEWER = Meteor.settings.public.user.role_viewer; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/ellipse/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/ellipse/component.jsx index 11ab1c6385..052002816a 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/ellipse/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/ellipse/component.jsx @@ -12,8 +12,6 @@ export default class EllipseDrawComponent extends Component { const { slideWidth, slideHeight, annotation } = this.props; const { points } = annotation; - // x1 and y1 - coordinates of the ellipse's top left corner - // x2 and y2 - coordinates of the ellipse's bottom right corner const x1 = points[0]; const y1 = points[1]; const x2 = points[2]; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx index 7c87acc2b2..005f3cf5ca 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/component.jsx @@ -149,18 +149,7 @@ export default class TextDrawComponent extends Component { return ( - - - - - ( - -); +const TextDrawContainer = (props) => { + const { isMultiUser, activeTextShapeId, annotation } = props; + + const usingUsersContext = useContext(UsersContext); + const { users } = usingUsersContext; + const currentUser = users[Auth.meetingID][Auth.userID]; + const userIsPresenter = currentUser.presenter; + + let isActive = false; + + if ((userIsPresenter || isMultiUser) && activeTextShapeId === annotation.id) { + isActive = true; + } + + return ; +}; export default withTracker((params) => { const { whiteboardId } = params; - const isPresenter = TextShapeService.isPresenter(); const isMultiUser = WhiteboardService.isMultiUserActive(whiteboardId); const activeTextShapeId = TextShapeService.activeTextShapeId(); - let isActive = false; - if ((isPresenter || isMultiUser) && activeTextShapeId === params.annotation.id) { - isActive = true; - } return { - isActive, setTextShapeValue: TextShapeService.setTextShapeValue, resetTextShapeActiveId: TextShapeService.resetTextShapeActiveId, + isMultiUser, + activeTextShapeId, }; })(TextDrawContainer); diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/service.js b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/service.js index 43cdbfd450..2e23bd22cb 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/service.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/text/service.js @@ -1,6 +1,4 @@ import Storage from '/imports/ui/services/storage/session'; -import Users from '/imports/ui/local-collections/users-collection/users'; -import Auth from '/imports/ui/services/auth'; const DRAW_SETTINGS = 'drawSettings'; @@ -20,11 +18,6 @@ const resetTextShapeActiveId = () => { } }; -const isPresenter = () => { - const currentUser = Users.findOne({ userId: Auth.userID }, { fields: { presenter: 1 } }); - return currentUser ? currentUser.presenter : false; -}; - const activeTextShapeId = () => { const drawSettings = Storage.getItem(DRAW_SETTINGS); return drawSettings ? drawSettings.textShape.textShapeActiveId : ''; @@ -33,6 +26,5 @@ const activeTextShapeId = () => { export default { setTextShapeValue, activeTextShapeId, - isPresenter, resetTextShapeActiveId, }; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/triangle/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/triangle/component.jsx index 1bf421da11..92b8130245 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/triangle/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/annotations/triangle/component.jsx @@ -12,21 +12,15 @@ export default class TriangleDrawComponent extends Component { const { slideWidth, slideHeight, annotation } = this.props; const { points } = annotation; - // points[0] and points[1] are x and y coordinates of the top left corner of the annotation - // points[2] and points[3] are x and y coordinates of the bottom right corner of the annotation - const xBottomLeft = points[0]; - const yBottomLeft = points[3]; - const xBottomRight = points[2]; - const yBottomRight = points[3]; - const xTop = ((xBottomRight - xBottomLeft) / 2) + xBottomLeft; - const yTop = points[1]; + const xApex = ((points[2] - points[0]) / 2) + points[0]; + const yApex = points[1]; - const path = `M${denormalizeCoord(xTop, slideWidth) - },${denormalizeCoord(yTop, slideHeight) - },${denormalizeCoord(xBottomLeft, slideWidth) - },${denormalizeCoord(yBottomLeft, slideHeight) - },${denormalizeCoord(xBottomRight, slideWidth) - },${denormalizeCoord(yBottomRight, slideHeight) + const path = `M${denormalizeCoord(xApex, slideWidth) + },${denormalizeCoord(yApex, slideHeight) + },${denormalizeCoord(points[0], slideWidth) + },${denormalizeCoord(points[3], slideHeight) + },${denormalizeCoord(points[2], slideWidth) + },${denormalizeCoord(points[3], slideHeight) }Z`; return path; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/service.js b/bigbluebutton-html5/imports/ui/components/whiteboard/service.js index 61d2d9663a..1c214b7b11 100755 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/service.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/service.js @@ -1,4 +1,4 @@ -import Users from '/imports/ui/local-collections/users-collection/users'; +import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user'; import addAnnotationQuery from '/imports/api/annotations/addAnnotation'; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx index efb0e98f98..10e8f3c3f7 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/container.jsx @@ -9,7 +9,7 @@ const WhiteboardToolbarContainer = props => ( ); export default withTracker((params) => { - const { whiteboardId } = params; + const { whiteboardId, isPresenter } = params; const data = { actions: { @@ -31,8 +31,8 @@ export default withTracker((params) => { }, textShapeActiveId: WhiteboardToolbarService.getTextShapeActiveId(), multiUser: WhiteboardService.isMultiUserActive(whiteboardId), - isPresenter: WhiteboardToolbarService.isPresenter(), - annotations: WhiteboardToolbarService.filterAnnotationList(), + isPresenter, + annotations: WhiteboardToolbarService.filterAnnotationList(isPresenter), isMeteorConnected: Meteor.status().connected, multiUserSize: WhiteboardService.getMultiUserSize(whiteboardId), }; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/service.js b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/service.js index a99d3f2a86..61e0c9e10d 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/service.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/service.js @@ -1,7 +1,5 @@ import { makeCall } from '/imports/ui/services/api'; import Storage from '/imports/ui/services/storage/session'; -import Users from '/imports/ui/local-collections/users-collection/users'; -import Auth from '/imports/ui/services/auth'; import getFromUserSettings from '/imports/ui/services/users-settings'; const DRAW_SETTINGS = 'drawSettings'; @@ -72,14 +70,8 @@ const getTextShapeActiveId = () => { return drawSettings ? drawSettings.textShape.textShapeActiveId : ''; }; -const isPresenter = () => { - const currentUser = Users.findOne({ userId: Auth.userID }, { fields: { presenter: 1 } }); - return currentUser ? currentUser.presenter : false; -}; - -const filterAnnotationList = () => { +const filterAnnotationList = (amIPresenter) => { const multiUserPenOnly = getFromUserSettings('bbb_multi_user_pen_only', WHITEBOARD_TOOLBAR.multiUserPenOnly); - const amIPresenter = isPresenter(); let filteredAnnotationList = WHITEBOARD_TOOLBAR.tools; @@ -120,6 +112,5 @@ export default { setColor, setTextShapeObject, getTextShapeActiveId, - isPresenter, filterAnnotationList, }; diff --git a/bigbluebutton-html5/imports/ui/local-collections/breakouts-collection/breakouts.js b/bigbluebutton-html5/imports/ui/local-collections/breakouts-collection/breakouts.js deleted file mode 100644 index f848b59ff3..0000000000 --- a/bigbluebutton-html5/imports/ui/local-collections/breakouts-collection/breakouts.js +++ /dev/null @@ -1,8 +0,0 @@ -import Breakouts from '/imports/api/breakouts'; -import AbstractCollection from '/imports/ui/local-collections/abstract-collection/abstract-collection'; - -const localBreakouts = new Mongo.Collection('local-breakouts', { connection: null }); - -export const localBreakoutsSync = new AbstractCollection(Breakouts, localBreakouts); - -export default localBreakouts; diff --git a/bigbluebutton-html5/imports/ui/local-collections/guest-users-collection/guest-users.js b/bigbluebutton-html5/imports/ui/local-collections/guest-users-collection/guest-users.js deleted file mode 100644 index 48b5a34649..0000000000 --- a/bigbluebutton-html5/imports/ui/local-collections/guest-users-collection/guest-users.js +++ /dev/null @@ -1,8 +0,0 @@ -import guestUsers from '/imports/api/guest-users'; -import AbstractCollection from '/imports/ui/local-collections/abstract-collection/abstract-collection'; - -const localGuestUsers = new Mongo.Collection('local-guest-users', { connection: null }); - -export const localGuestUsersSync = new AbstractCollection(guestUsers, localGuestUsers); - -export default localGuestUsers; diff --git a/bigbluebutton-html5/imports/ui/local-collections/meetings-collection/meetings.js b/bigbluebutton-html5/imports/ui/local-collections/meetings-collection/meetings.js deleted file mode 100644 index c27039d6cf..0000000000 --- a/bigbluebutton-html5/imports/ui/local-collections/meetings-collection/meetings.js +++ /dev/null @@ -1,8 +0,0 @@ -import Meetings from '/imports/api/meetings'; -import AbstractCollection from '/imports/ui/local-collections/abstract-collection/abstract-collection'; - -const localMeetings = new Mongo.Collection('local-meetings', { connection: null }); - -export const localMeetingsSync = new AbstractCollection(Meetings, localMeetings); - -export default localMeetings; diff --git a/bigbluebutton-html5/imports/ui/local-collections/users-collection/users.js b/bigbluebutton-html5/imports/ui/local-collections/users-collection/users.js deleted file mode 100644 index a735aad0e8..0000000000 --- a/bigbluebutton-html5/imports/ui/local-collections/users-collection/users.js +++ /dev/null @@ -1,8 +0,0 @@ -import Users from '/imports/api/users'; -import AbstractCollection from '/imports/ui/local-collections/abstract-collection/abstract-collection'; - -const localUsers = new Mongo.Collection('local-users', { connection: null }); - -export const localUsersSync = new AbstractCollection(Users, localUsers); - -export default localUsers; diff --git a/bigbluebutton-html5/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker.js b/bigbluebutton-html5/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker.js new file mode 100644 index 0000000000..b5e6b2b83b --- /dev/null +++ b/bigbluebutton-html5/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker.js @@ -0,0 +1,92 @@ +/* +This class allows other parts of the code to get called when an event (insert/update/delete) occurs in a server-side published cursor. + +In implementation time it was created for the publisher ( meteor live data hooks ) notify the listener ( LocalCollectionSynchronizer ) about the events. + +*/ +class CollectionEventsBroker { + static getKey(msg, updates) { + // the msg.msg has the collection event, + // see the collection hooks event object for more information + return `/${msg.collection}/${msg.msg}/`; + // TODO: also process the updates object + } + + constructor() { + this.callbacks = {}; + } + + addListener(collection, event, callback) { + try { + const index = CollectionEventsBroker.getKey({ collection, msg: event }); + const TheresCallback = this.callbacks[index]; + if (TheresCallback) { + throw new Error('There is already an associated callback for this event'); + } + this.callbacks[index] = callback; + return true; + } catch (err) { + console.error(err); + return false; + } + } + + dispatchEvent(msg, updates) { + try { + const msgIndex = CollectionEventsBroker.getKey(msg, updates); + const { fields } = msg; + const callback = this.callbacks[msgIndex]; + if (callback) { + callback({ ...fields, referenceId: msg.id }); + } + } catch (error) { + console.error('Error:', error); + } + // TODO: also process the updates object + } +} + +const collectionEventsBroker = new CollectionEventsBroker(); + +export const liveDataEventBrokerInitializer = () => { + Meteor.connection._processOneDataMessage = function (msg, updates) { + try { + const messageType = msg.msg; + let col = null; + if (msg.collection && msg.collection.indexOf('stream-cursor') === -1) { + col = Meteor.connection._stores[msg.collection]?._getCollection(); + } + collectionEventsBroker.dispatchEvent(msg, updates); + // msg is one of ['added', 'changed', 'removed', 'ready', 'updated'] + if (messageType === 'added') { + if (!col || !col.onAdded || col.onAdded(msg, updates)) { + this._process_added(msg, updates); + } + } else if (messageType === 'changed') { + if (!col || !col.onChanged || col.onChanged(msg, updates)) { + this._process_changed(msg, updates); + } + } else if (messageType === 'removed') { + if (!col || !col.onRemoved || col.onRemoved(msg, updates)) { + this._process_removed(msg, updates); + } + } else if (messageType === 'ready') { + if (!col || !col.onReady || col.onReady(msg, updates)) { + this._process_ready(msg, updates); + } + } else if (messageType === 'updated') { + if (!col || !col.onUpdated || col.onUpdated(msg, updates)) { + this._process_updated(msg, updates); + } + } else if (messageType === 'nosub') { + // ignore this + } else { + Meteor._debug('discarding unknown livedata data message type', msg); + } + } catch (err) { + console.error('Error when calling hooks', err); + } + }; +}; + +export default collectionEventsBroker; diff --git a/bigbluebutton-html5/imports/ui/local-collections/abstract-collection/abstract-collection.js b/bigbluebutton-html5/imports/ui/services/LocalCollectionSynchronizer/LocalCollectionSynchronizer.js similarity index 72% rename from bigbluebutton-html5/imports/ui/local-collections/abstract-collection/abstract-collection.js rename to bigbluebutton-html5/imports/ui/services/LocalCollectionSynchronizer/LocalCollectionSynchronizer.js index 940743f570..94763d9680 100644 --- a/bigbluebutton-html5/imports/ui/local-collections/abstract-collection/abstract-collection.js +++ b/bigbluebutton-html5/imports/ui/services/LocalCollectionSynchronizer/LocalCollectionSynchronizer.js @@ -1,7 +1,13 @@ import SubscriptionRegistry from '/imports/ui/services/subscription-registry/subscriptionRegistry'; -import CollectionEventsBroker from '/imports/ui/services/collection-hooks-callbacks/collection-hooks-callbacks'; +import CollectionEventsBroker from '/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker'; -class AbstractCollection { +/* +This class connects a local collection with the LiveDataEventBroker, propagating the changes of a server-side published cursor to a local collection. + +It also guarantee that in case of a reconnection or a re-subscription, the data is only removed after subscription is ready, avoiding the situation of missing data during re-synchronization. +*/ + +class LocalCollectionSynchronizer { constructor(serverCollection, localCollection, options = {}) { this.serverCollection = serverCollection; this.localCollection = localCollection; @@ -9,21 +15,24 @@ class AbstractCollection { this.lastSubscriptionId = ''; this.options = options; this.ignoreDeletes = false; - this.createObserver(); } + /* + This method allow to enable/disable the ignoreDeletes feature. + When enabled, system will skip the received deletes ( not apply on local collection ). + Don't panic: these deletes will be processed when the subscription gets ready - see removeOldSubscriptionData method. + */ setIgnoreDeletes(value) { this.ignoreDeletes = value; } // Replicates remote collection to local collection ( avoiding cleanup during the forced resync ) - createObserver() { + setupListeners() { const self = this; const addedCallback = function (item) { const subscription = SubscriptionRegistry .getSubscription(self.serverCollection._name); - if (item.id === 'publication-stop-marker' && item.stopped) { self.ignoreDeletes = true; return; @@ -34,8 +43,9 @@ class AbstractCollection { const wasEmpty = self.lastSubscriptionId === ''; self.lastSubscriptionId = subscription.subscriptionId; if (!wasEmpty) { - self.PollForReadyStatus(() => { + self.callWhenSubscriptionReady(() => { self.ignoreDeletes = false; + Session.set('globalIgnoreDeletes', false); self.removeOldSubscriptionData(); }); } @@ -73,7 +83,8 @@ class AbstractCollection { }; const removedCallback = function (item) { - if (self.ignoreDeletes) { + const globalIgnoreDeletes = Session.get('globalIgnoreDeletes'); + if (self.ignoreDeletes || globalIgnoreDeletes) { return; } const selector = { referenceId: item.referenceId }; @@ -86,7 +97,10 @@ class AbstractCollection { CollectionEventsBroker.addListener(this.serverCollection._name, 'removed', removedCallback); } - PollForReadyStatus(func) { + /* + This method calls the function received as parameter when the subscription gets ready. +*/ + callWhenSubscriptionReady(func) { const temp = (res) => { setTimeout(() => { const subscription = SubscriptionRegistry.getSubscription(this.serverCollection._name); @@ -103,6 +117,9 @@ class AbstractCollection { return tempPromise; } + /* + This method removes data from previous subscriptions after the current one is ready. + */ removeOldSubscriptionData() { const subscription = SubscriptionRegistry.getSubscription(this.serverCollection._name); @@ -122,4 +139,4 @@ class AbstractCollection { } } -export default AbstractCollection; +export default LocalCollectionSynchronizer; diff --git a/bigbluebutton-html5/imports/ui/services/auth/index.js b/bigbluebutton-html5/imports/ui/services/auth/index.js index 5aee3a3688..ee8a5fa549 100755 --- a/bigbluebutton-html5/imports/ui/services/auth/index.js +++ b/bigbluebutton-html5/imports/ui/services/auth/index.js @@ -244,6 +244,7 @@ class Auth { initAnnotationsStreamListener(); clearTimeout(validationTimeout); this.connectionID = authenticationTokenValidation.connectionId; + Session.set('userWillAuth', false); setTimeout(() => resolve(true), 100); break; default: diff --git a/bigbluebutton-html5/imports/ui/services/collection-hooks-callbacks/collection-hooks-callbacks.js b/bigbluebutton-html5/imports/ui/services/collection-hooks-callbacks/collection-hooks-callbacks.js deleted file mode 100644 index ff24247c8e..0000000000 --- a/bigbluebutton-html5/imports/ui/services/collection-hooks-callbacks/collection-hooks-callbacks.js +++ /dev/null @@ -1,43 +0,0 @@ -class CollectionEventsBroker { - static getKey(msg, updates) { - // the msg.msg has the collection event, - // see the collection hooks event object for more information - return `/${msg.collection}/${msg.msg}/`; - // TODO: also process the updates object - } - - constructor() { - this.callbacks = {}; - } - - addListener(collection, event, callback) { - try { - const index = CollectionEventsBroker.getKey({ collection, msg: event }); - const TheresCallback = this.callbacks[index]; - if (TheresCallback) { - throw new Error('There is already an associated callback for this event'); - } - this.callbacks[index] = callback; - return true; - } catch (err) { - console.error(err); - return false; - } - } - - dispatchEvent(msg, updates) { - try { - const msgIndex = CollectionEventsBroker.getKey(msg, updates); - const { fields } = msg; - const callback = this.callbacks[msgIndex]; - if (callback) { - callback({ ...fields, referenceId: msg.id }); - } - } catch (error) { - console.error('Error:', error); - } - // TODO: also process the updates object - } -} - -export default new CollectionEventsBroker(); diff --git a/bigbluebutton-html5/imports/ui/services/collection-hooks/collection-hooks.js b/bigbluebutton-html5/imports/ui/services/collection-hooks/collection-hooks.js deleted file mode 100644 index f0681510bc..0000000000 --- a/bigbluebutton-html5/imports/ui/services/collection-hooks/collection-hooks.js +++ /dev/null @@ -1,41 +0,0 @@ -import CollectionEventsBroker from '/imports/ui/services/collection-hooks-callbacks/collection-hooks-callbacks'; - -Meteor.connection._processOneDataMessage = function (msg, updates) { - try { - const messageType = msg.msg; - - let col = null; - if (msg.collection && msg.collection.indexOf('stream-cursor') === -1) { - col = Meteor.connection._stores[msg.collection]?._getCollection(); - } - CollectionEventsBroker.dispatchEvent(msg, updates); - // msg is one of ['added', 'changed', 'removed', 'ready', 'updated'] - if (messageType === 'added') { - if (!col || !col.onAdded || col.onAdded(msg, updates)) { - this._process_added(msg, updates); - } - } else if (messageType === 'changed') { - if (!col || !col.onChanged || col.onChanged(msg, updates)) { - this._process_changed(msg, updates); - } - } else if (messageType === 'removed') { - if (!col || !col.onRemoved || col.onRemoved(msg, updates)) { - this._process_removed(msg, updates); - } - } else if (messageType === 'ready') { - if (!col || !col.onReady || col.onReady(msg, updates)) { - this._process_ready(msg, updates); - } - } else if (messageType === 'updated') { - if (!col || !col.onUpdated || col.onUpdated(msg, updates)) { - this._process_updated(msg, updates); - } - } else if (messageType === 'nosub') { - // ignore this - } else { - Meteor._debug('discarding unknown livedata data message type', msg); - } - } catch (err) { - console.error('Error when calling hooks', err); - } -}; diff --git a/bigbluebutton-html5/imports/ui/services/meeting-settings/index.js b/bigbluebutton-html5/imports/ui/services/meeting-settings/index.js index 8af6646c29..0830209b9d 100644 --- a/bigbluebutton-html5/imports/ui/services/meeting-settings/index.js +++ b/bigbluebutton-html5/imports/ui/services/meeting-settings/index.js @@ -1,5 +1,5 @@ import Auth from '/imports/ui/services/auth'; -import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; +import Meetings from '/imports/api/meetings'; export default function getFromMeetingSettings(setting, defaultValue) { const prop = Meetings.findOne( diff --git a/bigbluebutton-html5/imports/ui/stylesheets/styled-components/breakpoints.js b/bigbluebutton-html5/imports/ui/stylesheets/styled-components/breakpoints.js index 50b2e65883..82aa979d42 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/styled-components/breakpoints.js +++ b/bigbluebutton-html5/imports/ui/stylesheets/styled-components/breakpoints.js @@ -1,10 +1,12 @@ const smallOnly = 'only screen and (max-width: 40em)'; const mediumOnly = 'only screen and (min-width: 40.063em) and (max-width: 64em)'; const mediumUp = 'only screen and (min-width: 40.063em)'; +const mediumDown = 'only screen and (max-width: 40.0629em)'; const landscape = "only screen and (orientation: landscape)"; const phoneLandscape = 'only screen and (max-width: 480px) and (orientation: landscape)'; const largeUp = 'only screen and (min-width: 64.063em)'; const hasPhoneDimentions = 'only screen and (max-height: 479px), only screen and (max-width: 479px)'; +const hasPhoneWidth = 'only screen and (max-width: 479px)'; export { smallOnly, @@ -14,4 +16,6 @@ export { phoneLandscape, largeUp, hasPhoneDimentions, + mediumDown, + hasPhoneWidth, }; diff --git a/bigbluebutton-html5/imports/ui/stylesheets/styled-components/palette.js b/bigbluebutton-html5/imports/ui/stylesheets/styled-components/palette.js index 6d6d82c5f7..cf0cf25811 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/styled-components/palette.js +++ b/bigbluebutton-html5/imports/ui/stylesheets/styled-components/palette.js @@ -1,110 +1,110 @@ -const colorWhite = '#FFF'; -const colorOffWhite = '#F3F6F9'; +const colorWhite = 'var(--color-white, #FFF)'; +const colorOffWhite = 'var(--color-off-white, #F3F6F9)'; -const colorBlack = '#000000'; +const colorBlack = 'var(--color-black, #000000)'; -const colorGray = '#4E5A66'; -const colorGrayDark = '#06172A'; -const colorGrayLight = '#8B9AA8'; -const colorGrayLighter = '#A7B3BD'; -const colorGrayLightest = '#D4D9DF'; +const colorGray = 'var(--color-gray, #4E5A66)'; +const colorGrayDark = 'var(--color-gray-dark, #06172A)'; +const colorGrayLight = 'var(--color-gray-light, #8B9AA8)'; +const colorGrayLighter = 'var(--color-gray-lighter, #A7B3BD)'; +const colorGrayLightest = 'var(--color-gray-lightest, #D4D9DF)'; -const colorBlueLight = '#54a1f3'; -const colorBlueLighter = '#92BCEA'; -const colorBlueLightest = '#E4ECF2'; +const colorBlueLight = 'var(--color-blue-light, #54a1f3)'; +const colorBlueLighter = 'var(--color-blue-lighter, #92BCEA)'; +const colorBlueLightest = 'var(--color-blue-lightest, #E4ECF2)'; -const colorTransparent = '#ff000000'; +const colorTransparent = 'var(--color-transparent, #ff000000)'; -const colorPrimary = '#0F70D7'; -const colorDanger = '#DF2721'; -const colorSuccess = '#008081'; -const colorWarning = 'purple'; -const colorOffline = colorGrayLight; -const colorMuted = '#586571'; -const colorMutedBackground = '#F3F6F9'; +const colorPrimary = 'var(--color-primary, #0F70D7)'; +const colorDanger = 'var(--color-danger, #DF2721)'; +const colorSuccess = 'var(--color-success, #008081)'; +const colorWarning = 'var(--color-warning, purple)'; +const colorOffline = `var(--color-offline, ${colorGrayLight})`; +const colorMuted = 'var(--color-muted, #586571)'; +const colorMutedBackground = 'var(--color-muted-background, #F3F6F9)'; -const colorBackground = colorGrayDark; -const userListBg = colorOffWhite; -const userListText = colorGray; -const unreadMessagesBg = colorDanger; -const colorGrayLabel = colorGray; -const colorText = colorGray; -const colorLink = colorPrimary; +const colorBackground = `var(--color-background, ${colorGrayDark})`; +const userListBg = `var(--user-list-bg, ${colorOffWhite})`; +const userListText = `var(--user-list-text, ${colorGray})`; +const unreadMessagesBg = `var(--unread-messages-bg, ${colorDanger})`; +const colorGrayLabel = `var(--color-gray-label, ${colorGray})`; +const colorText = `var(--color-text, ${colorGray})`; +const colorLink = `var(--color-link, ${colorPrimary})`; -const listItemBgHover = '#DCE4ED'; -const colorTipBg = '#333333'; -const itemFocusBorder = colorBlueLighter; +const listItemBgHover = 'var(--list-item-bg-hover, #DCE4ED)'; +const colorTipBg = 'var(--color-tip-bg, #333333)'; +const itemFocusBorder = `var(--item-focus-border, ${colorBlueLighter})`; -const btnDefaultColor = colorGray; -const btnDefaultBg = colorWhite; -const btnDefaultBorder = colorWhite; +const btnDefaultColor = `var(--btn-default-color, ${colorGray})`; +const btnDefaultBg = `var(--btn-default-bg, ${colorWhite})`; +const btnDefaultBorder = `var(--btn-default-border, ${colorWhite})`; -const btnPrimaryBorder = colorPrimary; -const btnPrimaryColor = colorWhite; -const btnPrimaryBg = colorPrimary; +const btnPrimaryBorder = `var(--btn-primary-border, ${colorPrimary})`; +const btnPrimaryColor = `var(--btn-primary-color, ${colorWhite})`; +const btnPrimaryBg = `var(--btn-primary-bg, ${colorPrimary})`; -const btnSuccessBorder = colorSuccess; -const btnSuccessColor = colorWhite; -const btnSuccessBg = colorSuccess; +const btnSuccessBorder = `var(--btn-success-border, ${colorSuccess})`; +const btnSuccessColor = `var(--btn-success-color, ${colorWhite})`; +const btnSuccessBg = `var(--btn-success-bg, ${colorSuccess})`; -const btnWarningBorder = colorWarning; -const btnWarningColor = colorWhite; -const btnWarningBg = colorWarning; +const btnWarningBorder = `var(--btn-warning-border, ${colorWarning})`; +const btnWarningColor = `var(--btn-warning-color, ${colorWhite})`; +const btnWarningBg = `var(--btn-warning-bg, ${colorWarning})`; -const btnDangerBorder = colorDanger; -const btnDangerColor = colorWhite; -const btnDangerBg = colorDanger; +const btnDangerBorder = `var(--btn-danger-border, ${colorDanger})`; +const btnDangerColor = `var(--btn-danger-color, ${colorWhite})`; +const btnDangerBg = `var(--btn-danger-bg, ${colorDanger})`; -const btnDarkBorder = colorDanger; -const btnDarkColor = colorWhite; -const btnDarkBg = colorGrayDark; +const btnDarkBorder = `var(--btn-dark-border, ${colorDanger})`; +const btnDarkColor = `var(--btn-dark-color, ${colorWhite})`; +const btnDarkBg = `var(--btn-dark-bg, ${colorGrayDark})`; -const btnOfflineBorder = colorOffline; -const btnOfflineColor = colorWhite; -const btnOfflineBg = colorOffline; +const btnOfflineBorder = `var(--btn-offline-border, ${colorOffline})`; +const btnOfflineColor = `var(--btn-offline-color, ${colorWhite})`; +const btnOfflineBg = `var(--btn-offline-bg, ${colorOffline})`; -const btnMutedBorder = colorMutedBackground; -const btnMutedColor = colorMuted; -const btnMutedBg = colorMutedBackground; +const btnMutedBorder = `var(--btn-muted-border, ${colorMutedBackground})`; +const btnMutedColor = `var(--btn-muted-color, ${colorMuted})`; +const btnMutedBg = `var(--btn-muted-bg, ${colorMutedBackground})`; -const toolbarButtonColor = btnDefaultColor; -const userThumbnailBorder = colorGrayLight; -const loaderBg = colorGrayDark; -const loaderBullet = colorWhite; +const toolbarButtonColor = `var(--toolbar-button-color, ${btnDefaultColor})`; +const userThumbnailBorder = `var(--user-thumbnail-border, ${colorGrayLight})`; +const loaderBg = `var(--loader-bg, ${colorGrayDark})`; +const loaderBullet = `var(--loader-bullet, ${colorWhite})`; -const systemMessageBackgroundColor = '#F9FBFC'; -const systemMessageBorderColor = '#C5CDD4'; -const systemMessageFontColor = colorGrayDark; -const colorHeading = colorGrayDark; -const palettePlaceholderText = '#787675'; -const pollAnnotationGray = '#333333'; +const systemMessageBackgroundColor = 'var(--system-message-background-color, #F9FBFC)'; +const systemMessageBorderColor = 'var(--system-message-border-color, #C5CDD4)'; +const systemMessageFontColor = `var(--system-message-font-color, ${colorGrayDark})`; +const colorHeading = `var(--color-heading, ${colorGrayDark})`; +const palettePlaceholderText = 'var(--palette-placeholder-text, #787675)'; +const pollAnnotationGray = 'var(--poll-annotation-gray, #333333)'; -const toolbarButtonBorderColor = colorGrayLighter; -const toolbarListColor = colorGray; -const toolbarButtonBg = btnDefaultBg; -const toolbarListBg = '#DDD'; -const toolbarListBgFocus = '#C6C6C6'; -const colorContentBackground = '#1B2A3A'; +const toolbarButtonBorderColor = `var(--toolbar-button-border-color, ${colorGrayLighter})`; +const toolbarListColor = `var(--toolbar-list-color, ${colorGray})`; +const toolbarButtonBg = `var(--toolbar-button-bg, ${btnDefaultBg})`; +const toolbarListBg = 'var(--toolbar-list-bg, #DDD)'; +const toolbarListBgFocus = 'var(--toolbar-list-bg-focus, #C6C6C6)'; +const colorContentBackground = 'var(--color-content-background, #1B2A3A)'; -const dropdownBg = colorWhite; +const dropdownBg = `var(--dropdown-bg, ${colorWhite})`; -const pollStatsBorderColor = '#D4D9DF'; -const pollBlue = '#1A73D4'; +const pollStatsBorderColor = 'var(--poll-stats-border-color, #D4D9DF)'; +const pollBlue = 'var(--poll-blue, #1A73D4)'; -const toastDefaultColor = colorWhite; -const toastDefaultBg = colorGray; +const toastDefaultColor = `var(--toast-default-color, ${colorWhite})`; +const toastDefaultBg = `var(--toast-default-bg, ${colorGray})`; -const toastInfoColor = colorWhite; -const toastInfoBg = colorPrimary; +const toastInfoColor = `var(--toast-info-color, ${colorWhite})`; +const toastInfoBg = `var(--toast-info-bg, ${colorPrimary})`; -const toastSuccessColor = colorWhite; -const toastSuccessBg = colorSuccess; +const toastSuccessColor = `var(--toast-success-color, ${colorWhite})`; +const toastSuccessBg = `var(--toast-success-bg, ${colorSuccess})`; -const toastErrorColor = colorWhite; -const toastErrorBg = colorDanger; +const toastErrorColor = `var(--toast-error-color, ${colorWhite})`; +const toastErrorBg = `var(--toast-error-bg, ${colorDanger})`; -const toastWarningColor = colorWhite; -const toastWarningBg = colorWarning; +const toastWarningColor = `var(--toast-warning-color, ${colorWhite})`; +const toastWarningBg = `var(--toast-warning-bg, ${colorWarning})`; export { colorWhite, diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json index cbe1916288..7dd9d75e56 100644 --- a/bigbluebutton-html5/package-lock.json +++ b/bigbluebutton-html5/package-lock.json @@ -3574,6 +3574,64 @@ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, + "meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "dependencies": { + "hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "normalize-package-data": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", + "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", + "requires": { + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "trim-newlines": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.0.2.tgz", + "integrity": "sha512-GJtWyq9InR/2HRiLZgpIKv+ufIKrVrvjQWEj7PxAXNc5dwbNJkqhAUoAGgzRmULAnoOM5EIpveYd3J2VeSAIew==" + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==" + } + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/bigbluebutton-html5/public/locales/ar.json b/bigbluebutton-html5/public/locales/ar.json index 308a34af3a..5540f75517 100644 --- a/bigbluebutton-html5/public/locales/ar.json +++ b/bigbluebutton-html5/public/locales/ar.json @@ -676,16 +676,24 @@ "app.connection-status.description": "عرض حالة اتصال المستخدم", "app.connection-status.empty": "لا توجد حاليا أي مشاكل في الاتصال قد تم التبليغ عنها", "app.connection-status.more": "أكثر", - "app.connection-status.copy": "نسخ بيانات الشبكة", + "app.connection-status.copy": "نسخ الإحصائيات", "app.connection-status.copied": "تم النسخ!", "app.connection-status.jitter": "تقطع", "app.connection-status.label": "حالة الاتصال", + "app.connection-status.settings": "ضبط الإعدادات الخاصة بك", "app.connection-status.no": "لا", "app.connection-status.notification": "تم الكشف عن فقدان اتصالك", "app.connection-status.offline": "غير متصل", + "app.connection-status.audioUploadRate": "معدل تحميل الصوت", + "app.connection-status.audioDownloadRate": "معدل تحميل الصوت", + "app.connection-status.videoUploadRate": "معدل تحميل الفيديو", + "app.connection-status.videoDownloadRate": "معدل تنزيل الفيديو", "app.connection-status.lostPackets": "الحزم المفقودة", "app.connection-status.usingTurn": "باستخدام TURN", "app.connection-status.yes": "نعم", + "app.connection-status.connectionStats": "إحصائيات الاتصال", + "app.connection-status.myLogs": "سجلاتي", + "app.connection-status.sessionLogs": "سجلات الجلسة", "app.learning-dashboard.label": "لوحة التعلم", "app.learning-dashboard.description": "افتح لوحة التعلم مع أنشطة المستخدمين", "app.learning-dashboard.clickHereToOpen": "افتح لوحة التعلم", diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index 6ece198628..2976a98326 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -679,16 +679,26 @@ "app.connection-status.description": "View users' connection status", "app.connection-status.empty": "There are currently no reported connection issues", "app.connection-status.more": "more", - "app.connection-status.copy": "Copy network data", + "app.connection-status.copy": "Copy Stats", "app.connection-status.copied": "Copied!", "app.connection-status.jitter": "Jitter", "app.connection-status.label": "Connection status", + "app.connection-status.settings": "Adjusting Your Settings", "app.connection-status.no": "No", "app.connection-status.notification": "Loss in your connection was detected", "app.connection-status.offline": "offline", + "app.connection-status.audioUploadRate": "Audio Upload Rate", + "app.connection-status.audioDownloadRate": "Audio Download Rate", + "app.connection-status.videoUploadRate": "Video Upload Rate", + "app.connection-status.videoDownloadRate": "Video Download Rate", "app.connection-status.lostPackets": "Lost packets", "app.connection-status.usingTurn": "Using TURN", "app.connection-status.yes": "Yes", + "app.connection-status.connectionStats": "Connection Stats", + "app.connection-status.myLogs": "My Logs", + "app.connection-status.sessionLogs": "Session Logs", + "app.connection-status.next": "Next page", + "app.connection-status.prev": "Previous page", "app.learning-dashboard.label": "Learning Dashboard", "app.learning-dashboard.description": "Open dashboard with users activities", "app.learning-dashboard.clickHereToOpen": "Open Learning Dashboard", @@ -916,15 +926,18 @@ "playback.player.thumbnails.wrapper.aria": "Thumbnails area", "playback.player.video.wrapper.aria": "Video area", "app.learningDashboard.dashboardTitle": "Learning Dashboard", - "app.learningDashboard.user": "User", + "app.learningDashboard.downloadSessionDataLabel": "Download Session Data", + "app.learningDashboard.lastUpdatedLabel": "Last updated at", + "app.learningDashboard.sessionDataDownloadedLabel": "Downloaded!", "app.learningDashboard.shareButton": "Share with others", "app.learningDashboard.shareLinkCopied": "Link successfully copied!", + "app.learningDashboard.user": "Users", "app.learningDashboard.indicators.meetingStatusEnded": "Ended", "app.learningDashboard.indicators.meetingStatusActive": "Active", "app.learningDashboard.indicators.usersOnline": "Active Users", "app.learningDashboard.indicators.usersTotal": "Total Number Of Users", "app.learningDashboard.indicators.polls": "Polls", - "app.learningDashboard.indicators.raiseHand": "Raise Hand", + "app.learningDashboard.indicators.timeline": "Timeline", "app.learningDashboard.indicators.activityScore": "Activity Score", "app.learningDashboard.indicators.duration": "Duration", "app.learningDashboard.usersTable.title": "Overview", @@ -939,10 +952,17 @@ "app.learningDashboard.usersTable.userStatusOnline": "Online", "app.learningDashboard.usersTable.userStatusOffline": "Offline", "app.learningDashboard.usersTable.noUsers": "No users yet", + "app.learningDashboard.usersTable.name": "Name", + "app.learningDashboard.usersTable.moderator": "Moderator", + "app.learningDashboard.usersTable.pollVotes": "Poll Votes", + "app.learningDashboard.usersTable.join": "Join", + "app.learningDashboard.usersTable.left": "Left", "app.learningDashboard.pollsTable.title": "Polling", "app.learningDashboard.pollsTable.anonymousAnswer": "Anonymous Poll (answers in the last row)", "app.learningDashboard.pollsTable.anonymousRowName": "Anonymous", - "app.learningDashboard.statusTimelineTable.title": "Status Timeline", + "app.learningDashboard.pollsTable.noPollsCreatedHeading": "No polls have been created", + "app.learningDashboard.pollsTable.noPollsCreatedMessage": "Once a poll has been sent to users, their results will appear in this list.", + "app.learningDashboard.statusTimelineTable.title": "Timeline", "app.learningDashboard.errors.invalidToken": "Invalid session token", "app.learningDashboard.errors.dataUnavailable": "Data is no longer available" } diff --git a/bigbluebutton-html5/public/locales/pt_BR.json b/bigbluebutton-html5/public/locales/pt_BR.json index 9ff874499d..7bac11132c 100644 --- a/bigbluebutton-html5/public/locales/pt_BR.json +++ b/bigbluebutton-html5/public/locales/pt_BR.json @@ -658,6 +658,10 @@ "app.connection-status.label": "Status da conexão", "app.connection-status.notification": "Sua conexão foi perdida", "app.connection-status.offline": "desconectado", + "app.connection-status.audioUploadRate": "Taxa de Upload de Áudio", + "app.connection-status.audioDownloadRate": "Taxa de Download de Áudio", + "app.connection-status.videoUploadRate": "Taxa de Upload de Video", + "app.connection-status.videoDownloadRate": "Taxa de Download de Video", "app.recording.startTitle": "Iniciar gravação", "app.recording.stopTitle": "Pausar gravação", "app.recording.resumeTitle": "Continuar gravação", diff --git a/build/packages-template/bigbluebutton/build.sh b/build/packages-template/bigbluebutton/build.sh index eb4a6c5aea..62f27b2e73 100755 --- a/build/packages-template/bigbluebutton/build.sh +++ b/build/packages-template/bigbluebutton/build.sh @@ -52,7 +52,7 @@ Standards-Version: 3.9.2 Package: bigbluebutton Version: $VERSION -Maintainer: Senfcall IT +Maintainer: ffdixon@bigbluebutton.org Depends: $DEPENDENCIES Architecture: amd64 Copyright: license.txt