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,258 @@
/*global
MimeMapper
*/
(function(exports) {
'use strict';
function proposeDownload(blob) {
console.log('proposeDownload');
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'app.zip';
a.hidden = true;
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(blob);
}
function uninstallIfNeeded(theme) {
if (!theme.manifestURL) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
var all = navigator.mozApps.mgmt.getAll();
all.onsuccess = (e) => {
var toDelete = e.target.result.find((app) => {
return (app.manifestURL === theme.manifestURL);
});
if (toDelete) {
var req = navigator.mozApps.mgmt.uninstall(toDelete);
req.onsuccess = resolve;
req.onerror = reject;
} else {
reject(new Error('could not find the app to uninstall'));
}
};
all.onerror = reject;
});
}
function importBlob(blob, enable) {
console.log('importBlob');
if (!navigator.getDeviceStorage) {
return Promise.reject(new Error('getDeviceStorage is unavailable.'));
}
// Save the blob to a file because we don't support importing memory blobs.
var sdcard = navigator.getDeviceStorage('sdcard');
return new Promise((resolve, reject) => {
function sendError(msg) {
console.log(msg);
reject(msg);
}
if (!sdcard) {
sendError('No SDCard!');
return;
}
var fileName = 'temp-app.zip';
var delReq = sdcard.delete(fileName);
delReq.onsuccess = delReq.onerror = function() {
var req = sdcard.addNamed(blob, fileName);
req.onsuccess = function(e) {
var getReq = sdcard.get(fileName);
getReq.onsuccess = function() {
var file = this.result;
navigator.mozApps.mgmt.import(file).then(
function(app) {
if (enable) {
var setting = { 'theme.selected' : app.manifestURL };
var req = navigator.mozSettings.createLock().set(setting);
req.onsuccess = resolve.bind(null, app.manifestURL);
req.onerror = sendError;
} else {
resolve(app.manifestURL);
}
},
function(error) { sendError('Error importing: ' + error.name); }
);
}
getReq.onerror = function() {
sendError('Error getting file: ' + this.error.name);
}
}
req.onerror = function(e) {
sendError('Error saving blob: ' + this.error.name);
}
};
});
}
function exportTheme(enable, theme) {
// Worker path configuration.
zip.workerScriptsPath = './js/libs/';
console.log('exportTheme ' + theme.title);
// TODO: use a stable appId.
var appId = 'app' + Math.round(Math.random() * 100000000);
var app = new AppZip();
app.metaData = {
installOrigin: 'http://gaiamobile.org',
manifestURL: 'app://theme' + appId + '.gaiamobile.org/update.webapp',
version: 1
};
app.manifest = {
name: theme.title,
package_path: '/application.zip',
};
// Build a simple package with a manifest and index.html
var inner = new PackageHelper();
var manifest = {
name: theme.title,
role: "theme",
type: "certified",
origin: 'app://theme' + appId + '.gaiamobile.org'
};
return inner.init().then(() => {
function addManifest() {
return inner.addResource('manifest.webapp', JSON.stringify(manifest));
}
function addCSS() {
return inner.addResource('shared/elements/gaia-theme/gaia-theme.css', userStyle(theme));
}
function addWallpaper(blob) {
// if we don't have a (valid) wallpaper we still want an empty JSON
var wallpaperFile = null;
var wallpaper = { homescreen: null };
if (blob) {
var type = MimeMapper.guessExtensionFromType(blob.type);
if (type) {
wallpaperFile = 'wallpaper.' + type;
wallpaper.homescreen = '/' + wallpaperFile;
} else {
console.error('Unrecognized type: ' + blob.type);
}
}
// We can't use Promise.all to add both files because it corrupts the
// zip
return Promise.resolve()
.then(() => inner.addResource(
'wallpaper.json', JSON.stringify(wallpaper)))
.then(function() {
if (wallpaperFile) {
inner.addResource(wallpaperFile, blob);
}
});
}
var zipPromise = addManifest().then(addCSS);
var image = null;
if (theme.autotheme) {
image = theme.autotheme.image;
}
zipPromise = zipPromise.then(() => addWallpaper(image));
return zipPromise.then(inner.asBlob.bind(inner))
.then((packageBlob) => {
app.packageblob = packageBlob;
return app.asBlob();
}).then((appBlob) => {
return uninstallIfNeeded(theme)
.then(() => {
return importBlob(appBlob, enable);
})
.then((manifestURL) => {
theme.manifestURL = manifestURL;
return Storage.updateTheme(theme);
})
.then(() => Promise.resolve(image &&
navigator.mozSettings.createLock().set({
'wallpaper.image': image
})
))
.catch((e) => {
console.error('Error while importing blob', e);
// Desktop debug
// proposeDownload(appBlob);
alert('This device is not theming compatible');
});
});
});
}
function userStyle(theme) {
var roots = [':root', '.theme-productivity', '.theme-communications',
'.theme-settings', '.theme-media'];
var str = '';
roots.forEach(function(root) {
str += root + ' {\n';
var group;
if (root === ':root') {
group = 'default';
// Add :root CSS variables here.
str += '--transition-duration: 0.2s;\n';
} else {
group = root.split('-')[1];
}
Object.keys(theme.groups[group]).forEach(function(sectionKey) {
var section = theme.groups[group][sectionKey];
Object.keys(section).forEach(function(key) {
str += key + ': ' + section[key] + ';\n';
});
});
str += '}\n';
});
return str;
}
function fakeInstall(css) {
var override = document.createElement('style');
override.innerHTML = css;
document.body.appendChild(override);
}
function checkIfSelected(theme) {
return new Promise((resolve, reject) => {
var req = navigator.mozSettings.createLock().get('theme.selected');
req.onsuccess = function(e) {
resolve([(req.result['theme.selected'] == theme.manifestURL), theme]);
};
req.onerror = reject;
});
}
exports.Generation = {
installTheme: function(id, enable) {
return Storage.fetchTheme(id)
.then(checkIfSelected)
.then((results) => {
return exportTheme(results[0] || enable, results[1]);
});
},
uninstallIfNeeded: function(theme) {
return checkIfSelected(theme)
.then((results) => {
var selected = results[0];
if (selected) {
alert("You can't remove the selected theme");
} else {
return uninstallIfNeeded(theme);
}
});
}
};
})(window);