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.
119
assets/webapps/camera.gaiamobile.org/shared/js/async_storage.js
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
this.asyncStorage = function () {
|
||||
'use strict';
|
||||
var DBNAME = 'asyncStorage';
|
||||
var DBVERSION = 1;
|
||||
var STORENAME = 'keyvaluepairs';
|
||||
var db = null;
|
||||
function withDatabase(f) {
|
||||
if (db) {
|
||||
f();
|
||||
} else {
|
||||
var openreq = indexedDB.open(DBNAME, DBVERSION);
|
||||
openreq.onerror = function withStoreOnError() {
|
||||
console.error('asyncStorage: can\'t open database:', openreq.error.name);
|
||||
};
|
||||
openreq.onupgradeneeded = function withStoreOnUpgradeNeeded() {
|
||||
openreq.result.createObjectStore(STORENAME);
|
||||
};
|
||||
openreq.onsuccess = function withStoreOnSuccess() {
|
||||
db = openreq.result;
|
||||
f();
|
||||
};
|
||||
}
|
||||
}
|
||||
function withStore(type, callback, oncomplete) {
|
||||
withDatabase(function () {
|
||||
var transaction = db.transaction(STORENAME, type);
|
||||
if (oncomplete) {
|
||||
transaction.oncomplete = oncomplete;
|
||||
}
|
||||
callback(transaction.objectStore(STORENAME));
|
||||
});
|
||||
}
|
||||
function getItem(key, callback) {
|
||||
var req;
|
||||
withStore('readonly', function getItemBody(store) {
|
||||
req = store.get(key);
|
||||
req.onerror = function getItemOnError() {
|
||||
console.error('Error in asyncStorage.getItem(): ', req.error.name);
|
||||
};
|
||||
}, function onComplete() {
|
||||
var value = req.result;
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
callback(value);
|
||||
});
|
||||
}
|
||||
function setItem(key, value, callback) {
|
||||
withStore('readwrite', function setItemBody(store) {
|
||||
var req = store.put(value, key);
|
||||
req.onerror = function setItemOnError() {
|
||||
console.error('Error in asyncStorage.setItem(): ', req.error.name);
|
||||
};
|
||||
}, callback);
|
||||
}
|
||||
function removeItem(key, callback) {
|
||||
withStore('readwrite', function removeItemBody(store) {
|
||||
var req = store.delete(key);
|
||||
req.onerror = function removeItemOnError() {
|
||||
console.error('Error in asyncStorage.removeItem(): ', req.error.name);
|
||||
};
|
||||
}, callback);
|
||||
}
|
||||
function clear(callback) {
|
||||
withStore('readwrite', function clearBody(store) {
|
||||
var req = store.clear();
|
||||
req.onerror = function clearOnError() {
|
||||
console.error('Error in asyncStorage.clear(): ', req.error.name);
|
||||
};
|
||||
}, callback);
|
||||
}
|
||||
function length(callback) {
|
||||
var req;
|
||||
withStore('readonly', function lengthBody(store) {
|
||||
req = store.count();
|
||||
req.onerror = function lengthOnError() {
|
||||
console.error('Error in asyncStorage.length(): ', req.error.name);
|
||||
};
|
||||
}, function onComplete() {
|
||||
callback(req.result);
|
||||
});
|
||||
}
|
||||
function key(n, callback) {
|
||||
if (n < 0) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
var req;
|
||||
withStore('readonly', function keyBody(store) {
|
||||
var advanced = false;
|
||||
req = store.openCursor();
|
||||
req.onsuccess = function keyOnSuccess() {
|
||||
var cursor = req.result;
|
||||
if (!cursor) {
|
||||
return;
|
||||
}
|
||||
if (n === 0 || advanced) {
|
||||
return;
|
||||
}
|
||||
advanced = true;
|
||||
cursor.advance(n);
|
||||
};
|
||||
req.onerror = function keyOnError() {
|
||||
console.error('Error in asyncStorage.key(): ', req.error.name);
|
||||
};
|
||||
}, function onComplete() {
|
||||
var cursor = req.result;
|
||||
callback(cursor ? cursor.key : null);
|
||||
});
|
||||
}
|
||||
return {
|
||||
getItem: getItem,
|
||||
setItem: setItem,
|
||||
removeItem: removeItem,
|
||||
clear: clear,
|
||||
length: length,
|
||||
key: key
|
||||
};
|
||||
}();
|
||||
301
assets/webapps/camera.gaiamobile.org/shared/js/blobview.js
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
var BlobView = function () {
|
||||
function fail(msg) {
|
||||
throw Error(msg);
|
||||
}
|
||||
var decoderCache = {};
|
||||
function getDecoder(encoding) {
|
||||
if (encoding in decoderCache) {
|
||||
return decoderCache[encoding];
|
||||
}
|
||||
var decoder = decoderCache[encoding] = new TextDecoder(encoding);
|
||||
return decoder;
|
||||
}
|
||||
function BlobView(blob, sliceOffset, sliceLength, slice, viewOffset, viewLength, littleEndian) {
|
||||
this.blob = blob;
|
||||
this.sliceOffset = sliceOffset;
|
||||
this.sliceLength = sliceLength;
|
||||
this.slice = slice;
|
||||
this.viewOffset = viewOffset;
|
||||
this.viewLength = viewLength;
|
||||
this.littleEndian = littleEndian;
|
||||
this.view = new DataView(slice, viewOffset, viewLength);
|
||||
this.buffer = slice;
|
||||
this.byteLength = viewLength;
|
||||
this.byteOffset = viewOffset;
|
||||
this.index = 0;
|
||||
}
|
||||
BlobView.get = function (blob, offset, length, callback, littleEndian) {
|
||||
if (offset < 0) {
|
||||
fail('negative offset');
|
||||
}
|
||||
if (length < 0) {
|
||||
fail('negative length');
|
||||
}
|
||||
if (offset > blob.size) {
|
||||
fail('offset larger than blob size');
|
||||
}
|
||||
if (offset + length > blob.size) {
|
||||
length = blob.size - offset;
|
||||
}
|
||||
var slice = blob.slice(offset, offset + length);
|
||||
var reader = new FileReader();
|
||||
reader.readAsArrayBuffer(slice);
|
||||
reader.onloadend = function () {
|
||||
var result = null;
|
||||
if (reader.result) {
|
||||
result = new BlobView(blob, offset, length, reader.result, 0, length, littleEndian || false);
|
||||
}
|
||||
callback(result, reader.error);
|
||||
};
|
||||
};
|
||||
BlobView.getFromArrayBuffer = function (buffer, offset, length, littleEndian) {
|
||||
return new BlobView(null, offset, length, buffer, offset, length, littleEndian);
|
||||
};
|
||||
BlobView.prototype = {
|
||||
constructor: BlobView,
|
||||
getMore: function (offset, length, callback) {
|
||||
if (!this.blob) {
|
||||
fail('no blob backing this BlobView');
|
||||
}
|
||||
if (offset >= this.sliceOffset && offset + length <= this.sliceOffset + this.sliceLength) {
|
||||
callback(new BlobView(this.blob, this.sliceOffset, this.sliceLength, this.slice, offset - this.sliceOffset, length, this.littleEndian));
|
||||
} else {
|
||||
BlobView.get(this.blob, offset, length, callback, this.littleEndian);
|
||||
}
|
||||
},
|
||||
littleEndian: function () {
|
||||
this.littleEndian = true;
|
||||
},
|
||||
bigEndian: function () {
|
||||
this.littleEndian = false;
|
||||
},
|
||||
getUint8: function (offset) {
|
||||
return this.view.getUint8(offset);
|
||||
},
|
||||
getInt8: function (offset) {
|
||||
return this.view.getInt8(offset);
|
||||
},
|
||||
getUint16: function (offset, le) {
|
||||
return this.view.getUint16(offset, le !== undefined ? le : this.littleEndian);
|
||||
},
|
||||
getInt16: function (offset, le) {
|
||||
return this.view.getInt16(offset, le !== undefined ? le : this.littleEndian);
|
||||
},
|
||||
getUint32: function (offset, le) {
|
||||
return this.view.getUint32(offset, le !== undefined ? le : this.littleEndian);
|
||||
},
|
||||
getInt32: function (offset, le) {
|
||||
return this.view.getInt32(offset, le !== undefined ? le : this.littleEndian);
|
||||
},
|
||||
getFloat32: function (offset, le) {
|
||||
return this.view.getFloat32(offset, le !== undefined ? le : this.littleEndian);
|
||||
},
|
||||
getFloat64: function (offset, le) {
|
||||
return this.view.getFloat64(offset, le !== undefined ? le : this.littleEndian);
|
||||
},
|
||||
readByte: function () {
|
||||
return this.view.getInt8(this.index++);
|
||||
},
|
||||
readUnsignedByte: function () {
|
||||
return this.view.getUint8(this.index++);
|
||||
},
|
||||
readShort: function (le) {
|
||||
var val = this.view.getInt16(this.index, le !== undefined ? le : this.littleEndian);
|
||||
this.index += 2;
|
||||
return val;
|
||||
},
|
||||
readUnsignedShort: function (le) {
|
||||
var val = this.view.getUint16(this.index, le !== undefined ? le : this.littleEndian);
|
||||
this.index += 2;
|
||||
return val;
|
||||
},
|
||||
readInt: function (le) {
|
||||
var val = this.view.getInt32(this.index, le !== undefined ? le : this.littleEndian);
|
||||
this.index += 4;
|
||||
return val;
|
||||
},
|
||||
readUnsignedInt: function (le) {
|
||||
var val = this.view.getUint32(this.index, le !== undefined ? le : this.littleEndian);
|
||||
this.index += 4;
|
||||
return val;
|
||||
},
|
||||
readFloat: function (le) {
|
||||
var val = this.view.getFloat32(this.index, le !== undefined ? le : this.littleEndian);
|
||||
this.index += 4;
|
||||
return val;
|
||||
},
|
||||
readDouble: function (le) {
|
||||
var val = this.view.getFloat64(this.index, le !== undefined ? le : this.littleEndian);
|
||||
this.index += 8;
|
||||
return val;
|
||||
},
|
||||
tell: function () {
|
||||
return this.index;
|
||||
},
|
||||
remaining: function () {
|
||||
return this.byteLength - this.index;
|
||||
},
|
||||
seek: function (index) {
|
||||
if (index < 0) {
|
||||
fail('negative index');
|
||||
}
|
||||
if (index > this.byteLength) {
|
||||
fail('index greater than buffer size');
|
||||
}
|
||||
this.index = index;
|
||||
},
|
||||
advance: function (n) {
|
||||
var index = this.index + n;
|
||||
if (index < 0) {
|
||||
fail('advance past beginning of buffer');
|
||||
}
|
||||
if (index > this.byteLength) {
|
||||
fail('advance past end of buffer');
|
||||
}
|
||||
this.index = index;
|
||||
},
|
||||
getUnsignedByteArray: function (offset, n) {
|
||||
return new Uint8Array(this.buffer, offset + this.viewOffset, n);
|
||||
},
|
||||
readUnsignedByteArray: function (n) {
|
||||
var val = new Uint8Array(this.buffer, this.index + this.viewOffset, n);
|
||||
this.index += n;
|
||||
return val;
|
||||
},
|
||||
getBit: function (offset, bit) {
|
||||
var byte = this.view.getUint8(offset);
|
||||
return (byte & 1 << bit) !== 0;
|
||||
},
|
||||
getUint24: function (offset, le) {
|
||||
var b1, b2, b3;
|
||||
if (le !== undefined ? le : this.littleEndian) {
|
||||
b1 = this.view.getUint8(offset);
|
||||
b2 = this.view.getUint8(offset + 1);
|
||||
b3 = this.view.getUint8(offset + 2);
|
||||
} else {
|
||||
b3 = this.view.getUint8(offset);
|
||||
b2 = this.view.getUint8(offset + 1);
|
||||
b1 = this.view.getUint8(offset + 2);
|
||||
}
|
||||
return (b3 << 16) + (b2 << 8) + b1;
|
||||
},
|
||||
readUint24: function (le) {
|
||||
var value = this.getUint24(this.index, le);
|
||||
this.index += 3;
|
||||
return value;
|
||||
},
|
||||
getBinaryText: function (offset, len) {
|
||||
var bytes = new Uint8Array(this.buffer, offset + this.viewOffset, len);
|
||||
return String.fromCharCode.apply(String, bytes);
|
||||
},
|
||||
readBinaryText: function (len) {
|
||||
var s = this.getBinaryText(this.index, len);
|
||||
this.index += len;
|
||||
return s;
|
||||
},
|
||||
getLatin1Text: function (offset, len) {
|
||||
var bytes = new Uint8Array(this.buffer, offset + this.viewOffset, len);
|
||||
return getDecoder('latin1').decode(bytes);
|
||||
},
|
||||
readLatin1Text: function (len) {
|
||||
var s = this.getLatin1Text(this.index, len);
|
||||
this.index += len;
|
||||
return s;
|
||||
},
|
||||
getUTF8Text: function (offset, len) {
|
||||
var bytes = new Uint8Array(this.buffer, offset + this.viewOffset, len);
|
||||
return getDecoder('utf-8').decode(bytes);
|
||||
},
|
||||
readUTF8Text: function (len) {
|
||||
var s = this.getUTF8Text(this.index, len);
|
||||
this.index += len;
|
||||
return s;
|
||||
},
|
||||
getUTF16Text: function (offset, len, le) {
|
||||
if (len % 2) {
|
||||
fail('len must be a multiple of two');
|
||||
}
|
||||
var bytes = new Uint8Array(this.buffer, offset + this.viewOffset, len);
|
||||
if (le === null || le === undefined) {
|
||||
var BOM = (bytes[0] << 8) + bytes[1];
|
||||
if (BOM === 65279) {
|
||||
bytes = bytes.subarray(2);
|
||||
le = false;
|
||||
} else if (BOM === 65534) {
|
||||
bytes = bytes.subarray(2);
|
||||
le = true;
|
||||
} else {
|
||||
le = true;
|
||||
}
|
||||
}
|
||||
var encoding = le ? 'utf-16le' : 'utf-16be';
|
||||
return getDecoder(encoding).decode(bytes);
|
||||
},
|
||||
readUTF16Text: function (len, le) {
|
||||
var s = this.getUTF16Text(this.index, len, le);
|
||||
this.index += len;
|
||||
return s;
|
||||
},
|
||||
getID3Uint28BE: function (offset) {
|
||||
var b1 = this.view.getUint8(offset) & 127;
|
||||
var b2 = this.view.getUint8(offset + 1) & 127;
|
||||
var b3 = this.view.getUint8(offset + 2) & 127;
|
||||
var b4 = this.view.getUint8(offset + 3) & 127;
|
||||
return b1 << 21 | b2 << 14 | b3 << 7 | b4;
|
||||
},
|
||||
readID3Uint28BE: function () {
|
||||
var value = this.getID3Uint28BE(this.index);
|
||||
this.index += 4;
|
||||
return value;
|
||||
},
|
||||
readNullTerminatedLatin1Text: function (size, advance_by_size = false) {
|
||||
var bytes = new Uint8Array(this.buffer, this.viewOffset + this.index, size);
|
||||
var nil = bytes.indexOf(0);
|
||||
if (nil !== -1) {
|
||||
bytes = bytes.subarray(0, nil);
|
||||
}
|
||||
var s = getDecoder('latin1').decode(bytes);
|
||||
if (nil === -1 || advance_by_size) {
|
||||
this.index += size;
|
||||
} else {
|
||||
this.index += nil + 1;
|
||||
}
|
||||
return s;
|
||||
},
|
||||
readNullTerminatedUTF8Text: function (size, advance_by_size = false) {
|
||||
var bytes = new Uint8Array(this.buffer, this.viewOffset + this.index, size);
|
||||
var nil = bytes.indexOf(0);
|
||||
if (nil !== -1) {
|
||||
bytes = bytes.subarray(0, nil);
|
||||
}
|
||||
var s = getDecoder('utf-8').decode(bytes);
|
||||
if (nil === -1 || advance_by_size) {
|
||||
this.index += size;
|
||||
} else {
|
||||
this.index += nil + 1;
|
||||
}
|
||||
return s;
|
||||
},
|
||||
readNullTerminatedUTF16Text: function (size, le, advance_by_size = false) {
|
||||
if (size % 2) {
|
||||
fail('size must be a multiple of two');
|
||||
}
|
||||
for (var len = 0; len < size; len += 2) {
|
||||
if (this.getUint16(this.index + len, le) === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
var s = this.getUTF16Text(this.index, len, le);
|
||||
if (len === size || advance_by_size) {
|
||||
this.index += size;
|
||||
} else {
|
||||
this.index += len + 2;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
};
|
||||
return {
|
||||
get: BlobView.get,
|
||||
getFromArrayBuffer: BlobView.getFromArrayBuffer
|
||||
};
|
||||
}();
|
||||
110
assets/webapps/camera.gaiamobile.org/shared/js/custom_dialog.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
var CustomDialog = function () {
|
||||
var container = null;
|
||||
var screen = null;
|
||||
var dialog = null;
|
||||
var header = null;
|
||||
var message = null;
|
||||
var yes = null;
|
||||
var no = null;
|
||||
return {
|
||||
hide: function dialog_hide() {
|
||||
if (screen === null) {
|
||||
return;
|
||||
}
|
||||
if (!container) {
|
||||
container = document.body;
|
||||
}
|
||||
container.removeChild(screen);
|
||||
screen = null;
|
||||
dialog = null;
|
||||
header = null;
|
||||
message = null;
|
||||
yes = null;
|
||||
no = null;
|
||||
},
|
||||
show: function dialog_show(title, msg, cancel, confirm, containerElement) {
|
||||
container = containerElement || document.body;
|
||||
if (screen === null) {
|
||||
screen = document.createElement('form');
|
||||
screen.setAttribute('role', 'dialog');
|
||||
screen.setAttribute('data-type', 'confirm');
|
||||
screen.id = 'dialog-screen';
|
||||
dialog = document.createElement('section');
|
||||
screen.appendChild(dialog);
|
||||
var decorateWithOptions = function cd_decorateWithOptions(type, options, elm, dialog) {
|
||||
if ('string' === typeof options) {
|
||||
elm.setAttribute('data-l10n-id', options);
|
||||
return elm;
|
||||
}
|
||||
var icon = options.icon;
|
||||
var textElm = elm;
|
||||
if (icon && '' !== icon) {
|
||||
textElm = document.createElement('span');
|
||||
var iconImg = new Image();
|
||||
iconImg.src = icon;
|
||||
iconImg.classList.add('custom-dialog-' + type + '-icon');
|
||||
elm.insertBefore(iconImg, elm.firstChild);
|
||||
elm.appendChild(textElm);
|
||||
}
|
||||
if (options.id) {
|
||||
document.l10n.setAttributes(textElm, options.id, options.args);
|
||||
} else {
|
||||
var text = options[type];
|
||||
textElm.textContent = text;
|
||||
}
|
||||
return elm;
|
||||
};
|
||||
var setElementText = function (element, options) {
|
||||
if ('string' === typeof options) {
|
||||
element.setAttribute('data-l10n-id', options);
|
||||
}
|
||||
if (options.id) {
|
||||
document.l10n.setAttributes(element, options.id, options.args);
|
||||
}
|
||||
};
|
||||
header = document.createElement('h1');
|
||||
header.id = 'dialog-title';
|
||||
if (title && title !== '') {
|
||||
header = decorateWithOptions('title', title, header, dialog);
|
||||
}
|
||||
dialog.appendChild(header);
|
||||
message = document.createElement('p');
|
||||
message.id = 'dialog-message';
|
||||
message = decorateWithOptions('message', msg, message, dialog);
|
||||
dialog.appendChild(message);
|
||||
var menu = document.createElement('menu');
|
||||
menu.dataset.items = 1;
|
||||
no = document.createElement('button');
|
||||
no.type = 'button';
|
||||
setElementText(no, cancel.title);
|
||||
no.id = 'dialog-no';
|
||||
no.addEventListener('click', clickHandler);
|
||||
menu.appendChild(no);
|
||||
if (confirm) {
|
||||
menu.dataset.items = 2;
|
||||
yes = document.createElement('button');
|
||||
yes.type = 'button';
|
||||
setElementText(yes, confirm.title);
|
||||
yes.id = 'dialog-yes';
|
||||
yes.className = confirm.recommend ? 'recommend' : 'danger';
|
||||
yes.addEventListener('click', clickHandler);
|
||||
menu.appendChild(yes);
|
||||
} else {
|
||||
no.classList.add('full');
|
||||
}
|
||||
screen.appendChild(menu);
|
||||
container.appendChild(screen);
|
||||
}
|
||||
screen.classList.add('visible');
|
||||
function clickHandler(evt) {
|
||||
screen.classList.remove('visible');
|
||||
if (evt.target === yes && confirm.callback) {
|
||||
confirm.callback();
|
||||
} else if (evt.target === no && cancel.callback) {
|
||||
cancel.callback();
|
||||
}
|
||||
}
|
||||
return screen;
|
||||
}
|
||||
};
|
||||
}();
|
||||
10
assets/webapps/camera.gaiamobile.org/shared/js/format.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
var Format = {
|
||||
padLeft: function (input, len, padWith) {
|
||||
padWith = padWith || ' ';
|
||||
var pad = len - (input + '').length;
|
||||
while (--pad > -1) {
|
||||
input = padWith + input;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
var GestureDetector = function () {
|
||||
function GD(e, options) {
|
||||
this.element = e;
|
||||
this.options = options || {};
|
||||
this.options.panThreshold = this.options.panThreshold || GD.PAN_THRESHOLD;
|
||||
this.state = initialState;
|
||||
this.timers = {};
|
||||
}
|
||||
GD.prototype.startDetecting = function () {
|
||||
var self = this;
|
||||
eventtypes.forEach(function (t) {
|
||||
self.element.addEventListener(t, self);
|
||||
});
|
||||
};
|
||||
GD.prototype.stopDetecting = function () {
|
||||
var self = this;
|
||||
eventtypes.forEach(function (t) {
|
||||
self.element.removeEventListener(t, self);
|
||||
});
|
||||
};
|
||||
GD.prototype.handleEvent = function (e) {
|
||||
var handler = this.state[e.type];
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
if (e.changedTouches) {
|
||||
for (var i = 0; i < e.changedTouches.length; i++) {
|
||||
handler(this, e, e.changedTouches[i]);
|
||||
handler = this.state[e.type];
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handler(this, e);
|
||||
}
|
||||
};
|
||||
GD.prototype.startTimer = function (type, time) {
|
||||
this.clearTimer(type);
|
||||
var self = this;
|
||||
this.timers[type] = setTimeout(function () {
|
||||
self.timers[type] = null;
|
||||
var handler = self.state[type];
|
||||
if (handler) {
|
||||
handler(self, type);
|
||||
}
|
||||
}, time);
|
||||
};
|
||||
GD.prototype.clearTimer = function (type) {
|
||||
if (this.timers[type]) {
|
||||
clearTimeout(this.timers[type]);
|
||||
this.timers[type] = null;
|
||||
}
|
||||
};
|
||||
GD.prototype.switchTo = function (state, event, touch) {
|
||||
this.state = state;
|
||||
if (state.init) {
|
||||
state.init(this, event, touch);
|
||||
}
|
||||
};
|
||||
GD.prototype.emitEvent = function (type, detail) {
|
||||
if (!this.target) {
|
||||
console.error('Attempt to emit event with no target');
|
||||
return;
|
||||
}
|
||||
var event = this.element.ownerDocument.createEvent('CustomEvent');
|
||||
event.initCustomEvent(type, true, true, detail);
|
||||
this.target.dispatchEvent(event);
|
||||
};
|
||||
GD.HOLD_INTERVAL = 1000;
|
||||
GD.PAN_THRESHOLD = 20;
|
||||
GD.DOUBLE_TAP_DISTANCE = 50;
|
||||
GD.DOUBLE_TAP_TIME = 500;
|
||||
GD.VELOCITY_SMOOTHING = 0.5;
|
||||
GD.SCALE_THRESHOLD = 20;
|
||||
GD.ROTATE_THRESHOLD = 22.5;
|
||||
GD.THRESHOLD_SMOOTHING = 0.9;
|
||||
var abs = Math.abs, floor = Math.floor, sqrt = Math.sqrt, atan2 = Math.atan2;
|
||||
var PI = Math.PI;
|
||||
var eventtypes = [
|
||||
'touchstart',
|
||||
'touchmove',
|
||||
'touchend'
|
||||
];
|
||||
function eventTime(e) {
|
||||
var ts = e.timeStamp;
|
||||
if (ts > 2 * Date.now()) {
|
||||
return Math.floor(ts / 1000);
|
||||
} else {
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
function coordinates(e, t) {
|
||||
return Object.freeze({
|
||||
screenX: t.screenX,
|
||||
screenY: t.screenY,
|
||||
clientX: t.clientX,
|
||||
clientY: t.clientY,
|
||||
timeStamp: eventTime(e)
|
||||
});
|
||||
}
|
||||
function midpoints(e, t1, t2) {
|
||||
return Object.freeze({
|
||||
screenX: floor((t1.screenX + t2.screenX) / 2),
|
||||
screenY: floor((t1.screenY + t2.screenY) / 2),
|
||||
clientX: floor((t1.clientX + t2.clientX) / 2),
|
||||
clientY: floor((t1.clientY + t2.clientY) / 2),
|
||||
timeStamp: eventTime(e)
|
||||
});
|
||||
}
|
||||
function between(c1, c2) {
|
||||
var r = GD.THRESHOLD_SMOOTHING;
|
||||
return Object.freeze({
|
||||
screenX: floor(c1.screenX + r * (c2.screenX - c1.screenX)),
|
||||
screenY: floor(c1.screenY + r * (c2.screenY - c1.screenY)),
|
||||
clientX: floor(c1.clientX + r * (c2.clientX - c1.clientX)),
|
||||
clientY: floor(c1.clientY + r * (c2.clientY - c1.clientY)),
|
||||
timeStamp: floor(c1.timeStamp + r * (c2.timeStamp - c1.timeStamp))
|
||||
});
|
||||
}
|
||||
function touchDistance(t1, t2) {
|
||||
var dx = t2.screenX - t1.screenX;
|
||||
var dy = t2.screenY - t1.screenY;
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
function touchDirection(t1, t2) {
|
||||
return atan2(t2.screenY - t1.screenY, t2.screenX - t1.screenX) * 180 / PI;
|
||||
}
|
||||
function touchRotation(d1, d2) {
|
||||
var angle = d2 - d1;
|
||||
if (angle > 180) {
|
||||
angle -= 360;
|
||||
} else if (angle <= -180) {
|
||||
angle += 360;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
function isDoubleTap(lastTap, thisTap) {
|
||||
var dx = abs(thisTap.screenX - lastTap.screenX);
|
||||
var dy = abs(thisTap.screenY - lastTap.screenY);
|
||||
var dt = thisTap.timeStamp - lastTap.timeStamp;
|
||||
return dx < GD.DOUBLE_TAP_DISTANCE && dy < GD.DOUBLE_TAP_DISTANCE && dt < GD.DOUBLE_TAP_TIME;
|
||||
}
|
||||
var initialState = {
|
||||
name: 'initialState',
|
||||
init: function (d, e, t) {
|
||||
d.target = null;
|
||||
d.start = d.last = null;
|
||||
d.touch1 = d.touch2 = null;
|
||||
d.vx = d.vy = null;
|
||||
d.startDistance = d.lastDistance = null;
|
||||
d.startDirection = d.lastDirection = null;
|
||||
d.lastMidpoint = null;
|
||||
d.scaled = d.rotated = null;
|
||||
if (e && t && e.type === 'touchstart') {
|
||||
initialState.touchstart(d, e, t);
|
||||
}
|
||||
},
|
||||
touchstart: function (d, e, t) {
|
||||
d.switchTo(touchStartedState, e, t);
|
||||
}
|
||||
};
|
||||
var touchStartedState = {
|
||||
name: 'touchStartedState',
|
||||
init: function (d, e, t) {
|
||||
d.target = e.target;
|
||||
d.touch1 = t.identifier;
|
||||
d.start = d.last = coordinates(e, t);
|
||||
if (d.options.holdEvents) {
|
||||
d.startTimer('holdtimeout', GD.HOLD_INTERVAL);
|
||||
}
|
||||
},
|
||||
touchstart: function (d, e, t) {
|
||||
d.clearTimer('holdtimeout');
|
||||
if (e.touches.length > 1) {
|
||||
d.switchTo(transformState, e, t);
|
||||
} else {
|
||||
console.warn('Ignoring missing touchend event. See bug 1162771.');
|
||||
d.switchTo(initialState, e, t);
|
||||
}
|
||||
},
|
||||
touchmove: function (d, e, t) {
|
||||
if (t.identifier !== d.touch1) {
|
||||
return;
|
||||
}
|
||||
if (abs(t.screenX - d.start.screenX) > d.options.panThreshold || abs(t.screenY - d.start.screenY) > d.options.panThreshold) {
|
||||
d.clearTimer('holdtimeout');
|
||||
d.switchTo(panStartedState, e, t);
|
||||
}
|
||||
},
|
||||
touchend: function (d, e, t) {
|
||||
if (t.identifier !== d.touch1) {
|
||||
return;
|
||||
}
|
||||
if (d.lastTap && isDoubleTap(d.lastTap, d.start)) {
|
||||
d.emitEvent('tap', d.start);
|
||||
d.emitEvent('dbltap', d.start);
|
||||
d.lastTap = null;
|
||||
} else {
|
||||
d.emitEvent('tap', d.start);
|
||||
d.lastTap = coordinates(e, t);
|
||||
}
|
||||
d.clearTimer('holdtimeout');
|
||||
d.switchTo(initialState);
|
||||
},
|
||||
holdtimeout: function (d) {
|
||||
d.switchTo(holdState);
|
||||
}
|
||||
};
|
||||
var panStartedState = {
|
||||
name: 'panStartedState',
|
||||
init: function (d, e, t) {
|
||||
d.start = d.last = between(d.start, coordinates(e, t));
|
||||
if (e.type === 'touchmove') {
|
||||
panStartedState.touchmove(d, e, t);
|
||||
}
|
||||
},
|
||||
touchmove: function (d, e, t) {
|
||||
if (t.identifier !== d.touch1) {
|
||||
return;
|
||||
}
|
||||
var current = coordinates(e, t);
|
||||
d.emitEvent('pan', {
|
||||
absolute: {
|
||||
dx: current.screenX - d.start.screenX,
|
||||
dy: current.screenY - d.start.screenY
|
||||
},
|
||||
relative: {
|
||||
dx: current.screenX - d.last.screenX,
|
||||
dy: current.screenY - d.last.screenY
|
||||
},
|
||||
position: current
|
||||
});
|
||||
var dt = current.timeStamp - d.last.timeStamp;
|
||||
var vx = (current.screenX - d.last.screenX) / dt;
|
||||
var vy = (current.screenY - d.last.screenY) / dt;
|
||||
if (d.vx == null) {
|
||||
d.vx = vx;
|
||||
d.vy = vy;
|
||||
} else {
|
||||
d.vx = d.vx * GD.VELOCITY_SMOOTHING + vx * (1 - GD.VELOCITY_SMOOTHING);
|
||||
d.vy = d.vy * GD.VELOCITY_SMOOTHING + vy * (1 - GD.VELOCITY_SMOOTHING);
|
||||
}
|
||||
d.last = current;
|
||||
},
|
||||
touchend: function (d, e, t) {
|
||||
if (t.identifier !== d.touch1) {
|
||||
return;
|
||||
}
|
||||
var current = coordinates(e, t);
|
||||
var dx = current.screenX - d.start.screenX;
|
||||
var dy = current.screenY - d.start.screenY;
|
||||
var angle = atan2(dy, dx) * 180 / PI;
|
||||
if (angle < 0) {
|
||||
angle += 360;
|
||||
}
|
||||
var direction;
|
||||
if (angle >= 315 || angle < 45) {
|
||||
direction = 'right';
|
||||
} else if (angle >= 45 && angle < 135) {
|
||||
direction = 'down';
|
||||
} else if (angle >= 135 && angle < 225) {
|
||||
direction = 'left';
|
||||
} else if (angle >= 225 && angle < 315) {
|
||||
direction = 'up';
|
||||
}
|
||||
d.emitEvent('swipe', {
|
||||
start: d.start,
|
||||
end: current,
|
||||
dx: dx,
|
||||
dy: dy,
|
||||
dt: e.timeStamp - d.start.timeStamp,
|
||||
vx: d.vx,
|
||||
vy: d.vy,
|
||||
direction: direction,
|
||||
angle: angle
|
||||
});
|
||||
d.switchTo(initialState);
|
||||
}
|
||||
};
|
||||
var holdState = {
|
||||
name: 'holdState',
|
||||
init: function (d) {
|
||||
d.emitEvent('holdstart', d.start);
|
||||
},
|
||||
touchmove: function (d, e, t) {
|
||||
var current = coordinates(e, t);
|
||||
d.emitEvent('holdmove', {
|
||||
absolute: {
|
||||
dx: current.screenX - d.start.screenX,
|
||||
dy: current.screenY - d.start.screenY
|
||||
},
|
||||
relative: {
|
||||
dx: current.screenX - d.last.screenX,
|
||||
dy: current.screenY - d.last.screenY
|
||||
},
|
||||
position: current
|
||||
});
|
||||
d.last = current;
|
||||
},
|
||||
touchend: function (d, e, t) {
|
||||
var current = coordinates(e, t);
|
||||
d.emitEvent('holdend', {
|
||||
start: d.start,
|
||||
end: current,
|
||||
dx: current.screenX - d.start.screenX,
|
||||
dy: current.screenY - d.start.screenY
|
||||
});
|
||||
d.switchTo(initialState);
|
||||
}
|
||||
};
|
||||
var transformState = {
|
||||
name: 'transformState',
|
||||
init: function (d, e, t) {
|
||||
d.touch2 = t.identifier;
|
||||
var t1 = e.touches.identifiedTouch(d.touch1);
|
||||
var t2 = e.touches.identifiedTouch(d.touch2);
|
||||
d.startDistance = d.lastDistance = touchDistance(t1, t2);
|
||||
d.startDirection = d.lastDirection = touchDirection(t1, t2);
|
||||
d.scaled = d.rotated = false;
|
||||
},
|
||||
touchmove: function (d, e, t) {
|
||||
if (t.identifier !== d.touch1 && t.identifier !== d.touch2) {
|
||||
return;
|
||||
}
|
||||
var t1 = e.touches.identifiedTouch(d.touch1);
|
||||
var t2 = e.touches.identifiedTouch(d.touch2);
|
||||
var midpoint = midpoints(e, t1, t2);
|
||||
var distance = touchDistance(t1, t2);
|
||||
var direction = touchDirection(t1, t2);
|
||||
var rotation = touchRotation(d.startDirection, direction);
|
||||
if (!d.scaled) {
|
||||
if (abs(distance - d.startDistance) > GD.SCALE_THRESHOLD) {
|
||||
d.scaled = true;
|
||||
d.startDistance = d.lastDistance = floor(d.startDistance + GD.THRESHOLD_SMOOTHING * (distance - d.startDistance));
|
||||
} else {
|
||||
distance = d.startDistance;
|
||||
}
|
||||
}
|
||||
if (!d.rotated) {
|
||||
if (abs(rotation) > GD.ROTATE_THRESHOLD) {
|
||||
d.rotated = true;
|
||||
} else {
|
||||
direction = d.startDirection;
|
||||
}
|
||||
}
|
||||
if (d.scaled || d.rotated) {
|
||||
d.emitEvent('transform', {
|
||||
absolute: {
|
||||
scale: distance / d.startDistance,
|
||||
rotate: touchRotation(d.startDirection, direction)
|
||||
},
|
||||
relative: {
|
||||
scale: distance / d.lastDistance,
|
||||
rotate: touchRotation(d.lastDirection, direction)
|
||||
},
|
||||
midpoint: midpoint
|
||||
});
|
||||
d.lastDistance = distance;
|
||||
d.lastDirection = direction;
|
||||
d.lastMidpoint = midpoint;
|
||||
}
|
||||
},
|
||||
touchend: function (d, e, t) {
|
||||
if (t.identifier === d.touch2) {
|
||||
d.touch2 = null;
|
||||
} else if (t.identifier === d.touch1) {
|
||||
d.touch1 = d.touch2;
|
||||
d.touch2 = null;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (d.scaled || d.rotated) {
|
||||
d.emitEvent('transformend', {
|
||||
absolute: {
|
||||
scale: d.lastDistance / d.startDistance,
|
||||
rotate: touchRotation(d.startDirection, d.lastDirection)
|
||||
},
|
||||
relative: {
|
||||
scale: 1,
|
||||
rotate: 0
|
||||
},
|
||||
midpoint: d.lastMidpoint
|
||||
});
|
||||
}
|
||||
d.switchTo(afterTransformState);
|
||||
}
|
||||
};
|
||||
var afterTransformState = {
|
||||
name: 'afterTransformState',
|
||||
touchstart: function (d, e, t) {
|
||||
d.switchTo(transformState, e, t);
|
||||
},
|
||||
touchend: function (d, e, t) {
|
||||
if (t.identifier === d.touch1) {
|
||||
d.switchTo(initialState);
|
||||
}
|
||||
}
|
||||
};
|
||||
return GD;
|
||||
}();
|
||||
284
assets/webapps/camera.gaiamobile.org/shared/js/intl/l20n.js
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
;var _createClass=function(){function defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if('value'in descriptor)
|
||||
descriptor.writable=true;Object.defineProperty(target,descriptor.key,descriptor);}}
|
||||
return function(Constructor,protoProps,staticProps){if(protoProps)
|
||||
defineProperties(Constructor.prototype,protoProps);if(staticProps)
|
||||
defineProperties(Constructor,staticProps);return Constructor;};}();function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError('Cannot call a class as a function');}}
|
||||
(function(){'use strict';function emit(listeners,...args){const type=args.shift();if(listeners['*']){listeners['*'].slice().forEach(listener=>listener.apply(this,args));}
|
||||
if(listeners[type]){listeners[type].slice().forEach(listener=>listener.apply(this,args));}}
|
||||
function addEventListener(listeners,type,listener){if(!(type in listeners)){listeners[type]=[];}
|
||||
listeners[type].push(listener);}
|
||||
function removeEventListener(listeners,type,listener){const typeListeners=listeners[type];const pos=typeListeners.indexOf(listener);if(pos===-1){return;}
|
||||
typeListeners.splice(pos,1);}
|
||||
let Client=function(){function Client(remote){_classCallCheck(this,Client);this.id=this;this.remote=remote;const listeners={};this.on=(...args)=>addEventListener(listeners,...args);this.emit=(...args)=>emit(listeners,...args);}
|
||||
_createClass(Client,[{key:'method',value:function method(name,...args){return this.remote[name](...args);}}]);return Client;}();function broadcast(type,data){Array.from(this.ctxs.keys()).forEach(client=>client.emit(type,data));}
|
||||
function L10nError(message,id,lang){this.name='L10nError';this.message=message;this.id=id;this.lang=lang;}
|
||||
L10nError.prototype=Object.create(Error.prototype);L10nError.prototype.constructor=L10nError;function load(type,url){return new Promise(function(resolve,reject){const xhr=new XMLHttpRequest();if(xhr.overrideMimeType){xhr.overrideMimeType(type);}
|
||||
xhr.open('GET',url,true);if(type==='application/json'){xhr.responseType='json';}
|
||||
xhr.addEventListener('load',function io_onload(e){if(e.target.status===200||e.target.status===0){resolve(e.target.response||e.target.responseText);}else{reject(new L10nError('Not found: '+url));}});xhr.addEventListener('error',reject);xhr.addEventListener('timeout',reject);try{xhr.send(null);}catch(e){if(e.name==='NS_ERROR_FILE_NOT_FOUND'){reject(new L10nError('Not found: '+url));}else{throw e;}}});}
|
||||
const io={extra:function(code,ver,path,type){return navigator.mozApps.getLocalizationResource(code,ver,path,type);},app:function(code,ver,path,type){switch(type){case'text':return load('text/plain',path);case'json':return load('application/json',path);default:throw new L10nError('Unknown file type: '+type);}}};function fetchResource(res,{code,src,ver}){const url=res.replace('{locale}',code);const type=res.endsWith('.json')?'json':'text';return io[src](code,ver,url,type);}
|
||||
const KNOWN_MACROS=['plural'];const MAX_PLACEABLE_LENGTH=2500;const FSI='\u2068';const PDI='\u2069';const resolutionChain=new WeakSet();function format(ctx,lang,args,entity){if(typeof entity==='string'){return[{},entity];}
|
||||
if(resolutionChain.has(entity)){throw new L10nError('Cyclic reference detected');}
|
||||
resolutionChain.add(entity);let rv;try{rv=resolveValue({},ctx,lang,args,entity.value,entity.index);}finally{resolutionChain.delete(entity);}
|
||||
return rv;}
|
||||
function resolveIdentifier(ctx,lang,args,id){if(KNOWN_MACROS.indexOf(id)>-1){return[{},ctx._getMacro(lang,id)];}
|
||||
if(args&&args.hasOwnProperty(id)){if(typeof args[id]==='string'||typeof args[id]==='number'&&!isNaN(args[id])){return[{},args[id]];}else{throw new L10nError('Arg must be a string or a number: '+id);}}
|
||||
if(id==='__proto__'){throw new L10nError('Illegal id: '+id);}
|
||||
const entity=ctx._getEntity(lang,id);if(entity){return format(ctx,lang,args,entity);}
|
||||
throw new L10nError('Unknown reference: '+id);}
|
||||
function subPlaceable(locals,ctx,lang,args,id){let newLocals,value;try{[newLocals,value]=resolveIdentifier(ctx,lang,args,id);}catch(err){return[{error:err},FSI+'{{ '+id+' }}'+PDI];}
|
||||
if(typeof value==='number'){const formatter=ctx._getNumberFormatter(lang);return[newLocals,formatter.format(value)];}
|
||||
if(typeof value==='string'){if(value.length>=MAX_PLACEABLE_LENGTH){throw new L10nError('Too many characters in placeable ('+value.length+', max allowed is '+MAX_PLACEABLE_LENGTH+')');}
|
||||
return[newLocals,FSI+value+PDI];}
|
||||
return[{},FSI+'{{ '+id+' }}'+PDI];}
|
||||
function interpolate(locals,ctx,lang,args,arr){return arr.reduce(function([localsSeq,valueSeq],cur){if(typeof cur==='string'){return[localsSeq,valueSeq+cur];}else{const[,value]=subPlaceable(locals,ctx,lang,args,cur.name);return[localsSeq,valueSeq+value];}},[locals,'']);}
|
||||
function resolveSelector(ctx,lang,args,expr,index){let selectorName;if(index[0].type==='call'&&index[0].expr.type==='prop'&&index[0].expr.expr.name==='cldr'){selectorName='plural';}else{selectorName=index[0].name;}
|
||||
const selector=resolveIdentifier(ctx,lang,args,selectorName)[1];if(typeof selector!=='function'){return selector;}
|
||||
const argValue=index[0].args?resolveIdentifier(ctx,lang,args,index[0].args[0].name)[1]:undefined;if(selectorName==='plural'){if(argValue===0&&'zero'in expr){return'zero';}
|
||||
if(argValue===1&&'one'in expr){return'one';}
|
||||
if(argValue===2&&'two'in expr){return'two';}}
|
||||
return selector(argValue);}
|
||||
function resolveValue(locals,ctx,lang,args,expr,index){if(!expr){return[locals,expr];}
|
||||
if(typeof expr==='string'||typeof expr==='boolean'||typeof expr==='number'){return[locals,expr];}
|
||||
if(Array.isArray(expr)){return interpolate(locals,ctx,lang,args,expr);}
|
||||
if(index){const selector=resolveSelector(ctx,lang,args,expr,index);if(selector in expr){return resolveValue(locals,ctx,lang,args,expr[selector]);}}
|
||||
const defaultKey=expr.__default||'other';if(defaultKey in expr){return resolveValue(locals,ctx,lang,args,expr[defaultKey]);}
|
||||
throw new L10nError('Unresolvable value');}
|
||||
const locales2rules={'af':3,'ak':4,'am':4,'ar':1,'asa':3,'az':0,'be':11,'bem':3,'bez':3,'bg':3,'bh':4,'bm':0,'bn':3,'bo':0,'br':20,'brx':3,'bs':11,'ca':3,'cgg':3,'chr':3,'cs':12,'cy':17,'da':3,'de':3,'dv':3,'dz':0,'ee':3,'el':3,'en':3,'eo':3,'es':3,'et':3,'eu':3,'fa':0,'ff':5,'fi':3,'fil':4,'fo':3,'fr':5,'fur':3,'fy':3,'ga':8,'gd':24,'gl':3,'gsw':3,'gu':3,'guw':4,'gv':23,'ha':3,'haw':3,'he':2,'hi':4,'hr':11,'hu':0,'id':0,'ig':0,'ii':0,'is':3,'it':3,'iu':7,'ja':0,'jmc':3,'jv':0,'ka':0,'kab':5,'kaj':3,'kcg':3,'kde':0,'kea':0,'kk':3,'kl':3,'km':0,'kn':0,'ko':0,'ksb':3,'ksh':21,'ku':3,'kw':7,'lag':18,'lb':3,'lg':3,'ln':4,'lo':0,'lt':10,'lv':6,'mas':3,'mg':4,'mk':16,'ml':3,'mn':3,'mo':9,'mr':3,'ms':0,'mt':15,'my':0,'nah':3,'naq':7,'nb':3,'nd':3,'ne':3,'nl':3,'nn':3,'no':3,'nr':3,'nso':4,'ny':3,'nyn':3,'om':3,'or':3,'pa':3,'pap':3,'pl':13,'ps':3,'pt':3,'rm':3,'ro':9,'rof':3,'ru':11,'rwk':3,'sah':0,'saq':3,'se':7,'seh':3,'ses':0,'sg':0,'sh':11,'shi':19,'sk':12,'sl':14,'sma':7,'smi':7,'smj':7,'smn':7,'sms':7,'sn':3,'so':3,'sq':3,'sr':11,'ss':3,'ssy':3,'st':3,'sv':3,'sw':3,'syr':3,'ta':3,'te':3,'teo':3,'th':0,'ti':4,'tig':3,'tk':3,'tl':4,'tn':3,'to':0,'tr':0,'ts':3,'tzm':22,'uk':11,'ur':3,'ve':3,'vi':0,'vun':3,'wa':4,'wae':3,'wo':0,'xh':3,'xog':3,'yo':0,'zh':0,'zu':3};function isIn(n,list){return list.indexOf(n)!==-1;}
|
||||
function isBetween(n,start,end){return typeof n===typeof start&&start<=n&&n<=end;}
|
||||
const pluralRules={'0':function(){return'other';},'1':function(n){if(isBetween(n%100,3,10)){return'few';}
|
||||
if(n===0){return'zero';}
|
||||
if(isBetween(n%100,11,99)){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'2':function(n){if(n!==0&&n%10===0){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'3':function(n){if(n===1){return'one';}
|
||||
return'other';},'4':function(n){if(isBetween(n,0,1)){return'one';}
|
||||
return'other';},'5':function(n){if(isBetween(n,0,2)&&n!==2){return'one';}
|
||||
return'other';},'6':function(n){if(n===0){return'zero';}
|
||||
if(n%10===1&&n%100!==11){return'one';}
|
||||
return'other';},'7':function(n){if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'8':function(n){if(isBetween(n,3,6)){return'few';}
|
||||
if(isBetween(n,7,10)){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'9':function(n){if(n===0||n!==1&&isBetween(n%100,1,19)){return'few';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'10':function(n){if(isBetween(n%10,2,9)&&!isBetween(n%100,11,19)){return'few';}
|
||||
if(n%10===1&&!isBetween(n%100,11,19)){return'one';}
|
||||
return'other';},'11':function(n){if(isBetween(n%10,2,4)&&!isBetween(n%100,12,14)){return'few';}
|
||||
if(n%10===0||isBetween(n%10,5,9)||isBetween(n%100,11,14)){return'many';}
|
||||
if(n%10===1&&n%100!==11){return'one';}
|
||||
return'other';},'12':function(n){if(isBetween(n,2,4)){return'few';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'13':function(n){if(isBetween(n%10,2,4)&&!isBetween(n%100,12,14)){return'few';}
|
||||
if(n!==1&&isBetween(n%10,0,1)||isBetween(n%10,5,9)||isBetween(n%100,12,14)){return'many';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'14':function(n){if(isBetween(n%100,3,4)){return'few';}
|
||||
if(n%100===2){return'two';}
|
||||
if(n%100===1){return'one';}
|
||||
return'other';},'15':function(n){if(n===0||isBetween(n%100,2,10)){return'few';}
|
||||
if(isBetween(n%100,11,19)){return'many';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'16':function(n){if(n%10===1&&n!==11){return'one';}
|
||||
return'other';},'17':function(n){if(n===3){return'few';}
|
||||
if(n===0){return'zero';}
|
||||
if(n===6){return'many';}
|
||||
if(n===2){return'two';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'18':function(n){if(n===0){return'zero';}
|
||||
if(isBetween(n,0,2)&&n!==0&&n!==2){return'one';}
|
||||
return'other';},'19':function(n){if(isBetween(n,2,10)){return'few';}
|
||||
if(isBetween(n,0,1)){return'one';}
|
||||
return'other';},'20':function(n){if((isBetween(n%10,3,4)||n%10===9)&&!(isBetween(n%100,10,19)||isBetween(n%100,70,79)||isBetween(n%100,90,99))){return'few';}
|
||||
if(n%1000000===0&&n!==0){return'many';}
|
||||
if(n%10===2&&!isIn(n%100,[12,72,92])){return'two';}
|
||||
if(n%10===1&&!isIn(n%100,[11,71,91])){return'one';}
|
||||
return'other';},'21':function(n){if(n===0){return'zero';}
|
||||
if(n===1){return'one';}
|
||||
return'other';},'22':function(n){if(isBetween(n,0,1)||isBetween(n,11,99)){return'one';}
|
||||
return'other';},'23':function(n){if(isBetween(n%10,1,2)||n%20===0){return'one';}
|
||||
return'other';},'24':function(n){if(isBetween(n,3,10)||isBetween(n,13,19)){return'few';}
|
||||
if(isIn(n,[2,12])){return'two';}
|
||||
if(isIn(n,[1,11])){return'one';}
|
||||
return'other';}};function getPluralRule(code){const index=locales2rules[code.replace(/-.*$/,'')];if(!(index in pluralRules)){return function(){return'other';};}
|
||||
return pluralRules[index];}
|
||||
const L20nIntl=typeof Intl!=='undefined'?Intl:{NumberFormat:function(){return{format:function(v){return v;}};}};let Context=function(){function Context(env,langs,resIds){_classCallCheck(this,Context);this.langs=langs;this.resIds=resIds;this.env=env;this.emit=(type,evt)=>env.emit(type,evt,this);}
|
||||
_createClass(Context,[{key:'_formatTuple',value:function _formatTuple(lang,args,entity,id,key){try{return format(this,lang,args,entity);}catch(err){err.id=key?id+'::'+key:id;err.lang=lang;this.emit('resolveerror',err);return[{error:err},err.id];}}},{key:'_formatEntity',value:function _formatEntity(lang,args,entity,id){const[,value]=this._formatTuple(lang,args,entity,id);const formatted={value,attrs:null};if(entity.attrs){formatted.attrs=Object.create(null);for(let key in entity.attrs){const[,attrValue]=this._formatTuple(lang,args,entity.attrs[key],id,key);formatted.attrs[key]=attrValue;}}
|
||||
return formatted;}},{key:'_formatValue',value:function _formatValue(lang,args,entity,id){return this._formatTuple(lang,args,entity,id)[1];}},{key:'fetch',value:function fetch(langs=this.langs){if(langs.length===0){return Promise.resolve(langs);}
|
||||
return Promise.all(this.resIds.map(resId=>this.env._getResource(langs[0],resId))).then(()=>langs);}},{key:'_resolve',value:function _resolve(langs,keys,formatter,prevResolved){const lang=langs[0];if(!lang){return reportMissing.call(this,keys,formatter,prevResolved);}
|
||||
let hasUnresolved=false;const resolved=keys.map((key,i)=>{if(prevResolved&&prevResolved[i]!==undefined){return prevResolved[i];}
|
||||
const[id,args]=Array.isArray(key)?key:[key,undefined];const entity=this._getEntity(lang,id);if(entity){return formatter.call(this,lang,args,entity,id);}
|
||||
this.emit('notfounderror',new L10nError('"'+id+'"'+' not found in '+lang.code,id,lang));hasUnresolved=true;});if(!hasUnresolved){return resolved;}
|
||||
return this.fetch(langs.slice(1)).then(nextLangs=>this._resolve(nextLangs,keys,formatter,resolved));}},{key:'formatEntities',value:function formatEntities(...keys){return this.fetch().then(langs=>this._resolve(langs,keys,this._formatEntity));}},{key:'formatValues',value:function formatValues(...keys){return this.fetch().then(langs=>this._resolve(langs,keys,this._formatValue));}},{key:'_getEntity',value:function _getEntity(lang,id){const cache=this.env.resCache;for(let i=0,resId;resId=this.resIds[i];i++){const resource=cache.get(resId+lang.code+lang.src);if(resource instanceof L10nError){continue;}
|
||||
if(id in resource){return resource[id];}}
|
||||
return undefined;}},{key:'_getNumberFormatter',value:function _getNumberFormatter(lang){if(!this.env.numberFormatters){this.env.numberFormatters=new Map();}
|
||||
if(!this.env.numberFormatters.has(lang)){const formatter=L20nIntl.NumberFormat(lang);this.env.numberFormatters.set(lang,formatter);return formatter;}
|
||||
return this.env.numberFormatters.get(lang);}},{key:'_getMacro',value:function _getMacro(lang,id){switch(id){case'plural':return getPluralRule(lang.code);default:return undefined;}}}]);return Context;}();function reportMissing(keys,formatter,resolved){const missingIds=new Set();keys.forEach((key,i)=>{if(resolved&&resolved[i]!==undefined){return;}
|
||||
const id=Array.isArray(key)?key[0]:key;missingIds.add(id);resolved[i]=formatter===this._formatValue?id:{value:id,attrs:null};});this.emit('notfounderror',new L10nError('"'+Array.from(missingIds).join(', ')+'"'+' not found in any language',missingIds));return resolved;}
|
||||
var MAX_PLACEABLES=100;var PropertiesParser={patterns:null,entryIds:null,emit:null,init:function(){this.patterns={comment:/^\s*#|^\s*$/,entity:/^([^=\s]+)\s*=\s*(.*)$/,multiline:/[^\\]\\$/,index:/\{\[\s*(\w+)(?:\(([^\)]*)\))?\s*\]\}/i,unicode:/\\u([0-9a-fA-F]{1,4})/g,entries:/[^\r\n]+/g,controlChars:/\\([\\\n\r\t\b\f\{\}\"\'])/g,placeables:/\{\{\s*([^\s]*?)\s*\}\}/};},parse:function(emit,source){if(!this.patterns){this.init();}
|
||||
this.emit=emit;var entries={};var lines=source.match(this.patterns.entries);if(!lines){return entries;}
|
||||
for(var i=0;i<lines.length;i++){var line=lines[i];if(this.patterns.comment.test(line)){continue;}
|
||||
while(this.patterns.multiline.test(line)&&i<lines.length){line=line.slice(0,-1)+lines[++i].trim();}
|
||||
var entityMatch=line.match(this.patterns.entity);if(entityMatch){try{this.parseEntity(entityMatch[1],entityMatch[2],entries);}catch(e){if(!this.emit){throw e;}}}}
|
||||
return entries;},parseEntity:function(id,value,entries){var name,key;var pos=id.indexOf('[');if(pos!==-1){name=id.substr(0,pos);key=id.substring(pos+1,id.length-1);}else{name=id;key=null;}
|
||||
var nameElements=name.split('.');if(nameElements.length>2){throw this.error('Error in ID: "'+name+'".'+' Nested attributes are not supported.');}
|
||||
var attr;if(nameElements.length>1){name=nameElements[0];attr=nameElements[1];if(attr[0]==='$'){throw this.error('Attribute can\'t start with "$"');}}else{attr=null;}
|
||||
this.setEntityValue(name,attr,key,this.unescapeString(value),entries);},setEntityValue:function(id,attr,key,rawValue,entries){var value=rawValue.indexOf('{{')>-1?this.parseString(rawValue):rawValue;var isSimpleValue=typeof value==='string';var root=entries;var isSimpleNode=typeof entries[id]==='string';if(!entries[id]&&(attr||key||!isSimpleValue)){entries[id]=Object.create(null);isSimpleNode=false;}
|
||||
if(attr){if(isSimpleNode){const val=entries[id];entries[id]=Object.create(null);entries[id].value=val;}
|
||||
if(!entries[id].attrs){entries[id].attrs=Object.create(null);}
|
||||
if(!entries[id].attrs&&!isSimpleValue){entries[id].attrs[attr]=Object.create(null);}
|
||||
root=entries[id].attrs;id=attr;}
|
||||
if(key){isSimpleNode=false;if(typeof root[id]==='string'){const val=root[id];root[id]=Object.create(null);root[id].index=this.parseIndex(val);root[id].value=Object.create(null);}
|
||||
root=root[id].value;id=key;isSimpleValue=true;}
|
||||
if(isSimpleValue){if(id in root){throw this.error('Duplicated id: '+id);}
|
||||
root[id]=value;}else{if(!root[id]){root[id]=Object.create(null);}
|
||||
root[id].value=value;}},parseString:function(str){var chunks=str.split(this.patterns.placeables);var complexStr=[];var len=chunks.length;var placeablesCount=(len-1)/2;if(placeablesCount>=MAX_PLACEABLES){throw this.error('Too many placeables ('+placeablesCount+', max allowed is '+MAX_PLACEABLES+')');}
|
||||
for(var i=0;i<chunks.length;i++){if(chunks[i].length===0){continue;}
|
||||
if(i%2===1){complexStr.push({type:'idOrVar',name:chunks[i]});}else{complexStr.push(chunks[i]);}}
|
||||
return complexStr;},unescapeString:function(str){if(str.lastIndexOf('\\')!==-1){str=str.replace(this.patterns.controlChars,'$1');}
|
||||
return str.replace(this.patterns.unicode,function(match,token){return String.fromCodePoint(parseInt(token,16));});},parseIndex:function(str){var match=str.match(this.patterns.index);if(!match){throw new L10nError('Malformed index');}
|
||||
if(match[2]){return[{type:'call',expr:{type:'prop',expr:{type:'glob',name:'cldr'},prop:'plural',cmpt:false},args:[{type:'idOrVar',name:match[2]}]}];}else{return[{type:'idOrVar',name:match[1]}];}},error:function(msg,type='parsererror'){const err=new L10nError(msg);if(this.emit){this.emit(type,err);}
|
||||
return err;}};const MAX_PLACEABLES$1=100;var L20nParser={parse:function(emit,string){this._source=string;this._index=0;this._length=string.length;this.entries=Object.create(null);this.emit=emit;return this.getResource();},getResource:function(){this.getWS();while(this._index<this._length){try{this.getEntry();}catch(e){if(e instanceof L10nError){this.getJunkEntry();if(!this.emit){throw e;}}else{throw e;}}
|
||||
if(this._index<this._length){this.getWS();}}
|
||||
return this.entries;},getEntry:function(){if(this._source[this._index]==='<'){++this._index;const id=this.getIdentifier();if(this._source[this._index]==='['){++this._index;return this.getEntity(id,this.getItemList(this.getExpression,']'));}
|
||||
return this.getEntity(id);}
|
||||
if(this._source.startsWith('/*',this._index)){return this.getComment();}
|
||||
throw this.error('Invalid entry');},getEntity:function(id,index){if(!this.getRequiredWS()){throw this.error('Expected white space');}
|
||||
const ch=this._source[this._index];const hasIndex=index!==undefined;const value=this.getValue(ch,hasIndex,hasIndex);let attrs;if(value===undefined){if(ch==='>'){throw this.error('Expected ">"');}
|
||||
attrs=this.getAttributes();}else{const ws1=this.getRequiredWS();if(this._source[this._index]!=='>'){if(!ws1){throw this.error('Expected ">"');}
|
||||
attrs=this.getAttributes();}}
|
||||
++this._index;if(id in this.entries){throw this.error('Duplicate entry ID "'+id,'duplicateerror');}
|
||||
if(!attrs&&!index&&typeof value==='string'){this.entries[id]=value;}else{this.entries[id]={value,attrs,index};}},getValue:function(ch=this._source[this._index],index=false,required=true){switch(ch){case'\'':case'"':return this.getString(ch,1);case'{':return this.getHash(index);}
|
||||
if(required){throw this.error('Unknown value type');}
|
||||
return;},getWS:function(){let cc=this._source.charCodeAt(this._index);while(cc===32||cc===10||cc===9||cc===13){cc=this._source.charCodeAt(++this._index);}},getRequiredWS:function(){const pos=this._index;let cc=this._source.charCodeAt(pos);while(cc===32||cc===10||cc===9||cc===13){cc=this._source.charCodeAt(++this._index);}
|
||||
return this._index!==pos;},getIdentifier:function(){const start=this._index;let cc=this._source.charCodeAt(this._index);if(cc>=97&&cc<=122||cc>=65&&cc<=90||cc===95){cc=this._source.charCodeAt(++this._index);}else{throw this.error('Identifier has to start with [a-zA-Z_]');}
|
||||
while(cc>=97&&cc<=122||cc>=65&&cc<=90||cc>=48&&cc<=57||cc===95){cc=this._source.charCodeAt(++this._index);}
|
||||
return this._source.slice(start,this._index);},getUnicodeChar:function(){for(let i=0;i<4;i++){let cc=this._source.charCodeAt(++this._index);if(cc>96&&cc<103||cc>64&&cc<71||cc>47&&cc<58){continue;}
|
||||
throw this.error('Illegal unicode escape sequence');}
|
||||
this._index++;return String.fromCharCode(parseInt(this._source.slice(this._index-4,this._index),16));},stringRe:/"|'|{{|\\/g,getString:function(opchar,opcharLen){const body=[];let placeables=0;this._index+=opcharLen;const start=this._index;let bufStart=start;let buf='';while(true){this.stringRe.lastIndex=this._index;const match=this.stringRe.exec(this._source);if(!match){throw this.error('Unclosed string literal');}
|
||||
if(match[0]==='"'||match[0]==='\''){if(match[0]!==opchar){this._index+=opcharLen;continue;}
|
||||
this._index=match.index+opcharLen;break;}
|
||||
if(match[0]==='{{'){if(placeables>MAX_PLACEABLES$1-1){throw this.error('Too many placeables, maximum allowed is '+MAX_PLACEABLES$1);}
|
||||
placeables++;if(match.index>bufStart||buf.length>0){body.push(buf+this._source.slice(bufStart,match.index));buf='';}
|
||||
this._index=match.index+2;this.getWS();body.push(this.getExpression());this.getWS();this._index+=2;bufStart=this._index;continue;}
|
||||
if(match[0]==='\\'){this._index=match.index+1;const ch2=this._source[this._index];if(ch2==='u'){buf+=this._source.slice(bufStart,match.index)+this.getUnicodeChar();}else if(ch2===opchar||ch2==='\\'){buf+=this._source.slice(bufStart,match.index)+ch2;this._index++;}else if(this._source.startsWith('{{',this._index)){buf+=this._source.slice(bufStart,match.index)+'{{';this._index+=2;}else{throw this.error('Illegal escape sequence');}
|
||||
bufStart=this._index;}}
|
||||
if(body.length===0){return buf+this._source.slice(bufStart,this._index-opcharLen);}
|
||||
if(this._index-opcharLen>bufStart||buf.length>0){body.push(buf+this._source.slice(bufStart,this._index-opcharLen));}
|
||||
return body;},getAttributes:function(){const attrs=Object.create(null);while(true){this.getAttribute(attrs);const ws1=this.getRequiredWS();const ch=this._source.charAt(this._index);if(ch==='>'){break;}else if(!ws1){throw this.error('Expected ">"');}}
|
||||
return attrs;},getAttribute:function(attrs){const key=this.getIdentifier();let index;if(this._source[this._index]==='['){++this._index;this.getWS();index=this.getItemList(this.getExpression,']');}
|
||||
this.getWS();if(this._source[this._index]!==':'){throw this.error('Expected ":"');}
|
||||
++this._index;this.getWS();const hasIndex=index!==undefined;const value=this.getValue(undefined,hasIndex);if(key in attrs){throw this.error('Duplicate attribute "'+key,'duplicateerror');}
|
||||
if(!index&&typeof value==='string'){attrs[key]=value;}else{attrs[key]={value,index};}},getHash:function(index){const items=Object.create(null);++this._index;this.getWS();let defKey;while(true){const[key,value,def]=this.getHashItem();items[key]=value;if(def){if(defKey){throw this.error('Default item redefinition forbidden');}
|
||||
defKey=key;}
|
||||
this.getWS();const comma=this._source[this._index]===',';if(comma){++this._index;this.getWS();}
|
||||
if(this._source[this._index]==='}'){++this._index;break;}
|
||||
if(!comma){throw this.error('Expected "}"');}}
|
||||
if(defKey){items.__default=defKey;}else if(!index){throw this.error('Unresolvable Hash Value');}
|
||||
return items;},getHashItem:function(){let defItem=false;if(this._source[this._index]==='*'){++this._index;defItem=true;}
|
||||
const key=this.getIdentifier();this.getWS();if(this._source[this._index]!==':'){throw this.error('Expected ":"');}
|
||||
++this._index;this.getWS();return[key,this.getValue(),defItem];},getComment:function(){this._index+=2;const start=this._index;const end=this._source.indexOf('*/',start);if(end===-1){throw this.error('Comment without a closing tag');}
|
||||
this._index=end+2;},getExpression:function(){let exp=this.getPrimaryExpression();while(true){let ch=this._source[this._index];if(ch==='.'||ch==='['){++this._index;exp=this.getPropertyExpression(exp,ch==='[');}else if(ch==='('){++this._index;exp=this.getCallExpression(exp);}else{break;}}
|
||||
return exp;},getPropertyExpression:function(idref,computed){let exp;if(computed){this.getWS();exp=this.getExpression();this.getWS();if(this._source[this._index]!==']'){throw this.error('Expected "]"');}
|
||||
++this._index;}else{exp=this.getIdentifier();}
|
||||
return{type:'prop',expr:idref,prop:exp,cmpt:computed};},getCallExpression:function(callee){this.getWS();return{type:'call',expr:callee,args:this.getItemList(this.getExpression,')')};},getPrimaryExpression:function(){const ch=this._source[this._index];switch(ch){case'$':++this._index;return{type:'var',name:this.getIdentifier()};case'@':++this._index;return{type:'glob',name:this.getIdentifier()};default:return{type:'id',name:this.getIdentifier()};}},getItemList:function(callback,closeChar){const items=[];let closed=false;this.getWS();if(this._source[this._index]===closeChar){++this._index;closed=true;}
|
||||
while(!closed){items.push(callback.call(this));this.getWS();let ch=this._source.charAt(this._index);switch(ch){case',':++this._index;this.getWS();break;case closeChar:++this._index;closed=true;break;default:throw this.error('Expected "," or "'+closeChar+'"');}}
|
||||
return items;},getJunkEntry:function(){const pos=this._index;let nextEntity=this._source.indexOf('<',pos);let nextComment=this._source.indexOf('/*',pos);if(nextEntity===-1){nextEntity=this._length;}
|
||||
if(nextComment===-1){nextComment=this._length;}
|
||||
let nextEntry=Math.min(nextEntity,nextComment);this._index=nextEntry;},error:function(message,type='parsererror'){const pos=this._index;let start=this._source.lastIndexOf('<',pos-1);const lastClose=this._source.lastIndexOf('>',pos-1);start=lastClose>start?lastClose+1:start;const context=this._source.slice(start,pos+10);const msg=message+' at pos '+pos+': `'+context+'`';const err=new L10nError(msg);if(this.emit){this.emit(type,err);}
|
||||
return err;}};function walkEntry(entry,fn){if(typeof entry==='string'){return fn(entry);}
|
||||
const newEntry=Object.create(null);if(entry.value){newEntry.value=walkValue(entry.value,fn);}
|
||||
if(entry.index){newEntry.index=entry.index;}
|
||||
if(entry.attrs){newEntry.attrs=Object.create(null);for(let key in entry.attrs){newEntry.attrs[key]=walkEntry(entry.attrs[key],fn);}}
|
||||
return newEntry;}
|
||||
function walkValue(value,fn){if(typeof value==='string'){return fn(value);}
|
||||
if(value.type){return value;}
|
||||
const newValue=Array.isArray(value)?[]:Object.create(null);const keys=Object.keys(value);for(let i=0,key;key=keys[i];i++){newValue[key]=walkValue(value[key],fn);}
|
||||
return newValue;}
|
||||
function createGetter(id,name){let _pseudo=null;return function getPseudo(){if(_pseudo){return _pseudo;}
|
||||
const reAlphas=/[a-zA-Z]/g;const reVowels=/[aeiouAEIOU]/g;const reWords=/[^\W0-9_]+/g;const reExcluded=/(%[EO]?\w|\{\s*.+?\s*\}|&[#\w]+;|<\s*.+?\s*>)/;const charMaps={'fr-x-psaccent':'ȦƁƇḒḖƑƓĦĪĴĶĿḾȠǾƤɊŘŞŦŬṼẆẊẎẐ[\\]^_`ȧƀƈḓḗƒɠħīĵķŀḿƞǿƥɋřşŧŭṽẇẋẏẑ','ar-x-psbidi':'\u2200ԐↃpƎɟפHIſӼ\u02E5WNOԀÒᴚS\u22A5\u2229ɅMXʎZ[\\]ᵥ_,ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz'};const mods={'fr-x-psaccent':val=>val.replace(reVowels,match=>match+match.toLowerCase()),'ar-x-psbidi':val=>val.replace(reWords,match=>'\u202E'+match+'\u202C')};const replaceChars=(map,val)=>val.replace(reAlphas,match=>map.charAt(match.charCodeAt(0)-65));const transform=val=>replaceChars(charMaps[id],mods[id](val));const apply=(fn,val)=>{if(!val){return val;}
|
||||
const parts=val.split(reExcluded);const modified=parts.map(function(part){if(reExcluded.test(part)){return part;}
|
||||
return fn(part);});return modified.join('');};return _pseudo={name:transform(name),process:str=>apply(transform,str)};};}
|
||||
const pseudo=Object.defineProperties(Object.create(null),{'fr-x-psaccent':{enumerable:true,get:createGetter('fr-x-psaccent','Runtime Accented')},'ar-x-psbidi':{enumerable:true,get:createGetter('ar-x-psbidi','Runtime Bidi')}});let Env=function(){function Env(fetchResource){_classCallCheck(this,Env);this.fetchResource=fetchResource;this.resCache=new Map();this.resRefs=new Map();this.numberFormatters=null;this.parsers={properties:PropertiesParser,l20n:L20nParser};const listeners={};this.emit=emit.bind(this,listeners);this.addEventListener=addEventListener.bind(this,listeners);this.removeEventListener=removeEventListener.bind(this,listeners);}
|
||||
_createClass(Env,[{key:'createContext',value:function createContext(langs,resIds){const ctx=new Context(this,langs,resIds);resIds.forEach(resId=>{const usedBy=this.resRefs.get(resId)||0;this.resRefs.set(resId,usedBy+1);});return ctx;}},{key:'destroyContext',value:function destroyContext(ctx){ctx.resIds.forEach(resId=>{const usedBy=this.resRefs.get(resId)||0;if(usedBy>1){return this.resRefs.set(resId,usedBy-1);}
|
||||
this.resRefs.delete(resId);this.resCache.forEach((val,key)=>key.startsWith(resId)?this.resCache.delete(key):null);});}},{key:'_parse',value:function _parse(syntax,lang,data){const parser=this.parsers[syntax];if(!parser){return data;}
|
||||
const emit=(type,err)=>this.emit(type,amendError(lang,err));return parser.parse.call(parser,emit,data);}},{key:'_create',value:function _create(lang,entries){if(lang.src!=='pseudo'){return entries;}
|
||||
const pseudoentries=Object.create(null);for(let key in entries){pseudoentries[key]=walkEntry(entries[key],pseudo[lang.code].process);}
|
||||
return pseudoentries;}},{key:'_getResource',value:function _getResource(lang,res){const cache=this.resCache;const id=res+lang.code+lang.src;if(cache.has(id)){return cache.get(id);}
|
||||
const syntax=res.substr(res.lastIndexOf('.')+1);const saveEntries=data=>{const entries=this._parse(syntax,lang,data);cache.set(id,this._create(lang,entries));};const recover=err=>{err.lang=lang;this.emit('fetcherror',err);cache.set(id,err);};const langToFetch=lang.src==='pseudo'?{code:'en-US',src:'app',ver:lang.ver}:lang;const resource=this.fetchResource(res,langToFetch).then(saveEntries,recover);cache.set(id,resource);return resource;}}]);return Env;}();function amendError(lang,err){err.lang=lang;return err;}
|
||||
function prioritizeLocales(def,availableLangs,requested){let supportedLocale;for(let i=0;i<requested.length;i++){const locale=requested[i];if(availableLangs.indexOf(locale)!==-1){supportedLocale=locale;break;}}
|
||||
if(!supportedLocale||supportedLocale===def){return[def];}
|
||||
return[supportedLocale,def];}
|
||||
function negotiateLanguages({appVersion,defaultLang,availableLangs},additionalLangs,prevLangs,requestedLangs){const allAvailableLangs=Object.keys(availableLangs).concat(Object.keys(additionalLangs)).concat(Object.keys(pseudo));const newLangs=prioritizeLocales(defaultLang,allAvailableLangs,requestedLangs);const langs=newLangs.map(code=>({code:code,src:getLangSource(appVersion,availableLangs,additionalLangs,code),ver:appVersion}));return{langs,haveChanged:!arrEqual(prevLangs,newLangs)};}
|
||||
function arrEqual(arr1,arr2){return arr1.length===arr2.length&&arr1.every((elem,i)=>elem===arr2[i]);}
|
||||
function getMatchingLangpack(appVersion,langpacks){for(let i=0,langpack;langpack=langpacks[i];i++){if(langpack.target===appVersion){return langpack;}}
|
||||
return null;}
|
||||
function getLangSource(appVersion,availableLangs,additionalLangs,code){if(additionalLangs&&additionalLangs[code]){const lp=getMatchingLangpack(appVersion,additionalLangs[code]);if(lp&&(!(code in availableLangs)||parseInt(lp.revision)>availableLangs[code])){return'extra';}}
|
||||
if(code in pseudo&&!(code in availableLangs)){return'pseudo';}
|
||||
return'app';}
|
||||
let Remote=function(){function Remote(fetchResource,broadcast){_classCallCheck(this,Remote);this.broadcast=broadcast;this.env=new Env(fetchResource);this.ctxs=new Map();}
|
||||
_createClass(Remote,[{key:'registerView',value:function registerView(view,resources,meta,additionalLangs,requestedLangs){const{langs}=negotiateLanguages(meta,additionalLangs,[],requestedLangs);this.ctxs.set(view,this.env.createContext(langs,resources));return langs;}},{key:'unregisterView',value:function unregisterView(view){this.ctxs.delete(view);return true;}},{key:'formatEntities',value:function formatEntities(view,keys){return this.ctxs.get(view).formatEntities(...keys);}},{key:'formatValues',value:function formatValues(view,keys){return this.ctxs.get(view).formatValues(...keys);}},{key:'changeLanguages',value:function changeLanguages(view,meta,additionalLangs,requestedLangs){const oldCtx=this.ctxs.get(view);const prevLangs=oldCtx.langs;const newLangs=negotiateLanguages(meta,additionalLangs,prevLangs,requestedLangs);this.ctxs.set(view,this.env.createContext(newLangs.langs,oldCtx.resIds));return newLangs;}},{key:'requestLanguages',value:function requestLanguages(requestedLangs){this.broadcast('languageschangerequest',requestedLangs);}},{key:'getName',value:function getName(code){return pseudo[code].name;}},{key:'processString',value:function processString(code,str){return pseudo[code].process(str);}}]);return Remote;}();const observerConfig={attributes:true,characterData:false,childList:true,subtree:true,attributeFilter:['data-l10n-id','data-l10n-args']};const observers=new WeakMap();function initMutationObserver(view){observers.set(view,{roots:new Set(),observer:new MutationObserver(mutations=>translateMutations(view,mutations))});}
|
||||
function translateRoots(view){return Promise.all([...observers.get(view).roots].map(root=>_translateFragment(view,root)));}
|
||||
function observe(view,root){const obs=observers.get(view);if(obs){obs.roots.add(root);obs.observer.observe(root,observerConfig);}}
|
||||
function disconnect(view,root,allRoots){const obs=observers.get(view);if(obs){obs.observer.disconnect();if(allRoots){return;}
|
||||
obs.roots.delete(root);obs.roots.forEach(other=>obs.observer.observe(other,observerConfig));}}
|
||||
function reconnect(view){const obs=observers.get(view);if(obs){obs.roots.forEach(root=>obs.observer.observe(root,observerConfig));}}
|
||||
const reOverlay=/<|&#?\w+;/;const allowed={elements:['a','em','strong','small','s','cite','q','dfn','abbr','data','time','code','var','samp','kbd','sub','sup','i','b','u','mark','ruby','rt','rp','bdi','bdo','span','br','wbr'],attributes:{global:['title','aria-label','aria-valuetext','aria-moz-hint'],a:['download'],area:['download','alt'],input:['alt','placeholder'],menuitem:['label'],menu:['label'],optgroup:['label'],option:['label'],track:['label'],img:['alt'],textarea:['placeholder'],th:['abbr']}};function overlayElement(element,translation){const value=translation.value;if(typeof value==='string'){if(!reOverlay.test(value)){element.textContent=value;}else{const tmpl=element.ownerDocument.createElement('template');tmpl.innerHTML=value;overlay(element,tmpl.content);}}
|
||||
for(let key in translation.attrs){const attrName=camelCaseToDashed(key);if(isAttrAllowed({name:attrName},element)){element.setAttribute(attrName,translation.attrs[key]);}}}
|
||||
function overlay(sourceElement,translationElement){const result=translationElement.ownerDocument.createDocumentFragment();let k,attr;let childElement;while(childElement=translationElement.childNodes[0]){translationElement.removeChild(childElement);if(childElement.nodeType===childElement.TEXT_NODE){result.appendChild(childElement);continue;}
|
||||
const index=getIndexOfType(childElement);const sourceChild=getNthElementOfType(sourceElement,childElement,index);if(sourceChild){overlay(sourceChild,childElement);result.appendChild(sourceChild);continue;}
|
||||
if(isElementAllowed(childElement)){const sanitizedChild=childElement.ownerDocument.createElement(childElement.nodeName);overlay(sanitizedChild,childElement);result.appendChild(sanitizedChild);continue;}
|
||||
result.appendChild(translationElement.ownerDocument.createTextNode(childElement.textContent));}
|
||||
sourceElement.textContent='';sourceElement.appendChild(result);if(translationElement.attributes){for(k=0,attr;attr=translationElement.attributes[k];k++){if(isAttrAllowed(attr,sourceElement)){sourceElement.setAttribute(attr.name,attr.value);}}}}
|
||||
function isElementAllowed(element){return allowed.elements.indexOf(element.tagName.toLowerCase())!==-1;}
|
||||
function isAttrAllowed(attr,element){const attrName=attr.name.toLowerCase();const tagName=element.tagName.toLowerCase();if(allowed.attributes.global.indexOf(attrName)!==-1){return true;}
|
||||
if(!allowed.attributes[tagName]){return false;}
|
||||
if(allowed.attributes[tagName].indexOf(attrName)!==-1){return true;}
|
||||
if(tagName==='input'&&attrName==='value'){const type=element.type.toLowerCase();if(type==='submit'||type==='button'||type==='reset'){return true;}}
|
||||
return false;}
|
||||
function getNthElementOfType(context,element,index){let nthOfType=0;for(let i=0,child;child=context.children[i];i++){if(child.nodeType===child.ELEMENT_NODE&&child.tagName===element.tagName){if(nthOfType===index){return child;}
|
||||
nthOfType++;}}
|
||||
return null;}
|
||||
function getIndexOfType(element){let index=0;let child;while(child=element.previousElementSibling){if(child.tagName===element.tagName){index++;}}
|
||||
return index;}
|
||||
function camelCaseToDashed(string){if(string==='ariaValueText'){return'aria-valuetext';}
|
||||
return string.replace(/[A-Z]/g,function(match){return'-'+match.toLowerCase();}).replace(/^-/,'');}
|
||||
const reHtml=/[&<>]/g;const htmlEntities={'&':'&','<':'<','>':'>'};function setAttributes(element,id,args){element.setAttribute('data-l10n-id',id);if(args){element.setAttribute('data-l10n-args',JSON.stringify(args));}}
|
||||
function getAttributes(element){return{id:element.getAttribute('data-l10n-id'),args:JSON.parse(element.getAttribute('data-l10n-args'))};}
|
||||
function getTranslatables(element){const nodes=Array.from(element.querySelectorAll('[data-l10n-id]'));if(typeof element.hasAttribute==='function'&&element.hasAttribute('data-l10n-id')){nodes.push(element);}
|
||||
return nodes;}
|
||||
function translateMutations(view,mutations){const targets=new Set();for(let mutation of mutations){switch(mutation.type){case'attributes':targets.add(mutation.target);break;case'childList':for(let addedNode of mutation.addedNodes){if(addedNode.nodeType===addedNode.ELEMENT_NODE){if(addedNode.childElementCount){getTranslatables(addedNode).forEach(targets.add.bind(targets));}else{if(addedNode.hasAttribute('data-l10n-id')){targets.add(addedNode);}}}}
|
||||
break;}}
|
||||
if(targets.size===0){return;}
|
||||
translateElements(view,Array.from(targets));}
|
||||
function _translateFragment(view,frag){return translateElements(view,getTranslatables(frag));}
|
||||
function getElementsTranslation(view,elems){const keys=elems.map(elem=>{const id=elem.getAttribute('data-l10n-id');const args=elem.getAttribute('data-l10n-args');return args?[id,JSON.parse(args.replace(reHtml,match=>htmlEntities[match]))]:id;});return view.formatEntities(...keys);}
|
||||
function translateElements(view,elements){return getElementsTranslation(view,elements).then(translations=>applyTranslations(view,elements,translations));}
|
||||
function applyTranslations(view,elems,translations){disconnect(view,null,true);for(let i=0;i<elems.length;i++){overlayElement(elems[i],translations[i]);}
|
||||
reconnect(view);}
|
||||
if(typeof NodeList==='function'&&!NodeList.prototype[Symbol.iterator]){NodeList.prototype[Symbol.iterator]=Array.prototype[Symbol.iterator];}
|
||||
function documentReady(){if(document.readyState!=='loading'){return Promise.resolve();}
|
||||
return new Promise(resolve=>{document.addEventListener('readystatechange',function onrsc(){document.removeEventListener('readystatechange',onrsc);resolve();});});}
|
||||
function getDirection(code){const tag=code.split('-')[0];return['ar','he','fa','ps','ur'].indexOf(tag)>=0?'rtl':'ltr';}
|
||||
if(navigator.languages===undefined){navigator.languages=[navigator.language];}
|
||||
function getResourceLinks(head){return Array.prototype.map.call(head.querySelectorAll('link[rel="localization"]'),el=>el.getAttribute('href'));}
|
||||
function getMeta(head){let availableLangs=Object.create(null);let defaultLang=null;let appVersion=null;const metas=Array.from(head.querySelectorAll('meta[name="availableLanguages"],'+'meta[name="defaultLanguage"],'+'meta[name="appVersion"]'));for(let meta of metas){const name=meta.getAttribute('name');const content=meta.getAttribute('content').trim();switch(name){case'availableLanguages':availableLangs=getLangRevisionMap(availableLangs,content);break;case'defaultLanguage':const[lang,rev]=getLangRevisionTuple(content);defaultLang=lang;if(!(lang in availableLangs)){availableLangs[lang]=rev;}
|
||||
break;case'appVersion':appVersion=content;}}
|
||||
return{defaultLang,availableLangs,appVersion};}
|
||||
function getLangRevisionMap(seq,str){return str.split(',').reduce((seq,cur)=>{const[lang,rev]=getLangRevisionTuple(cur);seq[lang]=rev;return seq;},seq);}
|
||||
function getLangRevisionTuple(str){const[lang,rev]=str.trim().split(':');return[lang,parseInt(rev)];}
|
||||
const viewProps=new WeakMap();let View=function(){function View(client,doc){_classCallCheck(this,View);this.pseudo={'fr-x-psaccent':createPseudo(this,'fr-x-psaccent'),'ar-x-psbidi':createPseudo(this,'ar-x-psbidi')};const initialized=documentReady().then(()=>init(this,client));this._interactive=initialized.then(()=>client);this.ready=initialized.then(langs=>translateView(this,langs));initMutationObserver(this);viewProps.set(this,{doc:doc,ready:false});client.on('languageschangerequest',requestedLangs=>this.requestLanguages(requestedLangs));}
|
||||
_createClass(View,[{key:'requestLanguages',value:function requestLanguages(requestedLangs,isGlobal){const method=isGlobal?client=>client.method('requestLanguages',requestedLangs):client=>changeLanguages(this,client,requestedLangs);return this._interactive.then(method);}},{key:'handleEvent',value:function handleEvent(){return this.requestLanguages(navigator.languages);}},{key:'formatEntities',value:function formatEntities(...keys){return this._interactive.then(client=>client.method('formatEntities',client.id,keys));}},{key:'formatValue',value:function formatValue(id,args){return this._interactive.then(client=>client.method('formatValues',client.id,[[id,args]])).then(values=>values[0]);}},{key:'formatValues',value:function formatValues(...keys){return this._interactive.then(client=>client.method('formatValues',client.id,keys));}},{key:'translateFragment',value:function translateFragment(frag){return _translateFragment(this,frag);}},{key:'observeRoot',value:function observeRoot(root){observe(this,root);}},{key:'disconnectRoot',value:function disconnectRoot(root){disconnect(this,root);}}]);return View;}();View.prototype.setAttributes=setAttributes;View.prototype.getAttributes=getAttributes;function createPseudo(view,code){return{getName:()=>view._interactive.then(client=>client.method('getName',code)),processString:str=>view._interactive.then(client=>client.method('processString',code,str))};}
|
||||
function init(view,client){const doc=viewProps.get(view).doc;const resources=getResourceLinks(doc.head);const meta=getMeta(doc.head);view.observeRoot(doc.documentElement);return getAdditionalLanguages().then(additionalLangs=>client.method('registerView',client.id,resources,meta,additionalLangs,navigator.languages));}
|
||||
function changeLanguages(view,client,requestedLangs){const doc=viewProps.get(view).doc;const meta=getMeta(doc.head);return getAdditionalLanguages().then(additionalLangs=>client.method('changeLanguages',client.id,meta,additionalLangs,requestedLangs)).then(({langs,haveChanged})=>haveChanged?translateView(view,langs):undefined);}
|
||||
function getAdditionalLanguages(){if(navigator.mozApps&&navigator.mozApps.getAdditionalLanguages){return navigator.mozApps.getAdditionalLanguages().catch(()=>Object.create(null));}
|
||||
return Promise.resolve(Object.create(null));}
|
||||
function translateView(view,langs){const props=viewProps.get(view);const html=props.doc.documentElement;if(props.ready){return translateRoots(view).then(()=>setAllAndEmit(html,langs));}
|
||||
const translated=langs[0].code===html.getAttribute('lang')?Promise.resolve():translateRoots(view).then(()=>setLangDir(html,langs));return translated.then(()=>{setLangs(html,langs);props.ready=true;});}
|
||||
function setLangs(html,langs){const codes=langs.map(lang=>lang.code);html.setAttribute('langs',codes.join(' '));}
|
||||
function setLangDir(html,langs){const code=langs[0].code;html.setAttribute('lang',code);html.setAttribute('dir',getDirection(code));}
|
||||
function setAllAndEmit(html,langs){setLangDir(html,langs);setLangs(html,langs);html.parentNode.dispatchEvent(new CustomEvent('DOMRetranslated',{bubbles:false,cancelable:false}));}
|
||||
const remote=new Remote(fetchResource,broadcast);const client=new Client(remote);document.l10n=new View(client,document);window.addEventListener('languagechange',document.l10n);document.addEventListener('additionallanguageschange',document.l10n);navigator.mozL10n={setAttributes:document.l10n.setAttributes,getAttributes:document.l10n.getAttributes,formatValue:(...args)=>document.l10n.formatValue(...args),translateFragment:(...args)=>document.l10n.translateFragment(...args),once:cb=>document.l10n.ready.then(cb),ready:cb=>document.l10n.ready.then(()=>{document.addEventListener('DOMRetranslated',cb);cb();})};}());
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
;var LazyLoader=function(){function LazyLoader(){this._loaded={};this._isLoading={};}
|
||||
LazyLoader.prototype={_js:function(file,callback){var script=document.createElement('script');script.src=file;script.async=false;script.addEventListener('load',callback);document.head.appendChild(script);this._isLoading[file]=script;},_css:function(file,callback){var style=document.createElement('link');style.type='text/css';style.rel='stylesheet';style.href=file;document.head.appendChild(style);callback();},_html:function(domNode,callback){if(domNode.getAttribute('is')){this.load(['/shared/js/html_imports.js'],function(){HtmlImports.populate(callback);}.bind(this));return;}
|
||||
for(var i=0;i<domNode.childNodes.length;i++){if(domNode.childNodes[i].nodeType==document.COMMENT_NODE){domNode.innerHTML=domNode.childNodes[i].nodeValue;break;}}
|
||||
window.dispatchEvent(new CustomEvent('lazyload',{detail:domNode}));callback();},getJSON:function(file,mozSystem){return new Promise(function(resolve,reject){var xhr;if(mozSystem){xhr=new XMLHttpRequest({mozSystem:true});}else{xhr=new XMLHttpRequest();}
|
||||
xhr.open('GET',file,true);xhr.responseType='json';xhr.onerror=function(error){reject(error);};xhr.onload=function(){if(xhr.response!==null){resolve(xhr.response);}else{reject(new Error('No valid JSON object was found ('+xhr.status+' '+xhr.statusText+')'));}};xhr.send();});},load:function(files,callback){var deferred={};deferred.promise=new Promise(resolve=>{deferred.resolve=resolve;});if(!Array.isArray(files)){files=[files];}
|
||||
var loadsRemaining=files.length,self=this;function perFileCallback(file){if(self._isLoading[file]){delete self._isLoading[file];}
|
||||
self._loaded[file]=true;if(--loadsRemaining===0){deferred.resolve();if(callback){callback();}}}
|
||||
for(var i=0;i<files.length;i++){var file=files[i];if(this._loaded[file.id||file]){perFileCallback(file);}else if(this._isLoading[file]){this._isLoading[file].addEventListener('load',perFileCallback.bind(null,file));}else{var method,idx;if(typeof file==='string'){method=file.match(/\.([^.]+)$/)[1];idx=file;}else{method='html';idx=file.id;}
|
||||
this['_'+method](file,perFileCallback.bind(null,idx));}}
|
||||
return deferred.promise;}};return new LazyLoader();}();
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
function cropResizeRotate(blob, cropRegion, outputSize, outputType, metadata, callback) {
|
||||
'use strict';
|
||||
const JPEG = 'image/jpeg';
|
||||
const PNG = 'image/png';
|
||||
const EXIF = 'image/jpeg+exif';
|
||||
switch (arguments.length) {
|
||||
case 2:
|
||||
callback = cropRegion;
|
||||
cropRegion = outputSize = outputType = metadata = null;
|
||||
break;
|
||||
case 3:
|
||||
callback = outputSize;
|
||||
outputSize = outputType = metadata = null;
|
||||
break;
|
||||
case 4:
|
||||
callback = outputType;
|
||||
outputType = metadata = null;
|
||||
break;
|
||||
case 5:
|
||||
callback = metadata;
|
||||
metadata = null;
|
||||
break;
|
||||
case 6:
|
||||
break;
|
||||
default:
|
||||
throw new Error('wrong number of arguments: ' + arguments.length);
|
||||
}
|
||||
if (cropRegion) {
|
||||
cropRegion = {
|
||||
left: cropRegion.left,
|
||||
top: cropRegion.top,
|
||||
width: cropRegion.width,
|
||||
height: cropRegion.height
|
||||
};
|
||||
}
|
||||
if (outputSize && typeof outputSize === 'object') {
|
||||
outputSize = {
|
||||
width: outputSize.width,
|
||||
height: outputSize.height
|
||||
};
|
||||
}
|
||||
if (metadata) {
|
||||
gotSize(metadata);
|
||||
} else {
|
||||
getImageSize(blob, gotSize, function (msg) {
|
||||
callback(msg);
|
||||
});
|
||||
}
|
||||
function gotSize(metadata) {
|
||||
var rawImageWidth = metadata.width;
|
||||
var rawImageHeight = metadata.height;
|
||||
var fullsize = rawImageWidth * rawImageHeight;
|
||||
var rotation = metadata.rotation || 0;
|
||||
var mirrored = metadata.mirrored || false;
|
||||
var rotatedImageWidth, rotatedImageHeight;
|
||||
if (rotation === 0 || rotation === 180) {
|
||||
rotatedImageWidth = rawImageWidth;
|
||||
rotatedImageHeight = rawImageHeight;
|
||||
} else {
|
||||
rotatedImageWidth = rawImageHeight;
|
||||
rotatedImageHeight = rawImageWidth;
|
||||
}
|
||||
if (!cropRegion) {
|
||||
cropRegion = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: rotatedImageWidth,
|
||||
height: rotatedImageHeight
|
||||
};
|
||||
} else {
|
||||
if (cropRegion.left < 0 || cropRegion.top < 0 || cropRegion.left + cropRegion.width > rotatedImageWidth || cropRegion.top + cropRegion.height > rotatedImageHeight) {
|
||||
callback('crop region does not fit inside image');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (outputSize === null || outputSize === undefined) {
|
||||
outputSize = {
|
||||
width: cropRegion.width,
|
||||
height: cropRegion.height
|
||||
};
|
||||
} else if (typeof outputSize === 'number') {
|
||||
if (outputSize <= 0) {
|
||||
callback('outputSize must be positive');
|
||||
return;
|
||||
}
|
||||
if (fullsize < outputSize) {
|
||||
outputSize = {
|
||||
width: cropRegion.width,
|
||||
height: cropRegion.height
|
||||
};
|
||||
} else {
|
||||
var ds = Downsample.areaAtLeast(outputSize / fullsize);
|
||||
outputSize = {
|
||||
width: ds.scale(cropRegion.width),
|
||||
height: ds.scale(cropRegion.height)
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!(outputSize.width > 0 && outputSize.height > 0)) {
|
||||
callback('outputSize width and height must be positive');
|
||||
return;
|
||||
}
|
||||
if (outputSize.width > cropRegion.width) {
|
||||
outputSize.width = cropRegion.width;
|
||||
}
|
||||
if (outputSize.height > cropRegion.height) {
|
||||
outputSize.height = cropRegion.height;
|
||||
}
|
||||
var scaleX = outputSize.width / cropRegion.width;
|
||||
var scaleY = outputSize.height / cropRegion.height;
|
||||
if (scaleY > scaleX) {
|
||||
var oldCropWidth = cropRegion.width;
|
||||
cropRegion.width = Math.round(outputSize.width / scaleY);
|
||||
cropRegion.left += oldCropWidth - cropRegion.width >> 1;
|
||||
} else if (scaleX > scaleY) {
|
||||
var oldCropHeight = cropRegion.height;
|
||||
cropRegion.height = Math.round(outputSize.height / scaleX);
|
||||
cropRegion.top += oldCropHeight - cropRegion.height >> 1;
|
||||
}
|
||||
if (outputType && outputType !== JPEG && outputType !== PNG && outputType !== EXIF) {
|
||||
callback('unsupported outputType: ' + outputType);
|
||||
return;
|
||||
}
|
||||
var preserveExif = false;
|
||||
if (blob.type === JPEG && outputType === EXIF) {
|
||||
preserveExif = true;
|
||||
outputType = JPEG;
|
||||
}
|
||||
if (rotation === 0 && !mirrored && (!outputType || blob.type === outputType) && outputSize.width === rawImageWidth && outputSize.height == rawImageHeight) {
|
||||
callback(null, blob);
|
||||
return;
|
||||
}
|
||||
var inputCropRegion;
|
||||
switch (rotation) {
|
||||
case 180:
|
||||
inputCropRegion = {
|
||||
left: rawImageWidth - cropRegion.left - cropRegion.width,
|
||||
top: rawImageHeight - cropRegion.top - cropRegion.height,
|
||||
width: cropRegion.width,
|
||||
height: cropRegion.height
|
||||
};
|
||||
break;
|
||||
case 90:
|
||||
inputCropRegion = {
|
||||
left: cropRegion.top,
|
||||
top: rawImageHeight - cropRegion.left - cropRegion.width,
|
||||
width: cropRegion.height,
|
||||
height: cropRegion.width
|
||||
};
|
||||
break;
|
||||
case 270:
|
||||
inputCropRegion = {
|
||||
left: rawImageWidth - cropRegion.top - cropRegion.height,
|
||||
top: cropRegion.left,
|
||||
width: cropRegion.height,
|
||||
height: cropRegion.width
|
||||
};
|
||||
break;
|
||||
default:
|
||||
inputCropRegion = {
|
||||
left: cropRegion.left,
|
||||
top: cropRegion.top,
|
||||
width: cropRegion.width,
|
||||
height: cropRegion.height
|
||||
};
|
||||
break;
|
||||
}
|
||||
if (mirrored) {
|
||||
if (rotation === 90 || rotation === 270) {
|
||||
inputCropRegion.top = rawImageHeight - inputCropRegion.top - inputCropRegion.height;
|
||||
} else {
|
||||
inputCropRegion.left = rawImageWidth - inputCropRegion.left - inputCropRegion.width;
|
||||
}
|
||||
}
|
||||
var baseURL = URL.createObjectURL(blob);
|
||||
var sampledsize;
|
||||
var downsample;
|
||||
if (blob.type === JPEG) {
|
||||
downsample = Downsample.sizeNoMoreThan(outputSize.width / cropRegion.width);
|
||||
sampledsize = downsample.scale(rawImageWidth) * downsample.scale(rawImageHeight);
|
||||
} else {
|
||||
downsample = Downsample.NONE;
|
||||
sampledsize = fullsize;
|
||||
}
|
||||
var url;
|
||||
var resizedWithMediaFragment = false;
|
||||
if (sampledsize < fullsize) {
|
||||
url = baseURL + downsample;
|
||||
resizedWithMediaFragment = true;
|
||||
} else {
|
||||
url = baseURL;
|
||||
}
|
||||
var offscreenImage = new Image();
|
||||
offscreenImage.src = url;
|
||||
offscreenImage.onerror = function () {
|
||||
callback('error decoding image: ' + url);
|
||||
};
|
||||
offscreenImage.onload = gotImage;
|
||||
function gotImage() {
|
||||
if (resizedWithMediaFragment) {
|
||||
if (offscreenImage.width < rawImageWidth || offscreenImage.height < rawImageHeight) {
|
||||
var sampleSizeX = rawImageWidth / offscreenImage.width;
|
||||
var sampleSizeY = rawImageHeight / offscreenImage.height;
|
||||
inputCropRegion.left = Math.round(inputCropRegion.left / sampleSizeX);
|
||||
inputCropRegion.top = Math.round(inputCropRegion.top / sampleSizeY);
|
||||
inputCropRegion.width = Math.round(inputCropRegion.width / sampleSizeX);
|
||||
inputCropRegion.height = Math.round(inputCropRegion.height / sampleSizeY);
|
||||
}
|
||||
}
|
||||
var canvas = document.createElement('canvas');
|
||||
var destWidth = canvas.width = outputSize.width;
|
||||
var destHeight = canvas.height = outputSize.height;
|
||||
var context = canvas.getContext('2d', { willReadFrequently: true });
|
||||
if (rotation || mirrored) {
|
||||
context.translate(canvas.width / 2, canvas.height / 2);
|
||||
if (mirrored) {
|
||||
context.scale(-1, 1);
|
||||
}
|
||||
switch (rotation) {
|
||||
case 90:
|
||||
context.rotate(Math.PI / 2);
|
||||
destWidth = canvas.height;
|
||||
destHeight = canvas.width;
|
||||
break;
|
||||
case 180:
|
||||
context.rotate(Math.PI);
|
||||
break;
|
||||
case 270:
|
||||
context.rotate(-Math.PI / 2);
|
||||
destWidth = canvas.height;
|
||||
destHeight = canvas.width;
|
||||
break;
|
||||
}
|
||||
if (rotation === 90 || rotation === 270) {
|
||||
context.translate(-canvas.height / 2, -canvas.width / 2);
|
||||
} else {
|
||||
context.translate(-canvas.width / 2, -canvas.height / 2);
|
||||
}
|
||||
}
|
||||
try {
|
||||
context.drawImage(offscreenImage, inputCropRegion.left, inputCropRegion.top, inputCropRegion.width, inputCropRegion.height, 0, 0, destWidth, destHeight);
|
||||
} catch (e) {
|
||||
callback('Failed to decode image in cropResizeRotate; ' + 'image may be corrupt or too large: ' + e);
|
||||
return;
|
||||
} finally {
|
||||
offscreenImage.onload = offscreenImage.onerror = null; offscreenImage.removeAttribute('src');
|
||||
URL.revokeObjectURL(baseURL);
|
||||
}
|
||||
var inputBlob = blob;
|
||||
var postCallback = preserveExif ? copyMetadata : gotEncodedBlob;
|
||||
canvas.toBlob(postCallback, outputType || JPEG);
|
||||
function copyMetadata(outputBlob) {
|
||||
try {
|
||||
LazyLoader.load(['shared/js/media/jpeg-exif.js'], () => {
|
||||
JPEGParser.readExifMetaData(inputBlob, (error, metaData) => {
|
||||
if (error || !metaData) {
|
||||
gotEncodedBlob(outputBlob);
|
||||
return;
|
||||
}
|
||||
metaData.Orientation = 1;
|
||||
if (metaData.PixelXDimension) {
|
||||
metaData.PixelXDimension = outputSize.width;
|
||||
}
|
||||
if (metaData.PixelYDimension) {
|
||||
metaData.PixelYDimension = outputSize.height;
|
||||
}
|
||||
JPEGParser.writeExifMetaData(outputBlob, metaData, (error, modifiedBlob) => {
|
||||
if (error) {
|
||||
console.error('Error' + error);
|
||||
}
|
||||
gotEncodedBlob(modifiedBlob);
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e.stack);
|
||||
}
|
||||
}
|
||||
function gotEncodedBlob(blob) {
|
||||
canvas.width = canvas.height = 0;
|
||||
canvas = context = null;
|
||||
callback(null, blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
(function (exports) {
|
||||
'use strict';
|
||||
function round(x) {
|
||||
return Math.round(x * 100) / 100;
|
||||
}
|
||||
function MozSampleSize(n, scale) {
|
||||
return Object.freeze({
|
||||
dimensionScale: round(scale),
|
||||
areaScale: round(scale * scale),
|
||||
toString: function () {
|
||||
return '#-moz-samplesize=' + n;
|
||||
},
|
||||
scale: function (x) {
|
||||
return Math.ceil(x * scale);
|
||||
}
|
||||
});
|
||||
}
|
||||
var NONE = Object.freeze({
|
||||
dimensionScale: 1,
|
||||
areaScale: 1,
|
||||
toString: function () {
|
||||
return '';
|
||||
},
|
||||
scale: function (x) {
|
||||
return x;
|
||||
}
|
||||
});
|
||||
var fragments = [
|
||||
NONE,
|
||||
MozSampleSize(2, 1 / 2),
|
||||
MozSampleSize(3, 3 / 8),
|
||||
MozSampleSize(4, 1 / 4),
|
||||
MozSampleSize(8, 1 / 8)
|
||||
];
|
||||
function sizeAtLeast(scale) {
|
||||
scale = round(scale);
|
||||
for (var i = 0; i < fragments.length; i++) {
|
||||
var f = fragments[i];
|
||||
if (f.dimensionScale <= scale) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return fragments[fragments.length - 1];
|
||||
}
|
||||
function sizeNoMoreThan(scale) {
|
||||
scale = round(scale);
|
||||
for (var i = fragments.length - 1; i >= 0; i--) {
|
||||
var f = fragments[i];
|
||||
if (f.dimensionScale >= scale) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
function areaAtLeast(scale) {
|
||||
scale = round(scale);
|
||||
for (var i = 0; i < fragments.length; i++) {
|
||||
var f = fragments[i];
|
||||
if (f.areaScale <= scale) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return fragments[fragments.length - 1];
|
||||
}
|
||||
function areaNoMoreThan(scale) {
|
||||
scale = round(scale);
|
||||
for (var i = fragments.length - 1; i >= 0; i--) {
|
||||
var f = fragments[i];
|
||||
if (f.areaScale >= scale) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
exports.Downsample = {
|
||||
sizeAtLeast: sizeAtLeast,
|
||||
sizeNoMoreThan: sizeNoMoreThan,
|
||||
areaAtLeast: areaAtLeast,
|
||||
areaNoMoreThan: areaNoMoreThan,
|
||||
NONE: NONE,
|
||||
MAX_SIZE_REDUCTION: 1 / fragments[fragments.length - 1].dimensionScale,
|
||||
MAX_AREA_REDUCTION: 1 / fragments[fragments.length - 1].areaScale
|
||||
};
|
||||
}(window));
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
function getVideoRotation(blob, rotationCallback) {
|
||||
function MP4Parser(blob, handlers) {
|
||||
BlobView.get(blob, 0, Math.min(1024, blob.size), function (data, error) {
|
||||
if (data.byteLength <= 8 || data.getBinaryText(4, 4) !== 'ftyp') {
|
||||
handlers.errorHandler('not an MP4 file');
|
||||
return;
|
||||
}
|
||||
parseAtom(data);
|
||||
});
|
||||
function parseAtom(data) {
|
||||
var offset = data.sliceOffset + data.viewOffset;
|
||||
var size = data.readUnsignedInt();
|
||||
var type = data.readBinaryText(4);
|
||||
var contentOffset = 8;
|
||||
if (size === 0) {
|
||||
size = blob.size - offset;
|
||||
} else if (size === 1) {
|
||||
size = data.readUnsignedInt() * 4294967296 + data.readUnsignedInt();
|
||||
contentOffset = 16;
|
||||
}
|
||||
var handler = handlers[type] || handlers.defaultHandler;
|
||||
if (typeof handler === 'function') {
|
||||
data.getMore(data.sliceOffset + data.viewOffset, size, function (atom) {
|
||||
var rv = handler(atom);
|
||||
if (rv !== 'done') {
|
||||
parseAtomAt(data, offset + size);
|
||||
}
|
||||
});
|
||||
} else if (handler === 'children') {
|
||||
var skip = type === 'meta' ? 4 : 0;
|
||||
parseAtomAt(data, offset + contentOffset + skip);
|
||||
} else if (handler === 'skip' || !handler) {
|
||||
parseAtomAt(data, offset + size);
|
||||
} else if (handler === 'done') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
function parseAtomAt(data, offset) {
|
||||
if (offset >= blob.size) {
|
||||
if (handlers.eofHandler) {
|
||||
handlers.eofHandler();
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
data.getMore(offset, 16, parseAtom);
|
||||
}
|
||||
}
|
||||
}
|
||||
MP4Parser(blob, {
|
||||
errorHandler: function (msg) {
|
||||
rotationCallback(msg);
|
||||
},
|
||||
eofHandler: function () {
|
||||
rotationCallback(null);
|
||||
},
|
||||
defaultHandler: 'skip',
|
||||
moov: 'children',
|
||||
trak: 'children',
|
||||
tkhd: function (data) {
|
||||
data.advance(48);
|
||||
var a = data.readUnsignedInt();
|
||||
var b = data.readUnsignedInt();
|
||||
data.advance(4);
|
||||
var c = data.readUnsignedInt();
|
||||
var d = data.readUnsignedInt();
|
||||
if (a === 0 && d === 0) {
|
||||
if (b === 65536 && c === 4294901760) {
|
||||
rotationCallback(90);
|
||||
} else if (b === 4294901760 && c === 65536) {
|
||||
rotationCallback(270);
|
||||
} else {
|
||||
rotationCallback('unexpected rotation matrix');
|
||||
}
|
||||
} else if (b === 0 && c === 0) {
|
||||
if (a === 65536 && d === 65536) {
|
||||
rotationCallback(0);
|
||||
} else if (a === 4294901760 && d === 4294901760) {
|
||||
rotationCallback(180);
|
||||
} else {
|
||||
rotationCallback('unexpected rotation matrix');
|
||||
}
|
||||
} else {
|
||||
rotationCallback('unexpected rotation matrix');
|
||||
}
|
||||
return 'done';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
function getImageSize(blob, callback, error) {
|
||||
'use strict';
|
||||
BlobView.get(blob, 0, Math.min(1024, blob.size), function (data) {
|
||||
if (data.byteLength <= 8) {
|
||||
error('corrupt image file');
|
||||
return;
|
||||
}
|
||||
var magic = data.getBinaryText(0, 8);
|
||||
if (magic.substring(0, 4) === 'GIF8') {
|
||||
try {
|
||||
callback({
|
||||
type: 'gif',
|
||||
width: data.getUint16(6, true),
|
||||
height: data.getUint16(8, true)
|
||||
});
|
||||
} catch (e) {
|
||||
error(e.toString());
|
||||
}
|
||||
} else if (magic.substring(0, 8) === '\x89PNG\r\n\x1A\n') {
|
||||
try {
|
||||
callback({
|
||||
type: 'png',
|
||||
width: data.getUint32(16, false),
|
||||
height: data.getUint32(20, false)
|
||||
});
|
||||
} catch (e) {
|
||||
error(e.toString());
|
||||
}
|
||||
} else if (magic.substring(0, 2) === 'BM' && data.getUint32(2, true) === blob.size) {
|
||||
try {
|
||||
var width, height;
|
||||
if (data.getUint16(14, true) === 12) {
|
||||
width = data.getUint16(18, true);
|
||||
height = data.getUint16(20, true);
|
||||
} else {
|
||||
width = data.getUint32(18, true);
|
||||
height = data.getUint32(22, true);
|
||||
}
|
||||
callback({
|
||||
type: 'bmp',
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
} catch (e) {
|
||||
error(e.toString());
|
||||
}
|
||||
} else if (magic.substring(0, 2) === 'ÿØ') {
|
||||
parseJPEGMetadata(blob, function (metadata) {
|
||||
if (metadata.progressive) {
|
||||
delete metadata.progressive;
|
||||
metadata.type = 'pjpeg';
|
||||
} else {
|
||||
metadata.type = 'jpeg';
|
||||
}
|
||||
callback(metadata);
|
||||
}, error);
|
||||
} else {
|
||||
error('unknown image type');
|
||||
}
|
||||
});
|
||||
}
|
||||
2937
assets/webapps/camera.gaiamobile.org/shared/js/media/jpeg-exif.js
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
function parseJPEGMetadata(file, metadataCallback, metadataError) {
|
||||
var metadata = {};
|
||||
var TIFFHeaderOffset = 0;
|
||||
BlobView.get(file, 0, Math.min(16 * 1024, file.size), function (data) {
|
||||
if (data.byteLength < 2 || data.getUint8(0) !== 255 || data.getUint8(1) !== 216) {
|
||||
metadataError('Not a JPEG file');
|
||||
return;
|
||||
}
|
||||
getSegment(data, 2, segmentHandler);
|
||||
});
|
||||
function getSegment(data, offset, callback) {
|
||||
try {
|
||||
var header = data.getUint8(offset);
|
||||
if (header !== 255) {
|
||||
metadataError('Malformed JPEG file: bad segment header');
|
||||
return;
|
||||
}
|
||||
var type = data.getUint8(offset + 1);
|
||||
var size = data.getUint16(offset + 2) + 2;
|
||||
var start = data.sliceOffset + data.viewOffset + offset;
|
||||
var isLast = start + size >= file.size;
|
||||
var length = isLast ? size : size + 4;
|
||||
data.getMore(start, length, function (data) {
|
||||
callback(type, size, data, isLast);
|
||||
});
|
||||
} catch (e) {
|
||||
metadataError(e.toString() + '\n' + e.stack);
|
||||
}
|
||||
}
|
||||
function segmentHandler(type, size, data, isLastSegment) {
|
||||
try {
|
||||
switch (type) {
|
||||
case 192:
|
||||
case 193:
|
||||
case 194:
|
||||
case 195:
|
||||
metadata.height = data.getUint16(5);
|
||||
metadata.width = data.getUint16(7);
|
||||
if (type === 194) {
|
||||
metadata.progressive = true;
|
||||
}
|
||||
metadataCallback(metadata);
|
||||
break;
|
||||
case 225:
|
||||
parseAPP1(data);
|
||||
default:
|
||||
if (isLastSegment) {
|
||||
metadataError('unexpected end of JPEG file');
|
||||
return;
|
||||
}
|
||||
getSegment(data, size, segmentHandler);
|
||||
}
|
||||
} catch (e) {
|
||||
metadataError(e.toString() + '\n' + e.stack);
|
||||
}
|
||||
}
|
||||
function parseAPP1(data) {
|
||||
if (data.getUint32(4, false) === 1165519206) {
|
||||
var exif = parseEXIFData(data);
|
||||
if (exif.THUMBNAIL && exif.THUMBNAILLENGTH) {
|
||||
var start = data.sliceOffset + data.viewOffset + TIFFHeaderOffset + exif.THUMBNAIL;
|
||||
metadata.preview = {
|
||||
start: start,
|
||||
end: start + exif.THUMBNAILLENGTH
|
||||
};
|
||||
}
|
||||
switch (exif.ORIENTATION) {
|
||||
case 2:
|
||||
metadata.rotation = 0;
|
||||
metadata.mirrored = true;
|
||||
break;
|
||||
case 3:
|
||||
metadata.rotation = 180;
|
||||
metadata.mirrored = false;
|
||||
break;
|
||||
case 4:
|
||||
metadata.rotation = 180;
|
||||
metadata.mirrored = true;
|
||||
break;
|
||||
case 5:
|
||||
metadata.rotation = 90;
|
||||
metadata.mirrored = true;
|
||||
break;
|
||||
case 6:
|
||||
metadata.rotation = 90;
|
||||
metadata.mirrored = false;
|
||||
break;
|
||||
case 7:
|
||||
metadata.rotation = 270;
|
||||
metadata.mirrored = true;
|
||||
break;
|
||||
case 8:
|
||||
metadata.rotation = 270;
|
||||
metadata.mirrored = false;
|
||||
break;
|
||||
default:
|
||||
metadata.rotation = 0;
|
||||
metadata.mirrored = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
function parseEXIFData(data) {
|
||||
var exif = {};
|
||||
var byteorder = data.getUint8(10);
|
||||
if (byteorder === 77) {
|
||||
byteorder = false;
|
||||
} else if (byteorder === 73) {
|
||||
byteorder = true;
|
||||
} else {
|
||||
throw Error('invalid byteorder in EXIF segment');
|
||||
}
|
||||
TIFFHeaderOffset = 10;
|
||||
if (data.getUint16(12, byteorder) !== 42) {
|
||||
throw Error('bad magic number in EXIF segment');
|
||||
}
|
||||
var offset = data.getUint32(14, byteorder);
|
||||
parseIFD(data, offset + TIFFHeaderOffset, byteorder, exif, true);
|
||||
var ifd0entries = data.getUint16(offset + TIFFHeaderOffset, byteorder);
|
||||
var ifd1 = data.getUint32(offset + 12 + 12 * ifd0entries, byteorder);
|
||||
if (ifd1 !== 0) {
|
||||
parseIFD(data, ifd1 + TIFFHeaderOffset, byteorder, exif, true);
|
||||
}
|
||||
return exif;
|
||||
}
|
||||
function parseIFD(data, offset, byteorder, exif, onlyParseOne) {
|
||||
var numentries = data.getUint16(offset, byteorder);
|
||||
for (var i = 0; i < numentries; i++) {
|
||||
parseEntry(data, offset + 2 + 12 * i, byteorder, exif);
|
||||
}
|
||||
if (onlyParseOne) {
|
||||
return;
|
||||
}
|
||||
var next = data.getUint32(offset + 2 + 12 * numentries, byteorder);
|
||||
if (next !== 0 && next < file.size) {
|
||||
parseIFD(data, next + TIFFHeaderOffset, byteorder, exif);
|
||||
}
|
||||
}
|
||||
var typesize = [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8,
|
||||
4,
|
||||
8
|
||||
];
|
||||
var tagnames = {
|
||||
'274': 'ORIENTATION',
|
||||
'513': 'THUMBNAIL',
|
||||
'514': 'THUMBNAILLENGTH'
|
||||
};
|
||||
function parseEntry(data, offset, byteorder, exif) {
|
||||
var tag = data.getUint16(offset, byteorder);
|
||||
var tagname = tagnames[tag];
|
||||
if (!tagname || exif[tagname]) {
|
||||
return;
|
||||
}
|
||||
var type = data.getUint16(offset + 2, byteorder);
|
||||
var count = data.getUint32(offset + 4, byteorder);
|
||||
var total = count * typesize[type];
|
||||
var valueOffset = total <= 4 ? offset + 8 : data.getUint32(offset + 8, byteorder) + TIFFHeaderOffset;
|
||||
exif[tagname] = parseValue(data, valueOffset, type, count, byteorder);
|
||||
}
|
||||
function parseValue(data, offset, type, count, byteorder) {
|
||||
var i;
|
||||
if (type === 2) {
|
||||
var codes = [];
|
||||
for (i = 0; i < count - 1; i++) {
|
||||
codes[i] = data.getUint8(offset + i);
|
||||
}
|
||||
return String.fromCharCode.apply(String, codes);
|
||||
} else {
|
||||
if (count == 1) {
|
||||
return parseOneValue(data, offset, type, byteorder);
|
||||
} else {
|
||||
var values = [];
|
||||
var size = typesize[type];
|
||||
for (i = 0; i < count; i++) {
|
||||
values[i] = parseOneValue(data, offset + size * i, type, byteorder);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
||||
function parseOneValue(data, offset, type, byteorder) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
case 7:
|
||||
return data.getUint8(offset);
|
||||
case 2:
|
||||
return null;
|
||||
case 3:
|
||||
return data.getUint16(offset, byteorder);
|
||||
case 4:
|
||||
return data.getUint32(offset, byteorder);
|
||||
case 5:
|
||||
return data.getUint32(offset, byteorder) / data.getUint32(offset + 4, byteorder);
|
||||
case 6:
|
||||
return data.getInt8(offset);
|
||||
case 8:
|
||||
return data.getInt16(offset, byteorder);
|
||||
case 9:
|
||||
return data.getInt32(offset, byteorder);
|
||||
case 10:
|
||||
return data.getInt32(offset, byteorder) / data.getInt32(offset + 4, byteorder);
|
||||
case 11:
|
||||
return data.getFloat32(offset, byteorder);
|
||||
case 12:
|
||||
return data.getFloat64(offset, byteorder);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,465 @@
|
|||
function MediaFrame(container, includeVideo, maxImageSize) {
|
||||
this.clear();
|
||||
if (typeof container === 'string') {
|
||||
container = document.getElementById(container);
|
||||
}
|
||||
this.container = container;
|
||||
this.maximumImageSize = maxImageSize || 0;
|
||||
this.image = new Image();
|
||||
this.image.className = 'image-view';
|
||||
this.image.style.opacity = 0;
|
||||
this.image.onload = function () {
|
||||
this.style.opacity = 1;
|
||||
};
|
||||
this.image.style.transformOrigin = 'center center';
|
||||
this.image.style.backgroundImage = 'none';
|
||||
this.image.style.backgroundSize = 'contain';
|
||||
this.image.style.backgroundRepeat = 'no-repeat';
|
||||
this.image.style.backgroundColor = '#222';
|
||||
this.container.appendChild(this.image);
|
||||
if (includeVideo !== false) {
|
||||
this.video = new VideoPlayer(container);
|
||||
this.video.hide();
|
||||
}
|
||||
container.classList.add('media-frame-container');
|
||||
MediaFrame.instancesToLocalize.set(container, this);
|
||||
}
|
||||
MediaFrame.instancesToLocalize = new WeakMap();
|
||||
function localize() {
|
||||
for (var container of document.querySelectorAll('.media-frame-container')) {
|
||||
var instance = MediaFrame.instancesToLocalize.get(container);
|
||||
if (instance) {
|
||||
instance.localize();
|
||||
}
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMRetranslated', localize);
|
||||
MediaFrame.computeMaxImageDecodeSize = function (mem) {
|
||||
if (!mem) {
|
||||
return 0;
|
||||
} else if (mem < 256) {
|
||||
return 2 * 1024 * 1024;
|
||||
} else if (mem < 512) {
|
||||
var screensize = screen.width * window.devicePixelRatio * screen.height * window.devicePixelRatio;
|
||||
if (mem < 325 && screensize > 480 * 800) {
|
||||
return 2.5 * 1024 * 1024;
|
||||
}
|
||||
return 3 * 1024 * 1024;
|
||||
} else if (mem < 1024) {
|
||||
return 5 * 1024 * 1024;
|
||||
} else {
|
||||
return mem / 1024 * 10 * 1024 * 1024;
|
||||
}
|
||||
};
|
||||
if (navigator.getFeature) {
|
||||
MediaFrame.pendingPromise = navigator.getFeature('hardware.memory');
|
||||
MediaFrame.pendingPromise.then(function resolve(mem) {
|
||||
delete MediaFrame.pendingPromise;
|
||||
MediaFrame.maxImageDecodeSize = MediaFrame.computeMaxImageDecodeSize(mem);
|
||||
}, function reject(err) {
|
||||
delete MediaFrame.pendingPromise;
|
||||
MediaFrame.maxImageDecodeSize = 0;
|
||||
});
|
||||
}
|
||||
MediaFrame.prototype.displayImage = function displayImage(blob, width, height, preview, rotation, mirrored) {
|
||||
var self = this;
|
||||
if (MediaFrame.pendingPromise) {
|
||||
MediaFrame.pendingPromise.then(function resolve() {
|
||||
self.displayImage(blob, width, height, preview, rotation, mirrored);
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.clear();
|
||||
this.imageblob = blob;
|
||||
this.fullSampleSize = computeFullSampleSize(blob, width, height);
|
||||
this.fullsizeWidth = this.fullSampleSize.scale(width);
|
||||
this.fullsizeHeight = this.fullSampleSize.scale(height);
|
||||
this.tinyPreviewURL = null;
|
||||
this.imageurl = URL.createObjectURL(blob);
|
||||
this.fullImageURL = this.imageurl + this.fullSampleSize;
|
||||
this.rotation = rotation || 0;
|
||||
this.mirrored = mirrored || false;
|
||||
this.displayingImage = true;
|
||||
this.localize();
|
||||
function isValidPreview(preview) {
|
||||
if (!preview) {
|
||||
return false;
|
||||
}
|
||||
if (!preview.width || !preview.height) {
|
||||
return false;
|
||||
}
|
||||
if (!preview.start && !preview.filename) {
|
||||
return false;
|
||||
}
|
||||
if (Math.abs(width / height - preview.width / preview.height) > 0.01) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function isBigEnoughPreview(preview) {
|
||||
if (!preview) {
|
||||
return false;
|
||||
}
|
||||
if (self.minimumPreviewWidth && self.minimumPreviewHeight) {
|
||||
return Math.max(preview.width, preview.height) >= Math.max(self.minimumPreviewWidth, self.minimumPreviewHeight) && Math.min(preview.width, preview.height) >= Math.min(self.minimumPreviewWidth, self.minimumPreviewHeight);
|
||||
}
|
||||
var screenWidth = window.innerWidth * window.devicePixelRatio;
|
||||
var screenHeight = window.innerHeight * window.devicePixelRatio;
|
||||
return (preview.width >= screenWidth || preview.height >= screenHeight) && (preview.width >= screenHeight || preview.height >= screenWidth);
|
||||
}
|
||||
var isValid = isValidPreview(preview);
|
||||
var isBigEnough = isBigEnoughPreview(preview);
|
||||
if (isValid && isBigEnough) {
|
||||
if (preview.start) {
|
||||
gotPreview(blob.slice(preview.start, preview.end, 'image/jpeg'), preview.width, preview.height);
|
||||
} else {
|
||||
var storage = navigator.getDeviceStorage('pictures');
|
||||
var getreq = storage.get(preview.filename);
|
||||
getreq.onsuccess = function () {
|
||||
gotPreview(getreq.result, preview.width, preview.height);
|
||||
};
|
||||
getreq.onerror = function () {
|
||||
noPreview();
|
||||
};
|
||||
}
|
||||
} else if (isValid) {
|
||||
this.tinyPreviewURL = URL.createObjectURL(blob.slice(preview.start, preview.end, 'image/jpeg'));
|
||||
noPreview(this.tinyPreviewURL);
|
||||
} else {
|
||||
noPreview();
|
||||
}
|
||||
function gotPreview(previewblob, previewWidth, previewHeight) {
|
||||
self.previewurl = URL.createObjectURL(previewblob);
|
||||
self.previewImageURL = self.previewurl;
|
||||
self.previewWidth = previewWidth;
|
||||
self.previewHeight = previewHeight;
|
||||
self.displayingPreview = true;
|
||||
self._displayImage(self.previewImageURL, self.previewWidth, self.previewHeight);
|
||||
}
|
||||
function noPreview(bgURL) {
|
||||
self.previewurl = null;
|
||||
var previewSampleSize = computePreviewSampleSize(blob, width, height);
|
||||
if (previewSampleSize !== Downsample.NONE) {
|
||||
self.previewImageURL = self.imageurl + previewSampleSize;
|
||||
self.previewWidth = previewSampleSize.scale(width);
|
||||
self.previewHeight = previewSampleSize.scale(height);
|
||||
self.displayingPreview = true;
|
||||
self._displayImage(self.previewImageURL, self.previewWidth, self.previewHeight, bgURL);
|
||||
} else {
|
||||
self.previewImageURL = null;
|
||||
self.displayingPreview = false;
|
||||
self._displayImage(self.fullImageURL, self.fullsizeWidth, self.fullsizeHeight);
|
||||
}
|
||||
}
|
||||
function computeFullSampleSize(blob, width, height) {
|
||||
if (blob.type !== 'image/jpeg') {
|
||||
return Downsample.NONE;
|
||||
}
|
||||
var max = MediaFrame.maxImageDecodeSize || 0;
|
||||
if (self.maximumImageSize && (max === 0 || self.maximumImageSize < max)) {
|
||||
max = self.maximumImageSize;
|
||||
}
|
||||
if (!max || width * height <= max) {
|
||||
return Downsample.NONE;
|
||||
}
|
||||
return Downsample.areaAtLeast(max / (width * height));
|
||||
}
|
||||
function computePreviewSampleSize(blob, width, height) {
|
||||
if (blob.type !== 'image/jpeg') {
|
||||
return Downsample.NONE;
|
||||
}
|
||||
var screenWidth = window.innerWidth * window.devicePixelRatio;
|
||||
var screenHeight = window.innerHeight * window.devicePixelRatio;
|
||||
var portraitScale = Math.min(screenWidth / width, screenHeight / height);
|
||||
var landscapeScale = Math.min(screenHeight / width, screenWidth / height);
|
||||
var scale = Math.max(portraitScale, landscapeScale);
|
||||
return Downsample.sizeNoMoreThan(scale);
|
||||
}
|
||||
};
|
||||
MediaFrame.prototype._displayImage = function (url, width, height, bg) {
|
||||
this.image.style.width = width + 'px';
|
||||
this.image.style.height = height + 'px';
|
||||
if (bg) {
|
||||
this.image.style.backgroundImage = 'url(' + bg + ')';
|
||||
requestAnimationFrame(() => {
|
||||
this.image.src = url;
|
||||
});
|
||||
} else {
|
||||
this.image.style.backgroundImage = 'none';
|
||||
this.image.src = url;
|
||||
}
|
||||
if (this.rotation === 0 || this.rotation === 180) {
|
||||
this.itemWidth = width;
|
||||
this.itemHeight = height;
|
||||
} else {
|
||||
this.itemWidth = height;
|
||||
this.itemHeight = width;
|
||||
}
|
||||
this.computeFit();
|
||||
this.setPosition();
|
||||
var temp = this.image.clientLeft;
|
||||
};
|
||||
MediaFrame.prototype.localize = function localize() {
|
||||
if (!this.displayingImage) {
|
||||
return;
|
||||
}
|
||||
var portrait = this.fullsizeWidth < this.fullsizeHeight;
|
||||
if (this.rotation == 90 || this.rotation == 270) {
|
||||
portrait = !portrait;
|
||||
}
|
||||
var timestamp = this.imageblob.lastModifiedDate;
|
||||
var orientationL10nId = portrait ? 'orientationPortrait' : 'orientationLandscape';
|
||||
document.l10n.formatValue(orientationL10nId).then(orientationText => {
|
||||
if (timestamp) {
|
||||
if (!this.dtf) {
|
||||
this.dtf = Intl.DateTimeFormat(navigator.languages, {
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
day: 'numeric',
|
||||
month: 'numeric',
|
||||
year: 'numeric'
|
||||
});
|
||||
}
|
||||
var ts = this.dtf.format(new Date(timestamp));
|
||||
document.l10n.setAttributes(this.image, 'imageDescription', {
|
||||
orientation: orientationText,
|
||||
timestamp: ts
|
||||
});
|
||||
} else {
|
||||
document.l10n.setAttributes(this.image, 'imageDescriptionNoTimestamp', { orientation: orientationText });
|
||||
}
|
||||
});
|
||||
};
|
||||
MediaFrame.prototype._switchToFullSizeImage = function _switchToFull() {
|
||||
if (!this.displayingImage || !this.displayingPreview) {
|
||||
return;
|
||||
}
|
||||
this.displayingPreview = false;
|
||||
this._displayImage(this.fullImageURL, this.fullsizeWidth, this.fullsizeHeight, this.previewImageURL);
|
||||
};
|
||||
MediaFrame.prototype._switchToPreviewImage = function _switchToPreview() {
|
||||
if (!this.displayingImage || this.displayingPreview || !this.previewImageURL) {
|
||||
return;
|
||||
}
|
||||
this.displayingPreview = true;
|
||||
this._displayImage(this.previewImageURL, this.previewWidth, this.previewHeight);
|
||||
};
|
||||
MediaFrame.prototype.displayVideo = function displayVideo(videoblob, posterblob, aspectRatio, rotation) {
|
||||
if (!this.video) {
|
||||
return;
|
||||
}
|
||||
this.clear();
|
||||
this.displayingVideo = true;
|
||||
this.videoblob = videoblob;
|
||||
this.posterblob = posterblob;
|
||||
this.videourl = URL.createObjectURL(videoblob);
|
||||
this.posterurl = URL.createObjectURL(posterblob);
|
||||
this.video.load(this.videourl, this.posterurl, aspectRatio, rotation || 0, videoblob.lastModifiedDate);
|
||||
this.video.show();
|
||||
};
|
||||
MediaFrame.prototype.clear = function clear() {
|
||||
this.displayingImage = false;
|
||||
this.displayingPreview = false;
|
||||
this.displayingVideo = false;
|
||||
this.itemWidth = this.itemHeight = null;
|
||||
this.imageblob = null;
|
||||
this.videoblob = null;
|
||||
this.posterblob = null;
|
||||
this.fullSampleSize = null;
|
||||
this.fullImageURL = null;
|
||||
this.previewImageURL = null;
|
||||
this.fullsizeWidth = this.fullsizeHeight = null;
|
||||
this.previewWidth = this.previewHeight = null;
|
||||
this.fit = null;
|
||||
if (this.imageurl) {
|
||||
URL.revokeObjectURL(this.imageurl);
|
||||
}
|
||||
this.imageurl = null;
|
||||
if (this.previewurl) {
|
||||
URL.revokeObjectURL(this.previewurl);
|
||||
}
|
||||
this.previewurl = null;
|
||||
if (this.tinyPreviewURL) {
|
||||
URL.revokeObjectURL(this.tinyPreviewURL);
|
||||
}
|
||||
this.tinyPreviewURL = null;
|
||||
if (this.image) {
|
||||
this.image.style.opacity = 0;
|
||||
this.image.style.backgroundImage = 'none';
|
||||
this.image.src = '';
|
||||
this.image.removeAttribute('aria-label');
|
||||
}
|
||||
if (this.video) {
|
||||
this.video.reset();
|
||||
this.video.hide();
|
||||
if (this.videourl) {
|
||||
URL.revokeObjectURL(this.videourl);
|
||||
}
|
||||
this.videourl = null;
|
||||
if (this.posterurl) {
|
||||
URL.revokeObjectURL(this.posterurl);
|
||||
}
|
||||
this.posterurl = null;
|
||||
}
|
||||
};
|
||||
MediaFrame.prototype.setPosition = function setPosition() {
|
||||
if (!this.fit || !this.displayingImage) {
|
||||
return;
|
||||
}
|
||||
var dx = this.fit.left, dy = this.fit.top;
|
||||
switch (this.rotation) {
|
||||
case 0:
|
||||
case 180:
|
||||
dx += (this.fit.width - this.itemWidth) / 2;
|
||||
dy += (this.fit.height - this.itemHeight) / 2;
|
||||
break;
|
||||
case 90:
|
||||
case 270:
|
||||
dx += (this.fit.width - this.itemHeight) / 2;
|
||||
dy += (this.fit.height - this.itemWidth) / 2;
|
||||
break;
|
||||
}
|
||||
var sx = this.mirrored ? -this.fit.scale : this.fit.scale;
|
||||
var sy = this.fit.scale;
|
||||
var transform = 'translate(' + dx + 'px, ' + dy + 'px) ' + 'scale(' + sx + ',' + sy + ')' + 'rotate(' + this.rotation + 'deg) ';
|
||||
this.image.style.transform = transform;
|
||||
};
|
||||
MediaFrame.prototype.computeFit = function computeFit() {
|
||||
if (!this.displayingImage) {
|
||||
return;
|
||||
}
|
||||
this.viewportWidth = this.container.offsetWidth;
|
||||
this.viewportHeight = this.container.offsetHeight;
|
||||
var scalex = this.viewportWidth / this.itemWidth;
|
||||
var scaley = this.viewportHeight / this.itemHeight;
|
||||
var scale = Math.min(Math.min(scalex, scaley), 1);
|
||||
var width = Math.floor(this.itemWidth * scale);
|
||||
var height = Math.floor(this.itemHeight * scale);
|
||||
this.fit = {
|
||||
width: width,
|
||||
height: height,
|
||||
left: Math.floor((this.viewportWidth - width) / 2),
|
||||
top: Math.floor((this.viewportHeight - height) / 2),
|
||||
scale: scale,
|
||||
baseScale: scale
|
||||
};
|
||||
};
|
||||
MediaFrame.prototype.reset = function reset() {
|
||||
if (this.displayingImage && !this.displayingPreview && this.previewImageURL) {
|
||||
this._switchToPreviewImage();
|
||||
return;
|
||||
}
|
||||
this.computeFit();
|
||||
this.setPosition();
|
||||
if (this.displayingVideo) {
|
||||
this.video.setPlayerSize();
|
||||
}
|
||||
};
|
||||
MediaFrame.prototype.resize = function resize() {
|
||||
var oldWidth = this.viewportWidth;
|
||||
var oldHeight = this.viewportHeight;
|
||||
var newWidth = this.container.offsetWidth;
|
||||
var newHeight = this.container.offsetHeight;
|
||||
var oldfit = this.fit;
|
||||
if (!oldfit) {
|
||||
return;
|
||||
}
|
||||
this.computeFit();
|
||||
var newfit = this.fit;
|
||||
if (Math.abs(oldfit.scale - oldfit.baseScale) < 0.01 || newfit.baseScale > oldfit.scale) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
oldfit.left += (newWidth - oldWidth) / 2;
|
||||
oldfit.top += (newHeight - oldHeight) / 2;
|
||||
oldfit.baseScale = newfit.baseScale;
|
||||
this.fit = oldfit;
|
||||
this.setPosition();
|
||||
};
|
||||
MediaFrame.prototype.zoom = function zoom(scale, fixedX, fixedY, time) {
|
||||
if (!this.displayingImage) {
|
||||
return;
|
||||
}
|
||||
if (this.displayingPreview) {
|
||||
this._switchToFullSizeImage();
|
||||
}
|
||||
if (this.fit.scale * scale > 1) {
|
||||
scale = 1 / this.fit.scale;
|
||||
} else if (this.fit.scale * scale < this.fit.baseScale) {
|
||||
scale = this.fit.baseScale / this.fit.scale;
|
||||
}
|
||||
this.fit.scale = this.fit.scale * scale;
|
||||
this.fit.width = Math.floor(this.itemWidth * this.fit.scale);
|
||||
this.fit.height = Math.floor(this.itemHeight * this.fit.scale);
|
||||
var photoX = fixedX - this.fit.left;
|
||||
var photoY = fixedY - this.fit.top;
|
||||
photoX = Math.floor(photoX * scale);
|
||||
photoY = Math.floor(photoY * scale);
|
||||
this.fit.left = fixedX - photoX;
|
||||
this.fit.top = fixedY - photoY;
|
||||
if (this.fit.width <= this.viewportWidth) {
|
||||
this.fit.left = (this.viewportWidth - this.fit.width) / 2;
|
||||
} else {
|
||||
if (this.fit.left > 0) {
|
||||
this.fit.left = 0;
|
||||
}
|
||||
if (this.fit.left + this.fit.width < this.viewportWidth) {
|
||||
this.fit.left = this.viewportWidth - this.fit.width;
|
||||
}
|
||||
}
|
||||
if (this.fit.height <= this.viewportHeight) {
|
||||
this.fit.top = (this.viewportHeight - this.fit.height) / 2;
|
||||
} else {
|
||||
if (this.fit.top > 0) {
|
||||
this.fit.top = 0;
|
||||
}
|
||||
if (this.fit.top + this.fit.height < this.viewportHeight) {
|
||||
this.fit.top = this.viewportHeight - this.fit.height;
|
||||
}
|
||||
}
|
||||
if (time) {
|
||||
var transition = 'transform ' + time + 'ms ease';
|
||||
this.image.style.transition = transition;
|
||||
var self = this;
|
||||
this.image.addEventListener('transitionend', function done() {
|
||||
self.image.removeEventListener('transitionend', done);
|
||||
self.image.style.transition = '';
|
||||
});
|
||||
}
|
||||
this.setPosition();
|
||||
};
|
||||
MediaFrame.prototype.pan = function (dx, dy) {
|
||||
if (!this.displayingImage) {
|
||||
return dx;
|
||||
}
|
||||
if (this.fit.height > this.viewportHeight) {
|
||||
this.fit.top += dy;
|
||||
if (this.fit.top > 0) {
|
||||
this.fit.top = 0;
|
||||
}
|
||||
if (this.fit.top + this.fit.height < this.viewportHeight) {
|
||||
this.fit.top = this.viewportHeight - this.fit.height;
|
||||
}
|
||||
}
|
||||
var extra = 0;
|
||||
if (this.fit.width <= this.viewportWidth) {
|
||||
extra = dx;
|
||||
} else {
|
||||
this.fit.left += dx;
|
||||
if (this.fit.left > 0) {
|
||||
extra = this.fit.left;
|
||||
this.fit.left = 0;
|
||||
}
|
||||
if (this.fit.left + this.fit.width < this.viewportWidth) {
|
||||
extra = this.fit.left + this.fit.width - this.viewportWidth;
|
||||
this.fit.left = this.viewportWidth - this.fit.width;
|
||||
}
|
||||
}
|
||||
this.setPosition();
|
||||
return extra;
|
||||
};
|
||||
MediaFrame.prototype.setMinimumPreviewSize = function (w, h) {
|
||||
this.minimumPreviewWidth = w;
|
||||
this.minimumPreviewHeight = h;
|
||||
};
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
function VideoPlayer(container) {
|
||||
if (typeof container === 'string') {
|
||||
container = document.getElementById(container);
|
||||
}
|
||||
container.classList.add('video-player-container');
|
||||
VideoPlayer.instancesToLocalize.set(container, this);
|
||||
function newelt(parent, type, classes, l10n_id, attributes) {
|
||||
var e = document.createElement(type);
|
||||
if (classes) {
|
||||
e.className = classes;
|
||||
}
|
||||
if (l10n_id) {
|
||||
e.dataset.l10nId = l10n_id;
|
||||
}
|
||||
if (attributes) {
|
||||
for (var attribute in attributes) {
|
||||
e.setAttribute(attribute, attributes[attribute]);
|
||||
}
|
||||
}
|
||||
parent.appendChild(e);
|
||||
return e;
|
||||
}
|
||||
var poster = newelt(container, 'img', 'videoPoster');
|
||||
var player = newelt(container, 'video', 'videoPlayer', null, { 'aria-hidden': true });
|
||||
var controls = newelt(container, 'div', 'videoPlayerControls');
|
||||
var playbutton = newelt(controls, 'button', 'videoPlayerPlayButton', 'playbackPlay');
|
||||
var footer = newelt(controls, 'div', 'videoPlayerFooter hidden');
|
||||
var pausebutton = newelt(footer, 'button', 'videoPlayerPauseButton', 'playbackPause');
|
||||
var slider = newelt(footer, 'div', 'videoPlayerSlider', null, {
|
||||
'role': 'slider',
|
||||
'aria-valuemin': 0
|
||||
});
|
||||
var elapsedText = newelt(slider, 'span', 'videoPlayerElapsedText');
|
||||
var progress = newelt(slider, 'div', 'videoPlayerProgress');
|
||||
var backgroundBar = newelt(progress, 'div', 'videoPlayerBackgroundBar');
|
||||
var elapsedBar = newelt(progress, 'div', 'videoPlayerElapsedBar');
|
||||
var playHead = newelt(progress, 'div', 'videoPlayerPlayHead');
|
||||
var durationText = newelt(slider, 'span', 'videoPlayerDurationText');
|
||||
var fullscreenButton = newelt(slider, 'button', 'videoPlayerFullscreenButton', 'playbackFullscreen');
|
||||
this.poster = poster;
|
||||
this.player = player;
|
||||
this.controls = controls;
|
||||
this.playing = false;
|
||||
player.preload = 'metadata';
|
||||
player.mozAudioChannelType = 'content';
|
||||
var self = this;
|
||||
var controlsHidden = false;
|
||||
var dragging = false;
|
||||
var endedTimer;
|
||||
var videourl;
|
||||
var posterurl;
|
||||
var rotation;
|
||||
var aspectRatio;
|
||||
var videotimestamp;
|
||||
var playbackWidth, playbackHeight;
|
||||
var playbackTime;
|
||||
var capturedFrame;
|
||||
this.load = function (video, posterimage, aspect, rotate, timestamp) {
|
||||
this.reset();
|
||||
videourl = video;
|
||||
posterurl = posterimage;
|
||||
rotation = rotate || 0;
|
||||
if (rotation === 0 || rotation === 180) {
|
||||
aspectRatio = aspect;
|
||||
} else {
|
||||
aspectRatio = 1 / aspect;
|
||||
}
|
||||
videotimestamp = timestamp;
|
||||
this.localize();
|
||||
this.init();
|
||||
setPlayerSize();
|
||||
};
|
||||
this.reset = function () {
|
||||
videourl = null;
|
||||
posterurl = null;
|
||||
rotation = null;
|
||||
aspectRatio = null;
|
||||
videotimestamp = 0;
|
||||
hidePlayer();
|
||||
hidePoster();
|
||||
};
|
||||
this.init = function () {
|
||||
playbackTime = 0;
|
||||
hidePlayer();
|
||||
showPoster();
|
||||
this.pause();
|
||||
};
|
||||
function hidePlayer() {
|
||||
player.style.display = 'none';
|
||||
player.removeAttribute('src');
|
||||
player.load();
|
||||
self.playerShowing = false;
|
||||
}
|
||||
function showPlayer() {
|
||||
if (self.onloading) {
|
||||
self.onloading();
|
||||
}
|
||||
player.style.display = 'block';
|
||||
player.src = videourl;
|
||||
self.playerShowing = true;
|
||||
player.oncanplay = function () {
|
||||
player.oncanplay = null;
|
||||
if (playbackTime !== 0) {
|
||||
player.currentTime = playbackTime;
|
||||
}
|
||||
self.play();
|
||||
};
|
||||
}
|
||||
function hidePoster() {
|
||||
poster.style.display = 'none';
|
||||
poster.removeAttribute('src');
|
||||
if (capturedFrame) {
|
||||
URL.revokeObjectURL(capturedFrame);
|
||||
capturedFrame = null;
|
||||
}
|
||||
}
|
||||
function showPoster() {
|
||||
poster.style.display = 'block';
|
||||
if (capturedFrame) {
|
||||
poster.src = capturedFrame;
|
||||
} else {
|
||||
poster.src = posterurl;
|
||||
}
|
||||
}
|
||||
this.setPlayerSize = setPlayerSize;
|
||||
this.pause = function pause() {
|
||||
if (self.playerShowing) {
|
||||
this.playing = false;
|
||||
player.pause();
|
||||
}
|
||||
footer.classList.add('hidden');
|
||||
controlsHidden = true;
|
||||
playbutton.classList.remove('hidden');
|
||||
if (this.onpaused) {
|
||||
this.onpaused();
|
||||
}
|
||||
};
|
||||
this.play = function play() {
|
||||
if (!this.playerShowing) {
|
||||
hidePoster();
|
||||
showPlayer();
|
||||
return;
|
||||
}
|
||||
playbutton.classList.add('hidden');
|
||||
this.playing = true;
|
||||
player.play();
|
||||
footer.classList.remove('hidden');
|
||||
controlsHidden = false;
|
||||
if (this.onplaying) {
|
||||
this.onplaying();
|
||||
}
|
||||
};
|
||||
fullscreenButton.addEventListener('tap', function (e) {
|
||||
if (self.onfullscreentap) {
|
||||
e.stopPropagation();
|
||||
self.onfullscreentap();
|
||||
}
|
||||
});
|
||||
playbutton.addEventListener('tap', function (e) {
|
||||
if (!self.playerShowing || player.paused) {
|
||||
self.play();
|
||||
}
|
||||
e.stopPropagation();
|
||||
});
|
||||
pausebutton.addEventListener('tap', function (e) {
|
||||
self.pause();
|
||||
e.stopPropagation();
|
||||
});
|
||||
container.addEventListener('tap', function (e) {
|
||||
if ((e.target === player || e.target === container) && !player.paused) {
|
||||
footer.classList.toggle('hidden');
|
||||
controlsHidden = !controlsHidden;
|
||||
}
|
||||
});
|
||||
player.onloadedmetadata = function () {
|
||||
var formattedTime = formatTime(player.duration);
|
||||
durationText.textContent = formattedTime;
|
||||
slider.setAttribute('aria-valuemax', player.duration);
|
||||
document.l10n.setAttributes(slider, 'playbackSeekBar', { 'duration': formattedTime });
|
||||
self.pause();
|
||||
};
|
||||
window.addEventListener('resize', function () {
|
||||
setPlayerSize();
|
||||
});
|
||||
player.onended = ended;
|
||||
function ended() {
|
||||
if (dragging) {
|
||||
return;
|
||||
}
|
||||
if (endedTimer) {
|
||||
clearTimeout(endedTimer);
|
||||
endedTimer = null;
|
||||
}
|
||||
self.pause();
|
||||
self.init();
|
||||
}
|
||||
player.ontimeupdate = updateTime;
|
||||
function updateTime() {
|
||||
if (!controlsHidden) {
|
||||
var formattedTime = formatTime(player.currentTime);
|
||||
elapsedText.textContent = formattedTime;
|
||||
slider.setAttribute('aria-valuenow', player.currentTime);
|
||||
slider.setAttribute('aria-valuetext', formattedTime);
|
||||
if (player.duration === Infinity || player.duration === 0) {
|
||||
return;
|
||||
}
|
||||
var percent = player.currentTime / player.duration * 100 + '%';
|
||||
var startEdge = document.documentElement.dir === 'ltr' ? 'left' : 'right';
|
||||
elapsedBar.style.width = percent;
|
||||
playHead.style[startEdge] = percent;
|
||||
}
|
||||
if (!endedTimer) {
|
||||
if (!dragging && player.currentTime >= player.duration - 1) {
|
||||
var timeUntilEnd = player.duration - player.currentTime + 0.5;
|
||||
endedTimer = setTimeout(ended, timeUntilEnd * 1000);
|
||||
}
|
||||
} else if (dragging && player.currentTime < player.duration - 1) {
|
||||
clearTimeout(endedTimer);
|
||||
endedTimer = null;
|
||||
}
|
||||
}
|
||||
window.addEventListener('visibilitychange', visibilityChanged);
|
||||
function visibilityChanged() {
|
||||
if (document.hidden) {
|
||||
if (!self.playerShowing) {
|
||||
return;
|
||||
}
|
||||
self.pause();
|
||||
if (player.currentTime !== 0) {
|
||||
playbackTime = player.currentTime;
|
||||
captureCurrentFrame(function (blob) {
|
||||
capturedFrame = URL.createObjectURL(blob);
|
||||
hidePlayer();
|
||||
showPoster();
|
||||
});
|
||||
} else {
|
||||
hidePlayer();
|
||||
showPoster();
|
||||
}
|
||||
}
|
||||
}
|
||||
function captureCurrentFrame(callback) {
|
||||
if (!player.src) {
|
||||
return;
|
||||
}
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = playbackWidth;
|
||||
canvas.height = playbackHeight;
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(player, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob(callback);
|
||||
}
|
||||
function setPlayerSize() {
|
||||
if (!aspectRatio) {
|
||||
return;
|
||||
}
|
||||
var containerWidth = container.clientWidth;
|
||||
var containerHeight = container.clientHeight;
|
||||
if (containerWidth > aspectRatio * containerHeight) {
|
||||
playbackHeight = containerHeight;
|
||||
playbackWidth = Math.round(playbackHeight * aspectRatio);
|
||||
} else {
|
||||
playbackWidth = containerWidth;
|
||||
playbackHeight = Math.round(playbackWidth / aspectRatio);
|
||||
}
|
||||
var elementWidth, elementHeight;
|
||||
if (rotation === 0 || rotation === 180) {
|
||||
elementWidth = playbackWidth;
|
||||
elementHeight = playbackHeight;
|
||||
} else {
|
||||
elementWidth = playbackHeight;
|
||||
elementHeight = playbackWidth;
|
||||
}
|
||||
poster.style.position = player.style.position = 'absolute';
|
||||
player.style.width = poster.style.width = elementWidth + 'px';
|
||||
player.style.height = poster.style.height = elementHeight + 'px';
|
||||
poster.style.left = player.style.left = (containerWidth - elementWidth) / 2 + 'px';
|
||||
poster.style.top = player.style.top = (containerHeight - elementHeight) / 2 + 'px';
|
||||
poster.style.transformOrigin = player.style.transformOrigin = '50% 50%';
|
||||
poster.style.transform = player.style.transform = 'rotate(' + rotation + 'deg)';
|
||||
}
|
||||
slider.addEventListener('pan', function pan(e) {
|
||||
e.stopPropagation();
|
||||
if (player.duration === Infinity) {
|
||||
return;
|
||||
}
|
||||
if (!dragging) {
|
||||
dragging = true;
|
||||
}
|
||||
var rect = backgroundBar.getBoundingClientRect();
|
||||
var position = (e.detail.position.clientX - rect.left) / rect.width;
|
||||
var pos = Math.min(Math.max(position, 0), 1);
|
||||
if (document.documentElement.dir === 'rtl') {
|
||||
pos = 1 - pos;
|
||||
}
|
||||
player.currentTime = player.duration * pos;
|
||||
updateTime();
|
||||
});
|
||||
slider.addEventListener('swipe', function swipe(e) {
|
||||
e.stopPropagation();
|
||||
dragging = false;
|
||||
if (player.currentTime >= player.duration) {
|
||||
self.pause();
|
||||
}
|
||||
});
|
||||
slider.addEventListener('keypress', function (e) {
|
||||
var step = Math.max(player.duration / 20, 2);
|
||||
if (e.keyCode == e.DOM_VK_DOWN) {
|
||||
player.currentTime -= step;
|
||||
} else if (e.keyCode == e.DOM_VK_UP) {
|
||||
player.currentTime += step;
|
||||
}
|
||||
});
|
||||
function formatTime(time) {
|
||||
time = Math.round(time);
|
||||
var minutes = Math.floor(time / 60);
|
||||
var seconds = time % 60;
|
||||
if (minutes < 60) {
|
||||
return Format.padLeft(minutes, 2, '0') + ':' + Format.padLeft(seconds, 2, '0');
|
||||
} else {
|
||||
var hours = Math.floor(minutes / 60);
|
||||
minutes = Math.round(minutes % 60);
|
||||
return hours + ':' + Format.padLeft(minutes, 2, '0') + ':' + Format.padLeft(seconds, 2, '0');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
var acm = navigator.mozAudioChannelManager;
|
||||
if (acm) {
|
||||
acm.addEventListener('headphoneschange', function onheadphoneschange() {
|
||||
if (!acm.headphones && self.playing) {
|
||||
self.pause();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.localize = function () {
|
||||
var portrait = aspectRatio < 1;
|
||||
var orientationL10nId = portrait ? 'orientationPortrait' : 'orientationLandscape';
|
||||
document.l10n.formatValue(orientationL10nId).then(orientationText => {
|
||||
if (videotimestamp) {
|
||||
if (!self.dtf) {
|
||||
self.dtf = Intl.DateTimeFormat(navigator.languages, {
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
day: 'numeric',
|
||||
month: 'numeric',
|
||||
year: 'numeric'
|
||||
});
|
||||
}
|
||||
var ts = this.dtf.format(new Date(videotimestamp));
|
||||
document.l10n.setAttributes(poster, 'videoDescription', {
|
||||
orientation: orientationText,
|
||||
timestamp: ts
|
||||
});
|
||||
} else {
|
||||
document.l10n.setAttributes(poster, 'videoDescriptionNoTimestamp', { orientation: orientationText });
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
VideoPlayer.prototype.hide = function () {
|
||||
this.controls.style.display = 'none';
|
||||
};
|
||||
VideoPlayer.prototype.show = function () {
|
||||
this.controls.style.display = 'block';
|
||||
};
|
||||
VideoPlayer.instancesToLocalize = new WeakMap();
|
||||
document.addEventListener('DOMRetranslated', function () {
|
||||
for (var container of document.querySelectorAll('.video-player-container')) {
|
||||
var instance = VideoPlayer.instancesToLocalize.get(container);
|
||||
if (instance) {
|
||||
instance.localize();
|
||||
}
|
||||
}
|
||||
});
|
||||
53
assets/webapps/camera.gaiamobile.org/shared/js/sanitizer.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
(function (root, factory) {
|
||||
'use strict';
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
root.Sanitizer = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
'use strict';
|
||||
var Sanitizer = {
|
||||
_entity: /[&<>"'/]/g,
|
||||
_entities: {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
'\'': ''',
|
||||
'/': '/'
|
||||
},
|
||||
getEntity: function (s) {
|
||||
return Sanitizer._entities[s];
|
||||
},
|
||||
escapeHTML: function (strings, ...values) {
|
||||
var result = '';
|
||||
for (var i = 0; i < strings.length; i++) {
|
||||
result += strings[i];
|
||||
if (i < values.length) {
|
||||
result += String(values[i]).replace(Sanitizer._entity, Sanitizer.getEntity);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
createSafeHTML: function (strings, ...values) {
|
||||
var escaped = Sanitizer.escapeHTML(strings, ...values);
|
||||
return {
|
||||
__html: escaped,
|
||||
toString: function () {
|
||||
return '[object WrappedHTMLObject]';
|
||||
},
|
||||
info: 'This is a wrapped HTML object. See https://developer.mozilla.or' + 'g/en-US/Firefox_OS/Security/Security_Automation for more.'
|
||||
};
|
||||
},
|
||||
unwrapSafeHTML: function (...htmlObjects) {
|
||||
var markupList = htmlObjects.map(function (obj) {
|
||||
return obj.__html;
|
||||
});
|
||||
return markupList.join('');
|
||||
}
|
||||
};
|
||||
return Sanitizer;
|
||||
}));
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Action menu</title>
|
||||
<meta name="description" content="Presenting a list of actions related to the App content">
|
||||
|
||||
<link rel="stylesheet" href="../action_menu.css">
|
||||
<!--
|
||||
- This <style> element is only used for the example code;
|
||||
- it is not required for the real use case.
|
||||
-->
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
body {
|
||||
background: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body role="application">
|
||||
<p dir="ltr">
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
onchange="document.documentElement.dir = this.checked ? 'rtl' : 'ltr'">
|
||||
RTL mode
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<form role="dialog" data-type="action">
|
||||
<header>
|
||||
<button><span class="icon icon-close">close</span></button>
|
||||
<menu type="toolbar">
|
||||
<button>done</button>
|
||||
</menu>
|
||||
<h1>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</h1>
|
||||
</header>
|
||||
<section>
|
||||
<p>Curabitur condimentum diam mollis lacus hendrerit congue. Phasellus porttitor orci elit, ac consequat est mollis ut. Ut consequat ipsum a ullamcorper aliquam.</p>
|
||||
</section>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 245 B |
|
After Width: | Height: | Size: 178 B |
|
After Width: | Height: | Size: 178 B |
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Action menu</title>
|
||||
<meta name="description" content="Presenting a list of actions related to the App content">
|
||||
|
||||
<link rel="stylesheet" href="../action_menu.css">
|
||||
<!--
|
||||
- These <style> and <script> elements are only used for the example code;
|
||||
- they are not required for the real use case.
|
||||
-->
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
body {
|
||||
background: none;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.onkeypress = event => {
|
||||
var dir = document.documentElement.dir;
|
||||
document.documentElement.dir = (dir === 'ltr') ? 'rtl' : 'ltr';
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body role="application">
|
||||
<form role="dialog" data-type="action">
|
||||
<header><span class="icon icon-back"></span>Title</header> <!-- this header is optional -->
|
||||
<menu type="toolbar">
|
||||
<button>Action 1</button>
|
||||
<button disabled> Action 2 (disabled) </button>
|
||||
<button> Action 3. </button>
|
||||
<button> الولايات 4</button>
|
||||
<button disabled> Action 5 (disabled) </button>
|
||||
<button> Action 6 </button>
|
||||
<button> Action 7 </button>
|
||||
<button disabled> Action 8 (disabled) </button>
|
||||
<button> Action 9 </button>
|
||||
<button> Cancel </button>
|
||||
</menu>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -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 |
|
|
@ -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 11.698,19.66 5.056,-5.057 -5.056,-5.056 c 0.31,-0.516 0.62,-1.032 0.93,-1.34 L 18.3,13.88 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: 292 B |
|
|
@ -0,0 +1,280 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Buttons</title>
|
||||
<link rel="stylesheet" href="../buttons.css">
|
||||
<link rel="stylesheet" href="../../elements/gaia-icons/gaia-icons-embedded.css">
|
||||
<link rel="stylesheet" href="../../elements/gaia-icons/bidi-helper.css">
|
||||
|
||||
<!--
|
||||
- These <style> and <script> elements are only used for the example code,
|
||||
- they are not needed for the real case.
|
||||
-->
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
label.bb-docs {
|
||||
font-family: sans-serif;
|
||||
font-size: 1.2rem;
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
h2.bb-docs {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 300;
|
||||
color: #666;
|
||||
margin: -0.1rem 0 0;
|
||||
background-color: #f5f5f5;
|
||||
padding: 0.4rem 3rem;
|
||||
border: solid 0.1rem #e8e8e8;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
div.bb-docs {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
div.bb-docs.skin-organic {
|
||||
background: #f4f4f4;
|
||||
}
|
||||
|
||||
div.bb-docs.skin-dark {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
body[role="application"] > [role="region"] {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-icon]:before {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
document.getElementById('dir').checked = false;
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body role="application">
|
||||
<p dir="ltr">
|
||||
<label class="bb-docs">
|
||||
<input id="dir" type="checkbox" checked="false"
|
||||
onchange="document.documentElement.dir = this.checked ? 'rtl' : 'ltr'">
|
||||
RTL mode
|
||||
</label>
|
||||
</p>
|
||||
<h2 class="bb-docs">Special buttons</h2>
|
||||
<div class="bb-docs skin-organic">
|
||||
<button>Default</button>
|
||||
<button class="recommend">Recommend</button>
|
||||
<button class="danger">Danger</button>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Buttons with icons</h2>
|
||||
<div class="bb-docs skin-organic">
|
||||
<button data-icon="forward" aria-label="forward" data-l10n-id="">Default</button>
|
||||
<button data-icon="forward" aria-label="forward" data-l10n-id="" class="recommend">Recommend</button>
|
||||
<button data-icon="forward" aria-label="forward" data-l10n-id="" class="danger">Danger</button>
|
||||
<button data-icon="forward-light" aria-label="forward-light" data-l10n-id="">Default</button>
|
||||
<button data-icon="back" aria-label="back" data-l10n-id="">Default</button>
|
||||
<button data-icon="back-light" aria-label="back-light" data-l10n-id="">Default</button>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Disabled</h2>
|
||||
<div class="bb-docs skin-organic">
|
||||
<button disabled>Default</button>
|
||||
<a href="#" role="button" aria-disabled="true" class="recommend bb-button">Recommend</a>
|
||||
<button disabled class="danger">Danger</button>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Skin dark</h2>
|
||||
<div class="bb-docs skin-dark">
|
||||
<button>Default</button>
|
||||
<button class="recommend">Recommend</button>
|
||||
<button class="danger">Danger</button>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Disabled</h2>
|
||||
<div class="bb-docs skin-dark">
|
||||
<button disabled>Default</button>
|
||||
<a href="#" role="button" aria-disabled="true" class="recommend bb-button">Recommend</a>
|
||||
<button disabled class="danger">Danger</button>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">List buttons</h2>
|
||||
<div class="bb-docs skin-organic">
|
||||
<ul>
|
||||
<li>
|
||||
<button>Default</button>
|
||||
</li>
|
||||
<li>
|
||||
<button disabled>Disabled</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="icon icon-view">
|
||||
Icon view button
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="icon icon-dialog">
|
||||
Icon dialog button
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<span class="button icon icon-dialog">
|
||||
<select>
|
||||
<option selected>Icon dialog with options</option>
|
||||
<option>option 1</option>
|
||||
<option>option 2</option>
|
||||
<option>option 3</option>
|
||||
</select>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<button class="icon icon-dialog" disabled>
|
||||
Icon dialog button disabled
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button data-icon="forward" aria-label="forward" data-l10n-id="">
|
||||
Side navigation
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button disabled data-icon="forward" aria-label="forward" data-l10n-id="">
|
||||
Side navigation
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
|
||||
Tuesday Sept. 18, 2012
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" aria-disabled="true" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
|
||||
Tuesday Sept. 18, 2012
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Skin Dark</h2>
|
||||
<div class="bb-docs skin-dark">
|
||||
<ul>
|
||||
<li>
|
||||
<button>Default</button>
|
||||
</li>
|
||||
<li>
|
||||
<button disabled>Disabled</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="icon icon-dialog">
|
||||
Dark icon dialog button
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button data-icon="forward" aria-label="forward" data-l10n-id="">
|
||||
Side navigation
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button disabled data-icon="forward" aria-label="forward" data-l10n-id="">
|
||||
Side navigation
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
|
||||
Tuesday Sept. 18, 2012
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a role="button" aria-disabled="true" data-icon="expand" class="bb-button" aria-label="expand" data-l10n-id="">
|
||||
Tuesday Sept. 18, 2012
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Select</h2>
|
||||
<div class="bb-docs">
|
||||
<select>
|
||||
<option>option 1</option>
|
||||
<option>option 2</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Fake select</h2>
|
||||
<div class="bb-docs">
|
||||
<ul>
|
||||
<li>
|
||||
<span class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
|
||||
<select>
|
||||
<option>option 1</option>
|
||||
<option>option 2</option>
|
||||
</select>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Disabled select</h2>
|
||||
<div class="bb-docs">
|
||||
<ul>
|
||||
<li>
|
||||
<span aria-disabled="true" class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
|
||||
<select>
|
||||
<option>option 1</option>
|
||||
<option>option 2</option>
|
||||
</select>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Skin dark</h2>
|
||||
<div class="bb-docs skin-dark">
|
||||
<select>
|
||||
<option>option 1</option>
|
||||
<option>option 2</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Fake select</h2>
|
||||
<div class="bb-docs skin-dark">
|
||||
<ul>
|
||||
<li>
|
||||
<span class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
|
||||
<select>
|
||||
<option>option 1</option>
|
||||
<option>option 2</option>
|
||||
</select>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span aria-disabled="true" class="bb-button" data-icon="expand" aria-label="expand" data-l10n-id="">
|
||||
<select>
|
||||
<option>option 1</option>
|
||||
<option>option 2</option>
|
||||
</select>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
|
@ -0,0 +1,65 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>Confirm</title>
|
||||
<meta name="description" content="Prompts user to take action">
|
||||
|
||||
<link rel="stylesheet" href="../confirm.css">
|
||||
|
||||
<!--
|
||||
- These <style> and <script> elements are only used for the example code;
|
||||
- they are not required for the real use case.
|
||||
-->
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
body {
|
||||
background: none;
|
||||
}
|
||||
#dir {
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
display: block;
|
||||
direction: ltr;
|
||||
background-color: white;
|
||||
}
|
||||
#dir input {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.onload = function(){
|
||||
var input = document.querySelector('#dir input');
|
||||
input.checked = false;
|
||||
input.onchange = function() {
|
||||
document.documentElement.dir = this.checked ? 'rtl' : 'ltr';
|
||||
};
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body role="application">
|
||||
<label id="dir">
|
||||
<input type="checkbox"> RTL mode
|
||||
</label>
|
||||
|
||||
<form role="dialog" data-type="confirm">
|
||||
<section>
|
||||
<h1>Confirmation</h1>
|
||||
<p>Are you sure you want to delete this contact?</p>
|
||||
</section>
|
||||
<menu>
|
||||
<button>Cancel</button>
|
||||
<button class="danger">Delete</button>
|
||||
</menu>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>Confirm</title>
|
||||
<meta name="description" content="Prompts user to take action">
|
||||
|
||||
<link rel="stylesheet" href="../confirm.css">
|
||||
|
||||
<!--
|
||||
- These <style> and <script> elements are only used for the example code;
|
||||
- they are not required for the real use case.
|
||||
-->
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
body {
|
||||
background: none;
|
||||
}
|
||||
#dir {
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
display: block;
|
||||
direction: ltr;
|
||||
background-color: white;
|
||||
}
|
||||
#dir input {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.onload = function(){
|
||||
var input = document.querySelector('#dir input');
|
||||
input.checked = false;
|
||||
input.onchange = function() {
|
||||
document.documentElement.dir = this.checked ? 'rtl' : 'ltr';
|
||||
};
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body role="application">
|
||||
<label id="dir">
|
||||
<input type="checkbox"> RTL mode
|
||||
</label>
|
||||
|
||||
<form role="dialog" data-type="confirm">
|
||||
<section>
|
||||
<h1>Confirmation</h1>
|
||||
<p dir="auto">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
|
||||
</section>
|
||||
<menu>
|
||||
<button>Cancel</button>
|
||||
<button class="danger">Delete</button>
|
||||
</menu>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>Confirm, w/o title</title>
|
||||
<meta name="description" content="Prompts user to take action">
|
||||
|
||||
<link rel="stylesheet" href="../confirm.css">
|
||||
|
||||
<!--
|
||||
- These <style> and <script> elements are only used for the example code;
|
||||
- they are not required for the real use case.
|
||||
-->
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
body {
|
||||
background: none;
|
||||
}
|
||||
#dir {
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
display: block;
|
||||
direction: ltr;
|
||||
background-color: white;
|
||||
}
|
||||
#dir input {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.onload = function(){
|
||||
var input = document.querySelector('#dir input');
|
||||
input.checked = false;
|
||||
input.onchange = function() {
|
||||
document.documentElement.dir = this.checked ? 'rtl' : 'ltr';
|
||||
};
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body role="application">
|
||||
<label id="dir">
|
||||
<input type="checkbox"> RTL mode
|
||||
</label>
|
||||
|
||||
<form role="dialog" data-type="confirm">
|
||||
<section>
|
||||
<p dir="auto">Are you sure you want to delete this contact?</p>
|
||||
</section>
|
||||
<menu>
|
||||
<button>Cancel</button>
|
||||
<button class="danger">Delete</button>
|
||||
</menu>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 99 B |
|
After Width: | Height: | Size: 789 B |
|
After Width: | Height: | Size: 502 B |
|
|
@ -0,0 +1,155 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>Progress & Activity Indicators</title>
|
||||
<meta name="description" content="Providing user with visual feedback that a process is active">
|
||||
|
||||
<link rel="stylesheet" href="../../style/headers.css">
|
||||
<link rel="stylesheet" href="../../style/confirm.css">
|
||||
<link rel="stylesheet" href="../progress_activity.css">
|
||||
|
||||
<!--
|
||||
- These <style> and <script> elements are only used for the example code,
|
||||
- they are not needed for the real use case.
|
||||
-->
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
body { background: none; }
|
||||
|
||||
h2.bb-docs {
|
||||
font-size: 1.8rem;
|
||||
font-family: sans-serif;
|
||||
font-weight: 300;
|
||||
color: #666;
|
||||
margin: 10px 0;
|
||||
background-color: #f5f5f5;
|
||||
padding: 0.4rem;
|
||||
padding-inline-start: 3rem;
|
||||
border: solid 1px #e8e8e8;
|
||||
}
|
||||
|
||||
form[role="dialog"][data-type="confirm"] {
|
||||
position: relative;
|
||||
height: 30rem;
|
||||
}
|
||||
|
||||
form[role="dialog"][data-type="confirm"] > section {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bb-steps progress[value] {
|
||||
width: 25%;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.onload = function(){
|
||||
document.getElementById('dir').checked = false;
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body role="application">
|
||||
<p dir="ltr">
|
||||
<label class="bb-docs">
|
||||
<input id="dir" type="checkbox" checked="false"
|
||||
onchange="document.documentElement.dir = this.checked ? 'rtl' : 'ltr'">
|
||||
RTL mode
|
||||
</label>
|
||||
</p>
|
||||
<section role="region">
|
||||
<h2 class="bb-docs">Spinner</h2>
|
||||
<div style="text-align: center">
|
||||
<progress></progress>
|
||||
</div>
|
||||
|
||||
<h2 class="bb-docs">Activity bar</h2>
|
||||
<progress class="pack-activity" value="0" max="100"></progress>
|
||||
|
||||
<h2 class="bb-docs">Progress steps</h2>
|
||||
<p class="bb-steps">
|
||||
<progress value="2" max="4"></progress>
|
||||
</p>
|
||||
|
||||
<h2 class="bb-docs">Progress bar</h2>
|
||||
<progress value="80" max="100"></progress>
|
||||
|
||||
<h2 class="bb-docs">Progress + activity bar</h2>
|
||||
<progress class="pack-activity" value="80" max="100"></progress>
|
||||
</section>
|
||||
|
||||
<h2 class="bb-docs">Progress bar + Confirm BB</h2>
|
||||
<form role="dialog" data-type="confirm">
|
||||
<section>
|
||||
<h1>Name of the action</h1>
|
||||
<p style="text-align: center">
|
||||
<progress></progress>
|
||||
</p>
|
||||
</section>
|
||||
<menu>
|
||||
<button class="full">Cancel</button>
|
||||
</menu>
|
||||
</form>
|
||||
|
||||
<br/>
|
||||
|
||||
<form role="dialog" data-type="confirm">
|
||||
<section>
|
||||
<h1>Name of the action</h1>
|
||||
<p role="status">
|
||||
<span>80%</span>
|
||||
<progress value="80" max="100"></progress>
|
||||
</p>
|
||||
</section>
|
||||
<menu>
|
||||
<button class="full">Cancel</button>
|
||||
</menu>
|
||||
</form>
|
||||
|
||||
<br/>
|
||||
|
||||
<form role="dialog" data-type="confirm">
|
||||
<section>
|
||||
<h1>Name of the action</h1>
|
||||
<p role="status">
|
||||
<progress class="pack-activity" value="0" max="100"></progress>
|
||||
</p>
|
||||
</section>
|
||||
<menu>
|
||||
<button class="full">Cancel</button>
|
||||
</menu>
|
||||
</form>
|
||||
|
||||
|
||||
<h2 class="bb-docs">Progress bar in Headers</h2>
|
||||
<section role="region">
|
||||
<header>
|
||||
<a href="#"><span class="icon icon-back">back</span></a>
|
||||
<h1>Song title</h1>
|
||||
</header>
|
||||
<progress value="80" max="100"></progress>
|
||||
<header>
|
||||
<h2>Subheader text</h2>
|
||||
</header>
|
||||
</section>
|
||||
|
||||
<section role="region">
|
||||
<header>
|
||||
<a href="#"><span class="icon icon-back">back</span></a>
|
||||
<h1>Song title</h1>
|
||||
</header>
|
||||
<progress class="pack-activity" value="0" max="100"></progress>
|
||||
<header>
|
||||
<h2>Subheader text</h2>
|
||||
</header>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||