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.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,856 @@
'use strict';
/* global _ */
/* global Cache */
/* global ConfirmDialog */
/* global Contacts */
/* global ContactsBTExport */
/* global ContactsExporter */
/* global ContactsSDExport */
/* global ContactsSIMExport */
/* global IccHandler */
/* global LazyLoader */
/* global navigationStack */
/* global Rest */
/* global SimContactsImporter */
/* global SimDomGenerator */
/* global utils */
/* global VCFReader */
/* global ContactsService */
/* global ExtServices */
/* global Loader */
/* global BulkDelete */
/* global ICE */
/* global Telemetry */
var contacts = window.contacts || {};
/***
This class handles all the activity regarding
the settings screen for contacts
**/
contacts.Settings = (function() {
var navigationHandler,
importSettingsHeader,
orderCheckBox,
orderItem,
orderByLastName,
setICEButton,
importSettingsPanel,
importSettingsTitle,
importContacts,
exportContacts,
importOptions,
exportOptions,
importLiveOption,
importGmailOption,
importSDOption,
exportSDOption,
newOrderByLastName = null,
PENDING_LOGOUT_KEY = 'pendingLogout',
bulkDeleteButton;
var EXPORT_TRANSITION_LEVEL = 2, DELETE_TRANSITION_LEVEL = 1;
// Initialise the settings screen (components, listeners ...)
var init = function initialize() {
// Create the DOM for our SIM cards and listen to any changes
IccHandler.init(new SimDomGenerator(), contacts.Settings.cardStateChanged);
initContainers();
// To avoid any race condition we listen for online events once
// containers have been initialized
window.addEventListener('online', checkOnline);
window.addEventListener('offline', checkOnline);
// Subscribe to events related to change state in the sd card
utils.sdcard.subscribeToChanges('check_sdcard', function(value) {
updateStorageOptions(utils.sdcard.checkStorageCard());
});
window.addEventListener('timeformatchange', updateTimestamps);
};
var hideSettings = function hideSettings() {
contacts.Settings.close();
};
// Get the different values that we will show in the app
var getData = function getData() {
var config = utils.cookie.load();
var order = config ? config.order : false;
orderByLastName = order;
newOrderByLastName = null;
updateOrderingUI();
};
var updateOrderingUI = function updateOrderingUI() {
var value = newOrderByLastName === null ? orderByLastName :
newOrderByLastName;
orderCheckBox.checked = value;
orderCheckBox.setAttribute('aria-checked', value);
};
var updateImportTitle = function updateImportTitle(l10nString) {
importSettingsTitle.setAttribute('data-l10n-id', l10nString);
};
// Initialises variables and listener for the UI
var initContainers = function initContainers() {
orderItem = document.getElementById('settingsOrder');
orderCheckBox = orderItem.querySelector('[name="order.lastname"]');
orderCheckBox.addEventListener('change', onOrderingChange.bind(this));
// Creating a navigation handler from this view
navigationHandler = new navigationStack('view-settings');
// Init panel & elements for caching them
importSettingsPanel = document.getElementById('import-settings');
importSDOption = document.getElementById('import-sd-option');
exportSDOption = document.getElementById('export-sd-option');
importSettingsTitle = document.getElementById('import-settings-title');
importLiveOption = document.getElementById('import-live-option');
importGmailOption = document.getElementById('import-gmail-option');
// ICE view
setICEButton = document.getElementById('set-ice');
/*
* Adding listeners
*/
document.getElementById('settings-close').addEventListener(
'click',
hideSettings
);
// Listener for updating the timestamp based on extServices
window.addEventListener('message', function updateList(e) {
if (e.data.type === 'import_updated') {
updateTimestamps();
checkNoContacts();
}
});
// Navigation back
importSettingsHeader = document.getElementById('import-settings-header');
importSettingsHeader.addEventListener('action', importSettingsBackHandler);
// Handlers for the navigation through the panels
importContacts = document.getElementById('importContacts');
importContacts.firstElementChild.
addEventListener('click', importContactsHandler);
exportContacts = document.getElementById('exportContacts');
exportContacts.firstElementChild.
addEventListener('click', exportContactsHandler);
// Handlers for the actions related with EXPORT/IMPORT
importOptions = document.getElementById('import-options');
importOptions.addEventListener('click', importOptionsHandler);
exportOptions = document.getElementById('export-options');
exportOptions.addEventListener('click', exportOptionsHandler);
// ICE view
setICEButton.addEventListener('click', showICEScreen);
// Bulk delete
bulkDeleteButton = document.getElementById('bulkDelete');
bulkDeleteButton.addEventListener('click', bulkDeleteHandler);
};
// UI event handlers
function importSettingsBackHandler() {
navigationHandler.back(function navigateBackHandler() {
// Removing the previous assigned style for having
// a clean view
importSettingsPanel.classList.remove('export');
importSettingsPanel.classList.remove('import');
});
}
function importContactsHandler() {
// Hide elements for export and transition
LazyLoader.load(['/contacts/js/utilities/telemetry.js'], () => {
importSettingsPanel.classList.remove('export');
importSettingsPanel.classList.add('import');
updateImportTitle('importContactsTitle');
navigationHandler.go('import-settings', 'right-left');
});
}
function exportContactsHandler() {
// Hide elements for import and transition
LazyLoader.load(['/contacts/js/export/contacts_exporter.js'], loadSearch);
function loadSearch() {
Loader.view('search', function() {
importSettingsPanel.classList.remove('import');
importSettingsPanel.classList.add('export');
updateImportTitle('exportContactsTitle');
navigationHandler.go('import-settings', 'right-left');
});
}
}
function showICEScreen(cb) {
LazyLoader.load([
'/contacts/js/utilities/ice_data.js',
'/contacts/js/views/ice_settings.js',
'/shared/js/contacts/utilities/ice_store.js'], function(){
ICE.refresh();
navigationHandler.go('ice-settings', 'right-left');
if (typeof cb === 'function') {
cb();
}
});
}
// Given an event, select wich should be the targeted
// import/export source
function getSource(e) {
var source = e.target.parentNode.dataset.source;
// Check special cases
if (source && source.indexOf('-') != -1) {
source = source.substr(0, source.indexOf('-'));
}
return source;
}
function logImportUsage(name) {
return Telemetry.logImportUsage(name);
}
function importOptionsHandler(e) {
/* jshint validthis:true */
var source = getSource(e);
switch (source) {
case 'sim':
var iccId = e.target.parentNode.dataset.iccid;
window.setTimeout(requireSimImport.bind(this,
onSimImport.bind(this, iccId)), 0);
break;
case 'sd':
window.setTimeout(requireOverlay.bind(this, onSdImport), 0);
break;
case 'gmail':
ExtServices.onContactsImported = logImportUsage.bind(null, 'gmail');
ExtServices.importGmail();
break;
case 'live':
ExtServices.onContactsImported = logImportUsage.bind(null, 'live');
ExtServices.importLive();
break;
}
}
var bulkDeleteHandler = function bulkDeleteHandler() {
LazyLoader.load(
[
'/contacts/js/contacts_bulk_delete.js',
'/contacts/js/contacts_remover.js'
],
function() {
Loader.view('search', function() {
contacts.List.selectFromList(_('DeleteTitle'),
function onSelectedContacts(promise, done) {
BulkDelete.performDelete(promise, done);
},
null,
navigationHandler,
{
transitionLevel: DELETE_TRANSITION_LEVEL
}
);
});
}
);
};
function exportOptionsHandler(e) {
var source = getSource(e);
switch (source) {
case 'sim':
var iccId = e.target.parentNode.dataset.iccid;
LazyLoader.load(['/contacts/js/export/sim.js'],
function() {
doExport(new ContactsSIMExport(IccHandler.getIccById(iccId)));
}
);
break;
case 'sd':
LazyLoader.load(
[
'/shared/js/device_storage/get_storage_if_available.js',
'/shared/js/device_storage/get_unused_filename.js',
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js',
'/contacts/js/export/sd.js'
],
function() {
doExport(new ContactsSDExport());
}
);
break;
case 'bluetooth':
LazyLoader.load(
[
'/shared/js/device_storage/get_storage_if_available.js',
'/shared/js/device_storage/get_unused_filename.js',
'/shared/js/contact2vcard.js',
'/shared/js/setImmediate.js',
'/contacts/js/export/bt.js'
],
function() {
doExport(new ContactsBTExport());
}
);
break;
}
}
function doExport(strategy) {
// Launch the selection mode in the list, and then invoke
// the export with the selected strategy.
contacts.List.selectFromList(_('exportContactsAction'),
function onSelectedContacts(promise) {
// Resolve the promise, meanwhile show an overlay to
// warn the user of the ongoin operation, dismiss it
// once we have the result
requireOverlay(function _loaded() {
utils.overlay.show('preparing-contacts', null, 'spinner');
promise.onsuccess = function onSuccess(ids) {
// Once we start the export process we can exit from select mode
// This will have to evolve once export errors can be captured
contacts.List.exitSelectMode();
var exporter = new ContactsExporter(strategy);
exporter.init(ids, function onExporterReady() {
// Leave the contact exporter to deal with the overlay
exporter.start();
});
};
promise.onerror = function onError() {
contacts.List.exitSelectMode();
utils.overlay.hide();
};
});
},
null,
navigationHandler,
{
isDanger: false,
transitionLevel: EXPORT_TRANSITION_LEVEL
}
);
}
// Options checking & updating
var checkSIMCard = function checkSIMCard() {
var statuses = IccHandler.getStatus();
statuses.forEach(function onStatus(status) {
enableSIMOptions(status.iccId, status.cardState);
});
};
// Disables/Enables an option and show the error if needed
var updateOptionStatus =
function updateOptionStatus(domOption, disabled, error) {
if (domOption === null) {
return;
}
var optionButton = domOption.firstElementChild;
if (disabled) {
optionButton.setAttribute('disabled', 'disabled');
if (error) {
domOption.classList.add('error');
} else {
domOption.classList.remove('error');
}
} else {
optionButton.removeAttribute('disabled');
domOption.classList.remove('error');
}
};
// Disables/Enables the actions over the sim import functionality
var enableSIMOptions = function enableSIMOptions(iccId, cardState) {
var importSimOption = document.getElementById('import-sim-option-' + iccId);
var exportSimOption = document.getElementById('export-sim-option-' + iccId);
var disabled = (cardState !== 'ready' && cardState !== 'illegal');
updateOptionStatus(importSimOption, disabled, true);
updateOptionStatus(exportSimOption, disabled, true);
};
/**
* Disables/Enables the actions over the sdcard import/export functionality
* @param {Boolean} cardAvailable Whether functions should be enabled or not.
*/
var updateStorageOptions = function updateStorageOptions(cardAvailable) {
// Enable/Disable button and shows/hides error message
updateOptionStatus(importSDOption, !cardAvailable, true);
updateOptionStatus(exportSDOption, !cardAvailable, true);
var importSDErrorL10nId = null;
var exportSDErrorL10nId = null;
var cardShared = utils.sdcard.status === utils.sdcard.SHARED;
if (!cardAvailable) {
importSDErrorL10nId = 'noMemoryCardMsg';
exportSDErrorL10nId = 'noMemoryCardMsgExport';
if (cardShared) {
importSDErrorL10nId = exportSDErrorL10nId = 'memoryCardUMSEnabled';
}
}
// update the message
var importSDErrorNode = importSDOption.querySelector('p.error-message');
if (importSDErrorL10nId) {
importSDErrorNode.setAttribute('data-l10n-id', importSDErrorL10nId);
} else {
importSDErrorNode.removeAttribute('data-l10n-id');
importSDErrorNode.textContent = '';
}
var exportSDErrorNode = exportSDOption.querySelector('p');
if (exportSDErrorL10nId) {
exportSDErrorNode.setAttribute('data-l10n-id', exportSDErrorL10nId);
} else {
exportSDErrorNode.removeAttribute('data-l10n-id');
exportSDErrorNode.textContent = '';
}
};
/**
* Loads the overlay class before showing
*/
function requireOverlay(callback) {
Loader.utility('Overlay', callback);
}
/**
* Loads required libraries for sim import
*/
function requireSimImport(callback) {
var libraries = ['Overlay', 'Import_sim_contacts'];
var pending = libraries.length;
libraries.forEach(function onPending(library) {
Loader.utility(library, next);
});
function next() {
if (!(--pending)) {
callback();
}
}
}
function resetWait(wakeLock) {
Contacts.hideOverlay();
if (wakeLock) {
wakeLock.unlock();
}
}
// Listens for any change in the ordering preferences
var onOrderingChange = function onOrderingChange(evt) {
newOrderByLastName = orderCheckBox.checked;
updateOrderingUI();
};
// Import contacts from SIM card and updates ui
var onSimImport = function onSimImport(iccId, done) {
var icc = IccHandler.getIccById(iccId);
if (icc === null) {
return;
}
var progress = Contacts.showOverlay('simContacts-reading',
'activityBar');
var wakeLock = navigator.requestWakeLock('cpu');
var cancelled = false, contactsRead = false;
var importer = new SimContactsImporter(icc);
utils.overlay.showMenu();
utils.overlay.oncancel = function oncancel() {
cancelled = true;
importer.finish();
if (contactsRead) {
// A message about canceling will be displayed while the current chunk
// is being cooked
progress.setClass('activityBar');
utils.overlay.hideMenu();
progress.setHeaderMsg('messageCanceling');
} else {
importer.onfinish(); // Early return while reading contacts
}
};
var totalContactsToImport;
var importedContacts = 0;
// Delay for showing feedback to the user after importing
var DELAY_FEEDBACK = 200;
importer.onread = function import_read(n) {
contactsRead = true;
totalContactsToImport = n;
if (totalContactsToImport > 0) {
progress.setClass('progressBar');
progress.setHeaderMsg('simContacts-importing');
progress.setTotal(totalContactsToImport);
}
};
importer.onfinish = function import_finish(numDupsMerged) {
window.setTimeout(function onfinish_import() {
resetWait(wakeLock);
if (importedContacts > 0) {
var source = 'sim-' + iccId;
utils.misc.setTimestamp(source, function() {
// Once the timestamp is saved, update the list
updateTimestamps();
checkNoContacts();
});
}
if (!cancelled) {
utils.status.show({
id: 'simContacts-imported3',
args: {
n: importedContacts
}
},
!numDupsMerged ? null : {
id: 'contactsMerged',
args: {
numDups: numDupsMerged
}
});
} else {
logImportUsage('sim');
}
typeof done === 'function' && done();
}, DELAY_FEEDBACK);
importer.onfinish = null;
};
importer.onimported = function imported_contact() {
importedContacts++;
if (!cancelled) {
progress.update();
}
};
importer.onerror = function import_error() {
var cancel = {
title: 'cancel',
callback: function() {
ConfirmDialog.hide();
}
};
var retry = {
title: 'retry',
isRecommend: true,
callback: function() {
ConfirmDialog.hide();
// And now the action is reproduced one more time
window.setTimeout(requireSimImport.bind(this,
onSimImport.bind(this, iccId)), 0);
}
};
ConfirmDialog.show(null, 'simContacts-error', cancel, retry);
resetWait(wakeLock);
};
importer.start();
};
var onSdImport = function onSdImport(cb) {
var cancelled = false;
var importer = null;
var progress = Contacts.showOverlay(
'memoryCardContacts-importing',
'activityBar',
'infiniteProgress'
);
utils.overlay.showMenu();
utils.overlay.oncancel = function() {
cancelled = true;
importer ? importer.finish() : Contacts.hideOverlay();
};
var wakeLock = navigator.requestWakeLock('cpu');
var importedContacts = 0;
// Delay for showing feedback to the user after importing
var DELAY_FEEDBACK = 200;
utils.sdcard.retrieveFiles([
'text/vcard',
'text/x-vcard',
'text/directory;profile=vCard',
'text/directory'
], ['vcf', 'vcard'], function(err, fileArray) {
if (err) {
return import_error(err, cb);
}
if (cancelled) {
return;
}
if (fileArray.length) {
var promises = [];
fileArray.forEach(file => {
promises.push(utils.sdcard.getTextFromFile(file, onContacts));
});
Promise.all(promises).then(results => {
var numDupsMerged = results.reduce((sum, current) => {
return sum + current;
});
window.setTimeout(() => {
utils.misc.setTimestamp('sd', () => {
// Once the timestamp is saved, update the list
updateTimestamps();
checkNoContacts();
resetWait(wakeLock);
if (!cancelled) {
var msg1 = {
id: 'memoryCardContacts-imported3',
args: {
n: importedContacts
}
};
var msg2 = !numDupsMerged ? null : {
id: 'contactsMerged',
args: {
numDups: numDupsMerged
}
};
utils.status.show(msg1, msg2);
logImportUsage('sd');
if (typeof cb === 'function') {
cb();
}
}
});
}, DELAY_FEEDBACK);
}).catch(error => {
import_error(error);
});
} else {
import_error('No contacts were found.', cb);
}
});
function onContacts(text) {
if (cancelled) {
return Promise.reject();
}
return new Promise((resolve, reject) => {
importer = new VCFReader(text);
if (!text || !importer) {
var error = 'No contacts were found';
import_error(error);
reject(error);
return;
}
importer.onimported = imported_contact;
importer.onerror = error => {
import_error(error);
reject(error);
};
importer.process((unused, numDupsMerged) => {
if (cancelled) {
reject('Cancelled');
Contacts.hideOverlay();
return;
}
resolve(numDupsMerged);
});
});
}
function imported_contact() {
importedContacts++;
progress.update();
}
function import_error(e, cb) {
var cancel = {
title: 'cancel',
callback: function() {
ConfirmDialog.hide();
}
};
var retry = {
title: 'retry',
isRecommend: true,
callback: function() {
ConfirmDialog.hide();
// And now the action is reproduced one more time
window.setTimeout(requireOverlay.bind(this, onSdImport), 0);
}
};
ConfirmDialog.show(null, 'memoryCardContacts-error', cancel,
retry);
resetWait(wakeLock);
if (typeof cb === 'function') {
cb();
}
}
};
// Dismiss settings window and execute operations if values got modified
var close = function close() {
if (newOrderByLastName != null &&
newOrderByLastName != orderByLastName && contacts.List) {
contacts.List.setOrderByLastName(newOrderByLastName);
utils.cookie.update({order: newOrderByLastName});
Cache.evict();
// Force the reset of the dom, we know that we changed the order
contacts.List.load(null, true);
orderByLastName = newOrderByLastName;
}
Contacts.goBack();
};
var checkOnline = function() {
// Perform pending automatic logouts
window.setTimeout(automaticLogout, 0);
// Other import services settings
updateOptionStatus(importGmailOption, !navigator.onLine, true);
updateOptionStatus(importLiveOption, !navigator.onLine, true);
};
var checkNoContacts = function checkNoContacts() {
var exportButton = exportContacts.firstElementChild;
ContactsService.isEmpty(function(error, isEmpty) {
if (error) {
window.console.warn(
'Error while trying to know the contact number',
error
);
// In case of error is safer to leave enabled
exportButton.removeAttribute('disabled');
bulkDeleteButton.removeAttribute('disabled');
return;
}
if (isEmpty) {
exportButton.setAttribute('disabled', 'disabled');
bulkDeleteButton.setAttribute('disabled', 'disabled');
setICEButton.setAttribute('disabled', 'disabled');
} else {
exportButton.removeAttribute('disabled');
bulkDeleteButton.removeAttribute('disabled');
setICEButton.removeAttribute('disabled');
}
});
};
function saveStatus(data) {
window.asyncStorage.setItem(PENDING_LOGOUT_KEY, data);
}
function automaticLogout() {
if (navigator.offLine === true) {
return;
}
LazyLoader.load(['/shared/js/contacts/utilities/http_rest.js'],
function() {
window.asyncStorage.getItem(PENDING_LOGOUT_KEY, function(data) {
if (!data) {
return;
}
var services = Object.keys(data);
var numResponses = 0;
services.forEach(function(service) {
var url = data[service];
var callbacks = {
success: function logout_success() {
numResponses++;
window.console.log('Successfully logged out: ', service);
delete data[service];
if (numResponses === services.length) {
saveStatus(data);
}
},
error: function logout_error() {
numResponses++;
if (numResponses === services.length) {
saveStatus(data);
}
},
timeout: function logout_timeout() {
numResponses++;
if (numResponses === services.length) {
saveStatus(data);
}
}
};
Rest.get(url, callbacks);
});
});
});
}
var updateTimestamps = function updateTimestamps() {
// TODO Add the same functionality to 'EXPORT' methods when ready.
var importSources =
document.querySelectorAll('#import-options li[data-source]');
Array.prototype.forEach.call(importSources, function(node) {
utils.misc.getTimestamp(node.dataset.source,
function(time) {
var spanID = 'notImported';
if (time) {
spanID = 'imported';
var timeElement = node.querySelector('p > time');
timeElement.setAttribute('datetime',
(new Date(time)).toLocaleString());
timeElement.textContent = utils.time.pretty(time);
}
node.querySelector('p > span').setAttribute('data-l10n-id', spanID);
});
});
};
var refresh = function refresh() {
getData();
checkOnline();
checkSIMCard();
utils.sdcard.getStatus(function statusUpdated() {
updateStorageOptions(utils.sdcard.checkStorageCard());
});
updateTimestamps();
checkNoContacts();
};
return {
'init': init,
'close': close,
'refresh': refresh,
'cardStateChanged': checkSIMCard,
'updateTimestamps': updateTimestamps,
'showICEScreen' : showICEScreen,
get navigation() { return navigationHandler; },
'importFromSDCard': onSdImport,
'importFromSIMCard': onSimImport
};
})();