<commit_before>/*jshint indent:2, curly:true eqeqeq:true, immed:true, latedef:true,
newcap:true, noarg:true, regexp:true, undef:true, trailing:true
white:true*/
/*global enyo:true, XT:true, XM:true, XV:true, _:true */

(function () {

  // ..........................................................
  // ACCOUNT TYPE
  //

  var TEXT = 0;
  var LIST = 1;
  var DATE = 2;

  /**

    @class
    @name XV.CharacteristicPicker
    @extends XV.PickerWidget
   */
  enyo.kind(/** @lends XV.CharacteristicPicker# */{
    name: "XV.CharacteristicPicker",
    kind: "XV.PickerWidget",
    classes: "xv-characteristic-picker",
    collection: "XM.characteristics",
    noneText: "_delete".loc(),
    noneClasses: "xv-negative",
    orderBy: [
      {attribute: 'order'},
      {attribute: 'name'}
    ]
  });

  /**

    @class
    @name XV.OptionsPicker
  */
  enyo.kind(/** @lends XV.OptionsPicker# */{
    name: "XV.OptionsPicker",
    published: {
      attr: null,
      collection: null,
      value: null
    },
    events: {
      onValueChange: ""
    },
    handlers: {
      onSelect: "itemSelected"
    },
    components: [
      {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
        components: [
        {kind: "onyx.PickerDecorator",
          components: [
          {classes: "xv-picker"},
          {name: "picker", kind: "onyx.Picker"}
        ]}
      ]}
    ],
    itemSelected: function (inSender, inEvent) {
      var value = this.$.picker.getSelected().content;
      this.setValue(value);
      return true;
    },
    setCollection: function (collection) {
      var model,
        value,
        i,
        c;
      this.collection = collection;
      collection.comparator = this.sort;
      collection.sort();
      this.$.picker.destroyClientControls();
      for (i = 0; i < collection.length; i++) {
        model = collection.at(i);
        value = model.get('value');
        c = this.$.picker.createComponent({ content: value });
        // Autormatically select first
        if (i === 0) { this.$.picker.setSelected(c); }
      }
      this.render();
    },
    setValue: function (value, options) {
      options = options || {};
      var oldValue = this.getValue(),
        inEvent,
        components = this.$.picker.getComponents(),
        component = _.find(components, function (c) {
          if (c.kind === "onyx.MenuItem") {
            return c.content === value;
          }
        });
      if (!component) { value = null; }
      this.$.picker.setSelected(component);

      if (value !== oldValue) {
        this.value = value;
        if (!options.silent) {
          inEvent = { originator: this, value: value };
          this.doValueChange(inEvent);
        }
      }
      return value;
    },
    sort: function (a, b) {
      var aord = a.get('order'),
        bord = b.get('order'),
        aname,
        bname;
      if (aord === bord) {
        aname = a.get('value');
        bname = b.get('value');
        return aname < bname ? -1 : 1;
      }
      return aord < bord ? -1 : 1;
    }
  });

  /**

    @class
    @name XV.CharacteristicItem
   */
  enyo.kind(/** @lends XV.CharacteristicItem# */{
    name: "XV.CharacteristicItem",
    kind: "FittableColumns",
    classes: "xv-characteristic-item",
    published: {
      value: null
    },
    handlers: {
      onValueChange: "controlValueChanged"
    },
    components: [
      {kind: "XV.CharacteristicPicker", attr: "characteristic",
        showLabel: false},
      {kind: "XV.InputWidget", attr: "value", showLabel: false},
      {kind: "XV.DateWidget", attr: "value", showLabel: false,
        showing: false},
      {kind: "XV.OptionsPicker", attr: "value", showLabel: false,
        showing: false}
    ],
    controlValueChanged: function (inSender, inEvent) {
      var attr = inSender.getAttr(),
        value = inSender.getValue(),
        attributes = {},
        model = this.getValue(),
        characteristic,
        empty;
      attributes[attr] = _.isDate(value) ? value.toJSON() : value;
      model.set(attributes);
      if (attr === 'characteristic') {
        if (value) {
          characteristic = model.get('characteristic');
          empty = characteristic && characteristic.get('characteristicType') === DATE ?
            null : "";
          model.set('value', empty);
          this.valueChanged();
        } else {
          model.destroy();
        }
      }
      return true;
    },
    getCharacteristicPicker: function () {
      return this.$.characteristicPicker;
    },
    valueChanged: function () {
      var model = this.getValue(),
        characteristic = model.get('characteristic'),
        type = characteristic ?
          characteristic.get('characteristicType') : TEXT,
        value = model.get('value'),
        valueWidget,
        options;
      switch (type)
      {
      case TEXT:
        this.$.dateWidget.hide();
        this.$.optionsPicker.hide();
        this.$.inputWidget.show();
        valueWidget = this.$.inputWidget;
        break;
      case DATE:
        this.$.optionsPicker.hide();
        this.$.inputWidget.hide();
        this.$.dateWidget.show();
        valueWidget = this.$.dateWidget;
        break;
      case LIST:
        this.$.dateWidget.hide();
        this.$.inputWidget.hide();
        this.$.optionsPicker.show();
        valueWidget = this.$.optionsPicker;
        options = characteristic.get('options');
        this.$.optionsPicker.setCollection(options);
      }
      this.$.characteristicPicker.setValue(characteristic, {silent: true});
      valueWidget.setValue(value, {silent: true});
    }
  });

  /**

    @class
    @name XV.CharacteristicsWidget
   */
  enyo.kind(/** @lends XV.CharacteristicsWidget# */{
    name: "XV.CharacteristicsWidget",
    classes: "xv-characteristics-widget",
    published: {
      attr: null,
      model: null,
      value: null,
      which: null
    },
    components: [
      {kind: "onyx.GroupboxHeader", content: "_characteristics".loc()},
      {kind: "Repeater", count: 0, onSetupItem: "setupItem", components: [
        {kind: "XV.CharacteristicItem"}
      ]},
      {kind: "FittableColumns", classes: "xv-characteristic-buttons",
        components: [
        {kind: "onyx.Button", name: "newButton",
          classes: "xv-characteristic-button", onclick: "newItem",
          content: "_new".loc()}
      ]}
    ],
    create: function () {
      this.inherited(arguments);
      // Hide this if there aren't any characteristics set up
      var attr = this.getWhich(),
        models = _.filter(XM.characteristics.models, function (char) {
          return char.get(attr);
        });
      if (!models.length) { this.hide(); }
    },
    lengthChanged: function () {
      this.$.repeater.setCount(this.readyModels().length);
    },
    newItem: function () {
      var Klass = XT.getObjectByName(this.getModel()),
        model = new Klass(null, { isNew: true });
      this.value.add(model);
    },
    readyModels: function () {
      return _.filter(this.value.models, function (model) {
        var status = model.getStatus(),
          K = XM.Model;
        // Avoiding bitwise because performance matters here
        return (status === K.READY_CLEAN ||
                status === K.READY_DIRTY ||
                status === K.READY_NEW);
      });
    },
    setupItem: function (inSender, inEvent) {
      var item = inEvent.item.$.characteristicItem,
        model = this.readyModels()[inEvent.index],
        which = this.getWhich(),
        picker = item.getCharacteristicPicker(),
        filter = function (models) {
          return _.filter(models, function (m) {
            return m.get(which);
          });
        };
      item.setValue(model);
      picker.filter = filter;
      picker.buildList();
      if (!model.get('characteristic')) {
        picker.select(1);
      }
      return true;
    },
    sort: function (a, b) {
      var achar = a.get('characteristic'),
        bchar = b.get('characteristic'),
        aord = achar ? achar.get('order') : null,
        bord = bchar ? bchar.get('order') : null,
        aname,
        bname;
      if (aord === bord) {
        aname = achar ? achar.get('name') : null;
        bname = bchar ? bchar.get('name') : null;
        return aname < bname ? -1 : 1;
      }
      return aord < bord ? -1 : 1;
    },
    setValue: function (value) {
      if (this.value) {
        this.value.off('add', this.lengthChanged, this);
        this.value.off('statusChange', this.lengthChanged, this);
      }
      this.value = value;
      this.value.on('add', this.lengthChanged, this);
      this.value.on('statusChange', this.lengthChanged, this);
      this.value.comparator = this.sort;
      this.value.sort();
      this.lengthChanged();
    }
  });

}());
<commit_msg>issue #18781: Fix some problems with characteristic behavior.<commit_after>/*jshint indent:2, curly:true eqeqeq:true, immed:true, latedef:true,
newcap:true, noarg:true, regexp:true, undef:true, trailing:true
white:true*/
/*global enyo:true, XT:true, XM:true, XV:true, _:true */

(function () {

  // ..........................................................
  // ACCOUNT TYPE
  //

  var TEXT = 0;
  var LIST = 1;
  var DATE = 2;

  /**

    @class
    @name XV.CharacteristicPicker
    @extends XV.PickerWidget
   */
  enyo.kind(/** @lends XV.CharacteristicPicker# */{
    name: "XV.CharacteristicPicker",
    kind: "XV.PickerWidget",
    classes: "xv-characteristic-picker",
    collection: "XM.characteristics",
    noneText: "_delete".loc(),
    noneClasses: "xv-negative",
    orderBy: [
      {attribute: 'order'},
      {attribute: 'name'}
    ]
  });

  /**

    @class
    @name XV.OptionsPicker
  */
  enyo.kind(/** @lends XV.OptionsPicker# */{
    name: "XV.OptionsPicker",
    published: {
      attr: null,
      collection: null,
      value: null
    },
    events: {
      onValueChange: ""
    },
    handlers: {
      onSelect: "itemSelected"
    },
    components: [
      {kind: "onyx.InputDecorator", classes: "xv-input-decorator",
        components: [
        {kind: "onyx.PickerDecorator",
          components: [
          {classes: "xv-picker"},
          {name: "picker", kind: "onyx.Picker"}
        ]}
      ]}
    ],
    itemSelected: function (inSender, inEvent) {
      var value = this.$.picker.getSelected().content;
      this.setValue(value);
      return true;
    },
    setCollection: function (collection) {
      var model,
        value,
        i,
        c;
      this.collection = collection;
      collection.comparator = this.sort;
      collection.sort();
      this.$.picker.destroyClientControls();
      for (i = 0; i < collection.length; i++) {
        model = collection.at(i);
        value = model.get('value');
        c = this.$.picker.createComponent({ content: value });
        // Autormatically select first
        if (i === 0) { this.$.picker.setSelected(c); }
      }
      this.render();
    },
    setValue: function (value, options) {
      options = options || {};
      var oldValue = this.getValue(),
        inEvent,
        components = this.$.picker.getComponents(),
        component = _.find(components, function (c) {
          if (c.kind === "onyx.MenuItem") {
            return c.content === value;
          }
        });
      if (!component) { value = null; }
      this.$.picker.setSelected(component);

      if (value !== oldValue) {
        this.value = value;
        if (!options.silent) {
          inEvent = { originator: this, value: value };
          this.doValueChange(inEvent);
        }
      }
      return value;
    },
    sort: function (a, b) {
      var aord = a.get('order'),
        bord = b.get('order'),
        aname,
        bname;
      if (aord === bord) {
        aname = a.get('value');
        bname = b.get('value');
        return aname < bname ? -1 : 1;
      }
      return aord < bord ? -1 : 1;
    }
  });

  /**

    @class
    @name XV.CharacteristicItem
   */
  enyo.kind(/** @lends XV.CharacteristicItem# */{
    name: "XV.CharacteristicItem",
    kind: "FittableColumns",
    classes: "xv-characteristic-item",
    published: {
      value: null
    },
    handlers: {
      onValueChange: "controlValueChanged"
    },
    components: [
      {kind: "XV.CharacteristicPicker", attr: "characteristic",
        showLabel: false},
      {kind: "XV.InputWidget", attr: "value", showLabel: false},
      {kind: "XV.DateWidget", attr: "value", showLabel: false,
        showing: false},
      {kind: "XV.OptionsPicker", attr: "value", showLabel: false,
        showing: false}
    ],
    controlValueChanged: function (inSender, inEvent) {
      var attr = inSender.getAttr(),
        value = inSender.getValue(),
        attributes = {},
        model = this.getValue(),
        characteristic,
        defaultVal;
      attributes[attr] = _.isDate(value) ? value.toJSON() : value;
      model.set(attributes);
      if (attr === 'characteristic') {
        if (value) {
          characteristic = model.get('characteristic');
          switch (characteristic.get('characteristicType'))
          {
          case DATE:
            defaultVal = null;
            break;
          case TEXT:
            defaultVal = "";
            break;
          case LIST:
            defaultVal = characteristic.get('options').models[0].get('value');
          }
          model.set('value', defaultVal);
          this.valueChanged();
        } else {
          model.destroy();
        }
      }
      return true;
    },
    getCharacteristicPicker: function () {
      return this.$.characteristicPicker;
    },
    valueChanged: function () {
      var model = this.getValue(),
        characteristic = model.get('characteristic'),
        type = characteristic ?
          characteristic.get('characteristicType') : TEXT,
        value = model.get('value'),
        valueWidget,
        options;
      switch (type)
      {
      case TEXT:
        this.$.dateWidget.hide();
        this.$.optionsPicker.hide();
        this.$.inputWidget.show();
        valueWidget = this.$.inputWidget;
        break;
      case DATE:
        this.$.optionsPicker.hide();
        this.$.inputWidget.hide();
        this.$.dateWidget.show();
        valueWidget = this.$.dateWidget;
        break;
      case LIST:
        this.$.dateWidget.hide();
        this.$.inputWidget.hide();
        this.$.optionsPicker.show();
        valueWidget = this.$.optionsPicker;
        options = characteristic.get('options');
        this.$.optionsPicker.setCollection(options);
      }
      this.$.characteristicPicker.setValue(characteristic, {silent: true});
      valueWidget.setValue(value, {silent: true});
    }
  });

  /**

    @class
    @name XV.CharacteristicsWidget
   */
  enyo.kind(/** @lends XV.CharacteristicsWidget# */{
    name: "XV.CharacteristicsWidget",
    classes: "xv-characteristics-widget",
    published: {
      attr: null,
      model: null,
      value: null,
      which: null
    },
    components: [
      {kind: "onyx.GroupboxHeader", content: "_characteristics".loc()},
      {kind: "Repeater", count: 0, onSetupItem: "setupItem", components: [
        {kind: "XV.CharacteristicItem"}
      ]},
      {kind: "FittableColumns", classes: "xv-characteristic-buttons",
        components: [
        {kind: "onyx.Button", name: "newButton",
          classes: "xv-characteristic-button", onclick: "newItem",
          content: "_new".loc()}
      ]}
    ],
    create: function () {
      this.inherited(arguments);
      // Hide this if there aren't any characteristics set up
      var attr = this.getWhich(),
        models = _.filter(XM.characteristics.models, function (char) {
          return char.get(attr);
        });
      if (!models.length) { this.hide(); }
    },
    lengthChanged: function () {
      this.$.repeater.setCount(this.readyModels().length);
    },
    newItem: function () {
      var Klass = XT.getObjectByName(this.getModel()),
        model = new Klass(null, { isNew: true });
      this.value.add(model);
    },
    readyModels: function () {
      return _.filter(this.value.models, function (model) {
        var status = model.getStatus(),
          K = XM.Model;
        // Avoiding bitwise because performance matters here
        return (status === K.READY_CLEAN ||
                status === K.READY_DIRTY ||
                status === K.READY_NEW);
      });
    },
    setupItem: function (inSender, inEvent) {
      var item = inEvent.item.$.characteristicItem,
        model = this.readyModels()[inEvent.index],
        which = this.getWhich(),
        picker = item.getCharacteristicPicker(),
        filter = function (models) {
          return _.filter(models, function (m) {
            return m.get(which);
          });
        };
      item.setValue(model);
      picker.filter = filter;
      picker.buildList();
      if (!model.get('characteristic')) {
        picker.select(1);
      }
      return true;
    },
    sort: function (a, b) {
      var astatus = a.getStatus(),
        bstatus = b.getStatus(),
        achar = a.get('characteristic'),
        bchar = b.get('characteristic'),
        aord = achar ? achar.get('order') : null,
        bord = bchar ? bchar.get('order') : null,
        aname,
        bname;
      if (astatus === bstatus) {
        if (aord === bord) {
          aname = achar ? achar.get('name') : null;
          bname = bchar ? bchar.get('name') : null;
          return aname < bname ? -1 : 1;
        }
        return aord < bord ? -1 : 1;
      }
      return astatus < bstatus ? -1 : 1;
    },
    setValue: function (value) {
      if (this.value) {
        this.value.off('add', this.lengthChanged, this);
        this.value.off('statusChange', this.lengthChanged, this);
      }
      this.value = value;
      this.value.on('add', this.lengthChanged, this);
      this.value.on('statusChange', this.lengthChanged, this);
      this.value.comparator = this.sort;
      this.value.sort();
      this.lengthChanged();
    }
  });

}());
<|endoftext|>


<commit_before>
/**
 * @package     hubzero-cms
 * @file        plugins/courses/outline/wiki.jquery.js
 * @copyright   Copyright 2005-2011 Purdue University. All rights reserved.
 * @license     http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3
 */

//-----------------------------------------------------------
//  Ensure we have our namespace
//-----------------------------------------------------------
if (!HUB) {
	var HUB = {};
}

//----------------------------------------------------------
//  Courses outline javascript
//----------------------------------------------------------
if (!jq) {
	var jq = $;
}

jQuery(document).ready(function($) {
	Hubzero.initApi();

	$('.fileupload').fileupload({
		dropZone: $('.wiki-files-upload'),
		dataType: 'json',
		singleFileUploads: false,
		add: function ( e, data ) {
			if ($('.wiki-title').val() === '') {
				$('.title-error').html('Please provide a title first').show();
				$('html, body').scrollTop(0);
			} else {
				$('.title-error').hide();
				data.submit();
			}
		},
		done: function ( e, data ) {
			var html = '';
			if ($('.wiki-files-available ul').length) {
				$.each(data.files, function ( i, file ) {
					html += '
';
					html += '' + file.name + '';
					html += '

';
					html += '
';
				});

				$('.wiki-files-list').prepend(html);
			} else {
				html  = '
';
				$.each(data.files, function ( i, file ) {
					html += '
';
					html += '' + file.name + '';
					html += '

';
					html += '
';
				});
				html += '
';
				$('.wiki-files-available').html(html);
			}

			if ($('#asset_id').val() === '') {
				$('#asset_id').val(data.result.assets.assets.asset_id);
			}
		},
		fail: function ( e, data ) {
			$('.title-error').html(data.jqXHR.responseText).show();
			$('html, body').scrollTop(0);
		}
	})
	.hover(function() {
		$(this).siblings('.wiki-files-upload').css('opacity', 1);
	}, function() {
		$(this).siblings('.wiki-files-upload').css('opacity', 0.7);
	});

	$('.edit-form').submit(function ( e ) {
		if (typeof CKEDITOR !== 'undefined') {
			for ( var instance in CKEDITOR.instances ) {
				CKEDITOR.instances[instance].updateElement();
			}
		}
	});

	$('.wiki-files-available').on('click', '.wiki-files-delete', function() {
		var t    = $(this),
			data = t.parents('.edit-form').serializeArray();

		data.push({name : "filename", value : t.prev('.wiki-files-filename').html()});

		$.ajax({
			url        : '/api/courses/asset/deletefile',
			dataType   : "json",
			type       : 'POST',
			cache      : false,
			data       : data,
			success    : function ( data, textStatus, jqXHR ) {
				t.parents('.wiki-file').fadeOut(500, function() {
					$(this).remove();
				});
			}
		});
	});
});<commit_msg>[PLG_COURSES_OUTLINE] show message, not entire response<commit_after>
/**
 * @package     hubzero-cms
 * @file        plugins/courses/outline/wiki.jquery.js
 * @copyright   Copyright 2005-2011 Purdue University. All rights reserved.
 * @license     http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3
 */

//-----------------------------------------------------------
//  Ensure we have our namespace
//-----------------------------------------------------------
if (!HUB) {
	var HUB = {};
}

//----------------------------------------------------------
//  Courses outline javascript
//----------------------------------------------------------
if (!jq) {
	var jq = $;
}

jQuery(document).ready(function($) {
	Hubzero.initApi();

	$('.fileupload').fileupload({
		dropZone: $('.wiki-files-upload'),
		dataType: 'json',
		singleFileUploads: false,
		add: function ( e, data ) {
			if ($('.wiki-title').val() === '') {
				$('.title-error').html('Please provide a title first').show();
				$('html, body').scrollTop(0);
			} else {
				$('.title-error').hide();
				data.submit();
			}
		},
		done: function ( e, data ) {
			var html = '';
			if ($('.wiki-files-available ul').length) {
				$.each(data.files, function ( i, file ) {
					html += '
';
					html += '' + file.name + '';
					html += '

';
					html += '
';
				});

				$('.wiki-files-list').prepend(html);
			} else {
				html  = '
';
				$.each(data.files, function ( i, file ) {
					html += '
';
					html += '' + file.name + '';
					html += '

';
					html += '
';
				});
				html += '
';
				$('.wiki-files-available').html(html);
			}

			if ($('#asset_id').val() === '') {
				$('#asset_id').val(data.result.assets.assets.asset_id);
			}
		},
		fail: function ( e, data ) {
			$('.title-error').html($.parseJSON(data.jqXHR.responseText).message).show();
			$('html, body').scrollTop(0);
		}
	})
	.hover(function() {
		$(this).siblings('.wiki-files-upload').css('opacity', 1);
	}, function() {
		$(this).siblings('.wiki-files-upload').css('opacity', 0.7);
	});

	$('.edit-form').submit(function ( e ) {
		if (typeof CKEDITOR !== 'undefined') {
			for ( var instance in CKEDITOR.instances ) {
				CKEDITOR.instances[instance].updateElement();
			}
		}
	});

	$('.wiki-files-available').on('click', '.wiki-files-delete', function() {
		var t    = $(this),
			data = t.parents('.edit-form').serializeArray();

		data.push({name : "filename", value : t.prev('.wiki-files-filename').html()});

		$.ajax({
			url        : '/api/courses/asset/deletefile',
			dataType   : "json",
			type       : 'POST',
			cache      : false,
			data       : data,
			success    : function ( data, textStatus, jqXHR ) {
				t.parents('.wiki-file').fadeOut(500, function() {
					$(this).remove();
				});
			}
		});
	});
});<|endoftext|>


<commit_before>const path = require('path')
const fs = require('fs')
const EventEmitter = require('events').EventEmitter

const mkdirp = require('mkdirp')
const util = require('./util')

let cacheInst

module.exports = class ReadlineSourceMap extends EventEmitter {
    constructor(opts) {
        super()
        opts = opts || {}
        this.sperator = opts.sperator || '|'
        this.filepath = opts.filepath
        this.speratorCode = this.sperator.charCodeAt()
    }

    /**
     * 初始化
     *
     * @static
     * @param {any} opts 配置项
     * @returns ReadlineSourceMap
     */
    static init(opts) {
        return cacheInst || (cacheInst = new ReadlineSourceMap(opts))
    }

    loadCache() {

        let self = this

        let lineCount = 0,
            speratorIdxArr = [],
            _bufs, rslt = {}

        return new Promise(async(resolve, reject) => {

            let isExists = await util.fileStatAsync(self.filepath)

            if (!isExists) {

                await new Promise((resolve, reject) => {
                    fs.writeFile(self.filepath, '', err => {
                        console.error(err)
                        err ? resolve(false) : resolve(true)
                    })
                })

                // await util.mkdir(path.basename(self.filepath))
            }

            let stream = fs.createReadStream(self.filepath)

            stream.on('data', chunk => {

                if (!chunk.length) return

                _bufs = chunk

                for (let i = 0, l = chunk.length; i < l; i++) {

                    if (chunk[i] === self.speratorCode) {

                        speratorIdxArr.push(i - 1)

                    } else if (chunk[i] === 10 || chunk[i] === 13) {

                        lineCount += 1

                        let
                            filename = chunk.slice(0, speratorIdxArr[0] + 1).toString(),
                            diskpath = chunk.slice(speratorIdxArr[0] + 2, i).toString()

                        rslt[filename] = diskpath

                        speratorIdxArr = []

                        self.emit('line', filename, diskpath)
                    }
                }
            })

            stream.on('end', () => {

                if (!lineCount && _bufs) {

                    let filename = _bufs.slice(0, speratorIdxArr[0] + 1).toString()

                    rslt[filename] = _bufs.slice(speratorIdxArr[0] + 2, _bufs.length).toString()
                }

                self.emit('loaded', rslt)

                resolve()
            })

            stream.on('error', err => {
                self.emit('error', err)
                reject(err)
            })
        })
    }

    storeCache(filename, diskpath) {

        let wStream = fs.createWriteStream(this.filepath, { flags: 'a' })

        wStream.write(filename)
        wStream.write(this.sperator)
        wStream.write(diskpath)
        wStream.write('\n')
        wStream.end()
    }
}<commit_msg>修复逐行解析算法错误导致的缓存重复和命中问题<commit_after>const path = require('path')
const fs = require('fs')
const EventEmitter = require('events').EventEmitter

const mkdirp = require('mkdirp')
const util = require('./util')

let cacheInst

module.exports = class ReadlineSourceMap extends EventEmitter {
    constructor(opts) {
        super()
        opts = opts || {}
        this.sperator = opts.sperator || '|'
        this.filepath = opts.filepath
        this.speratorCode = this.sperator.charCodeAt()
    }

    /**
     * 初始化
     *
     * @static
     * @param {any} opts 配置项
     * @returns ReadlineSourceMap
     */
    static init(opts) {
        return cacheInst || (cacheInst = new ReadlineSourceMap(opts))
    }

    loadCache() {

        let self = this,
            lineCount = 0,
            seperatorIndex = 0,
            rslt = {},
            _bufs

        return new Promise(async(resolve, reject) => {

            let isExists = await util.fileStatAsync(self.filepath)

            if (!isExists) {

                await new Promise((resolve, reject) => {
                    fs.writeFile(self.filepath, '', err => {
                        console.error(err)
                        err ? resolve(false) : resolve(true)
                    })
                })
            }

            let stream = fs.createReadStream(self.filepath)

            stream.on('data', chunk => {

                if (!chunk.length) return

                _bufs = chunk

                for (let lineStartIndex = 0, i = 0, l = chunk.length; i < l; i++) {

                    if (chunk[i] === self.speratorCode) {

                        seperatorIndex = i - 1

                    } else if (chunk[i] === 10 || chunk[i] === 13) {

                        lineCount += 1

                        let
                            filename = chunk.slice(lineStartIndex, seperatorIndex + 1).toString(),
                            diskpath = chunk.slice(seperatorIndex + 2, i).toString()

                        rslt[filename] = diskpath

                        lineStartIndex = i + 1

                        self.emit('line', filename, diskpath)
                    }
                }
            })

            stream.on('end', () => {

                if (!lineCount && _bufs) {

                    let filename = _bufs.slice(0, seperatorIndex + 1).toString()

                    rslt[filename] = _bufs.slice(seperatorIndex + 2, _bufs.length).toString()
                }

                self.emit('loaded', rslt)

                resolve()
            })

            stream.on('error', err => {
                self.emit('error', err)
                reject(err)
            })
        })
    }

    storeCache(filename, diskpath) {

        let wStream = fs.createWriteStream(this.filepath, {
            flags: 'a'
        })

        wStream.write(filename)
        wStream.write(this.sperator)
        wStream.write(diskpath)
        wStream.write('\n')
        wStream.end()
    }
}
<|endoftext|>


<commit_before>(function() {

var checkVersion = Dagaz.Model.checkVersion;

Dagaz.Model.checkVersion = function(design, name, value) {
  if (name != "retrograde-chess-extension") {
     checkVersion(design, name, value);
  }
}

var addDrops = function(board, pos, types, mode) {
  var moves = [];
  _.each(types, function(t) {
      var p = Dagaz.Model.createPiece(t, board.player);
      var m = Dagaz.Model.createMove(3);
      m.dropPiece(pos, p);
      m.goTo(board.turn);
      if (mode == 1) {
          moves.push(m);
      } else {
          board.moves.push(m);
      }
  });
  if (moves.length > 0) {
      board.moves = moves;
  }
}

var getTypes = function(design, board) {
  var cnt = [8, 2, 2, 2, 1, 1];
  _.each(design.allPositions(), function(pos) {
      var piece = board.getPiece(pos);
      if (piece === null) return;
      if (piece.player != board.player) return;
      cnt[piece.type]--;
  });
  var r = [];
  for (var t = 0; t < 5; t++) {
      if (cnt[t] > 0) r.push(t);
  }
  return r;
}

var checkDir = function(design, board, player, pos, dir, target) {
  var p = design.navigate(player, pos, dir);
  if (p === null) return false;
  return p == target;
}

var CheckInvariants = Dagaz.Model.CheckInvariants;

Dagaz.Model.CheckInvariants = function(board) {
  var design = Dagaz.Model.design;
  if ((board.parent !== null) && !_.isUndefined(board.move) && board.move.isSimpleMove()) {
      var pos = board.move.actions[0][0][0];
      var types = getTypes(design, board);
      if (design.inZone(0, board.parent.player, pos) || design.inZone(2, board.parent.player, pos)) {
          types = _.without(types, 0);
      }
      var piece = board.move.actions[0][2][0];
      if ((piece.type == 0) && (board.move.mode != 1)) return;
      var target = null; var mode = board.move.mode;
      if (design.inZone(0, board.parent.player, pos) && (_.indexOf([3, 4], +piece.type) >= 0)) {
          if (checkDir(design, board, board.parent.player, pos, 0, target) ||
              checkDir(design, board, board.parent.player, pos, 2, target)) {
              mode = 1;
          } else {
              target = null;
          }
      }
      addDrops(board, pos, types, mode, target);
  }
  _.each(board.moves, function(move) {
      if ((board.parent !== null) && !_.isUndefined(board.move) && (board.move.mode == 4)) {
          if (move.mode != 2) {
              move.failed = true;
              return;
          }
      }
      if (move.isSimpleMove()) {
          var pos = move.actions[0][0][0];
          var piece = move.actions[0][2][0];
          if (!design.inZone(0, board.player, pos)) return;
          if (_.indexOf([3, 4], +piece.type) < 0) return;
          var target = move.actions[0][1][0];
          if (!checkDir(design, board, board.player, pos, 0, target) &&
              !checkDir(design, board, board.player, pos, 2, target)) return;
          move.actions[0][2].push(piece.promote(0));
          move.mode = 3;
      }
  });
  CheckInvariants(board);
}

})();
<commit_msg>Retrograde Chess (extension fixed)<commit_after>(function() {

var checkVersion = Dagaz.Model.checkVersion;

Dagaz.Model.checkVersion = function(design, name, value) {
  if (name != "retrograde-chess-extension") {
     checkVersion(design, name, value);
  }
}

var addDrops = function(board, pos, types, mode) {
  var moves = [];
  _.each(types, function(t) {
      var p = Dagaz.Model.createPiece(t, board.player);
      var m = Dagaz.Model.createMove(3);
      m.dropPiece(pos, p);
      m.goTo(board.turn);
      if (mode == 1) {
          moves.push(m);
      } else {
          board.moves.push(m);
      }
  });
  if (moves.length > 0) {
      board.moves = moves;
  }
}

var getTypes = function(design, board) {
  var cnt = [8, 2, 2, 2, 1, 1];
  _.each(design.allPositions(), function(pos) {
      var piece = board.getPiece(pos);
      if (piece === null) return;
      if (piece.player != board.player) return;
      cnt[piece.type]--;
  });
  var r = [];
  for (var t = 0; t < 5; t++) {
      if (cnt[t] > 0) r.push(t);
  }
  return r;
}

var checkDir = function(design, board, player, pos, dir, target) {
  var p = design.navigate(player, pos, dir);
  if (p === null) return false;
  return p == target;
}

var CheckInvariants = Dagaz.Model.CheckInvariants;

Dagaz.Model.CheckInvariants = function(board) {
  var design = Dagaz.Model.design;
  if ((board.parent !== null) && !_.isUndefined(board.move) && board.move.isSimpleMove()) {
      var pos = board.move.actions[0][0][0];
      var types = getTypes(design, board);
      if (design.inZone(0, board.parent.player, pos) || design.inZone(2, board.parent.player, pos)) {
          types = _.without(types, 0);
      }
      var piece = board.move.actions[0][2][0];
      if ((piece.type != 0) || (board.move.mode == 1)) {
          var target = null; var mode = board.move.mode;
          if (design.inZone(0, board.parent.player, pos) && (_.indexOf([3, 4], +piece.type) >= 0)) {
              if (checkDir(design, board, board.parent.player, pos, 0, target) ||
                  checkDir(design, board, board.parent.player, pos, 2, target)) {
                  mode = 1;
              } else {
                  target = null;
              }
          }
          addDrops(board, pos, types, mode, target);
      }
  }
  _.each(board.moves, function(move) {
      if ((board.parent !== null) && !_.isUndefined(board.move) && (board.move.mode == 4)) {
          if (move.mode != 2) {
              move.failed = true;
              return;
          }
      }
      if (move.isSimpleMove()) {
          var pos = move.actions[0][0][0];
          var piece = move.actions[0][2][0];
          if (!design.inZone(0, board.player, pos)) return;
          if (_.indexOf([3, 4], +piece.type) < 0) return;
          var target = move.actions[0][1][0];
          if (!checkDir(design, board, board.player, pos, 0, target) &&
              !checkDir(design, board, board.player, pos, 2, target)) return;
          move.actions[0][2].push(piece.promote(0));
          move.mode = 3;
      }
  });
  CheckInvariants(board);
}

})();
<|endoftext|>


<commit_before>'use strict';

angular.module('mean.icu.ui.discussiondetails', []).controller('DiscussionDetailsController', DiscussionDetailsController);

function DiscussionDetailsController($scope, $rootScope, entity, tasks, context, tags, $state, $timeout, people, DiscussionsService, PermissionsService, ActivitiesService, EntityService, UsersService, $stateParams) {

  // ==================================================== init ==================================================== //

  if (($state.$current.url.source.includes("search")) || ($state.$current.url.source.includes("discussions"))) {
    $scope.item = entity || context.entity;
  } else {
    $scope.item = context.entity || entity;
  }

  if ($scope.item && ($state.current.name === 'main.tasks.byentity.details' || $state.current.name === 'main.search.discussion' || $state.current.name === 'main.discussions.all.details' || $state.current.name === 'main.discussions.byentity.details')) {
    $state.go('.activities');
  }

  $scope.editorOptions = {
    theme: 'bootstrap',
    buttons: ['bold', 'italic', 'underline', 'anchor', 'quote', 'orderedlist', 'unorderedlist']
  };
  $scope.statuses = ['new', 'scheduled', 'done', 'canceled', 'archived'];

  var errors = {
    'assign': 'please select assignee!',
    'startDate': 'please choose deadline!',
    'title': 'please fill title!',
    'location': 'please fill location'
  };

  $scope.entity = entity || context.entity;
  $scope.tasks = tasks.data || tasks;
  $scope.people = people.data || people;
  $scope.main = context.main;
  $scope.CanceledMailSend = false;
  $scope.tags = tags;
  $scope.me = UsersService.getMe().$$state.value;

  var currentState = $state.current.name;

  // backup for previous changes - for updates
  var backupEntity = JSON.parse(JSON.stringify($scope.item));

  if ($scope.people[Object.keys($scope.people).length - 1].name !== 'no select') {
    var newPeople = {
      name: 'no select'
    };
    $scope.people.push(_(newPeople).clone());
  }

  for (var i = 0; i < $scope.people.length; i++) {
    if ($scope.people[i] && ($scope.people[i].job == undefined || $scope.people[i].job == null)) {
      $scope.people[i].job = $scope.people[i].name;
    }
  }

  DiscussionsService.getStarred().then(function(starred) {
    $scope.item.star = _(starred).any(function(s) {
      return s._id === $scope.item._id;
    });
  });

  // ==================================================== onChanges ==================================================== //

  function navigateToDetails(discussion) {
    $scope.detailsState = context.entityName === 'all' ? 'main.discussions.all.details' : 'main.discussions.byentity.details';

    $state.go($scope.detailsState, {
      id: discussion._id,
      entity: $scope.currentContext.entityName,
      entityId: $scope.currentContext.entityId,
      starred: $stateParams.starred
    }, {
      reload: true
    });
  }

  $scope.onStar = function(value) {
    DiscussionsService.star($scope.item).then(function() {
      navigateToDetails($scope.item);
      // "$scope.item.star" will be change in 'ProjectsService.star' function
    });
  }

  $scope.onAssign = function(value) {
    $scope.item.assign = value;
    $scope.updateAndNotify($scope.item);
  }

  var activeLocationTimeout;
  $scope.updateLocation = function(discussion) {
    if (activeLocationTimeout) {
      clearTimeout(activeLocationTimeout)
    }
    activeLocationTimeout = setTimeout(function() {
      $scope.update(discussion, 'location')
    }, 2000);
  }

  $scope.onStatus = function(value) {
    $scope.item.status = value;
    $scope.update($scope.item, {
      name: 'status'
    })
  }

  $scope.onStatus = function(value) {
    $scope.item.status = value;
    $scope.update($scope.item, {
      name: 'status'
    })
  }

  $scope.onDateDue = function(item, type) {
    $scope.update(item, type);
  }

  // ==================================================== Menu events ==================================================== //

  $scope.recycle = function() {
    EntityService.recycle('discussions', $scope.item._id).then(function() {
      let clonedEntity = JSON.parse(JSON.stringify($scope.item));
      clonedEntity.status = "Recycled"
      // just for activity status
      DiscussionsService.updateStatus(clonedEntity, $scope.item).then(function(result) {
        ActivitiesService.data.push(result);
      });

      refreshList();
      if (currentState.indexOf('search') != -1) {
        $state.go(currentState, {
          entity: context.entityName,
          entityId: context.entityId
        }, {
          reload: true,
          query: $stateParams.query
        });
      } else {
        $state.go('main.discussions.all', {
          entity: 'all'
        }, {
          reload: true
        });
      }
    });
  }

  $scope.recycleRestore = function() {
    EntityService.recycleRestore('discussions', $scope.item._id).then(function() {
      let clonedEntity = JSON.parse(JSON.stringify($scope.item));
      clonedEntity.status = "un-deleted"
      // just for activity status
      DiscussionsService.updateStatus(clonedEntity, $scope.item).then(function(result) {
        ActivitiesService.data.push(result);
      });

      refreshList();

      var state = currentState.indexOf('search') !== -1 ? $state.current.name : 'main.discussions.all';
      $state.go(state, {
        entity: context.entityName,
        entityId: context.entityId
      }, {
        reload: true
      });
    });
  }

  function refreshList() {
    $rootScope.$broadcast('refreshList');
  }

  $scope.menuItems = [{
      label: 'recycleDiscussion',
      icon: 'times-circle',
      display: !$scope.item.hasOwnProperty('recycled'),
      action: $scope.recycle,
    }, {
      label: 'unrecycleDiscussion',
      icon: 'times-circle',
      display: $scope.item.hasOwnProperty('recycled'),
      action: $scope.recycleRestore,
  }];

  // ==================================================== Buttons ==================================================== //

  $scope.updateStatusForApproval = function(entity) {
    let context = {
      action: "updated",
      name: "status",
      type: "project"
    }
    entity.status = "waiting-approval";
    $scope.update(entity, 'status');
  }

  $scope.summary = function(discussion) {
    for (var key in errors) {
      if (!discussion[key]) {
        alert(errors[key]);
        return
      }
    }

    if (!(discussion.allDay || (discussion.startTime && discussion.endDate && discussion.endTime))) {
      alert("Dates problem");
      return;
    }

    DiscussionsService.summary(discussion).then(function(result) {
      discussion.status = result.status;
      var index = currentState.indexOf('main.search');
      $state.reload(index === 0 ? 'main.search' : 'main.tasks.byentity');
    });
  }

  $scope.schedule = function(discussion) {
    DiscussionsService.schedule(discussion).then(function(result) {
      discussion.status = result.status;
    });
  }

  $scope.cancele = function(discussion) {
    DiscussionsService.cancele(discussion).then(function(result) {
      discussion.status = result.status;
      $scope.CanceledMailSend = true;
    });
  }

  $scope.archive = function(discussion) {
    discussion.status = 'archived';
    DiscussionsService.update(discussion);
  }

  var scheduleAction = {
    label: 'scheduleDiscussion',
    method: $scope.schedule
  };

  var summaryAction = {
    label: 'sendSummary',
    method: $scope.summary
  };

  var archiveAction = {
    label: 'archiveDiscussion',
    method: $scope.archive
  };

  var canceleAction = {
    label: 'canceleDiscussion',
    method: $scope.cancele
  };

  $scope.statusesActionsMap = {
    new: scheduleAction,
    scheduled: summaryAction,
    done: archiveAction,
    canceled: canceleAction
  };

  // ==================================================== $watch: title / desc ==================================================== //

  var activeTitleTimeout;
  $scope.$watch('item.title', function(nVal, oVal) {
    if (nVal !== oVal && oVal) {
      if (activeTitleTimeout) {
        clearTimeout(activeTitleTimeout)
      }
      activeTitleTimeout = setTimeout(function() {
        $scope.update($scope.item, 'title')
      }, 2000);
    }
  });

  var activeDescriptionTimeout, nText, oText;
  $scope.$watch('item.description', function(nVal, oVal) {
    nText = nVal ? nVal.replace(/<(?:.|\n)*?>/gm, '') : '';
    oText = oVal ? oVal.replace(/<(?:.|\n)*?>/gm, '') : '';
    if (nText != oText && oText) {
      if (activeDescriptionTimeout) {
        clearTimeout(activeDescriptionTimeout)
      }
      activeDescriptionTimeout = setTimeout(function() {
        $scope.update($scope.item, 'description')
      }, 2000);
    }
  });

  // ==================================================== update ==================================================== //

  $scope.update = function(discussion, type) {

    DiscussionsService.update(discussion);
    switch (type) {
    case 'startDue':
    case 'endDue':
      DiscussionsService.updateDue(discussion, backupEntity, type).then(function(result) {
        backupEntity = JSON.parse(JSON.stringify($scope.item));
        ActivitiesService.data = ActivitiesService.data || [];
        ActivitiesService.data.push(result);
      });
      break;

    case 'status':
      DiscussionsService.updateStatus(discussion, backupEntity).then(function(result) {
        backupEntity = JSON.parse(JSON.stringify($scope.item));
        ActivitiesService.data = ActivitiesService.data || [];
        ActivitiesService.data.push(result);
      });
      break;

    case 'location':
      DiscussionsService.updateLocation(discussion, backupEntity).then(function(result) {
        backupEntity = JSON.parse(JSON.stringify($scope.item));
        ActivitiesService.data = ActivitiesService.data || [];
        ActivitiesService.data.push(result);
      });
      break;

    case 'assign':
      if (discussion.assign != null) {
        let filtered = discussion.watchers.filter(watcher=>{
          // check the assignee is not a watcher already
          return watcher == discussion.assign
        }
        );

        // add assignee as watcher
        if (filtered.length == 0) {
          discussion.watchers.push(discussion.assign);
        }
      }

      DiscussionsService.updateAssign(discussion, backupEntity).then(function(result) {
        backupEntity = JSON.parse(JSON.stringify($scope.item));
        ActivitiesService.data = ActivitiesService.data || [];
        ActivitiesService.data.push(result);
      });
      break;
    case 'title':
    case 'description':
      DiscussionsService.updateTitle(discussion, backupEntity, type).then(function(result) {
        backupEntity = JSON.parse(JSON.stringify($scope.item));
        ActivitiesService.data = ActivitiesService.data || [];
        ActivitiesService.data.push(result);
        refreshList();
      });
      break;
    }

    if (currentState.indexOf('search') != -1) {
      refreshList();
    }
  }

    $scope.updateAndNotify = function(item) {
        item.status = $scope.statuses[1];
        if (context.entityName === 'discussion') {
            item.discussion = context.entityId;
        }

        if (item.assign === undefined || item.assign === null) {
            delete item['assign'];
        } else {
            // check the assignee is not a watcher already
            let filtered = item.watchers.filter(watcher=>{
                    return watcher._id == item.assign
                }
            );

            if (filtered.length == 0) {
                item.watchers.push(item.assign);
            }
        }

        DiscussionsService.update(item).then(function(result) {
            if (context.entityName === 'project') {
                var projId = result.project ? result.project._id : undefined;
                if (projId !== context.entityId) {
                    $state.go('main.discussions.byentity', {
                        entity: context.entityName,
                        entityId: context.entityId
                    }, {
                        reload: true
                    });
                }
            }

            DiscussionsService.updateAssign(item, $scope.me, backupEntity).then(function(res) {
                backupEntity = JSON.parse(JSON.stringify(result));
                ActivitiesService.data.push(res);
            });
        });

    };

  $scope.updateCurrentDiscussion = function() {
    DiscussionsService.currentDiscussionName = $scope.item.title;
  }

  $scope.delayedUpdate = _.debounce($scope.update, 500);

  // ==================================================== havePermissions ==================================================== //

  $scope.isRecycled = $scope.item.hasOwnProperty('recycled');

  $scope.enableRecycled = true;
  $scope.havePermissions = function(type, enableRecycled) {
    enableRecycled = enableRecycled || !$scope.isRecycled;
    return (PermissionsService.havePermissions($scope.entity, type) && enableRecycled);
  }

  $scope.haveEditiorsPermissions = function() {
    return PermissionsService.haveEditorsPerms($scope.entity);
  }

  $scope.permsToSee = function() {
    return PermissionsService.haveAnyPerms($scope.entity);
  }

}
<commit_msg>fixed changing discussion status after assigning user https://github.com/linnovate/root/issues/948<commit_after>'use strict';

angular.module('mean.icu.ui.discussiondetails', []).controller('DiscussionDetailsController', DiscussionDetailsController);

function DiscussionDetailsController($scope, $rootScope, entity, tasks, context, tags, $state, $timeout, people, DiscussionsService, PermissionsService, ActivitiesService, EntityService, UsersService, $stateParams) {

  // ==================================================== init ==================================================== //

  if (($state.$current.url.source.includes("search")) || ($state.$current.url.source.includes("discussions"))) {
    $scope.item = entity || context.entity;
  } else {
    $scope.item = context.entity || entity;
  }

  if ($scope.item && ($state.current.name === 'main.tasks.byentity.details' || $state.current.name === 'main.search.discussion' || $state.current.name === 'main.discussions.all.details' || $state.current.name === 'main.discussions.byentity.details')) {
    $state.go('.activities');
  }

  $scope.editorOptions = {
    theme: 'bootstrap',
    buttons: ['bold', 'italic', 'underline', 'anchor', 'quote', 'orderedlist', 'unorderedlist']
  };
  $scope.statuses = ['new', 'scheduled', 'done', 'canceled', 'archived'];

  var errors = {
    'assign': 'please select assignee!',
    'startDate': 'please choose deadline!',
    'title': 'please fill title!',
    'location': 'please fill location'
  };

  $scope.entity = entity || context.entity;
  $scope.tasks = tasks.data || tasks;
  $scope.people = people.data || people;
  $scope.main = context.main;
  $scope.CanceledMailSend = false;
  $scope.tags = tags;
  $scope.me = UsersService.getMe().$$state.value;

  var currentState = $state.current.name;

  // backup for previous changes - for updates
  var backupEntity = JSON.parse(JSON.stringify($scope.item));

  if ($scope.people[Object.keys($scope.people).length - 1].name !== 'no select') {
    var newPeople = {
      name: 'no select'
    };
    $scope.people.push(_(newPeople).clone());
  }

  for (var i = 0; i < $scope.people.length; i++) {
    if ($scope.people[i] && ($scope.people[i].job == undefined || $scope.people[i].job == null)) {
      $scope.people[i].job = $scope.people[i].name;
    }
  }

  DiscussionsService.getStarred().then(function(starred) {
    $scope.item.star = _(starred).any(function(s) {
      return s._id === $scope.item._id;
    });
  });

  // ==================================================== onChanges ==================================================== //

  function navigateToDetails(discussion) {
    $scope.detailsState = context.entityName === 'all' ? 'main.discussions.all.details' : 'main.discussions.byentity.details';

    $state.go($scope.detailsState, {
      id: discussion._id,
      entity: $scope.currentContext.entityName,
      entityId: $scope.currentContext.entityId,
      starred: $stateParams.starred
    }, {
      reload: true
    });
  }

  $scope.onStar = function(value) {
    DiscussionsService.star($scope.item).then(function() {
      navigateToDetails($scope.item);
      // "$scope.item.star" will be change in 'ProjectsService.star' function
    });
  }

  $scope.onAssign = function(value) {
    $scope.item.assign = value;
    $scope.updateAndNotify($scope.item);
  }

  var activeLocationTimeout;
  $scope.updateLocation = function(discussion) {
    if (activeLocationTimeout) {
      clearTimeout(activeLocationTimeout)
    }
    activeLocationTimeout = setTimeout(function() {
      $scope.update(discussion, 'location')
    }, 2000);
  }

  $scope.onStatus = function(value) {
    $scope.item.status = value;
    $scope.update($scope.item, {
      name: 'status'
    })
  }

  $scope.onStatus = function(value) {
    $scope.item.status = value;
    $scope.update($scope.item, {
      name: 'status'
    })
  }

  $scope.onDateDue = function(item, type) {
    $scope.update(item, type);
  }

  // ==================================================== Menu events ==================================================== //

  $scope.recycle = function() {
    EntityService.recycle('discussions', $scope.item._id).then(function() {
      let clonedEntity = JSON.parse(JSON.stringify($scope.item));
      clonedEntity.status = "Recycled"
      // just for activity status
      DiscussionsService.updateStatus(clonedEntity, $scope.item).then(function(result) {
        ActivitiesService.data.push(result);
      });

      refreshList();
      if (currentState.indexOf('search') != -1) {
        $state.go(currentState, {
          entity: context.entityName,
          entityId: context.entityId
        }, {
          reload: true,
          query: $stateParams.query
        });
      } else {
        $state.go('main.discussions.all', {
          entity: 'all'
        }, {
          reload: true
        });
      }
    });
  }

  $scope.recycleRestore = function() {
    EntityService.recycleRestore('discussions', $scope.item._id).then(function() {
      let clonedEntity = JSON.parse(JSON.stringify($scope.item));
      clonedEntity.status = "un-deleted"
      // just for activity status
      DiscussionsService.updateStatus(clonedEntity, $scope.item).then(function(result) {
        ActivitiesService.data.push(result);
      });

      refreshList();

      var state = currentState.indexOf('search') !== -1 ? $state.current.name : 'main.discussions.all';
      $state.go(state, {
        entity: context.entityName,
        entityId: context.entityId
      }, {
        reload: true
      });
    });
  }

  function refreshList() {
    $rootScope.$broadcast('refreshList');
  }

  $scope.menuItems = [{
      label: 'recycleDiscussion',
      icon: 'times-circle',
      display: !$scope.item.hasOwnProperty('recycled'),
      action: $scope.recycle,
    }, {
      label: 'unrecycleDiscussion',
      icon: 'times-circle',
      display: $scope.item.hasOwnProperty('recycled'),
      action: $scope.recycleRestore,
  }];

  // ==================================================== Buttons ==================================================== //

  $scope.updateStatusForApproval = function(entity) {
    let context = {
      action: "updated",
      name: "status",
      type: "project"
    }
    entity.status = "waiting-approval";
    $scope.update(entity, 'status');
  }

  $scope.summary = function(discussion) {
    for (var key in errors) {
      if (!discussion[key]) {
        alert(errors[key]);
        return
      }
    }

    if (!(discussion.allDay || (discussion.startTime && discussion.endDate && discussion.endTime))) {
      alert("Dates problem");
      return;
    }

    DiscussionsService.summary(discussion).then(function(result) {
      discussion.status = result.status;
      var index = currentState.indexOf('main.search');
      $state.reload(index === 0 ? 'main.search' : 'main.tasks.byentity');
    });
  }

  $scope.schedule = function(discussion) {
    DiscussionsService.schedule(discussion).then(function(result) {
      discussion.status = result.status;
    });
  }

  $scope.cancele = function(discussion) {
    DiscussionsService.cancele(discussion).then(function(result) {
      discussion.status = result.status;
      $scope.CanceledMailSend = true;
    });
  }

  $scope.archive = function(discussion) {
    discussion.status = 'archived';
    DiscussionsService.update(discussion);
  }

  var scheduleAction = {
    label: 'scheduleDiscussion',
    method: $scope.schedule
  };

  var summaryAction = {
    label: 'sendSummary',
    method: $scope.summary
  };

  var archiveAction = {
    label: 'archiveDiscussion',
    method: $scope.archive
  };

  var canceleAction = {
    label: 'canceleDiscussion',
    method: $scope.cancele
  };

  $scope.statusesActionsMap = {
    new: scheduleAction,
    scheduled: summaryAction,
    done: archiveAction,
    canceled: canceleAction
  };

  // ==================================================== $watch: title / desc ==================================================== //

  var activeTitleTimeout;
  $scope.$watch('item.title', function(nVal, oVal) {
    if (nVal !== oVal && oVal) {
      if (activeTitleTimeout) {
        clearTimeout(activeTitleTimeout)
      }
      activeTitleTimeout = setTimeout(function() {
        $scope.update($scope.item, 'title')
      }, 2000);
    }
  });

  var activeDescriptionTimeout, nText, oText;
  $scope.$watch('item.description', function(nVal, oVal) {
    nText = nVal ? nVal.replace(/<(?:.|\n)*?>/gm, '') : '';
    oText = oVal ? oVal.replace(/<(?:.|\n)*?>/gm, '') : '';
    if (nText != oText && oText) {
      if (activeDescriptionTimeout) {
        clearTimeout(activeDescriptionTimeout)
      }
      activeDescriptionTimeout = setTimeout(function() {
        $scope.update($scope.item, 'description')
      }, 2000);
    }
  });

  // ==================================================== update ==================================================== //

  $scope.update = function(discussion, type) {

    DiscussionsService.update(discussion);
    switch (type) {
    case 'startDue':
    case 'endDue':
      DiscussionsService.updateDue(discussion, backupEntity, type).then(function(result) {
        backupEntity = JSON.parse(JSON.stringify($scope.item));
        ActivitiesService.data = ActivitiesService.data || [];
        ActivitiesService.data.push(result);
      });
      break;

    case 'status':
      DiscussionsService.updateStatus(discussion, backupEntity).then(function(result) {
        backupEntity = JSON.parse(JSON.stringify($scope.item));
        ActivitiesService.data = ActivitiesService.data || [];
        ActivitiesService.data.push(result);
      });
      break;

    case 'location':
      DiscussionsService.updateLocation(discussion, backupEntity).then(function(result) {
        backupEntity = JSON.parse(JSON.stringify($scope.item));
        ActivitiesService.data = ActivitiesService.data || [];
        ActivitiesService.data.push(result);
      });
      break;

    case 'assign':
      if (discussion.assign != null) {
        let filtered = discussion.watchers.filter(watcher=>{
          // check the assignee is not a watcher already
          return watcher == discussion.assign
        }
        );

        // add assignee as watcher
        if (filtered.length == 0) {
          discussion.watchers.push(discussion.assign);
        }
      }

      DiscussionsService.updateAssign(discussion, backupEntity).then(function(result) {
        backupEntity = JSON.parse(JSON.stringify($scope.item));
        ActivitiesService.data = ActivitiesService.data || [];
        ActivitiesService.data.push(result);
      });
      break;
    case 'title':
    case 'description':
      DiscussionsService.updateTitle(discussion, backupEntity, type).then(function(result) {
        backupEntity = JSON.parse(JSON.stringify($scope.item));
        ActivitiesService.data = ActivitiesService.data || [];
        ActivitiesService.data.push(result);
        refreshList();
      });
      break;
    }

    if (currentState.indexOf('search') != -1) {
      refreshList();
    }
  }

    $scope.updateAndNotify = function(item) {
        if (context.entityName === 'discussion') {
            item.discussion = context.entityId;
        }

        if (item.assign === undefined || item.assign === null) {
            delete item['assign'];
        } else {
            // check the assignee is not a watcher already
            let filtered = item.watchers.filter(watcher=>{
                    return watcher._id == item.assign
                }
            );

            if (filtered.length == 0) {
                item.watchers.push(item.assign);
            }
        }

        DiscussionsService.update(item).then(function(result) {
            if (context.entityName === 'project') {
                var projId = result.project ? result.project._id : undefined;
                if (projId !== context.entityId) {
                    $state.go('main.discussions.byentity', {
                        entity: context.entityName,
                        entityId: context.entityId
                    }, {
                        reload: true
                    });
                }
            }

            DiscussionsService.updateAssign(item, $scope.me, backupEntity).then(function(res) {
                backupEntity = JSON.parse(JSON.stringify(result));
                ActivitiesService.data.push(res);
            });
        });

    };

  $scope.updateCurrentDiscussion = function() {
    DiscussionsService.currentDiscussionName = $scope.item.title;
  }

  $scope.delayedUpdate = _.debounce($scope.update, 500);

  // ==================================================== havePermissions ==================================================== //

  $scope.isRecycled = $scope.item.hasOwnProperty('recycled');

  $scope.enableRecycled = true;
  $scope.havePermissions = function(type, enableRecycled) {
    enableRecycled = enableRecycled || !$scope.isRecycled;
    return (PermissionsService.havePermissions($scope.entity, type) && enableRecycled);
  }

  $scope.haveEditiorsPermissions = function() {
    return PermissionsService.haveEditorsPerms($scope.entity);
  }

  $scope.permsToSee = function() {
    return PermissionsService.haveAnyPerms($scope.entity);
  }

}
<|endoftext|>


<commit_before>'use strict';

let gulp = require('gulp');
let sass = require('gulp-sass');
let packager = require('electron-packager');
let path = require('path');

const BUILD_FOLDER = 'build';
const RELEASE_FOLDER = 'release';
const LINUX = 'linux';
const MAC = 'osx';
const WINDOWS = 'windows';
const ARCH_64 = '64bit';
const ARCH_32 = '32bit';

/* Development tasks */
gulp.task('sass', function () {
  gulp.src('./resources/sass/all.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(sass({outputStyle: 'compressed'}))
    .pipe(gulp.dest('./resources/css/'));
});

gulp.task('sass:watch', function () {
  gulp.watch('./resources/sass/**/*.scss', ['sass']);
});

gulp.task('dev', ['sass', 'sass:watch']);

/* Build tasks */
gulp.task('build', ['build:linux', 'build:osx', 'build:win']);

// Linux
gulp.task('build:linux', ['build:linux64', 'build:linux32']);

gulp.task('build:linux64', ['sass'], function () {
  var buildOpts = getDefaultBuildOpts();

  buildOpts.platform = 'linux';
  buildOpts.arch = 'x64';
  buildOpts.out = path.join(BUILD_FOLDER, LINUX, ARCH_64);

  build(buildOpts);
});

gulp.task('build:linux32', ['sass'], function () {
  var buildOpts = getDefaultBuildOpts();

  buildOpts.platform = 'linux';
  buildOpts.arch = 'ia32';
  buildOpts.out = path.join(BUILD_FOLDER, LINUX, ARCH_32);

  build(buildOpts);
});

// Mac
gulp.task('build:osx', ['build:osx64']);

gulp.task('build:osx64', ['sass'], function () {
  var buildOpts = getDefaultBuildOpts();

  buildOpts.platform = 'darwin';
  buildOpts.arch = 'x64';
  buildOpts.out = path.join(BUILD_FOLDER, MAC, ARCH_64);
  buildOpts.icon = 'resources/images/icon.icns';

  build(buildOpts);
});

// Windows
gulp.task('build:win', ['build:win64', 'build:win32']);

gulp.task('build:win64', ['sass'], function () {
  var buildOpts = getDefaultBuildOpts();

  buildOpts.platform = 'win32';
  buildOpts.arch = 'x64';
  buildOpts.out = path.join(BUILD_FOLDER, WINDOWS, ARCH_64);
  buildOpts.icon = 'resources/images/icon.ico';

  build(buildOpts);
});

gulp.task('build:win32', ['sass'], function () {
  var buildOpts = getDefaultBuildOpts();

  buildOpts.platform = 'win32';
  buildOpts.arch = 'ia32';
  buildOpts.out = path.join(BUILD_FOLDER, WINDOWS, ARCH_32);
  buildOpts.icon = 'resources/images/icon.ico';

  build(buildOpts);
});

// Return a default build options to Electron
function getDefaultBuildOpts() {
  return {
    dir: __dirname,
    name: 'GitPie',
    version: '0.36.4',
    ignore: getIgnoreRegex(),

    // Fallbacks

    icon: 'resources/images/icon.png',
    out: BUILD_FOLDER
  };
}

function build(electronBuildOpts) {
  var fs = require('fs-extra');

  fs.removeSync(electronBuildOpts.out);

  packager(electronBuildOpts, function (err, appPath) {

    if (err) {
      console.error('Error building electron application for "' + electronBuildOpts.platform + '" arch "' + electronBuildOpts.arch + '". Error:', err);
    } else {
      console.info('Build application for "' + electronBuildOpts.platform + '" arch "' + electronBuildOpts.arch + '" in ', appPath, ' with success!');
    }
  });
}

function getIgnoreRegex() {
  var pack = require('./package'),
    execSync = require('child_process').execSync,
    nonIgnoredModules = [],
    ignoreRegex =  '^/node_modules/(?!({modules}))';

  function findDependentModule (parentModule, module) {
    var pkgModule;

    if (parentModule == 'main') {
      pkgModule = require('./node_modules/'.concat(module).concat('/package'));
    } else {

      try {
        pkgModule = require('./node_modules/'.concat(module).concat('.').concat(parentModule).concat('/package'));
      } catch (err) {
        pkgModule = require('./node_modules/'.concat(module).concat('/package'));
      }

    }

    nonIgnoredModules.push(module);

    if (Object.keys(pkgModule.dependencies).length > 0) {

      for (var i in pkgModule.dependencies) {
        findDependentModule(module, i);
      }

    }
  }

  var npmVersion = execSync('npm -v'),
    i = 0;

  if (parseInt(npmVersion.toString()[0]) >= 3) {
    // The npm modules structure is flat
    for (i in pack.dependencies) {
      findDependentModule('main', i);
    }
  } else {
    // The npm modules structure is not flat
    for (i in pack.dependencies) {
      nonIgnoredModules.push(i);
    }
  }

  ignoreRegex = ignoreRegex.replace('{modules}', nonIgnoredModules.join('|'));

  return [ignoreRegex, '^/'.concat(BUILD_FOLDER)];
}

/* Packing tasks */

// Windows
gulp.task('pack:win64', function () {
  var electronBuilder = require('electron-builder');
  var fs = require('fs-extra');
  var releasePath = path.join(RELEASE_FOLDER, WINDOWS, ARCH_64);

  console.log('Creating windows inataller on', releasePath, '...');

  electronBuilder.init().build({
    appPath: path.join(BUILD_FOLDER, WINDOWS, ARCH_64, 'GitPie-win32-x64'),
    platform: 'win',
    config: 'app/core/packager/config.json',
    out: releasePath
  },
  function (err) {

    if (err) {
      console.error('Error creating windows installer.', err);
    } else {
      console.log('Windows installer created with success on ', releasePath);
    }
  });
});

// Linux
gulp.task('pack:linux64', function () {
  var electronBuilder = require('electron-builder');
  var fs = require('fs-extra');
  var releasePath = path.join(RELEASE_FOLDER, LINUX, ARCH_64);
  var targz = require('tar.gz');

  fs.ensureDirSync(releasePath);

  console.log('Start compressing ', path.join(BUILD_FOLDER, LINUX, ARCH_64, 'GitPie-linux-x64'), 'please wait...');

  targz().compress(path.join(BUILD_FOLDER, LINUX, ARCH_64, 'GitPie-linux-x64'), path.join(releasePath, 'GitPie-linux-x64.tar.gz'))
    .then(function(){
      console.log('File', path.join(releasePath, 'GitPie-linux-x64.tar.gz'), 'created!');
    })
    .catch(function(err){
      console.error('Error compressing ', path.join(BUILD_FOLDER, WINDOWS, ARCH_64, 'GitPie-linux-x64'), 'folder.', err);
    });
});

// Mac
gulp.task('pack:osx64', function () {
  var electronBuilder = require('electron-builder');
  var fs = require('fs-extra');
  var releasePath = path.join(RELEASE_FOLDER, MAC, ARCH_64);
  var AdmZip = require('adm-zip');
  var zip = new AdmZip();

  fs.ensureDirSync(releasePath);

  console.log('Start compressing ', path.join(BUILD_FOLDER, MAC, ARCH_64, 'GitPie-darwin-x64'), 'please wait...');

  zip.addLocalFolder(path.join(BUILD_FOLDER, MAC, ARCH_64, 'GitPie-darwin-x64'));
  zip.writeZip(path.join(releasePath, 'GitPie-darwin-x64.zip'));

  console.log('File', path.join(releasePath, 'GitPie-darwin-x64.zip'), 'created!');
});
<commit_msg>EnsureDirSync for pack:win64 task<commit_after>'use strict';

let gulp = require('gulp');
let sass = require('gulp-sass');
let packager = require('electron-packager');
let path = require('path');

const BUILD_FOLDER = 'build';
const RELEASE_FOLDER = 'release';
const LINUX = 'linux';
const MAC = 'osx';
const WINDOWS = 'windows';
const ARCH_64 = '64bit';
const ARCH_32 = '32bit';

/* Development tasks */
gulp.task('sass', function () {
  gulp.src('./resources/sass/all.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(sass({outputStyle: 'compressed'}))
    .pipe(gulp.dest('./resources/css/'));
});

gulp.task('sass:watch', function () {
  gulp.watch('./resources/sass/**/*.scss', ['sass']);
});

gulp.task('dev', ['sass', 'sass:watch']);

/* Build tasks */
gulp.task('build', ['build:linux', 'build:osx', 'build:win']);

// Linux
gulp.task('build:linux', ['build:linux64', 'build:linux32']);

gulp.task('build:linux64', ['sass'], function () {
  var buildOpts = getDefaultBuildOpts();

  buildOpts.platform = 'linux';
  buildOpts.arch = 'x64';
  buildOpts.out = path.join(BUILD_FOLDER, LINUX, ARCH_64);

  build(buildOpts);
});

gulp.task('build:linux32', ['sass'], function () {
  var buildOpts = getDefaultBuildOpts();

  buildOpts.platform = 'linux';
  buildOpts.arch = 'ia32';
  buildOpts.out = path.join(BUILD_FOLDER, LINUX, ARCH_32);

  build(buildOpts);
});

// Mac
gulp.task('build:osx', ['build:osx64']);

gulp.task('build:osx64', ['sass'], function () {
  var buildOpts = getDefaultBuildOpts();

  buildOpts.platform = 'darwin';
  buildOpts.arch = 'x64';
  buildOpts.out = path.join(BUILD_FOLDER, MAC, ARCH_64);
  buildOpts.icon = 'resources/images/icon.icns';

  build(buildOpts);
});

// Windows
gulp.task('build:win', ['build:win64', 'build:win32']);

gulp.task('build:win64', ['sass'], function () {
  var buildOpts = getDefaultBuildOpts();

  buildOpts.platform = 'win32';
  buildOpts.arch = 'x64';
  buildOpts.out = path.join(BUILD_FOLDER, WINDOWS, ARCH_64);
  buildOpts.icon = 'resources/images/icon.ico';

  build(buildOpts);
});

gulp.task('build:win32', ['sass'], function () {
  var buildOpts = getDefaultBuildOpts();

  buildOpts.platform = 'win32';
  buildOpts.arch = 'ia32';
  buildOpts.out = path.join(BUILD_FOLDER, WINDOWS, ARCH_32);
  buildOpts.icon = 'resources/images/icon.ico';

  build(buildOpts);
});

// Return a default build options to Electron
function getDefaultBuildOpts() {
  return {
    dir: __dirname,
    name: 'GitPie',
    version: '0.36.4',
    ignore: getIgnoreRegex(),

    // Fallbacks

    icon: 'resources/images/icon.png',
    out: BUILD_FOLDER
  };
}

function build(electronBuildOpts) {
  var fs = require('fs-extra');

  fs.removeSync(electronBuildOpts.out);

  packager(electronBuildOpts, function (err, appPath) {

    if (err) {
      console.error('Error building electron application for "' + electronBuildOpts.platform + '" arch "' + electronBuildOpts.arch + '". Error:', err);
    } else {
      console.info('Build application for "' + electronBuildOpts.platform + '" arch "' + electronBuildOpts.arch + '" in ', appPath, ' with success!');
    }
  });
}

function getIgnoreRegex() {
  var pack = require('./package'),
    execSync = require('child_process').execSync,
    nonIgnoredModules = [],
    ignoreRegex =  '^/node_modules/(?!({modules}))';

  function findDependentModule (parentModule, module) {
    var pkgModule;

    if (parentModule == 'main') {
      pkgModule = require('./node_modules/'.concat(module).concat('/package'));
    } else {

      try {
        pkgModule = require('./node_modules/'.concat(module).concat('.').concat(parentModule).concat('/package'));
      } catch (err) {
        pkgModule = require('./node_modules/'.concat(module).concat('/package'));
      }

    }

    nonIgnoredModules.push(module);

    if (Object.keys(pkgModule.dependencies).length > 0) {

      for (var i in pkgModule.dependencies) {
        findDependentModule(module, i);
      }

    }
  }

  var npmVersion = execSync('npm -v'),
    i = 0;

  if (parseInt(npmVersion.toString()[0]) >= 3) {
    // The npm modules structure is flat
    for (i in pack.dependencies) {
      findDependentModule('main', i);
    }
  } else {
    // The npm modules structure is not flat
    for (i in pack.dependencies) {
      nonIgnoredModules.push(i);
    }
  }

  ignoreRegex = ignoreRegex.replace('{modules}', nonIgnoredModules.join('|'));

  return [ignoreRegex, '^/'.concat(BUILD_FOLDER)];
}

/* Packing tasks */

// Windows
gulp.task('pack:win64', function () {
  var electronBuilder = require('electron-builder');
  var fs = require('fs-extra');
  var releasePath = path.join(RELEASE_FOLDER, WINDOWS, ARCH_64);

  fs.ensureDirSync(releasePath);

  console.log('Creating windows inataller on', releasePath, '...');

  electronBuilder.init().build({
    appPath: path.join(BUILD_FOLDER, WINDOWS, ARCH_64, 'GitPie-win32-x64'),
    platform: 'win',
    config: 'app/core/packager/config.json',
    out: releasePath
  },
  function (err) {

    if (err) {
      console.error('Error creating windows installer.', err);
    } else {
      console.log('Windows installer created with success on ', releasePath);
    }
  });
});

// Linux
gulp.task('pack:linux64', function () {
  var electronBuilder = require('electron-builder');
  var fs = require('fs-extra');
  var releasePath = path.join(RELEASE_FOLDER, LINUX, ARCH_64);
  var targz = require('tar.gz');

  fs.ensureDirSync(releasePath);

  console.log('Start compressing ', path.join(BUILD_FOLDER, LINUX, ARCH_64, 'GitPie-linux-x64'), 'please wait...');

  targz().compress(path.join(BUILD_FOLDER, LINUX, ARCH_64, 'GitPie-linux-x64'), path.join(releasePath, 'GitPie-linux-x64.tar.gz'))
    .then(function(){
      console.log('File', path.join(releasePath, 'GitPie-linux-x64.tar.gz'), 'created!');
    })
    .catch(function(err){
      console.error('Error compressing ', path.join(BUILD_FOLDER, WINDOWS, ARCH_64, 'GitPie-linux-x64'), 'folder.', err);
    });
});

// Mac
gulp.task('pack:osx64', function () {
  var electronBuilder = require('electron-builder');
  var fs = require('fs-extra');
  var releasePath = path.join(RELEASE_FOLDER, MAC, ARCH_64);
  var AdmZip = require('adm-zip');
  var zip = new AdmZip();

  fs.ensureDirSync(releasePath);

  console.log('Start compressing ', path.join(BUILD_FOLDER, MAC, ARCH_64, 'GitPie-darwin-x64'), 'please wait...');

  zip.addLocalFolder(path.join(BUILD_FOLDER, MAC, ARCH_64, 'GitPie-darwin-x64'));
  zip.writeZip(path.join(releasePath, 'GitPie-darwin-x64.zip'));

  console.log('File', path.join(releasePath, 'GitPie-darwin-x64.zip'), 'created!');
});
<|endoftext|>


<commit_before>import Vector2 from '../vector2'
// TODO: don't use this directly, refer to it from game so this is completely separate
import {Time} from '../constants/private'

// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf
// See section on "moving objects"

// if it overlaps, return the projection vector
function checkProjectionOverlap(vertices1, vertices2, vector, MTV) {
  let min1 = Infinity
  let max1 = -Infinity
  let min2 = Infinity
  let max2 = -Infinity
  vertices1.map(function(vertex) {
    let val = Vector2.ProjectScalar(vertex, vector)
    min1 = Math.min(min1, val)
    max1 = Math.max(max1, val)
  })

  vertices2.map(function(vertex) {
    let val = Vector2.ProjectScalar(vertex, vector)
    min2 = Math.min(min2, val)
    max2 = Math.max(max2, val)
  })

  // no overlap
  if (min1 > max2 || max1 < min2) {
    return null
  } else {
    // the magnitude of the overlap
    let newMTVMagnitude = Math.min(max2 - min1, max1 - min2)
    let vectorMag = vector.mag()
    // if the magnitude is smaller than the magnitude of the current mtv then we should update
    // the mtv with the new values
    if (newMTVMagnitude < MTV.mag()) {
      MTV.x = vector.x * newMTVMagnitude / vectorMag
      MTV.y = vector.y * newMTVMagnitude / vectorMag
    }
    return MTV
  }
}

// TODO: for now it assumes both gobs are convex
// returns null if no collision, otherwise returns MTV
// http://www.metanetsoftware.com/technique/tutorialA.html
export function SAT(gob1, gob2) {
  // get the absolute vertices
  let vertices1 = gob1.getAABB()
  let vertices2 = gob2.getAABB()

  // gather the normals
  let normals1 = gob1.getNormals()
  let normals2 = gob2.getNormals()

  let MTV = new Vector2(-Infinity, -Infinity)

  // for each normal, project both shapes onto the normal. if they don't intersect, then immediately return false.
  // otherwise return true at the end
  for (let i=0; i<normals1.length; i++) {
    MTV = checkProjectionOverlap(vertices1, vertices2, normals1[i], MTV)
    if (MTV == null) {
      return false
    }
  }

  for (let i=0; i<normals2.length; i++) {
    MTV = checkProjectionOverlap(vertices1, vertices2, normals2[i], MTV)
    if (MTV == null) {
      return false
    }
  }

  // TODO: adjust the gobs here instead of in collision engine
  resolveStaticCollision(gob1, gob2, MTV)

  return true
}

/* resolve a collision between two non-moving objects (project them out) */
export function resolveStaticCollision(gob1, gob2, MTV) {
  // first project gob1 and gob2 out of the MTV by determining the proportion of VELOCITY
  let v1m = gob1.velocity.mag()
  let v2m = gob2.velocity.mag()

  let totalVelocity = v1m + v2m

  let p1 = v1m / (v1m + v2m)
  let p2 = v2m / (v2m + v1m)

  let [left, right] = gob1.position.x >= gob2.position.x ? [gob2, gob1] : [gob1, gob2]
  let [top, bottom] = gob1.position.y >= gob2.position.y ? [gob2, gob1] : [gob1, gob2]

  right.position.x += MTV.x
  bottom.position.y -= MTV.y
}

// TODO: Have restitution ONLY BE APPLIED for normal velocities
// ALSO TODO: Have restitution be applied PER OBJEcT. It's just easier to
// think about that way. if you don't want an object to have energy lost in
// collistion, just set the mass to 0!
// http://vobarian.com/collisions/2dcollisions2.pdf
function adjustPhysicsVelocity(gob1, gob2, normal) {
  // tangent is the normal of the normal
  const tangent = normal.orthol()
  // const unitNormal = Vector2.Normalized(normal)

  const unitNormal = Vector2.Normalized(normal)
  const unitTangent = Vector2.Normalized(tangent)

  const m1 = gob1.mass
  const m2 = gob2.mass

  const v1n = Vector2.ProjectScalar(gob1.velocity, normal)
  const v1t = Vector2.ProjectScalar(gob1.velocity, tangent)
  const v2n = Vector2.ProjectScalar(gob2.velocity, normal)
  const v2t = Vector2.ProjectScalar(gob2.velocity, tangent)

  // coefficient of restitution == bounciness
  // See http://www.box2dflash.org/docs/2.0.2/manual#Friction_and_Restitution
  // const restitution = Math.max(gob1.bounce, gob2.bounce)
  //
  // Instead of having a single restitution value, we just use restitution
  // per object. this way, dev's can more easily imagine how individual objects
  // are affected by collisions
  //
  // (v1n * (m1 - m2) + 2 * m2 * v2n) / (m1 + m2)
  const newv1n = gob1.bounce * (v1n * (m1 - m2) + 2 * m2 * v2n) / (m1 + m2)
  // for tangent values, we need to adjust it according to the friction
  const newv1t = Math.max(0, 1 - gob1.friction) * v1t

  // coefficient of restitution == bounciness
  // (v2n * (m2 - m1) + 2 * m1 * v1n) / (m1 + m2)
  const newv2n = gob2.bounce * (v2n * (m2 - m1) + 2 * m1 * v1n) / (m1 + m2)
  const newv2t = Math.max(0, 1 - gob2.friction) * v2t

  // find the new velocity normals
  const newVelocityNormal1 = Vector2.Multiply(unitNormal, newv1n)
  const newVelocityNormal2 = Vector2.Multiply(unitNormal, newv2n)

  const newVelocityTangent1 = Vector2.Multiply(unitTangent, newv1t)
  const newVelocityTangent2 = Vector2.Multiply(unitTangent, newv2t)

  const newVelocity1 = Vector2.Add(newVelocityNormal1, newVelocityTangent1)
  const newVelocity2 = Vector2.Add(newVelocityNormal2, newVelocityTangent2)

  gob1.velocity.x = newVelocity1.x
  gob1.velocity.y = newVelocity1.y
  gob2.velocity.x = newVelocity2.x
  gob2.velocity.y = newVelocity2.y
}

// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf
export function movingSAT(gob1, gob2) {
  // get the absolute vertices
  let vertices1 = gob1.getAABB()
  let vertices2 = gob2.getAABB()

  // velocity
  const velocity = Vector2.Subtract(gob2._velocity, gob1._velocity)

  // gather the normals
  let normals = gob1.getNormals().concat(gob2.getNormals())

  let MTV = new Vector2(-Infinity, -Infinity)

  // we are calculating in terms of a single timestep
  let TMax = Time.dts
  let TFirst = 0
  let TLast = Infinity

  let Length = 0

  // if there has been a collision, calculate how far it's gone
  let TElapsed = 0
  let edge1
  let edge2
  let normal

  // for each normal, project both shapes onto the normal.
  // if they don't intersect, then immediately return false.
  // otherwise return true at the end
  for (let i=0; i<normals.length; i++) {
    // speed is the projection of the speed onto the normal
    const speed = Vector2.ProjectScalar(velocity, normals[i])
    let T

    let min0 = Infinity
    let max0 = -Infinity
    let min1 = Infinity
    let max1 = -Infinity

    let min0Index = Infinity
    let max0Index = -Infinity
    let min1Index = Infinity
    let max1Index = -Infinity

    // get max and min of collision
    vertices1.map(function(vertex, index) {
      let val = Vector2.ProjectScalar(vertex, normals[i])
      min0 = Math.min(min0, val)
      if (val > max0) {
        max0 = val
        max0Index = index
      }
    })

    vertices2.map(function(vertex, index) {
      let val = Vector2.ProjectScalar(vertex, normals[i])
      min1 = Math.min(min1, val)
      if (val > max1) {
        max1 = val
        max1Index = index
      }
    })

    if (max1 < min0) {
      if (speed <= 0) {
        return false
      }
      T = (min0 - max1) / speed
      if (T > TFirst) {
        TFirst = T
        Length = min0 - max1
        normal = normals[i]
      }
      if (TFirst > TMax) {
        return false
      }
      T = (max0 - min1) / speed
      if (T < TLast) {
        TLast = T
      }
      if (TFirst > TLast) {
        return false
      }
    } else if (max0 < min1) {
      if (speed >= 0) {
        return false
      }
      T = (max0 - min1) / speed
      if (T > TFirst) {
        TFirst = T
        Length = max0 - min1
        normal = normals[i]
      }
      if (TFirst > TMax) {
        return false
      }
      T = (min0 - max1) / speed
      if (T < TLast) {
        TLast = T
      }
      if (TFirst > TLast) {
        return false
      }
    } else {
      if (speed > 0) {
        T = (max0 - min1) / speed
        if (T < TLast) {
          TLast = T
        }
        if (TFirst > TLast) {
          return false
        }
      } else if (speed < 0) {
        T = (min0 - max1) / speed
        if (T < TLast) {
          TLast = T
        }
        if (TFirst > TLast) {
          return false
        }
      }
    }

    // set TElapsed
    // the minimum of the two possible overlaps, to determine actual overlap
    if (speed !== 0) {
      TElapsed = Math.min(Math.abs(max0 - min1), Math.abs(max1 - min0)) / Math.abs(speed)
    }
  }

  // collided! we limit the gob resolution to this method, to preserve modularity
  if (TFirst !== 0) {
    // it's going to collide with this next move. in this case, we need to
    // adjust so it doesn't overshoot.

    // TODO: THIS IS A HACK. we set it to be at TFirst - 0.0001 so that there
    // isn't going to be an overlap
    gob1.position.y = gob1._position.y + gob1._velocity.y  * (TFirst - 0.0001)
    gob1.position.x = gob1._position.x + gob1._velocity.x  * (TFirst - 0.0001)

    gob2.position.y = gob2._position.y + gob2._velocity.y  * (TFirst - 0.0001)
    gob2.position.x = gob2._position.x + gob2._velocity.x  * (TFirst - 0.0001)

    adjustPhysicsVelocity(gob1, gob2, normal)

    // TODO: a leftover of (Time.dts - (TFirst - 0.0001)) of new velocity for
    // each will be lost
  }

  return true
}
<commit_msg>fix the collision detection<commit_after>import Vector2 from '../vector2'
// TODO: don't use this directly, refer to it from game so this is completely separate
import {Time} from '../constants/private'

// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf
// See section on "moving objects"

// if it overlaps, return the projection vector
function checkProjectionOverlap(vertices1, vertices2, vector, MTV) {
  let min1 = Infinity
  let max1 = -Infinity
  let min2 = Infinity
  let max2 = -Infinity
  vertices1.map(function(vertex) {
    let val = Vector2.ProjectScalar(vertex, vector)
    min1 = Math.min(min1, val)
    max1 = Math.max(max1, val)
  })

  vertices2.map(function(vertex) {
    let val = Vector2.ProjectScalar(vertex, vector)
    min2 = Math.min(min2, val)
    max2 = Math.max(max2, val)
  })

  // no overlap
  if (min1 > max2 || max1 < min2) {
    return null
  } else {
    // the magnitude of the overlap
    let newMTVMagnitude = Math.min(max2 - min1, max1 - min2)
    let vectorMag = vector.mag()
    // if the magnitude is smaller than the magnitude of the current mtv then we should update
    // the mtv with the new values
    if (newMTVMagnitude < MTV.mag()) {
      MTV.x = vector.x * newMTVMagnitude / vectorMag
      MTV.y = vector.y * newMTVMagnitude / vectorMag
    }
    return MTV
  }
}

// TODO: for now it assumes both gobs are convex
// returns null if no collision, otherwise returns MTV
// http://www.metanetsoftware.com/technique/tutorialA.html
export function SAT(gob1, gob2) {
  // get the absolute vertices
  let vertices1 = gob1.getAABB()
  let vertices2 = gob2.getAABB()

  // gather the normals
  let normals1 = gob1.getNormals()
  let normals2 = gob2.getNormals()

  let MTV = new Vector2(-Infinity, -Infinity)

  // for each normal, project both shapes onto the normal. if they don't intersect, then immediately return false.
  // otherwise return true at the end
  for (let i=0; i<normals1.length; i++) {
    MTV = checkProjectionOverlap(vertices1, vertices2, normals1[i], MTV)
    if (MTV == null) {
      return false
    }
  }

  for (let i=0; i<normals2.length; i++) {
    MTV = checkProjectionOverlap(vertices1, vertices2, normals2[i], MTV)
    if (MTV == null) {
      return false
    }
  }

  // TODO: adjust the gobs here instead of in collision engine
  resolveStaticCollision(gob1, gob2, MTV)

  return true
}

/* resolve a collision between two non-moving objects (project them out) */
export function resolveStaticCollision(gob1, gob2, MTV) {
  // first project gob1 and gob2 out of the MTV by determining the proportion of VELOCITY
  let v1m = gob1.velocity.mag()
  let v2m = gob2.velocity.mag()

  let totalVelocity = v1m + v2m

  let p1 = v1m / (v1m + v2m)
  let p2 = v2m / (v2m + v1m)

  let [left, right] = gob1.position.x >= gob2.position.x ? [gob2, gob1] : [gob1, gob2]
  let [top, bottom] = gob1.position.y >= gob2.position.y ? [gob2, gob1] : [gob1, gob2]

  right.position.x += MTV.x
  bottom.position.y -= MTV.y
}

// TODO: Have restitution ONLY BE APPLIED for normal velocities
// ALSO TODO: Have restitution be applied PER OBJEcT. It's just easier to
// think about that way. if you don't want an object to have energy lost in
// collistion, just set the mass to 0!
// http://vobarian.com/collisions/2dcollisions2.pdf
function adjustPhysicsVelocity(gob1, gob2, normal) {
  // tangent is the normal of the normal
  const tangent = normal.orthol()
  // const unitNormal = Vector2.Normalized(normal)

  const unitNormal = Vector2.Normalized(normal)
  const unitTangent = Vector2.Normalized(tangent)

  const m1 = gob1.mass
  const m2 = gob2.mass

  const v1n = Vector2.ProjectScalar(gob1.velocity, normal)
  const v1t = Vector2.ProjectScalar(gob1.velocity, tangent)
  const v2n = Vector2.ProjectScalar(gob2.velocity, normal)
  const v2t = Vector2.ProjectScalar(gob2.velocity, tangent)

  // coefficient of restitution == bounciness
  // See http://www.box2dflash.org/docs/2.0.2/manual#Friction_and_Restitution
  // const restitution = Math.max(gob1.bounce, gob2.bounce)
  //
  // Instead of having a single restitution value, we just use restitution
  // per object. this way, dev's can more easily imagine how individual objects
  // are affected by collisions
  //
  // (v1n * (m1 - m2) + 2 * m2 * v2n) / (m1 + m2)
  const newv1n = gob1.bounce * (v1n * (m1 - m2) + 2 * m2 * v2n) / (m1 + m2)
  // for tangent values, we need to adjust it according to the friction
  const newv1t = (1 - Math.max(0, 1 - gob1.friction)) * v1t

  // coefficient of restitution == bounciness
  // (v2n * (m2 - m1) + 2 * m1 * v1n) / (m1 + m2)
  const newv2n = gob2.bounce * (v2n * (m2 - m1) + 2 * m1 * v1n) / (m1 + m2)
  const newv2t = (1 - Math.max(0, 1 - gob2.friction)) * v2t

  // find the new velocity normals
  const newVelocityNormal1 = Vector2.ScalarProduct(unitNormal, newv1n)
  const newVelocityNormal2 = Vector2.ScalarProduct(unitNormal, newv2n)

  const newVelocityTangent1 = Vector2.ScalarProduct(unitTangent, newv1t)
  const newVelocityTangent2 = Vector2.ScalarProduct(unitTangent, newv2t)

  const newVelocity1 = Vector2.Sum(newVelocityNormal1, newVelocityTangent1)
  const newVelocity2 = Vector2.Sum(newVelocityNormal2, newVelocityTangent2)

  gob1.velocity.x = newVelocity1.x
  gob1.velocity.y = newVelocity1.y
  gob2.velocity.x = newVelocity2.x
  gob2.velocity.y = newVelocity2.y
}

// https://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf
export function movingSAT(gob1, gob2) {
  // get the absolute vertices
  let vertices1 = gob1.getAABB()
  let vertices2 = gob2.getAABB()

  // velocity
  const velocity = Vector2.Difference(gob2._velocity, gob1._velocity)

  // gather the normals
  let normals = gob1.getNormals().concat(gob2.getNormals())

  let MTV = new Vector2(-Infinity, -Infinity)

  // we are calculating in terms of a single timestep
  let TMax = Time.dts
  let TFirst = 0
  let TLast = Infinity

  let Length = 0

  // if there has been a collision, calculate how far it's gone
  let TElapsed = 0
  let edge1
  let edge2
  let normal

  // for each normal, project both shapes onto the normal.
  // if they don't intersect, then immediately return false.
  // otherwise return true at the end
  for (let i=0; i<normals.length; i++) {
    // speed is the projection of the speed onto the normal
    const speed = Vector2.ProjectScalar(velocity, normals[i])
    let T

    let min0 = Infinity
    let max0 = -Infinity
    let min1 = Infinity
    let max1 = -Infinity

    let min0Index = Infinity
    let max0Index = -Infinity
    let min1Index = Infinity
    let max1Index = -Infinity

    // get max and min of collision
    vertices1.map(function(vertex, index) {
      let val = Vector2.ProjectScalar(vertex, normals[i])
      min0 = Math.min(min0, val)
      if (val > max0) {
        max0 = val
        max0Index = index
      }
    })

    vertices2.map(function(vertex, index) {
      let val = Vector2.ProjectScalar(vertex, normals[i])
      min1 = Math.min(min1, val)
      if (val > max1) {
        max1 = val
        max1Index = index
      }
    })

    if (max1 < min0) {
      if (speed <= 0) {
        return false
      }
      T = (min0 - max1) / speed
      if (T > TFirst) {
        TFirst = T
        Length = min0 - max1
        normal = normals[i]
      }
      if (TFirst > TMax) {
        return false
      }
      T = (max0 - min1) / speed
      if (T < TLast) {
        TLast = T
      }
      if (TFirst > TLast) {
        return false
      }
    } else if (max0 < min1) {
      if (speed >= 0) {
        return false
      }
      T = (max0 - min1) / speed
      if (T > TFirst) {
        TFirst = T
        Length = max0 - min1
        normal = normals[i]
      }
      if (TFirst > TMax) {
        return false
      }
      T = (min0 - max1) / speed
      if (T < TLast) {
        TLast = T
      }
      if (TFirst > TLast) {
        return false
      }
    } else {
      if (speed > 0) {
        T = (max0 - min1) / speed
        if (T < TLast) {
          TLast = T
        }
        if (TFirst > TLast) {
          return false
        }
      } else if (speed < 0) {
        T = (min0 - max1) / speed
        if (T < TLast) {
          TLast = T
        }
        if (TFirst > TLast) {
          return false
        }
      }
    }

    // set TElapsed
    // the minimum of the two possible overlaps, to determine actual overlap
    if (speed !== 0) {
      TElapsed = Math.min(Math.abs(max0 - min1), Math.abs(max1 - min0)) / Math.abs(speed)
    }
  }

  // collided! we limit the gob resolution to this method, to preserve modularity
  if (TFirst !== 0) {
    // it's going to collide with this next move. in this case, we need to
    // adjust so it doesn't overshoot.

    // TODO: THIS IS A HACK. we set it to be at TFirst - 0.0001 so that there
    // isn't going to be an overlap
    gob1.position.y = gob1._position.y + gob1._velocity.y  * (TFirst - 0.0001)
    gob1.position.x = gob1._position.x + gob1._velocity.x  * (TFirst - 0.0001)

    gob2.position.y = gob2._position.y + gob2._velocity.y  * (TFirst - 0.0001)
    gob2.position.x = gob2._position.x + gob2._velocity.x  * (TFirst - 0.0001)

    adjustPhysicsVelocity(gob1, gob2, normal)

    // TODO: a leftover of (Time.dts - (TFirst - 0.0001)) of new velocity for
    // each will be lost
  }

  return true
}
<|endoftext|>


<commit_before>var tokens = require('./setupenv')
var request = require('supertest')
var app = require('../app')
var log = require('winston')
let chai = require('chai');
let chaiHttp = require('chai-http');
let should = chai.should();
chai.use(chaiHttp);

describe('Cases', () => {
  describe('Lookup', () => {
    it('finds case 100', (done) => {
      chai.request(app)
        .get('/case/100')
        .set('Content-Type', 'application/json')
        .set('Accept', 'application/json')
        .send({
        })
        .end((err, res) => {
          res.should.have.status(200);
          done();
        });
    })
  })
  describe('Adding', () => {
    it('fails without authentication', (done) => {
      chai.request(app)
        .post('/case/new')
        .set('Content-Type', 'application/json')
        .set('Accept', 'application/json')
        .send({
        })
        .end((err, res) => {
          res.should.have.status(401);
          done();
        });
    });
    it('works with authentication', (done) => {
      chai.request(app)
        .post('/case/new')
        .set('Content-Type', 'application/json')
        .set('Accept', 'application/json')
        .set('Authorization', 'Bearer ' + tokens.user_token)
        .send({
        })
        .end((err, res) => {
          res.should.have.status(200);
          done();
        });
    });
  });
  let userID = tokens.user_payload.user_id;
  describe('Counting by country', () => {
    it('returns stuff', (done) => {
      chai.request(app)
        .get('/case/countsByCountry')
        .set('Content-Type', 'application/json')
        .set('Accept', 'application/json')
        .set('Authorization', 'Bearer ' + tokens.user_token)
        .end((err, res) => {
          var countryCounts = res.body.data.countryCounts
          countryCounts.should.have.property('france')
          res.should.have.status(200);
          done();
        });
    });
  })
  describe('Get case with tags', () => {
    if('This case should have 3 tags', (done) => {
      chai.request(app)
        .get('/case/39')
        .set('Content-Type', 'application/json')
        .set('Accept', 'application/json')
        .end((err, res) => {
            res.body.OK.should.be.equal.to('true')
            res.should.have.status(200);
            let the_case = res.body.data;
            the_case.tags.should.have.lengthOf(3);
            done();
        });
    });
  })
})
<commit_msg>fix test with it vs. if<commit_after>var tokens = require('./setupenv')
var request = require('supertest')
var app = require('../app')
var log = require('winston')
let chai = require('chai');
let chaiHttp = require('chai-http');
let should = chai.should();
chai.use(chaiHttp);

describe('Cases', () => {
  describe('Lookup', () => {
    it('finds case 100', (done) => {
      chai.request(app)
        .get('/case/100')
        .set('Content-Type', 'application/json')
        .set('Accept', 'application/json')
        .send({
        })
        .end((err, res) => {
          res.should.have.status(200);
          done();
        });
    });
  });
  describe('Adding', () => {
    it('fails without authentication', (done) => {
      chai.request(app)
        .post('/case/new')
        .set('Content-Type', 'application/json')
        .set('Accept', 'application/json')
        .send({
        })
        .end((err, res) => {
          res.should.have.status(401);
          done();
        });
    });
    it('works with authentication', (done) => {
      chai.request(app)
        .post('/case/new')
        .set('Content-Type', 'application/json')
        .set('Accept', 'application/json')
        .set('Authorization', 'Bearer ' + tokens.user_token)
        .send({
        })
        .end((err, res) => {
          res.should.have.status(200);
          done();
        });
    });
  });
  let userID = tokens.user_payload.user_id;
  describe('Counting by country', () => {
    it('returns stuff', (done) => {
      chai.request(app)
        .get('/case/countsByCountry')
        .set('Content-Type', 'application/json')
        .set('Accept', 'application/json')
        .set('Authorization', 'Bearer ' + tokens.user_token)
        .end((err, res) => {
          var countryCounts = res.body.data.countryCounts
          countryCounts.should.have.property('france')
          res.should.have.status(200);
          done();
        });
    });
  });
  describe('Get case with tags', () => {
    it('should have 3 tags', (done) => {
      chai.request(app)
        .get('/case/39')
        .set('Content-Type', 'application/json')
        .set('Accept', 'application/json')
        .end((err, res) => {
            res.body.OK.should.equal(true)
            res.should.have.status(200);
            let the_case = res.body.data;
            the_case.tags.should.have.lengthOf(3);
            done();
        });
    });
  });
});
<|endoftext|>