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,430 @@
'use strict';
/* global GaiaGrid */
(function(exports) {
/**
* The relative size of icons when in a collapsed group.
*/
const COLLAPSE_RATIO = 0.33;
/**
* Maximum number of icons that are visible in a collapsed group.
*/
const COLLAPSED_GROUP_SIZE = 6;
/**
* Space to be reserved at the sides of collapsed group items, in pixels.
*/
const COLLAPSED_GROUP_MARGIN_LEFT = 23;
const COLLAPSED_GROUP_MARGIN_RIGHT = 53;
/**
* Time, in ms, before removing the 'toggling' class from the expand/collapse
* toggle element.
*/
const TOGGLE_TIMEOUT = 250;
/**
* A replacement for the default Divider class that implements group
* collapsing and provides convenience functions for group drag'n'drop.
*/
function Group(detail) {
this.detail = detail || {};
this.detail.type = 'divider';
this.detail.index = 0;
this.pendingCollapse = this.detail.collapsed = !!this.detail.collapsed;
}
Group.prototype = {
__proto__: GaiaGrid.GridItem.prototype,
gridWidth: 4,
/**
* Height in pixels of the header part of the group.
*/
get headerHeight() {
return this.detail.collapsed ? 20 : 30;
},
/**
* The collapsed state. Used to defer the actual state change of
* collapse/expand so that anything re-rendering the grid between setting
* the state and the re-render caused by the state change doesn't cause
* the group to be positioned incorrectly.
*/
pendingCollapse: false,
pendingCollapseTimeout: null,
/**
* Height in pixels of the background of the group.
*/
backgroundHeight: 0,
lastBackgroundHeight: null,
/**
* Height in pixels of the separator at the bottom of the group.
*/
separatorHeight: 0,
/**
* The scale used for collapsed icons, taking into account column size.
*/
collapseRatio: COLLAPSE_RATIO,
/**
* Height in pixels of the group. When collapsed, this includes the
* icons associated with the group.
*/
get pixelHeight() {
return this.detail.collapsed ?
this.backgroundHeight :
this.separatorHeight;
},
/**
* Returns the number of items in the group. Relies on the item index
* being correct.
*/
get size() {
var size = 0;
for (var i = this.detail.index - 1, items = this.grid.items;
i >= 0 && items[i].detail.type !== 'divider'; i--) {
size++;
}
return size;
},
/**
* Creates the element and its children for this group item.
*/
_createElement: function() {
var group = this.element = document.createElement('section');
group.className = 'divider group newly-created';
// Create the background (only seen in edit mode)
var span = document.createElement('span');
span.className = 'background';
group.appendChild(span);
this.backgroundSpanElement = span;
// Create an element for a drop-shadow (only seen when active)
span = document.createElement('span');
span.className = 'shadow';
group.appendChild(span);
this.shadowSpanElement = span;
// Create the header (container for the move gripper and
// expand/collapse toggle)
span = document.createElement('span');
span.className = 'header';
group.appendChild(span);
this.headerSpanElement = span;
// Create the move gripper
span = document.createElement('span');
span.className = 'gripper';
this.headerSpanElement.appendChild(span);
// Create the expand/collapse toggle
var button = document.createElement('button');
button.className = 'toggle';
button.dataset.l10nId = 'gaia-grid-toggle-expanded';
this.headerSpanElement.appendChild(button);
this.toggleElement = button;
// Create the group separator (only seen in non-edit mode)
span = document.createElement('span');
span.className = 'separator';
group.appendChild(span);
this.dividerSpanElement = span;
// Create a child span for the separator to act as the indicator for
// dropping icons/groups during editing.
this.dividerSpanElement.appendChild(document.createElement('span'));
this.grid.element.appendChild(group);
this.separatorHeight = this.dividerSpanElement.clientHeight;
this.lastBackgroundHeight = null;
},
/**
* Renders the apps within the group. If the group is not collapsed, this
* just validates the style class of the group.
*/
_renderChildren: function(nApps) {
var isRTL = (document.documentElement.dir === 'rtl');
if (!this.detail.collapsed) {
this.element.classList.remove('collapsed');
return;
}
// If collapsed, we need to style and position the icons in the group
// into one row.
this.element.classList.add('collapsed');
var y = this.y;
var index = this.detail.index;
var width = Math.round(
(this.grid.layout.constraintSize -
COLLAPSED_GROUP_MARGIN_LEFT - COLLAPSED_GROUP_MARGIN_RIGHT) /
COLLAPSED_GROUP_SIZE);
var x = isRTL ?
(this.grid.layout.constraintSize - COLLAPSED_GROUP_MARGIN_RIGHT) :
COLLAPSED_GROUP_MARGIN_LEFT;
y += this.headerHeight;
var maxGridItemWidth =
this.grid.layout.constraintSize / this.grid.layout.minIconsPerRow;
this.collapseRatio =
(maxGridItemWidth / this.grid.layout.gridItemWidth) * COLLAPSE_RATIO;
for (var i = index - nApps; i < index; i++) {
var item = this.grid.items[i];
if (this.detail.collapsed) {
item.scale = this.collapseRatio;
var itemVisible = (i - (index - nApps)) < COLLAPSED_GROUP_SIZE;
if (!itemVisible) {
item.setCoordinates(isRTL ? x + width : x - width, y);
} else {
item.setCoordinates(x, y);
x += isRTL ? -width: width;
}
item.render();
item.element.classList.add('collapsed');
if (!itemVisible) {
item.element.classList.add('hidden');
}
}
}
},
/**
* Gets the y-position of the group. When the group is expanded, its y
* position is actually the y-position of the separator underneath the
* group. This gets the visible y-position of the group.
*/
getRealYPosition: function(nApps) {
if (this.detail.collapsed) {
return this.y;
}
return this.grid.items[this.detail.index - nApps].y -
this.headerHeight;
},
/**
* Renders the icon to the grid component.
*/
render: function() {
// Generate the content if we need to
var createdElements = false;
if (!this.element) {
createdElements = true;
this._createElement();
}
// Render children
var nApps = this.size;
this._renderChildren(nApps);
// Calculate group position.
// If we're not collapsed, the group's position will be underneath its
// icons, but we want it to display above.
var y = this.getRealYPosition(nApps);
if (y !== this.lastY) {
// Place the header span
this.headerSpanElement.style.transform =
'translate(0px, ' + y + 'px)';
}
if (this.toggleElement) {
var toggleLabel = this.detail.collapsed ? 'collapsed' : 'expanded';
this.toggleElement.dataset.l10nId = 'gaia-grid-toggle-' + toggleLabel;
}
// Calculate the height of the background span
if (this.detail.collapsed) {
this.backgroundHeight =
Math.round(this.collapseRatio * this.grid.layout.gridIconSize * 1.5);
} else {
var height = Math.ceil(nApps / this.grid.layout.cols);
this.backgroundHeight = (height || 1) * this.grid.layout.gridItemHeight;
}
this.backgroundHeight += this.headerHeight;
if (y != this.lastY ||
this.backgroundHeight !== this.lastBackgroundHeight) {
// Place and size the background span element
this.backgroundSpanElement.style.transform =
'translate(0px, ' + y + 'px) scale(1, ' + this.backgroundHeight + ')';
// Place and size the shadow span element
this.shadowSpanElement.style.transform =
'translateY(' + y + 'px)';
this.shadowSpanElement.style.height = this.backgroundHeight + 'px';
// Place the divider after this point
this.dividerSpanElement.style.transform =
'translate(0px, ' + (y + this.backgroundHeight) + 'px)';
}
// Update the cached size values
this.lastBackgroundHeight = this.backgroundHeight;
this.lastY = y;
// Now include the separator in the background height
this.backgroundHeight += this.separatorHeight;
// Fade in newly-created groups
if (createdElements) {
// Force a reflow to make sure the transform transition doesn't play
this.element.clientTop;
this.element.classList.remove('newly-created');
}
},
forEachItem: function(callback) {
var nApps = this.size;
var index = this.detail.index;
for (var i = index - nApps; i < index; i++) {
callback(this.grid.items[i]);
}
},
setActive: function(active) {
GaiaGrid.GridItem.prototype.setActive.call(this, active);
// Make sure we're collapsed
this.collapse();
// Mark our child items as active/inactive with us so they pick up the
// right style when dragged.
var callback = active ?
function(item) { item.element.classList.add('active'); } :
function(item) { item.element.classList.remove('active'); };
this.forEachItem(callback);
},
/*
* As the start of a collapse/expand animation can involve a lot of work,
* the response isn't always instant. To help alleviate this, we show a
* quick response on the toggle element first before initiating the rest
* of the work.
*/
_rerenderOnToggle: function() {
if (this.pendingCollapseTimeout) {
clearTimeout(this.pendingCollapseTimeout);
this.pendingCollapseTimeout = null;
}
if (this.toggleElement) {
this.toggleElement.classList.add('toggling');
setTimeout(() => { this.toggleElement.classList.remove('toggling'); },
TOGGLE_TIMEOUT);
}
// Ideally, this setTimeout would be a requestAnimationFrame, but the
// above class-adding seems to end up coalesced with the code inside
// requestAnimationFrame, rather than it happening on the next frame.
// The same thing happens with any setTimeout lower than about 20ms too.
this.pendingCollapseTimeout = setTimeout(() => {
this.detail.collapsed = this.pendingCollapse;
this.pendingCollapseTimeout = null;
if (!this.detail.collapsed) {
// Remove collapsed styling from all icons
this.forEachItem(function(item) {
item.scale = 1;
if (item.element) {
item.element.classList.remove('collapsed');
item.element.classList.remove('hidden');
}
});
}
// If the item is active, re-render it so that its pixel-height is
// correct when the rest of the grid is rendered.
if (this.active) {
this.render();
}
this.grid.render();
var dragging = this.grid.dragdrop && this.grid.dragdrop.inDragAction;
if (dragging) {
// If we're dragging, make sure to reposition the icon in the correct
// place, as the render call won't redraw us
this.grid.dragdrop.updateIconPosition();
} else {
// If we're not dragging, save the collapsed state
window.dispatchEvent(new CustomEvent('gaiagrid-saveitems'));
// Request attention so that we're as visible as we can be after
// expanding/collapsing
this.requestAttention();
}
}, 20);
},
collapse: function() {
if (this.detail.collapsed) {
return;
}
this.pendingCollapse = true;
this._rerenderOnToggle();
},
expand: function() {
if (!this.detail.collapsed) {
return;
}
this.pendingCollapse = false;
this._rerenderOnToggle();
},
launch: function(target) {
if (target !== this.toggleElement) {
return;
}
if (this.detail.collapsed) {
this.expand();
} else {
this.collapse();
}
},
remove: function() {
if (this.element) {
this.element.parentNode.removeChild(this.element);
}
},
isDraggable: function() {
return true;
},
requestAttention: function() {
var rect = {
x: 0,
y: this.getRealYPosition(this.size),
width: this.gridWidth * this.grid.layout.gridItemWidth,
height: this.backgroundHeight
};
this.grid.element.dispatchEvent(
new CustomEvent('gaiagrid-attention', { detail: rect }));
}
};
exports.GaiaGrid.Divider = Group; // Override the non-grouping divider
}(window));