Last active
July 6, 2018 14:28
-
-
Save hogashi/4ab8e9512cb71eeb585f5f7d9530a00e to your computer and use it in GitHub Desktop.
hogas-LTtimer is a simple LT timer works on browser.
This file contains 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
<!DOCTYPE html> | |
<html lang="ja"> | |
<head> | |
<meta charset="utf-8"> | |
<title>lttimer</title> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
font-family: Consolas, monospace; | |
} | |
html, body { | |
width: 100%; | |
height: 100%; | |
} | |
#main { | |
width: 100%; | |
height: 100%; | |
min-height: 400px; | |
display: flex; | |
flex-flow: column; | |
justify-content: space-around; | |
align-items: center; | |
box-sizing: border-box; | |
} | |
.isRunning { | |
background-color: #dfe; | |
} | |
.hurry { | |
background-color: #fbb; | |
} | |
#interaction { | |
display: flex; | |
align-items: center; | |
font-size: 40px; | |
} | |
#input { | |
display: block; | |
flex: 0 1 auto; | |
width: 150px; | |
padding: 10px; | |
box-sizing: border-box; | |
font-size: 50px; | |
height: 80px; | |
} | |
.valid { | |
border: #3da solid 3px; | |
background-color: #fff; | |
} | |
.invalid { | |
border: #f00 solid 3px; | |
background-color: #fee; | |
} | |
button { | |
display: block; | |
flex: 0 1 auto; | |
width: 80px; | |
height: 60px; | |
margin-left: 10px; | |
font-size: 20px; | |
background: #fff; | |
border: 2px solid #000; | |
} | |
#indicator { | |
flex: 0 1 auto; | |
display: flex; | |
flex-flow: column; | |
justify-content: center; | |
align-items: center; | |
font-size: 300px; | |
} | |
.end { | |
color: #fff; | |
background-color: #000; | |
} | |
#description { | |
flex: 0 1 auto; | |
} | |
</style> | |
</head> | |
<body> | |
<main id="main"> | |
<div id="interaction"> | |
minute(s): | |
<input id="input" class="" type="text" placeholder="1~99" /> | |
<button id="resetButton">[re]<br>set</button> | |
<button id="startStopButton">start<br>stop</button> | |
</div> | |
<div id="indicator"> | |
00:00 | |
</div> | |
<div id="description"> | |
<b>Space/S</b>: start/stop, | |
<b>Escape/R</b>: reset, | |
<b>C-r/F5</b>: init, | |
<b>Arrows</b>: [in/de]crease time | |
</div> | |
</main> | |
<script> | |
// indicator | |
// propTypes: { | |
// id: string.isRequired, | |
// } | |
class TimerIndicator { | |
constructor(props) { | |
this.props = props | |
this.indicator = document.getElementById(this.props.id) | |
} | |
// zero fill in 2 figures | |
// returns: string | |
_zfill(num) { | |
return `00${num}`.slice(-2) | |
} | |
updateView(second) { | |
const sec = second % 60 | |
const min = parseInt((second - sec) / 60) | |
this.indicator.innerHTML = `${this._zfill(min)}:${this._zfill(sec)}` | |
if (second > 0) { | |
this.indicator.setAttribute('class', null) | |
} | |
else { | |
this.indicator.setAttribute('class', 'end') | |
} | |
} | |
} | |
// button | |
// propTypes: { | |
// id: string.isRequired, | |
// onClick: func.isRequired | |
// } | |
class Button { | |
constructor(props) { | |
this.props = props | |
this.button = document.getElementById(this.props.id) | |
this.button.addEventListener('click', this._onClick.bind(this)) | |
} | |
_onClick(e) { | |
this.button.blur() | |
this.props.onClick(e) | |
} | |
} | |
// input | |
// propTypes: { | |
// id: string.isRequired, | |
// onTimeSubmit: func.isRequired | |
// } | |
class TimerInput { | |
constructor(props) { | |
this.props = props | |
this.input = document.getElementById(this.props.id) | |
this.input.focus() | |
} | |
// test for is 1 ~ 99 | |
// returns: boolean | |
_isValidMinute(min) { | |
if (Math.round(min) === min // is integer | |
&& min >= 1 | |
&& min <= 99) { | |
return true | |
} | |
return false | |
} | |
onSubmit() { | |
const minute = parseInt(this.input.value) | |
const isValid = this._isValidMinute(minute) | |
if (isValid) { | |
this.input.setAttribute('class', 'valid') | |
this.input.blur() | |
// pass second | |
this.props.onTimeSubmit(minute * 60) | |
} | |
else { | |
this.input.setAttribute('class', 'invalid') | |
this.input.focus() | |
} | |
} | |
} | |
// root controller | |
class TimerController { | |
constructor() { | |
this.state = { | |
isRunning: false, | |
startSecond: 0, | |
nowSecond: 0, | |
timerId: null | |
} | |
// main container | |
this.main = document.getElementById('main') | |
// timer indicator | |
this.timerIndicator = | |
new TimerIndicator({ | |
id: 'indicator', | |
}) | |
// time input | |
this.timerInput = | |
new TimerInput({ | |
id: 'input', | |
onTimeSubmit: this._resetTimer.bind(this) | |
}) | |
// buttons | |
this.resetButton = | |
new Button({ | |
id: 'resetButton', | |
onClick: this._onPressResetButton.bind(this) | |
}) | |
this.startStopButton = | |
new Button({ | |
id: 'startStopButton', | |
onClick: this._switchTimer.bind(this) | |
}) | |
document.addEventListener('keydown', this._onKeydown.bind(this)) | |
} | |
// args: KeyboardEvent | |
_onKeydown(e) { | |
// no timer operations if inputting | |
if (document.activeElement === input) { | |
if (e.key === 'Enter') { | |
this.timerInput.onSubmit() | |
} | |
return | |
} | |
const keydownOperations = { | |
' ': () => { this._switchTimer() }, | |
's': () => { this._switchTimer() }, | |
'Escape': () => { this._resetTimer(this.state.startSecond) }, | |
'r': () => { this._resetTimer(this.state.startSecond) }, | |
'ArrowRight': () => { this._modifySecond(-10) }, | |
'ArrowLeft': () => { this._modifySecond(10) }, | |
'ArrowDown': () => { this._modifySecond(-1) }, | |
'ArrowUp': () => { this._modifySecond(1) }, | |
} | |
const key = Object.keys(keydownOperations) | |
.filter(k => { | |
return k === e.key | |
})[0] | |
if (key) { | |
keydownOperations[key]() | |
} | |
} | |
_onPressResetButton() { | |
this.timerInput.onSubmit() | |
} | |
// update the timer view with the state | |
_updateView() { | |
const { isRunning, nowSecond, startSecond } = this.state | |
this.timerIndicator.updateView(nowSecond) | |
if (isRunning) { | |
if (nowSecond < startSecond * 0.2) { | |
this.main.setAttribute('class', 'hurry') | |
} | |
else { | |
this.main.setAttribute('class', 'isRunning') | |
} | |
} | |
else { | |
this.main.setAttribute('class', 'notRunning') | |
} | |
} | |
// modify the second of the state | |
// args: integer | |
_modifySecond(diff) { | |
const nextSecond = this.state.nowSecond + diff | |
// time is allowed to be between 00:00-99:59 | |
if (0 <= nextSecond && nextSecond <= 99 * 60 + 59) { | |
this.state.nowSecond = nextSecond | |
this._updateView() | |
} | |
} | |
// (re)set the timer to startSecond | |
_resetTimer(second) { | |
this._stopTimer() | |
this.state.startSecond = second | |
this.state.nowSecond = this.state.startSecond | |
this._updateView() | |
} | |
_startTimer() { | |
if (this.state.nowSecond > 0) { | |
this.state.isRunning = true | |
this.state.timerId = setInterval(() => { | |
this.state.nowSecond -= 1 | |
if (this.state.nowSecond <= 0) { | |
// stop timer | |
this.state.nowSecond = 0 | |
this._stopTimer() | |
} | |
this._updateView() | |
}, 1000) | |
} | |
// update view even not started | |
this._updateView() | |
} | |
_stopTimer() { | |
clearInterval(this.state.timerId) | |
this.state.isRunning = false | |
this._updateView() | |
} | |
// if on -> then off | |
// if off -> then on | |
_switchTimer() { | |
if (this.state.isRunning) { | |
this._stopTimer() | |
return | |
} | |
this._startTimer() | |
} | |
} | |
const lttimer = new TimerController() | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment