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,214 @@
define(function(require, exports, module) {
'use strict';
/**
* Dependencies
*/
var debug = require('debug')('controller:media');
var bindAll = require('lib/bind-all');
/**
* Exports
*/
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);
// Preload libraries we may need after capturing to maintain snappiness
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 = []; // All the pictures and videos we know about
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 we're handling a pick activity we don't generate thumbnails
if (this.app.activity.pick) {
return;
}
var self = this;
if (item.isVideo) {
// If the new media is video, use it as-is
addNewMedia(item);
} else {
// If it is a photo, find its EXIF preview first
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();
}
};
/**
* Delete all items in the media controller
* when storage becomes unavailable.
*
* @param {String} status
*/
MediaController.prototype.onStorageChanged = function(status) {
if (status === 'unavailable') {
this.configure();
this.updateThumbnail();
}
};
/**
* As a privacy feature, when the camera app is used from the lockscreen
* and the lockscreen is actually locked with a passcode, we don't want
* the camera to retain any state from one use to the next. So if the
* camera is hidden (i.e. if the phone returns to the lockscreen) we
* forget our state. In practice, it appears that the system app actually
* kills the camera when this happens, so this code is redundant.
*/
MediaController.prototype.onHidden = function() {
if (this.app.inSecureMode) {
this.configure(); // Forget all stored images
this.updateThumbnail(); // Get rid of any thumbnail
}
};
MediaController.prototype.updateThumbnail = function() {
// This is the media item that we want to create a thumbnail for.
var media = this.items[0] || null;
if (media === this.currentItem) {
return;
}
if (media === null) {
this.app.emit('newthumbnail', null);
return;
}
// To create a thumbanil, we first need to figure out which image
// blob to use, and what the size and rotation of that image is.
var blob; // The image we'll create the thumbnail from.
var metadata = { // The metadata for that image.
rotation: media.rotation,
mirrored: media.mirrored
};
if (media.isVideo) {
// If it is a video we can create a thumbnail from the poster image
blob = media.poster.blob;
metadata.width = media.poster.width;
metadata.height = media.poster.height;
} else {
// If it is a photo we want to use the EXIF preview rather than
// decoding the whole image if we can.
if (media.preview) {
// The Tarako may produce EXIF previews that have the wrong
// aspect ratio and are distorted. Check for that, and if the
// aspect ratio is not right, then create a thumbnail from the
// full size image
var fullRatio = media.width / media.height;
var previewRatio = media.preview.width / media.preview.height;
// If aspect ratios match, create thumbnail from EXIF preview
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 a thumbnail couldn't be obtained from the EXIF preview,
// use the full image
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);
});
});
};
/**
* Delete and update items in the preview gallery
* when images/videos are deleted by others
*
* @param {Object} filepath
*/
MediaController.prototype.onItemDeleted = function(data) {
var deleteIdx = -1;
var deletedFilepath = typeof(data) === 'object' ? data.path : data;
// find the item in items
for (var n = 0; n < this.items.length; n++) {
if (this.items[n].filepath === deletedFilepath) {
deleteIdx = n;
break;
}
}
// Exit when item not found
if (n === this.items.length) { return; }
var item = this.items[deleteIdx];
// Check if this event is being stopped such as in the case
// of resizing an image for a share activity.
if (item.resizing) { return; }
this.items.splice(deleteIdx, 1);
this.updateThumbnail();
this.app.emit('media:deleted');
};
});