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');
};
});