<commit_before>/*
	Line

	Will create a new line at a specified area. If it is beyond the length of the current lines then it will be created at the end.
*/
define([
	'events/ClickHandler',
	'coords/getOffsetFromClick',
	'libs/pubsub',
	'utils/text/insertCharacter'

	], function(
		ClickHandler,
		getOffsetFromClick,
		pubsub,
		insertCharacter
		) {

		debugger;

	var Line = function(linePosition) {
		this.linePosition = linePosition;
		this.node = document.createElement('div');
		this.innerLine = document.createElement('div');
		this.innerLine.classList.add('nonrte-line-inner');
		this.node.classList.add('nonrte-line');
		this.textNode = document.createTextNode('');
		this.node.appendChild(this.innerLine);
		this.innerLine.appendChild(this.textNode);

		this.lineSegmentData = [
			{
				text: '',
				wrappers: []
			}
		];

		ClickHandler(this.innerLine, this.lineClickHandle.bind(this));

		return this;
	};

	Line.prototype.getNode = function() {
		return this.node;
	};

	Line.prototype.getTextNode = function() {
		return this.textNode;
	};

	Line.prototype.getLineNode = function() {
		return this.innerLine;
	};

	Line.prototype.dataLength = function() {
		return this.textNode.data.length;
	};

	Line.prototype.setLineData = function(textData) {
		this.textNode.data = textData;
	};

	Line.prototype.setLineHtml = function(html) {
		this.getLineNode().innerHTML = html;
	}

	Line.prototype.insertCharacter = function(character, position) {
		var op = {},
			lineOffset = 0,
			insertAtIndex = insertCharacter;
		if (this.lineSegmentData.length == 0) {
			op.text = character;
			op.wrappers = [];
		} else {
			this.lineSegmentData.forEach(function(lineSegment) {
				var offset = lineOffset + lineSegment.text.length,
					insert;
				if (offset >= position) {
					insert = offset - position;
					lineSegment.text = insertAtIndex(lineSegment.text, insert, character);
				}
			})
		}
	}

	Line.prototype.getLineData = function() {
		return this.textNode.data;
	};
	Line.prototype.getLineDataSegments = function() {
		return this.lineSegmentData;
	}

	//THIS IS REALLY BAD AND PROBABLY SHOULDNT BE IN EXISTENCE
	Line.prototype.getPosition = function() {
		return this.linePosition;
	};

	Line.prototype.getLineHeight = function(characterPosition) {
		//In the future we need to adjust based upon what character the cursor is next to
		return this.innerLine.clientHeight;
	}

	Line.prototype.lineClickHandle = function(e) {
		var offset = getOffsetFromClick(this.textNode.data, {x: e.offsetX, y: e.offsetY});
		offset.offsetX += 2;
		var message = {
			line : this,
			original : e,
			offsets : {x : e.offsetX, y : e.offsetY},
			characterOffset : offset

		};
		pubsub.publish('lineClick', message);

	}

	return Line;

});<commit_msg>Update Line.js<commit_after>/*
	Line

	Will create a new line at a specified area. If it is beyond the length of the current lines then it will be created at the end.
*/
define([
	'events/ClickHandler',
	'coords/getOffsetFromClick',
	'libs/pubsub',
	'utils/text/insertCharacter'

	], function(
		ClickHandler,
		getOffsetFromClick,
		pubsub,
		insertCharacter
		) {

		debugger;

	var Line = function(linePosition) {
		this.linePosition = linePosition;
		this.node = document.createElement('div');
		this.innerLine = document.createElement('div');
		this.innerLine.classList.add('nonrte-line-inner');
		this.node.classList.add('nonrte-line');
		this.textNode = document.createTextNode('');
		this.node.appendChild(this.innerLine);
		this.innerLine.appendChild(this.textNode);

		this.lineSegmentData = [
			{
				text: '',
				wrappers: []
			}
		];

		ClickHandler(this.innerLine, this.lineClickHandle.bind(this));

		return this;
	};

	Line.prototype.getNode = function() {
		return this.node;
	};

	Line.prototype.getTextNode = function() {
		return this.textNode;
	};

	Line.prototype.getLineNode = function() {
		return this.innerLine;
	};

	Line.prototype.dataLength = function() {
		return this.textNode.data.length;
	};

	Line.prototype.setLineData = function(textData) {
		this.textNode.data = textData;
	};

	Line.prototype.setLineHtml = function(html) {
		this.getLineNode().innerHTML = html;
	}

	Line.prototype.insertCharacter = function(character, position) {
		var op = {},
			lineOffset = 0,
			insertAtIndex = insertCharacter;
		if (this.lineSegmentData.length == 0) {
			op.text = character;
			op.wrappers = [];
		} else {
			this.lineSegmentData.forEach(function(lineSegment) {
				var offset = lineOffset = lineOffset + lineSegment.text.length,
					insert;
				if (offset >= position) {
					insert = offset - position;
					lineSegment.text = insertAtIndex(lineSegment.text, insert, character);
				}
			})
		}
	}

	Line.prototype.getLineData = function() {
		return this.textNode.data;
	};
	Line.prototype.getLineDataSegments = function() {
		return this.lineSegmentData;
	}

	//THIS IS REALLY BAD AND PROBABLY SHOULDNT BE IN EXISTENCE
	Line.prototype.getPosition = function() {
		return this.linePosition;
	};

	Line.prototype.getLineHeight = function(characterPosition) {
		//In the future we need to adjust based upon what character the cursor is next to
		return this.innerLine.clientHeight;
	}

	Line.prototype.lineClickHandle = function(e) {
		var offset = getOffsetFromClick(this.textNode.data, {x: e.offsetX, y: e.offsetY});
		offset.offsetX += 2;
		var message = {
			line : this,
			original : e,
			offsets : {x : e.offsetX, y : e.offsetY},
			characterOffset : offset

		};
		pubsub.publish('lineClick', message);

	}

	return Line;

});
<|endoftext|>


<commit_before>/* global Mousetrap */

import Ember from 'ember';

export default function(bindEvent, unbindEvent) {

  return Ember.Mixin.create({

    bindShortcuts: function() {
      var self = this;
      var shortcuts = this.get('keyboardShortcuts');

      if (Ember.typeOf(shortcuts) !== 'object') { return; }

      this.mousetraps = [];

      Object.keys(shortcuts).forEach(function(shortcut) {
        var actionObject   = shortcuts[shortcut];
        var mousetrap      = new Mousetrap(document.body);
        var preventDefault = true;

        function invokeAction(action) {
          var type = Ember.typeOf(action);

          if (type === 'string') {
            mousetrap.bind(shortcut, function(){
              self.send(action);
              return preventDefault !== true;
            });
          }
          else if (type === 'function') {
            mousetrap.bind(shortcut, action.bind(self));
          }
          else {
            throw new Error('Invalid value for keyboard shortcut: ' + action);
          }
        }

        if (Ember.typeOf(actionObject) === 'object') {
          if (actionObject.global === false) {
            mousetrap = new Mousetrap(document);
          } else if (actionObject.scoped) {
            mousetrap = new Mousetrap(self.get('element'));
          } else if (actionObject.targetElement) {
            mousetrap = new Mousetrap(actionObject.targetElement);
          }

          if (actionObject.preventDefault === false) {
            preventDefault = false;
          }

          invokeAction(actionObject.action);
        } else {
          invokeAction(actionObject);
        }

        self.mousetraps.push(mousetrap);

      });

    }.on(bindEvent),

    unbindShortcuts: function() {
      this.mousetraps.forEach(
        (mousetrap) => mousetrap.reset()
      );
    }.on(unbindEvent)

  });

}
<commit_msg>Removed usage of prototype extensions<commit_after>/* global Mousetrap */

import Ember from 'ember';

export default function(bindEvent, unbindEvent) {

  return Ember.Mixin.create({

    bindShortcuts: Ember.on(bindEvent, function() {
      var self = this;
      var shortcuts = this.get('keyboardShortcuts');

      if (Ember.typeOf(shortcuts) !== 'object') { return; }

      this.mousetraps = [];

      Object.keys(shortcuts).forEach(function(shortcut) {
        var actionObject   = shortcuts[shortcut];
        var mousetrap      = new Mousetrap(document.body);
        var preventDefault = true;

        function invokeAction(action) {
          var type = Ember.typeOf(action);

          if (type === 'string') {
            mousetrap.bind(shortcut, function(){
              self.send(action);
              return preventDefault !== true;
            });
          }
          else if (type === 'function') {
            mousetrap.bind(shortcut, action.bind(self));
          }
          else {
            throw new Error('Invalid value for keyboard shortcut: ' + action);
          }
        }

        if (Ember.typeOf(actionObject) === 'object') {
          if (actionObject.global === false) {
            mousetrap = new Mousetrap(document);
          } else if (actionObject.scoped) {
            mousetrap = new Mousetrap(self.get('element'));
          } else if (actionObject.targetElement) {
            mousetrap = new Mousetrap(actionObject.targetElement);
          }

          if (actionObject.preventDefault === false) {
            preventDefault = false;
          }

          invokeAction(actionObject.action);
        } else {
          invokeAction(actionObject);
        }

        self.mousetraps.push(mousetrap);

      });

    }),

    unbindShortcuts: Ember.on(unbindEvent, function() {
      this.mousetraps.forEach(
        (mousetrap) => mousetrap.reset()
      );
    })

  });

}
<|endoftext|>


<commit_before>import { registerAsyncHelper } from '@ember/test';
import { triggerEvent } from '@ember/test-helpers';

/**
  Drags elements by an offset specified in pixels.

  Examples

      drag(
        'mouse',
        '.some-list li[data-item=uno]',
        function() {
          return { dy: 50, dx: 20 };
        }
      );

  @method drag
  @param {'mouse'|'touch'} [mode]
    event mode
  @param {String} [itemSelector]
    selector for the element to drag
  @param {Function} [offsetFn]
    function returning the offset by which to drag
  @param {Object} [callbacks]
    callbacks that are fired at the different stages of the interaction
  @return {Promise}
*/

export function drag(app, mode, itemSelector, offsetFn, callbacks = {}) {
  let start, move, end, which;

  const {
    andThen,
    findWithAssert,
    wait
  } = app.testHelpers;

  if (mode === 'mouse') {
    start = 'mousedown';
    move = 'mousemove';
    end = 'mouseup';
    which = 1;
  } else if (mode === 'touch') {
    start = 'touchstart';
    move = 'touchmove';
    end = 'touchend';
  } else {
    throw new Error(`Unsupported mode: '${mode}'`);
  }

  andThen(() => {
    let item = findWithAssert(itemSelector);
    let itemOffset = item.offset();
    let offset = offsetFn();
    let itemElement = item.get(0);
    let rect = itemElement.getBoundingClientRect();
    // firefox gives some elements, like <svg>, a clientHeight of 0.
    // we can try to grab it off the parent instead to have a better
    // guess at what the scale is.
    // https://bugzilla.mozilla.org/show_bug.cgi?id=874811#c14
    // https://stackoverflow.com/a/13647345
    // https://stackoverflow.com/a/5042051
    let dx = offset.dx || 0;
    let dy = offset.dy || 0;
    let clientHeight = (itemElement.clientHeight || item.offsetHeight) || itemElement.parentNode.offsetHeight;
    let scale = clientHeight / (rect.bottom - rect.top);
    let halfwayX = itemOffset.left + (dx * scale) / 2;
    let halfwayY = itemOffset.top + (dy * scale) / 2;
    let targetX = itemOffset.left + (dx * scale);
    let targetY = itemOffset.top + (dy * scale);

    andThen(() => {
      triggerEvent(itemElement, start, {
        clientX: itemOffset.left,
        clientY: itemOffset.top,
        which
      });
    });

    if (callbacks.dragstart) {
      andThen(callbacks.dragstart);
    }

    andThen(() => {
      triggerEvent(itemElement, move, {
        clientX: itemOffset.left,
        clientY: itemOffset.top
      });
    });

    if (callbacks.dragmove) {
      andThen(callbacks.dragmove);
    }

    andThen(() => {
      triggerEvent(itemElement, move, {
        clientX: halfwayX,
        clientY: halfwayY
      });
    });

    andThen(() => {
      triggerEvent(itemElement, move, {
        clientX: targetX,
        clientY: targetY
      });
    });

    andThen(() => {
      triggerEvent(itemElement, end, {
        clientX: targetX,
        clientY: targetY
      });
    });

    if (callbacks.dragend) {
      andThen(callbacks.dragend);
    }
  });

  return wait();
}

export default registerAsyncHelper('drag', drag);
<commit_msg>[bugfix] Tweak clientHeight calculation in drag helper<commit_after>import { registerAsyncHelper } from '@ember/test';
import { triggerEvent } from '@ember/test-helpers';

/**
  Drags elements by an offset specified in pixels.

  Examples

      drag(
        'mouse',
        '.some-list li[data-item=uno]',
        function() {
          return { dy: 50, dx: 20 };
        }
      );

  @method drag
  @param {'mouse'|'touch'} [mode]
    event mode
  @param {String} [itemSelector]
    selector for the element to drag
  @param {Function} [offsetFn]
    function returning the offset by which to drag
  @param {Object} [callbacks]
    callbacks that are fired at the different stages of the interaction
  @return {Promise}
*/

export function drag(app, mode, itemSelector, offsetFn, callbacks = {}) {
  let start, move, end, which;

  const {
    andThen,
    findWithAssert,
    wait
  } = app.testHelpers;

  if (mode === 'mouse') {
    start = 'mousedown';
    move = 'mousemove';
    end = 'mouseup';
    which = 1;
  } else if (mode === 'touch') {
    start = 'touchstart';
    move = 'touchmove';
    end = 'touchend';
  } else {
    throw new Error(`Unsupported mode: '${mode}'`);
  }

  andThen(() => {
    let item = findWithAssert(itemSelector);
    let itemOffset = item.offset();
    let offset = offsetFn();
    let itemElement = item.get(0);
    let rect = itemElement.getBoundingClientRect();
    // firefox gives some elements, like <svg>, a clientHeight of 0.
    // we can try to grab it off the parent instead to have a better
    // guess at what the scale is.
    // https://bugzilla.mozilla.org/show_bug.cgi?id=874811#c14
    // https://stackoverflow.com/a/13647345
    // https://stackoverflow.com/a/5042051
    let dx = offset.dx || 0;
    let dy = offset.dy || 0;
    let clientHeight = itemElement.clientHeight || itemElement.offsetHeight || itemElement.parentNode.offsetHeight;
    let scale = clientHeight / (rect.bottom - rect.top);
    let halfwayX = itemOffset.left + (dx * scale) / 2;
    let halfwayY = itemOffset.top + (dy * scale) / 2;
    let targetX = itemOffset.left + (dx * scale);
    let targetY = itemOffset.top + (dy * scale);

    andThen(() => {
      triggerEvent(itemElement, start, {
        clientX: itemOffset.left,
        clientY: itemOffset.top,
        which
      });
    });

    if (callbacks.dragstart) {
      andThen(callbacks.dragstart);
    }

    andThen(() => {
      triggerEvent(itemElement, move, {
        clientX: itemOffset.left,
        clientY: itemOffset.top
      });
    });

    if (callbacks.dragmove) {
      andThen(callbacks.dragmove);
    }

    andThen(() => {
      triggerEvent(itemElement, move, {
        clientX: halfwayX,
        clientY: halfwayY
      });
    });

    andThen(() => {
      triggerEvent(itemElement, move, {
        clientX: targetX,
        clientY: targetY
      });
    });

    andThen(() => {
      triggerEvent(itemElement, end, {
        clientX: targetX,
        clientY: targetY
      });
    });

    if (callbacks.dragend) {
      andThen(callbacks.dragend);
    }
  });

  return wait();
}

export default registerAsyncHelper('drag', drag);
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Mixin.create({
    modelReady: false,
    history: Ember.inject.service('history'),
    states: Ember.A([]),
    isGroupClosed: false,
    track: function(){
        this.set('states', Ember.A([]));
        this.set('modelReady', true);
        this.set('isGroupClosed', false);

        this.eachAttribute((name, meta) => {
            if(meta.options.stateless === true) return ;
            this.addObserver(name, () => {
                this.propObserver(name, meta);
            });
        });

        this.eachRelationship((name, descriptor) => {
            if(descriptor.options.stateless === true) return ;

            this.addObserver(name, () => {
                this.propObserver(name, descriptor);
            });

            if(descriptor.kind === 'hasMany' && Ember.isPresent(this.get(name).get('content'))){
                this.get(name).get('content').forEach(r => {
                    if(r.track) {
                        r.track();
                    }
                });
            }

            if(descriptor.kind === 'belongsTo'){
                if(this.get(name).track) {
                    this.get(name).track();
                }
            }
        });
    },

    propObserver: function(key, meta){
        var relation = this.get(key);
        var changedAttr = this.changedAttributes();

        if(Object.keys(changedAttr).length === 0 && !meta.kind) {
            return;
        }

        if(changedAttr[key]){
            var groupName = meta.options && meta.options.group;
            var stackable = meta.options && meta.options.sackable;
            var historyLastModel = this.get('history.stack.lastObject');

            var state = {
                key: key,
                type: 'attr',
                change: this.get(key),
                model: this
            };

            if ((stackable || (groupName && !this.get('isGroupClosed'))) && historyLastModel == this) {
                //if this is the current model in history update the records and get the prev one as history back value
                var prevState = this.getPrevState(key);
                if (groupName) {
                    this.updateLastGroup(state, groupName);
                }else {
                    //if we recording in this last state get the prev one as base value
                    state.value = (prevState && prevState.change) || changedAttr[key][0],
                    this.updateLastState(state);
                }
            }else {
                this.set('isGroupClosed', false);
                //if the current model in history is diffarent or property isn`t stackable or grouped add new record in stack
                var lastState = this.getLastState(key)
                if (groupName) {
                    this.saveGroup(state, groupName);
                }else {
                    //set value based on prev record
                    state.value = (lastState && lastState.change) || changedAttr[key][0],
                    this.saveState(state);
                }
            }
        }

        if(meta.kind === 'hasMany'){
            var items = relation.get('content').filter(r => {
                return r.get('isNew') && !r.get('isTracked');
            });

            if(items.length > 0){
                this.saveState({
                    key: key,
                    type: 'hasMany',
                    change: items.get('firstObject'),
                    model: this
                });

                items.forEach(item => {
                    item.track();
                    item.set('isTracked', true);
                });
            }
        }

        if(meta.kind === 'belongsTo'){
            if(relation){
                this.get('history').push({
                    key: key,
                    type: 'belongsTo',
                    change: relation,
                    model: this
                });
            }
        }
    },

    removeObserver: function(key, child){
        this.get('history').push(this);

        this.saveState({
            key: key,
            type: 'remove:hasMany',
            change: child,
            model: this
        });
    },

    removeRecord: function(record){
        record.set('isRemoved', true);
        this.get(record.constructor.typeKey + 's').removeObject(record);

        this.get('states').pushObject({
            key: record.constructor.typeKey + 's',
            type: 'remove:hasMany',
            change: record,
            model: this
        });
    },

    updateLastGroup: function(state, groupName){
        var key = state.key;
        var prevGroup = this.getPrevState(groupName);
        var lastGroup = this.getLastState(groupName);
        var changedAttr = this.changedAttributes();
        var group = {
            key: groupName,
            type: 'group',
            model: this,
            value: {}
        };

        //if we update the group and we have already recorded group in states use it as base value
        if (prevGroup && prevGroup.type === "group" && prevGroup.key === groupName) {
            //set base values
            state.value = (prevGroup.value[key] && prevGroup.value[key].change) || changedAttr[key][0];
        }else {
            state.value = changedAttr[key][0];
        }

        //if we have recorded group just update it
        if (lastGroup && lastGroup.type === "group" && lastGroup.key === groupName){
            if (lastGroup.value[key]) {
                lastGroup.value[key].value = state.value;
                lastGroup.value[key].change = state.change;

                this.updateLastState(lastGroup);
            }else {
                lastGroup.value[key] = state;
                this.updateLastState(lastGroup);
            }
        }else {
            state.value = changedAttr[key][0];
            group.value[key] = state;
            this.saveState(group);
        }
    },

    saveGroup: function(state, groupName){
        var key = state.key;
        var lastGroup = this.getLastState(groupName);
        var changedAttr = this.changedAttributes();
        var group = {
            key: groupName,
            type: 'group',
            model: this,
            value: {}
        };

        //if we have recorded group off this type get changes as base value
        if (lastGroup && lastGroup.type === "group" && lastGroup.key === groupName) {
            state.value = (lastGroup.value[key] && lastGroup.value[key].change) || changedAttr[key][0];
            if (state.change === state.value) {
                return;
            }
            group.value[key] = state;
            this.saveState(group);
        }else {
            state.value = changedAttr[key][0];
            if (state.change === state.value) {
                return;
            }
            group.value[key] = state;
            this.saveState(group);
        }
    },

    closeGroup: function(){
        this.set('isGroupClosed', true);
    },

    updateLastState: function(state){
        var lastState = this.get('states.lastObject');

        if (state.change === state.value) {
            //the value will be the same if ww set it from restore
            return;
        }

        if (lastState && lastState.key === state.key && lastState.type !== 'hasMany' && lastState.type !== 'remove:hasMany') {
            lastState.change = state.change;
            lastState.value = state.value;
            return true;
        }
        this.saveState(state);
    },

    getLastState: function(key){
        var state = null;
        var len = this.get('states.length');

        while(len--){
            state = this.get('states').get(len);
            if (state.key === key) {
                return state;
            }
        }
    },

    getPrevState: function(key){
        var state = null;
        var find = false;
        var len = this.get('states.length');

        while(len--){
            state = this.get('states').get(len);
            if (state.key === key) {
                if (find == true){
                    return state;
                }
                find = true;
            }
        }
    },

    saveState: function(state){
        if (state.change === state.value) {
            //the value will be the same if ww set it from restore
            return;
        }
        this.get('history').push(this);
        this.get('states').pushObject(state);
        console.error('States', this.get('states'));
        console.error('History', this.get('history'));
    },

    restore: function(states){
        var states = states || this.get('states');
        var state = states.popObject();

        if (!state) {
            return false;
        }

        if (Ember.isArray(state)) {
            while(this.restore(state)){
                //do nothing
            }
        }else {
            this.restoreFromState(state);
        }
        console.log('After Restore', states);
        return true;
    },

    restoreFromState: function(state){
        if(state.type !== 'hasMany' && state.type !== 'remove:hasMany'){
            if (state.type === "group") {
                if (state.value) {
                    for (var key in state.value) {
                        if (Object.prototype.hasOwnProperty.call(state.value, key)) {
                            this.set(state.value[key].key, state.value[key].value);
                        }
                    }
                }
            }else {
                this.set(state.key, state.value);
            }
        }
        else{
            if(state.type === 'remove:hasMany'){
                state.change.set('isRemoved', false);
                state.change.set('isAdded', true);
                this.get(state.key).addObject(state.change);
            }
            else{
                this.get(state.key).removeObject(state.change);
            }
        }
    }
});
<commit_msg>Fix a spelling error<commit_after>import Ember from 'ember';

export default Ember.Mixin.create({
    modelReady: false,
    history: Ember.inject.service('history'),
    states: Ember.A([]),
    isGroupClosed: false,
    track: function(){
        this.set('states', Ember.A([]));
        this.set('modelReady', true);
        this.set('isGroupClosed', false);

        this.eachAttribute((name, meta) => {
            if(meta.options.stateless === true) return ;
            this.addObserver(name, () => {
                this.propObserver(name, meta);
            });
        });

        this.eachRelationship((name, descriptor) => {
            if(descriptor.options.stateless === true) return ;

            this.addObserver(name, () => {
                this.propObserver(name, descriptor);
            });

            if(descriptor.kind === 'hasMany' && Ember.isPresent(this.get(name).get('content'))){
                this.get(name).get('content').forEach(r => {
                    if(r.track) {
                        r.track();
                    }
                });
            }

            if(descriptor.kind === 'belongsTo'){
                if(this.get(name).track) {
                    this.get(name).track();
                }
            }
        });
    },

    propObserver: function(key, meta){
        var relation = this.get(key);
        var changedAttr = this.changedAttributes();

        if(Object.keys(changedAttr).length === 0 && !meta.kind) {
            return;
        }

        if(changedAttr[key]){
            var groupName = meta.options && meta.options.group;
            var stackable = meta.options && meta.options.stackable;
            var historyLastModel = this.get('history.stack.lastObject');

            var state = {
                key: key,
                type: 'attr',
                change: this.get(key),
                model: this
            };

            if ((stackable || (groupName && !this.get('isGroupClosed'))) && historyLastModel == this) {
                //if this is the current model in history update the records and get the prev one as history back value
                var prevState = this.getPrevState(key);
                if (groupName) {
                    this.updateLastGroup(state, groupName);
                }else {
                    //if we recording in this last state get the prev one as base value
                    state.value = (prevState && prevState.change) || changedAttr[key][0],
                    this.updateLastState(state);
                }
            }else {
                this.set('isGroupClosed', false);
                //if the current model in history is diffarent or property isn`t stackable or grouped add new record in stack
                var lastState = this.getLastState(key)
                if (groupName) {
                    this.saveGroup(state, groupName);
                }else {
                    //set value based on prev record
                    state.value = (lastState && lastState.change) || changedAttr[key][0],
                    this.saveState(state);
                }
            }
        }

        if(meta.kind === 'hasMany'){
            var items = relation.get('content').filter(r => {
                return r.get('isNew') && !r.get('isTracked');
            });

            if(items.length > 0){
                this.saveState({
                    key: key,
                    type: 'hasMany',
                    change: items.get('firstObject'),
                    model: this
                });

                items.forEach(item => {
                    item.track();
                    item.set('isTracked', true);
                });
            }
        }

        if(meta.kind === 'belongsTo'){
            if(relation){
                this.get('history').push({
                    key: key,
                    type: 'belongsTo',
                    change: relation,
                    model: this
                });
            }
        }
    },

    removeObserver: function(key, child){
        this.get('history').push(this);

        this.saveState({
            key: key,
            type: 'remove:hasMany',
            change: child,
            model: this
        });
    },

    removeRecord: function(record){
        record.set('isRemoved', true);
        this.get(record.constructor.typeKey + 's').removeObject(record);

        this.get('states').pushObject({
            key: record.constructor.typeKey + 's',
            type: 'remove:hasMany',
            change: record,
            model: this
        });
    },

    updateLastGroup: function(state, groupName){
        var key = state.key;
        var prevGroup = this.getPrevState(groupName);
        var lastGroup = this.getLastState(groupName);
        var changedAttr = this.changedAttributes();
        var group = {
            key: groupName,
            type: 'group',
            model: this,
            value: {}
        };

        //if we update the group and we have already recorded group in states use it as base value
        if (prevGroup && prevGroup.type === "group" && prevGroup.key === groupName) {
            //set base values
            state.value = (prevGroup.value[key] && prevGroup.value[key].change) || changedAttr[key][0];
        }else {
            state.value = changedAttr[key][0];
        }

        //if we have recorded group just update it
        if (lastGroup && lastGroup.type === "group" && lastGroup.key === groupName){
            if (lastGroup.value[key]) {
                lastGroup.value[key].value = state.value;
                lastGroup.value[key].change = state.change;

                this.updateLastState(lastGroup);
            }else {
                lastGroup.value[key] = state;
                this.updateLastState(lastGroup);
            }
        }else {
            state.value = changedAttr[key][0];
            group.value[key] = state;
            this.saveState(group);
        }
    },

    saveGroup: function(state, groupName){
        var key = state.key;
        var lastGroup = this.getLastState(groupName);
        var changedAttr = this.changedAttributes();
        var group = {
            key: groupName,
            type: 'group',
            model: this,
            value: {}
        };

        //if we have recorded group off this type get changes as base value
        if (lastGroup && lastGroup.type === "group" && lastGroup.key === groupName) {
            state.value = (lastGroup.value[key] && lastGroup.value[key].change) || changedAttr[key][0];
            if (state.change === state.value) {
                return;
            }
            group.value[key] = state;
            this.saveState(group);
        }else {
            state.value = changedAttr[key][0];
            if (state.change === state.value) {
                return;
            }
            group.value[key] = state;
            this.saveState(group);
        }
    },

    closeGroup: function(){
        this.set('isGroupClosed', true);
    },

    updateLastState: function(state){
        var lastState = this.get('states.lastObject');

        if (state.change === state.value) {
            //the value will be the same if ww set it from restore
            return;
        }

        if (lastState && lastState.key === state.key && lastState.type !== 'hasMany' && lastState.type !== 'remove:hasMany') {
            lastState.change = state.change;
            lastState.value = state.value;
            return true;
        }
        this.saveState(state);
    },

    getLastState: function(key){
        var state = null;
        var len = this.get('states.length');

        while(len--){
            state = this.get('states').get(len);
            if (state.key === key) {
                return state;
            }
        }
    },

    getPrevState: function(key){
        var state = null;
        var find = false;
        var len = this.get('states.length');

        while(len--){
            state = this.get('states').get(len);
            if (state.key === key) {
                if (find == true){
                    return state;
                }
                find = true;
            }
        }
    },

    saveState: function(state){
        if (state.change === state.value) {
            //the value will be the same if ww set it from restore
            return;
        }
        this.get('history').push(this);
        this.get('states').pushObject(state);
        console.error('States', this.get('states'));
        console.error('History', this.get('history'));
    },

    restore: function(states){
        var states = states || this.get('states');
        var state = states.popObject();

        if (!state) {
            return false;
        }

        if (Ember.isArray(state)) {
            while(this.restore(state)){
                //do nothing
            }
        }else {
            this.restoreFromState(state);
        }
        console.log('After Restore', states);
        return true;
    },

    restoreFromState: function(state){
        if(state.type !== 'hasMany' && state.type !== 'remove:hasMany'){
            if (state.type === "group") {
                if (state.value) {
                    for (var key in state.value) {
                        if (Object.prototype.hasOwnProperty.call(state.value, key)) {
                            this.set(state.value[key].key, state.value[key].value);
                        }
                    }
                }
            }else {
                this.set(state.key, state.value);
            }
        }
        else{
            if(state.type === 'remove:hasMany'){
                state.change.set('isRemoved', false);
                state.change.set('isAdded', true);
                this.get(state.key).addObject(state.change);
            }
            else{
                this.get(state.key).removeObject(state.change);
            }
        }
    }
});
<|endoftext|>


<commit_before>module.exports = ClassName

function ClassName(defaultValue, values, animationDuration, onChange, modifInit) {
  if (!(this instanceof ClassName)) {
    return new ClassName(defaultValue)
  }

  switch (arguments.length) {
    case 1:
      if (Array.isArray(defaultValue)) {
        values = defaultValue
        defaultValue = null
      }
      else {
        defaultValue = defaultValue || {}
        values = defaultValue.values
        animationDuration = defaultValue.animationDuration
        onChange = defaultValue.onChange
        defaultValue = defaultValue.default
      }
      break
    case 2:
      switch (typeof values) {
        case "number":
          animationDuration = values
          values = [defaultValue]
          break
        case "function":
          onChange = values
          values = [defaultValue]
          break
      }
      break
    case 3:
      switch (typeof animationDuration) {
        case "function":
          onChange = animationDuration
          animationDuration = 0
          break
      }
      break
  }

  if (!Array.isArray(values)) {
    values = [defaultValue]
  }
  if (typeof animationDuration != "number") {
    animationDuration = 0
  }
  if (typeof onChange != "function") {
    onChange = null
  }

  this.default = defaultValue
  this.values = values
  this.onchange = onChange
  this.animationDuration = animationDuration
  this.value = null
  this.timerId = null
  this.toggledValue = null
}

ClassName.prototype.clone = function() {
  return new this.constructor(this)
}

ClassName.prototype.reset = function(element, context) {
  var currentClassName = null
  var hasInitialValue = this.values.some(function(value) {
    if (value && element.classList.contains(value)) {
      currentClassName = value
      return true
    }
    return false
  })

  if (hasInitialValue) {
    this.value = currentClassName
  }
  else if (this.default != null) {
    this.set(this.default, element, context)
  }
}

ClassName.prototype.get = function() {
  return this.value
}

ClassName.prototype.hasValue = function() {
  return this.value != null
}

ClassName.prototype.isSet = function(element) {
  return this.value != null && element.classList.contains(this.value)
}

ClassName.prototype.toggle = function(element, context) {
  if (this.isSet(element)) {
    this.toggledValue = this.value
    return this.remove(element, context)
  }

  var value = this.toggledValue == null
    ? this.values[0]
    : this.toggledValue
  this.toggledValue = null

  return this.set(value, element, context)
}

ClassName.prototype.set = function(value, element, context) {
  context = context || element

  var previousValue = this.value
  var newValue = value

  if (previousValue === newValue || !~this.values.indexOf(newValue)) {
    return Promise.resolve()
  }
  if (previousValue && element.classList.contains(previousValue)) {
    element.classList.remove(previousValue)
  }
  this.value = newValue
  if (newValue) {
    element.classList.add(newValue)
  }

  return callOnChange(this, context, previousValue, newValue)
}

ClassName.prototype.remove = function(element, context) {
  context = context || element
  if (this.value == null) {
    return Promise.resolve()
  }
  if (this.timerId) {
    clearTimeout(this.timerId)
    this.timerId = null
  }

  var previousValue = this.value

  if (previousValue && element.classList.contains(previousValue)) {
    element.classList.remove(previousValue)
  }
  this.value = null

  return callOnChange(this, context, previousValue, null)
}

function callOnChange(modifier, context, previousValue, newValue) {
  return new Promise(function(resolve) {
    if (modifier.animationDuration) {
      if (modifier.timerId) {
        clearTimeout(modifier.timerId)
        modifier.timerId = null
      }
      modifier.timerId = setTimeout(resolve, modifier.animationDuration)
    }
    else {
      resolve()
    }
  }).then(function() {
    if (typeof modifier.onchange == "function") {
      return modifier.onchange.call(context, previousValue, newValue)
    }
  })
}
<commit_msg>fix onChange property name<commit_after>module.exports = ClassName

function ClassName(defaultValue, values, animationDuration, onChange, modifInit) {
  if (!(this instanceof ClassName)) {
    return new ClassName(defaultValue)
  }

  switch (arguments.length) {
    case 1:
      if (Array.isArray(defaultValue)) {
        values = defaultValue
        defaultValue = null
      }
      else {
        defaultValue = defaultValue || {}
        values = defaultValue.values
        animationDuration = defaultValue.animationDuration
        onChange = defaultValue.onChange
        defaultValue = defaultValue.default
      }
      break
    case 2:
      switch (typeof values) {
        case "number":
          animationDuration = values
          values = [defaultValue]
          break
        case "function":
          onChange = values
          values = [defaultValue]
          break
      }
      break
    case 3:
      switch (typeof animationDuration) {
        case "function":
          onChange = animationDuration
          animationDuration = 0
          break
      }
      break
  }

  if (!Array.isArray(values)) {
    values = [defaultValue]
  }
  if (typeof animationDuration != "number") {
    animationDuration = 0
  }
  if (typeof onChange != "function") {
    onChange = null
  }

  this.default = defaultValue
  this.values = values
  this.onChange = onChange
  this.animationDuration = animationDuration
  this.value = null
  this.timerId = null
  this.toggledValue = null
}

ClassName.prototype.clone = function() {
  return new this.constructor(this)
}

ClassName.prototype.reset = function(element, context) {
  var currentClassName = null
  var hasInitialValue = this.values.some(function(value) {
    if (value && element.classList.contains(value)) {
      currentClassName = value
      return true
    }
    return false
  })

  if (hasInitialValue) {
    this.value = currentClassName
  }
  else if (this.default != null) {
    this.set(this.default, element, context)
  }
}

ClassName.prototype.get = function() {
  return this.value
}

ClassName.prototype.hasValue = function() {
  return this.value != null
}

ClassName.prototype.isSet = function(element) {
  return this.value != null && element.classList.contains(this.value)
}

ClassName.prototype.toggle = function(element, context) {
  if (this.isSet(element)) {
    this.toggledValue = this.value
    return this.remove(element, context)
  }

  var value = this.toggledValue == null
    ? this.values[0]
    : this.toggledValue
  this.toggledValue = null

  return this.set(value, element, context)
}

ClassName.prototype.set = function(value, element, context) {
  context = context || element

  var previousValue = this.value
  var newValue = value

  if (previousValue === newValue || !~this.values.indexOf(newValue)) {
    return Promise.resolve()
  }
  if (previousValue && element.classList.contains(previousValue)) {
    element.classList.remove(previousValue)
  }
  this.value = newValue
  if (newValue) {
    element.classList.add(newValue)
  }

  return callOnChange(this, context, previousValue, newValue)
}

ClassName.prototype.remove = function(element, context) {
  context = context || element
  if (this.value == null) {
    return Promise.resolve()
  }
  if (this.timerId) {
    clearTimeout(this.timerId)
    this.timerId = null
  }

  var previousValue = this.value

  if (previousValue && element.classList.contains(previousValue)) {
    element.classList.remove(previousValue)
  }
  this.value = null

  return callOnChange(this, context, previousValue, null)
}

function callOnChange(modifier, context, previousValue, newValue) {
  return new Promise(function(resolve) {
    if (modifier.animationDuration) {
      if (modifier.timerId) {
        clearTimeout(modifier.timerId)
        modifier.timerId = null
      }
      modifier.timerId = setTimeout(resolve, modifier.animationDuration)
    }
    else {
      resolve()
    }
  }).then(function() {
    if (typeof modifier.onChange == "function") {
      return modifier.onChange.call(context, previousValue, newValue)
    }
  })
}
<|endoftext|>


<commit_before>// Documents viewer

'use strict';

var syncHeight = require('./utils/sync-height')
  , _          = require('mano').i18n.bind('User: Submitted')
  , scrollBottom     = require('./utils/scroll-to-bottom')
  , nextTick = require('next-tick')
  , curry = require('es5-ext/function/#/curry')
  , _d = _;

module.exports = function (doc, sideContent) {
	var elem, scrollableElem;

	return [h2(_d(doc.label)),
		section(
			{ class: 'section-primary' },
			h2(_("Documents history")),
			scrollableElem = div(
				{ class: 'submitted-user-history-wrapper' },
				table(
					{ class: 'submitted-user-history' },
					tbody(
						doc.statusLog.ordered,
						function (log) {
							th(log.label);
							td(log.time);
							td(log.text && log.text.split('\n').filter(Boolean).map(curry.call(p, 1)));
						}
					)
				)
			)
		),
		nextTick(function () { scrollBottom(scrollableElem); }),
		section({ class: 'submitted-preview-new' },
			div({ class: 'section-primary submitted-preview-new-document' },
				div({ class: 'container-with-nav' },
					h3(span({ class: 'submitted-preview-new-item-number' })),

					_if(gt(doc.files.ordered._size, 1),
						div({ class: 'submitted-preview-new-documents-navigation' },
							div(
								{ id: 'submitted-preview-new-navigation-top' },
								a({ class: 'previous' }, span({ class: 'fa fa-chevron-circle-left' },
										_("Previous"))),
								span(span({ class: 'current-index' }, "1"), " / ",
									doc.files.ordered._size),
								a({ class: 'next' }, span({ class: 'fa fa-chevron-circle-right' }, _("Next")))
							))
						)
					),

				elem = ul({ id: 'doc-previews', class: 'submitted-preview-new-image-placeholder' },
					doc.files.ordered, function (file) {
						li({ class: _if(eq(file, doc.files.ordered._first), 'active') },
							img({ zoomOnHover: true, src: or(resolve(file._preview, '_url'),
								resolve(file._thumb, '_url')) }));
					}, doc),

				insert(_if(gt(doc.files.ordered._size, 1),
					div({ class: 'submitted-preview-new-documents-navigation' },
						div(
							{ id: 'submitted-preview-new-navigation-bottom' },
							a({ class: 'previous' }, span({ class: 'fa fa-chevron-circle-left' }, _("Previous"))),
							span(span({ class: 'current-index' }, "1"), " / ",
								doc.files.ordered._size),
							a({ class: 'next' }, span({ class: 'fa fa-chevron-circle-right' }, _("Next"))),
							legacy('hashNavOrderedListControls',
								'submitted-preview-new-navigation-top', 'doc-previews',
									'doc-preview'),
							legacy('hashNavOrderedListControls',
									'submitted-preview-new-navigation-bottom', 'doc-previews',
									'doc-preview'),
							legacy('hashNavOrderedList', 'doc-previews', 'doc-preview')
						)))),

				syncHeight(elem)),
				div({ class: 'section-primary submitted-preview-new-side-data' },
					a({ class: 'button-main' }, "Download document"),
					sideContent)
			)];
};
<commit_msg>Master configuration on documents page. #700<commit_after>// Documents viewer

'use strict';

var syncHeight = require('./utils/sync-height')
  , _          = require('mano').i18n.bind('User: Submitted')
  , scrollBottom     = require('./utils/scroll-to-bottom')
  , nextTick = require('next-tick')
  , curry = require('es5-ext/function/#/curry')
  , _d = _
  , db = require('mano').db;

module.exports = function (doc, sideContent) {
	var elem, scrollableElem, master;
	if (doc.issuedBy.constructor === db.Institution) {
		master = doc.master;
	} else {
		master = doc.owner.owner.owner.owner;
	}
	return [h2(_d(doc.label, { entity: master })),
		section(
			{ class: 'section-primary' },
			h2(_("Documents history")),
			scrollableElem = div(
				{ class: 'submitted-user-history-wrapper' },
				table(
					{ class: 'submitted-user-history' },
					tbody(
						doc.statusLog.ordered,
						function (log) {
							th(log.label);
							td(log.time);
							td(log.text && log.text.split('\n').filter(Boolean).map(curry.call(p, 1)));
						}
					)
				)
			)
		),
		nextTick(function () { scrollBottom(scrollableElem); }),
		section({ class: 'submitted-preview-new' },
			div({ class: 'section-primary submitted-preview-new-document' },
				div({ class: 'container-with-nav' },
					h3(span({ class: 'submitted-preview-new-item-number' })),

					_if(gt(doc.files.ordered._size, 1),
						div({ class: 'submitted-preview-new-documents-navigation' },
							div(
								{ id: 'submitted-preview-new-navigation-top' },
								a({ class: 'previous' }, span({ class: 'fa fa-chevron-circle-left' },
										_("Previous"))),
								span(span({ class: 'current-index' }, "1"), " / ",
									doc.files.ordered._size),
								a({ class: 'next' }, span({ class: 'fa fa-chevron-circle-right' }, _("Next")))
							))
						)
					),

				elem = ul({ id: 'doc-previews', class: 'submitted-preview-new-image-placeholder' },
					doc.files.ordered, function (file) {
						li({ class: _if(eq(file, doc.files.ordered._first), 'active') },
							img({ zoomOnHover: true, src: or(resolve(file._preview, '_url'),
								resolve(file._thumb, '_url')) }));
					}, doc),

				insert(_if(gt(doc.files.ordered._size, 1),
					div({ class: 'submitted-preview-new-documents-navigation' },
						div(
							{ id: 'submitted-preview-new-navigation-bottom' },
							a({ class: 'previous' }, span({ class: 'fa fa-chevron-circle-left' }, _("Previous"))),
							span(span({ class: 'current-index' }, "1"), " / ",
								doc.files.ordered._size),
							a({ class: 'next' }, span({ class: 'fa fa-chevron-circle-right' }, _("Next"))),
							legacy('hashNavOrderedListControls',
								'submitted-preview-new-navigation-top', 'doc-previews',
									'doc-preview'),
							legacy('hashNavOrderedListControls',
									'submitted-preview-new-navigation-bottom', 'doc-previews',
									'doc-preview'),
							legacy('hashNavOrderedList', 'doc-previews', 'doc-preview')
						)))),

				syncHeight(elem)),
				div({ class: 'section-primary submitted-preview-new-side-data' },
					a({ class: 'button-main' }, "Download document"),
					sideContent)
			)];
};
<|endoftext|>


<commit_before>define(function(require, exports, module) {
    var Entity = require('famous/core/Entity');
    var Group = require('famous/core/Group');
    var OptionsManager = require('famous/core/OptionsManager');
    var Transform = require('famous/core/Transform');
    var Utility = require('famous/utilities/Utility');
    var ViewSequence = require('famous/core/ViewSequence');
    var EventHandler = require('famous/core/EventHandler');

    /**
     * Scroller lays out a collection of renderables, and will browse through them based on
     * accessed position. Scroller also broadcasts an 'edgeHit' event, with a position property of the location of the edge,
     * when you've hit the 'edges' of it's renderable collection.
     * @class Scroller
     * @constructor
      * @event error
     * @param {Options} [options] An object of configurable options.
     * @param {Number} [options.direction=Utility.Direction.Y] Using the direction helper found in the famous Utility
     * module, this option will lay out the Scroller instance's renderables either horizontally
     * (x) or vertically (y). Utility's direction is essentially either zero (X) or one (Y), so feel free
     * to just use integers as well.
     * @param {Number} [clipSize=undefined] The size of the area (in pixels) that Scroller will display content in.
     * @param {Number} [margin=undefined] The size of the area (in pixels) that Scroller will process renderables' associated calculations in.
     */
    function Scroller(options) {
        this.options = Object.create(this.constructor.DEFAULT_OPTIONS);
        this._optionsManager = new OptionsManager(this.options);
        if (options) this._optionsManager.setOptions(options);

        this._node = null;
        this._position = 0;

        // used for shifting nodes
        this._positionOffset = 0;

        this._positionGetter = null;
        this._outputFunction = null;
        this._masterOutputFunction = null;
        this.outputFrom();

        this._onEdge = 0; // -1 for top, 1 for bottom

        this.group = new Group();
        this.group.add({render: _innerRender.bind(this)});

        this._entityId = Entity.register(this);
        this._size = [undefined, undefined];
        this._contextSize = [undefined, undefined];

        this._eventInput = new EventHandler();
        this._eventOutput = new EventHandler();

        EventHandler.setInputHandler(this, this._eventInput);
        EventHandler.setOutputHandler(this, this._eventOutput);
    }

    Scroller.DEFAULT_OPTIONS = {
        direction: Utility.Direction.Y,
        margin: 0,
        clipSize: undefined,
        groupScroll: false
    };

    var EDGE_TOLERANCE = 0; //slop for detecting passing the edge

    function _sizeForDir(size) {
        if (!size) size = this._contextSize;
        var dimension = this.options.direction;
        return (size[dimension] === undefined) ? this._contextSize[dimension] : size[dimension];
    }

    function _output(node, offset, target) {
        var size = node.getSize ? node.getSize() : this._contextSize;
        var transform = this._outputFunction(offset);
        target.push({transform: transform, target: node.render()});
        return _sizeForDir.call(this, size);
    }

    function _getClipSize() {
        if (this.options.clipSize !== undefined) return this.options.clipSize;
        if (this._contextSize[this.options.direction] > this.getCumulativeSize()[this.options.direction]) {
            return _sizeForDir.call(this, this.getCumulativeSize());
        } else {
            return _sizeForDir.call(this, this._contextSize);
        }
    }

    /**
    * Returns the cumulative size of the renderables in the view sequence
    * @method getCumulativeSize
    * @return {array} a two value array of the view sequence's cumulative size up to the index.
    */
    Scroller.prototype.getCumulativeSize = function(index) {
        if (index === undefined) index = this._node._.cumulativeSizes.length - 1;
        return this._node._.getSize(index);
    };

    /**
     * Patches the Scroller instance's options with the passed-in ones.
     * @method setOptions
     * @param {Options} options An object of configurable options for the Scroller instance.
     */
    Scroller.prototype.setOptions = function setOptions(options) {
        this._optionsManager.setOptions(options);
        if (this.options.groupScroll)
            this.group.pipe(this._eventOutput);
        else
            this.group.unpipe(this._eventOutput);
    };

    /**
     * Tells you if the Scroller instance is on an edge.
     * @method onEdge
     * @return {Boolean} Whether the Scroller instance is on an edge or not.
     */
    Scroller.prototype.onEdge = function onEdge() {
        return this._onEdge;
    };

    /**
     * Allows you to overwrite the way Scroller lays out it's renderables. Scroller will
     * pass an offset into the function. By default the Scroller instance just translates each node
     * in it's direction by the passed-in offset.
     * Scroller will translate each renderable down
     * @method outputFrom
     * @param {Function} fn A function that takes an offset and returns a transform.
     * @param {Function} [masterFn]
     */
    Scroller.prototype.outputFrom = function outputFrom(fn, masterFn) {
        if (!fn) {
            fn = function(offset) {
                return (this.options.direction === Utility.Direction.X) ? Transform.translate(offset, 0) : Transform.translate(0, offset);
            }.bind(this);
            if (!masterFn) masterFn = fn;
        }
        this._outputFunction = fn;
        this._masterOutputFunction = masterFn ? masterFn : function(offset) {
            return Transform.inverse(fn(-offset));
        };
    };

    /**
     * The Scroller instance's method for reading from an external position. Scroller uses
     * the external position to actually scroll through it's renderables.
     * @method positionFrom
     * @param {Getter} position Can be either a function that returns a position,
     * or an object with a get method that returns a position.
     */
    Scroller.prototype.positionFrom = function positionFrom(position) {
        if (position instanceof Function) this._positionGetter = position;
        else if (position && position.get) this._positionGetter = position.get.bind(position);
        else {
            this._positionGetter = null;
            this._position = position;
        }
        if (this._positionGetter) this._position = this._positionGetter.call(this);
    };

    /**
     * Sets the collection of renderables under the Scroller instance's control.
     *
     * @method sequenceFrom
     * @param node {Array|ViewSequence} Either an array of renderables or a Famous viewSequence.
     * @chainable
     */
    Scroller.prototype.sequenceFrom = function sequenceFrom(node) {
        if (node instanceof Array) node = new ViewSequence({array: node});
        this._node = node;
        this._positionOffset = 0;
    };

    /**
     * Returns the width and the height of the Scroller instance.
     *
     * @method getSize
     * @return {Array} A two value array of the Scroller instance's current width and height (in that order).
     */
    Scroller.prototype.getSize = function getSize(actual) {
        return actual ? this._contextSize : this._size;
    };

    /**
     * Generate a render spec from the contents of this component.
     *
     * @private
     * @method render
     * @return {number} Render spec for this component
     */
    Scroller.prototype.render = function render() {
        if (!this._node) return null;
        if (this._positionGetter) this._position = this._positionGetter.call(this);
        return this._entityId;
    };

    /**
     * Apply changes from this component to the corresponding document element.
     * This includes changes to classes, styles, size, content, opacity, origin,
     * and matrix transforms.
     *
     * @private
     * @method commit
     * @param {Context} context commit context
     */
    Scroller.prototype.commit = function commit(context) {
        var transform = context.transform;
        var opacity = context.opacity;
        var origin = context.origin;
        var size = context.size;

        // reset edge detection on size change
        if (!this.options.clipSize && (size[0] !== this._contextSize[0] || size[1] !== this._contextSize[1])) {
            this._onEdge = 0;
            this._contextSize[0] = size[0];
            this._contextSize[1] = size[1];

            if (this.options.direction === Utility.Direction.X) {
                this._size[0] = _getClipSize.call(this);
                this._size[1] = undefined;
            }
            else {
                this._size[0] = undefined;
                this._size[1] = _getClipSize.call(this);
            }
        }

        var scrollTransform = this._masterOutputFunction(-this._position);

        return {
            transform: Transform.multiply(transform, scrollTransform),
            size: size,
            opacity: opacity,
            origin: origin,
            target: this.group.render()
        };
    };

    function _normalizeState() {
        var nodeSize = _sizeForDir.call(this, this._node.getSize());
        var nextNode = this._node && this._node.getNext ? this._node.getNext() : null;
        while (nextNode && this._position + this._positionOffset >= nodeSize) {
            this._positionOffset -= nodeSize;
            this._node = nextNode;
            nodeSize = _sizeForDir.call(this, this._node.getSize());
            nextNode = this._node && this._node.getNext ? this._node.getNext() : null;
        }
        var prevNode = this._node && this._node.getPrevious ? this._node.getPrevious() : null;
        while (prevNode && this._position + this._positionOffset < 0) {
            var prevNodeSize = _sizeForDir.call(this, prevNode.getSize());
            this._positionOffset += prevNodeSize;
            this._node = prevNode;
            prevNode = this._node && this._node.getPrevious ? this._node.getPrevious() : null;
        }
    }

    function _innerRender() {
        var size = null;
        var position = this._position;
        var result = [];

        var offset = -this._positionOffset;
        var clipSize = _getClipSize.call(this);
        var currNode = this._node;
        while (currNode && offset - position < clipSize + this.options.margin) {
            offset += _output.call(this, currNode, offset, result);
            currNode = currNode.getNext ? currNode.getNext() : null;
        }

        var sizeNode = this._node;
        var nodesSize = _sizeForDir.call(this, sizeNode.getSize()) || 0;
        if (offset < clipSize) {
            while (sizeNode && nodesSize < clipSize) {
                sizeNode = sizeNode.getPrevious();
                if (sizeNode) nodesSize += _sizeForDir.call(this, sizeNode.getSize()) || 0;
            }
            sizeNode = this._node;
            while (sizeNode && nodesSize < clipSize) {
                sizeNode = sizeNode.getNext();
                if (sizeNode) nodesSize += _sizeForDir.call(this, sizeNode.getSize()) || 0;
            }
        }

        if (!currNode && offset - position < clipSize - EDGE_TOLERANCE) {
            if (this._onEdge !== 1){
                this._onEdge = 1;
                this._eventOutput.emit('onEdge', {
                    position: offset - clipSize
                });
            }
        }
        else if (!this._node.getPrevious() && position < -EDGE_TOLERANCE) {
            if (this._onEdge !== -1) {
                this._onEdge = -1;
                this._eventOutput.emit('onEdge', {
                    position: 0
                });
            }
        }
        else {
            if (this._onEdge !== 0){
                this._onEdge = 0;
                this._eventOutput.emit('offEdge');
            }
        }

        // backwards
        currNode = (this._node && this._node.getPrevious) ? this._node.getPrevious() : null;
        offset = -this._positionOffset;
        if (currNode) {
            size = currNode.getSize ? currNode.getSize() : this._contextSize;
            offset -= _sizeForDir.call(this, size) || 0;
        }

        while (currNode && ((offset - position) > -(clipSize + this.options.margin))) {
            _output.call(this, currNode, offset, result);
            currNode = currNode.getPrevious ? currNode.getPrevious() : null;
            if (currNode) {
                size = currNode.getSize ? currNode.getSize() : this._contextSize;
                offset -= _sizeForDir.call(this, size) || 0;
            }
        }

//        _normalizeState.call(this);
        return result;
    }

    module.exports = Scroller;
});
<commit_msg>Removed undefined checks from sizeForDir calls<commit_after>define(function(require, exports, module) {
    var Entity = require('famous/core/Entity');
    var Group = require('famous/core/Group');
    var OptionsManager = require('famous/core/OptionsManager');
    var Transform = require('famous/core/Transform');
    var Utility = require('famous/utilities/Utility');
    var ViewSequence = require('famous/core/ViewSequence');
    var EventHandler = require('famous/core/EventHandler');

    /**
     * Scroller lays out a collection of renderables, and will browse through them based on
     * accessed position. Scroller also broadcasts an 'edgeHit' event, with a position property of the location of the edge,
     * when you've hit the 'edges' of it's renderable collection.
     * @class Scroller
     * @constructor
      * @event error
     * @param {Options} [options] An object of configurable options.
     * @param {Number} [options.direction=Utility.Direction.Y] Using the direction helper found in the famous Utility
     * module, this option will lay out the Scroller instance's renderables either horizontally
     * (x) or vertically (y). Utility's direction is essentially either zero (X) or one (Y), so feel free
     * to just use integers as well.
     * @param {Number} [clipSize=undefined] The size of the area (in pixels) that Scroller will display content in.
     * @param {Number} [margin=undefined] The size of the area (in pixels) that Scroller will process renderables' associated calculations in.
     */
    function Scroller(options) {
        this.options = Object.create(this.constructor.DEFAULT_OPTIONS);
        this._optionsManager = new OptionsManager(this.options);
        if (options) this._optionsManager.setOptions(options);

        this._node = null;
        this._position = 0;

        // used for shifting nodes
        this._positionOffset = 0;

        this._positionGetter = null;
        this._outputFunction = null;
        this._masterOutputFunction = null;
        this.outputFrom();

        this._onEdge = 0; // -1 for top, 1 for bottom

        this.group = new Group();
        this.group.add({render: _innerRender.bind(this)});

        this._entityId = Entity.register(this);
        this._size = [undefined, undefined];
        this._contextSize = [undefined, undefined];

        this._eventInput = new EventHandler();
        this._eventOutput = new EventHandler();

        EventHandler.setInputHandler(this, this._eventInput);
        EventHandler.setOutputHandler(this, this._eventOutput);
    }

    Scroller.DEFAULT_OPTIONS = {
        direction: Utility.Direction.Y,
        margin: 0,
        clipSize: undefined,
        groupScroll: false
    };

    var EDGE_TOLERANCE = 0; //slop for detecting passing the edge

    function _sizeForDir(size) {
        if (!size) size = this._contextSize;
        var dimension = this.options.direction;
        return (size[dimension] === undefined) ? this._contextSize[dimension] : size[dimension];
    }

    function _output(node, offset, target) {
        var size = node.getSize ? node.getSize() : this._contextSize;
        var transform = this._outputFunction(offset);
        target.push({transform: transform, target: node.render()});
        return _sizeForDir.call(this, size);
    }

    function _getClipSize() {
        if (this.options.clipSize !== undefined) return this.options.clipSize;
        if (this._contextSize[this.options.direction] > this.getCumulativeSize()[this.options.direction]) {
            return _sizeForDir.call(this, this.getCumulativeSize());
        } else {
            return _sizeForDir.call(this, this._contextSize);
        }
    }

    /**
    * Returns the cumulative size of the renderables in the view sequence
    * @method getCumulativeSize
    * @return {array} a two value array of the view sequence's cumulative size up to the index.
    */
    Scroller.prototype.getCumulativeSize = function(index) {
        if (index === undefined) index = this._node._.cumulativeSizes.length - 1;
        return this._node._.getSize(index);
    };

    /**
     * Patches the Scroller instance's options with the passed-in ones.
     * @method setOptions
     * @param {Options} options An object of configurable options for the Scroller instance.
     */
    Scroller.prototype.setOptions = function setOptions(options) {
        this._optionsManager.setOptions(options);
        if (this.options.groupScroll)
            this.group.pipe(this._eventOutput);
        else
            this.group.unpipe(this._eventOutput);
    };

    /**
     * Tells you if the Scroller instance is on an edge.
     * @method onEdge
     * @return {Boolean} Whether the Scroller instance is on an edge or not.
     */
    Scroller.prototype.onEdge = function onEdge() {
        return this._onEdge;
    };

    /**
     * Allows you to overwrite the way Scroller lays out it's renderables. Scroller will
     * pass an offset into the function. By default the Scroller instance just translates each node
     * in it's direction by the passed-in offset.
     * Scroller will translate each renderable down
     * @method outputFrom
     * @param {Function} fn A function that takes an offset and returns a transform.
     * @param {Function} [masterFn]
     */
    Scroller.prototype.outputFrom = function outputFrom(fn, masterFn) {
        if (!fn) {
            fn = function(offset) {
                return (this.options.direction === Utility.Direction.X) ? Transform.translate(offset, 0) : Transform.translate(0, offset);
            }.bind(this);
            if (!masterFn) masterFn = fn;
        }
        this._outputFunction = fn;
        this._masterOutputFunction = masterFn ? masterFn : function(offset) {
            return Transform.inverse(fn(-offset));
        };
    };

    /**
     * The Scroller instance's method for reading from an external position. Scroller uses
     * the external position to actually scroll through it's renderables.
     * @method positionFrom
     * @param {Getter} position Can be either a function that returns a position,
     * or an object with a get method that returns a position.
     */
    Scroller.prototype.positionFrom = function positionFrom(position) {
        if (position instanceof Function) this._positionGetter = position;
        else if (position && position.get) this._positionGetter = position.get.bind(position);
        else {
            this._positionGetter = null;
            this._position = position;
        }
        if (this._positionGetter) this._position = this._positionGetter.call(this);
    };

    /**
     * Sets the collection of renderables under the Scroller instance's control.
     *
     * @method sequenceFrom
     * @param node {Array|ViewSequence} Either an array of renderables or a Famous viewSequence.
     * @chainable
     */
    Scroller.prototype.sequenceFrom = function sequenceFrom(node) {
        if (node instanceof Array) node = new ViewSequence({array: node});
        this._node = node;
        this._positionOffset = 0;
    };

    /**
     * Returns the width and the height of the Scroller instance.
     *
     * @method getSize
     * @return {Array} A two value array of the Scroller instance's current width and height (in that order).
     */
    Scroller.prototype.getSize = function getSize(actual) {
        return actual ? this._contextSize : this._size;
    };

    /**
     * Generate a render spec from the contents of this component.
     *
     * @private
     * @method render
     * @return {number} Render spec for this component
     */
    Scroller.prototype.render = function render() {
        if (!this._node) return null;
        if (this._positionGetter) this._position = this._positionGetter.call(this);
        return this._entityId;
    };

    /**
     * Apply changes from this component to the corresponding document element.
     * This includes changes to classes, styles, size, content, opacity, origin,
     * and matrix transforms.
     *
     * @private
     * @method commit
     * @param {Context} context commit context
     */
    Scroller.prototype.commit = function commit(context) {
        var transform = context.transform;
        var opacity = context.opacity;
        var origin = context.origin;
        var size = context.size;

        // reset edge detection on size change
        if (!this.options.clipSize && (size[0] !== this._contextSize[0] || size[1] !== this._contextSize[1])) {
            this._onEdge = 0;
            this._contextSize[0] = size[0];
            this._contextSize[1] = size[1];

            if (this.options.direction === Utility.Direction.X) {
                this._size[0] = _getClipSize.call(this);
                this._size[1] = undefined;
            }
            else {
                this._size[0] = undefined;
                this._size[1] = _getClipSize.call(this);
            }
        }

        var scrollTransform = this._masterOutputFunction(-this._position);

        return {
            transform: Transform.multiply(transform, scrollTransform),
            size: size,
            opacity: opacity,
            origin: origin,
            target: this.group.render()
        };
    };

    function _normalizeState() {
        var nodeSize = _sizeForDir.call(this, this._node.getSize());
        var nextNode = this._node && this._node.getNext ? this._node.getNext() : null;
        while (nextNode && this._position + this._positionOffset >= nodeSize) {
            this._positionOffset -= nodeSize;
            this._node = nextNode;
            nodeSize = _sizeForDir.call(this, this._node.getSize());
            nextNode = this._node && this._node.getNext ? this._node.getNext() : null;
        }
        var prevNode = this._node && this._node.getPrevious ? this._node.getPrevious() : null;
        while (prevNode && this._position + this._positionOffset < 0) {
            var prevNodeSize = _sizeForDir.call(this, prevNode.getSize());
            this._positionOffset += prevNodeSize;
            this._node = prevNode;
            prevNode = this._node && this._node.getPrevious ? this._node.getPrevious() : null;
        }
    }

    function _innerRender() {
        var size = null;
        var position = this._position;
        var result = [];

        var offset = -this._positionOffset;
        var clipSize = _getClipSize.call(this);
        var currNode = this._node;
        while (currNode && offset - position < clipSize + this.options.margin) {
            offset += _output.call(this, currNode, offset, result);
            currNode = currNode.getNext ? currNode.getNext() : null;
        }

        var sizeNode = this._node;
        var nodesSize = _sizeForDir.call(this, sizeNode.getSize());
        if (offset < clipSize) {
            while (sizeNode && nodesSize < clipSize) {
                sizeNode = sizeNode.getPrevious();
                if (sizeNode) nodesSize += _sizeForDir.call(this, sizeNode.getSize());
            }
            sizeNode = this._node;
            while (sizeNode && nodesSize < clipSize) {
                sizeNode = sizeNode.getNext();
                if (sizeNode) nodesSize += _sizeForDir.call(this, sizeNode.getSize());
            }
        }

        if (!currNode && offset - position < clipSize - EDGE_TOLERANCE) {
            if (this._onEdge !== 1){
                this._onEdge = 1;
                this._eventOutput.emit('onEdge', {
                    position: offset - clipSize
                });
            }
        }
        else if (!this._node.getPrevious() && position < -EDGE_TOLERANCE) {
            if (this._onEdge !== -1) {
                this._onEdge = -1;
                this._eventOutput.emit('onEdge', {
                    position: 0
                });
            }
        }
        else {
            if (this._onEdge !== 0){
                this._onEdge = 0;
                this._eventOutput.emit('offEdge');
            }
        }

        // backwards
        currNode = (this._node && this._node.getPrevious) ? this._node.getPrevious() : null;
        offset = -this._positionOffset;
        if (currNode) {
            size = currNode.getSize ? currNode.getSize() : this._contextSize;
            offset -= _sizeForDir.call(this, size);
        }

        while (currNode && ((offset - position) > -(clipSize + this.options.margin))) {
            _output.call(this, currNode, offset, result);
            currNode = currNode.getPrevious ? currNode.getPrevious() : null;
            if (currNode) {
                size = currNode.getSize ? currNode.getSize() : this._contextSize;
                offset -= _sizeForDir.call(this, size);
            }
        }

//        _normalizeState.call(this);
        return result;
    }

    module.exports = Scroller;
});
<|endoftext|>


<commit_before>jQuery(function($) {
  window.dataExplorer = null;
  window.explorerDiv = $('.data-explorer-here');

  // This is some fancy stuff to allow configuring the multiview from
  // parameters in the query string
  //
  // For more on state see the view documentation.
  var state = recline.View.parseQueryString(decodeURIComponent(window.location.search));
  if (state) {
    _.each(state, function(value, key) {
      try {
        value = JSON.parse(value);
      } catch(e) {}
      state[key] = value;
    });
  } else {
    state.url = 'demo';
  }
  var dataset = null;
  if (state.dataset || state.url) {
    var datasetInfo = _.extend({
        url: state.url,
        backend: state.backend
      },
      state.dataset
    );
    dataset = new recline.Model.Dataset(datasetInfo);
  } else {
    var dataset = new recline.Model.Dataset({
      records: [
        {id: 0, date: '2011-01-01', x: 1, y: 2, z: 3, country: 'DE', title: 'first', lat:52.56, lon:13.40},
        {id: 1, date: '2011-02-02', x: 2, y: 4, z: 24, country: 'UK', title: 'second', lat:54.97, lon:-1.60},
        {id: 2, date: '2011-03-03', x: 3, y: 6, z: 9, country: 'US', title: 'third', lat:40.00, lon:-75.5},
        {id: 3, date: '2011-04-04', x: 4, y: 8, z: 6, country: 'UK', title: 'fourth', lat:57.27, lon:-6.20},
        {id: 4, date: '2011-05-04', x: 5, y: 10, z: 15, country: 'UK', title: 'fifth', lat:51.58, lon:0},
        {id: 5, date: '2011-06-02', x: 6, y: 12, z: 18, country: 'DE', title: 'sixth', lat:51.04, lon:7.9}
      ],
      // let's be really explicit about fields
      // Plus take opportunity to set date to be a date field and set some labels
      fields: [
        {id: 'id'},
        {id: 'date', type: 'date'},
        {id: 'x', type: 'number'},
        {id: 'y', type: 'number'},
        {id: 'z', type: 'number'},
        {id: 'country', 'label': 'Country'},
        {id: 'title', 'label': 'Title'},
        {id: 'lat'},
        {id: 'lon'}
      ]
    });
  }
  createExplorer(dataset, state);
});

// make Explorer creation / initialization in a function so we can call it
// again and again
var createExplorer = function(dataset, state) {
  // remove existing data explorer view
  var reload = false;
  if (window.dataExplorer) {
    window.dataExplorer.remove();
    reload = true;
  }
  window.dataExplorer = null;
  var $el = $('<div />');
  $el.appendTo(window.explorerDiv);

  var views = [
    {
      id: 'grid',
      label: 'Grid',
      view: new recline.View.SlickGrid({
        model: dataset
      })
    },
    {
      id: 'graph',
      label: 'Graph',
      view: new recline.View.Graph({
        model: dataset
      })
    },
    {
      id: 'map',
      label: 'Map',
      view: new recline.View.Map({
        model: dataset
      })
    },
    {
      id: 'transform',
      label: 'Transform',
      view: new recline.View.Transform({
        model: dataset
      })
    }
  ];

  window.dataExplorer = new recline.View.MultiView({
    model: dataset,
    el: $el,
    state: state,
    views: views
  });
}

<commit_msg>[xs] Bug fix: dataset is already defined.<commit_after>jQuery(function($) {
  window.dataExplorer = null;
  window.explorerDiv = $('.data-explorer-here');

  // This is some fancy stuff to allow configuring the multiview from
  // parameters in the query string
  //
  // For more on state see the view documentation.
  var state = recline.View.parseQueryString(decodeURIComponent(window.location.search));
  if (state) {
    _.each(state, function(value, key) {
      try {
        value = JSON.parse(value);
      } catch(e) {}
      state[key] = value;
    });
  } else {
    state.url = 'demo';
  }
  var dataset = null;
  if (state.dataset || state.url) {
    var datasetInfo = _.extend({
        url: state.url,
        backend: state.backend
      },
      state.dataset
    );
    dataset = new recline.Model.Dataset(datasetInfo);
  } else {
    dataset = new recline.Model.Dataset({
      records: [
        {id: 0, date: '2011-01-01', x: 1, y: 2, z: 3, country: 'DE', title: 'first', lat:52.56, lon:13.40},
        {id: 1, date: '2011-02-02', x: 2, y: 4, z: 24, country: 'UK', title: 'second', lat:54.97, lon:-1.60},
        {id: 2, date: '2011-03-03', x: 3, y: 6, z: 9, country: 'US', title: 'third', lat:40.00, lon:-75.5},
        {id: 3, date: '2011-04-04', x: 4, y: 8, z: 6, country: 'UK', title: 'fourth', lat:57.27, lon:-6.20},
        {id: 4, date: '2011-05-04', x: 5, y: 10, z: 15, country: 'UK', title: 'fifth', lat:51.58, lon:0},
        {id: 5, date: '2011-06-02', x: 6, y: 12, z: 18, country: 'DE', title: 'sixth', lat:51.04, lon:7.9}
      ],
      // let's be really explicit about fields
      // Plus take opportunity to set date to be a date field and set some labels
      fields: [
        {id: 'id'},
        {id: 'date', type: 'date'},
        {id: 'x', type: 'number'},
        {id: 'y', type: 'number'},
        {id: 'z', type: 'number'},
        {id: 'country', 'label': 'Country'},
        {id: 'title', 'label': 'Title'},
        {id: 'lat'},
        {id: 'lon'}
      ]
    });
  }
  createExplorer(dataset, state);
});

// make Explorer creation / initialization in a function so we can call it
// again and again
var createExplorer = function(dataset, state) {
  // remove existing data explorer view
  var reload = false;
  if (window.dataExplorer) {
    window.dataExplorer.remove();
    reload = true;
  }
  window.dataExplorer = null;
  var $el = $('<div />');
  $el.appendTo(window.explorerDiv);

  var views = [
    {
      id: 'grid',
      label: 'Grid',
      view: new recline.View.SlickGrid({
        model: dataset
      })
    },
    {
      id: 'graph',
      label: 'Graph',
      view: new recline.View.Graph({
        model: dataset
      })
    },
    {
      id: 'map',
      label: 'Map',
      view: new recline.View.Map({
        model: dataset
      })
    },
    {
      id: 'transform',
      label: 'Transform',
      view: new recline.View.Transform({
        model: dataset
      })
    }
  ];

  window.dataExplorer = new recline.View.MultiView({
    model: dataset,
    el: $el,
    state: state,
    views: views
  });
}

<|endoftext|>