<commit_before>const Native		= require("./Native");
const EventEmitter	= require('events').EventEmitter;
const SemanticSDP	= require("semantic-sdp");
const IncomingStreamTrack	= require("./IncomingStreamTrack");

const SDPInfo		= SemanticSDP.SDPInfo;
const Setup		= SemanticSDP.Setup;
const MediaInfo		= SemanticSDP.MediaInfo;
const CandidateInfo	= SemanticSDP.CandidateInfo;
const DTLSInfo		= SemanticSDP.DTLSInfo;
const ICEInfo		= SemanticSDP.ICEInfo;
const StreamInfo	= SemanticSDP.StreamInfo;
const TrackInfo		= SemanticSDP.TrackInfo;

/**
 * The incoming streams represent the recived media stream from a remote peer.
 */
class IncomingStream
{
	constructor(transport,info)
	{
		//Store id
		this.id = info.id;

		//Store native transport
		this.transport = transport;

		//Store sources
		this.tracks = new Map();

		//For each tracks
		for (let track of info.getTracks().values())
		{
			//Is it audio or video
			const type	 = track.getMedia()==="audio" ? 0 : 1;

			//The map of incoming sources
			const sources	 = {};

			//Get encodings
			const encodings	 = track.getEncodings();

			//If it is a simulcast encoding
			if (encodings.length)
			{
				//For each encoding
				for (let i=0; i<encodings.length; ++i)
				{
					//For each alternative
					for (let j=0; j<encodings[i].length; ++j)
					{
						//Create single incoming source
						const source = new Native.RTPIncomingSourceGroup(type);

						//Get rid
						const rid = encodings[i][j].getId();

						//Set rtp stream id
						source.rid = new Native.StringFacade(rid);

						//Add it to transport
						if (!this.transport.AddIncomingSourceGroup(source))
							//Launch exception
							throw new Error("Could not add incoming source group to native transport");
						//Append to sources
						sources[rid] = source;
					}
				}
			//Check if it is a chrome-like sumulcast
			} else if (track.hasSourceGroup("SIM")) {
				//Get MID group
				const SIM = track.getSourceGroup("SIM");
				//Get ssrcs
				const ssrcs = SIM.getSSRCs();

				//Check the other groups
				const groups  = track.getSourceGroups();

				//For each ssrc in MID
				for (let i=0; i<ssrcs.length; ++i)
				{
					//Create single incoming source
					const source = new Native.RTPIncomingSourceGroup(type);

					//Set source data
					source.media.ssrc = ssrcs[i];

					//Check groups
					for (let j=0; j<groups.length; ++j)
					{
						//Check if it is from us
						if (groups[j].getSSRCs()[0]===ssrcs[i])
						{
							//Check if it is one of the ones we care
							switch(groups[j].getSemantics())
							{
								case "FID":
									//Set rtx ssrc
									source.rtx.ssrc = groups[j].getSSRCs()[1];
									break;
								case "FEC-FR":
									//Set flex fec ssrc
									source.fec.ssrc = groups[j].getSSRCs()[1];
									break;
							}
						}
					}

					//Add it to transport
					if (!this.transport.AddIncomingSourceGroup(source))
						//Launch exception
						throw new Error("Could not add incoming source group to native transport");
					//Append to sources with MID index value
					sources[i] = source;
				}
			} else {
				//Create single incoming source
				const source = new Native.RTPIncomingSourceGroup(type);

				//Set source data
				source.media.ssrc = track.getSSRCs ()[0];

				//Check groups
				const FID    = track.getSourceGroup("FID");
				const FEC_FR = track.getSourceGroup("FEC-FR");

				//Set rtx ssrc
				source.rtx.ssrc = FID ? FID.getSSRCs()[1] : 0;

				//Set flec fex ssrc
				source.fec.ssrc = FEC_FR ? FEC_FR.getSSRCs()[1] : 0;

				//Add it to transport
				if (!this.transport.AddIncomingSourceGroup(source))
					//Launch exception
					throw new Error("Could not add incoming source group to native transport");
				//Append to sources with empty rid
				sources[''] = source;
			}

			//Create new track
			const incomingStreamTrack = new IncomingStreamTrack(track.getMedia(),track.getId(),Native.TransportToReceiver(transport),sources);

			//Add listener
			incomingStreamTrack.on("stop",()=>{
				//REmove from map
				this.tracks.delete(track.getId());
				//For each source
				for (let id of Object.keys(sources))
					//Remove source group
					this.transport.RemoveIncomingSourceGroup(sources[id]);
			});
			//Add it to map
			this.tracks.set(track.getId(),incomingStreamTrack);
		}
		//Create event emitter
		this.emitter = new EventEmitter();
	}

	/**
	 * The media stream id as announced on the SDP
	 * @returns {String}
	 */
	getId()
	{
		return this.id;
	}

	/**
	 * Add event listener
	 * @param {String} event	- Event name
	 * @param {function} listener	- Event listener
	 * @returns {IncomingStream}
	 */
	on()
	{
		//Delegate event listeners to event emitter
		this.emitter.on.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Add event listener once
	 * @param {String} event	- Event name
	 * @param {function} listener	- Event listener
	 * @returns {IncomingStream}
	 */
	once()
	{
		//Delegate event listeners to event emitter
		this.emitter.once.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Remove event listener
	 * @param {String} event	- Event name
	 * @param {function} listener	- Event listener
	 * @returns {IncomingStream}
	 */
	off()
	{
		//Delegate event listeners to event emitter
		this.emitter.removeListener.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Get statistics for all tracks in the stream
	 *
	 * See OutgoingStreamTrack.getStats for information about the stats returned by each track.
	 *
	 * @returns {Map<String>,Object} Map with stats by trackId
	 */
	getStats()
	{
		const stats = {};

		//for each track
		for (let track of this.tracks.values())
			//Append stats
			stats[track.getId()] = track.getStats();

		return stats;
	}

	/**
	 * Get track by id
	 * @param {String} trackId	- The track id
	 * @returns {IncomingStreamTrack}	- requested track or null
	 */
	getTrack(trackId)
	{
		//get it
		return this.tracks.get(trackId);
	}

	/**
	 * Get all the tracks
	* @returns {Array<IncomingStreamTrack>}	- Array of tracks
	 */
	getTracks()
	{
		//Return a track array
		return Array.from(this.tracks.values());
	}
	/**
	 * Get an array of the media stream audio tracks
	 * @returns {Array<IncomingStreamTrack>}	- Array of tracks
	 */
	getAudioTracks()
	{
		var audio = [];

		//For each track
		for (let track of this.tracks.values())
			//If it is an video track
			if(track.getMedia().toLowerCase()==="audio")
				//Append to tracks
				audio.push(track);
		//Return all tracks
		return audio;
	}

	/**
	 * Get an array of the media stream video tracks
	 * @returns {Array<IncomingStreamTrack>}	- Array of tracks
	 */
	getVideoTracks()
	{
		var video = [];

		//For each track
		for (let track of this.tracks.values())
			//If it is an video track
			if(track.getMedia().toLowerCase()==="video")
				//Append to tracks
				video.push(track);
		//Return all tracks
		return video;
	}

	/**
	 * Removes the media strem from the transport and also detaches from any attached incoming stream
	 */
	stop()
	{
		//Don't call it twice
		if (!this.transport) return;

		//Stop all streams
		for (let track of this.tracks.values())
			//Stop track
			track.stop();

		//Clear tracks jic
		this.tracks.clear();

		/**
		* IncomingStream stopped event
		*
		* @event IncomingStream#stopped
		* @type {object}
		*/
		this.emitter.emit("stopped");

		//Remove transport reference, so destructor is called on GC
		this.transport = null;
	}
}

module.exports = IncomingStream;<commit_msg>Allow rids with ssrc<commit_after>const Native		= require("./Native");
const EventEmitter	= require('events').EventEmitter;
const SemanticSDP	= require("semantic-sdp");
const IncomingStreamTrack	= require("./IncomingStreamTrack");

const SDPInfo		= SemanticSDP.SDPInfo;
const Setup		= SemanticSDP.Setup;
const MediaInfo		= SemanticSDP.MediaInfo;
const CandidateInfo	= SemanticSDP.CandidateInfo;
const DTLSInfo		= SemanticSDP.DTLSInfo;
const ICEInfo		= SemanticSDP.ICEInfo;
const StreamInfo	= SemanticSDP.StreamInfo;
const TrackInfo		= SemanticSDP.TrackInfo;

/**
 * The incoming streams represent the recived media stream from a remote peer.
 */
class IncomingStream
{
	constructor(transport,info)
	{
		//Store id
		this.id = info.id;

		//Store native transport
		this.transport = transport;

		//Store sources
		this.tracks = new Map();

		//For each tracks
		for (let track of info.getTracks().values())
		{
			//Is it audio or video
			const type	 = track.getMedia()==="audio" ? 0 : 1;

			//The map of incoming sources
			const sources	 = {};

			//Get encodings
			const encodings	 = track.getEncodings();

			//If it is a simulcast encoding
			if (encodings.length)
			{
				//For each encoding
				for (let i=0; i<encodings.length; ++i)
				{
					//For each alternative
					for (let j=0; j<encodings[i].length; ++j)
					{
						//Create single incoming source
						const source = new Native.RTPIncomingSourceGroup(type);

						//Get rid
						const rid = encodings[i][j].getId();

						//Set rtp stream id
						source.rid = new Native.StringFacade(rid);

						//Get RID params
						const params = encodings[i][j].getParams();

						//If it has ssrc
						if (params && params.has("ssrc"))
						{
							//Get it
							source.media.ssrc = parseInt(params.get("ssrc"));

							//Check ssrc groups
							const groups  = track.getSourceGroups();

							//Check groups
							for (let k=0; j<groups.length; ++j)
							{
								//Check if it is from us
								if (groups[k].getSSRCs()[0]===source.media.ssrc)
								{
									//Check if it is one of the ones we care
									switch(groups[j].getSemantics())
									{
										case "FID":
											//Set rtx ssrc
											source.rtx.ssrc = groups[k].getSSRCs()[1];
											break;
										case "FEC-FR":
											//Set flex fec ssrc
											source.fec.ssrc = groups[k].getSSRCs()[1];
											break;
									}
								}
							}
						}

						//Add it to transport
						if (!this.transport.AddIncomingSourceGroup(source))
							//Launch exception
							throw new Error("Could not add incoming source group to native transport");
						//Append to sources
						sources[rid] = source;
					}
				}
			//Check if it is a chrome-like sumulcast
			} else if (track.hasSourceGroup("SIM")) {
				//Get MID group
				const SIM = track.getSourceGroup("SIM");
				//Get ssrcs
				const ssrcs = SIM.getSSRCs();

				//Check the other groups
				const groups  = track.getSourceGroups();

				//For each ssrc in MID
				for (let i=0; i<ssrcs.length; ++i)
				{
					//Create single incoming source
					const source = new Native.RTPIncomingSourceGroup(type);

					//Set source data
					source.media.ssrc = ssrcs[i];

					//Check groups
					for (let j=0; j<groups.length; ++j)
					{
						//Check if it is from us
						if (groups[j].getSSRCs()[0]===ssrcs[i])
						{
							//Check if it is one of the ones we care
							switch(groups[j].getSemantics())
							{
								case "FID":
									//Set rtx ssrc
									source.rtx.ssrc = groups[j].getSSRCs()[1];
									break;
								case "FEC-FR":
									//Set flex fec ssrc
									source.fec.ssrc = groups[j].getSSRCs()[1];
									break;
							}
						}
					}

					//Add it to transport
					if (!this.transport.AddIncomingSourceGroup(source))
						//Launch exception
						throw new Error("Could not add incoming source group to native transport");
					//Append to sources with MID index value
					sources[i] = source;
				}
			} else {
				//Create single incoming source
				const source = new Native.RTPIncomingSourceGroup(type);

				//Set source data
				source.media.ssrc = track.getSSRCs ()[0];

				//Check groups
				const FID    = track.getSourceGroup("FID");
				const FEC_FR = track.getSourceGroup("FEC-FR");

				//Set rtx ssrc
				source.rtx.ssrc = FID ? FID.getSSRCs()[1] : 0;

				//Set flec fex ssrc
				source.fec.ssrc = FEC_FR ? FEC_FR.getSSRCs()[1] : 0;

				//Add it to transport
				if (!this.transport.AddIncomingSourceGroup(source))
					//Launch exception
					throw new Error("Could not add incoming source group to native transport");
				//Append to sources with empty rid
				sources[''] = source;
			}

			//Create new track
			const incomingStreamTrack = new IncomingStreamTrack(track.getMedia(),track.getId(),Native.TransportToReceiver(transport),sources);

			//Add listener
			incomingStreamTrack.on("stop",()=>{
				//REmove from map
				this.tracks.delete(track.getId());
				//For each source
				for (let id of Object.keys(sources))
					//Remove source group
					this.transport.RemoveIncomingSourceGroup(sources[id]);
			});
			//Add it to map
			this.tracks.set(track.getId(),incomingStreamTrack);
		}
		//Create event emitter
		this.emitter = new EventEmitter();
	}

	/**
	 * The media stream id as announced on the SDP
	 * @returns {String}
	 */
	getId()
	{
		return this.id;
	}

	/**
	 * Add event listener
	 * @param {String} event	- Event name
	 * @param {function} listener	- Event listener
	 * @returns {IncomingStream}
	 */
	on()
	{
		//Delegate event listeners to event emitter
		this.emitter.on.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Add event listener once
	 * @param {String} event	- Event name
	 * @param {function} listener	- Event listener
	 * @returns {IncomingStream}
	 */
	once()
	{
		//Delegate event listeners to event emitter
		this.emitter.once.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Remove event listener
	 * @param {String} event	- Event name
	 * @param {function} listener	- Event listener
	 * @returns {IncomingStream}
	 */
	off()
	{
		//Delegate event listeners to event emitter
		this.emitter.removeListener.apply(this.emitter, arguments);
		//Return object so it can be chained
		return this;
	}

	/**
	 * Get statistics for all tracks in the stream
	 *
	 * See OutgoingStreamTrack.getStats for information about the stats returned by each track.
	 *
	 * @returns {Map<String>,Object} Map with stats by trackId
	 */
	getStats()
	{
		const stats = {};

		//for each track
		for (let track of this.tracks.values())
			//Append stats
			stats[track.getId()] = track.getStats();

		return stats;
	}

	/**
	 * Get track by id
	 * @param {String} trackId	- The track id
	 * @returns {IncomingStreamTrack}	- requested track or null
	 */
	getTrack(trackId)
	{
		//get it
		return this.tracks.get(trackId);
	}

	/**
	 * Get all the tracks
	* @returns {Array<IncomingStreamTrack>}	- Array of tracks
	 */
	getTracks()
	{
		//Return a track array
		return Array.from(this.tracks.values());
	}
	/**
	 * Get an array of the media stream audio tracks
	 * @returns {Array<IncomingStreamTrack>}	- Array of tracks
	 */
	getAudioTracks()
	{
		var audio = [];

		//For each track
		for (let track of this.tracks.values())
			//If it is an video track
			if(track.getMedia().toLowerCase()==="audio")
				//Append to tracks
				audio.push(track);
		//Return all tracks
		return audio;
	}

	/**
	 * Get an array of the media stream video tracks
	 * @returns {Array<IncomingStreamTrack>}	- Array of tracks
	 */
	getVideoTracks()
	{
		var video = [];

		//For each track
		for (let track of this.tracks.values())
			//If it is an video track
			if(track.getMedia().toLowerCase()==="video")
				//Append to tracks
				video.push(track);
		//Return all tracks
		return video;
	}

	/**
	 * Removes the media strem from the transport and also detaches from any attached incoming stream
	 */
	stop()
	{
		//Don't call it twice
		if (!this.transport) return;

		//Stop all streams
		for (let track of this.tracks.values())
			//Stop track
			track.stop();

		//Clear tracks jic
		this.tracks.clear();

		/**
		* IncomingStream stopped event
		*
		* @event IncomingStream#stopped
		* @type {object}
		*/
		this.emitter.emit("stopped");

		//Remove transport reference, so destructor is called on GC
		this.transport = null;
	}
}

module.exports = IncomingStream;<|endoftext|>


<commit_before>(function() {
    var bars = window.Plotly.Bars = {};

    bars.calc = function(gd,gdc) {
        // ignore as much processing as possible (and including in autorange) if bar is not visible
        if(gdc.visible===false) { return; }

        if(gdc.type==='histogram') { return Plotly.Histogram.calc(gd,gdc); }

        // depending on bar direction, set position and size axes and data ranges
        // note: this logic for choosing orientation is duplicated in graph_obj->setstyles
        var xa = Plotly.Axes.getFromId(gd,gdc.xaxis||'x'),
            ya = Plotly.Axes.getFromId(gd,gdc.yaxis||'y'),
            orientation = gdc.orientation || ((gdc.x && !gdc.y) ? 'h' : 'v'),
            pos, size, i;
        if(orientation=='h') {
            size = xa.makeCalcdata(gdc,'x');
            pos = ya.makeCalcdata(gdc,'y');
        }
        else {
            size = ya.makeCalcdata(gdc,'y');
            pos = xa.makeCalcdata(gdc,'x');
        }

        // create the "calculated data" to plot
        var serieslen = Math.min(pos.length,size.length),
            cd = [];
        for(i=0;i<serieslen;i++) {
            if(($.isNumeric(pos[i]) && $.isNumeric(size[i]))) {
                cd.push({p:pos[i],s:size[i],b:0});
            }
        }
        if(cd[0]) { cd[0].t = {orientation:orientation};}
        return cd;
    };

    // bar chart stacking/grouping positioning and autoscaling calculations
    // for each direction separately calculate the ranges and positions
    // note that this handles histograms too
    // now doing this one subplot at a time
    bars.setPositions = function(gd,plotinfo) {
        var gl = gd.layout,
            xa = plotinfo.x,
            ya = plotinfo.y,
            i, j;

        ['v','h'].forEach(function(dir){
            var bl = [],
                pLetter = {v:'x',h:'y'}[dir],
                sLetter = {v:'y',h:'x'}[dir],
                pa = plotinfo[pLetter],
                sa = plotinfo[sLetter];

            gd.calcdata.forEach(function(cd,i) {
                var t=cd[0].t;
                if(t.visible!==false && Plotly.Plots.isBar(t.type) &&
                  (t.orientation||'v')==dir && (t.xaxis||'x')==xa._id && (t.yaxis||'y')==ya._id) {
                    bl.push(i);
                }
            });
            if(!bl.length) { return; }

            // bar position offset and width calculation
            // bl1 is a list of traces (in calcdata) to look at together
            // to find the maximum size bars that won't overlap
            // for stacked or grouped bars, this is all vertical or horizontal bars
            // for overlaid bars, call this individually on each trace.
            function barposition(bl1) {
                // find the min. difference between any points in any traces in bl1
                var pvals=[];
                bl1.forEach(function(i){
                    gd.calcdata[i].forEach(function(v){ pvals.push(v.p); });
                });
                var dv = Plotly.Lib.distinctVals(pvals),
                    pv2 = dv.vals,
                    barDiff = dv.minDiff;

                // check if all the traces have only independent positions
                // if so, let them have full width even if mode is group
                var overlap = false,
                    comparelist = [];
                if(gl.barmode=='group') {
                    bl1.forEach(function(i) {
                        if(overlap) { return; }
                        gd.calcdata[i].forEach(function(v) {
                            if(overlap) { return; }
                            comparelist.forEach(function(cp) {
                                if(Math.abs(v.p-cp)<barDiff) { overlap = true; }
                            });
                        });
                        if(overlap) { return; }
                        gd.calcdata[i].forEach(function(v) { comparelist.push(v.p); });
                    });
                }

                // check forced minimum dtick
                Plotly.Axes.minDtick(pa,barDiff,pv2[0],overlap);

                // position axis autorange - always tight fitting
                Plotly.Axes.expand(pa,pv2,{vpad:barDiff/2});
                // bar widths and position offsets
                barDiff*=(1-gl.bargap);
                if(overlap) { barDiff/=bl.length; }

                var barCenter;
                function setBarCenter(v) { v[pLetter] = v.p + barCenter; }

                for(var i=0; i<bl1.length; i++){
                    var t=gd.calcdata[bl1[i]][0].t;
                    t.barwidth = barDiff*(1-gl.bargroupgap);
                    t.poffset = ((overlap ? (2*i+1-bl1.length)*barDiff : 0 ) - t.barwidth)/2;
                    t.dbar = dv.minDiff;
                    // store the bar center in each calcdata item
                    barCenter = t.poffset + t.barwidth/2;
                    gd.calcdata[bl1[i]].forEach(setBarCenter);
                }
            }
            if(gl.barmode=='overlay') { bl.forEach(function(bli){ barposition([bli]); }); }
            else { barposition(bl); }

            // bar size range and stacking calculation
            if(gl.barmode=='stack'){
                // for stacked bars, we need to evaluate every step in every stack,
                // because negative bars mean the extremes could be anywhere
                // also stores the base (b) of each bar in calcdata so we don't have to redo this later
                var sMax = sa.l2c(sa.c2l(0)),
                    sMin = sMax,
                    sums={},
                    v=0,
                    sumround = gd.calcdata[bl[0]][0].t.barwidth/100, // make sure...
                    sv = 0; //... if p is different only by rounding, we still stack
                for(i=0; i<bl.length; i++){ // trace index
                    var ti = gd.calcdata[bl[i]];
                    for(j=0; j<ti.length; j++) {
                        sv = Math.round(ti[j].p/sumround);
                        ti[j].b = (sums[sv]||0);
                        v = ti[j].b+ti[j].s;
                        // store the bar top in each calcdata item
                        ti[j][sLetter] = v;
                        sums[sv] = v;
                        if($.isNumeric(sa.c2l(v))) {
                            sMax = Math.max(sMax,v);
                            sMin = Math.min(sMin,v);
                        }
                    }
                }
                Plotly.Axes.expand(sa,[sMin,sMax],{tozero:true,padded:true});
            }
            else {
                // for grouped or overlaid bars, just make sure zero is included,
                // along with the tops of each bar, and store these bar tops in calcdata
                var fs = function(v){ v[sLetter] = v.s; return v.s; };
                for(i=0; i<bl.length; i++){
                    Plotly.Axes.expand(sa,gd.calcdata[bl[i]].map(fs),{tozero:true,padded:true});
                }
            }
        });
    };

    bars.plot = function(gd,plotinfo,cdbar) {
        var xa = plotinfo.x,
            ya = plotinfo.y,
            gl = gd.layout;
        // make the container for scatter plots (so error bars can find them along with bars)
        var bartraces = plotinfo.plot.select('.barlayer').selectAll('g.trace.bars') // <-- select trace group
            .data(cdbar)
          .enter().append('g') // <-- add a trace for each calcdata
            .attr('class','trace bars');

        bartraces.append('g')
            .attr('class','points')
            .each(function(d){
                var t = d[0].t; // <-- get trace-wide formatting object
                d3.select(this).selectAll('path')
                    .data(Plotly.Lib.identity)
                  .enter().append('path')
                    .each(function(di){
                        // now display the bar
                        // clipped xf/yf (2nd arg true): non-positive
                        // log values go off-screen by plotwidth
                        // so you see them continue if you drag the plot
                        var x0,x1,y0,y1;
                        if(t.orientation=='h') {
                            y0 = ya.c2p(t.poffset+di.p);
                            y1 = ya.c2p(t.poffset+di.p+t.barwidth);
                            x0 = xa.c2p(di.b,true);
                            x1 = xa.c2p(di.s+di.b,true);
                        }
                        else {
                            x0 = xa.c2p(t.poffset+di.p);
                            x1 = xa.c2p(t.poffset+di.p+t.barwidth);
                            y1 = ya.c2p(di.s+di.b,true);
                            y0 = ya.c2p(di.b,true);
                        }

                        if(!$.isNumeric(x0)||!$.isNumeric(x1)||!$.isNumeric(y0)||!$.isNumeric(y1)||x0==x1||y0==y1) {
                            d3.select(this).remove();
                            return;
                        }
                        var lw = (di.mlw+1 || t.mlw+1 || (di.t ? di.t.mlw : 0)+1)-1,
                            offset = d3.round((lw/2)%1,2);
                        function roundWithLine(v) {
                            // if there are explicit gaps, don't round, it can make the gaps look crappy
                            return (gl.bargap===0 && gl.bargroupgap===0) ?
                                d3.round(Math.round(v)-offset,2) : v;
                        }
                        function expandToVisible(v,vc) {
                            // if it's not in danger of disappearing entirely, round more precisely
                            return Math.abs(v-vc)>=2 ? roundWithLine(v) :
                            // but if it's very thin, expand it so it's necessarily visible,
                            // even if it might overlap its neighbor
                            (v>vc ? Math.ceil(v) : Math.floor(v));
                        }
                        if(!gl._forexport) {
                            // if bars are not fully opaque or they have a line around them, round to integer
                            // pixels, mainly for safari so we prevent overlaps from its expansive pixelation.
                            // if the bars ARE fully opaque and have no line, expand to a full pixel to make
                            // sure we can see them
                            var op = Plotly.Drawing.opacity(di.mc || t.mc || (di.t ? di.t.mc : '')),
                                fixpx = (op<1 || lw>0.01) ? roundWithLine : expandToVisible;
                            x0 = fixpx(x0,x1);
                            x1 = fixpx(x1,x0);
                            y0 = fixpx(y0,y1);
                            y1 = fixpx(y1,y0);
                        }
                        d3.select(this).attr('d','M'+x0+','+y0+'V'+y1+'H'+x1+'V'+y0+'Z');
                    });
            });
    };

    bars.style = function(gp,gl) {
        var s = gp.selectAll('g.trace.bars'),
            barcount = s.size();

        // first trace styling
        // for bars only: combine trace and fill opacity (no, can't do that because of bar outlines)
        s.style('opacity',function(d){ return d[0].t.op; }) // *Plotly.Drawing.opacity(d[0].t.fc)
        // for gapless (either stacked or neighboring grouped) bars use crispEdges
        // to turn off antialiasing so an artificial gap isn't introduced.
        .each(function(d){
            if((gl.barmode=='stack' && barcount>1) || (gl.bargap===0 && gl.bargroupgap===0 && !d[0].t.mlw)){
                d3.select(this).attr('shape-rendering','crispEdges');
            }
        });

        // then style the individual bars
        s.selectAll('g.points').each(function(d){
            var t = d.t||d[0].t;
            d3.select(this).selectAll('path').each(function(d) {
                // allow all marker and marker line colors to be scaled by given max and min to colorscales
                var w = (d.mlw+1 || t.mlw+1 || (d.t ? d.t.mlw : 0)+1) - 1,
                    p = d3.select(this);
                p.style('stroke-width',w+'px')
                    .call(Plotly.Drawing.fillColor,
                        Plotly.Drawing.tryColorscale(s,t,'m')(d.mc || t.mc || (d.t ? d.t.mc : '')));
                if(w) {
                    p.call(Plotly.Drawing.strokeColor,
                        Plotly.Drawing.tryColorscale(s,t,'ml')(d.mlc || t.mlc || (d.t ? d.t.mlc : '')));
                }
            });
            // TODO: text markers on bars, either extra text or just bar values
            // d3.select(this).selectAll('text')
            //     .call(Plotly.Drawing.textPointStyle,d.t||d[0].t);
        });
    };

}()); // end Bars object definition
<commit_msg>delint bars.js<commit_after>(function() {
    'use strict';

    // ---Plotly global modules
    /* global Plotly:false */

    // ---external global dependencies
    /* global d3:false */

    var bars = window.Plotly.Bars = {};

    bars.calc = function(gd,gdc) {
        // ignore as much processing as possible (including
        // in autorange) if bar is not visible
        if(gdc.visible===false) { return; }

        if(gdc.type==='histogram') { return Plotly.Histogram.calc(gd,gdc); }

        // depending on bar direction, set position and size axes
        // and data ranges
        // note: this logic for choosing orientation is
        // duplicated in graph_obj->setstyles
        var xa = Plotly.Axes.getFromId(gd,gdc.xaxis||'x'),
            ya = Plotly.Axes.getFromId(gd,gdc.yaxis||'y'),
            orientation = gdc.orientation || ((gdc.x && !gdc.y) ? 'h' : 'v'),
            pos, size, i;
        if(orientation==='h') {
            size = xa.makeCalcdata(gdc,'x');
            pos = ya.makeCalcdata(gdc,'y');
        }
        else {
            size = ya.makeCalcdata(gdc,'y');
            pos = xa.makeCalcdata(gdc,'x');
        }

        // create the "calculated data" to plot
        var serieslen = Math.min(pos.length,size.length),
            cd = [];
        for(i=0;i<serieslen;i++) {
            if(($.isNumeric(pos[i]) && $.isNumeric(size[i]))) {
                cd.push({p:pos[i],s:size[i],b:0});
            }
        }
        if(cd[0]) { cd[0].t = {orientation:orientation};}
        return cd;
    };

    // bar chart stacking/grouping positioning and autoscaling calculations
    // for each direction separately calculate the ranges and positions
    // note that this handles histograms too
    // now doing this one subplot at a time
    bars.setPositions = function(gd,plotinfo) {
        var gl = gd.layout,
            xa = plotinfo.x,
            ya = plotinfo.y,
            i, j;

        ['v','h'].forEach(function(dir){
            var bl = [],
                pLetter = {v:'x',h:'y'}[dir],
                sLetter = {v:'y',h:'x'}[dir],
                pa = plotinfo[pLetter],
                sa = plotinfo[sLetter];

            gd.calcdata.forEach(function(cd,i) {
                var t=cd[0].t;
                if(t.visible!==false && Plotly.Plots.isBar(t.type) &&
                        (t.orientation||'v')===dir &&
                        (t.xaxis||'x')===xa._id &&
                        (t.yaxis||'y')===ya._id) {
                    bl.push(i);
                }
            });
            if(!bl.length) { return; }

            // bar position offset and width calculation
            // bl1 is a list of traces (in calcdata) to look at together
            // to find the maximum size bars that won't overlap
            // for stacked or grouped bars, this is all vertical or horizontal
            // bars for overlaid bars, call this individually on each trace.
            function barposition(bl1) {
                // find the min. difference between any points
                // in any traces in bl1
                var pvals=[];
                bl1.forEach(function(i){
                    gd.calcdata[i].forEach(function(v){ pvals.push(v.p); });
                });
                var dv = Plotly.Lib.distinctVals(pvals),
                    pv2 = dv.vals,
                    barDiff = dv.minDiff;

                // check if all the traces have only independent positions
                // if so, let them have full width even if mode is group
                var overlap = false,
                    comparelist = [];
                if(gl.barmode==='group') {
                    bl1.forEach(function(i) {
                        if(overlap) { return; }
                        gd.calcdata[i].forEach(function(v) {
                            if(overlap) { return; }
                            comparelist.forEach(function(cp) {
                                if(Math.abs(v.p-cp)<barDiff) { overlap = true; }
                            });
                        });
                        if(overlap) { return; }
                        gd.calcdata[i].forEach(function(v) {
                            comparelist.push(v.p);
                        });
                    });
                }

                // check forced minimum dtick
                Plotly.Axes.minDtick(pa,barDiff,pv2[0],overlap);

                // position axis autorange - always tight fitting
                Plotly.Axes.expand(pa,pv2,{vpad:barDiff/2});

                // bar widths and position offsets
                barDiff*=(1-gl.bargap);
                if(overlap) { barDiff/=bl.length; }

                var barCenter;
                function setBarCenter(v) { v[pLetter] = v.p + barCenter; }

                for(var i=0; i<bl1.length; i++){
                    var t=gd.calcdata[bl1[i]][0].t;
                    t.barwidth = barDiff*(1-gl.bargroupgap);
                    t.poffset = ((overlap ? (2*i+1-bl1.length)*barDiff : 0 ) -
                        t.barwidth)/2;
                    t.dbar = dv.minDiff;

                    // store the bar center in each calcdata item
                    barCenter = t.poffset + t.barwidth/2;
                    gd.calcdata[bl1[i]].forEach(setBarCenter);
                }
            }
            if(gl.barmode==='overlay') {
                bl.forEach(function(bli){ barposition([bli]); });
            }
            else { barposition(bl); }

            // bar size range and stacking calculation
            if(gl.barmode==='stack'){
                // for stacked bars, we need to evaluate every step in every
                // stack, because negative bars mean the extremes could be
                // anywhere
                // also stores the base (b) of each bar in calcdata
                // so we don't have to redo this later
                var sMax = sa.l2c(sa.c2l(0)),
                    sMin = sMax,
                    sums={},
                    v=0,

                    // make sure if p is different only by rounding,
                    // we still stack
                    sumround = gd.calcdata[bl[0]][0].t.barwidth/100,
                    sv = 0;
                for(i=0; i<bl.length; i++){ // trace index
                    var ti = gd.calcdata[bl[i]];
                    for(j=0; j<ti.length; j++) {
                        sv = Math.round(ti[j].p/sumround);
                        ti[j].b = (sums[sv]||0);
                        v = ti[j].b+ti[j].s;

                        // store the bar top in each calcdata item
                        ti[j][sLetter] = v;
                        sums[sv] = v;
                        if($.isNumeric(sa.c2l(v))) {
                            sMax = Math.max(sMax,v);
                            sMin = Math.min(sMin,v);
                        }
                    }
                }
                Plotly.Axes.expand(sa,[sMin,sMax],{tozero:true,padded:true});
            }
            else {
                // for grouped or overlaid bars, just make sure zero is
                // included, along with the tops of each bar, and store
                // these bar tops in calcdata
                var fs = function(v){ v[sLetter] = v.s; return v.s; };

                for(i=0; i<bl.length; i++){
                    Plotly.Axes.expand(sa,gd.calcdata[bl[i]].map(fs),
                        {tozero:true,padded:true});
                }
            }
        });
    };

    bars.plot = function(gd,plotinfo,cdbar) {
        var xa = plotinfo.x,
            ya = plotinfo.y,
            gl = gd.layout;

        var bartraces = plotinfo.plot.select('.barlayer')
            .selectAll('g.trace.bars')
                .data(cdbar)
          .enter().append('g')
            .attr('class','trace bars');

        bartraces.append('g')
            .attr('class','points')
            .each(function(d){
                var t = d[0].t;
                d3.select(this).selectAll('path')
                    .data(Plotly.Lib.identity)
                  .enter().append('path')
                    .each(function(di){
                        // now display the bar
                        // clipped xf/yf (2nd arg true): non-positive
                        // log values go off-screen by plotwidth
                        // so you see them continue if you drag the plot
                        var x0,x1,y0,y1;
                        if(t.orientation==='h') {
                            y0 = ya.c2p(t.poffset+di.p);
                            y1 = ya.c2p(t.poffset+di.p+t.barwidth);
                            x0 = xa.c2p(di.b,true);
                            x1 = xa.c2p(di.s+di.b,true);
                        }
                        else {
                            x0 = xa.c2p(t.poffset+di.p);
                            x1 = xa.c2p(t.poffset+di.p+t.barwidth);
                            y1 = ya.c2p(di.s+di.b,true);
                            y0 = ya.c2p(di.b,true);
                        }

                        if(!$.isNumeric(x0) || !$.isNumeric(x1) ||
                                !$.isNumeric(y0) || !$.isNumeric(y1) ||
                                x0===x1 || y0===y1) {
                            d3.select(this).remove();
                            return;
                        }
                        var lw = (di.mlw+1 || t.mlw+1 ||
                                (di.t ? di.t.mlw : 0)+1)-1,
                            offset = d3.round((lw/2)%1,2);
                        function roundWithLine(v) {
                            // if there are explicit gaps, don't round,
                            // it can make the gaps look crappy
                            return (gl.bargap===0 && gl.bargroupgap===0) ?
                                d3.round(Math.round(v)-offset,2) : v;
                        }
                        function expandToVisible(v,vc) {
                            // if it's not in danger of disappearing entirely,
                            // round more precisely
                            return Math.abs(v-vc)>=2 ? roundWithLine(v) :
                            // but if it's very thin, expand it so it's
                            // necessarily visible, even if it might overlap
                            // its neighbor
                            (v>vc ? Math.ceil(v) : Math.floor(v));
                        }
                        if(!gl._forexport) {
                            // if bars are not fully opaque or they have a line
                            // around them, round to integer pixels, mainly for
                            // safari so we prevent overlaps from its expansive
                            // pixelation. if the bars ARE fully opaque and have
                            // no line, expand to a full pixel to make sure we
                            // can see them
                            var op = Plotly.Drawing.opacity(
                                    di.mc || t.mc || (di.t ? di.t.mc : '')),
                                fixpx = (op<1 || lw>0.01) ?
                                    roundWithLine : expandToVisible;
                            x0 = fixpx(x0,x1);
                            x1 = fixpx(x1,x0);
                            y0 = fixpx(y0,y1);
                            y1 = fixpx(y1,y0);
                        }
                        d3.select(this).attr('d',
                            'M'+x0+','+y0+'V'+y1+'H'+x1+'V'+y0+'Z');
                    });
            });
    };

    bars.style = function(gp,gl) {
        var s = gp.selectAll('g.trace.bars'),
            barcount = s.size();

        // first trace styling
        s.style('opacity',function(d){ return d[0].t.op; })

        // for gapless (either stacked or neighboring grouped) bars use
        // crispEdges to turn off antialiasing so an artificial gap
        // isn't introduced.
        .each(function(d){
            if((gl.barmode==='stack' && barcount>1) ||
                    (gl.bargap===0 && gl.bargroupgap===0 && !d[0].t.mlw)){
                d3.select(this).attr('shape-rendering','crispEdges');
            }
        });

        // then style the individual bars
        s.selectAll('g.points').each(function(d){
            var t = d.t||d[0].t;
            d3.select(this).selectAll('path').each(function(d) {
                // allow all marker and marker line colors to be scaled
                // by given max and min to colorscales
                var w = (d.mlw+1 || t.mlw+1 || (d.t ? d.t.mlw : 0)+1) - 1,
                    p = d3.select(this);
                p.style('stroke-width',w+'px')
                    .call(Plotly.Drawing.fillColor,
                        Plotly.Drawing.tryColorscale(s,t,'m')(
                            d.mc || t.mc || (d.t ? d.t.mc : '')));
                if(w) {
                    p.call(Plotly.Drawing.strokeColor,
                        Plotly.Drawing.tryColorscale(s,t,'ml')(
                            d.mlc || t.mlc || (d.t ? d.t.mlc : '')));
                }
            });
            // TODO: text markers on bars, either extra text or just bar values
            // d3.select(this).selectAll('text')
            //     .call(Plotly.Drawing.textPointStyle,d.t||d[0].t);
        });
    };

}()); // end Bars object definition
<|endoftext|>


<commit_before>'use strict';

var clear           = require('es5-ext/array/#/clear')
  , assign          = require('es5-ext/object/assign')
  , forEach         = require('es5-ext/object/for-each')
  , some            = require('es5-ext/object/some')
  , value           = require('es5-ext/object/valid-value')
  , isSet           = require('es6-set/is-set')
  , d               = require('d')
  , autoBind        = require('d/auto-bind')
  , validDbjs       = require('dbjs/valid-dbjs')
  , DbjsEvent       = require('dbjs/_setup/event')
  , validDbjsObject = require('dbjs/valid-dbjs-object')
  , serializeKey    = require('dbjs/_setup/serialize/key')

  , create = Object.create, getPrototypeOf = Object.getPrototypeOf
  , hasOwnProperty = Object.prototype.hasOwnProperty
  , SyncRoot, SyncMaster, propagateComputedItem
  , resolveTargetValue, resolveTargetObject;

resolveTargetObject = function (db, objectId) {
	var object = db.objects.unserialize(objectId);
	if ((object._kind_ === 'descriptor') && object.nested) return object.object._get_(object._sKey_);
	return object;
};
resolveTargetValue = function (db, objectId, sKey) {
	return resolveTargetObject(db, objectId)._get_(sKey);
};
propagateComputedItem = function (stamp, baseId, value, key) {
	var item = this.base.db.objects.unserialize(baseId + '*' + serializeKey(key));
	if ((value === undefined) && getPrototypeOf(item)._value_) value = false;
	new DbjsEvent(item, value, stamp); //jslint: ignore
};

SyncRoot = function (db, propertyName) {
	this.db = db;
	this.map = create(null);
	this.masterMap = create(null);
	this.propertyName = propertyName;
};

Object.defineProperties(SyncRoot.prototype, {
	sync: d(function (object) {
		validDbjsObject(object);
		if (this.masterMap[object.__id__]) return;
		this.masterMap[object.__id__] = true;
		this._syncMaster(object);
	}),
	_syncMaster: d(function (object) {
		if (this.map[object.__id__]) return;
		this.map[object.__id__] = true;
		this.map[object.__id__] = new SyncMaster(object, this);
	}),
	_unsyncMaster: d(function (object) {
		if (this.masterMap[object.__id__]) return;
		if (some(this.map, function (sync) {
				if (sync === true) return;
				if (sync.object === object) return;
				if (sync.descendants[object.__id__]) return true;
			})) {
			return;
		}
		this.map[object.__id__].remove();
	})
});

SyncMaster = function (object, base) {
	this.object = object;
	this.base = base;
	this.namesToSync = create(null);
	this.observed = [];
	this.descendants = create(null);
	this.extValuesMap = create(null);
	object.on('turn', this.onTurn);
	this.onTurn(object._lastOwnEvent_);
};
Object.defineProperties(SyncMaster.prototype, assign({
	initialized: d(false),
	initialize: d(function () {
		this.initialized = true;
		this.syncProperties(this.object);
		this.object.on('update', this.onDbEvent);
	}),
	syncProperties: d(function (object) {
		var sKey, desc, baseDesc, observable, event;

		for (sKey in object.__descriptors__) {
			desc = object.__descriptors__[sKey];
			if (desc.reverse != null) continue;
			if (desc.nested) {
				this.syncProperties(object.get(desc.key));
				continue;
			}
			if (desc[this.base.propertyName] === undefined) continue;
			baseDesc = desc;
			while (!baseDesc.hasOwnProperty(this.base.propertyName)) baseDesc = getPrototypeOf(baseDesc);
			if (!baseDesc.master.isPrototypeOf(object.master)) continue;

			// Export!
			if (!desc._resolveValueGetter_()) {

				// Static
				this.namesToSync[object.__id__ + '/' + sKey] = true;
				if (desc.multiple) {
					if (!object.hasOwnProperty('__multiples__')) continue;
					if (!hasOwnProperty.call(object.__multiples__, sKey)) continue;
					forEach(object.__multiples__[sKey], this.syncObjectDirectly, this);
					continue;
				}
				if (desc.object !== object) continue;
				this.syncObjectDirectly(desc);
				continue;
			}

			// Computable
			if (desc.multiple) {
				this.observed.push(observable = object._get_(sKey));
				event = object._getPropertyLastEvent_(sKey);
				this.groundComputedSet(observable, event);
				this.syncComputedSet(observable, resolveTargetValue(this.base.db, object.__id__, sKey),
					event);
				observable.on('change', this.onSetChangeEvent);
				continue;
			}
			this.observed.push(observable = object._getObservable_(sKey));
			this.syncComputedValue(observable, object._getPropertyLastEvent_(sKey));
			observable.on('change', this.onChangeEvent);
		}
	}),
	syncObjectDirectly: d(function (object) {
		var event = object._lastOwnEvent_;
		if (!event) return;
		this.syncEventDirectly(event);
	}),
	syncExternal: d(function (path, object, isMultiple, multipleValue) {
		var id, prevId;
		object = object.master;
		if (object === this.object) return;
		if (typeof object === 'function') return;
		if (object._kind_ !== 'object') return;
		if (object.constructor.prototype === object) return;
		id = object.__id__;
		if (isMultiple) {
			if (multipleValue) {
				if (!this.extValuesMap[path]) this.extValuesMap[path] = create(null);
				if (this.extValuesMap[path][id]) return;
				this.extValuesMap[path][id] = true;
				if (!this.descendants[id]) {
					this.descendants[id] = 0;
					this.base._syncMaster(object);
				}
				++this.descendants[id];
				return;
			}
			if (!this.extValuesMap[path]) return;
			if (!this.extValuesMap[path][id]) return;
			delete this.extValuesMap[path][id];
			if (!--this.descendants[id]) this.base._unsyncMaster(object);
			return;
		}
		prevId = this.extValuesMap[path];
		if (prevId && (!--this.descendants[prevId])) {
			this.base._unsyncMaster(object.database.objects.getById(prevId));
		}
		this.extValuesMap[path] = id;
		if (!this.descendants[id]) {
			this.descendants[id] = 0;
			this.base._syncMaster(object);
		}
		++this.descendants[id];
	}),
	syncEventDirectly: d(function (event) {
		var value = event.value, object = event.object, targetEvent;
		if (object._kind_ === 'descriptor') {
			if (value && value.hasOwnProperty('__id__')) {
				this.syncExternal(object.__valueId__, value);
				value = this.base.db.objects.getById(value.__id__);
			}
		} else if (object._kind_ === 'item') {
			if (object.key.hasOwnProperty('__id__')) {
				this.syncExternal(object.object.__id__ + '/' + object._pSKey_, object.key, true, value);
			}
		} else if (object._kind_ === 'object') {
			if (value && value.hasOwnProperty('__id__')) {
				value = this.base.db.objects.getById(value.__id__);
			}
		}
		object = this.base.db.objects.unserialize(event.object.__valueId__, value);
		targetEvent = object._lastOwnEvent_;
		if (targetEvent && (targetEvent.stamp >= event.stamp)) return;
		new DbjsEvent(object, value, event.stamp, event.sourceId, event.index); //jslint: ignore
	}),
	syncComputedValue: d(function (observable, event) {
		var source = observable.value
		  , targetObject = resolveTargetObject(this.base.db, observable.object.__id__)
		  , target = targetObject._get_(observable.__sKey__)
		  , stamp, targetLastEvent;
		if (source === target) return;
		if (source && source.hasOwnProperty('__id__')) this.syncExternal(observable.dbId, source);
		stamp = (event && event.stamp) || 0;
		targetLastEvent = targetObject._getOwnDescriptor_(observable.__sKey__)._lastOwnEvent_;
		if (targetLastEvent && (targetLastEvent.stamp >= stamp)) stamp = targetLastEvent.stamp + 1;
		new DbjsEvent(targetObject._getOwnDescriptor_(observable.__sKey__),
			source, stamp, event && event.sourceId); //jslint: ignore
		target = targetObject._get_(observable.__sKey__);
		if (source == null) {
			if (target == null) return;
		} else if (typeof source === typeof target) {
			return;
		}
		throw new Error("Database reduction error:\n" +
			"\tComputed value (" + observable.dbId + ") in result database after propagation didn't " +
			"match one in source\n" +
			"\tMost likely it's caused by model not being completely tagged for propagation\n" +
			"\t(type of propagated value doesn't match defined type)");
	}),
	groundComputedSet: d(function (observable, event) {
		var targetObject = resolveTargetObject(this.base.db, observable.object.__id__)
		  , stamp, targetLastEvent, desc;
		stamp = (event && event.stamp) || 0;
		desc = targetObject._getOwnDescriptor_(observable.__sKey__);
		targetLastEvent = desc._lastOwnEvent_;
		if (targetLastEvent && (targetLastEvent.stamp >= stamp)) stamp = targetLastEvent.stamp + 1;
		new DbjsEvent(desc, null, stamp, event && event.sourceId); //jslint: ignore
	}),
	syncComputedSet: d(function (source, target, event) {
		var sourceIterator, targetIterator, item, isDifferent, stamp;

		if (source.size === target.size) {
			if (!source.size) return;
			sourceIterator = source.values();
			targetIterator = target.values();
			item = sourceIterator.next();
			while (!item.done) {
				if (item.value !== targetIterator.next().value) {
					isDifferent = true;
					break;
				}
				item = sourceIterator.next();
			}
			if (sourceIterator._destroy) sourceIterator._destroy();
			if (targetIterator._destroy) targetIterator._destroy();
			if (!isDifferent) return;
		}
		stamp = (event && event.stamp) || 0;
		forEach(target.__setData__, function (item) {
			var event = item._lastOwnEvent_;
			if (!event) return;
			if (event.stamp >= stamp) stamp = event.stamp + 1;
		});
		target.forEach(function (item) {
			var sObj;
			if (!source.has(item)) {
				if (item.hasOwnProperty('__id__')) {
					sObj = source.object.database.objects.getById(item.__id__);
					if (!sObj) {
						throw new TypeError("Could not find '" + item.__id__ + "' object in source database");
					}
					this.syncExternal(target.dbId, sObj, true, undefined);
				}
				propagateComputedItem.call(this, stamp++, target.dbId, undefined, item);
			}
		}, this);
		source.forEach(function (item) {
			if (item.hasOwnProperty('__id__')) this.syncExternal(target.dbId, item, true, true);
			propagateComputedItem.call(this, stamp++, target.dbId, true, item);
		}, this);
		if (source.size !== target.size) {
			throw new Error("Database reduction error:\n" +
				"\tComputed set (" + source.dbId + ") in result database after propagation didn't " +
				"match one in source\n" +
				"\tMost likely it's caused by model not being completely tagged for propagation\n" +
				"\t(type of propagated set values doesn't match defined type)");
		}
	}),
	destroy: d(function () {
		this.object.off('update', this.onDbEvent);
		this.observed.forEach(function (observable) {
			observable.off('change', isSet(observable) ? this.onSetChangeEvent : this.onChangeEvent);
		}, this);
		clear.call(this.observed);
		this.initialized = false;
	}),
	remove: d(function () {
		this.destroy();
		delete this.base.map[this.object.__id__];
		delete this.base.masterMap[this.object.__id__];
		this.object.off('turn', this.onTurn);
	})
}, autoBind({
	onTurn: d(function (event) {
		this.syncEventDirectly(event);
		if (this.object.constructor.__id__ === 'Base') {
			if (!this.initialized) return;
			this.destroy();
			return;
		}
		if (this.initialized) return;
		this.initialize();
	}),
	onDbEvent: d(function (event) {
		var object = event.object;
		switch (object._kind_) {
		case 'descriptor':
			if (!this.namesToSync[object.__valueId__]) return;
			break;
		case 'item':
			if (!this.namesToSync[object.object.__id__ + '/' + object._pSKey_]) return;
			break;
		case 'object':
			return;
		case 'sub-descriptor':
			return;
		}
		this.syncEventDirectly(event);
	}),
	onChangeEvent: d(function (event) { this.syncComputedValue(event.target, event.dbjs); }),
	onSetChangeEvent: d(function (event) {
		this.syncComputedSet(event.target, resolveTargetValue(this.base.db,
			event.target.object.__id__, event.target.__sKey__), event.dbjs);
	})
})));

module.exports = function (targetDb, propertyName) {
	var sync = new SyncRoot(validDbjs(targetDb), value(propertyName));
	return sync.sync.bind(sync);
};
<commit_msg>Assure accurate object type resolution<commit_after>'use strict';

var clear           = require('es5-ext/array/#/clear')
  , assign          = require('es5-ext/object/assign')
  , forEach         = require('es5-ext/object/for-each')
  , some            = require('es5-ext/object/some')
  , value           = require('es5-ext/object/valid-value')
  , isSet           = require('es6-set/is-set')
  , d               = require('d')
  , autoBind        = require('d/auto-bind')
  , validDbjs       = require('dbjs/valid-dbjs')
  , DbjsEvent       = require('dbjs/_setup/event')
  , validDbjsObject = require('dbjs/valid-dbjs-object')
  , serializeKey    = require('dbjs/_setup/serialize/key')

  , create = Object.create, getPrototypeOf = Object.getPrototypeOf
  , hasOwnProperty = Object.prototype.hasOwnProperty
  , SyncRoot, SyncMaster, propagateComputedItem
  , resolveTargetValue, resolveTargetObject;

resolveTargetObject = function (db, objectId) {
	var object = db.objects.unserialize(objectId);
	if ((object._kind_ === 'descriptor') && object.nested) return object.object._get_(object._sKey_);
	return object;
};
resolveTargetValue = function (db, objectId, sKey) {
	return resolveTargetObject(db, objectId)._get_(sKey);
};
propagateComputedItem = function (stamp, baseId, value, key) {
	var item = this.base.db.objects.unserialize(baseId + '*' + serializeKey(key));
	if ((value === undefined) && getPrototypeOf(item)._value_) value = false;
	new DbjsEvent(item, value, stamp); //jslint: ignore
};

SyncRoot = function (db, propertyName) {
	this.db = db;
	this.map = create(null);
	this.masterMap = create(null);
	this.propertyName = propertyName;
};

Object.defineProperties(SyncRoot.prototype, {
	sync: d(function (object) {
		validDbjsObject(object);
		if (this.masterMap[object.__id__]) return;
		this.masterMap[object.__id__] = true;
		this._syncMaster(object);
	}),
	_syncMaster: d(function (object) {
		if (this.map[object.__id__]) return;
		this.map[object.__id__] = true;
		this.map[object.__id__] = new SyncMaster(object, this);
	}),
	_unsyncMaster: d(function (object) {
		if (this.masterMap[object.__id__]) return;
		if (some(this.map, function (sync) {
				if (sync === true) return;
				if (sync.object === object) return;
				if (sync.descendants[object.__id__]) return true;
			})) {
			return;
		}
		this.map[object.__id__].remove();
	})
});

SyncMaster = function (object, base) {
	this.object = object;
	this.base = base;
	this.namesToSync = create(null);
	this.observed = [];
	this.descendants = create(null);
	this.extValuesMap = create(null);
	object.on('turn', this.onTurn);
	this.onTurn(object._lastOwnEvent_);
};
Object.defineProperties(SyncMaster.prototype, assign({
	initialized: d(false),
	initialize: d(function () {
		this.initialized = true;
		this.syncProperties(this.object);
		this.object.on('update', this.onDbEvent);
	}),
	syncProperties: d(function (object) {
		var sKey, desc, baseDesc, observable, event;

		for (sKey in object.__descriptors__) {
			desc = object.__descriptors__[sKey];
			if (desc.reverse != null) continue;
			if (desc.nested) {
				this.syncProperties(object.get(desc.key));
				continue;
			}
			if (desc[this.base.propertyName] === undefined) continue;
			baseDesc = desc;
			while (!baseDesc.hasOwnProperty(this.base.propertyName)) baseDesc = getPrototypeOf(baseDesc);
			if (!baseDesc.master.isPrototypeOf(object.master)) continue;

			// Export!
			if (!desc._resolveValueGetter_()) {

				// Static
				this.namesToSync[object.__id__ + '/' + sKey] = true;
				if (desc.multiple) {
					if (!object.hasOwnProperty('__multiples__')) continue;
					if (!hasOwnProperty.call(object.__multiples__, sKey)) continue;
					forEach(object.__multiples__[sKey], this.syncObjectDirectly, this);
					continue;
				}
				if (desc.object !== object) continue;
				this.syncObjectDirectly(desc);
				continue;
			}

			// Computable
			if (desc.multiple) {
				this.observed.push(observable = object._get_(sKey));
				event = object._getPropertyLastEvent_(sKey);
				this.groundComputedSet(observable, event);
				this.syncComputedSet(observable, resolveTargetValue(this.base.db, object.__id__, sKey),
					event);
				observable.on('change', this.onSetChangeEvent);
				continue;
			}
			this.observed.push(observable = object._getObservable_(sKey));
			this.syncComputedValue(observable, object._getPropertyLastEvent_(sKey));
			observable.on('change', this.onChangeEvent);
		}
	}),
	syncObjectDirectly: d(function (object) {
		var event = object._lastOwnEvent_;
		if (!event) return;
		this.syncEventDirectly(event);
	}),
	syncExternal: d(function (path, object, isMultiple, multipleValue) {
		var id, prevId;
		object = object.master;
		if (object === this.object) return;
		if (typeof object === 'function') return;
		if (object._kind_ !== 'object') return;
		if (object.constructor.prototype === object) return;
		id = object.__id__;
		if (isMultiple) {
			if (multipleValue) {
				if (!this.extValuesMap[path]) this.extValuesMap[path] = create(null);
				if (this.extValuesMap[path][id]) return;
				this.extValuesMap[path][id] = true;
				if (!this.descendants[id]) {
					this.descendants[id] = 0;
					this.base._syncMaster(object);
				}
				++this.descendants[id];
				return;
			}
			if (!this.extValuesMap[path]) return;
			if (!this.extValuesMap[path][id]) return;
			delete this.extValuesMap[path][id];
			if (!--this.descendants[id]) this.base._unsyncMaster(object);
			return;
		}
		prevId = this.extValuesMap[path];
		if (prevId && (!--this.descendants[prevId])) {
			this.base._unsyncMaster(object.database.objects.getById(prevId));
		}
		this.extValuesMap[path] = id;
		if (!this.descendants[id]) {
			this.descendants[id] = 0;
			this.base._syncMaster(object);
		}
		++this.descendants[id];
	}),
	syncEventDirectly: d(function (event) {
		var value = event.value, object = event.object, targetEvent, constructor;
		if (object._kind_ === 'descriptor') {
			if (value && value.hasOwnProperty('__id__')) {
				this.syncExternal(object.__valueId__, value);
				value = this.base.db.objects.getById(value.__id__);
			}
		} else if (object._kind_ === 'item') {
			if (object.key.hasOwnProperty('__id__')) {
				this.syncExternal(object.object.__id__ + '/' + object._pSKey_, object.key, true, value);
			}
		} else if (object._kind_ === 'object') {
			if (value && value.hasOwnProperty('__id__')) {
				value = this.base.db.objects.getById(value.__id__);
				if (value.constructor.prototype === value) constructor = value.constructor;
			}
		}
		object = this.base.db.objects.unserialize(event.object.__valueId__, constructor);
		targetEvent = object._lastOwnEvent_;
		if (targetEvent && (targetEvent.stamp >= event.stamp)) return;
		new DbjsEvent(object, value, event.stamp, event.sourceId, event.index); //jslint: ignore
	}),
	syncComputedValue: d(function (observable, event) {
		var source = observable.value
		  , targetObject = resolveTargetObject(this.base.db, observable.object.__id__)
		  , target = targetObject._get_(observable.__sKey__)
		  , stamp, targetLastEvent;
		if (source === target) return;
		if (source && source.hasOwnProperty('__id__')) this.syncExternal(observable.dbId, source);
		stamp = (event && event.stamp) || 0;
		targetLastEvent = targetObject._getOwnDescriptor_(observable.__sKey__)._lastOwnEvent_;
		if (targetLastEvent && (targetLastEvent.stamp >= stamp)) stamp = targetLastEvent.stamp + 1;
		new DbjsEvent(targetObject._getOwnDescriptor_(observable.__sKey__),
			source, stamp, event && event.sourceId); //jslint: ignore
		target = targetObject._get_(observable.__sKey__);
		if (source == null) {
			if (target == null) return;
		} else if (typeof source === typeof target) {
			return;
		}
		throw new Error("Database reduction error:\n" +
			"\tComputed value (" + observable.dbId + ") in result database after propagation didn't " +
			"match one in source\n" +
			"\tMost likely it's caused by model not being completely tagged for propagation\n" +
			"\t(type of propagated value doesn't match defined type)");
	}),
	groundComputedSet: d(function (observable, event) {
		var targetObject = resolveTargetObject(this.base.db, observable.object.__id__)
		  , stamp, targetLastEvent, desc;
		stamp = (event && event.stamp) || 0;
		desc = targetObject._getOwnDescriptor_(observable.__sKey__);
		targetLastEvent = desc._lastOwnEvent_;
		if (targetLastEvent && (targetLastEvent.stamp >= stamp)) stamp = targetLastEvent.stamp + 1;
		new DbjsEvent(desc, null, stamp, event && event.sourceId); //jslint: ignore
	}),
	syncComputedSet: d(function (source, target, event) {
		var sourceIterator, targetIterator, item, isDifferent, stamp;

		if (source.size === target.size) {
			if (!source.size) return;
			sourceIterator = source.values();
			targetIterator = target.values();
			item = sourceIterator.next();
			while (!item.done) {
				if (item.value !== targetIterator.next().value) {
					isDifferent = true;
					break;
				}
				item = sourceIterator.next();
			}
			if (sourceIterator._destroy) sourceIterator._destroy();
			if (targetIterator._destroy) targetIterator._destroy();
			if (!isDifferent) return;
		}
		stamp = (event && event.stamp) || 0;
		forEach(target.__setData__, function (item) {
			var event = item._lastOwnEvent_;
			if (!event) return;
			if (event.stamp >= stamp) stamp = event.stamp + 1;
		});
		target.forEach(function (item) {
			var sObj;
			if (!source.has(item)) {
				if (item.hasOwnProperty('__id__')) {
					sObj = source.object.database.objects.getById(item.__id__);
					if (!sObj) {
						throw new TypeError("Could not find '" + item.__id__ + "' object in source database");
					}
					this.syncExternal(target.dbId, sObj, true, undefined);
				}
				propagateComputedItem.call(this, stamp++, target.dbId, undefined, item);
			}
		}, this);
		source.forEach(function (item) {
			if (item.hasOwnProperty('__id__')) this.syncExternal(target.dbId, item, true, true);
			propagateComputedItem.call(this, stamp++, target.dbId, true, item);
		}, this);
		if (source.size !== target.size) {
			throw new Error("Database reduction error:\n" +
				"\tComputed set (" + source.dbId + ") in result database after propagation didn't " +
				"match one in source\n" +
				"\tMost likely it's caused by model not being completely tagged for propagation\n" +
				"\t(type of propagated set values doesn't match defined type)");
		}
	}),
	destroy: d(function () {
		this.object.off('update', this.onDbEvent);
		this.observed.forEach(function (observable) {
			observable.off('change', isSet(observable) ? this.onSetChangeEvent : this.onChangeEvent);
		}, this);
		clear.call(this.observed);
		this.initialized = false;
	}),
	remove: d(function () {
		this.destroy();
		delete this.base.map[this.object.__id__];
		delete this.base.masterMap[this.object.__id__];
		this.object.off('turn', this.onTurn);
	})
}, autoBind({
	onTurn: d(function (event) {
		this.syncEventDirectly(event);
		if (this.object.constructor.__id__ === 'Base') {
			if (!this.initialized) return;
			this.destroy();
			return;
		}
		if (this.initialized) return;
		this.initialize();
	}),
	onDbEvent: d(function (event) {
		var object = event.object;
		switch (object._kind_) {
		case 'descriptor':
			if (!this.namesToSync[object.__valueId__]) return;
			break;
		case 'item':
			if (!this.namesToSync[object.object.__id__ + '/' + object._pSKey_]) return;
			break;
		case 'object':
			return;
		case 'sub-descriptor':
			return;
		}
		this.syncEventDirectly(event);
	}),
	onChangeEvent: d(function (event) { this.syncComputedValue(event.target, event.dbjs); }),
	onSetChangeEvent: d(function (event) {
		this.syncComputedSet(event.target, resolveTargetValue(this.base.db,
			event.target.object.__id__, event.target.__sKey__), event.dbjs);
	})
})));

module.exports = function (targetDb, propertyName) {
	var sync = new SyncRoot(validDbjs(targetDb), value(propertyName));
	return sync.sync.bind(sync);
};
<|endoftext|>


<commit_before>/**
 * @license Copyright 2013 Google Inc. All rights reserved.
 * Use of this source code is governed by the Apache license that can be
 * found in the LICENSE file.
 */

/**
 * Draws the image file to the canvas.
 */
bitmapper.setCanvasToImage = function() {
  var image = bitmapper.imageFile.image;
  bitmapper.sourceCanvas.width = image.width;
  bitmapper.sourceCanvas.height = image.height;
  var sourceContext = bitmapper.sourceCanvas.getContext('2d');
  sourceContext.drawImage(image, 0, 0);
  bitmapper.zoomManager.drawDisplayCanvas();
};

/**
 * Displays a file picker and loads the file.
 * Set file entry then load file.
 */
bitmapper.openFile = function() {
  chrome.fileSystem.chooseEntry(
      {
        'type': 'openWritableFile',
        'accepts': [{'description': '*.png', 'extensions': ['png']}]
      },
      function(entry) {
        if (!entry) {
          bitmapper.statusMessage('Nothing selected.');
        } else if (!bitmapper.imageFile) {
          bitmapper.imageFile = new bitmapper.ImageFile();
        } else {
          // Enable save button only if user opens file and doesn't cancel.
          document.getElementById('saveButton').disabled = false;
        }
        bitmapper.zoomManager.setZoomFactor(1);
        bitmapper.imageFile.loadFile(entry, bitmapper.setCanvasToImage);
        // TODO(dadisusila): Handle errors while loading.
        bitmapper.statusMessage(bitmapper.imageFile.fileEntry.name +
                                ' opened.');
      });
};

/**
 * Opens save dialog box and allows user to save image.
 * Set the file entry then save.
 */
bitmapper.saveAsFile = function() {
  chrome.fileSystem.chooseEntry(
      {'type': 'saveFile'},
      function(entry) {
        if (!entry) {
          bitmapper.statusMessage('Nothing selected.');
        } else {
          if (!bitmapper.imageFile)
            bitmapper.imageFile = new bitmapper.ImageFile();
          bitmapper.imageFile.setFileEntry(entry);
          bitmapper.saveFile();
          document.getElementById('saveButton').disabled = false;
        }
      });
};

/**
 * Saves to current file entry.
 */
bitmapper.saveFile = function() {
  // TODO(dadisusila): Handle error cases for saving.
  bitmapper.statusMessage(bitmapper.imageFile.fileEntry.name + ' saved.');
  bitmapper.imageFile.saveFile(bitmapper.sourceCanvas);
};

/**
 * Outputs status message.
 * @param {string} status
 */
bitmapper.statusMessage = function(status) {
  document.getElementById('output').textContent = status;
};

/**
 * Clears the canvas.
 */
bitmapper.clearCanvas = function() {
  var sourceContext = bitmapper.sourceCanvas.getContext('2d');
  sourceContext.clearRect(0, 0, bitmapper.sourceCanvas.width,
      bitmapper.sourceCanvas.height);
  bitmapper.zoomManager.drawDisplayCanvas();
};

/**
 * Scales to double.
 */
bitmapper.zoomIn = function() {
  if (!bitmapper.zoomManager.exceededZoomLimit()) {
    bitmapper.zoomManager.setZoomFactor(
        bitmapper.zoomManager.getZoomFactor() * 2);
    bitmapper.zoomManager.drawDisplayCanvas();
  }
};

/**
 * Scales to half.
 */
bitmapper.zoomOut = function() {
  bitmapper.zoomManager.setZoomFactor(
      bitmapper.zoomManager.getZoomFactor() * 0.5);
  bitmapper.zoomManager.drawDisplayCanvas();
};

/**
 * Gets mouse co-ordinates for source canvas.
 * @param {Event} mouseEvent
 */
bitmapper.mouseMoveCoordinates = function(mouseEvent) {
  bitmapper.mousemove(
      bitmapper.zoomManager.getSourceCoordinate(mouseEvent.offsetX),
      bitmapper.zoomManager.getSourceCoordinate(mouseEvent.offsetY),
      bitmapper.colorPalette.getSelectedColor());
};

/**
 * Gets mouse co-ordinates for source canvas.
 * @param {Event} mouseEvent
 */
bitmapper.mouseDownCoordinates = function(mouseEvent) {
  bitmapper.mousedown(
      bitmapper.zoomManager.getSourceCoordinate(mouseEvent.offsetX),
      bitmapper.zoomManager.getSourceCoordinate(mouseEvent.offsetY),
      bitmapper.colorPalette.getSelectedColor());
};

/**
 * Set background color of selected color box to selected color.
 */
bitmapper.setSelectedColorBox = function() {
  document.getElementById('colorSelector').value =
      bitmapper.rgbToHex(bitmapper.colorPalette.getSelectedColor());
};

/**
 * Changes background of selected cell in palette to selected color.
 * @this {HTMLElement}
 */
bitmapper.updatePalette = function() {
  bitmapper.colorPalette.updateCellColor(
      this.value, bitmapper.colorPalette.getSelectedIndex());
};

/**
 * Entry point.
 */
bitmapper.start = function() {
  bitmapper.displayCanvas = document.getElementById('imageCanvas');
  bitmapper.sourceCanvas = document.getElementById('sourceCanvas');

  document.getElementById('clearButton').onclick = function() {
    bitmapper.clearCanvas();
  };

  var open = document.getElementById('openButton');
  open.addEventListener('click', bitmapper.openFile, false);

  // Create zoom manager at start.
  bitmapper.zoomManager = new bitmapper.ZoomManager(
      bitmapper.sourceCanvas, bitmapper.displayCanvas);

  bitmapper.displayCanvas.addEventListener('mousedown',
      bitmapper.mouseDownCoordinates, false);
  bitmapper.displayCanvas.addEventListener('mousemove',
      bitmapper.mouseMoveCoordinates, false);
  bitmapper.displayCanvas.addEventListener('mouseup',
      bitmapper.mouseup, false);
  bitmapper.displayCanvas.addEventListener('mouseleave',
      bitmapper.mouseup, false);

  var save = document.getElementById('saveButton');
  save.addEventListener('click', bitmapper.saveFile, false);

  var saveAs = document.getElementById('saveAsButton');
  saveAs.addEventListener('click', bitmapper.saveAsFile, false);

  document.getElementById('zoomInButton')
      .addEventListener('click', bitmapper.zoomIn, false);
  document.getElementById('zoomOutButton')
      .addEventListener('click', bitmapper.zoomOut, false);

  // Create color palette at start.
  // Callback sets the color selector box to the selected color.
  bitmapper.colorPalette = new bitmapper.ColorPalette(
      document.getElementById('paletteContainer'),
            bitmapper.setSelectedColorBox);
  var initialColors = ['#000000', '#ffff00', '#0000ff', '#ff00ff',
                      '#cc00ff', '#9900ff', '#ff6600', '#0099ff'];
  bitmapper.colorPalette.generatePalette(initialColors);
  // Set color selector as first color in palette.
  bitmapper.setSelectedColorBox();

  document.getElementById('colorSelector')
     .addEventListener('change', bitmapper.updatePalette, false);
};

/** Closure called when the window finishes loading. */
window.onload = bitmapper.start;
<commit_msg>Rework enable save button after first open.<commit_after>/**
 * @license Copyright 2013 Google Inc. All rights reserved.
 * Use of this source code is governed by the Apache license that can be
 * found in the LICENSE file.
 */

/**
 * Draws the image file to the canvas.
 */
bitmapper.setCanvasToImage = function() {
  var image = bitmapper.imageFile.image;
  bitmapper.sourceCanvas.width = image.width;
  bitmapper.sourceCanvas.height = image.height;
  var sourceContext = bitmapper.sourceCanvas.getContext('2d');
  sourceContext.drawImage(image, 0, 0);
  bitmapper.zoomManager.setZoomFactor(1);
  bitmapper.zoomManager.drawDisplayCanvas();
};

/**
 * Displays a file picker and loads the file.
 * Set file entry then load file.
 */
bitmapper.openFile = function() {
  chrome.fileSystem.chooseEntry(
      {
        'type': 'openWritableFile',
        'accepts': [{'description': '*.png', 'extensions': ['png']}]
      },
      function(entry) {
        if (!entry) {
          bitmapper.statusMessage('Nothing selected.');
          return;
        }
        if (!bitmapper.imageFile)
          bitmapper.imageFile = new bitmapper.ImageFile();

        // Enable save button only if user opens file and doesn't cancel.
        document.getElementById('saveButton').disabled = false;
        bitmapper.imageFile.loadFile(entry, bitmapper.setCanvasToImage);
        // TODO(dadisusila): Handle errors while loading.
        bitmapper.statusMessage(bitmapper.imageFile.fileEntry.name +
                                ' opened.');
      });
};

/**
 * Opens save dialog box and allows user to save image.
 * Set the file entry then save.
 */
bitmapper.saveAsFile = function() {
  chrome.fileSystem.chooseEntry(
      {'type': 'saveFile'},
      function(entry) {
        if (!entry) {
          bitmapper.statusMessage('Nothing selected.');
        } else {
          if (!bitmapper.imageFile)
            bitmapper.imageFile = new bitmapper.ImageFile();
          bitmapper.imageFile.setFileEntry(entry);
          bitmapper.saveFile();
          document.getElementById('saveButton').disabled = false;
        }
      });
};

/**
 * Saves to current file entry.
 */
bitmapper.saveFile = function() {
  // TODO(dadisusila): Handle error cases for saving.
  bitmapper.statusMessage(bitmapper.imageFile.fileEntry.name + ' saved.');
  bitmapper.imageFile.saveFile(bitmapper.sourceCanvas);
};

/**
 * Outputs status message.
 * @param {string} status
 */
bitmapper.statusMessage = function(status) {
  document.getElementById('output').textContent = status;
};

/**
 * Clears the canvas.
 */
bitmapper.clearCanvas = function() {
  var sourceContext = bitmapper.sourceCanvas.getContext('2d');
  sourceContext.clearRect(0, 0, bitmapper.sourceCanvas.width,
      bitmapper.sourceCanvas.height);
  bitmapper.zoomManager.drawDisplayCanvas();
};

/**
 * Scales to double.
 */
bitmapper.zoomIn = function() {
  if (!bitmapper.zoomManager.exceededZoomLimit()) {
    bitmapper.zoomManager.setZoomFactor(
        bitmapper.zoomManager.getZoomFactor() * 2);
    bitmapper.zoomManager.drawDisplayCanvas();
  }
};

/**
 * Scales to half.
 */
bitmapper.zoomOut = function() {
  bitmapper.zoomManager.setZoomFactor(
      bitmapper.zoomManager.getZoomFactor() * 0.5);
  bitmapper.zoomManager.drawDisplayCanvas();
};

/**
 * Gets mouse co-ordinates for source canvas.
 * @param {Event} mouseEvent
 */
bitmapper.mouseMoveCoordinates = function(mouseEvent) {
  bitmapper.mousemove(
      bitmapper.zoomManager.getSourceCoordinate(mouseEvent.offsetX),
      bitmapper.zoomManager.getSourceCoordinate(mouseEvent.offsetY),
      bitmapper.colorPalette.getSelectedColor());
};

/**
 * Gets mouse co-ordinates for source canvas.
 * @param {Event} mouseEvent
 */
bitmapper.mouseDownCoordinates = function(mouseEvent) {
  bitmapper.mousedown(
      bitmapper.zoomManager.getSourceCoordinate(mouseEvent.offsetX),
      bitmapper.zoomManager.getSourceCoordinate(mouseEvent.offsetY),
      bitmapper.colorPalette.getSelectedColor());
};

/**
 * Set background color of selected color box to selected color.
 */
bitmapper.setSelectedColorBox = function() {
  document.getElementById('colorSelector').value =
      bitmapper.rgbToHex(bitmapper.colorPalette.getSelectedColor());
};

/**
 * Changes background of selected cell in palette to selected color.
 * @this {HTMLElement}
 */
bitmapper.updatePalette = function() {
  bitmapper.colorPalette.updateCellColor(
      this.value, bitmapper.colorPalette.getSelectedIndex());
};

/**
 * Entry point.
 */
bitmapper.start = function() {
  bitmapper.displayCanvas = document.getElementById('imageCanvas');
  bitmapper.sourceCanvas = document.getElementById('sourceCanvas');

  document.getElementById('clearButton').onclick = function() {
    bitmapper.clearCanvas();
  };

  var open = document.getElementById('openButton');
  open.addEventListener('click', bitmapper.openFile, false);

  // Create zoom manager at start.
  bitmapper.zoomManager = new bitmapper.ZoomManager(
      bitmapper.sourceCanvas, bitmapper.displayCanvas);

  bitmapper.displayCanvas.addEventListener('mousedown',
      bitmapper.mouseDownCoordinates, false);
  bitmapper.displayCanvas.addEventListener('mousemove',
      bitmapper.mouseMoveCoordinates, false);
  bitmapper.displayCanvas.addEventListener('mouseup',
      bitmapper.mouseup, false);
  bitmapper.displayCanvas.addEventListener('mouseleave',
      bitmapper.mouseup, false);

  var save = document.getElementById('saveButton');
  save.addEventListener('click', bitmapper.saveFile, false);

  var saveAs = document.getElementById('saveAsButton');
  saveAs.addEventListener('click', bitmapper.saveAsFile, false);

  document.getElementById('zoomInButton')
      .addEventListener('click', bitmapper.zoomIn, false);
  document.getElementById('zoomOutButton')
      .addEventListener('click', bitmapper.zoomOut, false);

  // Create color palette at start.
  // Callback sets the color selector box to the selected color.
  bitmapper.colorPalette = new bitmapper.ColorPalette(
      document.getElementById('paletteContainer'),
            bitmapper.setSelectedColorBox);
  var initialColors = ['#000000', '#ffff00', '#0000ff', '#ff00ff',
                      '#cc00ff', '#9900ff', '#ff6600', '#0099ff'];
  bitmapper.colorPalette.generatePalette(initialColors);
  // Set color selector as first color in palette.
  bitmapper.setSelectedColorBox();

  document.getElementById('colorSelector')
     .addEventListener('change', bitmapper.updatePalette, false);
};

/** Closure called when the window finishes loading. */
window.onload = bitmapper.start;
<|endoftext|>


<commit_before>import AbstractConnection from "./AbstractConnection";

let connCounter = 0;

const mockAPI = {
	create: (callback) => {
		connCounter++;
		callback({connectionId: connCounter});
	},

	connect: (connectionId, callback) => {
		callback(connectionId);
	},

	send: (connectionId, arrayBuffer, callback) => {}, // TODO

	onReceive: {
		addListener: (info) => {}, // TODO
	},

	onReceiveError: {
		addListener: (info) = {}, // TODO
	},

	close: (connectionId, callback) => callback(),
};

export default class MockConnection extends AbstractConnection {

	static isSupported() {
		return true;
	}

	static connect() {
		return new Promise(resolve => {
			mockAPI.create(createInfo => {
				mockAPI.connect(createInfo.connectionId, () => {
					resolve(new MockConnection(mockAPI, createInfo.connectionId));
				});
			});
		});
	}

	close(callback) {
		this.api.close(this.id, () => {
			this.isClosed = true;
			if (callback) {
				callback();
			}
		});
	}

	// Sends arrayBuffer and returns a Promise for it.
	// The promise gets resolved if all bytes of arrayBuffer were sent
	// and rejected if less bytes were sent, or if there was another error.
	send(arrayBuffer) {
		return new Promise((resolve, reject) => {
			this.api.send(this.id, arrayBuffer, (bytesSent) => {
				if (bytesSent === arrayBuffer.byteLength) {
					resolve({arrayBuffer, bytesSent, error: null});
				} else {
					reject({arrayBuffer, bytesSent, error: new Error("not all bytes sent")});
				}
			});
		});
	}

}
<commit_msg>fix typo<commit_after>import AbstractConnection from "./AbstractConnection";

let connCounter = 0;

const mockAPI = {
	create: (callback) => {
		connCounter++;
		callback({connectionId: connCounter});
	},

	connect: (connectionId, callback) => {
		callback(connectionId);
	},

	send: (connectionId, arrayBuffer, callback) => {}, // TODO

	onReceive: {
		addListener: (info) => {}, // TODO
	},

	onReceiveError: {
		addListener: (info) => {}, // TODO
	},

	close: (connectionId, callback) => callback(),
};

export default class MockConnection extends AbstractConnection {

	static isSupported() {
		return true;
	}

	static connect() {
		return new Promise(resolve => {
			mockAPI.create(createInfo => {
				mockAPI.connect(createInfo.connectionId, () => {
					resolve(new MockConnection(mockAPI, createInfo.connectionId));
				});
			});
		});
	}

	close(callback) {
		this.api.close(this.id, () => {
			this.isClosed = true;
			if (callback) {
				callback();
			}
		});
	}

	// Sends arrayBuffer and returns a Promise for it.
	// The promise gets resolved if all bytes of arrayBuffer were sent
	// and rejected if less bytes were sent, or if there was another error.
	send(arrayBuffer) {
		return new Promise((resolve, reject) => {
			this.api.send(this.id, arrayBuffer, (bytesSent) => {
				if (bytesSent === arrayBuffer.byteLength) {
					resolve({arrayBuffer, bytesSent, error: null});
				} else {
					reject({arrayBuffer, bytesSent, error: new Error("not all bytes sent")});
				}
			});
		});
	}

}
<|endoftext|>


<commit_before>const pluralize = require('./pluralize.js')

class I18nContext {
  constructor (repository, languageCode, templateData, config) {
    this.repository = repository
    this.defaultContext = templateData
    this.config = config
    this.locale(languageCode || this.config.defaultLanguage)
  }

  locale (languageCode) {
    if (!languageCode) {
      return this.languageCode
    }

    const code = languageCode.toLowerCase()
    const shortCode = code.split('-')[0]

    if (!this.repository[code] || !this.repository[shortCode]) {
      this.languageCode = this.config.defaultLanguage
      this.shortLanguageCode = this.languageCode.split('-')[0]
      return
    }

    this.languageCode = code
    this.shortLanguageCode = shortCode
  }

  getTemplate (languageCode, resourceKey = '') {
    return resourceKey
      .split('.')
      .reduce((acc, key) => acc && acc[key], this.repository[languageCode])
  }

  t (resourceKey, context) {
    let template = this.getTemplate(this.languageCode, resourceKey) || this.getTemplate(this.shortLanguageCode, resourceKey)
    if (!template) {
      if (!this.config.allowMissing) {
        throw new Error(`telegraf-i18n: '${this.languageCode}.${resourceKey}' not found`)
      }
      template = () => resourceKey
    }
    return template(Object.assign({
      pluralize: pluralize(this.shortLanguageCode)
    }, this.defaultContext, context))
  }
}

module.exports = I18nContext
<commit_msg>fix: select locale en for en-US<commit_after>const pluralize = require('./pluralize.js')

class I18nContext {
  constructor (repository, languageCode, templateData, config) {
    this.repository = repository
    this.defaultContext = templateData
    this.config = config
    this.locale(languageCode || this.config.defaultLanguage)
  }

  locale (languageCode) {
    if (!languageCode) {
      return this.languageCode
    }

    const code = languageCode.toLowerCase()
    const shortCode = code.split('-')[0]

    if (!this.repository[code] && !this.repository[shortCode]) {
      this.languageCode = this.config.defaultLanguage
      this.shortLanguageCode = this.languageCode.split('-')[0]
      return
    }

    this.languageCode = code
    this.shortLanguageCode = shortCode
  }

  getTemplate (languageCode, resourceKey = '') {
    return resourceKey
      .split('.')
      .reduce((acc, key) => acc && acc[key], this.repository[languageCode])
  }

  t (resourceKey, context) {
    let template = this.getTemplate(this.languageCode, resourceKey) || this.getTemplate(this.shortLanguageCode, resourceKey)
    if (!template) {
      if (!this.config.allowMissing) {
        throw new Error(`telegraf-i18n: '${this.languageCode}.${resourceKey}' not found`)
      }
      template = () => resourceKey
    }
    return template(Object.assign({
      pluralize: pluralize(this.shortLanguageCode)
    }, this.defaultContext, context))
  }
}

module.exports = I18nContext
<|endoftext|>


<commit_before>require('./support/helper');

var path = require("path")
  , expect = require("expect.js")
  , fs = require("fs")
  , Backbone = require("backbone")
  , _ = require("underscore")
  , jqts = require("../lib/util").jqts
  , EndDash = require("../lib/end-dash")
  , generateTemplate = require("./support/generate_template")

describe("A template with partials", function() {
  it("should do collections", function() {
    var templates = [
      "/support/templates/partials.html",
      "/support/templates/embedded_partial.html",
      "/support/templates/list_item.html"
    ]

    var model = {
      items: new Backbone.Collection([{ variable: "wat1" }, { variable: "wat2" }]),
      thing: new Backbone.Model({ name: "Zach" })
    }

    _(templates).each(function(template) {
      EndDash.registerTemplate(template, fs.readFileSync(__dirname + template).toString())
    })

    var template = generateTemplate(model, '/support/templates/partials.html')

    expect($(".items- li div div:nth-child(1)").html()).to.be("wat1")
    expect($(".items- li div div:nth-child(2)").html()).to.be("wat2")
    expect($(".thing- .name-").html()).to.be("Zach")
    expect($("embed").length).to.be(0)
  })
})
<commit_msg>test/partials<commit_after>require('./support/helper');

var path = require("path"),
    expect = require("expect.js"),
    fs = require("fs"),
    Backbone = require("backbone"),
    _ = require("underscore"),
    jqts = require("../lib/util").jqts,
    EndDash = require("../lib/end-dash"),
    generateTemplate = require("./support/generate_template");

describe("A template with partials", function() {
  it("should do collections", function() {
    var templates = [
      "/support/templates/partials.html",
      "/support/templates/embedded_partial.html",
      "/support/templates/list_item.html"
    ];

    var model = {
      items: new Backbone.Collection([{ variable: "wat1" }, { variable: "wat2" }]),
      thing: new Backbone.Model({ name: "Zach" })
    };

    _(templates).each(function(template) {
      EndDash.registerTemplate(template, fs.readFileSync(__dirname + template).toString());
    });

    var template = generateTemplate(model, '/support/templates/partials.html');

    expect($(".items- li div div:nth-child(1)").html()).to.be("wat1");
    expect($(".items- li div div:nth-child(2)").html()).to.be("wat2");
    expect($(".thing- .name-").html()).to.be("Zach");
    expect($("embed").length).to.be(0);
  });
});
<|endoftext|>


<commit_before>(function($){

  function convertDiff(name, table) {
    var inline = table.className == 'inline';
    var ths = table.tHead.rows[0].cells;
    var afile, bfile;
    if ( inline ) {
        afile = ths[0].title;
        bfile = ths[1].title;
    } else {
        afile = $(ths[0]).find('a').text();
        bfile = $(ths[1]).find('a').text();
    }
    if ( afile.match(/^Revision /) ) {
        afile = 'a/' + name;
        bfile = 'b/' + name;
    }
    var lines = [
      "Index: " + name,
      "===================================================================",
      "--- " + afile.replace(/File /, ''),
      "+++ " + bfile.replace(/File /, ''),
    ];
    var sepIndex = 0;
    var oldOffset = 0, oldLength = 0, newOffset = 0, newLength = 0;

    for (var i = 0; i
 0) {
          if (!oldOffset && oldLength) oldOffset = 1
          if (!newOffset && newLength) newOffset = 1
          lines[sepIndex] = lines[sepIndex]
            .replace("{1}", oldOffset).replace("{2}", oldLength)
            .replace("{3}", newOffset).replace("{4}", newLength);
        }
        sepIndex = lines.length;
        lines.push("@@ -{1},{2} +{3},{4} @@");
        oldOffset = 0, oldLength = 0, newOffset = 0, newLength = 0;
        if (tBody.className == "skipped") continue;
      }
      var tmpLines = [];
      for (var j = 0; j < tBody.rows.length; j++) {
        var cells = tBody.rows[j].cells;
        var oldLineNo = parseInt($(cells[0]).text());
        var newLineNo = parseInt($(cells[inline ? 1 : 2]).text());
        if (tBody.className == 'unmod') {
          lines.push(" " + $(cells[inline ? 2 : 1]).text());
          oldLength += 1;
          newLength += 1;
          if (!oldOffset) oldOffset = oldLineNo;
          if (!newOffset) newOffset = newLineNo;
        } else {
          var oldLine;
          var newLine;
          if (inline) {
            oldLine = newLine = $(cells[2]).text();
          } else {
            oldLine = $(cells[1]).text();
            newLine = $(cells[3]).text();
          }
          if (!isNaN(oldLineNo)) {
            lines.push("-" + oldLine);
            oldLength += 1;
          }
          if (!isNaN(newLineNo)) {
            tmpLines.push("+" + newLine);
            newLength += 1;
          }
        }
      }
      if (tmpLines.length > 0) {
        lines = lines.concat(tmpLines);
      }
    }

    if (!oldOffset && oldLength) oldOffset = 1;
    if (!newOffset && newLength) newOffset = 1;
    lines[sepIndex] = lines[sepIndex]
      .replace("{1}", oldOffset).replace("{2}", oldLength)
      .replace("{3}", newOffset).replace("{4}", newLength);

    /* remove trailing   and join lines (with CR for IExplorer) */
    for ( var i = 0; i < lines.length; i++ )
        if ( lines[i] )
            lines[i] = lines[i].replace(/\xa0$/, '');
    return lines.join($.browser.msie ? "\r" : "\n");
  }

  $(document).ready(function($) {
    $("div.diff h2").each(function() {
      var switcher = $("").prependTo(this);
      var name = $.trim($(this).text());
      var table = $(this).siblings("table").get(0);
      if (! table) return;
      var pre = $("

").hide().insertAfter(table);
      $("Tabular").click(function() {
        $(pre).hide();
        $(table).show();
        $(this).addClass("active").siblings("span").removeClass("active");
        return false;
      }).addClass("active").appendTo(switcher);
      $("Unified").click(function() {
        $(table).hide();
        if (!pre.get(0).firstChild) pre.text(convertDiff(name, table));
        $(pre).fadeIn("fast")
        $(this).addClass("active").siblings("span").removeClass("active");
        return false;
      }).appendTo(switcher);
    });
  });

})(jQuery);
<commit_msg>Plain text diff Javascript converter now handles the ''No newline at end of file'' condition.<commit_after>(function($){

  function convertDiff(name, table) {
    var inline = table.className == 'inline';
    var ths = table.tHead.rows[0].cells;
    var afile, bfile;
    if ( inline ) {
        afile = ths[0].title;
        bfile = ths[1].title;
    } else {
        afile = $(ths[0]).find('a').text();
        bfile = $(ths[1]).find('a').text();
    }
    if ( afile.match(/^Revision /) ) {
        afile = 'a/' + name;
        bfile = 'b/' + name;
    }
    var lines = [
      "Index: " + name,
      "===================================================================",
      "--- " + afile.replace(/File /, ''),
      "+++ " + bfile.replace(/File /, ''),
    ];
    var sepIndex = 0;
    var oldOffset = 0, oldLength = 0, newOffset = 0, newLength = 0;

    for (var i = 0; i
 0) {
          if (!oldOffset && oldLength) oldOffset = 1
          if (!newOffset && newLength) newOffset = 1
          lines[sepIndex] = lines[sepIndex]
            .replace("{1}", oldOffset).replace("{2}", oldLength)
            .replace("{3}", newOffset).replace("{4}", newLength);
        }
        sepIndex = lines.length;
        lines.push("@@ -{1},{2} +{3},{4} @@");
        oldOffset = 0, oldLength = 0, newOffset = 0, newLength = 0;
        if (tBody.className == "skipped") continue;
      }
      var tmpLines = [];
      for (var j = 0; j < tBody.rows.length; j++) {
        var cells = tBody.rows[j].cells;
        var oldLineNo = parseInt($(cells[0]).text());
        var newLineNo = parseInt($(cells[inline ? 1 : 2]).text());
        if (tBody.className == 'unmod') {
          lines.push(" " + $(cells[inline ? 2 : 1]).text());
          oldLength += 1;
          newLength += 1;
          if (!oldOffset) oldOffset = oldLineNo;
          if (!newOffset) newOffset = newLineNo;
        } else {
          var oldLine;
          var newLine;
          var oldTag = "-";
          var newTag = "+";
          if (inline) {
            oldLine = newLine = $(cells[2]).text();
            if ($('em', cells[2]).length) oldTag = newTag = "\\";
          } else {
            oldLine = $(cells[1]).text();
            if ($('em', cells[1]).length) oldTag = "\\";
            newLine = $(cells[3]).text();
            if ($('em', cells[3]).length) newTag = "\\";
          }
          if (!isNaN(oldLineNo)) {
            lines.push(oldTag + oldLine);
            oldLength += 1;
          }
          if (!isNaN(newLineNo)) {
            tmpLines.push(newTag + newLine);
            newLength += 1;
          }
        }
      }
      if (tmpLines.length > 0) {
        lines = lines.concat(tmpLines);
      }
    }

    if (!oldOffset && oldLength) oldOffset = 1;
    if (!newOffset && newLength) newOffset = 1;
    lines[sepIndex] = lines[sepIndex]
      .replace("{1}", oldOffset).replace("{2}", oldLength)
      .replace("{3}", newOffset).replace("{4}", newLength);

    /* remove trailing   and join lines (with CR for IExplorer) */
    for ( var i = 0; i < lines.length; i++ )
        if ( lines[i] )
            lines[i] = lines[i].replace(/\xa0$/, '');
    return lines.join($.browser.msie ? "\r" : "\n");
  }

  $(document).ready(function($) {
    $("div.diff h2").each(function() {
      var switcher = $("").prependTo(this);
      var name = $.trim($(this).text());
      var table = $(this).siblings("table").get(0);
      if (! table) return;
      var pre = $("

").hide().insertAfter(table);
      $("Tabular").click(function() {
        $(pre).hide();
        $(table).show();
        $(this).addClass("active").siblings("span").removeClass("active");
        return false;
      }).addClass("active").appendTo(switcher);
      $("Unified").click(function() {
        $(table).hide();
        if (!pre.get(0).firstChild) pre.text(convertDiff(name, table));
        $(pre).fadeIn("fast")
        $(this).addClass("active").siblings("span").removeClass("active");
        return false;
      }).appendTo(switcher);
    });
  });

})(jQuery);
<|endoftext|>