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

272
gaia/apps/homescreen/js/pages.js Executable file
View file

@ -0,0 +1,272 @@
/* global PagesStore, IconsHelper */
'use strict';
(function(exports) {
/**
* The size in pixels of the icons in the pinned pages.
*/
const PAGES_ICON_SIZE = 30;
function Pages() {
// Element references
this.panel = document.getElementById('pages-panel');
this.panels = document.getElementById('panels');
this.pages = document.getElementById('pages');
this.scrollable = document.querySelector('#pages-panel > .scrollable');
this.bottombar = document.getElementById('bottombar');
this.remove = document.getElementById('remove');
this.done = document.getElementById('done');
// Tracking if the list is empty
this.empty = true;
// Edit mode
this.editMode = false;
// Dialogs
this.dialogs = [];
// Signal handlers
this.pages.addEventListener('click', this);
this.pages.addEventListener('contextmenu', this);
this.pages.addEventListener('keydown', this);
window.addEventListener('resize', this);
this.done.addEventListener('click', e => {
e.preventDefault();
this.exitEditMode();
});
this.remove.addEventListener('click', e => {
e.preventDefault();
this.unpinSelectedCard();
});
// Initialise and populate pinned pages
this.pagesStore = new PagesStore('places');
this.pagesStore.init().then(() => {
// Triggered when pages are added and updated.
document.addEventListener('places-set', (e) => {
var id = e.detail.id;
this.pagesStore.get(id).then((page) => {
for (var child of this.pages.children) {
if (child.dataset.id === id) {
if (!page.data.pinned) {
this.removeCard(child);
} else {
this.updatePinnedPage(child, page.data);
}
return;
}
}
if (!page.data.pinned) {
return;
}
// A new page was pinned.
this.addPinnedPage(page.data);
});
});
document.addEventListener('places-removed', (e) => {
var id = e.detail.id;
for (var child of this.pages.children) {
if (child.dataset.id === id) {
this.removeCard(child);
return;
}
}
});
document.addEventListener('places-cleared', () => {
this.exitEditMode();
for (var child of this.pages.children) {
this.pages.removeChild(child);
}
if (!this.empty) {
this.empty = true;
this.panel.classList.add('empty');
}
});
},
(e) => {
console.error('Error initialising pinned pages', e);
}).then(() => {
return this.pagesStore.getAll().then((pages) => {
for (var page of pages) {
this.addPinnedPage(page.data);
}
// If there are no pinned pages, display helpful information.
if (this.empty) {
this.panel.classList.add('empty');
}
}, (e) => {
console.error('Error getting pinned pages', e);
});
});
}
Pages.prototype = {
updatePinnedPage: function(card, page) {
card.title = page.title;
card.dataset.id = page.url;
card.style.order = -Math.round(page.pinTime / 1000);
page.meta = page.meta || {};
if (page.screenshot) {
page.meta.screenshot = page.screenshot;
}
setTimeout(function(card, page) {
card.meta = page.meta;
IconsHelper.getIconBlob(page.url, PAGES_ICON_SIZE, page)
.then((iconObj) => {
if (iconObj.blob) {
var iconURL = URL.createObjectURL(iconObj.blob);
card.icon = `url(${iconURL})`;
}
})
.catch((e) => {
console.error('Failed to fetch icon', e);
});
}.bind(this, card, page));
},
addPinnedPage: function(page) {
var pinCard = document.createElement('gaia-pin-card');
// Make the card accessible/allow activation
pinCard.tabIndex = 0;
pinCard.setAttribute('role', 'link');
this.updatePinnedPage(pinCard, page);
this.pages.appendChild(pinCard);
if (this.empty) {
this.empty = false;
this.panel.classList.remove('empty');
}
},
launchCard: function(card) {
var features = {
name: card.title,
icon: card.icon,
remote: true
};
window.open(card.dataset.id, '_blank', Object.keys(features).map(
key => encodeURIComponent(key) + '=' + encodeURIComponent(features[key])
).join(','));
},
enterEditMode: function(card) {
if (this.selectedCard) {
this.selectedCard.classList.remove('selected');
}
if (card) {
this.selectedCard = card;
card.classList.add('selected');
}
this.editMode = true;
document.body.classList.add('edit-mode');
this.remove.classList.add('active');
},
exitEditMode: function() {
if (!this.editMode) {
return;
}
this.editMode = false;
document.body.classList.remove('edit-mode');
this.remove.classList.remove('active');
if (this.selectedCard) {
this.selectedCard.classList.remove('selected');
this.selectedCard = null;
}
},
removeCard: function(card) {
if (this.selectedCard === card) {
this.selectedCard = null;
}
this.pages.removeChild(card, () => {
if (!this.empty && this.pages.children.length === 0) {
this.exitEditMode();
this.empty = true;
this.panel.classList.add('empty');
}
});
},
unpinSelectedCard: function() {
if (!this.selectedCard) {
return;
}
var id = this.selectedCard.dataset.id;
// Remove child immediately, datastore operations can be quite slow
this.removeCard(this.selectedCard);
this.pagesStore.get(id).then(entry => {
entry.data.pinned = false;
this.pagesStore.datastore.put(entry.data, id).then(() => {},
e => {
console.error('Error unpinning page:', e);
});
}, e => {
console.error('Error retrieving page to unpin:', e);
});
},
handleEvent: function(e) {
switch (e.type) {
case 'click':
if (e.target.nodeName !== 'GAIA-PIN-CARD') {
break;
}
if (this.editMode) {
this.enterEditMode(e.target);
} else {
this.launchCard(e.target);
}
break;
case 'contextmenu':
if (e.target.nodeName !== 'GAIA-PIN-CARD') {
break;
}
this.enterEditMode(e.target);
break;
case 'keydown':
if (e.target.nodeName !== 'GAIA-PIN-CARD') {
break;
}
switch (e.keyCode) {
case 32: // Space
case 13: // Enter
this.launchCard(e.target);
}
break;
case 'resize':
this.pages.synchronise();
break;
}
}
};
exports.Pages = Pages;
}(window));