Based on freeCodeCamp's Pomodoro Clock project, this React clock is styled with CSS (conditional animation in the countdown).
Created
November 4, 2019 16:03
-
-
Save CalisaP/a916a61ab3a5231c6fe7926ee154296f to your computer and use it in GitHub Desktop.
Pomodoro Clock
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
| <head> | |
| </head> | |
| <body> | |
| <div id="app"/> | |
| </body> |
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
| class PomodoroClock extends React.Component{ | |
| constructor(props){ | |
| super(props); | |
| this.state = { | |
| timeLeftInSeconds: 1500, | |
| sessionLength: 25, | |
| breakLength: 5, | |
| timerStatus: false, | |
| currentTimer: "Session", | |
| interval: 0 | |
| } | |
| this.incrementSession = this.incrementSession.bind(this); | |
| this.decrementSession = this.decrementSession.bind(this); | |
| this.incrementBreak = this.incrementBreak.bind(this); | |
| this.decrementBreak = this.decrementBreak.bind(this); | |
| this.time = this.time.bind(this); | |
| this.reset = this.reset.bind(this); | |
| this.timer = this.timer.bind(this); | |
| this.countdown = this.countdown.bind(this); | |
| this.countdownTrigger = this.countdownTrigger.bind(this); | |
| this.switchTimer = this.switchTimer.bind(this); | |
| this.alarmRef = React.createRef(); | |
| this.alarm = this.alarm.bind(this); | |
| this.timerControl = this.timerControl.bind(this); | |
| } | |
| incrementSession(e){ | |
| // If timer is in Session mode, updates session length AND time left | |
| if (this.state.currentTimer === "Session" && !this.state.timerStatus){ | |
| if (this.state.sessionLength < 60){ | |
| this.setState({ | |
| sessionLength: this.state.sessionLength + 1, | |
| timeLeftInSeconds: (this.state.sessionLength + 1) * 60 | |
| }); | |
| } | |
| // If timer is in Break mode, updates session length | |
| } else if (this.state.currentTimer === "Break" && !this.state.timerStatus){ | |
| if (this.state.sessionLength < 60){ | |
| this.setState({ | |
| sessionLength: this.state.sessionLength + 1, | |
| }); | |
| } | |
| } | |
| }; | |
| decrementSession(e){ | |
| // If timer is in Session mode, updates session length AND time left | |
| if (this.state.currentTimer === "Session" && !this.state.timerStatus){ | |
| if (this.state. sessionLength > 1){ | |
| this.setState({ | |
| sessionLength: this.state.sessionLength - 1, | |
| timeLeftInSeconds: (this.state.sessionLength - 1) * 60 | |
| }); | |
| } | |
| // If timer is in Break mode, updates session length | |
| } else if (this.state.currentTimer === "Break" && !this.state.timerStatus){ | |
| if (this.state. sessionLength > 1){ | |
| this.setState({ | |
| sessionLength: this.state.sessionLength - 1 | |
| }); | |
| } | |
| } | |
| }; | |
| incrementBreak(e){ | |
| // If timer is in Session mode, updates break length | |
| if (this.state.currentTimer === "Session" && !this.state.timerStatus){ | |
| if (this.state.breakLength < 60){ | |
| this.setState({ | |
| breakLength: this.state.breakLength + 1 | |
| }); | |
| } | |
| // If timer is in Break mode, updates break length AND time left | |
| } else if (this.state.currentTimer === "Break" && !this.state.timerStatus){ | |
| if (this.state.breakLength < 60){ | |
| this.setState({ | |
| breakLength: this.state.breakLength + 1, | |
| timeLeftInSeconds: (this.state.breakLength + 1) * 60 | |
| }); | |
| } | |
| } | |
| }; | |
| decrementBreak(e){ | |
| // If timer is in Session mode, updates break length | |
| if (this.state.currentTimer === "Session" && !this.state.timerStatus){ | |
| if (this.state. breakLength > 1){ | |
| this.setState({ | |
| breakLength: this.state.breakLength - 1 | |
| }); | |
| } | |
| // If timer is in Break mode, updates break length AND time left | |
| } else if (this.state.currentTimer === "Break" && !this.state.timerStatus){ | |
| if (this.state. breakLength > 1){ | |
| this.setState({ | |
| breakLength: this.state.breakLength - 1, | |
| timeLeftInSeconds: (this.state.breakLength - 1) * 60 | |
| }); | |
| } | |
| } | |
| }; | |
| // Converts time left to 00:00 format | |
| time(){ | |
| let minutes = Math.floor(this.state.timeLeftInSeconds / 60); | |
| let seconds = this.state.timeLeftInSeconds - minutes * 60; | |
| // Adds '0' for values less than 10 | |
| if (minutes < 10){ | |
| minutes = "0" + minutes | |
| } else { | |
| minutes = minutes | |
| }; | |
| if (seconds < 10){ | |
| seconds = "0" + seconds | |
| } else { | |
| seconds = seconds | |
| }; | |
| return minutes + ":" + seconds; | |
| }; | |
| reset(e){ | |
| this.setState({ | |
| timeLeftInSeconds: 1500, | |
| sessionLength: 25, | |
| breakLength: 5, | |
| timerStatus: false, | |
| currentTimer: "Session" | |
| }) | |
| // Stops timer | |
| clearInterval(this.state.interval); | |
| // Stops alarm & resets it | |
| this.alarmRef.current.pause(); | |
| this.alarmRef.current.currentTime = 0; | |
| }; | |
| timer(e){ | |
| // Turns timer on if it is off | |
| if (this.state.timerStatus === false){ | |
| // Starts countdown | |
| this.countdownTrigger(); | |
| this.setState({ | |
| timerStatus: true, | |
| }); | |
| // Turns timer off if it is on | |
| } else if (this.state.timerStatus === true){ | |
| clearInterval(this.state.interval); | |
| return this.setState({ | |
| timerStatus: false | |
| }) | |
| } | |
| }; | |
| // Manages the countdown | |
| countdownTrigger(){ | |
| this.setState({ | |
| interval: setInterval(() => { | |
| this.countdown(); | |
| this.timerControl(); | |
| }, 1000) | |
| }); | |
| }; | |
| countdown(){ | |
| // Counts down from current time left | |
| this.setState({ | |
| timeLeftInSeconds: this.state.timeLeftInSeconds - 1 | |
| }); | |
| }; | |
| timerControl(){ | |
| // Play alarm sound at 00:00 | |
| this.alarm(this.state.timeLeftInSeconds); | |
| // If the session reaches 00:00 | |
| if (this.state.timeLeftInSeconds < 0 && this.state.currentTimer === "Session"){ | |
| // Stops timer | |
| clearInterval(this.state.interval); | |
| // Restarts countdown | |
| this.countdownTrigger(); | |
| // Updates timer displayed to value of break length & switches to break timer | |
| this.switchTimer(this.state.breakLength * 60, "Break"); | |
| console.log("Session End; Break Length: " + this.state.timeLeftInSeconds + " or " + this.time()); | |
| // If the break reaches 00:00 | |
| } else if (this.state.timeLeftInSeconds < 0 && this.state.currentTimer === "Break"){ | |
| // Stops timer | |
| clearInterval(this.state.interval); | |
| // Restarts countdown | |
| this.countdownTrigger(); | |
| // Updates timer displayed to value of session length & switches to session timer | |
| this.switchTimer(this.state.sessionLength * 60, "Session"); | |
| console.log("Break End; Session Length: " + this.state.timeLeftInSeconds + " or " + this.time()); | |
| } | |
| }; | |
| switchTimer(timeLeft, newTimer){ | |
| this.setState({ | |
| timeLeftInSeconds: timeLeft, | |
| currentTimer: newTimer | |
| }); | |
| }; | |
| // Sounds alarm when the countdown reaches zero | |
| alarm(time){ | |
| if (time === 0){ | |
| this.alarmRef.current.currentTime = 0; | |
| this.alarmRef.current.play(); | |
| } | |
| }; | |
| render(){ | |
| return( | |
| <div className="container"> | |
| <h1 id="title" className="text-center">Pomodoro Clock</h1> | |
| <div className="row justify-content-center"> | |
| <div className="col-10"> | |
| <BreakAndSession | |
| sessionLength={this.state.sessionLength} | |
| incrementSession={this.incrementSession} | |
| decrementSession={this.decrementSession} | |
| breakLength={this.state.breakLength} | |
| incrementBreak={this.incrementBreak} | |
| decrementBreak={this.decrementBreak} | |
| /> | |
| <Timer | |
| currentTimer={this.state.currentTimer} | |
| time = {this.time} | |
| timeLeft = {this.state.timeLeftInSeconds} | |
| /> | |
| <Controls | |
| reset={this.reset} | |
| timer={this.timer} | |
| /> | |
| <audio | |
| id="beep" | |
| src="http://www.wavsource.com/snds_2018-06-03_5106726768923853/sfx/alarm_beep.wav" | |
| preload="auto" | |
| ref={this.alarmRef} | |
| /> | |
| </div> | |
| </div> | |
| <a href="https://codepen.io/JMThinker"><p id="footer-text" class="text-center">A Calisa P. Project</p> </a> | |
| </div> | |
| ) | |
| } | |
| } | |
| class BreakAndSession extends React.Component{ | |
| constructor(props){ | |
| super(props); | |
| } | |
| render(){ | |
| return( | |
| <div className="container"> | |
| <div className="row justify-content-center"> | |
| <div id="break" className="col "> | |
| <h2 id="break-label" className="text-center">Break Length</h2> | |
| <button | |
| type="button" | |
| id="break-decrement" | |
| value="-" | |
| onClick={this.props.decrementBreak} | |
| className="break-session" | |
| ><i class="fa fa-angle-double-down" aria-hidden="true"/></button> | |
| <button | |
| type="button" | |
| id="break-increment" | |
| value="+" | |
| onClick={this.props.incrementBreak} | |
| className="break-session" | |
| ><i className="fa fa-angle-double-up" aria-hidden="true"/></button> | |
| <p id="break-length" className="text-center">{this.props.breakLength}</p> | |
| </div> | |
| <div id="session" className="col"> | |
| <h2 id="session-label" className="text-center">Session Length</h2> | |
| <button | |
| type="button" | |
| id="session-decrement" | |
| value="-" | |
| onClick={this.props.decrementSession} | |
| className="break-session" | |
| ><i class="fa fa-angle-double-down" aria-hidden="true"/></button> | |
| <button | |
| type="button" | |
| id="session-increment" | |
| value="+" | |
| onClick={this.props.incrementSession} | |
| className="break-session" | |
| ><i className="fa fa-angle-double-up" aria-hidden="true"/></button> | |
| <p id="session-length" className="text-center">{this.props.sessionLength}</p> | |
| </div> | |
| </div> | |
| </div> | |
| ) | |
| }; | |
| } | |
| class Timer extends React.Component{ | |
| constructor(props){ | |
| super(props); | |
| } | |
| render(){ | |
| const timeLeft = this.props.timeLeft; | |
| return( | |
| <div className="container"> | |
| <div className="row justify-content-center"> | |
| <div className="col"> | |
| <h2 id="timer-label" className="text-center">{this.props.currentTimer}</h2> | |
| <p id={timeLeft > 5? "time-left" : "time-left-red"} className="text-center">{this.props.time()}</p> | |
| </div> | |
| </div> | |
| </div> | |
| ) | |
| }; | |
| } | |
| class Controls extends React.Component{ | |
| constructor(props){ | |
| super(props); | |
| } | |
| render(){ | |
| return( | |
| <div className="container"> | |
| <div className="row justify-content-center"> | |
| <div className="col"> | |
| <button | |
| type="button" | |
| id="start_stop" | |
| className="controls" | |
| onClick={this.props.timer} | |
| ><i class="fas fa-play"/><i class="fas fa-stop"/></button> | |
| <button | |
| type="button" | |
| id="reset" | |
| className="controls" | |
| onClick={this.props.reset} | |
| ><i class="fas fa-sync"/></button> | |
| </div> | |
| </div> | |
| </div> | |
| ) | |
| }; | |
| } | |
| ReactDOM.render(<PomodoroClock />, document.getElementById("app")); |
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
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script> |
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
| @font-face {font-family: streetCornerThin; src: url("//db.onlinewebfonts.com/t/786d4327194a533a3a67f462c7652535.eot"); src: url("//db.onlinewebfonts.com/t/786d4327194a533a3a67f462c7652535.eot?#iefix") format("embedded-opentype"), url("//db.onlinewebfonts.com/t/786d4327194a533a3a67f462c7652535.woff2") format("woff2"), url("//db.onlinewebfonts.com/t/786d4327194a533a3a67f462c7652535.woff") format("woff"), url("//db.onlinewebfonts.com/t/786d4327194a533a3a67f462c7652535.ttf") format("truetype"), url("//db.onlinewebfonts.com/t/786d4327194a533a3a67f462c7652535.svg#Street Corner Thin") format("svg"); } | |
| body{ | |
| font-family: streetCornerThin; | |
| background-image: url(https://www.designbolts.com/wp-content/uploads/2013/02/crafted-paper-pattern-Grey-Seamless-Pattern-For-Website-Background.jpg); | |
| } | |
| #title{ | |
| padding-top: 30px; | |
| padding-bottom: 30px; | |
| font-weight: bold; | |
| } | |
| .break-session, .controls{ | |
| width: 33.33%; | |
| background-color: transparent; | |
| border: none; | |
| font-size: 30px; | |
| position: relative; | |
| left: 15% | |
| } | |
| #break-length, #session-length{ | |
| font-size: 30px; | |
| } | |
| @font-face { | |
| font-family: digital; src: url("//db.onlinewebfonts.com/t/055d8bd397b68d5d121796f56b904640.eot"); src: url("//db.onlinewebfonts.com/t/055d8bd397b68d5d121796f56b904640.eot?#iefix") format("embedded-opentype"), url("//db.onlinewebfonts.com/t/055d8bd397b68d5d121796f56b904640.woff2") format("woff2"), url("//db.onlinewebfonts.com/t/055d8bd397b68d5d121796f56b904640.woff") format("woff"), url("//db.onlinewebfonts.com/t/055d8bd397b68d5d121796f56b904640.ttf") format("truetype"), url("//db.onlinewebfonts.com/t/055d8bd397b68d5d121796f56b904640.svg#Digital") format("svg"); } | |
| #time-left{ | |
| font-family: digital; | |
| font-size: 60px; | |
| } | |
| @keyframes blink{ | |
| 0%{ | |
| transform: scale(1); | |
| } | |
| 49%{ | |
| transform: scale(1.5); | |
| } | |
| 50%{ | |
| transform: scale(1.5); | |
| } | |
| 99%{ | |
| transform: scale(1.5); | |
| } | |
| 100%{ | |
| transform: scale(1); | |
| } | |
| } | |
| #time-left-red{ | |
| font-family: digital; | |
| font-size: 60px; | |
| color: red; | |
| animation: blink 0.8s infinite; | |
| } | |
| #footer-text{ | |
| padding-top: 30px; | |
| color: black; | |
| text-decoration: underline; | |
| } |
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
| <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" /> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.min.css" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment