VulpesOS/gaia/shared/js/tz_select.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

310 lines
9.3 KiB
JavaScript
Executable file

/* globals IccHelper */
/* exported tzSelect */
'use strict';
function tzSelect(regionSelector, citySelector, onchange, onload) {
const TIMEZONE_FILE = '/shared/resources/tz.json';
const APN_TZ_FILE = '/shared/resources/apn_tz.json';
const TIMEZONE_NONE = 'NONE';
const TIMEZONE_NETWORK = 'NETWORK';
const TIMEZONE_SIM = 'SIM';
const TIMEZONE_USER = 'USER';
const TIMEZONE_PREVIOUS_SETTING = 'PREVIOUS_SETTING';
function loadJSON(href, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', href, true);
xhr.responseType = 'json';
xhr.onerror = callback;
xhr.onload = function() {
callback(xhr.response);
};
xhr.onerror = function() {
console.error('Error getting file');
callback(xhr.response);
};
xhr.send();
}
/**
* Guess the current timezone from the MCC/MNC tuple
*/
function getDefaultTimezoneID(callback) {
if (!callback) {
return;
}
// Worst case scenario: default to New York (which is just as good or
// bad as anything else -- we used to default to Pago Pago)
var tzDefault = 'America/New_York';
var source = TIMEZONE_NONE;
// retrieve MCC/MNC: use the current network codes when available,
// default to the SIM codes if necessary.
var mcc, mnc;
var connections = window.navigator.mozMobileConnections || [];
for (var i = 0; i < connections.length; ++i) {
var conn = connections[i];
if (conn && conn.voice && conn.voice.network && conn.voice.connected) {
// we have connection available, so we use it
mcc = conn.voice.network.mcc;
mnc = conn.voice.network.mnc;
source = TIMEZONE_NETWORK;
break;
}
}
if (!mcc && IccHelper && IccHelper.iccInfo) {
// we don't have connection available, we rely on the SIM
mcc = IccHelper.iccInfo.mcc;
mnc = IccHelper.iccInfo.mnc;
source = TIMEZONE_SIM;
// if SIM is not available, mcc and mnc are null,
// so we wait for a future event where we have access to the SIM.
if (IccHelper.cardState !== 'ready') {
IccHelper.addEventListener('iccinfochange', function simReady() {
if (IccHelper.iccInfo.mcc) {
IccHelper.removeEventListener('iccinfochange', simReady);
}
getDefaultTimezoneID(callback);
});
}
}
if (!mcc) {
callback(tzDefault, TIMEZONE_NONE);
return;
}
// most MCC values only match one country (hence one timezone);
// for the few MCC values that match several countries, rely on MNC.
loadJSON(APN_TZ_FILE, function(response) {
if (response) {
var tz = response[mcc];
if (typeof(tz) === 'string') {
callback(tz, source);
return;
} else if (tz && (mnc in tz)) {
callback(tz[mnc], source);
return;
}
}
callback(tzDefault, TIMEZONE_NONE);
});
}
/**
* Activate a timezone selector UI
*/
function newTZSelector(onchangeTZ, currentID, source) {
// for region, we use whatever is BEFORE the /
var gRegion = currentID.replace(/\/.*/, '');
// for city, we use whatever is AFTER the /
var gCity = currentID.replace(/.*?\//, '');
var gTZ = null;
function fillSelectElement(selector, options) {
selector.innerHTML = '';
options.sort(function(a, b) {
return (a.text > b.text);
});
for (var i = 0; i < options.length; i++) {
var option = document.createElement('option');
option.textContent = options[i].text;
option.selected = options[i].selected;
option.value = options[i].value;
selector.appendChild(option);
}
}
// This is a function for options that are localizable.
// Currently Regions are, while Cities use the old versio
function fillSelectL10nElement(selector, options) {
selector.innerHTML = '';
const collator = Intl.Collator(navigator.languages, {
usage: 'sort',
});
const frag = document.createDocumentFragment();
const items = options.map(option => {
const item = document.createElement('option');
item.setAttribute('data-l10n-id', option.l10nId);
item.selected = option.selected;
item.value = option.value;
frag.appendChild(item);
return item;
});
return document.l10n.translateFragment(frag).then(() => {
items.sort(
(a, b) => collator.compare(a.textContent, b.textContent)
);
items.forEach(item => frag.appendChild(item));
selector.appendChild(frag);
});
}
function getSelectedText(selector) {
var options = selector.querySelectorAll('option');
return options[selector.selectedIndex].textContent;
}
function fillRegions() {
var options = [];
for (var c in gTZ) {
options.push({
l10nId: 'tzRegion-' + c,
value: c,
selected: (c === gRegion)
});
}
fillSelectL10nElement(regionSelector, options).then(fillCities);
}
function fillCities() {
gRegion = regionSelector.value;
var list = gTZ[gRegion];
var options = [];
for (var i = 0; i < list.length; i++) {
options.push({
text: list[i].name || list[i].city.replace(/_/g, ' '),
value: i,
selected: (list[i].city === gCity)
});
}
fillSelectElement(citySelector, options);
setTimezone();
}
function setTimezone() {
if (source === TIMEZONE_PREVIOUS_SETTING) {
onload && onload(getTZInfo(), true);
return;
}
onchangeTZ(getTZInfo(), {
'changedByUser': source === TIMEZONE_USER,
'needsConfirmation': source !== TIMEZONE_USER &&
source !== TIMEZONE_NETWORK});
}
function getTZInfo() {
var res = gTZ[gRegion][citySelector.value];
gCity = res.city;
var offset = res.offset.split(',');
return {
id: res.id || gRegion + '/' + res.city,
region: getSelectedText(regionSelector),
city: getSelectedText(citySelector),
cc: res.cc,
utcOffset: offset[0],
dstOffset: offset[1]
};
}
regionSelector.onchange = function() {
source = TIMEZONE_USER;
fillCities();
};
citySelector.onchange = function() {
source = TIMEZONE_USER;
setTimezone();
};
loadJSON(TIMEZONE_FILE, function loadTZ(response) {
gTZ = response;
fillRegions();
});
}
/**
* Monitor time.timezone changes
*/
function newTZObserver() {
var settings = window.navigator.mozSettings;
if (!settings) {
return;
}
settings.addObserver('time.timezone', function(event) {
setTimezoneDescription(event.settingValue);
});
var systemTimeChanged = function() {
onchange(systemTimeChanged.tz, systemTimeChanged.needsConfirmation);
};
function updateTZ(tz, options) {
// Update data for our callback.
systemTimeChanged.tz = tz;
systemTimeChanged.needsConfirmation = options.needsConfirmation;
// Set the timezone in the settings (this causes system time to change).
var req = settings.createLock().set({'time.timezone': tz.id});
req.onsuccess = function updateTZ_callback() {
// Store the user manually selected timezone separately
if (options.changedByUser) {
settings.createLock().set({'time.timezone.user-selected': tz.id});
} else {
onload && onload(tz, options.needsConfirmation);
}
};
}
function initSelector(initialValue, source) {
// Wait until the timezone is actually set before calling the callback.
// Only register this once!
onchange && window.addEventListener('moztimechange', systemTimeChanged);
// Get rid of our listener when we unload.
onchange && window.addEventListener('unload', function() {
window.removeEventListener('moztimechange', systemTimeChanged);
});
// initialize the timezone selector with the initial TZ setting
newTZSelector(updateTZ, initialValue, source);
}
var reqTimezone = settings.createLock().get('time.timezone');
reqTimezone.onsuccess = function dt_getStatusSuccess() {
// load the timezone the user manually selected last time,
// or get the default timezone for the current carrier
var reqUserTZ = settings.createLock().get('time.timezone.user-selected');
reqUserTZ.onsuccess = function dt_getUserTimezoneSuccess() {
var userSelTimezone = reqUserTZ.result['time.timezone.user-selected'];
if (userSelTimezone) {
initSelector(userSelTimezone, TIMEZONE_PREVIOUS_SETTING);
} else {
getDefaultTimezoneID(initSelector);
}
};
};
function setTimezoneDescription(timezoneID) {
var oldSelectedIndex = regionSelector.selectedIndex;
regionSelector.value = timezoneID.replace(/\/.*/, '');
if (regionSelector.selectedIndex == -1) {
regionSelector.selectedIndex = oldSelectedIndex;
}
oldSelectedIndex = citySelector.selectedIndex;
citySelector.value = timezoneID.replace(/.*?\//, '');
if (citySelector.selectedIndex == -1) {
citySelector.selectedIndex = oldSelectedIndex;
}
}
}
/**
* Startup -- make sure webL10n is ready before using tzSelect()
*/
newTZObserver();
}