Skip to content

Instantly share code, notes, and snippets.

@rebolyte
Last active April 12, 2019 01:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rebolyte/3c9c18a252684f2ce79f851b08677355 to your computer and use it in GitHub Desktop.
Save rebolyte/3c9c18a252684f2ce79f851b08677355 to your computer and use it in GitHub Desktop.
stopwatch exercise
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="description" content="Cross-timezone stopwatch">
<meta name="keywords" content="time, watch, stopwatch, clock, timer">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Stopwatch</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.5.3/css/bulma.min.css">
<style type="text/css">
[v-cloak] {
display: none;
}
html {
position: relative;
min-height: 100%;
}
body {
/* margin bottom by footer height */
margin-bottom: 9rem;
}
.link {
text-decoration: underline;
cursor: pointer;
}
.strike {
text-decoration: line-through;
}
.sticky-footer {
position: absolute;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<nav class="nav has-shadow">
<div class="container">
<div class="nav-left">
<span class="nav-item"><strong>STPWTCH</strong></span>
<a class="nav-item is-tab" active-class="is-active">Home</a>
<a class="nav-item is-tab" active-class="is-active">Features</a>
</div>
<div class="nav-right">
<a class="nav-item is-tab">Account</a>
<a class="nav-item is-tab">Log In</a>
</div>
</div>
</nav>
<section class="section">
<div class="container">
<h1 class="title">Stopwatch</h1>
<h2 class="subtitle">Interact with the app and feel your productivity rise</h2>
<div id="app" class="columns" v-cloak>
<div class="column is-two-thirds">
<div v-if="!geoSupported" class="has-text-danger">Uh-oh! Geolocation is not supported by your browser!</div>
<table class="table">
<thead>
<tr>
<th>Start Time</th>
<th>Start Lat/Long</th>
<th>End Time</th>
<th>End Lat/Long</th>
<th>Elapsed</th>
</tr>
</thead>
<tbody>
<tr is="app-entry-display" v-for="entry in timeEntries" :entry="entry"></tr>
</tbody>
</table>
</div>
<div class="column">
<button type="button" class="button is-info" @click="toggleTimer">{{ timerRunning ? 'Stop' : 'Start' }}</button>
</div>
</div>
</div>
</section>
<footer class="footer sticky-footer">
<div class="container">
<div class="content has-text-centered">
<p><strong>Stopwatch example</strong>, built in 1.25 hours, &copy; rebolyte 2017</p>
</div>
</div>
</footer>
<script>
// thanks @davidgilbertson
var scripts = [
'https://unpkg.com/vue@2.4.4/dist/vue.js',
// 'https://unpkg.com/axios@0.16.1/dist/axios.min.js',
'https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js',
'main.js'
];
var newBrowser = (
'Promise' in window &&
'assign' in Object
);
if (!newBrowser) {
scripts.unshift(
'https://unpkg.com/es6-object-assign@1.1.0/dist/object-assign.min.js',
'https://unpkg.com/native-promise-only@0.8.1/lib/npo.src.js'
)
}
scripts.forEach(function (src) {
var scriptEl = document.createElement('script');
scriptEl.src = src;
// https://www.html5rocks.com/en/tutorials/speed/script-loading/
scriptEl.async = false;
document.head.appendChild(scriptEl);
});
</script>
</body>
</html>
/* global _, Vue, console */
(function () {
'use strict';
// --- UTILS ----------------------------
const uniqueIdRandom = () =>
Math.random()
.toString(36)
.substr(2);
function entryFactory(startLoc) {
return {
id: uniqueIdRandom(),
startTime: moment().format(),
startLoc,
endTime: null,
endLoc: {
lat: null,
lng: null
}
};
}
function getGeo() {
return new Promise((resolve, reject) => {
function geoSuccess(position) {
resolve({ lat: position.coords.latitude, lng: position.coords.longitude });
}
function geoError() {
reject("Geolocation error");
}
const geoOptions = {
enableHighAccuracy: true,
maximumAge: 30000,
timeout: 27000
};
navigator.geolocation.getCurrentPosition(geoSuccess, geoError, geoOptions);
})
}
// --- COMPONENTS -----------------------
let EntryDisplay = {
props: ['entry'],
template: `
<tr>
<td>{{ entry.startTime | date }}</td>
<td>{{ entry.startLoc.lat }} / {{ entry.startLoc.lng }}</td>
<td><span v-if="entry.endTime">{{ entry.endTime | date }}</span></td>
<td><span v-if="entry.endLoc.lat && entry.startLoc.lng">{{ entry.endLoc.lat }} / {{ entry.endLoc.lng }}</span></td>
<td>{{ entry.endTime && diff }}</td>
</tr>
`,
computed: {
diff() {
// https://stackoverflow.com/a/30338449/2486583
let s = moment.parseZone(this.entry.startTime);
let e = moment.parseZone(this.entry.endTime);
return moment.utc(moment.duration(e - s).as('milliseconds')).format('HH:mm:ss')
}
},
filters: {
date(v) {
return moment.parseZone(v).format('LLL Z');
}
}
};
// --- MAIN COMPONENT -----------------------
let app = new Vue({
el: '#app',
data: {
timeEntries: [],
timerRunning: false,
geoSupported: true
},
created() {
if (!navigator.geolocation){
this.geoSupported = false;
}
// check localStorage to see if we have an incomplete time entry
let entries = JSON.parse(localStorage.getItem('timeEntries'));
this.timeEntries = _.sortBy(entries, entry => moment(entry.startTime).unix());
this.timerRunning = !_.isEmpty(this.timeEntries) && _.isNull(_.last(this.timeEntries).endTime);
},
components: {
'app-entry-display': EntryDisplay
},
methods: {
toggleTimer() {
getGeo().then(resp => {
if (this.timerRunning) {
let last = _.last(this.timeEntries);
last.endTime = moment().format();
last.endLoc = resp;
this.timerRunning = false;
} else {
this.timeEntries.push(entryFactory(resp));
this.timerRunning = true;
}
// console.log('toggle timer', JSON.stringify(this.timeEntries));
localStorage.setItem('timeEntries', JSON.stringify(this.timeEntries));
}).catch(err => {
console.error(err);
});
}
}
});
}());
@rebolyte
Copy link
Author

rebolyte commented Oct 16, 2017

@rebolyte
Copy link
Author

Original exercise prompt:

Professor Higginbotham has challenged you to a race around the world in a steam-powered zeppelin of your own invention. In order to verify your time at each leg of the race, you will need to create a stopwatch web application consisting of a start/stop button and a history table. Each time you start the stopwatch, the application inserts a new row into the history table that records the start time (including the timezone where you started the timer), and the current latitude and longitude. When you stop the stopwatch, the application will record the time, timezone, latitude, and longitude, as well as the amount of time that has elapsed.

Your wi-fi will be spotty in the zeppelin, so you will need to ensure the history table is viewable offline as you marvel at your record-setting speed. You can use HTML5 geolocation and localStorage to help in your task. Good luck!

Helpful links to get you started

  • Using geolocation
  • Using the Web Storage API

Stretch Goal

Add a reset button that clears the time entry history table.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment