<commit_before>// TODO: sub Segments?

(function(global, undefined) {
    "use strict";

    // TODO: test if this is there (i.e. don't just assume, handle it
    // when it's not there)
    var Raphael = global.Raphael,

    // but this is very much a temporary thing until I understand a better
    // way to pass this in
      r = Raphael("canvas", 600, 400),

      pub = {};

    /*** Objects ***/

    pub.Point = function( x, y ) {
        this.x = x;
        this.y = y;
    };

    pub.Point.prototype.draw = function() {
        if ( !this.display ) {
            var attr = {fill: "#000"};
            this.display = r.circle(this.x, this.y, 2).attr(attr);
        }
    };

    pub.Segment = function( pt1, pt2 ) {
        // ugly, but some other logic depends on left-most being the first point
        if ( pt1.x <= pt2.x ) {
            this.A = pt1;
            this.B = pt2;
        } else {
            this.A = pt2;
            this.B = pt1;
        }

        this.length = Math.sqrt(Math.pow(this.B.x - this.A.x, 2) +
                                Math.pow(this.B.y - this.A.y, 2));
    };

    pub.Segment.prototype.draw = function(stroke) {
        var pt2String = function( pt ) {
            return pt.x+", "+pt.y;
        };

        if ( !this.display ) {
            var path = "M"+pt2String(this.A)+"L"+pt2String(this.B),
                attr = {
                    "stroke-width": 2,
                    "stroke": stroke || "#000"
                };

            this.display = r.path(path).attr(attr);
        }
    };

    // get change from A to B
    pub.Segment.prototype.getChangePerLength = function( coord ) {
        if ( coord === "x" ) {
            return (this.B.x - this.A.x) / this.length;
        } else {
            return (this.B.y - this.A.y) / this.length;
        }
    };

    pub.Segment.prototype.drawA = function() {
        this.A.draw();
    };

    pub.Segment.prototype.drawB = function() {
        this.B.draw();
    };

    pub.Circle = function( point, rad ) {
        this.center = point;
        this.cx = point.x;
        this.cy = point.y;
        this.rad = rad;
    };

    pub.Circle.prototype.draw = function(stroke) {
        if ( !this.display ) {
            var attr = {
                "stroke-width": 2,
                "stroke": stroke || "#000"
            };

            this.display = r.circle(this.cx, this.cy, this.rad).attr(attr);
        }
    };

    // the idea is that instead of having a separate triangle object, im just going
    // to have a proposition function return a group of segments.
    pub.EleGroup = function( eles ) {
        for ( var ele in eles ) {
            this[ele] = eles[ele];
        }
    };

    pub.EleGroup.prototype.draw = function( stroke ) {
        for ( var ele in this ) {
            if ( this.hasOwnProperty(ele) ) {
                this[ele].draw( stroke );
            }
        }
    };

    /*** Utility funcitons ***/

    pub.circFromSeg = function( segment, endpoint ) {
        if ( endpoint === "A" ) {
            return new pub.Circle(segment.A, segment.length);
        } else {
            return new pub.Circle(segment.B, segment.length);
        }
    };

    // The idea is to return just the extension segment, not a segment
    // that includes both the extension and the original segment
    //
    // direction = 1 means A --> B, -1 means B --> A
    pub.extendSegment = function( segment, endpoint, length, direction ) {
        var segpt;
        if ( endpoint === "A" ) {
            segpt = segment.A;
        } else {
            segpt = segment.B;
        }

        var y_inc = direction * segment.getChangePerLength("y") * length;
        var x_inc = direction * segment.getChangePerLength("x") * length;
        var extendpt = new pub.Point(segpt.x + x_inc, segpt.y + y_inc);

        return new pub.Segment(segpt, extendpt);

    };

    pub.findCircsIntersection = function( circ1, circ2 ) {
        var Lcirc, Rcirc;

        if ( circ1.cx <= circ2.cx ) {
            Lcirc = circ1;
            Rcirc = circ2;
        } else {
            Lcirc = circ2;
            Rcirc = circ1;
        }

        var btwn_centers = new pub.Segment(Lcirc.center, Rcirc.center);

        // x^2 + y^2 = R^2
        // (x-d)^2 + y^2 = r^2
        // solve for x:
        //
        // (x-d)^2 - x = r^2 - R^2
        // x = (d^2 - r^2 + R^2) / 2d
        //
        // Plug it back into the first equation to get y
        // Note that this is from the reference frame that assumes
        // the left-most circle is the origin
        // and the x-axis is along the line between the circle centers
        // so once we get x, y
        var d = btwn_centers.length;
        var x = (Math.pow(d, 2)
               - Math.pow(Rcirc.rad, 2)
               + Math.pow(Lcirc.rad, 2))
               / (2*d);
        var y = Math.sqrt(Math.pow(Lcirc.rad, 2) - Math.pow(x, 2));

        // get the coords of the point at the intersection between
        // btwn_centers and the line drawn from the 2 intersections of the circles
        // TODO: test and handle when there's zero or one intersection points
        // for now I will assume that less than two intersection points is an
        // uninteresting case

        // Also, we will use radians in this household, young man
        var cLineAng = Raphael.rad(
                         Raphael.angle(
                           Rcirc.cx, Rcirc.cy, Lcirc.cx, Lcirc.cy));

        var cLineX = Lcirc.cx + x*Math.cos(cLineAng),
            cLineY = Lcirc.cy + x*Math.sin(cLineAng);

        var cLinePt = new pub.Point(cLineX, cLineY);

        // finally get the coords of the top intersection (since there are
        // generally two)
        var interPtX = cLinePt.x + y*Math.sin(cLineAng),
            interPtY = cLinePt.y - y*Math.cos(cLineAng);

        var interPt = new pub.Point(interPtX, interPtY);

        return interPt;
    };

    // finds intersection of circle and segment for a segment passing
    // through the center of the circle
    pub.findCircCenterSegIntersection = function( circ, seg ) {
        // find change per length of the segment, then
        // multiply by the radius of the circle to find the change
        // from circle to intersection
        var interY = circ.cy + circ.rad * seg.getChangePerLength("y"),
            interX = circ.cx + circ.rad * seg.getChangePerLength("x");

        return new pub.Point(interX, interY);

    };

    /*** Proposition logic ***/

    // Prop1 - takes a segment, returns an equilateral triangle with the segment
    // as one of the sides
    pub.Prop1 = function( seg ) {
        var c1 = pub.circFromSeg(seg, "A"),
            c2 = pub.circFromSeg(seg, "B");
        var inter = pub.findCircsIntersection(c1,c2);

        var Aside = new pub.Segment(seg.A, inter),
            Bside = new pub.Segment(seg.B, inter);

        return new pub.EleGroup(
            {
                orig: seg,
                sideA: Aside,
                sideB: Bside
            });

    };

    // Prop2 - takes a segment and a point, returns a segment located at the point
    // that's equal in length to the segment
    pub.Prop2 = function( seg, pt ) {
        var seg1 = new pub.Segment(seg.B, pt);

        var eqtri = pub.Prop1(seg1);

        // now make the circle based on the original given seg
        var seg_circ = pub.circFromSeg(seg, "B");

        //extend the other side of the equilateral triangle
        var ext_seg = pub.extendSegment(eqtri.sideA, "B", seg.length + 30, 1);

        //find intersection of circle and extended line
        var interpt = pub.findCircCenterSegIntersection(seg_circ, ext_seg);

        // the other point in the eq tri (not B or C)
        var eqtri_otherpt = eqtri.sideA.A;

        // line from other point of the eqtri to the intersection of the
        // extension and the circle
        var ext_inter_seg = new pub.Segment(eqtri_otherpt, interpt);

        var ext_inter_seg_circ = pub.circFromSeg(ext_inter_seg, "A");

        // extend the remaining side of the eq tri
        var last_ext_seg = pub.extendSegment(eqtri.sideB, "B", seg.length + 30, 1) ;

        // find intersection of the newest circle and the last_ext_seg
        var inter2pt = pub.findCircCenterSegIntersection(ext_inter_seg_circ, last_ext_seg);

        return new pub.Segment(pt, inter2pt);

    };

    // register globally
    global.euclib = pub;

})(this);

<commit_msg>add space around stroke params in the draw functions for Segment and Circle (i forgot i was doing that)<commit_after>// TODO: sub Segments?

(function(global, undefined) {
    "use strict";

    // TODO: test if this is there (i.e. don't just assume, handle it
    // when it's not there)
    var Raphael = global.Raphael,

    // but this is very much a temporary thing until I understand a better
    // way to pass this in
      r = Raphael("canvas", 600, 400),

      pub = {};

    /*** Objects ***/

    pub.Point = function( x, y ) {
        this.x = x;
        this.y = y;
    };

    pub.Point.prototype.draw = function() {
        if ( !this.display ) {
            var attr = {fill: "#000"};
            this.display = r.circle(this.x, this.y, 2).attr(attr);
        }
    };

    pub.Segment = function( pt1, pt2 ) {
        // ugly, but some other logic depends on left-most being the first point
        if ( pt1.x <= pt2.x ) {
            this.A = pt1;
            this.B = pt2;
        } else {
            this.A = pt2;
            this.B = pt1;
        }

        this.length = Math.sqrt(Math.pow(this.B.x - this.A.x, 2) +
                                Math.pow(this.B.y - this.A.y, 2));
    };

    pub.Segment.prototype.draw = function( stroke ) {
        var pt2String = function( pt ) {
            return pt.x+", "+pt.y;
        };

        if ( !this.display ) {
            var path = "M"+pt2String(this.A)+"L"+pt2String(this.B),
                attr = {
                    "stroke-width": 2,
                    "stroke": stroke || "#000"
                };

            this.display = r.path(path).attr(attr);
        }
    };

    // get change from A to B
    pub.Segment.prototype.getChangePerLength = function( coord ) {
        if ( coord === "x" ) {
            return (this.B.x - this.A.x) / this.length;
        } else {
            return (this.B.y - this.A.y) / this.length;
        }
    };

    pub.Segment.prototype.drawA = function() {
        this.A.draw();
    };

    pub.Segment.prototype.drawB = function() {
        this.B.draw();
    };

    pub.Circle = function( point, rad ) {
        this.center = point;
        this.cx = point.x;
        this.cy = point.y;
        this.rad = rad;
    };

    pub.Circle.prototype.draw = function( stroke ) {
        if ( !this.display ) {
            var attr = {
                "stroke-width": 2,
                "stroke": stroke || "#000"
            };

            this.display = r.circle(this.cx, this.cy, this.rad).attr(attr);
        }
    };

    // the idea is that instead of having a separate triangle object, im just going
    // to have a proposition function return a group of segments.
    pub.EleGroup = function( eles ) {
        for ( var ele in eles ) {
            this[ele] = eles[ele];
        }
    };

    pub.EleGroup.prototype.draw = function( stroke ) {
        for ( var ele in this ) {
            if ( this.hasOwnProperty(ele) ) {
                this[ele].draw( stroke );
            }
        }
    };

    /*** Utility funcitons ***/

    pub.circFromSeg = function( segment, endpoint ) {
        if ( endpoint === "A" ) {
            return new pub.Circle(segment.A, segment.length);
        } else {
            return new pub.Circle(segment.B, segment.length);
        }
    };

    // The idea is to return just the extension segment, not a segment
    // that includes both the extension and the original segment
    //
    // direction = 1 means A --> B, -1 means B --> A
    pub.extendSegment = function( segment, endpoint, length, direction ) {
        var segpt;
        if ( endpoint === "A" ) {
            segpt = segment.A;
        } else {
            segpt = segment.B;
        }

        var y_inc = direction * segment.getChangePerLength("y") * length;
        var x_inc = direction * segment.getChangePerLength("x") * length;
        var extendpt = new pub.Point(segpt.x + x_inc, segpt.y + y_inc);

        return new pub.Segment(segpt, extendpt);

    };

    pub.findCircsIntersection = function( circ1, circ2 ) {
        var Lcirc, Rcirc;

        if ( circ1.cx <= circ2.cx ) {
            Lcirc = circ1;
            Rcirc = circ2;
        } else {
            Lcirc = circ2;
            Rcirc = circ1;
        }

        var btwn_centers = new pub.Segment(Lcirc.center, Rcirc.center);

        // x^2 + y^2 = R^2
        // (x-d)^2 + y^2 = r^2
        // solve for x:
        //
        // (x-d)^2 - x = r^2 - R^2
        // x = (d^2 - r^2 + R^2) / 2d
        //
        // Plug it back into the first equation to get y
        // Note that this is from the reference frame that assumes
        // the left-most circle is the origin
        // and the x-axis is along the line between the circle centers
        // so once we get x, y
        var d = btwn_centers.length;
        var x = (Math.pow(d, 2)
               - Math.pow(Rcirc.rad, 2)
               + Math.pow(Lcirc.rad, 2))
               / (2*d);
        var y = Math.sqrt(Math.pow(Lcirc.rad, 2) - Math.pow(x, 2));

        // get the coords of the point at the intersection between
        // btwn_centers and the line drawn from the 2 intersections of the circles
        // TODO: test and handle when there's zero or one intersection points
        // for now I will assume that less than two intersection points is an
        // uninteresting case

        // Also, we will use radians in this household, young man
        var cLineAng = Raphael.rad(
                         Raphael.angle(
                           Rcirc.cx, Rcirc.cy, Lcirc.cx, Lcirc.cy));

        var cLineX = Lcirc.cx + x*Math.cos(cLineAng),
            cLineY = Lcirc.cy + x*Math.sin(cLineAng);

        var cLinePt = new pub.Point(cLineX, cLineY);

        // finally get the coords of the top intersection (since there are
        // generally two)
        var interPtX = cLinePt.x + y*Math.sin(cLineAng),
            interPtY = cLinePt.y - y*Math.cos(cLineAng);

        var interPt = new pub.Point(interPtX, interPtY);

        return interPt;
    };

    // finds intersection of circle and segment for a segment passing
    // through the center of the circle
    pub.findCircCenterSegIntersection = function( circ, seg ) {
        // find change per length of the segment, then
        // multiply by the radius of the circle to find the change
        // from circle to intersection
        var interY = circ.cy + circ.rad * seg.getChangePerLength("y"),
            interX = circ.cx + circ.rad * seg.getChangePerLength("x");

        return new pub.Point(interX, interY);

    };

    /*** Proposition logic ***/

    // Prop1 - takes a segment, returns an equilateral triangle with the segment
    // as one of the sides
    pub.Prop1 = function( seg ) {
        var c1 = pub.circFromSeg(seg, "A"),
            c2 = pub.circFromSeg(seg, "B");
        var inter = pub.findCircsIntersection(c1,c2);

        var Aside = new pub.Segment(seg.A, inter),
            Bside = new pub.Segment(seg.B, inter);

        return new pub.EleGroup(
            {
                orig: seg,
                sideA: Aside,
                sideB: Bside
            });

    };

    // Prop2 - takes a segment and a point, returns a segment located at the point
    // that's equal in length to the segment
    pub.Prop2 = function( seg, pt ) {
        var seg1 = new pub.Segment(seg.B, pt);

        var eqtri = pub.Prop1(seg1);

        // now make the circle based on the original given seg
        var seg_circ = pub.circFromSeg(seg, "B");

        //extend the other side of the equilateral triangle
        var ext_seg = pub.extendSegment(eqtri.sideA, "B", seg.length + 30, 1);

        //find intersection of circle and extended line
        var interpt = pub.findCircCenterSegIntersection(seg_circ, ext_seg);

        // the other point in the eq tri (not B or C)
        var eqtri_otherpt = eqtri.sideA.A;

        // line from other point of the eqtri to the intersection of the
        // extension and the circle
        var ext_inter_seg = new pub.Segment(eqtri_otherpt, interpt);

        var ext_inter_seg_circ = pub.circFromSeg(ext_inter_seg, "A");

        // extend the remaining side of the eq tri
        var last_ext_seg = pub.extendSegment(eqtri.sideB, "B", seg.length + 30, 1) ;

        // find intersection of the newest circle and the last_ext_seg
        var inter2pt = pub.findCircCenterSegIntersection(ext_inter_seg_circ, last_ext_seg);

        return new pub.Segment(pt, inter2pt);

    };

    // register globally
    global.euclib = pub;

})(this);

<|endoftext|>


<commit_before>"use strict";

window.gswaSynth = function() {
	this.data = {};
	this.simplePlayStack = [];
	this._currId = 0;
};

gswaSynth.prototype = {
	setContext( ctx ) {
		this.ctx = ctx;
	},
	connect( node ) {
		this.connectedTo = node;
		for ( var id in this.nodeStack ) {
			this.nodeStack[ id ].connect( node );
		}
	},
	disconnect() {
		this.connectedTo = null;
		for ( var id in this.nodeStack ) {
			this.nodeStack[ id ].disconnect();
		}
	},
	simpleStart( key ) {
		var oscObj, oscId, oscNode;

		for ( oscId in this.data.oscillators ) {
			oscObj = this.data.oscillators[ oscId ];
			oscNode = this._newOscNode( key, oscObj.type, oscObj.detune );
			if ( oscNode ) {
				this.simplePlayStack.push( oscNode );
				oscNode.start( 0 );
			}
		}
	},
	start( key, when, offset, duration ) {
		var oscObj, oscId, oscNode;

		for ( oscId in this.data.oscillators ) {
			oscObj = this.data.oscillators[ oscId ];
			oscNode = this._newOscNode( key, oscObj.type, oscObj.detune );
			if ( oscNode ) {
				oscObj.nodeStack[ this._currId ] = oscNode;
				oscNode.onended = this._removeOscNode.bind( this, oscObj, this._currId );
				++this._currId;
				++oscObj.nodeStackLength;
				oscNode.start( when || 0 ); // ?
				if ( arguments.length > 3 ) {
					oscNode.stop( when + duration );
				}
			}
		}
	},
	stop() {
		var nodeStack, oscId, nodeId,
			oscs = this.data.oscillators;

		this.simplePlayStack.forEach( function( oscNode ) {
			oscNode.stop();
		} );
		this.simplePlayStack.length = 0;
		for ( oscId in oscs ) {
			nodeStack = oscs[ oscId ].nodeStack;
			for ( nodeId in nodeStack ) {
				nodeStack[ nodeId ].stop();
			}
		}
	},
	change( obj ) {
		var osc, oscId, data = this.data;

		gswaSynth.assignDeep( data, obj );
		for ( oscId in obj.oscillators ) {
			osc = data.oscillators[ oscId ];
			osc.nodeStack = {};
			osc.nodeStackLength = 0;
		}
	},

	// private:
	_newOscNode( key, type, detune ) {
		var oscNode = this.ctx.createOscillator();

		oscNode.connect( this.connectedTo );
		oscNode.frequency.value = gswaSynth.keyToHz[ key ];
		oscNode.type = type;
		oscNode.detune.value = detune;
		return oscNode;
	},
	_removeOscNode( oscObj, id ) {
		delete oscObj.nodeStack[ id ];
		--oscObj.nodeStackLength;
	}
};

gswaSynth.keyToHz = {
	"A0":    27.5,
	"A#0":   29.1353,
	"B0":    30.8677,
	"C1":    32.7032,
	"C#1":   34.6479,
	"D1":    36.7081,
	"D#1":   38.8909,
	"E1":    41.2035,
	"F1":    43.6536,
	"F#1":   46.2493,
	"G1":    48.9995,
	"G#1":   51.9130,
	"A1":    55,
	"A#1":   58.2705,
	"B1":    61.7354,
	"C2":    65.4064,
	"C#2":   69.2957,
	"D2":    73.4162,
	"D#2":   77.7817,
	"E2":    82.4069,
	"F2":    87.3071,
	"F#2":   92.4986,
	"G2":    97.9989,
	"G#2":  103.826,
	"A2":   110,
	"A#2":  116.541,
	"B2":   123.471,
	"C3":   130.813,
	"C#3":  138.591,
	"D3":   146.832,
	"D#3":  155.563,
	"E3":   164.814,
	"F3":   174.614,
	"F#3":  184.997,
	"G3":   195.998,
	"G#3":  207.652,
	"A3":   220,
	"A#3":  233.082,
	"B3":   246.942,
	"C4":   261.626,
	"C#4":  277.183,
	"D4":   293.665,
	"D#4":  311.127,
	"E4":   329.628,
	"F4":   349.228,
	"F#4":  369.994,
	"G4":   391.995,
	"G#4":  415.305,
	"A4":   440,
	"A#4":  466.164,
	"B4":   493.883,
	"C5":   523.251,
	"C#5":  554.365,
	"D5":   587.33,
	"D#5":  622.254,
	"F#5":  739.989,
	"E5":   659.255,
	"F5":   698.456,
	"G5":   783.991,
	"G#5":  830.609,
	"A5":   880,
	"A#5":  932.328,
	"B5":   987.767,
	"C6":  1046.5,
	"C#6": 1108.73,
	"D6":  1174.66,
	"D#6": 1244.51,
	"E6":  1318.51,
	"F6":  1396.91,
	"F#6": 1479.98,
	"G6":  1567.98,
	"G#6": 1661.22,
	"A6":  1760,
	"A#6": 1864.66,
	"B6":  1975.53,
	"C7":  2093,
	"C#7": 2217.46,
	"D7":  2349.32,
	"D#7": 2489.02,
	"E7":  2637.02,
	"F7":  2793.83,
	"F#7": 2959.96,
	"G7":  3135.96,
	"G#7": 3322.44,
	"A7":  3520,
	"A#7": 3729.31,
	"B7":  3951.07,
	"C8":  4186.01
};
<commit_msg>gswaSynth: change oscillators during playback<commit_after>"use strict";

window.gswaSynth = function() {
	this.data = {};
	this.simplePlayStack = [];
	this._currId = 0;
};

gswaSynth.prototype = {
	setContext( ctx ) {
		this.ctx = ctx;
	},
	connect( node ) {
		this.connectedTo = node;
		for ( var id in this.nodeStack ) {
			this.nodeStack[ id ].connect( node );
		}
	},
	disconnect() {
		this.connectedTo = null;
		for ( var id in this.nodeStack ) {
			this.nodeStack[ id ].disconnect();
		}
	},
	simpleStart( key ) {
		var oscObj, oscId, oscNode;

		for ( oscId in this.data.oscillators ) {
			oscObj = this.data.oscillators[ oscId ];
			oscNode = this._newOscNode( key, oscObj.type, oscObj.detune, oscObj.gain );

			if ( oscNode ) {
				this.simplePlayStack.push( oscNode );
				oscNode.start( 0 );
			}
		}
	},
	start( key, when, offset, duration ) {
		var oscObj, oscId, oscNode;

		for ( oscId in this.data.oscillators ) {
			lg("start")
			oscObj = this.data.oscillators[ oscId ];
			oscNode = this._newOscNode( key, oscObj.type, oscObj.detune, oscObj.gain );

			if ( oscNode ) {
				oscNode._when = when;
				oscNode._duration = duration;
				oscNode._key = key;
				oscObj.nodeStack[ this._currId ] = oscNode;
				oscNode.onended = this._removeOscNode.bind( this, oscObj, this._currId );
				++this._currId;
				++oscObj.nodeStackLength;
				oscNode.start( when || 0 ); // ?
				if ( arguments.length > 3 ) {
					oscNode.stop( when + duration );
				}
			}
		}
	},
	stop() {
		var nodeStack, oscId, nodeId,
			oscs = this.data.oscillators;

		this.simplePlayStack.forEach( function( oscNode ) {
			oscNode.stop();
		} );
		this.simplePlayStack.length = 0;
		for ( oscId in oscs ) {
			nodeStack = oscs[ oscId ].nodeStack;
			for ( nodeId in nodeStack ) {
				nodeStack[ nodeId ].stop();
			}
		}
	},
	change( obj ) {
		var osc, oscId, firstOscData,
		 	data = this.data;

		// Check if data.oscillators is empty
		// If not, save the first oscillator.
		// If the change occurs during the playback
		// The first oscillator will be used to create the nodeStack of the new oscillators
		for ( oscId in data.oscillators ) {
			firstOscData = data[ oscId ];
			break;
		}

		for ( oscId in obj.oscillators ) {
			var oscSrc = obj.oscillators[ oscId ];

			// If the oscillators object exists
			// And if it's not empty
			// And if there is already an oscillator with the ID oscId
			// Update or remove
			// Else create
			firstOscData && oscId in data.oscillators ?
				oscSrc ? this._updateOscillator( oscSrc, data.oscillators[ oscId ] )
					   : this._deleteOscillator( oscSrc, data.oscillators[ oscId ] )
			: this._createOscillator( firstOscData, oscSrc, oscId );
		}
	},

	// private:
	_newOscNode( key, type, detune, gain ) {
		var oscNode = this.ctx.createOscillator();

		oscNode.frequency.value = gswaSynth.keyToHz[ key ];
		oscNode.type = type;
		oscNode.detune.value = detune;
		lg(oscNode);
		lg(gain);
		oscNode.connect( gain );
		gain.connect( this.connectedTo );
		return oscNode;
	},
	_removeOscNode( oscObj, id ) {
		delete oscObj.nodeStack[ id ];
		--oscObj.nodeStackLength;
	},
	_createOscillator( firstOscData, oscSrc, oscId ) {
		var newOsc, newOscNode, oscNodeBase;

		if ( !oscSrc ) {
			return;
		}

		// If oscillators didn't exist
		if ( !( "oscillators" in this.data ) ) {
			this.data.oscillators = {};
		}

		// create the new oscillator
		newOsc = this.data.oscillators[ oscId ] = {
			"type": oscSrc.type,
			"detune": oscSrc.detune,
			"gain": this.ctx.createGain(),
			"nodeStack": {},
			"nodeStackLength": 0
		};

		if ( oscSrc.gainValue ) {
			newOsc.gain.gain.value = newOsc.gainValue = oscSrc.gainValue;
		}

		// If there are at least one oscillator and its nodeStack's not empty (when playing)
		// Use it to fill the new oscillator's nodeStack
		if ( firstOscData && firstOscData.nodeStackLength != 0 ) {
			for ( nodeId in firstOscData.nodeStack ) {
				oscNodeSrc = firstOscData.nodeStack[ nodeId ];
				newOscNode = this._newOscNode( oscNodeSrc.key, firstOscData.type, firstOscData.detune, newOsc.gain );
				newOsc.nodeStack.push( newOscNode );
				newOsc.nodeStackLength++;
				newOsc.onended = this._removeOscNode.bind( this, newOscNode, oscId );
				newOsc._when = oscNodeSrc._when;
				newOsc._duration = oscNodeSrc._duration;
				newOscNode.start( newOsc._when );
				oscNode.stop( newOsc._when + newOsc._duration );
			}
		}
	},
	_updateOscillator( oscSrc, oscDest ) {
		oscDest.type = oscSrc.type;
		oscDest.detune = oscSrc.detune;
		oscDest.gainValue = oscDest.gain.gain.value = oscSrc.gainValue;

		for ( nodeId in oscDest.nodeStack ) {
			oscDest.nodeStack[ nodeId ].type = oscDest.type;
			oscDest.nodeStack[ nodeId ].detune.value = oscDest.detune;
		}
	},
	_deleteOscillator( osc ) {
		for ( nodeId in osc.nodeStack ) {
			osc.nodeStack[ nodeId ].stop();
		}
		osc = undefined;
	}
};

gswaSynth.keyToHz = {
	"A0":    27.5,
	"A#0":   29.1353,
	"B0":    30.8677,
	"C1":    32.7032,
	"C#1":   34.6479,
	"D1":    36.7081,
	"D#1":   38.8909,
	"E1":    41.2035,
	"F1":    43.6536,
	"F#1":   46.2493,
	"G1":    48.9995,
	"G#1":   51.9130,
	"A1":    55,
	"A#1":   58.2705,
	"B1":    61.7354,
	"C2":    65.4064,
	"C#2":   69.2957,
	"D2":    73.4162,
	"D#2":   77.7817,
	"E2":    82.4069,
	"F2":    87.3071,
	"F#2":   92.4986,
	"G2":    97.9989,
	"G#2":  103.826,
	"A2":   110,
	"A#2":  116.541,
	"B2":   123.471,
	"C3":   130.813,
	"C#3":  138.591,
	"D3":   146.832,
	"D#3":  155.563,
	"E3":   164.814,
	"F3":   174.614,
	"F#3":  184.997,
	"G3":   195.998,
	"G#3":  207.652,
	"A3":   220,
	"A#3":  233.082,
	"B3":   246.942,
	"C4":   261.626,
	"C#4":  277.183,
	"D4":   293.665,
	"D#4":  311.127,
	"E4":   329.628,
	"F4":   349.228,
	"F#4":  369.994,
	"G4":   391.995,
	"G#4":  415.305,
	"A4":   440,
	"A#4":  466.164,
	"B4":   493.883,
	"C5":   523.251,
	"C#5":  554.365,
	"D5":   587.33,
	"D#5":  622.254,
	"F#5":  739.989,
	"E5":   659.255,
	"F5":   698.456,
	"G5":   783.991,
	"G#5":  830.609,
	"A5":   880,
	"A#5":  932.328,
	"B5":   987.767,
	"C6":  1046.5,
	"C#6": 1108.73,
	"D6":  1174.66,
	"D#6": 1244.51,
	"E6":  1318.51,
	"F6":  1396.91,
	"F#6": 1479.98,
	"G6":  1567.98,
	"G#6": 1661.22,
	"A6":  1760,
	"A#6": 1864.66,
	"B6":  1975.53,
	"C7":  2093,
	"C#7": 2217.46,
	"D7":  2349.32,
	"D#7": 2489.02,
	"E7":  2637.02,
	"F7":  2793.83,
	"F#7": 2959.96,
	"G7":  3135.96,
	"G#7": 3322.44,
	"A7":  3520,
	"A#7": 3729.31,
	"B7":  3951.07,
	"C8":  4186.01
};
<|endoftext|>


<commit_before>
var activeDoc;

brapi.runtime.onInstalled.addListener(function() {
  if (brapi.contextMenus)
  brapi.contextMenus.create({
    id: "read-selection",
    title: brapi.i18n.getMessage("context_read_selection"),
    contexts: ["selection"]
  });
})

if (brapi.contextMenus)
brapi.contextMenus.onClicked.addListener(function(info, tab) {
  if (info.menuItemId == "read-selection")
    stop()
      .then(function() {
        return playText(info.selectionText, function(err) {
          if (err) console.error(err);
        })
      })
      .catch(console.error)
})

if (brapi.commands)
brapi.commands.onCommand.addListener(function(command) {
  if (command == "play") {
    getPlaybackState()
      .then(function(state) {
        if (state == "PLAYING") return pause();
        else if (state == "STOPPED" || state == "PAUSED") {
          return play(function(err) {
            if (err) console.error(err);
          })
        }
      })
      .catch(console.error)
  }
  else if (command == "stop") stop();
  else if (command == "forward") forward();
  else if (command == "rewind") rewind();
})

if (brapi.ttsEngine) (function() {
  brapi.ttsEngine.onSpeak.addListener(function(utterance, options, onEvent) {
    options = Object.assign({}, options, {voice: {voiceName: options.voiceName}});
    remoteTtsEngine.speak(utterance, options, onEvent);
  });
  brapi.ttsEngine.onStop.addListener(remoteTtsEngine.stop);
  brapi.ttsEngine.onPause.addListener(remoteTtsEngine.pause);
  brapi.ttsEngine.onResume.addListener(remoteTtsEngine.resume);
})()

function playText(text, onEnd) {
  if (!activeDoc) {
    activeDoc = new Doc(new SimpleSource(text.split(/(?:\r?\n){2,}/)), function(err) {
      handleError(err);
      closeDoc();
      if (typeof onEnd == "function") onEnd(err);
    })
  }
  return activeDoc.play()
    .catch(function(err) {
      handleError(err);
      closeDoc();
      throw err;
    })
}

function play(onEnd) {
  if (!activeDoc) {
    activeDoc = new Doc(new TabSource(), function(err) {
      handleError(err);
      closeDoc();
      if (typeof onEnd == "function") onEnd(err);
    })
  }
  return activeDoc.play()
    .catch(function(err) {
      handleError(err);
      closeDoc();
      throw err;
    })
}

function stop() {
  if (activeDoc) {
    activeDoc.stop();
    closeDoc();
    return Promise.resolve();
  }
  else return Promise.resolve();
}

function pause() {
  if (activeDoc) return activeDoc.pause();
  else return Promise.resolve();
}

function getPlaybackState() {
  if (activeDoc) return activeDoc.getState();
  else return Promise.resolve("STOPPED");
}

function getActiveSpeech() {
  if (activeDoc) return activeDoc.getActiveSpeech();
  else return Promise.resolve(null);
}

function closeDoc() {
  if (activeDoc) {
    activeDoc.close();
    activeDoc = null;
  }
}

function forward() {
  if (activeDoc) return activeDoc.forward();
  else return Promise.reject(new Error("Can't forward, not active"));
}

function rewind() {
  if (activeDoc) return activeDoc.rewind();
  else return Promise.reject(new Error("Can't rewind, not active"));
}

function seek(n) {
  if (activeDoc) return activeDoc.seek(n);
  else return Promise.reject(new Error("Can't seek, not active"));
}

function handleError(err) {
  if (err) {
    var code = /^{/.test(err.message) ? JSON.parse(err.message).code : err.message;
    if (code == "error_payment_required") clearSettings(["voiceName"]);
    reportError(err);
  }
}

function reportError(err) {
  if (err && err.stack) {
    var details = err.stack;
    if (!details.startsWith(err.name)) details = err.name + ": " + err.message + "\n" + details;
    getState("lastUrl")
      .then(function(url) {return reportIssue(url, details)})
      .catch(console.error)
  }
}

function reportIssue(url, comment) {
  var manifest = brapi.runtime.getManifest();
  return getSettings()
    .then(function(settings) {
      if (url) settings.url = url;
      settings.version = manifest.version;
      settings.userAgent = navigator.userAgent;
      return ajaxPost(config.serviceUrl + "/read-aloud/report-issue", {
        url: JSON.stringify(settings),
        comment: comment
      })
    })
}
<commit_msg>fix FF losing context menu after restart<commit_after>
var activeDoc;

brapi.runtime.onInstalled.addListener(installContextMenus);
if (getBrowser() == "firefox") brapi.runtime.onStartup.addListener(installContextMenus);

function installContextMenus() {
  if (brapi.contextMenus)
  brapi.contextMenus.create({
    id: "read-selection",
    title: brapi.i18n.getMessage("context_read_selection"),
    contexts: ["selection"]
  });
}

if (brapi.contextMenus)
brapi.contextMenus.onClicked.addListener(function(info, tab) {
  if (info.menuItemId == "read-selection")
    stop()
      .then(function() {
        return playText(info.selectionText, function(err) {
          if (err) console.error(err);
        })
      })
      .catch(console.error)
})

if (brapi.commands)
brapi.commands.onCommand.addListener(function(command) {
  if (command == "play") {
    getPlaybackState()
      .then(function(state) {
        if (state == "PLAYING") return pause();
        else if (state == "STOPPED" || state == "PAUSED") {
          return play(function(err) {
            if (err) console.error(err);
          })
        }
      })
      .catch(console.error)
  }
  else if (command == "stop") stop();
  else if (command == "forward") forward();
  else if (command == "rewind") rewind();
})

if (brapi.ttsEngine) (function() {
  brapi.ttsEngine.onSpeak.addListener(function(utterance, options, onEvent) {
    options = Object.assign({}, options, {voice: {voiceName: options.voiceName}});
    remoteTtsEngine.speak(utterance, options, onEvent);
  });
  brapi.ttsEngine.onStop.addListener(remoteTtsEngine.stop);
  brapi.ttsEngine.onPause.addListener(remoteTtsEngine.pause);
  brapi.ttsEngine.onResume.addListener(remoteTtsEngine.resume);
})()

function playText(text, onEnd) {
  if (!activeDoc) {
    activeDoc = new Doc(new SimpleSource(text.split(/(?:\r?\n){2,}/)), function(err) {
      handleError(err);
      closeDoc();
      if (typeof onEnd == "function") onEnd(err);
    })
  }
  return activeDoc.play()
    .catch(function(err) {
      handleError(err);
      closeDoc();
      throw err;
    })
}

function play(onEnd) {
  if (!activeDoc) {
    activeDoc = new Doc(new TabSource(), function(err) {
      handleError(err);
      closeDoc();
      if (typeof onEnd == "function") onEnd(err);
    })
  }
  return activeDoc.play()
    .catch(function(err) {
      handleError(err);
      closeDoc();
      throw err;
    })
}

function stop() {
  if (activeDoc) {
    activeDoc.stop();
    closeDoc();
    return Promise.resolve();
  }
  else return Promise.resolve();
}

function pause() {
  if (activeDoc) return activeDoc.pause();
  else return Promise.resolve();
}

function getPlaybackState() {
  if (activeDoc) return activeDoc.getState();
  else return Promise.resolve("STOPPED");
}

function getActiveSpeech() {
  if (activeDoc) return activeDoc.getActiveSpeech();
  else return Promise.resolve(null);
}

function closeDoc() {
  if (activeDoc) {
    activeDoc.close();
    activeDoc = null;
  }
}

function forward() {
  if (activeDoc) return activeDoc.forward();
  else return Promise.reject(new Error("Can't forward, not active"));
}

function rewind() {
  if (activeDoc) return activeDoc.rewind();
  else return Promise.reject(new Error("Can't rewind, not active"));
}

function seek(n) {
  if (activeDoc) return activeDoc.seek(n);
  else return Promise.reject(new Error("Can't seek, not active"));
}

function handleError(err) {
  if (err) {
    var code = /^{/.test(err.message) ? JSON.parse(err.message).code : err.message;
    if (code == "error_payment_required") clearSettings(["voiceName"]);
    reportError(err);
  }
}

function reportError(err) {
  if (err && err.stack) {
    var details = err.stack;
    if (!details.startsWith(err.name)) details = err.name + ": " + err.message + "\n" + details;
    getState("lastUrl")
      .then(function(url) {return reportIssue(url, details)})
      .catch(console.error)
  }
}

function reportIssue(url, comment) {
  var manifest = brapi.runtime.getManifest();
  return getSettings()
    .then(function(settings) {
      if (url) settings.url = url;
      settings.version = manifest.version;
      settings.userAgent = navigator.userAgent;
      return ajaxPost(config.serviceUrl + "/read-aloud/report-issue", {
        url: JSON.stringify(settings),
        comment: comment
      })
    })
}
<|endoftext|>


<commit_before>/**
 * gulpfile.js for php-library developement
 *
 * 1. install node.js & npm
 * 2. $ npm install
 * 3. $ gulp serve
 * 4. open http://localhost:9000/ (livereload enabled)
 * 5. coding on src/*.php and tests/*.php
 *
 * enjoy!
 *
 * @license https://creativecommons.org/publicdomain/zero/1.0/ CC0-1.0 (No Rights Reserved.)
 * @link https://github.com/spindle/spindle-lib-template
 */
var gulp = require('gulp');
var exec = require('child_process').exec;
var browserSync = require('browser-sync').create();

gulp.task('default', ['test', 'inspect']);

function phpunit(done) {
    exec('vendor/bin/phpunit --colors=always', function(err, stdout, stderr){
        console.log(stdout);
        console.error(stderr);
        done();
    });
}

function inspect(done) {
    var r = 2;
    function wait() {
        --r || done();
    }
    exec([
        'vendor/bin/pdepend',
        '--jdepend-chart=artifacts/pdepend.svg',
        '--overview-pyramid=artifacts/pyramid.svg',
        '--summary-xml=artifacts/summary.xml',
        'src/'
    ].join(' '), wait);

    exec('vendor/bin/phpcs', function(err, stdout, stderr){
        console.log(stdout);
        console.error(stderr);
        wait();
    });
}

gulp.task('test', phpunit);

gulp.task('inspect', inspect);

gulp.task('start', function(){
    browserSync.init({
        server: {
            baseDir: "artifacts/"
        }
    });

    gulp.watch(['src/**/*.php', 'tests/**/*Test.php'], {}, function(ev){
        var r = 2;
        function wait() {
            --r || browserSync.reload();
        }
        inspect(wait);
        phpunit(wait);
    });
});

<commit_msg>Fixed gulpfile<commit_after>/**
 * gulpfile.js for php-library developement
 *
 * 1. install node.js & npm
 * 2. $ npm install
 * 3. $ npm start
 */
var gulp = require('gulp');
var exec = require('child_process').exec;
var browserSync = require('browser-sync').create();

gulp.task('default', ['test', 'inspect']);

function phpunit(done) {
    var p = exec('vendor/bin/phpunit --colors=always');
    p.stdout.pipe(process.stdout);
    p.stderr.pipe(process.stderr);
    p.on('end', done);
}

function inspect(done) {
    var r = 2;
    function wait() {
        --r || done();
    }
    exec([
        'vendor/bin/pdepend',
        '--jdepend-chart=artifacts/pdepend.svg',
        '--overview-pyramid=artifacts/pyramid.svg',
        '--summary-xml=artifacts/summary.xml',
        'src/'
    ].join(' '), wait);

    var p = exec('vendor/bin/phpcs', wait);
}

gulp.task('test', phpunit);

gulp.task('inspect', inspect);

gulp.task('start', function(){
    browserSync.init({
        server: {
            baseDir: "artifacts/"
        }
    });

    gulp.watch(['src/**/*.php', 'tests/**/*Test.php'], {}, function(ev){
        var r = 2;
        function wait() {
            --r || browserSync.reload();
        }
        inspect(wait);
        phpunit(wait);
    });
});

<|endoftext|>


<commit_before>export default function validateControls (controls) {

  controls.setEach('widgetIsValid', true);

  var regex = {
    email: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
    url: /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/
  };

  var invalidate = function (control, message) {
    control.set('widgetIsValid', false);
    control.get('widgetErrors').pushObject(message);
  };

  controls.forEach(function (control) {

    var value = control.get('value'),
        options = control.getWithDefault('meta.data', {});

    control.set('widgetErrors', Ember.A([]));

    if (!value) {
      if (control.get('required')) {
        invalidate(control, 'This field is required');
      }
      return;
    }

    switch (control.get('controlType.widget')) {
    case 'textfield':
    case 'textarea':
      if (options.min && value.length < options.min) {
        invalidate(control, 'This field has a minimum length of ' + options.min + '.');
      }
      if (options.max && value.length > options.max) {
        invalidate(control, 'This field has a maximum length of ' + options.max + '.');
      }
      break;
    case 'number':
      if (!Ember.$.isNumeric(value)) {
        invalidate(control, 'This field must be a number.');
      }
      if (options.min && parseInt(value, 10) < options.min) {
        invalidate(control, 'This field has a minimum value of ' + options.min + '.');
      }
      if (options.max && parseInt(value, 10) > options.max) {
        invalidate(control, 'This field has a maximum value of ' + options.max + '.');
      }
      break;
    case 'email':
      if (!regex.email.test(value)) {
        invalidate(control, 'This field must be an email address.');
      }
      break;
    case 'url':
      if (!regex.email.test(value)) {
        invalidate(control, 'This field must be a URL.');
      }
      break;
    case 'datetime':
      if (!moment(value).isValid()) {
        invalidate(control, 'This field must be a valid date and time.');
      }
      break;
    }

  });

}
<commit_msg>getWithDefault will maintain null. we do not want that.<commit_after>export default function validateControls (controls) {

  controls.setEach('widgetIsValid', true);

  var regex = {
    email: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
    url: /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/
  };

  var invalidate = function (control, message) {
    control.set('widgetIsValid', false);
    control.get('widgetErrors').pushObject(message);
  };

  controls.forEach(function (control) {

    var value = control.get('value'),
        options = control.get('meta.data') || {};

    control.set('widgetErrors', Ember.A([]));

    if (!value) {
      if (control.get('required')) {
        invalidate(control, 'This field is required');
      }
      return;
    }

    switch (control.get('controlType.widget')) {
    case 'textfield':
    case 'textarea':
      if (options.min && value.length < options.min) {
        invalidate(control, 'This field has a minimum length of ' + options.min + '.');
      }
      if (options.max && value.length > options.max) {
        invalidate(control, 'This field has a maximum length of ' + options.max + '.');
      }
      break;
    case 'number':
      if (!Ember.$.isNumeric(value)) {
        invalidate(control, 'This field must be a number.');
      }
      if (options.min && parseInt(value, 10) < options.min) {
        invalidate(control, 'This field has a minimum value of ' + options.min + '.');
      }
      if (options.max && parseInt(value, 10) > options.max) {
        invalidate(control, 'This field has a maximum value of ' + options.max + '.');
      }
      break;
    case 'email':
      if (!regex.email.test(value)) {
        invalidate(control, 'This field must be an email address.');
      }
      break;
    case 'url':
      if (!regex.email.test(value)) {
        invalidate(control, 'This field must be a URL.');
      }
      break;
    case 'datetime':
      if (!moment(value).isValid()) {
        invalidate(control, 'This field must be a valid date and time.');
      }
      break;
    }

  });

}
<|endoftext|>


<commit_before>import {WorkspaceStore, ComponentRegistry} from 'nylas-exports'
import DraftList from './draft-list'
import DraftListToolbar from './draft-list-toolbar'
import DraftListSendStatus from './draft-list-send-status'
import {DraftDeleteButton} from "./draft-toolbar-buttons"

export function activate() {
  WorkspaceStore.defineSheet(
    'Drafts',
    {root: true},
    {list: ['RootSidebar', 'DraftList']}
  )

  ComponentRegistry.register(DraftList, {location: WorkspaceStore.Location.DraftList})
  ComponentRegistry.register(DraftListToolbar, {location: WorkspaceStore.Location.DraftList.Toolbar})
  ComponentRegistry.register(DraftDeleteButton, {role: 'DraftActionsToolbarButton'})
  ComponentRegistry.register(DraftListSendStatus, {role: 'DraftList:DraftStatus'})
}

export function deactivate() {
  ComponentRegistry.unregister(DraftList)
  ComponentRegistry.unregister(DraftListToolbar)
  ComponentRegistry.unregister(DraftDeleteButton)
  ComponentRegistry.unregister(DraftListSendStatus)
}
<commit_msg>fix(draft-list): Select proper sheet when launching in drafts perspective<commit_after>import {WorkspaceStore, ComponentRegistry, Actions} from 'nylas-exports'
import DraftList from './draft-list'
import DraftListToolbar from './draft-list-toolbar'
import DraftListSendStatus from './draft-list-send-status'
import {DraftDeleteButton} from "./draft-toolbar-buttons"

export function activate() {
  WorkspaceStore.defineSheet(
    'Drafts',
    {root: true},
    {list: ['RootSidebar', 'DraftList']}
  )
  if (NylasEnv.savedState.perspective &&
    NylasEnv.savedState.perspective.type === "DraftsMailboxPerspective") {
    Actions.selectRootSheet(WorkspaceStore.Sheet.Drafts);
  }

  ComponentRegistry.register(DraftList, {location: WorkspaceStore.Location.DraftList})
  ComponentRegistry.register(DraftListToolbar, {location: WorkspaceStore.Location.DraftList.Toolbar})
  ComponentRegistry.register(DraftDeleteButton, {role: 'DraftActionsToolbarButton'})
  ComponentRegistry.register(DraftListSendStatus, {role: 'DraftList:DraftStatus'})
}

export function deactivate() {
  ComponentRegistry.unregister(DraftList)
  ComponentRegistry.unregister(DraftListToolbar)
  ComponentRegistry.unregister(DraftDeleteButton)
  ComponentRegistry.unregister(DraftListSendStatus)
}
<|endoftext|>


<commit_before>////
//// Usage: ./node_modules/.bin/gulp build, clean, test, etc.
////

var gulp = require('gulp');
var jsdoc = require('gulp-jsdoc');
var mocha = require('gulp-mocha');
var uglify = require('gulp-uglify');
var rename = require("gulp-rename");
var git = require('gulp-git');
var bump = require('gulp-bump');
//var pandoc = require('gulp-pandoc');

var paths = {
    readme: ['./README.md'],
    tests: ['tests/*.test.js', 'tests/*.tests.js'],
    docable: ['lib/*.js', './README.md'],
    transients:['./doc/*', '!./doc/README.org']
};

// Browser runtime environment construction.
gulp.task('build', ['patch-bump', 'doc']);

gulp.task('patch-bump', function(){
    gulp.src('./package.json')
	.pipe(bump({type: 'patch'}))
	.pipe(gulp.dest('./'));
});

// Build docs directory with JSDoc.
//gulp.task('doc', ['md-to-org', 'jsdoc']);
gulp.task('doc', ['jsdoc']);

// // Convert README.org to a README.md for JSDoc to use as an index.
// // TODO: My pandoc does not support the org reader yet, so di it the other way
// // for now
// //gulp.task('org-to-md', function() {
// gulp.task('md-to-org', function() {
//     gulp.src('./README.md')
//     //gulp.src('./README.org')
//         .pipe(pandoc({
// 	    // from: 'orgmode',
// 	    // to: 'markdown',
// 	    from: 'markdown',
// 	    to: 'orgmode',
// 	    ext: '.org',
// 	    args: ['--smart']
// 	}))
//         .pipe(gulp.dest('./'));
// });

// Build docs directory with JSDoc.
gulp.task('jsdoc', function() {
    gulp.src(paths.docable, paths.readme)
        .pipe(jsdoc('./doc'));
});

// Get rid of anything that is transient.
gulp.task('clean', function(cb) {
    del(paths.transients);
});

// Testing with mocha/chai.
gulp.task('test', function() {
    return gulp.src(paths.tests, { read: false }).pipe(mocha({
	reporter: 'spec',
	globals: {
	    // Use a different should.
	    should: require('chai').should()
	}
    }));
});

//gulp.task('release', ['build', 'publish-npm', 'git-commit', 'git-tag']);
gulp.task('release', ['build', 'publish-npm']);

// Needs to have ""
gulp.task('publish-npm', function() {
    var npm = require("npm");
    npm.load(function (er, npm) {
	// NPM
	npm.commands.publish();
    });
});

gulp.task('git-commit', function(){
    console.log('TODO: WORK IN PROGRESS');
    // Make a note in the git repo.
    var pkg = require('./package.json');
    var pver = pkg.version;
    gulp.src('./*')
	.pipe(git.commit('Package/version tracking for go-exp/widget: ' + pver));
});

gulp.task('git-tag', function(){
    console.log('TODO: WORK IN PROGRESS');
    // Make a note in the git repo.
    var pkg = require('./package.json');
    var pver = pkg.version;
    git.tag('go-exp-widget-' + pver, 'version message', function (err){
	if(err) throw err;
    });
});

// Rerun doc build when a file changes.
gulp.task('watch-doc', function() {
  gulp.watch(paths.docable, ['doc']);
  gulp.watch(paths.readme, ['doc']);
});

// The default task (called when you run `gulp` from cli)
//gulp.task('default', ['watch', 'scripts', 'images']);
gulp.task('default', function() {
    console.log("'allo 'allo!");
});
<commit_msg>gulp'<commit_after>////
//// Usage: ./node_modules/.bin/gulp build, clean, test, etc.
////

var gulp = require('gulp');
var jsdoc = require('gulp-jsdoc');
var mocha = require('gulp-mocha');
var uglify = require('gulp-uglify');
var rename = require("gulp-rename");
var git = require('gulp-git');
var bump = require('gulp-bump');
var del = require('del');

var paths = {
    readme: ['./README.md'],
    tests: ['tests/*.test.js', 'tests/*.tests.js'],
    docable: ['lib/*.js', './README.md'],
    transients:['./doc/*', '!./doc/README.org']
};

// Browser runtime environment construction.
gulp.task('build', ['patch-bump', 'doc']);

gulp.task('patch-bump', function(cb){
    gulp.src('./package.json')
	.pipe(bump({type: 'patch'}))
	.pipe(gulp.dest('./'));
    cb(null);
});

gulp.task('minor-bump', function(cb){
    gulp.src('./package.json')
	.pipe(bump({type: 'minor'}))
	.pipe(gulp.dest('./'));
    cb(null);
});

gulp.task('major-bump', function(cb){
    gulp.src('./package.json')
	.pipe(bump({type: 'major'}))
	.pipe(gulp.dest('./'));
    cb(null);
});

// Build docs directory with JSDoc.
//gulp.task('doc', ['md-to-org', 'jsdoc']);
gulp.task('doc', ['jsdoc']);

// Build docs directory with JSDoc.
// Completely dependent on clean before running doc.
gulp.task('jsdoc', ['clean'], function(cb) {
    gulp.src(paths.docable, paths.readme)
        .pipe(jsdoc('./doc'));
    cb(null);
});

// Get rid of anything that is transient.
gulp.task('clean', function(cb) {
    del(paths.transients);
    cb(null);
});

// Testing with mocha/chai.
gulp.task('test', function() {
    return gulp.src(paths.tests, { read: false }).pipe(mocha({
	reporter: 'spec',
	globals: {
	    // Use a different should.
	    should: require('chai').should()
	}
    }));
});

//gulp.task('release', ['build', 'publish-npm', 'git-commit', 'git-tag']);
gulp.task('release', ['build', 'publish-npm']);

// Needs to have ""
gulp.task('publish-npm', function() {
    var npm = require("npm");
    npm.load(function (er, npm) {
	// NPM
	npm.commands.publish();
    });
});

gulp.task('git-commit', function(){
    console.log('TODO: WORK IN PROGRESS');
    // Make a note in the git repo.
    var pkg = require('./package.json');
    var pver = pkg.version;
    gulp.src('./*')
	.pipe(git.commit('Package/version tracking for go-exp/widget: ' + pver));
});

gulp.task('git-tag', function(){
    console.log('TODO: WORK IN PROGRESS');
    // Make a note in the git repo.
    var pkg = require('./package.json');
    var pver = pkg.version;
    git.tag('go-exp-widget-' + pver, 'version message', function (err){
	if(err) throw err;
    });
});

// Rerun doc build when a file changes.
gulp.task('watch-doc', function() {
  gulp.watch(paths.docable, ['doc']);
  gulp.watch(paths.readme, ['doc']);
});

// The default task (called when you run `gulp` from cli)
//gulp.task('default', ['watch', 'scripts', 'images']);
gulp.task('default', function() {
    console.log("'allo 'allo!");
});
<|endoftext|>


<commit_before>export default function validateControls (controls) {

  controls.setEach('widgetIsValid', true);

  var regex = {
    email: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
    url: /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/
  };

  var invalidate = function (control, message) {
    control.set('widgetIsValid', false);
    control.get('widgetErrors').pushObject(message);
  };

  controls.forEach(function (control) {

    var value = control.get('value'),
        options = control.get('meta.data') || {};

    control.set('widgetErrors', Ember.A([]));

    if (!value) {
      if (control.get('required')) {
        invalidate(control, 'This field is required');
      }
      return;
    }

    switch (control.get('controlType.widget')) {
    case 'textfield':
    case 'textarea':
      if (options.min && value.length < options.min) {
        invalidate(control, 'This field has a minimum length of ' + options.min + '.');
      }
      if (options.max && value.length > options.max) {
        invalidate(control, 'This field has a maximum length of ' + options.max + '.');
      }
      break;
    case 'number':
      if (!Ember.$.isNumeric(value)) {
        invalidate(control, 'This field must be a number.');
      }
      if (options.min && parseInt(value, 10) < options.min) {
        invalidate(control, 'This field has a minimum value of ' + options.min + '.');
      }
      if (options.max && parseInt(value, 10) > options.max) {
        invalidate(control, 'This field has a maximum value of ' + options.max + '.');
      }
      break;
    case 'email':
      if (!regex.email.test(value)) {
        invalidate(control, 'This field must be an email address.');
      }
      break;
    case 'url':
      if (!regex.email.test(value)) {
        invalidate(control, 'This field must be a URL.');
      }
      break;
    case 'datetime':
      if (!moment(value).isValid()) {
        invalidate(control, 'This field must be a valid date and time.');
      }
      break;
    }

  });

}
<commit_msg>test against url regex, not email. fixes #54<commit_after>export default function validateControls (controls) {

  controls.setEach('widgetIsValid', true);

  var regex = {
    email: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
    url: /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/
  };

  var invalidate = function (control, message) {
    control.set('widgetIsValid', false);
    control.get('widgetErrors').pushObject(message);
  };

  controls.forEach(function (control) {

    var value = control.get('value'),
        options = control.get('meta.data') || {};

    control.set('widgetErrors', Ember.A([]));

    if (!value) {
      if (control.get('required')) {
        invalidate(control, 'This field is required');
      }
      return;
    }

    switch (control.get('controlType.widget')) {
    case 'textfield':
    case 'textarea':
      if (options.min && value.length < options.min) {
        invalidate(control, 'This field has a minimum length of ' + options.min + '.');
      }
      if (options.max && value.length > options.max) {
        invalidate(control, 'This field has a maximum length of ' + options.max + '.');
      }
      break;
    case 'number':
      if (!Ember.$.isNumeric(value)) {
        invalidate(control, 'This field must be a number.');
      }
      if (options.min && parseInt(value, 10) < options.min) {
        invalidate(control, 'This field has a minimum value of ' + options.min + '.');
      }
      if (options.max && parseInt(value, 10) > options.max) {
        invalidate(control, 'This field has a maximum value of ' + options.max + '.');
      }
      break;
    case 'email':
      if (!regex.email.test(value)) {
        invalidate(control, 'This field must be an email address.');
      }
      break;
    case 'url':
      if (!regex.url.test(value)) {
        invalidate(control, 'This field must be a URL.');
      }
      break;
    case 'datetime':
      if (!moment(value).isValid()) {
        invalidate(control, 'This field must be a valid date and time.');
      }
      break;
    }

  });

}
<|endoftext|>


<commit_before>var express = require('express')
var router = express.Router()
var User = require('../models/User')
var Category = require('../models/Category')
var Content = require('../models/Content')

let responseData = {}

router.use((req, res, next) => {
  responseData = {
    code: 0,
    message: '成功'
  }
  next()
})

router.use((req, res, next) => {
  if (!req.session.user || !req.session.user.isAdmin) {
    res.statusCode = 403
    responseData.code = 1
    responseData.message = '对不起，只有管理员才可以进入后台管理'
    res.json(responseData)
    return
  }
  next()
})

router.param('user_id', function (req, res, next, id) {
  req.user = {
    id
  }
  next()
})

router.get('/users', (req, res, next) => {
  let page = Number(req.query.page) || 1
  const limit = Number(req.query.limit) || 10
  let pages = 0

  User.estimatedDocumentCount().then(total => {
    pages = Math.ceil(total / limit)
    page = Math.min(page, pages)
    page = Math.max(page, 1)
    var skip = (page - 1) * limit

    User.find({}, { password: 0 }).lean().limit(limit).skip(skip).then(users => {
      res.json({ ...responseData, list: users, total, page, limit })
    })
  })
})

router.delete('/users/:user_id', (req, res, next) => {
  var id = req.user.id || ''

  User.remove({ _id: id }).then(() => {
    res.json(responseData)
  })
})

router.get('/category', (req, res) => {
  var page = Number(req.query.page) || 1
  var limit = 10
  var pages = 0

  Category.count().then(count => {
    pages = Math.ceil(count / limit)
    page = Math.min(page, pages)
    page = Math.max(page, 1)
    var skip = (page - 1) * limit

    Category.find().sort({_id: -1}).limit(limit).skip(skip).then(categories => {
      res.render('admin/category_index', {
        userInfo: req.userInfo,
        categories: categories,
        page: page,
        count: count,
        limit: limit,
        pages: pages
      })
    })
  })
})

router.get('/category/add', (req, res) => {
  res.render('admin/category_add', {
    userInfo: req.userInfo
  })
})

router.post('/category/add', (req, res) => {
  var name = req.body.name || ''
  if (name === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '名称不能为空！'
    })
    return
  }

  Category.findOne({name: name}).then(rs => {
    if (rs) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '分类已经存在了！'
      })
      return Promise.reject()
    } else {
      return new Category({
        name: name
      }).save()
    }
  }).then(newCategory => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '分类保存成功！',
      url: '/admin/category'
    })
  })
})

router.get('/category/edit', (req, res) => {
  var id = req.query.id || ''

  Category.findOne({_id: id}).then(category => {
    if (!category) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '分类信息不存在！'
      })
    } else {
      res.render('admin/category_edit', {
        userInfo: req.userInfo,
        category: category
      })
    }
  })
})

router.post('/category/edit', (req, res) => {
  var id = req.query.id || ''
  var name = req.body.name || ''

  if (name === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '名称不能为空！'
    })
    return
  }

  Category.findOne({_id: id}).then(category => {
    if (!category) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '分类信息不存在！'
      })
    } else {
      if (category.name === name) {
        res.render('admin/success', {
          userInfo: req.userInfo,
          message: '分类信息修改成功！',
          url: '/admin/category'
        })
        return Promise.reject()
      } else {
        return Category.findOne({
          _id: {$ne: id},
          name: name
        })
      }
    }
  }).then(sameCategory => {
    if (sameCategory) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '数据库中已经存在同名分类了！'
      })
      return Promise.reject()
    } else {
      return Category.update({
        _id: id
      }, {
        name: name
      })
    }
  }).then(() => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '分类信息修改成功！',
      url: '/admin/category'
    })
  })
})

router.get('/category/delete', (req, res) => {
  var id = req.query.id || ''

  Category.remove({_id: id}).then(() => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '分类删除成功'
    })
  })
})

router.get('/content', (req, res) => {
  var page = Number(req.query.page) || 1
  var limit = 10
  var pages = 0

  Content.count().then(count => {
    pages = Math.ceil(count / limit)
    page = Math.min(page, pages)
    page = Math.max(page, 1)
    var skip = (page - 1) * limit

    Content.find().sort({_id: -1}).limit(limit).skip(skip).populate(['category', 'user']).then(contents => {
      res.render('admin/content_index', {
        userInfo: req.userInfo,
        contents: contents,
        page: page,
        count: count,
        limit: limit,
        pages: pages
      })
    })
  })
})

router.get('/content/add', (req, res) => {
  Category.find().then(categories => {
    res.render('admin/content_add', {
      userInfo: req.userInfo,
      categories: categories
    })
  })
})

router.post('/content/add', (req, res) => {
  if (req.body.category === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容分类不能为空！'
    })
  }

  if (req.body.category === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容标题不能为空！'
    })
  }

  new Content({
    category: req.body.category,
    title: req.body.title,
    user: req.userInfo._id.toString(),
    description: req.body.description,
    content: req.body.content
  }).save().then(rs => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '内容保存成功！',
      url: '/admin/content'
    })
  })
})

router.get('/content/edit', (req, res) => {
  var id = req.query.id || ''
  var categories = []

  Category.find().then(rs => {
    categories = rs
    return Content.findOne({_id: id}).populate('category')
  }).then(content => {
    if (!content) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '指定内容不存在！'
      })
    } else {
      res.render('admin/content_edit', {
        userInfo: req.userInfo,
        content: content,
        categories: categories
      })
    }
  })
})

router.post('/content/edit', (req, res) => {
  var id = req.query.id || ''

  if (id === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容ID不能为空！'
    })
  }

  if (req.body.category === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容分类不能为空！'
    })
  }

  if (req.body.category === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容标题不能为空！'
    })
  }

  Content.update({
    _id: id
  }, {
    category: req.body.category,
    title: req.body.title,
    description: req.body.description,
    content: req.body.content,
    addTime: new Date()
  }).then(() => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '内容修改成功！',
      url: '/admin/content'
    })
  })
})

router.get('/content/delete', (req, res) => {
  var id = req.query.id || ''

  if (id === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容ID不能为空！'
    })
  }

  Content.remove({_id: id}).then(() => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '内容删除成功'
    })
  })
})

module.exports = router
<commit_msg>feat: get '/categories'<commit_after>var express = require('express')
var router = express.Router()
var User = require('../models/User')
var Category = require('../models/Category')
var Content = require('../models/Content')

const calcPage = (page = 1, total, limit) => {
  const pages = Math.ceil(total / limit) || 0
  page = Math.min(page, pages)
  page = Math.max(page, 1)
  return page
}

const calcSkip = (page, limit) => (page - 1) * limit

let responseData = {}

router.use((req, res, next) => {
  responseData = {
    code: 0,
    message: '成功'
  }
  next()
})

router.use((req, res, next) => {
  if (!req.session.user || !req.session.user.isAdmin) {
    res.statusCode = 403
    responseData.code = 1
    responseData.message = '对不起，只有管理员才可以进入后台管理'
    res.json(responseData)
    return
  }
  next()
})

router.param('user_id', function (req, res, next, id) {
  req.user = {
    id
  }
  next()
})

router.get('/users', (req, res, next) => {
  let page = Number(req.query.page) || 1
  const limit = Number(req.query.limit) || 10

  User.estimatedDocumentCount().then(total => {
    page = calcPage(page, total, limit)
    const skip = calcSkip(page, limit)

    User.find({}, { password: 0 }).lean().limit(limit).skip(skip).then(users => {
      res.json({ ...responseData, list: users, total, page, limit })
    })
  })
})

router.delete('/users/:user_id', (req, res, next) => {
  var id = req.user.id || ''

  User.remove({ _id: id }).then(() => {
    res.json(responseData)
  })
})

router.get('/categories', (req, res) => {
  let page = Number(req.query.page) || 1
  const limit = Number(req.query.limit) || 10

  Category.estimatedDocumentCount().then(total => {
    page = calcPage(page, total, limit)
    const skip = calcSkip(page, limit)

    Category.find().lean().sort({_id: -1}).limit(limit).skip(skip).then(categories => {
      res.json({ ...responseData, list: categories, total, page, limit })
    })
  })
})

router.get('/category/add', (req, res) => {
  res.render('admin/category_add', {
    userInfo: req.userInfo
  })
})

router.post('/category/add', (req, res) => {
  var name = req.body.name || ''
  if (name === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '名称不能为空！'
    })
    return
  }

  Category.findOne({name: name}).then(rs => {
    if (rs) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '分类已经存在了！'
      })
      return Promise.reject()
    } else {
      return new Category({
        name: name
      }).save()
    }
  }).then(newCategory => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '分类保存成功！',
      url: '/admin/category'
    })
  })
})

router.get('/category/edit', (req, res) => {
  var id = req.query.id || ''

  Category.findOne({_id: id}).then(category => {
    if (!category) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '分类信息不存在！'
      })
    } else {
      res.render('admin/category_edit', {
        userInfo: req.userInfo,
        category: category
      })
    }
  })
})

router.post('/category/edit', (req, res) => {
  var id = req.query.id || ''
  var name = req.body.name || ''

  if (name === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '名称不能为空！'
    })
    return
  }

  Category.findOne({_id: id}).then(category => {
    if (!category) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '分类信息不存在！'
      })
    } else {
      if (category.name === name) {
        res.render('admin/success', {
          userInfo: req.userInfo,
          message: '分类信息修改成功！',
          url: '/admin/category'
        })
        return Promise.reject()
      } else {
        return Category.findOne({
          _id: {$ne: id},
          name: name
        })
      }
    }
  }).then(sameCategory => {
    if (sameCategory) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '数据库中已经存在同名分类了！'
      })
      return Promise.reject()
    } else {
      return Category.update({
        _id: id
      }, {
        name: name
      })
    }
  }).then(() => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '分类信息修改成功！',
      url: '/admin/category'
    })
  })
})

router.get('/category/delete', (req, res) => {
  var id = req.query.id || ''

  Category.remove({_id: id}).then(() => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '分类删除成功'
    })
  })
})

router.get('/content', (req, res) => {
  var page = Number(req.query.page) || 1
  var limit = 10
  var pages = 0

  Content.count().then(count => {
    pages = Math.ceil(count / limit)
    page = Math.min(page, pages)
    page = Math.max(page, 1)
    var skip = (page - 1) * limit

    Content.find().sort({_id: -1}).limit(limit).skip(skip).populate(['category', 'user']).then(contents => {
      res.render('admin/content_index', {
        userInfo: req.userInfo,
        contents: contents,
        page: page,
        count: count,
        limit: limit,
        pages: pages
      })
    })
  })
})

router.get('/content/add', (req, res) => {
  Category.find().then(categories => {
    res.render('admin/content_add', {
      userInfo: req.userInfo,
      categories: categories
    })
  })
})

router.post('/content/add', (req, res) => {
  if (req.body.category === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容分类不能为空！'
    })
  }

  if (req.body.category === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容标题不能为空！'
    })
  }

  new Content({
    category: req.body.category,
    title: req.body.title,
    user: req.userInfo._id.toString(),
    description: req.body.description,
    content: req.body.content
  }).save().then(rs => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '内容保存成功！',
      url: '/admin/content'
    })
  })
})

router.get('/content/edit', (req, res) => {
  var id = req.query.id || ''
  var categories = []

  Category.find().then(rs => {
    categories = rs
    return Content.findOne({_id: id}).populate('category')
  }).then(content => {
    if (!content) {
      res.render('admin/error', {
        userInfo: req.userInfo,
        message: '指定内容不存在！'
      })
    } else {
      res.render('admin/content_edit', {
        userInfo: req.userInfo,
        content: content,
        categories: categories
      })
    }
  })
})

router.post('/content/edit', (req, res) => {
  var id = req.query.id || ''

  if (id === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容ID不能为空！'
    })
  }

  if (req.body.category === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容分类不能为空！'
    })
  }

  if (req.body.category === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容标题不能为空！'
    })
  }

  Content.update({
    _id: id
  }, {
    category: req.body.category,
    title: req.body.title,
    description: req.body.description,
    content: req.body.content,
    addTime: new Date()
  }).then(() => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '内容修改成功！',
      url: '/admin/content'
    })
  })
})

router.get('/content/delete', (req, res) => {
  var id = req.query.id || ''

  if (id === '') {
    res.render('admin/error', {
      userInfo: req.userInfo,
      message: '内容ID不能为空！'
    })
  }

  Content.remove({_id: id}).then(() => {
    res.render('admin/success', {
      userInfo: req.userInfo,
      message: '内容删除成功'
    })
  })
})

module.exports = router
<|endoftext|>


<commit_before>
/**
 * Tilemap - constructor
 *
 * @param {Array} layer - layer data from the map, arranged in mapheight lists of mapwidth Phaser.Tile objects (2d array)
 *
 */
PIXI.Tilemap = function(texture, mapwidth, mapheight, tilewidth, tileheight, layer)
{
    PIXI.DisplayObjectContainer.call(this);

    /**
     * The texture of the Tilemap
     *
     * @property texture
     * @type Texture
     */
    this.texture = texture;

    // faster access to the tile dimensions
    this.tileWide = tilewidth;
    this.tileHigh = tileheight;
    this.mapWide = mapwidth;
    this.mapHigh = mapheight;

    // precalculate the width of the source texture in entire tile units
    this.texTilesWide = Math.ceil(this.texture.width / this.tileWide);
    this.texTilesHigh = Math.ceil(this.texture.height / this.tileHigh);

    // proportion of texture used by one tile (uv coordinate scales)
    this.scalex = this.tileWide / this.texture.width;
    this.scaley = this.tileHigh / this.texture.height;

    // TODO: switch here to create DisplayObjectContainer at correct size for the render mode
    this.width = this.mapWide * this.tileWide;
    this.height = this.mapHigh * this.tileHigh;

    this.layer = layer;

    /**
     * Remember last tile drawn to avoid unnecessary set-up
     *
     * @type Integer
     */
    this.lastTile = -1;

    /**
     * Whether the Tilemap is dirty or not
     *
     * @property dirty
     * @type Boolean
     */
    this.dirty = true;

    /**
     * The blend mode to be applied to the sprite. Set to PIXI.blendModes.NORMAL to remove any blend mode.
     *
     * @property blendMode
     * @type Number
     * @default PIXI.blendModes.NORMAL;
     */
    this.blendMode = PIXI.blendModes.NORMAL;

    // calculate size of map
    var mapSize = mapwidth * mapheight * 16;

    // create buffer data for the webgl rendering of this tile
    this.buffer = new PIXI.Float32Array( mapSize * 16 );
};

// constructor
PIXI.Tilemap.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
PIXI.Tilemap.prototype.constructor = PIXI.Tilemap;

PIXI.Tilemap.prototype.update = function() {};
PIXI.Tilemap.prototype.postUpdate = function() {};

// override PIXI.DisplayObjectContainer _renderWebGL
PIXI.Tilemap.prototype._renderWebGL = function(renderSession)
{
    // if the sprite is not visible or the alpha is 0 then no need to render this element
    if(!this.visible || this.alpha <= 0)
    {
        return;
    }

    renderSession.spriteBatch.stop();

    // init! init!
    if(!this._vertexBuffer)
    {
      this._initWebGL(renderSession);
    }

    renderSession.shaderManager.setShader(renderSession.shaderManager.tilemapShader);

    this._renderWholeTilemap(renderSession);

    renderSession.spriteBatch.start();
};

PIXI.Tilemap.prototype._initWebGL = function(renderSession)
{
    var gl = renderSession.gl;

    this._vertexBuffer = gl.createBuffer();
    this._indexBuffer = gl.createBuffer();
    this._uvBuffer = gl.createBuffer();
    this._colorBuffer = gl.createBuffer();

    // create a GL buffer to transfer all the vertex position data through
    this.positionBuffer = gl.createBuffer();

    // bind the buffer to the RAM resident positionBuffer
    gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );
    gl.bufferData( gl.ARRAY_BUFFER, this.buffer, gl.STATIC_DRAW );
};

PIXI.Tilemap.prototype.makeProjection = function(_width, _height)
{
  // project coordinates into a 2x2 number range, starting at (-1, 1)
  var m = new PIXI.Float32Array(9);
  m[0] = 2 / _width;
  m[1] = 0;
  m[2] = 0;

  m[3] = 0;
  m[4] = -2 / _height;
  m[5] = 0;

  m[6] = -1;
  m[7] = 1;
  m[8] = 1;
  return m;
};

/*
PIXI.Tilemap.prototype.makeTransform = function(_x, _y, _angleInRadians, _scaleX, _scaleY)
{
  var c = Math.cos( _angleInRadians );
  var s = Math.sin( _angleInRadians );
  var m = new Float32Array(9);
  m[0] = c * _scaleX;
  m[1] = -s * _scaleY;
  m[2] = 0;
  m[3] = s * _scaleX;
  m[4] = c * _scaleY;
  m[5] = 0;
  m[6] = _x;
  m[7] = _y;
  m[8] = 1;
  return m;
};
*/

PIXI.Tilemap.prototype._renderBatch = function( renderSession )
{
  if ( this.glBatch )
  {
    var gl = renderSession.gl;

    // TODO: should probably use destination buffer dimensions (halved)
    var screenWide2 = this.game.width * 0.5;
    var screenHigh2 = this.game.height * 0.5;

    // size of one pixel in the source texture
    var iTextureWide = 1.0 / this.texture.width;
    var iTextureHigh = 1.0 / this.texture.height;

    // size of one tile in the source texture
    var srcWide = this.tileWide * iTextureWide;
    var srcHigh = this.tileHigh * iTextureHigh;

    // pre-calculate inverse half-buffer dimensions
    var iWide = 1.0 / screenWide2;
    var iHigh = 1.0 / screenHigh2;

    var wide = this.tileWide * 0.5 / screenWide2;
    var high = this.tileHigh * 0.5 / screenHigh2;

    var buffer = this.buffer;
    var oldR, oldT, uvl, uvt;

    // process entire glBatch into a single webGl draw buffer for a TRIANGLE_STRIP blit
    var c = 0;
    for(var i = 0, l = this.glBatch.length; i < l; i++)
    {
      // sx: this.drawCoords[coordIndex],
      // sy: this.drawCoords[coordIndex + 1],
      // sw: this.tileWidth,
      // sh: this.tileHeight,
      // dx: x,
      // dy: y,
      // dw: this.tileWidth,
      // dh: this.tileHeight
      var t = this.glBatch[i];

      var x = t.dx * iWide - 1;
      var y = 1 - t.dy * iHigh;

      var lft = x - wide;
      var bot = y + high;

      var uvl = t.sx * iTextureWide;
      var uvt = t.sy * iTextureHigh;

      // for every tile except the first one, use degenerate triangle to separate the tiles
      // TODO: it might be possible to avoid this by drawing triangles more intelligently (e.g. each row with inverting the first/second triangle alternately)
      if ( c > 0 )
      {
        // add a degenerate triangle: repeat the last vertex
        buffer[ c     ] = oldR;
        buffer[ c + 1 ] = oldT;
        // then repeat the next vertex
        buffer[ c + 4 ] = lft;
        buffer[ c + 5 ] = bot;
        // pad with texture coordinates (probably not needed)
        buffer[ c + 2 ] = buffer[ c + 6 ] = uvl;
        buffer[ c + 3 ] = buffer[ c + 7 ] = uvt;

        // advance the buffer index
        c += 8;
      }

      // calculate the destination location of the tile in screen units (-1..1)
      buffer[ c      ] = buffer[ c +  4 ] = lft;
      buffer[ c +  1 ] = buffer[ c +  9 ] = bot;
      buffer[ c +  8 ] = buffer[ c +  12] = oldR = x + wide;
      buffer[ c +  5 ] = buffer[ c +  13] = oldT = y - high;

      // calculate the uv coordinates of the tile source image
      buffer[ c +  2 ] = buffer[ c +  6 ] = uvl;
      buffer[ c +  3 ] = buffer[ c +  11] = uvt;
      buffer[ c +  10] = buffer[ c +  14] = uvl + srcWide;
      buffer[ c +  7 ] = buffer[ c +  15] = uvt + srcHigh;

      // advance the buffer index
      c += 16;
    }

    if ( c > 0 )
    {
      var shader = renderSession.shaderManager.tilemapShader;

      // set the scroll offset (in screen units)
      gl.uniform2f( shader.uScrollOffset, this.scrollX * iWide, -this.scrollY * iHigh );

      // upload the VBO
      gl.bufferData( gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW );

      // prepare the shader attributes
      gl.vertexAttribPointer( shader.aPosition, 4, gl.FLOAT, false, 0, 0 );

      // draw the entire VBO in one call
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, Math.floor(c / 4));
    }
  }
};

/**
 * render the entire tilemap, one layer at a time, one tile at a time
 * using a fast webgl tile render
 *
 * @param  {[type]} renderSession [description]
 */
PIXI.Tilemap.prototype._renderWholeTilemap = function(renderSession)
{
  var gl = renderSession.gl;
  var shader = renderSession.shaderManager.tilemapShader;

  renderSession.blendModeManager.setBlendMode(this.blendMode);

  // set the uniforms and texture
  gl.uniformMatrix3fv( shader.uProjectionMatrix, false, this.makeProjection(this.game.width, this.game.height) );
  gl.uniform1i( shader.uImageSampler, 0 );
  gl.activeTexture(gl.TEXTURE0);
  gl.uniform2f(shader.uTileSize, this.tileWide, this.tileHigh);

  // check if a texture is dirty..
  if(this.texture.baseTexture._dirty[gl.id])
  {
      renderSession.renderer.updateTexture(this.texture.baseTexture);
  }
  else
  {
      // bind the current texture
      gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]);
  }

  // bind the source buffer
  gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );

  // draw the batched tile list
  this._renderBatch( renderSession );
};

/**
 * When the texture is updated, this event will fire to update the scale and frame
 *
 * @method onTextureUpdate
 * @param event
 * @private
 */

PIXI.Tilemap.prototype.onTextureUpdate = function()
{
    this.updateFrame = true;
};

/**
 * Returns the bounds of the map as a rectangle. The bounds calculation takes the worldTransform into account.
 *
 * @method getBounds
 * @param matrix {Matrix} the transformation matrix of the sprite
 * @return {Rectangle} the framing rectangle
 */
PIXI.Tilemap.prototype.getBounds = function(matrix)
{
    var worldTransform = matrix || this.worldTransform;

    var a = worldTransform.a;
    var b = worldTransform.b;
    var c = worldTransform.c;
    var d = worldTransform.d;
    var tx = worldTransform.tx;
    var ty = worldTransform.ty;

    var maxX = -Infinity;
    var maxY = -Infinity;

    var minX = Infinity;
    var minY = Infinity;

    var vertices = [
      0, 0,
      this.mapWide * this.tileWide, 0,
      this.mapWide * this.tileWide, this.mapHigh * this.tileHigh,
      0, this.mapHigh * this.tileHigh
    ];
    for (var i = 0, n = vertices.length; i < n; i += 2)
    {
        var rawX = vertices[i], rawY = vertices[i + 1];
        var x = (a * rawX) + (c * rawY) + tx;
        var y = (d * rawY) + (b * rawX) + ty;

        minX = x < minX ? x : minX;
        minY = y < minY ? y : minY;

        maxX = x > maxX ? x : maxX;
        maxY = y > maxY ? y : maxY;
    }

    if (minX === -Infinity || maxY === Infinity)
    {
        return PIXI.EmptyRectangle;
    }

    var bounds = this._bounds;

    bounds.x = minX;
    bounds.width = maxX - minX;

    bounds.y = minY;
    bounds.height = maxY - minY;

    // store a reference so that if this function gets called again in the render cycle we do not have to recalculate
    this._currentBounds = bounds;

    return bounds;
};

<commit_msg>Fixed bug where scroll was effectively applied twice.<commit_after>
/**
 * Tilemap - constructor
 *
 * @param {Array} layer - layer data from the map, arranged in mapheight lists of mapwidth Phaser.Tile objects (2d array)
 *
 */
PIXI.Tilemap = function(texture, mapwidth, mapheight, tilewidth, tileheight, layer)
{
    PIXI.DisplayObjectContainer.call(this);

    /**
     * The texture of the Tilemap
     *
     * @property texture
     * @type Texture
     */
    this.texture = texture;

    // faster access to the tile dimensions
    this.tileWide = tilewidth;
    this.tileHigh = tileheight;
    this.mapWide = mapwidth;
    this.mapHigh = mapheight;

    // precalculate the width of the source texture in entire tile units
    this.texTilesWide = Math.ceil(this.texture.width / this.tileWide);
    this.texTilesHigh = Math.ceil(this.texture.height / this.tileHigh);

    // proportion of texture used by one tile (uv coordinate scales)
    this.scalex = this.tileWide / this.texture.width;
    this.scaley = this.tileHigh / this.texture.height;

    // TODO: switch here to create DisplayObjectContainer at correct size for the render mode
    this.width = this.mapWide * this.tileWide;
    this.height = this.mapHigh * this.tileHigh;

    this.layer = layer;

    /**
     * Remember last tile drawn to avoid unnecessary set-up
     *
     * @type Integer
     */
    this.lastTile = -1;

    /**
     * Whether the Tilemap is dirty or not
     *
     * @property dirty
     * @type Boolean
     */
    this.dirty = true;

    /**
     * The blend mode to be applied to the sprite. Set to PIXI.blendModes.NORMAL to remove any blend mode.
     *
     * @property blendMode
     * @type Number
     * @default PIXI.blendModes.NORMAL;
     */
    this.blendMode = PIXI.blendModes.NORMAL;

    // calculate size of map
    var mapSize = mapwidth * mapheight * 16;

    // create buffer data for the webgl rendering of this tile
    this.buffer = new PIXI.Float32Array( mapSize * 16 );
};

// constructor
PIXI.Tilemap.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
PIXI.Tilemap.prototype.constructor = PIXI.Tilemap;

PIXI.Tilemap.prototype.update = function() {};
PIXI.Tilemap.prototype.postUpdate = function() {};

// override PIXI.DisplayObjectContainer _renderWebGL
PIXI.Tilemap.prototype._renderWebGL = function(renderSession)
{
    // if the sprite is not visible or the alpha is 0 then no need to render this element
    if(!this.visible || this.alpha <= 0)
    {
        return;
    }

    renderSession.spriteBatch.stop();

    // init! init!
    if(!this._vertexBuffer)
    {
      this._initWebGL(renderSession);
    }

    renderSession.shaderManager.setShader(renderSession.shaderManager.tilemapShader);

    this._renderWholeTilemap(renderSession);

    renderSession.spriteBatch.start();
};

PIXI.Tilemap.prototype._initWebGL = function(renderSession)
{
    var gl = renderSession.gl;

    this._vertexBuffer = gl.createBuffer();
    this._indexBuffer = gl.createBuffer();
    this._uvBuffer = gl.createBuffer();
    this._colorBuffer = gl.createBuffer();

    // create a GL buffer to transfer all the vertex position data through
    this.positionBuffer = gl.createBuffer();

    // bind the buffer to the RAM resident positionBuffer
    gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );
    gl.bufferData( gl.ARRAY_BUFFER, this.buffer, gl.STATIC_DRAW );
};

PIXI.Tilemap.prototype.makeProjection = function(_width, _height)
{
  // project coordinates into a 2x2 number range, starting at (-1, 1)
  var m = new PIXI.Float32Array(9);
  m[0] = 2 / _width;
  m[1] = 0;
  m[2] = 0;

  m[3] = 0;
  m[4] = -2 / _height;
  m[5] = 0;

  m[6] = -1;
  m[7] = 1;
  m[8] = 1;
  return m;
};

/*
PIXI.Tilemap.prototype.makeTransform = function(_x, _y, _angleInRadians, _scaleX, _scaleY)
{
  var c = Math.cos( _angleInRadians );
  var s = Math.sin( _angleInRadians );
  var m = new Float32Array(9);
  m[0] = c * _scaleX;
  m[1] = -s * _scaleY;
  m[2] = 0;
  m[3] = s * _scaleX;
  m[4] = c * _scaleY;
  m[5] = 0;
  m[6] = _x;
  m[7] = _y;
  m[8] = 1;
  return m;
};
*/

PIXI.Tilemap.prototype._renderBatch = function( renderSession )
{
  if ( this.glBatch )
  {
    var gl = renderSession.gl;

    // TODO: should probably use destination buffer dimensions (halved)
    var screenWide2 = this.game.width * 0.5;
    var screenHigh2 = this.game.height * 0.5;

    // size of one pixel in the source texture
    var iTextureWide = 1.0 / this.texture.width;
    var iTextureHigh = 1.0 / this.texture.height;

    // size of one tile in the source texture
    var srcWide = this.tileWide * iTextureWide;
    var srcHigh = this.tileHigh * iTextureHigh;

    // pre-calculate inverse half-buffer dimensions
    var iWide = 1.0 / screenWide2;
    var iHigh = 1.0 / screenHigh2;

    var wide = this.tileWide * 0.5 / screenWide2;
    var high = this.tileHigh * 0.5 / screenHigh2;

    var buffer = this.buffer;
    var oldR, oldT, uvl, uvt;

    // process entire glBatch into a single webGl draw buffer for a TRIANGLE_STRIP blit
    var c = 0;
    for(var i = 0, l = this.glBatch.length; i < l; i++)
    {
      // sx: this.drawCoords[coordIndex],
      // sy: this.drawCoords[coordIndex + 1],
      // sw: this.tileWidth,
      // sh: this.tileHeight,
      // dx: x,
      // dy: y,
      // dw: this.tileWidth,
      // dh: this.tileHeight
      var t = this.glBatch[i];

      var x = t.dx * iWide - 1;
      var y = 1 - t.dy * iHigh;

      var lft = x - wide;
      var bot = y + high;

      var uvl = t.sx * iTextureWide;
      var uvt = t.sy * iTextureHigh;

      // for every tile except the first one, use degenerate triangle to separate the tiles
      // TODO: it might be possible to avoid this by drawing triangles more intelligently (e.g. each row with inverting the first/second triangle alternately)
      if ( c > 0 )
      {
        // add a degenerate triangle: repeat the last vertex
        buffer[ c     ] = oldR;
        buffer[ c + 1 ] = oldT;
        // then repeat the next vertex
        buffer[ c + 4 ] = lft;
        buffer[ c + 5 ] = bot;
        // pad with texture coordinates (probably not needed)
        buffer[ c + 2 ] = buffer[ c + 6 ] = uvl;
        buffer[ c + 3 ] = buffer[ c + 7 ] = uvt;

        // advance the buffer index
        c += 8;
      }

      // calculate the destination location of the tile in screen units (-1..1)
      buffer[ c      ] = buffer[ c +  4 ] = lft;
      buffer[ c +  1 ] = buffer[ c +  9 ] = bot;
      buffer[ c +  8 ] = buffer[ c +  12] = oldR = x + wide;
      buffer[ c +  5 ] = buffer[ c +  13] = oldT = y - high;

      // calculate the uv coordinates of the tile source image
      buffer[ c +  2 ] = buffer[ c +  6 ] = uvl;
      buffer[ c +  3 ] = buffer[ c +  11] = uvt;
      buffer[ c +  10] = buffer[ c +  14] = uvl + srcWide;
      buffer[ c +  7 ] = buffer[ c +  15] = uvt + srcHigh;

      // advance the buffer index
      c += 16;
    }

    if ( c > 0 )
    {
      var shader = renderSession.shaderManager.tilemapShader;

      // upload the VBO
      gl.bufferData( gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW );

      // prepare the shader attributes
      gl.vertexAttribPointer( shader.aPosition, 4, gl.FLOAT, false, 0, 0 );

      // draw the entire VBO in one call
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, Math.floor(c / 4));
    }
  }
};

/**
 * render the entire tilemap, one layer at a time, one tile at a time
 * using a fast webgl tile render
 *
 * @param  {[type]} renderSession [description]
 */
PIXI.Tilemap.prototype._renderWholeTilemap = function(renderSession)
{
  var gl = renderSession.gl;
  var shader = renderSession.shaderManager.tilemapShader;

  renderSession.blendModeManager.setBlendMode(this.blendMode);

  // set the uniforms and texture
  gl.uniformMatrix3fv( shader.uProjectionMatrix, false, this.makeProjection(this.game.width, this.game.height) );
  gl.uniform1i( shader.uImageSampler, 0 );
  gl.activeTexture(gl.TEXTURE0);
  gl.uniform2f(shader.uTileSize, this.tileWide, this.tileHigh);
  // set the base offset (in screen units) (it's zero, scrolling is now handled by the batch buffer values)
  gl.uniform2f( shader.uScrollOffset, 0, 0 );
  //gl.uniform2f( shader.uScrollOffset, this.scrollX * iWide, -this.scrollY * iHigh );

  // check if a texture is dirty..
  if(this.texture.baseTexture._dirty[gl.id])
  {
      renderSession.renderer.updateTexture(this.texture.baseTexture);
  }
  else
  {
      // bind the current texture
      gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]);
  }

  // bind the source buffer
  gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );

  // draw the batched tile list
  this._renderBatch( renderSession );
};

/**
 * When the texture is updated, this event will fire to update the scale and frame
 *
 * @method onTextureUpdate
 * @param event
 * @private
 */

PIXI.Tilemap.prototype.onTextureUpdate = function()
{
    this.updateFrame = true;
};

/**
 * Returns the bounds of the map as a rectangle. The bounds calculation takes the worldTransform into account.
 *
 * @method getBounds
 * @param matrix {Matrix} the transformation matrix of the sprite
 * @return {Rectangle} the framing rectangle
 */
PIXI.Tilemap.prototype.getBounds = function(matrix)
{
    var worldTransform = matrix || this.worldTransform;

    var a = worldTransform.a;
    var b = worldTransform.b;
    var c = worldTransform.c;
    var d = worldTransform.d;
    var tx = worldTransform.tx;
    var ty = worldTransform.ty;

    var maxX = -Infinity;
    var maxY = -Infinity;

    var minX = Infinity;
    var minY = Infinity;

    var vertices = [
      0, 0,
      this.mapWide * this.tileWide, 0,
      this.mapWide * this.tileWide, this.mapHigh * this.tileHigh,
      0, this.mapHigh * this.tileHigh
    ];
    for (var i = 0, n = vertices.length; i < n; i += 2)
    {
        var rawX = vertices[i], rawY = vertices[i + 1];
        var x = (a * rawX) + (c * rawY) + tx;
        var y = (d * rawY) + (b * rawX) + ty;

        minX = x < minX ? x : minX;
        minY = y < minY ? y : minY;

        maxX = x > maxX ? x : maxX;
        maxY = y > maxY ? y : maxY;
    }

    if (minX === -Infinity || maxY === Infinity)
    {
        return PIXI.EmptyRectangle;
    }

    var bounds = this._bounds;

    bounds.x = minX;
    bounds.width = maxX - minX;

    bounds.y = minY;
    bounds.height = maxY - minY;

    // store a reference so that if this function gets called again in the render cycle we do not have to recalculate
    this._currentBounds = bounds;

    return bounds;
};

<|endoftext|>