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,82 @@
define([
'require',
'exports',
'module',
'lib/prepare-preview-blob',
'debug',
'views/confirm',
'lib/bind-all'
], function (require, exports, module) {
var prepareBlob = require('lib/prepare-preview-blob');
var debug = require('debug')('controller:confirm');
var ConfirmView = require('views/confirm');
var bindAll = require('lib/bind-all');
module.exports = function (options) {
return new ConfirmController(options);
};
module.exports.ConfirmController = ConfirmController;
function ConfirmController(app) {
this.app = app;
this.settings = app.settings;
this.activity = app.activity;
this.camera = app.camera;
this.container = app.el;
this.ConfirmView = app.ConfirmView || ConfirmView;
this.prepareBlob = app.prepareBlob || prepareBlob;
bindAll(this);
this.bindEvents();
debug('initialized');
}
ConfirmController.prototype.renderView = function () {
if (!this.activity.pick) {
return;
}
if (!this.view) {
this.view = new this.ConfirmView();
this.view.maxPreviewSize = window.CONFIG_MAX_IMAGE_PIXEL_SIZE;
this.view.render().appendTo(this.container);
this.view.once('click:select', this.onSelectMedia);
this.view.on('click:retake', this.onRetakeMedia);
this.view.on('loadingvideo', this.app.firer('busy'));
this.view.on('playingvideo', this.app.firer('ready'));
}
this.view.show();
this.app.set('confirmViewVisible', true);
};
ConfirmController.prototype.bindEvents = function () {
this.camera.on('newimage', this.renderView);
this.camera.on('change:recording', this.onRecordingChange);
this.app.on('newmedia', this.onNewMedia);
};
ConfirmController.prototype.onRecordingChange = function (recording) {
if (!this.activity.pick) {
return;
}
if (recording === 'starting') {
this.recording = true;
} else if (recording === 'error') {
this.recording = false;
} else if (recording === 'stopped' && this.recording) {
this.renderView();
}
};
ConfirmController.prototype.onNewMedia = function (newMedia) {
if (!this.activity.pick) {
return;
}
this.newMedia = newMedia;
if (newMedia.isVideo) {
this.view.showVideo(newMedia);
} else {
this.prepareBlob(this.newMedia.blob, this.view.showImage);
}
};
ConfirmController.prototype.onSelectMedia = function () {
this.app.emit('confirm:selected', this.newMedia);
};
ConfirmController.prototype.onRetakeMedia = function () {
this.view.hide();
this.view.clearMediaFrame();
this.app.set('confirmViewVisible', false);
};
});

View file

@ -0,0 +1,48 @@
define([
'require',
'exports',
'module',
'debug',
'views/countdown',
'lib/bind-all'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('controller:countdown');
var CountdownView = require('views/countdown');
var bindAll = require('lib/bind-all');
module.exports = function (app) {
return new CountdownController(app);
};
module.exports.CountdownController = CountdownController;
function CountdownController(app) {
bindAll(this);
this.app = app;
this.view = app.views.countdown || new CountdownView();
this.view.appendTo(app.el);
this.bindEvents();
debug('initialized');
}
CountdownController.prototype.bindEvents = function () {
this.app.on('countdown:started', this.start);
this.app.on('countdown:tick', this.update);
this.app.on('countdown:ended', this.clear);
};
CountdownController.prototype.start = function (seconds) {
this.update(seconds);
this.view.show();
debug('started');
};
CountdownController.prototype.update = function (seconds) {
if (seconds < 1) {
seconds = '';
}
var immanent = seconds <= 3;
this.view.set(seconds).setImmanent(immanent);
if (immanent && seconds > 0) {
this.app.emit('countdown:immanent');
}
};
CountdownController.prototype.clear = function () {
this.view.hide(this.view.reset);
};
});

View file

@ -0,0 +1,50 @@
define([
'require',
'exports',
'module',
'debug',
'views/indicators'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('controller:indicators');
var IndicatorsView = require('views/indicators');
module.exports = function (app) {
return new IndicatorsController(app);
};
module.exports.IndicatorsController = IndicatorsController;
function IndicatorsController(app) {
this.app = app;
this.settings = app.settings;
this.configure = this.configure.bind(this);
this.createView();
this.configure();
this.bindEvents();
debug('initialized');
}
IndicatorsController.prototype.createView = function () {
debug('create view');
this.view = this.app.views.indicators || new IndicatorsView();
this.view.appendTo(this.app.el);
debug('view created');
};
IndicatorsController.prototype.bindEvents = function () {
this.settings.countdown.on('change:selected', this.view.setter('countdown'));
this.settings.mode.on('change:selected', this.view.setter('mode'));
this.settings.hdr.on('change:selected', this.view.setter('hdr'));
this.app.on('change:batteryStatus', this.view.setter('battery'));
this.app.on('change:recording', this.view.setter('recording'));
this.app.on('settings:configured', this.configure);
this.app.on('settings:opened', this.view.setter('ariaHidden', true));
this.app.on('settings:closed', this.view.setter('ariaHidden', false));
this.app.on('previewgallery:opened', this.view.hide);
this.app.on('previewgallery:closed', this.view.show);
debug('events bound');
};
IndicatorsController.prototype.configure = function () {
debug('configuring');
this.view.set('hdr', this.settings.hdr.selected('key'));
this.view.set('countdown', this.settings.countdown.selected('key'));
this.view.set('battery', this.app.get('batteryStatus'));
debug('configured');
};
});

View file

@ -0,0 +1,151 @@
define([
'require',
'exports',
'module',
'debug',
'lib/bind-all'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('controller:media');
var bindAll = require('lib/bind-all');
module.exports = function (app) {
return new MediaController(app);
};
module.exports.MediaController = MediaController;
function MediaController(app) {
bindAll(this);
this.app = app;
this.require = app.require;
this.settings = app.settings;
this.bindEvents();
this.configure();
debug('initialized');
}
MediaController.prototype.bindEvents = function () {
this.app.on('storage:itemdeleted', this.onItemDeleted);
this.app.on('previewgallery:deletevideo', this.onItemDeleted);
this.app.on('previewgallery:deletepicture', this.onItemDeleted);
this.app.on('storage:changed', this.onStorageChanged);
this.app.on('newmedia', this.onNewMedia);
this.app.on('hidden', this.onHidden);
this.app.on('lazyloaded', this.configured);
var self = this;
this.app.once('capture', function () {
var lib = [
'lib/create-thumbnail-image',
'lib/prepare-preview-blob'
];
self.require(lib, function () {
});
});
debug('events bound');
};
MediaController.prototype.configure = function () {
this.items = [];
this.currentItem = null;
var dpr = window.devicePixelRatio;
this.thumbnailSize = {
width: this.settings.previewGallery.get('thumbnailWidth') * dpr,
height: this.settings.previewGallery.get('thumbnailHeight') * dpr
};
this.configured();
};
MediaController.prototype.configured = function () {
this.app.emit('media:configured', this.items);
};
MediaController.prototype.onNewMedia = function (item) {
if (this.app.activity.pick) {
return;
}
var self = this;
if (item.isVideo) {
addNewMedia(item);
} else {
this.require(['lib/prepare-preview-blob'], function (preparePreview) {
preparePreview(item.blob, function (metadata) {
metadata.blob = item.blob;
metadata.filepath = item.filepath;
addNewMedia(metadata);
});
});
}
function addNewMedia(item) {
self.items.unshift(item);
self.updateThumbnail();
}
};
MediaController.prototype.onStorageChanged = function (status) {
if (status === 'unavailable') {
this.configure();
this.updateThumbnail();
}
};
MediaController.prototype.onHidden = function () {
if (this.app.inSecureMode) {
this.configure();
this.updateThumbnail();
}
};
MediaController.prototype.updateThumbnail = function () {
var media = this.items[0] || null;
if (media === this.currentItem) {
return;
}
if (media === null) {
this.app.emit('newthumbnail', null);
return;
}
var blob;
var metadata = {
rotation: media.rotation,
mirrored: media.mirrored
};
if (media.isVideo) {
blob = media.poster.blob;
metadata.width = media.poster.width;
metadata.height = media.poster.height;
} else {
if (media.preview) {
var fullRatio = media.width / media.height;
var previewRatio = media.preview.width / media.preview.height;
if (Math.abs(fullRatio - previewRatio) < 0.01) {
blob = media.blob.slice(media.preview.start, media.preview.end, 'image/jpeg');
metadata.width = media.preview.width;
metadata.height = media.preview.height;
}
}
if (!blob) {
blob = media.blob;
metadata.width = media.width;
metadata.height = media.height;
}
}
var self = this;
this.require(['lib/create-thumbnail-image'], function (createThumbnailImage) {
createThumbnailImage(blob, metadata, self.thumbnailSize, thumb => {
self.currentItem = media;
self.app.emit('newthumbnail', thumb);
});
});
};
MediaController.prototype.onItemDeleted = function (data) {
var deleteIdx = -1;
var deletedFilepath = typeof data === 'object' ? data.path : data;
for (var n = 0; n < this.items.length; n++) {
if (this.items[n].filepath === deletedFilepath) {
deleteIdx = n;
break;
}
}
if (n === this.items.length) {
return;
}
var item = this.items[deleteIdx];
if (item.resizing) {
return;
}
this.items.splice(deleteIdx, 1);
this.updateThumbnail();
this.app.emit('media:deleted');
};
});

View file

@ -0,0 +1,226 @@
define([
'require',
'exports',
'module',
'debug',
'lib/string-utils',
'lib/bind-all',
'views/preview-gallery',
'CustomDialog'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('controller:preview-gallery');
var StringUtils = require('lib/string-utils');
var bindAll = require('lib/bind-all');
var PreviewGalleryView = require('views/preview-gallery');
var CustomDialog = require('CustomDialog');
module.exports = function (app) {
return new PreviewGalleryController(app);
};
module.exports.PreviewGalleryController = PreviewGalleryController;
function PreviewGalleryController(app) {
bindAll(this);
this.app = app;
this.require = app.require;
this.settings = app.settings;
this.dialog = app.dialog || CustomDialog;
this.resizeImageAndSave = app.resizeImageAndSave;
this.currentItemIndex = 0;
this.bindEvents();
debug('initialized');
}
PreviewGalleryController.prototype.bindEvents = function () {
this.app.on('media:configured', this.updatePreviewGallery);
this.app.on('media:deleted', this.updatePreviewGallery);
this.app.on('preview', this.openPreview);
this.app.on('hidden', this.closePreview);
debug('events bound');
};
PreviewGalleryController.prototype.resetPreviewGallery = function (items) {
this.items = items;
};
PreviewGalleryController.prototype.openPreview = function () {
if (this.app.activity.pick || this.view || this.app.hidden) {
return;
}
var maxPreviewSize = window.CONFIG_MAX_IMAGE_PIXEL_SIZE;
this.view = new PreviewGalleryView();
this.view.maxPreviewSize = maxPreviewSize;
this.view.render().appendTo(this.app.el);
this.view.on('click:gallery', this.onGalleryButtonClick);
this.view.on('click:share', this.shareCurrentItem);
this.view.on('click:delete', this.deleteCurrentItem);
this.view.on('click:back', this.closePreview);
this.view.on('swipe', this.handleSwipe);
this.view.on('click:options', this.onOptionsClick);
this.view.on('loadingvideo', this.app.firer('busy'));
this.view.on('playingvideo', this.app.firer('ready'));
var secureMode = this.app.inSecureMode;
this.view.set('secure-mode', secureMode);
this.view.open();
this.previewItem();
this.app.emit('previewgallery:opened');
};
PreviewGalleryController.prototype.closePreview = function () {
if (this.view) {
this.currentItemIndex = 0;
this.view.close();
this.view.destroy();
this.view = null;
}
this.app.emit('previewgallery:closed');
};
PreviewGalleryController.prototype.onGalleryButtonClick = function () {
if (this.app.inSecureMode) {
return;
}
var MozActivity = window.MozActivity;
this.mozActivity = new MozActivity({
name: 'browse',
data: { type: 'photos' }
});
};
PreviewGalleryController.prototype.onOptionsClick = function () {
if (this.app.inSecureMode) {
return;
}
this.view.showOptionsMenu();
};
PreviewGalleryController.prototype.shareCurrentItem = function () {
var index = this.currentItemIndex;
var item = this.items[index];
if (this.app.inSecureMode || !item || item.resizing) {
return;
}
var type = item.isVideo ? 'video/*' : 'image/*';
var filename = StringUtils.lastPathComponent(item.filepath);
var launchShareActivity = function (blob) {
var activity = new window.MozActivity({
name: 'share',
data: {
type: type,
number: 1,
blobs: [blob],
filenames: [filename],
filepaths: [item.filepath]
}
});
activity.onerror = function (e) {
console.warn('Share activity error:', activity.error.name);
};
};
if (item.isVideo) {
launchShareActivity(item.blob);
return;
}
var self = this;
item.resizing = true;
this.app.emit('busy', 'resizingImage');
var maxSize = this.settings.activity.get('maxSharePixelSize');
this.require(['lib/resize-image-and-save'], function (resize) {
if (!self.resizeImageAndSave) {
self.resizeImageAndSave = resize;
}
self.resizeImageAndSave({
blob: item.blob,
size: maxSize
}, function (resizedBlob) {
if (resizedBlob !== item.blob) {
item.blob = resizedBlob;
if (maxSize && maxSize.width && maxSize.height) {
item.width = maxSize.width;
item.height = maxSize.height;
} else if (item.rotation === 90 || item.rotation === 270) {
var tmp = item.width;
item.width = item.height;
item.height = tmp;
}
delete item.rotation;
}
delete item.resizing;
self.app.emit('ready');
launchShareActivity(resizedBlob);
});
});
};
PreviewGalleryController.prototype.deleteCurrentItem = function () {
if (this.app.inSecureMode) {
return;
}
var index = this.currentItemIndex;
var item = this.items[index];
var filepath = item.filepath;
var dialog = this.dialog;
var self = this;
var msg;
if (item.isVideo) {
msg = 'delete-video?';
} else {
msg = 'delete-photo?';
}
dialog.show('', msg, {
title: 'cancel',
callback: closeDialog
}, {
title: 'delete',
callback: deleteItem,
recommend: false
});
function closeDialog() {
dialog.hide();
}
function deleteItem() {
dialog.hide();
if (item.isVideo) {
self.app.emit('previewgallery:deletevideo', filepath);
} else {
self.app.emit('previewgallery:deletepicture', filepath);
}
}
};
PreviewGalleryController.prototype.updatePreviewGallery = function (items) {
if (items) {
this.items = items;
}
if (this.items.length === 0) {
this.closePreview();
} else {
if (this.currentItemIndex === this.items.length) {
this.currentItemIndex = this.items.length - 1;
}
var isOpened = this.view ? true : false;
if (isOpened) {
this.previewItem();
}
}
};
PreviewGalleryController.prototype.handleSwipe = function (direction) {
if (direction === 'left') {
this.next();
} else if (direction === 'right') {
this.previous();
}
};
PreviewGalleryController.prototype.next = function () {
if (this.currentItemIndex < this.items.length - 1) {
this.currentItemIndex += 1;
this.previewItem();
}
};
PreviewGalleryController.prototype.previous = function () {
if (this.currentItemIndex > 0) {
this.currentItemIndex -= 1;
this.previewItem();
}
};
PreviewGalleryController.prototype.previewItem = function () {
var index = this.currentItemIndex;
var item = this.items[index];
this.view.updateCountText(index + 1, this.items.length);
if (item.isVideo) {
this.view.showVideo(item);
} else {
this.view.showImage(item);
}
};
});

View file

@ -0,0 +1,40 @@
define([
'require',
'exports',
'module',
'debug',
'views/recording-timer'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('controller:recording-timer');
var RecordingTimerView = require('views/recording-timer');
module.exports = function (app) {
return new RecordingTimerController(app);
};
module.exports.RecordingTimerController = RecordingTimerController;
function RecordingTimerController(app) {
this.onRecordingChange = this.onRecordingChange.bind(this);
this.app = app;
this.createView();
this.bindEvents();
debug('initialized');
}
RecordingTimerController.prototype.createView = function () {
this.view = this.app.view || new RecordingTimerView();
this.view.appendTo(this.app.el);
};
RecordingTimerController.prototype.bindEvents = function () {
this.app.on('change:recording', this.onRecordingChange);
this.app.on('camera:recorderTimeUpdate', this.view.value);
debug('events bound');
};
RecordingTimerController.prototype.onRecordingChange = function (recording) {
debug('recording: %s', recording);
if (!recording) {
this.view.hide();
return;
}
this.view.value(0);
this.view.show();
};
});

View file

@ -0,0 +1,33 @@
define([
'require',
'exports',
'module',
'debug',
'lib/sounds'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('controller:sounds');
var Sounds = require('lib/sounds');
module.exports = function (app) {
return new SoundsController(app);
};
module.exports.SoundsController = SoundsController;
function SoundsController(app) {
var list = app.settings.sounds.get('list');
this.sounds = new Sounds(list);
this.app = app;
this.bindEvents();
debug('initialized');
}
SoundsController.prototype.bindEvents = function () {
this.app.on('change:recording', this.onRecordingChange.bind(this));
this.app.on('camera:willrecord', this.sounds.player('recordingStart'));
this.app.on('camera:shutter', this.sounds.player('shutter'));
this.app.on('countdown:immanent', this.sounds.player('countdown'));
};
SoundsController.prototype.onRecordingChange = function (recording) {
if (!recording) {
this.sounds.play('recordingEnd');
}
};
});

View file

@ -0,0 +1,65 @@
define([
'require',
'exports',
'module',
'debug',
'views/zoom-bar',
'lib/bind-all'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('controller:zoom-bar');
var ZoomBar = require('views/zoom-bar');
var bindAll = require('lib/bind-all');
module.exports = function (app) {
return new ZoomBarController(app);
};
module.exports.ZoomBarController = ZoomBarController;
function ZoomBarController(app) {
bindAll(this);
this.app = app;
this.camera = app.camera;
this.createView();
this.bindEvents();
this.configureZoomBar();
debug('initialized');
}
ZoomBarController.prototype.createView = function () {
this.view = this.app.views.zoombar || new ZoomBar();
this.view.hide();
this.view.appendTo(this.app.el);
};
ZoomBarController.prototype.bindEvents = function () {
this.view.on('change', this.onChange);
this.camera.on('zoomconfigured', this.onZoomConfigured);
this.camera.on('zoomchanged', this.setZoom);
};
ZoomBarController.prototype.configureZoomBar = function () {
var zoomSupported = this.camera.isZoomSupported();
var zoomEnabled = this.app.settings.zoom.enabled();
this.enableZoom = zoomSupported && zoomEnabled;
};
ZoomBarController.prototype.onChange = function (value) {
var minimumZoom = this.camera.getMinimumZoom();
var maximumZoom = this.camera.getMaximumZoom();
var range = maximumZoom - minimumZoom;
var zoom = range * value / 100 + minimumZoom;
this.camera.setZoom(zoom);
};
ZoomBarController.prototype.onZoomConfigured = function (zoom) {
this.configureZoomBar();
this.setZoom(zoom);
this.view.hide();
};
ZoomBarController.prototype.setZoom = function (zoom) {
if (!this.enableZoom) {
return;
}
debug('set zoom');
var minimumZoom = this.camera.getMinimumZoom();
var maximumZoom = this.camera.getMaximumZoom();
var range = maximumZoom - minimumZoom;
var percent = (zoom - minimumZoom) / range * 100;
this.view.setValue(percent);
debug('zoom set');
};
});

View file

@ -0,0 +1,19 @@
define([
'require',
'exports',
'module',
'cropResizeRotate'
], function (require, exports, module) {
'use strict';
var cropResizeRotate = require('cropResizeRotate');
module.exports = function (imageBlob, metadata, thumbnailSize, done) {
cropResizeRotate(imageBlob, null, thumbnailSize, null, metadata, function (error, resizedBlob) {
if (error) {
console.error('Error while resizing image: ' + error);
done(imageBlob);
return;
}
done(resizedBlob);
});
};
});

View file

@ -0,0 +1,84 @@
define([
'require',
'exports',
'module',
'asyncStorage',
'debug',
'format'
], function (require, exports, module) {
'use strict';
var asyncStorage = require('asyncStorage');
var debug = require('debug')('dcf');
var format = require('format');
var dcfConfigLoaded = false;
var deferredArgs = null;
var defaultSeq = {
file: 1,
dir: 100
};
var dcfConfig = {
key: 'dcf_key',
seq: null,
postFix: 'MZLLA',
prefix: {
video: 'VID_',
image: 'IMG_'
},
ext: {
video: '3gp',
image: 'jpg'
}
};
exports.init = function () {
debug('initializing');
exports.checkFileCounter(function () {
if (deferredArgs) {
var args = deferredArgs;
exports.createDCFFilename(args.storage, args.type, args.callback);
deferredArgs = null;
}
debug('initialized');
});
};
exports.checkFileCounter = function (callback) {
asyncStorage.getItem(dcfConfig.key, function (value) {
dcfConfigLoaded = true;
dcfConfig.seq = value ? value : defaultSeq;
if (callback) {
callback();
}
});
};
exports.createDCFFilename = function (storage, type, callback) {
if (!dcfConfigLoaded) {
deferredArgs = {
storage: storage,
type: type,
callback: callback
};
return;
}
var dir = 'DCIM/' + dcfConfig.seq.dir + dcfConfig.postFix + '/';
var filename = dcfConfig.prefix[type] + format.padLeft(dcfConfig.seq.file, 4, '0') + '.' + dcfConfig.ext[type];
var filepath = dir + filename;
var req = storage.get(filepath);
req.onsuccess = function () {
dcfConfig.seq.file = 1;
dcfConfig.seq.dir += 1;
asyncStorage.setItem(dcfConfig.key, dcfConfig.seq, function () {
exports.createDCFFilename(storage, type, callback);
});
};
req.onerror = function () {
if (dcfConfig.seq.file < 9999) {
dcfConfig.seq.file += 1;
} else {
dcfConfig.seq.file = 1;
dcfConfig.seq.dir += 1;
}
asyncStorage.setItem(dcfConfig.key, dcfConfig.seq, function () {
callback(filepath, filename, dir);
});
};
};
});

View file

@ -0,0 +1,26 @@
define([
'require',
'exports',
'module',
'format'
], function (require, exports, module) {
'use strict';
var format = require('format');
function digits(value) {
return format.padLeft(value, 2, '0');
}
module.exports = function (ms) {
var totalSeconds = ms / 1000;
var seconds = Math.round(totalSeconds % 60);
var minutes = Math.floor(totalSeconds / 60);
var hours;
if (minutes < 60) {
return digits(minutes) + ':' + digits(seconds);
} else {
hours = Math.floor(minutes / 60);
minutes = Math.round(minutes % 60);
return hours + ':' + digits(minutes) + ':' + digits(seconds);
}
return '';
};
});

View file

@ -0,0 +1,51 @@
define([
'require',
'exports',
'module',
'GestureDetector'
], function (require, exports, module) {
'use strict';
var GestureDetector = require('GestureDetector');
module.exports = addPanAndZoomHandlers;
function addPanAndZoomHandlers(frame, swipeCallback) {
var container = frame.container;
var gestureDetector = new GestureDetector(container);
gestureDetector.startDetecting();
var swipeAmount = 0;
container.addEventListener('dbltap', handleDoubleTap);
container.addEventListener('transform', handleTransform);
container.addEventListener('pan', handlePan);
if (swipeCallback) {
container.addEventListener('swipe', handleSwipe);
}
function handleDoubleTap(e) {
var scale;
if (frame.fit.scale > frame.fit.baseScale) {
scale = frame.fit.baseScale / frame.fit.scale;
} else {
scale = 2;
}
frame.zoom(scale, e.detail.clientX, e.detail.clientY, 200);
}
function handleTransform(e) {
frame.zoom(e.detail.relative.scale, e.detail.midpoint.clientX, e.detail.midpoint.clientY);
}
function handlePan(e) {
var dx = e.detail.relative.dx;
var dy = e.detail.relative.dy;
if (swipeCallback) {
dx += swipeAmount;
swipeAmount = frame.pan(dx, dy);
swipeCallback(swipeAmount);
} else {
frame.pan(dx, dy);
}
}
function handleSwipe(e) {
if (swipeAmount !== 0) {
swipeCallback(swipeAmount, e.detail.vx);
swipeAmount = 0;
}
}
}
});

View file

@ -0,0 +1,31 @@
define([
'require',
'exports',
'module',
'jpegMetaDataParser'
], function (require, exports, module) {
'use strict';
var parseJpegMetadata = require('jpegMetaDataParser');
module.exports = function (blob, done) {
parseJpegMetadata(blob, onJpegParsed);
function onJpegParsed(metadata) {
metadata.blob = blob;
if (!metadata.preview) {
done(metadata);
return;
}
var start = metadata.preview.start;
var end = metadata.preview.end;
var previewBlob = blob.slice(start, end, 'image/jpeg');
parseJpegMetadata(previewBlob, onSuccess, onError);
function onSuccess(previewMetadata) {
metadata.preview.width = previewMetadata.width;
metadata.preview.height = previewMetadata.height;
done(metadata);
}
function onError() {
done(metadata);
}
}
};
});

View file

@ -0,0 +1,41 @@
define([
'require',
'exports',
'module',
'lib/storage',
'cropResizeRotate'
], function (require, exports, module) {
'use strict';
var Storage = require('lib/storage');
var cropResizeRotate = require('cropResizeRotate');
module.exports = function (options, done) {
var blob = options.blob;
var outputSize = options.width && options.height ? {
width: options.width,
height: options.height
} : options.size || null;
cropResizeRotate(blob, null, outputSize, 'image/jpeg+exif', function (error, resizedBlob) {
if (error) {
console.error('Error while resizing image: ' + error);
done(blob);
return;
}
if (resizedBlob === blob) {
done(blob);
return;
}
var storage = new Storage();
storage.deletePicture(blob.name, addPicture);
function addPicture(error) {
if (error) {
done(blob);
return;
}
storage.addPicture(resizedBlob, { filepath: blob.name }, onSavedPicture);
}
function onSavedPicture(error, filepath, absolutePath, fileBlob) {
done(fileBlob);
}
});
};
});

View file

@ -0,0 +1,75 @@
define([
'require',
'exports',
'module'
], function (require, exports, module) {
'use strict';
module.exports = Sounds;
function Sounds(list) {
this.items = {};
(list || []).forEach(this.add, this);
}
Sounds.prototype.add = function (data) {
var self = this;
var sound = {
name: data.name,
url: data.url,
setting: data.setting,
enabled: false
};
this.items[data.name] = sound;
this.isEnabled(sound, function (value) {
self.setEnabled(sound, value);
self.observeSetting(sound);
});
};
Sounds.prototype.isEnabled = function (sound, done) {
setTimeout(function () {
var mozSettings = navigator.mozSettings;
var key = sound.setting;
if (!mozSettings) {
return;
}
mozSettings.createLock().get(key).onsuccess = onSuccess;
function onSuccess(e) {
var result = e.target.result[key];
done(result);
}
});
};
Sounds.prototype.observeSetting = function (sound) {
var mozSettings = navigator.mozSettings;
var key = sound.setting;
var self = this;
if (mozSettings) {
mozSettings.addObserver(key, function (e) {
self.setEnabled(sound, e.settingValue);
});
}
};
Sounds.prototype.setEnabled = function (sound, value) {
sound.enabled = value;
};
Sounds.prototype.play = function (name) {
var sound = this.items[name];
if (!sound.audio) {
sound.audio = this.createAudio(sound.url);
}
this.playSound(sound);
};
Sounds.prototype.player = function (name) {
return function () {
this.play(name);
}.bind(this);
};
Sounds.prototype.playSound = function (sound) {
if (sound.enabled) {
sound.audio.cloneNode(true).play();
}
};
Sounds.prototype.createAudio = function (url) {
var audio = new Audio(url);
audio.mozAudioChannelType = 'notification';
return audio;
};
});

View file

@ -0,0 +1,20 @@
define(['require'], function (require) {
'use strict';
var StringUtils = {};
StringUtils.toCamelCase = function (str) {
str = str.toLowerCase().replace(/_/g, '-');
return str.replace(/-([a-z])/g, function (g) {
return g[1].toUpperCase();
});
};
StringUtils.toHyphenate = function (str) {
return str.replace(/([a-z][A-Z])/g, function (g) {
return g[0] + '-' + g[1].toLowerCase();
});
};
StringUtils.lastPathComponent = function (path) {
var pathComponents = path.split('/');
return pathComponents[pathComponents.length - 1];
};
return StringUtils;
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,103 @@
define([
'require',
'exports',
'module',
'debug',
'lib/panzoom',
'lib/orientation',
'MediaFrame',
'lib/bind',
'view'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:confirm');
var addPanAndZoomHandlers = require('lib/panzoom');
var orientation = require('lib/orientation');
var MediaFrame = require('MediaFrame');
var bind = require('lib/bind');
var View = require('view');
module.exports = View.extend({
name: 'confirm',
initialize: function () {
this.on('destroy', this.onDestroy);
},
render: function () {
this.el.innerHTML = this.template();
this.els.mediaFrame = this.find('.js-media-frame');
this.els.retake = this.find('.js-retake');
this.els.select = this.find('.js-select');
this.disableButtons();
this.setupMediaFrame();
delete this.template;
debug('rendered');
return this.bindEvents();
},
bindEvents: function () {
bind(this.els.retake, 'click', this.onButtonClick);
bind(this.els.select, 'click', this.onButtonClick);
return this;
},
setupMediaFrame: function () {
this.mediaFrame = new MediaFrame(this.els.mediaFrame, true, this.maxPreviewSize);
this.mediaFrame.video.onloading = this.onVideoLoading;
this.mediaFrame.video.onplaying = this.onVideoPlaying;
addPanAndZoomHandlers(this.mediaFrame);
window.addEventListener('resize', this.onResize);
return this;
},
onVideoLoading: function () {
this.emit('loadingvideo', 'loadingVideo');
},
onVideoPlaying: function () {
this.emit('playingvideo');
},
clearMediaFrame: function () {
this.mediaFrame.clear();
this.disableButtons();
},
hide: function () {
this.el.classList.add('hidden');
orientation.lock();
},
show: function () {
this.el.classList.remove('hidden');
orientation.unlock();
},
disableButtons: function () {
this.els.retake.setAttribute('disabled', true);
this.els.select.setAttribute('disabled', true);
},
enableButtons: function () {
this.els.retake.removeAttribute('disabled');
this.els.select.removeAttribute('disabled');
},
showImage: function (image) {
this.enableButtons();
this.mediaFrame.displayImage(image.blob, image.width, image.height, image.preview, image.rotation, image.mirrored);
return this;
},
showVideo: function (video) {
this.enableButtons();
this.mediaFrame.displayVideo(video.blob, video.poster.blob, video.poster.width / video.poster.height, video.rotation);
return this;
},
template: function () {
return '<div class="confirm-media-frame js-media-frame"></div>' + '<footer id="confirm-controls" class="confirm-controls">' + '<button class="retake-button js-retake" data-l10n-id="retake-button" ' + 'name="retake"></button>' + '<button class="select-button test-confirm-select js-select" ' + 'data-l10n-id="select-button" name="select"></button></footer>';
},
onButtonClick: function (event) {
var el = event.currentTarget;
var name = el.getAttribute('name');
this.emit('click:' + name);
},
onResize: function () {
this.mediaFrame.resize();
if (this.mediaFrame.displayingVideo) {
this.mediaFrame.video.setPlayerSize();
}
},
onDestroy: function () {
window.removeEventListener('resize', this.onResize);
this.mediaFrame.clear();
}
});
});

View file

@ -0,0 +1,52 @@
define([
'require',
'exports',
'module',
'debug',
'view'
], function (require, exports, module) {
var debug = require('debug')('view:countdown');
var View = require('view');
module.exports = View.extend({
name: 'countdown',
initialize: function () {
this.render();
},
render: function () {
this.el.innerHTML = this.template();
this.els.count = this.find('.js-count');
delete this.template;
debug('rendered');
return this;
},
set: function (time) {
this.els.count.textContent = time;
this.el.classList.remove('shrink');
this.reflow = this.el.offsetTop;
this.el.classList.add('shrink');
debug('set time: %s, near: %s', time);
return this;
},
show: function () {
this.el.classList.remove('hidden');
this.el.classList.add('visible');
},
hide: function (callback) {
this.el.classList.remove('visible');
this.el.classList.add('hidden');
setTimeout(callback, 200);
return this;
},
setImmanent: function (value) {
this.el.classList.toggle('immanent', value);
},
reset: function () {
this.els.count.textContent = '';
this.el.classList.remove('immanent', 'shrink');
return this;
},
template: function () {
return '<div class="countdown_circle-1"></div>' + '<div class="countdown_circle-2"></div>' + '<div class="countdown_count">' + '<div class="rotates js-count"></div>' + '</div>';
}
});
});

View file

@ -0,0 +1,27 @@
define([
'require',
'exports',
'module',
'debug',
'view'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:indicators');
var View = require('view');
module.exports = View.extend({
name: 'indicators',
tag: 'ul',
initialize: function () {
this.render();
},
render: function () {
this.el.innerHTML = this.template();
delete this.template;
debug('rendered');
return this;
},
template: function () {
return '<li class="indicator_countdown rotates" data-icon="self-timer" ' + 'data-l10n-id="self-timer-indicator"></li>' + '<li class="indicator_hdr rotates" data-icon="hdr" ' + 'data-l10n-id="hdr-indicator"></li>' + '<li class="indicator_geolocation rotates" data-icon="location" ' + 'data-l10n-id="location-indicator"></li>' + '<li class="indicator_battery rotates" data-icon="battery-3" ' + 'data-l10n-id="battery-low-indicator"></li>';
}
});
});

View file

@ -0,0 +1,84 @@
define([
'require',
'exports',
'module',
'debug',
'view',
'lib/bind'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:overlay');
var View = require('view');
var bind = require('lib/bind');
module.exports = View.extend({
className: 'overlay',
initialize: function (options) {
var data = this.getData(options.type);
if (!data) {
return;
}
this.data('type', options.type);
this.data('closable', options.closable);
this.render(data);
},
render: function (data) {
this.el.innerHTML = this.template(data);
this.els.buttons = { close: this.find('.js-close-btn') };
delete this.template;
debug('rendered');
return this.bindEvents();
},
rendered: function () {
return this.el.innerHTML.length > 0;
},
bindEvents: function () {
bind(this.els.buttons.close, 'click', this.onButtonClick);
return this;
},
template: function (data) {
return '<form role="dialog" data-type="confirm">' + '<section>' + '<h1 class="overlay-title" data-l10n-id="' + data.title + '"></h1>' + '<p id="overlay-text" data-l10n-id="' + data.body + '"></p>' + '</section>' + '<menu class="overlay-menu-close">' + '<button class="full js-close-btn" type="button" ' + 'data-l10n-id="' + data.closeButtonText + '" name="close-btn">' + '</button>' + '</menu>' + '</form>';
},
getData: function (type) {
var data = {};
switch (type) {
case 'unavailable':
data.title = 'nocard2-title';
data.body = 'nocard3-text';
break;
case 'nospace':
data.title = 'nospace2-title';
data.body = 'nospace2-text';
break;
case 'shared':
data.title = 'pluggedin2-title';
data.body = 'pluggedin2-text';
break;
case 'shutdown':
data.title = 'battery-shutdown-title';
data.body = 'battery-shutdown-text';
break;
case 'request-fail':
data.title = 'camera-unavailable-title';
data.body = 'camera-unavailable-text';
break;
default:
return false;
}
data.closeButtonText = 'close-button';
return data;
},
data: function (key, value) {
switch (arguments.length) {
case 1:
return this.el.getAttribute('data-' + key);
case 2:
this.el.setAttribute('data-' + key, value);
}
},
onButtonClick: function (event) {
var el = event.currentTarget;
var name = el.getAttribute('name');
this.emit('click:' + name);
}
});
});

View file

@ -0,0 +1,230 @@
define([
'require',
'debug',
'lib/panzoom',
'lib/orientation',
'MediaFrame',
'lib/bind',
'attach',
'view',
'gaia-header'
], function (require) {
'use strict';
var debug = require('debug')('view:preview-gallery');
var addPanAndZoomHandlers = require('lib/panzoom');
var orientation = require('lib/orientation');
var MediaFrame = require('MediaFrame');
var bind = require('lib/bind');
var attach = require('attach');
var View = require('view');
require('gaia-header');
var SWIPE_DISTANCE_THRESHOLD = window.innerWidth / 3;
var SWIPE_VELOCITY_THRESHOLD = 1;
var SWIPE_DURATION = 150;
var FADE_IN_DURATION = 450;
return View.extend({
name: 'preview-gallery',
className: 'offscreen',
initialize: function () {
debug('initialized');
},
render: function () {
this.el.innerHTML = this.template();
this.els.frameContainer = this.find('.js-frame-container');
this.els.previewMenu = this.find('.js-preview-menu');
this.els.mediaFrame = this.find('.js-media-frame');
this.els.countText = this.find('.js-count-text');
this.els.options = this.find('.js-options');
this.els.header = this.find('.js-header');
this.els.share = this.find('.js-share');
window.dispatchEvent(new CustomEvent('lazyload', { detail: this.el }));
this.configure();
delete this.template;
debug('rendered');
return this.bindEvents();
},
bindEvents: function () {
bind(this.el, 'tap', this.onTap);
bind(this.els.header, 'action', this.firer('click:back'));
bind(this.els.options, 'click', this.onButtonClick);
bind(this.els.share, 'click', this.onButtonClick);
bind(this.els.mediaFrame, 'wheel', this.onFrameWheel);
return this;
},
configure: function () {
this.currentIndex = this.lastIndex = 0;
this.frame = new MediaFrame(this.els.mediaFrame, true, this.maxPreviewSize);
this.frame.video.onloading = this.onVideoLoading;
this.frame.video.onplaying = this.onVideoPlaying;
this.frame.video.onpaused = this.onVideoPaused;
addPanAndZoomHandlers(this.frame, this.swipeCallback);
},
template: function () {
return '<div class="preview-menu js-preview-menu">' + '<gaia-header class="js-header" action="back" ignore-dir>' + '<h1 data-l10n-id="preview">Preview</h1>' + '<button class="preview-share-icon js-share"' + 'name="share" data-icon="share" ' + 'data-l10n-id="share-button"></button>' + '<button class="preview-option-icon ' + 'js-options" name="options" data-icon="more" ' + 'data-l10n-id="more-button"></button>' + '</gaia-header>' + '</div>' + '<div class="frame-container js-frame-container">' + '<div class="media-frame js-media-frame"></div>' + '</div>' + '<div class="count-text js-count-text"></div>';
},
onTap: function () {
if (this.videoPlaying) {
return;
}
var isShown = this.els.previewMenu.classList.contains('visible');
if (isShown) {
this.previewMenuFadeOut();
} else {
this.previewMenuFadeIn();
}
},
previewMenuFadeIn: function () {
this.els.previewMenu.classList.add('visible');
},
previewMenuFadeOut: function () {
this.els.previewMenu.classList.remove('visible');
},
onFrameWheel: function (event) {
if (event.deltaMode !== event.DOM_DELTA_PAGE || event.deltaY) {
return;
}
if (this.videoPlaying) {
this.onVideoPaused();
}
if (event.deltaX > 0) {
this.emit('swipe', 'left');
} else if (event.deltaX < 0) {
this.emit('swipe', 'right');
}
},
swipeCallback: function (swipeAmount, swipeVelocity) {
var self = this;
if (swipeVelocity === undefined) {
this.els.frameContainer.style.transform = 'translate(' + swipeAmount + 'px, 0)';
return;
}
var direction, translation;
if (swipeAmount > 0 && swipeVelocity > 0 && this.currentIndex > 1 && (swipeAmount > SWIPE_DISTANCE_THRESHOLD || swipeVelocity > SWIPE_VELOCITY_THRESHOLD)) {
direction = 'right';
translation = '100%';
} else if (swipeAmount <= 0 && swipeVelocity <= 0 && this.currentIndex < this.lastIndex && (swipeAmount < -SWIPE_DISTANCE_THRESHOLD || swipeVelocity < -SWIPE_VELOCITY_THRESHOLD)) {
direction = 'left';
translation = '-100%';
}
if (!direction) {
animate('transform', 'translate(0,0)', SWIPE_DURATION);
return;
}
if (this.videoPlaying) {
this.onVideoPaused();
}
animate('transform', 'translate(' + translation + ', 0)', SWIPE_DURATION, function () {
self.emit('swipe', direction);
window.requestAnimationFrame(function () {
self.els.frameContainer.style.opacity = 0;
self.els.frameContainer.style.transform = 'translate(0,0)';
window.requestAnimationFrame(function () {
animate('opacity', 1, FADE_IN_DURATION);
});
});
});
function animate(property, value, duration, done) {
var e = self.els.frameContainer;
e.addEventListener('transitionend', onTransitionEnd);
e.style.transitionProperty = property;
e.style.transitionDuration = duration + 'ms';
e.style[property] = value;
function onTransitionEnd() {
e.removeEventListener('transitionend', onTransitionEnd);
delete e.style.transitionProperty;
delete e.style.transitionDuration;
if (done) {
done();
}
}
}
},
onButtonClick: function (e, el) {
if (this.videoPlaying) {
return;
}
el = el || e.currentTarget;
var name = el.getAttribute('name');
if (this.optionsMenuContainer) {
this.hideOptionsMenu();
}
this.emit('click:' + name, e);
e.stopPropagation();
},
open: function () {
window.addEventListener('resize', this.onResize);
orientation.unlock();
this.previewMenuFadeIn();
this.el.classList.remove('offscreen');
},
close: function () {
window.removeEventListener('resize', this.onResize);
orientation.lock();
this.previewMenuFadeOut();
this.el.classList.add('offscreen');
this.frame.clear();
},
updateCountText: function (current, total) {
this.currentIndex = current;
this.lastIndex = total;
this.els.countText.textContent = current + '/' + total;
this.els.mediaFrame.setAttribute('data-l10n-id', 'media-frame');
this.els.mediaFrame.setAttribute('data-l10n-args', JSON.stringify({
total: total,
current: current
}));
},
onResize: function () {
this.frame.resize();
if (this.frame.displayingVideo) {
this.frame.video.setPlayerSize();
}
},
showImage: function (image) {
this.frame.displayImage(image.blob, image.width, image.height, image.preview, image.rotation, image.mirrored);
},
showVideo: function (video) {
this.frame.displayVideo(video.blob, video.poster.blob, video.poster.width / video.poster.height, video.rotation);
},
onVideoLoading: function () {
this.emit('loadingvideo', 'loadingVideo');
},
onVideoPlaying: function () {
if (this.videoPlaying) {
return;
}
this.videoPlaying = true;
this.previewMenuFadeOut();
this.emit('playingvideo');
},
onVideoPaused: function () {
if (!this.videoPlaying) {
return;
}
this.videoPlaying = false;
this.previewMenuFadeIn();
},
showOptionsMenu: function () {
this.optionsMenuContainer = document.createElement('div');
this.optionsMenuContainer.innerHTML = this.optionTemplate();
this.el.appendChild(this.optionsMenuContainer);
this.el.classList.add('action-menu');
this.menu = this.find('.js-menu');
var cancelButton = this.find('.js-cancel');
bind(cancelButton, 'click', this.hideOptionsMenu);
if (this.menu) {
attach.on(this.menu, 'click', '.js-btn', this.onButtonClick);
}
},
hideOptionsMenu: function () {
if (this.optionsMenuContainer) {
this.el.classList.remove('action-menu');
this.optionsMenuContainer.parentElement.removeChild(this.optionsMenuContainer);
this.optionsMenuContainer = null;
}
},
optionTemplate: function () {
return '<form class="visible" data-type="action"' + 'role="dialog" data-z-index-level="action-menu">' + '<header data-l10n-id="options">Options</header>' + '<menu class="js-menu">' + '<button class="js-btn" name="gallery" data-l10n-id="open-gallery">' + 'Open Gallery' + '</button>' + '<button class="js-btn" name="delete" data-l10n-id="delete">' + 'Delete' + '</button>' + '<button class="js-cancel" data-action="cancel" data-l10n-id="cancel">' + 'Cancel' + '</button>' + '</menu>' + '</form>';
}
});
});

View file

@ -0,0 +1,24 @@
define([
'require',
'exports',
'module',
'debug',
'lib/format-timer',
'view'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:recording-timer');
var formatTimer = require('lib/format-timer');
var View = require('view');
module.exports = View.extend({
name: 'recording-timer',
initialize: function () {
this.value(0);
this.el.setAttribute('role', 'timer');
},
value: function (value) {
this.el.textContent = formatTimer(value);
debug('set value: %s', value);
}
});
});

View file

@ -0,0 +1,76 @@
define([
'require',
'exports',
'module',
'debug',
'attach',
'view'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:setting-options');
var attach = require('attach');
var View = require('view');
module.exports = View.extend({
name: 'setting-options',
initialize: function (options) {
this.model = options.model;
this.l10n = options.l10n || document.l10n;
this.on('destroy', this.onDestroy);
attach(this.el, 'click', 'li', this.onOptionClick);
attach(this.el, 'click', '.js-back', this.firer('click:back'));
this.model.on('change:selected', this.onSelectedChange);
debug('initialized');
},
onDestroy: function () {
this.model.off('change:selected', this.onSelectedChange);
},
onOptionClick: function (event, el) {
var key = el.getAttribute('data-key');
this.emit('click:option', key, this.model);
},
onSelectedChange: function (key) {
var next = this.els[key];
this.els.selected.classList.remove('selected');
next.classList.add('selected');
this.els.selected = next;
},
render: function () {
var data = this.model.get();
this.selectedKey = data.selected;
this.el.innerHTML = this.template(data);
this.els.ul = this.find('.js-list');
var localizable = data.optionsLocalizable === false ? false : true;
data.options.forEach(this.renderOption.bind(this, localizable));
delete this.template;
debug('rendered');
return this;
},
renderOption: function (localizable, option) {
var li = document.createElement('li');
var isSelected = option.key === this.selectedKey;
if (localizable) {
li.setAttribute('data-l10n-id', option.title);
this.l10n.formatValue(option.title).then(title => {
li.setAttribute('aria-label', title);
});
} else {
li.textContent = option.title;
li.setAttribute('aria-label', option.title);
}
li.setAttribute('data-key', option.key);
li.setAttribute('role', 'option');
li.className = 'setting-option';
li.dataset.icon = 'tick';
this.els.ul.appendChild(li);
this.els[option.key] = li;
if (isSelected) {
li.classList.add('selected');
li.setAttribute('aria-selected', true);
this.els.selected = li;
}
},
template: function (data) {
return '<div class="inner">' + '<div class="settings_header">' + '<div class="settings-back-btn js-back" ' + 'data-icon="left" role="button" data-l10n-id="back-button"></div>' + '<h2 aria-level="1" class="settings_title" data-l10n-id="' + data.header + '"></h2>' + '</div>' + '<div class="settings_items">' + '<ul role="listbox" class="inner js-list"></ul>' + '</div>' + '</div>';
}
});
});

View file

@ -0,0 +1,58 @@
define([
'require',
'exports',
'module',
'debug',
'lib/bind',
'view'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:setting');
var bind = require('lib/bind');
var View = require('view');
module.exports = View.extend({
tag: 'li',
name: 'setting',
initialize: function (options) {
this.l10n = options.l10n || document.l10n;
this.model = options.model;
this.model.on('change', this.render);
this.on('destroy', this.onDestroy);
this.el.dataset.icon = this.model.get('icon');
this.el.classList.add('test-' + this.model.get('title') + '-setting');
bind(this.el, 'click', this.onClick);
},
onClick: function () {
this.emit('click', this);
},
onDestroy: function () {
this.model.off('change', this.render);
},
render: function () {
var data = this.model.get();
data.selected = this.model.selected();
data.value = data.selected && data.selected.title;
this.el.setAttribute('role', 'option');
var localizedValue;
if (data.optionsLocalizable === false) {
localizedValue = Promise.resolve(data.value);
} else {
localizedValue = this.l10n.formatValue(data.value);
}
localizedValue.then(valueString => {
this.l10n.setAttributes(this.el, 'setting-option-' + data.title, { value: valueString });
this.el.innerHTML = this.template({
titleL10n: 'data-l10n-id="' + data.title + '"',
valueL10n: data.optionsLocalizable !== false ? 'data-l10n-id="' + data.value + '"' : '',
valueText: data.optionsLocalizable === false ? data.value : ''
});
delete this.template;
debug('rendered (item %s)', data.key);
});
return this;
},
template: function (data) {
return '<div class="setting_text">' + '<h4 class="setting_title" ' + data.titleL10n + '></h4>' + '<h5 class="setting_value" ' + data.valueL10n + '>' + data.valueText + '</h5>' + '</div>';
}
});
});

View file

@ -0,0 +1,97 @@
define([
'require',
'exports',
'module',
'debug',
'views/setting-options',
'views/setting',
'view',
'lib/bind'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:settings');
var OptionsView = require('views/setting-options');
var SettingView = require('views/setting');
var View = require('view');
var bind = require('lib/bind');
module.exports = View.extend({
name: 'settings',
fadeTime: 150,
initialize: function (options) {
this.OptionsView = options.OptionsView || OptionsView;
this.items = options.items;
this.children = [];
this.on('destroy', this.onDestroy);
bind(this.el, 'click', this.onClick);
},
onClick: function (e) {
e.stopPropagation();
},
onItemClick: function (view) {
this.showSetting(view.model);
},
showSetting: function (model) {
this.optionsView = new this.OptionsView({ model: model }).render().appendTo(this.els.pane2).on('click:option', this.firer('click:option')).on('click:back', this.goBack);
this.showPane(2);
},
onDestroy: function () {
this.children.forEach(this.destroyChild);
this.destroyOptionsView();
debug('destroyed');
},
render: function () {
this.el.innerHTML = this.template();
this.els.items = this.find('.js-items');
this.els.pane2 = this.find('.js-pane-2');
this.els.close = this.find('.js-close');
this.items.forEach(this.addItem);
delete this.template;
debug('rendered');
return this.bindEvents();
},
bindEvents: function () {
bind(this.els.close, 'click', this.firer('click:close'));
return this;
},
goBack: function () {
this.showPane(1);
this.destroyOptionsView();
},
destroyChild: function (view) {
view.destroy();
debug('destroyed child');
},
destroyOptionsView: function () {
if (this.optionsView) {
this.optionsView.destroy();
this.optionsView = null;
debug('options view destroyed');
}
},
addItem: function (model) {
var setting = new SettingView({ model: model }).render().appendTo(this.els.items).on('click', this.onItemClick);
this.children.push(setting);
debug('add item key: %s', model.key);
},
showPane: function (name) {
this.el.setAttribute('show-pane', 'pane-' + name);
},
fadeIn: function (done) {
setTimeout(this.show);
if (!done) {
return;
}
setTimeout(done, this.fadeTime);
},
fadeOut: function (done) {
this.hide();
if (!done) {
return;
}
setTimeout(done, this.fadeTime);
},
template: function () {
return '<div class="pane pane-1">' + '<div class="inner">' + '<div class="settings_inner">' + '<div class="settings_header">' + '<h2 class="settings_title" data-l10n-id="options" ' + 'aria-level="1"></h2>' + '</div>' + '<div class="settings_items">' + '<ul class="inner js-items" role="listbox"></ul>' + '</div>' + '</div>' + '</div>' + '</div>' + '<div class="pane pane-2">' + '<div class="inner js-pane-2"></div>' + '</div>' + '<div role="button" class="settings_close js-close" data-icon="menu" ' + 'data-l10n-id="close-menu-button"></div>';
}
});
});

View file

@ -0,0 +1,170 @@
define([
'require',
'exports',
'module',
'debug',
'view',
'lib/bind',
'lib/orientation'
], function (require, exports, module) {
'use strict';
var debug = require('debug')('view:zoom-bar');
var View = require('view');
var bind = require('lib/bind');
var orientation = require('lib/orientation');
var lastTouch = null;
var clamp = function (value, minimum, maximum) {
return Math.min(Math.max(value, minimum), maximum);
};
module.exports = View.extend({
name: 'zoom-bar',
initialize: function () {
this.render();
this._orientation = orientation.get();
this.zoomBarIndicatorInterval = 10;
this.zoomBarInactivityTimeout = 3000;
},
render: function () {
this.el.innerHTML = this.template();
this.els.maxIndicator = this.find('.zoom-bar-max-indicator');
this.els.minIndicator = this.find('.zoom-bar-min-indicator');
this.els.inner = this.find('.zoom-bar-inner');
this.els.track = this.find('.zoom-bar-track');
this.els.scrubber = this.find('.zoom-bar-scrubber');
delete this.template;
debug('rendered');
return this.bindEvents();
},
bindEvents: function () {
bind(this.els.scrubber, 'touchstart', this.onTouchStart);
bind(this.els.scrubber, 'touchmove', this.onTouchMove);
bind(this.els.scrubber, 'touchend', this.onTouchEnd);
bind(this.els.maxIndicator, 'click', this.onIncrement);
bind(this.els.minIndicator, 'click', this.onDecrement);
orientation.on('orientation', this.setOrientation);
return this;
},
template: function () {
return '<div class="zoom-bar-max-indicator"></div>' + '<div class="zoom-bar-inner">' + '<div class="zoom-bar-track"></div>' + '<div class="zoom-bar-scrubber"></div>' + '</div>' + '<div class="zoom-bar-min-indicator"></div>';
},
onTouchStart: function (evt) {
lastTouch = evt.touches[0];
this.resetInactivityTimeout();
this.setScrubberActive(true);
this._innerHeight = this.els.inner.offsetHeight;
evt.stopPropagation();
},
onTouchMove: function (evt) {
if (!lastTouch) {
return;
}
var touch = evt.touches[0];
var deltaX = lastTouch.pageX - touch.pageX;
var deltaY = lastTouch.pageY - touch.pageY;
var scale = 100 / this._innerHeight;
deltaX *= scale;
deltaY *= scale;
switch (this._orientation) {
case 0:
this.setValue(this._value + deltaY, true);
break;
case 90:
this.setValue(this._value + deltaX, true);
break;
case 180:
this.setValue(this._value - deltaY, true);
break;
case 270:
this.setValue(this._value - deltaX, true);
break;
}
lastTouch = touch;
},
onTouchEnd: function (evt) {
if (!lastTouch) {
return;
}
lastTouch = null;
this.resetInactivityTimeout();
this.setScrubberActive(false);
},
onIncrement: function (evt) {
this.setValue(this._value + this.zoomBarIndicatorInterval, true);
this.flashScrubberActive();
evt.stopPropagation();
},
onDecrement: function (evt) {
this.setValue(this._value - this.zoomBarIndicatorInterval, true);
this.flashScrubberActive();
evt.stopPropagation();
},
_orientation: 0,
setOrientation: function (orientation) {
var el = this.el;
el.classList.remove('zooming');
window.requestAnimationFrame(function () {
el.style.transitionDuration = '0ms';
window.requestAnimationFrame(function () {
el.style.transitionDuration = '';
});
});
this._orientation = orientation;
},
_value: 0,
setValue: function (value, emitChange) {
this.resetInactivityTimeout();
var lastValue = this._value;
this._value = clamp(value, 0, 100);
if (this._value === lastValue) {
return;
}
if (this._value === 0) {
this.els.minIndicator.classList.add('active');
} else {
this.els.minIndicator.classList.remove('active');
}
if (this._value === 100) {
this.els.maxIndicator.classList.add('active');
} else {
this.els.maxIndicator.classList.remove('active');
}
var self = this;
window.requestAnimationFrame(function () {
self.els.track.style.top = 100 - self._value + '%';
self.els.scrubber.style.bottom = self._value + '%';
});
if (emitChange) {
this.emit('change', this._value);
}
},
setScrubberActive: function (active) {
window.clearTimeout(this._scrubberTimeout);
if (active) {
this.els.scrubber.classList.add('active');
} else {
this.els.scrubber.classList.remove('active');
}
},
flashScrubberActive: function () {
this.setScrubberActive(true);
var self = this;
this._scrubberTimeout = window.setTimeout(function () {
self.setScrubberActive(false);
}, 150);
},
resetInactivityTimeout: function () {
window.clearTimeout(this._inactivityTimeout);
this.show();
var self = this;
this._inactivityTimeout = window.setTimeout(function () {
self.hide();
}, this.zoomBarInactivityTimeout);
},
show: function () {
this.el.classList.add('zooming');
},
hide: function () {
this.el.classList.remove('zooming');
}
});
});