<commit_before>import $ from 'jquery'
import MainPage from './pages/main'
import Me from './models/me'

require('./styles/main.styl')

window.app = {
  init() {
    this.me = new Me({name: 'muraken720', age: 25})
    this.mainPage = new MainPage({el: $('.container'), model: this.me})
    this.mainPage.render()
  }
}

app.init();

<commit_msg>use jquery ready<commit_after>import $ from 'jquery'
import MainPage from './pages/main'
import Me from './models/me'

require('./styles/main.styl')

$(() => {
  window.app = {
    init() {
      this.me = new Me({name: 'muraken720', age: 25})
      this.mainPage = new MainPage({el: $('.container'), model: this.me})
      this.mainPage.render()
      //Backbone.history.start();
    }
  }

  app.init();

})

<|endoftext|>


<commit_before>(function($, undefined) {

/**
 * Unobtrusive scripting adapter for jQuery
 *
 * Requires jQuery 1.6.0 or later.
 * https://github.com/rails/jquery-ujs

 * Uploading file using rails.js
 * =============================
 *
 * By default, browsers do not allow files to be uploaded via AJAX. As a result, if there are any non-blank file fields
 * in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means.
 *
 * The `ajax:aborted:file` event allows you to bind your own handler to process the form submission however you wish.
 *
 * Ex:
 *     $('form').live('ajax:aborted:file', function(event, elements){
 *       // Implement own remote file-transfer handler here for non-blank file inputs passed in `elements`.
 *       // Returning false in this handler tells rails.js to disallow standard form submission
 *       return false;
 *     });
 *
 * The `ajax:aborted:file` event is fired when a file-type input is detected with a non-blank value.
 *
 * Third-party tools can use this hook to detect when an AJAX file upload is attempted, and then use
 * techniques like the iframe method to upload the file instead.
 *
 * Required fields in rails.js
 * ===========================
 *
 * If any blank required inputs (required="required") are detected in the remote form, the whole form submission
 * is canceled. Note that this is unlike file inputs, which still allow standard (non-AJAX) form submission.
 *
 * The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs.
 *
 * !! Note that Opera does not fire the form's submit event if there are blank required inputs, so this event may never
 *    get fired in Opera. This event is what causes other browsers to exhibit the same submit-aborting behavior.
 *
 * Ex:
 *     $('form').live('ajax:aborted:required', function(event, elements){
 *       // Returning false in this handler tells rails.js to submit the form anyway.
 *       // The blank required inputs are passed to this function in `elements`.
 *       return ! confirm("Would you like to submit the form with missing info?");
 *     });
 */

  // Shorthand to make it a little easier to call public rails functions from within rails.js
  var rails;

  $.rails = rails = {
    // Link elements bound by jquery-ujs
    linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',

    // Select elements bound by jquery-ujs
    inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',

    // Form elements bound by jquery-ujs
    formSubmitSelector: 'form',

    // Form input elements bound by jquery-ujs
    formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not(button[type])',

    // Form input elements disabled during form submission
    disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',

    // Form input elements re-enabled after form submission
    enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',

    // Form required input elements
    requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',

    // Form file input elements
    fileInputSelector: 'input:file',

    // Link onClick disable selector with possible reenable after remote submission
    linkDisableSelector: 'a[data-disable-with]',

    // Make sure that every Ajax request sends the CSRF token
    CSRFProtection: function(xhr) {
      var token = $('meta[name="csrf-token"]').attr('content');
      if (token) xhr.setRequestHeader('X-CSRF-Token', token);
    },

    // Triggers an event on an element and returns false if the event result is false
    fire: function(obj, name, data) {
      var event = $.Event(name);
      obj.trigger(event, data);
      return event.result !== false;
    },

    // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
    confirm: function(message) {
      return confirm(message);
    },

    // Default ajax function, may be overridden with custom function in $.rails.ajax
    ajax: function(options) {
      return $.ajax(options);
    },

    // Submits "remote" forms and links with ajax
    handleRemote: function(element) {
      var method, url, data,
        crossDomain = element.data('cross-domain') || null,
        dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType),
        options;

      if (rails.fire(element, 'ajax:before')) {

        if (element.is('form')) {
          method = element.attr('method');
          url = element.attr('action');
          data = element.serializeArray();
          // memoized value from clicked submit button
          var button = element.data('ujs:submit-button');
          if (button) {
            data.push(button);
            element.data('ujs:submit-button', null);
          }
        } else if (element.is(rails.inputChangeSelector)) {
          method = element.data('method');
          url = element.data('url');
          data = element.serialize();
          if (element.data('params')) data = data + "&" + element.data('params');
        } else {
          method = element.data('method');
          url = element.attr('href');
          data = element.data('params') || null;
        }

        options = {
          type: method || 'GET', data: data, dataType: dataType, crossDomain: crossDomain,
          // stopping the "ajax:beforeSend" event will cancel the ajax request
          beforeSend: function(xhr, settings) {
            if (settings.dataType === undefined) {
              xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
            }
            return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
          },
          success: function(data, status, xhr) {
            element.trigger('ajax:success', [data, status, xhr]);
          },
          complete: function(xhr, status) {
            element.trigger('ajax:complete', [xhr, status]);
          },
          error: function(xhr, status, error) {
            element.trigger('ajax:error', [xhr, status, error]);
          }
        };
        // Only pass url to `ajax` options if not blank
        if (url) { options.url = url; }

        return rails.ajax(options);
      } else {
        return false;
      }
    },

    // Handles "data-method" on links such as:
    // Delete
    handleMethod: function(link) {
      var href = link.attr('href'),
        method = link.data('method'),
        target = link.attr('target'),
        csrf_token = $('meta[name=csrf-token]').attr('content'),
        csrf_param = $('meta[name=csrf-param]').attr('content'),
        form = $('<form method="post" action="' + href + '"></form>'),
        metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';

      if (csrf_param !== undefined && csrf_token !== undefined) {
        metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
      }

      if (target) { form.attr('target', target); }

      form.hide().append(metadata_input).appendTo('body');
      form.submit();
    },

    /* Disables form elements:
      - Caches element value in 'ujs:enable-with' data store
      - Replaces element text with value of 'data-disable-with' attribute
      - Sets disabled property to true
    */
    disableFormElements: function(form) {
      form.find(rails.disableSelector).each(function() {
        var element = $(this), method = element.is('button') ? 'html' : 'val';
        element.data('ujs:enable-with', element[method]());
        element[method](element.data('disable-with'));
        element.prop('disabled', true);
      });
    },

    /* Re-enables disabled form elements:
      - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
      - Sets disabled property to false
    */
    enableFormElements: function(form) {
      form.find(rails.enableSelector).each(function() {
        var element = $(this), method = element.is('button') ? 'html' : 'val';
        if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
        element.prop('disabled', false);
      });
    },

   /* For 'data-confirm' attribute:
      - Fires `confirm` event
      - Shows the confirmation dialog
      - Fires the `confirm:complete` event

      Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
      Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
      Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
      return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
   */
    allowAction: function(element) {
      var message = element.data('confirm'),
          answer = false, callback;
      if (!message) { return true; }

      if (rails.fire(element, 'confirm')) {
        answer = rails.confirm(message);
        callback = rails.fire(element, 'confirm:complete', [answer]);
      }
      return answer && callback;
    },

    // Helper function which checks for blank inputs in a form that match the specified CSS selector
    blankInputs: function(form, specifiedSelector, nonBlank) {
      var inputs = $(), input,
        selector = specifiedSelector || 'input,textarea';
      form.find(selector).each(function() {
        input = $(this);
        // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs
        if (nonBlank ? input.val() : !input.val()) {
          inputs = inputs.add(input);
        }
      });
      return inputs.length ? inputs : false;
    },

    // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
    nonBlankInputs: function(form, specifiedSelector) {
      return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
    },

    // Helper function, needed to provide consistent behavior in IE
    stopEverything: function(e) {
      $(e.target).trigger('ujs:everythingStopped');
      e.stopImmediatePropagation();
      return false;
    },

    // find all the submit events directly bound to the form and
    // manually invoke them. If anyone returns false then stop the loop
    callFormSubmitBindings: function(form, event) {
      var events = form.data('events'), continuePropagation = true;
      if (events !== undefined && events['submit'] !== undefined) {
        $.each(events['submit'], function(i, obj){
          if (typeof obj.handler === 'function') return continuePropagation = obj.handler(event);
        });
      }
      return continuePropagation;
    },

    //  replace element's html with the 'data-disable-with' after storing original html
    //  and prevent clicking on it
    disableElement: function(element) {
      element.data('ujs:enable-with', element.html()); // store enabled state
      element.html(element.data('disable-with')); // set to disabled state
      element.bind('click.railsDisable', function(e) { // prevent further clicking
        return rails.stopEverything(e)
      });
    },

    // restore element to its original state which was disabled by 'disableElement' above
    enableElement: function(element) {
      if (element.data('ujs:enable-with') !== undefined) {
        element.html(element.data('ujs:enable-with')); // set to old enabled state
        // this should be element.removeData('ujs:enable-with')
        // but, there is currently a bug in jquery which makes hyphenated data attributes not get removed
        element.data('ujs:enable-with', false); // clean up cache
      }
      element.unbind('click.railsDisable'); // enable element
    }

  };

  $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});

  $(document).delegate(rails.linkDisableSelector, 'ajax:complete', function() {
      rails.enableElement($(this));
  });

  $(document).delegate(rails.linkClickSelector, 'click.rails', function(e) {
    var link = $(this), method = link.data('method'), data = link.data('params');
    if (!rails.allowAction(link)) return rails.stopEverything(e);

    if (link.is(rails.linkDisableSelector)) rails.disableElement(link);

    if (link.data('remote') !== undefined) {
      if ( (e.metaKey || e.ctrlKey) && (!method || method === 'GET') && !data ) { return true; }

      if (rails.handleRemote(link) === false) { rails.enableElement(link); }
      return false;

    } else if (link.data('method')) {
      rails.handleMethod(link);
      return false;
    }
  });

  $(document).delegate(rails.inputChangeSelector, 'change.rails', function(e) {
    var link = $(this);
    if (!rails.allowAction(link)) return rails.stopEverything(e);

    rails.handleRemote(link);
    return false;
  });

  $(document).delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
    var form = $(this),
      remote = form.data('remote') !== undefined,
      blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
      nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);

    if (!rails.allowAction(form)) return rails.stopEverything(e);

    // skip other logic when required values are missing or file upload is present
    if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
      return rails.stopEverything(e);
    }

    if (remote) {
      if (nonBlankFileInputs) {
        return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
      }

      // If browser does not support submit bubbling, then this live-binding will be called before direct
      // bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
      if (!$.support.submitBubbles && $().jquery < '1.7' && rails.callFormSubmitBindings(form, e) === false) return rails.stopEverything(e);

      rails.handleRemote(form);
      return false;

    } else {
      // slight timeout so that the submit button gets properly serialized
      setTimeout(function(){ rails.disableFormElements(form); }, 13);
    }
  });

  $(document).delegate(rails.formInputClickSelector, 'click.rails', function(event) {
    var button = $(this);

    if (!rails.allowAction(button)) return rails.stopEverything(event);

    // register the pressed submit button
    var name = button.attr('name'),
      data = name ? {name:name, value:button.val()} : null;

    button.closest('form').data('ujs:submit-button', data);
  });

  $(document).delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function(event) {
    if (this == event.target) rails.disableFormElements($(this));
  });

  $(document).delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) {
    if (this == event.target) rails.enableFormElements($(this));
  });

})( jQuery );<commit_msg>no longer need with jquery-rails in use<commit_after><|endoftext|>


<commit_before>/*
 * @namespace DomUtil
 *
 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
 * tree, used by Leaflet internally.
 *
 * Most functions expecting or returning a `HTMLElement` also work for
 * SVG elements. The only difference is that classes refer to CSS classes
 * in HTML and SVG classes in SVG.
 */

L.DomUtil = {

	// @function get(id: String|HTMLElement): HTMLElement
	// Returns an element given its DOM id, or returns the element itself
	// if it was passed directly.
	get: function (id) {
		return typeof id === 'string' ? document.getElementById(id) : id;
	},

	// @function getStyle(el: HTMLElement, styleAttrib: String): String
	// Returns the value for a certain style attribute on an element,
	// including computed values or values set through CSS.
	getStyle: function (el, style) {

		var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);

		if ((!value || value === 'auto') && document.defaultView) {
			var css = document.defaultView.getComputedStyle(el, null);
			value = css ? css[style] : null;
		}

		return value === 'auto' ? null : value;
	},

	// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
	// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
	create: function (tagName, className, container) {

		var el = document.createElement(tagName);
		el.className = className;

		if (container) {
			container.appendChild(el);
		}

		return el;
	},

	// @function remove(el: HTMLElement)
	// Removes `el` from its parent element
	remove: function (el) {
		var parent = el.parentNode;
		if (parent) {
			parent.removeChild(el);
		}
	},

	// @function empty(el: HTMLElement)
	// Removes all of `el`'s children elements from `el`
	empty: function (el) {
		while (el.firstChild) {
			el.removeChild(el.firstChild);
		}
	},

	// @function toFront(el: HTMLElement)
	// Makes `el` the last children of its parent, so it renders in front of the other children.
	toFront: function (el) {
		el.parentNode.appendChild(el);
	},

	// @function toBack(el: HTMLElement)
	// Makes `el` the first children of its parent, so it renders back from the other children.
	toBack: function (el) {
		var parent = el.parentNode;
		parent.insertBefore(el, parent.firstChild);
	},

	// @function hasClass(el: HTMLElement, name: String): Boolean
	// Returns `true` if the element's class attribute contains `name`.
	hasClass: function (el, name) {
		if (el.classList !== undefined) {
			return el.classList.contains(name);
		}
		var className = L.DomUtil.getClass(el);
		return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
	},

	// @function addClass(el: HTMLElement, name: String)
	// Adds `name` to the element's class attribute.
	addClass: function (el, name) {
		if (el.classList !== undefined) {
			var classes = L.Util.splitWords(name);
			for (var i = 0, len = classes.length; i < len; i++) {
				el.classList.add(classes[i]);
			}
		} else if (!L.DomUtil.hasClass(el, name)) {
			var className = L.DomUtil.getClass(el);
			L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
		}
	},

	// @function removeClass(el: HTMLElement, name: String)
	// Removes `name` from the element's class attribute.
	removeClass: function (el, name) {
		if (el.classList !== undefined) {
			el.classList.remove(name);
		} else {
			L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
		}
	},

	// @function setClass(el: HTMLElement, name: String)
	// Sets the element's class.
	setClass: function (el, name) {
		if (el.className.baseVal === undefined) {
			el.className = name;
		} else {
			// in case of SVG element
			el.className.baseVal = name;
		}
	},

	// @function setClass(el: HTMLElement, name: String)
	// Sets the element's class.
	getClass: function (el) {
		return el.className.baseVal === undefined ? el.className : el.className.baseVal;
	},

	// @function setOpacity(el: HTMLElement, opacity: Number)
	// Set the opacity of an element (including old IE support).
	// `opacity` must be a number from `0` to `1`.
	setOpacity: function (el, value) {

		if ('opacity' in el.style) {
			el.style.opacity = value;

		} else if ('filter' in el.style) {
			L.DomUtil._setOpacityIE(el, value);
		}
	},

	_setOpacityIE: function (el, value) {
		var filter = false,
		    filterName = 'DXImageTransform.Microsoft.Alpha';

		// filters collection throws an error if we try to retrieve a filter that doesn't exist
		try {
			filter = el.filters.item(filterName);
		} catch (e) {
			// don't set opacity to 1 if we haven't already set an opacity,
			// it isn't needed and breaks transparent pngs.
			if (value === 1) { return; }
		}

		value = Math.round(value * 100);

		if (filter) {
			filter.Enabled = (value !== 100);
			filter.Opacity = value;
		} else {
			el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
		}
	},

	// @function testProp(props: String[]): String|false
	// Goes through the array of style names and returns the first name
	// that is a valid style name for an element. If no such name is found,
	// it returns false. Useful for vendor-prefixed styles like `transform`.
	testProp: function (props) {

		var style = document.documentElement.style;

		for (var i = 0; i < props.length; i++) {
			if (props[i] in style) {
				return props[i];
			}
		}
		return false;
	},

	// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
	// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
	// and optionally scaled by `scale`. Does not have an effect if the
	// browser doesn't support 3D CSS transforms.
	setTransform: function (el, offset, scale) {
		var pos = offset || new L.Point(0, 0);

		el.style[L.DomUtil.TRANSFORM] =
			(L.Browser.ie3d ?
				'translate(' + pos.x + 'px,' + pos.y + 'px)' :
				'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
			(scale ? ' scale(' + scale + ')' : '');
	},

	// @function setPosition(el: HTMLElement, position: Point)
	// Sets the position of `el` to coordinates specified by `position`,
	// using CSS translate or top/left positioning depending on the browser
	// (used by Leaflet internally to position its layers).
	setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])

		/*eslint-disable */
		el._leaflet_pos = point;
		/*eslint-enable */

		if (L.Browser.any3d) {
			L.DomUtil.setTransform(el, point);
		} else {
			el.style.left = point.x + 'px';
			el.style.top = point.y + 'px';
		}
	},

	// @function getPosition(el: HTMLElement): Point
	// Returns the coordinates of an element previously positioned with setPosition.
	getPosition: function (el) {
		// this method is only used for elements previously positioned using setPosition,
		// so it's safe to cache the position for performance

		return el._leaflet_pos || new L.Point(0, 0);
	}
};

(function () {
	// prefix style property names

	// @property TRANSFORM: String
	// Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
	L.DomUtil.TRANSFORM = L.DomUtil.testProp(
			['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);

	// webkitTransition comes first because some browser versions that drop vendor prefix don't do
	// the same for the transitionend event, in particular the Android 4.1 stock browser

	// @property TRANSITION: String
	// Vendor-prefixed transform style name.
	var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
			['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);

	L.DomUtil.TRANSITION_END =
			transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';

	// @function disableTextSelection()
	// Prevents the user from generating `selectstart` DOM events, usually generated
	// when the user drags the mouse through a page with text. Used internally
	// by Leaflet to override the behaviour of any click-and-drag interaction on
	// the map. Affects drag interactions on the whole document.

	// @function enableTextSelection()
	// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
	if ('onselectstart' in document) {
		L.DomUtil.disableTextSelection = function () {
			L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
		};
		L.DomUtil.enableTextSelection = function () {
			L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
		};

	} else {
		var userSelectProperty = L.DomUtil.testProp(
			['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);

		L.DomUtil.disableTextSelection = function () {
			if (userSelectProperty) {
				var style = document.documentElement.style;
				this._userSelect = style[userSelectProperty];
				style[userSelectProperty] = 'none';
			}
		};
		L.DomUtil.enableTextSelection = function () {
			if (userSelectProperty) {
				document.documentElement.style[userSelectProperty] = this._userSelect;
				delete this._userSelect;
			}
		};
	}

	// @function disableImageDrag()
	// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
	// for `dragstart` DOM events, usually generated when the user drags an image.
	L.DomUtil.disableImageDrag = function () {
		L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
	};

	// @function enableImageDrag()
	// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
	L.DomUtil.enableImageDrag = function () {
		L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
	};

	// @function preventOutline(el: HTMLElement)
	// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
	// of the element `el` invisible. Used internally by Leaflet to prevent
	// focusable elements from displaying an outline when the user performs a
	// drag interaction on them.
	L.DomUtil.preventOutline = function (element) {
		while (element.tabIndex === -1) {
			element = element.parentNode;
		}
		if (!element || !element.style) { return; }
		L.DomUtil.restoreOutline();
		this._outlineElement = element;
		this._outlineStyle = element.style.outline;
		element.style.outline = 'none';
		L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
	};

	// @function restoreOutline()
	// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
	L.DomUtil.restoreOutline = function () {
		if (!this._outlineElement) { return; }
		this._outlineElement.style.outline = this._outlineStyle;
		delete this._outlineElement;
		delete this._outlineStyle;
		L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
	};
})();
<commit_msg>fix DomUtil.getClass docstring (#4438)<commit_after>/*
 * @namespace DomUtil
 *
 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
 * tree, used by Leaflet internally.
 *
 * Most functions expecting or returning a `HTMLElement` also work for
 * SVG elements. The only difference is that classes refer to CSS classes
 * in HTML and SVG classes in SVG.
 */

L.DomUtil = {

	// @function get(id: String|HTMLElement): HTMLElement
	// Returns an element given its DOM id, or returns the element itself
	// if it was passed directly.
	get: function (id) {
		return typeof id === 'string' ? document.getElementById(id) : id;
	},

	// @function getStyle(el: HTMLElement, styleAttrib: String): String
	// Returns the value for a certain style attribute on an element,
	// including computed values or values set through CSS.
	getStyle: function (el, style) {

		var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);

		if ((!value || value === 'auto') && document.defaultView) {
			var css = document.defaultView.getComputedStyle(el, null);
			value = css ? css[style] : null;
		}

		return value === 'auto' ? null : value;
	},

	// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
	// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
	create: function (tagName, className, container) {

		var el = document.createElement(tagName);
		el.className = className;

		if (container) {
			container.appendChild(el);
		}

		return el;
	},

	// @function remove(el: HTMLElement)
	// Removes `el` from its parent element
	remove: function (el) {
		var parent = el.parentNode;
		if (parent) {
			parent.removeChild(el);
		}
	},

	// @function empty(el: HTMLElement)
	// Removes all of `el`'s children elements from `el`
	empty: function (el) {
		while (el.firstChild) {
			el.removeChild(el.firstChild);
		}
	},

	// @function toFront(el: HTMLElement)
	// Makes `el` the last children of its parent, so it renders in front of the other children.
	toFront: function (el) {
		el.parentNode.appendChild(el);
	},

	// @function toBack(el: HTMLElement)
	// Makes `el` the first children of its parent, so it renders back from the other children.
	toBack: function (el) {
		var parent = el.parentNode;
		parent.insertBefore(el, parent.firstChild);
	},

	// @function hasClass(el: HTMLElement, name: String): Boolean
	// Returns `true` if the element's class attribute contains `name`.
	hasClass: function (el, name) {
		if (el.classList !== undefined) {
			return el.classList.contains(name);
		}
		var className = L.DomUtil.getClass(el);
		return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
	},

	// @function addClass(el: HTMLElement, name: String)
	// Adds `name` to the element's class attribute.
	addClass: function (el, name) {
		if (el.classList !== undefined) {
			var classes = L.Util.splitWords(name);
			for (var i = 0, len = classes.length; i < len; i++) {
				el.classList.add(classes[i]);
			}
		} else if (!L.DomUtil.hasClass(el, name)) {
			var className = L.DomUtil.getClass(el);
			L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
		}
	},

	// @function removeClass(el: HTMLElement, name: String)
	// Removes `name` from the element's class attribute.
	removeClass: function (el, name) {
		if (el.classList !== undefined) {
			el.classList.remove(name);
		} else {
			L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
		}
	},

	// @function setClass(el: HTMLElement, name: String)
	// Sets the element's class.
	setClass: function (el, name) {
		if (el.className.baseVal === undefined) {
			el.className = name;
		} else {
			// in case of SVG element
			el.className.baseVal = name;
		}
	},

	// @function getClass(el: HTMLElement): String
	// Returns the element's class.
	getClass: function (el) {
		return el.className.baseVal === undefined ? el.className : el.className.baseVal;
	},

	// @function setOpacity(el: HTMLElement, opacity: Number)
	// Set the opacity of an element (including old IE support).
	// `opacity` must be a number from `0` to `1`.
	setOpacity: function (el, value) {

		if ('opacity' in el.style) {
			el.style.opacity = value;

		} else if ('filter' in el.style) {
			L.DomUtil._setOpacityIE(el, value);
		}
	},

	_setOpacityIE: function (el, value) {
		var filter = false,
		    filterName = 'DXImageTransform.Microsoft.Alpha';

		// filters collection throws an error if we try to retrieve a filter that doesn't exist
		try {
			filter = el.filters.item(filterName);
		} catch (e) {
			// don't set opacity to 1 if we haven't already set an opacity,
			// it isn't needed and breaks transparent pngs.
			if (value === 1) { return; }
		}

		value = Math.round(value * 100);

		if (filter) {
			filter.Enabled = (value !== 100);
			filter.Opacity = value;
		} else {
			el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
		}
	},

	// @function testProp(props: String[]): String|false
	// Goes through the array of style names and returns the first name
	// that is a valid style name for an element. If no such name is found,
	// it returns false. Useful for vendor-prefixed styles like `transform`.
	testProp: function (props) {

		var style = document.documentElement.style;

		for (var i = 0; i < props.length; i++) {
			if (props[i] in style) {
				return props[i];
			}
		}
		return false;
	},

	// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
	// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
	// and optionally scaled by `scale`. Does not have an effect if the
	// browser doesn't support 3D CSS transforms.
	setTransform: function (el, offset, scale) {
		var pos = offset || new L.Point(0, 0);

		el.style[L.DomUtil.TRANSFORM] =
			(L.Browser.ie3d ?
				'translate(' + pos.x + 'px,' + pos.y + 'px)' :
				'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
			(scale ? ' scale(' + scale + ')' : '');
	},

	// @function setPosition(el: HTMLElement, position: Point)
	// Sets the position of `el` to coordinates specified by `position`,
	// using CSS translate or top/left positioning depending on the browser
	// (used by Leaflet internally to position its layers).
	setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])

		/*eslint-disable */
		el._leaflet_pos = point;
		/*eslint-enable */

		if (L.Browser.any3d) {
			L.DomUtil.setTransform(el, point);
		} else {
			el.style.left = point.x + 'px';
			el.style.top = point.y + 'px';
		}
	},

	// @function getPosition(el: HTMLElement): Point
	// Returns the coordinates of an element previously positioned with setPosition.
	getPosition: function (el) {
		// this method is only used for elements previously positioned using setPosition,
		// so it's safe to cache the position for performance

		return el._leaflet_pos || new L.Point(0, 0);
	}
};

(function () {
	// prefix style property names

	// @property TRANSFORM: String
	// Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
	L.DomUtil.TRANSFORM = L.DomUtil.testProp(
			['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);

	// webkitTransition comes first because some browser versions that drop vendor prefix don't do
	// the same for the transitionend event, in particular the Android 4.1 stock browser

	// @property TRANSITION: String
	// Vendor-prefixed transform style name.
	var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
			['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);

	L.DomUtil.TRANSITION_END =
			transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';

	// @function disableTextSelection()
	// Prevents the user from generating `selectstart` DOM events, usually generated
	// when the user drags the mouse through a page with text. Used internally
	// by Leaflet to override the behaviour of any click-and-drag interaction on
	// the map. Affects drag interactions on the whole document.

	// @function enableTextSelection()
	// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
	if ('onselectstart' in document) {
		L.DomUtil.disableTextSelection = function () {
			L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
		};
		L.DomUtil.enableTextSelection = function () {
			L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
		};

	} else {
		var userSelectProperty = L.DomUtil.testProp(
			['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);

		L.DomUtil.disableTextSelection = function () {
			if (userSelectProperty) {
				var style = document.documentElement.style;
				this._userSelect = style[userSelectProperty];
				style[userSelectProperty] = 'none';
			}
		};
		L.DomUtil.enableTextSelection = function () {
			if (userSelectProperty) {
				document.documentElement.style[userSelectProperty] = this._userSelect;
				delete this._userSelect;
			}
		};
	}

	// @function disableImageDrag()
	// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
	// for `dragstart` DOM events, usually generated when the user drags an image.
	L.DomUtil.disableImageDrag = function () {
		L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
	};

	// @function enableImageDrag()
	// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
	L.DomUtil.enableImageDrag = function () {
		L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
	};

	// @function preventOutline(el: HTMLElement)
	// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
	// of the element `el` invisible. Used internally by Leaflet to prevent
	// focusable elements from displaying an outline when the user performs a
	// drag interaction on them.
	L.DomUtil.preventOutline = function (element) {
		while (element.tabIndex === -1) {
			element = element.parentNode;
		}
		if (!element || !element.style) { return; }
		L.DomUtil.restoreOutline();
		this._outlineElement = element;
		this._outlineStyle = element.style.outline;
		element.style.outline = 'none';
		L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
	};

	// @function restoreOutline()
	// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
	L.DomUtil.restoreOutline = function () {
		if (!this._outlineElement) { return; }
		this._outlineElement.style.outline = this._outlineStyle;
		delete this._outlineElement;
		delete this._outlineStyle;
		L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
	};
})();
<|endoftext|>


<commit_before>define(['marionette', 'backgrid',
    'utils',
    'modules/projects/views/addto'], function(Marionette, Backgrid, utils, AddToProjectView) {

    return {
        ClickableRow: Backgrid.Row.extend({
            events: {
                'click': 'onClick',
            },

            onClick: function(e) {
                if ($(e.target).is('i') || $(e.target).is('a') || $(e.target).is('input') || $(e.target).hasClass('editable')) return
                if (this.cookie && this.model.get('PROP')) app.cookie(this.model.get('PROP'))
                app.trigger(this.event, this.model.get(this.argument))
            },
        }),

        BGSelectRow: Backgrid.Row.extend({
            events: {
                'click': 'onClick',
            },
            onClick: function() {
                this.model.trigger('backgrid:select', this.model, !this.$el.find('input[type=checkbox]').is(':checked'))
            },
        }),

        TemplateCell: Backgrid.Cell.extend({
            render: function() {
                this.$el.empty();

                if (!this.column.get('test') || (this.column.get('test') && this.model.get(this.column.get('test')) && this.model.get(this.column.get('test')) != '0')) {

                    var temp = _.isFunction(this.getTemplate) ? _.result(this, 'getTemplate') : this.column.get('template')
                    var t = _.isFunction(temp) ? temp : _.template(temp)

                    var data = _.extend({}, this.model.toJSON(), { APIURL: app.apiurl })
                    this.$el.html(t(data))
                }

                this.delegateEvents();
                return this;
            }
        }),

        ProjectCell: Backgrid.Cell.extend({
            events: {
              'click a.atp': 'addToProject',
            },

            addToProject: function(e) {
              e.preventDefault()
              app.dialog.show(new AddToProjectView({ name: this.model.get(this.column.get('itemname')), type: this.column.get('itemtype'), iid: this.model.get(this.column.get('itemid')) }))
            },

            render: function() {
              this.$el.append(' Add to Project')
              return this
            }
        }),

        HTMLCell: Backgrid.Cell.extend({
            render: function(column) {
                this.$el.empty();
                this.$el.html(this.model.get(this.column.get('name')));
                if (!this.column.get('center')) this.$el.addClass('la')
                this.delegateEvents();
                return this;
            }
        }),

        StatusCell: Backgrid.Cell.extend({
            render: function() {
                this.$el.empty()

                var st = ''
                _.each(['R', 'SC', 'AI', 'DC', 'AP'], function(t) {
                    if (this.model.get(t) > 0) st = '

'
                }, this)

                if (st) this.$el.append('
'+st+'
')

                return this
            }
        }),

        ShadedCell: Backgrid.Cell.extend({
            render: function() {
                var val = this.model.get(this.column.get('name'))
                this.$el.text(val)

                var vals = this.model.collection.fullCollection.pluck(this.column.get('name'))
                var avg = _.reduce(vals, function(v, n) { return v + n }, 0) / vals.length

                col = null
                if (val > avg) {
                    col = utils.shadeColor('#00cc00', 1-(0.35*(val/avg)))
                }

                if (val < avg) {
                    col = utils.shadeColor('#cc0000', 0.55*(val/avg))
                }

                if (col) this.$el.css('background-color', col)

                return this
            }
        })
    }

})
    <commit_msg>dont trigger row select when clicking input field<commit_after>define(['marionette', 'backgrid',
    'utils',
    'modules/projects/views/addto'], function(Marionette, Backgrid, utils, AddToProjectView) {

    return {
        ClickableRow: Backgrid.Row.extend({
            events: {
                'click': 'onClick',
            },

            onClick: function(e) {
                if ($(e.target).is('i') || $(e.target).is('a') || $(e.target).is('input') || $(e.target).hasClass('editable')) return
                if (this.cookie && this.model.get('PROP')) app.cookie(this.model.get('PROP'))
                app.trigger(this.event, this.model.get(this.argument))
            },
        }),

        BGSelectRow: Backgrid.Row.extend({
            events: {
                'click': 'onClick',
            },
            onClick: function(e) {
                if ($(e.target).is('input[type=checkbox]')) return
                this.model.trigger('backgrid:select', this.model, !this.$el.find('input[type=checkbox]').is(':checked'))
            },
        }),

        TemplateCell: Backgrid.Cell.extend({
            render: function() {
                this.$el.empty();

                if (!this.column.get('test') || (this.column.get('test') && this.model.get(this.column.get('test')) && this.model.get(this.column.get('test')) != '0')) {

                    var temp = _.isFunction(this.getTemplate) ? _.result(this, 'getTemplate') : this.column.get('template')
                    var t = _.isFunction(temp) ? temp : _.template(temp)

                    var data = _.extend({}, this.model.toJSON(), { APIURL: app.apiurl })
                    this.$el.html(t(data))
                }

                this.delegateEvents();
                return this;
            }
        }),

        ProjectCell: Backgrid.Cell.extend({
            events: {
              'click a.atp': 'addToProject',
            },

            addToProject: function(e) {
              e.preventDefault()
              app.dialog.show(new AddToProjectView({ name: this.model.get(this.column.get('itemname')), type: this.column.get('itemtype'), iid: this.model.get(this.column.get('itemid')) }))
            },

            render: function() {
              this.$el.append(' Add to Project')
              return this
            }
        }),

        HTMLCell: Backgrid.Cell.extend({
            render: function(column) {
                this.$el.empty();
                this.$el.html(this.model.get(this.column.get('name')));
                if (!this.column.get('center')) this.$el.addClass('la')
                this.delegateEvents();
                return this;
            }
        }),

        StatusCell: Backgrid.Cell.extend({
            render: function() {
                this.$el.empty()

                var st = ''
                _.each(['R', 'SC', 'AI', 'DC', 'AP'], function(t) {
                    if (this.model.get(t) > 0) st = '

'
                }, this)

                if (st) this.$el.append('
'+st+'
')

                return this
            }
        }),

        ShadedCell: Backgrid.Cell.extend({
            render: function() {
                var val = this.model.get(this.column.get('name'))
                this.$el.text(val)

                var vals = this.model.collection.fullCollection.pluck(this.column.get('name'))
                var avg = _.reduce(vals, function(v, n) { return v + n }, 0) / vals.length

                col = null
                if (val > avg) {
                    col = utils.shadeColor('#00cc00', 1-(0.35*(val/avg)))
                }

                if (val < avg) {
                    col = utils.shadeColor('#cc0000', 0.55*(val/avg))
                }

                if (col) this.$el.css('background-color', col)

                return this
            }
        })
    }

})
    <|endoftext|>


<commit_before>/**
 * model.js
 * @flow
 */

import _ from "lodash";
import Store from "oxalis/store";
import type {
  DatasetType,
  BoundingBoxObjectType,
  RestrictionsType,
  SettingsType,
  NodeType,
  EdgeType,
  CommentType,
  BranchPointType,
  SkeletonTracingTypeTracingType,
  VolumeTracingTypeTracingType,
  TaskType,
} from "oxalis/store";
import type { UrlManagerState } from "oxalis/controller/url_manager";
import {
  setDatasetAction,
  setViewModeAction,
  setControlModeAction,
} from "oxalis/model/actions/settings_actions";
import {
  setActiveNodeAction,
  initializeSkeletonTracingAction,
} from "oxalis/model/actions/skeletontracing_actions";
import { initializeVolumeTracingAction } from "oxalis/model/actions/volumetracing_actions";
import { initializeReadOnlyTracingAction } from "oxalis/model/actions/readonlytracing_actions";
import { setTaskAction } from "oxalis/model/actions/task_actions";
import {
  setPositionAction,
  setZoomStepAction,
  setRotationAction,
} from "oxalis/model/actions/flycam_actions";
import { saveNowAction } from "oxalis/model/actions/save_actions";
import window from "libs/window";
import Utils from "libs/utils";
import Binary from "oxalis/model/binary";
import ConnectionInfo from "oxalis/model/binarydata_connection_info";
import { getIntegerZoomStep } from "oxalis/model/accessors/flycam_accessor";
import constants, { Vector3Indicies, ControlModeEnum, ModeValues } from "oxalis/constants";
import type { Vector3, ControlModeType } from "oxalis/constants";
import Request from "libs/request";
import Toast from "libs/toast";
import ErrorHandling from "libs/error_handling";
import WkLayer from "oxalis/model/binary/layers/wk_layer";
import NdStoreLayer from "oxalis/model/binary/layers/nd_store_layer";
import update from "immutability-helper";
import UrlManager from "oxalis/controller/url_manager";

type SkeletonContentTreeType = {
  id: number,
  color: ?Vector3,
  name: string,
  timestamp: number,
  comments: Array<CommentType>,
  branchPoints: Array<BranchPointType>,
  edges: Array<EdgeType>,
  nodes: Array<NodeType>,
};

export type SkeletonContentDataType = {
  activeNode: null | number,
  trees: Array<SkeletonContentTreeType>,
  zoomLevel: number,
  customLayers: null,
  zoomLevel: number,
};

export type VolumeContentDataType = {
  activeCell: null | number,
  nextCell: ?number,
  customLayers: Array<Object>,
  boundingBox: BoundingBoxObjectType,
  name: string,
  zoomLevel: number,
};

export type ServerTracing<T> = {
  actions: Array<any>,
  content: {
    boundingBox: BoundingBoxObjectType,
    contentData: T,
    contentType: string,
    dataSet: DatasetType,
    editPosition: Vector3,
    editRotation: Vector3,
    settings: SettingsType,
  },
  contentType: string,
  created: string,
  dataSetName: string,
  downloadUrl: string,
  formattedHash: string,
  id: string,
  name: string,
  restrictions: RestrictionsType,
  state: UrlManagerState,
  stateLabel: string,
  stats: any,
  task: TaskType,
  tracingTime: number,
  typ: SkeletonTracingTypeTracingType | VolumeTracingTypeTracingType,
  user: any,
  version: number,
  isPublic: boolean,
  tags: Array<string>,
  description: string,
};

// TODO: Non-reactive
export class OxalisModel {
  HANDLED_ERROR = "error_was_handled";

  connectionInfo: ConnectionInfo;
  binary: {
    [key: string]: Binary,
  };
  lowerBoundary: Vector3;
  upperBoundary: Vector3;
  tracing: ServerTracing<SkeletonContentDataType | VolumeContentDataType>;

  async fetch(
    tracingType: SkeletonTracingTypeTracingType,
    tracingId: string,
    controlMode: ControlModeType,
    initialFetch: boolean,
  ) {
    Store.dispatch(setControlModeAction(controlMode));

    let infoUrl;
    if (controlMode === ControlModeEnum.TRACE) {
      // Include /readOnly part whenever it is in the pathname
      const isReadOnly = window.location.pathname.endsWith("/readOnly");
      const readOnlyPart = isReadOnly ? "readOnly/" : "";
      infoUrl = `/annotations/${tracingType}/${tracingId}/${readOnlyPart}info`;
    } else {
      infoUrl = `/annotations/${tracingType}/${tracingId}/info`;
    }

    const tracing: ServerTracing<*> = await Request.receiveJSON(infoUrl);

    let error;
    const dataset = tracing.content.dataSet;
    if (tracing.error) {
      ({ error } = tracing);
    } else if (!dataset) {
      error = "Selected dataset doesn't exist";
    } else if (!dataset.dataLayers) {
      if (dataset.name) {
        error = `Please, double check if you have the dataset '${dataset.name}' imported.`;
      } else {
        error = "Please, make sure you have a dataset imported.";
      }
    }

    if (error) {
      Toast.error(error);
      throw this.HANDLED_ERROR;
    }

    if (!tracing.restrictions.allowAccess) {
      error = "You are not allowed to access this tracing";
      throw this.HANDLED_ERROR;
    }

    // Make sure subsequent fetch calls are always for the same dataset
    if (!_.isEmpty(this.binary)) {
      ErrorHandling.assert(
        _.isEqual(dataset, Store.getState().dataset),
        "Model.fetch was called for a task with another dataset, without reloading the page.",
      );
    }

    ErrorHandling.assertExtendContext({
      task: tracing.id,
      dataSet: dataset.name,
    });

    Store.dispatch(setDatasetAction(dataset));
    Store.dispatch(setTaskAction(tracing.task));

    // Only initialize the model once.
    // There is no need to reinstantiate the binaries if the dataset didn't change.
    if (initialFetch) {
      this.initializeModel(tracing);
    }
    this.initializeTracing(tracing, initialFetch);

    return tracing;
  }

  determineAllowedModes(settings: SettingsType) {
    // The order of allowedModes should be independent from the server and instead be similar to ModeValues
    let allowedModes = _.intersection(ModeValues, settings.allowedModes);

    const colorLayer = _.find(Store.getState().dataset.dataLayers, { category: "color" });
    if (colorLayer != null && colorLayer.elementClass !== "uint8") {
      allowedModes = allowedModes.filter(mode => !constants.MODES_ARBITRARY.includes(mode));
    }

    let preferredMode = null;
    if (settings.preferredMode != null) {
      const modeId = settings.preferredMode;
      if (allowedModes.includes(modeId)) {
        preferredMode = modeId;
      }
    }

    return { preferredMode, allowedModes };
  }

  initializeTracing(
    tracing: ServerTracing<SkeletonContentDataType | VolumeContentDataType>,
    initialFetch: boolean,
  ) {
    const { allowedModes, preferredMode } = this.determineAllowedModes(tracing.content.settings);
    _.extend(tracing.content.settings, { allowedModes, preferredMode });

    const isVolume = tracing.content.settings.allowedModes.includes("volume");
    const controlMode = Store.getState().temporaryConfiguration.controlMode;
    if (controlMode === ControlModeEnum.TRACE) {
      if (isVolume) {
        ErrorHandling.assert(
          this.getSegmentationBinary() != null,
          "Volume is allowed, but segmentation does not exist",
        );
        const volumeTracing: ServerTracing<VolumeContentDataType> = (tracing: ServerTracing<any>);
        Store.dispatch(initializeVolumeTracingAction(volumeTracing));
      } else {
        const skeletonTracing: ServerTracing<SkeletonContentDataType> = (tracing: ServerTracing<
          any,
        >);
        Store.dispatch(initializeSkeletonTracingAction(skeletonTracing));
      }
    } else {
      const readOnlyTracing: ServerTracing<SkeletonContentDataType> = (tracing: ServerTracing<any>);
      Store.dispatch(initializeReadOnlyTracingAction(readOnlyTracing));
    }

    if (initialFetch) {
      Store.dispatch(setZoomStepAction(tracing.content.contentData.zoomLevel));
    }

    // Initialize 'flight', 'oblique' or 'orthogonal'/'volume' mode
    if (allowedModes.length === 0) {
      Toast.error("There was no valid allowed tracing mode specified.");
    } else {
      const mode = preferredMode || UrlManager.initialState.mode || allowedModes[0];
      Store.dispatch(setViewModeAction(mode));
    }

    this.applyState(UrlManager.initialState, tracing);
  }

  initializeModel(tracing: ServerTracing<SkeletonContentDataType | VolumeContentDataType>) {
    const dataset = tracing.content.dataSet;
    const { dataStore } = dataset;

    const LayerClass = (() => {
      switch (dataStore.typ) {
        case "webknossos-store":
          return WkLayer;
        case "ndstore":
          return NdStoreLayer;
        default:
          throw new Error(`Unknown datastore type: ${dataStore.typ}`);
      }
    })();

    const layers = this.getLayerInfos(tracing.content.contentData.customLayers).map(
      layerInfo => new LayerClass(layerInfo, dataStore),
    );

    this.connectionInfo = new ConnectionInfo();
    this.binary = {};

    let maxZoomStep = -Infinity;

    for (const layer of layers) {
      const maxLayerZoomStep = Math.log(Math.max(...layer.resolutions)) / Math.LN2;
      this.binary[layer.name] = new Binary(tracing, layer, maxLayerZoomStep, this.connectionInfo);
      maxZoomStep = Math.max(maxZoomStep, maxLayerZoomStep);
    }

    this.buildMappingsObject();

    if (this.getColorBinaries().length === 0) {
      Toast.error("No data available! Something seems to be wrong with the dataset.");
      throw this.HANDLED_ERROR;
    }

    this.computeBoundaries();

    // TODO: remove
    this.tracing = tracing;
  }

  // For now, since we have no UI for this
  buildMappingsObject() {
    const segmentationBinary = this.getSegmentationBinary();

    if (segmentationBinary != null) {
      window.mappings = {
        getAll() {
          return segmentationBinary.mappings.getMappingNames();
        },
        getActive() {
          return segmentationBinary.activeMapping;
        },
        activate(mapping) {
          return segmentationBinary.setActiveMapping(mapping);
        },
      };
    }
  }

  getColorBinaries() {
    return _.filter(this.binary, binary => binary.category === "color");
  }

  getSegmentationBinary() {
    return _.find(this.binary, binary => binary.category === "segmentation");
  }

  getBinaryByName(name: string) {
    return this.binary[name];
  }

  getLayerInfos(userLayers: ?Array<Object>) {
    // Overwrite or extend layers with userLayers

    const dataset = Store.getState().dataset;
    const layers = dataset == null ? [] : _.clone(dataset.dataLayers);
    if (userLayers == null) {
      return layers;
    }

    for (const userLayer of userLayers) {
      const existingLayerIndex = _.findIndex(
        layers,
        layer => layer.name === Utils.__guard__(userLayer.fallback, x => x.layerName),
      );
      const existingLayer = layers[existingLayerIndex];

      if (existingLayer != null) {
        layers[existingLayerIndex] = update(userLayer, {
          $merge: { mappings: existingLayer.mappings },
        });
      } else {
        layers.push(userLayer);
      }
    }

    return layers;
  }

  shouldDisplaySegmentationData(): boolean {
    const segmentationOpacity = Store.getState().datasetConfiguration.segmentationOpacity;
    if (segmentationOpacity === 0) {
      return false;
    }
    const currentViewMode = Store.getState().temporaryConfiguration.viewMode;

    // Currently segmentation data can only be displayed in orthogonal and volume mode
    const canModeDisplaySegmentationData = constants.MODES_PLANE.includes(currentViewMode);
    return this.getSegmentationBinary() != null && canModeDisplaySegmentationData;
  }

  canDisplaySegmentationData(): boolean {
    return getIntegerZoomStep(Store.getState()) <= 1;
  }

  computeBoundaries() {
    this.lowerBoundary = [Infinity, Infinity, Infinity];
    this.upperBoundary = [-Infinity, -Infinity, -Infinity];

    for (const key of Object.keys(this.binary)) {
      const binary = this.binary[key];
      for (const i of Vector3Indicies) {
        this.lowerBoundary[i] = Math.min(this.lowerBoundary[i], binary.lowerBoundary[i]);
        this.upperBoundary[i] = Math.max(this.upperBoundary[i], binary.upperBoundary[i]);
      }
    }
  }

  applyState(
    state: UrlManagerState,
    tracing: ServerTracing<SkeletonContentDataType | VolumeContentDataType>,
  ) {
    Store.dispatch(setPositionAction(state.position || tracing.content.editPosition));
    if (state.zoomStep != null) {
      Store.dispatch(setZoomStepAction(state.zoomStep));
    }

    const rotation = state.rotation || tracing.content.editRotation;
    if (rotation != null) {
      Store.dispatch(setRotationAction(rotation));
    }

    if (state.activeNode != null) {
      Store.dispatch(setActiveNodeAction(state.activeNode));
    }
  }

  stateSaved() {
    const state = Store.getState();
    const storeStateSaved = !state.save.isBusy && state.save.queue.length === 0;
    const pushQueuesSaved = _.reduce(
      this.binary,
      (saved, binary) => saved && binary.pushQueue.stateSaved(),
      true,
    );
    return storeStateSaved && pushQueuesSaved;
  }

  save = async () => {
    Store.dispatch(saveNowAction());
    while (!this.stateSaved()) {
      // eslint-disable-next-line no-await-in-loop
      await Utils.sleep(500);
    }
  };
}

// export the model as a singleton
export default new OxalisModel();
<commit_msg>allow to create a new segmentation for volume tracings by ignoring existing segmentation layers (#2033)<commit_after>/**
 * model.js
 * @flow
 */

import _ from "lodash";
import Store from "oxalis/store";
import type {
  DatasetType,
  BoundingBoxObjectType,
  RestrictionsType,
  SettingsType,
  NodeType,
  EdgeType,
  CommentType,
  BranchPointType,
  SkeletonTracingTypeTracingType,
  VolumeTracingTypeTracingType,
  TaskType,
} from "oxalis/store";
import type { UrlManagerState } from "oxalis/controller/url_manager";
import {
  setDatasetAction,
  setViewModeAction,
  setControlModeAction,
} from "oxalis/model/actions/settings_actions";
import {
  setActiveNodeAction,
  initializeSkeletonTracingAction,
} from "oxalis/model/actions/skeletontracing_actions";
import { initializeVolumeTracingAction } from "oxalis/model/actions/volumetracing_actions";
import { initializeReadOnlyTracingAction } from "oxalis/model/actions/readonlytracing_actions";
import { setTaskAction } from "oxalis/model/actions/task_actions";
import {
  setPositionAction,
  setZoomStepAction,
  setRotationAction,
} from "oxalis/model/actions/flycam_actions";
import { saveNowAction } from "oxalis/model/actions/save_actions";
import window from "libs/window";
import Utils from "libs/utils";
import Binary from "oxalis/model/binary";
import ConnectionInfo from "oxalis/model/binarydata_connection_info";
import { getIntegerZoomStep } from "oxalis/model/accessors/flycam_accessor";
import constants, { Vector3Indicies, ControlModeEnum, ModeValues } from "oxalis/constants";
import type { Vector3, ControlModeType } from "oxalis/constants";
import Request from "libs/request";
import Toast from "libs/toast";
import ErrorHandling from "libs/error_handling";
import WkLayer from "oxalis/model/binary/layers/wk_layer";
import NdStoreLayer from "oxalis/model/binary/layers/nd_store_layer";
import update from "immutability-helper";
import UrlManager from "oxalis/controller/url_manager";

type SkeletonContentTreeType = {
  id: number,
  color: ?Vector3,
  name: string,
  timestamp: number,
  comments: Array<CommentType>,
  branchPoints: Array<BranchPointType>,
  edges: Array<EdgeType>,
  nodes: Array<NodeType>,
};

export type SkeletonContentDataType = {
  activeNode: null | number,
  trees: Array<SkeletonContentTreeType>,
  zoomLevel: number,
  customLayers: null,
  zoomLevel: number,
};

export type VolumeContentDataType = {
  activeCell: null | number,
  nextCell: ?number,
  customLayers: Array<Object>,
  boundingBox: BoundingBoxObjectType,
  name: string,
  zoomLevel: number,
};

export type ServerTracing<T> = {
  actions: Array<any>,
  content: {
    boundingBox: BoundingBoxObjectType,
    contentData: T,
    contentType: string,
    dataSet: DatasetType,
    editPosition: Vector3,
    editRotation: Vector3,
    settings: SettingsType,
  },
  contentType: string,
  created: string,
  dataSetName: string,
  downloadUrl: string,
  formattedHash: string,
  id: string,
  name: string,
  restrictions: RestrictionsType,
  state: UrlManagerState,
  stateLabel: string,
  stats: any,
  task: TaskType,
  tracingTime: number,
  typ: SkeletonTracingTypeTracingType | VolumeTracingTypeTracingType,
  user: any,
  version: number,
  isPublic: boolean,
  tags: Array<string>,
  description: string,
};

// TODO: Non-reactive
export class OxalisModel {
  HANDLED_ERROR = "error_was_handled";

  connectionInfo: ConnectionInfo;
  binary: {
    [key: string]: Binary,
  };
  lowerBoundary: Vector3;
  upperBoundary: Vector3;
  tracing: ServerTracing<SkeletonContentDataType | VolumeContentDataType>;

  async fetch(
    tracingType: SkeletonTracingTypeTracingType,
    tracingId: string,
    controlMode: ControlModeType,
    initialFetch: boolean,
  ) {
    Store.dispatch(setControlModeAction(controlMode));

    let infoUrl;
    if (controlMode === ControlModeEnum.TRACE) {
      // Include /readOnly part whenever it is in the pathname
      const isReadOnly = window.location.pathname.endsWith("/readOnly");
      const readOnlyPart = isReadOnly ? "readOnly/" : "";
      infoUrl = `/annotations/${tracingType}/${tracingId}/${readOnlyPart}info`;
    } else {
      infoUrl = `/annotations/${tracingType}/${tracingId}/info`;
    }

    const tracing: ServerTracing<*> = await Request.receiveJSON(infoUrl);

    let error;
    const dataset = tracing.content.dataSet;
    if (tracing.error) {
      ({ error } = tracing);
    } else if (!dataset) {
      error = "Selected dataset doesn't exist";
    } else if (!dataset.dataLayers) {
      if (dataset.name) {
        error = `Please, double check if you have the dataset '${dataset.name}' imported.`;
      } else {
        error = "Please, make sure you have a dataset imported.";
      }
    }

    if (error) {
      Toast.error(error);
      throw this.HANDLED_ERROR;
    }

    if (!tracing.restrictions.allowAccess) {
      error = "You are not allowed to access this tracing";
      throw this.HANDLED_ERROR;
    }

    // Make sure subsequent fetch calls are always for the same dataset
    if (!_.isEmpty(this.binary)) {
      ErrorHandling.assert(
        _.isEqual(dataset, Store.getState().dataset),
        "Model.fetch was called for a task with another dataset, without reloading the page.",
      );
    }

    ErrorHandling.assertExtendContext({
      task: tracing.id,
      dataSet: dataset.name,
    });

    Store.dispatch(setDatasetAction(dataset));
    Store.dispatch(setTaskAction(tracing.task));

    // Only initialize the model once.
    // There is no need to reinstantiate the binaries if the dataset didn't change.
    if (initialFetch) {
      this.initializeModel(tracing);
    }
    this.initializeTracing(tracing, initialFetch);

    return tracing;
  }

  determineAllowedModes(settings: SettingsType) {
    // The order of allowedModes should be independent from the server and instead be similar to ModeValues
    let allowedModes = _.intersection(ModeValues, settings.allowedModes);

    const colorLayer = _.find(Store.getState().dataset.dataLayers, { category: "color" });
    if (colorLayer != null && colorLayer.elementClass !== "uint8") {
      allowedModes = allowedModes.filter(mode => !constants.MODES_ARBITRARY.includes(mode));
    }

    let preferredMode = null;
    if (settings.preferredMode != null) {
      const modeId = settings.preferredMode;
      if (allowedModes.includes(modeId)) {
        preferredMode = modeId;
      }
    }

    return { preferredMode, allowedModes };
  }

  initializeTracing(
    tracing: ServerTracing<SkeletonContentDataType | VolumeContentDataType>,
    initialFetch: boolean,
  ) {
    const { allowedModes, preferredMode } = this.determineAllowedModes(tracing.content.settings);
    _.extend(tracing.content.settings, { allowedModes, preferredMode });

    const isVolume = tracing.content.settings.allowedModes.includes("volume");
    const controlMode = Store.getState().temporaryConfiguration.controlMode;
    if (controlMode === ControlModeEnum.TRACE) {
      if (isVolume) {
        ErrorHandling.assert(
          this.getSegmentationBinary() != null,
          "Volume is allowed, but segmentation does not exist",
        );
        const volumeTracing: ServerTracing<VolumeContentDataType> = (tracing: ServerTracing<any>);
        Store.dispatch(initializeVolumeTracingAction(volumeTracing));
      } else {
        const skeletonTracing: ServerTracing<SkeletonContentDataType> = (tracing: ServerTracing<
          any,
        >);
        Store.dispatch(initializeSkeletonTracingAction(skeletonTracing));
      }
    } else {
      const readOnlyTracing: ServerTracing<SkeletonContentDataType> = (tracing: ServerTracing<any>);
      Store.dispatch(initializeReadOnlyTracingAction(readOnlyTracing));
    }

    if (initialFetch) {
      Store.dispatch(setZoomStepAction(tracing.content.contentData.zoomLevel));
    }

    // Initialize 'flight', 'oblique' or 'orthogonal'/'volume' mode
    if (allowedModes.length === 0) {
      Toast.error("There was no valid allowed tracing mode specified.");
    } else {
      const mode = preferredMode || UrlManager.initialState.mode || allowedModes[0];
      Store.dispatch(setViewModeAction(mode));
    }

    this.applyState(UrlManager.initialState, tracing);
  }

  initializeModel(tracing: ServerTracing<SkeletonContentDataType | VolumeContentDataType>) {
    const dataset = tracing.content.dataSet;
    const { dataStore } = dataset;

    const LayerClass = (() => {
      switch (dataStore.typ) {
        case "webknossos-store":
          return WkLayer;
        case "ndstore":
          return NdStoreLayer;
        default:
          throw new Error(`Unknown datastore type: ${dataStore.typ}`);
      }
    })();

    const layers = this.getLayerInfos(tracing.content.contentData.customLayers).map(
      layerInfo => new LayerClass(layerInfo, dataStore),
    );

    this.connectionInfo = new ConnectionInfo();
    this.binary = {};

    let maxZoomStep = -Infinity;

    for (const layer of layers) {
      const maxLayerZoomStep = Math.log(Math.max(...layer.resolutions)) / Math.LN2;
      this.binary[layer.name] = new Binary(tracing, layer, maxLayerZoomStep, this.connectionInfo);
      maxZoomStep = Math.max(maxZoomStep, maxLayerZoomStep);
    }

    this.buildMappingsObject();

    if (this.getColorBinaries().length === 0) {
      Toast.error("No data available! Something seems to be wrong with the dataset.");
      throw this.HANDLED_ERROR;
    }

    this.computeBoundaries();

    // TODO: remove
    this.tracing = tracing;
  }

  // For now, since we have no UI for this
  buildMappingsObject() {
    const segmentationBinary = this.getSegmentationBinary();

    if (segmentationBinary != null) {
      window.mappings = {
        getAll() {
          return segmentationBinary.mappings.getMappingNames();
        },
        getActive() {
          return segmentationBinary.activeMapping;
        },
        activate(mapping) {
          return segmentationBinary.setActiveMapping(mapping);
        },
      };
    }
  }

  getColorBinaries() {
    return _.filter(this.binary, binary => binary.category === "color");
  }

  getSegmentationBinary() {
    return _.find(this.binary, binary => binary.category === "segmentation");
  }

  getBinaryByName(name: string) {
    return this.binary[name];
  }

  getLayerInfos(userLayers: ?Array<Object>) {
    // Overwrite or extend layers with userLayers
    const dataset = Store.getState().dataset;
    let layers = dataset == null ? [] : _.clone(dataset.dataLayers);
    if (userLayers == null) {
      return layers;
    }

    for (const userLayer of userLayers) {
      // Find the fallback layer which should be used as the foundation of the userLayer
      const existingLayerIndex = _.findIndex(
        layers,
        layer => layer.name === Utils.__guard__(userLayer.fallback, x => x.layerName),
      );
      const existingLayer = layers[existingLayerIndex];

      if (existingLayer != null) {
        layers[existingLayerIndex] = update(userLayer, {
          $merge: { mappings: existingLayer.mappings },
        });
      } else {
        // Remove other segmentation layers, since we are adding a new one.
        // This is a temporary workaround. In the long term we want to support
        // multiple segmentation layers.
        layers = layers.filter(layer => layer.category !== "segmentation");
        layers.push(userLayer);
      }
    }

    return layers;
  }

  shouldDisplaySegmentationData(): boolean {
    const segmentationOpacity = Store.getState().datasetConfiguration.segmentationOpacity;
    if (segmentationOpacity === 0) {
      return false;
    }
    const currentViewMode = Store.getState().temporaryConfiguration.viewMode;

    // Currently segmentation data can only be displayed in orthogonal and volume mode
    const canModeDisplaySegmentationData = constants.MODES_PLANE.includes(currentViewMode);
    return this.getSegmentationBinary() != null && canModeDisplaySegmentationData;
  }

  canDisplaySegmentationData(): boolean {
    return getIntegerZoomStep(Store.getState()) <= 1;
  }

  computeBoundaries() {
    this.lowerBoundary = [Infinity, Infinity, Infinity];
    this.upperBoundary = [-Infinity, -Infinity, -Infinity];

    for (const key of Object.keys(this.binary)) {
      const binary = this.binary[key];
      for (const i of Vector3Indicies) {
        this.lowerBoundary[i] = Math.min(this.lowerBoundary[i], binary.lowerBoundary[i]);
        this.upperBoundary[i] = Math.max(this.upperBoundary[i], binary.upperBoundary[i]);
      }
    }
  }

  applyState(
    state: UrlManagerState,
    tracing: ServerTracing<SkeletonContentDataType | VolumeContentDataType>,
  ) {
    Store.dispatch(setPositionAction(state.position || tracing.content.editPosition));
    if (state.zoomStep != null) {
      Store.dispatch(setZoomStepAction(state.zoomStep));
    }

    const rotation = state.rotation || tracing.content.editRotation;
    if (rotation != null) {
      Store.dispatch(setRotationAction(rotation));
    }

    if (state.activeNode != null) {
      Store.dispatch(setActiveNodeAction(state.activeNode));
    }
  }

  stateSaved() {
    const state = Store.getState();
    const storeStateSaved = !state.save.isBusy && state.save.queue.length === 0;
    const pushQueuesSaved = _.reduce(
      this.binary,
      (saved, binary) => saved && binary.pushQueue.stateSaved(),
      true,
    );
    return storeStateSaved && pushQueuesSaved;
  }

  save = async () => {
    Store.dispatch(saveNowAction());
    while (!this.stateSaved()) {
      // eslint-disable-next-line no-await-in-loop
      await Utils.sleep(500);
    }
  };
}

// export the model as a singleton
export default new OxalisModel();
<|endoftext|>


<commit_before>(function($) {
"use strict";

function Sortable(el, options) {
    //TODO: drag handle
    var self = this,
        $sortable = $(el),
        container_type = $sortable[0].nodeName,
        node_type = (container_type == 'OL' || container_type == 'UL') ? 'LI' : 'DIV',
        defaults = {
            //options
            container: container_type,
            container_type: container_type,
            nodes: node_type,
            nodes_type: node_type,
            auto_container_class: 'sortable_container',
            autocreate: false,
            group: false,
            //callbacks
            update: null
        };

    self.$sortable = $sortable.data('sortable', self);
    self.options = $.extend({}, defaults, options);

    self.init();
}

Sortable.prototype.invoke = function(command) {
    var self = this;
    if (command === 'destroy') {
        return self.destroy();
    } else if (command === 'serialize') {
        return self.serialize(self.$sortable);
    }
};

Sortable.prototype.init = function() {
    var self = this;

    $('html').unselectable();

    self.$sortable
    .addClass('sortable')
    .on('destroy.sortable', function() {
        self.destroy();
    });

    self.find_nodes().each(function(ix, node) {
        self.init_node(node);
    });
};

Sortable.prototype.destroy = function() {
    var self = this;

    $('html').unselectable('destroy');

    self.$sortable
    .removeClass('sortable')
    .off('.sortable');

    self.find_nodes().each(function(ix, node) {
        self.destroy_node(node);
    });
};

Sortable.prototype.init_node = function(node) {
    var self = this,
        $node = $(node),
        $clone,
        $placeholder,
        position;

    function find_insert_point(pos) {
        var containers,
            best;

        if (!pos) {
            return;
        }

        containers = self.$sortable
        .add(self.$sortable.find(self.options.container))
        .not($node.find(self.options.container))
        .not($clone.find(self.options.container))
        .not(self.find_nodes());

        $placeholder.hide();
        containers.each(function(ix, container) {
            var childnum = $(container).children().length,
                n,
                candidate,
                dist;

            for (n = 0; n <= childnum; n++) {
                candidate = self.create_placeholder().nthChild(container, n);
                dist = self.square_dist(candidate.offset(), pos);
                candidate.remove();

                if (!best || best.dist > dist) {
                    best = {container: container, n: n, dist: dist};
                }
            }
        });
        $placeholder.show();

        return best;
    }

    $node.dragaware({
        /**
         * drag start - create clone and placeholder, keep drag start position.
         */
        dragstart: function(evt) {
            $clone = $node.clone()
                          .addClass('sortable_clone')
                          .insertAfter($node)
                          .offset($node.offset());
            $placeholder = self.create_placeholder()
                               .css({height: $node.outerHeight(), width: $node.outerWidth()})
                               .insertAfter($node);
            $node.hide();

            position = new PositionHelper($clone.offset());

            if (self.options.autocreate) {
                self.find_nodes().filter(function(ix, el) {
                    return $(el).find(self.options.container).length == 0;
                }).append('<' + self.options.container_type + ' class="' + self.options.auto_container_class + '"/>');
            }
        },

        /**
         * drag - reposition clone, check for best insert position, move placeholder in dom accordingly.
         */
        drag: function(evt, delta) {
            var pos = position.absolutize(delta),
                best = find_insert_point(pos);

            $clone.offset(pos);
            $placeholder.nthChild(best.container, best.n);
        },

        /**
         * drag stop - clean up.
         */
        dragstop: function(evt, delta) {
            var pos = position.absolutize(delta),
                best = find_insert_point(pos);

            if (best) {
                $node.nthChild(best.container, best.n);
            }
            $node.show();

            if ($clone) {
                $clone.remove();
            }
            if ($placeholder) {
                $placeholder.remove();
            }
            $clone = null;
            $placeholder = null;

            if (best && self.options.update) {
                self.options.update.call(self.$sortable, evt, self);
            }
        }
    });
};

Sortable.prototype.destroy_node = function(node) {
    $(node).dragaware('destroy');
};

Sortable.prototype.serialize = function(container) {
    var self = this;
    return container.children(self.options.nodes).not(self.options.container).map(function(ix, el) {
        var $el = $(el),
            text = $el.clone().children().remove().end().text().trim(), //text only without children
            id = $el.attr('id'),
            node = {id: id || text};
        if ($el.find(self.options.nodes).length) {
            node.children = self.serialize($el.children(self.options.container));
        }
        return node;
    }).get();
};

Sortable.prototype.find_nodes = function() {
    var self = this;
    return self.$sortable.find(self.options.nodes).not(self.options.container);
};

Sortable.prototype.create_placeholder = function() {
    var self = this;
    return $('<' + self.options.nodes_type + '/>').addClass('sortable_placeholder');
};

Sortable.prototype.square_dist = function(pos1, pos2) {
    return Math.pow(pos2.left - pos1.left, 2) + Math.pow(pos2.top - pos1.top, 2);
};

function Draggable(el, options) {
    var self = this,
        defaults = {
            //options
            revert: false,
            placeholder: false,
            droptarget: false,
            //callbacks
            update: null,
            drop: null
        };

    self.$draggable = $(el).data('draggable', self);
    self.options = $.extend({}, defaults, options);

    self.init();
}

Draggable.prototype.init = function() {
    var self = this,
        $clone,
        $placeholder,
        position;

    self.$draggable
    .addClass('draggable')
    .on('destroy.draggable', function() {
        self.destroy();
    });

    function check_droptarget(pos) {
        var $over;

        $('.hovering').removeClass('hovering');

        $clone.hide();
        $over = $(document.elementFromPoint(pos.x, pos.y)).closest(self.options.droptarget);
        $clone.show();

        if ($over.length) {
            $over.addClass('hovering');
            return $over;
        }
    }

    self.$draggable.dragaware({
        /**
         * drag start - create clone and placeholder, keep drag start position.
         */
        dragstart: function(evt) {
            if (self.options.placeholder) {
                $placeholder = self.create_clone('draggable_placeholder');
            }

            if (self.options.revert) {
                $clone = self.create_clone('draggable_clone');
                self.$draggable.invisible();
            } else {
                $clone = self.$draggable;
            }

            position = new PositionHelper($clone.offset());
        },

        /**
         * drag - reposition clone.
         */
        drag: function(evt, delta, pos) {
            var $droptarget = check_droptarget(pos);
            $clone.offset(position.absolutize(delta));
        },

        /**
         * drag stop - clean up.
         */
        dragstop: function(evt, delta, pos) {
            var $droptarget = check_droptarget(pos);

            if (self.options.placeholder) {
                $placeholder.remove();
            }

            if (self.options.revert) {
                $clone.remove();
                self.$draggable.visible();
            }

            $clone = null;
            $placeholder = null;

            if (self.options.update) {
                self.options.update.call(self.$draggable, evt, self);
            }

            if ($droptarget) {
                if (self.options.drop) {
                    self.options.drop.call(self.$draggable, evt, $droptarget[0]);
                }
                $droptarget.removeClass('hovering');
            }
        }
    });
};

Draggable.prototype.destroy = function() {
    var self = this;

    self.$draggable
    .dragaware('destroy')
    .removeClass('draggable')
    .off('.draggable');
};

Draggable.prototype.create_clone = function(classname) {
    var self = this;
    return self.$draggable
    .clone()
    .addClass(classname)
    .insertAfter(self.$draggable)
    .offset(self.$draggable.offset());
};

function Dragaware(el, options) {
    var $dragaware = $(el),
        pos = null,
        lastpos = null;

    function evtpos(evt) {
        evt = window.hasOwnProperty('event') ? window.event : evt;
        evt = evt.hasOwnProperty('touches') ? evt.touches[0] : evt;
        return {x: evt.pageX, y: evt.pageY};
    }

    function relpos(evt) {
        var p = evtpos(evt);
        return {dx: p.x - pos.x, dy: p.y - pos.y};
    }

    function start(evt) {
        if (evt.type == 'touchstart' || evt.button == 0) {
            evt.stopPropagation();
            pos = evtpos(evt);
            if (options.dragstart) {
                options.dragstart.call($dragaware, evt);
            }

            //late binding of event listeners
            $(document)
            .on('touchend.dragaware mouseup.dragaware click.dragaware', end)
            .on('touchmove.dragaware mousemove.dragaware', move);
        }
    }

    function move(evt) {
        if (pos && options.drag) {
            evt.stopPropagation();
            lastpos = relpos(evt);
            options.drag.call($dragaware, evt, lastpos, evtpos(evt));
        }
    }

    function end(evt) {
        if (pos && options.dragstop) {
            evt.stopPropagation();
            options.dragstop.call($dragaware, evt, lastpos, evtpos(evt));
        }
        pos = false;
        lastpos = false;

        //unbinding of event listeners
        $(document)
        .off('.dragaware');
    }

    $dragaware
    .addClass('dragaware')
    .on('touchstart.dragaware mousedown.dragaware', start);

    $dragaware.on('destroy.dragaware', function() {
        $dragaware
        .removeClass('dragaware')
        .off('.dragaware');
    });
}

function PositionHelper(origin) {
    this.origin = origin;
}
PositionHelper.prototype.absolutize = function(relpos) {
    if (!relpos) {
        return this.origin;
    }
    return {top: this.origin.top + relpos.dy, left: this.origin.left + relpos.dx};
};

// Plugin registration.

/**
 * Sortable plugin.
 */
$.fn.sortable = function(options) {
    var filtered = this.not(function() {
            return $(this).is('.sortable') || $(this).closest('.sortable').length;
        });

    if (this.data('sortable') && typeof options === 'string') {
        return this.data('sortable').invoke(options);
    }

    if (filtered.length && options && options.group) {
        new Sortable(filtered, options);
    } else {
        filtered.each(function(ix, el) {
            new Sortable(el, options);
        });
    }
    return this;
};

/**
 * Draggable plugin.
 */
$.fn.draggable = function(options) {
    if (options === 'destroy') {
        this.trigger('destroy.draggable');
    } else {
        this.not('.draggable').each(function(ix, el) {
            new Draggable(el, options);
        });
    }
    return this;
};

/**
 * Dragaware plugin.
 */
$.fn.dragaware = function(options) {
    if (options === 'destroy') {
        this.trigger('destroy.dragaware');
    } else {
        this.not('.dragaware').each(function(ix, el) {
            new Dragaware(el, options);
        });
    }
    return this;
};

/**
 * Inserts the current selection as nth child into first element matching selector.
 */
$.fn.nthChild = function(selector, n) {
    var $container = $(selector).eq(0),
        $children = $container.children();
    if (n == 0) {
        $container.prepend(this);
    } else if (n >= $children.length) {
        $container.append(this);
    } else {
        $children.eq(n).before(this);
    }
    return this;
};

/**
 * Disables mouse selection.
 */
$.fn.unselectable = function(command) {
    function disable() {
        return false;
    }

    if (command == 'destroy') {
        return this
        .removeClass('unselectable')
        .removeAttr('unselectable')
        .off('selectstart.unselectable');
    } else {
        return this
        .addClass('unselectable')
        .attr('unselectable','on')
        .on('selectstart.unselectable', disable);
    }
};

$.fn.invisible = function() {
    return this.css({visibility: 'hidden'});
};

$.fn.visible = function() {
    return this.css({visibility: 'visible'});
};

}(jQuery));<commit_msg>add indicator class while dragging<commit_after>(function($) {
"use strict";

function Sortable(el, options) {
    //TODO: drag handle
    //TODO: trigger events (update etc.)
    var self = this,
        $sortable = $(el),
        container_type = $sortable[0].nodeName,
        node_type = (container_type == 'OL' || container_type == 'UL') ? 'LI' : 'DIV',
        defaults = {
            //options
            container: container_type,
            container_type: container_type,
            nodes: node_type,
            nodes_type: node_type,
            auto_container_class: 'sortable_container',
            autocreate: false,
            group: false,
            //callbacks
            update: null
        };

    self.$sortable = $sortable.data('sortable', self);
    self.options = $.extend({}, defaults, options);

    self.init();
}

Sortable.prototype.invoke = function(command) {
    var self = this;
    if (command === 'destroy') {
        return self.destroy();
    } else if (command === 'serialize') {
        return self.serialize(self.$sortable);
    }
};

Sortable.prototype.init = function() {
    var self = this;

    $('html').unselectable();

    self.$sortable
    .addClass('sortable')
    .on('destroy.sortable', function() {
        self.destroy();
    });

    self.find_nodes().each(function(ix, node) {
        self.init_node(node);
    });
};

Sortable.prototype.destroy = function() {
    var self = this;

    $('html').unselectable('destroy');

    self.$sortable
    .removeClass('sortable')
    .off('.sortable');

    self.find_nodes().each(function(ix, node) {
        self.destroy_node(node);
    });
};

Sortable.prototype.init_node = function(node) {
    var self = this,
        $node = $(node),
        $clone,
        $placeholder,
        position;

    function find_insert_point(pos) {
        var containers,
            best;

        if (!pos) {
            return;
        }

        containers = self.$sortable
        .add(self.$sortable.find(self.options.container))
        .not($node.find(self.options.container))
        .not($clone.find(self.options.container))
        .not(self.find_nodes());

        $placeholder.hide();
        containers.each(function(ix, container) {
            var childnum = $(container).children().length,
                n,
                candidate,
                dist;

            for (n = 0; n <= childnum; n++) {
                candidate = self.create_placeholder().nthChild(container, n);
                dist = self.square_dist(candidate.offset(), pos);
                candidate.remove();

                if (!best || best.dist > dist) {
                    best = {container: container, n: n, dist: dist};
                }
            }
        });
        $placeholder.show();

        return best;
    }

    $node.dragaware({
        /**
         * drag start - create clone and placeholder, keep drag start position.
         */
        dragstart: function(evt) {
            $clone = $node.clone()
                          .addClass('sortable_clone')
                          .insertAfter($node)
                          .offset($node.offset());
            $placeholder = self.create_placeholder()
                               .css({height: $node.outerHeight(), width: $node.outerWidth()})
                               .insertAfter($node);
            $node.hide();

            position = new PositionHelper($clone.offset());

            if (self.options.autocreate) {
                self.find_nodes().filter(function(ix, el) {
                    return $(el).find(self.options.container).length == 0;
                }).append('<' + self.options.container_type + ' class="' + self.options.auto_container_class + '"/>');
            }
        },

        /**
         * drag - reposition clone, check for best insert position, move placeholder in dom accordingly.
         */
        drag: function(evt, delta) {
            var pos = position.absolutize(delta),
                best = find_insert_point(pos);

            $clone.offset(pos);
            $placeholder.nthChild(best.container, best.n);
        },

        /**
         * drag stop - clean up.
         */
        dragstop: function(evt, delta) {
            var pos = position.absolutize(delta),
                best = find_insert_point(pos);

            if (best) {
                $node.nthChild(best.container, best.n);
            }
            $node.show();

            if ($clone) {
                $clone.remove();
            }
            if ($placeholder) {
                $placeholder.remove();
            }
            $clone = null;
            $placeholder = null;

            if (best && self.options.update) {
                self.options.update.call(self.$sortable, evt, self);
            }
        }
    });
};

Sortable.prototype.destroy_node = function(node) {
    $(node).dragaware('destroy');
};

Sortable.prototype.serialize = function(container) {
    var self = this;
    return container.children(self.options.nodes).not(self.options.container).map(function(ix, el) {
        var $el = $(el),
            text = $el.clone().children().remove().end().text().trim(), //text only without children
            id = $el.attr('id'),
            node = {id: id || text};
        if ($el.find(self.options.nodes).length) {
            node.children = self.serialize($el.children(self.options.container));
        }
        return node;
    }).get();
};

Sortable.prototype.find_nodes = function() {
    var self = this;
    return self.$sortable.find(self.options.nodes).not(self.options.container);
};

Sortable.prototype.create_placeholder = function() {
    var self = this;
    return $('<' + self.options.nodes_type + '/>').addClass('sortable_placeholder');
};

Sortable.prototype.square_dist = function(pos1, pos2) {
    return Math.pow(pos2.left - pos1.left, 2) + Math.pow(pos2.top - pos1.top, 2);
};

function Draggable(el, options) {
    var self = this,
        defaults = {
            //options
            revert: false,
            placeholder: false,
            droptarget: false,
            //callbacks
            update: null,
            drop: null
        };

    self.$draggable = $(el).data('draggable', self);
    self.options = $.extend({}, defaults, options);

    self.init();
}

Draggable.prototype.init = function() {
    var self = this,
        $clone,
        $placeholder,
        position;

    self.$draggable
    .addClass('draggable')
    .on('destroy.draggable', function() {
        self.destroy();
    });

    function check_droptarget(pos) {
        var $over;

        $('.hovering').removeClass('hovering');

        $clone.hide();
        $over = $(document.elementFromPoint(pos.x, pos.y)).closest(self.options.droptarget);
        $clone.show();

        if ($over.length) {
            $over.addClass('hovering');
            return $over;
        }
    }

    self.$draggable.dragaware({
        /**
         * drag start - create clone and placeholder, keep drag start position.
         */
        dragstart: function(evt) {
            if (self.options.placeholder) {
                $placeholder = self.create_clone('draggable_placeholder');
            }

            if (self.options.revert) {
                $clone = self.create_clone('draggable_clone');
                self.$draggable.invisible();
            } else {
                $clone = self.$draggable;
            }

            position = new PositionHelper($clone.offset());
        },

        /**
         * drag - reposition clone.
         */
        drag: function(evt, delta, pos) {
            var $droptarget = check_droptarget(pos);
            $clone.offset(position.absolutize(delta));
        },

        /**
         * drag stop - clean up.
         */
        dragstop: function(evt, delta, pos) {
            var $droptarget = check_droptarget(pos);

            if (self.options.placeholder) {
                $placeholder.remove();
            }

            if (self.options.revert) {
                $clone.remove();
                self.$draggable.visible();
            }

            $clone = null;
            $placeholder = null;

            if (self.options.update) {
                self.options.update.call(self.$draggable, evt, self);
            }

            if ($droptarget) {
                if (self.options.drop) {
                    self.options.drop.call(self.$draggable, evt, $droptarget[0]);
                }
                $droptarget.removeClass('hovering');
            }
        }
    });
};

Draggable.prototype.destroy = function() {
    var self = this;

    self.$draggable
    .dragaware('destroy')
    .removeClass('draggable')
    .off('.draggable');
};

Draggable.prototype.create_clone = function(classname) {
    var self = this;
    return self.$draggable
    .clone()
    .addClass(classname)
    .insertAfter(self.$draggable)
    .offset(self.$draggable.offset());
};

function Dragaware(el, options) {
    var $dragaware = $(el),
        pos = null,
        lastpos = null;

    function evtpos(evt) {
        evt = window.hasOwnProperty('event') ? window.event : evt;
        evt = evt.hasOwnProperty('touches') ? evt.touches[0] : evt;
        return {x: evt.pageX, y: evt.pageY};
    }

    function relpos(evt) {
        var p = evtpos(evt);
        return {dx: p.x - pos.x, dy: p.y - pos.y};
    }

    function start(evt) {
        if (evt.type == 'touchstart' || evt.button == 0) {
            evt.stopPropagation();
            pos = evtpos(evt);
            if (options.dragstart) {
                options.dragstart.call($dragaware, evt);
            }

            $dragaware.addClass('dragging');

            //late binding of event listeners
            $(document)
            .on('touchend.dragaware mouseup.dragaware click.dragaware', end)
            .on('touchmove.dragaware mousemove.dragaware', move);
        }
    }

    function move(evt) {
        if (pos && options.drag) {
            evt.stopPropagation();
            lastpos = relpos(evt);
            options.drag.call($dragaware, evt, lastpos, evtpos(evt));
        }
    }

    function end(evt) {
        if (pos && options.dragstop) {
            evt.stopPropagation();
            options.dragstop.call($dragaware, evt, lastpos, evtpos(evt));
        }
        pos = false;
        lastpos = false;

        $dragaware.removeClass('dragging');

        //unbinding of event listeners
        $(document)
        .off('.dragaware');
    }

    $dragaware
    .addClass('dragaware')
    .on('touchstart.dragaware mousedown.dragaware', start);

    $dragaware.on('destroy.dragaware', function() {
        $dragaware
        .removeClass('dragaware')
        .off('.dragaware');
    });
}

function PositionHelper(origin) {
    this.origin = origin;
}
PositionHelper.prototype.absolutize = function(relpos) {
    if (!relpos) {
        return this.origin;
    }
    return {top: this.origin.top + relpos.dy, left: this.origin.left + relpos.dx};
};

// Plugin registration.

/**
 * Sortable plugin.
 */
$.fn.sortable = function(options) {
    var filtered = this.not(function() {
            return $(this).is('.sortable') || $(this).closest('.sortable').length;
        });

    if (this.data('sortable') && typeof options === 'string') {
        return this.data('sortable').invoke(options);
    }

    if (filtered.length && options && options.group) {
        new Sortable(filtered, options);
    } else {
        filtered.each(function(ix, el) {
            new Sortable(el, options);
        });
    }
    return this;
};

/**
 * Draggable plugin.
 */
$.fn.draggable = function(options) {
    if (options === 'destroy') {
        this.trigger('destroy.draggable');
    } else {
        this.not('.draggable').each(function(ix, el) {
            new Draggable(el, options);
        });
    }
    return this;
};

/**
 * Dragaware plugin.
 */
$.fn.dragaware = function(options) {
    if (options === 'destroy') {
        this.trigger('destroy.dragaware');
    } else {
        this.not('.dragaware').each(function(ix, el) {
            new Dragaware(el, options);
        });
    }
    return this;
};

/**
 * Inserts the current selection as nth child into first element matching selector.
 */
$.fn.nthChild = function(selector, n) {
    var $container = $(selector).eq(0),
        $children = $container.children();
    if (n == 0) {
        $container.prepend(this);
    } else if (n >= $children.length) {
        $container.append(this);
    } else {
        $children.eq(n).before(this);
    }
    return this;
};

/**
 * Disables mouse selection.
 */
$.fn.unselectable = function(command) {
    function disable() {
        return false;
    }

    if (command == 'destroy') {
        return this
        .removeClass('unselectable')
        .removeAttr('unselectable')
        .off('selectstart.unselectable');
    } else {
        return this
        .addClass('unselectable')
        .attr('unselectable','on')
        .on('selectstart.unselectable', disable);
    }
};

$.fn.invisible = function() {
    return this.css({visibility: 'hidden'});
};

$.fn.visible = function() {
    return this.css({visibility: 'visible'});
};

}(jQuery));<|endoftext|>