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

344
gaia/apps/camera/js/views/controls.js vendored Executable file
View file

@ -0,0 +1,344 @@
define(function(require, exports, module) {
'use strict';
/**
* Dependencies
*/
var debug = require('debug')('view:controls');
var debounce = require('lib/debounce');
var bind = require('lib/bind');
var View = require('view');
var Drag = require('drag');
/**
* Exports
*/
module.exports = View.extend({
name: 'controls',
className: 'test-controls',
initialize: function(options) {
this.drag = options && options.drag; // test hook
this.once('inserted', this.setupSwitch);
this.render();
},
switchPositions: {
left: 'picture',
right: 'video',
picture: 'left',
video: 'right'
},
// {node}: {data-l10n-id} pairs used for localization.
elsL10n: {
cancel: 'controls-button-close',
thumbnail: 'preview-button',
capture: 'capture-button'
},
render: function() {
this.el.innerHTML = this.template();
// Get nodes
this.els.switchHandle = this.find('.js-switch-handle');
this.els.thumbnail = this.find('.js-thumbnail');
this.els.capture = this.find('.js-capture');
this.els.cancel = this.find('.js-cancel');
this.els.switch = this.find('.js-switch');
this.els.icons = {
camera: this.find('.js-icon-camera'),
video: this.find('.js-icon-video')
};
// Clean up
delete this.template;
debug('rendered');
return this.bindEvents();
},
/**
* Respond to click events on the buttons
* other than the switch, which is a special
* case.
*
* We 'debouce' the callback to defend
* against button-bashing.
*
* @return {ControlsView} for chaining
* @private
*/
bindEvents: function() {
this.onButtonClick = debounce(this.onButtonClick, 300, true);
bind(this.els.thumbnail, 'click', this.onButtonClick);
bind(this.els.capture, 'click', this.onButtonClick);
bind(this.els.cancel, 'click', this.onButtonClick);
return this;
},
/**
* Create the draggable switch.
*
* We debouce the tapped callback to
* defend against button-bashing.
*
* @private
*/
setupSwitch: function() {
debug('setup dragger');
// Wait until the document is complete
// to avoid any forced sync reflows.
if (document.readyState !== 'complete') {
window.addEventListener('load', this.setupSwitch);
debug('deferred switch setup till after load');
return;
}
// Prefer existing drag (test hook)
this.drag = this.drag || new Drag({
handle: this.els.switchHandle,
container: this.els.switch
});
this.drag.on('tapped', debounce(this.onSwitchTapped, 300, true));
this.drag.on('ended', this.drag.snapToClosestEdge);
this.drag.on('translate', this.onSwitchTranslate);
this.drag.on('snapped', this.onSwitchSnapped);
this.drag.updateDimensions();
this.updateSwitchPosition();
// Tidy up
window.removeEventListener('load', this.setupSwitch);
},
setCaptureLabel: function(recording) {
this.els.capture.setAttribute('data-l10n-id',
recording ? 'stop-capture-button' : 'capture-button');
},
onSwitchSnapped: function(edges) {
var mode = this.switchPositions[edges.x];
var changed = mode !== this.get('mode');
if (changed) { this.onSwitchChanged(); }
},
onSwitchChanged: function() {
this.emit('modechanged');
},
onSwitchTapped: function(e) {
e.preventDefault();
e.stopPropagation();
debug('switch tapped');
this.onSwitchChanged();
},
onSwitchTranslate: function(e) {
this.setSwitchIcon(e.position.ratio.x);
},
setSwitchIcon: function(ratio) {
var skew = 2;
var ratioSkewed = ratio * skew;
var camera = Math.max(0, 1 - ratioSkewed);
var video = Math.max(0, -1 + ratioSkewed);
this.els.icons.camera.style.opacity = camera;
this.els.icons.video.style.opacity = video;
debug('set switch icon camera: %s, video: %s', camera, video);
},
/**
* Set view screen reader visibility. In some cases, though the view is behind
* an overlay and not hidden off screen, it still needs to be
* hidden/inaccessible from the screen reader.
*/
setScreenReaderVisible: function(visible) {
this.el.setAttribute('aria-hidden', !visible);
},
onButtonClick: function(e) {
e.stopPropagation();
debug('button click');
var name = e.currentTarget.getAttribute('name');
this.emit('click:' + name, e);
},
suspendModeSwitch: function(suspended) {
if (suspended) {
this.set('switch-toggle-disabled');
} else {
this.unset('switch-toggle-disabled');
}
},
setMode: function(mode) {
debug('set mode: %s', mode);
this.set('mode', mode);
this.switchPosition = this.switchPositions[mode];
var ratio = { left: 0, right: 1 }[this.switchPosition];
this.updateSwitchPosition();
this.setSwitchIcon(ratio);
// Set appropriate mode switch label for screen reader.
this.els.switch.setAttribute('data-l10n-id', mode + '-mode-button');
debug('mode set pos: %s', this.switchPosition);
},
updateSwitchPosition: function() {
debug('updateSwitchPosition');
if (!this.drag) { return; }
this.drag.set({ x: this.switchPosition });
debug('updated switch position: %s', this.switchPosition);
},
setThumbnail: function(blob) {
if (!this.els.image) {
this.els.image = new Image();
this.els.image.classList.add('test-thumbnail');
this.els.thumbnail.appendChild(this.els.image);
this.set('thumbnail', true);
} else {
window.URL.revokeObjectURL(this.els.image.src);
}
this.els.image.src = window.URL.createObjectURL(blob);
debug('thumbnail set');
},
removeThumbnail: function() {
if (this.els.image) {
this.els.thumbnail.removeChild(this.els.image);
window.URL.revokeObjectURL(this.els.image.src);
this.els.image = null;
}
this.set('thumbnail', false);
},
/**
* NOTE: The below functions are a first
* attempt at replacing the default View
* `.set()`, `.enable()` and `.disable()` APIs
* to avoid having to use attributes to style
* state in our CSS.
*/
set: function(key, value) {
if (typeof key !== 'string') { return; }
if (arguments.length === 1) { value = true; }
if (!value) { return this.unset(key); }
var attr = 'data-' + key;
var oldValue = this.el.getAttribute(attr);
var oldClass = oldValue && classFrom(key, oldValue);
var newClass = classFrom(key, value);
if (oldClass) { this.el.classList.remove(oldClass); }
if (newClass) { this.el.classList.add(newClass); }
this.el.setAttribute(attr, value);
debug('remove: %s, add: %s', oldClass, newClass);
debug('attr key: %s, value: %s', attr, value);
},
get: function(key) {
var attr = 'data-' + key;
return this.el.getAttribute(attr);
},
unset: function(key) {
var attr = 'data-' + key;
var value = this.el.getAttribute(attr);
this.el.classList.remove(classFrom(key, value));
this.el.removeAttribute(attr);
},
enable: function(key) {
this.set(key ? key + '-enabled' : 'enabled');
this.unset(key ? key + '-disabled' : 'disabled');
},
disable: function(key) {
this.set(key ? key + '-disabled' : 'disabled');
this.unset(key ? key + '-enabled' : 'enabled');
},
/**
* Localize the template based on a list of localizable elements - elsL10n. In
* case the template is loaded before l10n is ready, localize will peform the
* initial localization.
*/
localize: function() {
// Switch mode label depends on the mode that is currently set.
var mode = this.get('mode') || 'picture';
this.els.switch.setAttribute('data-l10n-id', mode + '-mode-button');
},
template: function() {
/*jshint maxlen:false*/
return '<div class="controls-left">' +
'<div class="controls-button controls-thumbnail-button test-thumbnail js-thumbnail rotates" ' +
'name="thumbnail" role="button" data-l10n-id="preview-button"></div>' +
'<div class="controls-button controls-cancel-pick-button test-cancel-pick rotates js-cancel" ' +
'name="cancel" data-icon="close" role="button" data-l10n-id="controls-button-close"></div>' +
'</div>' +
'<div class="controls-middle">' +
'<div class="capture-button test-capture rotates js-capture" name="capture" ' +
'data-l10n-id="capture-button" role="button">' +
'<div class="circle outer-circle"></div>' +
'<div class="circle inner-circle"></div>' +
'<div class="center" data-icon="camera" aria-hidden="true"></div>' +
'</div>' +
'</div>' +
'<div class="controls-right">' +
'<div class="mode-switch test-switch" name="switch">' +
'<div class="inner js-switch" role="button">' +
'<div class="mode-switch_bg-icon rotates" data-icon="camera" aria-hidden="true"></div>' +
'<div class="mode-switch_bg-icon rotates" data-icon="video" aria-hidden="true"></div>' +
'<div class="mode-switch_handle js-switch-handle" aria-hidden="true">' +
'<div class="mode-switch_current-icon camera rotates js-icon-camera" data-icon="camera" aria-hidden="true"></div>' +
'<div class="mode-switch_current-icon video rotates js-icon-video" data-icon="video" aria-hidden="true"></div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
}
});
/**
* Examples:
*
* classFrom('recording', true); //=> 'recording'
* classFrom('flash', 'on'); //=> 'flash-on'
* classFrom('recording', false); //=> ''
* classFrom('recording'); //=> 'recording'
* classFrom('recording', 'true'); //=> 'recording'
* classFrom('recording', 'false'); //=> ''
*
* @param {String} key
* @param {*} value
* @return {String}
*/
function classFrom(key, value) {
value = detectBooleans(value);
if (typeof value === 'boolean') {
return value ? key : '';
} else if (value) {
return key + '-' + value ;
} else {
return key;
}
}
function detectBooleans(value) {
if (typeof value === 'boolean') { return value; }
else if (value === 'true') { return true; }
else if (value === 'false') { return false; }
else { return value; }
}
});