Created
April 26, 2018 06:20
-
-
Save noblesilence/f9e6efae30089e8e2efcfc00359fa925 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* decaffeinate suggestions: | |
* DS101: Remove unnecessary use of Array.from | |
* DS102: Remove unnecessary code created because of implicit returns | |
* DS104: Avoid inline assignments | |
* DS205: Consider reworking code to avoid use of IIFEs | |
* DS207: Consider shorter variations of null checks | |
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md | |
*/ | |
const React = require("react"); | |
const PropTypes = require("prop-types"); | |
const CreateReactClass = require("create-react-class"); | |
const ReactCountdownClock = CreateReactClass({ | |
_seconds: 0, | |
_radius: null, | |
_fraction: null, | |
_content: null, | |
_canvas: null, | |
_timeoutIds: [], | |
_scale: window.devicePixelRatio || 1, | |
displayName: "ReactCountdownClock", | |
componentDidUpdate(prevProps) { | |
if (prevProps.seconds !== this.props.seconds) { | |
this._seconds = this._startSeconds(); | |
this._stopTimer(); | |
this._setupTimer(); | |
} | |
if (prevProps.color !== this.props.color) { | |
this._drawBackground(); | |
this._updateCanvas(); | |
} | |
if (prevProps.paused !== this.props.paused) { | |
if (!this.props.paused) { | |
this._startTimer(); | |
} | |
if (this.props.paused) { | |
this._pauseTimer(); | |
} | |
} | |
if (prevProps.stopped !== this.props.stopped) { | |
this._clearBackground(); | |
this._clearTimer(); | |
this._seconds = this._startSeconds(); | |
this._stopTimer(); | |
return this._setupTimer(); | |
} | |
}, | |
componentDidMount() { | |
this._seconds = this._startSeconds(); | |
return this._setupTimer(); | |
}, | |
componentWillUnmount() { | |
return this._cancelTimer(); | |
}, | |
_startSeconds() { | |
// To prevent a brief flash of the start time when not paused | |
if (this.props.paused && this.props.stopped) { | |
return this.props.seconds; | |
} else { | |
return this.props.seconds - 0.01; | |
} | |
}, | |
_setupTimer() { | |
this._setScale(); | |
this._setupCanvases(); | |
this._drawBackground(); | |
this._drawTimer(); | |
if (!this.props.paused && !this.props.stopped) { | |
return this._startTimer(); | |
} | |
}, | |
_updateCanvas() { | |
this._clearTimer(); | |
return this._drawTimer(); | |
}, | |
_setScale() { | |
this._radius = this.props.size / 2; | |
this._fraction = 2 / this._seconds; | |
this._tickPeriod = this._calculateTick(); | |
return (this._innerRadius = this.props.weight | |
? this._radius - this.props.weight | |
: this._radius / 1.8); | |
}, | |
_calculateTick() { | |
// Tick period (milleseconds) needs to be fast for smaller time periods and slower | |
// for longer ones. This provides smoother rendering. It should never exceed 1 second. | |
const tickScale = 1.8; | |
const tick = this._seconds * tickScale; | |
if (tick > 1000) { | |
return 1000; | |
} else { | |
return tick; | |
} | |
}, | |
_setupCanvases() { | |
if (this._background && this._timer) { | |
return; | |
} | |
this._background = this.refs.background.getContext("2d"); | |
this._background.scale(this._scale, this._scale); | |
this._timer = this.refs.timer.getContext("2d"); | |
this._timer.textAlign = "center"; | |
this._timer.textBaseline = "middle"; | |
this._timer.scale(this._scale, this._scale); | |
if (this.props.onClick != null) { | |
return this.refs.component.addEventListener("click", this.props.onClick); | |
} | |
}, | |
_startTimer() { | |
// Give it a moment to collect it's thoughts for smoother render | |
return this._timeoutIds.push(setTimeout(() => this._tick()), 200); | |
}, | |
_pauseTimer() { | |
this._stopTimer(); | |
return this._updateCanvas(); | |
}, | |
_stopTimer() { | |
return Array.from(this._timeoutIds).map(timeout => clearTimeout(timeout)); | |
}, | |
_cancelTimer() { | |
this._stopTimer(); | |
if (this.props.onClick != null) { | |
return this.refs.component.removeEventListener( | |
"click", | |
this.props.onClick | |
); | |
} | |
}, | |
_tick() { | |
const start = Date.now(); | |
return this._timeoutIds.push( | |
setTimeout(() => { | |
const duration = (Date.now() - start) / 1000; | |
this._seconds -= duration; | |
if (this._seconds <= 0) { | |
this._seconds = 0; | |
this._handleComplete(); | |
return this._clearTimer(); | |
} else { | |
this._updateCanvas(); | |
return this._tick(); | |
} | |
}, this._tickPeriod) | |
); | |
}, | |
_handleComplete() { | |
if (this.props.onComplete) { | |
return this.props.onComplete(); | |
} | |
}, | |
_clearBackground() { | |
return this._background.clearRect( | |
0, | |
0, | |
this.refs.timer.width, | |
this.refs.timer.height | |
); | |
}, | |
_clearTimer() { | |
if (this.refs.timer != null) { | |
return this._timer.clearRect( | |
0, | |
0, | |
this.refs.timer.width, | |
this.refs.timer.height | |
); | |
} | |
}, | |
_drawBackground() { | |
this._clearBackground(); | |
this._background.beginPath(); | |
this._background.globalAlpha = this.props.alpha / 3; | |
this._background.fillStyle = this.props.color; | |
this._background.arc( | |
this._radius, | |
this._radius, | |
this._radius, | |
0, | |
Math.PI * 2, | |
false | |
); | |
this._background.arc( | |
this._radius, | |
this._radius, | |
this._innerRadius, | |
Math.PI * 2, | |
0, | |
true | |
); | |
this._background.closePath(); | |
return this._background.fill(); | |
}, | |
_formattedTime() { | |
let left; | |
const decimals = | |
(left = this._seconds < 10 && this.props.showMilliseconds) != null | |
? left | |
: { 1: 0 }; | |
if (this.props.timeFormat === "hms") { | |
let seconds; | |
const hours = parseInt(this._seconds / 3600) % 24; | |
const minutes = parseInt(this._seconds / 60) % 60; | |
if (decimals) { | |
seconds = (Math.floor(this._seconds * 10) / 10).toFixed(decimals); | |
} else { | |
seconds = Math.floor(this._seconds % 60); | |
} | |
let hoursStr = `${hours}`; | |
let minutesStr = `${minutes}`; | |
let secondsStr = `${seconds}`; | |
if (hours < 10) { | |
hoursStr = `0${hours}`; | |
} | |
if (minutes < 10 && hours >= 1) { | |
minutesStr = `0${minutes}`; | |
} | |
if (seconds < 10 && (minutes >= 1 || hours >= 1)) { | |
secondsStr = `0${seconds}`; | |
} | |
const timeParts = []; | |
if (hours > 0) { | |
timeParts.push(hoursStr); | |
} | |
if (minutes > 0 || hours > 0) { | |
timeParts.push(minutesStr); | |
} | |
timeParts.push(secondsStr); | |
return timeParts.join(":"); | |
} else { | |
return (Math.floor(this._seconds * 10) / 10).toFixed(decimals); | |
} | |
}, | |
_fontSize(timeString) { | |
if (this.props.fontSize === "auto") { | |
const scale = (() => { | |
switch (timeString.length) { | |
case 8: | |
return 4; // hh:mm:ss | |
case 5: | |
return 3; // mm:ss | |
default: | |
return 2; // ss or ss.s | |
} | |
})(); | |
const size = this._radius / scale; | |
return `${size}px`; | |
} else { | |
return this.props.fontSize; | |
} | |
}, | |
_drawTimer() { | |
const percent = this._fraction * this._seconds + 1.5; | |
const formattedTime = this._formattedTime(); | |
const text = | |
this.props.paused && this.props.pausedText != null | |
? this.props.pausedText | |
: this.props.stopped | |
? "" | |
: formattedTime; | |
// Timer | |
this._timer.globalAlpha = this.props.alpha; | |
this._timer.fillStyle = this.props.color; | |
this._timer.font = `bold ${this._fontSize(formattedTime)} ${ | |
this.props.font | |
}`; | |
this._timer.fillText(text, this._radius, this._radius); | |
this._timer.beginPath(); | |
this._timer.arc( | |
this._radius, | |
this._radius, | |
this._radius, | |
Math.PI * 1.5, | |
Math.PI * percent, | |
false | |
); | |
this._timer.arc( | |
this._radius, | |
this._radius, | |
this._innerRadius, | |
Math.PI * percent, | |
Math.PI * 1.5, | |
true | |
); | |
this._timer.closePath(); | |
return this._timer.fill(); | |
}, | |
render() { | |
const canvasStyle = { | |
position: "absolute", | |
width: this.props.size, | |
height: this.props.size | |
}; | |
const canvasProps = { | |
style: canvasStyle, | |
height: this.props.size * this._scale, | |
width: this.props.size * this._scale | |
}; | |
return ( | |
<div | |
ref="component" | |
className="react-countdown-clock" | |
style={{ width: this.props.size, height: this.props.size }} | |
> | |
<canvas {...Object.assign({ ref: "background" }, canvasProps)} /> | |
<canvas {...Object.assign({ ref: "timer" }, canvasProps)} /> | |
</div> | |
); | |
} | |
}); | |
ReactCountdownClock.propTypes = { | |
seconds: PropTypes.number, | |
size: PropTypes.number, | |
weight: PropTypes.number, | |
color: PropTypes.string, | |
fontSize: PropTypes.string, | |
font: PropTypes.string, | |
alpha: PropTypes.number, | |
timeFormat: PropTypes.string, | |
onComplete: PropTypes.func, | |
onClick: PropTypes.func, | |
showMilliseconds: PropTypes.bool, | |
stopped: PropTypes.bool, | |
paused: PropTypes.bool, | |
pausedText: PropTypes.string | |
}; | |
ReactCountdownClock.defaultProps = { | |
seconds: 60, | |
size: 300, | |
color: "#000", | |
alpha: 1, | |
timeFormat: "hms", | |
fontSize: "auto", | |
font: "Arial", | |
showMilliseconds: true, | |
paused: false, | |
stopped: false | |
}; | |
module.exports = ReactCountdownClock; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment