VulpesOS/gaia/apps/clock/js/panels/stopwatch/main.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

324 lines
9.3 KiB
JavaScript
Executable file

define(function(require) {
'use strict';
/* global IntlHelper */
var Panel = require('panel');
var Stopwatch = require('stopwatch');
var Template = require('template');
var html = require('text!panels/stopwatch/panel.html');
var lapHtml = require('text!panels/stopwatch/list_item.html');
var priv = new WeakMap();
// This value is chosen such that the only reason you'll hit it is
// because you're super bored, and small enough that phones won't
// puke when displaying this many rows in the lap list.
var MAX_STOPWATCH_LAPS = 1000;
IntlHelper.define('timer-msS', 'mozduration', {
minUnit: 'millisecond',
maxUnit: 'minute'
});
IntlHelper.define('lap-number', 'number', {
style: 'decimal',
useGrouping: false
});
/**
* Stopwatch.Panel
*
* Construct a UI panel for the Stopwatch panel.
*
* @return {Stopwatch.Panel} Stopwatch.Panel object.
*
*/
Stopwatch.Panel = function(element) {
Panel.apply(this, arguments);
this.nodes = {};
this.lapTemplate = Template(lapHtml);
this.interval = null;
this.screenWakeLock = null;
// Store maxLaps as a dataset attribute for easy access in tests.
this.element.dataset.maxLaps = MAX_STOPWATCH_LAPS;
this.element.innerHTML = html;
// Gather elements
[
'start', 'pause', 'resume',
'lap', 'reset', 'time',
'lap-list', 'laps'
].forEach(function(sel) {
this.nodes[sel] = this.element.querySelector('.stopwatch-' + sel);
}, this);
// Bind click events
[
'start', 'pause', 'resume', 'lap', 'reset'
].forEach(function(action) {
var e = this.nodes[action];
if (priv.has(e)) {
priv.delete(e);
}
priv.set(e, {
action: action == 'resume' ? 'start' : action
});
e.addEventListener('click', this);
}, this);
element.addEventListener(
'panel-visibilitychange', this.onvisibilitychange.bind(this));
IntlHelper.observe('timer-msS', this.updateTimeStrings.bind(this));
this.setStopwatch(new Stopwatch());
};
Stopwatch.Panel.prototype = Object.create(Panel.prototype);
Stopwatch.Panel.prototype.updateTimeStrings = function() {
IntlHelper.get('timer-msS').then(formatter => {
var stopwatch = priv.get(this).stopwatch;
var e = stopwatch.getElapsedTime();
var time = formatter.format(e);
this.nodes.time.textContent = time;
var node = this.nodes.laps;
var laps = stopwatch.getLaps();
laps.push(stopwatch.nextLap());
var lapnodes = node.querySelectorAll('li.lap-cell');
var lapFormatter = IntlHelper.get('lap-number');
for (var i = 0; i < laps.length; i++) {
var lapnum = laps.length - 1 - i;
lapnodes[i].querySelector('.lap-duration').textContent =
formatter.format(laps[lapnum].duration);
lapnodes[i].querySelector('.lap-name').setAttribute('data-l10n-args',
JSON.stringify({ n: lapFormatter.format(lapnum + 1) }));
}
});
};
Stopwatch.Panel.prototype.update = function() {
var swp = priv.get(this);
var e = swp.stopwatch.getElapsedTime();
return IntlHelper.get('timer-msS').then(formatter => {
var time = formatter.format(e);
this.nodes.time.textContent = time;
this.nodes.time.classList.toggle(
'over-100-minutes', e >= 1000 * 60 * 100);
this.activeLap(false);
});
};
Stopwatch.Panel.prototype.showButtons = function() {
Array.prototype.forEach.call(arguments, function(a) {
this.nodes[a].classList.remove('hidden');
}, this);
};
Stopwatch.Panel.prototype.hideButtons = function() {
Array.prototype.forEach.call(arguments, function(a) {
this.nodes[a].classList.add('hidden');
}, this);
};
Stopwatch.Panel.prototype.setState = function(state) {
switch (state) {
case Stopwatch.RUNNING:
this.onstart();
break;
case Stopwatch.PAUSED:
this.onpause();
break;
case Stopwatch.RESET:
this.onreset();
break;
}
return this.update();
};
Stopwatch.Panel.prototype.setStopwatch = function(stopwatch) {
priv.set(this, {
stopwatch: stopwatch
});
//Clear any existing lap indicators and make new ones
var lapsUl = this.nodes.laps;
lapsUl.textContent = '';
var laps = stopwatch.getLaps();
for (var i = 0; i < laps.length; i++) {
this.onlap(laps[i]);
}
this.checkLapButton();
return this.setState(stopwatch.getState());
};
Stopwatch.Panel.prototype.onvisibilitychange = function(evt) {
var stopwatch = priv.get(this).stopwatch;
if (evt.detail.isVisible) {
this.setState(stopwatch.getState());
}
if (this.screenWakeLock) {
this.screenWakeLock.unlock();
this.screenWakeLock = null;
}
};
Stopwatch.Panel.prototype.checkLapButton = function() {
var swp = priv.get(this);
var maxLaps = parseInt(this.element.dataset.maxLaps, 10);
// As the Stopwatch doesn't include the current "lap", we must
// subtract one from maxLaps when deciding whether or not we can
// add a lap. Using these calculations, if maxLaps is 10, the last
// lap visible in the UI will be "Lap 10". Additionally, this
// button can only be shown if the "pause" button is also visible,
// as it must respect the state of the other buttons.
var canAddLaps = (swp.stopwatch.getLaps().length < maxLaps - 1) &&
!this.nodes.pause.classList.contains('hidden');
this.nodes.lap.classList.toggle('hidden', !canAddLaps);
};
Stopwatch.Panel.prototype.handleEvent = function(event) {
if (event.type == 'animationend') {
Panel.prototype.handleEvent.apply(this, arguments);
return;
}
var swp = priv.get(this);
var button = priv.get(event.target);
if (swp.stopwatch && swp.stopwatch[button.action]) {
try {
// call action on stopwatch
var val = swp.stopwatch[button.action]();
// call panel handler
this['on' + button.action](val);
} catch (err) {
if (err instanceof Stopwatch.MaxLapsException) {
// do nothing
} else {
throw err;
}
}
this.checkLapButton();
}
};
Stopwatch.Panel.prototype.onstart = function() {
var tickfn = (function() {
this.update();
this.tick = requestAnimationFrame(tickfn);
}).bind(this);
tickfn();
//bug#983393
var laps = priv.get(this).stopwatch.getLaps().length+1;
var maxLaps = parseInt(this.element.dataset.maxLaps, 10);
if (laps<maxLaps) {
this.showButtons('pause', 'lap');
this.hideButtons('start', 'resume', 'reset');
}
this.screenWakeLock = navigator.requestWakeLock('screen');
};
Stopwatch.Panel.prototype.onpause = function() {
cancelAnimationFrame(this.tick);
this.update();
this.nodes.reset.removeAttribute('disabled');
this.showButtons('resume', 'reset');
this.hideButtons('pause', 'start', 'lap');
if (this.screenWakeLock) {
this.screenWakeLock.unlock();
this.screenWakeLock = null;
}
};
Stopwatch.Panel.prototype.onresume = function() {
this.onstart();
};
function createLapDom(num, time) {
/* jshint validthis:true */
var li = document.createElement('li');
li.setAttribute('class', 'lap-cell');
li.innerHTML = this.lapTemplate.interpolate();
IntlHelper.get('timer-msS').then(formatter => {
li.querySelector('.lap-duration').textContent = formatter.format(time);
});
var lapFormatter = IntlHelper.get('lap-number');
document.l10n.setAttributes(
li.querySelector('.lap-name'),
'lap-number',
{ n: lapFormatter.format(num) }
);
return li;
}
function updateLapDom(num, time, li) {
IntlHelper.get('timer-msS').then(formatter => {
li.querySelector('.lap-duration').textContent = formatter.format(time);
});
var lapFormatter = IntlHelper.get('lap-number');
document.l10n.setAttributes(
li.querySelector('.lap-name'),
'lap-number',
{ n: lapFormatter.format(num) }
);
return li;
}
Stopwatch.Panel.prototype.activeLap = function(force) {
var stopwatch = priv.get(this).stopwatch;
var num = stopwatch.getLaps().length + 1;
if (num === 1 && !force) {
return;
}
var node = this.nodes.laps;
var lapnodes = node.querySelectorAll('li.lap-cell');
var time = stopwatch.nextLap().duration;
if (lapnodes.length === 0) {
node.appendChild(createLapDom.call(this, num, time));
} else {
updateLapDom.call(this, num, time, lapnodes[0]);
}
};
Stopwatch.Panel.prototype.onlap = function(val) {
var stopwatch = priv.get(this).stopwatch;
var node = this.nodes.laps;
var laps = stopwatch.getLaps();
var num = laps.length;
this.activeLap(true);
var li = createLapDom.call(this, num, val ? val.duration : 0);
if (laps.length > 1) {
var lapnodes = node.querySelectorAll('li.lap-cell');
node.insertBefore(li, lapnodes[1]);
} else {
node.appendChild(li);
}
};
Stopwatch.Panel.prototype.onreset = function() {
cancelAnimationFrame(this.tick);
this.showButtons('start', 'reset');
this.hideButtons('pause', 'resume', 'lap');
this.nodes.reset.setAttribute('disabled', 'true');
// clear lap list
this.nodes.laps.textContent = '';
this.update();
};
return Stopwatch.Panel;
});