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,35 @@
define(function(require, exports, module) {
'use strict';
module.exports = function animatedScroll(opts) {
var {container, content, scrollTop} = opts;
var maxScroll = container.scrollHeight - container.clientHeight;
// safeguard against negative or `null` scrollTop (0 < scrollTop < maxScroll)
scrollTop = Math.min(Math.max(scrollTop, 0), maxScroll);
var diff = container.scrollTop - scrollTop;
var seconds = Math.abs(diff) / 500;
// hide scrollbar during animation to avoid errors introduced by user trying
// to scroll while transition is still running; it also avoids weird
// sensation of content moving while scrollbar is still at same position
container.style.overflowY = 'hidden';
window.requestAnimationFrame(() => {
content.style.transform = 'translateY(' + diff + 'px)';
// easeOutQuart borrowed from http://matthewlein.com/ceaser/
content.style.transition = 'transform ' + seconds + 's ' +
'cubic-bezier(0.165, 0.840, 0.440, 1.000)';
});
content.addEventListener('transitionend', function setScrollTop() {
content.removeEventListener('transitionend', setScrollTop);
content.style.transform = '';
content.style.transition = '';
container.scrollTop = scrollTop;
container.style.overflowY = 'scroll';
});
};
});

View file

@ -0,0 +1,29 @@
define(function(require, exports) {
'use strict';
// right now the calendar app only displays 8 different colors so it's a good
// idea to memoize the results of hexToBackground to avoid calculating it for
// each busytime
var memoized = {};
exports.hexToBackground = function(hex) {
if (!(hex in memoized)) {
// we need 20% opacity for background; it's simpler to use rgba than to
// create a new layer and set opacity:20%
var {r, g, b} = hexToChannels(hex);
memoized[hex] = `rgba(${r}, ${g}, ${b}, 0.2)`;
}
return memoized[hex];
};
function hexToChannels(hex) {
var val = parseInt(hex.replace(/#/, ''), 16);
return {
r: val >> 16,
g: val >> 8 & 0xFF,
b: val & 0xFF
};
}
});

View file

@ -0,0 +1,61 @@
define(function(require, exports) {
'use strict';
/**
* Gets the element absolute offset top relative to the window top.
*/
exports.absoluteOffsetTop = function(el) {
var top = 0;
while (el) {
var pos = window.getComputedStyle(el).position;
if (pos === 'absolute' || pos === 'relative') {
top += el.offsetTop;
}
el = el.parentElement;
}
return top;
};
/**
* Gets the closest (parent) element that matches the given selector.
*/
exports.closest = function(el, selector) {
while (el) {
if (matches(el, selector)) {
return el;
}
el = el.parentElement;
}
};
function matches(el, selector) {
// "matches" is only unprefixed on Fx 34
return 'matches' in el ?
el.matches(selector) :
el.mozMatchesSelector(selector);
}
/**
* converts HTML string into a DOM element
*/
exports.buildElement = function(html) {
var fragment = document.createElement('div');
fragment.innerHTML = html;
return fragment.firstChild;
};
/**
* detach elements from DOM
* supports single element or array-like objects
*/
exports.removeElements = function(els) {
if (!els) {
return;
}
els = 'length' in els ? Array.from(els) : Array.from(arguments);
els.forEach(el => {
el && el.parentNode && el.parentNode.removeChild(el);
});
};
});

View file

@ -0,0 +1,93 @@
// these methods are all borrowed from MOUT.js (released under MIT license)
define(function(require, exports) {
'use strict';
// TODO: add MOUT.js as a dependency!!!! this is just a temporary solution
exports.norm = function norm(val, min, max){
// 0 / 0 === NaN
if (val === min && min === max) {
return 1;
}
return (val - min) / (max - min);
};
exports.clamp = function clamp(val, min, max) {
return val < min ? min : (val > max ? max : val);
};
exports.round = function round(value, radix){
radix = radix || 1; // default round 1
return Math.round(value / radix) * radix;
};
exports.floor = function floor(val, step) {
step = Math.abs(step || 1);
return Math.floor(val / step) * step;
};
exports.ceil = function ceil(val, step) {
step = Math.abs(step || 1);
return Math.ceil(val / step) * step;
};
exports.lerp = function lerp(ratio, start, end){
return start + (end - start) * ratio;
};
exports.debounce = function debounce(fn, threshold, isAsap){
var timeout, result;
function debounced(){
//jshint -W040
var args = arguments, context = this;
//jshint +W040
function delayed(){
if (! isAsap) {
result = fn.apply(context, args);
}
timeout = null;
}
if (timeout) {
clearTimeout(timeout);
} else if (isAsap) {
result = fn.apply(context, args);
}
timeout = setTimeout(delayed, threshold);
return result;
}
debounced.cancel = function(){
clearTimeout(timeout);
};
return debounced;
};
exports.throttle = function throttle(fn, delay) {
var context, timeout, result, args,
diff,
prevCall = 0;
function delayed() {
prevCall = Date.now();
timeout = null;
result = fn.apply(context, args);
}
function throttled() {
//jshint -W040
context = this;
//jshint +W040
args = arguments;
diff = delay - (Date.now() - prevCall);
if (diff <= 0) {
clearTimeout(timeout);
delayed();
} else if (!timeout) {
timeout = setTimeout(delayed, diff);
}
return result;
}
throttled.cancel = function() {
clearTimeout(timeout);
};
return throttled;
};
});

View file

@ -0,0 +1,135 @@
/**
* Conflict manager
*/
define(function(require, exports, module) {
'use strict';
/**
* Module dependencies
*/
var ConflictSpan = require('conflict_span');
var IntervalTree = require('interval_tree');
var Timespan = require('common/timespan');
function Overlap() {
this.reset();
}
module.exports = Overlap;
Overlap.prototype = {
reset: function() {
this.tree = new IntervalTree();
this.conflicts = {};
this.elements = {};
},
add: function(myBusytime, element) {
this.tree.add(myBusytime);
this.elements[myBusytime._id] = element;
// Check for conflicts, bail if none
var related = this._findRelated(myBusytime);
if (0 === related.length) {
return;
}
var myID = myBusytime._id;
var myCS = this.conflicts[myID];
var self = this;
related.forEach(function(otherBusytime) {
// Get the other's ID, skip the current
var otherID = otherBusytime._id;
if (otherID === myID) {
return;
}
var otherCS = self.conflicts[otherID];
if (!myCS && !otherCS) {
// This is a brand new conflict.
myCS = new ConflictSpan(self);
myCS.add(myBusytime).add(otherBusytime);
} else if (myCS && !otherCS) {
// Other time can join this one's existing span
myCS.add(otherBusytime);
} else if (!myCS && otherCS) {
// This time can join the other's existing span
myCS = otherCS.add(myBusytime);
} else if (myCS && otherCS && myCS != otherCS) {
// Both already in different spans, so absorb other into this
myCS.absorb(otherCS);
}
});
},
/**
* Remove a busytime from the collection.
* Unlike other methods you must pass a real
* busytime object.
*
* @param {Object} busytime full busytime object.
*/
remove: function(busytime) {
this._clearLayout(busytime);
this.tree.remove(busytime);
delete this.elements[busytime._id];
var myID = busytime._id;
var myCS = this.conflicts[myID];
if (myCS) {
myCS.remove(busytime);
}
},
/**
* Get the ConflictSpan associated with this busytime, if any.
*
* @param {Object|String} busytime id or busytime object.
* @return {Object} associated ConflictSpan, if any.
*/
getConflictSpan: function(busytime) {
var id = this._busytimeId(busytime);
return this.conflicts[id];
},
/**
* @param {Object|String} busytime id or busytime object.
* @return {HTMLElement} associated dom element.
*/
getElement: function(busytime) {
var id = this._busytimeId(busytime);
return this.elements[id];
},
/** private */
_busytimeId: function(busytime) {
return (typeof(busytime) === 'string') ? busytime : busytime._id;
},
/**
* Search tree for busytimes that overlap with the given.
*/
_findRelated: function(busytime) {
//XXX: this is bad encapsulation but
// we generate these when we insert
// the points in the tree.
var span = new Timespan(busytime._startDateMS, busytime._endDateMS);
return this.tree.query(span);
},
/**
* Clear the layout from a busytime element, presumably because it has just
* been removed from conflict.
*
* @param {Object} busytime full busytime object.
*/
_clearLayout: function(busytime) {
var el = this.elements[busytime._id];
el.style.width = '';
el.style.left = '';
el.classList.remove('has-overlaps', 'many-overlaps');
}
};
});

View file

@ -0,0 +1,58 @@
define(function(require, exports) {
'use strict';
/**
* Get the port of the url.
* @param {string} url full url.
* @return {?number} port number, null if none found.
*/
exports.getPort = function(url) {
var parts = url.split(':');
if (parts.length < 2) {
return null;
}
// If we found a port and path, it's the last part
var candidate = parts[parts.length - 1];
parts = candidate.split('/');
// If we found a port, it's the first part
candidate = parts[0];
if (!isInteger(candidate)) {
return null;
}
return parseInt(candidate, 10);
};
/**
* Get the scheme of the url. Note that this only detects http and https.
* @param {string} url full url.
* @return {?string} uri scheme (ie http), null if none found.
*/
exports.getScheme = function(url) {
var parts = url.split(':');
if (parts.length < 2) {
return null;
}
// If we found a scheme, it's the first part
var candidate = parts[0];
if (candidate !== 'http' && candidate !== 'https') {
return null;
}
return candidate;
};
/**
* Decide whether or not this string represents an integer.
* @param {string} str some string.
* @param {boolean} whether or not str represents an integer.
*/
function isInteger(str) {
return (/^\d+$/).test(str);
}
});