WIP: Re-implement app with vanilla JS & HTML
Signed-off-by: William Brawner <me@wbrawner.com>
This commit is contained in:
parent
ac154ed38f
commit
d7254da5a6
3 changed files with 380 additions and 566 deletions
|
@ -17,27 +17,47 @@ body {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
form label {
|
||||
display: block;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
a:link, a:hover, a:visited, a:active {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.timersLoaded #loader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timersLoaded.noTimers .no-timers {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.no-timers {
|
||||
display: block;
|
||||
display: none;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
font-size: 1.5em;
|
||||
height: calc(100% - 60px);
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.timersLoaded:not(.noTimers) .timers {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.timers {
|
||||
display: none;
|
||||
position: relative;
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.timer {
|
||||
border-bottom: 1px solid #BDBDBD;
|
||||
max-height: 200px;
|
||||
padding:2px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
@ -188,9 +208,7 @@ a:link, a:hover, a:visited, a:active {
|
|||
}
|
||||
|
||||
header {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
top: 0;
|
||||
position: sticky;
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
@ -200,17 +218,16 @@ header {
|
|||
|
||||
header h1 {
|
||||
font-weight: normal;
|
||||
font-size: 1.25em;
|
||||
font-size: 1.2em;
|
||||
text-align: left;
|
||||
line-height: 60px;
|
||||
padding-left: 60px;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.hamburger-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
width: 58px;
|
||||
height: 12px;
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
@ -236,38 +253,12 @@ header h1 {
|
|||
color: #212121;
|
||||
padding: 14px 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.settings-menu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
color: inherit;
|
||||
background-color: #fff;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow-y: scroll;
|
||||
max-width: 320px;
|
||||
z-index: 9999;
|
||||
|
||||
}
|
||||
|
||||
.settings-menu-overlay {
|
||||
position: fixed;
|
||||
background-color: rgb(0, 0, 0);
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: block;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
transition: opacity 0.25s linear;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.settings-menu img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.question-circle {
|
||||
|
@ -280,32 +271,6 @@ header h1 {
|
|||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.settings-menu .fixed.bottom {
|
||||
padding: 15px;
|
||||
border-top: 1px solid#BDBDBD;
|
||||
width: 100%;
|
||||
max-width: 290px;
|
||||
}
|
||||
|
||||
.settings-menu a {
|
||||
color: #212121;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.settings-menu a i {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
button.add-timer {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
|
@ -318,16 +283,17 @@ button.add-timer {
|
|||
font-size: 2em;
|
||||
color: #fff;
|
||||
background: #FF5722;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.top-menu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 60px;
|
||||
border-bottom: 1px solid #BDBDBD;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 3px 0 #BDBDBD;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-menu a:last-of-type {
|
||||
|
@ -336,13 +302,23 @@ button.add-timer {
|
|||
|
||||
.new-timer {
|
||||
position: fixed;
|
||||
top: 100%;
|
||||
top: 0;
|
||||
transform: translateY(100vh);
|
||||
padding: 0;
|
||||
width: 100vw;
|
||||
z-index: 999;
|
||||
background: #fff;
|
||||
min-height: 100vh;
|
||||
opacity: 0;
|
||||
height: 100vh;
|
||||
transition: transform cubic-bezier(0.22, 0.61, 0.36, 1) 0.25s;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editing .new-timer {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.new-timer.slideUp .top-menu {
|
||||
|
@ -350,8 +326,21 @@ button.add-timer {
|
|||
}
|
||||
|
||||
#timer-setup {
|
||||
overflow: scroll;
|
||||
padding-top: 62px;
|
||||
overflow-y: scroll;
|
||||
max-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
@media all and (min-width: 1024px) {
|
||||
.new-timer {
|
||||
max-width: 800px;
|
||||
max-height: 600px;
|
||||
box-shadow: 0px 0px 3px #bdbdbd;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#timer-setup {
|
||||
max-height: 540px;
|
||||
}
|
||||
}
|
||||
|
||||
#timer-setup input, #timer-setup textarea, #timer-setup select {
|
||||
|
|
119
src/index.html
119
src/index.html
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html >
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
@ -27,19 +27,18 @@
|
|||
<meta name="msapplication-config" content="/img/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffeb3b">
|
||||
</head>
|
||||
<body>
|
||||
<body onload="loadTimers()">
|
||||
<header>
|
||||
<a href="#" class="hamburger-icon">
|
||||
<img class="button menu-icon" src="img/menu.svg" />
|
||||
</a>
|
||||
<h1>Interval Timer</h1>
|
||||
</header>
|
||||
<div class="timers">
|
||||
<div id="loader">
|
||||
<img class="loader" src="img/spinner.png" />
|
||||
<div class="loader-overlay"></div>
|
||||
<div class="no-timers">
|
||||
<h3>It looks like you haven't configured any timers yet. Click on the orange "+" icon below to get started!</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="no-timers">
|
||||
<p style="max-width: 600px;">It looks like you haven't configured any timers yet. Click on the orange "+" icon below to get started!</p>
|
||||
</div>
|
||||
<div class="timers">
|
||||
<div class="timer animate-repeat">
|
||||
<a href="javascript:void(0)">
|
||||
<h2>Timer Name</h2>
|
||||
|
@ -68,89 +67,51 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-menu-overlay" ></div>
|
||||
<div class="settings-menu">
|
||||
<img src="./img/menu-logo.png" />
|
||||
<div class="fixed bottom">
|
||||
<a href="https://wbrawner.com/contact/" target="_blank"><span class="question-circle">?</span>Help & feedback</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="new-timer" >
|
||||
<div class="new-timer">
|
||||
<div class="top-menu">
|
||||
<a class="menu-button" href="javascript:void(0)" >✕</a>
|
||||
<a class="menu-button" href="javascript:void(0)" >✓</a>
|
||||
<button class="menu-button" onclick="cancelEdit()" >✕</a>
|
||||
<button class="menu-button" onclick="saveTimer()" >✓</a>
|
||||
</div>
|
||||
<form id="timer-setup" name="timer-setup">
|
||||
<div class="input-wrapper">
|
||||
<input type="text" placeholder="Name">
|
||||
<label for="timerName">Name:</label>
|
||||
<input type="text" name="timerName">
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<textarea placeholder="Description"></textarea>
|
||||
</div>
|
||||
<div class="select-wrapper">
|
||||
<select class="min" >
|
||||
<option value="" disabled selected>Warm-up Min</option>
|
||||
</select>
|
||||
<span style="width: 2%;">
|
||||
:
|
||||
</span>
|
||||
<select class="sec" >
|
||||
<option value="" disabled selected>Warm-up Sec</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select-wrapper">
|
||||
<select class="min" >
|
||||
<option value="" disabled selected>Low-intensity Min</option>
|
||||
</select>
|
||||
<span style="width: 2%;">
|
||||
:
|
||||
</span>
|
||||
<select class="sec" >
|
||||
<option value="" disabled selected>Low-intensity Sec</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select-wrapper">
|
||||
<select class="min" >
|
||||
<option value="" disabled selected>High-intensity Min</option>
|
||||
</select>
|
||||
<span style="width: 2%;">
|
||||
:
|
||||
</span>
|
||||
<select class="sec" >
|
||||
<option value="" disabled selected>High-intensity Sec</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select-wrapper">
|
||||
<select class="min" >
|
||||
<option value="" disabled selected>Rest Min</option>
|
||||
</select>
|
||||
<span style="width: 2%;">
|
||||
:
|
||||
</span>
|
||||
<select class="sec" >
|
||||
<option value="" disabled selected>Rest Sec</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select-wrapper">
|
||||
<select class="min" >
|
||||
<option value="" disabled selected>CoolDown Min</option>
|
||||
</select>
|
||||
<span style="width: 2%;">
|
||||
:
|
||||
</span>
|
||||
<select class="sec" >
|
||||
<option value="" disabled selected>CoolDown Sec</option>
|
||||
</select>
|
||||
<label for="timerDescription">Description:</label>
|
||||
<textarea name="timerDescription"></textarea>
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<input type="number" placeholder="Rounds">
|
||||
<label for="warmUp">Warm-up:</label>
|
||||
<input type="number" name="warmUp" />
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<input type="number" placeholder="Cycles">
|
||||
<label for="low">Low-intensity:</label>
|
||||
<input type="number" name="low" />
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<label for="high">High-intensity:</label>
|
||||
<input type="number" name="high" />
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<label for="rest">Rest:</label>
|
||||
<input type="number" name="rest" />
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<label for="cool">Cooldown:</label>
|
||||
<input type="number" name="cool" />
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<label for="sets">Sets:</label>
|
||||
<input type="number" name="sets">
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<label for="rounds">Rounds:</label>
|
||||
<input type="number" name="rounds">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<button class="add-timer" onclick="openNewTimer()">+</button>
|
||||
<button class="add-timer" onclick="editTimer()">+</button>
|
||||
<script async>
|
||||
if('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker
|
||||
|
|
688
src/js/app.js
688
src/js/app.js
|
@ -1,423 +1,287 @@
|
|||
if (!window.indexedDB) {
|
||||
// TODO: Show this as an inline alert instead
|
||||
window.alert("Your browser doesn't support a stable version of IndexedDB. You will not be able to save your timers.");
|
||||
}
|
||||
|
||||
(function () {
|
||||
let wakeLock = null;
|
||||
var app = angular.module('interval-timer', ['indexedDB', 'itemSwipe', 'ngAnimate', 'ngCookies', 'ngTouch'])
|
||||
.config(function ($indexedDBProvider) {
|
||||
$indexedDBProvider
|
||||
.connection('interval-timer')
|
||||
.upgradeDatabase(1, function (event, db, tx) {
|
||||
var objStore = db.createObjectStore('timers', { keyPath: 'id' });
|
||||
objStore.createIndex('timer_obj', 'timer', { unique: false });
|
||||
})
|
||||
});
|
||||
var defaultTimer = {
|
||||
"id": 0,
|
||||
"name": "",
|
||||
"description": "",
|
||||
"warmUp": {
|
||||
"min": 0,
|
||||
"sec": 0,
|
||||
"time": 0
|
||||
},
|
||||
"highIntensity": {
|
||||
"min": 0,
|
||||
"sec": 0,
|
||||
"time": 0
|
||||
},
|
||||
"lowIntensity": {
|
||||
"min": 0,
|
||||
"sec": 0,
|
||||
"time": 0
|
||||
},
|
||||
"coolDown": {
|
||||
"min": 0,
|
||||
"sec": 0,
|
||||
"time": 0
|
||||
},
|
||||
"rest": {
|
||||
"min": 0,
|
||||
"sec": 0,
|
||||
"time": 0
|
||||
},
|
||||
"rounds": 1,
|
||||
"cycles": 1
|
||||
};
|
||||
app.controller('timerCtrl', ['$scope', '$cookies', '$indexedDB', function ($scope, $cookies, $indexedDB) {
|
||||
$scope.defaults = defaultTimer;
|
||||
function getSavedTimer() {
|
||||
var savedTimer = $cookies.getObject("timer");
|
||||
if (typeof savedTimer == "object") {
|
||||
return savedTimer;
|
||||
} else {
|
||||
return $scope.defaults;
|
||||
}
|
||||
}
|
||||
$scope.lockScreen = function () {
|
||||
if ($scope.timerActive) {
|
||||
try {
|
||||
navigator.wakeLock.request('screen').then((lock) => {
|
||||
wakeLock = lock;
|
||||
wakeLock.addEventListener('release', () => {
|
||||
console.log('Screen Wake Lock was released');
|
||||
});
|
||||
console.log('Screen Wake Lock is active');
|
||||
}).catch((err) => {
|
||||
console.error(`Failed to aquire wakelock: ${err.name}, ${err.message}`);
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(`Failed to aquire wakelock: ${err.name}, ${err.message}`);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
wakeLock.release();
|
||||
wakeLock = null;
|
||||
} catch (err) {
|
||||
console.error(`Failed to release wakelock: ${err.name}, ${err.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
$scope.timers = {}
|
||||
$scope.periods = [
|
||||
"warmUp",
|
||||
"lowIntensity",
|
||||
"highIntensity",
|
||||
"rest",
|
||||
"coolDown"
|
||||
];
|
||||
$scope.timersLoaded = false;
|
||||
$scope.noTimers = false;
|
||||
$scope.setTimers = function () {
|
||||
$indexedDB.openStore('timers', function (timers) {
|
||||
timers.getAll()
|
||||
.then(function (data) {
|
||||
$scope.timers = data;
|
||||
if (data.length == 0) {
|
||||
$scope.noTimers = false;
|
||||
} else {
|
||||
$scope.noTimers = true;
|
||||
}
|
||||
$scope.timersLoaded = true;
|
||||
})
|
||||
})
|
||||
}
|
||||
$scope.setTimers();
|
||||
$scope.timer = {};
|
||||
$scope.config = {};
|
||||
$scope.initObj = new Promise(function (res, rej) {
|
||||
$scope.periods.forEach(function (period) {
|
||||
$scope.config[period] = {};
|
||||
$scope[period] = {};
|
||||
$scope.timer[period] = {};
|
||||
})
|
||||
res($scope.config);
|
||||
});
|
||||
$scope.initObj.then(function () {
|
||||
$scope.config.warmUp.beep = new Audio('audio/warm.mp3');
|
||||
$scope.config.lowIntensity.beep = new Audio('audio/low.mp3');
|
||||
$scope.config.highIntensity.beep = new Audio('audio/high.mp3');
|
||||
$scope.config.rest.beep = new Audio('audio/rest.mp3');
|
||||
$scope.config.coolDown.beep = new Audio('audio/cool.mp3');
|
||||
$scope.warmUp.active = true;
|
||||
})
|
||||
$scope.round = 1;
|
||||
$scope.cycle = 1;
|
||||
$scope.settingsOpen = false;
|
||||
$scope.closeSettings = false;
|
||||
$scope.newTimerOpen = false;
|
||||
$scope.newTimerClosed = false;
|
||||
$scope.showTimerInterface = false;
|
||||
$scope.setPeriod = function (period, playBeep) {
|
||||
if (typeof playBeep == "undefined") {
|
||||
playBeep = true;
|
||||
}
|
||||
clearInterval($scope.countdown);
|
||||
$scope[period].active = true;
|
||||
$scope.time = $scope.timer[period].time;
|
||||
if (playBeep) {
|
||||
if ($scope.config[period].beep) {
|
||||
$scope.config[period].beep.play();
|
||||
}
|
||||
}
|
||||
$scope.startTimer();
|
||||
}
|
||||
$scope.setWarmUp = function () {
|
||||
$scope.setPeriod('warmUp');
|
||||
}
|
||||
$scope.setCoolDown = function () {
|
||||
clearInterval($scope.countdown);
|
||||
$scope.coolDown.active = true;
|
||||
$scope.time = $scope.timer.coolDown.time;
|
||||
$scope.coolDownBeep.play();
|
||||
$scope.startTimer();
|
||||
}
|
||||
$scope.setLowIntensity = function () {
|
||||
clearInterval($scope.countdown);
|
||||
$scope.lowIntensity.active = true;
|
||||
$scope.time = $scope.timer.lowIntensity.time;
|
||||
$scope.lowIntensityBeep.play();
|
||||
$scope.startTimer();
|
||||
}
|
||||
$scope.setHighIntensity = function () {
|
||||
clearInterval($scope.countdown);
|
||||
$scope.highIntensity.active = true;
|
||||
$scope.time = $scope.timer.highIntensity.time;
|
||||
$scope.highIntensityBeep.play();
|
||||
$scope.startTimer();
|
||||
}
|
||||
$scope.startWarmUp = function () {
|
||||
if ($scope.time === 0) {
|
||||
$scope.warmUp.active = false;
|
||||
$scope.setPeriod('lowIntensity');
|
||||
}
|
||||
$scope.$apply(function () {
|
||||
$scope.time--;
|
||||
});
|
||||
};
|
||||
$scope.startCoolDown = function () {
|
||||
if ($scope.time === 0) {
|
||||
$scope.coolDown.active = false;
|
||||
$scope.resetTimer();
|
||||
}
|
||||
$scope.$apply(function () {
|
||||
$scope.time--;
|
||||
});
|
||||
};
|
||||
$scope.startRest = function () {
|
||||
if ($scope.time === 0) {
|
||||
$scope.rest.active = false;
|
||||
$scope.setPeriod('lowIntensity');
|
||||
}
|
||||
$scope.$apply(function () {
|
||||
$scope.time--;
|
||||
});
|
||||
};
|
||||
$scope.startLowIntensity = function () {
|
||||
if ($scope.time === 0) {
|
||||
$scope.lowIntensity.active = false;
|
||||
$scope.setPeriod('highIntensity');
|
||||
}
|
||||
$scope.$apply(function () {
|
||||
$scope.time--;
|
||||
});
|
||||
};
|
||||
$scope.startHighIntensity = function () {
|
||||
if ($scope.time === 0) {
|
||||
$scope.highIntensity.active = false;
|
||||
if ($scope.round == $scope.timer.rounds && $scope.cycle == $scope.timer.cycles) {
|
||||
$scope.setPeriod('coolDown');
|
||||
} else if ($scope.round == $scope.timer.rounds && $scope.cycle < $scope.timer.cycles) {
|
||||
$scope.cycle++;
|
||||
$scope.round = 1;
|
||||
$scope.setPeriod('rest');
|
||||
} else {
|
||||
$scope.setPeriod('lowIntensity');
|
||||
$scope.round++;
|
||||
}
|
||||
}
|
||||
$scope.$apply(function () {
|
||||
$scope.time--;
|
||||
});
|
||||
};
|
||||
$scope.getTimes = function () {
|
||||
$scope.periods.forEach(function (period) {
|
||||
var min = 0;
|
||||
if (typeof $scope.timer[period].min == "number" && $scope.timer[period].min > 0) {
|
||||
min = $scope.timer[period].min * 60;
|
||||
}
|
||||
var sec = $scope.timer[period].sec;
|
||||
$scope.timer[period].time = min + sec;
|
||||
})
|
||||
};
|
||||
$scope.startTimer = function () {
|
||||
$scope.timerActive = true;
|
||||
$scope.lockScreen();
|
||||
if ($scope.warmUp.active) {
|
||||
$scope.countdown = setInterval($scope.startWarmUp, 1000);
|
||||
}
|
||||
if ($scope.coolDown.active) {
|
||||
$scope.countdown = setInterval($scope.startCoolDown, 1000);
|
||||
}
|
||||
if ($scope.rest.active) {
|
||||
$scope.countdown = setInterval($scope.startRest, 1000);
|
||||
}
|
||||
if ($scope.highIntensity.active) {
|
||||
$scope.countdown = setInterval($scope.startHighIntensity, 1000);
|
||||
}
|
||||
if ($scope.lowIntensity.active) {
|
||||
$scope.countdown = setInterval($scope.startLowIntensity, 1000);
|
||||
}
|
||||
};
|
||||
$scope.pauseTimer = function () {
|
||||
$scope.timerActive = false;
|
||||
$scope.lockScreen();
|
||||
clearInterval($scope.countdown);
|
||||
};
|
||||
$scope.stepBack = function () {
|
||||
if ($scope.warmUp.active) {
|
||||
$scope.resetTimer();
|
||||
return;
|
||||
};
|
||||
if ($scope.lowIntensity.active) {
|
||||
$scope.lowIntensity.active = false;
|
||||
if ($scope.round == 1 && $scope.cycle == 1) {
|
||||
$scope.setPeriod('warmUp');
|
||||
return;
|
||||
}
|
||||
if ($scope.round == 1 && $scope.cycle > 1) {
|
||||
$scope.setPeriod('rest');
|
||||
return;
|
||||
}
|
||||
$scope.setPeriod('highIntensity');
|
||||
$scope.round--;
|
||||
return;
|
||||
};
|
||||
if ($scope.highIntensity.active) {
|
||||
$scope.highIntensity.active = false;
|
||||
$scope.setPeriod('lowIntensity');
|
||||
return;
|
||||
};
|
||||
if ($scope.rest.active) {
|
||||
$scope.rest.active = false;
|
||||
$scope.cycle--;
|
||||
$scope.round = $scope.timer.rounds;
|
||||
$scope.setPeriod('highIntensity');
|
||||
return;
|
||||
}
|
||||
if ($scope.coolDown.active) {
|
||||
$scope.coolDown.active = false;
|
||||
$scope.setPeriod('highIntensity');
|
||||
return;
|
||||
}
|
||||
};
|
||||
$scope.stepForward = function () {
|
||||
if ($scope.warmUp.active) {
|
||||
$scope.warmUp.active = false;
|
||||
$scope.setPeriod('lowIntensity');
|
||||
return;
|
||||
}
|
||||
if ($scope.rest.active) {
|
||||
$scope.rest.active = false;
|
||||
$scope.setPeriod('lowIntensity');
|
||||
return;
|
||||
};
|
||||
if ($scope.lowIntensity.active) {
|
||||
$scope.lowIntensity.active = false;
|
||||
$scope.setPeriod('highIntensity');
|
||||
return;
|
||||
};
|
||||
if ($scope.highIntensity.active) {
|
||||
$scope.highIntensity.active = false;
|
||||
if ($scope.round == $scope.timer.rounds && $scope.cycle == $scope.timer.cycles) {
|
||||
$scope.setPeriod('coolDown');
|
||||
return;
|
||||
} else if ($scope.round == $scope.timer.rounds && $scope.cycle < $scope.timer.cycles) {
|
||||
$scope.cycle++;
|
||||
$scope.round = 1;
|
||||
$scope.setPeriod('rest');
|
||||
return;
|
||||
}
|
||||
$scope.setPeriod('lowIntensity');
|
||||
$scope.round++;
|
||||
return;
|
||||
};
|
||||
};
|
||||
$scope.resetTimer = function () {
|
||||
clearInterval($scope.countdown);
|
||||
$scope.timerActive = false;
|
||||
$scope.lockScreen();
|
||||
$scope.round = 1;
|
||||
$scope.cycle = 1;
|
||||
$scope.lowIntensity.active = false;
|
||||
$scope.highIntensity.active = false;
|
||||
$scope.coolDown.active = false;
|
||||
$scope.warmUp.active = true;
|
||||
$scope.time = $scope.timer.warmUp.time;
|
||||
};
|
||||
$scope.makeId = function () {
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const defaultTimer = {
|
||||
"id": 0,
|
||||
"name": "",
|
||||
"description": "",
|
||||
"warmUp": 300,
|
||||
"highIntensity": 60,
|
||||
"lowIntensity": 30,
|
||||
"rest": 60,
|
||||
"coolDown": 300,
|
||||
"rounds": 4,
|
||||
"cycles": 2
|
||||
}
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
const Phase = {
|
||||
WARM: {
|
||||
name: "Warm Up",
|
||||
sound: new Audio('audio/warm.mp3')
|
||||
},
|
||||
LOW: {
|
||||
name: "Low Intensity",
|
||||
sound: new Audio('audio/low.mp3')
|
||||
},
|
||||
HIGH: {
|
||||
name: "High Intensity",
|
||||
sound: new Audio('audio/high.mp3')
|
||||
},
|
||||
REST: {
|
||||
name: "Rest",
|
||||
sound: new Audio('audio/rest.mp3')
|
||||
},
|
||||
COOL: {
|
||||
name: "Cool Down",
|
||||
sound: new Audio('audio/cool.mp3')
|
||||
},
|
||||
}
|
||||
|
||||
return text;
|
||||
let _state = {
|
||||
timers: null,
|
||||
timer: null,
|
||||
timerJob: null,
|
||||
timerComplete: false,
|
||||
set: 1,
|
||||
round: 1,
|
||||
phase: Phase.WARM,
|
||||
timeRemaining: 0,
|
||||
editingTimer: false,
|
||||
};
|
||||
|
||||
let db = null;
|
||||
let timerStore = null;
|
||||
|
||||
function loadTimers() {
|
||||
let state = copyState();
|
||||
const dbRequest = window.indexedDB.open('interval-timer', 1);
|
||||
dbRequest.onerror = (event) => {
|
||||
console.error(`Failed to open IndexedDB`, event)
|
||||
}
|
||||
|
||||
dbRequest.onsuccess = (event) => {
|
||||
db = event.target.result;
|
||||
let transaction = db.transaction(["timers"]);
|
||||
let objectStore = transaction.objectStore("timers");
|
||||
const request = objectStore.getAll();
|
||||
request.onerror = function (event) {
|
||||
console.error('Failed to load timers', event)
|
||||
};
|
||||
$scope.openTimer = function (id) {
|
||||
$indexedDB.openStore('timers', function (timers) {
|
||||
timers.find(id)
|
||||
.then(function (data) {
|
||||
$scope.timer = data;
|
||||
$scope.getTimes();
|
||||
$scope.resetTimer();
|
||||
$scope.showTimerInterface = true;
|
||||
})
|
||||
})
|
||||
}
|
||||
$scope.closeTimer = function () {
|
||||
$scope.showTimerInterface = false;
|
||||
}
|
||||
$scope.deleteTimer = function (item) {
|
||||
item.removed = true;
|
||||
setTimeout(function () {
|
||||
$indexedDB.openStore('timers', function (timers) {
|
||||
var result = timers.delete(item.id)
|
||||
.then(function (e) {
|
||||
$scope.setTimers();
|
||||
})
|
||||
})
|
||||
}, 500);
|
||||
request.onsuccess = function (event) {
|
||||
console.log("Loaded timers", event)
|
||||
state.timers = event.target.result;
|
||||
updateState(state)
|
||||
};
|
||||
$scope.saveTimer = function () {
|
||||
$scope.newTimer.id = $scope.makeId();
|
||||
$indexedDB.openStore('timers', function (timers) {
|
||||
var result = timers.insert($scope.newTimer)
|
||||
.then(function (e) {
|
||||
$scope.newTimer = {};
|
||||
$scope.setTimers();
|
||||
})
|
||||
}
|
||||
|
||||
dbRequest.onupgradeneeded = (event) => {
|
||||
db = event.target.result;
|
||||
timerStore = db.createObjectStore('timers', { keyPath: 'id' });
|
||||
}
|
||||
}
|
||||
|
||||
function saveTimer() {
|
||||
// let transaction = db.transaction(["timers"], "readwrite");
|
||||
// transaction.oncomplete
|
||||
let state = copyState();
|
||||
if (!state.timers) {
|
||||
state.timers = [];
|
||||
}
|
||||
// TODO: get values from fields and then clear them here
|
||||
state.timers.push(defaultTimer)
|
||||
updateState({
|
||||
...state,
|
||||
editingTimer: false,
|
||||
});
|
||||
}
|
||||
|
||||
function deleteTimer() {
|
||||
// var request = db.transaction(["customers"], "readwrite")
|
||||
// .objectStore("customers")
|
||||
// .delete("444-44-4444");
|
||||
// request.onsuccess = function(event) {
|
||||
// It's gone!
|
||||
// };
|
||||
}
|
||||
|
||||
function openTimer() {
|
||||
|
||||
}
|
||||
|
||||
function editTimer() {
|
||||
updateState({
|
||||
editingTimer: true
|
||||
})
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
updateState({
|
||||
editingTimer: false
|
||||
})
|
||||
}
|
||||
|
||||
function toggleTimer() {
|
||||
let state = copyState();
|
||||
if (state.timerJob != null) {
|
||||
clearInterval(state.timerJob)
|
||||
state.timerJob = null
|
||||
} else {
|
||||
state.timerJob = setInterval(timerLoop, 1000);
|
||||
}
|
||||
updateState(state);
|
||||
lockScreen();
|
||||
}
|
||||
|
||||
function timerLoop() {
|
||||
let state = copyState();
|
||||
state.timeRemaining -= 1;
|
||||
if (state.timeRemaining <= 0) {
|
||||
goForward();
|
||||
}
|
||||
updateState(state);
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
let state = copyState();
|
||||
if (!state.timer) return;
|
||||
switch (state.phase) {
|
||||
case Phase.WARM:
|
||||
state.timeRemaining = state.timer.warmUp;
|
||||
break;
|
||||
case Phase.LOW:
|
||||
if (state.set == state.timer.sets && state.round == state.timer.rounds) {
|
||||
state.phase = Phase.WARM;
|
||||
state.timeRemaining = state.timer.warmUp;
|
||||
} else if (state.set == state.timer.sets && state.round < state.timer.rounds) {
|
||||
state.phase = Phase.REST;
|
||||
state.timeRemaining = state.timer.rest;
|
||||
} else {
|
||||
state.set += 1;
|
||||
state.phase = Phase.HIGH;
|
||||
state.timeRemaining = state.timer.highIntensity;
|
||||
}
|
||||
break;
|
||||
case Phase.HIGH:
|
||||
state.phase = Phase.LOW;
|
||||
state.timeRemaining = state.timer.lowIntensity;
|
||||
break;
|
||||
case Phase.REST:
|
||||
state.round += 1;
|
||||
state.phase = Phase.HIGH;
|
||||
state.set = state.timer.sets;
|
||||
state.timeRemaining = state.timer.highIntensity;
|
||||
break;
|
||||
case Phase.COOL:
|
||||
state.phase = Phase.HIGH;
|
||||
state.timeRemaining = state.timer.highIntensity;
|
||||
break;
|
||||
}
|
||||
updateState({
|
||||
...state,
|
||||
timerComplete: false,
|
||||
})
|
||||
}
|
||||
|
||||
function goForward() {
|
||||
let state = copyState();
|
||||
if (!state.timer) return;
|
||||
state.timerComplete = state.phase === Phase.COOL
|
||||
switch (state.phase) {
|
||||
case Phase.WARM:
|
||||
state.phase = Phase.LOW;
|
||||
state.timeRemaining = state.timer.lowIntensity;
|
||||
break;
|
||||
case Phase.LOW:
|
||||
state.phase = Phase.HIGH;
|
||||
state.timeRemaining = state.timer.highIntensity;
|
||||
break;
|
||||
case Phase.HIGH:
|
||||
if (state.set > 1) {
|
||||
state.set -= 1;
|
||||
state.phase = Phase.LOW;
|
||||
state.timeRemaining = state.timer.lowIntensity;
|
||||
} else if (state.round > 1) {
|
||||
state.round -= 1;
|
||||
state.phase = Phase.REST;
|
||||
state.timeRemaining = state.timer.rest;
|
||||
} else {
|
||||
state.phase = Phase.COOL;
|
||||
state.timeRemaining = state.timer.coolDown;
|
||||
}
|
||||
break;
|
||||
case Phase.REST:
|
||||
state.set = state.timer.sets;
|
||||
state.phase = Phase.LOW;
|
||||
state.timeRemaining = state.timer.lowIntensity;
|
||||
break;
|
||||
case Phase.COOL:
|
||||
state.timeRemaining = 0;
|
||||
break;
|
||||
}
|
||||
updateState(state);
|
||||
}
|
||||
|
||||
function copyState() {
|
||||
return JSON.parse(JSON.stringify(_state));
|
||||
}
|
||||
|
||||
const debug = false;
|
||||
|
||||
function updateState(changes) {
|
||||
if (debug) console.log("Before", _state)
|
||||
const immutableChanges = JSON.parse(JSON.stringify(changes));
|
||||
_state = {
|
||||
..._state,
|
||||
...immutableChanges
|
||||
}
|
||||
if (debug) console.log("After: ", _state)
|
||||
if (_state.timers) {
|
||||
document.body.classList.add('timersLoaded')
|
||||
if (_state.timers.length === 0) {
|
||||
document.body.classList.add('noTimers')
|
||||
} else {
|
||||
document.body.classList.remove('noTimers')
|
||||
}
|
||||
}
|
||||
if (_state.editingTimer) {
|
||||
document.body.classList.add('editing')
|
||||
} else {
|
||||
document.body.classList.remove('editing')
|
||||
}
|
||||
}
|
||||
|
||||
// The wakeLock has to be separate from the state because it doesn't get
|
||||
// transferred correctly when the state is copied
|
||||
let wakeLock = null;
|
||||
|
||||
function lockScreen() {
|
||||
let state = copyState();
|
||||
if (state.timerJob) {
|
||||
if (wakeLock) {
|
||||
console.log("Ignoring request to lock screen, should already be locked");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
navigator.wakeLock.request('screen').then((lock) => {
|
||||
wakeLock = lock;
|
||||
wakeLock.addEventListener('release', () => {
|
||||
console.log('Screen Wake Lock was released');
|
||||
});
|
||||
console.log('Screen Wake Lock is active');
|
||||
}).catch((err) => {
|
||||
console.error(`Failed to aquire wakelock: ${err.name}, ${err.message}`);
|
||||
})
|
||||
};
|
||||
$scope.getTimers = function () {
|
||||
return $scope.timers;
|
||||
} catch (err) {
|
||||
console.error(`Failed to aquire wakelock: ${err.name}, ${err.message}`);
|
||||
}
|
||||
$scope.getNumberRange = function (number) {
|
||||
range = [];
|
||||
for (var i = 0; i <= number; i++) {
|
||||
range.push(i);
|
||||
}
|
||||
return range
|
||||
} else {
|
||||
try {
|
||||
wakeLock.release();
|
||||
wakeLock = null;
|
||||
} catch (err) {
|
||||
console.error(`Failed to release wakelock: ${err.name}, ${err.message}`)
|
||||
}
|
||||
$scope.openNewTimer = function () {
|
||||
$scope.newTimerClosed = false;
|
||||
$scope.newTimerOpen = true;
|
||||
}
|
||||
$scope.validateNewTimer = function (newTimer) {
|
||||
timerSkel = $scope.defaults;
|
||||
angular.merge(timerSkel, newTimer);
|
||||
if (timerSkel.rounds < 1) {
|
||||
timerSkel.rounds = 1;
|
||||
}
|
||||
if (timerSkel.cycles < 1) {
|
||||
timerSkel.cycles = 1;
|
||||
}
|
||||
return new Promise(function (res, rej) {
|
||||
res(timerSkel);
|
||||
})
|
||||
}
|
||||
$scope.saveNewTimer = function () {
|
||||
$scope.validateNewTimer($scope.newTimer)
|
||||
.then(function (newTimer) {
|
||||
$scope.newTimer = newTimer;
|
||||
$scope.saveTimer();
|
||||
$scope.closeNewTimer();
|
||||
})
|
||||
}
|
||||
$scope.closeNewTimer = function () {
|
||||
$scope.newTimerOpen = false;
|
||||
$scope.newTimerClosed = true;
|
||||
}
|
||||
}]);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue