%PDF- %PDF-
Direktori : /proc/985914/root/data/old/home/stash/stash/atlassian-stash/static/util/ |
Current File : //proc/985914/root/data/old/home/stash/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 {string} name - of the shortcut defined in keyboard-shortcuts.xml. * @param {boolean=} 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() }; } var windowScroll = 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()); // This is a hack to ensure that the window scroll property continues to update when when things are not // listening to it. This ensure that the page wont jump around when you switch between things that are // controlled by the browsers native scrolling and fake scrolling. windowScroll.onValue($.noop); return windowScroll; }); /** * 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); var windowSizeProperty = 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() }); // This is a hack to ensure that the window size property continues to update when when things are not // listening to it. This ensure that the page wont jump around when you switch between things that are // controlled by the browsers native scrolling and fake scrolling. windowSizeProperty.onValue($.noop); return windowSizeProperty; }); /** * 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); }); }; });