Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save linuxenko/fa3d0e7ca0d3221d8117 to your computer and use it in GitHub Desktop.

Select an option

Save linuxenko/fa3d0e7ca0d3221d8117 to your computer and use it in GitHub Desktop.
Build a Pomodoro Clock [freeCodeCamp [Advanced Projects]] (Challenge)

Build a Pomodoro Clock [freeCodeCamp [Advanced Projects]] (Challenge)

Rule #1: Don't look at the example project's code. Figure it out for yourself.

Rule #2: Fulfill the below user stories. Use whichever libraries or APIs you need. Give it your own personal style.

User Story: I can start a 25 minute pomodoro, and the timer will go off once 25 minutes has elapsed.

User Story: I can reset the clock for my next pomodoro.

User Story: I can customize the length of each pomodoro.

A Pen by Svetlana Linuxenko on CodePen.

License.

<div class="pomodoro-container" ng-app="pomodoroApp" ng-controller="pomodoroCtrl">
<h2 class="text-center title">Pomodoro Clock</h2>
<div class="pomodoro-badge container">
<div class="row">
<div class="col-xs-6 text-center">
<h5>BREAK</h5>
<span range-btn data-btn="break" data-time="break" data-minutes='00' data-seconds="15"></span>
</div>
<div class="col-xs-6 text-center">
<h5>SESSION</h5>
<span range-btn data-btn="session" data-time="session" data-minutes="25" data-seconds="00"></span>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="pomodoro text-center">
<canvas id="canvas" width="264" height="260"></canvas>
<div class="pomodoro-img"></div>
<span class="display" ng-click="displayAction()">
<span class="state">{{state}}</span>
<span>{{display}}</span>
</span>
</div>
</div>
</div>
</div>
<div class="copy"><a href="http://www.linuxenko.pro">&copy; Svetlana Linuxenko</a></div>
</div>
var app = angular.module('pomodoroApp', []);
app.controller('pomodoroCtrl', function($scope) {
$scope.session = 1500;
$scope.break = 15;
$scope.display = 'Start';
$scope.type = 'session';
$scope.state = 'stoped';
var interval = null;
var sessionTime = null;
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var quart = Math.PI / 2;
var circ = Math.PI * 2;
var radius = 86;
var drawCircle = function(percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(120, 120, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = '#fff';
ctx.lineCap = 'square';
ctx.lineWidth = 12.0;
ctx.stroke();
};
function drawProgress() {
var current =
(100 * sessionTime) / Number.parseInt($scope[$scope.type]);
current / 100;
ctx.save();
ctx.clearRect(0,0, 260, 260);
drawCircle(current / 100);
ctx.restore();
}
$scope.$watch('session', function(val) {
if ($scope.state !== 'stoped') {
$scope.type = 'session';
$scope.state = 'stoped';
resetState();
}
});
$scope.$watch('break', function(val) {
if ($scope.state !== 'stoped') {
$scope.type = 'break';
$scope.state = 'stoped';
resetState();
}
});
function toTime(time) {
var m = Math.floor(time / 60);
var s = Math.round(time % 60);
if (m < 10) { m = '0' + m; }
if (s < 10) { s = '0' + s; }
return m +' : ' + s;
}
function timer() {
sessionTime -= 1;
if (sessionTime < 0) {
$scope.type = $scope.type === 'session' ? 'break' : 'session';
$scope.state = 'stoped';
resetState();
return;
}
$scope.display = toTime(sessionTime);
$scope.$apply();
drawProgress();
}
function resetState() {
clearInterval(interval);
if ($scope.state === 'stoped') {
sessionTime = Number.parseInt($scope[$scope.type]) + 1;
}
if ($scope.state === 'paused' || $scope.state === 'stoped' ) {
setTimeout(timer, 1);
interval = setInterval(timer, 1000);
$scope.state = $scope.type;
} else {
$scope.state = 'paused';
}
}
$scope.displayAction = resetState;
drawProgress();
});
app.directive('rangeBtn', function() {
return {
restrict : 'A',
scope : {
minutes : '@',
seconds : '@',
time : '='
},
link : function(scope, element, attrs) {
element.on('click', function(e) {
if (scope.frozen === true) {return;}
var targ =
e.target.parentNode.parentNode.className;
if (targ !== 'minutes' && targ !== 'seconds') {return;}
var sign = e.target.parentNode.className.split(' ')[0];
var val = Number.parseInt(attrs[targ], 10);
function leadIt(v) {
if (v < 10) {
return '0'+v;
}
return v;
}
if (sign === 'plus') {
if (val < 59) {
val += 1;
} else {
val = 0;
}
}
if (sign === 'minus') {
if (val > 0) {
val -= 1;
} else {
val = 59;
}
}
attrs.$set(targ, leadIt(val));
scope.$apply(function() {
scope[targ] = leadIt(val);
scope.time =
Number.parseInt(attrs['minutes'], 10) * 60 +
Number.parseInt(attrs['seconds'], 10);
});
//scope.session = 'aaaa';
//scope.$apply();
});
},
template : '<div class="minutes"><span class="plus control"><i class="fa fa-caret-up"></i></span>' +
'<span class="timer">{{minutes}}</span>' +
'<span class="minus control"><i class="fa fa-caret-down"></i></span></div>'
+
'<div class="seconds"><span class="plus control"><i class="fa fa-caret-up"></i></span>' +
'<span class="timer">{{seconds}}</span>' +
'<span class="minus control"><i class="fa fa-caret-down"></i></span></div>'
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script>
@import url(https://fonts.googleapis.com/css?family=Roboto);
body,html,.pomodoro-container {
width: 100%;
height: 100%;
background: radial-gradient(#564da3, #524da3);
font-family: 'Roboto', sans-serif;
color: #fff;
}
@import url(https://fonts.googleapis.com/css?family=Lobster);
h2,h5 {
text-shadow: 0px 0px 1px #000;
}
h2 {
font-family: 'Lobster';
}
* {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: -moz-none;
-o-user-select: none;
user-select: none;
}
.pomodoro-container {
display: flex;
flex-direction: column;
.copy {
display: flex;
align-items: flex-end;
justify-content: center;
padding: 10px 0px;
a {
color: #fff;
text-shadow: -1px 0px 2px #000;
}
}
}
.pomodoro-badge {
margin: auto;
max-width: 500px;
}
.pomodoro {
display: flex;
margin: 10px auto;
width: 400px;
height: 200px;
background-scale: cover;
position: relative;
z-index: 5;
canvas {
position: absolute;
z-index: 4;
top: -5px;
left: 82px;
}
.display {
display: flex;
position: relative;
z-index: 10;
margin: auto;
display: block;
font-size: 38px;
padding-left: 10px;
text-transform: uppercase;
font-weight: bold;
cursor: pointer;
color: #fff;
text-shadow: 0px 0px 1px #444;
.state {
font-size: 14px;
display: block;
line-height: 12px;
margin-top: 60px;
}
}
}
[range-btn] {
margin: 14px 0px;
display: inline-block;
width: 70px;
height: 30px;
background: radial-gradient(#764da3, #564da3);
position: relative;
div {
display: inline-block;
font-size: 22px;
position: relative;
.timer {
cursor: default;
text-shadow: 0px 0px 1px #333;
}
&:last-child:before {
content: ':';
margin: 0px 4px;
}
.control {
position: absolute;
display: block;
width: 5px;
height: 5px;
cursor: pointer;
opacity: 0.2;
transition: opacity .2s linear;
&:hover,
&:focus {
opacity: 1;
transition: opacity .2s linear;
}
}
.minus {
top: 20px;
left: 5px;
}
.plus {
top: -22px;
left: 5px;
}
&:last-child {
.plus, .minus {
left: 20px;
}
}
}
}
.pomodoro-img {
position: absolute;
z-index: 5;
width : 100%;
height: 100%;
background: transparent url('http://www.linuxenko.pro/showcase/freecodecamp/pomodoro.svg') center center no-repeat;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment