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;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form label {
|
||||||
|
display: block;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
a:link, a:hover, a:visited, a:active {
|
a:link, a:hover, a:visited, a:active {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timersLoaded #loader {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timersLoaded.noTimers .no-timers {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.no-timers {
|
.no-timers {
|
||||||
display: block;
|
display: none;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 1.5em;
|
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 {
|
.timers {
|
||||||
|
display: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 60px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer {
|
.timer {
|
||||||
border-bottom: 1px solid #BDBDBD;
|
border-bottom: 1px solid #BDBDBD;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
padding:2px;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,9 +208,7 @@ a:link, a:hover, a:visited, a:active {
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
position: absolute;
|
position: sticky;
|
||||||
z-index: 999;
|
|
||||||
top: 0;
|
|
||||||
height: 60px;
|
height: 60px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -200,17 +218,16 @@ header {
|
||||||
|
|
||||||
header h1 {
|
header h1 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 1.25em;
|
font-size: 1.2em;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
line-height: 60px;
|
padding: 1em;
|
||||||
padding-left: 60px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hamburger-icon {
|
.hamburger-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 20px;
|
width: 58px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
padding: 24px 20px;
|
padding: 24px 20px;
|
||||||
}
|
}
|
||||||
|
@ -236,38 +253,12 @@ header h1 {
|
||||||
color: #212121;
|
color: #212121;
|
||||||
padding: 14px 20px;
|
padding: 14px 20px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
.settings-menu {
|
height: 100%;
|
||||||
position: absolute;
|
box-sizing: border-box;
|
||||||
top: 0;
|
vertical-align: middle;
|
||||||
left: -100%;
|
cursor: pointer;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.question-circle {
|
.question-circle {
|
||||||
|
@ -280,32 +271,6 @@ header h1 {
|
||||||
margin-right: 0.5em;
|
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 {
|
button.add-timer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
|
@ -318,16 +283,17 @@ button.add-timer {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #FF5722;
|
background: #FF5722;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-menu {
|
.top-menu {
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
height: 60px;
|
height: 60px;
|
||||||
border-bottom: 1px solid #BDBDBD;
|
border-bottom: 1px solid #BDBDBD;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: 0 0 3px 0 #BDBDBD;
|
box-shadow: 0 0 3px 0 #BDBDBD;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-menu a:last-of-type {
|
.top-menu a:last-of-type {
|
||||||
|
@ -336,13 +302,23 @@ button.add-timer {
|
||||||
|
|
||||||
.new-timer {
|
.new-timer {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 100%;
|
top: 0;
|
||||||
|
transform: translateY(100vh);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
min-height: 100vh;
|
height: 100vh;
|
||||||
opacity: 0;
|
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 {
|
.new-timer.slideUp .top-menu {
|
||||||
|
@ -350,8 +326,21 @@ button.add-timer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#timer-setup {
|
#timer-setup {
|
||||||
overflow: scroll;
|
overflow-y: scroll;
|
||||||
padding-top: 62px;
|
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 {
|
#timer-setup input, #timer-setup textarea, #timer-setup select {
|
||||||
|
|
117
src/index.html
117
src/index.html
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html >
|
<html lang="en-US">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
@ -27,19 +27,18 @@
|
||||||
<meta name="msapplication-config" content="/img/browserconfig.xml">
|
<meta name="msapplication-config" content="/img/browserconfig.xml">
|
||||||
<meta name="theme-color" content="#ffeb3b">
|
<meta name="theme-color" content="#ffeb3b">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body onload="loadTimers()">
|
||||||
<header>
|
<header>
|
||||||
<a href="#" class="hamburger-icon">
|
|
||||||
<img class="button menu-icon" src="img/menu.svg" />
|
|
||||||
</a>
|
|
||||||
<h1>Interval Timer</h1>
|
<h1>Interval Timer</h1>
|
||||||
</header>
|
</header>
|
||||||
<div class="timers">
|
<div id="loader">
|
||||||
<img class="loader" src="img/spinner.png" />
|
<img class="loader" src="img/spinner.png" />
|
||||||
<div class="loader-overlay"></div>
|
<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">
|
<div class="timer animate-repeat">
|
||||||
<a href="javascript:void(0)">
|
<a href="javascript:void(0)">
|
||||||
<h2>Timer Name</h2>
|
<h2>Timer Name</h2>
|
||||||
|
@ -68,89 +67,51 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-menu-overlay" ></div>
|
<div class="new-timer">
|
||||||
<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="top-menu">
|
<div class="top-menu">
|
||||||
<a class="menu-button" href="javascript:void(0)" >✕</a>
|
<button class="menu-button" onclick="cancelEdit()" >✕</a>
|
||||||
<a class="menu-button" href="javascript:void(0)" >✓</a>
|
<button class="menu-button" onclick="saveTimer()" >✓</a>
|
||||||
</div>
|
</div>
|
||||||
<form id="timer-setup" name="timer-setup">
|
<form id="timer-setup" name="timer-setup">
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<input type="text" placeholder="Name">
|
<label for="timerName">Name:</label>
|
||||||
|
<input type="text" name="timerName">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<textarea placeholder="Description"></textarea>
|
<label for="timerDescription">Description:</label>
|
||||||
</div>
|
<textarea name="timerDescription"></textarea>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<input type="number" placeholder="Rounds">
|
<label for="warmUp">Warm-up:</label>
|
||||||
|
<input type="number" name="warmUp" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-wrapper">
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<button class="add-timer" onclick="openNewTimer()">+</button>
|
<button class="add-timer" onclick="editTimer()">+</button>
|
||||||
<script async>
|
<script async>
|
||||||
if('serviceWorker' in navigator) {
|
if('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
|
|
636
src/js/app.js
636
src/js/app.js
|
@ -1,62 +1,268 @@
|
||||||
if (!window.indexedDB) {
|
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.");
|
window.alert("Your browser doesn't support a stable version of IndexedDB. You will not be able to save your timers.");
|
||||||
}
|
}
|
||||||
|
|
||||||
(function () {
|
const defaultTimer = {
|
||||||
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,
|
"id": 0,
|
||||||
"name": "",
|
"name": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"warmUp": {
|
"warmUp": 300,
|
||||||
"min": 0,
|
"highIntensity": 60,
|
||||||
"sec": 0,
|
"lowIntensity": 30,
|
||||||
"time": 0
|
"rest": 60,
|
||||||
|
"coolDown": 300,
|
||||||
|
"rounds": 4,
|
||||||
|
"cycles": 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const Phase = {
|
||||||
|
WARM: {
|
||||||
|
name: "Warm Up",
|
||||||
|
sound: new Audio('audio/warm.mp3')
|
||||||
},
|
},
|
||||||
"highIntensity": {
|
LOW: {
|
||||||
"min": 0,
|
name: "Low Intensity",
|
||||||
"sec": 0,
|
sound: new Audio('audio/low.mp3')
|
||||||
"time": 0
|
|
||||||
},
|
},
|
||||||
"lowIntensity": {
|
HIGH: {
|
||||||
"min": 0,
|
name: "High Intensity",
|
||||||
"sec": 0,
|
sound: new Audio('audio/high.mp3')
|
||||||
"time": 0
|
|
||||||
},
|
},
|
||||||
"coolDown": {
|
REST: {
|
||||||
"min": 0,
|
name: "Rest",
|
||||||
"sec": 0,
|
sound: new Audio('audio/rest.mp3')
|
||||||
"time": 0
|
|
||||||
},
|
},
|
||||||
"rest": {
|
COOL: {
|
||||||
"min": 0,
|
name: "Cool Down",
|
||||||
"sec": 0,
|
sound: new Audio('audio/cool.mp3')
|
||||||
"time": 0
|
|
||||||
},
|
},
|
||||||
"rounds": 1,
|
}
|
||||||
"cycles": 1
|
|
||||||
|
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)
|
||||||
};
|
};
|
||||||
app.controller('timerCtrl', ['$scope', '$cookies', '$indexedDB', function ($scope, $cookies, $indexedDB) {
|
request.onsuccess = function (event) {
|
||||||
$scope.defaults = defaultTimer;
|
console.log("Loaded timers", event)
|
||||||
function getSavedTimer() {
|
state.timers = event.target.result;
|
||||||
var savedTimer = $cookies.getObject("timer");
|
updateState(state)
|
||||||
if (typeof savedTimer == "object") {
|
};
|
||||||
return savedTimer;
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
return $scope.defaults;
|
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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$scope.lockScreen = function () {
|
if (_state.editingTimer) {
|
||||||
if ($scope.timerActive) {
|
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 {
|
try {
|
||||||
navigator.wakeLock.request('screen').then((lock) => {
|
navigator.wakeLock.request('screen').then((lock) => {
|
||||||
wakeLock = lock;
|
wakeLock = lock;
|
||||||
|
@ -78,346 +284,4 @@ if (!window.indexedDB) {
|
||||||
console.error(`Failed to release wakelock: ${err.name}, ${err.message}`)
|
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";
|
|
||||||
|
|
||||||
for (var i = 0; i < 5; i++)
|
|
||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
||||||
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
$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);
|
|
||||||
};
|
|
||||||
$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();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
};
|
|
||||||
$scope.getTimers = function () {
|
|
||||||
return $scope.timers;
|
|
||||||
}
|
|
||||||
$scope.getNumberRange = function (number) {
|
|
||||||
range = [];
|
|
||||||
for (var i = 0; i <= number; i++) {
|
|
||||||
range.push(i);
|
|
||||||
}
|
|
||||||
return range
|
|
||||||
}
|
|
||||||
$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