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 @@
;window.COMPONENTS_BASE_URL='/shared/elements/';

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
/* Generated by grunt-webfont */
/* Based on https://github.com/endtwist/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.css */
@font-face {
font-family: "gaia-icons";
src: url("fonts/gaia-icons.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
[data-icon]:before,
.ligature-icons {
font-family: "gaia-icons";
content: attr(data-icon);
display: inline-block;
font-weight: 500;
font-style: normal;
text-decoration: inherit;
text-transform: none;
text-rendering: optimizeLegibility;
font-size: 30px;
-webkit-font-smoothing: antialiased;
}
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):before {
/* Highlight cases where icons are not localized and still visible to screen
reader */
background: rgba(255, 0, 0, 0.4);
}
[data-icon]:not([data-l10n-id]):not([aria-label]):not([aria-hidden="true"]):after {
background: #333;
color: #fff;
position: absolute;
width: 20rem;
border-radius: 0.5rem;
padding: 0.5rem;
content: "Icon is not localized and is still visible to screen reader. Please add data-l10n-id or aria-label for localization or use aria-hidden to hide the icon from screen reader.";
z-index: 9999;
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><path fill="#667174" fill-opacity=".7" d="m 18.2995,19.66 -5.056,-5.057 5.056,-5.056 c -0.31,-0.516 -0.62,-1.032 -0.93,-1.34 l -5.672,5.673 v 1.652 l 5.675,5.676 c 0.31,-0.515 0.722,-0.928 0.93,-1.547 z"/></svg>

After

Width:  |  Height:  |  Size: 294 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path fill="#667174" fill-opacity=".7" d="M11.8,19.9l4.9-4.9l-4.9-4.9c0.3-0.5,0.6-1,0.9-1.3l5.5,5.5c0,0.3,0,0.5,0,0.8c0,0.3,0,0.5,0,0.8l-5.5,5.5 C12.4,20.9,12,20.5,11.8,19.9z"/></svg>

After

Width:  |  Height:  |  Size: 243 B

View file

@ -0,0 +1,18 @@
'use strict';
/* global ComponentUtils */
window.GaiaButtons = (function(win) {
// Extend from the HTMLElement prototype
var proto = Object.create(HTMLElement.prototype);
// Allow baseurl to be overridden (used for demo page)
var baseurl = window.GaiaButtonsBaseurl ||
'/shared/elements/gaia_buttons/';
proto.createdCallback = function() {
ComponentUtils.style.call(this, baseurl);
};
// Register and return the constructor
return document.registerElement('gaia-buttons', { prototype: proto });
})(window);

View file

@ -0,0 +1,281 @@
/* ---------------------------------------
* gaia-buttons:
* Container for buttons e.g:
* action menu, confirm, edit mode, FTU
* --------------------------------------- */
gaia-buttons {
display: flex;
justify-content: center;
align-items: center;
height: 7rem;
box-sizing: border-box;
pointer-events: auto;
}
gaia-buttons:not(.inline) {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
border-top: 0.1rem solid #e7e7e7;
padding: 0;
background-color: #f4f4f4;
}
/* this 'end' padding must match the buttons 'start' margin */
gaia-buttons:not(.inline):-moz-dir(ltr) { padding-right: 1.5rem; }
gaia-buttons:not(.inline):-moz-dir(rtl) { padding-left: 1.5rem; }
gaia-buttons.inline a,
gaia-buttons.inline button {
margin: 0;
}
button::-moz-focus-inner {
border: none;
outline: none;
}
a,
button {
position: relative;
font-family: sans-serif;
font-style: italic;
width: 100%;
height: 4rem;
margin: 0;
padding: 0 0.2rem;
box-sizing: border-box;
display: inline-block;
vertical-align: middle;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
background: #d8d8d8;
border: none;
border-radius: 2rem;
font-weight: normal;
font-size: 1.6rem;
line-height: 4rem;
color: #333;
text-align: center;
text-decoration: none;
outline: none;
}
:-moz-dir(ltr) a,
:-moz-dir(ltr) button {
margin-left: 1.5rem;
}
:-moz-dir(rtl) a,
:-moz-dir(rtl) button {
margin-right: 1.5rem;
}
/* Recommend */
a.recommend,
button.recommend {
background-color: #00caf2;
color: #fff;
}
/* Danger */
a.danger,
button.danger {
background-color: #e51e1e;
color: #fff;
}
/* Pressed */
a:active,
button:active {
background-color: #b2f2ff;
color: #fff;
}
a::-moz-selection,
button::-moz-selection {
background-color: transparent;
}
/* Disabled */
a[aria-disabled="true"],
button[disabled] {
background-color: rgba(0,0,0,0.05);
color: #c7c7c7;
pointer-events: auto;
}
a[aria-disabled="true"].recommend,
button[disabled].recommend {
background-color: #97cbd5;
color: rgba(255,255,255,0.5);
}
a[aria-disabled="true"].danger,
button[disabled].danger {
background-color: #eb7474;
color: rgba(255,255,255,0.5);
}
a[aria-disabled="true"]::-moz-selection,
button[disabled]::-moz-selection {
-moz-user-select: none;
}
/* Hidden */
[hidden] {
visibility: hidden;
}
/* Icons */
a.icon,
button.icon {
position: relative;
}
a.icon:before,
button.icon:before {
content: "";
width: 3rem;
height: 3rem;
position: absolute;
top: 50%;
margin-top: -1.5rem;
background: transparent no-repeat center center / 100% auto;
}
:-moz-dir(ltr) a.icon:before,
:-moz-dir(ltr) button.icon:before { right: 0.5rem; }
:-moz-dir(rtl) a.icon:before,
:-moz-dir(rtl) button.icon:before { left: 0.5rem; }
a.icon-next:before,
button.icon-next:before {
background-size: 3rem;
}
:-moz-dir(ltr) a.icon-next:before,
:-moz-dir(ltr) button.icon-next:before {
background-image: url(images/right-light.svg);
}
:-moz-dir(rtl) a.icon-next:before,
:-moz-dir(rtl) button.icon-next:before {
background-image: url(images/left-light.svg);
}
a.icon-previous:before,
button.icon-previous:before {
background-size: 3rem;
}
:-moz-dir(ltr) a.icon-previous:before,
:-moz-dir(ltr) button.icon-previous:before {
left: 0.5rem;
background-image: url(images/left-light.svg);
}
:-moz-dir(rtl) a.icon-previous:before,
:-moz-dir(rtl) button.icon-previous:before {
right: 0.5rem;
background-image: url(images/right-light.svg);
}
/* -----------------------------------------------------------------
SKIN: dark
----------------------------------------------------------------- */
gaia-buttons[skin="dark"] {
background: #4d4d4d;
border: none;
}
/* Pressed */
[skin="dark"] a:active,
[skin="dark"] button:active {
background-color: #00aacc;
}
/* Disabled */
[skin="dark"] a[aria-disabled="true"],
[skin="dark"] button[disabled] {
background-color: #565656;
color: rgba(255,255,255,0.4);
}
[skin="dark"] a[aria-disabled="true"].recommend,
[skin="dark"] button[disabled].recommend {
background-color: #006579;
}
[skin="dark"] a[aria-disabled="true"].danger,
[skin="dark"] button[disabled].danger {
background-color: #730f0f;
}
/* -----------------------------------------------------------------
Transitions
----------------------------------------------------------------- */
[transition] {
display: flex;
transition: visibility var(--transition-duration);
}
[transition][hidden] {
pointer-events: none;
}
[transition="slide"] {
transform: translateY(0);
transition: transform var(--transition-duration) ease,
visibility var(--transition-duration);
}
[transition="slide"][hidden] {
transform: translateY(100%);
}
[transition="fade"] {
opacity: 1;
transition: opacity var(--transition-duration) ease,
visibility var(--transition-duration);
}
[transition="fade"][hidden] {
opacity: 0;
}
/* -----------------------------------------------------------------
RESPONSIVE: tablet
----------------------------------------------------------------- */
@media (min-width: 768px) {
gaia-buttons {
height: 10rem;
}
gaia-buttons:not(.inline) {
border-top-width: 0.2rem;
}
/* this 'end' padding must match the buttons 'start' margin */
gaia-buttons:not(.inline):-moz-dir(ltr) { padding-right: 3rem; }
gaia-buttons:not(.inline):-moz-dir(rtl) { padding-left: 3rem; }
a,
button {
font-size: 2rem;
height: 5rem;
line-height: 5rem;
border-radius: 2.5rem;
max-width: 40rem;
}
:-moz-dir(ltr) a,
:-moz-dir(ltr) button {
margin-left: 3rem;
}
:-moz-dir(rtl) a,
:-moz-dir(rtl) button {
margin-right: 3rem;
}
}

View file

@ -0,0 +1,72 @@
'use strict';
/* global ComponentUtils */
/**
* The gaia-confirm component displays a dialog in which the user has a
* choice to confirm or cancel the action. It may be displayed along with a
* title, description, and image. Buttons may also be configured.
* @requires GaiaButtons
*/
window.GaiaConfirm = (function(win) {
// Extend from the HTMLElement prototype
var proto = Object.create(HTMLElement.prototype);
// Allow baseurl to be overridden (used for demo page)
var baseurl = window.GaiaConfirmBaseurl ||
'/shared/elements/gaia_confirm/';
/**
preventDefault and prevent other listeners from hearing abut changes to a
particular event object.
@param {Array[Element]|Null} unless a list of elements to allow events to hit.
@param {Event} event triggered by addEventListener, etc...
*/
function eatEvent(unless, event) {
// prevent handlers from everything else unless the target is on the list.
if (unless && unless.indexOf(event.target) !== -1) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
}
proto.createdCallback = function() {
var shadow = this.createShadowRoot();
this._template = template.content.cloneNode(true);
shadow.appendChild(this._template);
ComponentUtils.style.call(this, baseurl);
var confirm = this.querySelector('gaia-buttons .confirm');
var cancel = this.querySelector('gaia-buttons .cancel');
if (confirm) {
confirm.addEventListener('click', (e) => {
eatEvent(null, e);
this.dispatchEvent(new CustomEvent('confirm'));
});
}
if (cancel) {
cancel.addEventListener('click', (e) => {
eatEvent(null, e);
this.dispatchEvent(new CustomEvent('cancel'));
});
}
};
var template = document.createElement('template');
template.innerHTML = `<form role="dialog" class="confirm">
<section>
<content select="h1"></content>
<content select="p"></content>
</section>
<content select="gaia-buttons">
</content>
</form>`;
// Register and return the constructor
return document.registerElement('gaia-confirm', { prototype: proto });
})(window);

View file

@ -0,0 +1,155 @@
/* ----------------------------------
* Confirm
* ---------------------------------- */
/* Main dialog setup */
gaia-confirm {
background: #2d2d2d;
overflow: hidden;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding-bottom: 7rem;
font-size: 0;
text-align: start;
/* Using font-size: 0; we avoid the unwanted visual space (about 3px)
created by white-spaces and break lines in the code betewen inline-block elements */
color: #fff;
display: flex;
align-items: stretch;
flex-direction: column;
justify-content: center;
transition: transform ease-in-out 0.3s, visibility 0.3s;
/**
magic number ideally to place confirm dialogs on top.
*/
z-index: 2147483646;
}
gaia-confirm[hidden] {
transform: translateY(100%);
visibility: hidden;
pointer-events: none;
}
.confirm section {
height: 100%;
font-weight: 300;
font-size: 2.2rem;
color: #FAFAFA;
padding: 0 1.5rem;
box-sizing: padding-box;
width: 100%;
display: inline-block;
max-height: 100%;
vertical-align: middle;
white-space: normal;
}
h1 {
font-weight: normal;
font-size: 1.6rem;
line-height: 1.5em;
color: #fff;
margin: 0;
padding: 1rem 1.5rem 0;
overflow: hidden;
max-height: 7rem;
word-wrap: break-word;
}
/* Specific component code */
p {
word-wrap: break-word;
margin: 1rem 0 0;
padding: 1rem 1.5rem;
border-top: 0.1rem solid #686868;
line-height: 3rem;
}
p img {
margin-inline-end: 2rem;
}
p img:-moz-dir(ltr) { float: left; }
p img:-moz-dir(rtl) { float: right; }
p strong {
font-weight: 300;
}
p small {
font-size: 1.4rem;
font-weight: normal;
color: #cbcbcb;
display: block;
}
dl {
border-top: 0.1rem solid #686868;
margin: 1rem 0 0;
overflow: hidden;
padding-top: 1rem;
font-size: 1.6rem;
line-height: 2.2rem;
}
dl > dt {
clear: both;
width: 7rem;
padding-inline-start: 1.5rem;
font-weight: 500;
text-align: start;
}
dl > dt:-moz-dir(ltr) { float: left; }
dl > dt:-moz-dir(rtl) { float: right; }
dl > dd {
padding-inline-end: 1.5rem;
font-weight: 300;
text-overflow: ellipsis;
vertical-align: top;
overflow: hidden;
}
figure {
margin: 1.5rem;
display: flex;
align-items: center;
}
figure img {
padding-inline-end: 1.5rem;
flex-shrink: 0;
}
figure figcaption {
font-size: 2.1rem;
font-weight: 300;
}
ul {
margin: 0;
padding: 2rem 1.5rem;
border-top: 0.1rem solid #686868;
list-style: none;
font-size: 1.6rem;
font-weight: normal;
}
ul li {
word-wrap: break-word;
line-height: 2rem;
padding-bottom: 1.5rem;
}
ul li:last-child {
padding-bottom: 0;
}
ul li ul {
border: none;
margin: 0;
padding: 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,844 @@
'use strict';
/* global devicePixelRatio */
/* global GaiaGrid */
(function(exports) {
const ACTIVE_SCALE = 1.4;
/* This is an alternative delay used to initiate icon movement when in edit
* mode. The normal contextmenu delay is too long once we're in edit mode
* and causes user confusion, but a short delay globally would cause too many
* accidental triggers of edit mode.
*/
const EDIT_LONG_PRESS_DELAY = 200;
/* The movement threshold to use for the above synthetic long-press. */
const EDIT_LONG_PRESS_THRESHOLD = 4 * devicePixelRatio;
/* This delay is the time passed once users stop the finger over an icon and
* the rearrange is performed */
const REARRANGE_DELAY = 30;
/* The page is scrolled via javascript if an icon is being moved, and is
* within a length of a page edge configured by this value */
const EDGE_PAGE_THRESHOLD = 50;
/* This is the delay before calling finish when handling touchend. */
const TOUCH_END_FINISH_DELAY = 20;
/* The maximum frequency with which the grid will be rearranged in response
* to the item position changing, in ms.
*/
const REARRANGE_FREQUENCY = 100;
/* The amount of cool-down time after a rearrange animation. To avoid
* rearranging too frequently and causing jank and user confusion.
*/
const REARRANGE_COOLDOWN = 750;
const SCREEN_HEIGHT = window.innerHeight;
const scrollStep = Math.round(SCREEN_HEIGHT / EDGE_PAGE_THRESHOLD);
/* The scroll step will be 5 times bigger over the edge */
const maxScrollStepFactor = 5;
function DragDrop(gridView) {
this.gridView = gridView;
this.container = gridView.element;
this.scrollable = document.documentElement;
this.container.addEventListener('touchstart', this);
this.container.addEventListener('touchmove', this);
this.container.addEventListener('touchend', this);
window.addEventListener('touchcancel', this);
this.container.addEventListener('click', this);
this.container.addEventListener('contextmenu', this);
}
DragDrop.prototype = {
/**
* The current touchmove target.
* @type {DomElement}
*/
target: null,
/**
* The edit-mode long-press timeout
*/
longPressTimeout: null,
/**
* The position of the first touchstart of the current event block
*/
touchStart: { x: 0, y: 0, screenX: 0, screenY: 0 },
/**
* If we have moved an icon, this indicates that we need to save the state
* @type {boolean}
*/
dirty: false,
/**
* Whether or not we are in edit mode.
* @type {boolean}
*/
inEditMode: false,
/**
* The item being hovered over.
* @type {GaiaGrid.GridItem}
*/
hoverItem: null,
/**
* The group being hovered over.
* @type {GaiaGrid.Group}
*/
hoverGroup: null,
/*
* An element that's aligned to the top of the grid and should be
* considered as obscuring the grid when in edit mode.
*/
editHeaderElement: null,
/**
* The timeout used to rearrange the grid during dragging.
*/
rearrangeGridTimeout: null,
/**
* The time of the last reposition call.
*/
lastRepositionTime: 0,
/**
* Returns the maximum active scale value.
*/
get activeScale() {
return ACTIVE_SCALE;
},
/**
* Returns true if we are currently dragging an icon.
*/
get inDragAction() {
return this.target && this.target.classList.contains('active');
},
/**
* Returns the delay before calling finish in response to a touch-end event.
*/
get touchEndFinishDelay() {
return TOUCH_END_FINISH_DELAY;
},
/**
* Begins the drag/drop interaction.
* Enlarges the icon.
* Sets additional data to make the touchmove handler faster.
*/
begin: function(e) {
// Stop icon launching while we are in active state
this.gridView.stop();
window.dispatchEvent(new CustomEvent('gaiagrid-dragdrop-begin'));
this.hoverItem = null;
this.rearrangeDelay = null;
this.doRearrange = null;
this.enterEditMode();
this.container.classList.add('dragging');
this.icon.scale = ACTIVE_SCALE;
this.icon.setActive(true);
this.container.parentNode.style.overflow = 'hidden';
// Work around e.pageX/e.pageY being null (to make it easier to work with
// injected events, or old versions of Marionette)
this.initialPageX = (typeof e.pageX === 'undefined') ?
this.icon.x : e.pageX;
this.initialPageY = (typeof e.pageY === 'undefined') ?
this.icon.y : e.pageY;
this.currentTouch = {
pageX: this.initialPageX,
pageY: this.initialPageY
};
var items = this.gridView.items;
var lastElement = items[items.length - 1];
this.maxScroll = lastElement.y + lastElement.pixelHeight +
(this.icon.pixelHeight * ACTIVE_SCALE);
// If this is a group, or the sole icon in a group, make sure the
// surrounding groups are marked as invalid-drop so you can't initiate a
// move that would have no result.
var itemIndex = this.icon.detail.index;
if (this.icon.detail.type === 'divider') {
this.icon.element.classList.add('invalid-drop');
if (itemIndex > 0) {
for (var i = itemIndex - 1; i >= 0; i--) {
var item = items[i];
if (item.detail.type === 'divider') {
item.element.classList.add('invalid-drop');
break;
}
}
}
} else {
var itemBefore = itemIndex ? items[itemIndex - 1] : null;
var itemAfter = items[itemIndex + 1];
if ((itemAfter.detail.type === 'placeholder' ||
itemAfter.detail.type === 'divider') &&
(!itemBefore || itemBefore.detail.type === 'divider')) {
if (itemBefore) {
itemBefore.element.classList.add('invalid-drop');
}
var group, groupIndex = itemIndex;
do {
group = items[++groupIndex];
} while (group.detail.type !== 'divider');
group.element.classList.add('invalid-drop');
}
}
// Redraw the icon at the new position and scale
this.updateIconPosition();
// Rearrange grid to highlight the group underneath the icon
this.rearrangeGrid();
},
finish: function() {
// Complete the repositioning timeout
if (this.rearrangeGridTimeout !== null) {
this.rearrangeGrid();
}
// Remove the dragging property after the icon has transitioned into
// place to avoid jank due to animations starting that are disabled
// when dragging.
this.icon.element.addEventListener('transitionend', this);
this.currentTouch = null;
if (this.doRearrange !== null) {
if (this.rearrangeDelay) {
clearTimeout(this.rearrangeDelay);
}
this.doRearrange.call(this);
} else {
this.icon.requestAttention();
}
// Hand back responsibility to Grid view to render the dragged item.
this.icon.scale = 1;
this.icon.setActive(false);
this.icon.element.classList.remove('hovering');
this.target = null;
this.gridView.render();
// Save icon state if we need to
if (this.dirty) {
window.dispatchEvent(new CustomEvent('gaiagrid-saveitems'));
}
this.dirty = false;
setTimeout(function nextTick() {
this.gridView.start();
window.dispatchEvent(new CustomEvent('gaiagrid-dragdrop-finish'));
}.bind(this));
},
finalize: function() {
this.container.parentNode.style.overflow = '';
this.container.classList.remove('dragging');
this.container.classList.remove('hover-over-top');
if (this.icon) {
this.icon.element.removeEventListener('transitionend', this);
this.icon = null;
}
if (this.hoverItem) {
this.hoverItem.element.classList.remove('hovered');
this.hoverItem = null;
}
if (this.hoverGroup) {
this.hoverGroup.element.classList.remove('drop-target');
this.hoverGroup = null;
}
for (var i = 0, iLen = this.gridView.items.length;
i < iLen; i++) {
var item = this.gridView.items[i];
item.element.classList.remove('invalid-drop');
}
},
/**
* The closer to edge the faster (bigger step).
** Distance 0px -> 5 times faster
** Distance 25px -> 2.5 times faster
** Distance 50px (EDGE_PAGE_THRESHOLD) -> 0 times
*/
getScrollStep: function(distanceToEdge) {
var factor = maxScrollStepFactor;
if (distanceToEdge > 0) {
factor *= ((EDGE_PAGE_THRESHOLD - distanceToEdge) /
EDGE_PAGE_THRESHOLD);
}
return Math.round(scrollStep * factor);
},
/**
* Positions the dragged icon, rearranges the grid and scrolls the page if
* needed.
* The page is scrolled via javascript if an icon is being moved,
* and is within a percentage of a page edge.
*/
positionAndScrollIfNeeded: function() {
var touch = this.currentTouch;
if (!touch || !this.inDragAction) {
this.isScrolling = false;
return;
}
var scrollStep;
var docScroll = this.scrollable.scrollTop;
var distanceFromTop = Math.abs(touch.pageY - docScroll);
var distanceFromHeader = distanceFromTop -
(this.editHeaderElement ? this.editHeaderElement.clientHeight : 0);
this.isScrolling = true;
if (distanceFromTop > SCREEN_HEIGHT - EDGE_PAGE_THRESHOLD) {
var maxY = this.maxScroll;
scrollStep = this.getScrollStep(SCREEN_HEIGHT - distanceFromTop);
// We cannot exceed the maximum scroll value
if (touch.pageY >= maxY || maxY - touch.pageY < scrollStep) {
this.isScrolling = false;
}
} else if (touch.pageY > 0 && distanceFromHeader < EDGE_PAGE_THRESHOLD) {
// We cannot go below the minimum scroll value
scrollStep = -this.getScrollStep(distanceFromHeader);
scrollStep = Math.max(scrollStep, -this.scrollable.scrollTop);
if (scrollStep >= 0) {
this.isScrolling = false;
}
} else {
this.isScrolling = false;
}
if (!this.isScrolling) {
this.updateIconPosition();
this.deferredRearrangeGrid();
return;
}
this.updateIconPosition();
this.scrollable.scrollTop += scrollStep;
touch.pageY += scrollStep;
exports.requestAnimationFrame(this.positionAndScrollIfNeeded.bind(this));
},
/**
* Highlights the group of the given icon index, or the group if the index
* belongs to a group.
* @param {Integer} index The grid index of the icon or group.
*/
highlightGroup: function(index) {
for (var i = index, iLen = this.gridView.items.length;
i < iLen; i++) {
var item = this.gridView.items[i];
if (item.detail.type === 'divider') {
if (this.hoverGroup != item) {
if (this.hoverGroup) {
this.hoverGroup.element.classList.remove('drop-target');
}
this.hoverGroup = item;
item.element.classList.add('drop-target');
}
break;
}
}
},
inFirstGroup: function(index) {
for (var i = index; i >= 0; i--) {
if (this.gridView.items[i].detail.type === 'divider') {
return false;
}
}
return true;
},
/**
* Positions an icon on the grid using the current touch coordinates.
*/
updateIconPosition: function() {
var oldX = this.icon.x;
var oldY = this.icon.y;
var newX = this.currentTouch.pageX - (this.initialPageX - this.icon.x);
var newY = this.currentTouch.pageY - (this.initialPageY - this.icon.y);
// Adjust new icon coordinates for the slightly inflated scale so that
// it appears centered around the touch point. Dividers aren't scaled
// during dragging.
if (this.icon.detail.type !== 'divider') {
newX = newX - ((this.icon.scale * this.gridView.layout.gridItemWidth) -
this.gridView.layout.gridItemWidth) / 2;
newY = newY - ((this.icon.scale * this.icon.pixelHeight) -
this.icon.pixelHeight) / 2;
}
this.icon.setCoordinates(newX, newY);
this.icon.render();
this.icon.setCoordinates(oldX, oldY);
},
/**
* Will call rearrangeGrid after a short period, calculated based on how
* recently the grid was last rearranged.
*/
deferredRearrangeGrid: function() {
if (this.rearrangeGridTimeout !== null) {
return;
}
var delay = Math.max(
REARRANGE_FREQUENCY, REARRANGE_COOLDOWN -
Math.max(0, Date.now() - this.lastRepositionTime));
this.rearrangeGridTimeout = setTimeout(() => {
this.rearrangeGridTimeout = null;
this.rearrangeGrid();
}, delay);
},
/**
* Rearranges the grid icons and groups based on the current position of
* the dragged icon.
*/
rearrangeGrid: function() {
if (this.rearrangeGridTimeout !== null) {
clearTimeout(this.rearrangeGridTimeout);
this.rearrangeGridTimeout = null;
}
if (this.isScrolling) {
// We don't want the grid to shift while we're auto-scrolling
return;
}
var iconIsDivider = this.icon.detail.type === 'divider';
var pageX = this.currentTouch.pageX - (this.initialPageX - this.icon.x);
var pageY = this.currentTouch.pageY - (this.initialPageY - this.icon.y);
// Reposition in the icons array if necessary.
// Find the icon with the closest X/Y position of the move,
// and insert ours before it.
var foundIndex = 0;
var insertDividerAtTop = !this.gridView.config.features.disableSections;
pageX += this.gridView.layout.gridItemWidth / 2;
pageY += this.icon.pixelHeight / 2;
if (pageY >= 0) {
foundIndex =
this.gridView.getNearestItemIndex(pageX, pageY, iconIsDivider);
// If we're dragging a group over the first icon, a divider will
// be inserted at the top.
if (!(iconIsDivider && foundIndex === 0)) {
this.container.classList.remove('hover-over-top');
insertDividerAtTop = false;
}
}
// Clear the rearrange callback and hover item if we aren't hovering over
// anything.
this.doRearrange = null;
if (this.rearrangeDelay) {
clearTimeout(this.rearrangeDelay);
this.rearrangeDelay = null;
}
if (this.hoverItem) {
this.hoverItem.element.classList.remove('hovered');
this.hoverItem = null;
}
// Add the 'hovering' class to the dragged icon.
if (foundIndex !== null) {
this.icon.element.classList.add('hovering');
} else {
this.icon.element.classList.remove('hovering');
}
// Nothing to do if we find the dragged icon or no icon
if (!insertDividerAtTop &&
(foundIndex === null || foundIndex === this.icon.detail.index)) {
if (!iconIsDivider) {
this.highlightGroup(this.icon.detail.index);
}
return;
}
var foundItem = this.gridView.items[foundIndex];
// If we're a divider being dragged, only allow dropping on either the
// first item or another divider.
if (iconIsDivider &&
foundIndex > 0 &&
foundItem.detail.type !== 'divider') {
// Remove the hovering class from a group if it's not hovering over
// a valid drop position or itself.
this.icon.element.classList.remove('hovering');
return;
}
// If the item isn't a group, trigger the
// hovered state on the found item.
if (!insertDividerAtTop && !iconIsDivider) {
this.hoverItem = foundItem;
this.hoverItem.element.classList.add('hovered');
}
// Delay rearranging when we're dragging groups to when they're dropped
var rearrangeAfterDelay = !iconIsDivider;
// Determine if we want to create a new divider. We do this when hovering
// over a divider with an icon, or when dragging an icon to the very top
// of the grid.
var createDivider = insertDividerAtTop;
if (!insertDividerAtTop && !iconIsDivider &&
(foundItem.detail.type === 'divider')) {
// Allow dropping into a collapsed group if the new position is in
// the top 2/3 of the group.
if (foundItem.detail.collapsed &&
pageY <= foundItem.y + (foundItem.pixelHeight * 2/3)) {
rearrangeAfterDelay = false;
foundItem.element.classList.remove('hovered');
// When dragging an item forwards, it gets placed after the found
// item, adjust to the item before the divider.
if (this.icon.detail.index < foundIndex) {
foundItem = this.gridView.items[foundIndex - 1];
}
} else {
createDivider = true;
}
}
if (createDivider) {
// Remove the group background highlight if we're between groups
if (this.hoverGroup) {
this.hoverGroup.element.classList.remove('drop-target');
this.hoverGroup = null;
}
// Cancel rearrangement if it would have no effect.
var redundantRearrange = false;
if (insertDividerAtTop) {
if (iconIsDivider) {
redundantRearrange = this.inFirstGroup(this.icon.detail.index - 1);
} else {
redundantRearrange =
(this.icon.detail.index === 0 &&
this.gridView.items[1].detail.type === 'placeholder');
}
} else {
redundantRearrange = this.gridView.items[foundIndex].element.
classList.contains('invalid-drop');
}
if (redundantRearrange) {
if (this.hoverItem) {
this.hoverItem.element.classList.remove('hovered');
this.hoverItem = null;
}
return;
} else if (insertDividerAtTop) {
this.container.classList.add('hover-over-top');
}
this.doRearrange =
this.createNewDivider.bind(this,
insertDividerAtTop ? null : foundItem);
} else {
if (!iconIsDivider) {
// Change the display of the group the icon is hovering over to
// indicate it can be dropped.
this.highlightGroup(foundIndex);
}
this.doRearrange = this.rearrange.bind(this, foundItem);
if (rearrangeAfterDelay) {
this.rearrangeDelay =
setTimeout(this.doRearrange.bind(this), REARRANGE_DELAY);
}
}
},
/**
* Creates a new divider in GridView.items and rearranges the currently
* dragged icon into the newly created section.
* @param {Object} tDivider The divider after which to place a new divider,
* or null to insert a divider at the beginning on the grid.
*/
createNewDivider: function(tDivider) {
var items = this.gridView.items;
var tIndex = tDivider ? items.indexOf(tDivider) + 1 : 0;
// Create the new divider
var newDivider = new GaiaGrid.Divider();
items.splice(tIndex, 0, newDivider);
// Place the dragged item into the new empty section
var sIndex = items.indexOf(this.icon);
this.rearrange(sIndex >= tIndex ? newDivider : tDivider);
// Make sure the new divider/group is in view
newDivider.requestAttention();
},
/**
* Rearranges items in GridView.items
* @param {Object} tItem The item to position the dragged icon at.
*/
rearrange: function(tItem) {
// Clear the rearrange callback
this.doRearrange = null;
this.rearrangeDelay = null;
// We get a reference to the position of this.icon within the items
// array. Because placeholders are shifting around while we are dragging,
// we can't trust the detail.index attribute. This will be fixed on every
// render call though.
var sIndex = this.gridView.items.indexOf(this.icon);
var tIndex = this.gridView.items.indexOf(tItem);
// Check how many items we need to rearrange (if it's a group, there will
// be multiple items.
var eIndex = sIndex;
if (this.icon.detail.type === 'divider') {
for (; sIndex > 0 &&
this.gridView.items[sIndex - 1].detail.type !== 'divider';
sIndex--) {}
// Modify the tIndex to make sure we're inserting immediately after
// another divider or at the start of the container.
if (this.gridView.items[tIndex].detail.type === 'divider') {
tIndex++;
} else {
tIndex = 0;
}
if (sIndex === tIndex) {
// Nothing to do.
return;
}
} else if (sIndex < tIndex) {
tIndex++;
}
// We may have changed tIndex, so refresh our item pointer
tItem = this.gridView.items[tIndex];
// Remove the items from the array
var lastItemOffset = eIndex - sIndex;
var toInsert = this.gridView.items.splice(sIndex, lastItemOffset + 1);
// Craft the parameter list to reinsert the items at the correct place
toInsert.unshift(tItem ? this.gridView.items.indexOf(tItem) :
this.gridView.items.length, 0);
// Reinsert items
this.rearrangeDelay = null;
this.dirty = true;
this.gridView.items.splice.apply(this.gridView.items, toInsert);
var oldX = this.icon.x;
var oldY = this.icon.y;
this.gridView.render();
// In this case, we don't want to compensate for the icon moving, so
// we need to correct our initial values to stop the icon from jumping
// after rearranging.
this.initialPageX -= oldX - this.icon.x;
this.initialPageY -= oldY - this.icon.y;
this.lastRepositionTime = Date.now();
},
enterEditMode: function() {
this.inEditMode = true;
this.container.classList.add('edit-mode');
document.body.classList.add('edit-mode');
this.gridView.element.dispatchEvent(
new CustomEvent('editmode-start'));
document.addEventListener('visibilitychange', this);
this.gridView.render();
},
exitEditMode: function() {
// If we're in the middle of a drag, cancel it.
if (this.icon) {
this.finish();
this.finalize();
}
this.inEditMode = false;
this.cancelLongPressTimeout();
this.container.classList.remove('edit-mode');
document.body.classList.remove('edit-mode');
this.gridView.element.dispatchEvent(new CustomEvent('editmode-end'));
document.removeEventListener('visibilitychange', this);
this.gridView.render();
},
cancelLongPressTimeout: function() {
if (this.longPressTimeout !== null) {
clearTimeout(this.longPressTimeout);
this.longPressTimeout = null;
}
},
inLongPressThreshold: function(x, y) {
return (
Math.abs(x - this.touchStart.screenX) < EDIT_LONG_PRESS_THRESHOLD &&
Math.abs(y - this.touchStart.screenY) < EDIT_LONG_PRESS_THRESHOLD);
},
/**
* General event handler.
*/
handleEvent: function(e) {
switch(e.type) {
case 'visibilitychange':
if (document.hidden) {
this.exitEditMode();
}
break;
case 'touchstart':
this.cancelLongPressTimeout();
if (e.touches.length > 1) {
if (this.inDragAction) {
this.finish();
}
return;
}
if (this.inEditMode && !this.icon) {
this.touchStart.x = e.touches[0].pageX;
this.touchStart.y = e.touches[0].pageY;
this.touchStart.screenX = e.touches[0].screenX;
this.touchStart.screenY = e.touches[0].screenY;
this.longPressTimeout = setTimeout(() => {
this.longPressTimeout = null;
this.handleEvent({
type: 'contextmenu',
target: e.target,
pageX: this.touchStart.x,
pageY: this.touchStart.y,
preventDefault: function() {},
stopImmediatePropagation: function() {}
});
}, EDIT_LONG_PRESS_DELAY);
}
break;
case 'contextmenu':
this.cancelLongPressTimeout();
if (this.icon) {
e.stopImmediatePropagation();
e.preventDefault();
return;
}
if (e.defaultPrevented) {
// other handlers already handled this.
// in the future, we should use the shadow root and dispatch a
// "contextmenu" event from here instead.
return;
}
if (!e.target) {
return;
}
this.icon = this.gridView.findItemFromElement(e.target, true);
if (!this.icon || !this.icon.element || !this.icon.isDraggable() ||
this.icon.detail.type === 'placeholder') {
this.icon = null;
return;
}
e.stopImmediatePropagation();
e.preventDefault();
this.target = this.icon.element;
this.begin(e);
break;
case 'touchmove':
var touch = e.touches[0];
if (this.inDragAction) {
this.currentTouch = {
pageX: touch.pageX,
pageY: touch.pageY
};
if (!this.isScrolling) {
this.positionAndScrollIfNeeded();
}
} else if (this.longPressTimeout !== null &&
!this.inLongPressThreshold(touch.screenX,
touch.screenY)) {
this.cancelLongPressTimeout();
}
break;
case 'click':
case 'touchcancel':
if (this.inDragAction) {
this.finish();
this.finalize();
}
this.cancelLongPressTimeout();
break;
case 'touchend':
if (this.inDragAction) {
// Ensure the app is not launched
e.stopImmediatePropagation();
e.preventDefault();
// As contextmenu event can be synthesized, it's possible for it
// to happen in the same event-loop as touchend, meaning
// finish would be called immediately after begin. This would
// mean that the icon doesn't get a chance to transition and
// the transitionend that we expect won't be called, so finalize
// also won't be called, leaving us in an inconsistent state.
// It would be so great if there was a transitionstart event :(
setTimeout(() => { this.finish(); }, TOUCH_END_FINISH_DELAY);
}
this.cancelLongPressTimeout();
break;
case 'transitionend':
this.finalize();
break;
}
}
};
exports.GridDragDrop = DragDrop;
}(window));

View file

@ -0,0 +1,3 @@
;'use strict';(function(exports){const SHADOW_BLUR=1;const SHADOW_OFFSET_Y=1;const SHADOW_OFFSET_X=1;const SHADOW_COLOR='rgba(0, 0, 0, 0.2)';const DEFAULT_BACKGROUND_COLOR='rgb(228, 234, 238)';const UNSCALED_CANVAS_PADDING=2;const CANVAS_PADDING=UNSCALED_CANVAS_PADDING*devicePixelRatio;function IconRenderer(icon){this._icon=icon;}
IconRenderer.TYPE={CLIP:'clip',FAVICON:'favicon',STANDARD:'standard',};IconRenderer.prototype={unscaledCanvasPadding:UNSCALED_CANVAS_PADDING*2,get _maxSize(){return this._icon.grid.layout.gridMaxIconSize;},_createCanvas:function(){const CANVAS_PADDING_BOTTOM=1*devicePixelRatio;var canvas=document.createElement('canvas');canvas.width=this._maxSize+(CANVAS_PADDING*2);canvas.height=this._maxSize+CANVAS_PADDING+CANVAS_PADDING_BOTTOM;return canvas;},_createClipCanvas:function(){var canvas=document.createElement('canvas');canvas.width=this._maxSize+(CANVAS_PADDING*2);canvas.height=this._maxSize+(CANVAS_PADDING*2);return canvas;},_decorateShadowCanvas:function(canvas){var ctx=canvas.getContext('2d',{willReadFrequently:true});ctx.shadowColor=SHADOW_COLOR;ctx.shadowBlur=SHADOW_BLUR;ctx.shadowOffsetY=SHADOW_OFFSET_Y;ctx.shadowOffsetX=SHADOW_OFFSET_X;return ctx;},clip:function(img){return new Promise((resolve)=>{var shadowCanvas=this._createCanvas();var shadowCtx=this._decorateShadowCanvas(shadowCanvas);var clipCanvas=this._createClipCanvas();var clipCtx=clipCanvas.getContext('2d',{willReadFrequently:true});clipCtx.beginPath();clipCtx.arc(clipCanvas.width/2,clipCanvas.height/2,clipCanvas.height/2,0,2*Math.PI);clipCtx.clip();clipCtx.drawImage(img,0,0,clipCanvas.width,clipCanvas.height);shadowCtx.drawImage(clipCanvas,CANVAS_PADDING,CANVAS_PADDING,this._maxSize,this._maxSize);shadowCanvas.toBlob(resolve);});},favicon:function(img){if(img.width>this._icon.grid.layout.gridIconSize/2){return this.clip(img);}
return new Promise((resolve)=>{var shadowCanvas=this._createCanvas();var shadowCtx=this._decorateShadowCanvas(shadowCanvas);var iconWidth;var iconHeight;shadowCtx.beginPath();shadowCtx.arc(shadowCanvas.width/2,shadowCanvas.height/2,shadowCanvas.height/2-CANVAS_PADDING,0,2*Math.PI,false);shadowCtx.fillStyle=DEFAULT_BACKGROUND_COLOR;shadowCtx.fill();iconWidth=iconHeight=this._maxSize*0.55;shadowCtx.shadowBlur=0;shadowCtx.shadowOffsetY=0;shadowCtx.mozImageSmoothingEnabled=false;shadowCtx.drawImage(img,(shadowCanvas.width-iconWidth)/2,(shadowCanvas.height-iconHeight)/2,iconWidth,iconHeight);shadowCanvas.toBlob(resolve);});},standard:function(img){return new Promise((resolve)=>{var shadowCanvas=this._createCanvas();var shadowCtx=this._decorateShadowCanvas(shadowCanvas);shadowCtx.drawImage(img,CANVAS_PADDING,CANVAS_PADDING,this._maxSize,this._maxSize);shadowCanvas.toBlob(resolve);});}};exports.GridIconRenderer=IconRenderer;}(window));

View file

@ -0,0 +1,9 @@
;'use strict';(function(exports){const maxIconsPerRow=4;const iconScaleFactorMaxIconsPerRow=5;const minIconsPerRow=3;const iconScaleFactorMinIconsPerRow=3.8;const distanceBetweenIconsWithMinIconsPerRow=36;const distanceBetweenIconsWithMaxIconsPerRow=38;const eachTextRowHeight=20;var constraintSize;function GridLayout(gridView){this.gridView=gridView;if(window.verticalPreferences){verticalPreferences.get('grid.cols').then(value=>{this.cols=value;this.onReady();},this.onReady);verticalPreferences.addEventListener('updated',this);}else{this.onReady();}}
GridLayout.prototype={minIconsPerRow:minIconsPerRow,maxIconsPerRow:maxIconsPerRow,_cols:minIconsPerRow,_offsetY:0,_percent:1,_dividerLineHeight:0,get percent(){return this._percent;},get cols(){return this._cols;},set cols(value){if(!value||value===this._cols){return;}
if(window.verticalPreferences){verticalPreferences.put('grid.cols',value);}
this._offsetY=0;this._percent=value==minIconsPerRow?1:minIconsPerRow/maxIconsPerRow;this._cols=value;},get constraintSize(){if(!constraintSize){this.calculateSize();}
return constraintSize;},get gridItemHeight(){var height=this.gridIconSize+
(this._cols===minIconsPerRow?distanceBetweenIconsWithMinIconsPerRow:distanceBetweenIconsWithMaxIconsPerRow);var defaultTextRows=2;var definedRows=parseInt(this.gridView.element.getAttribute('text-rows'),10);if(definedRows>defaultTextRows){height+=(definedRows-defaultTextRows)*eachTextRowHeight;}
return height;},get gridItemWidth(){return this.constraintSize/this._cols;},get gridMaxIconSize(){var baseSize=(this.constraintSize/iconScaleFactorMinIconsPerRow);return baseSize*devicePixelRatio;},get gridIconSize(){var numCols=this._cols;var size=this.constraintSize/numCols;if(numCols===minIconsPerRow){size=this.constraintSize/iconScaleFactorMinIconsPerRow;}else if(numCols===maxIconsPerRow){size=this.constraintSize/iconScaleFactorMaxIconsPerRow;}
return Math.floor(size);},get offsetY(){return this._offsetY;},set offsetY(value){this._offsetY=value;},stepYAxis:function(value){this._offsetY+=value;},handleEvent:function(e){switch(e.type){case'updated':var prop=e.target;if(prop.name==='grid.cols'){this.cols=parseInt(prop.value,10);this.gridView.render();}
break;}},calculateSize:function(){constraintSize=Math.min(window.screen.width,window.screen.height);},onReady:function(){window.dispatchEvent(new CustomEvent('gaiagrid-layout-ready'));}};exports.GridLayout=GridLayout;}(window));

View file

@ -0,0 +1,47 @@
;'use strict';(function(exports){const APP_LAUNCH_TIMEOUT=3000;function GridView(config){this.config=config;this.clickIcon=this.clickIcon.bind(this);this.onVisibilityChange=this.onVisibilityChange.bind(this);if(config.features.zoom){this.zoom=new GridZoom(this);}
this.layout=new GridLayout(this);if(config.element.hasAttribute('cols')){this.layout.cols=parseInt(config.element.getAttribute('cols'),10);}
this.start();}
GridView.prototype={icons:{},items:[],get element(){return this.config.element;},set cols(value){this.layout.cols=value;},_launchingApp:false,add:function(item,insertTo,expandGroup){if(!item){return;}
if(item.identifier){if(this.icons[item.identifier]){console.log('Error, duplicate identifier: ',item.identifier,new Error().stack);return;}
this.icons[item.identifier]=item;}else if(item.detail.type!=='divider'&&item.detail.type!=='placeholder'){console.log('Error, could not load identifier for object: ',JSON.stringify(item.detail));return;}
if(!isNaN(parseFloat(insertTo))&&isFinite(insertTo)){this.items.splice(insertTo,0,item);}else{insertTo=this.items.length;this.items.push(item);}
if(expandGroup){for(var i=insertTo+1,iLen=this.items.length;i<iLen;i++){var divider=this.items[i];if(divider.detail.type==='divider'){if(divider.detail.collapsed){divider.expand();}
break;}}}},getNearestItemIndex:function(x,y,isRow){var foundIndex=null;var leastDistance=null;var itemMiddleOffset=this.layout.gridItemWidth/2;for(var i=0,iLen=this.items.length;i<iLen;i++){var item=this.items[i];if(!item.isDraggable()){continue;}
if(this.config.features.disableSections&&item.detail.type==='divider'){continue;}
if(item.detail.type!=='divider'&&item.element.classList.contains('collapsed')){continue;}
var middleX=item.x+itemMiddleOffset;var middleY=item.y+item.pixelHeight/2;var xDistance=(isRow||item.detail.type==='divider')?0:x-middleX;var yDistance=y-middleY;var distance=Math.sqrt(xDistance*xDistance+
yDistance*yDistance);if(leastDistance===null||distance<leastDistance){leastDistance=distance;foundIndex=i;}}
return foundIndex;},start:function(){this.element.addEventListener('click',this.clickIcon);window.addEventListener('visibilitychange',this.onVisibilityChange);},stop:function(){this.element.removeEventListener('click',this.clickIcon);window.removeEventListener('visibilitychange',this.onVisibilityChange);},findItemFromElement:function(element,excludeCollapsedIcons){while(element&&element.parentNode!==this.element){element=element.parentNode;}
if(!element){return null;}
var i,iLen=this.items.length;var identifier=element.dataset.identifier;var icon=this.icons[identifier];if(!icon){for(i=0;i<iLen;i++){if(this.items[i].element===element){icon=this.items[i];break;}}}
if(icon&&excludeCollapsedIcons){if(icon.detail.type!=='divider'&&icon.detail.type!=='placeholder'&&icon.element.classList.contains('collapsed')){for(i=icon.detail.index+1;i<iLen;i++){if(this.items[i].detail.type==='divider'){return this.items[i];}}
console.warn('Collapsed icon found with no group');icon=null;}}
return icon;},onVisibilityChange:function(){this._launchingApp=false;},clickIcon:function(e){e.preventDefault();var inEditMode=this.dragdrop&&this.dragdrop.inEditMode;var action='launch';if(e.target.classList.contains('remove')){action='remove';}
var icon=this.findItemFromElement(e.target);if(!icon){return;}
if(action==='launch'){if(inEditMode&&e.target.classList.contains('icon')){if(!icon.isEditable()){return;}
action='edit';}else{if(!icon[action]){return;}
icon.element.classList.add('launching');var returnTimeout=500;setTimeout(function stateReturn(){if(icon.element){icon.element.classList.remove('launching');}},returnTimeout);}}
if((icon.detail.type==='app'||icon.detail.type==='bookmark')&&this._launchingApp){return;}
if((icon.detail.type==='app'&&icon.appState==='ready')||icon.detail.type==='bookmark'){this._launchingApp=true;if(this._launchingTimeout){window.clearTimeout(this._launchingTimeout);this._launchingTimeout=null;}
this._launchingTimeout=window.setTimeout(function(){this._launchingTimeout=null;this._launchingApp=false;}.bind(this),APP_LAUNCH_TIMEOUT);}
icon[action](e.target);},cleanItems:function(skipDivider){var appCount=0;var toRemove=[];this.items.forEach(function(item,idx){if(item instanceof GaiaGrid.Divider){if(appCount===0){toRemove.push(idx);}
appCount=0;}else{appCount++;}},this);toRemove.reverse();toRemove.forEach(function(idx){var removed=this.items.splice(idx,1)[0];removed.remove();},this);if(skipDivider){return;}
var lastItem=this.items[this.items.length-1];if(!lastItem||!(lastItem instanceof GaiaGrid.Divider)){this.items.push(new GaiaGrid.Divider());}},removeAllPlaceholders:function(){var toSplice=[];var previousItem;this.items.forEach(function(item,idx){if(item instanceof GaiaGrid.Placeholder){if((!previousItem||(previousItem&&previousItem instanceof GaiaGrid.Divider))&&this.dragdrop&&this.dragdrop.inDragAction){return;}
toSplice.push(idx);}
previousItem=item;},this);toSplice.reverse().forEach(function(idx){this.items.splice(idx,1)[0].remove();},this);},clear:function(){for(var i=0,iLen=this.items.length;i<iLen;i++){var item=this.items[i];if(item.element){this.element.removeChild(item.element);item.element=null;item.lastX=null;item.lastY=null;item.lastScale=null;}}
this.items=[];this.icons={};},createPlaceholders:function(coordinates,idx,count){var isRTL=(document.documentElement.dir==='rtl');for(var i=0;i<count;i++){var item=new GaiaGrid.Placeholder();this.items.splice(idx+i,0,item);item.setPosition(idx+i);var xPosition=(coordinates[0]+i)*this.layout.gridItemWidth;if(isRTL){xPosition=(this.layout.constraintSize-this.layout.gridItemWidth)-
xPosition;}
item.setCoordinates(xPosition,this.layout.offsetY);item.render();}},render:function(options){var self=this;options=options||{};this.removeAllPlaceholders();this.cleanItems(options.skipDivider);var oldHeight=this.layout.offsetY;this.layout.offsetY=0;var x=0;var y=0;function step(item){var pixelHeight=item.pixelHeight;self.layout.stepYAxis(pixelHeight);x=0;y++;}
var pendingCachedIcons=0;var onCachedIconRendered=()=>{if(--pendingCachedIcons<=0){this.element.removeEventListener('cached-icon-rendered',onCachedIconRendered);this.element.dispatchEvent(new CustomEvent('cached-icons-rendered'));}};this.element.addEventListener('cached-icon-rendered',onCachedIconRendered);var nextDivider=null;var oddDivider=true;var isRTL=(document.documentElement.dir==='rtl');for(var idx=0;idx<this.items.length;idx++){var item=this.items[idx];if(options.rerender&&item.element){this.element.removeChild(item.element);item.element=null;}
if(item.detail.type==='divider'){nextDivider=null;}else{if(!nextDivider){for(var i=idx+1;i<this.items.length;i++){if(this.items[i].detail.type==='divider'){nextDivider=this.items[i];oddDivider=!oddDivider;break;}}
if(nextDivider&&!nextDivider.detail.collapsed){this.layout.offsetY+=nextDivider.headerHeight;}}
if(nextDivider&&nextDivider.detail.collapsed){item.setPosition(idx);continue;}}
if(x>0&&item.gridWidth>1&&x+item.gridWidth>=this.layout.cols){var remaining=this.layout.cols-x;this.createPlaceholders([x,y],idx,remaining);idx+=remaining;item=this.items[idx];var lastItemInRow=this.items[idx-1];step(lastItemInRow);}
item.setPosition(idx);if(!options.skipItems){item.hasCachedIcon&&++pendingCachedIcons;var xPosition=x*this.layout.gridItemWidth;if(isRTL){xPosition=(this.layout.constraintSize-this.layout.gridItemWidth)-
xPosition;}
item.setCoordinates(xPosition,this.layout.offsetY);if(!item.active){item.render();}
if(item.detail.type==='divider'){if(oddDivider){item.element.classList.add('odd');}else{item.element.classList.remove('odd');}}}
x+=item.gridWidth;if((x>=this.layout.cols)&&(idx<this.items.length-1)){step(item);}}
if(this.layout.offsetY!=oldHeight){if(this.dragdrop&&this.dragdrop.inDragAction){this.layout.offsetY=oldHeight;}else{this.element.dispatchEvent(new CustomEvent('gaiagrid-resize',{detail:this.layout.offsetY}));}}
this.element.setAttribute('cols',this.layout.cols);pendingCachedIcons===0&&onCachedIconRendered();this.loadDragDrop();},loadDragDrop:function(){if(!this.dragdrop&&this.config.features.dragdrop){LazyLoader.load('shared/elements/gaia_grid/js/grid_dragdrop.js',()=>{if(this.dragdrop){return;}
this.dragdrop=new GridDragDrop(this);});}}};exports.GridView=GridView;}(window));

View file

@ -0,0 +1,160 @@
'use strict';
(function(exports) {
const pinchThreshold = Math.round(window.innerWidth / 12);
function GridZoom(gridView) {
this.gridView = gridView;
this.touches = 0;
this.zoomStartTouches = [];
this.zoomStartDistance = 0;
this.container = document.getElementById('zoom');
this.arrows = this.container.querySelector('.arrows');
this.curtain = this.container.querySelector('.curtain');
this.indicator = this.container.querySelector('.indicator');
this.start();
window.addEventListener('gaiagrid-dragdrop-begin', this.stop.bind(this));
window.addEventListener('gaiagrid-dragdrop-finish', this.start.bind(this));
}
GridZoom.prototype = {
/**
* Starts listening for touchstart events.
* This is what starts listening events to start the drag & drop operation.
*/
start: function() {
window.addEventListener('touchstart', this);
},
/**
* Stops listening for touchstart events.
* This will effectively disable pinch-to-zoom.
*/
stop: function() {
window.removeEventListener('touchstart', this);
},
_attachGestureListeners: function() {
window.addEventListener('touchmove', this);
window.addEventListener('touchend', this);
},
_stopGestureListeners: function() {
window.removeEventListener('touchmove', this);
window.removeEventListener('touchend', this);
},
_resetState: function() {
this.zoomInProgress = false;
this.container.hidden = true;
this.indicator.classList.remove('active');
this.arrows.classList.remove('zooming', 'grow', 'shrink');
this.arrows.style.transform = '';
},
/**
* General Event Handler
*/
handleEvent: function(e) {
if (e.type === 'touchend' && this.zoomStartTouches) {
if (!this.zoomInProgress) {
this._resetState();
}
this._stopGestureListeners();
return;
}
if (!e.touches || e.touches.length !== 2) {
return;
}
// Sort touches by ascending pageX position.
var touches = [e.touches[0], e.touches[1]].sort(function(a, b) {
return a.pageX - b.pageX;
});
var touchDistance = Math.sqrt(
(touches[0].pageX - touches[1].pageX) *
(touches[0].pageX - touches[1].pageX) +
(touches[0].pageY - touches[1].pageY) *
(touches[0].pageY - touches[1].pageY));
var layout = this.gridView.layout;
switch(e.type) {
case 'touchstart':
this.container.hidden = false;
this.zoomStartTouches = touches;
this.zoomStartDistance = touchDistance;
if (layout.cols < layout.maxIconsPerRow) {
this.arrows.classList.add('grow');
} else {
this.arrows.classList.add('shrink');
}
this.indicator.dataset.cols = layout.cols;
this._attachGestureListeners();
break;
case 'touchmove':
if (layout.cols < layout.maxIconsPerRow &&
touchDistance < this.zoomStartDistance &&
Math.abs(touchDistance - this.zoomStartDistance) >
pinchThreshold) {
this.gridView.cols = layout.maxIconsPerRow;
this.zoomInProgress = true;
} else if (layout.cols > layout.minIconsPerRow &&
touchDistance > this.zoomStartDistance &&
Math.abs(touchDistance - this.zoomStartDistance) >
pinchThreshold) {
this.gridView.cols = layout.minIconsPerRow;
this.zoomInProgress = true;
} else {
return;
}
// For now just implement a canned animation
this._stopGestureListeners();
var ontransitionend = function() {
this.arrows.removeEventListener('transitionend', ontransitionend);
// Change the indicator color at the end.
this.indicator.classList.add('active');
this.gridView.render();
// Reset zoom element state after a set time.
var zoomHideTime = 400;
setTimeout(this._resetState.bind(this), zoomHideTime);
}.bind(this);
this.arrows.addEventListener('transitionend', ontransitionend);
this.indicator.dataset.cols = layout.cols;
this.arrows.classList.add('zooming');
// Force a sync reflow
document.body.clientHeight;
var scaleTransform = 'scale(1)';
if (this.zoomInProgress && touchDistance < this.zoomStartDistance) {
scaleTransform = 'scale(0.4)';
}
this.arrows.style.transform = scaleTransform;
break;
}
}
};
exports.GridZoom = GridZoom;
}(window));

View file

@ -0,0 +1,4 @@
;'use strict';(function(exports){const TYPE='bookmark';function Bookmark(record,features){this.detail=record;this.features=features||{};this.detail.type=TYPE;}
Bookmark.prototype={__proto__:GaiaGrid.GridItem.prototype,renderer:GridIconRenderer.TYPE.FAVICON,get pixelHeight(){return this.grid.layout.gridItemHeight;},gridWidth:1,get name(){return this.detail.name;},get icon(){return this.detail.icon||this.defaultIcon;},get identifier(){return this.detail.id;},update:GaiaGrid.GridItem.prototype.updateFromDatastore,isEditable:function(){return this.features&&this.features.isEditable!==false;},isRemovable:function(){return this.features&&this.features.isRemovable!==false;},isDraggable:function(){return this.features&&this.features.isDraggable!==false;},render:function(){GaiaGrid.GridItem.prototype.render.call(this);this.element.classList.add('bookmark');if(this.isEditable()){this.element.classList.add('editable');}},launch:function(){var features={name:this.name,icon:this.icon,remote:true};var url=this.detail.url;if(this.features.search){features.searchName=this.name;features.searchUrl=url;}
window.open(url,'_samescope',Object.keys(features).map(function eachFeature(key){return encodeURIComponent(key)+'='+
encodeURIComponent(features[key]);}).join(','));},edit:function(){new MozActivity({name:'save-bookmark',data:{type:'url',url:this.detail.id}});}};exports.GaiaGrid.Bookmark=Bookmark;}(window));

View file

@ -0,0 +1,3 @@
;'use strict';(function(exports){function Divider(){this.detail={type:'divider',index:0};}
Divider.prototype={__proto__:GaiaGrid.GridItem.prototype,x:0,y:0,headerHeight:0,get pixelHeight(){return(this.grid.layout.cols>3)?50:60;},gridWidth:4,scale:1,render:function(){if(!this.element){var divider=this.element=document.createElement('section');divider.className='divider';var span=document.createElement('span');span.className='spacer';divider.appendChild(span);this.grid.element.appendChild(divider);}
this.element.style.transform='translate(0 ,'+this.y+'px)';},remove:function(){if(this.element){this.element.parentNode.removeChild(this.element);}},isDraggable:function(){return false;}};exports.GaiaGrid.Divider=Divider;}(window));

View file

@ -0,0 +1,31 @@
;'use strict';(function(exports){const ICON_BLOB_DECORATED_EVENT='iconblobdecorated';const ICON_BLOB_ERROR_EVENT='gaiagrid-iconbloberror';const FETCH_XHR_TIMEOUT=10000;function fetchBlob(uri){return new Promise(function(accept,reject){var xhr=new XMLHttpRequest({mozAnon:true,mozSystem:true});xhr.open('GET',uri,true);xhr.responseType='blob';xhr.timeout=FETCH_XHR_TIMEOUT;xhr.send();xhr.onload=function(){var status=xhr.status;if(status!==0&&status!==200){reject(new Error('Got HTTP status '+status+' trying to load '+uri));return;}
accept(xhr.response);};xhr.onerror=xhr.ontimeout=function(){reject(new Error('Error while HTTP GET: ',uri));};});}
function GridItem(){this.detail={};}
GridItem.prototype={x:0,y:0,pixelHeight:0,gridWidth:1,scale:1,persistToDB:true,lastX:null,lastY:null,lastScale:null,get hasCachedIcon(){return this.detail&&this.detail.decoratedIconBlob;},renderer:GridIconRenderer.TYPE.STANDARD,iconState:'none',get defaultIcon(){return'/shared/elements/gaia_grid/images/default_icon.png';},get grid(){return document.getElementsByTagName('gaia-grid')[0]._grid;},isRemovable:function(){return false;},isEditable:function(){return false;},isDraggable:function(){return true;},isIconFromOrigin:function(){return this.icon.startsWith('http')||this.icon.startsWith('app:');},setPosition:function(position){this.detail.index=position;},setCoordinates:function(x,y){this.x=x;this.y=y;},requestAttention:function(){var rect={x:this.x,y:this.y,width:this.gridWidth*this.grid.layout.gridItemWidth,height:this.pixelHeight};this.grid.element.dispatchEvent(new CustomEvent('gaiagrid-attention',{detail:rect}));},closestIconSizeFromList:function(choices){if(!choices){return 0;}
var sizes=Object.keys(choices);if(!sizes.length){return 0;}
var maxSize=this.grid.layout.gridMaxIconSize;var filter=(a)=>a>=maxSize?a:0;var bestSize=Math.min(...sizes.map(filter));bestSize=bestSize||Math.max(...sizes);return bestSize||0;},closestIconFromList:function(choices){var icon;if(Array.isArray(choices)){var manifest={'icons':choices};var maxSize=this.grid.layout.gridMaxIconSize;icon=window.IconsHelper.getBestIconFromWebManifest(manifest,maxSize);return icon?(new URL(icon,this.app.manifestURL)).href:null;}
var accurateSize=this.closestIconSizeFromList(choices);if(accurateSize===0){return this.defaultIcon;}
icon=choices[accurateSize];if(!UrlHelper.hasScheme(icon)){icon=this.app.origin+icon;}
return icon;},renderIconFromSrc:function(url){var background=new Image();background.src=url;background.onload=this._decorateIcon.bind(this,background);},renderIconFromBlob:function(blob){var background=new Image();background.src=URL.createObjectURL(blob);background.onload=()=>{this._decorateIcon(background);URL.revokeObjectURL(background.src);};background.onerror=()=>{URL.revokeObjectURL(background.src);if(this.hasCachedIcon){this._stampElementWithIcon('blobcache');this._displayDecoratedIcon(this.detail.decoratedIconBlob,true);return;}
this.renderIconFromSrc(this.defaultIcon);this._stampElementWithIcon(this.defaultIcon);};},_decorateIcon:function(img){var strategy=this.detail.renderer||this.renderer;this.rendererInstance=new GridIconRenderer(this);this.rendererInstance[strategy](img).then(this._displayDecoratedIcon.bind(this));},_displayDecoratedIcon:function(blob,isCachedIcon){if(!this.element){return;}
var style=this.element.style;if(!style.backgroundSize){style.height=this.grid.layout.gridItemHeight+'px';var backgroundHeight=((this.grid.layout.gridIconSize*(1/this.grid.layout.percent))+
GridIconRenderer.prototype.unscaledCanvasPadding);if(this.grid.layout.gridItemHeight>backgroundHeight){style.backgroundSize=backgroundHeight+'px';}else{style.backgroundSize=style.height;}}
if(isCachedIcon){var url=URL.createObjectURL(blob);style.backgroundImage='url('+url+')';this.element.dataset.backgroundImage=url;var img=new Image();img.onload=img.onerror=()=>{this.grid.element.dispatchEvent(new CustomEvent('cached-icon-rendered'));};img.src=url;return;}
this._compareBlobs(blob,this.detail.decoratedIconBlob).then((equal)=>{if(equal){return;}
style.backgroundImage='url('+URL.createObjectURL(blob)+')';this.detail.decoratedIconBlob=blob;this.grid.element.dispatchEvent(new CustomEvent(ICON_BLOB_DECORATED_EVENT,{detail:this}));var bgImg=this.element.dataset.backgroundImage;bgImg&&URL.revokeObjectURL(bgImg);});},_compareBlobs:function(blob1,blob2){return new Promise(function(resolve){if(!blob1||!blob2||blob1.type!==blob2.type||blob1.size!==blob2.size){resolve(false);return;}
var startBytes=127;var bytesHash=16;var reader=new FileReader();reader.onloadend=function(){var result1=reader.result;reader=new FileReader();reader.onloadend=reader.onerror=function(){resolve(result1===reader.result);};reader.readAsDataURL(blob2.slice(startBytes,startBytes+bytesHash));};reader.onerror=function(){resolve(false);};reader.readAsDataURL(blob1.slice(startBytes,startBytes+bytesHash));});},fetchIconBlob:function(){return fetchBlob(this.icon);},_stampElementWithIcon:function(uri){if(!this.element||uri.startsWith('data:')){return;}
if(uri.length>255){uri=uri.slice(0,255);}
this.element.dataset.testIcon=uri;},renderIcon:function(renderCachedIcon){var icon=this.icon;this.iconState='pending';if(!icon){return;}
if(renderCachedIcon&&this.hasCachedIcon){this._displayDecoratedIcon(this.detail.decoratedIconBlob,true);var resolveIcon=()=>{this.grid.element.removeEventListener('cached-icons-rendered',resolveIcon);this.doRenderIcon(icon);};this.grid.element.addEventListener('cached-icons-rendered',resolveIcon);}else{this.doRenderIcon(icon);}},doRenderIcon:function(icon){if(!this.isIconFromOrigin()){this._stampElementWithIcon(icon);this.renderIconFromSrc(icon);this.iconState='success';return Promise.resolve();}
var eventTarget=this.grid.element;var blobNotFound=()=>{this.iconState='error';if(!this.hasCachedIcon){this.renderIconFromSrc(this.defaultIcon);this._stampElementWithIcon(this.defaultIcon);}else{this._stampElementWithIcon('blobcache');}
eventTarget.dispatchEvent(new CustomEvent(ICON_BLOB_ERROR_EVENT,{detail:this}));};return this.fetchIconBlob().then((blob)=>{if(!blob){return blobNotFound();}
this.renderIconFromBlob(blob);this.iconState='success';this._stampElementWithIcon(icon);}).catch((err)=>{blobNotFound();});},removeFromGrid:function(){var idx=this.grid.items.indexOf(this);if(idx===-1){console.error('Attempting to remove self before item has been added!');return;}
this.grid.items.splice(idx,1);delete this.grid.icons[this.identifier];if(this.element){this.element.parentNode.removeChild(this.element);}
this.grid.render({from:idx-1});},remove:function(){this.grid.element.dispatchEvent(new CustomEvent('removeitem',{detail:this}));},render:function(){var scale=this.grid.layout.percent*this.scale;if(!this.element){var tile=document.createElement('div');tile.className='icon';tile.dataset.identifier=this.identifier;tile.dataset.isDraggable=this.isDraggable();tile.setAttribute('role','link');tile.setAttribute('tabindex',0);tile.style.width=(this.grid.layout.constraintSize/3)+'px';var nameContainerEl=document.createElement('p');nameContainerEl.style.marginTop=((this.grid.layout.gridIconSize*(1/this.grid.layout.percent))+
(GridIconRenderer.prototype.unscaledCanvasPadding/2))+'px';tile.appendChild(nameContainerEl);var nameEl=document.createElement('span');nameEl.className='title';nameEl.setAttribute('dir','auto');nameContainerEl.appendChild(nameEl);if(this.isRemovable()){var removeEl=document.createElement('span');removeEl.className='remove';tile.appendChild(removeEl);}
this.element=tile;this.updateTitle();this.renderIcon(true);this.grid.element.appendChild(tile);}
this.transform(this.x,this.y,scale);},transform:function(x,y,scale,element){scale=scale||1;if(!element){if(x===this.lastX&&y===this.lastY&&scale===this.lastScale){return;}
element=this.element;this.lastX=x;this.lastY=y;this.lastScale=scale;}
element.style.transform='translate('+x+'px,'+y+'px) scale('+scale+')';},setActive:function(active){if(active){this.element.classList.add('active');this.active=true;}else{this.element.classList.remove('active');this.active=false;}},updateTitle:function(){if(!this.element){return;}
var nameEl=this.element.querySelector('.title');if(this.asyncName){this.asyncName().then(function(name){nameEl.textContent=name;});}else{nameEl.textContent=this.name;}},updateFromDatastore:function(record){var iconChanged=record.icon!==this.icon;var nameChanged=record.name!==this.name;var type=this.detail.type;var lastIcon=this.icon;record.type=type;this.detail=record;if(nameChanged){this.updateTitle();}
if(iconChanged&&record.icon){this.renderIcon();}else if(!record.icon){this.detail.icon=lastIcon;}}};exports.GaiaGrid.GridItem=GridItem;}(window));

View file

@ -0,0 +1,20 @@
;'use strict';(function(exports){const COLLAPSE_RATIO=0.33;const COLLAPSED_GROUP_SIZE=6;const COLLAPSED_GROUP_MARGIN_LEFT=23;const COLLAPSED_GROUP_MARGIN_RIGHT=53;const TOGGLE_TIMEOUT=250;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,get headerHeight(){return this.detail.collapsed?20:30;},pendingCollapse:false,pendingCollapseTimeout:null,backgroundHeight:0,lastBackgroundHeight:null,separatorHeight:0,collapseRatio:COLLAPSE_RATIO,get pixelHeight(){return this.detail.collapsed?this.backgroundHeight:this.separatorHeight;},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;},_createElement:function(){var group=this.element=document.createElement('section');group.className='divider group newly-created';var span=document.createElement('span');span.className='background';group.appendChild(span);this.backgroundSpanElement=span;span=document.createElement('span');span.className='shadow';group.appendChild(span);this.shadowSpanElement=span;span=document.createElement('span');span.className='header';group.appendChild(span);this.headerSpanElement=span;span=document.createElement('span');span.className='gripper';this.headerSpanElement.appendChild(span);var button=document.createElement('button');button.className='toggle';button.dataset.l10nId='gaia-grid-toggle-expanded';this.headerSpanElement.appendChild(button);this.toggleElement=button;span=document.createElement('span');span.className='separator';group.appendChild(span);this.dividerSpanElement=span;this.dividerSpanElement.appendChild(document.createElement('span'));this.grid.element.appendChild(group);this.separatorHeight=this.dividerSpanElement.clientHeight;this.lastBackgroundHeight=null;},_renderChildren:function(nApps){var isRTL=(document.documentElement.dir==='rtl');if(!this.detail.collapsed){this.element.classList.remove('collapsed');return;}
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');}}}},getRealYPosition:function(nApps){if(this.detail.collapsed){return this.y;}
return this.grid.items[this.detail.index-nApps].y-
this.headerHeight;},render:function(){var createdElements=false;if(!this.element){createdElements=true;this._createElement();}
var nApps=this.size;this._renderChildren(nApps);var y=this.getRealYPosition(nApps);if(y!==this.lastY){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;}
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){this.backgroundSpanElement.style.transform='translate(0px, '+y+'px) scale(1, '+this.backgroundHeight+')';this.shadowSpanElement.style.transform='translateY('+y+'px)';this.shadowSpanElement.style.height=this.backgroundHeight+'px';this.dividerSpanElement.style.transform='translate(0px, '+(y+this.backgroundHeight)+'px)';}
this.lastBackgroundHeight=this.backgroundHeight;this.lastY=y;this.backgroundHeight+=this.separatorHeight;if(createdElements){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);this.collapse();var callback=active?function(item){item.element.classList.add('active');}:function(item){item.element.classList.remove('active');};this.forEachItem(callback);},_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);}
this.pendingCollapseTimeout=setTimeout(()=>{this.detail.collapsed=this.pendingCollapse;this.pendingCollapseTimeout=null;if(!this.detail.collapsed){this.forEachItem(function(item){item.scale=1;if(item.element){item.element.classList.remove('collapsed');item.element.classList.remove('hidden');}});}
if(this.active){this.render();}
this.grid.render();var dragging=this.grid.dragdrop&&this.grid.dragdrop.inDragAction;if(dragging){this.grid.dragdrop.updateIconPosition();}else{window.dispatchEvent(new CustomEvent('gaiagrid-saveitems'));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;}(window));

View file

@ -0,0 +1,51 @@
'use strict';
/* global GaiaGrid */
/* global GridIconRenderer */
/* global MozActivity */
/* jshint nonew: false */
(function(exports) {
const TYPE = 'marketplace-app';
/**
* Represents a single bookmark icon on the homepage.
*/
function MarketPlaceApp(record) {
this.detail = record;
this.detail.type = TYPE;
}
MarketPlaceApp.prototype = {
__proto__: GaiaGrid.Bookmark.prototype,
renderer: GridIconRenderer.TYPE.STANDARD,
/**
* Returns the icon image path.
*/
get icon() {
var icon = this._accurateIcon;
if (!icon) {
icon = this._accurateIcon = this.closestIconFromList(
this.detail.icons);
}
return icon;
},
launch: function() {
new MozActivity({
name: 'marketplace-app',
data: {
slug: this.detail.slug
}
});
},
};
exports.GaiaGrid.MarketPlaceApp = MarketPlaceApp;
}(window));

View file

@ -0,0 +1,19 @@
;'use strict';(function(exports){const IDENTIFIER_SEP='-';var MOZAPP_EVENTS=['downloadsuccess','downloaderror','downloadavailable','downloadapplied','progress'];var APP_LOADING='loading';var APP_ERROR='error';var APP_UNRECOVERABLE='unrecoverable';var APP_PAUSED='paused';var APP_READY='ready';function localizeString(str){var userLang=document.documentElement.lang;if(navigator.mozL10n&&navigator.mozL10n.ctx.qps.indexOf(userLang)!==-1){return navigator.mozL10n.qps[userLang].translate(str);}
return str;}
function Mozapp(app,entryPoint,details){this.app=app;this.entryPoint=entryPoint;this.detail={type:'app',manifestURL:app.manifestURL,entryPoint:entryPoint,index:0,decoratedIconBlob:details&&details.decoratedIconBlob};MOZAPP_EVENTS.forEach(function(type){this.app['on'+type]=this.handleEvent.bind(this);},this);this.setAppState(this._determineState(app));}
Mozapp.HIDDEN_ROLES=['system','input','homescreen','search','addon','langpack'];Mozapp.prototype={__proto__:GaiaGrid.GridItem.prototype,_isHiddenRole:function(){var manifest=this.app.manifest;if(!manifest||!manifest.role){return null;}
return Mozapp.HIDDEN_ROLES.indexOf(manifest.role)!==-1;},_determineState:function(app){if(app.installState==='pending'&&!app.downloadAvailable&&!app.readyToApplyDownload){return APP_UNRECOVERABLE;}
if(app.downloading){return APP_LOADING;}
if(app.downloadError&&app.downloadError.name){if(app.downloadError.name==='DOWNLOAD_CANCELED'){return APP_PAUSED;}
return APP_ERROR;}
if(app.installState==='pending'){return APP_PAUSED;}
return APP_READY;},handleEvent:function(event){switch(event.type){case'progress':case'downloaderror':case'downloadsuccess':this.setAppState(this._determineState(this.app));break;case'downloadapplied':if(this._isHiddenRole()){console.warn('Removing hidden app from the grid',this.name);return this.removeFromGrid();}
delete this._accurateIcon;delete this._accurateIconSize;this.setAppState(this._determineState(this.app));this.renderIcon();window.dispatchEvent(new CustomEvent('downloadapplied',{detail:{id:this.app.manifestURL}}));break;}},fetchIconBlob:function(){if(!this._accurateIconSize){this._accurateIconSize=this.closestIconSizeFromList(this.descriptor.icons);}
var result=this._accurateIconSize?navigator.mozApps.mgmt.getIcon(this.app,this._accurateIconSize,this.entryPoint||''):GaiaGrid.GridItem.prototype.fetchIconBlob.call(this);return result;},get pixelHeight(){return this.grid.layout.gridItemHeight;},gridWidth:1,get name(){var userLang=document.documentElement.lang;if(navigator.mozL10n&&userLang in navigator.mozL10n.qps){return navigator.mozL10n.qps[userLang].translate(this.descriptor.short_name||this.descriptor.name);}
var locales=this.descriptor.locales;var localized=locales&&locales[userLang]&&(locales[userLang].short_name||locales[userLang].name);return localized||this.descriptor.short_name||this.descriptor.name;},asyncName:function(){var userLang=document.documentElement.lang;var ep=this.entryPoint||undefined;if(!this.app.getLocalizedValue){return new Promise((resolve,reject)=>{reject();});}
return this.app.getLocalizedValue('short_name',userLang,ep).then(shortName=>localizeString(shortName),this.app.getLocalizedValue.bind(this.app,'name',userLang,ep)).then(name=>localizeString(name),()=>this.name);},get icon(){var icon=this._accurateIcon;if(!icon){icon=this._accurateIcon=this.closestIconFromList(this.descriptor.icons);}
return icon;},get descriptor(){var manifest=this.app.manifest||this.app.updateManifest;if(this.entryPoint&&manifest.entry_points&&manifest.entry_points[this.entryPoint]){return manifest.entry_points[this.entryPoint];}
return manifest;},identifierSeparator:IDENTIFIER_SEP,get identifier(){var identifier=[this.app.manifestURL];if(this.entryPoint){identifier.push(this.entryPoint);}
return identifier.join(IDENTIFIER_SEP);},isRemovable:function(){return this.app.removable;},unrecoverableError:function(){navigator.mozApps.mgmt.uninstall(this.app);},cancel:function(){var dialog=new ConfirmDialogHelper({type:'pause',title:{id:'gaia-grid-stop-download-title',args:{name:this.name}},body:'gaia-grid-stop-download-body',cancel:{title:'gaia-grid-cancel'},confirm:{title:'gaia-grid-stop-download-action',type:'danger',cb:()=>this.app.cancelDownload()}});dialog.show(document.body);},resume:function(){var dialog=new ConfirmDialogHelper({type:'resume',title:'gaia-grid-resume-download-title',body:{id:'gaia-grid-resume-download-body',args:{name:this.name}},cancel:{title:'gaia-grid-cancel'},confirm:{title:'gaia-grid-resume-download-action',cb:()=>{this.setAppState(APP_LOADING);this.app.download();}}});dialog.show(document.body);},setAppState:function(state){this.appState=state;if(this.element){this.element.dataset.appState=state;}},render:function(){var needsStateSet=!this.element;GaiaGrid.GridItem.prototype.render.apply(this,arguments);if(needsStateSet){this.setAppState(this.appState);}},launch:function(){var app=this.app;switch(this._determineState(app)){case APP_UNRECOVERABLE:return this.unrecoverableError();case APP_ERROR:case APP_PAUSED:return this.resume();case APP_LOADING:return this.cancel();}
window.performance.mark('appLaunch@'+app.origin);if(this.entryPoint){return app.launch(this.entryPoint);}
return app.launch();}};exports.GaiaGrid.Mozapp=Mozapp;}(window));

View file

@ -0,0 +1,3 @@
;'use strict';(function(exports){function Placeholder(){this.detail={type:'placeholder',index:0};}
Placeholder.prototype={__proto__:GaiaGrid.GridItem.prototype,get pixelHeight(){return this.grid.layout.gridItemHeight;},createsGroupOnDrop:false,gridWidth:1,persistToDB:false,get name(){return'';},render:function(){if(!this.element){var tile=document.createElement('div');tile.className='icon placeholder';tile.style.height=this.pixelHeight+'px';this.element=tile;this.grid.element.appendChild(tile);}
this.transform(this.x,this.y,this.grid.layout.percent);},remove:function(){if(this.element){this.element.parentNode.removeChild(this.element);}}};exports.GaiaGrid.Placeholder=Placeholder;}(window));

View file

@ -0,0 +1,7 @@
;window.GaiaGrid=(function(win){'use strict';var proto=Object.create(HTMLElement.prototype);var baseurl=window.GaiaGridBaseurl||'/shared/elements/gaia_grid/';proto.createdCallback=function(){var shadow=this.createShadowRoot();this._template=template.content.cloneNode(true);this._styleHack();this._grid=new GridView({element:this,features:{dragdrop:this.getAttribute('dragdrop')!==null,zoom:this.getAttribute('zoom')!==null,disableSections:this.getAttribute('disable-sections')!==null}});shadow.appendChild(this._template);};proto.add=function(){this._grid.add.apply(this._grid,arguments);};proto.clear=function(){this._grid.clear.apply(this._grid,arguments);};proto.render=function(){this._grid.layout.calculateSize();this._grid.render.apply(this._grid,arguments);};proto.start=function(){this._grid.start.apply(this._grid,arguments);};proto.stop=function(){this._grid.stop.apply(this._grid,arguments);};proto.getIcons=function(){return this._grid.icons;};proto.getItems=function(){return this._grid.items;};proto.getIcon=function(identifier){return this._grid.icons[identifier];};proto.removeIconByIdentifier=function(identifier){delete this._grid.icons[identifier];};proto.removeItemByIndex=function(itemIndex){this._grid.items.splice(itemIndex,1);};proto.popDivider=function(){var items=this._grid.items;if(items.length&&items[items.length-1].detail.type==='divider'){return items.pop();}
return null;};proto.appendItem=function(item){var items=this._grid.items;var lastIndex=items.length-1;if(items[lastIndex].detail.type==='divider'){this.add(item,lastIndex);}else{this.add(item);}};proto.appendItemToExpandedGroup=function(item){var items=this._grid.items;var lastIndex=items.length-1;if(items[lastIndex].detail.type==='divider'&&items[lastIndex].detail.collapsed){items[lastIndex].expand();}
this.appendItem(item);};proto.setEditHeaderElement=function(element){if(this._grid.dragdrop){this._grid.dragdrop.editHeaderElement=element;}};Object.defineProperty(proto,'maxIconSize',{get:function(){return this._grid.layout.gridMaxIconSize;}});proto.getIndexLastIcon=function(){var items=this._grid.items;for(var i=this._grid.items.length-1;i>=0;i--){if(items[i]instanceof GaiaGrid.Mozapp){return i;}}};proto.removeNonVisualElements=function(){var nonVisualElements=[GaiaGrid.Placeholder];function isNonVisual(elem){var retVal=false;for(var i=0,iLen=nonVisualElements.length;i<iLen&&!retVal;i++){retVal=elem instanceof nonVisualElements[i];}
return retVal;}
var i=0;var iLen=this._grid.items.length;while(i<iLen){this._grid.items[i].detail.index=i;if(isNonVisual(this._grid.items[i])){this._grid.items.splice(i,1);iLen-=1;}else{i+=1;}}};proto.moveTo=function(orig,dst){this._grid.items.splice(dst,0,this._grid.items.splice(orig,1)[0]);};proto._styleHack=function(){var style=this._template.querySelector('style');this.appendChild(style.cloneNode(true));};var stylesheet=baseurl+'style.css';var template=document.createElement('template');template.innerHTML=`<style scoped>
@import url(${stylesheet});</style>
<content></content>`;return document.registerElement('gaia-grid',{prototype:proto});})(window);

View file

@ -0,0 +1,409 @@
.icon[data-app-state="loading"]:after {
content: "";
background: no-repeat top center url('images/app_install_spinner.png');
background-size: contain;
height: 8.4rem;
position: absolute;
top: 0;
left: 0;
width: 100%;
/**
Keyframe must be defined outside of the web component.
See bug 1018269
*/
animation: rotate 2.25s linear infinite;
}
.icon[data-app-state="loading"] {
/**
We must align this value with the value of [data-app-state]:after height the background size
here is overridden at the element level elsewhere which causes the spinner to be aligned too
low.
*/
background-size: 8.4rem !important;
background-image: url('images/app_installing.png') !important;
}
.icon[data-app-state="unrecoverable"] {
background-image: url('images/app_install_unrecoverable.png') !important;
}
.icon[data-app-state="error"] {
background-image: url('images/app_install_failed.png') !important;
}
.icon[data-app-state="paused"] {
background-image: url('images/app_install_canceled.png') !important;
}
.icon {
position: absolute;
transform-origin: 0 0;
display: inline-block;
text-align: center;
background: no-repeat top center;
z-index: 3;
outline: 0;
}
.icon.launching {
opacity: .65;
}
/* XXX: Override the .remove element */
.edit-mode .collection.hovered .remove {
background: url(images/add.png) no-repeat;
background-size: 2rem;
}
.icon p {
pointer-events: none;
height: 3.2rem;
width: 100%;
margin: 0;
padding: 0;
}
.icon .title {
display: inline-block;
margin-top: 0.1rem;
/* We need this rule in order not to hide the drop shadow */
padding: 0 0.2rem 1rem;
text-shadow: 0.1rem 0.1rem 0.3rem rgba(0, 0, 0, 0.3),
0.2rem 0.4rem 1rem rgba(0, 0, 0, 0.4);
font-size: 1.4rem;
color: #fff;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: calc(100% - 0.4rem);
box-sizing: border-box;
vertical-align: top;
transition: opacity var(--transition-duration);
}
/* We allow more space for text in the search app by hooking into the text-rows attribute on gaia-grid. */
[text-rows="3"] .icon .title {
height: 5.8rem;
}
[cols="4"] .icon .title {
/* Font size is the same independent on grid configuration */
font-size: calc(1.2rem * 1.333);
margin-top: 0.5rem;
line-height: 1.94rem;
}
.edit-mode .icon.collapsed {
pointer-events: none;
}
.icon.collapsed .title {
opacity: 0;
}
.icon.hidden {
pointer-events: none;
opacity: 0;
}
.divider {
position: absolute;
width: 100%;
padding-top: 2.3rem;
border-left: 0.6rem solid transparent;
border-right: 0.6rem solid transparent;
box-sizing: border-box;
z-index: 2;
}
[cols="4"] .divider {
padding-top: 1.6rem;
}
.divider span.spacer {
position: absolute;
bottom: 0;
display: block;
background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.5));
margin: 0 25%;
width: 50%;
height: 0.2rem;
transition: opacity var(--transition-duration);
}
.dragging .icon.active:not(.hidden) {
z-index: 6;
opacity: 0.65;
}
.dragging .divider:not(.group),
.icon:not(.active),
.dropped {
transition: transform var(--transition-duration),
opacity var(--transition-duration);
}
.icon.active {
will-change: transform;
}
.edit-mode .icon .remove {
display: block;
position: absolute;
top: 0.1rem;
left: 0.5rem;
width: 2.6rem;
height: 2.6rem;
background: url(images/remove.png) no-repeat;
background-size: 2rem;
/*
Ensure that remove is on top of the icon and has some extra
padding so it is easy to click.
*/
z-index: 200;
padding: 0.5rem;
}
.icon .remove,
.icon.collapsed .remove,
.edit-mode .icon.active .remove {
display: none;
}
.edit-mode .icon.editable .title {
background-color: rgba(132, 132, 132, 0.7);
border-radius: 2rem;
margin-top: .4rem;
padding: 0 1rem;
width: auto;
max-width: 100%;
}
/*
Disable events for placeholders, so we don't trigger touch events in edit mode.
We only do this in edit-mode, otherwise we might trigger entering edit mode
instead of the context menu.
*/
.edit-mode .placeholder {
pointer-events: none;
}
#confirm-message {
z-index: 100;
}
.dragging .group.active {
z-index: 5;
}
.divider.group {
border: none;
padding: 0;
}
.group > span {
display: block;
position: absolute;
width: 100%;
transition: background-color var(--transition-duration),
opacity var(--transition-duration);
}
.group:not(.active):not(.newly-created) > span {
transition: background-color var(--transition-duration),
opacity var(--transition-duration),
transform var(--transition-duration);
}
.group.active > span {
will-change: transform;
}
.group .header {
height: 4rem;
z-index: 4;
}
.group .header span,
.group .header button {
position: absolute;
height: 4rem;
top: 0;
background-position: center top;
background-repeat: no-repeat;
background-size: 100% 2rem;
transition: transform 0.25s, opacity 0.25s;
}
.group .header .gripper {
left: calc(50% - 4.2rem);
width: 8.4rem;
background-image: url('images/move_gripper.png');
opacity: 0;
transform: translate(0, -100%);
pointer-events: none;
}
.edit-mode .group:not(.collapsed) .header .gripper {
opacity: 1;
transform: translate(0, 0);
pointer-events: auto;
}
.group .header .toggle {
width: 5.34rem;
right: 0;
border: none;
margin: 0;
padding: 0;
background-color: transparent;
background-image: url('images/collapse_arrow.png');
transform-origin: 50% 1.6rem;
pointer-events: auto;
}
.group .header .toggle.toggling {
opacity: 0.5;
transition: transform 0.25s;
}
.group.collapsed .header .toggle {
transform: translateY(2rem) rotateZ(180deg);
}
.group.active .header .toggle {
opacity: 0.5;
}
.group .background {
background-color: rgba(255, 255, 255, 0.15);
opacity: 0;
pointer-events: none;
height: 0.1rem;
transform-origin: 0 0;
}
.group.odd:not(.newly-created) .background,
.edit-mode .group:not(.newly-created) .background {
opacity: 1;
}
.group.active .background {
background-color: rgba(51, 51, 51, 0.9);
}
.group.hovering .background,
.group.drop-target .background {
background-color: rgba(27, 185, 225, 0.5);
}
.group .shadow {
background-color: rgba(255, 255, 255, 0.15);
opacity: 0;
pointer-events: none;
}
.edit-mode .group.active .shadow {
background-color: transparent;
box-shadow: 0 0.9rem 1.2rem rgba(0, 0, 0, 0.63);
opacity: 1;
}
.group .separator {
margin: 0;
height: 0.8rem;
pointer-events: none;
}
.group .separator > span {
display: block;
width: 100%;
height: 100%;
transition: transform 0.25s, opacity 0.25s;
opacity: 0;
}
.group.odd .separator {
opacity: 1;
}
.group.odd .separator > span {
background-color: rgba(255, 255, 255, 0.15);
opacity: 1;
}
.edit-mode .group .separator,
.edit-mode .group .separator > span {
opacity: 0;
}
/* For preventing entering edit mode when long-pressing on a placeholder */
.edit-mode .group .background,
.edit-mode .group .shadow,
.group.collapsed .header,
.group.collapsed .background,
.group.collapsed .shadow {
pointer-events: none;
}
.edit-mode .group.collapsed .background {
pointer-events: auto;
}
.dragging .group:not(.active) .separator {
opacity: 1;
}
.dragging .group:not(.active):not(.invalid-drop) .separator > span {
background-color: #0095dd;
transform: scaleY(0.325);
opacity: 0.4;
}
.dragging .group.hovered:not(.active) .separator > span {
transform: scaleY(1);
opacity: 1;
}
/* A hint element for dragging items to the top of the group */
.edit-mode::after {
content: ' ';
display: block;
width: 100%;
height: 0.8rem;
z-index: 2;
opacity: 0;
background-color: #0095dd;
position: relative;
top: -0.8rem;
transition: opacity 0.25s;
}
.edit-mode.hover-over-top::after {
opacity: 1;
}
/* Shift container downwards when hovering over the top to reveal the group
* creation hint.
*/
.edit-mode {
transition: transform 0.25s;
}
.edit-mode.hover-over-top {
transform: translateY(0.8rem);
}
/* RTL View */
:-moz-dir(rtl) .group .header .toggle {
left: 0;
right: unset;
}

View file

@ -0,0 +1,24 @@
/**
Some css properties are global (keyframes) and cannot be specified
to to only apply to our shadow dom so we hack around this by including
this file without scoping it.
*/
/**
Bug 1018269 - we need to specify the keyframe outside
of the web component...
*/
@keyframes rotate {
from {
transform: rotate(1deg)
}
to {
transform: rotate(360deg)
}
}
:-moz-dir(rtl) gaia-grid {
direction: ltr;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

View file

@ -0,0 +1,62 @@
'use strict';
/* global ComponentUtils */
window.GaiaMenu = (function(win) {
// Extend from HTMLElement prototype
var proto = Object.create(HTMLElement.prototype);
// Allow baseurl to be overridden (used for demo page)
var baseurl = window.GaiaMenuBaseurl ||
'/shared/elements/gaia_menu/';
proto.createdCallback = function () {
var shadow = this.createShadowRoot();
this._template = template.content.cloneNode(true);
var cancelButton = this._template.querySelector('.gaia-menu-cancel button');
cancelButton.addEventListener('click', function () {
this.hide();
this.dispatchEvent(new CustomEvent('gaiamenu-cancel'));
}.bind(this));
shadow.appendChild(this._template);
ComponentUtils.style.call(this, baseurl);
document.l10n.ready.then(this.localize.bind(this));
document.addEventListener('DOMRetranslated', this.localize.bind(this));
};
proto.localize = function() {
document.l10n.formatValue('gaia-menu-cancel').then(value => {
this.shadowRoot.querySelector('button').textContent = value;
});
};
proto.show = function() {
this.removeAttribute('hidden');
};
proto.hide = function() {
this.setAttribute('hidden', 'hidden');
};
var template = document.createElement('template');
template.innerHTML =
`<form role="dialog" data-type="action">
<content select="header"></content>
<menu>
<content select="button"></content>
<span class="gaia-menu-cancel">
<button>Cancel</button>
</span>
</menu>
</form>`;
// Register and return the constructor
return document.registerElement('gaia-menu', { prototype: proto });
})(window);

View file

@ -0,0 +1,314 @@
/* ----------------------------------
* gaia_menu
* ---------------------------------- */
/* default alignment follows the UI direction, not the content direction */
:-moz-dir(ltr) [dir] { text-align: left; }
:-moz-dir(rtl) [dir] { text-align: right; }
/* Main dialog setup */
gaia-menu {
background-color: #2d2d2d;
overflow: hidden;
position: fixed;
z-index: 100;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 0 0 7rem;
/**
* Using "font-size: 0" to avoid the unwanted visual space (about 3px) created
* by white spaces and line breaks in the code between inline-block elements.
*/
font-size: 0;
color: #fff;
display: block;
transition: transform ease-in-out var(--transition-duration),
visibility var(--transition-duration);
}
gaia-menu[hidden] {
transform: translateY(100%);
visibility: hidden;
}
gaia-menu:before {
content: '';
display: inline-block;
vertical-align: top;
width: 0.1rem;
height: 100%;
margin-inline-start: -0.1rem;
}
header {
-moz-box-sizing: border-box;
font-size: 1.6rem;
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 10;
height: 5rem;
line-height: 4.9rem;
color: #fff;
white-space: nowrap;
text-overflow: ellipsis;
display: block;
overflow: hidden;
font-weight: normal;
padding: 0 3rem;
border-bottom: 0.1rem solid #616262;
}
header h1 {
font-size: 1.6rem;
line-height: 4.9rem;
color: #fff;
white-space: nowrap;
text-overflow: ellipsis;
display: block;
overflow: hidden;
margin: 0;
height: 100%;
font-weight: normal;
}
/* Generic set of actions in toolbar */
header menu.toolbar {
height: 100%;
margin: 0 -3rem;
margin-inline-start: 1rem;
padding: 0;
}
header:-moz-dir(ltr) menu.toolbar { float: right; }
header:-moz-dir(rtl) menu.toolbar { float: left; }
header menu.toolbar a,
header menu.toolbar button {
-moz-box-sizing: border-box;
position: relative;
width: auto;
min-width: 5rem;
height: 4.9rem;
margin-bottom: 0;
padding: 0 1rem;
border-radius: 0;
line-height: 4.9rem;
background: none;
text-align: center;
text-shadow: none;
z-index: 5;
}
header:-moz-dir(ltr) menu.toolbar a,
header:-moz-dir(ltr) menu.toolbar button {
float: left;
}
header:-moz-dir(rtl) menu.toolbar a,
header:-moz-dir(rtl) menu.toolbar button {
float: right;
}
header a,
header button {
border: none;
padding: 0;
overflow: hidden;
font-weight: 400;
font-size: 1.5rem;
line-height: 1.1em;
color: #fff;
border-radius: 0;
text-decoration: none;
}
button::-moz-focus-inner {
outline: none;
border: none;
}
/* Pressed state */
header a:active .icon:after,
header button:active .icon:after,
header menu.toolbar a:active,
header menu.toolbar button:active {
background: #008aaa;
}
/* Disabled state */
header menu.toolbar a[aria-disabled="true"],
header menu.toolbar button[disabled] {
opacity: 0.5;
pointer-events: none;
}
/* Icon definitions */
header .icon {
position: relative;
float: none;
display: inline-block;
vertical-align: top;
width: 4rem;
height: 4.9rem;
margin: 0 -1rem;
background: transparent no-repeat center center / 3rem auto;
font-size: 0;
overflow: hidden;
}
header .icon.icon-close {
background-image: url(images/icons/close.png);
}
header .icon.icon-back {
background-image: url(images/icons/back.png);
}
/* Navigation links (back, cancel, etc) */
header > button,
header > a {
width: 5rem;
height: 4.9rem;
background: none;
overflow: visible;
margin: 0 -1rem;
margin-inline-start: -3rem;
position: relative;
}
header:-moz-dir(ltr) > button,
header:-moz-dir(ltr) > a {
float: left;
}
header:-moz-dir(rtl) > button,
header:-moz-dir(rtl) > a {
float: right;
}
header > button::-moz-focus-inner {
border: 0;
padding: 0;
}
header > button .icon,
header > a .icon {
position: static;
display: block;
width: 3rem;
height: 4.9rem;
margin: 0;
overflow: visible;
font-size: 0;
}
header > button .icon:after,
header > a .icon:after {
content: "";
position: absolute;
offset-inline-start: 0;
top: 0;
z-index: -1;
width: 3rem;
height: 4.9rem;
}
/* Content */
section {
color: #fff;
position: absolute;
top: 7rem;
left: 1.5rem;
right: 1.5rem;
bottom: 2rem;
font-size: 1.6rem;
overflow: scroll;
}
/* Specific component code */
menu {
margin: 0;
padding: 0;
max-height: calc(100% - 13.5rem); /*header(5rem) + footer(7rem) + padding(1.5rem)*/
height: auto;
padding-top: 1.5rem;
overflow: auto;
width: auto;
border: none;
background: none;
position: absolute;
left: 0;
right: 0;
bottom: 7rem;
}
button {
width: calc(100% - 3rem);
height: 4rem;
-moz-box-sizing: border-box;
vertical-align: middle;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-weight: normal;
line-height: 4rem;
outline: none;
font-size: 1.6rem;
text-shadow: none;
padding: 0 1.2rem;
margin: 0 1.5rem 1rem;
background: rgba(87, 87, 87, 0.8);
border: none;
border-radius: 0;
position: relative;
color: #fff;
font-family: sans-serif;
font-style: italic;
}
button:-moz-dir(ltr) { text-align: left; }
button:-moz-dir(rtl) { text-align: right; }
span.gaia-menu-cancel {
position: fixed;
width: 100%;
background: #4d4d4d;
height: 7rem;
offset-inline-start: 0;
bottom: 0;
}
span.gaia-menu-cancel button {
position: fixed;
bottom: 0;
offset-inline-start: 0;
color: #333;
background: #d8d8d8;
text-align: center;
border: none;
margin-bottom: 1.5rem;
border-radius: 2rem;
}
button:nth-last-child(2) {
margin-bottom: 1.5rem;
}
/* Press state */
button:active,
span.gaia-menu-cancel button:active {
background: #00aacc;
color: #fff;
}
/* Disabled */
button[disabled] {
background-color: rgba(87, 87, 87, 0.8);
color: rgba(255, 255, 255, 0.4);
opacity: 0.5;
pointer-events: none;
}
button[disabled]:last-child {
background-color: #e7e7e7;
color: #a6a6a6;
opacity: 1;
}