Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,248 @@
'use strict';
define(function(require) {
var elements = new WeakMap();
var ObservableArray = require('modules/mvvm/observable_array');
/*
* A ListView takes an ObservableArray or an ordinary array, and generate/
* manipulate the corresponding DOM elements of the content in the array using
* the given template function. If the array is an ObservableArray, ListView
* updates the DOM elements accordingly when the array is manipulated.
*/
var ListView = function(root, observableArray, templateFunc) {
var _observableArray = null;
var _root = root;
var _templateFunc = templateFunc;
var _enabled = true;
// keep track of observeables so we can unobserve
var observerMap = new WeakMap();
function releaseObservers(element) {
var observable = observerMap.get(element);
if (!observable || typeof observable !== 'object') {
return;
}
var observers = observerMap.get(observable);
if (observers) {
observers.forEach(function eachObserver(observer) {
observable.unobserve(observer);
});
observerMap.delete(observable);
observerMap.delete(element);
}
}
// helper to observe something and automatically remove old observers
function observeAndCall(observable, map) {
var observers = observerMap.get(observable) || [];
if (observerMap.has(observable)) {
observers = observerMap.get(observable);
}
observers = Object.keys(map).map(function eachProperty(property) {
observable.observe(property, map[property]);
map[property]();
return map[property];
});
observerMap.set(observable, observers);
}
var _helper = {
observeAndCall: observeAndCall
};
var _handleEvent = function(event) {
// Ignore the change event when the list view is not enabled.
if (!_enabled) {
return;
}
var data = event.detail;
switch (event.type) {
case 'insert':
_insert(data.index, data.items);
break;
case 'remove':
_remove(data.index, data.count);
break;
case 'replace':
_replace(data.index, data.newValue);
break;
case 'reset':
_reset(data.items || []);
break;
default:
break;
}
};
var _insert = function(index, items) {
if (items.length <= 0) {
return;
}
// add DOM elements
var referenceElement = _root.children[index];
for (var i = items.length - 1; i >= 0; i--) {
var curElement = _templateFunc(items[i], undefined, _helper);
observerMap.set(curElement, items[i]);
_root.insertBefore(curElement, referenceElement);
referenceElement = curElement;
}
};
var _remove = function(index, count) {
if (count === 0) {
return;
}
// remove DOM elements
if (count >= _root.childElementCount) {
// clear all
while (_root.firstElementChild) {
releaseObservers(_root.firstElementChild);
_root.removeChild(_root.firstElementChild);
}
} else {
var nextElement = _root.children[index];
for (var i = 0; i < count; i++) {
if (nextElement) {
var temp = nextElement.nextElementSibling;
releaseObservers(nextElement);
_root.removeChild(nextElement);
nextElement = temp;
} else {
break;
}
}
}
};
var _replace = function(index, value) {
var element = _root.children[index];
if (element) {
releaseObservers(element);
var newElement = _templateFunc(value, element, _helper);
observerMap.set(element, value);
if (newElement !== element) {
_root.insertBefore(newElement, element);
_root.removeChild(element);
}
}
};
var _reset = function(items) {
var itemCount = items.length;
var elementCount = _root.childElementCount;
if (itemCount === 0) {
_remove(0, elementCount);
} else if (itemCount <= elementCount) {
items.forEach(function(item, index) {
_replace(index, item);
});
// remove extra elements
_remove(itemCount, elementCount - itemCount);
} else {
var slicedItems = items.slice(0, elementCount);
var remainingItems = items.slice(elementCount);
slicedItems.forEach(function(item, index) {
_replace(index, item);
});
// add extra elements
_insert(elementCount, remainingItems);
}
};
var _enabledChanged = function() {
if (_enabled) {
_reset(_observableArray.array);
}
};
var view = {
set: function lv_set(newArray) {
if (_observableArray) {
_observableArray.removeEventListener('insert', _handleEvent);
_observableArray.removeEventListener('remove', _handleEvent);
_observableArray.removeEventListener('replace', _handleEvent);
_observableArray.removeEventListener('reset', _handleEvent);
}
if (!newArray) {
// clear all existing items
if (_observableArray) {
_remove(0, _observableArray.length);
_observableArray = null;
}
return;
}
// If new array is an ordinary array, convert it to an ObservableArray.
if (Array.isArray(newArray)) {
newArray = ObservableArray(newArray);
}
_observableArray = newArray;
// An ObservableArray fires the four events for notifying its changes.
// Register to these events for updating UI.
_observableArray.addEventListener('insert', _handleEvent);
_observableArray.addEventListener('remove', _handleEvent);
_observableArray.addEventListener('replace', _handleEvent);
_observableArray.addEventListener('reset', _handleEvent);
if (this.enabled) {
_reset(_observableArray.array);
}
},
destroy: function() {
// unobserve from array and null everything out
if (_observableArray) {
_observableArray.removeEventListener('insert', _handleEvent);
_observableArray.removeEventListener('remove', _handleEvent);
_observableArray.removeEventListener('replace', _handleEvent);
_observableArray.removeEventListener('reset', _handleEvent);
}
_remove(0, _observableArray.length);
elements.delete(_root);
_root = null;
_observableArray = null;
_templateFunc = null;
_enabled = false;
},
get element() {
return _root;
},
set enabled(value) {
if (_enabled !== value) {
_enabled = value;
_enabledChanged();
}
},
get enabled() {
return _enabled;
}
};
if (elements.has(_root)) {
// destroy old ListView if we setup a second one
elements.get(_root).destroy();
}
elements.set(_root, view);
// emtpy element at creation time
_root.innerHTML = '';
view.set(observableArray);
return view;
};
return ListView;
});

View file

@ -0,0 +1,360 @@
/**
* Observable provides ways of defining properties that the value changes of
* them can be observed. In addition to normal properties, it allows to
* create read-only properties and dependency properties.
*
* Observable creation:
* There are two ways of defining an Observable: object literal or extending
* from Observable. Object literal is useful when defining a singleton or you
* want to create an observable easily.
*
* @example
* var observable = Observable({
* prop: 10,
* func: function() {}
* });
*
* Extending from Observable allow you to define a class of Observable. The
* advantage of this compared to object literal is that the accessers are
* shared across all instances of the class. The syntax compatible with the
* javascript prototype definition.
*
* @example
* var ExtendedObservable = Module.create(function ExtendedObservable() {
* this.super(Observable).call(this);
* }).extend(Observable);
* Observable.defineObservableProperty(ExtendedObservable.prototype, 'prop',
* {
* value: 10
* });
* ExtendedObservable.prototype.func = function() {};
*
* // Extend from NewObservable
* var ExtendedObservable2 = Module.create(function() {
* // constructor
* }).extend(ExtendedObservable);
* Observable.defineObservableProperty(
* ExtendedObservable2.prototype, 'prop2',
* {
* value: 20
* });
*
* var observable = ExtendedObservable2();
* console.log(observable.prop); // 10
* console.log(observable.prop2); // 20
*
* Defining a read-only property:
* This is only supported when extending from Observable. When you define a
* read-only property, an internal property with a '_' prefix is defined at
* the same time so you can still change the value inside the observable.
*
* @example
* Observable.defineObservableProperty(ExtendedObservable.prototype, 'prop',
* {
* readonly: true,
* value: 10
* });
* ExtendedObservable.prototype.inc = function() {
* this._prop = 100;
* };
* var observable = new ExtendedObservable();
* observable.prop = 100; // throws an exception
* observable.inc(); // observers on "prop" are called
*
* Defining a dependency property:
* This is only supported when extending from Observable. You provide a list
* of the dependent properties and when each of them changes, the observsers
* on the defined property are called. Dependency properties are read-only and
* their values are determined by the specified getter.
*
* @example
* Observable.defineObservableProperty(ExtendedObservable.prototype, 'prop',
* {
* value: 10
* });
* Observable.defineObservableProperty(
* ExtendedObservable.prototype, 'prop2',
* {
* value: 20
* });
* Observable.defineObservableProperty(
* ExtendedObservable.prototype, 'dependencyProp',
* {
* dependency: ['prop', 'prop2'],
* get: function() {
* return this.prop + this.prop2;
* }
* });
* var observable = new ExtendedObservable();
* observable.prop = 100; // observers on "dependencyProp" are called
* observable.prop2 = 200; // observers on "dependencyProp" are called
* // observable.dependencyProp is 300.
*
* @module modules/mvvm/observable
*/
define(function(require) {
'use strict';
var Module = require('modules/base/module');
var DependencyGraph = require('modules/base/dependency_graph');
var OP_PREFIX = (name) => { return '$OP_' + name; };
/**
* @class Observable
* @requires module:modules/base/module
* @requires module:modules/base/dependency_graph
* @returns {Observable}
*/
var Observable = Module.create(function Observable(object) {
this._observers = {};
if (object) {
this._initWithObject(object);
}
});
/**
* Initialize the observable with a prototype object. This is not required
* for objects that are defined using prototype as everything should be
* defined via Observable.defineObservableProperty explicitly.
*
* @access private
* @memberOf Observable.prototype
* @param {Object} object
*/
Observable.prototype._initWithObject = function o_init(object) {
for (var name in object) {
// If name is a function, simply add it to the observable.
if (typeof object[name] === 'function') {
this[name] = object[name];
} else {
_defineObservableProperty(this, name, {
value: object[name]
});
}
}
};
/**
* Notify the value change of a property.
*
* @access private
* @memberOf Observable.prototype
* @param {String} name
* @param {Object} newValue
* @param {Object} oldValue
*/
Observable.prototype._notify = function o__notify(name, newValue, oldValue) {
var observers = this._observers[name];
if (observers) {
observers.forEach(function(observer) {
observer(newValue, oldValue);
});
}
};
/**
* Remove an observer from a property.
*
* @access private
* @memberOf Observable.prototype
* @param {Function} observer
* @param {String} name
*/
Observable.prototype._removeObserver =
function o__removeObserver(observer, name) {
// arguments in reverse order to support .bind(observer) for the
// unbind from all case
var observers = this._observers[name];
if (observers) {
var index = observers.indexOf(observer);
if (index >= 0) {
observers.splice(index, 1);
}
}
};
/**
* Observe a property with an observer. The observer is called when the
* property changes.
*
* @access public
* @memberOf Observable.prototype
* @param {String} name
* @param {Function} observer
*/
Observable.prototype.observe = function o_observe(name, observer) {
if (typeof observer !== 'function') {
return;
}
(this._observers[name] = this._observers[name] || []).push(observer);
};
/**
* Unobserve a property
*
* @access public
* @memberOf Observable.prototype
* @param {String} name
* @param {Function} observer
*/
Observable.prototype.unobserve = function o_unobserve(name, observer) {
if (typeof name === 'function') {
// (observer) -- remove from every key in _observers
Object.keys(this._observers).forEach(
this._removeObserver.bind(this, name));
} else {
if (observer) {
// (prop, observer) -- remove observer from the specific prop
this._removeObserver(observer, name);
} else if (name in this._observers) {
// (prop) -- otherwise remove all observers for property
this._observers[name] = [];
}
}
};
// Static functions
var _dependencyGraphs = new Map();
/**
* Each module should have its own dependency graph that decides what
* observers to called when a property changes. The function returns the
* dependency graph of a module. It creates one if the map does not exist.
*
* @param {Object} modulePrototype
*/
function _getDependencyGraph(modulePrototype) {
var dependencyGraph = _dependencyGraphs.get(modulePrototype);
if (!dependencyGraph) {
// register a new dependency graph of the module based on the existing
// dependency graph on the module.
dependencyGraph = DependencyGraph(modulePrototype._dependencyGraph);
modulePrototype._dependencyGraph = dependencyGraph;
_dependencyGraphs.set(modulePrototype, dependencyGraph);
}
return dependencyGraph;
}
/**
* The function helps query the values of all dependent properties of a
* specified property.
*
* @param {Observable} observable
* @param {String} sourceProperty
* The source property name.
*/
function _getAllDependentValues(observable, sourceProperty) {
var dependentList = observable._dependencyGraph &&
observable._dependencyGraph.getAllDependent(sourceProperty);
if (dependentList && dependentList.length) {
return dependentList.map((name) => {
return {
name: name,
value: observable[name]
};
});
} else {
return null;
}
}
function _getterTemplate(name, defaultValue) {
return function() {
var value = this[OP_PREFIX(name)];
if (typeof value === 'undefined') {
value = this[OP_PREFIX(name)] = defaultValue;
}
return value;
};
}
function _setterTemplate(name) {
return function(value) {
var oldValue = this[name];
if (oldValue !== value) {
// cache the old values of all dependent
var dependentValues = _getAllDependentValues(this, name);
// change the value
this[OP_PREFIX(name)] = value;
// notify the changes
this._notify(name, value, oldValue);
if (dependentValues) {
dependentValues.forEach((obj) => {
this._notify(obj.name, this[obj.name], obj.value);
});
}
}
};
}
function _defineObservablePropertyCore(object, name, options) {
var dependency = options && options.dependency;
// Update dependency information
if (dependency) {
var dependencyGraph = _getDependencyGraph(object);
dependency.forEach((dependentName) => {
// name depends on dependentName
dependencyGraph.addDependency(name, dependentName);
});
}
Object.defineProperty(object, name, {
enumerable: true,
get: options.get,
set: options.set
});
}
function _defineObservableProperty(object, name, options) {
if (options && options.readonly) {
var internalName = '_' + name;
_defineObservablePropertyCore(object, name, {
dependency: [internalName],
get: function() {
return this[internalName];
}
});
_defineObservablePropertyCore(object, internalName, {
get: _getterTemplate(internalName, options && options.value),
set: _setterTemplate(internalName)
});
} else if (options && options.dependency && options.dependency.length) {
if (typeof options.get !== 'function') {
throw new Error('Observable: getter of ' + name + ' is invalid');
}
_defineObservablePropertyCore(object, name, {
dependency: options.dependency,
get: options.get
});
} else {
_defineObservablePropertyCore(object, name, {
get: _getterTemplate(name, options && options.value),
set: _setterTemplate(name)
});
}
}
/**
* Observe a property with an observer. The observer is called when the
* property changes.
*
* @access public
* @memberOf Observable
* @param {Object} object
* @param {String} name
* @param {Object} options
* @param {Boolean} options.readonly
* Indicating if the property is read-only.
* @param {Array.<String>} options.dependency
* List of the dependent properties.
* @param {Function} options.get
* Getter of the property.
*/
Object.defineProperty(Observable, 'defineObservableProperty', {
get: function() {
return _defineObservableProperty;
}
});
return Observable;
});

View file

@ -0,0 +1,228 @@
/**
* ObservableArray is able to notify its change through events of four basic
* operations including 'insert', 'remove', 'replace', 'reset'.
* ObservableArray implements most used methods including 'push', 'pop', and
* 'splice'. Due to the syntax limitation it provides 'get' and 'set' for
* manuplication values stored in the array. 'reset' is also provided for
* replacing te entire array with another one. In addition to these methods,
* methods including 'forEach', 'map', 'every', 'some', 'indexOf',
* 'lastIndexOf', 'reduce', and 'reduceRight'.
*
* ObservableArray creation:
* It can be initialized by an ordinary array. If the array is not given, an
* empty is used by default.
*
* @example
* var observableArray = ObservableArray([1, 2, 3]);
*
* Events:
* Information regarding the operation is provided in the event detail. Please
* check the following example:
*
* @example
* var observableArray = ObservableArray([1, 2, 3]);
* var handler = {
* handleEvent: function(event) { console.log(JSON.stringify(event)); }
* }
* observableArray.addEventListener('insert', handler);
* observableArray.addEventListener('remove', handler);
* observableArray.addEventListener('replace', handler);
* observableArray.addEventListener('reset', handler);
*
* observableArray.push(4);
* // observableArray is [1, 2, 3, 4]
* // event.type is 'insert'
* // event.detail is {index: 3, count: 1, items: [4]}
*
* observableArray.pop();
* // observableArray is [1, 2, 3]
* // event.type is 'remove'
* // event.detail is {index: 3, count: 1, items: [4]}
*
* observableArray.set(2, 4);
* // observableArray is [1, 2, 4]
* // event.type is 'replace'
* // event.detail is {index: 2, oldValue: 3, newValue: 4}
*
* observableArray.reset([1, 2, 3]);
* // observableArray is [1, 2, 3]
* // event.type is 'reset'
* // event.detail is {items: [1, 2, 3]}
*
* observableArray.splice(2, 1, [4, 5, 6]);
* // observableArray is [1, 2, 4, 5, 6]
* // First event:
* // event.type is 'remove'
* // event.detail is {index: 2, count: 1, items: [3]}
* // Second event:
* // event.type is 'insert'
* // event.detail is {index: 2, count: 3, items: [4, 5, 6]}
*
* @module modules/mvvm/observable_array
*/
define(function(require) {
'use strict';
var Module = require('modules/base/module');
var EventEmitter = require('modules/base/event_emitter');
var Observable = require('modules/mvvm/observable');
var Events = ['insert', 'remove', 'replace', 'reset'];
var ReadOnlyMethods = ['forEach', 'map', 'every', 'some', 'indexOf',
'lastIndexOf', 'reduce', 'reduceRight'];
/**
* @class ObservableArray
* @requires module:modules/base/module
* @requires module:modules/base/event_emitter
* @requires module:modules/mvvm/observable
* @returns {ObservableArray}
*/
var ObservableArray = Module.create(function ObservableArray(array) {
this.super(Observable).call(this);
this.super(EventEmitter).call(this, Events);
this._array = array || [];
this._length = this._array.length;
}).extend(Observable, EventEmitter);
/**
* An observable property indicating the length of the array.
*
* @access public
* @type {Number}
*/
Observable.defineObservableProperty(ObservableArray.prototype, 'length', {
readonly: true
});
/**
* An observable property representing the inner array.
*
* @access public
* @type {Array}
*/
Observable.defineObservableProperty(ObservableArray.prototype, 'array', {
readonly: true
});
ReadOnlyMethods.forEach(function(op) {
ObservableArray.prototype[op] = function() {
return this._array[op].apply(this._array, arguments);
};
});
ObservableArray.prototype.push = function(item) {
this._array.push(item);
this._length = this._array.length;
this._emitEvent('insert', {
index: this._array.length - 1,
count: 1,
items: [item]
});
};
ObservableArray.prototype.pop = function() {
if (!this._array.length) {
return null;
}
var item = this._array.pop();
this._length = this._array.length;
this._emitEvent('remove', {
index: this._array.length,
count: 1,
items: [item]
});
return item;
};
ObservableArray.prototype.splice = function(index, count) {
if (arguments.length < 2) {
return [];
}
// Fix the arguments based on the behavior of the real spice function.
if (index >= this._length) {
index = this._length - 1;
} else if (index < 0) {
index = this._length + index;
}
if (count < 0) {
count = 0;
}
var addedItems = Array.prototype.slice.call(arguments, 2);
var removedItems = this._array.splice.apply(this._array, arguments);
this._length = this._array.length;
if (removedItems.length) {
this._emitEvent('remove', {
index: index,
count: count,
items: removedItems
});
}
if (addedItems.length) {
this._emitEvent('insert', {
index: index,
count: addedItems.length,
items: addedItems
});
}
return removedItems;
};
/**
* Set a value to the field of a specfied index.
*
* @access public
* @param {Number} index
* @param {Object} value
*/
ObservableArray.prototype.set = function(index, value) {
if (index < 0 || index >= this._array.length) {
this.throw('set: out of range');
}
var oldValue = this._array[index];
this._array[index] = value;
this._emitEvent('replace', {
index: index,
oldValue: oldValue,
newValue: value
});
};
/**
* Get the value from the field of a specfied index.
*
* @access public
* @param {Number} index
* @returns {Object}
*/
ObservableArray.prototype.get = function(index) {
return this._array[index];
};
/**
* Replace the entire array with another one.
*
* @access public
* @param {Array} array
*/
ObservableArray.prototype.reset = function(array) {
this._array = array || [];
this._length = this._array.length;
this._emitEvent('reset', {
items: this._array
});
};
return ObservableArray;
});