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,242 @@
'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.data;
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.unobserve(_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.observe('insert', _handleEvent);
_observableArray.observe('remove', _handleEvent);
_observableArray.observe('replace', _handleEvent);
_observableArray.observe('reset', _handleEvent);
if (this.enabled) {
_reset(_observableArray.array);
}
},
destroy: function() {
// unobserve from array and null everything out
if (_observableArray) {
_observableArray.unobserve(_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,102 @@
'use strict';
define(function() {
function unobserve(_eventHandlers, prop, handler) {
// arguments in reverse order to support .bind(handler) for the
// unbind from all case
function removeHandler(handler, prop) {
var handlers = _eventHandlers[prop];
if (!handlers) {
return;
}
var index = handlers.indexOf(handler);
if (index >= 0) {
handlers.splice(index, 1);
}
}
if (typeof prop === 'function') {
// (handler) -- remove from every key in _eventHandlers
Object.keys(_eventHandlers).forEach(removeHandler.bind(null, prop));
} else if (handler) {
// (prop, handler) -- remove handler from the specific prop
removeHandler(handler, prop);
} else if (prop in _eventHandlers) {
// (prop) -- otherwise remove all handlers for property
_eventHandlers[prop] = [];
}
}
/*
* An Observable is able to notify its property change. It is initialized by
* an ordinary object.
*/
function Observable(obj) {
var _eventHandlers = {};
var _observable = {
observe: function o_observe(p, handler) {
/*
* We should check if _observable[_p] exists. Since _observable[_p] is
* created along with _eventHandlers[p], here we simply check
* _eventHandlers[p].
*/
var handlers = _eventHandlers[p];
if (handlers) {
handlers.push(handler);
}
},
/**
* unobserve([prop], handler) - remove handler from observeable callbacks
*/
unobserve: unobserve.bind(null, _eventHandlers)
};
var _getFunctionTemplate = function(p) {
return function() {
return _observable['_' + p];
};
};
var _setFunctionTemplate = function(p) {
return function(value) {
var oldValue = _observable['_' + p];
if (oldValue !== value) {
_observable['_' + p] = value;
var handlers = _eventHandlers[p];
handlers.forEach(function(handler) {
handler(value, oldValue);
});
}
};
};
/*
* Iterate all properties in the object and create corresponding getter and
* setter for them.
*/
for (var p in obj) {
// If p is a function, simply add it to the observable.
if (typeof obj[p] === 'function') {
_observable[p] = obj[p];
continue;
}
_eventHandlers[p] = [];
Object.defineProperty(_observable, '_' + p, {
value: obj[p],
writable: true
});
Object.defineProperty(_observable, p, {
enumerable: true,
get: _getFunctionTemplate(p),
set: _setFunctionTemplate(p)
});
}
return _observable;
}
return Observable;
});

View file

@ -0,0 +1,153 @@
'use strict';
define(function() {
function unobserve(_eventHandlers, prop, handler) {
// arguments in reverse order to support .bind(handler) for the
// unbind from all case
function removeHandler(handler, prop) {
var handlers = _eventHandlers[prop];
if (!handlers) {
return;
}
var index = handlers.indexOf(handler);
if (index >= 0) {
handlers.splice(index, 1);
}
}
if (typeof prop === 'function') {
// (handler) -- remove from every key in _eventHandlers
Object.keys(_eventHandlers).forEach(removeHandler.bind(null, prop));
} else if (handler) {
// (prop, handler) -- remove handler from the specific prop
removeHandler(handler, prop);
} else if (prop in _eventHandlers) {
// (prop) -- otherwise remove all handlers for property
_eventHandlers[prop] = [];
}
}
/*
* An ObservableArray is able to notify its change through four basic
* operations including 'insert', 'remove', 'replace', 'reset'. It is
* initialized by an ordinary array.
*/
function ObservableArray(array) {
var _array = array || [];
var _eventHandlers = {
'insert': [],
'remove': [],
'replace': [],
'reset': []
};
var _notify = function(eventType, data) {
var handlers = _eventHandlers[eventType];
handlers.forEach(function(handler) {
handler({
type: eventType,
data: data
});
});
};
return {
get length() {
return _array.length;
},
get array() {
return _array;
},
forEach: function oa_foreach(func) {
_array.forEach(func);
},
observe: function oa_observe(eventType, handler) {
var handlers = _eventHandlers[eventType];
if (handlers) {
handlers.push(handler);
}
},
unobserve: unobserve.bind(null, _eventHandlers),
push: function oa_push(item) {
_array.push(item);
_notify('insert', {
index: _array.length - 1,
count: 1,
items: [item]
});
},
pop: function oa_pop() {
if (!_array.length) {
return;
}
var item = _array.pop();
_notify('remove', {
index: _array.length,
count: 1
});
return item;
},
splice: function oa_splice(index, count) {
if (arguments.length < 2) {
return;
}
var addedItems = Array.prototype.slice.call(arguments, 2);
_array.splice.apply(_array, arguments);
if (count) {
_notify('remove', {
index: index,
count: count
});
}
if (addedItems.length) {
_notify('insert', {
index: index,
count: addedItems.length,
items: addedItems
});
}
},
set: function oa_set(index, value) {
if (index < 0 || index >= _array.length) {
return;
}
var oldValue = _array[index];
_array[index] = value;
_notify('replace', {
index: index,
oldValue: oldValue,
newValue: value
});
},
get: function oa_get(index) {
return _array[index];
},
reset: function oa_reset(array) {
_array = array;
_notify('reset', {
items: _array
});
}
};
}
return ObservableArray;
});