<commit_before>define([
    'jquery',
    '../logging',
    './inject',
    '../lib/jquery.form/jquery.form'
], function($, logging, inject) {
    var log = logging.getLogger('ajaxify');

    var init = function($el, opts) {
        // skip elements that are covered by old-style injection
        if ($el.is('.injection,[data-injection]')) {
            log.debug('skipping element claimed by old injection', $el);
            return false;
        }

        var url = ($el.attr('action') || $el.attr('href') || '').split('#')[0];
        if (!url) {
            log.error('Element has neither action nor href', $el);
            return false;
        }

        // ajaxify form
        if ($el.is('form')) {
            if (($el.attr('method') || '').toLowerCase() === 'get') {
                log.warn('Ignoring form method GET, enforcing POST');
            }
            $el.ajaxForm({
                context: $el,
                // in plone we use this to figure out whether a form is
                // requested or submitted
                // XXX: consider setting this via $.ajaxSetup in a
                // specific application
                data: {submit: "submit"},
                // enforce POST, as we compare the url to figure out
                // who was triggered
                // XXX: this should not be needed anymore, leaving it
                // in for now not to cause additional problems
                //type: $el.attr('method') || 'POST',
                type: 'POST'
                // XXX: url may not be set here but needs to be
                // determined upon submit in jquery.forms, as a
                // redirect will change the action of already
                // ajaxified forms
                //url: url
            });
        } else {
            $el.on('click.ajaxify', function(ev, opts) {
                ev.preventDefault();
                $.ajax({
                    context: $el,
                    url: url
                });
            });
        }

        $el.ajaxError(function(ev, jqxhr, ajaxopts, error) {
            // ajaxHandlers are global, we are only interested in our form
            if (url !== ajaxopts.url) {
                log.debug('ignoring ajax event', ajaxopts.url, url);
                return;
            }
            if (jqxhr.status === 0) {
                log.debug('ignoring error due to canceled request');
                return;
            }
            log.debug('error', ev, jqxhr, opts, error);

            // XXX: this needs to be solved differently
            var msg = [jqxhr.status, jqxhr.statusText, error, opts && opts.url].join(' '),
                // XXX: error notification pattern!
                $error = $('
' +
                           '
Error
' +
                           '
'+msg+'
' +
                           '
');
            inject.append($error, $('body'));
        });

        $el.ajaxSuccess(function(ev, jqxhr, ajaxopts, data) {
            // ajaxHandlers are global, we are only interested in our form
            // XXX: figure out how much of this is true with a multi-form test
            if (ev.target !== $el[0]) {
                //log.debug('ignoring ajax event for', $(ev.target), 'not',  $el[0]);
                return;
            }
            if (url !== ajaxopts.url) {
                //log.debug('ignoring ajax event', ajaxopts.url, href[0]);
                return;
            }

            var redirect = jqxhr.getResponseHeader('X-Patterns-Redirect-Url'),
                oldurl = jqxhr.getResponseHeader('X-Patterns-Previous-Url'),
                reload = jqxhr.getResponseHeader('X-Patterns-Reload');

            if ((reload !== null) && (reload !== undefined)) {
                log.debug('received reload request');

                $('form.inject.reload').submit();
                $('a.inject.reload:not(.navigation a)').click();
                $('.navigation a.inject.reload.current').click();
            } else if (redirect) {
                log.debug('received redirect request', redirect);

                // perform redirect
                window.location.href = redirect;
            }

            // We are done
            log.debug('success', ev, jqxhr, opts);

            // XXX: below here disabled for now
            // if (!oldurl) {
            //     log.error('Missing header: X-Patterns-Previous-Url');
            //     return;
            // }
            // if (!oldurl.slice(-1) === '/') oldurl = oldurl + '/';

            // $('form[action^="' + oldurl + '"]').each(function() {
            //     var $form = $(this),
            //         action = $form.attr('action').replace(oldurl, redirect);
            //     $form.attr({action: action});
            //     log.debug('rewrote form action: s:', oldurl, ":", redirect, ":", $form);
            // });

            // XXX: rewrite anchors that point to the old url?
        });

        return $el;
    };

    return {
        markup_trigger: 'form.ajaxify, a.ajaxify',
        initialised_class: 'ajaxified',
        init: init
    };
});
// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<commit_msg>remove unsued ajaxify<commit_after><|endoftext|>


<commit_before>define(["pex/core/Context"], function(Context) {
  function Texture(target) {
    if (target) {
      console.log("Texture+");
      this.init(target);
    }
  }

  Texture.prototype.init = function(target) {
    this.gl = Context.currentContext.gl;
    this.target = target;
    this.handle = this.gl.createTexture();
  }

  //### bind ( unit )
  //Binds the texture to the current GL context.
  //`unit` - texture unit in which to place the texture *{ Number/Int }* = 0
  Texture.prototype.bind = function(unit) {
    unit = unit ? unit : 0;
    this.gl.activeTexture(this.gl.TEXTURE0 + unit);
    this.gl.bindTexture(this.target,  this.handle);
  }

  return Texture;
});<commit_msg>Added constant for Texture Float in OpenGL<commit_after>define(["pex/core/Context"], function(Context) {
  function Texture(target) {
    if (target) {
      console.log("Texture+");
      this.init(target);
    }
  }

  Texture.RGBA32F = 34836;

  Texture.prototype.init = function(target) {
    this.gl = Context.currentContext.gl;
    this.target = target;
    this.handle = this.gl.createTexture();
  }

  //### bind ( unit )
  //Binds the texture to the current GL context.
  //`unit` - texture unit in which to place the texture *{ Number/Int }* = 0
  Texture.prototype.bind = function(unit) {
    unit = unit ? unit : 0;
    this.gl.activeTexture(this.gl.TEXTURE0 + unit);
    this.gl.bindTexture(this.target,  this.handle);
  }

  return Texture;
});<|endoftext|>


<commit_before>/* global Politespace:true */
(function( $ ) {
	"use strict";

	// jQuery Plugin

	var componentName = "politespace",
		initSelector = "[data-" + componentName + "]";

	$.fn[ componentName ] = function(){
		return this.each( function(){
			var $t = $( this );
			if( $t.data( componentName ) ) {
				return;
			}

			var polite = new Politespace( this );
			if( polite.useProxy() ) {
				polite.createProxy();
			}

			$( this )
				.bind( "politespace-update", function() {
					polite.update();
					polite.updateProxy();
				})
				.bind( "input keydown", function() {
					polite.updateProxy();
				})
				.bind( "blur", function() {
					$( this ).closest( ".politespace-proxy" ).addClass( "active" );
					polite.update();
					polite.updateProxy();
				})
				.bind( "focus", function() {
					$( this ).closest( ".politespace-proxy" ).removeClass( "active" );
					polite.reset();
				})
				.data( componentName, polite );

			polite.update();
		});
	};

	// auto-init on enhance (which is called on domready)
	$( document ).bind( "enhance", function( e ) {
		var $sel = $( e.target ).is( initSelector ) ? $( e.target ) : $( initSelector, e.target );
		$sel[ componentName ]();
	});

}( jQuery ));
<commit_msg>enable external control of the proxy<commit_after>/* global Politespace:true */
(function( $ ) {
	"use strict";

	// jQuery Plugin

	var componentName = "politespace",
		initSelector = "[data-" + componentName + "]";

	$.fn[ componentName ] = function(){
		return this.each( function(){
			var $t = $( this );
			if( $t.data( componentName ) ) {
				return;
			}

			var polite = new Politespace( this );
			if( polite.useProxy() ) {
				polite.createProxy();
			}

			$( this )
				.bind( "politespace-hide-proxy", function() {
					$( this ).closest( ".politespace-proxy" ).removeClass( "active" );
				})
				.bind( "politespace-show-proxy", function() {
					$( this ).closest( ".politespace-proxy" ).addClass( "active" );

					if( polite.useProxy() ) {
						polite.update();
						polite.updateProxy();
					}
				})
				.bind( "input keydown", function() {
					polite.updateProxy();
				})
				.bind( "blur", function() {
					polite.update();

					if( polite.useProxy() ){
						$( this ).closest( ".politespace-proxy" ).addClass( "active" );
						polite.updateProxy();
					}
				})
				.bind( "focus", function() {
					$( this ).closest( ".politespace-proxy" ).removeClass( "active" );
					polite.reset();
				})
				.data( componentName, polite );

			polite.update();
		});
	};

	// auto-init on enhance (which is called on domready)
	$( document ).bind( "enhance", function( e ) {
		var $sel = $( e.target ).is( initSelector ) ? $( e.target ) : $( initSelector, e.target );
		$sel[ componentName ]();
	});

}( jQuery ));
<|endoftext|>


<commit_before>const Long = require('long')

const INT8_SIZE = 1
const INT16_SIZE = 2
const INT32_SIZE = 4
const INT64_SIZE = 8

const MOST_SIGNIFICANT_BIT = 0x80 // 128
const OTHER_BITS = 0x7f // 127

module.exports = class Decoder {
  static int32Size() {
    return INT32_SIZE
  }

  constructor(buffer) {
    this.buffer = buffer
    this.offset = 0
  }

  readInt8() {
    const value = this.buffer.readInt8(this.offset)
    this.offset += INT8_SIZE
    return value
  }

  readInt16() {
    const value = this.buffer.readInt16BE(this.offset)
    this.offset += INT16_SIZE
    return value
  }

  canReadInt32() {
    return Buffer.byteLength(this.buffer) - this.offset >= INT32_SIZE
  }

  readInt32() {
    const value = this.buffer.readInt32BE(this.offset)
    this.offset += INT32_SIZE
    return value
  }

  canReadInt64() {
    return Buffer.byteLength(this.buffer) - this.offset >= INT64_SIZE
  }

  readInt64() {
    const lowBits = this.buffer.readInt32BE(this.offset + 4)
    const highBits = this.buffer.readInt32BE(this.offset)
    const value = new Long(lowBits, highBits)
    this.offset += INT64_SIZE
    return value
  }

  readString() {
    const byteLength = this.readInt16()

    if (byteLength === -1) {
      return null
    }

    const stringBuffer = this.buffer.slice(this.offset, this.offset + byteLength)
    const value = stringBuffer.toString('utf8')
    this.offset += byteLength
    return value
  }

  canReadBytes(length) {
    return Buffer.byteLength(this.buffer) - this.offset >= length
  }

  readBytes() {
    const byteLength = this.readInt32()

    if (byteLength === -1) {
      return null
    }

    const stringBuffer = this.buffer.slice(this.offset, this.offset + byteLength)
    this.offset += byteLength
    return stringBuffer
  }

  readBoolean() {
    return this.readInt8() === 1
  }

  readAll() {
    const currentSize = Buffer.byteLength(this.buffer)
    const remainingBytes = currentSize - this.offset
    const newBuffer = Buffer.alloc(remainingBytes)
    this.buffer.copy(newBuffer, 0, this.offset, currentSize)
    return newBuffer
  }

  readArray(reader) {
    const length = this.readInt32()

    if (length === -1) {
      return []
    }

    const array = []
    for (let i = 0; i < length; i++) {
      array.push(reader(this))
    }

    return array
  }

  async readArrayAsync(reader) {
    const length = this.readInt32()

    if (length === -1) {
      return []
    }

    const array = []
    for (let i = 0; i < length; i++) {
      array.push(await reader(this))
    }

    return array
  }

  readSignedVarInt32() {
    let currentByte
    let result = 0
    let i = 0

    do {
      currentByte = this.buffer[this.offset++]
      result += (currentByte & OTHER_BITS) << i
      i += 7
    } while (currentByte >= MOST_SIGNIFICANT_BIT)

    return this.decodeZigZag(result)
  }

  decodeZigZag(value) {
    return (value >>> 1) ^ -(value & 1)
  }

  readSignedVarInt64() {
    let currentByte
    let result = Long.fromInt(0)
    let i = 0

    do {
      currentByte = this.buffer[this.offset++]
      result = result.add(Long.fromInt(currentByte & OTHER_BITS).shiftLeft(i))
      i += 7
    } while (currentByte >= MOST_SIGNIFICANT_BIT)

    return this.decodeZigZag64(result)
  }

  decodeZigZag64(longValue) {
    return longValue.shiftRightUnsigned(1).xor(longValue.and(Long.fromInt(1)).negate())
  }

  slice(size) {
    return new Decoder(this.buffer.slice(this.offset, this.offset + size))
  }

  forward(size) {
    this.offset += size
  }
}
<commit_msg>Rename decoder#readSignedVarInt32 to varInt and readSignedVarInt64 to varLong<commit_after>const Long = require('long')

const INT8_SIZE = 1
const INT16_SIZE = 2
const INT32_SIZE = 4
const INT64_SIZE = 8

const MOST_SIGNIFICANT_BIT = 0x80 // 128
const OTHER_BITS = 0x7f // 127

module.exports = class Decoder {
  static int32Size() {
    return INT32_SIZE
  }

  constructor(buffer) {
    this.buffer = buffer
    this.offset = 0
  }

  readInt8() {
    const value = this.buffer.readInt8(this.offset)
    this.offset += INT8_SIZE
    return value
  }

  readInt16() {
    const value = this.buffer.readInt16BE(this.offset)
    this.offset += INT16_SIZE
    return value
  }

  canReadInt32() {
    return Buffer.byteLength(this.buffer) - this.offset >= INT32_SIZE
  }

  readInt32() {
    const value = this.buffer.readInt32BE(this.offset)
    this.offset += INT32_SIZE
    return value
  }

  canReadInt64() {
    return Buffer.byteLength(this.buffer) - this.offset >= INT64_SIZE
  }

  readInt64() {
    const lowBits = this.buffer.readInt32BE(this.offset + 4)
    const highBits = this.buffer.readInt32BE(this.offset)
    const value = new Long(lowBits, highBits)
    this.offset += INT64_SIZE
    return value
  }

  readString() {
    const byteLength = this.readInt16()

    if (byteLength === -1) {
      return null
    }

    const stringBuffer = this.buffer.slice(this.offset, this.offset + byteLength)
    const value = stringBuffer.toString('utf8')
    this.offset += byteLength
    return value
  }

  canReadBytes(length) {
    return Buffer.byteLength(this.buffer) - this.offset >= length
  }

  readBytes() {
    const byteLength = this.readInt32()

    if (byteLength === -1) {
      return null
    }

    const stringBuffer = this.buffer.slice(this.offset, this.offset + byteLength)
    this.offset += byteLength
    return stringBuffer
  }

  readBoolean() {
    return this.readInt8() === 1
  }

  readAll() {
    const currentSize = Buffer.byteLength(this.buffer)
    const remainingBytes = currentSize - this.offset
    const newBuffer = Buffer.alloc(remainingBytes)
    this.buffer.copy(newBuffer, 0, this.offset, currentSize)
    return newBuffer
  }

  readArray(reader) {
    const length = this.readInt32()

    if (length === -1) {
      return []
    }

    const array = []
    for (let i = 0; i < length; i++) {
      array.push(reader(this))
    }

    return array
  }

  async readArrayAsync(reader) {
    const length = this.readInt32()

    if (length === -1) {
      return []
    }

    const array = []
    for (let i = 0; i < length; i++) {
      array.push(await reader(this))
    }

    return array
  }

  readVarInt() {
    let currentByte
    let result = 0
    let i = 0

    do {
      currentByte = this.buffer[this.offset++]
      result += (currentByte & OTHER_BITS) << i
      i += 7
    } while (currentByte >= MOST_SIGNIFICANT_BIT)

    return this.decodeZigZag(result)
  }

  decodeZigZag(value) {
    return (value >>> 1) ^ -(value & 1)
  }

  readVarLong() {
    let currentByte
    let result = Long.fromInt(0)
    let i = 0

    do {
      currentByte = this.buffer[this.offset++]
      result = result.add(Long.fromInt(currentByte & OTHER_BITS).shiftLeft(i))
      i += 7
    } while (currentByte >= MOST_SIGNIFICANT_BIT)

    return this.decodeZigZag64(result)
  }

  decodeZigZag64(longValue) {
    return longValue.shiftRightUnsigned(1).xor(longValue.and(Long.fromInt(1)).negate())
  }

  slice(size) {
    return new Decoder(this.buffer.slice(this.offset, this.offset + size))
  }

  forward(size) {
    this.offset += size
  }
}
<|endoftext|>


<commit_before>var prototypo		= require('prototypo.js');
var assign			= require('es6-object-assign').assign;
var EventEmitter	= require('wolfy87-eventemitter');
var glyph			= require('./utils/glyph');
var mouseHandlers	= require('./utils/mouseHandlers');
var init			= require('./utils/init');
var loadFont		= require('./utils/loadFont');

var _ = { assign: assign },
	paper = prototypo.paper;

// constructor
function PrototypoCanvas( opts ) {
	paper.setup( opts.canvas );
	paper.settings.hitTolerance = 1;
	// enable pointerevents on the canvas
	opts.canvas.setAttribute('touch-action', 'none');

	this.opts = _.assign({
		fill: true,
		shoNodes: false,
		zoomFactor: 0.05,
		jQueryListeners: true,
		glyphrUrl: 'http://www.glyphrstudio.com/online/'
	}, opts);

	this.canvas = opts.canvas;
	this.view = paper.view;
	this.view.center = [ 0, 0 ];
	this.project = paper.project;
	this.project.activeLayer.applyMatrix = false;
	this.project.activeLayer.scale( 1, -1 );
	this.worker = opts.worker;
	this._queue = [];
	this._fill = this.opts.fill;
	this._showNodes = this.opts.showNodes;
	this.fontsMap = {};
	this.isMousedown = false;
	this.exportingZip = false;

	// this.grid = new Grid( paper );

	// bind workerHandlers
	if ( this.worker ) {
		this.worker.port.addEventListener('message', function(e) {
			// the job might have been cancelled
			if ( !this.currentJob ) {
				return;
			}

			if ( this.currentJob.callback ) {
				this.currentJob.callback( e.data );

			// default callback for buffers: use it as a font
			} else if ( e.data instanceof ArrayBuffer ) {
				try {
					this.font.addToFonts( e.data );
					this.emitEvent( 'worker.fontLoaded');

				} catch ( error ) {
					this.emitEvent( 'fonterror', [ error ] );
				}
			}

			this.currentJob = false;
			this.dequeue();

		}.bind(this));
	}

	// bind mouseHandlers (jQuery is an optional dependency)
	if ( ( 'jQuery' in window ) && this.opts.jQueryListeners ) {
		var $ = window.jQuery,
			type = ( 'PointerEventsPolyfill' in window ) ||
				( 'PointerEvent' in window ) ? 'pointer' : 'mouse';

		$(opts.canvas).on( 'wheel', this.onWheel.bind(this) );

		$(opts.canvas).on( type + 'move', this.onMove.bind(this) );

		$(opts.canvas).on( type + 'down', this.onDown.bind(this) );

		$(document).on( type + 'up', this.onUp.bind(this) );
	}

	// setup raf loop
	var raf = window.requestAnimationFrame ||
			window.webkitRequestAnimationFrame,
		updateLoop = function() {
			raf(updateLoop);

			if (
				!this.latestRafValues ||
				!this.currGlyph ||
				this.exportingZip
			) {
				return;
			}

			this.font.update( this.latestRafValues, [ this.currGlyph ] );
			this.view.update();
			delete this.latestRafValues;

		}.bind(this);
	updateLoop();
}

PrototypoCanvas.prototype = Object.create( EventEmitter.prototype );
PrototypoCanvas.init = init;
PrototypoCanvas.prototype.loadFont = loadFont;
_.assign( PrototypoCanvas.prototype, mouseHandlers );

Object.defineProperties( PrototypoCanvas.prototype, {
	zoom: {
		get: function() {
			return this.view.zoom;
		},
		set: function( zoom ) {
			this.view.zoom = zoom;
			// this.grid.zoom = zoom;
		}
	},
	fill: {
		get: function() {
			return this._fill;
		},
		set: function( bool ) {
			this._fill = bool;
			this.displayGlyph();
		}
	},
	showNodes: {
		get: function() {
			return this._showNodes;
		},
		set: function( bool ) {
			this._showNodes = bool;
			this.displayGlyph();
		}
	},
	showCoords: {
		get: function() {
			return paper.settings.drawCoords;
		},
		set: function( bool ) {
			paper.settings.drawCoords = bool;
			this.displayGlyph();
		}
	},
	subset: {
		get: function() {
			return this.font.subset;
		},
		set: function( set ) {
			this.enqueue({
				type: 'subset',
				data: set
			});

			this.font.subset = set;
		}
	}
});

PrototypoCanvas.prototype.displayGlyph = glyph.displayGlyph;

PrototypoCanvas.prototype.displayChar = function( code ) {
	this.latestChar = code;
	this.displayGlyph( typeof code === 'string' ?
		this.font.charMap[ code.charCodeAt(0) ] : code
	);
};

// overwrite the appearance of #selected items in paper.js
paper.PaperScope.prototype.Path.prototype._drawSelected = glyph._drawSelected;
_.assign( paper.settings, {
	handleSize: 6,
	handleColor: '#FF725E',
	skeletonColor: '#24D390',
	nodeColor: '#00C4D6',
	drawCoords: false,
	handleFont: '12px monospace'
});

// The worker queue is not your ordinary queue: the priority of the job is
// defined arbitrarily, and any message previously present
// at this position will be overwritten. The priorities associated to the
// message type are hardcoded below (in ascending priority order).
PrototypoCanvas.priorities = [
	'update',
	'subset',
	'svgFont',
	'otfFont',
	'alternate'
];

PrototypoCanvas.prototype.enqueue = function( message ) {
	var priority = PrototypoCanvas.priorities.indexOf( message.type );

	if (this._queue[ priority ] === undefined) {
		this._queue[ priority ] = [];
	}

	if ( message.serialized ) {
		this._queue[ priority ].push(message);

	} else {
		this._queue[ priority ][0] = message;
	}

	this.dequeue();
};

PrototypoCanvas.prototype.dequeue = function() {
	if ( this.currentJob || !this.worker ) {
		return;
	}

	// send the highest priority mesage in the queue (0 is lowest)
	for ( var i = this._queue.length; i--; ) {
		if ( this._queue[i] && this._queue[i].length > 0 ) {
			this.currentJob = this._queue[i].shift();

			// the callback function shouldn't be sent
			var cb = this.currentJob.callback;
			delete this.currentJob.callback;

			this.worker.port.postMessage( this.currentJob );

			this.currentJob.callback = cb;
			break;
		}
	}
};

PrototypoCanvas.prototype.emptyQueue = function() {
	this._queue = [];
	this.currentJob = false;
};

PrototypoCanvas.prototype.update = function( values ) {
	// latestValues are used in displayGlyph
	// latestWorkerValues is used and disposed by th/sue fontBufferHandler
	// latestRafValues is used and disposed by the raf loop
	// so we need all three!
	this.latestValues = this.latestRafValues = values;

	this.enqueue({
		type: 'update',
		data: values
	});
};

PrototypoCanvas.prototype.setAlternateFor = function( unicode, glyphName ) {
	if ( !glyphName ) {
		Object.keys(unicode).forEach(function(code) {

			if ( +code === this.currGlyph.src.unicode ) {
				this.displayChar( this.font.glyphMap[unicode[code]] );
			}

			this.font.setAlternateFor(code, unicode[code]);
		}.bind(this));

		this.enqueue({
			type: 'alternate',
			data: {
				altList: unicode
			}
		});
	} else {
		this.font.setAlternateFor( unicode, glyphName );

		this.displayChar( this.font.glyphMap[glyphName] );

		this.enqueue({
			type: 'alternate',
			data: {
				unicode: unicode,
				glyphName: glyphName
			}
		});
	}
	this.update( this.latestValues );
};

PrototypoCanvas.prototype.download =
	function( cb, name, merged, values, user) {
		this.generateOtf(function( data ) {
			this.font.download( data, merged, name, user );
			if ( cb ) {
				cb();
			}
		}.bind(this), name, false, values);
	};

PrototypoCanvas.prototype.getBlob = function( cb, name, merged, values ) {
	return new Promise(function( resolve, reject ) {
		try {
			this.generateOtf( function( data ) {
				resolve( {
					buffer: data,
					variant: name.style
				});
				if ( cb ) {
					cb();
				}
			}, name, merged, values, true);
		} catch ( err ) {
			reject(err);
		}
	}.bind(this));
};

PrototypoCanvas.prototype.generateOtf =
	function( cb, name, merged, values, serialized ) {
		if ( !this.worker || ( !this.latestValues && !values ) ) {
			// the UI should wait for the first update to happen before allowing
			// the download button to be clicked
			return false;
		}

		this.enqueue({
			type: 'otfFont',
			data: {
				family: name && name.family,
				style: name && name.style,
				merged: merged,
				values: values
			},
			callback: function( data ) {
				if ( cb ) {
					cb(data);
				}
			},
			serialized: serialized
		});
	};

PrototypoCanvas.prototype.openInGlyphr = function( cb, name, merged, values, user ) {
	if ( !this.worker || !this.latestValues ) {
		// the UI should wait for the first update to happen before allowing
		// the download button to be clicked
		return false;
	}

	this.enqueue({
		// otf/svg switch
		type: 'otfFont',
		// type: 'svgFont',
		callback: function( data ) {
			var handler = function(e) {
				window.removeEventListener('message', handler);
				if ( e.data !== 'ready' ) {
					return;
				}
				// otf/svg switch
				e.source.postMessage( data, e.origin, [ data ] );
				// e.source.postMessage( data, e.origin );
				if ( cb ) {
					cb();
				}
			};

			// font backup
			this.generateOtf(function( arrayBuffer ) {
				fetch('http://localhost:3000/' +
					name.family + '/' +
					name.style + '/' +
					user +
					(name.template ? '/' + name.template : ''), {
						method: 'POST',
						headers: { 'Content-Type': 'application/otf' },
						body: arrayBuffer
				});
			}.bind(this), name, false, values);

			window.open( this.opts.glyphrUrl );
			window.addEventListener('message', handler);
		}.bind(this)
	});
};

module.exports = PrototypoCanvas;
<commit_msg>fixed the localhost url to send glyphr export directly to merge.prototyop.io<commit_after>var prototypo		= require('prototypo.js');
var assign			= require('es6-object-assign').assign;
var EventEmitter	= require('wolfy87-eventemitter');
var glyph			= require('./utils/glyph');
var mouseHandlers	= require('./utils/mouseHandlers');
var init			= require('./utils/init');
var loadFont		= require('./utils/loadFont');

var _ = { assign: assign },
	paper = prototypo.paper;

// constructor
function PrototypoCanvas( opts ) {
	paper.setup( opts.canvas );
	paper.settings.hitTolerance = 1;
	// enable pointerevents on the canvas
	opts.canvas.setAttribute('touch-action', 'none');

	this.opts = _.assign({
		fill: true,
		shoNodes: false,
		zoomFactor: 0.05,
		jQueryListeners: true,
		glyphrUrl: 'http://www.glyphrstudio.com/online/'
	}, opts);

	this.canvas = opts.canvas;
	this.view = paper.view;
	this.view.center = [ 0, 0 ];
	this.project = paper.project;
	this.project.activeLayer.applyMatrix = false;
	this.project.activeLayer.scale( 1, -1 );
	this.worker = opts.worker;
	this._queue = [];
	this._fill = this.opts.fill;
	this._showNodes = this.opts.showNodes;
	this.fontsMap = {};
	this.isMousedown = false;
	this.exportingZip = false;

	// this.grid = new Grid( paper );

	// bind workerHandlers
	if ( this.worker ) {
		this.worker.port.addEventListener('message', function(e) {
			// the job might have been cancelled
			if ( !this.currentJob ) {
				return;
			}

			if ( this.currentJob.callback ) {
				this.currentJob.callback( e.data );

			// default callback for buffers: use it as a font
			} else if ( e.data instanceof ArrayBuffer ) {
				try {
					this.font.addToFonts( e.data );
					this.emitEvent( 'worker.fontLoaded');

				} catch ( error ) {
					this.emitEvent( 'fonterror', [ error ] );
				}
			}

			this.currentJob = false;
			this.dequeue();

		}.bind(this));
	}

	// bind mouseHandlers (jQuery is an optional dependency)
	if ( ( 'jQuery' in window ) && this.opts.jQueryListeners ) {
		var $ = window.jQuery,
			type = ( 'PointerEventsPolyfill' in window ) ||
				( 'PointerEvent' in window ) ? 'pointer' : 'mouse';

		$(opts.canvas).on( 'wheel', this.onWheel.bind(this) );

		$(opts.canvas).on( type + 'move', this.onMove.bind(this) );

		$(opts.canvas).on( type + 'down', this.onDown.bind(this) );

		$(document).on( type + 'up', this.onUp.bind(this) );
	}

	// setup raf loop
	var raf = window.requestAnimationFrame ||
			window.webkitRequestAnimationFrame,
		updateLoop = function() {
			raf(updateLoop);

			if (
				!this.latestRafValues ||
				!this.currGlyph ||
				this.exportingZip
			) {
				return;
			}

			this.font.update( this.latestRafValues, [ this.currGlyph ] );
			this.view.update();
			delete this.latestRafValues;

		}.bind(this);
	updateLoop();
}

PrototypoCanvas.prototype = Object.create( EventEmitter.prototype );
PrototypoCanvas.init = init;
PrototypoCanvas.prototype.loadFont = loadFont;
_.assign( PrototypoCanvas.prototype, mouseHandlers );

Object.defineProperties( PrototypoCanvas.prototype, {
	zoom: {
		get: function() {
			return this.view.zoom;
		},
		set: function( zoom ) {
			this.view.zoom = zoom;
			// this.grid.zoom = zoom;
		}
	},
	fill: {
		get: function() {
			return this._fill;
		},
		set: function( bool ) {
			this._fill = bool;
			this.displayGlyph();
		}
	},
	showNodes: {
		get: function() {
			return this._showNodes;
		},
		set: function( bool ) {
			this._showNodes = bool;
			this.displayGlyph();
		}
	},
	showCoords: {
		get: function() {
			return paper.settings.drawCoords;
		},
		set: function( bool ) {
			paper.settings.drawCoords = bool;
			this.displayGlyph();
		}
	},
	subset: {
		get: function() {
			return this.font.subset;
		},
		set: function( set ) {
			this.enqueue({
				type: 'subset',
				data: set
			});

			this.font.subset = set;
		}
	}
});

PrototypoCanvas.prototype.displayGlyph = glyph.displayGlyph;

PrototypoCanvas.prototype.displayChar = function( code ) {
	this.latestChar = code;
	this.displayGlyph( typeof code === 'string' ?
		this.font.charMap[ code.charCodeAt(0) ] : code
	);
};

// overwrite the appearance of #selected items in paper.js
paper.PaperScope.prototype.Path.prototype._drawSelected = glyph._drawSelected;
_.assign( paper.settings, {
	handleSize: 6,
	handleColor: '#FF725E',
	skeletonColor: '#24D390',
	nodeColor: '#00C4D6',
	drawCoords: false,
	handleFont: '12px monospace'
});

// The worker queue is not your ordinary queue: the priority of the job is
// defined arbitrarily, and any message previously present
// at this position will be overwritten. The priorities associated to the
// message type are hardcoded below (in ascending priority order).
PrototypoCanvas.priorities = [
	'update',
	'subset',
	'svgFont',
	'otfFont',
	'alternate'
];

PrototypoCanvas.prototype.enqueue = function( message ) {
	var priority = PrototypoCanvas.priorities.indexOf( message.type );

	if (this._queue[ priority ] === undefined) {
		this._queue[ priority ] = [];
	}

	if ( message.serialized ) {
		this._queue[ priority ].push(message);

	} else {
		this._queue[ priority ][0] = message;
	}

	this.dequeue();
};

PrototypoCanvas.prototype.dequeue = function() {
	if ( this.currentJob || !this.worker ) {
		return;
	}

	// send the highest priority mesage in the queue (0 is lowest)
	for ( var i = this._queue.length; i--; ) {
		if ( this._queue[i] && this._queue[i].length > 0 ) {
			this.currentJob = this._queue[i].shift();

			// the callback function shouldn't be sent
			var cb = this.currentJob.callback;
			delete this.currentJob.callback;

			this.worker.port.postMessage( this.currentJob );

			this.currentJob.callback = cb;
			break;
		}
	}
};

PrototypoCanvas.prototype.emptyQueue = function() {
	this._queue = [];
	this.currentJob = false;
};

PrototypoCanvas.prototype.update = function( values ) {
	// latestValues are used in displayGlyph
	// latestWorkerValues is used and disposed by th/sue fontBufferHandler
	// latestRafValues is used and disposed by the raf loop
	// so we need all three!
	this.latestValues = this.latestRafValues = values;

	this.enqueue({
		type: 'update',
		data: values
	});
};

PrototypoCanvas.prototype.setAlternateFor = function( unicode, glyphName ) {
	if ( !glyphName ) {
		Object.keys(unicode).forEach(function(code) {

			if ( +code === this.currGlyph.src.unicode ) {
				this.displayChar( this.font.glyphMap[unicode[code]] );
			}

			this.font.setAlternateFor(code, unicode[code]);
		}.bind(this));

		this.enqueue({
			type: 'alternate',
			data: {
				altList: unicode
			}
		});
	} else {
		this.font.setAlternateFor( unicode, glyphName );

		this.displayChar( this.font.glyphMap[glyphName] );

		this.enqueue({
			type: 'alternate',
			data: {
				unicode: unicode,
				glyphName: glyphName
			}
		});
	}
	this.update( this.latestValues );
};

PrototypoCanvas.prototype.download =
	function( cb, name, merged, values, user) {
		this.generateOtf(function( data ) {
			this.font.download( data, merged, name, user );
			if ( cb ) {
				cb();
			}
		}.bind(this), name, false, values);
	};

PrototypoCanvas.prototype.getBlob = function( cb, name, merged, values ) {
	return new Promise(function( resolve, reject ) {
		try {
			this.generateOtf( function( data ) {
				resolve( {
					buffer: data,
					variant: name.style
				});
				if ( cb ) {
					cb();
				}
			}, name, merged, values, true);
		} catch ( err ) {
			reject(err);
		}
	}.bind(this));
};

PrototypoCanvas.prototype.generateOtf =
	function( cb, name, merged, values, serialized ) {
		if ( !this.worker || ( !this.latestValues && !values ) ) {
			// the UI should wait for the first update to happen before allowing
			// the download button to be clicked
			return false;
		}

		this.enqueue({
			type: 'otfFont',
			data: {
				family: name && name.family,
				style: name && name.style,
				merged: merged,
				values: values
			},
			callback: function( data ) {
				if ( cb ) {
					cb(data);
				}
			},
			serialized: serialized
		});
	};

PrototypoCanvas.prototype.openInGlyphr = function( cb, name, merged, values, user ) {
	if ( !this.worker || !this.latestValues ) {
		// the UI should wait for the first update to happen before allowing
		// the download button to be clicked
		return false;
	}

	this.enqueue({
		// otf/svg switch
		type: 'otfFont',
		// type: 'svgFont',
		callback: function( data ) {
			var handler = function(e) {
				window.removeEventListener('message', handler);
				if ( e.data !== 'ready' ) {
					return;
				}
				// otf/svg switch
				e.source.postMessage( data, e.origin, [ data ] );
				// e.source.postMessage( data, e.origin );
				if ( cb ) {
					cb();
				}
			};

			// font backup
			this.generateOtf(function( arrayBuffer ) {
				fetch(
					[
						'https://merge.prototypo.io',
						name.family,
						name.style,
						user,
						name.template || 'unknown'
					].join('/'), {
						method: 'POST',
						headers: { 'Content-Type': 'application/otf' },
						body: arrayBuffer
				});
			}.bind(this), name, false, values);

			window.open( this.opts.glyphrUrl );
			window.addEventListener('message', handler);
		}.bind(this)
	});
};

module.exports = PrototypoCanvas;
<|endoftext|>


<commit_before>var Drawing = function (p, pulse, config) {
  var defaults = {};
  defaults.totalFrames = 1000;

  defaults.points = [];
  defaults.totalPoints = 1000;

  defaults.finalRotation = p.PI;

  defaults.config = config;
  defaults.gridWidth = config.totalCols * p.width;
  defaults.gridHeight = config.totalRows * p.height;

  defaults.longEdge = defaults.gridWidth;
  if (defaults.gridHeight > defaults.longEdge) defaults.longEdge = defaults.gridHeight;

  defaults.xOffset = (config.col - 1) * p.width;
  defaults.yOffset = (config.row - 2) * p.height;

  defaults.rotationX = 0;
  defaults.rotationY = 0;

  p.pulsar.merge(defaults, pulse);

  this.pulse = defaults;

  this.pulse.framesLeft = this.pulse.totalFrames;

  // generate initial point field
  for (var currentPoint = 0; currentPoint < this.pulse.totalPoints; currentPoint++) {
    this.pulse.points[currentPoint] = this.generateNewPoint();
  }
};

Drawing.prototype.draw = function (p) {
  var self = this;
  var pulse = self.pulse;

  // translate the origin into the middle of the pulsar grid
  // the above offsets are then used to translate the
  // field relative to each screen

  p.push();
  p.translate(pulse.gridWidth/2 - pulse.xOffset + pulse.rotationX, pulse.gridHeight/2 - pulse.yOffset - pulse.rotationY);
  p.rotate(pulse.finalRotation * (pulse.framesLeft - pulse.totalFrames)/pulse.totalFrames);

  for (var currentPoint = 0; currentPoint < pulse.totalPoints; currentPoint++) {
    var point = pulse.points[currentPoint];

    if (!pulse.randomColour) {
      p.stroke(255);
    } else {
      var rand = Math.floor(Math.random()*15986198417);
      //console.log(rand)
      p.stroke(rand*(pulse.randomColour)%255,
               rand*(pulse.randomColour*pulse.randomColour)%255,
               rand*(pulse.randomColour*pulse.randomColour*pulse.randomColour)%255);
    }
    p.strokeWeight(currentPoint % 16 + 8);
    p.point(point.x, point.y);
  }
  p.pop();

  pulse.framesLeft--;
};

Drawing.prototype.generateNewPoint = function() {
  var pulse = this.pulse;
  return {
    x: Math.floor(Math.random() * (2*(pulse.longEdge))) - (pulse.longEdge),
    y: Math.floor(Math.random() * (2*(pulse.longEdge))) - (pulse.longEdge)
  }
}

Drawing.prototype.update = function (p, pulse, config) {
  p.pulsar.merge(this.pulse, pulse);
}

Drawing.prototype.done = function () {
  if (this.pulse.framesLeft < 0) {
    return true;
  } else {
    return false;
  }
}

module.exports = Drawing;<commit_msg>Vary stroke size in starfield<commit_after>var Drawing = function (p, pulse, config) {
  var defaults = {};
  defaults.totalFrames = 1000;

  defaults.points = [];
  defaults.totalPoints = 1000;

  defaults.finalRotation = p.PI;

  if (pulse.target) {
    if (pulse.target.col) {
      config.totalCols = 1;
    }
    if (pulse.target.row) {
      config.totalRows = 1;
    }
  }

  defaults.config = config;
  defaults.gridWidth = config.totalCols * p.width;
  defaults.gridHeight = config.totalRows * p.height;

  defaults.longEdge = defaults.gridWidth;
  if (defaults.gridHeight > defaults.longEdge) defaults.longEdge = defaults.gridHeight;

  defaults.xOffset = (config.col - 1) * p.width;
  defaults.yOffset = (config.row - 2) * p.height;

  defaults.rotationX = 0;
  defaults.rotationY = 0;

  defaults.strokeSize = 16;

  p.pulsar.merge(defaults, pulse);

  this.pulse = defaults;

  this.pulse.framesLeft = this.pulse.totalFrames;

  // generate initial point field
  for (var currentPoint = 0; currentPoint < this.pulse.totalPoints; currentPoint++) {
    this.pulse.points[currentPoint] = this.generateNewPoint();
  }
};

Drawing.prototype.draw = function (p) {
  var self = this;
  var pulse = self.pulse;

  // translate the origin into the middle of the pulsar grid
  // the above offsets are then used to translate the
  // field relative to each screen

  p.push();
  p.translate(pulse.gridWidth/2 - pulse.xOffset + pulse.rotationX, pulse.gridHeight/2 - pulse.yOffset - pulse.rotationY);
  p.rotate(pulse.finalRotation * (pulse.framesLeft - pulse.totalFrames)/pulse.totalFrames);

  for (var currentPoint = 0; currentPoint < pulse.totalPoints; currentPoint++) {
    var point = pulse.points[currentPoint];

    if (!pulse.randomColour) {
      p.stroke(255);
    } else {
      var rand = Math.floor(Math.random()*15986198417);
      //console.log(rand)
      p.stroke(rand*(pulse.randomColour)%255,
               rand*(pulse.randomColour*pulse.randomColour)%255,
               rand*(pulse.randomColour*pulse.randomColour*pulse.randomColour)%255);
    }
    p.strokeWeight(currentPoint % pulse.strokeSize/2 + pulse.strokeSize);
    p.point(point.x, point.y);
  }
  p.pop();

  pulse.framesLeft--;
};

Drawing.prototype.generateNewPoint = function() {
  var pulse = this.pulse;
  return {
    x: Math.floor(Math.random() * (2*(pulse.longEdge))) - (pulse.longEdge),
    y: Math.floor(Math.random() * (2*(pulse.longEdge))) - (pulse.longEdge)
  }
}

Drawing.prototype.update = function (p, pulse, config) {
  p.pulsar.merge(this.pulse, pulse);
}

Drawing.prototype.done = function () {
  if (this.pulse.framesLeft < 0) {
    return true;
  } else {
    return false;
  }
}

module.exports = Drawing;<|endoftext|>


<commit_before>//File name: app.js
//Description: the entry point to the chat app server
//Last updated: 4/28/2019

// Name of .jar file for BloomingLeaf project must be Blooming.jar
//var userPath = "/Users/<your user path here>/BloomingLeaf"
var userPath = "/Users/judySmith/git/BloomingLeaf"

var http = require('http'),
    url = require('url'),
    fileServer = require('./node/fileServer.js'),
    fs = require('fs');
    qs = require('./node/query.js'),
    utils = require('./node/utils.js');
    exec = require('child_process').exec;

//TODO: If wait is not longer needed, this function can be removed.
function wait(ms){
   var start = new Date().getTime();
   var end = start;
   while(end < start + ms) {
     end = new Date().getTime();
  }
}

function processGet(path,queryObj,res) {
    // if URL includes a path to a file, call file server method
    if (path && path.length > 1) {
        // precede the given path with name of the subdirectory in which
        // the client files are stored

        fileServer.serve_static_file(userPath+"/leaf-ui"+path,res);
    }
    // check if there is a query. The query object will always be created
    // even if there is no quesry string. So check using a property that you
    // know will be part of any query sent by the client. This is application
    // specific - depends on our knowledge of what the client will serve
    // alternatively - you can check if (url.parse(req.url).search)
    // search is the query string
    /*else if (queryObj.request) {
        qs.processQuery(queryObj,res);
    } else {
        // if not file path or query were sent - read and serve default html file
        fileServer.serve_static_file("public_html/index.html",res);
        fileServed = true;
    }*/

}

function processPost(queryObj,req,res) {
    var body = '';
    req.on('data', data => {body += data;}); // get the request data
    req.on('end', () => {          // request fully received - process it
        if (body || queryObj.name) {
            queryObj.message = body; // specific to the chat application
            qs.processQuery(queryObj,res);
        }
    fs.writeFileSync(userPath+"/leaf-analysis/temp/default.json",body);
    passIntoJar(res);

    // //TODO: Can this function be written in an asynchronous call?
    // wait(1000);

    // //TODO: Can this be made asynchronous by moving the follow code down to to "HERE"
    // //read from output.out and pass the data as a string with the response
    // analysisFile = fs.readFileSync(userPath+"/leaf-analysis/temp/output.out");
    // analysisFileString = String(analysisFile);
    // res.writeHead(200, { "Content-Type" : 'text/plain'});
    // // send data
    // res.write(analysisFileString);
    // res.end();
    });

}

// server call back function
function handle_incoming_request(req, res) {
    // get the path of the file to served
    var path = url.parse(req.url).pathname;
    // get a query (true makes sure the query string is parsed into an object)
    var queryObj = url.parse(req.url,"true").query;
    if (req.method.toLowerCase() == "get")
        processGet(path,queryObj,res);
    else if (req.method.toLowerCase() == "post")
        processPost(queryObj,req,res);
    else
        utils.sendText(res,400,"Server accepts only GET ans POST requests");
}

/*
*This function executed the jar file
*/
function passIntoJar(res) {
    child = exec('java -jar '+userPath+'/leaf-analysis/bin/Blooming.jar ',
        function (error, stdout, stderr){
            if(error !== null){
                console.log('exec error: ' + error);
            }
            else{
                //Analysis return code.
                analysisFile = fs.readFileSync(userPath+"/leaf-analysis/temp/output.out");
                analysisFileString = String(analysisFile);
                res.writeHead(200, { "Content-Type" : 'text/plain'});
                // send data
                res.write(analysisFileString);
                res.end();

                return stdout;
            }
        });
    return child;
}

var server = http.createServer(handle_incoming_request);

server.listen(8080);<commit_msg>Update app.js<commit_after>//File name: app.js
//Description: the entry point to the chat app server
//Last updated: 4/28/2019

// Name of .jar file for BloomingLeaf project must be Blooming.jar
//var userPath = "/Users/<your user path here>/BloomingLeaf"
var userPath = "/Users/meganvarnum/git/BloomingLeaf"

var http = require('http'),
    url = require('url'),
    fileServer = require('./node/fileServer.js'),
    fs = require('fs');
    qs = require('./node/query.js'),
    utils = require('./node/utils.js');
    exec = require('child_process').exec;

//TODO: If wait is not longer needed, this function can be removed.
function wait(ms){
   var start = new Date().getTime();
   var end = start;
   while(end < start + ms) {
     end = new Date().getTime();
  }
}

function processGet(path,queryObj,res) {
    // if URL includes a path to a file, call file server method
    if (path && path.length > 1) {
        // precede the given path with name of the subdirectory in which
        // the client files are stored

        fileServer.serve_static_file(userPath+"/leaf-ui"+path,res);
    }
    // check if there is a query. The query object will always be created
    // even if there is no quesry string. So check using a property that you
    // know will be part of any query sent by the client. This is application
    // specific - depends on our knowledge of what the client will serve
    // alternatively - you can check if (url.parse(req.url).search)
    // search is the query string
    /*else if (queryObj.request) {
        qs.processQuery(queryObj,res);
    } else {
        // if not file path or query were sent - read and serve default html file
        fileServer.serve_static_file("public_html/index.html",res);
        fileServed = true;
    }*/

}

function processPost(queryObj,req,res) {
    var body = '';
    req.on('data', data => {body += data;}); // get the request data
    req.on('end', () => {          // request fully received - process it
        if (body || queryObj.name) {
            queryObj.message = body; // specific to the chat application
            qs.processQuery(queryObj,res);
        }
    fs.writeFileSync(userPath+"/leaf-analysis/temp/default.json",body);
    passIntoJar(res);

    // //TODO: Can this function be written in an asynchronous call?
    // wait(1000);

    // //TODO: Can this be made asynchronous by moving the follow code down to to "HERE"
    // //read from output.out and pass the data as a string with the response
    // analysisFile = fs.readFileSync(userPath+"/leaf-analysis/temp/output.out");
    // analysisFileString = String(analysisFile);
    // res.writeHead(200, { "Content-Type" : 'text/plain'});
    // // send data
    // res.write(analysisFileString);
    // res.end();
    });

}

// server call back function
function handle_incoming_request(req, res) {
    // get the path of the file to served
    var path = url.parse(req.url).pathname;
    // get a query (true makes sure the query string is parsed into an object)
    var queryObj = url.parse(req.url,"true").query;
    if (req.method.toLowerCase() == "get")
        processGet(path,queryObj,res);
    else if (req.method.toLowerCase() == "post")
        processPost(queryObj,req,res);
    else
        utils.sendText(res,400,"Server accepts only GET ans POST requests");
}

/*
*This function executed the jar file
*/
function passIntoJar(res) {
    child = exec('java -jar '+userPath+'/leaf-analysis/bin/Blooming.jar ',
        function (error, stdout, stderr){
            if(error !== null){
                console.log('exec error: ' + error);
            }
            else{
                //Analysis return code.
                analysisFile = fs.readFileSync(userPath+"/leaf-analysis/temp/output.out");
                analysisFileString = String(analysisFile);
                res.writeHead(200, { "Content-Type" : 'text/plain'});
                // send data
                res.write(analysisFileString);
                res.end();

                return stdout;
            }
        });
    return child;
}

var server = http.createServer(handle_incoming_request);

server.listen(8080);<|endoftext|>


<commit_before>import {
    ADD_COMMENTS,
    ADD_VOTE_COMMENT,
    CREATE_COMMENT,
    REMOVE_COMMENT,
    REMOVE_VOTE_COMMENT,
    SET_COMMENTS,
    UPDATE_COMMENT
} from "../actions/Comment";
import {REMOVE_POST} from "../actions/Post";

const initialStateComments = {
    comments: []
};

export default function comment(state = initialStateComments, action) {
    let newState = JSON.parse(JSON.stringify(state));
    switch (action.type) {
        case REMOVE_COMMENT :
            newState.comments.find(comment => comment.id === action.id).deleted = true;
            return newState;

        case REMOVE_POST :
            newState.comments.filter(comment => comment.parentId === action.id).map(comment => {
                comment.parentDeleted = true;
            });
            return newState;

        case CREATE_COMMENT:
            newState.comments.push(action.comment);
            return newState;

        case ADD_VOTE_COMMENT:
            newState.comments.find(comment => comment.id === action.id).voteScore += 1;
            return newState;

        case REMOVE_VOTE_COMMENT:
            newState.comments.find(comment => comment.id === action.id).voteScore -= 1;
            return newState;

        case UPDATE_COMMENT:
            newState.comments.find(comment => comment.id === action.comment.id).body = action.comment.body;
            return newState;

        case ADD_COMMENTS:
            newState.comments = newState.comments.filter(comment => comment.parentId !== action.postId);
            newState.comments = newState.comments.concat(action.comments);
            return newState;

        case SET_COMMENTS:
            return {
                comments: action.comments
            };

        default :
            return state
    }
}<commit_msg>fix: Update timestamp of comment<commit_after>import {
    ADD_COMMENTS,
    ADD_VOTE_COMMENT,
    CREATE_COMMENT,
    REMOVE_COMMENT,
    REMOVE_VOTE_COMMENT,
    SET_COMMENTS,
    UPDATE_COMMENT
} from "../actions/Comment";
import {REMOVE_POST} from "../actions/Post";

const initialStateComments = {
    comments: []
};

export default function comment(state = initialStateComments, action) {
    let newState = JSON.parse(JSON.stringify(state));
    switch (action.type) {
        case REMOVE_COMMENT :
            newState.comments.find(comment => comment.id === action.id).deleted = true;
            return newState;

        case REMOVE_POST :
            newState.comments.filter(comment => comment.parentId === action.id).map(comment => {
                comment.parentDeleted = true;
            });
            return newState;

        case CREATE_COMMENT:
            newState.comments.push(action.comment);
            return newState;

        case ADD_VOTE_COMMENT:
            newState.comments.find(comment => comment.id === action.id).voteScore += 1;
            return newState;

        case REMOVE_VOTE_COMMENT:
            newState.comments.find(comment => comment.id === action.id).voteScore -= 1;
            return newState;

        case UPDATE_COMMENT:
            let commentUpdated = newState.comments.find(comment => comment.id === action.comment.id);
            commentUpdated.body = action.comment.body;
            commentUpdated.timestamp = action.comment.timestamp;
            return newState;

        case ADD_COMMENTS:
            newState.comments = newState.comments.filter(comment => comment.parentId !== action.postId);
            newState.comments = newState.comments.concat(action.comments);
            return newState;

        case SET_COMMENTS:
            return {
                comments: action.comments
            };

        default :
            return state
    }
}<|endoftext|>


<commit_before>/* eslint-disable */
// export default function detailReducer(state = {}, action) {
/* istanbul ignore next */
window.reducers = window.reducers || {};
window.reducers.detailReducer = (state = {}, action) => {
  switch (action.type) {
    case 'FETCH_DETAIL':
      return {
        ...state,
        dataSelection: null,
        detail: {
          endpoint: action.payload,
          reload: Boolean(state.detail && state.detail.endpoint === action.payload),
          isLoading: true,
          isFullscreen: action.payload && action.payload.includes('catalogus/api')
        },
        layerSelection: {
          ...state.layerSelection,
          isEnabled: false
        },
        map: {
          ...state.map,
          isFullscreen: false,
          isLoading: true
        },
        page: {
          ...state.page,
          name: null,
          type: null
        },
        search: null,
        straatbeeld: null
      };

      case 'SHOW_DETAIL':
        return {
          ...state,
          detail: {
            ...state.detail,
            display: action.payload.display,
            geometry: action.payload.geometry,
            isLoading: false,
            reload: false
          },
          map: {
            ...state.map,
            isLoading: false
          }
        };

    case 'DETAIL_FULLSCREEN':
      return {
        ...state,
        detail: {
          ...state.detail,
          isFullscreen: action.payload
        }
      };

    default:
      return state;
  }
};
<commit_msg>fixed indentation tg-4493<commit_after>/* eslint-disable */
// export default function detailReducer(state = {}, action) {
/* istanbul ignore next */
window.reducers = window.reducers || {};
window.reducers.detailReducer = (state = {}, action) => {
  switch (action.type) {
    case 'FETCH_DETAIL':
      return {
        ...state,
        dataSelection: null,
        detail: {
          endpoint: action.payload,
          reload: Boolean(state.detail && state.detail.endpoint === action.payload),
          isLoading: true,
          isFullscreen: action.payload && action.payload.includes('catalogus/api')
        },
        layerSelection: {
          ...state.layerSelection,
          isEnabled: false
        },
        map: {
          ...state.map,
          isFullscreen: false,
          isLoading: true
        },
        page: {
          ...state.page,
          name: null,
          type: null
        },
        search: null,
        straatbeeld: null
      };

      case 'SHOW_DETAIL':
        return {
          ...state,
          detail: {
            ...state.detail,
            display: action.payload.display,
            geometry: action.payload.geometry,
            isLoading: false,
            reload: false
          },
          map: {
            ...state.map,
            isLoading: false
          }
        };

      case 'DETAIL_FULLSCREEN':
        return {
          ...state,
          detail: {
            ...state.detail,
            isFullscreen: action.payload
          }
        };

    default:
      return state;
  }
};
<|endoftext|>


<commit_before>require("./polyfill");

var sourceMapSupport = require("source-map-support");
var to5              = require("./index");
var _                = require("lodash");

sourceMapSupport.install({
  retrieveSourceMap: function (source) {
    var map = maps[source];
    if (map) {
      return {
        url: null,
        map: map
      };
    } else {
      return null;
    }
  }
});

//

var ignoreRegex = /node_modules/;
var exts        = {};
var maps        = {};
var old         = require.extensions[".js"];

var loader = function (m, filename) {
  if (ignoreRegex && ignoreRegex.test(filename)) {
    return old.apply(this, arguments);
  }

  var result = to5.transformFileSync(filename, {
    sourceMap: true
  });

  maps[filename] = result.map;

  m._compile(result.code, filename);
};

var hookExtensions = function (_exts) {
  _.each(exts, function (old, ext) {
    require.extensions[ext] = old;
  });

  exts = {};

  _.each(_exts, function (ext) {
    exts[ext] = require.extensions[ext];
    require.extensions[ext] = loader;
  });
};

hookExtensions([".es6", ".js"]);

module.exports = function (opts) {
  opts = opts || {};
  if (_.isRegExp(opts)) opts = { ignoreRegex: opts };

  if (opts.ignoreRegex != null) {
    ignoreRegex = opts.ignoreRegex;
  }

  if (opts.extensions) hookExtensions(opts.extensions);
};
<commit_msg>add blacklist syntax feature checking to 6to5/register<commit_after>require("./polyfill");

var sourceMapSupport = require("source-map-support");
var to5              = require("./index");
var _                = require("lodash");

sourceMapSupport.install({
  retrieveSourceMap: function (source) {
    var map = maps[source];
    if (map) {
      return {
        url: null,
        map: map
      };
    } else {
      return null;
    }
  }
});

//

var blacklist  = [];

var blacklistTest = function (transformer, code) {
  try {
    eval(code);
    blacklist.push(transformer);
  } catch (err) {
    if (err.name !== "SyntaxError") throw err;
  }
};

blacklistTest("generators", "function test *() {}");
blacklistTest("letScoping", "let test = 0;");

//

var ignoreRegex = /node_modules/;
var exts        = {};
var maps        = {};
var old         = require.extensions[".js"];

var loader = function (m, filename) {
  if (ignoreRegex && ignoreRegex.test(filename)) {
    return old.apply(this, arguments);
  }

  var result = to5.transformFileSync(filename, {
    blacklist: blacklist,
    sourceMap: true
  });

  maps[filename] = result.map;

  m._compile(result.code, filename);
};

var hookExtensions = function (_exts) {
  _.each(exts, function (old, ext) {
    require.extensions[ext] = old;
  });

  exts = {};

  _.each(_exts, function (ext) {
    exts[ext] = require.extensions[ext];
    require.extensions[ext] = loader;
  });
};

hookExtensions([".es6", ".js"]);

module.exports = function (opts) {
  opts = opts || {};
  if (_.isRegExp(opts)) opts = { ignoreRegex: opts };

  if (opts.ignoreRegex != null) {
    ignoreRegex = opts.ignoreRegex;
  }

  if (opts.extensions) hookExtensions(opts.extensions);
};
<|endoftext|>


<commit_before>/**
 * @module Fire.Runtime
 */

var JS = Fire.JS;
var getClassName = JS.getClassName;

var NodeWrapper = require('./wrappers/node');
var SceneWrapper = require('./wrappers/scene');
var EngineWrapper = require('./wrappers/engine');

//var runtimeSceneWrapper = null;
var runtimeMixinOptions = null;

//This dictionary stores all the registered WrapperTypes, and use MenuPath as key.
//@property menuToWrapper
//@type {object}
var menuToWrapper = {};

/**
 * 通过注册 runtime 的 type 为某个解释器, 使得这份 type 具备序列化, Inspector 中展示的能力
 * @method registerNodeType
 * @param {function} nodeType
 * @param {NodeWrapper} nodeWrapper
 * @param {string} [menuPath] - Optional, the menu path name. Eg. "Rendering/Camera"
 */
function registerNodeType (nodeType, nodeWrapper, menuPath) {
    if (! Fire.isChildClassOf(nodeWrapper, NodeWrapper)) {
        Fire.error('%s must be child class of %s!', getClassName(nodeWrapper), getClassName(NodeWrapper));
        return;
    }
    if (nodeType.prototype.hasOwnProperty('_FB_WrapperType')) {
        Fire.error('%s is already registered!', getClassName(nodeType));
        return;
    }
    //if (Fire.isChildClassOf(nodeWrapper, SceneWrapper)) {
    //    if (!FIRE_TEST && runtimeSceneWrapper) {
    //        Fire.error('The %s can only register once!', getClassName(SceneWrapper));
    //    }
    //    else {
    //        runtimeSceneWrapper = nodeWrapper;
    //    }
    //}

    nodeType.prototype._FB_WrapperType = nodeWrapper;

    // TODO - 菜单应该在 package.json 里注册
    if (menuPath) {
        menuToWrapper[menuPath] = nodeWrapper;
    }
}

/**
 * 通过注册 mixin 的描述来让 engine-framework 懂得如何 mixin 一份 FireClass 到 runtime 的 nodeType 中。
 * @method registerMixin
 * @param {object} mixinOptions
 * @param {function} mixinOptions.mixin - mixin method
 */
function registerMixin (mixinOptions) {
    runtimeMixinOptions = mixinOptions;
}

/**
 * 注册一份引擎实例，注册后的引擎可以通过 Fire.engine 进行访问。
 * @method registerEngine
 * @param {EngineWrapper} engineInstance
 */
function registerEngine (engineInstance) {
    if (FIRE_EDITOR) {
        if (!(engineInstance instanceof EngineWrapper)) {
            Fire.error('The engine to register must be child class of %s', getClassName(EngineWrapper));
            return;
        }
        if (Fire.engine) {
            Fire.error('The engine is already registered!');
            return;
        }
    }
    Fire.engine = engineInstance;
    JS.obsolete(Fire, 'Fire.Engine', 'engine');
}

/**
 * @module Fire
 */

/**
 * @property {EngineWrapper} Engine - The instance of current registered engine.
 */

/**
 * 返回已注册的 NodeWrapper 类型，如果 nodeOrNodeType 是实例，则返回自身类型对应的 NodeWrapper 或继承树上方的最近一个注册的 NodeWrapper。
 * 如果 nodeOrNodeType 是构造函数，则只返回自身对应的 NodeWrapper。
 * @method getWrapperType
 * @param {object|function} nodeOrNodeType
 * @return {Fire.Runtime.NodeWrapper|undefined}
 */
function getWrapperType (nodeOrNodeType) {
    if (typeof nodeOrNodeType !== 'function') {
        return nodeOrNodeType._FB_WrapperType;
    }
    else {
        return nodeOrNodeType.prototype._FB_WrapperType;
    }
}

//// 值得注意的是, 不同的 runtime 中, 他们 runtimeType 的 mixin 的关键字将会有些许变动, 比如: 有些 runtime 的 node 不支持 event,
//// 那么 listeners 关键字: 在这些 runtime 中将会失效, 我们可以 warning user.
//Fire.registerMixin = require('./mixin');
//

module.exports = {
    registerNodeType: registerNodeType,
    getWrapperType: getWrapperType,
    //getRegisteredSceneWrapper: function () {
    //    return runtimeSceneWrapper;
    //},

    registerToCoreLevel: function () {
        if (FIRE_EDITOR) {
            var nodeCreateMenus = [];
            for (var key in menuToWrapper) {
                nodeCreateMenus.push({
                    menuPath: key,
                    id: JS._getClassId(menuToWrapper[key])
                });
            }
            Editor.remote.nodeCreateMenus = nodeCreateMenus;
        }
    },

    registerMixin: registerMixin,
    /**
     * get current registered mixin options
     * @method getMixinOptions
     * @return {object}
     */
    getMixinOptions: function () {
        return runtimeMixinOptions;
    },

    registerEngine: registerEngine
};
<commit_msg>should not use Editor.remote to set value (fireball-x/fireball#88)<commit_after>/**
 * @module Fire.Runtime
 */

var JS = Fire.JS;
var getClassName = JS.getClassName;

var NodeWrapper = require('./wrappers/node');
var SceneWrapper = require('./wrappers/scene');
var EngineWrapper = require('./wrappers/engine');

//var runtimeSceneWrapper = null;
var runtimeMixinOptions = null;

//This dictionary stores all the registered WrapperTypes, and use MenuPath as key.
//@property menuToWrapper
//@type {object}
var menuToWrapper = {};

/**
 * 通过注册 runtime 的 type 为某个解释器, 使得这份 type 具备序列化, Inspector 中展示的能力
 * @method registerNodeType
 * @param {function} nodeType
 * @param {NodeWrapper} nodeWrapper
 * @param {string} [menuPath] - Optional, the menu path name. Eg. "Rendering/Camera"
 */
function registerNodeType (nodeType, nodeWrapper, menuPath) {
    if (! Fire.isChildClassOf(nodeWrapper, NodeWrapper)) {
        Fire.error('%s must be child class of %s!', getClassName(nodeWrapper), getClassName(NodeWrapper));
        return;
    }
    if (nodeType.prototype.hasOwnProperty('_FB_WrapperType')) {
        Fire.error('%s is already registered!', getClassName(nodeType));
        return;
    }
    //if (Fire.isChildClassOf(nodeWrapper, SceneWrapper)) {
    //    if (!FIRE_TEST && runtimeSceneWrapper) {
    //        Fire.error('The %s can only register once!', getClassName(SceneWrapper));
    //    }
    //    else {
    //        runtimeSceneWrapper = nodeWrapper;
    //    }
    //}

    nodeType.prototype._FB_WrapperType = nodeWrapper;

    // TODO - 菜单应该在 package.json 里注册
    if (menuPath) {
        menuToWrapper[menuPath] = nodeWrapper;
    }
}

/**
 * 通过注册 mixin 的描述来让 engine-framework 懂得如何 mixin 一份 FireClass 到 runtime 的 nodeType 中。
 * @method registerMixin
 * @param {object} mixinOptions
 * @param {function} mixinOptions.mixin - mixin method
 */
function registerMixin (mixinOptions) {
    runtimeMixinOptions = mixinOptions;
}

/**
 * 注册一份引擎实例，注册后的引擎可以通过 Fire.engine 进行访问。
 * @method registerEngine
 * @param {EngineWrapper} engineInstance
 */
function registerEngine (engineInstance) {
    if (FIRE_EDITOR) {
        if (!(engineInstance instanceof EngineWrapper)) {
            Fire.error('The engine to register must be child class of %s', getClassName(EngineWrapper));
            return;
        }
        if (Fire.engine) {
            Fire.error('The engine is already registered!');
            return;
        }
    }
    Fire.engine = engineInstance;
    JS.obsolete(Fire, 'Fire.Engine', 'engine');
}

/**
 * @module Fire
 */

/**
 * @property {EngineWrapper} Engine - The instance of current registered engine.
 */

/**
 * 返回已注册的 NodeWrapper 类型，如果 nodeOrNodeType 是实例，则返回自身类型对应的 NodeWrapper 或继承树上方的最近一个注册的 NodeWrapper。
 * 如果 nodeOrNodeType 是构造函数，则只返回自身对应的 NodeWrapper。
 * @method getWrapperType
 * @param {object|function} nodeOrNodeType
 * @return {Fire.Runtime.NodeWrapper|undefined}
 */
function getWrapperType (nodeOrNodeType) {
    if (typeof nodeOrNodeType !== 'function') {
        return nodeOrNodeType._FB_WrapperType;
    }
    else {
        return nodeOrNodeType.prototype._FB_WrapperType;
    }
}

//// 值得注意的是, 不同的 runtime 中, 他们 runtimeType 的 mixin 的关键字将会有些许变动, 比如: 有些 runtime 的 node 不支持 event,
//// 那么 listeners 关键字: 在这些 runtime 中将会失效, 我们可以 warning user.
//Fire.registerMixin = require('./mixin');
//

module.exports = {
    registerNodeType: registerNodeType,
    getWrapperType: getWrapperType,
    //getRegisteredSceneWrapper: function () {
    //    return runtimeSceneWrapper;
    //},

    registerToCoreLevel: function () {
        if (FIRE_EDITOR) {
            // register create node menu
            var nodeCreateMenus = [];
            for (var key in menuToWrapper) {
                nodeCreateMenus.push({
                    menuPath: key,
                    id: JS._getClassId(menuToWrapper[key])
                });
            }
            Editor.sendToCore('app:register-menu', 'create-node', nodeCreateMenus);
        }
    },

    registerMixin: registerMixin,
    /**
     * get current registered mixin options
     * @method getMixinOptions
     * @return {object}
     */
    getMixinOptions: function () {
        return runtimeMixinOptions;
    },

    registerEngine: registerEngine
};
<|endoftext|>


<commit_before>const GoogleSpreadsheet = require('google-spreadsheet');
const { lift } = require('when/node');
const chrono = require('chrono-node');
const moment = require('moment');
const mitt = require('mitt');

const logger = require('./logger');

const SHEET_KEY = process.env.SHEET_KEY;
const CREDS = {
  client_email: process.env.GOOGLE_CREDS_EMAIL,
  private_key: (process.env.GOOGLE_CREDS_KEY || '').replace(/\\n/g, `\n`).replace(/^[\"\']+|[\"\']+$/g, '')
};
const SYNC_DELAY = parseInt(process.env.SYNC_DELAY, 10) || 60000;
const DAY_ROWS = 9;

class ScheduleManager {
  constructor() {
    this.emitter = mitt();
    this.on = this.emitter.on;
    this.log = logger.bind(logger, 'SM');
    this.logError = (error) => error && this.log('error', error);

    this.lessons = [];
    this.lastUpdateTime = null;

    this.recurrenceSync = this.recurrenceSync.bind(this);
    this.sync = this.sync.bind(this);
    this.loadSheet = this.loadSheet.bind(this);
    this.loadUserCells = this.loadUserCells.bind(this);
    this.addUserToLesson = this.addUserToLesson.bind(this);
    this.removeUserFromLesson = this.removeUserFromLesson.bind(this);

    const doc = new GoogleSpreadsheet(SHEET_KEY);
    this.docInit = lift(doc.useServiceAccountAuth)(CREDS)
      .then(() => doc);
  }

  recurrenceSync() {
    return this.sync()
      .then(() => setTimeout(this.recurrenceSync, SYNC_DELAY));
  }

  sync() {
    let updateTime;
    return this.docInit
      .then(doc => lift(doc.getInfo)())
      .then(data => {
        updateTime = chrono.strict.parseDate(data.updated);
        if (moment(this.lastUpdateTime).isSame(updateTime)) {
          return Promise.reject();
        }
        return data.worksheets;
      })
      .then(sheets => Promise.all(sheets.map(this.loadSheet)))
      .then(data => data.filter(x => x))
      .then(data => data.reduce((list, items) => list.concat(items), []))
      .then(data => data.filter(x => x))
      .then(lessons => lessons.sort((a, b) => a.start - b.start))
      .then(lessons => {
        this.lessons = lessons;
        this.lastUpdateTime = updateTime;

        this.log('sync:done');
        this.emitter.emit('sync_done', lessons);
      }, (error) => {
        this.logError(error);
      });
  }

  loadSheet(sheet) {
    if (!sheet.title.endsWith('week of the year')) {
      return undefined;
    }
    const url = ScheduleManager.getSheetUrl(sheet);
    const sheetId = sheet.id;

    const byDay = (num) => (cell) => cell.row >= (num - 1) * DAY_ROWS + 1 && cell.row <= num * DAY_ROWS;
    const byCol = (col) => (cell) => cell.col === col;
    const byRow = (row) => (cell) => cell.row % DAY_ROWS === row;
    const byRowGE = (row) => (cell) => (cell.row - 1) % DAY_ROWS >= row - 1;
    const byPos = (col, row) => (cell) => cell.col === col && cell.row % DAY_ROWS === row;
    const getValue = (cell) => cell ? cell.value : null;

    const readLesson = ({ url, sheetId, parseTime, trainer }) => (cells) => {
      const time = parseTime(getValue(cells.find(byRow(2))));
      return {
        start: time && time.start.date() || null,
        end: time && time.end && time.end.date() || null,
        topic: getValue(cells.find(byRow(3))),
        users: cells.filter(byRowGE(4)).map(cell => cell.value),
        trainer,
        cellCol: cells[0].col,
        cellRow: Math.floor((cells[0].row - 1) / DAY_ROWS) * DAY_ROWS + 1,
        sheetId,
        url
      };
    };

    const readDay = ({ url, sheetId }) => (cells) => {
      const trainer = getValue(cells.find(byPos(1, 1)));
      const date = chrono.parseDate(getValue(cells.find(byPos(2, 1))));
      const parseTime = (text) => chrono.parse(text.replace(/\./g, ':'), date)[0];

      if (!trainer) {
        return undefined;
      }

      return [1, 2, 3]
        .map(num => cells.filter(byCol(num)))
        .map(readLesson({ url, sheetId, parseTime, trainer }));
    };

    const readDays = ({ url, sheetId }) => (cells) => {
      return [1, 2]
        .map(num => cells.filter(byDay(num)))
        .map(readDay({ url, sheetId }))
        .reduce((list, items) => list.concat(items), []);
    };

    return lift(sheet.getCells)({ 'return-empty': false })
      .then(readDays({ url, sheetId }));
  }

  loadUserCells(lesson, withEmpty = true) {
    return this.docInit
      .then(doc => lift(doc.getCells)(lesson.sheetId, {
        'return-empty': withEmpty,
        'min-row': lesson.cellRow + 3,
        'max-row': lesson.cellRow + DAY_ROWS - 1,
        'min-col': lesson.cellCol,
        'max-col': lesson.cellCol
      }));
  }

  updateLessonUsers(lesson) {
    return this.loadUserCells(lesson, false)
      .then(cells => lesson.users = cells.map(cell => cell.value));
  }

  addUserToLesson(name, lesson) {
    return this.loadUserCells(lesson)
      .then(cells => cells.find(cell => !cell.value))
      .then(cell => cell && lift(cell.setValue)(name) || Promise.reject('No empty cells'))
      .then(() => this.log('user:add', name, lesson.start))
      .then(() => this.updateLessonUsers(lesson))
      .catch(err => (this.logError(err), Promise.reject(err)));
  }

  removeUserFromLesson(name, lesson){
    const shiftCells = (cell, index, list) => lift(cell.setValue)((list[index + 1] || { value: '' }).value);
    return this.loadUserCells(lesson)
      .then(cells => {
        const index = cells.findIndex(cell => cell.value === name);
        if (index < 0) {
          return Promise.reject('Name not found');
        }
        const lastIndex = cells.findIndex(cell => !cell.value) + 1 || DAY_ROWS;
        return Promise.all(cells.slice(index, lastIndex).map(shiftCells));
      })
      .then(() => this.log('user:remove', name, lesson.start))
      .then(() => this.updateLessonUsers(lesson))
      .catch(err => (this.logError(err), Promise.reject(err)));
  }

  findLessonByDate(date) {
    const m = moment(date);
    return this.lessons.find(lesson => m.isSame(lesson.start));
  }

  findLessonsByDateRange([date1, date2]) {
    const m1 = moment(date1);
    const m2 = moment(date2);
    return this.lessons
      .filter(lesson => m1.isSameOrBefore(lesson.start) && m2.isSameOrAfter(lesson.start));
  }

  static getSheetUrl(sheet) {
    return sheet._links['http://schemas.google.com/visualization/2008#visualizationApi'].replace('gviz/tq?','#');
  }
}

module.exports = ScheduleManager;
<commit_msg>enroll limit up to 6<commit_after>const GoogleSpreadsheet = require('google-spreadsheet');
const { lift } = require('when/node');
const chrono = require('chrono-node');
const moment = require('moment');
const mitt = require('mitt');

const logger = require('./logger');

const SHEET_KEY = process.env.SHEET_KEY;
const CREDS = {
  client_email: process.env.GOOGLE_CREDS_EMAIL,
  private_key: (process.env.GOOGLE_CREDS_KEY || '').replace(/\\n/g, `\n`).replace(/^[\"\']+|[\"\']+$/g, '')
};
const SYNC_DELAY = parseInt(process.env.SYNC_DELAY, 10) || 60000;
const DAY_ROWS = 9;

class ScheduleManager {
  constructor() {
    this.emitter = mitt();
    this.on = this.emitter.on;
    this.log = logger.bind(logger, 'SM');
    this.logError = (error) => error && this.log('error', error);

    this.lessons = [];
    this.lastUpdateTime = null;

    this.recurrenceSync = this.recurrenceSync.bind(this);
    this.sync = this.sync.bind(this);
    this.loadSheet = this.loadSheet.bind(this);
    this.loadUserCells = this.loadUserCells.bind(this);
    this.addUserToLesson = this.addUserToLesson.bind(this);
    this.removeUserFromLesson = this.removeUserFromLesson.bind(this);

    const doc = new GoogleSpreadsheet(SHEET_KEY);
    this.docInit = lift(doc.useServiceAccountAuth)(CREDS)
      .then(() => doc);
  }

  recurrenceSync() {
    return this.sync()
      .then(() => setTimeout(this.recurrenceSync, SYNC_DELAY));
  }

  sync() {
    let updateTime;
    return this.docInit
      .then(doc => lift(doc.getInfo)())
      .then(data => {
        updateTime = chrono.strict.parseDate(data.updated);
        if (moment(this.lastUpdateTime).isSame(updateTime)) {
          return Promise.reject();
        }
        return data.worksheets;
      })
      .then(sheets => Promise.all(sheets.map(this.loadSheet)))
      .then(data => data.filter(x => x))
      .then(data => data.reduce((list, items) => list.concat(items), []))
      .then(data => data.filter(x => x))
      .then(lessons => lessons.sort((a, b) => a.start - b.start))
      .then(lessons => {
        this.lessons = lessons;
        this.lastUpdateTime = updateTime;

        this.log('sync:done');
        this.emitter.emit('sync_done', lessons);
      }, (error) => {
        this.logError(error);
      });
  }

  loadSheet(sheet) {
    if (!sheet.title.endsWith('week of the year')) {
      return undefined;
    }
    const url = ScheduleManager.getSheetUrl(sheet);
    const sheetId = sheet.id;

    const byDay = (num) => (cell) => cell.row >= (num - 1) * DAY_ROWS + 1 && cell.row <= num * DAY_ROWS;
    const byCol = (col) => (cell) => cell.col === col;
    const byRow = (row) => (cell) => cell.row % DAY_ROWS === row;
    const byRowGE = (row) => (cell) => (cell.row - 1) % DAY_ROWS >= row - 1;
    const byPos = (col, row) => (cell) => cell.col === col && cell.row % DAY_ROWS === row;
    const getValue = (cell) => cell ? cell.value : null;

    const readLesson = ({ url, sheetId, parseTime, trainer }) => (cells) => {
      const time = parseTime(getValue(cells.find(byRow(2))));
      return {
        start: time && time.start.date() || null,
        end: time && time.end && time.end.date() || null,
        topic: getValue(cells.find(byRow(3))),
        users: cells.filter(byRowGE(4)).map(cell => cell.value),
        trainer,
        cellCol: cells[0].col,
        cellRow: Math.floor((cells[0].row - 1) / DAY_ROWS) * DAY_ROWS + 1,
        sheetId,
        url
      };
    };

    const readDay = ({ url, sheetId }) => (cells) => {
      const trainer = getValue(cells.find(byPos(1, 1)));
      const date = chrono.parseDate(getValue(cells.find(byPos(2, 1))));
      const parseTime = (text) => chrono.parse(text.replace(/\./g, ':'), date)[0];

      if (!trainer) {
        return undefined;
      }

      return [1, 2, 3]
        .map(num => cells.filter(byCol(num)))
        .map(readLesson({ url, sheetId, parseTime, trainer }));
    };

    const readDays = ({ url, sheetId }) => (cells) => {
      return [1, 2]
        .map(num => cells.filter(byDay(num)))
        .map(readDay({ url, sheetId }))
        .reduce((list, items) => list.concat(items), []);
    };

    return lift(sheet.getCells)({ 'return-empty': false })
      .then(readDays({ url, sheetId }));
  }

  loadUserCells(lesson, withEmpty = true) {
    return this.docInit
      .then(doc => lift(doc.getCells)(lesson.sheetId, {
        'return-empty': withEmpty,
        'min-row': lesson.cellRow + 3,
        'max-row': lesson.cellRow + DAY_ROWS,// - 1,
        'min-col': lesson.cellCol,
        'max-col': lesson.cellCol
      }));
  }

  updateLessonUsers(lesson) {
    return this.loadUserCells(lesson, false)
      .then(cells => lesson.users = cells.map(cell => cell.value));
  }

  addUserToLesson(name, lesson) {
    return this.loadUserCells(lesson)
      .then(cells => cells.find(cell => !cell.value))
      .then(cell => cell && lift(cell.setValue)(name) || Promise.reject('No empty cells'))
      .then(() => this.log('user:add', name, lesson.start))
      .then(() => this.updateLessonUsers(lesson))
      .catch(err => (this.logError(err), Promise.reject(err)));
  }

  removeUserFromLesson(name, lesson){
    const shiftCells = (cell, index, list) => lift(cell.setValue)((list[index + 1] || { value: '' }).value);
    return this.loadUserCells(lesson)
      .then(cells => {
        const index = cells.findIndex(cell => cell.value === name);
        if (index < 0) {
          return Promise.reject('Name not found');
        }
        const lastIndex = cells.findIndex(cell => !cell.value) + 1 || DAY_ROWS;
        return Promise.all(cells.slice(index, lastIndex).map(shiftCells));
      })
      .then(() => this.log('user:remove', name, lesson.start))
      .then(() => this.updateLessonUsers(lesson))
      .catch(err => (this.logError(err), Promise.reject(err)));
  }

  findLessonByDate(date) {
    const m = moment(date);
    return this.lessons.find(lesson => m.isSame(lesson.start));
  }

  findLessonsByDateRange([date1, date2]) {
    const m1 = moment(date1);
    const m2 = moment(date2);
    return this.lessons
      .filter(lesson => m1.isSameOrBefore(lesson.start) && m2.isSameOrAfter(lesson.start));
  }

  static getSheetUrl(sheet) {
    return sheet._links['http://schemas.google.com/visualization/2008#visualizationApi'].replace('gviz/tq?','#');
  }
}

module.exports = ScheduleManager;
<|endoftext|>