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

214
gaia/shared/js/option_menu.js Executable file
View file

@ -0,0 +1,214 @@
'use strict';
/*
Generic action menu. Options should have the following structure:
new OptionMenu(options);
options {
items: An array of menu options to render
eg.
[
{
name: 'Lorem ipsum',
l10nId: 'lorem',
l10nArgs: 'ipsum',
method: function optionMethod(param1, param2) {
// Method and params if needed
},
params: ['param1', '123123123']
},
....
,
Last option has a different UI compared with the previous one.
This is because it's recommended to use as a 'Cancel' option
{
name: 'Cancel',
l10nId: 'Cancel'
method: function optionMethod(param) {
// Method and param if needed
},
params: ['Optional params'],
// Optional boolean flag to tell the
// menu button handlers that this option
// will not execute the "complete" callback.
// Defaults to "false"
incomplete: false [true]
}
],
// Optional header text or node
header: ...,
// additional classes on the dialog, as an array of strings
classes: ...
// Optional section text or node
section: ...
// Optional data-type: confirm or action
type: 'confirm'
// Optional callback to be invoked when a
// button in the menu is pressed. Can be
// overridden by an "incomplete: true" set
// on the menu item in the items array.
complete: function() {...}
}
*/
var OptionMenu = function(options) {
if (!options || !options.items || options.items.length === 0) {
return;
}
// Create a private, weakly held entry for
// this instances DOM object references
// More info:
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/WeakMap
var handlers = new WeakMap();
// Retrieve items to be rendered
var items = options.items;
// Create the structure
this.form = document.createElement('form');
this.form.dataset.type = options.type || 'action';
this.form.dataset.subtype = 'menu';
this.form.setAttribute('role', 'dialog');
this.form.tabIndex = -1;
var classList = this.form.classList;
if (options.classes) {
classList.add.apply(classList, options.classes);
}
// We append title if needed
if (options.header) {
var header = document.createElement('header');
if (typeof options.header === 'string') {
header.textContent = options.header || '';
} else if (options.header.l10nId) {
header.setAttribute('data-l10n-id', options.header.l10nId);
} else {
header.appendChild(options.header);
}
this.form.appendChild(header);
}
if (options.section) {
var section = document.createElement('section');
if (typeof options.section === 'string') {
section.textContent = options.section || '';
} else {
section.appendChild(options.section);
}
this.form.appendChild(section);
}
// We append a menu with the list of options
var menu = document.createElement('menu');
menu.dataset.items = items.length;
// For each option, we append the item and listener
items.forEach(function renderOption(item, index) {
var button = document.createElement('button');
button.type = 'button';
if (item.l10nId) {
navigator.mozL10n.setAttributes(button, item.l10nId, item.l10nArgs);
} else if (item.name && item.name.length) {
button.textContent = item.name || '';
} else {
// no l10n or name, just empty item, don't add to the menu
return;
}
// Wrap the last button in a container in order to style it differently.
if (index === items.length - 1) {
var lastButtonWrapper = document.createElement('span');
lastButtonWrapper.className = 'last-button-container';
menu.appendChild(lastButtonWrapper);
lastButtonWrapper.appendChild(button);
} else {
menu.appendChild(button);
}
// Add a mapping from the button object
// directly to its options item.
item.incomplete = item.incomplete || false;
handlers.set(button, item);
});
this.form.addEventListener('submit', function(event) {
event.preventDefault();
});
this.form.addEventListener('transitionend', function(event) {
if (event.target !== this.form) {
return;
}
if (!this.form.classList.contains('visible') && this.form.parentNode) {
this.form.remove();
} else {
// Focus form for accessibility
this.form.focus();
}
document.body.classList.remove('dialog-animating');
}.bind(this));
menu.addEventListener('click', function(event) {
var action = handlers.get(event.target);
var method;
// Delegate operation to target method. This allows
// for a custom "Cancel" to be provided by calling program.
//
// Further operations should only be processed if
// an actual button was pressed.
if (typeof action !== 'undefined') {
method = action.method || function() {};
method.apply(null, action.params || []);
// Hide action menu when click is received
this.hide();
if (typeof options.complete === 'function' && !action.incomplete) {
options.complete();
}
}
}.bind(this));
// Appending the action menu to the form
this.form.appendChild(menu);
};
// We prototype functions to show/hide the UI of action-menu
OptionMenu.prototype.show = function() {
// Remove the focus to hide the keyboard asap
document.activeElement && document.activeElement.blur();
if (!this.form.parentNode) {
document.body.appendChild(this.form);
// Flush style on form so that the show transition plays once we add
// the visible class.
this.form.clientTop;
}
this.form.classList.add('visible');
document.body.classList.add('dialog-animating');
};
OptionMenu.prototype.hide = function() {
this.form.classList.remove('visible');
};