<commit_before>const
  fs = require('fs'),
  path = require('path')

const
  { logError, writeFile } = require('./build.utils'),
  typeRoot = path.resolve(__dirname, '../types'),
  distRoot = path.resolve(__dirname, '../dist/types'),
  resolvePath = file => path.resolve(distRoot, file),
  extraInterfaces = {},
  // eslint-disable-next-line no-useless-escape
  toCamelCase = s => s.replace(/(\-\w)/g, m => { return m[1].toUpperCase() })

function writeLine (fileContent, line = '', indent = 0) {
  fileContent.push(`${line.padStart(line.length + (indent * 4), ' ')}\n`)
}

function write (fileContent, text = '') {
  fileContent.push(`${text}`)
}

const typeMap = new Map([
  ['Array', 'any[]'],
  ['Any', 'any'],
  ['Component', 'Vue'],
  ['String', 'string'],
  ['Boolean', 'boolean'],
  ['Number', 'number']
])

function convertTypeVal (type, def, required) {
  if (def.tsType !== void 0) {
    return def.tsType
  }

  const t = type.trim()

  if (typeMap.has(t)) {
    return typeMap.get(t)
  }

  if (t === 'Object') {
    if (def.definition) {
      const defs = getPropDefinitions(def.definition, required)
      return defs && defs.length > 0 ? `{\n        ${getPropDefinitions(def.definition, required).join('\n        ')} }` : 'any'
    }

    return 'any'
  }

  return t
}

function getTypeVal (def, required) {
  return Array.isArray(def.type)
    ? def.type.map(type => convertTypeVal(type, def, required)).join(' | ')
    : convertTypeVal(def.type, def, required)
}

function getPropDefinition (key, propDef, required) {
  const propName = toCamelCase(key)

  if (!propName.startsWith('...')) {
    const propType = getTypeVal(propDef, required)
    addToExtraInterfaces(propDef)
    return `${propName}${!propDef.required && !required ? '?' : ''} : ${propType}`
  }
}

function getPropDefinitions (propDefs, required) {
  const defs = []

  for (const key in propDefs) {
    const def = getPropDefinition(key, propDefs[key], required)
    def && defs.push(def)
  }

  return defs
}

function getMethodDefinition (key, methodDef, required) {
  let def = ''
  def += `${key} (`
  if (methodDef.params) {
    // TODO: Verify if this should be optional even for plugins
    const params = getPropDefinitions(methodDef.params)
    def += params.join(', ')
  }
  const returns = methodDef.returns
  def += `): ${returns ? getTypeVal(returns, required) : 'void'}`

  return def
}

function getObjectParamDefinition (def, required) {
  let res = []

  Object.keys(def).forEach(propName => {
    const propDef = def[propName]
    if (propDef.type && propDef.type === 'Function') {
      res.push(
        getMethodDefinition(propName, propDef, required)
      )
    }
    else {
      res.push(
        getPropDefinition(propName, propDef, required)
      )
    }
  })

  return res
}

function getInjectionDefinition (injectionName, typeDef) {
  // Get property injection point
  for (var propKey in typeDef.props) {
    const propDef = typeDef.props[propKey]
    if (propDef.tsInjectionPoint) {
      return getPropDefinition(injectionName, propDef, true)
    }
  }

  // Get method injection point
  for (var methodKey in typeDef.methods) {
    const methodDef = typeDef.methods[methodKey]
    if (methodDef.tsInjectionPoint) {
      return getMethodDefinition(injectionName, methodDef, true)
    }
  }
}

function copyPredefinedTypes (dir, parentDir) {
  fs.readdirSync(dir).forEach(file => {
    const fullPath = path.resolve(dir, file)
    const stats = fs.lstatSync(fullPath)
    if (stats.isFile()) {
      writeFile(
        resolvePath(parentDir ? parentDir + file : file),
        fs.readFileSync(fullPath)
      )
    }
    else if (stats.isDirectory()) {
      const p = resolvePath(parentDir ? parentDir + file : file)
      if (!fs.existsSync(p)) {
        fs.mkdirSync(p)
      }
      copyPredefinedTypes(fullPath, parentDir ? parentDir + file : file + '/')
    }
  })
}

function addToExtraInterfaces (def, required) {
  if (
    def !== void 0 &&
    def.tsType !== void 0 &&
    extraInterfaces[def.tsType] === void 0 &&
    def.definition !== void 0
  ) {
    extraInterfaces[def.tsType] = getObjectParamDefinition(
      def.definition, required
    )
  }
}

function writeQuasarPluginProps (contents, nameName, props, isLast) {
  writeLine(contents, `${nameName}: {`, 1)
  props.forEach(prop => writeLine(contents, prop, 2))
  writeLine(contents, `}${isLast ? '' : ','}`, 1)
}

function addQuasarPluginOptions (contents, components, directives, plugins) {
  writeLine(contents, `export interface QuasarPluginOptions {`)
  writeLine(contents, `lang: any,`, 1)
  writeLine(contents, `config: any,`, 1)
  writeLine(contents, `iconSet: any,`, 1)
  writeQuasarPluginProps(contents, 'components', components)
  writeQuasarPluginProps(contents, 'directives', directives)
  writeQuasarPluginProps(contents, 'plugins', plugins, true)
  writeLine(contents, `}`)
  writeLine(contents)
}

function writeIndexDTS (apis) {
  var contents = []
  var quasarTypeContents = []
  var components = []
  var directives = []
  var plugins = []

  writeLine(contents, `import Vue, { VueConstructor, PluginObject } from 'vue'`)
  writeLine(contents)
  writeLine(quasarTypeContents, 'export as namespace quasar')
  writeLine(quasarTypeContents, `export * from './utils'`)

  const injections = {}

  apis.forEach(data => {
    const content = data.api
    const typeName = data.name

    const extendsVue = (content.type === 'component' || content.type === 'mixin')
    const typeValue = `${extendsVue ? `VueConstructor<${typeName}>` : typeName}`
    // Add Type to the appropriate section of types
    const propTypeDef = `${typeName}: ${typeValue}`
    if (content.type === 'component') {
      write(components, propTypeDef)
    }
    else if (content.type === 'directive') {
      write(directives, propTypeDef)
    }
    else if (content.type === 'plugin') {
      write(plugins, propTypeDef)
    }

    // Declare class
    writeLine(quasarTypeContents, `export const ${typeName}: ${extendsVue ? `VueConstructor<${typeName}>` : typeName}`)
    writeLine(contents, `export interface ${typeName} ${extendsVue ? 'extends Vue ' : ''}{`)

    // Write Props
    const props = getPropDefinitions(content.props, content.type === 'plugin')
    props.forEach(prop => writeLine(contents, prop, 1))

    // Write Methods
    for (const methodKey in content.methods) {
      write(contents, `    ${methodKey}(`)
      const method = content.methods[methodKey]
      if (method.params) {
        const params = getPropDefinitions(method.params)
        write(contents, params.join(', '))
      }
      const returns = method.returns
      writeLine(contents, `): ${returns ? getTypeVal(returns, content.type === 'plugin') : 'void'}`)

      addToExtraInterfaces(returns, true)
    }

    // Close class declaration
    writeLine(contents, `}`)
    writeLine(contents)

    // Copy Injections for type declaration
    if (content.type === 'plugin') {
      if (content.injection) {
        const injectionParts = content.injection.split('.')
        if (!injections[injectionParts[0]]) {
          injections[injectionParts[0]] = []
        }
        let def = getInjectionDefinition(injectionParts[1], content)
        if (!def) {
          def = `${injectionParts[1]}: ${typeName}`
        }
        injections[injectionParts[0]].push(def)
      }
    }
  })

  Object.keys(extraInterfaces).forEach(name => {
    writeLine(contents, `export interface ${name} {`)
    extraInterfaces[name].forEach(def => {
      writeLine(contents, def, 1)
    })
    writeLine(contents, `}\n`)
  })

  // Write injection types
  for (const key in injections) {
    const injectionDefs = injections[key]
    if (injectionDefs) {
      writeLine(contents, `export interface ${key.toUpperCase().replace('$', '')}VueGlobals {`)
      for (const defKey in injectionDefs) {
        writeLine(contents, injectionDefs[defKey], 1)
      }
      writeLine(contents, '}')
    }
  }

  writeLine(contents)

  // Extend Vue instance with injections
  if (injections) {
    writeLine(contents, `declare module 'vue/types/vue' {`)
    writeLine(contents, 'interface Vue {', 1)

    for (const key3 in injections) {
      writeLine(contents, `${key3}: ${key3.toUpperCase().replace('$', '')}VueGlobals`, 2)
    }
    writeLine(contents, '}', 1)
    writeLine(contents, '}')
  }

  addQuasarPluginOptions(contents, components, directives, plugins)

  quasarTypeContents.forEach(line => write(contents, line))

  writeLine(contents, `export const Quasar: PluginObject<QuasarPluginOptions>`)
  writeLine(contents)

  writeLine(contents, `import './vue'`)

  writeFile(resolvePath('index.d.ts'), contents.join(''))
}

module.exports.generate = function (data) {
  const apis = data.plugins
    .concat(data.directives)
    .concat(data.components)

  try {
    copyPredefinedTypes(typeRoot)
    writeIndexDTS(apis)
  }
  catch (err) {
    logError(`build.types.js: something went wrong...`)
    console.log()
    console.error(err)
    console.log()
    process.exit(1)
  }
}
<commit_msg>Change Quasar Plugin options to support only passing some options. (#4645)<commit_after>const
  fs = require('fs'),
  path = require('path')

const
  { logError, writeFile } = require('./build.utils'),
  typeRoot = path.resolve(__dirname, '../types'),
  distRoot = path.resolve(__dirname, '../dist/types'),
  resolvePath = file => path.resolve(distRoot, file),
  extraInterfaces = {},
  // eslint-disable-next-line no-useless-escape
  toCamelCase = s => s.replace(/(\-\w)/g, m => { return m[1].toUpperCase() })

function writeLine (fileContent, line = '', indent = 0) {
  fileContent.push(`${line.padStart(line.length + (indent * 4), ' ')}\n`)
}

function write (fileContent, text = '') {
  fileContent.push(`${text}`)
}

const typeMap = new Map([
  ['Array', 'any[]'],
  ['Any', 'any'],
  ['Component', 'Vue'],
  ['String', 'string'],
  ['Boolean', 'boolean'],
  ['Number', 'number']
])

function convertTypeVal (type, def, required) {
  if (def.tsType !== void 0) {
    return def.tsType
  }

  const t = type.trim()

  if (typeMap.has(t)) {
    return typeMap.get(t)
  }

  if (t === 'Object') {
    if (def.definition) {
      const defs = getPropDefinitions(def.definition, required)
      return defs && defs.length > 0 ? `{\n        ${getPropDefinitions(def.definition, required).join('\n        ')} }` : 'any'
    }

    return 'any'
  }

  return t
}

function getTypeVal (def, required) {
  return Array.isArray(def.type)
    ? def.type.map(type => convertTypeVal(type, def, required)).join(' | ')
    : convertTypeVal(def.type, def, required)
}

function getPropDefinition (key, propDef, required) {
  const propName = toCamelCase(key)

  if (!propName.startsWith('...')) {
    const propType = getTypeVal(propDef, required)
    addToExtraInterfaces(propDef)
    return `${propName}${!propDef.required && !required ? '?' : ''} : ${propType}`
  }
}

function getPropDefinitions (propDefs, required) {
  const defs = []

  for (const key in propDefs) {
    const def = getPropDefinition(key, propDefs[key], required)
    def && defs.push(def)
  }

  return defs
}

function getMethodDefinition (key, methodDef, required) {
  let def = ''
  def += `${key} (`
  if (methodDef.params) {
    // TODO: Verify if this should be optional even for plugins
    const params = getPropDefinitions(methodDef.params)
    def += params.join(', ')
  }
  const returns = methodDef.returns
  def += `): ${returns ? getTypeVal(returns, required) : 'void'}`

  return def
}

function getObjectParamDefinition (def, required) {
  let res = []

  Object.keys(def).forEach(propName => {
    const propDef = def[propName]
    if (propDef.type && propDef.type === 'Function') {
      res.push(
        getMethodDefinition(propName, propDef, required)
      )
    }
    else {
      res.push(
        getPropDefinition(propName, propDef, required)
      )
    }
  })

  return res
}

function getInjectionDefinition (injectionName, typeDef) {
  // Get property injection point
  for (var propKey in typeDef.props) {
    const propDef = typeDef.props[propKey]
    if (propDef.tsInjectionPoint) {
      return getPropDefinition(injectionName, propDef, true)
    }
  }

  // Get method injection point
  for (var methodKey in typeDef.methods) {
    const methodDef = typeDef.methods[methodKey]
    if (methodDef.tsInjectionPoint) {
      return getMethodDefinition(injectionName, methodDef, true)
    }
  }
}

function copyPredefinedTypes (dir, parentDir) {
  fs.readdirSync(dir).forEach(file => {
    const fullPath = path.resolve(dir, file)
    const stats = fs.lstatSync(fullPath)
    if (stats.isFile()) {
      writeFile(
        resolvePath(parentDir ? parentDir + file : file),
        fs.readFileSync(fullPath)
      )
    }
    else if (stats.isDirectory()) {
      const p = resolvePath(parentDir ? parentDir + file : file)
      if (!fs.existsSync(p)) {
        fs.mkdirSync(p)
      }
      copyPredefinedTypes(fullPath, parentDir ? parentDir + file : file + '/')
    }
  })
}

function addToExtraInterfaces (def, required) {
  if (
    def !== void 0 &&
    def.tsType !== void 0 &&
    extraInterfaces[def.tsType] === void 0 &&
    def.definition !== void 0
  ) {
    extraInterfaces[def.tsType] = getObjectParamDefinition(
      def.definition, required
    )
  }
}

function writeQuasarPluginProps (contents, nameName, props, isLast) {
  writeLine(contents, `${nameName}: {`, 1)
  props.forEach(prop => writeLine(contents, prop, 2))
  writeLine(contents, `}${isLast ? '' : ','}`, 1)
}

function addQuasarPluginOptions (contents, components, directives, plugins) {
  writeLine(contents, `export interface QuasarPluginOptions {`)
  writeLine(contents, `lang: any,`, 1)
  writeLine(contents, `config: any,`, 1)
  writeLine(contents, `iconSet: any,`, 1)
  writeQuasarPluginProps(contents, 'components', components)
  writeQuasarPluginProps(contents, 'directives', directives)
  writeQuasarPluginProps(contents, 'plugins', plugins, true)
  writeLine(contents, `}`)
  writeLine(contents)
}

function writeIndexDTS (apis) {
  var contents = []
  var quasarTypeContents = []
  var components = []
  var directives = []
  var plugins = []

  writeLine(contents, `import Vue, { VueConstructor, PluginObject } from 'vue'`)
  writeLine(contents)
  writeLine(quasarTypeContents, 'export as namespace quasar')
  writeLine(quasarTypeContents, `export * from './utils'`)

  const injections = {}

  apis.forEach(data => {
    const content = data.api
    const typeName = data.name

    const extendsVue = (content.type === 'component' || content.type === 'mixin')
    const typeValue = `${extendsVue ? `VueConstructor<${typeName}>` : typeName}`
    // Add Type to the appropriate section of types
    const propTypeDef = `${typeName}?: ${typeValue}`
    if (content.type === 'component') {
      write(components, propTypeDef)
    }
    else if (content.type === 'directive') {
      write(directives, propTypeDef)
    }
    else if (content.type === 'plugin') {
      write(plugins, propTypeDef)
    }

    // Declare class
    writeLine(quasarTypeContents, `export const ${typeName}: ${extendsVue ? `VueConstructor<${typeName}>` : typeName}`)
    writeLine(contents, `export interface ${typeName} ${extendsVue ? 'extends Vue ' : ''}{`)

    // Write Props
    const props = getPropDefinitions(content.props, content.type === 'plugin')
    props.forEach(prop => writeLine(contents, prop, 1))

    // Write Methods
    for (const methodKey in content.methods) {
      write(contents, `    ${methodKey}(`)
      const method = content.methods[methodKey]
      if (method.params) {
        const params = getPropDefinitions(method.params)
        write(contents, params.join(', '))
      }
      const returns = method.returns
      writeLine(contents, `): ${returns ? getTypeVal(returns, content.type === 'plugin') : 'void'}`)

      addToExtraInterfaces(returns, true)
    }

    // Close class declaration
    writeLine(contents, `}`)
    writeLine(contents)

    // Copy Injections for type declaration
    if (content.type === 'plugin') {
      if (content.injection) {
        const injectionParts = content.injection.split('.')
        if (!injections[injectionParts[0]]) {
          injections[injectionParts[0]] = []
        }
        let def = getInjectionDefinition(injectionParts[1], content)
        if (!def) {
          def = `${injectionParts[1]}: ${typeName}`
        }
        injections[injectionParts[0]].push(def)
      }
    }
  })

  Object.keys(extraInterfaces).forEach(name => {
    writeLine(contents, `export interface ${name} {`)
    extraInterfaces[name].forEach(def => {
      writeLine(contents, def, 1)
    })
    writeLine(contents, `}\n`)
  })

  // Write injection types
  for (const key in injections) {
    const injectionDefs = injections[key]
    if (injectionDefs) {
      writeLine(contents, `export interface ${key.toUpperCase().replace('$', '')}VueGlobals {`)
      for (const defKey in injectionDefs) {
        writeLine(contents, injectionDefs[defKey], 1)
      }
      writeLine(contents, '}')
    }
  }

  writeLine(contents)

  // Extend Vue instance with injections
  if (injections) {
    writeLine(contents, `declare module 'vue/types/vue' {`)
    writeLine(contents, 'interface Vue {', 1)

    for (const key3 in injections) {
      writeLine(contents, `${key3}: ${key3.toUpperCase().replace('$', '')}VueGlobals`, 2)
    }
    writeLine(contents, '}', 1)
    writeLine(contents, '}')
  }

  addQuasarPluginOptions(contents, components, directives, plugins)

  quasarTypeContents.forEach(line => write(contents, line))

  writeLine(contents, `export const Quasar: PluginObject<Partial<QuasarPluginOptions>>`)
  writeLine(contents)

  writeLine(contents, `import './vue'`)

  writeFile(resolvePath('index.d.ts'), contents.join(''))
}

module.exports.generate = function (data) {
  const apis = data.plugins
    .concat(data.directives)
    .concat(data.components)

  try {
    copyPredefinedTypes(typeRoot)
    writeIndexDTS(apis)
  }
  catch (err) {
    logError(`build.types.js: something went wrong...`)
    console.log()
    console.error(err)
    console.log()
    process.exit(1)
  }
}
<|endoftext|>


<commit_before>/*
 * jQuery UI Effects Highlight @VERSION
 *
 * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Effects/Highlight
 *
 * Depends:
 *	effects.core.js
 */
(function($) {

$.effects.highlight = function(o) {

	return this.queue(function() {

		// Create element
		var el = $(this), props = ['backgroundImage','backgroundColor','opacity'];

		// Set options
		var mode = $.effects.setMode(el, o.options.mode || 'show'); // Set Mode
		var color = o.options.color || "#ffff99"; // Default highlight color
		var oldColor = el.css("backgroundColor");

		// Adjust
		$.effects.save(el, props); el.show(); // Save & Show
		el.css({backgroundImage: 'none', backgroundColor: color}); // Shift

		// Animation
		var animation = {backgroundColor: oldColor };
		if (mode == "hide") animation['opacity'] = 0;

		// Animate
		el.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() {
			if(mode == "hide") el.hide();
			$.effects.restore(el, props);
			if (mode == "show" && !$.support.opacity) this.style.removeAttribute('filter');
			if(o.callback) o.callback.apply(this, arguments);
			el.dequeue();
		}});

	});

};

})(jQuery);
<commit_msg>Highlight effect: refactor.<commit_after>/*
 * jQuery UI Effects Highlight @VERSION
 *
 * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Effects/Highlight
 *
 * Depends:
 *	effects.core.js
 */
(function($) {

$.effects.highlight = function(o) {
	return this.queue(function() {
		var elem = $(this),
			props = ['backgroundImage', 'backgroundColor', 'opacity'],
			mode = $.effects.setMode(elem, o.options.mode || 'show'),
			animation = {
				backgroundColor: elem.css('backgroundColor')
			};

		if (mode == 'hide') {
			animation.opacity = 0;
		}

		$.effects.save(elem, props);
		elem
			.show()
			.css({
				backgroundImage: 'none',
				backgroundColor: o.options.color || '#ffff99'
			})
			.animate(animation, {
				queue: false,
				duration: o.duration,
				easing: o.options.easing,
				complete: function() {
					(mode == 'hide' && elem.hide());
					$.effects.restore(elem, props);
					(mode == 'show' && !$.support.opacity && this.style.removeAttribute('filter'));
					(o.callback && o.callback.apply(this, arguments));
					elem.dequeue();
				}
			});
	});
};

})(jQuery);
<|endoftext|>


<commit_before>/*!
 * jQuery UI Tooltip @VERSION
 *
 * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.widget.js
 *	jquery.ui.position.js
 */
(function( $ ) {

var increments = 0;

function addDescribedBy( elem, id ) {
	var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
	describedby.push( id );
	elem
		.data( "ui-tooltip-id", id )
		.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
}

function removeDescribedBy( elem ) {
	var id = elem.data( "ui-tooltip-id" ),
		describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
		index = $.inArray( id, describedby );
	if ( index !== -1 ) {
		describedby.splice( index, 1 );
	}

	elem.removeData( "ui-tooltip-id" );
	describedby = $.trim( describedby.join( " " ) );
	if ( describedby ) {
		elem.attr( "aria-describedby", describedby );
	} else {
		elem.removeAttr( "aria-describedby" );
	}
}

$.widget( "ui.tooltip", {
	version: "@VERSION",
	options: {
		content: function() {
			return $( this ).attr( "title" );
		},
		hide: true,
		items: "[title]",
		position: {
			my: "left+15 center",
			at: "right center",
			collision: "flipfit flipfit"
		},
		show: true,
		tooltipClass: null,
		track: false,

		// callbacks
		close: null,
		open: null
	},

	_create: function() {
		this._on({
			mouseover: "open",
			focusin: "open"
		});

		// IDs of generated tooltips, needed for destroy
		this.tooltips = {};
	},

	_setOption: function( key, value ) {
		if ( key === "disabled" ) {
			this[ value ? "_disable" : "_enable" ]();
			this.options[ key ] = value;
			// disable element style changes
			return;
		}
		this._super( key, value );
	},

	_disable: function() {
		var that = this;

		// close open tooltips
		$.each( this.tooltips, function( id, element ) {
			var event = $.Event( "blur" );
			event.target = event.currentTarget = element[0];
			that.close( event, true );
		});

		// remove title attributes to prevent native tooltips
		this.element.find( this.options.items ).andSelf().each(function() {
			var element = $( this );
			if ( element.is( "[title]" ) ) {
				element
					.data( "ui-tooltip-title", element.attr( "title" ) )
					.attr( "title", "" );
			}
		});
	},

	_enable: function() {
		// restore title attributes
		this.element.find( this.options.items ).andSelf().each(function() {
			var element = $( this );
			if ( element.data( "ui-tooltip-title" ) ) {
				element.attr( "title", element.data( "ui-tooltip-title" ) );
			}
		});
	},

	open: function( event ) {
		var content,
			that = this,
			target = $( event ? event.target : this.element )
				.closest( this.options.items );

		// if ui-tooltip-id exists, then the tooltip is already open
		if ( !target.length || target.data( "ui-tooltip-id" ) ) {
			return;
		}

		if ( target.attr( "title" ) ) {
			target.data( "ui-tooltip-title", target.attr( "title" ) );
		}

		target.data( "tooltip-open", true );

		content = this.options.content.call( target[0], function( response ) {
			// ignore async response if tooltip was closed already
			if ( !target.data( "tooltip-open" ) ) {
				return;
			}
			// IE may instantly serve a cached response for ajax requests
			// delay this call to _open so the other call to _open runs first
			setTimeout(function() {
				that._open( event, target, response );
			}, 1 );
		});
		if ( content ) {
			that._open( event, target, content );
		}
	},

	_open: function( event, target, content ) {
		var tooltip, positionOption;
		if ( !content ) {
			return;
		}

		// Content can be updated multiple times. If the tooltip already
		// exists, then just update the content and bail.
		tooltip = this._find( target );
		if ( tooltip.length ) {
			tooltip.find( ".ui-tooltip-content" ).html( content );
			return;
		}

		// if we have a title, clear it to prevent the native tooltip
		// we have to check first to avoid defining a title if none exists
		// (we don't want to cause an element to start matching [title])
		//
		// We use removeAttr only for key events, to allow IE to export the correct
		// accessible attributes. For mouse events, set to empty string to avoid
		// native tooltip showing up (happens only when removing inside mouseover).
		if ( target.is( "[title]" ) ) {
			if ( event && event.type === "mouseover" ) {
				target.attr( "title", "" );
			} else {
				target.removeAttr( "title" );
			}
		}

		tooltip = this._tooltip( target );
		addDescribedBy( target, tooltip.attr( "id" ) );
		tooltip.find( ".ui-tooltip-content" ).html( content );

		function position( event ) {
			positionOption.of = event;
			tooltip.position( positionOption );
		}
		if ( this.options.track && /^mouse/.test( event.originalEvent.type ) ) {
			positionOption = $.extend( {}, this.options.position );
			this._on( this.document, {
				mousemove: position
			});
			// trigger once to override element-relative positioning
			position( event );
		} else {
			tooltip.position( $.extend({
				of: target
			}, this.options.position ) );
		}

		tooltip.hide();

		this._show( tooltip, this.options.show );

		this._trigger( "open", event, { tooltip: tooltip } );

		this._on( target, {
			mouseleave: "close",
			focusout: "close",
			keyup: function( event ) {
				if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
					var fakeEvent = $.Event(event);
					fakeEvent.currentTarget = target[0];
					this.close( fakeEvent, true );
				}
			}
		});
	},

	close: function( event, force ) {
		var that = this,
			target = $( event ? event.currentTarget : this.element ),
			tooltip = this._find( target );

		// disabling closes the tooltip, so we need to track when we're closing
		// to avoid an infinite loop in case the tooltip becomes disabled on close
		if ( this.closing ) {
			return;
		}

		// don't close if the element has focus
		// this prevents the tooltip from closing if you hover while focused
		//
		// we have to check the event type because tabbing out of the document
		// may leave the element as the activeElement
		if ( !force && event && event.type !== "focusout" &&
				this.document[0].activeElement === target[0] ) {
			return;
		}

		// only set title if we had one before (see comment in _open())
		if ( target.data( "ui-tooltip-title" ) ) {
			target.attr( "title", target.data( "ui-tooltip-title" ) );
		}

		removeDescribedBy( target );

		tooltip.stop( true );
		this._hide( tooltip, this.options.hide, function() {
			$( this ).remove();
			delete that.tooltips[ this.id ];
		});

		target.removeData( "tooltip-open" );
		this._off( target, "mouseleave focusout keyup" );

		// TODO use _off
		this.document.unbind( "mousemove.tooltip" );

		this.closing = true;
		this._trigger( "close", event, { tooltip: tooltip } );
		this.closing = false;
	},

	_tooltip: function( element ) {
		var id = "ui-tooltip-" + increments++,
			tooltip = $( "<div>" )
				.attr({
					id: id,
					role: "tooltip"
				})
				.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
					( this.options.tooltipClass || "" ) );
		$( "<div>" )
			.addClass( "ui-tooltip-content" )
			.appendTo( tooltip );
		tooltip.appendTo( this.document[0].body );
		if ( $.fn.bgiframe ) {
			tooltip.bgiframe();
		}
		this.tooltips[ id ] = element;
		return tooltip;
	},

	_find: function( target ) {
		var id = target.data( "ui-tooltip-id" );
		return id ? $( "#" + id ) : $();
	},

	_destroy: function() {
		$.each( this.tooltips, function( id ) {
			$( "#" + id ).remove();
		});
	}
});

}( jQuery ) );
<commit_msg>Tooltip: Reposition tracking tooltips when they gain focus.<commit_after>/*!
 * jQuery UI Tooltip @VERSION
 *
 * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.widget.js
 *	jquery.ui.position.js
 */
(function( $ ) {

var increments = 0;

function addDescribedBy( elem, id ) {
	var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
	describedby.push( id );
	elem
		.data( "ui-tooltip-id", id )
		.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
}

function removeDescribedBy( elem ) {
	var id = elem.data( "ui-tooltip-id" ),
		describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
		index = $.inArray( id, describedby );
	if ( index !== -1 ) {
		describedby.splice( index, 1 );
	}

	elem.removeData( "ui-tooltip-id" );
	describedby = $.trim( describedby.join( " " ) );
	if ( describedby ) {
		elem.attr( "aria-describedby", describedby );
	} else {
		elem.removeAttr( "aria-describedby" );
	}
}

$.widget( "ui.tooltip", {
	version: "@VERSION",
	options: {
		content: function() {
			return $( this ).attr( "title" );
		},
		hide: true,
		items: "[title]",
		position: {
			my: "left+15 center",
			at: "right center",
			collision: "flipfit flipfit"
		},
		show: true,
		tooltipClass: null,
		track: false,

		// callbacks
		close: null,
		open: null
	},

	_create: function() {
		this._on({
			mouseover: "open",
			focusin: "open"
		});

		// IDs of generated tooltips, needed for destroy
		this.tooltips = {};
	},

	_setOption: function( key, value ) {
		if ( key === "disabled" ) {
			this[ value ? "_disable" : "_enable" ]();
			this.options[ key ] = value;
			// disable element style changes
			return;
		}
		this._super( key, value );
	},

	_disable: function() {
		var that = this;

		// close open tooltips
		$.each( this.tooltips, function( id, element ) {
			var event = $.Event( "blur" );
			event.target = event.currentTarget = element[0];
			that.close( event, true );
		});

		// remove title attributes to prevent native tooltips
		this.element.find( this.options.items ).andSelf().each(function() {
			var element = $( this );
			if ( element.is( "[title]" ) ) {
				element
					.data( "ui-tooltip-title", element.attr( "title" ) )
					.attr( "title", "" );
			}
		});
	},

	_enable: function() {
		// restore title attributes
		this.element.find( this.options.items ).andSelf().each(function() {
			var element = $( this );
			if ( element.data( "ui-tooltip-title" ) ) {
				element.attr( "title", element.data( "ui-tooltip-title" ) );
			}
		});
	},

	open: function( event ) {
		var content,
			that = this,
			target = $( event ? event.target : this.element )
				.closest( this.options.items );

		// No element to show a tooltip for
		if ( !target.length ) {
			return;
		}

		// If the tooltip is open and we're tracking then reposition the tooltip.
		// This makes sure that a tracking tooltip doesn't obscure a focused element
		// if the user was hovering when the element gained focused.
		if ( this.options.track && target.data( "ui-tooltip-id" ) ) {
			this._find( target ).position( $.extend({
				of: target
			}, this.options.position ) );
			return;
		}

		if ( target.attr( "title" ) ) {
			target.data( "ui-tooltip-title", target.attr( "title" ) );
		}

		target.data( "tooltip-open", true );

		content = this.options.content.call( target[0], function( response ) {
			// ignore async response if tooltip was closed already
			if ( !target.data( "tooltip-open" ) ) {
				return;
			}
			// IE may instantly serve a cached response for ajax requests
			// delay this call to _open so the other call to _open runs first
			setTimeout(function() {
				that._open( event, target, response );
			}, 1 );
		});
		if ( content ) {
			that._open( event, target, content );
		}
	},

	_open: function( event, target, content ) {
		var tooltip, positionOption;
		if ( !content ) {
			return;
		}

		// Content can be updated multiple times. If the tooltip already
		// exists, then just update the content and bail.
		tooltip = this._find( target );
		if ( tooltip.length ) {
			tooltip.find( ".ui-tooltip-content" ).html( content );
			return;
		}

		// if we have a title, clear it to prevent the native tooltip
		// we have to check first to avoid defining a title if none exists
		// (we don't want to cause an element to start matching [title])
		//
		// We use removeAttr only for key events, to allow IE to export the correct
		// accessible attributes. For mouse events, set to empty string to avoid
		// native tooltip showing up (happens only when removing inside mouseover).
		if ( target.is( "[title]" ) ) {
			if ( event && event.type === "mouseover" ) {
				target.attr( "title", "" );
			} else {
				target.removeAttr( "title" );
			}
		}

		tooltip = this._tooltip( target );
		addDescribedBy( target, tooltip.attr( "id" ) );
		tooltip.find( ".ui-tooltip-content" ).html( content );

		function position( event ) {
			positionOption.of = event;
			tooltip.position( positionOption );
		}
		if ( this.options.track && /^mouse/.test( event.originalEvent.type ) ) {
			positionOption = $.extend( {}, this.options.position );
			this._on( this.document, {
				mousemove: position
			});
			// trigger once to override element-relative positioning
			position( event );
		} else {
			tooltip.position( $.extend({
				of: target
			}, this.options.position ) );
		}

		tooltip.hide();

		this._show( tooltip, this.options.show );

		this._trigger( "open", event, { tooltip: tooltip } );

		this._on( target, {
			mouseleave: "close",
			focusout: "close",
			keyup: function( event ) {
				if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
					var fakeEvent = $.Event(event);
					fakeEvent.currentTarget = target[0];
					this.close( fakeEvent, true );
				}
			}
		});
	},

	close: function( event, force ) {
		var that = this,
			target = $( event ? event.currentTarget : this.element ),
			tooltip = this._find( target );

		// disabling closes the tooltip, so we need to track when we're closing
		// to avoid an infinite loop in case the tooltip becomes disabled on close
		if ( this.closing ) {
			return;
		}

		// don't close if the element has focus
		// this prevents the tooltip from closing if you hover while focused
		//
		// we have to check the event type because tabbing out of the document
		// may leave the element as the activeElement
		if ( !force && event && event.type !== "focusout" &&
				this.document[0].activeElement === target[0] ) {
			return;
		}

		// only set title if we had one before (see comment in _open())
		if ( target.data( "ui-tooltip-title" ) ) {
			target.attr( "title", target.data( "ui-tooltip-title" ) );
		}

		removeDescribedBy( target );

		tooltip.stop( true );
		this._hide( tooltip, this.options.hide, function() {
			$( this ).remove();
			delete that.tooltips[ this.id ];
		});

		target.removeData( "tooltip-open" );
		this._off( target, "mouseleave focusout keyup" );
		this._off( this.document, "mousemove" );

		this.closing = true;
		this._trigger( "close", event, { tooltip: tooltip } );
		this.closing = false;
	},

	_tooltip: function( element ) {
		var id = "ui-tooltip-" + increments++,
			tooltip = $( "<div>" )
				.attr({
					id: id,
					role: "tooltip"
				})
				.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
					( this.options.tooltipClass || "" ) );
		$( "<div>" )
			.addClass( "ui-tooltip-content" )
			.appendTo( tooltip );
		tooltip.appendTo( this.document[0].body );
		if ( $.fn.bgiframe ) {
			tooltip.bgiframe();
		}
		this.tooltips[ id ] = element;
		return tooltip;
	},

	_find: function( target ) {
		var id = target.data( "ui-tooltip-id" );
		return id ? $( "#" + id ) : $();
	},

	_destroy: function() {
		$.each( this.tooltips, function( id ) {
			$( "#" + id ).remove();
		});
	}
});

}( jQuery ) );
<|endoftext|>


<commit_before>import { combineReducers } from 'redux'
import { reducer as formReducer, SubmissionError } from 'redux-form'
import { reducer as searchReducer } from 'redux-search'
import hash from 'object-hash'

import { reducers as dashboardReducers } from 'pages/Dashboard/reducers'
import { reducers as projectReducers } from 'pages/Project/reducers'
import { reducers as searchReducers } from 'pages/Search/reducers'
import { reducers as staffReducers } from 'pages/Staff/reducers'
import { HttpRequestHelper } from 'shared/utils/httpRequestHelper'
import { SHOW_ALL, SORT_BY_FAMILY_GUID } from 'shared/utils/constants'
import {
  createObjectsByIdReducer, loadingReducer, zeroActionsReducer, createSingleObjectReducer, createSingleValueReducer,
} from './utils/reducerFactories'
import modalReducers from './utils/modalReducer'

/**
 * Action creator and reducers in one file as suggested by https://github.com/erikras/ducks-modular-redux
 */

// actions
export const RECEIVE_DATA = 'RECEIVE_DATA'
export const REQUEST_PROJECTS = 'REQUEST_PROJECTS'
export const RECEIVE_SAVED_SEARCHES = 'RECEIVE_SAVED_SEARCHES'
export const REQUEST_SAVED_SEARCHES = 'REQUEST_SAVED_SEARCHES'
const REQUEST_SAVED_VARIANTS = 'REQUEST_SAVED_VARIANTS'
const RECEIVE_SAVED_VARIANT_FAMILIES = 'RECEIVE_SAVED_VARIANT_FAMILIES'
const REQUEST_GENES = 'REQUEST_GENES'
const REQUEST_GENE_LISTS = 'REQUEST_GENE_LISTS'
const REQUEST_GENE_LIST = 'REQUEST_GENE_LIST'
const UPDATE_SAVED_VARIANT_TABLE_STATE = 'UPDATE_VARIANT_STATE'
const UPDATE_IGV_VISIBILITY = 'UPDATE_IGV_VISIBILITY'
const REQUEST_USERS = 'REQUEST_USERS'
const RECEIVE_USERS = 'RECEIVE_USERS'

// action creators

// A helper action that handles create, update and delete requests
export const updateEntity = (values, receiveDataAction, urlPath, idField, actionSuffix, getUrlPath) => {
  return (dispatch, getState) => {
    if (getUrlPath) {
      urlPath = getUrlPath(getState())
    }

    let action = 'create'
    if (values[idField]) {
      urlPath = `${urlPath}/${values[idField]}`
      action = values.delete ? 'delete' : 'update'
    }

    return new HttpRequestHelper(`${urlPath}/${action}${actionSuffix || ''}`,
      (responseJson) => {
        dispatch({ type: receiveDataAction, updatesById: responseJson })
      },
      (e) => {
        throw new SubmissionError({ _error: [e.message] })
      },
    ).post(values)
  }
}

export const fetchProjects = () => {
  return (dispatch) => {
    dispatch({ type: REQUEST_PROJECTS })
    new HttpRequestHelper('/api/dashboard',
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
      },
      e => dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} }),
    ).get()
  }
}

export const loadUserOptions = () => {
  return (dispatch) => {
    dispatch({ type: REQUEST_USERS })
    new HttpRequestHelper('/api/users/get_all',
      (responseJson) => {
        dispatch({ type: RECEIVE_USERS, newValue: responseJson })
      },
      (e) => {
        dispatch({ type: RECEIVE_USERS, error: e.message, newValue: [] })
      },
    ).get()
  }
}

export const loadStaffOptions = () => {
  return (dispatch) => {
    dispatch({ type: REQUEST_USERS })
    new HttpRequestHelper('/api/users/get_all_staff',
      (responseJson) => {
        dispatch({ type: RECEIVE_USERS, newValue: responseJson })
      },
      (e) => {
        dispatch({ type: RECEIVE_USERS, error: e.message, newValue: [] })
      },
    ).get()
  }
}

/**
 * POSTS a request to update the specified project and dispatches the appropriate events when the request finishes
 * Accepts a values object that includes any data to be posted as well as the following keys:
 *
 * action: A string representation of the action to perform. Can be "create", "update" or "delete". Defaults to "update"
 * projectGuid: The GUID for the project to update. If omitted, the action will be set to "create"
 * projectField: A specific field to update (e.g. "categories"). Should be used for fields which have special server-side logic for updating
 */
export const updateProject = (values) => {
  const actionSuffix = values.projectField ? `_project_${values.projectField}` : '_project'
  return updateEntity(values, RECEIVE_DATA, '/api/project', 'projectGuid', actionSuffix)
}

export const updateFamily = (values) => {
  return (dispatch) => {
    const familyField = values.familyField ? `_${values.familyField}` : ''
    return new HttpRequestHelper(`/api/family/${values.familyGuid}/update${familyField}`,
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: { familiesByGuid: responseJson } })
      },
      (e) => {
        throw new SubmissionError({ _error: [e.message] })
      },
    ).post(values)
  }
}

export const updateIndividual = (values) => {
  return (dispatch) => {
    return new HttpRequestHelper(`/api/individual/${values.individualGuid}/update`,
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: { individualsByGuid: responseJson } })
      },
      (e) => {
        throw new SubmissionError({ _error: [e.message] })
      },
    ).post(values)
  }
}

export const loadGene = (geneId) => {
  return (dispatch, getState) => {
    const gene = getState().genesById[geneId]
    if (!gene || !gene.notes || !gene.expression) {
      dispatch({ type: REQUEST_GENES })
      new HttpRequestHelper(`/api/gene_info/${geneId}`,
        (responseJson) => {
          dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
        },
        (e) => {
          dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
        },
      ).get()
    }
  }
}

export const loadGenes = (geneIds) => {
  return (dispatch, getState) => {
    const state = getState()
    if ([...geneIds].some(geneId => !state.genesById[geneId])) {
      dispatch({ type: REQUEST_GENES })
      new HttpRequestHelper('/api/genes_info',
        (responseJson) => {
          dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
        },
        (e) => {
          dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
        },
      ).get({ geneIds: [...geneIds] })
    }
  }
}

export const loadLocusLists = () => {
  return (dispatch) => {
    dispatch({ type: REQUEST_GENE_LISTS })
    new HttpRequestHelper('/api/locus_lists',
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
      },
      (e) => {
        dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
      },
    ).get()
  }
}

export const loadLocusListItems = (locusListId) => {
  return (dispatch, getState) => {
    const locusList = getState().locusListsByGuid[locusListId]
    if (locusListId && !(locusList && locusList.items)) {
      dispatch({ type: REQUEST_GENE_LIST })
      new HttpRequestHelper(`/api/locus_lists/${locusListId}`,
        (responseJson) => {
          dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
        },
        (e) => {
          const updates = { locusListsByGuid: { [locusListId]: { items: [] } } }
          dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: updates })
        },
      ).get()
    }
  }
}

export const updateGeneNote = (values) => {
  return updateEntity(values, RECEIVE_DATA, `/api/gene_info/${values.geneId || values.gene_id}/note`, 'noteGuid')
}

export const navigateSavedHashedSearch = (search, navigateSearch) => {
  return (dispatch) => {
    const searchHash = hash.MD5(search)
    dispatch({ type: RECEIVE_SAVED_SEARCHES, updatesById: { searchesByHash: { [searchHash]: search } } })
    navigateSearch(`/variant_search/results/${searchHash}`)
  }
}

export const loadSavedVariants = (familyGuids, variantGuid, tag) => {
  return (dispatch, getState) => {
    const state = getState()
    const projectGuid = state.currentProjectGuid

    let url = projectGuid ? `/api/project/${projectGuid}/saved_variants` : `/api/staff/saved_variants/${tag}`

    // Do not load if already loaded
    let expectedFamilyGuids
    if (variantGuid) {
      if (state.savedVariantsByGuid[variantGuid]) {
        return
      }
      url = `${url}/${variantGuid}`
    } else if (projectGuid) {
      expectedFamilyGuids = familyGuids
      if (!expectedFamilyGuids) {
        expectedFamilyGuids = Object.values(state.familiesByGuid).filter(
          family => family.projectGuid === projectGuid).map(({ familyGuid }) => familyGuid)
      }
      if (expectedFamilyGuids.length > 0 && expectedFamilyGuids.every(family => state.savedVariantFamilies[family])) {
        return
      }
    } else if (!tag) {
      return
    }

    dispatch({ type: REQUEST_SAVED_VARIANTS })
    new HttpRequestHelper(url,
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
        if (expectedFamilyGuids) {
          dispatch({
            type: RECEIVE_SAVED_VARIANT_FAMILIES,
            updates: expectedFamilyGuids.reduce((acc, family) => ({ ...acc, [family]: true }), {}),
          })
        }
      },
      (e) => {
        dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
      },
    ).get(familyGuids ? { families: familyGuids.join(',') } : {})
  }
}

const updateSavedVariant = (values, action = 'create') => {
  return (dispatch, getState) => {
    return new HttpRequestHelper(`/api/saved_variant/${action}`,
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
      },
      (e) => {
        throw new SubmissionError({ _error: [e.message] })
      },
    ).post({ searchHash: getState().currentSearchHash, ...values })
  }
}

export const updateVariantNote = (values) => {
  if (values.variantGuid) {
    return updateEntity(values, RECEIVE_DATA, `/api/saved_variant/${values.variantGuid}/note`, 'noteGuid')
  }
  return updateSavedVariant(values)
}

export const updateVariantTags = (values) => {
  const urlPath = values.variantGuid ? `${values.variantGuid}/update_tags` : 'create'
  return updateSavedVariant(values, urlPath)
}

export const updateLocusList = (values) => {
  return (dispatch) => {
    let action = 'create'
    if (values.locusListGuid) {
      action = `${values.locusListGuid}/${values.delete ? 'delete' : 'update'}`
    }

    return new HttpRequestHelper(`/api/locus_lists/${action}`,
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
      },
      (e) => {
        let error = e.message.replace('(400)', '')
        if (e.body && e.body.invalidLocusListItems) {
          error = `${error} Invalid genes/ intervals: ${e.body.invalidLocusListItems.join(', ')}`
        }
        throw new SubmissionError({
          _error: [error],
        })
      },
    ).post(values)
  }
}

export const updateSavedVariantTable = updates => ({ type: UPDATE_SAVED_VARIANT_TABLE_STATE, updates })
export const updateIgvReadsVisibility = updates => ({ type: UPDATE_IGV_VISIBILITY, updates })

// root reducer
const rootReducer = combineReducers(Object.assign({
  projectCategoriesByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'projectCategoriesByGuid'),
  projectsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'projectsByGuid'),
  projectsLoading: loadingReducer(REQUEST_PROJECTS, RECEIVE_DATA),
  familiesByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'familiesByGuid'),
  individualsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'individualsByGuid'),
  samplesByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'samplesByGuid'),
  analysisGroupsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'analysisGroupsByGuid'),
  mmeResultsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'mmeResultsByGuid'),
  genesById: createObjectsByIdReducer(RECEIVE_DATA, 'genesById'),
  genesLoading: loadingReducer(REQUEST_GENES, RECEIVE_DATA),
  locusListsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'locusListsByGuid'),
  locusListsLoading: loadingReducer(REQUEST_GENE_LISTS, RECEIVE_DATA),
  locusListLoading: loadingReducer(REQUEST_GENE_LIST, RECEIVE_DATA),
  savedVariantsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'savedVariantsByGuid'),
  savedVariantsLoading: loadingReducer(REQUEST_SAVED_VARIANTS, RECEIVE_DATA),
  searchesByHash: createObjectsByIdReducer(RECEIVE_SAVED_SEARCHES, 'searchesByHash'),
  savedSearchesByGuid: createObjectsByIdReducer(RECEIVE_SAVED_SEARCHES, 'savedSearchesByGuid'),
  savedVariantFamilies: createSingleObjectReducer(RECEIVE_SAVED_VARIANT_FAMILIES),
  savedSearchesLoading: loadingReducer(REQUEST_SAVED_SEARCHES, RECEIVE_SAVED_SEARCHES),
  user: zeroActionsReducer,
  newUser: zeroActionsReducer,
  usersByUsername: createSingleValueReducer(RECEIVE_USERS, {}),
  userOptionsLoading: loadingReducer(REQUEST_USERS, RECEIVE_USERS),
  form: formReducer,
  search: searchReducer,
  savedVariantTableState: createSingleObjectReducer(UPDATE_SAVED_VARIANT_TABLE_STATE, {
    hideExcluded: false,
    hideReviewOnly: false,
    categoryFilter: SHOW_ALL,
    sort: SORT_BY_FAMILY_GUID,
    page: 1,
    recordsPerPage: 25,
  }, false),
  igvReadsVisibility: createSingleObjectReducer(UPDATE_IGV_VISIBILITY),
}, modalReducers, dashboardReducers, projectReducers, searchReducers, staffReducers))

export default rootReducer
<commit_msg>combine get user and get analsyts<commit_after>import { combineReducers } from 'redux'
import { reducer as formReducer, SubmissionError } from 'redux-form'
import { reducer as searchReducer } from 'redux-search'
import hash from 'object-hash'

import { reducers as dashboardReducers } from 'pages/Dashboard/reducers'
import { reducers as projectReducers } from 'pages/Project/reducers'
import { reducers as searchReducers } from 'pages/Search/reducers'
import { reducers as staffReducers } from 'pages/Staff/reducers'
import { HttpRequestHelper } from 'shared/utils/httpRequestHelper'
import { SHOW_ALL, SORT_BY_FAMILY_GUID } from 'shared/utils/constants'
import {
  createObjectsByIdReducer, loadingReducer, zeroActionsReducer, createSingleObjectReducer, createSingleValueReducer,
} from './utils/reducerFactories'
import modalReducers from './utils/modalReducer'

/**
 * Action creator and reducers in one file as suggested by https://github.com/erikras/ducks-modular-redux
 */

// actions
export const RECEIVE_DATA = 'RECEIVE_DATA'
export const REQUEST_PROJECTS = 'REQUEST_PROJECTS'
export const RECEIVE_SAVED_SEARCHES = 'RECEIVE_SAVED_SEARCHES'
export const REQUEST_SAVED_SEARCHES = 'REQUEST_SAVED_SEARCHES'
const REQUEST_SAVED_VARIANTS = 'REQUEST_SAVED_VARIANTS'
const RECEIVE_SAVED_VARIANT_FAMILIES = 'RECEIVE_SAVED_VARIANT_FAMILIES'
const REQUEST_GENES = 'REQUEST_GENES'
const REQUEST_GENE_LISTS = 'REQUEST_GENE_LISTS'
const REQUEST_GENE_LIST = 'REQUEST_GENE_LIST'
const UPDATE_SAVED_VARIANT_TABLE_STATE = 'UPDATE_VARIANT_STATE'
const UPDATE_IGV_VISIBILITY = 'UPDATE_IGV_VISIBILITY'
const REQUEST_USERS = 'REQUEST_USERS'
const RECEIVE_USERS = 'RECEIVE_USERS'

// action creators

// A helper action that handles create, update and delete requests
export const updateEntity = (values, receiveDataAction, urlPath, idField, actionSuffix, getUrlPath) => {
  return (dispatch, getState) => {
    if (getUrlPath) {
      urlPath = getUrlPath(getState())
    }

    let action = 'create'
    if (values[idField]) {
      urlPath = `${urlPath}/${values[idField]}`
      action = values.delete ? 'delete' : 'update'
    }

    return new HttpRequestHelper(`${urlPath}/${action}${actionSuffix || ''}`,
      (responseJson) => {
        dispatch({ type: receiveDataAction, updatesById: responseJson })
      },
      (e) => {
        throw new SubmissionError({ _error: [e.message] })
      },
    ).post(values)
  }
}

export const fetchProjects = () => {
  return (dispatch) => {
    dispatch({ type: REQUEST_PROJECTS })
    new HttpRequestHelper('/api/dashboard',
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
      },
      e => dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} }),
    ).get()
  }
}

export const loadUserOptions = (staffOnly) => {

  return (dispatch) => {
    const url = staffOnly ? '/api/users/get_all_staff' : '/api/users/get_all'
    dispatch({ type: REQUEST_USERS })
    new HttpRequestHelper(url,
      (responseJson) => {
        dispatch({ type: RECEIVE_USERS, newValue: responseJson })
      },
      (e) => {
        dispatch({ type: RECEIVE_USERS, error: e.message, newValue: [] })
      },
    ).get()
  }
}

export const loadStaffOptions = () => loadUserOptions(true)

/**
 * POSTS a request to update the specified project and dispatches the appropriate events when the request finishes
 * Accepts a values object that includes any data to be posted as well as the following keys:
 *
 * action: A string representation of the action to perform. Can be "create", "update" or "delete". Defaults to "update"
 * projectGuid: The GUID for the project to update. If omitted, the action will be set to "create"
 * projectField: A specific field to update (e.g. "categories"). Should be used for fields which have special server-side logic for updating
 */
export const updateProject = (values) => {
  const actionSuffix = values.projectField ? `_project_${values.projectField}` : '_project'
  return updateEntity(values, RECEIVE_DATA, '/api/project', 'projectGuid', actionSuffix)
}

export const updateFamily = (values) => {
  return (dispatch) => {
    const familyField = values.familyField ? `_${values.familyField}` : ''
    return new HttpRequestHelper(`/api/family/${values.familyGuid}/update${familyField}`,
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: { familiesByGuid: responseJson } })
      },
      (e) => {
        throw new SubmissionError({ _error: [e.message] })
      },
    ).post(values)
  }
}

export const updateIndividual = (values) => {
  return (dispatch) => {
    return new HttpRequestHelper(`/api/individual/${values.individualGuid}/update`,
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: { individualsByGuid: responseJson } })
      },
      (e) => {
        throw new SubmissionError({ _error: [e.message] })
      },
    ).post(values)
  }
}

export const loadGene = (geneId) => {
  return (dispatch, getState) => {
    const gene = getState().genesById[geneId]
    if (!gene || !gene.notes || !gene.expression) {
      dispatch({ type: REQUEST_GENES })
      new HttpRequestHelper(`/api/gene_info/${geneId}`,
        (responseJson) => {
          dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
        },
        (e) => {
          dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
        },
      ).get()
    }
  }
}

export const loadGenes = (geneIds) => {
  return (dispatch, getState) => {
    const state = getState()
    if ([...geneIds].some(geneId => !state.genesById[geneId])) {
      dispatch({ type: REQUEST_GENES })
      new HttpRequestHelper('/api/genes_info',
        (responseJson) => {
          dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
        },
        (e) => {
          dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
        },
      ).get({ geneIds: [...geneIds] })
    }
  }
}

export const loadLocusLists = () => {
  return (dispatch) => {
    dispatch({ type: REQUEST_GENE_LISTS })
    new HttpRequestHelper('/api/locus_lists',
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
      },
      (e) => {
        dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
      },
    ).get()
  }
}

export const loadLocusListItems = (locusListId) => {
  return (dispatch, getState) => {
    const locusList = getState().locusListsByGuid[locusListId]
    if (locusListId && !(locusList && locusList.items)) {
      dispatch({ type: REQUEST_GENE_LIST })
      new HttpRequestHelper(`/api/locus_lists/${locusListId}`,
        (responseJson) => {
          dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
        },
        (e) => {
          const updates = { locusListsByGuid: { [locusListId]: { items: [] } } }
          dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: updates })
        },
      ).get()
    }
  }
}

export const updateGeneNote = (values) => {
  return updateEntity(values, RECEIVE_DATA, `/api/gene_info/${values.geneId || values.gene_id}/note`, 'noteGuid')
}

export const navigateSavedHashedSearch = (search, navigateSearch) => {
  return (dispatch) => {
    const searchHash = hash.MD5(search)
    dispatch({ type: RECEIVE_SAVED_SEARCHES, updatesById: { searchesByHash: { [searchHash]: search } } })
    navigateSearch(`/variant_search/results/${searchHash}`)
  }
}

export const loadSavedVariants = (familyGuids, variantGuid, tag) => {
  return (dispatch, getState) => {
    const state = getState()
    const projectGuid = state.currentProjectGuid

    let url = projectGuid ? `/api/project/${projectGuid}/saved_variants` : `/api/staff/saved_variants/${tag}`

    // Do not load if already loaded
    let expectedFamilyGuids
    if (variantGuid) {
      if (state.savedVariantsByGuid[variantGuid]) {
        return
      }
      url = `${url}/${variantGuid}`
    } else if (projectGuid) {
      expectedFamilyGuids = familyGuids
      if (!expectedFamilyGuids) {
        expectedFamilyGuids = Object.values(state.familiesByGuid).filter(
          family => family.projectGuid === projectGuid).map(({ familyGuid }) => familyGuid)
      }
      if (expectedFamilyGuids.length > 0 && expectedFamilyGuids.every(family => state.savedVariantFamilies[family])) {
        return
      }
    } else if (!tag) {
      return
    }

    dispatch({ type: REQUEST_SAVED_VARIANTS })
    new HttpRequestHelper(url,
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
        if (expectedFamilyGuids) {
          dispatch({
            type: RECEIVE_SAVED_VARIANT_FAMILIES,
            updates: expectedFamilyGuids.reduce((acc, family) => ({ ...acc, [family]: true }), {}),
          })
        }
      },
      (e) => {
        dispatch({ type: RECEIVE_DATA, error: e.message, updatesById: {} })
      },
    ).get(familyGuids ? { families: familyGuids.join(',') } : {})
  }
}

const updateSavedVariant = (values, action = 'create') => {
  return (dispatch, getState) => {
    return new HttpRequestHelper(`/api/saved_variant/${action}`,
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
      },
      (e) => {
        throw new SubmissionError({ _error: [e.message] })
      },
    ).post({ searchHash: getState().currentSearchHash, ...values })
  }
}

export const updateVariantNote = (values) => {
  if (values.variantGuid) {
    return updateEntity(values, RECEIVE_DATA, `/api/saved_variant/${values.variantGuid}/note`, 'noteGuid')
  }
  return updateSavedVariant(values)
}

export const updateVariantTags = (values) => {
  const urlPath = values.variantGuid ? `${values.variantGuid}/update_tags` : 'create'
  return updateSavedVariant(values, urlPath)
}

export const updateLocusList = (values) => {
  return (dispatch) => {
    let action = 'create'
    if (values.locusListGuid) {
      action = `${values.locusListGuid}/${values.delete ? 'delete' : 'update'}`
    }

    return new HttpRequestHelper(`/api/locus_lists/${action}`,
      (responseJson) => {
        dispatch({ type: RECEIVE_DATA, updatesById: responseJson })
      },
      (e) => {
        let error = e.message.replace('(400)', '')
        if (e.body && e.body.invalidLocusListItems) {
          error = `${error} Invalid genes/ intervals: ${e.body.invalidLocusListItems.join(', ')}`
        }
        throw new SubmissionError({
          _error: [error],
        })
      },
    ).post(values)
  }
}

export const updateSavedVariantTable = updates => ({ type: UPDATE_SAVED_VARIANT_TABLE_STATE, updates })
export const updateIgvReadsVisibility = updates => ({ type: UPDATE_IGV_VISIBILITY, updates })

// root reducer
const rootReducer = combineReducers(Object.assign({
  projectCategoriesByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'projectCategoriesByGuid'),
  projectsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'projectsByGuid'),
  projectsLoading: loadingReducer(REQUEST_PROJECTS, RECEIVE_DATA),
  familiesByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'familiesByGuid'),
  individualsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'individualsByGuid'),
  samplesByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'samplesByGuid'),
  analysisGroupsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'analysisGroupsByGuid'),
  mmeResultsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'mmeResultsByGuid'),
  genesById: createObjectsByIdReducer(RECEIVE_DATA, 'genesById'),
  genesLoading: loadingReducer(REQUEST_GENES, RECEIVE_DATA),
  locusListsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'locusListsByGuid'),
  locusListsLoading: loadingReducer(REQUEST_GENE_LISTS, RECEIVE_DATA),
  locusListLoading: loadingReducer(REQUEST_GENE_LIST, RECEIVE_DATA),
  savedVariantsByGuid: createObjectsByIdReducer(RECEIVE_DATA, 'savedVariantsByGuid'),
  savedVariantsLoading: loadingReducer(REQUEST_SAVED_VARIANTS, RECEIVE_DATA),
  searchesByHash: createObjectsByIdReducer(RECEIVE_SAVED_SEARCHES, 'searchesByHash'),
  savedSearchesByGuid: createObjectsByIdReducer(RECEIVE_SAVED_SEARCHES, 'savedSearchesByGuid'),
  savedVariantFamilies: createSingleObjectReducer(RECEIVE_SAVED_VARIANT_FAMILIES),
  savedSearchesLoading: loadingReducer(REQUEST_SAVED_SEARCHES, RECEIVE_SAVED_SEARCHES),
  user: zeroActionsReducer,
  newUser: zeroActionsReducer,
  usersByUsername: createSingleValueReducer(RECEIVE_USERS, {}),
  userOptionsLoading: loadingReducer(REQUEST_USERS, RECEIVE_USERS),
  form: formReducer,
  search: searchReducer,
  savedVariantTableState: createSingleObjectReducer(UPDATE_SAVED_VARIANT_TABLE_STATE, {
    hideExcluded: false,
    hideReviewOnly: false,
    categoryFilter: SHOW_ALL,
    sort: SORT_BY_FAMILY_GUID,
    page: 1,
    recordsPerPage: 25,
  }, false),
  igvReadsVisibility: createSingleObjectReducer(UPDATE_IGV_VISIBILITY),
}, modalReducers, dashboardReducers, projectReducers, searchReducers, staffReducers))

export default rootReducer
<|endoftext|>


<commit_before>/*
 * server.js: OpenStack Cloud server
 *
 * (C) 2013 Nodejitsu Inc.
 *
 */

var utile   = require('utile'),
    compute = require('../../core/compute'),
    base    = require('../../core/compute/server');

var Server = exports.Server = function Server(client, details) {
  base.Server.call(this, client, details);
};

utile.inherits(Server, base.Server);

Server.prototype._setProperties = function (details) {
  var self = this;
  // Set core properties
  this.id   = details.id;
  this.name = details.name;

  if (details.status) {
    switch (details.status.toUpperCase()) {
      case 'BUILD':
      case 'REBUILD':
        this.status = "PROVISIONING";
        break;
      case 'ACTIVE':
        this.status = "RUNNING";
        break;
      case 'SUSPENDED':
        this.status = "STOPPED";
        break;
      case 'REBOOT':
      case 'HARD_REBOOT':
        this.status = "REBOOT";
        break;
      case 'QUEUE_RESIZE':
      case 'PREP_RESIZE':
      case 'RESIZE':
      case 'VERIFY_RESIZE':
      case 'SHARE_IP':
      case 'SHARE_IP_NO_CONFIG':
      case 'DELETE_IP':
      case 'PASSWORD':
        this.status = "UPDATING";
        break;
      case 'RESCUE':
        this.status = 'ERROR';
        break;
      default:
        this.status = "UNKNOWN";
        break;
    }
  }

  //
  // Set extra properties
  //
  this.progress  = details.progress  || this.progress;
  this.imageId   = details.imageId   || this.imageId;
  this.adminPass = details.adminPass || this.adminPass;
  this.addresses = details.addresses || {};
  this.metadata  = details.metadata  || {};
  this.flavorId  = details.flavorId  || this.flavorId;
  this.hostId    = details.hostId    || this.hostId;
  this.original  = this.openstack = details;

  if (Object.keys(this.addresses).length && !this.addresses.public
    && !this.addresses.private) {
    this.addresses = Object.keys(this.addresses)
      .map(function (network) {
        return self.addresses[network];
      })
      .reduce(function (all, interfaces) {
        Object.keys(interfaces).map(function (interface) {
          return interfaces[interface].addr;
        })
        .forEach(function (addr) {
          return compute.isPrivate(addr)
            ? all['private'].push(addr)
            : all['public'].push(addr);
        });

        return all;
      }, { public: [], private: [] });
  }

  // Try to set the flavorId using a flavor object
  if (typeof this.flavorId === "undefined" &&
      details.flavor && details.flavor.id) {
    this.flavorId = details.flavor.id;
  }

  // Try to set the imageId using an image object
  if (typeof this.imageId === "undefined" &&
      details.image && details.image.id) {
    this.imageId = details.image.id;
  }
};

//
// Updates the backup schedule for this instance.
// Parameters: backup callback
//
Server.prototype.updateBackup = function (backup, callback) {
  var self = this;
  this.client.updateServerBackup(this.id, backup, function (err, res) {
    if (err) {
      return callback(err);
    }

    self.backups = backup;
    callback(null, res);
  });
};

//
// Disables the backup schedule for this instance.
// Parameters: callback
//
Server.prototype.disableBackup = function (callback) {
  this.client.disableServerBackup(this.id, callback);
};

//
// Updates the addresses for this instance
// Parameters: type['public' || 'private]? callback
//
Server.prototype.getAddresses = function (type, callback) {
  if (!callback && typeof type === 'function') {
    callback = type;
    type = '';
  }

  var self = this;
  this.client.getServerAddresses(this, type, function (err, addresses) {
    if (err) {
      return callback(err);
    }

    if (type === '') {
      self.addresses = addresses;
    }
    else {
      self.addresses = addresses || {};
      self.addresses[type] = addresses[type];
    }

    callback(null, addresses);
  });
};

//
// Gets the backup schedule for this instance
// Parameters: callback
//
Server.prototype.getBackup = function (callback) {
  var self = this;
  this.client.getServerBackup(this.id, function (err, backups) {
    if (err) {
      return callback(err);
    }

    self.backups = backups;
    callback(null, backups);
  });
};<commit_msg>Add Status SHUTOFF case to OpenStack provider<commit_after>/*
 * server.js: OpenStack Cloud server
 *
 * (C) 2013 Nodejitsu Inc.
 *
 */

var utile   = require('utile'),
    compute = require('../../core/compute'),
    base    = require('../../core/compute/server');

var Server = exports.Server = function Server(client, details) {
  base.Server.call(this, client, details);
};

utile.inherits(Server, base.Server);

Server.prototype._setProperties = function (details) {
  var self = this;
  // Set core properties
  this.id   = details.id;
  this.name = details.name;

  if (details.status) {
    switch (details.status.toUpperCase()) {
      case 'BUILD':
      case 'REBUILD':
        this.status = "PROVISIONING";
        break;
      case 'ACTIVE':
        this.status = "RUNNING";
        break;
      case 'SUSPENDED':
      case 'SHUTOFF':
        this.status = "STOPPED";
        break;
      case 'REBOOT':
      case 'HARD_REBOOT':
        this.status = "REBOOT";
        break;
      case 'QUEUE_RESIZE':
      case 'PREP_RESIZE':
      case 'RESIZE':
      case 'VERIFY_RESIZE':
      case 'SHARE_IP':
      case 'SHARE_IP_NO_CONFIG':
      case 'DELETE_IP':
      case 'PASSWORD':
        this.status = "UPDATING";
        break;
      case 'RESCUE':
        this.status = 'ERROR';
        break;
      default:
        this.status = "UNKNOWN";
        break;
    }
  }

  //
  // Set extra properties
  //
  this.progress  = details.progress  || this.progress;
  this.imageId   = details.imageId   || this.imageId;
  this.adminPass = details.adminPass || this.adminPass;
  this.addresses = details.addresses || {};
  this.metadata  = details.metadata  || {};
  this.flavorId  = details.flavorId  || this.flavorId;
  this.hostId    = details.hostId    || this.hostId;
  this.original  = this.openstack = details;

  if (Object.keys(this.addresses).length && !this.addresses.public
    && !this.addresses.private) {
    this.addresses = Object.keys(this.addresses)
      .map(function (network) {
        return self.addresses[network];
      })
      .reduce(function (all, interfaces) {
        Object.keys(interfaces).map(function (interface) {
          return interfaces[interface].addr;
        })
        .forEach(function (addr) {
          return compute.isPrivate(addr)
            ? all['private'].push(addr)
            : all['public'].push(addr);
        });

        return all;
      }, { public: [], private: [] });
  }

  // Try to set the flavorId using a flavor object
  if (typeof this.flavorId === "undefined" &&
      details.flavor && details.flavor.id) {
    this.flavorId = details.flavor.id;
  }

  // Try to set the imageId using an image object
  if (typeof this.imageId === "undefined" &&
      details.image && details.image.id) {
    this.imageId = details.image.id;
  }
};

//
// Updates the backup schedule for this instance.
// Parameters: backup callback
//
Server.prototype.updateBackup = function (backup, callback) {
  var self = this;
  this.client.updateServerBackup(this.id, backup, function (err, res) {
    if (err) {
      return callback(err);
    }

    self.backups = backup;
    callback(null, res);
  });
};

//
// Disables the backup schedule for this instance.
// Parameters: callback
//
Server.prototype.disableBackup = function (callback) {
  this.client.disableServerBackup(this.id, callback);
};

//
// Updates the addresses for this instance
// Parameters: type['public' || 'private]? callback
//
Server.prototype.getAddresses = function (type, callback) {
  if (!callback && typeof type === 'function') {
    callback = type;
    type = '';
  }

  var self = this;
  this.client.getServerAddresses(this, type, function (err, addresses) {
    if (err) {
      return callback(err);
    }

    if (type === '') {
      self.addresses = addresses;
    }
    else {
      self.addresses = addresses || {};
      self.addresses[type] = addresses[type];
    }

    callback(null, addresses);
  });
};

//
// Gets the backup schedule for this instance
// Parameters: callback
//
Server.prototype.getBackup = function (callback) {
  var self = this;
  this.client.getServerBackup(this.id, function (err, backups) {
    if (err) {
      return callback(err);
    }

    self.backups = backups;
    callback(null, backups);
  });
};<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var View = require('view');
var template = require('./template');
var truncate = require('truncate');
var config = require('config');
var Participants = require('participants-box');
var ProposalClauses = require('proposal-clauses');
var o = require('dom');

/**
 * Expose ProposalArticle
 */

module.exports = ProposalArticle;

/**
 * Creates a new proposal-article view
 * from proposals object.
 *
 * @param {Object} proposal proposal's object data
 * @return {ProposalArticle} `ProposalArticle` instance.
 * @api public
 */

function ProposalArticle (proposal) {
  if (!(this instanceof ProposalArticle)) {
    return new ProposalArticle(proposal);
  };

  this.proposal = proposal;
  this.clauses = proposal.clauses.sort(function(a, b) {
    var sort = a.order - b.order;
    sort = sort > 0 ? 1 : -1;
    return sort;
  });

  var baseUrl = config.protocol + "://" + config.host + (config.publicPort ? (":" + config.publicPort) : "");

  View.call(this, template, {
    proposal: proposal,
    clauses: this.clauses,
    baseUrl: baseUrl,
    truncate: truncate
  });

  this.participants = new Participants(proposal.participants || []);
  this.participants.appendTo(this.find('.participants')[0]);
  this.participants.fetch();

  this.proposalClauses = new ProposalClauses(proposal);
  this.proposalClauses.appendTo('.clauses');

  commentable(proposal.summary, proposal.id).appendTo(this.find('.summary')[0]);
}

/**
 * Inherit from View
 */

View(ProposalArticle);

/**
 * Turn on event handlers on this view
 */

ProposalArticle.prototype.switchOn = function() {
  this.bind('click', '.clauses a.read-more', 'showclauses');
}

ProposalArticle.prototype.showclauses = function(ev) {
  ev.preventDefault();

  this.find('.clauses .clause.hide').removeClass('hide');
  this.unbind('click', '.clauses a.read-more', 'showclauses');
  this.find('.clauses a.read-more').remove();
}

function commentable(els, proposalId) {
  if ('string' === typeof els) els = o(els);
  return els
    .find('div')
    .each(function(div, i) {
      div.attr('data-section-id', proposalId + '-' + i)
        .addClass('commentable-section');
    })
}
<commit_msg>[admin-law-form] Fixed richtext error when rendering legacy law text #438<commit_after>
/**
 * Module dependencies.
 */

var View = require('view');
var template = require('./template');
var truncate = require('truncate');
var config = require('config');
var Participants = require('participants-box');
var ProposalClauses = require('proposal-clauses');
var o = require('dom');

/**
 * Expose ProposalArticle
 */

module.exports = ProposalArticle;

/**
 * Creates a new proposal-article view
 * from proposals object.
 *
 * @param {Object} proposal proposal's object data
 * @return {ProposalArticle} `ProposalArticle` instance.
 * @api public
 */

function ProposalArticle (proposal) {
  if (!(this instanceof ProposalArticle)) {
    return new ProposalArticle(proposal);
  };

  this.proposal = proposal;
  this.clauses = proposal.clauses.sort(function(a, b) {
    var sort = a.order - b.order;
    sort = sort > 0 ? 1 : -1;
    return sort;
  });

  var baseUrl = config.protocol + "://" + config.host + (config.publicPort ? (":" + config.publicPort) : "");

  View.call(this, template, {
    proposal: proposal,
    clauses: this.clauses,
    baseUrl: baseUrl,
    truncate: truncate
  });

  this.participants = new Participants(proposal.participants || []);
  this.participants.appendTo(this.find('.participants')[0]);
  this.participants.fetch();

  this.proposalClauses = new ProposalClauses(proposal);
  this.proposalClauses.appendTo('.clauses');

  this.summary = this.find('.summary').html(proposal.summary);
  commentable(this.summary, proposal.id);
}

/**
 * Inherit from View
 */

View(ProposalArticle);

/**
 * Turn on event handlers on this view
 */

ProposalArticle.prototype.switchOn = function() {
  this.bind('click', '.clauses a.read-more', 'showclauses');
}

ProposalArticle.prototype.showclauses = function(ev) {
  ev.preventDefault();

  this.find('.clauses .clause.hide').removeClass('hide');
  this.unbind('click', '.clauses a.read-more', 'showclauses');
  this.find('.clauses a.read-more').remove();
}

function commentable(els, proposalId) {
  var divs = els.find('div');

  if (!divs || divs.length === 0) {
    // Old-fashioned law format
    var paragraphs = els.html().split('\n');
    var html = '';
    paragraphs.forEach(function(val, i) {
      html += '
'
        + val + '
';
    });
    els.html(html);
  } else {
    // New law format
    divs.each(function(div, i) {
      div.attr('data-section-id', proposalId + '-' + i)
        .addClass('commentable-section');
    });
  }
}
<|endoftext|>


<commit_before>import t from 't-component'
import debug from 'debug'
import user from '../user/user.js'
import config from '../config/config.js'
import request from '../request/request.js'
import SideComments from 'democracyos-side-comments'
import View from '../view/view.js'
import template from './template.jade'

let log = debug('democracyos:proposal-clauses')

export default class ProposalClauses extends View {

  constructor (topic) {
    super(template, { clauses: topic.clauses })
    this.topic = topic
    this.loadComments()
  }

  _handleError (err) {
    // FIXME: We need visual feedback here
    log('Error: %j', err)
  }

  loadComments () {
    this
      .load()
      .ready(this.sideComments.bind(this))
  }

  load () {
    let self = this

    request
      .get(`/api/topic/${this.topic.id}/sidecomments`)
      .end((err, res) => {
        if (err) return self._handleError(err)
        self.comments = res.body
        self.state('loaded')
      })

    return this
  }

  sideComments () {
    let className = '.commentable-container'
    let sections = this.getClausesSections()
    let locals = { trans: t, locale: config.locale, reference: this.reference }
    let userComment = user.id ? {
      id: user.id,
      avatarUrl: user.avatar,
      name: user.firstName + ' ' + user.lastName
    } : null

    let sideComments = new SideComments(className, userComment, sections, locals)
    sideComments.on('commentPosted', this.onClauseCommentPosted.bind(this))
    sideComments.on('commentUpdated', this.onClauseCommentUpdated.bind(this))
    sideComments.on('commentDeleted', this.onClauseCommentDeleted.bind(this))

    this.sideComments = sideComments
  }

  getClausesSections () {
    // FIXME: Fix this, please. Wat?
    let topic = this.topic
    let self = this
    let sections = []

    topic.clauses.forEach(function (clause) {
      let section = {}
      section.sectionId = clause.id

      function filterBySection (comment) {
        return comment.reference === clause.id
      }

      section.comments = self.comments.filter(filterBySection).map(self.formatComment)
      sections.push(section)
    })

    return sections
  }

  formatComment (comment) {
    comment.authorAvatarUrl = comment.author.avatar
    comment.authorName = comment.author.fullName
    comment.authorId = comment.author.id
    comment.comment = comment.text
    comment.sectionId = comment.reference
    return comment
  }

  _insertComment (err, res) {
    if (err) return log('Error when posting clause comment %s', err)
    let comment = this.formatComment(res.body)
    this.sideComments.insertComment(comment)
  }

  _replaceComment (err, res) {
    if (err) return log('Error when posting clause comment %s', err)
    let comment = this.formatComment(res.body)
    this.sideComments.replaceComment(comment)
  }

  onClauseCommentPosted (comment) {
    request
      .post(`/api/topic/${comment.sectionId}/comment`)
      .send({
        topicId: this.topic.id,
        context: 'paragraph',
        text: comment.comment,
        reference: comment.sectionId
      })
      .end(this._insertComment.bind(this))
  }

  onClauseCommentUpdated (comment) {
    request
      .put(`/api/comment/${comment.id}`)
      .send({ text: comment.comment, reference: comment.sectionId })
      .end(this._replaceComment.bind(this))
  }

  onClauseCommentDeleted (comment) {
    let self = this
    request
      .del(`/api/comment/${comment.id}`)
      .end((err, res) => {
        if (err) return self._handleError(err)
        self.sideComments.removeComment(comment.sectionId, comment.id)
      })
  }
}
<commit_msg>disable side-comments<commit_after>import t from 't-component'
import debug from 'debug'
import user from '../user/user.js'
import config from '../config/config.js'
import request from '../request/request.js'
import SideComments from 'democracyos-side-comments'
import View from '../view/view.js'
import template from './template.jade'

let log = debug('democracyos:proposal-clauses')

export default class ProposalClauses extends View {

  constructor (topic) {
    super(template, { clauses: topic.clauses })
    this.topic = topic
    this.loadComments()
  }

  _handleError (err) {
    // FIXME: We need visual feedback here
    log('Error: %j', err)
  }

  loadComments () {
    this
      .load()
      .ready(this.sideComments.bind(this))
  }

  load () {
    let self = this

    request
      .get(`/api/topic/${this.topic.id}/sidecomments`)
      .end((err, res) => {
        if (err) return self._handleError(err)
        self.comments = res.body
        self.state('loaded')
      })

    return this
  }

  sideComments () {
    return; // Side comments disabled
    let className = '.commentable-container'
    let sections = this.getClausesSections()
    let locals = { trans: t, locale: config.locale, reference: this.reference }
    let userComment = user.id ? {
      id: user.id,
      avatarUrl: user.avatar,
      name: user.firstName + ' ' + user.lastName
    } : null

    let sideComments = new SideComments(className, userComment, sections, locals)
    sideComments.on('commentPosted', this.onClauseCommentPosted.bind(this))
    sideComments.on('commentUpdated', this.onClauseCommentUpdated.bind(this))
    sideComments.on('commentDeleted', this.onClauseCommentDeleted.bind(this))

    this.sideComments = sideComments
  }

  getClausesSections () {
    // FIXME: Fix this, please. Wat?
    let topic = this.topic
    let self = this
    let sections = []

    topic.clauses.forEach(function (clause) {
      let section = {}
      section.sectionId = clause.id

      function filterBySection (comment) {
        return comment.reference === clause.id
      }

      section.comments = self.comments.filter(filterBySection).map(self.formatComment)
      sections.push(section)
    })

    return sections
  }

  formatComment (comment) {
    comment.authorAvatarUrl = comment.author.avatar
    comment.authorName = comment.author.fullName
    comment.authorId = comment.author.id
    comment.comment = comment.text
    comment.sectionId = comment.reference
    return comment
  }

  _insertComment (err, res) {
    if (err) return log('Error when posting clause comment %s', err)
    let comment = this.formatComment(res.body)
    this.sideComments.insertComment(comment)
  }

  _replaceComment (err, res) {
    if (err) return log('Error when posting clause comment %s', err)
    let comment = this.formatComment(res.body)
    this.sideComments.replaceComment(comment)
  }

  onClauseCommentPosted (comment) {
    request
      .post(`/api/topic/${comment.sectionId}/comment`)
      .send({
        topicId: this.topic.id,
        context: 'paragraph',
        text: comment.comment,
        reference: comment.sectionId
      })
      .end(this._insertComment.bind(this))
  }

  onClauseCommentUpdated (comment) {
    request
      .put(`/api/comment/${comment.id}`)
      .send({ text: comment.comment, reference: comment.sectionId })
      .end(this._replaceComment.bind(this))
  }

  onClauseCommentDeleted (comment) {
    let self = this
    request
      .del(`/api/comment/${comment.id}`)
      .end((err, res) => {
        if (err) return self._handleError(err)
        self.sideComments.removeComment(comment.sectionId, comment.id)
      })
  }
}
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */

var page = require('page')
  , domify = require('domify')
  , t = require('t')
  , events = require('events')
  , classes = require('classes')
  , request = require('superagent')
  , options = require('./options')
  , closest = require('closest')
  , autocomplete = require('autocomplete')
  , Enumerable = require('enumerable')
  , Chart = require('Chart.js')
  , log = require('debug')('proposal-options');

/**
 * Expose ProposalOptions.
 */

module.exports = ProposalOptions;

var buttonsBox = delegationBox = null;

/**
 * Proposal Options view
 *
 * @param {Array} proposals list of proposals
 * @param {Object} selected proposal object
 * @return {ProposalOptions} `ProposalOptions` instance.
 * @api public
 */

function ProposalOptions (proposal, citizen) {
  if (!(this instanceof ProposalOptions)) {
    return new ProposalOptions(proposals);
  }

  this.proposal = proposal;
  this.citizen = citizen;
  this.options = domify(options({ proposal: proposal, citizen: citizen, t: t }));
  this.events = events(this.options, this);

  this.events.bind('click .vote-box .direct-vote .vote-option', 'vote');
  //UNDONE: this.events.bind('click .vote-box .proxy-vote .vote-option', 'proxy');
  this.events.bind('click .vote-box .meta-data .change-vote', 'changevote');
  this.events.bind('click .toggle-results', 'toggleresults');

  this.buttonsBox = this.options.querySelector('.vote-box .vote-options');
  this.delegationBox = this.options.querySelector('.vote-box .delegation-box');

  this.delegationInput = this.options.querySelector('.vote-box .delegation-box .delegation-input');
  //UNDONE: this.initializeAutocomplete(this.delegationInput);
}

/**
 * Toggle voting buttons
 *
 * @param {Object} toggle element whose visibility is to be toggled
 * @return {ProposalOptions} instance of `ProposalOptions`
 * @api public
 */

ProposalOptions.prototype.toggleVisibility = function(el) {

  classes(el).toggle('hide');
}

/**
 * Vote for option
 *
 * @param {Object} ev event
 * @api private
 */

ProposalOptions.prototype.vote = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, '[data-proposal]')
    , id = target.getAttribute('data-proposal')
    , value;

  if (classes(target).has('vote-no')) {
    value = 'negative';
  } else if (classes(target).has('vote-yes')) {
    value = 'positive';
  } else if (classes(target).has('vote-abstain')) {
    value = 'neutral';
  }

  log('casting vote %s for %s', value, id);
  this.post(
    '/api/law/:id/vote'.replace(':id', id),
    { value: value },
    function (err, res) {
      if (err || !res.ok) {
        log('Failed cast %s for %s with error: %j', value, id, err || res.error);
        return;
      };

      page(window.location.pathname)
    }
  );
}

/**
 * Event handler for clicking the delegation button
 * @param  {Object} ev event
 * @return {ProposalOptions}    instance of `ProposalOptions`
 * @api private
 */

ProposalOptions.prototype.proxy = function(ev) {
  ev.preventDefault();

  this.toggleVisibility(this.buttonsBox);
  this.toggleVisibility(this.delegationBox);
}

/**
 * Change vote
 *
 * @param {Object} ev event
 * @api private
 */

ProposalOptions.prototype.changevote = function(ev) {
  ev.preventDefault();

  classes(this.buttonsBox).toggle('hide');
};

ProposalOptions.prototype.post = function(path, payload, fn) {
  request
  .post(path)
  .set('Accept', 'application/json')
  .send(payload)
  .end(fn);
};

/**
 * Render options
 *
 * @return {NodeElement} proposals options
 * @api public
 */

ProposalOptions.prototype.render = function() {
  // this.renderChart(); UNDONE: from previous layout; was breaking rendering
  return this.options;
}

ProposalOptions.prototype.initializeAutocomplete = function(input) {
  var self = this;
  var me = this.citizen;

  autocomplete(input, '/api/citizen/search?q=:q', {accept: 'application/json'})
  .label('fullName')
  .value('id')
  .parse(function(data){
    return Enumerable(data).reject('id == "' + me.id + '"').value();
  })
  .format(function(label, q) {
    //Format results displayed in select-box
    var r = new RegExp('(?:' + q + ')', 'i');
    return label.replace(r, '$&');
  })
  .on('select', function(trusteeId) {
    log('autocomplete - selected %s', trusteeId);
    self.delegateVote(trusteeId);
  })
};

/**
 * Posts a new delegation
 *
 * @param  {String} trusteeId ID of the delegation's trustee
 * @return {ProposalOptions} instance of `ProposalOptions`
 * @api private
 */

ProposalOptions.prototype.delegateVote = function(trusteeId) {
  var payload = { tag: this.proposal.tag, tid: trusteeId };

  log('delegation payload: %j',payload);

  this.post(
    '/api/delegation/create',
    payload,
    function(res) {
      log('delegation response: %j',res);
      page(window.location.pathname);
    }
  );
};

/**
 * Toggle results box
 *
 * @param {Event} ev
 * @api private
 */

ProposalOptions.prototype.toggleresults = function(ev) {
  ev.preventDefault();

  classes(this.options.querySelector('.results-box')).toggle('hide');
};

/**
 * Render chart into options block
 *
 * @return {ProposalOptions} `ProposalOptions` instance.
 * @api public
 * @deprecated
 */

ProposalOptions.prototype.renderChart = function() {
  var container = this.options.querySelector('#vote-chart');
  var vote = this.proposal.vote;
  var data = [];

  if (vote.census.length) {
    data.push({
      value: vote.positive.length,
      color: "#a4cb53",
      label: "YES",
      labelColor: "white",
      labelAlign: "center"
    });
    data.push({
      value: vote.negative.length,
      color: "#d95e59",
      label: "NO",
      labelColor: "white",
      labelAlign: "center"
    });

    new Chart(container.getContext('2d')).Pie(data);
  } else {
    var p = document.createElement('p');
    p.classList.add('alert');
    p.classList.add('alert-info');
    p.innerHTML = "No voting yet."
    container.parentNode.insertBefore(p, container);
  }
}<commit_msg>[proposal-options] - Apply standard `this.el` for view template instance<commit_after>/**
 * Module dependencies.
 */

var page = require('page')
  , domify = require('domify')
  , t = require('t')
  , events = require('events')
  , classes = require('classes')
  , request = require('superagent')
  , options = require('./options')
  , closest = require('closest')
  , autocomplete = require('autocomplete')
  , Enumerable = require('enumerable')
  , Chart = require('Chart.js')
  , log = require('debug')('proposal-options');

/**
 * Expose ProposalOptions.
 */

module.exports = ProposalOptions;

var buttonsBox = delegationBox = null;

/**
 * Proposal Options view
 *
 * @param {Array} proposals list of proposals
 * @param {Object} selected proposal object
 * @return {ProposalOptions} `ProposalOptions` instance.
 * @api public
 */

function ProposalOptions (proposal, citizen) {
  if (!(this instanceof ProposalOptions)) {
    return new ProposalOptions(proposals);
  }

  this.proposal = proposal;
  this.citizen = citizen;
  this.el = domify(options({ proposal: proposal, citizen: citizen, t: t }));
  this.events = events(this.el, this);

  this.events.bind('click .vote-box .direct-vote .vote-option', 'vote');
  //UNDONE: this.events.bind('click .vote-box .proxy-vote .vote-option', 'proxy');
  this.events.bind('click .vote-box .meta-data .change-vote', 'changevote');
  this.events.bind('click .toggle-results', 'toggleresults');

  this.buttonsBox = this.el.querySelector('.vote-box .vote-options');
  this.delegationBox = this.el.querySelector('.vote-box .delegation-box');

  this.delegationInput = this.el.querySelector('.vote-box .delegation-box .delegation-input');
  //UNDONE: this.initializeAutocomplete(this.delegationInput);
}

/**
 * Toggle voting buttons
 *
 * @param {Object} toggle element whose visibility is to be toggled
 * @return {ProposalOptions} instance of `ProposalOptions`
 * @api public
 */

ProposalOptions.prototype.toggleVisibility = function(el) {

  classes(el).toggle('hide');
}

/**
 * Vote for option
 *
 * @param {Object} ev event
 * @api private
 */

ProposalOptions.prototype.vote = function(ev) {
  ev.preventDefault();

  var target = ev.delegateTarget || closest(ev.target, '[data-proposal]')
    , id = target.getAttribute('data-proposal')
    , value;

  if (classes(target).has('vote-no')) {
    value = 'negative';
  } else if (classes(target).has('vote-yes')) {
    value = 'positive';
  } else if (classes(target).has('vote-abstain')) {
    value = 'neutral';
  }

  log('casting vote %s for %s', value, id);
  this.post(
    '/api/law/:id/vote'.replace(':id', id),
    { value: value },
    function (err, res) {
      if (err || !res.ok) {
        log('Failed cast %s for %s with error: %j', value, id, err || res.error);
        return;
      };

      page(window.location.pathname)
    }
  );
}

/**
 * Event handler for clicking the delegation button
 * @param  {Object} ev event
 * @return {ProposalOptions}    instance of `ProposalOptions`
 * @api private
 */

ProposalOptions.prototype.proxy = function(ev) {
  ev.preventDefault();

  this.toggleVisibility(this.buttonsBox);
  this.toggleVisibility(this.delegationBox);
}

/**
 * Change vote
 *
 * @param {Object} ev event
 * @api private
 */

ProposalOptions.prototype.changevote = function(ev) {
  ev.preventDefault();

  classes(this.buttonsBox).toggle('hide');
};

ProposalOptions.prototype.post = function(path, payload, fn) {
  request
  .post(path)
  .set('Accept', 'application/json')
  .send(payload)
  .end(fn);
};

/**
 * Render options
 *
 * @return {NodeElement} proposals options
 * @api public
 */

ProposalOptions.prototype.render = function() {
  // this.renderChart(); UNDONE: from previous layout; was breaking rendering
  return this.el;
}

ProposalOptions.prototype.initializeAutocomplete = function(input) {
  var self = this;
  var me = this.citizen;

  autocomplete(input, '/api/citizen/search?q=:q', {accept: 'application/json'})
  .label('fullName')
  .value('id')
  .parse(function(data){
    return Enumerable(data).reject('id == "' + me.id + '"').value();
  })
  .format(function(label, q) {
    //Format results displayed in select-box
    var r = new RegExp('(?:' + q + ')', 'i');
    return label.replace(r, '$&');
  })
  .on('select', function(trusteeId) {
    log('autocomplete - selected %s', trusteeId);
    self.delegateVote(trusteeId);
  })
};

/**
 * Posts a new delegation
 *
 * @param  {String} trusteeId ID of the delegation's trustee
 * @return {ProposalOptions} instance of `ProposalOptions`
 * @api private
 */

ProposalOptions.prototype.delegateVote = function(trusteeId) {
  var payload = { tag: this.proposal.tag, tid: trusteeId };

  log('delegation payload: %j',payload);

  this.post(
    '/api/delegation/create',
    payload,
    function(res) {
      log('delegation response: %j',res);
      page(window.location.pathname);
    }
  );
};

/**
 * Toggle results box
 *
 * @param {Event} ev
 * @api private
 */

ProposalOptions.prototype.toggleresults = function(ev) {
  ev.preventDefault();

  classes(this.el.querySelector('.results-box')).toggle('hide');
};

/**
 * Render chart into options block
 *
 * @return {ProposalOptions} `ProposalOptions` instance.
 * @api public
 * @deprecated
 */

ProposalOptions.prototype.renderChart = function() {
  var container = this.el.querySelector('#vote-chart');
  var vote = this.proposal.vote;
  var data = [];

  if (vote.census.length) {
    data.push({
      value: vote.positive.length,
      color: "#a4cb53",
      label: "YES",
      labelColor: "white",
      labelAlign: "center"
    });
    data.push({
      value: vote.negative.length,
      color: "#d95e59",
      label: "NO",
      labelColor: "white",
      labelAlign: "center"
    });

    new Chart(container.getContext('2d')).Pie(data);
  } else {
    var p = document.createElement('p');
    p.classList.add('alert');
    p.classList.add('alert-info');
    p.innerHTML = "No voting yet."
    container.parentNode.insertBefore(p, container);
  }
}<|endoftext|>