<commit_before>#!/usr/bin/env js
/**
 * Interactive interpreter for jacket.
 * This repl is useful for debugging since it prints
 * the preprocessed code, the parsed code, and the
 * translated code to stdout. The translated code is
 * pretty printed by default.
 *
 */
var _debug_repl = function () {
	var line, lines, wellness;

	load("preprocessor.js");
	load("parser.js");
	load("translator.js");
	load("built_ins.js");
	load("pretty.js");
	load("wellness.js");

	eval(_translate(_parse(_preprocess(read("../lib/stdlib.jkt")))));
	print("stdlib.jkt loaded.");

	print("#################");
	print("## jacket REPL ##");
	print("## v0.1        ##");
	print("#################");

	while (1) {
		putstr("> ");
		line = readline();
		lines = line;
		wellness = _wellness(lines);

		while (!wellness) {
			putstr("... ");
			line = readline();
			lines += line;
			wellness = _wellness(lines);
		}

		prepr = _preprocess(lines);
		parse = _parse(prepr);
		trans = _pretty(_translate(parse));

		print("preprocessed:");
		putstr('\t');
		if (prepr[prepr.length-1] == '\n')
			putstr(prepr);
		else
			print(prepr);
		putstr('\n');

		print("parsed:");
		putstr('\t');
		if (parse[parse.length-1] == '\n')
			putstr(_show_array(parse));
		else
			print(_show_array(parse));
		putstr('\n');

		print("translated:");
		putstr("    ");
		if (trans[trans.length-1] == '\n')
			putstr(trans);
		else
			print(trans);
		putstr('\n');

		print("evaluated:");
		putstr('\t');
		if (trans[trans.length-1] == '\n')
			putstr(eval(trans));
		else
			print(eval(trans));
		putstr('\n');
	}

}();
<commit_msg>improved readibility in output<commit_after>#!/usr/bin/env js
/**
 * Interactive interpreter for jacket.
 * This repl is useful for debugging since it prints
 * the preprocessed code, the parsed code, and the
 * translated code to stdout. The translated code is
 * pretty printed by default.
 *
 */
var _debug_repl = function () {
	var line, lines, wellness;

	load("preprocessor.js");
	load("parser.js");
	load("translator.js");
	load("built_ins.js");
	load("pretty.js");
	load("wellness.js");

	eval(_translate(_parse(_preprocess(read("../lib/stdlib.jkt")))));
	print("stdlib.jkt loaded.");

	print("#################");
	print("## jacket REPL ##");
	print("## v0.1        ##");
	print("#################");

	while (1) {
		putstr("> ");
		line = readline();
		lines = line;
		wellness = _wellness(lines);

		while (!wellness) {
			putstr("... ");
			line = readline();
			lines += line;
			wellness = _wellness(lines);
		}

		prepr = _preprocess(lines);
		parse = _parse(prepr);
		trans = _pretty(_translate(parse));

		putstr('\n\n');
		print("preprocessed:");
		putstr('\t');
		if (prepr[prepr.length-1] == '\n')
			putstr(prepr);
		else
			print(prepr);
		putstr('\n');

		print("parsed:");
		putstr('\t');
		if (parse[parse.length-1] == '\n')
			putstr(_show_array(parse));
		else
			print(_show_array(parse));
		putstr('\n');

		print("translated:");
		putstr("    ");
		if (trans[trans.length-1] == '\n')
			putstr(trans);
		else
			print(trans);
		putstr('\n');

		print("evaluated:");
		putstr('\t');
		evaled = eval(trans);
		if (_is_array(evaled))
			out = _show_array(evaled);
		else
			out = evaled
		if (trans[trans.length-1] == '\n')
			putstr(out);
		else
			print(out);
		putstr('\n');
	}

}();
<|endoftext|>


<commit_before>import bind from 'lodash.bind'
import isArray from 'lodash.isarray'
import isFunction from 'lodash.isfunction'

import {
  isPromise,
  noop,
  pFinally
} from './utils'

// ===================================================================

const {
  defineProperties,
  defineProperty,
  getOwnPropertyDescriptor
} = Object

// ===================================================================

// See: https://github.com/jayphelps/core-decorators.js#autobind
//
// TODO: make it work for all class methods.
export const autobind = (target, key, {
  configurable,
  enumerable,
  value: fn,
  writable
}) => ({
  configurable,
  enumerable,

  get () {
    const bounded = bind(fn, this)

    defineProperty(this, key, {
      configurable: true,
      enumerable: false,
      value: bounded,
      writable: true
    })

    return bounded
  },
  set (newValue) {
    if (this === target) {
      // New value directly set on the prototype.
      delete this[key]
      this[key] = newValue
    } else {
      // New value set on a child object.

      // Cannot use assignment because it will call the setter on
      // the prototype.
      defineProperty(this, key, {
        configurable: true,
        enumerable: true,
        value: newValue,
        writable: true
      })
    }
  }
})

// -------------------------------------------------------------------

// Debounce decorator for methods.
//
// See: https://github.com/wycats/javascript-decorators
//
// TODO: make it work for single functions.
export const debounce = duration => (target, name, descriptor) => {
  const fn = descriptor.value

  // This symbol is used to store the related data directly on the
  // current object.
  const s = Symbol()

  function debounced () {
    const data = this[s] || (this[s] = {
      lastCall: 0,
      wrapper: null
    })

    const now = Date.now()
    if (now > data.lastCall + duration) {
      data.lastCall = now
      try {
        const result = fn.apply(this, arguments)
        data.wrapper = () => result
      } catch (error) {
        data.wrapper = () => { throw error }
      }
    }
    return data.wrapper()
  }
  debounced.reset = obj => { delete obj[s] }

  descriptor.value = debounced
  return descriptor
}

// -------------------------------------------------------------------

const _push = Array.prototype.push

export const deferrable = (target, name, descriptor) => {
  let fn
  function newFn () {
    const deferreds = []
    const defer = fn => {
      deferreds.push(fn)
    }
    defer.clear = () => {
      deferreds.length = 0
    }

    const args = [ defer ]
    _push.apply(args, arguments)

    let executeDeferreds = () => {
      let i = deferreds.length
      while (i) {
        deferreds[--i]()
      }
    }

    try {
      const result = fn.apply(this, args)

      if (isPromise(result)) {
        result::pFinally(executeDeferreds)

        // Do not execute the deferreds in the finally block.
        executeDeferreds = noop
      }

      return result
    } finally {
      executeDeferreds()
    }
  }

  if (descriptor) {
    fn = descriptor.value
    descriptor.value = newFn

    return descriptor
  }

  fn = target
  return newFn
}

// Deferred functions are only executed on failures.
//
// i.e.: defer.clear() is automatically called in case of success.
deferrable.onFailure = (target, name, descriptor) => {
  let fn
  function newFn (defer) {
    const result = fn.apply(this, arguments)

    return isPromise(result)
      ? result.then(result => {
        defer.clear()
        return result
      })
      : (defer.clear(), result)
  }

  if (descriptor) {
    fn = descriptor.value
    descriptor.value = newFn
  } else {
    fn = target
    target = newFn
  }

  return deferrable(target, name, descriptor)
}

// -------------------------------------------------------------------

const _ownKeys = (
  typeof Reflect !== 'undefined' && Reflect.ownKeys ||
  (({
    getOwnPropertyNames: names,
    getOwnPropertySymbols: symbols
  }) => symbols
    ? obj => names(obj).concat(symbols(obj))
    : names
  )(Object)
)

const _bindPropertyDescriptor = (descriptor, thisArg) => {
  const { get, set, value } = descriptor
  if (get) {
    descriptor.get = bind(get, thisArg)
  }
  if (set) {
    descriptor.set = bind(set, thisArg)
  }

  if (isFunction(value)) {
    descriptor.value = bind(value, thisArg)
  }

  return descriptor
}

const _isIgnoredProperty = name => (
  name[0] === '_' ||
  name === 'constructor'
)

const _isIgnoredStaticProperty = name => (
  name === 'length' ||
  name === 'name' ||
  name === 'prototype'
)

export const mixin = MixIns => Class => {
  if (!isArray(MixIns)) {
    MixIns = [ MixIns ]
  }

  const { name } = Class

  const Decorator = (...args) => {
    const instance = new Class(...args)

    for (const MixIn of MixIns) {
      const { prototype } = MixIn
      const mixinInstance = new MixIn(instance)
      const descriptors = { __proto__: null }
      for (const prop of _ownKeys(prototype)) {
        if (_isIgnoredProperty(prop)) {
          continue
        }

        if (prop in instance) {
          throw new Error(`${name}#${prop} is already defined`)
        }

        descriptors[prop] = _bindPropertyDescriptor(
          getOwnPropertyDescriptor(prototype, prop),
          mixinInstance
        )
      }
      defineProperties(instance, descriptors)
    }

    return instance
  }

  // Copy original and mixed-in static properties on Decorator class.
  const descriptors = { __proto__: null }
  for (const prop of _ownKeys(Class)) {
    descriptors[prop] = getOwnPropertyDescriptor(Class, prop)
  }
  for (const MixIn of MixIns) {
    for (const prop of _ownKeys(MixIn)) {
      if (_isIgnoredStaticProperty(prop)) {
        continue
      }

      if (prop in descriptors) {
        throw new Error(`${name}.${prop} is already defined`)
      }

      descriptors[prop] = getOwnPropertyDescriptor(MixIn, prop)
    }
  }
  defineProperties(Decorator, descriptors)

  return Decorator
}
<commit_msg>@deferrable.onSuccess()<commit_after>import bind from 'lodash.bind'
import isArray from 'lodash.isarray'
import isFunction from 'lodash.isfunction'

import {
  isPromise,
  noop,
  pFinally
} from './utils'

// ===================================================================

const {
  defineProperties,
  defineProperty,
  getOwnPropertyDescriptor
} = Object

// ===================================================================

// See: https://github.com/jayphelps/core-decorators.js#autobind
//
// TODO: make it work for all class methods.
export const autobind = (target, key, {
  configurable,
  enumerable,
  value: fn,
  writable
}) => ({
  configurable,
  enumerable,

  get () {
    const bounded = bind(fn, this)

    defineProperty(this, key, {
      configurable: true,
      enumerable: false,
      value: bounded,
      writable: true
    })

    return bounded
  },
  set (newValue) {
    if (this === target) {
      // New value directly set on the prototype.
      delete this[key]
      this[key] = newValue
    } else {
      // New value set on a child object.

      // Cannot use assignment because it will call the setter on
      // the prototype.
      defineProperty(this, key, {
        configurable: true,
        enumerable: true,
        value: newValue,
        writable: true
      })
    }
  }
})

// -------------------------------------------------------------------

// Debounce decorator for methods.
//
// See: https://github.com/wycats/javascript-decorators
//
// TODO: make it work for single functions.
export const debounce = duration => (target, name, descriptor) => {
  const fn = descriptor.value

  // This symbol is used to store the related data directly on the
  // current object.
  const s = Symbol()

  function debounced () {
    const data = this[s] || (this[s] = {
      lastCall: 0,
      wrapper: null
    })

    const now = Date.now()
    if (now > data.lastCall + duration) {
      data.lastCall = now
      try {
        const result = fn.apply(this, arguments)
        data.wrapper = () => result
      } catch (error) {
        data.wrapper = () => { throw error }
      }
    }
    return data.wrapper()
  }
  debounced.reset = obj => { delete obj[s] }

  descriptor.value = debounced
  return descriptor
}

// -------------------------------------------------------------------

const _push = Array.prototype.push

export const deferrable = (target, name, descriptor) => {
  let fn
  function newFn () {
    const deferreds = []
    const defer = fn => {
      deferreds.push(fn)
    }
    defer.clear = () => {
      deferreds.length = 0
    }

    const args = [ defer ]
    _push.apply(args, arguments)

    let executeDeferreds = () => {
      let i = deferreds.length
      while (i) {
        deferreds[--i]()
      }
    }

    try {
      const result = fn.apply(this, args)

      if (isPromise(result)) {
        result::pFinally(executeDeferreds)

        // Do not execute the deferreds in the finally block.
        executeDeferreds = noop
      }

      return result
    } finally {
      executeDeferreds()
    }
  }

  if (descriptor) {
    fn = descriptor.value
    descriptor.value = newFn

    return descriptor
  }

  fn = target
  return newFn
}

// Deferred functions are only executed on failures.
//
// i.e.: defer.clear() is automatically called in case of success.
deferrable.onFailure = (target, name, descriptor) => {
  let fn
  function newFn (defer) {
    const result = fn.apply(this, arguments)

    return isPromise(result)
      ? result.then(result => {
        defer.clear()
        return result
      })
      : (defer.clear(), result)
  }

  if (descriptor) {
    fn = descriptor.value
    descriptor.value = newFn
  } else {
    fn = target
    target = newFn
  }

  return deferrable(target, name, descriptor)
}

// Deferred functions are only executed on success.
//
// i.e.: defer.clear() is automatically called in case of failure.
deferrable.onSuccess = (target, name, descriptor) => {
  let fn
  function newFn (defer) {
    try {
      const result = fn.apply(this, arguments)

      return isPromise(result)
        ? result.then(null, error => {
          defer.clear()
          throw error
        })
        : result
    } catch (error) {
      defer.clear()
      throw error
    }
  }

  if (descriptor) {
    fn = descriptor.value
    descriptor.value = newFn
  } else {
    fn = target
    target = newFn
  }

  return deferrable(target, name, descriptor)
}

// -------------------------------------------------------------------

const _ownKeys = (
  typeof Reflect !== 'undefined' && Reflect.ownKeys ||
  (({
    getOwnPropertyNames: names,
    getOwnPropertySymbols: symbols
  }) => symbols
    ? obj => names(obj).concat(symbols(obj))
    : names
  )(Object)
)

const _bindPropertyDescriptor = (descriptor, thisArg) => {
  const { get, set, value } = descriptor
  if (get) {
    descriptor.get = bind(get, thisArg)
  }
  if (set) {
    descriptor.set = bind(set, thisArg)
  }

  if (isFunction(value)) {
    descriptor.value = bind(value, thisArg)
  }

  return descriptor
}

const _isIgnoredProperty = name => (
  name[0] === '_' ||
  name === 'constructor'
)

const _isIgnoredStaticProperty = name => (
  name === 'length' ||
  name === 'name' ||
  name === 'prototype'
)

export const mixin = MixIns => Class => {
  if (!isArray(MixIns)) {
    MixIns = [ MixIns ]
  }

  const { name } = Class

  const Decorator = (...args) => {
    const instance = new Class(...args)

    for (const MixIn of MixIns) {
      const { prototype } = MixIn
      const mixinInstance = new MixIn(instance)
      const descriptors = { __proto__: null }
      for (const prop of _ownKeys(prototype)) {
        if (_isIgnoredProperty(prop)) {
          continue
        }

        if (prop in instance) {
          throw new Error(`${name}#${prop} is already defined`)
        }

        descriptors[prop] = _bindPropertyDescriptor(
          getOwnPropertyDescriptor(prototype, prop),
          mixinInstance
        )
      }
      defineProperties(instance, descriptors)
    }

    return instance
  }

  // Copy original and mixed-in static properties on Decorator class.
  const descriptors = { __proto__: null }
  for (const prop of _ownKeys(Class)) {
    descriptors[prop] = getOwnPropertyDescriptor(Class, prop)
  }
  for (const MixIn of MixIns) {
    for (const prop of _ownKeys(MixIn)) {
      if (_isIgnoredStaticProperty(prop)) {
        continue
      }

      if (prop in descriptors) {
        throw new Error(`${name}.${prop} is already defined`)
      }

      descriptors[prop] = getOwnPropertyDescriptor(MixIn, prop)
    }
  }
  defineProperties(Decorator, descriptors)

  return Decorator
}
<|endoftext|>


<commit_before>/**
 * Demo-repo2.js
 */

alert('demo-repo2.js');<commit_msg>add empty js file for demo<commit_after><|endoftext|>


<commit_before>import _clone from "lodash/clone";
import _mapValues from "lodash/mapValues";
import _forOwn from "lodash/forOwn";
import _intersection from "lodash/intersection";
import _keys from "lodash/keys";
import _map from "lodash/map";
import _each from "lodash/forEach";
import _size from "lodash/size";
import _findKey from "lodash/findKey";
import _uniq from "lodash/uniq";

function defaultDispatchInterceptor(action, dispatch) {
  dispatch(action);
}

class Dispatcher {
  constructor(stores) {
    this.stores = {};
    this.currentDispatch = null;
    this.currentActionType = null;
    this.waitingToDispatch = [];
    this.dispatchInterceptor = defaultDispatchInterceptor;
    this._boundDispatch = this._dispatch.bind(this);

    if (stores) {
      Object.keys(stores).forEach(storeName => {
        this.addStore(storeName, stores[storeName]);
      });
    }
  }

  addStore(name, store) {
    store.dispatcher = this; // eslint-disable-line

    this.stores[name] = store;
  }

  dispatch(action) {
    this.dispatchInterceptor(action, this._boundDispatch);
  }

  _dispatch(action) {
    if (!action || !action.type) {
      throw new Error("Can only dispatch actions with a 'type' property");
    }

    if (this.currentDispatch) {
      throw new Error(
        `Cannot dispatch an action ('${action.type}') while another action` +
          `('${this.currentActionType}') is being dispatched`
      );
    }

    this.waitingToDispatch = _clone(this.stores);

    this.currentActionType = action.type;
    this.currentDispatch = _mapValues(this.stores, () => ({
      resolved: false,
      waitingOn: [],
      waitCallback: null
    }));

    try {
      this.doDispatchLoop(action);
    } finally {
      this.currentActionType = null;
      this.currentDispatch = null;
    }
  }

  doDispatchLoop(action) {
    const dispatchedThisLoop = [];
    const removeFromDispatchQueue = [];

    let dispatch;
    let canBeDispatchedTo;
    let wasHandled = false;

    _forOwn(this.waitingToDispatch, (value, key) => {
      dispatch = this.currentDispatch[key];
      canBeDispatchedTo =
        !dispatch.waitingOn.length ||
        !_intersection(dispatch.waitingOn, _keys(this.waitingToDispatch))
          .length;

      if (canBeDispatchedTo) {
        if (dispatch.waitCallback) {
          const stores = _map(dispatch.waitingOn, store => this.stores[store]);

          const fn = dispatch.waitCallback;

          dispatch.waitCallback = null;
          dispatch.waitingOn = [];
          dispatch.resolved = true;

          fn(...stores);

          wasHandled = true;
        } else {
          dispatch.resolved = true;

          const handled = this.stores[key].__handleAction__(action);

          if (handled) {
            wasHandled = true;
          }
        }

        dispatchedThisLoop.push(key);

        if (this.currentDispatch[key].resolved) {
          removeFromDispatchQueue.push(key);
        }
      }
    });

    if (_keys(this.waitingToDispatch).length && !dispatchedThisLoop.length) {
      const storesWithCircularWaits = _keys(this.waitingToDispatch).join(", ");

      throw new Error(
        `Indirect circular wait detected among: ${storesWithCircularWaits}`
      );
    }

    _each(removeFromDispatchQueue, key => {
      delete this.waitingToDispatch[key];
    });

    if (_size(this.waitingToDispatch)) {
      this.doDispatchLoop(action);
    }

    if (!wasHandled && console && console.warn) {
      console.warn(
        `An action of type ${action.type} was dispatched, but no store handled it`
      );
    }
  }

  waitForStores(store, stores, fn) {
    if (!this.currentDispatch) {
      throw new Error("Cannot wait unless an action is being dispatched");
    }

    const waitingStoreName = _findKey(this.stores, val => val === store);

    if (stores.indexOf(waitingStoreName) > -1) {
      throw new Error("A store cannot wait on itself");
    }

    const dispatch = this.currentDispatch[waitingStoreName];

    if (dispatch.waitingOn.length) {
      throw new Error(`${waitingStoreName} already waiting on stores`);
    }

    _each(stores, storeName => {
      const storeDispatch = this.currentDispatch[storeName];

      if (!this.stores[storeName]) {
        throw new Error(`Cannot wait for non-existing store ${storeName}`);
      }

      if (storeDispatch.waitingOn.indexOf(waitingStoreName) > -1) {
        throw new Error(
          `Circular wait detected between ${waitingStoreName} and ${storeName}`
        );
      }
    });

    dispatch.resolved = false;
    dispatch.waitingOn = _uniq(dispatch.waitingOn.concat(stores));
    dispatch.waitCallback = fn;
  }

  setDispatchInterceptor(fn) {
    this.dispatchInterceptor = fn || defaultDispatchInterceptor;
  }
}

export default Dispatcher;
<commit_msg>Remove usage of `lodash/keys`<commit_after>import _clone from "lodash/clone";
import _mapValues from "lodash/mapValues";
import _forOwn from "lodash/forOwn";
import _intersection from "lodash/intersection";
import _map from "lodash/map";
import _each from "lodash/forEach";
import _size from "lodash/size";
import _findKey from "lodash/findKey";
import _uniq from "lodash/uniq";

function defaultDispatchInterceptor(action, dispatch) {
  dispatch(action);
}

class Dispatcher {
  constructor(stores) {
    this.stores = {};
    this.currentDispatch = null;
    this.currentActionType = null;
    this.waitingToDispatch = [];
    this.dispatchInterceptor = defaultDispatchInterceptor;
    this._boundDispatch = this._dispatch.bind(this);

    if (stores) {
      Object.keys(stores).forEach(storeName => {
        this.addStore(storeName, stores[storeName]);
      });
    }
  }

  addStore(name, store) {
    store.dispatcher = this; // eslint-disable-line

    this.stores[name] = store;
  }

  dispatch(action) {
    this.dispatchInterceptor(action, this._boundDispatch);
  }

  _dispatch(action) {
    if (!action || !action.type) {
      throw new Error("Can only dispatch actions with a 'type' property");
    }

    if (this.currentDispatch) {
      throw new Error(
        `Cannot dispatch an action ('${action.type}') while another action` +
          `('${this.currentActionType}') is being dispatched`
      );
    }

    this.waitingToDispatch = _clone(this.stores);

    this.currentActionType = action.type;
    this.currentDispatch = _mapValues(this.stores, () => ({
      resolved: false,
      waitingOn: [],
      waitCallback: null
    }));

    try {
      this.doDispatchLoop(action);
    } finally {
      this.currentActionType = null;
      this.currentDispatch = null;
    }
  }

  doDispatchLoop(action) {
    const dispatchedThisLoop = [];
    const removeFromDispatchQueue = [];

    let dispatch;
    let canBeDispatchedTo;
    let wasHandled = false;

    _forOwn(this.waitingToDispatch, (value, key) => {
      dispatch = this.currentDispatch[key];
      canBeDispatchedTo =
        !dispatch.waitingOn.length ||
        !_intersection(dispatch.waitingOn, Object.keys(this.waitingToDispatch))
          .length;

      if (canBeDispatchedTo) {
        if (dispatch.waitCallback) {
          const stores = _map(dispatch.waitingOn, store => this.stores[store]);

          const fn = dispatch.waitCallback;

          dispatch.waitCallback = null;
          dispatch.waitingOn = [];
          dispatch.resolved = true;

          fn(...stores);

          wasHandled = true;
        } else {
          dispatch.resolved = true;

          const handled = this.stores[key].__handleAction__(action);

          if (handled) {
            wasHandled = true;
          }
        }

        dispatchedThisLoop.push(key);

        if (this.currentDispatch[key].resolved) {
          removeFromDispatchQueue.push(key);
        }
      }
    });

    if (
      Object.keys(this.waitingToDispatch).length &&
      !dispatchedThisLoop.length
    ) {
      const storesWithCircularWaits = Object.keys(this.waitingToDispatch).join(
        ", "
      );

      throw new Error(
        `Indirect circular wait detected among: ${storesWithCircularWaits}`
      );
    }

    _each(removeFromDispatchQueue, key => {
      delete this.waitingToDispatch[key];
    });

    if (_size(this.waitingToDispatch)) {
      this.doDispatchLoop(action);
    }

    if (!wasHandled && console && console.warn) {
      console.warn(
        `An action of type ${action.type} was dispatched, but no store handled it`
      );
    }
  }

  waitForStores(store, stores, fn) {
    if (!this.currentDispatch) {
      throw new Error("Cannot wait unless an action is being dispatched");
    }

    const waitingStoreName = _findKey(this.stores, val => val === store);

    if (stores.indexOf(waitingStoreName) > -1) {
      throw new Error("A store cannot wait on itself");
    }

    const dispatch = this.currentDispatch[waitingStoreName];

    if (dispatch.waitingOn.length) {
      throw new Error(`${waitingStoreName} already waiting on stores`);
    }

    _each(stores, storeName => {
      const storeDispatch = this.currentDispatch[storeName];

      if (!this.stores[storeName]) {
        throw new Error(`Cannot wait for non-existing store ${storeName}`);
      }

      if (storeDispatch.waitingOn.indexOf(waitingStoreName) > -1) {
        throw new Error(
          `Circular wait detected between ${waitingStoreName} and ${storeName}`
        );
      }
    });

    dispatch.resolved = false;
    dispatch.waitingOn = _uniq(dispatch.waitingOn.concat(stores));
    dispatch.waitCallback = fn;
  }

  setDispatchInterceptor(fn) {
    this.dispatchInterceptor = fn || defaultDispatchInterceptor;
  }
}

export default Dispatcher;
<|endoftext|>


<commit_before>//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Calculate steps needed to redraw the visualization
//------------------------------------------------------------------------------
d3plus.draw.steps = function(vars) {

  var steps = []

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // If it has one, run the current app's setup function.
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      return typeof d3plus.apps[vars.type.value].setup == "function"
    },
    "function": d3plus.apps[vars.type.value].setup,
    "message": "Initializing \""+vars.type.value+"\""
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Remove any lingering tooltips.
  //----------------------------------------------------------------------------
  steps.push({
    "function": function(vars) {
      if (vars.type.previous && vars.type.value != vars.type.previous) {
        d3plus.tooltip.remove(vars.type.previous);
      }
      d3plus.tooltip.remove(vars.type.value);
    },
    "message": "Resetting Tooltips"
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Create SVG group elements if the container is new or has changed
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      return vars.container.changed
    },
    "function": d3plus.draw.enter,
    "message": "Creating SVG Elements"
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Create group for current app, if it doesn't exist.
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      return !(vars.type.value in vars.g.apps)
    },
    "function": function(vars) {

      vars.g.apps[vars.type.value] = vars.g.app
        .selectAll("g#"+vars.type.value)
        .data([vars.type.value])

      vars.g.apps[vars.type.value].enter().append("g")
        .attr("id",vars.type.value)
        .attr("opacity",0)

    },
    "message": "Creating \""+vars.type.value+"\" Group"
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // If new data is detected, analyze and reset it.
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      return vars.data.changed
    },
    "function": function(vars) {

      vars.data.filtered = null
      vars.data.grouped = null
      vars.data.app = null
      vars.data.restricted = null
      vars.nodes.restricted = null
      vars.edges.restricted = null

      d3plus.data.keys(vars,"data")

    },
    "message": "Analyzing New Data"
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // If new attributes are detected, analyze them.
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      return vars.attrs.changed
    },
    "function": function(vars) {

      d3plus.data.keys(vars,"attrs")

    },
    "message": "Analyzing New Attributes"
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Filter Data if variables with "data_filter" have been changed
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {

      vars.check = []
      for (k in vars) {
        if (vars[k] && vars[k]["data_filter"] && vars[k].changed) {
          vars.check.push(k)
        }
      }

      return !vars.data.filtered || vars.check.length || vars.active.changed ||
        vars.temp.changed || vars.total.changed
    },
    "function": function(vars) {

      vars.data.grouped = null
      vars.data.app = null;

      d3plus.data.filter(vars)

    },
    "message": "Filtering Data"
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Restricts Filtered Data if objects have "Solo" or "Mute"
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      return vars.mute.length > 0 || vars.solo.length > 0
    },
    "function": d3plus.data.restrict,
    "message": "Filtering Data",
    "otherwise": function(vars) {

      if (vars.filtered || !vars.data.restricted || vars.check.length) {

        vars.data.restricted = d3plus.utils.copy(vars.data.filtered)
        vars.data.grouped = null
        vars.data.app = null
        vars.nodes.restricted = null
        vars.edges.restricted = null
        vars.filtered = false

      }

    }
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Formats Data to type "group", if it does not exist.
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      return !vars.data.grouped
    },
    "function": function(vars) {
      vars.data.grouped = d3plus.data.format(vars,"grouped")
    },
    "message": "Formatting Data"
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Fetches data "pool" to use for scales and overall values
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      var year = !vars.time.fixed.value ? ["all"] : null
      return (year === null && (vars.time.solo.changed || vars.time.mute.changed)) || !vars.data.pool
    },
    "function": function(vars) {
      var year = !vars.time.fixed.value ? ["all"] : null
      vars.data.pool = d3plus.data.fetch(vars,"grouped",year)
    },
    "message": "Formatting Data"
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Fetch the correct Data for the App
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      return !vars.data.app || vars.depth.changed ||
          vars.time.solo.changed || vars.time.mute.changed ||
          vars.solo.length || vars.mute.length
    },
    "function": function(vars) {
      vars.data.app = d3plus.data.fetch(vars,"grouped")
    },
    "message": "Formatting Data"
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Format nodes/edges if needed
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      var edge_req = d3plus.apps[vars.type.value].requirements.indexOf("edges") >= 0
      return (!vars.edges.linked || vars.edges.changed)
        && edge_req && vars.edges.value
    },
    "function": d3plus.data.edges,
    "message": "Analyzing Network"
  })
  steps.push({
    "check": function(vars) {
      var node_req = d3plus.apps[vars.type.value].requirements.indexOf("nodes") >= 0
      return node_req && (!vars.nodes.positions || vars.nodes.changed)
        && vars.nodes.value && vars.edges.value
    },
    "function": d3plus.data.nodes,
    "message": "Analyzing Network"
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Calculate color scale if type is number
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {

      if (vars.color.changed && vars.color.key) {

        if (typeof vars.color.key == "object") {
          if (vars.color.key[vars.id.key]) {
            var color_id = vars.color.key[vars.id.key]
          }
          else {
            var color_id = vars.color.key[Object.keys(vars.color.key)[0]]
          }
        }
        else {
          var color_id = vars.color.key
        }

        if (vars.data.keys && color_id in vars.data.keys) {
          vars.color.type = vars.data.keys[color_id]
        }
        else if (vars.attrs.keys && color_id in vars.attrs.keys) {
          vars.color.type = vars.attrs.keys[color_id]
        }
        else {
          vars.color.type = undefined
        }

      }
      else if (!vars.color.key) {
        vars.color.type = vars.data.keys[vars.id.key]
      }

      return vars.data.value && vars.color.type == "number" &&
              (vars.color.changed || vars.data.changed || vars.depth.changed ||
                (vars.time.fixed.value &&
                  (vars.time.solo.changed || vars.time.mute.changed)
                )
              )

    },
    "function": d3plus.data.color,
    "message": "Calculating Colors",
    "otherwise": function(vars) {
      if (!vars.color.type != "number") {
        vars.color.scale = null
      }
    }
  })

  steps.push({
    "function": function(vars) {
      d3plus.ui.titles(vars)
      d3plus.ui.legend(vars)
      d3plus.ui.timeline(vars)
      d3plus.ui.history(vars)
      vars.app_height -= (vars.margin.top+vars.margin.bottom)
      d3plus.ui.focus(vars)
      d3plus.draw.update(vars)
    },
    "message": "Updating UI"
  })

  steps.push({
    "function": [
      d3plus.draw.errors,
      d3plus.draw.app,
      d3plus.shape.edges,
      d3plus.shape.draw,
      d3plus.draw.finish
      ],
    "message": "Drawing Visualization"
  })

  return steps

}
<commit_msg>only run certain draw steps when vars.update == true<commit_after>//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Calculate steps needed to redraw the visualization
//------------------------------------------------------------------------------
d3plus.draw.steps = function(vars) {

  var steps = []

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // If it has one, run the current app's setup function.
  //----------------------------------------------------------------------------
  steps.push({
    "check": function(vars) {
      return vars.update && typeof d3plus.apps[vars.type.value].setup == "function"
    },
    "function": d3plus.apps[vars.type.value].setup,
    "message": "Initializing \""+vars.type.value+"\""
  })

  //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Remove any lingering tooltips.
  //----------------------------------------------------------------------------
  steps.push({
    "function": function(vars) {
      if (vars.type.previous && vars.type.value != vars.type.previous) {
        d3plus.tooltip.remove(vars.type.previous);
      }
      d3plus.tooltip.remove(vars.type.value);
    },
    "message": "Resetting Tooltips"
  })

  if (vars.update) {

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Create SVG group elements if the container is new or has changed
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {
        return vars.container.changed
      },
      "function": d3plus.draw.enter,
      "message": "Creating SVG Elements"
    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Create group for current app, if it doesn't exist.
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {
        return !(vars.type.value in vars.g.apps)
      },
      "function": function(vars) {

        vars.g.apps[vars.type.value] = vars.g.app
          .selectAll("g#"+vars.type.value)
          .data([vars.type.value])

        vars.g.apps[vars.type.value].enter().append("g")
          .attr("id",vars.type.value)
          .attr("opacity",0)

      },
      "message": "Creating \""+vars.type.value+"\" Group"
    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // If new data is detected, analyze and reset it.
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {
        return vars.data.changed
      },
      "function": function(vars) {

        vars.data.filtered = null
        vars.data.grouped = null
        vars.data.app = null
        vars.data.restricted = null
        vars.nodes.restricted = null
        vars.edges.restricted = null

        d3plus.data.keys(vars,"data")

      },
      "message": "Analyzing New Data"
    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // If new attributes are detected, analyze them.
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {
        return vars.attrs.changed
      },
      "function": function(vars) {

        d3plus.data.keys(vars,"attrs")

      },
      "message": "Analyzing New Attributes"
    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Filter Data if variables with "data_filter" have been changed
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {

        vars.check = []
        for (k in vars) {
          if (vars[k] && vars[k]["data_filter"] && vars[k].changed) {
            vars.check.push(k)
          }
        }

        return !vars.data.filtered || vars.check.length || vars.active.changed ||
          vars.temp.changed || vars.total.changed
      },
      "function": function(vars) {

        vars.data.grouped = null
        vars.data.app = null;

        d3plus.data.filter(vars)

      },
      "message": "Filtering Data"
    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Restricts Filtered Data if objects have "Solo" or "Mute"
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {
        return vars.mute.length > 0 || vars.solo.length > 0
      },
      "function": d3plus.data.restrict,
      "message": "Filtering Data",
      "otherwise": function(vars) {

        if (vars.filtered || !vars.data.restricted || vars.check.length) {

          vars.data.restricted = d3plus.utils.copy(vars.data.filtered)
          vars.data.grouped = null
          vars.data.app = null
          vars.nodes.restricted = null
          vars.edges.restricted = null
          vars.filtered = false

        }

      }
    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Formats Data to type "group", if it does not exist.
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {
        return !vars.data.grouped
      },
      "function": function(vars) {
        vars.data.grouped = d3plus.data.format(vars,"grouped")
      },
      "message": "Formatting Data"
    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Fetches data "pool" to use for scales and overall values
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {
        var year = !vars.time.fixed.value ? ["all"] : null
        return (year === null && (vars.time.solo.changed || vars.time.mute.changed)) || !vars.data.pool
      },
      "function": function(vars) {
        var year = !vars.time.fixed.value ? ["all"] : null
        vars.data.pool = d3plus.data.fetch(vars,"grouped",year)
      },
      "message": "Formatting Data"
    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Fetch the correct Data for the App
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {
        return !vars.data.app || vars.depth.changed ||
            vars.time.solo.changed || vars.time.mute.changed ||
            vars.solo.length || vars.mute.length
      },
      "function": function(vars) {
        vars.data.app = d3plus.data.fetch(vars,"grouped")
      },
      "message": "Formatting Data"
    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Format nodes/edges if needed
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {
        var edge_req = d3plus.apps[vars.type.value].requirements.indexOf("edges") >= 0
        return (!vars.edges.linked || vars.edges.changed)
          && edge_req && vars.edges.value
      },
      "function": d3plus.data.edges,
      "message": "Analyzing Network"
    })
    steps.push({
      "check": function(vars) {
        var node_req = d3plus.apps[vars.type.value].requirements.indexOf("nodes") >= 0
        return node_req && (!vars.nodes.positions || vars.nodes.changed)
          && vars.nodes.value && vars.edges.value
      },
      "function": d3plus.data.nodes,
      "message": "Analyzing Network"
    })

    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // Calculate color scale if type is number
    //----------------------------------------------------------------------------
    steps.push({
      "check": function(vars) {

        if (vars.color.changed && vars.color.key) {

          if (typeof vars.color.key == "object") {
            if (vars.color.key[vars.id.key]) {
              var color_id = vars.color.key[vars.id.key]
            }
            else {
              var color_id = vars.color.key[Object.keys(vars.color.key)[0]]
            }
          }
          else {
            var color_id = vars.color.key
          }

          if (vars.data.keys && color_id in vars.data.keys) {
            vars.color.type = vars.data.keys[color_id]
          }
          else if (vars.attrs.keys && color_id in vars.attrs.keys) {
            vars.color.type = vars.attrs.keys[color_id]
          }
          else {
            vars.color.type = undefined
          }

        }
        else if (!vars.color.key) {
          vars.color.type = vars.data.keys[vars.id.key]
        }

        return vars.data.value && vars.color.key && vars.color.type == "number" &&
                (vars.color.changed || vars.data.changed || vars.depth.changed ||
                  (vars.time.fixed.value &&
                    (vars.time.solo.changed || vars.time.mute.changed)
                  )
                )

      },
      "function": d3plus.data.color,
      "message": "Calculating Colors",
      "otherwise": function(vars) {
        if (!vars.color.type != "number") {
          vars.color.scale = null
        }
      }
    })

    steps.push({
      "function": function(vars) {
        vars.margin = {"top": 0, "right": 0, "bottom": 0, "left": 0}
        d3plus.ui.titles(vars)
        d3plus.ui.legend(vars)
        d3plus.ui.timeline(vars)
        d3plus.ui.history(vars)
        vars.app_height -= (vars.margin.top+vars.margin.bottom)
      },
      "message": "Updating UI"
    })

  }

  steps.push({
    "function": [
      d3plus.ui.focus,
      d3plus.draw.update
    ],
    "message": "Updating UI"
  })

  steps.push({
    "function": function(vars) {
      if (vars.update) {
        d3plus.draw.errors(vars)
        d3plus.draw.app(vars)
        d3plus.shape.edges(vars)
        d3plus.shape.draw(vars)
      }
      d3plus.draw.focus(vars)
      d3plus.draw.finish(vars)
    },
    "message": "Drawing Visualization"
  })

  return steps

}
<|endoftext|>


<commit_before>import {Plugin} from "prosemirror-state"
import {dropPoint} from "prosemirror-transform"

// :: (options: ?Object) → Plugin
// Create a plugin that, when added to a ProseMirror instance,
// causes a decoration to show up at the drop position when something
// is dragged over the editor.
//
//   options::- These options are supported:
//
//     color:: ?string
//     The color of the cursor. Defaults to `black`.
//
//     width:: ?number
//     The precise width of the cursor in pixels. Defaults to 1.
//
//     class:: ?string
//     A CSS class name to add to the cursor element.
export function dropCursor(options = {}) {
  return new Plugin({
    view(editorView) { return new DropCursorView(editorView, options) }
  })
}

class DropCursorView {
  constructor(editorView, options) {
    this.editorView = editorView
    this.width = options.width || 1
    this.color = options.color || "black"
    this.class = options.class
    this.cursorPos = null
    this.element = null
    this.timeout = null

    this.handlers = ["dragover", "dragend", "drop", "dragleave"].map(name => {
      let handler = e => this[name](e)
      editorView.dom.addEventListener(name, handler)
      return {name, handler}
    })
  }

  destroy() {
    this.handlers.forEach(({name, handler}) => this.editorView.dom.removeEventListener(name, handler))
  }

  update(editorView, prevState) {
    if (this.cursorPos != null && prevState.doc != editorView.state.doc) this.updateOverlay()
  }

  setCursor(pos) {
    if (pos == this.cursorPos) return
    this.cursorPos = pos
    if (pos == null) {
      this.element.parentNode.removeChild(this.element)
      this.element = null
    } else {
      this.updateOverlay()
    }
  }

  updateOverlay() {
    let $pos = this.editorView.state.doc.resolve(this.cursorPos), rect
    if (!$pos.parent.inlineContent) {
      let before = $pos.nodeBefore, after = $pos.nodeAfter
      if (before || after) {
        let nodeRect = this.editorView.nodeDOM(this.cursorPos - (before ?  before.nodeSize : 0)).getBoundingClientRect()
        let top = before ? nodeRect.bottom : nodeRect.top
        if (before && after)
          top = (top + this.editorView.nodeDOM(this.cursorPos).getBoundingClientRect().top) / 2
        rect = {left: nodeRect.left, right: nodeRect.right, top: top - this.width / 2, bottom: top + this.width / 2}
      }
    }
    if (!rect) {
      let coords = this.editorView.coordsAtPos(this.cursorPos)
      rect = {left: coords.left - this.width / 2, right: coords.left + this.width / 2, top: coords.top, bottom: coords.bottom}
    }

    let parent = this.editorView.dom.offsetParent
    if (!this.element) {
      this.element = parent.appendChild(document.createElement("div"))
      if (this.class) this.element.className = this.class
      this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none; background-color: " + this.color
    }
    let parentLeft, parentTop
    if (!parent || parent == document.body && getComputedStyle(parent).position == "static") {
      parentLeft = -pageXOffset
      parentTop = -pageYOffset
    } else {
      let rect = parent.getBoundingClientRect()
      parentLeft = rect.left - parent.scrollLeft
      parentTop = rect.top - parent.scrollTop
    }
    this.element.style.left = (rect.left - parentLeft) + "px"
    this.element.style.top = (rect.top - parentTop) + "px"
    this.element.style.width = (rect.right - rect.left) + "px"
    this.element.style.height = (rect.bottom - rect.top) + "px"
  }

  scheduleRemoval(timeout) {
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => this.setCursor(null), timeout)
  }

  dragover(event) {
    if (!this.editorView.editable) return
    let pos = this.editorView.posAtCoords({left: event.clientX, top: event.clientY})
    if (pos) {
      let target = pos.pos
      if (this.editorView.dragging && this.editorView.dragging.slice) {
        target = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice)
        if (target == null) return this.setCursor(null)
      }
      this.setCursor(target)
      this.scheduleRemoval(5000)
    }
  }

  dragend() {
    this.scheduleRemoval(20)
  }

  drop() {
    this.scheduleRemoval(20)
  }

  dragleave(event) {
    if (event.target == this.editorView.dom || !this.editorView.dom.contains(event.relatedTarget))
      this.setCursor(null)
  }
}
<commit_msg>Avoid crash when the drop pos is no longer in the document<commit_after>import {Plugin} from "prosemirror-state"
import {dropPoint} from "prosemirror-transform"

// :: (options: ?Object) → Plugin
// Create a plugin that, when added to a ProseMirror instance,
// causes a decoration to show up at the drop position when something
// is dragged over the editor.
//
//   options::- These options are supported:
//
//     color:: ?string
//     The color of the cursor. Defaults to `black`.
//
//     width:: ?number
//     The precise width of the cursor in pixels. Defaults to 1.
//
//     class:: ?string
//     A CSS class name to add to the cursor element.
export function dropCursor(options = {}) {
  return new Plugin({
    view(editorView) { return new DropCursorView(editorView, options) }
  })
}

class DropCursorView {
  constructor(editorView, options) {
    this.editorView = editorView
    this.width = options.width || 1
    this.color = options.color || "black"
    this.class = options.class
    this.cursorPos = null
    this.element = null
    this.timeout = null

    this.handlers = ["dragover", "dragend", "drop", "dragleave"].map(name => {
      let handler = e => this[name](e)
      editorView.dom.addEventListener(name, handler)
      return {name, handler}
    })
  }

  destroy() {
    this.handlers.forEach(({name, handler}) => this.editorView.dom.removeEventListener(name, handler))
  }

  update(editorView, prevState) {
    if (this.cursorPos != null && prevState.doc != editorView.state.doc) {
      if (this.cursorPos > editorView.state.doc.content.size) this.setCursor(null)
      else this.updateOverlay()
    }
  }

  setCursor(pos) {
    if (pos == this.cursorPos) return
    this.cursorPos = pos
    if (pos == null) {
      this.element.parentNode.removeChild(this.element)
      this.element = null
    } else {
      this.updateOverlay()
    }
  }

  updateOverlay() {
    let $pos = this.editorView.state.doc.resolve(this.cursorPos), rect
    if (!$pos.parent.inlineContent) {
      let before = $pos.nodeBefore, after = $pos.nodeAfter
      if (before || after) {
        let nodeRect = this.editorView.nodeDOM(this.cursorPos - (before ?  before.nodeSize : 0)).getBoundingClientRect()
        let top = before ? nodeRect.bottom : nodeRect.top
        if (before && after)
          top = (top + this.editorView.nodeDOM(this.cursorPos).getBoundingClientRect().top) / 2
        rect = {left: nodeRect.left, right: nodeRect.right, top: top - this.width / 2, bottom: top + this.width / 2}
      }
    }
    if (!rect) {
      let coords = this.editorView.coordsAtPos(this.cursorPos)
      rect = {left: coords.left - this.width / 2, right: coords.left + this.width / 2, top: coords.top, bottom: coords.bottom}
    }

    let parent = this.editorView.dom.offsetParent
    if (!this.element) {
      this.element = parent.appendChild(document.createElement("div"))
      if (this.class) this.element.className = this.class
      this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none; background-color: " + this.color
    }
    let parentLeft, parentTop
    if (!parent || parent == document.body && getComputedStyle(parent).position == "static") {
      parentLeft = -pageXOffset
      parentTop = -pageYOffset
    } else {
      let rect = parent.getBoundingClientRect()
      parentLeft = rect.left - parent.scrollLeft
      parentTop = rect.top - parent.scrollTop
    }
    this.element.style.left = (rect.left - parentLeft) + "px"
    this.element.style.top = (rect.top - parentTop) + "px"
    this.element.style.width = (rect.right - rect.left) + "px"
    this.element.style.height = (rect.bottom - rect.top) + "px"
  }

  scheduleRemoval(timeout) {
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => this.setCursor(null), timeout)
  }

  dragover(event) {
    if (!this.editorView.editable) return
    let pos = this.editorView.posAtCoords({left: event.clientX, top: event.clientY})
    if (pos) {
      let target = pos.pos
      if (this.editorView.dragging && this.editorView.dragging.slice) {
        target = dropPoint(this.editorView.state.doc, target, this.editorView.dragging.slice)
        if (target == null) return this.setCursor(null)
      }
      this.setCursor(target)
      this.scheduleRemoval(5000)
    }
  }

  dragend() {
    this.scheduleRemoval(20)
  }

  drop() {
    this.scheduleRemoval(20)
  }

  dragleave(event) {
    if (event.target == this.editorView.dom || !this.editorView.dom.contains(event.relatedTarget))
      this.setCursor(null)
  }
}
<|endoftext|>


<commit_before>import { StringEditor } from './string';

export var IpEditor = StringEditor.extend({

  preBuild: function() {
    this._super();
    var pattern, patternmessage;

    if (this.schema.format == 'hostname') {
      // https://www.regextester.com/103452
      pattern = '(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)';
      patternmessage = this.options.patternmessage || this.translate('error_hostname');
    }
    else if (this.schema.format == 'ipv6') {
      pattern = '^(?:(?:(?:[a-fA-F0-9]{1,4}:){6}|(?=(?:[a-fA-F0-9]{0,4}:){2,6}(?:[0-9]{1,3}\.){3}[0-9]{1,3}$)(([0-9a-fA-F]{1,4}:){1,5}|:)((:[0-9a-fA-F]{1,4}){1,5}:|:)|::(?:[a-fA-F0-9]{1,4}:){5})(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])|(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|(?=(?:[a-fA-F0-9]{0,4}:){0,7}[a-fA-F0-9]{0,4}$)(([0-9a-fA-F]{1,4}:){1,7}|:)((:[0-9a-fA-F]{1,4}){1,7}|:)|(?:[a-fA-F0-9]{1,4}:){7}:|:(:[a-fA-F0-9]{1,4}){7})$';
      patternmessage = this.options.patternmessage || this.translate('error_ipv6');
      // Set cleave options if no existing options is present
      if (this.schema.options && !this.schema.options.cleave) this.schema.options.cleave = {
          delimiters: [':'],
          blocks: [4, 4, 4, 4, 4, 4, 4, 4],
          uppercase: true
        };
    }
    else {
      pattern = '^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$';
      patternmessage = this.options.patternmessage || this.translate('error_ipv4');
      // Set cleave options if no existing options is present
      if (this.schema.options && !this.schema.options.cleave) this.schema.options.cleave = {
          delimiters: ['.'],
          blocks: [3, 3, 3, 3],
          numericOnly: true
        };
    }

    // Set custom pattern error validation message
    if (!this.jsoneditor.validator.schema.properties[this.key].options) this.jsoneditor.validator.schema.properties[this.key].options = {};
    this.options.patternmessage = this.jsoneditor.validator.schema.properties[this.key].options.patternmessage = patternmessage;

    // Force pattern validation
    this.jsoneditor.validator.schema.properties[this.key].pattern = this.schema.pattern = pattern;

  }
});
<commit_msg>Fix cleave integration in IP editor<commit_after>import { StringEditor } from './string';
import { $extend, $each } from '../utilities';

export var IpEditor = StringEditor.extend({

  preBuild: function() {
    this._super();
    var pattern, patternmessage;

    if (this.schema.format == 'hostname') {
      // https://www.regextester.com/103452
      pattern = '(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)';
      patternmessage = this.options.patternmessage || this.translate('error_hostname');
    }
    else if (this.schema.format == 'ipv6') {
      pattern = '^(?:(?:(?:[a-fA-F0-9]{1,4}:){6}|(?=(?:[a-fA-F0-9]{0,4}:){2,6}(?:[0-9]{1,3}\.){3}[0-9]{1,3}$)(([0-9a-fA-F]{1,4}:){1,5}|:)((:[0-9a-fA-F]{1,4}){1,5}:|:)|::(?:[a-fA-F0-9]{1,4}:){5})(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])|(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|(?=(?:[a-fA-F0-9]{0,4}:){0,7}[a-fA-F0-9]{0,4}$)(([0-9a-fA-F]{1,4}:){1,7}|:)((:[0-9a-fA-F]{1,4}){1,7}|:)|(?:[a-fA-F0-9]{1,4}:){7}:|:(:[a-fA-F0-9]{1,4}){7})$';
      patternmessage = this.options.patternmessage || this.translate('error_ipv6');
      // Set cleave options if no existing options is present
      if (this.schema.options && !this.schema.options.cleave) this.schema.options.cleave = {
          delimiters: [':'],
          blocks: [4, 4, 4, 4, 4, 4, 4, 4],
          uppercase: true
        };
    }
    else {
      pattern = '^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$';
      patternmessage = this.options.patternmessage || this.translate('error_ipv4');
      // Set cleave options if no existing options is present
      if (this.schema.options && !this.schema.options.cleave) this.schema.options.cleave = {
          delimiters: ['.'],
          blocks: [3, 3, 3, 3],
          numericOnly: true
        };
    }

    // Set custom pattern error validation message
    if (!this.jsoneditor.validator.schema.properties[this.key].options) this.jsoneditor.validator.schema.properties[this.key].options = {};
    this.options.patternmessage = this.jsoneditor.validator.schema.properties[this.key].options.patternmessage = patternmessage;

    // Force pattern validation
    this.jsoneditor.validator.schema.properties[this.key].pattern = this.schema.pattern = pattern;

    // Update options object
    this.options = $extend(this.options, this.schema.options || {});
  }
});
<|endoftext|>


<commit_before>var $break = { };

var Enumerable = {
  each: function(iterator, context) {
    var index = 0;
    try {
      this._each(function(value) {
        iterator.call(context, value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

  eachSlice: function(number, iterator, context) {
    var index = -number, slices = [], array = this.toArray();
    if (number < 1) return array;
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context);
  },

  all: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = true;
    this.each(function(value, index) {
      result = result && !!iterator.call(context, value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = false;
    this.each(function(value, index) {
      if (result = !!iterator.call(context, value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];
    this.each(function(value, index) {
      results.push(iterator.call(context, value, index));
    });
    return results;
  },

  detect: function(iterator, context) {
    var result;
    this.each(function(value, index) {
      if (iterator.call(context, value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(filter, iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];

    if (Object.isString(filter))
      filter = new RegExp(filter);

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator.call(context, value, index));
    });
    return results;
  },

  include: function(object) {
    if (Object.isFunction(this.indexOf))
      if (this.indexOf(object) != -1) return true;

    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inGroupsOf: function(number, fillWith) {
    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  },

  inject: function(memo, iterator, context) {
    this.each(function(value, index) {
      memo = iterator.call(context, memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator.call(context, value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator, context) {
    return this.map(function(value, index) {
      return {
        value: value,
        criteria: iterator.call(context, value, index)
      };
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.map();
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  },

  size: function() {
    return this.toArray().length;
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
};

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  filter:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray,
  every:   Enumerable.all,
  some:    Enumerable.any
});
<commit_msg>Reorganize enumerable.js.<commit_after>var $break = { };

var Enumerable = (function() {
  function each(iterator, context) {
    var index = 0;
    try {
      this._each(function(value) {
        iterator.call(context, value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  }

  function eachSlice(number, iterator, context) {
    var index = -number, slices = [], array = this.toArray();
    if (number < 1) return array;
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context);
  }

  function all(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = true;
    this.each(function(value, index) {
      result = result && !!iterator.call(context, value, index);
      if (!result) throw $break;
    });
    return result;
  }

  function any(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = false;
    this.each(function(value, index) {
      if (result = !!iterator.call(context, value, index))
        throw $break;
    });
    return result;
  }

  function collect(iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];
    this.each(function(value, index) {
      results.push(iterator.call(context, value, index));
    });
    return results;
  }

  function detect(iterator, context) {
    var result;
    this.each(function(value, index) {
      if (iterator.call(context, value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  }

  function findAll(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  }

  function grep(filter, iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];

    if (Object.isString(filter))
      filter = new RegExp(filter);

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator.call(context, value, index));
    });
    return results;
  }

  function include(object) {
    if (Object.isFunction(this.indexOf))
      if (this.indexOf(object) != -1) return true;

    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  }

  function inGroupsOf(number, fillWith) {
    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  }

  function inject(memo, iterator, context) {
    this.each(function(value, index) {
      memo = iterator.call(context, memo, value, index);
    });
    return memo;
  }

  function invoke(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  }

  function max(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value >= result)
        result = value;
    });
    return result;
  }

  function min(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value < result)
        result = value;
    });
    return result;
  }

  function partition(iterator, context) {
    iterator = iterator || Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator.call(context, value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  }

  function pluck(property) {
    var results = [];
    this.each(function(value) {
      results.push(value[property]);
    });
    return results;
  }

  function reject(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  }

  function sortBy(iterator, context) {
    return this.map(function(value, index) {
      return {
        value: value,
        criteria: iterator.call(context, value, index)
      };
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  }

  function toArray() {
    return this.map();
  }

  function zip() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  }

  function size() {
    return this.toArray().length;
  }

  function inspect() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }

  return {
    each:       each,
    eachSlice:  eachSlice,
    all:        all,
    every:      all,
    any:        any,
    some:       any,
    collect:    collect,
    map:        collect,
    detect:     detect,
    findAll:    findAll,
    select:     findAll,
    filter:     findAll,
    grep:       grep,
    include:    include,
    member:     include,
    inGroupsOf: inGroupsOf,
    inject:     inject,
    invoke:     invoke,
    max:        max,
    min:        min,
    partition:  partition,
    pluck:      pluck,
    reject:     reject,
    sortBy:     sortBy,
    toArray:    toArray,
    entries:    toArray,
    zip:        zip,
    size:       size,
    inspect:    inspect,
    find:       detect
  };
})();
<|endoftext|>


<commit_before>var express   = require('express');
var bp        = require('body-parser');
var cp        = require('cookie-parser');
var wrap      = require('co-express');
var models    = require('./models');
var container = require('./container');
var app       = express();

app.use(bp.json());
app.use(cp());

app.get('/experiments', wrap(function* (req, res) {
    var experiments = yield container.get('experiments_datamapper').fetchAll();

    res.status(200);
    res.json({ data: { experiments: experiments } });
}));

app.post('/experiments', wrap(function* (req, res) {
    var experiment = new models.ExperimentT(req.body.experiment);

    // Validations
    if (!experiment.name) {
        res.status(400);
        res.json({
            err_code: 'BAD_DATA',
            err_msg : 'Missing: experiment name'
        });
        return;
    }

    // Save it
    var id = yield container.get('experiments_datamapper').insert(experiment);

    // fetch again
    var fetched = yield container.get('experiments_datamapper').fetchById(id);

    res.status(201);
    res.json({ data: { experiment: fetched } });
}));

app.get('/experiments/:id', wrap(function *(req, res, next) {
    var experiment = yield container.get('experiments_datamapper').fetchById(req.params.id);

    if (!experiment) {
        return next();
    }

    res.json({ data: { experiment: experiment } });
}));

app.put('/experiments/:id', wrap(function *(req, res, next) {
    // fetch existing
    var existing = yield container.get('experiments_datamapper').fetchById(req.params.id);

    if (!existing) {
        return next();
    }

    var patch = new models.ExperimentT(req.body.experiment);

    // Validations
    if (!patch.name) {
        res.status(400);
        res.json({
            err_code: 'BAD_DATA',
            err_msg : 'Missing: experiment name'
        });
        return;
    }

    // update
    patch.id = req.params.id;
    yield container.get('experiments_datamapper').update(patch);

    // increase version if required
    if (existing.exposure_percent !== patch.exposure_percent) {
        yield container.get('experiments_datamapper').upgradeVersion(req.params.id);
    }

    // fetch again
    var fetched = yield container.get('experiments_datamapper').fetchById(req.params.id);

    res.json({ data: { experiment: fetched } });
}));

app.get('/experiments/:id/variations', wrap(function *(req, res, next) {
    var experiment = yield container.get('experiments_datamapper').fetchById(req.params.id);

    if (!experiment) {
        return next();
    }

    var variations = yield container.get('variations_datamapper').fetchByExperimentId(req.params.id);
    res.json({ data: { variations: variations } });
}));

app.post('/experiments/:id/variations', wrap(function *(req, res, next) {
    var experiment = yield container.get('experiments_datamapper').fetchById(req.params.id);

    if (!experiment) {
        return next();
    }

    // validations
    var variation = new models.VariationT(req.body.variation);
    variation.experiment_id  = experiment.id;

    if (!variation.name) {
        res.status(400);
        res.json({
            err_code: 'BAD_DATA',
            err_msg : 'Missing: variation name'
        });
        return;
    }

    // create
    var id = yield container.get('variations_datamapper').insert(variation);

    // fetch again
    var fetched = yield container.get('variations_datamapper').fetchById(id);

    res.status(201);
    res.json({ data: { variation: fetched } });
}));

app.put('/experiments/:experiment_id/variations/:variation_id', wrap(function *(req, res, next) {
    var existing = yield container.get('variations_datamapper').fetchById(req.params.variation_id);

    if (!existing) {
        return next();
    }

    if (existing.experiment_id != req.params.experiment_id) {
        return next();
    }

    // validations
    var variation = new models.VariationT(req.body.variation);
    variation.experiment_id  = existing.experiment_id;
    variation.id             = existing.id;

    if (!variation.name) {
        res.status(400);
        res.json({
            err_code: 'BAD_DATA',
            err_msg : 'Missing: variation name'
        });
        return;
    }

    // update
    yield container.get('variations_datamapper').update(variation);

    // fetch again
    var fetched = yield container.get('variations_datamapper').fetchById(req.params.variation_id);

    res.status(200);
    res.json({ data: { variation: fetched } });
}));

function serialize(dict) {
    var set = [];
    for (var key in dict) {
        if (dict.hasOwnProperty(key)) {
            set.push(dict[key]);
        }
    }

    return set
        .map(function (exp) {
            return [
                exp.id,
                exp.version,
                exp.is_usr_participating ? exp.usr_variation.id : 0
            ].join(':');
        })
        .join(',');
}

function unserialize(str) {
    var dict = {};
    str.split(',').forEach(function (expstr) {
        var parts                = expstr.split(':');
        var exp                  = new models.ExperimentT();
        exp.id                   = Number(parts[0]);
        exp.version              = Number(parts[1]);
        exp.is_usr_participating = parts[2] !== '0';
        if (exp.is_usr_participating) {
            exp.usr_variation    = new models.VariationT();
            exp.usr_variation.id = Number(parts[2]);
        }
        dict[parts[0]]           = exp;
    });
    return dict;
}

app.get('/allocate', wrap(function* (req, res) {
    var dict = req.query.current ? unserialize(req.query.current) : {};

    // Get all active experiments
    var experiments = yield container.get('experiments_datamapper').fetchAllActive();

    var tasks = experiments.map(function (exp) {
        return function *() {
            // If user has same version, skip
            if (dict[exp.id] && dict[exp.id].version === exp.version) {
                exp.is_usr_participating = dict[exp.id].is_usr_participating;
                exp.usr_variation        = dict[exp.id].usr_variation;
                return;
            }

            // Decide participation
            var rand                 = Math.random() * 100;
            exp.is_usr_participating = rand - exp.exposure_percent < 0;

            // If participating, allocate bucket
            if (exp.is_usr_participating) {
                // get variations
                exp.variations = yield container.get('variations_datamapper').fetchByExperimentId(exp.id);

                rand = Math.random() * 100;
                var sum = 0;
                for (var i = 0; i < exp.variations.length; ++i) {
                    if (rand - (sum + exp.variations[i].split_percent) < 0) {
                        exp.usr_variation = exp.variations[i];
                        break;
                    }
                    sum += exp.variations[i].split_percent;
                }
            }

            dict[exp.id] = exp;
        };
    });

    yield tasks;
    res.json({ data: { experiments: experiments, serialized : serialize(dict) } });
}));

app.use(function (err, req, res, next) {
    console.error(err.stack);
    res.status(500);
    res.json({
        err_code: 'UNKNOWN',
        err_msg : 'An unknown error occured'
    });
});

app.use(function (req, res) {
    res.status(404);
    res.json({
        err_code: 'NOT_FOUND',
        err_msg : 'Requested resource not found'
    });
});

module.exports = app;
<commit_msg>Enabled cors<commit_after>var express   = require('express');
var bp        = require('body-parser');
var cp        = require('cookie-parser');
var wrap      = require('co-express');
var models    = require('./models');
var container = require('./container');
var app       = express();

app.use(function(req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
    next();
});
app.use(bp.json());
app.use(cp());

app.get('/experiments', wrap(function* (req, res) {
    var experiments = yield container.get('experiments_datamapper').fetchAll();

    res.status(200);
    res.json({ data: { experiments: experiments } });
}));

app.post('/experiments', wrap(function* (req, res) {
    var experiment = new models.ExperimentT(req.body.experiment);

    // Validations
    if (!experiment.name) {
        res.status(400);
        res.json({
            err_code: 'BAD_DATA',
            err_msg : 'Missing: experiment name'
        });
        return;
    }

    // Save it
    var id = yield container.get('experiments_datamapper').insert(experiment);

    // fetch again
    var fetched = yield container.get('experiments_datamapper').fetchById(id);

    res.status(201);
    res.json({ data: { experiment: fetched } });
}));

app.get('/experiments/:id', wrap(function *(req, res, next) {
    var experiment = yield container.get('experiments_datamapper').fetchById(req.params.id);

    if (!experiment) {
        return next();
    }

    res.json({ data: { experiment: experiment } });
}));

app.put('/experiments/:id', wrap(function *(req, res, next) {
    // fetch existing
    var existing = yield container.get('experiments_datamapper').fetchById(req.params.id);

    if (!existing) {
        return next();
    }

    var patch = new models.ExperimentT(req.body.experiment);

    // Validations
    if (!patch.name) {
        res.status(400);
        res.json({
            err_code: 'BAD_DATA',
            err_msg : 'Missing: experiment name'
        });
        return;
    }

    // update
    patch.id = req.params.id;
    yield container.get('experiments_datamapper').update(patch);

    // increase version if required
    if (existing.exposure_percent !== patch.exposure_percent) {
        yield container.get('experiments_datamapper').upgradeVersion(req.params.id);
    }

    // fetch again
    var fetched = yield container.get('experiments_datamapper').fetchById(req.params.id);

    res.json({ data: { experiment: fetched } });
}));

app.get('/experiments/:id/variations', wrap(function *(req, res, next) {
    var experiment = yield container.get('experiments_datamapper').fetchById(req.params.id);

    if (!experiment) {
        return next();
    }

    var variations = yield container.get('variations_datamapper').fetchByExperimentId(req.params.id);
    res.json({ data: { variations: variations } });
}));

app.post('/experiments/:id/variations', wrap(function *(req, res, next) {
    var experiment = yield container.get('experiments_datamapper').fetchById(req.params.id);

    if (!experiment) {
        return next();
    }

    // validations
    var variation = new models.VariationT(req.body.variation);
    variation.experiment_id  = experiment.id;

    if (!variation.name) {
        res.status(400);
        res.json({
            err_code: 'BAD_DATA',
            err_msg : 'Missing: variation name'
        });
        return;
    }

    // create
    var id = yield container.get('variations_datamapper').insert(variation);

    // fetch again
    var fetched = yield container.get('variations_datamapper').fetchById(id);

    res.status(201);
    res.json({ data: { variation: fetched } });
}));

app.put('/experiments/:experiment_id/variations/:variation_id', wrap(function *(req, res, next) {
    var existing = yield container.get('variations_datamapper').fetchById(req.params.variation_id);

    if (!existing) {
        return next();
    }

    if (existing.experiment_id != req.params.experiment_id) {
        return next();
    }

    // validations
    var variation = new models.VariationT(req.body.variation);
    variation.experiment_id  = existing.experiment_id;
    variation.id             = existing.id;

    if (!variation.name) {
        res.status(400);
        res.json({
            err_code: 'BAD_DATA',
            err_msg : 'Missing: variation name'
        });
        return;
    }

    // update
    yield container.get('variations_datamapper').update(variation);

    // fetch again
    var fetched = yield container.get('variations_datamapper').fetchById(req.params.variation_id);

    res.status(200);
    res.json({ data: { variation: fetched } });
}));

function serialize(dict) {
    var set = [];
    for (var key in dict) {
        if (dict.hasOwnProperty(key)) {
            set.push(dict[key]);
        }
    }

    return set
        .map(function (exp) {
            return [
                exp.id,
                exp.version,
                exp.is_usr_participating ? exp.usr_variation.id : 0
            ].join(':');
        })
        .join(',');
}

function unserialize(str) {
    var dict = {};
    str.split(',').forEach(function (expstr) {
        var parts                = expstr.split(':');
        var exp                  = new models.ExperimentT();
        exp.id                   = Number(parts[0]);
        exp.version              = Number(parts[1]);
        exp.is_usr_participating = parts[2] !== '0';
        if (exp.is_usr_participating) {
            exp.usr_variation    = new models.VariationT();
            exp.usr_variation.id = Number(parts[2]);
        }
        dict[parts[0]]           = exp;
    });
    return dict;
}

app.get('/allocate', wrap(function* (req, res) {
    var dict = req.query.current ? unserialize(req.query.current) : {};

    // Get all active experiments
    var experiments = yield container.get('experiments_datamapper').fetchAllActive();

    var tasks = experiments.map(function (exp) {
        return function *() {
            // If user has same version, skip
            if (dict[exp.id] && dict[exp.id].version === exp.version) {
                exp.is_usr_participating = dict[exp.id].is_usr_participating;
                exp.usr_variation        = dict[exp.id].usr_variation;
                return;
            }

            // Decide participation
            var rand                 = Math.random() * 100;
            exp.is_usr_participating = rand - exp.exposure_percent < 0;

            // If participating, allocate bucket
            if (exp.is_usr_participating) {
                // get variations
                exp.variations = yield container.get('variations_datamapper').fetchByExperimentId(exp.id);

                rand = Math.random() * 100;
                var sum = 0;
                for (var i = 0; i < exp.variations.length; ++i) {
                    if (rand - (sum + exp.variations[i].split_percent) < 0) {
                        exp.usr_variation = exp.variations[i];
                        break;
                    }
                    sum += exp.variations[i].split_percent;
                }
            }

            dict[exp.id] = exp;
        };
    });

    yield tasks;
    res.json({ data: { experiments: experiments, serialized : serialize(dict) } });
}));

app.use(function (err, req, res, next) {
    console.error(err.stack);
    res.status(500);
    res.json({
        err_code: 'UNKNOWN',
        err_msg : 'An unknown error occured'
    });
});

app.use(function (req, res) {
    res.status(404);
    res.json({
        err_code: 'NOT_FOUND',
        err_msg : 'Requested resource not found'
    });
});

module.exports = app;
<|endoftext|>