<commit_before>/*
 * L.Control.Layers is a control to allow users to switch between different layers on the map.
 */

L.Control.Layers = L.Control.extend({
	options: {
		collapsed: true,
		position: 'topright',
		autoZIndex: true,
		hideSingleBase: true
	},

	initialize: function (baseLayers, overlays, options) {
		L.setOptions(this, options);

		this._layers = {};
		this._lastZIndex = 0;
		this._handlingClick = false;

		for (var i in baseLayers) {
			this._addLayer(baseLayers[i], i);
		}

		for (i in overlays) {
			this._addLayer(overlays[i], i, true);
		}
	},

	onAdd: function () {
		this._initLayout();
		this._update();

		return this._container;
	},

	addBaseLayer: function (layer, name) {
		this._addLayer(layer, name);
		return this._update();
	},

	addOverlay: function (layer, name) {
		this._addLayer(layer, name, true);
		return this._update();
	},

	removeLayer: function (layer) {
		layer.off('add remove', this._onLayerChange, this);

		delete this._layers[L.stamp(layer)];
		return this._update();
	},

	_initLayout: function () {
		var className = 'leaflet-control-layers',
		    container = this._container = L.DomUtil.create('div', className);

		// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
		container.setAttribute('aria-haspopup', true);

		if (!L.Browser.touch) {
			L.DomEvent
				.disableClickPropagation(container)
				.disableScrollPropagation(container);
		} else {
			L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
		}

		var form = this._form = L.DomUtil.create('form', className + '-list');

		if (this.options.collapsed) {
			if (!L.Browser.android) {
				L.DomEvent.on(container, {
					mouseenter: this._expand,
					mouseleave: this._collapse
				}, this);
			}

			var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
			link.href = '#';
			link.title = 'Layers';

			if (L.Browser.touch) {
				L.DomEvent
				    .on(link, 'click', L.DomEvent.stop)
				    .on(link, 'click', this._expand, this);
			} else {
				L.DomEvent.on(link, 'focus', this._expand, this);
			}

			// work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
			L.DomEvent.on(form, 'click', function () {
				setTimeout(L.bind(this._onInputClick, this), 0);
			}, this);

			this._map.on('click', this._collapse, this);
			// TODO keyboard accessibility
		} else {
			this._expand();
		}

		this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
		this._separator = L.DomUtil.create('div', className + '-separator', form);
		this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);

		container.appendChild(form);
	},

	_addLayer: function (layer, name, overlay) {
		layer.on('add remove', this._onLayerChange, this);

		var id = L.stamp(layer);

		this._layers[id] = {
			layer: layer,
			name: name,
			overlay: overlay
		};

		if (this.options.autoZIndex && layer.setZIndex) {
			this._lastZIndex++;
			layer.setZIndex(this._lastZIndex);
		}
	},

	_update: function () {
		if (!this._container) { return; }

		L.DomUtil.empty(this._baseLayersList);
		L.DomUtil.empty(this._overlaysList);

		var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;

		for (i in this._layers) {
			obj = this._layers[i];
			this._addItem(obj);
			overlaysPresent = overlaysPresent || obj.overlay;
			baseLayersPresent = baseLayersPresent || !obj.overlay;
			baseLayersCount += !obj.overlay ? 1 : 0;
		}

		// Hide base layers section if there's only one layer.
		if (this.options.hideSingleBase) {
			baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
			this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
		}

		this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';

		return this;
	},

	_onLayerChange: function (e) {
		if (!this._handlingClick) {
			this._update();
		}

		var overlay = this._layers[L.stamp(e.target)].overlay;

		var type = overlay ?
			(e.type === 'add' ? 'overlayadd' : 'overlayremove') :
			(e.type === 'add' ? 'baselayerchange' : null);

		if (type) {
			this._map.fire(type, e.target);
		}
	},

	// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
	_createRadioElement: function (name, checked) {

		var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
				name + '"' + (checked ? ' checked="checked"' : '') + '/>';

		var radioFragment = document.createElement('div');
		radioFragment.innerHTML = radioHtml;

		return radioFragment.firstChild;
	},

	_addItem: function (obj) {
		var label = document.createElement('label'),
		    checked = this._map.hasLayer(obj.layer),
		    input;

		if (obj.overlay) {
			input = document.createElement('input');
			input.type = 'checkbox';
			input.className = 'leaflet-control-layers-selector';
			input.defaultChecked = checked;
		} else {
			input = this._createRadioElement('leaflet-base-layers', checked);
		}

		input.layerId = L.stamp(obj.layer);

		L.DomEvent.on(input, 'click', this._onInputClick, this);

		var name = document.createElement('span');
		name.innerHTML = ' ' + obj.name;

		label.appendChild(input);
		label.appendChild(name);

		var container = obj.overlay ? this._overlaysList : this._baseLayersList;
		container.appendChild(label);

		return label;
	},

	_onInputClick: function () {
		var inputs = this._form.getElementsByTagName('input'),
		    input, layer, hasLayer;
		var addedLayers = [],
		    removedLayers = [];

		this._handlingClick = true;

		for (var i = 0, len = inputs.length; i < len; i++) {
			input = inputs[i];
			layer = this._layers[input.layerId].layer;
			hasLayer = this._map.hasLayer(layer);

			if (input.checked && !hasLayer) {
				addedLayers.push(layer);

			} else if (!input.checked && hasLayer) {
				removedLayers.push(layer);
			}
		}

		// Bugfix issue 2318: Should remove all old layers before readding new ones
		for (i = 0; i < removedLayers.length; i++) {
			this._map.removeLayer(removedLayers[i]);
		}
		for (i = 0; i < addedLayers.length; i++) {
			this._map.addLayer(addedLayers[i]);
		}

		this._handlingClick = false;

		this._refocusOnMap();
	},

	_expand: function () {
		L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
	},

	_collapse: function () {
		L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
	}
});

L.control.layers = function (baseLayers, overlays, options) {
	return new L.Control.Layers(baseLayers, overlays, options);
};
<commit_msg>Disabled by default so it doesn't break existing apps.<commit_after>/*
 * L.Control.Layers is a control to allow users to switch between different layers on the map.
 */

L.Control.Layers = L.Control.extend({
	options: {
		collapsed: true,
		position: 'topright',
		autoZIndex: true,
		hideSingleBase: false
	},

	initialize: function (baseLayers, overlays, options) {
		L.setOptions(this, options);

		this._layers = {};
		this._lastZIndex = 0;
		this._handlingClick = false;

		for (var i in baseLayers) {
			this._addLayer(baseLayers[i], i);
		}

		for (i in overlays) {
			this._addLayer(overlays[i], i, true);
		}
	},

	onAdd: function () {
		this._initLayout();
		this._update();

		return this._container;
	},

	addBaseLayer: function (layer, name) {
		this._addLayer(layer, name);
		return this._update();
	},

	addOverlay: function (layer, name) {
		this._addLayer(layer, name, true);
		return this._update();
	},

	removeLayer: function (layer) {
		layer.off('add remove', this._onLayerChange, this);

		delete this._layers[L.stamp(layer)];
		return this._update();
	},

	_initLayout: function () {
		var className = 'leaflet-control-layers',
		    container = this._container = L.DomUtil.create('div', className);

		// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
		container.setAttribute('aria-haspopup', true);

		if (!L.Browser.touch) {
			L.DomEvent
				.disableClickPropagation(container)
				.disableScrollPropagation(container);
		} else {
			L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
		}

		var form = this._form = L.DomUtil.create('form', className + '-list');

		if (this.options.collapsed) {
			if (!L.Browser.android) {
				L.DomEvent.on(container, {
					mouseenter: this._expand,
					mouseleave: this._collapse
				}, this);
			}

			var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
			link.href = '#';
			link.title = 'Layers';

			if (L.Browser.touch) {
				L.DomEvent
				    .on(link, 'click', L.DomEvent.stop)
				    .on(link, 'click', this._expand, this);
			} else {
				L.DomEvent.on(link, 'focus', this._expand, this);
			}

			// work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
			L.DomEvent.on(form, 'click', function () {
				setTimeout(L.bind(this._onInputClick, this), 0);
			}, this);

			this._map.on('click', this._collapse, this);
			// TODO keyboard accessibility
		} else {
			this._expand();
		}

		this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
		this._separator = L.DomUtil.create('div', className + '-separator', form);
		this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);

		container.appendChild(form);
	},

	_addLayer: function (layer, name, overlay) {
		layer.on('add remove', this._onLayerChange, this);

		var id = L.stamp(layer);

		this._layers[id] = {
			layer: layer,
			name: name,
			overlay: overlay
		};

		if (this.options.autoZIndex && layer.setZIndex) {
			this._lastZIndex++;
			layer.setZIndex(this._lastZIndex);
		}
	},

	_update: function () {
		if (!this._container) { return; }

		L.DomUtil.empty(this._baseLayersList);
		L.DomUtil.empty(this._overlaysList);

		var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;

		for (i in this._layers) {
			obj = this._layers[i];
			this._addItem(obj);
			overlaysPresent = overlaysPresent || obj.overlay;
			baseLayersPresent = baseLayersPresent || !obj.overlay;
			baseLayersCount += !obj.overlay ? 1 : 0;
		}

		// Hide base layers section if there's only one layer.
		if (this.options.hideSingleBase) {
			baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
			this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
		}

		this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';

		return this;
	},

	_onLayerChange: function (e) {
		if (!this._handlingClick) {
			this._update();
		}

		var overlay = this._layers[L.stamp(e.target)].overlay;

		var type = overlay ?
			(e.type === 'add' ? 'overlayadd' : 'overlayremove') :
			(e.type === 'add' ? 'baselayerchange' : null);

		if (type) {
			this._map.fire(type, e.target);
		}
	},

	// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
	_createRadioElement: function (name, checked) {

		var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
				name + '"' + (checked ? ' checked="checked"' : '') + '/>';

		var radioFragment = document.createElement('div');
		radioFragment.innerHTML = radioHtml;

		return radioFragment.firstChild;
	},

	_addItem: function (obj) {
		var label = document.createElement('label'),
		    checked = this._map.hasLayer(obj.layer),
		    input;

		if (obj.overlay) {
			input = document.createElement('input');
			input.type = 'checkbox';
			input.className = 'leaflet-control-layers-selector';
			input.defaultChecked = checked;
		} else {
			input = this._createRadioElement('leaflet-base-layers', checked);
		}

		input.layerId = L.stamp(obj.layer);

		L.DomEvent.on(input, 'click', this._onInputClick, this);

		var name = document.createElement('span');
		name.innerHTML = ' ' + obj.name;

		label.appendChild(input);
		label.appendChild(name);

		var container = obj.overlay ? this._overlaysList : this._baseLayersList;
		container.appendChild(label);

		return label;
	},

	_onInputClick: function () {
		var inputs = this._form.getElementsByTagName('input'),
		    input, layer, hasLayer;
		var addedLayers = [],
		    removedLayers = [];

		this._handlingClick = true;

		for (var i = 0, len = inputs.length; i < len; i++) {
			input = inputs[i];
			layer = this._layers[input.layerId].layer;
			hasLayer = this._map.hasLayer(layer);

			if (input.checked && !hasLayer) {
				addedLayers.push(layer);

			} else if (!input.checked && hasLayer) {
				removedLayers.push(layer);
			}
		}

		// Bugfix issue 2318: Should remove all old layers before readding new ones
		for (i = 0; i < removedLayers.length; i++) {
			this._map.removeLayer(removedLayers[i]);
		}
		for (i = 0; i < addedLayers.length; i++) {
			this._map.addLayer(addedLayers[i]);
		}

		this._handlingClick = false;

		this._refocusOnMap();
	},

	_expand: function () {
		L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
	},

	_collapse: function () {
		L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
	}
});

L.control.layers = function (baseLayers, overlays, options) {
	return new L.Control.Layers(baseLayers, overlays, options);
};
<|endoftext|>


<commit_before>/***
!StyleSheet
.private {
	background-color: #FF00FF;
}

.public {
	background-color: #00FFFF;
}
!Code
***/
//{{{
(function($) {

// hijack displayTiddler to add private/public class to story tiddlers
var _displayTiddler = Story.prototype.displayTiddler;
Story.prototype.displayTiddler = function(srcElement, tiddler, template,
	animate, unused, customFields, toggle, animationSrc) {
	var el = _displayTiddler.apply(this, arguments);
	if(!(tiddler instanceof Tiddler)) {
		tiddler = store.getTiddler(tiddler);
	}
	if(tiddler) {
		var container = tiddler.fields["server.bag"];
		if(!container) { // new tiddler
			var workspace = tiddler.fields["server.workspace"];
			container = workspace ? workspace.split("/")[1] : null;
		}
		var type = container ? container.split("_")[1] : null;
		if(type) { // TODO: explicitly check for private/public!?
			$(el).addClass(type);
		}
	}
};

config.shadowTiddlers.StyleSheetTiddlySpace = store.getTiddlerText(tiddler.title + "##StyleSheet");
store.addNotification("StyleSheetTiddlySpace", refreshStyles);

})(jQuery);
//}}}
<commit_msg>visualization: added type "external", changed styling<commit_after>/***
!StyleSheet
.public,
.private,
.external {
	border-left: 5px solid #A2F7A2;
}

.public {
	border-color: #C7EAFF;
}

.private {
	border-color: #FFCAE9;
}
!Code
***/
//{{{
(function($) {

var determineSpace = function(container_name) { // TODO: should be globally available
	var container = container_name.split("_"); // XXX: brittle (space name must not contain underscores)
	if(container.length == 1) {
		return false;
	} else {
		return {
			name: container[0],
			type: container[1]
		};
	}
};

// determine current space -- TODO: should be cached globally!?
var workspace = config.defaultCustomFields["server.workspace"];
var container = workspace ? workspace.split("/")[1] : null;
var current_space = container ? determineSpace(container): null;

// hijack displayTiddler to add private/public class to story tiddlers
var _displayTiddler = Story.prototype.displayTiddler;
Story.prototype.displayTiddler = function(srcElement, tiddler, template,
	animate, unused, customFields, toggle, animationSrc) {
	var el = _displayTiddler.apply(this, arguments);
	if(!(tiddler instanceof Tiddler)) {
		tiddler = store.getTiddler(tiddler);
	}
	if(tiddler) {
		var container = tiddler.fields["server.bag"];
		if(!container) { // new tiddler
			var workspace = tiddler.fields["server.workspace"];
			container = workspace ? workspace.split("/")[1] : null;
		}
		if(container) {
			var space = determineSpace(container);
			var type = space && space.name == current_space.name ? space.type : "external";
			$(el).addClass(type);
		}
	}
};

config.shadowTiddlers.StyleSheetTiddlySpace = store.getTiddlerText(tiddler.title + "##StyleSheet");
store.addNotification("StyleSheetTiddlySpace", refreshStyles);

})(jQuery);
//}}}
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var chai = require('chai');
chai.use(require('chai-datetime'));
global.expect = expect;
<commit_msg>travis<commit_after>var expect = require('chai').expect;
global.expect = expect;
<|endoftext|>


<commit_before>import { removeItems } from '../utils';
import DisplayObject from './DisplayObject';

/**
 * A Container represents a collection of display objects.
 * It is the base class of all display objects that act as a container for other objects.
 *
 *```js
 * let container = new PIXI.Container();
 * container.addChild(sprite);
 * ```
 *
 * @class
 * @extends PIXI.DisplayObject
 * @memberof PIXI
 */
export default class Container extends DisplayObject
{
    /**
     *
     */
    constructor()
    {
        super();

        /**
         * The array of children of this container.
         *
         * @member {PIXI.DisplayObject[]}
         * @readonly
         */
        this.children = [];
    }

    /**
     * Overridable method that can be used by Container subclasses whenever the children array is modified
     *
     * @private
     */
    onChildrenChange()
    {
        /* empty */
    }

    /**
     * Adds one or more children to the container.
     *
     * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)`
     *
     * @param {...PIXI.DisplayObject} child - The DisplayObject(s) to add to the container
     * @return {PIXI.DisplayObject} The first child that was added.
     */
    addChild(child)
    {
        const argumentsLength = arguments.length;

        // if there is only one argument we can bypass looping through the them
        if (argumentsLength > 1)
        {
            // loop through the arguments property and add all children
            // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes
            for (let i = 0; i < argumentsLength; i++)
            {
                this.addChild(arguments[i]);
            }
        }
        else
        {
            // if the child has a parent then lets remove it as Pixi objects can only exist in one place
            if (child.parent)
            {
                child.parent.removeChild(child);
            }

            child.parent = this;

            // ensure a transform will be recalculated..
            this.transform._parentID = -1;
            this._boundsID++;

            this.children.push(child);

            // TODO - lets either do all callbacks or all events.. not both!
            this.onChildrenChange(this.children.length - 1);
            child.emit('added', this);
        }

        return child;
    }

    /**
     * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown
     *
     * @param {PIXI.DisplayObject} child - The child to add
     * @param {number} index - The index to place the child in
     * @return {PIXI.DisplayObject} The child that was added.
     */
    addChildAt(child, index)
    {
        if (index < 0 || index > this.children.length)
        {
            throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`);
        }

        if (child.parent)
        {
            child.parent.removeChild(child);
        }

        child.parent = this;

        this.children.splice(index, 0, child);

        // TODO - lets either do all callbacks or all events.. not both!
        this.onChildrenChange(index);
        child.emit('added', this);

        return child;
    }

    /**
     * Swaps the position of 2 Display Objects within this container.
     *
     * @param {PIXI.DisplayObject} child - First display object to swap
     * @param {PIXI.DisplayObject} child2 - Second display object to swap
     */
    swapChildren(child, child2)
    {
        if (child === child2)
        {
            return;
        }

        const index1 = this.getChildIndex(child);
        const index2 = this.getChildIndex(child2);

        this.children[index1] = child2;
        this.children[index2] = child;
        this.onChildrenChange(index1 < index2 ? index1 : index2);
    }

    /**
     * Returns the index position of a child DisplayObject instance
     *
     * @param {PIXI.DisplayObject} child - The DisplayObject instance to identify
     * @return {number} The index position of the child display object to identify
     */
    getChildIndex(child)
    {
        const index = this.children.indexOf(child);

        if (index === -1)
        {
            throw new Error('The supplied DisplayObject must be a child of the caller');
        }

        return index;
    }

    /**
     * Changes the position of an existing child in the display object container
     *
     * @param {PIXI.DisplayObject} child - The child DisplayObject instance for which you want to change the index number
     * @param {number} index - The resulting index number for the child display object
     */
    setChildIndex(child, index)
    {
        if (index < 0 || index >= this.children.length)
        {
            throw new Error('The supplied index is out of bounds');
        }

        const currentIndex = this.getChildIndex(child);

        removeItems(this.children, currentIndex, 1); // remove from old position
        this.children.splice(index, 0, child); // add at new position
        this.onChildrenChange(index);
    }

    /**
     * Returns the child at the specified index
     *
     * @param {number} index - The index to get the child at
     * @return {PIXI.DisplayObject} The child at the given index, if any.
     */
    getChildAt(index)
    {
        if (index < 0 || index >= this.children.length)
        {
            throw new Error(`getChildAt: Index (${index}) does not exist.`);
        }

        return this.children[index];
    }

    /**
     * Removes one or more children from the container.
     *
     * @param {...PIXI.DisplayObject} child - The DisplayObject(s) to remove
     * @return {PIXI.DisplayObject} The first child that was removed.
     */
    removeChild(child)
    {
        const argumentsLength = arguments.length;

        // if there is only one argument we can bypass looping through the them
        if (argumentsLength > 1)
        {
            // loop through the arguments property and add all children
            // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes
            for (let i = 0; i < argumentsLength; i++)
            {
                this.removeChild(arguments[i]);
            }
        }
        else
        {
            const index = this.children.indexOf(child);

            if (index === -1) return null;

            child.parent = null;
            removeItems(this.children, index, 1);

            // ensure a transform will be recalculated..
            this.transform._parentID = -1;
            this._boundsID++;

            // TODO - lets either do all callbacks or all events.. not both!
            this.onChildrenChange(index);
            child.emit('removed', this);
        }

        return child;
    }

    /**
     * Removes a child from the specified index position.
     *
     * @param {number} index - The index to get the child from
     * @return {PIXI.DisplayObject} The child that was removed.
     */
    removeChildAt(index)
    {
        const child = this.getChildAt(index);

        child.parent = null;
        removeItems(this.children, index, 1);

        // TODO - lets either do all callbacks or all events.. not both!
        this.onChildrenChange(index);
        child.emit('removed', this);

        return child;
    }

    /**
     * Removes all children from this container that are within the begin and end indexes.
     *
     * @param {number} [beginIndex=0] - The beginning position.
     * @param {number} [endIndex=this.children.length] - The ending position. Default value is size of the container.
     * @returns {DisplayObject[]} List of removed children
     */
    removeChildren(beginIndex = 0, endIndex)
    {
        const begin = beginIndex;
        const end = typeof endIndex === 'number' ? endIndex : this.children.length;
        const range = end - begin;
        let removed;

        if (range > 0 && range <= end)
        {
            removed = this.children.splice(begin, range);

            for (let i = 0; i < removed.length; ++i)
            {
                removed[i].parent = null;
            }

            this.onChildrenChange(beginIndex);

            for (let i = 0; i < removed.length; ++i)
            {
                removed[i].emit('removed', this);
            }

            return removed;
        }
        else if (range === 0 && this.children.length === 0)
        {
            return [];
        }

        throw new RangeError('removeChildren: numeric values are outside the acceptable range.');
    }

    /**
     * Updates the transform on all children of this container for rendering
     *
     * @private
     */
    updateTransform()
    {
        this._boundsID++;

        this.transform.updateTransform(this.parent.transform);

        // TODO: check render flags, how to process stuff here
        this.worldAlpha = this.alpha * this.parent.worldAlpha;

        for (let i = 0, j = this.children.length; i < j; ++i)
        {
            const child = this.children[i];

            if (child.visible)
            {
                child.updateTransform();
            }
        }
    }

    /**
     * Recalculates the bounds of the container.
     *
     */
    calculateBounds()
    {
        this._bounds.clear();

        this._calculateBounds();

        for (let i = 0; i < this.children.length; i++)
        {
            const child = this.children[i];

            if (!child.visible || !child.renderable)
            {
                continue;
            }

            child.calculateBounds();

            // TODO: filter+mask, need to mask both somehow
            if (child._mask)
            {
                child._mask.calculateBounds();
                this._bounds.addBoundsMask(child._bounds, child._mask._bounds);
            }
            else if (child.filterArea)
            {
                this._bounds.addBoundsArea(child._bounds, child.filterArea);
            }
            else
            {
                this._bounds.addBounds(child._bounds);
            }
        }

        this._lastBoundsID = this._boundsID;
    }

    /**
     * Recalculates the bounds of the object. Override this to
     * calculate the bounds of the specific object (not including children).
     *
     */
    _calculateBounds()
    {
        // FILL IN//
    }

    /**
     * Renders the object using the WebGL renderer
     *
     * @param {PIXI.WebGLRenderer} renderer - The renderer
     */
    renderWebGL(renderer)
    {
        // if the object is not visible or the alpha is 0 then no need to render this element
        if (!this.visible || this.worldAlpha <= 0 || !this.renderable)
        {
            return;
        }

        // do a quick check to see if this element has a mask or a filter.
        if (this._mask || this._filters)
        {
            this.renderAdvancedWebGL(renderer);
        }
        else
        {
            this._renderWebGL(renderer);

            // simple render children!
            for (let i = 0, j = this.children.length; i < j; ++i)
            {
                this.children[i].renderWebGL(renderer);
            }
        }
    }

    /**
     * Render the object using the WebGL renderer and advanced features.
     *
     * @private
     * @param {PIXI.WebGLRenderer} renderer - The renderer
     */
    renderAdvancedWebGL(renderer)
    {
        renderer.currentRenderer.flush();

        const filters = this._filters;
        const mask = this._mask;

        // push filter first as we need to ensure the stencil buffer is correct for any masking
        if (filters)
        {
            if (!this._enabledFilters)
            {
                this._enabledFilters = [];
            }

            this._enabledFilters.length = 0;

            for (let i = 0; i < filters.length; i++)
            {
                if (filters[i].enabled)
                {
                    this._enabledFilters.push(filters[i]);
                }
            }

            if (this._enabledFilters.length)
            {
                renderer.filterManager.pushFilter(this, this._enabledFilters);
            }
        }

        if (mask)
        {
            renderer.maskManager.pushMask(this, this._mask);
        }

        renderer.currentRenderer.start();

        // add this object to the batch, only rendered if it has a texture.
        this._renderWebGL(renderer);

        // now loop through the children and make sure they get rendered
        for (let i = 0, j = this.children.length; i < j; i++)
        {
            this.children[i].renderWebGL(renderer);
        }

        renderer.currentRenderer.flush();

        if (mask)
        {
            renderer.maskManager.popMask(this, this._mask);
        }

        if (filters && this._enabledFilters && this._enabledFilters.length)
        {
            renderer.filterManager.popFilter();
        }

        renderer.currentRenderer.start();
    }

    /**
     * To be overridden by the subclasses.
     *
     * @private
     * @param {PIXI.WebGLRenderer} renderer - The renderer
     */
    _renderWebGL(renderer) // eslint-disable-line no-unused-vars
    {
        // this is where content itself gets rendered...
    }

    /**
     * To be overridden by the subclass
     *
     * @private
     * @param {PIXI.CanvasRenderer} renderer - The renderer
     */
    _renderCanvas(renderer) // eslint-disable-line no-unused-vars
    {
        // this is where content itself gets rendered...
    }

    /**
     * Renders the object using the Canvas renderer
     *
     * @param {PIXI.CanvasRenderer} renderer - The renderer
     */
    renderCanvas(renderer)
    {
        // if not visible or the alpha is 0 then no need to render this
        if (!this.visible || this.worldAlpha <= 0 || !this.renderable)
        {
            return;
        }

        if (this._mask)
        {
            renderer.maskManager.pushMask(this._mask);
        }

        this._renderCanvas(renderer);
        for (let i = 0, j = this.children.length; i < j; ++i)
        {
            this.children[i].renderCanvas(renderer);
        }

        if (this._mask)
        {
            renderer.maskManager.popMask(renderer);
        }
    }

    /**
     * Removes all internal references and listeners as well as removes children from the display list.
     * Do not use a Container after calling `destroy`.
     *
     * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options
     *  have been set to that value
     * @param {boolean} [options.children=false] - if set to true, all the children will have their destroy
     *  method called as well. 'options' will be passed on to those calls.
     */
    destroy(options)
    {
        super.destroy();

        const destroyChildren = typeof options === 'boolean' ? options : options && options.children;

        const oldChildren = this.removeChildren(0, this.children.length);

        if (destroyChildren)
        {
            for (let i = 0; i < oldChildren.length; ++i)
            {
                oldChildren[i].destroy(options);
            }
        }
    }

    /**
     * The width of the Container, setting this will actually modify the scale to achieve the value set
     *
     * @member {number}
     * @memberof PIXI.Container#
     */
    get width()
    {
        return this.scale.x * this.getLocalBounds().width;
    }

    /**
     * Sets the width of the container by modifying the scale.
     *
     * @param {number} value - The value to set to.
     */
    set width(value)
    {
        const width = this.getLocalBounds().width;

        if (width !== 0)
        {
            this.scale.x = value / width;
        }
        else
        {
            this.scale.x = 1;
        }

        this._width = value;
    }

    /**
     * The height of the Container, setting this will actually modify the scale to achieve the value set
     *
     * @member {number}
     * @memberof PIXI.Container#
     */
    get height()
    {
        return this.scale.y * this.getLocalBounds().height;
    }

    /**
     * Sets the height of the container by modifying the scale.
     *
     * @param {number} value - The value to set to.
     */
    set height(value)
    {
        const height = this.getLocalBounds().height;

        if (height !== 0)
        {
            this.scale.y = value / height;
        }
        else
        {
            this.scale.y = 1;
        }

        this._height = value;
    }
}

// performance increase to avoid using call.. (10x faster)
Container.prototype.containerUpdateTransform = Container.prototype.updateTransform;
<commit_msg>fix a small typo in container.js (#3250)<commit_after>import { removeItems } from '../utils';
import DisplayObject from './DisplayObject';

/**
 * A Container represents a collection of display objects.
 * It is the base class of all display objects that act as a container for other objects.
 *
 *```js
 * let container = new PIXI.Container();
 * container.addChild(sprite);
 * ```
 *
 * @class
 * @extends PIXI.DisplayObject
 * @memberof PIXI
 */
export default class Container extends DisplayObject
{
    /**
     *
     */
    constructor()
    {
        super();

        /**
         * The array of children of this container.
         *
         * @member {PIXI.DisplayObject[]}
         * @readonly
         */
        this.children = [];
    }

    /**
     * Overridable method that can be used by Container subclasses whenever the children array is modified
     *
     * @private
     */
    onChildrenChange()
    {
        /* empty */
    }

    /**
     * Adds one or more children to the container.
     *
     * Multiple items can be added like so: `myContainer.addChild(thingOne, thingTwo, thingThree)`
     *
     * @param {...PIXI.DisplayObject} child - The DisplayObject(s) to add to the container
     * @return {PIXI.DisplayObject} The first child that was added.
     */
    addChild(child)
    {
        const argumentsLength = arguments.length;

        // if there is only one argument we can bypass looping through the them
        if (argumentsLength > 1)
        {
            // loop through the arguments property and add all children
            // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes
            for (let i = 0; i < argumentsLength; i++)
            {
                this.addChild(arguments[i]);
            }
        }
        else
        {
            // if the child has a parent then lets remove it as Pixi objects can only exist in one place
            if (child.parent)
            {
                child.parent.removeChild(child);
            }

            child.parent = this;

            // ensure a transform will be recalculated..
            this.transform._parentID = -1;
            this._boundsID++;

            this.children.push(child);

            // TODO - lets either do all callbacks or all events.. not both!
            this.onChildrenChange(this.children.length - 1);
            child.emit('added', this);
        }

        return child;
    }

    /**
     * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown
     *
     * @param {PIXI.DisplayObject} child - The child to add
     * @param {number} index - The index to place the child in
     * @return {PIXI.DisplayObject} The child that was added.
     */
    addChildAt(child, index)
    {
        if (index < 0 || index > this.children.length)
        {
            throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`);
        }

        if (child.parent)
        {
            child.parent.removeChild(child);
        }

        child.parent = this;

        this.children.splice(index, 0, child);

        // TODO - lets either do all callbacks or all events.. not both!
        this.onChildrenChange(index);
        child.emit('added', this);

        return child;
    }

    /**
     * Swaps the position of 2 Display Objects within this container.
     *
     * @param {PIXI.DisplayObject} child - First display object to swap
     * @param {PIXI.DisplayObject} child2 - Second display object to swap
     */
    swapChildren(child, child2)
    {
        if (child === child2)
        {
            return;
        }

        const index1 = this.getChildIndex(child);
        const index2 = this.getChildIndex(child2);

        this.children[index1] = child2;
        this.children[index2] = child;
        this.onChildrenChange(index1 < index2 ? index1 : index2);
    }

    /**
     * Returns the index position of a child DisplayObject instance
     *
     * @param {PIXI.DisplayObject} child - The DisplayObject instance to identify
     * @return {number} The index position of the child display object to identify
     */
    getChildIndex(child)
    {
        const index = this.children.indexOf(child);

        if (index === -1)
        {
            throw new Error('The supplied DisplayObject must be a child of the caller');
        }

        return index;
    }

    /**
     * Changes the position of an existing child in the display object container
     *
     * @param {PIXI.DisplayObject} child - The child DisplayObject instance for which you want to change the index number
     * @param {number} index - The resulting index number for the child display object
     */
    setChildIndex(child, index)
    {
        if (index < 0 || index >= this.children.length)
        {
            throw new Error('The supplied index is out of bounds');
        }

        const currentIndex = this.getChildIndex(child);

        removeItems(this.children, currentIndex, 1); // remove from old position
        this.children.splice(index, 0, child); // add at new position
        this.onChildrenChange(index);
    }

    /**
     * Returns the child at the specified index
     *
     * @param {number} index - The index to get the child at
     * @return {PIXI.DisplayObject} The child at the given index, if any.
     */
    getChildAt(index)
    {
        if (index < 0 || index >= this.children.length)
        {
            throw new Error(`getChildAt: Index (${index}) does not exist.`);
        }

        return this.children[index];
    }

    /**
     * Removes one or more children from the container.
     *
     * @param {...PIXI.DisplayObject} child - The DisplayObject(s) to remove
     * @return {PIXI.DisplayObject} The first child that was removed.
     */
    removeChild(child)
    {
        const argumentsLength = arguments.length;

        // if there is only one argument we can bypass looping through the them
        if (argumentsLength > 1)
        {
            // loop through the arguments property and add all children
            // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes
            for (let i = 0; i < argumentsLength; i++)
            {
                this.removeChild(arguments[i]);
            }
        }
        else
        {
            const index = this.children.indexOf(child);

            if (index === -1) return null;

            child.parent = null;
            removeItems(this.children, index, 1);

            // ensure a transform will be recalculated..
            this.transform._parentID = -1;
            this._boundsID++;

            // TODO - lets either do all callbacks or all events.. not both!
            this.onChildrenChange(index);
            child.emit('removed', this);
        }

        return child;
    }

    /**
     * Removes a child from the specified index position.
     *
     * @param {number} index - The index to get the child from
     * @return {PIXI.DisplayObject} The child that was removed.
     */
    removeChildAt(index)
    {
        const child = this.getChildAt(index);

        child.parent = null;
        removeItems(this.children, index, 1);

        // TODO - lets either do all callbacks or all events.. not both!
        this.onChildrenChange(index);
        child.emit('removed', this);

        return child;
    }

    /**
     * Removes all children from this container that are within the begin and end indexes.
     *
     * @param {number} [beginIndex=0] - The beginning position.
     * @param {number} [endIndex=this.children.length] - The ending position. Default value is size of the container.
     * @returns {DisplayObject[]} List of removed children
     */
    removeChildren(beginIndex = 0, endIndex)
    {
        const begin = beginIndex;
        const end = typeof endIndex === 'number' ? endIndex : this.children.length;
        const range = end - begin;
        let removed;

        if (range > 0 && range <= end)
        {
            removed = this.children.splice(begin, range);

            for (let i = 0; i < removed.length; ++i)
            {
                removed[i].parent = null;
            }

            this.onChildrenChange(beginIndex);

            for (let i = 0; i < removed.length; ++i)
            {
                removed[i].emit('removed', this);
            }

            return removed;
        }
        else if (range === 0 && this.children.length === 0)
        {
            return [];
        }

        throw new RangeError('removeChildren: numeric values are outside the acceptable range.');
    }

    /**
     * Updates the transform on all children of this container for rendering
     *
     * @private
     */
    updateTransform()
    {
        this._boundsID++;

        this.transform.updateTransform(this.parent.transform);

        // TODO: check render flags, how to process stuff here
        this.worldAlpha = this.alpha * this.parent.worldAlpha;

        for (let i = 0, j = this.children.length; i < j; ++i)
        {
            const child = this.children[i];

            if (child.visible)
            {
                child.updateTransform();
            }
        }
    }

    /**
     * Recalculates the bounds of the container.
     *
     */
    calculateBounds()
    {
        this._bounds.clear();

        this._calculateBounds();

        for (let i = 0; i < this.children.length; i++)
        {
            const child = this.children[i];

            if (!child.visible || !child.renderable)
            {
                continue;
            }

            child.calculateBounds();

            // TODO: filter+mask, need to mask both somehow
            if (child._mask)
            {
                child._mask.calculateBounds();
                this._bounds.addBoundsMask(child._bounds, child._mask._bounds);
            }
            else if (child.filterArea)
            {
                this._bounds.addBoundsArea(child._bounds, child.filterArea);
            }
            else
            {
                this._bounds.addBounds(child._bounds);
            }
        }

        this._lastBoundsID = this._boundsID;
    }

    /**
     * Recalculates the bounds of the object. Override this to
     * calculate the bounds of the specific object (not including children).
     *
     */
    _calculateBounds()
    {
        // FILL IN//
    }

    /**
     * Renders the object using the WebGL renderer
     *
     * @param {PIXI.WebGLRenderer} renderer - The renderer
     */
    renderWebGL(renderer)
    {
        // if the object is not visible or the alpha is 0 then no need to render this element
        if (!this.visible || this.worldAlpha <= 0 || !this.renderable)
        {
            return;
        }

        // do a quick check to see if this element has a mask or a filter.
        if (this._mask || this._filters)
        {
            this.renderAdvancedWebGL(renderer);
        }
        else
        {
            this._renderWebGL(renderer);

            // simple render children!
            for (let i = 0, j = this.children.length; i < j; ++i)
            {
                this.children[i].renderWebGL(renderer);
            }
        }
    }

    /**
     * Render the object using the WebGL renderer and advanced features.
     *
     * @private
     * @param {PIXI.WebGLRenderer} renderer - The renderer
     */
    renderAdvancedWebGL(renderer)
    {
        renderer.currentRenderer.flush();

        const filters = this._filters;
        const mask = this._mask;

        // push filter first as we need to ensure the stencil buffer is correct for any masking
        if (filters)
        {
            if (!this._enabledFilters)
            {
                this._enabledFilters = [];
            }

            this._enabledFilters.length = 0;

            for (let i = 0; i < filters.length; i++)
            {
                if (filters[i].enabled)
                {
                    this._enabledFilters.push(filters[i]);
                }
            }

            if (this._enabledFilters.length)
            {
                renderer.filterManager.pushFilter(this, this._enabledFilters);
            }
        }

        if (mask)
        {
            renderer.maskManager.pushMask(this, this._mask);
        }

        renderer.currentRenderer.start();

        // add this object to the batch, only rendered if it has a texture.
        this._renderWebGL(renderer);

        // now loop through the children and make sure they get rendered
        for (let i = 0, j = this.children.length; i < j; i++)
        {
            this.children[i].renderWebGL(renderer);
        }

        renderer.currentRenderer.flush();

        if (mask)
        {
            renderer.maskManager.popMask(this, this._mask);
        }

        if (filters && this._enabledFilters && this._enabledFilters.length)
        {
            renderer.filterManager.popFilter();
        }

        renderer.currentRenderer.start();
    }

    /**
     * To be overridden by the subclasses.
     *
     * @private
     * @param {PIXI.WebGLRenderer} renderer - The renderer
     */
    _renderWebGL(renderer) // eslint-disable-line no-unused-vars
    {
        // this is where content itself gets rendered...
    }

    /**
     * To be overridden by the subclass
     *
     * @private
     * @param {PIXI.CanvasRenderer} renderer - The renderer
     */
    _renderCanvas(renderer) // eslint-disable-line no-unused-vars
    {
        // this is where content itself gets rendered...
    }

    /**
     * Renders the object using the Canvas renderer
     *
     * @param {PIXI.CanvasRenderer} renderer - The renderer
     */
    renderCanvas(renderer)
    {
        // if not visible or the alpha is 0 then no need to render this
        if (!this.visible || this.worldAlpha <= 0 || !this.renderable)
        {
            return;
        }

        if (this._mask)
        {
            renderer.maskManager.pushMask(this._mask);
        }

        this._renderCanvas(renderer);
        for (let i = 0, j = this.children.length; i < j; ++i)
        {
            this.children[i].renderCanvas(renderer);
        }

        if (this._mask)
        {
            renderer.maskManager.popMask(renderer);
        }
    }

    /**
     * Removes all internal references and listeners as well as removes children from the display list.
     * Do not use a Container after calling `destroy`.
     *
     * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options
     *  have been set to that value
     * @param {boolean} [options.children=false] - if set to true, all the children will have their destroy
     *  method called as well. 'options' will be passed on to those calls.
     */
    destroy(options)
    {
        super.destroy();

        const destroyChildren = typeof options === 'boolean' ? options : options && options.children;

        const oldChildren = this.removeChildren(0, this.children.length);

        if (destroyChildren)
        {
            for (let i = 0; i < oldChildren.length; ++i)
            {
                oldChildren[i].destroy(options);
            }
        }
    }

    /**
     * The width of the Container, setting this will actually modify the scale to achieve the value set
     *
     * @member {number}
     * @memberof PIXI.Container#
     */
    get width()
    {
        return this.scale.x * this.getLocalBounds().width;
    }

    /**
     * Sets the width of the container by modifying the scale.
     *
     * @param {number} value - The value to set to.
     */
    set width(value)
    {
        const width = this.getLocalBounds().width;

        if (width !== 0)
        {
            this.scale.x = value / width;
        }
        else
        {
            this.scale.x = 1;
        }

        this._width = value;
    }

    /**
     * The height of the Container, setting this will actually modify the scale to achieve the value set
     *
     * @member {number}
     * @memberof PIXI.Container#
     */
    get height()
    {
        return this.scale.y * this.getLocalBounds().height;
    }

    /**
     * Sets the height of the container by modifying the scale.
     *
     * @param {number} value - The value to set to.
     */
    set height(value)
    {
        const height = this.getLocalBounds().height;

        if (height !== 0)
        {
            this.scale.y = value / height;
        }
        else
        {
            this.scale.y = 1;
        }

        this._height = value;
    }
}

// performance increase to avoid using call.. (10x faster)
Container.prototype.containerUpdateTransform = Container.prototype.updateTransform;
<|endoftext|>


<commit_before>import test from 'ava';
import { Observable } from 'rxjs/Rx';
import State from '../src/state';

const OBSERVABLE_TIMEOUT = 100;

test('get and set', t => {
  const state = new State();
  state.set('a.b', { c: { d: 1 } });

  t.deepEqual(state.get(), { a: { b: { c: { d: 1 } } } });
  t.deepEqual(state.get(''), { a: { b: { c: { d: 1 } } } });
  t.deepEqual(state.get('a'), { b: { c: { d: 1 } } });
  t.deepEqual(state.get('a.b'), { c: { d: 1 } });
  t.deepEqual(state.get('a.b.c'), { d: 1 });
  t.deepEqual(state.get('a.b.c.d'), 1);
});

test('undefined', t => {
  const state = new State();
  t.is(state.get(), undefined);
  t.is(state.get('not.fount'), undefined);
});

test('cursor get', t => {
  const state = new State();
  state.set({
    a: {
      b: {
        c: 1,
      },
    },
  });
  t.is(state.cursor().get(), state.get());
  t.is(state.cursor('a').get(), state.get('a'));
  t.is(state.cursor('a').get('b'), state.get('a.b'));
  t.is(state.cursor('a').cursor('b').get(), state.get('a.b'));
  t.true(state.cursor('a') instanceof State);
});

test('cursor set', t => {
  const state = new State();
  const cursorState = state.cursor('a').cursor('b');
  cursorState.set({ c: { d: 1 } });
  cursorState.set('c.e', 2);
  t.deepEqual(state.get(), { a: { b: { c: { d: 1, e: 2 } } } });
});

test('event emit', t => {
  t.plan(4);
  const state = new State();
  process.nextTick(() => {
    // should emit
    state.set('', 1);
    state.set('a', 1);
    state.set('a.b', 1);
    // should not emit
    state.set('c.d', 1);
  });
  return Observable
    .from(state.listen('change'))
    .map(() => t.pass())
    .timeoutWith(OBSERVABLE_TIMEOUT, Observable.empty());
});

test('event emit with cursor', t => {
  t.plan(4);
  const state = new State();
  process.nextTick(() => {
    // should emit
    state.set('', 1);
    state.set('a', 1);
    state.cursor('a').set('c', 1);
    // should not emit
    state.set('a.b', 1);
    state.set('c.d', 1);
  });
  return Observable
    .from(state.cursor('a').listen('change'))
    .map(() => t.pass())
    .timeoutWith(OBSERVABLE_TIMEOUT, Observable.empty());
});
<commit_msg>fix comment mistask<commit_after>import test from 'ava';
import { Observable } from 'rxjs/Rx';
import State from '../src/state';

const OBSERVABLE_TIMEOUT = 100;

test('get and set', t => {
  const state = new State();
  state.set('a.b', { c: { d: 1 } });

  t.deepEqual(state.get(), { a: { b: { c: { d: 1 } } } });
  t.deepEqual(state.get(''), { a: { b: { c: { d: 1 } } } });
  t.deepEqual(state.get('a'), { b: { c: { d: 1 } } });
  t.deepEqual(state.get('a.b'), { c: { d: 1 } });
  t.deepEqual(state.get('a.b.c'), { d: 1 });
  t.deepEqual(state.get('a.b.c.d'), 1);
});

test('undefined', t => {
  const state = new State();
  t.is(state.get(), undefined);
  t.is(state.get('not.fount'), undefined);
});

test('cursor get', t => {
  const state = new State();
  state.set({
    a: {
      b: {
        c: 1,
      },
    },
  });
  t.is(state.cursor().get(), state.get());
  t.is(state.cursor('a').get(), state.get('a'));
  t.is(state.cursor('a').get('b'), state.get('a.b'));
  t.is(state.cursor('a').cursor('b').get(), state.get('a.b'));
  t.true(state.cursor('a') instanceof State);
});

test('cursor set', t => {
  const state = new State();
  const cursorState = state.cursor('a').cursor('b');
  cursorState.set({ c: { d: 1 } });
  cursorState.set('c.e', 2);
  t.deepEqual(state.get(), { a: { b: { c: { d: 1, e: 2 } } } });
});

test('event emit', t => {
  t.plan(4);
  const state = new State();
  process.nextTick(() => {
    // should emit
    state.set('', 1);
    state.set('a', 1);
    state.set('a.b', 1);
    // should not emit
    state.set('c.d', 1);
  });
  return Observable
    .from(state.listen('change'))
    .map(() => t.pass())
    .timeoutWith(OBSERVABLE_TIMEOUT, Observable.empty());
});

test('event emit with cursor', t => {
  t.plan(4);
  const state = new State();
  process.nextTick(() => {
    // should emit
    state.set('', 1);
    state.set('a', 1);
    state.set('a.b', 1);
    state.cursor('a').set('c', 1);
    // should not emit
    state.set('c.d', 1);
  });
  return Observable
    .from(state.cursor('a').listen('change'))
    .map(() => t.pass())
    .timeoutWith(OBSERVABLE_TIMEOUT, Observable.empty());
});
<|endoftext|>


<commit_before>//partStream:
//quadruplets?
//mesh(bin),source, params , metadata,

import {
  addEntityInstances$,
  updateInstance$,
  deleteInstances$,
  duplicateInstances$,
  deleteAllInstances$,
  selectEntities$
   } from './actions'

import {exists} from '../../utils/obsUtils'
import {mergeData} from '../../utils/modelUtils'

import logger from '../../utils/log'
let log = logger("entities")
log.setLevel("info")

import Rx from 'rx'
let fromEvent = Rx.Observable.fromEvent
let Observable = Rx.Observable
let merge = Rx.Observable.merge

import {generateUUID} from 'usco-kernel2/src/utils'
import {toArray} from '../../utils/utils'

///defaults, what else ?
const defaults = {
  instances:[],
  //secondary storage of instances, for (flat) faster/simpler access
  byId:{}
}

/*
let partInstance = {
    name: data.name,
    iuid: generateUUID(),
    typeUid: data.typeUid,
    color: "#07a9ff",
    pos: [ 0, 0, h/2 ],
    rot: [ 0, 0, 0 ],
    sca: [ 1, 1, 1 ],
    bbox:data.bbox
}*/

//just experimenting with thoughts about component based system
let partComponents = []
function makeCoreComponent(typeUid){
  return  Rx.Observable.just({
    name: data.name,
    iuid: generateUUID(),
    typeUid: typeUid,
    color: "#07a9ff"
  })
}

function makeTransformComponent(){
  return  Rx.Observable.just({
    pos: [ 0, 0, 0 ],
    rot: [ 0, 0, 0 ],
    sca: [ 1, 1, 1 ]
  })
}

function makeBoundingComponent(){
  return Rx.Observable.just({
    min:[0,0,0],
    max:[0,0,0]
  })
}

let bounds$ = makeBoundingComponent()
let transforms$ = makeTransformComponent()
let combo = Rx.Observable.combineLatest(
  transforms$
  ,bounds$,function(transforms,bounds){
    return {
      transforms
      ,bounds}
  })

///helper functions
function reduceToIuidBasedHash(instances){
  return instances.reduce(function(prev,cur){
      prev[cur.iuid] = cur
      return prev
    },{})
}

/*add a new entity instance*/
function addInstances(state, {input,settings}){
  log.info("adding entity instance(s)", input)

  let entities = toArray(input)

  let instances = state.instances.concat(entities)
  let byId = reduceToIuidBasedHash(instances)

  state = mergeData( state, {instances, byId} )

  //set selections, if need be
  if(settings.autoSelectNewEntities){
    //FIXME !! issue with ordering of things: at this stage, state is not updated !!
    //selectEntities$( entities.map(i=>i.iuid) )
  }
  return state
}

/*remove an entity : it actually only
  removes it from the active assembly
*/
function deleteInstance(state, input){
  log.info("removing entities ", input)

  let instances = state.instances
    .filter( entity => (input.indexOf(entity.iuid)===-1) )
  let byId = reduceToIuidBasedHash(instances)

  state = mergeData( state, {instances, byId} )

  return state
}

/*delete all entities from current entities*/
function deleteAllInstances(state, input){
  log.info("removing all entities ")

  state = mergeData( state, {instances:[], byId:{} } )

  return state
}

//replace all existing data with new one: can be used in case of undo redos, loading etc
function replaceInstances(state, input){
  log.info("replacing entities data with",input)

  state = mergeData( state, input )

  return state
}

/*set entites properties*/
function updateInstances(state, input){
  log.info("updating entities with data", input)

  if(!input) return state
  let newData = toArray(input)

  let updatedEntities = newData
    .filter(e=>e.iuids !== undefined)
    .map(function(entry){
      let iuid = entry.iuids
      let tgtEntity = state.byId[iuid]
      tgtEntity = mergeData(tgtEntity, entry)
      return tgtEntity
    })
    .reduce(function(prev,cur){
      prev[cur.iuid] = cur
      return prev
    },{})

  let byId = mergeData(state.byId, updatedEntities)
  let instances = Object.keys(byId).map(key=>byId[key])

  state = mergeData( state, {instances, byId} )

  return state
}

/*create duplicates of given entities*/
function duplicateInstances(state, {input,settings}){
  log.info("duplicating entity instances", input)

  let sources = toArray(input)

  function duplicate(original){
    let doNotCopy = ["iuid","name"]
    let onlyCopy = ["cid","pos","rot","sca","color","typeUid"]

    let dupe = {
      iuid:generateUUID()
    }
    for(let key in original ){
      if( onlyCopy.indexOf( key ) > -1 ){
        dupe[key] = JSON.parse(JSON.stringify(original[key]))
      }
    }
    //FIXME : needs to work with all entity types
    dupe.name = original.name + ""
    return dupe
  }

  let dupes = sources.map(duplicate)

  let instances = state.instances.concat(dupes)
  let byId = reduceToIuidBasedHash(instances)
  state = mergeData( state, {instances, byId} )

  //set selections, if need be
  //if(settings.autoSelectNewEntities) selectEntities$( dupes.map(i=>i.iuid) )

  return state
}

//////

function makeModification$(intent){

  /*TODO: implement*/
  /*let addEntityInstanceTo$ = intent.addEntityInstanceTo$
    ( instance , parent){
    let parent = parent || null
  }*/

  let _createInstance$ = intent.createInstance$
    .map((data) => (entitiesData) => {
    })

  let _addInstances$ = intent.addInstances$
    .filter(exists)
    //splice in settings
    .withLatestFrom(intent.settings$,function(data,settings){
      return {nentities:data,settings}
    })
    .map(({nentities,settings}) => (entitiesData) => {
      return addInstances(entitiesData,{input:nentities,settings})
    })

  let _updateInstance$ = intent.updateInstance$
    .map((nData) => (entitiesData) => {
      return updateInstances(entitiesData,nData)
    })

  let _deleteInstances$ = intent.deleteInstances$
    .map((remEntitites) => (entitiesData) => {
      return deleteInstance(entitiesData, remEntitites)
    })
    .do( selectEntities$([]) ) //set selections

  let _deleteAllInstances$ = intent.deleteAllInstances$
    .map(() => (entitiesData) => {
      return deleteAllInstances(entitiesData)
    })
    .do( selectEntities$([]) ) //set selections

  let _duplicateInstances$  = intent.duplicateInstances$
    //splice in settings
    .withLatestFrom(intent.settings$,function(data,settings){
      return {sources:data,settings}
    })
    .map(({sources,settings}) => (entitiesData) => {
      return duplicateInstances(entitiesData, {input:sources,settings})
    })

  let _replaceAll$ = intent.replaceAll$
    .map((newData) => (existingData) => {
      return replaceInstances(entitiesData, newData)
    })

  //FIXME : in parts ?
  /*let foo$ = new Rx.Subject()
  let rescaleBBox$ = foo$
    .map((newData) => (existingData) => {
      //let rescaleVertices = require('rescale-vertices')
    })*/

  return merge(
    _addInstances$
    ,_updateInstance$
    ,_deleteInstances$
    ,_deleteAllInstances$
    ,_duplicateInstances$

    ,_createInstance$
    ,_replaceAll$
  )
}

function entities(intent, source) {
  let source$ = source || Observable.just(defaults)
  let modification$ = makeModification$(intent)

  return modification$
    .merge(source$)
    .scan((entityData, modFn) => modFn(entityData))//combine existing data with new one
    //.distinctUntilChanged()
    .shareReplay(1)
}

export default entities

//just an idea: listing of available actions
let availableActions = {
  "delete"   :deleteInstances$
  ,"deleteAll":deleteAllInstances$
  ,"duplicate":duplicateInstances$
}

export {availableActions}<commit_msg>chore(entities) removed duplicate "component - based" code<commit_after>//partStream:
//quadruplets?
//mesh(bin),source, params , metadata,

import {
  addEntityInstances$,
  updateInstance$,
  deleteInstances$,
  duplicateInstances$,
  deleteAllInstances$,
  selectEntities$
   } from './actions'

import {exists} from '../../utils/obsUtils'
import {mergeData} from '../../utils/modelUtils'

import logger from '../../utils/log'
let log = logger("entities")
log.setLevel("info")

import Rx from 'rx'
let fromEvent = Rx.Observable.fromEvent
let Observable = Rx.Observable
let merge = Rx.Observable.merge

import {generateUUID} from 'usco-kernel2/src/utils'
import {toArray} from '../../utils/utils'

///defaults, what else ?
const defaults = {
  instances:[],
  //secondary storage of instances, for (flat) faster/simpler access
  byId:{}
}

///helper functions
function reduceToIuidBasedHash(instances){
  return instances.reduce(function(prev,cur){
      prev[cur.iuid] = cur
      return prev
    },{})
}

/*add a new entity instance*/
function addInstances(state, {input,settings}){
  log.info("adding entity instance(s)", input)

  let entities = toArray(input)

  let instances = state.instances.concat(entities)
  let byId = reduceToIuidBasedHash(instances)

  state = mergeData( state, {instances, byId} )

  //set selections, if need be
  if(settings.autoSelectNewEntities){
    //FIXME !! issue with ordering of things: at this stage, state is not updated !!
    //selectEntities$( entities.map(i=>i.iuid) )
  }
  return state
}

/*remove an entity : it actually only
  removes it from the active assembly
*/
function deleteInstance(state, input){
  log.info("removing entities ", input)

  let instances = state.instances
    .filter( entity => (input.indexOf(entity.iuid)===-1) )
  let byId = reduceToIuidBasedHash(instances)

  state = mergeData( state, {instances, byId} )

  return state
}

/*delete all entities from current entities*/
function deleteAllInstances(state, input){
  log.info("removing all entities ")

  state = mergeData( state, {instances:[], byId:{} } )

  return state
}

//replace all existing data with new one: can be used in case of undo redos, loading etc
function replaceInstances(state, input){
  log.info("replacing entities data with",input)

  state = mergeData( state, input )

  return state
}

/*set entites properties*/
function updateInstances(state, input){
  log.info("updating entities with data", input)

  if(!input) return state
  let newData = toArray(input)

  let updatedEntities = newData
    .filter(e=>e.iuids !== undefined)
    .map(function(entry){
      let iuid = entry.iuids
      let tgtEntity = state.byId[iuid]
      tgtEntity = mergeData(tgtEntity, entry)
      return tgtEntity
    })
    .reduce(function(prev,cur){
      prev[cur.iuid] = cur
      return prev
    },{})

  let byId = mergeData(state.byId, updatedEntities)
  let instances = Object.keys(byId).map(key=>byId[key])

  state = mergeData( state, {instances, byId} )

  return state
}

/*create duplicates of given entities*/
function duplicateInstances(state, {input,settings}){
  log.info("duplicating entity instances", input)

  let sources = toArray(input)

  function duplicate(original){
    let doNotCopy = ["iuid","name"]
    let onlyCopy = ["cid","pos","rot","sca","color","typeUid"]

    let dupe = {
      iuid:generateUUID()
    }
    for(let key in original ){
      if( onlyCopy.indexOf( key ) > -1 ){
        dupe[key] = JSON.parse(JSON.stringify(original[key]))
      }
    }
    //FIXME : needs to work with all entity types
    dupe.name = original.name + ""
    return dupe
  }

  let dupes = sources.map(duplicate)

  let instances = state.instances.concat(dupes)
  let byId = reduceToIuidBasedHash(instances)
  state = mergeData( state, {instances, byId} )

  //set selections, if need be
  //if(settings.autoSelectNewEntities) selectEntities$( dupes.map(i=>i.iuid) )

  return state
}

//////

function makeModification$(intent){

  /*TODO: implement*/
  /*let addEntityInstanceTo$ = intent.addEntityInstanceTo$
    ( instance , parent){
    let parent = parent || null
  }*/

  let _createInstance$ = intent.createInstance$
    .map((data) => (entitiesData) => {
    })

  let _addInstances$ = intent.addInstances$
    .filter(exists)
    //splice in settings
    .withLatestFrom(intent.settings$,function(data,settings){
      return {nentities:data,settings}
    })
    .map(({nentities,settings}) => (entitiesData) => {
      return addInstances(entitiesData,{input:nentities,settings})
    })

  let _updateInstance$ = intent.updateInstance$
    .map((nData) => (entitiesData) => {
      return updateInstances(entitiesData,nData)
    })

  let _deleteInstances$ = intent.deleteInstances$
    .map((remEntitites) => (entitiesData) => {
      return deleteInstance(entitiesData, remEntitites)
    })
    .do( selectEntities$([]) ) //set selections

  let _deleteAllInstances$ = intent.deleteAllInstances$
    .map(() => (entitiesData) => {
      return deleteAllInstances(entitiesData)
    })
    .do( selectEntities$([]) ) //set selections

  let _duplicateInstances$  = intent.duplicateInstances$
    //splice in settings
    .withLatestFrom(intent.settings$,function(data,settings){
      return {sources:data,settings}
    })
    .map(({sources,settings}) => (entitiesData) => {
      return duplicateInstances(entitiesData, {input:sources,settings})
    })

  let _replaceAll$ = intent.replaceAll$
    .map((newData) => (existingData) => {
      return replaceInstances(entitiesData, newData)
    })

  //FIXME : in parts ?
  /*let foo$ = new Rx.Subject()
  let rescaleBBox$ = foo$
    .map((newData) => (existingData) => {
      //let rescaleVertices = require('rescale-vertices')
    })*/

  return merge(
    _addInstances$
    ,_updateInstance$
    ,_deleteInstances$
    ,_deleteAllInstances$
    ,_duplicateInstances$

    ,_createInstance$
    ,_replaceAll$
  )
}

function entities(intent, source) {
  let source$ = source || Observable.just(defaults)
  let modification$ = makeModification$(intent)

  return modification$
    .merge(source$)
    .scan((entityData, modFn) => modFn(entityData))//combine existing data with new one
    //.distinctUntilChanged()
    .shareReplay(1)
}

export default entities

//just an idea: listing of available actions
let availableActions = {
  "delete"   :deleteInstances$
  ,"deleteAll":deleteAllInstances$
  ,"duplicate":duplicateInstances$
}

export {availableActions}<|endoftext|>


<commit_before>import Utils from "../Utils.js";

/**
 * Base64 operations.
 *
 * @author n1474335 [n1474335@gmail.com]
 * @copyright Crown Copyright 2016
 * @license Apache-2.0
 *
 * @namespace
 */
const Base64 = {

    /**
     * @constant
     * @default
     */
    ALPHABET: "A-Za-z0-9+/=",
    /**
     * @constant
     * @default
     */
    ALPHABET_OPTIONS: [
        {name: "Standard: A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
        {name: "URL safe: A-Za-z0-9-_", value: "A-Za-z0-9-_"},
        {name: "Filename safe: A-Za-z0-9+-=", value: "A-Za-z0-9+\\-="},
        {name: "itoa64: ./0-9A-Za-z=", value: "./0-9A-Za-z="},
        {name: "XML: A-Za-z0-9_.", value: "A-Za-z0-9_."},
        {name: "y64: A-Za-z0-9._-", value: "A-Za-z0-9._-"},
        {name: "z64: 0-9a-zA-Z+/=", value: "0-9a-zA-Z+/="},
        {name: "Radix-64: 0-9A-Za-z+/=", value: "0-9A-Za-z+/="},
        {name: "Uuencoding: [space]-_", value: " -_"},
        {name: "Xxencoding: +-0-9A-Za-z", value: "+\\-0-9A-Za-z"},
        {name: "BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r", value: "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r"},
        {name: "ROT13: N-ZA-Mn-za-m0-9+/=", value: "N-ZA-Mn-za-m0-9+/="},
        {name: "UNIX crypt: ./0-9A-Za-z", value: "./0-9A-Za-z"},
    ],

    /**
     * To Base64 operation.
     *
     * @param {byteArray} input
     * @param {Object[]} args
     * @returns {string}
     */
    runTo: function(input, args) {
        const alphabet = args[0] || Base64.ALPHABET;
        return Utils.toBase64(input, alphabet);
    },

    /**
     * @constant
     * @default
     */
    REMOVE_NON_ALPH_CHARS: true,

    /**
     * From Base64 operation.
     *
     * @param {string} input
     * @param {Object[]} args
     * @returns {byteArray}
     */
    runFrom: function(input, args) {
        let alphabet = args[0] || Base64.ALPHABET,
            removeNonAlphChars = args[1];

        return Utils.fromBase64(input, alphabet, "byteArray", removeNonAlphChars);
    },

    /**
     * @constant
     * @default
     */
    BASE32_ALPHABET: "A-Z2-7=",

    /**
     * To Base32 operation.
     *
     * @param {byteArray} input
     * @param {Object[]} args
     * @returns {string}
     */
    runTo32: function(input, args) {
        if (!input) return "";

        let alphabet = args[0] ?
                Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
            output = "",
            chr1, chr2, chr3, chr4, chr5,
            enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
            i = 0;

        while (i < input.length) {
            chr1 = input[i++];
            chr2 = input[i++];
            chr3 = input[i++];
            chr4 = input[i++];
            chr5 = input[i++];

            enc1 = chr1 >> 3;
            enc2 = ((chr1 & 7) << 2) | (chr2 >> 6);
            enc3 = (chr2 >> 1) & 31;
            enc4 = ((chr2 & 1) << 4) | (chr3 >> 4);
            enc5 = ((chr3 & 15) << 1) | (chr4 >> 7);
            enc6 = (chr4 >> 2) & 31;
            enc7 = ((chr4 & 3) << 3) | (chr5 >> 5);
            enc8 = chr5 & 31;

            if (isNaN(chr2)) {
                enc3 = enc4 = enc5 = enc6 = enc7 = enc8 = 32;
            } else if (isNaN(chr3)) {
                enc5 = enc6 = enc7 = enc8 = 32;
            } else if (isNaN(chr4)) {
                enc6 = enc7 = enc8 = 32;
            } else if (isNaN(chr5)) {
                enc8 = 32;
            }

            output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) +
                alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) +
                alphabet.charAt(enc7) + alphabet.charAt(enc8);
        }

        return output;
    },

    /**
     * From Base32 operation.
     *
     * @param {string} input
     * @param {Object[]} args
     * @returns {byteArray}
     */
    runFrom32: function(input, args) {
        if (!input) return [];

        let alphabet = args[0] ?
                Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
            removeNonAlphChars = args[0];

        let output = [],
            chr1, chr2, chr3, chr4, chr5,
            enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
            i = 0;

        if (removeNonAlphChars) {
            const re = new RegExp("[^" + alphabet.replace(/[\]\\\-^]/g, "\\$&") + "]", "g");
            input = input.replace(re, "");
        }

        while (i < input.length) {
            enc1 = alphabet.indexOf(input.charAt(i++));
            enc2 = alphabet.indexOf(input.charAt(i++) || "=");
            enc3 = alphabet.indexOf(input.charAt(i++) || "=");
            enc4 = alphabet.indexOf(input.charAt(i++) || "=");
            enc5 = alphabet.indexOf(input.charAt(i++) || "=");
            enc6 = alphabet.indexOf(input.charAt(i++) || "=");
            enc7 = alphabet.indexOf(input.charAt(i++) || "=");
            enc8 = alphabet.indexOf(input.charAt(i++) || "=");

            chr1 = (enc1 << 3) | (enc2 >> 2);
            chr2 = ((enc2 & 3) << 6) | (enc3 << 1) | (enc4 >> 4);
            chr3 = ((enc4 & 15) << 4) | (enc5 >> 1);
            chr4 = ((enc5 & 1) << 7) | (enc6 << 2) | (enc7 >> 3);
            chr5 = ((enc7 & 7) << 5) | enc8;

            output.push(chr1);
            if (enc2 & 3 !== 0 || enc3 !== 32) output.push(chr2);
            if (enc4 & 15 !== 0 || enc5 !== 32) output.push(chr3);
            if (enc5 & 1 !== 0 || enc6 !== 32) output.push(chr4);
            if (enc7 & 7 !== 0 || enc8 !== 32) output.push(chr5);
        }

        return output;
    },

    /**
     * @constant
     * @default
     */
    SHOW_IN_BINARY: false,
    /**
     * @constant
     * @default
     */
    OFFSETS_SHOW_VARIABLE: true,

    /**
     * Show Base64 offsets operation.
     *
     * @param {byteArray} input
     * @param {Object[]} args
     * @returns {html}
     */
    runOffsets: function(input, args) {
        let alphabet = args[0] || Base64.ALPHABET,
            showVariable = args[1],
            offset0 = Utils.toBase64(input, alphabet),
            offset1 = Utils.toBase64([0].concat(input), alphabet),
            offset2 = Utils.toBase64([0, 0].concat(input), alphabet),
            len0 = offset0.indexOf("="),
            len1 = offset1.indexOf("="),
            len2 = offset2.indexOf("="),
            script = "",
            staticSection = "",
            padding = "";

        if (input.length < 1) {
            return "Please enter a string.";
        }

        // Highlight offset 0
        if (len0 % 4 === 2) {
            staticSection = offset0.slice(0, -3);
            offset0 = "" +
                staticSection + "" +
                "" + offset0.substr(offset0.length - 3, 1) + "" +
                "" + offset0.substr(offset0.length - 2) + "";
        } else if (len0 % 4 === 3) {
            staticSection = offset0.slice(0, -2);
            offset0 = "" +
                staticSection + "" +
                "" + offset0.substr(offset0.length - 2, 1) + "" +
                "" + offset0.substr(offset0.length - 1) + "";
        } else {
            staticSection = offset0;
            offset0 = "" +
                staticSection + "";
        }

        if (!showVariable) {
            offset0 = staticSection;
        }

        // Highlight offset 1
        padding = "" + offset1.substr(0, 1) + "" +
            "" + offset1.substr(1, 1) + "";
        offset1 = offset1.substr(2);
        if (len1 % 4 === 2) {
            staticSection = offset1.slice(0, -3);
            offset1 = padding + "" +
                staticSection + "" +
                "" + offset1.substr(offset1.length - 3, 1) + "" +
                "" + offset1.substr(offset1.length - 2) + "";
        } else if (len1 % 4 === 3) {
            staticSection = offset1.slice(0, -2);
            offset1 = padding + "" +
                staticSection + "" +
                "" + offset1.substr(offset1.length - 2, 1) + "" +
                "" + offset1.substr(offset1.length - 1) + "";
        } else {
            staticSection = offset1;
            offset1 = padding +  "" +
                staticSection + "";
        }

        if (!showVariable) {
            offset1 = staticSection;
        }

        // Highlight offset 2
        padding = "" + offset2.substr(0, 2) + "" +
            "" + offset2.substr(2, 1) + "";
        offset2 = offset2.substr(3);
        if (len2 % 4 === 2) {
            staticSection = offset2.slice(0, -3);
            offset2 = padding + "" +
                staticSection + "" +
                "" + offset2.substr(offset2.length - 3, 1) + "" +
                "" + offset2.substr(offset2.length - 2) + "";
        } else if (len2 % 4 === 3) {
            staticSection = offset2.slice(0, -2);
            offset2 = padding + "" +
                staticSection + "" +
                "" + offset2.substr(offset2.length - 2, 1) + "" +
                "" + offset2.substr(offset2.length - 1) + "";
        } else {
            staticSection = offset2;
            offset2 = padding +  "" +
                staticSection + "";
        }

        if (!showVariable) {
            offset2 = staticSection;
        }

        return (showVariable ? "Characters highlighted in green could change if the input is surrounded by more data." +
            "\nCharacters highlighted in red are for padding purposes only." +
            "\nUnhighlighted characters are static." +
            "\nHover over the static sections to see what they decode to on their own.\n" +
            "\nOffset 0: " + offset0 +
            "\nOffset 1: " + offset1 +
            "\nOffset 2: " + offset2 +
            script :
            offset0 + "\n" + offset1 + "\n" + offset2);
    },

    /**
     * Highlight to Base64
     *
     * @param {Object[]} pos
     * @param {number} pos[].start
     * @param {number} pos[].end
     * @param {Object[]} args
     * @returns {Object[]} pos
     */
    highlightTo: function(pos, args) {
        pos[0].start = Math.floor(pos[0].start / 3 * 4);
        pos[0].end = Math.ceil(pos[0].end / 3 * 4);
        return pos;
    },

    /**
     * Highlight from Base64
     *
     * @param {Object[]} pos
     * @param {number} pos[].start
     * @param {number} pos[].end
     * @param {Object[]} args
     * @returns {Object[]} pos
     */
    highlightFrom: function(pos, args) {
        pos[0].start = Math.ceil(pos[0].start / 4 * 3);
        pos[0].end = Math.floor(pos[0].end / 4 * 3);
        return pos;
    },

};

export default Base64;
<commit_msg>Fixed bug in 'Show Base64 offsets' where highlights did not show<commit_after>import Utils from "../Utils.js";

/**
 * Base64 operations.
 *
 * @author n1474335 [n1474335@gmail.com]
 * @copyright Crown Copyright 2016
 * @license Apache-2.0
 *
 * @namespace
 */
const Base64 = {

    /**
     * @constant
     * @default
     */
    ALPHABET: "A-Za-z0-9+/=",
    /**
     * @constant
     * @default
     */
    ALPHABET_OPTIONS: [
        {name: "Standard: A-Za-z0-9+/=", value: "A-Za-z0-9+/="},
        {name: "URL safe: A-Za-z0-9-_", value: "A-Za-z0-9-_"},
        {name: "Filename safe: A-Za-z0-9+-=", value: "A-Za-z0-9+\\-="},
        {name: "itoa64: ./0-9A-Za-z=", value: "./0-9A-Za-z="},
        {name: "XML: A-Za-z0-9_.", value: "A-Za-z0-9_."},
        {name: "y64: A-Za-z0-9._-", value: "A-Za-z0-9._-"},
        {name: "z64: 0-9a-zA-Z+/=", value: "0-9a-zA-Z+/="},
        {name: "Radix-64: 0-9A-Za-z+/=", value: "0-9A-Za-z+/="},
        {name: "Uuencoding: [space]-_", value: " -_"},
        {name: "Xxencoding: +-0-9A-Za-z", value: "+\\-0-9A-Za-z"},
        {name: "BinHex: !-,-0-689@A-NP-VX-Z[`a-fh-mp-r", value: "!-,-0-689@A-NP-VX-Z[`a-fh-mp-r"},
        {name: "ROT13: N-ZA-Mn-za-m0-9+/=", value: "N-ZA-Mn-za-m0-9+/="},
        {name: "UNIX crypt: ./0-9A-Za-z", value: "./0-9A-Za-z"},
    ],

    /**
     * To Base64 operation.
     *
     * @param {byteArray} input
     * @param {Object[]} args
     * @returns {string}
     */
    runTo: function(input, args) {
        const alphabet = args[0] || Base64.ALPHABET;
        return Utils.toBase64(input, alphabet);
    },

    /**
     * @constant
     * @default
     */
    REMOVE_NON_ALPH_CHARS: true,

    /**
     * From Base64 operation.
     *
     * @param {string} input
     * @param {Object[]} args
     * @returns {byteArray}
     */
    runFrom: function(input, args) {
        let alphabet = args[0] || Base64.ALPHABET,
            removeNonAlphChars = args[1];

        return Utils.fromBase64(input, alphabet, "byteArray", removeNonAlphChars);
    },

    /**
     * @constant
     * @default
     */
    BASE32_ALPHABET: "A-Z2-7=",

    /**
     * To Base32 operation.
     *
     * @param {byteArray} input
     * @param {Object[]} args
     * @returns {string}
     */
    runTo32: function(input, args) {
        if (!input) return "";

        let alphabet = args[0] ?
                Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
            output = "",
            chr1, chr2, chr3, chr4, chr5,
            enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
            i = 0;

        while (i < input.length) {
            chr1 = input[i++];
            chr2 = input[i++];
            chr3 = input[i++];
            chr4 = input[i++];
            chr5 = input[i++];

            enc1 = chr1 >> 3;
            enc2 = ((chr1 & 7) << 2) | (chr2 >> 6);
            enc3 = (chr2 >> 1) & 31;
            enc4 = ((chr2 & 1) << 4) | (chr3 >> 4);
            enc5 = ((chr3 & 15) << 1) | (chr4 >> 7);
            enc6 = (chr4 >> 2) & 31;
            enc7 = ((chr4 & 3) << 3) | (chr5 >> 5);
            enc8 = chr5 & 31;

            if (isNaN(chr2)) {
                enc3 = enc4 = enc5 = enc6 = enc7 = enc8 = 32;
            } else if (isNaN(chr3)) {
                enc5 = enc6 = enc7 = enc8 = 32;
            } else if (isNaN(chr4)) {
                enc6 = enc7 = enc8 = 32;
            } else if (isNaN(chr5)) {
                enc8 = 32;
            }

            output += alphabet.charAt(enc1) + alphabet.charAt(enc2) + alphabet.charAt(enc3) +
                alphabet.charAt(enc4) + alphabet.charAt(enc5) + alphabet.charAt(enc6) +
                alphabet.charAt(enc7) + alphabet.charAt(enc8);
        }

        return output;
    },

    /**
     * From Base32 operation.
     *
     * @param {string} input
     * @param {Object[]} args
     * @returns {byteArray}
     */
    runFrom32: function(input, args) {
        if (!input) return [];

        let alphabet = args[0] ?
                Utils.expandAlphRange(args[0]).join("") : "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
            removeNonAlphChars = args[0];

        let output = [],
            chr1, chr2, chr3, chr4, chr5,
            enc1, enc2, enc3, enc4, enc5, enc6, enc7, enc8,
            i = 0;

        if (removeNonAlphChars) {
            const re = new RegExp("[^" + alphabet.replace(/[\]\\\-^]/g, "\\$&") + "]", "g");
            input = input.replace(re, "");
        }

        while (i < input.length) {
            enc1 = alphabet.indexOf(input.charAt(i++));
            enc2 = alphabet.indexOf(input.charAt(i++) || "=");
            enc3 = alphabet.indexOf(input.charAt(i++) || "=");
            enc4 = alphabet.indexOf(input.charAt(i++) || "=");
            enc5 = alphabet.indexOf(input.charAt(i++) || "=");
            enc6 = alphabet.indexOf(input.charAt(i++) || "=");
            enc7 = alphabet.indexOf(input.charAt(i++) || "=");
            enc8 = alphabet.indexOf(input.charAt(i++) || "=");

            chr1 = (enc1 << 3) | (enc2 >> 2);
            chr2 = ((enc2 & 3) << 6) | (enc3 << 1) | (enc4 >> 4);
            chr3 = ((enc4 & 15) << 4) | (enc5 >> 1);
            chr4 = ((enc5 & 1) << 7) | (enc6 << 2) | (enc7 >> 3);
            chr5 = ((enc7 & 7) << 5) | enc8;

            output.push(chr1);
            if (enc2 & 3 !== 0 || enc3 !== 32) output.push(chr2);
            if (enc4 & 15 !== 0 || enc5 !== 32) output.push(chr3);
            if (enc5 & 1 !== 0 || enc6 !== 32) output.push(chr4);
            if (enc7 & 7 !== 0 || enc8 !== 32) output.push(chr5);
        }

        return output;
    },

    /**
     * @constant
     * @default
     */
    SHOW_IN_BINARY: false,
    /**
     * @constant
     * @default
     */
    OFFSETS_SHOW_VARIABLE: true,

    /**
     * Show Base64 offsets operation.
     *
     * @param {byteArray} input
     * @param {Object[]} args
     * @returns {html}
     */
    runOffsets: function(input, args) {
        let alphabet = args[0] || Base64.ALPHABET,
            showVariable = args[1],
            offset0 = Utils.toBase64(input, alphabet),
            offset1 = Utils.toBase64([0].concat(input), alphabet),
            offset2 = Utils.toBase64([0, 0].concat(input), alphabet),
            len0 = offset0.indexOf("="),
            len1 = offset1.indexOf("="),
            len2 = offset2.indexOf("="),
            script = "",
            staticSection = "",
            padding = "";

        if (input.length < 1) {
            return "Please enter a string.";
        }

        // Highlight offset 0
        if (len0 % 4 === 2) {
            staticSection = offset0.slice(0, -3);
            offset0 = "" +
                staticSection + "" +
                "" + offset0.substr(offset0.length - 3, 1) + "" +
                "" + offset0.substr(offset0.length - 2) + "";
        } else if (len0 % 4 === 3) {
            staticSection = offset0.slice(0, -2);
            offset0 = "" +
                staticSection + "" +
                "" + offset0.substr(offset0.length - 2, 1) + "" +
                "" + offset0.substr(offset0.length - 1) + "";
        } else {
            staticSection = offset0;
            offset0 = "" +
                staticSection + "";
        }

        if (!showVariable) {
            offset0 = staticSection;
        }

        // Highlight offset 1
        padding = "" + offset1.substr(0, 1) + "" +
            "" + offset1.substr(1, 1) + "";
        offset1 = offset1.substr(2);
        if (len1 % 4 === 2) {
            staticSection = offset1.slice(0, -3);
            offset1 = padding + "" +
                staticSection + "" +
                "" + offset1.substr(offset1.length - 3, 1) + "" +
                "" + offset1.substr(offset1.length - 2) + "";
        } else if (len1 % 4 === 3) {
            staticSection = offset1.slice(0, -2);
            offset1 = padding + "" +
                staticSection + "" +
                "" + offset1.substr(offset1.length - 2, 1) + "" +
                "" + offset1.substr(offset1.length - 1) + "";
        } else {
            staticSection = offset1;
            offset1 = padding +  "" +
                staticSection + "";
        }

        if (!showVariable) {
            offset1 = staticSection;
        }

        // Highlight offset 2
        padding = "" + offset2.substr(0, 2) + "" +
            "" + offset2.substr(2, 1) + "";
        offset2 = offset2.substr(3);
        if (len2 % 4 === 2) {
            staticSection = offset2.slice(0, -3);
            offset2 = padding + "" +
                staticSection + "" +
                "" + offset2.substr(offset2.length - 3, 1) + "" +
                "" + offset2.substr(offset2.length - 2) + "";
        } else if (len2 % 4 === 3) {
            staticSection = offset2.slice(0, -2);
            offset2 = padding + "" +
                staticSection + "" +
                "" + offset2.substr(offset2.length - 2, 1) + "" +
                "" + offset2.substr(offset2.length - 1) + "";
        } else {
            staticSection = offset2;
            offset2 = padding +  "" +
                staticSection + "";
        }

        if (!showVariable) {
            offset2 = staticSection;
        }

        return (showVariable ? "Characters highlighted in green could change if the input is surrounded by more data." +
            "\nCharacters highlighted in red are for padding purposes only." +
            "\nUnhighlighted characters are static." +
            "\nHover over the static sections to see what they decode to on their own.\n" +
            "\nOffset 0: " + offset0 +
            "\nOffset 1: " + offset1 +
            "\nOffset 2: " + offset2 +
            script :
            offset0 + "\n" + offset1 + "\n" + offset2);
    },

    /**
     * Highlight to Base64
     *
     * @param {Object[]} pos
     * @param {number} pos[].start
     * @param {number} pos[].end
     * @param {Object[]} args
     * @returns {Object[]} pos
     */
    highlightTo: function(pos, args) {
        pos[0].start = Math.floor(pos[0].start / 3 * 4);
        pos[0].end = Math.ceil(pos[0].end / 3 * 4);
        return pos;
    },

    /**
     * Highlight from Base64
     *
     * @param {Object[]} pos
     * @param {number} pos[].start
     * @param {number} pos[].end
     * @param {Object[]} args
     * @returns {Object[]} pos
     */
    highlightFrom: function(pos, args) {
        pos[0].start = Math.ceil(pos[0].start / 4 * 3);
        pos[0].end = Math.floor(pos[0].end / 4 * 3);
        return pos;
    },

};

export default Base64;
<|endoftext|>


<commit_before>/**
 * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/requirejs/domReady for details
 */
/*jslint */
/*global require: false, define: false, requirejs: false,
 window: false, clearInterval: false, document: false,
 self: false, setInterval: false */

var isTop, testDiv, scrollIntervalId,
  isBrowser = typeof window !== "undefined" && window.document,
  isPageLoaded = !isBrowser,
  doc = isBrowser ? document : null,
  readyCalls = [];

var resolve, readyPromise = new Promise(function(resolve_){
  resolve = resolve_
})

function runCallbacks(callbacks) {
  var i;
  for (i = 0; i < callbacks.length; i += 1) {
    callbacks[i](doc);
  }
}

function callReady() {
  var callbacks = readyCalls;

  if (isPageLoaded) {
    //Call the DOM ready callbacks
    if (callbacks.length) {
      readyCalls = [];
      runCallbacks(callbacks);
    }
    // resolves promise
    resolve(doc)
  }
}

/**
 * Sets the page as loaded.
 */
function pageLoaded() {
  if (!isPageLoaded) {
    isPageLoaded = true;
    if (scrollIntervalId) {
      clearInterval(scrollIntervalId);
    }

    callReady();
  }
}

if (isBrowser) {
  if (document.addEventListener) {
    //Standards. Hooray! Assumption here that if standards based,
    //it knows about DOMContentLoaded.
    document.addEventListener("DOMContentLoaded", pageLoaded, false);
    window.addEventListener("load", pageLoaded, false);
  } else if (window.attachEvent) {
    window.attachEvent("onload", pageLoaded);

    testDiv = document.createElement('div');
    try {
      isTop = window.frameElement === null;
    } catch (e) {}

    //DOMContentLoaded approximation that uses a doScroll, as found by
    //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/,
    //but modified by other contributors, including jdalton
    if (testDiv.doScroll && isTop && window.external) {
      scrollIntervalId = setInterval(function () {
        try {
          testDiv.doScroll();
          pageLoaded();
        } catch (e) {}
      }, 30);
    }
  }

  //Check if document already complete, and if so, just trigger page load
  //listeners. Latest webkit browsers also use "interactive", and
  //will fire the onDOMContentLoaded before "interactive" but not after
  //entering "interactive" or "complete". More details:
  //http://dev.w3.org/html5/spec/the-end.html#the-end
  //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded
  //Hmm, this is more complicated on further use, see "firing too early"
  //bug: https://github.com/requirejs/domReady/issues/1
  //so removing the || document.readyState === "interactive" test.
  //There is still a window.onload binding that should get fired if
  //DOMContentLoaded is missed.
  if (document.readyState === "complete") {
    pageLoaded();
  }
}

/** PUBLIC API **/

/**
 * Registers a callback for DOM ready. If DOM is already ready, the
 * callback is called immediately.
 * @param {Function} callback
 */
export default function domReady(callback) {
  if (!isBrowser) {
    console.error('runtime.domReady requires a browser environment and will be ignored.')
    return
  }
  if (isPageLoaded) {
    callback(doc);
  } else {
    readyCalls.push(callback);
  }
  return readyPromise
}<commit_msg>removes jslint options<commit_after>/**
 * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
 * Available via the MIT or new BSD license.
 * see: http://github.com/requirejs/domReady for details
 */

var isTop, testDiv, scrollIntervalId,
  isBrowser = typeof window !== "undefined" && window.document,
  isPageLoaded = !isBrowser,
  doc = isBrowser ? document : null,
  readyCalls = [];

var resolve, readyPromise = new Promise(function(resolve_){
  resolve = resolve_
})

function runCallbacks(callbacks) {
  var i;
  for (i = 0; i < callbacks.length; i += 1) {
    callbacks[i](doc);
  }
}

function callReady() {
  var callbacks = readyCalls;

  if (isPageLoaded) {
    //Call the DOM ready callbacks
    if (callbacks.length) {
      readyCalls = [];
      runCallbacks(callbacks);
    }
    // resolves promise
    resolve(doc)
  }
}

/**
 * Sets the page as loaded.
 */
function pageLoaded() {
  if (!isPageLoaded) {
    isPageLoaded = true;
    if (scrollIntervalId) {
      clearInterval(scrollIntervalId);
    }

    callReady();
  }
}

if (isBrowser) {
  if (document.addEventListener) {
    //Standards. Hooray! Assumption here that if standards based,
    //it knows about DOMContentLoaded.
    document.addEventListener("DOMContentLoaded", pageLoaded, false);
    window.addEventListener("load", pageLoaded, false);
  } else if (window.attachEvent) {
    window.attachEvent("onload", pageLoaded);

    testDiv = document.createElement('div');
    try {
      isTop = window.frameElement === null;
    } catch (e) {}

    //DOMContentLoaded approximation that uses a doScroll, as found by
    //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/,
    //but modified by other contributors, including jdalton
    if (testDiv.doScroll && isTop && window.external) {
      scrollIntervalId = setInterval(function () {
        try {
          testDiv.doScroll();
          pageLoaded();
        } catch (e) {}
      }, 30);
    }
  }

  //Check if document already complete, and if so, just trigger page load
  //listeners. Latest webkit browsers also use "interactive", and
  //will fire the onDOMContentLoaded before "interactive" but not after
  //entering "interactive" or "complete". More details:
  //http://dev.w3.org/html5/spec/the-end.html#the-end
  //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded
  //Hmm, this is more complicated on further use, see "firing too early"
  //bug: https://github.com/requirejs/domReady/issues/1
  //so removing the || document.readyState === "interactive" test.
  //There is still a window.onload binding that should get fired if
  //DOMContentLoaded is missed.
  if (document.readyState === "complete") {
    pageLoaded();
  }
}

/** PUBLIC API **/

/**
 * Registers a callback for DOM ready. If DOM is already ready, the
 * callback is called immediately.
 * @param {Function} callback
 */
export default function domReady(callback) {
  if (!isBrowser) {
    console.error('runtime.domReady requires a browser environment and will be ignored.')
    return
  }
  if (isPageLoaded) {
    callback(doc);
  } else {
    readyCalls.push(callback);
  }
  return readyPromise
}<|endoftext|>