Add timer view
This commit is contained in:
parent
5463a4a423
commit
f4a5421843
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 (
|
||||
<Styled.TimerPreset>
|
||||
{preset.map((p, index) => {
|
||||
const label = Service.buildPresetLabel(p);
|
||||
|
||||
return (
|
||||
<Styled.TimerLine
|
||||
key={index}
|
||||
>
|
||||
<Styled.TimerPresetButton
|
||||
label="-"
|
||||
onClick={() => Service.subtractTime(p, timerStatus.time)}
|
||||
/>
|
||||
<Styled.TimerPresetButton
|
||||
color="primary"
|
||||
label={label}
|
||||
onClick={() => Service.setTime(p)}
|
||||
/>
|
||||
<Styled.TimerPresetButton
|
||||
label="+"
|
||||
onClick={() => Service.addTime(p, timerStatus.time)}
|
||||
/>
|
||||
</Styled.TimerLine>
|
||||
);
|
||||
})}
|
||||
</Styled.TimerPreset>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<Styled.StopwatchTime>
|
||||
<input
|
||||
type="number"
|
||||
value={hours}
|
||||
maxLength="2"
|
||||
max={Service.getMaxHours()}
|
||||
min="0"
|
||||
onChange={this.handleOnHoursChange}
|
||||
/>
|
||||
<Styled.StopwatchTimeColon>:</Styled.StopwatchTimeColon>
|
||||
<input
|
||||
type="number"
|
||||
value={minutes}
|
||||
maxLength="2"
|
||||
max="59"
|
||||
min="0"
|
||||
onChange={this.handleOnMinutesChange}
|
||||
/>
|
||||
<Styled.StopwatchTimeColon>:</Styled.StopwatchTimeColon>
|
||||
<input
|
||||
type="number"
|
||||
value={seconds}
|
||||
maxLength="2"
|
||||
max="59"
|
||||
min="0"
|
||||
onChange={this.handleOnSecondsChange}
|
||||
/>
|
||||
</Styled.StopwatchTime>
|
||||
{this.renderPreset()}
|
||||
{this.renderControls()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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"
|
||||
>
|
||||
<Styled.TimerMinimizeButton
|
||||
onClick={() => 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"}
|
||||
|
@ -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 (
|
||||
<Timer {...this.props}>
|
||||
{this.props.children}
|
||||
</Timer>
|
||||
);
|
||||
}
|
||||
const TimerContainer = ({ children, ...props }) => {
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
return (
|
||||
<Timer {...{ layoutContextDispatch, ...props}}>
|
||||
{children}
|
||||
</Timer>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTracker(() => {
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
@ -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()}
|
||||
</span>
|
||||
</Styled.ListItem>
|
||||
);
|
||||
@ -129,7 +133,7 @@ class Timer extends Component {
|
||||
aria-hidden
|
||||
ref={this.timeRef}
|
||||
>
|
||||
{TimerService.getTimeAsString(accumulated, stopwatch)}
|
||||
{this.getTime()}
|
||||
</span>
|
||||
</Styled.ListItem>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user