%PDF- %PDF-
| Direktori : /proc/self/root/data/old/home/stash/atlassian-stash/static/util/ |
| Current File : //proc/self/root/data/old/home/stash/atlassian-stash/static/util/bacon.js |
define('util/bacon', [
'aui',
'bacon',
'jquery',
'underscore',
'util/events',
'util/function',
'util/performance',
'exports'
], function(
AJS,
Bacon,
$,
_,
events,
fn,
performance,
exports
) {
'use strict';
/**
* Listens for Stash events and return them as a Bacon stream.
*
* @param name of the event
* @returns {Bacon<*>}
*/
exports.events = function on(name) {
return Bacon.fromBinder(function (sink) {
return events.chain().on(name, sink).destroy;
});
};
/**
* Listens for stash keyboard shortcuts and return the events as a Bacon stream.
* NOTE: This call needs to happen before onReady(), but after that consumers can register whenever they like.
*
* @param name of the shortcut defined in keyboard-shortcuts.xml.
* @param once if true don't subscribe to this stream - for testing only
* @returns {Bacon<Event>}
*/
exports.keyboardEvents = function keyboardEvents(name, once) {
var stream = Bacon.fromBinder(function (sink) {
var callbacks = [];
callbacks.push(events.chain().on('stash.keyboard.shortcuts.' + name, function(keys) {
// If it wasn't for 'this' we could flatMap on exports.events() :(
var type = this.execute ? this : AJS.whenIType(keys);
type.execute(sink);
// If 'once' is not set then we can never unbind, so no point having a small memory leak
if (once) {
callbacks.push(type.unbind);
}
}).destroy);
return _.partial(fn.applyAll, callbacks);
});
// Only for testing
if (!once) {
// Bacon needs at least one subscriber to start, and we need to subscribe to this event before the page loads
stream.onValue(AJS.$.noop);
}
return stream;
};
/**
* Split a stream into arrays based on a function callback.
* Note that this doesn't group all elements in the stream, just <i>adjacent</i> ones.
*
* https://github.com/baconjs/bacon.js/issues/144
*
* @param stream {Bacon}
* @param f {function} callback function that returns the value from the stream to be split on
* @returns {Bacon}
*/
exports.split = function split(stream, f) {
return Bacon.fromBinder(function (sink) {
var values = [];
var lastValue;
var callbacks = [];
callbacks.push(stream.onValue(function (value) {
var newValue = f(value);
if (lastValue && newValue !== lastValue) {
sink(values);
values = [];
}
lastValue = newValue;
values.push(value);
}));
// Flush any remaining values at the end of the stream so we don't miss anything
callbacks.push(stream.onEnd(function () {
if (values.length > 0) {
sink(values);
}
sink(new Bacon.End());
}));
return _.partial(fn.applyAll, callbacks);
});
};
/**
* Converts a bacon stream to an array (usually for testing).
*
* @param stream {Bacon}
* @returns {Array} of the all values in the stream
*/
exports.toArray = function toArray(stream) {
var values = [];
stream.onValue(function (value) {
values.push(value);
});
return values;
};
/**
* Returns a Bacon property that describes the window scroll position, with each element shaped like {{ left:number, top:number }}
*
* @type function
* @returns {Bacon.Property}
*/
exports.getWindowScrollProperty = _.once(function() {
var $window = $(window);
function getWindowScroll() {
return {
left : $window.scrollLeft(),
top : $window.scrollTop()
};
}
return Bacon.fromBinder(function(sink) {
var enqueue = performance.enqueueCapped(requestAnimationFrame, sink);
// ensure there is a trailing scroll event by debouncing an enqueue call
// by more than one animation frame
var debouncedEnqueue = _.debounce(enqueue, 20);
$window.on('scroll', enqueue).on('scroll', debouncedEnqueue);
return function() {
$window.off('scroll', enqueue).off('scroll', debouncedEnqueue);
};
})
.map(getWindowScroll)
.skipDuplicates(function(a, b) {
return a.left === b.left && a.top === b.top;
})
.toProperty(getWindowScroll());
});
/**
* Returns a Bacon property that describes the window size, with each element shaped like {{ width:number, height:number }}
*
* @type function
* @returns {Bacon.Property}
*/
exports.getWindowSizeProperty = _.once(function() {
var $window = $(window);
return Bacon.fromBinder(function(sink) {
var boundEvents = events.chain().on('window.resize', function (w, h) {
sink(new Bacon.Next({
width : w,
height : h
}));
});
return function() {
boundEvents.destroy();
};
}).toProperty({
width : $window.width(),
height : $window.height()
});
});
/**
* Given a Bacon stream, take all the elements that occur between the first occurrence of two elements.
*
* If start and end are the same element, we return 0 or 1 element, depending in whether start/end is found in the stream
* and depending on whether `inclusive` is set for either one.
*
* @param {Bacon.EventStream} stream - the stream to filter
* @param {Object} options - describes the limits
* @param {*} options.start - the starting element
* @param {*} options.end - the ending element
* @param {boolean} [options.startInclusive=false] - whether the start element should be included in the resulting stream
* @param {boolean} [options.endInclusive=false] - whether the end element should be included in the resulting stream
* @param {Function} [options.equals] - a function to use for equality comparison. Default is ===
* @returns {Bacon.EventStream}
*/
exports.takeBetween = function takeBetween(stream, options) {
var start = options.start;
var end = options.end;
var startInclusive = options.startInclusive;
var endInclusive = options.endInclusive;
var equals = options.equals || function(a, b) { return a === b; };
if (equals(start, end)) {
if (startInclusive || endInclusive) {
return stream.skipWhile(function(item) {
return !equals(item, start);
}).take(1);
}
return Bacon.never();
}
var foundStart, foundEnd;
return stream.skipWhile(function(item) {
if (foundEnd || foundStart) {
return false;
}
if (equals(item, start)) {
foundStart = true;
return !startInclusive;
}
if (equals(item, end)) {
foundEnd = true;
return !endInclusive;
}
return true;
}).takeWhile(function(item) {
if (equals(item, start)) {
foundStart = true;
return startInclusive;
}
if (equals(item, end)) {
foundEnd = true;
return endInclusive;
}
return !(foundEnd && foundStart);
});
};
});