VulpesOS/gaia/apps/clock/js/panels/timer/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

318 lines
8.2 KiB
JavaScript
Executable file

define(function(require) {
'use strict';
/* global IntlHelper */
var Panel = require('panel');
var Picker = require('picker/picker');
var View = require('view');
var Utils = require('utils');
var Timer = require('timer');
var Sounds = require('sounds');
var FormButton = require('form_button');
var html = require('text!panels/timer/panel.html');
var AudioManager = require('audio_manager');
var asyncStorage = require('shared/js/async_storage');
var priv = new WeakMap();
function timeFromPicker(value) {
var hm, ms;
hm = value.split(':');
ms = Utils.dateMath.toMS({
hours: hm[0],
minutes: hm[1]
});
return ms;
}
IntlHelper.define('timer-hms', 'mozduration', {
minUnit: 'second',
maxUnit: 'hour'
});
/**
* Timer.Panel
*
* Construct a UI panel for the Timer panel.
*
* @return {Timer.Panel} Timer.Panel object.
*
*/
Timer.Panel = function(element) {
Panel.apply(this, arguments);
element.innerHTML = html;
this.timer = null;
this.nodes = {};
this.picker = new Picker({
element: this.element.querySelector('#time-picker'),
pickers: {
hours: {
range: [0, 23],
l10nId: 'nSpinnerHours'
},
minutes: {
range: [0, 59],
isPadded: true,
l10nId: 'nSpinnerMinutes'
}
}
});
this.ringtonePlayer = AudioManager.createAudioPlayer();
// Gather elements
[
'create', 'cancel', 'dialog', 'pause', 'start', 'sound', 'time', 'vibrate',
'plus'
].forEach(function(id) {
this.nodes[id] = this.element.querySelector('#timer-' + id);
}, this);
// Bind click events
[
'create', 'cancel', 'pause', 'start', 'plus'
].forEach(function(action) {
var element = this.nodes[action];
if (priv.has(element)) {
priv.delete(element);
}
priv.set(element, {
action: action,
panel: this
});
element.addEventListener('click', this.onclick.bind(this), false);
}, this);
var sound = this.nodes.sound;
sound.addEventListener('blur', this.pauseAlarm.bind(this), false);
sound.addEventListener('change', this.previewAlarm.bind(this), false);
var soundMenuConfig = {
id: 'timer-sound-menu',
formatLabel: Sounds.formatLabel
};
this.soundButton = new FormButton(sound, soundMenuConfig);
this.soundButton.refresh();
element.addEventListener('panel-visibilitychange',
this.onvisibilitychange.bind(this));
// The start button is disable by default (picker at 00:00 by default)
this.nodes.create.setAttribute('disabled', 'true');
var create = this.nodes.create;
var picker = this.picker;
var enableButton = () => {
if(timeFromPicker(picker.value) === 0) {
create.setAttribute('disabled', 'true');
} else {
create.removeAttribute('disabled');
}
this.saveCurrentSettingsAsDefaults();
};
// The start button is enable if the value of the timer is not 00:00
picker.nodes.minutes.addEventListener('transitionend', enableButton);
picker.nodes.hours.addEventListener('transitionend', enableButton);
this.restoreDefaults().then(() => {
enableButton(); // Update the enabled state to match the new defaults.
});
this.nodes.vibrate.addEventListener(
'change', (e) => this.saveCurrentSettingsAsDefaults());
this.nodes.sound.addEventListener(
'change', (e) => this.saveCurrentSettingsAsDefaults());
Timer.singleton(function(err, timer) {
this.timer = timer;
var onTimerEvent = this.onTimerEvent.bind(this);
window.addEventListener('timer-start', onTimerEvent);
window.addEventListener('timer-pause', onTimerEvent);
window.addEventListener('timer-tick', onTimerEvent);
window.addEventListener('timer-end', onTimerEvent);
IntlHelper.observe('timer-hms', this.update.bind(this));
if (this.visible) {
// If the timer panel already became visible before we fetched
// the timer, we must update the display to show the proper
// timer status.
this.onvisibilitychange({ detail: { isVisible: true } });
}
}.bind(this));
};
Timer.Panel.prototype = Object.create(Panel.prototype);
Timer.Panel.prototype.restoreDefaults = function() {
return new Promise((resolve) => {
asyncStorage.getItem('timer-defaults', (values) => {
values = values || {};
if (values.sound != null) {
this.soundButton.value = values.sound;
}
if (values.time != null) {
this.picker.value = values.time;
}
if (values.vibrate != null) {
this.nodes.vibrate.checked = values.vibrate;
}
resolve();
});
});
};
Timer.Panel.prototype.saveCurrentSettingsAsDefaults = function() {
asyncStorage.setItem('timer-defaults', {
sound: this.soundButton.value,
time: this.picker.value,
vibrate: this.nodes.vibrate.checked
});
};
Timer.Panel.prototype.onvisibilitychange = function(evt) {
var isVisible = evt.detail.isVisible;
var timer = this.timer;
if (isVisible) {
// No active timer, or timer has expired...
// - Show the new timer dialog
if (timer === null || timer.state === Timer.INITIAL) {
this.showDialog();
} else {
// Active timer exists, might be started by an external app, so we need
// to make sure dialog is hidden and buttons/time are on correct state
this.toggleButtons();
this.hideDialog();
this.update();
}
} else {
this.pauseAlarm();
}
};
Timer.Panel.prototype._toggleDialog = function(isVisible = true) {
View.instance(this.nodes.dialog).visible = isVisible;
return this;
};
Timer.Panel.prototype.showDialog = function() {
return this._toggleDialog(true);
};
Timer.Panel.prototype.hideDialog = function() {
return this._toggleDialog(false);
};
Timer.Panel.prototype.onTimerEvent = function(event) {
switch (event.type) {
case 'timer-start':
// new timer dialog should always be hidden during timer-start (even
// if started from external app)
this.hideDialog();
/* falls through */
case 'timer-pause':
this.toggleButtons();
/* falls through */
case 'timer-tick':
this.update();
break;
case 'timer-end':
this.showDialog();
break;
}
};
/**
* Given milliseconds, render the time as a rounded-to-seconds
* countdown.
*/
Timer.Panel.prototype.update = function() {
return IntlHelper.get('timer-hms').then(formatter => {
var remaining = this.timer.remaining;
var newText = formatter.format(remaining);
// Use localized caching here to prevent unnecessary DOM repaints.
if (this._cachedTimerText !== newText) {
this.nodes.time.textContent = this._cachedTimerText = newText;
}
});
};
Timer.Panel.prototype.toggleButtons = function() {
var nodes = this.nodes;
var started = this.timer.state === Timer.STARTED;
nodes.pause.classList.toggle('hidden', !started);
nodes.start.classList.toggle('hidden', started);
};
/**
* previewAlarm Plays the currently selected alarm value on a loop.
*/
Timer.Panel.prototype.previewAlarm = function() {
var ringtoneName = Utils.getSelectedValueByIndex(this.nodes.sound);
this.ringtonePlayer.playRingtone(ringtoneName);
};
/**
* pauseAlarm stops the alarm if it is playing
*/
Timer.Panel.prototype.pauseAlarm = function() {
this.ringtonePlayer.pause();
};
/**
* handleEvent Handler for all panel bound UI events.
* (`this` context object is not Timer.Panel)
*
* @param {Event} event The Event object.
*/
Timer.Panel.prototype.onclick = function(event) {
var meta = priv.get(event.target);
var panel = meta.panel;
var nodes = panel.nodes;
var timer = panel.timer;
var time;
if (meta.action === 'create') {
time = timeFromPicker(panel.picker.value);
if (!time) {
return;
}
timer.duration = time;
timer.sound = nodes.sound.value;
timer.vibrate = nodes.vibrate.checked;
timer.start();
// Hide the new timer dialog
panel.hideDialog();
} else if (meta.action === 'plus') {
timer.plus(+event.target.dataset.value);
// need to update immediately cause timer might be paused during click
panel.update();
} else {
// meta.action => timer[meta.action]()
//
// ie.
//
// if start => timer.start()
// if pause => timer.pause()
// if cancel => timer.cancel()
//
timer[meta.action]();
}
timer.commit();
};
return Timer.Panel;
});