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

364 lines
11 KiB
JavaScript
Executable file

'use strict';
/* global KeyEvent, mozIntl, IntlHelper */
define(function(require) {
var Alarm = require('alarm');
var ClockView = require('panels/alarm/clock_view');
var AudioManager = require('audio_manager');
var FormButton = require('form_button');
var Sounds = require('sounds');
var Utils = require('utils');
var Panel = require('panel');
var html = require('text!panels/alarm_edit/panel.html');
var constants = require('constants');
IntlHelper.define('weekday-long', 'datetime', {
weekday: 'long'
});
var AlarmEdit = function() {
Panel.apply(this, arguments);
this.element.innerHTML = html;
var handleDomEvent = this.handleDomEvent.bind(this);
this.element.addEventListener('panel-visibilitychange',
this.handleVisibilityChange.bind(this));
this.selects = {};
[
'time', 'repeat', 'sound', 'snooze'
].forEach(function(id) {
this.selects[id] = this.element.querySelector('#' + id + '-select');
}, this);
this.inputs = {
name: this.element.querySelector('#alarm-name'),
volume: this.element.querySelector('#alarm-volume-input')
};
this.headers = {
header: this.element.querySelector('#alarm-header')
};
this.buttons = {};
[
'delete', 'done'
].forEach(function(id) {
this.buttons[id] = this.element.querySelector('#alarm-' + id);
}, this);
this.checkboxes = {
vibrate: this.element.querySelector('#vibrate-checkbox')
};
this.buttons.time = new FormButton(this.selects.time, {
formatLabel: function(value) {
var date = new Date();
// This split(':') is locale-independent per HTML5 <input type=time>
var splitValue = value.split(':');
date.setHours(splitValue[0]);
date.setMinutes(splitValue[1]);
return { raw: Utils.getLocalizedTimeText(date) };
}.bind(this)
});
this.buttons.repeat = new FormButton(this.selects.repeat, {
id: 'repeat-menu',
formatLabel: Utils.summarizeDaysOfWeek
});
this.buttons.sound = new FormButton(this.selects.sound, {
id: 'sound-menu',
formatLabel: Sounds.formatLabel
});
this.buttons.snooze = new FormButton(this.selects.snooze, {
id: 'snooze-menu',
formatLabel: function(snooze) {
var numFormatter = IntlHelper.get('digit-nopadding');
return {
id: 'alarmMinutes',
args: {minutes: numFormatter.format(parseInt(snooze))}
};
}
});
this.scrollList = this.element.querySelector('#edit-alarm');
this.repeatSelect = this.element.querySelector('#repeat-select');
// When the system pops up the ValueSelector, it inadvertently
// messes with the scrollTop of the current panel. This is a
// workaround for bug 981255 until the Edit panel becomes a new
// window per bug 922651.
this.element.addEventListener('scroll', function() {
this.element.scrollTop = 0;
}.bind(this));
// When the language changes, the value of 'firstDayOfTheWeek'
// might change and we need to update translations of weekdays
this.updateL10n();
document.addEventListener('DOMRetranslated', this.updateL10n.bind(this));
this.headers.header.addEventListener('action', handleDomEvent);
this.buttons.done.addEventListener('click', handleDomEvent);
this.selects.sound.addEventListener('change', handleDomEvent);
this.selects.sound.addEventListener('blur', handleDomEvent);
this.selects.repeat.addEventListener('change', handleDomEvent);
this.buttons.delete.addEventListener('click', handleDomEvent);
this.inputs.name.addEventListener('keypress', this.handleNameInput);
this.inputs.volume.addEventListener('change', handleDomEvent);
this.isSaving = false;
// If the phone locks during preview, or an alarm fires, pause the sound.
// TODO: When this is no longer a singleton, unbind the listener.
window.addEventListener('visibilitychange', function() {
if (document.hidden) {
this.stopPreviewSound();
// Ensure the keyboard goes away.
document.activeElement.blur();
}
}.bind(this));
};
AlarmEdit.prototype = Object.create(Panel.prototype);
Utils.extend(AlarmEdit.prototype, {
alarm: null,
ringtonePlayer: AudioManager.createAudioPlayer(),
handleNameInput: function(evt) {
// If the user presses enter on the name label, dismiss the
// keyboard to allow them to continue filling out the other
// fields. This is not in the `handleEvent` function because we
// only want to call `.preventDefault` sometimes.
if (evt.keyCode === KeyEvent.DOM_VK_RETURN) {
evt.preventDefault();
evt.target.blur();
}
},
updateL10n: function() {
// Move the weekdays around to properly account for the firstDay.
mozIntl.calendarInfo('firstDayOfTheWeek').then(firstDay => {
var formatter = IntlHelper.get('weekday-long');
var options = [...this.repeatSelect.querySelectorAll('option')];
var optionsByDay = {};
options.forEach(option => {
var value = option.getAttribute('value');
optionsByDay[value] = option;
this.repeatSelect.removeChild(option);
});
for (var i = 0; i < 7; i++) {
var day = (i + firstDay) % options.length;
var option = optionsByDay[day.toString()];
var dayDate = new Date(constants.KNOWN_SUNDAY);
dayDate.setDate(constants.KNOWN_SUNDAY.getDate() + firstDay + i);
option.textContent = formatter.format(dayDate);
this.repeatSelect.appendChild(option);
}
});
var numFormatter = IntlHelper.get('digit-nopadding');
var elements = [...document.querySelectorAll('#snooze-select > option')];
elements.forEach(element => {
var value = parseInt(element.value);
element.setAttribute('data-l10n-args',
JSON.stringify({minutes: numFormatter.format(value)}));
});
},
// The name `handleEvent` is already defined by the Panel class, so this
// method must be named uniquely to avoid overriding that functionality.
handleDomEvent: function aev_handleDomEvent(evt) {
evt.preventDefault();
var input = evt.target;
if (!input) {
return;
}
switch (input) {
case this.headers.header:
ClockView.show();
break;
case this.buttons.done:
ClockView.show();
this.save();
break;
case this.selects.sound:
switch (evt.type) {
case 'change':
this.previewSound();
break;
case 'blur':
this.stopPreviewSound();
break;
}
break;
case this.buttons.delete:
ClockView.show();
this.delete();
break;
case this.selects.repeat:
this.alarm.repeat = this.buttons.repeat.value;
break;
case this.inputs.volume:
// Alarm Volume is applied to all alarms.
AudioManager.setAlarmVolume(this.getAlarmVolumeValue());
break;
}
},
focusMenu: function aev_focusMenu(menu) {
setTimeout(function() { menu.focus(); }, 10);
},
handleVisibilityChange: function aev_show(evt) {
var isVisible = evt.detail.isVisible;
var alarm;
if (!isVisible) {
return;
}
// `navData` is set by the App module in `navigate`.
alarm = this.navData;
// scroll to top of form list
this.scrollList.scrollTop = 0;
this.element.classList.toggle('new', !alarm);
this.alarm = new Alarm(alarm); // alarm may be null
// Set to empty string if the Alarm doesn't have an ID,
// otherwise dataset will automatically stringify it
// to be "undefined" rather than "".
this.element.dataset.id = this.alarm.id || '';
this.inputs.name.value = this.alarm.label;
AudioManager.requestAlarmVolume().then(function(volume) {
this.inputs.volume.value = AudioManager.getAlarmVolume();
}.bind(this));
// Init time, repeat, sound, snooze selection menu.
this.initTimeSelect();
this.initRepeatSelect();
this.initSoundSelect();
this.initSnoozeSelect();
this.checkboxes.vibrate.checked = this.alarm.vibrate;
// Update the labels for any FormButton dropdowns that have
// changed, because setting <select>.value does not fire a change
// event.
for (var key in this.buttons) {
var button = this.buttons[key];
if (button instanceof FormButton) {
button.refresh();
}
}
location.hash = '#alarm-edit-panel';
},
initTimeSelect: function aev_initTimeSelect() {
// HTML5 <input type=time> expects 24-hour HH:MM format.
var hour = parseInt(this.alarm.hour, 10);
var minute = parseInt(this.alarm.minute, 10);
this.selects.time.value = (hour < 10 ? '0' : '') + hour +
':' + (minute < 10 ? '0' : '') + minute;
},
getTimeSelect: function aev_getTimeSelect() {
// HTML5 <input type=time> returns data in 24-hour HH:MM format.
var splitTime = this.selects.time.value.split(':');
return { hour: splitTime[0], minute: splitTime[1] };
},
initRepeatSelect: function aev_initRepeatSelect() {
this.buttons.repeat.value = this.alarm.repeat;
},
initSoundSelect: function aev_initSoundSelect() {
this.buttons.sound.value = this.alarm.sound;
},
getSoundSelect: function aev_getSoundSelect() {
return this.buttons.sound.value !== '0' && this.buttons.sound.value;
},
previewSound: function aev_previewSound() {
var ringtoneName = this.getSoundSelect();
this.ringtonePlayer.playRingtone(ringtoneName);
},
stopPreviewSound: function aev_stopPreviewSound() {
this.ringtonePlayer.pause();
},
initSnoozeSelect: function aev_initSnoozeSelect() {
this.buttons.snooze.value = this.alarm.snooze;
},
getSnoozeSelect: function aev_getSnoozeSelect() {
return this.buttons.snooze.value;
},
getRepeatSelect: function aev_getRepeatSelect() {
return this.buttons.repeat.value;
},
getAlarmVolumeValue: function() {
return parseFloat(this.inputs.volume.value);
},
save: function aev_save(callback) {
if (this.isSaving) {
// Ignore double-taps on the "Save" button. When this view gets
// refactored, we should opt for a more coherent way of managing
// UI state to avoid glitches like this.
return;
}
var alarm = this.alarm;
if (this.element.dataset.id && this.element.dataset.id !== '') {
alarm.id = parseInt(this.element.dataset.id, 10);
} else {
delete alarm.id;
}
alarm.label = this.inputs.name.value;
var time = this.getTimeSelect();
alarm.hour = time.hour;
alarm.minute = time.minute;
alarm.repeat = this.buttons.repeat.value;
alarm.sound = this.getSoundSelect();
alarm.vibrate = this.checkboxes.vibrate.checked;
alarm.snooze = parseInt(this.getSnoozeSelect(), 10);
AudioManager.setAlarmVolume(this.getAlarmVolumeValue());
this.isSaving = true;
alarm.schedule('normal').then(() => {
this.isSaving = false;
window.dispatchEvent(new CustomEvent('alarm-changed', {
detail: { alarm: alarm, showBanner: true }
}));
callback && callback(null, alarm);
});
},
delete: function aev_delete(callback) {
if (!this.alarm.id) {
setTimeout(callback.bind(null, new Error('no alarm id')), 0);
return;
}
this.alarm.delete().then(callback);
}
});
return AlarmEdit;
});