From f4a54218435af4fe1c4908aae1fd397d14403036 Mon Sep 17 00:00:00 2001 From: Pedro Beschorner Marin Date: Sun, 26 Apr 2020 14:44:01 -0300 Subject: [PATCH] Add timer view --- .../api/timer/server/modifiers/addTimer.js | 4 +- .../api/timer/server/modifiers/updateTimer.js | 2 +- .../imports/ui/components/timer/component.jsx | 117 ++++++++++++- .../imports/ui/components/timer/container.jsx | 16 +- .../imports/ui/components/timer/service.js | 162 ++++++++++++++---- .../imports/ui/components/timer/styles.js | 45 +++++ .../user-list-content/timer/component.jsx | 56 +++--- 7 files changed, 331 insertions(+), 71 deletions(-) diff --git a/bigbluebutton-html5/imports/api/timer/server/modifiers/addTimer.js b/bigbluebutton-html5/imports/api/timer/server/modifiers/addTimer.js index b1e12429ac..517acbead7 100644 --- a/bigbluebutton-html5/imports/api/timer/server/modifiers/addTimer.js +++ b/bigbluebutton-html5/imports/api/timer/server/modifiers/addTimer.js @@ -30,10 +30,10 @@ export default function addTimer(meetingId) { } if (numChanged) { - return Logger.info(`Added timer meeting=${meetingId}`); + return Logger.debug(`Added timer meeting=${meetingId}`); } - return Logger.info(`Upserted timer meeting=${meetingId}`); + return Logger.debug(`Upserted timer meeting=${meetingId}`); }; return Timer.upsert(selector, modifier, cb); diff --git a/bigbluebutton-html5/imports/api/timer/server/modifiers/updateTimer.js b/bigbluebutton-html5/imports/api/timer/server/modifiers/updateTimer.js index be2f83065e..2e78aa46b3 100644 --- a/bigbluebutton-html5/imports/api/timer/server/modifiers/updateTimer.js +++ b/bigbluebutton-html5/imports/api/timer/server/modifiers/updateTimer.js @@ -120,7 +120,7 @@ export default function updateTimer(action, meetingId, time = 0, stopwatch = tru return Logger.error(`Updating timer at collection: ${err}`); } - return Logger.info(`Updated timer action=${action} meetingId=${meetingId}`); + return Logger.debug(`Updated timer action=${action} meetingId=${meetingId}`); }; return Timer.update(selector, modifier, cb); diff --git a/bigbluebutton-html5/imports/ui/components/timer/component.jsx b/bigbluebutton-html5/imports/ui/components/timer/component.jsx index 18a8d47933..47db4a48dc 100644 --- a/bigbluebutton-html5/imports/ui/components/timer/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/timer/component.jsx @@ -50,6 +50,9 @@ class Timer extends PureComponent { this.handleControlClick = this.handleControlClick.bind(this); this.handleSwitchToStopwatch = this.handleSwitchToStopwatch.bind(this); this.handleSwitchToTimer = this.handleSwitchToTimer.bind(this); + this.handleOnHoursChange = this.handleOnHoursChange.bind(this); + this.handleOnMinutesChange = this.handleOnMinutesChange.bind(this); + this.handleOnSecondsChange = this.handleOnSecondsChange.bind(this); } handleControlClick() { @@ -62,6 +65,36 @@ class Timer extends PureComponent { } } + handleOnHoursChange(event) { + const { timerStatus } = this.props; + const { target } = event; + + if (target && target.value) { + const hours = parseInt(target.value); + Service.setHours(hours, timerStatus.time); + } + } + + handleOnMinutesChange(event) { + const { timerStatus } = this.props; + const { target } = event; + + if (target && target.value) { + const minutes = parseInt(target.value); + Service.setMinutes(minutes, timerStatus.time); + } + } + + handleOnSecondsChange(event) { + const { timerStatus } = this.props; + const { target } = event; + + if (target && target.value) { + const seconds = parseInt(target.value); + Service.setSeconds(seconds, timerStatus.time); + } + } + handleSwitchToStopwatch() { const { timerStatus } = this.props; @@ -103,8 +136,85 @@ class Timer extends PureComponent { ); } + renderPreset() { + const { timerStatus } = this.props; + const preset = Service.getPreset(); + + return ( + + {preset.map((p, index) => { + const label = Service.buildPresetLabel(p); + + return ( + + Service.subtractTime(p, timerStatus.time)} + /> + Service.setTime(p)} + /> + Service.addTime(p, timerStatus.time)} + /> + + ); + })} + + ); + } + renderTimer() { - return null; + const { timerStatus } = this.props; + const { time } = timerStatus; + + const timeArray = Service.getTimeAsString(time).split(':'); + + const hasHours = timeArray.length === 3; + + const hours = hasHours ? timeArray[0] : '00'; + const minutes = hasHours ? timeArray[1] : timeArray[0]; + const seconds = hasHours ? timeArray[2] : timeArray[1]; + + return ( +
+ + + : + + : + + + {this.renderPreset()} + {this.renderControls()} +
+ ); } renderContent() { @@ -140,10 +250,11 @@ class Timer extends PureComponent { isRTL, isActive, isModerator, + layoutContextDispatch, } = this.props; if (!isActive || !isModerator) { - Session.set('openPanel', 'userlist'); + Service.closePanel(layoutContextDispatch) return null; } @@ -156,7 +267,7 @@ class Timer extends PureComponent { data-test="timerTitle" > Session.set('openPanel', 'userlist')} + onClick={() => Service.closePanel(layoutContextDispatch)} aria-label={intl.formatMessage(intlMessages.hideTimerLabel)} label={intl.formatMessage(intlMessages.title)} icon={isRTL ? "right_arrow" : "left_arrow"} diff --git a/bigbluebutton-html5/imports/ui/components/timer/container.jsx b/bigbluebutton-html5/imports/ui/components/timer/container.jsx index afa959c383..339e625b05 100644 --- a/bigbluebutton-html5/imports/ui/components/timer/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/timer/container.jsx @@ -2,15 +2,15 @@ import React, { PureComponent } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import Timer from './component'; import Service from './service'; +import { layoutDispatch } from '/imports/ui/components/layout/context'; -class TimerContainer extends PureComponent { - render() { - return ( - - {this.props.children} - - ); - } +const TimerContainer = ({ children, ...props }) => { + const layoutContextDispatch = layoutDispatch(); + return ( + + {children} + + ); } export default withTracker(() => { diff --git a/bigbluebutton-html5/imports/ui/components/timer/service.js b/bigbluebutton-html5/imports/ui/components/timer/service.js index 8b0191a570..7fcbc73aca 100644 --- a/bigbluebutton-html5/imports/ui/components/timer/service.js +++ b/bigbluebutton-html5/imports/ui/components/timer/service.js @@ -4,6 +4,7 @@ import Auth from '/imports/ui/services/auth'; import { makeCall } from '/imports/ui/services/api'; import { Session } from 'meteor/session'; import Users from '/imports/api/users'; +import Logger from '/imports/startup/client/logger'; import { ACTIONS, PANELS } from '../layout/enums'; const TIMER_CONFIG = Meteor.settings.public.timer; @@ -13,6 +14,15 @@ const MILLI_IN_HOUR = 3600000; const MILLI_IN_MINUTE = 60000; const MILLI_IN_SECOND = 1000; +const MAX_HOURS = 23; + +const MAX_TIME = 999 + + (59 * MILLI_IN_SECOND) + + (59 * MILLI_IN_MINUTE) + + (MAX_HOURS * MILLI_IN_HOUR); + +const getMaxHours = () => MAX_HOURS; + isActive = () => { const timer = Timer.findOne( { meetingId: Auth.meetingID }, @@ -29,14 +39,8 @@ const getDefaultTime = () => TIMER_CONFIG.time * MILLI_IN_MINUTE; const getInterval = () => TIMER_CONFIG.interval; -const getPresetSeconds = () => { - const { preset } = TIMER_CONFIG.preset; - - return preset.map(seconds => seconds * MILLI_IN_SECOND); -}; - -const getPresetMinutes = () => { - const { preset } = TIMER_CONFIG.preset; +const getPreset = () => { + const { preset } = TIMER_CONFIG; return preset.map(minutes => minutes * MILLI_IN_MINUTE); }; @@ -150,64 +154,75 @@ const getTimerStatus = () => { } }; -const getTimeAsString = (milliseconds, stopwatch) => { - let milli = milliseconds; +const getTimeAsString = (time, stopwatch) => { + let milliseconds = time; - let hours = Math.floor(milli / MILLI_IN_HOUR); + let hours = Math.floor(milliseconds / MILLI_IN_HOUR); const mHours = hours * MILLI_IN_HOUR; - let minutes = Math.floor((milli - mHours) / MILLI_IN_MINUTE); + let minutes = Math.floor((milliseconds - mHours) / MILLI_IN_MINUTE); const mMinutes = minutes * MILLI_IN_MINUTE; - let seconds = Math.floor((milli - mHours - mMinutes) / MILLI_IN_SECOND); + let seconds = Math.floor((milliseconds - mHours - mMinutes) / MILLI_IN_SECOND); const mSeconds = seconds * MILLI_IN_SECOND; - milli = milli - mHours - mMinutes - mSeconds; + milliseconds = milliseconds - mHours - mMinutes - mSeconds; - let time = ''; + let timeAsString = ''; // Only add hour if it exists if (hours > 0) { if (hours < 10) { - time += `0${hours}:`; + timeAsString += `0${hours}:`; } else { - time += `${hours}:`; + timeAsString += `${hours}:`; } } // Add minute if exists, has at least an hour // or is not stopwatch if (minutes > 0 || hours > 0 || !stopwatch) { - if (hours < 10) { - time += `0${minutes}:`; + if (minutes < 10) { + timeAsString += `0${minutes}:`; } else { - time += `${minutes}:`; + timeAsString += `${minutes}:`; } } // Always add seconds if (seconds < 10) { - time += `0${seconds}`; + timeAsString += `0${seconds}`; } else { - time += `${seconds}`; + timeAsString += `${seconds}`; } // Only add milliseconds if it's a stopwatch if (stopwatch) { - if (milli < 10) { - time += `:00${milli}`; - } else if (milli < 100) { - time += `:0${milli}`; + if (milliseconds < 10) { + timeAsString += `:00${milliseconds}`; + } else if (milliseconds < 100) { + timeAsString += `:0${milliseconds}`; } else { - time += `:${milli}`; + timeAsString += `:${milliseconds}`; } } - return time; + return timeAsString; }; const isPanelOpen = () => Session.get('openPanel') === 'timer'; +const closePanel = (layoutContextDispatch) => { + layoutContextDispatch({ + type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN, + value: false, + }); + layoutContextDispatch({ + type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL, + value: PANELS.NONE, + }); +}; + const togglePanel = (sidebarContentPanel, layoutContextDispatch) => { layoutContextDispatch({ type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN, @@ -228,6 +243,84 @@ const isModerator = () => { ).role === ROLE_MODERATOR; }; +const setHours = (hours, time) => { + if (!isNaN(hours) && hours >= 0 && hours <= MAX_HOURS) { + const currentHours = Math.floor(time / MILLI_IN_HOUR); + + const diff = (hours - currentHours) * MILLI_IN_HOUR; + setTimer(time + diff); + } else { + Logger.warn('Invalid time'); + } +}; + +const setMinutes = (minutes, time) => { + if (!isNaN(minutes) && minutes >= 0 && minutes <= 59) { + const currentHours = Math.floor(time / MILLI_IN_HOUR); + const mHours = currentHours * MILLI_IN_HOUR; + + const currentMinutes = Math.floor((time - mHours) / MILLI_IN_MINUTE); + + const diff = (minutes - currentMinutes) * MILLI_IN_MINUTE; + setTimer(time + diff); + } else { + Logger.warn('Invalid time'); + } +}; + +const setSeconds = (seconds, time) => { + if (!isNaN(seconds) && seconds >= 0 && seconds <= 59) { + const currentHours = Math.floor(time / MILLI_IN_HOUR); + const mHours = currentHours * MILLI_IN_HOUR; + + const currentMinutes = Math.floor((time - mHours) / MILLI_IN_MINUTE); + const mMinutes = currentMinutes * MILLI_IN_MINUTE; + + const currentSeconds = Math.floor((time - mHours - mMinutes) / MILLI_IN_SECOND); + + const diff = (seconds - currentSeconds) * MILLI_IN_SECOND; + setTimer(time + diff); + } else { + Logger.warn('Invalid time'); + } +}; + +const subtractTime = (preset, time) => { + if (!isNaN(preset)) { + const min = 0; + setTimer(Math.max(time - preset, min)); + } else { + Logger.warn('Invalid time'); + } +}; + +const setTime = (preset) => { + if (!isNaN(preset)) { + setTimer(preset); + } else { + Logger.warn('Invalid time'); + } +}; + +const addTime = (preset, time) => { + if (!isNaN(preset)) { + const max = MAX_TIME; + setTimer(Math.min(time + preset, max)); + } else { + Logger.warn('Invalid time'); + } +}; + +const buildPresetLabel = (preset) => { + const minutes = preset / MILLI_IN_MINUTE; + + if (minutes < 10) { + return `0${minutes}"00'`; + } + + return `${minutes}"00'`; +}; + export default { isActive, isEnabled, @@ -236,16 +329,23 @@ export default { startTimer, stopTimer, switchTimer, - setTimer, + setHours, + setMinutes, + setSeconds, + setTime, + subtractTime, + addTime, resetTimer, activateTimer, deactivateTimer, getInterval, - getPresetSeconds, - getPresetMinutes, + getPreset, + getMaxHours, getTimer, getTimerStatus, getTimeAsString, + closePanel, togglePanel, isModerator, + buildPresetLabel, }; diff --git a/bigbluebutton-html5/imports/ui/components/timer/styles.js b/bigbluebutton-html5/imports/ui/components/timer/styles.js index a65867ca94..7fde866b38 100644 --- a/bigbluebutton-html5/imports/ui/components/timer/styles.js +++ b/bigbluebutton-html5/imports/ui/components/timer/styles.js @@ -98,7 +98,47 @@ const TimerSwitchButton = styled(Button)` margin: 0 .5rem; `; +const StopwatchTime = styled.div` + display: flex; + margin-top: 2rem; + width: 100%; + height: 4rem; + font-size: x-large; + justify-content: center; + + input { + width: 5rem; + } +`; + +const StopwatchTimeColon = styled.span` + align-self: center; +`; + +const TimerPreset = styled.span` + display: flex; + width: 100%; + margin-top: 1rem; + flex-direction: column; + .button { + + } +`; + +const TimerLine = styled.div` + display: flex; + justify-content: center; + width: 100%; + padding: .5rem 0; +`; + +const TimerPresetButton = styled(Button)` + margin: 0 .5rem; +` + const TimerControls = styled.div` + margin-top: 4rem; + height: 4rem; display: flex; width: 100%; justify-content: center; @@ -117,6 +157,11 @@ export default { TimerContent, TimerType, TimerSwitchButton, + StopwatchTime, + StopwatchTimeColon, + TimerPreset, + TimerLine, + TimerPresetButton, TimerControls, TimerControlButton, }; \ No newline at end of file diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/timer/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/timer/component.jsx index b99d4a3ac7..d036e48b48 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/timer/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/timer/component.jsx @@ -40,16 +40,18 @@ class Timer extends Component { componentDidUpdate(prevProps) { const { timer } = this.props; - const { - running, - stopwatch, - } = timer; - const { timer: prevTimer } = prevProps; - const { - running: prevRunning, - stopwatch: prevStopwatch, - } = prevTimer; + + this.updateInterval(prevTimer, timer); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + updateInterval(prevTimer, timer) { + const { running } = timer; + const { running: prevRunning } = prevTimer; if (!prevRunning && running) { this.interval = setInterval(this.updateTime, TimerService.getInterval()); @@ -60,31 +62,33 @@ class Timer extends Component { } } - componentWillUnmount() { - clearInterval(this.interval); - } - - updateTime() { + getTime() { const { timer } = this.props; const { stopwatch, + running, time, accumulated, timestamp, } = timer; + const overTime = running ? (Date.now() - timestamp) : 0; + const elapsedTime = accumulated + overTime; + + let updatedTime; + if (stopwatch) { + updatedTime = elapsedTime; + } else { + updatedTime = Math.max(time - elapsedTime, 0); + } + + return TimerService.getTimeAsString(updatedTime, stopwatch); + } + + updateTime() { const { current } = this.timeRef; if (current) { - const accumulatedTime = accumulated + (Date.now() - timestamp); - let updatedTime; - - if (stopwatch) { - updatedTime = accumulatedTime; - } else { - updatedTime = Math.max(time - accumulatedTime, 0); - } - - current.textContent = TimerService.getTimeAsString(updatedTime, stopwatch); + current.textContent = this.getTime(); } } @@ -112,7 +116,7 @@ class Timer extends Component { aria-hidden ref={this.timeRef} > - {TimerService.getTimeAsString(accumulated, stopwatch)} + {this.getTime()} ); @@ -129,7 +133,7 @@ class Timer extends Component { aria-hidden ref={this.timeRef} > - {TimerService.getTimeAsString(accumulated, stopwatch)} + {this.getTime()} );