<commit_before>// @flow

import clone from 'lodash/cloneDeep'
import moment from 'moment'
import validator from 'validator'

import type {Entity, GtfsSpecField, ScheduleException} from '../../types'
import type {EditorTables} from '../../types/reducers'

import {getTableById} from './gtfs'

export type EditorValidationIssue = {
  field: string,
  invalid: boolean,
  reason: string
}

export function doesNotExist (value: any): boolean {
  return value === '' || value === null || typeof value === 'undefined'
}

/**
 * Validate if a value is ok.
 *
 * Returns false if the value is ok.
 * Returns an EditorValidationIssue object if the value is not ok.
 */
export function validate (
  field: GtfsSpecField,
  value: any,
  entities: ?Array<Entity>,
  entity: ?Entity,
  tableData: EditorTables
): EditorValidationIssue | false {
  const {inputType, required, name} = field
  const valueDoesNotExist = doesNotExist(value)
  const isRequiredButEmpty = required && valueDoesNotExist
  const isOptionalAndEmpty = !required && valueDoesNotExist
  const agencies = getTableById(tableData, 'agency')
  let locationType: ?number = null

  // entity.locationtype is a string.  Convert to number for conditinals later on.
  if (entity && entity.location_type !== null) {
    locationType = parseInt(entity.location_type, 10)
  }

  // setting as a variable here because of eslint bug
  type CheckPositiveOutput = {
    num?: number,
    result: false | EditorValidationIssue
  }

  /**
   * Construct an EditorValidationIssue for the field name and reason (defaults to
   * empty field message).
   */
  function validationIssue (reason: string, field = name) {
    return {field, invalid: true, reason}
  }

  /**
   * Construct an EditorValidationIssue for this field, used if it is required
   * and has an empty value.
   */
  function emptyFieldValidationIssue (field = name) {
    return validationIssue('Required field must not be empty', field)
  }

  /**
   * Checks whether value is a positive number
   */
  function checkPositiveNumber (): CheckPositiveOutput {
    if (isRequiredButEmpty) {
      return {
        result: emptyFieldValidationIssue()
      }
    } else if (isOptionalAndEmpty) {
      return {
        result: false
      }
    }
    const num = parseFloat(value)

    // make sure value is parseable to a number
    if (isNaN(num)) {
      return {
        result: validationIssue('Field must be a valid number')
      }
    }

    // make sure value is positive
    if (num < 0) {
      return {
        result: validationIssue('Field must be a positive number')
      }
    }

    return {
      num,
      result: false
    }
  }

  /**
   * Checks whether value is a positive number
   */
  function checkGtfsRouteType (): CheckPositiveOutput {
    // Route type should meet required/optional criteria and be positive.
    const positiveNumberCheck = checkPositiveNumber()
    const { num, result } = positiveNumberCheck

    if (result !== false) return positiveNumberCheck

    // Make sure positive number value is one of those defined for route type options.
    // (force string comparison to avoid number/string ambiguities)
    const isValid = num !== undefined && num !== null && field.options && field.options.find(
      opt => opt.value.toString() === num.toString()
    )
    if (!isValid) {
      return {
        result: validationIssue('Field must be a valid route type')
      }
    }

    return {
      num,
      result: false
    }
  }

  switch (inputType) {
    case 'GTFS_ID':
      // Indices contains list of all indexes of occurrences of the ID value.
      const indices = []
      const idList = entities ? entities.map(e => {
        // $FlowFixMe too many unions for flow to handle
        return name in e ? e[name] : null
      }) : []
      let idx = idList.indexOf(value)
      while (idx !== -1) {
        indices.push(idx)
        idx = idList.indexOf(value, idx + 1)
      }
      const isNotUnique = !!(
        entities &&
        entity &&
        value &&
        (indices.length > 1 ||
          (indices.length > 0 && entities[indices[0]].id !== entity.id))
      )
      if (
        name === 'agency_id' &&
        idList.length > 1 &&
        valueDoesNotExist
      ) {
        return validationIssue('Identifier is required if more than one agency exists')
      }
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else if (isNotUnique) {
        return validationIssue('Identifier must be unique')
      } else {
        return false
      }
    case 'TEXT':
      if (
        name === 'stop_name' &&
        !value &&
        locationType !== null &&
        (typeof locationType === 'number' && locationType <= 2)
      ) {
        return validationIssue('Stop name is required for stop, station, and entrance location types.')
      }
      if (name === 'route_short_name' && !value && entity && entity.route_long_name) {
        return false
      } else if (
        name === 'route_long_name' &&
        !value &&
        entity &&
        entity.route_short_name
      ) {
        return false
      } else {
        if (isRequiredButEmpty) {
          return emptyFieldValidationIssue()
        } else {
          return false
        }
      }
    case 'GTFS_TRIP':
    case 'GTFS_SHAPE':
    case 'GTFS_BLOCK':
    case 'GTFS_FARE':
    case 'GTFS_SERVICE':
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else {
        return false
      }
    case 'URL':
      const isNotUrl = value && !validator.isURL(value)
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else if (isNotUrl) {
        return validationIssue('Field must contain valid URL.')
      } else {
        return false
      }
    case 'EMAIL':
      const isNotEmail = value && !validator.isEmail(value)
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else if (isNotEmail) {
        return validationIssue('Field must contain valid email address.')
      } else {
        return false
      }
    case 'GTFS_ZONE':
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else {
        return false
      }
    case 'TIMEZONE':
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else {
        return false
      }
    case 'LANGUAGE':
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else {
        return false
      }
    case 'LATITUDE':
      const isNotLat = value > 90 || value < -90
      if (isNotLat) {
        return validationIssue('Field must be valid latitude.')
      }
      if (isOptionalAndEmpty && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) {
        return validationIssue('Latitude and Longitude are required for your current location type')
      }
      return false
    case 'LONGITUDE':
      const isNotLng = value > 180 || value < -180
      if (isNotLng) {
        return validationIssue('Field must be valid longitude.')
      }
      if (isOptionalAndEmpty && typeof locationType === 'number' && locationType <= 2) {
        return validationIssue('Latitude and Longitude are required for your current location type')
      }
      return false
    case 'TIME':
    case 'NUMBER':
      const isNotANumber = isNaN(value)
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else if (isNotANumber) {
        return validationIssue('Field must be valid number')
      } else {
        return false
      }
    case 'DAY_OF_WEEK_BOOLEAN':
      let hasService = false
      const DAYS_OF_WEEK: Array<string> = [
        'monday',
        'tuesday',
        'wednesday',
        'thursday',
        'friday',
        'saturday',
        'sunday'
      ]
      for (var i = 0; i < DAYS_OF_WEEK.length; i++) {
        if (entity && entity[DAYS_OF_WEEK[i]]) {
          hasService = true
        }
      }
      if (!hasService && name === 'monday') {
        // only add validation issue for one day of week (monday)
        return validationIssue('Calendar must have service for at least one day')
      }
      return false
    case 'DROPDOWN':
      if (
        isRequiredButEmpty &&
        field.options &&
        field.options.findIndex(o => o.value === '') === -1
      ) {
        return emptyFieldValidationIssue()
      } else {
        return false
      }
    case 'GTFS_AGENCY':
      if (
        name === 'agency_id' &&
        agencies.length > 1
      ) {
        if (valueDoesNotExist) {
          return validationIssue('Field must be populated for feeds with more than one agency.')
        }
      }
      return false
    case 'EXCEPTION_DATE': // a date cannot be selected more than once (for all exceptions)
      const dateMap = {}
      // Clone exceptions to avoid mutating table in store.
      const scheduleExceptions: Array<ScheduleException> = clone(getTableById(tableData, 'scheduleexception'))
      if (entity) {
        const entityId = entity.id
        const exceptionIndex = scheduleExceptions.findIndex(se => se.id === entityId)
        if (exceptionIndex !== -1) {
          scheduleExceptions.splice(exceptionIndex, 1)
        }
        const castedScheduleException: ScheduleException = ((entity: any): ScheduleException)
        if (scheduleExceptions.map(ex => ex.name).includes(castedScheduleException.name)) {
          const reason = `${castedScheduleException.name} is already used in another exception`
          return validationIssue(reason, 'name')
        }
        scheduleExceptions.push(castedScheduleException)
      }

      for (let i = 0; i < scheduleExceptions.length; i++) {
        if (scheduleExceptions[i].dates) {
          scheduleExceptions[i].dates.map(d => {
            if (typeof dateMap[d] === 'undefined') {
              dateMap[d] = []
            }
            dateMap[d].push(scheduleExceptions[i].id)
          })
        }
      }
      if (!value || value.length === 0) {
        return emptyFieldValidationIssue('dates')
      }
      // check if date already exists in this or other exceptions
      for (let i = 0; i < value.length; i++) {
        const dateItemName = `dates-${i}`
        if (dateMap[value[i]] && dateMap[value[i]].length > 1) {
          // eslint-disable-next-line standard/computed-property-even-spacing
          const reason = `Date (${value[
            i
          ]}) cannot appear more than once for all exceptions`
          return validationIssue(reason, dateItemName)
        } else if (!moment(value[i], 'YYYYMMDD', true).isValid()) {
          return emptyFieldValidationIssue(dateItemName)
        }
      }
      return false
    case 'POSITIVE_INT':
      const positiveNumberCheck = checkPositiveNumber()
      if (positiveNumberCheck.result !== false) return positiveNumberCheck.result
      if (isOptionalAndEmpty) return false

      // check for floating value or decimal point
      if (
        typeof positiveNumberCheck.num === 'number' &&
        (
          positiveNumberCheck.num % 1 > 0 ||
          (
            value.indexOf &&
            value.indexOf('.') > -1
          )
        )
      ) {
        return validationIssue('Field must be a positive integer')
      }
      return false
    case 'POSITIVE_NUM':
      return checkPositiveNumber().result
    case 'GTFS_ROUTE_TYPE':
      return checkGtfsRouteType().result
    case 'GTFS_ROUTE':
    case 'GTFS_STOP':
    case 'DATE':
    case 'COLOR':
    default:
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      }
      return false
  }
}
<commit_msg>refactor(exception name validation): Add quotes to error name<commit_after>// @flow

import clone from 'lodash/cloneDeep'
import moment from 'moment'
import validator from 'validator'

import type {Entity, GtfsSpecField, ScheduleException} from '../../types'
import type {EditorTables} from '../../types/reducers'

import {getTableById} from './gtfs'

export type EditorValidationIssue = {
  field: string,
  invalid: boolean,
  reason: string
}

export function doesNotExist (value: any): boolean {
  return value === '' || value === null || typeof value === 'undefined'
}

/**
 * Validate if a value is ok.
 *
 * Returns false if the value is ok.
 * Returns an EditorValidationIssue object if the value is not ok.
 */
export function validate (
  field: GtfsSpecField,
  value: any,
  entities: ?Array<Entity>,
  entity: ?Entity,
  tableData: EditorTables
): EditorValidationIssue | false {
  const {inputType, required, name} = field
  const valueDoesNotExist = doesNotExist(value)
  const isRequiredButEmpty = required && valueDoesNotExist
  const isOptionalAndEmpty = !required && valueDoesNotExist
  const agencies = getTableById(tableData, 'agency')
  let locationType: ?number = null

  // entity.locationtype is a string.  Convert to number for conditinals later on.
  if (entity && entity.location_type !== null) {
    locationType = parseInt(entity.location_type, 10)
  }

  // setting as a variable here because of eslint bug
  type CheckPositiveOutput = {
    num?: number,
    result: false | EditorValidationIssue
  }

  /**
   * Construct an EditorValidationIssue for the field name and reason (defaults to
   * empty field message).
   */
  function validationIssue (reason: string, field = name) {
    return {field, invalid: true, reason}
  }

  /**
   * Construct an EditorValidationIssue for this field, used if it is required
   * and has an empty value.
   */
  function emptyFieldValidationIssue (field = name) {
    return validationIssue('Required field must not be empty', field)
  }

  /**
   * Checks whether value is a positive number
   */
  function checkPositiveNumber (): CheckPositiveOutput {
    if (isRequiredButEmpty) {
      return {
        result: emptyFieldValidationIssue()
      }
    } else if (isOptionalAndEmpty) {
      return {
        result: false
      }
    }
    const num = parseFloat(value)

    // make sure value is parseable to a number
    if (isNaN(num)) {
      return {
        result: validationIssue('Field must be a valid number')
      }
    }

    // make sure value is positive
    if (num < 0) {
      return {
        result: validationIssue('Field must be a positive number')
      }
    }

    return {
      num,
      result: false
    }
  }

  /**
   * Checks whether value is a positive number
   */
  function checkGtfsRouteType (): CheckPositiveOutput {
    // Route type should meet required/optional criteria and be positive.
    const positiveNumberCheck = checkPositiveNumber()
    const { num, result } = positiveNumberCheck

    if (result !== false) return positiveNumberCheck

    // Make sure positive number value is one of those defined for route type options.
    // (force string comparison to avoid number/string ambiguities)
    const isValid = num !== undefined && num !== null && field.options && field.options.find(
      opt => opt.value.toString() === num.toString()
    )
    if (!isValid) {
      return {
        result: validationIssue('Field must be a valid route type')
      }
    }

    return {
      num,
      result: false
    }
  }

  switch (inputType) {
    case 'GTFS_ID':
      // Indices contains list of all indexes of occurrences of the ID value.
      const indices = []
      const idList = entities ? entities.map(e => {
        // $FlowFixMe too many unions for flow to handle
        return name in e ? e[name] : null
      }) : []
      let idx = idList.indexOf(value)
      while (idx !== -1) {
        indices.push(idx)
        idx = idList.indexOf(value, idx + 1)
      }
      const isNotUnique = !!(
        entities &&
        entity &&
        value &&
        (indices.length > 1 ||
          (indices.length > 0 && entities[indices[0]].id !== entity.id))
      )
      if (
        name === 'agency_id' &&
        idList.length > 1 &&
        valueDoesNotExist
      ) {
        return validationIssue('Identifier is required if more than one agency exists')
      }
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else if (isNotUnique) {
        return validationIssue('Identifier must be unique')
      } else {
        return false
      }
    case 'TEXT':
      if (
        name === 'stop_name' &&
        !value &&
        locationType !== null &&
        (typeof locationType === 'number' && locationType <= 2)
      ) {
        return validationIssue('Stop name is required for stop, station, and entrance location types.')
      }
      if (name === 'route_short_name' && !value && entity && entity.route_long_name) {
        return false
      } else if (
        name === 'route_long_name' &&
        !value &&
        entity &&
        entity.route_short_name
      ) {
        return false
      } else {
        if (isRequiredButEmpty) {
          return emptyFieldValidationIssue()
        } else {
          return false
        }
      }
    case 'GTFS_TRIP':
    case 'GTFS_SHAPE':
    case 'GTFS_BLOCK':
    case 'GTFS_FARE':
    case 'GTFS_SERVICE':
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else {
        return false
      }
    case 'URL':
      const isNotUrl = value && !validator.isURL(value)
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else if (isNotUrl) {
        return validationIssue('Field must contain valid URL.')
      } else {
        return false
      }
    case 'EMAIL':
      const isNotEmail = value && !validator.isEmail(value)
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else if (isNotEmail) {
        return validationIssue('Field must contain valid email address.')
      } else {
        return false
      }
    case 'GTFS_ZONE':
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else {
        return false
      }
    case 'TIMEZONE':
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else {
        return false
      }
    case 'LANGUAGE':
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else {
        return false
      }
    case 'LATITUDE':
      const isNotLat = value > 90 || value < -90
      if (isNotLat) {
        return validationIssue('Field must be valid latitude.')
      }
      if (isOptionalAndEmpty && locationType !== null && (typeof locationType === 'number' && locationType <= 2)) {
        return validationIssue('Latitude and Longitude are required for your current location type')
      }
      return false
    case 'LONGITUDE':
      const isNotLng = value > 180 || value < -180
      if (isNotLng) {
        return validationIssue('Field must be valid longitude.')
      }
      if (isOptionalAndEmpty && typeof locationType === 'number' && locationType <= 2) {
        return validationIssue('Latitude and Longitude are required for your current location type')
      }
      return false
    case 'TIME':
    case 'NUMBER':
      const isNotANumber = isNaN(value)
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      } else if (isNotANumber) {
        return validationIssue('Field must be valid number')
      } else {
        return false
      }
    case 'DAY_OF_WEEK_BOOLEAN':
      let hasService = false
      const DAYS_OF_WEEK: Array<string> = [
        'monday',
        'tuesday',
        'wednesday',
        'thursday',
        'friday',
        'saturday',
        'sunday'
      ]
      for (var i = 0; i < DAYS_OF_WEEK.length; i++) {
        if (entity && entity[DAYS_OF_WEEK[i]]) {
          hasService = true
        }
      }
      if (!hasService && name === 'monday') {
        // only add validation issue for one day of week (monday)
        return validationIssue('Calendar must have service for at least one day')
      }
      return false
    case 'DROPDOWN':
      if (
        isRequiredButEmpty &&
        field.options &&
        field.options.findIndex(o => o.value === '') === -1
      ) {
        return emptyFieldValidationIssue()
      } else {
        return false
      }
    case 'GTFS_AGENCY':
      if (
        name === 'agency_id' &&
        agencies.length > 1
      ) {
        if (valueDoesNotExist) {
          return validationIssue('Field must be populated for feeds with more than one agency.')
        }
      }
      return false
    case 'EXCEPTION_DATE': // a date cannot be selected more than once (for all exceptions)
      const dateMap = {}
      // Clone exceptions to avoid mutating table in store.
      const scheduleExceptions: Array<ScheduleException> = clone(getTableById(tableData, 'scheduleexception'))
      if (entity) {
        const entityId = entity.id
        const exceptionIndex = scheduleExceptions.findIndex(se => se.id === entityId)
        if (exceptionIndex !== -1) {
          scheduleExceptions.splice(exceptionIndex, 1)
        }
        const castedScheduleException: ScheduleException = ((entity: any): ScheduleException)
        if (scheduleExceptions.map(ex => ex.name).includes(castedScheduleException.name)) {
          const reason = `"${castedScheduleException.name}" is already used in another exception`
          return validationIssue(reason, 'name')
        }
        scheduleExceptions.push(castedScheduleException)
      }

      for (let i = 0; i < scheduleExceptions.length; i++) {
        if (scheduleExceptions[i].dates) {
          scheduleExceptions[i].dates.map(d => {
            if (typeof dateMap[d] === 'undefined') {
              dateMap[d] = []
            }
            dateMap[d].push(scheduleExceptions[i].id)
          })
        }
      }
      if (!value || value.length === 0) {
        return emptyFieldValidationIssue('dates')
      }
      // check if date already exists in this or other exceptions
      for (let i = 0; i < value.length; i++) {
        const dateItemName = `dates-${i}`
        if (dateMap[value[i]] && dateMap[value[i]].length > 1) {
          // eslint-disable-next-line standard/computed-property-even-spacing
          const reason = `Date (${value[
            i
          ]}) cannot appear more than once for all exceptions`
          return validationIssue(reason, dateItemName)
        } else if (!moment(value[i], 'YYYYMMDD', true).isValid()) {
          return emptyFieldValidationIssue(dateItemName)
        }
      }
      return false
    case 'POSITIVE_INT':
      const positiveNumberCheck = checkPositiveNumber()
      if (positiveNumberCheck.result !== false) return positiveNumberCheck.result
      if (isOptionalAndEmpty) return false

      // check for floating value or decimal point
      if (
        typeof positiveNumberCheck.num === 'number' &&
        (
          positiveNumberCheck.num % 1 > 0 ||
          (
            value.indexOf &&
            value.indexOf('.') > -1
          )
        )
      ) {
        return validationIssue('Field must be a positive integer')
      }
      return false
    case 'POSITIVE_NUM':
      return checkPositiveNumber().result
    case 'GTFS_ROUTE_TYPE':
      return checkGtfsRouteType().result
    case 'GTFS_ROUTE':
    case 'GTFS_STOP':
    case 'DATE':
    case 'COLOR':
    default:
      if (isRequiredButEmpty) {
        return emptyFieldValidationIssue()
      }
      return false
  }
}
<|endoftext|>


<commit_before>import { encode, decode } from 'sourcemap-codec';

function traceSegment ( loc, mappings ) {
	const line = loc[0];
	const column = loc[1];

	const segments = mappings[ line ];

	for ( let i = 0; i < segments.length; i += 1 ) {
		const segment = segments[i];

		if ( segment[0] > column ) return null;

		if ( segment[0] === column ) {
			if ( segment[1] !== 0 ) {
				throw new Error( 'Bad sourcemap' );
			}

			return [ segment[2], segment[3] ];
		}
	}

	return null;
}

export default function collapseSourcemaps ( map, modules ) {
	const chains = modules.map( module => {
		return module.sourceMapChain.map( map => decode( map.mappings ) );
	});

	const decodedMappings = decode( map.mappings );

	const tracedMappings = decodedMappings.map( line => {
		let tracedLine = [];

		line.forEach( segment => {
			const sourceIndex = segment[1];
			const sourceCodeLine = segment[2];
			const sourceCodeColumn = segment[3];

			const chain = chains[ sourceIndex ];

			let i = chain.length;
			let traced = [ sourceCodeLine, sourceCodeColumn ];

			while ( i-- && traced ) {
				traced = traceSegment( traced, chain[i] );
			}

			if ( traced ) {
				tracedLine.push([
					segment[0],
					segment[1],
					traced[0],
					traced[1]
					// TODO name?
				]);
			}
		});

		return tracedLine;
	});

	map.mappings = encode( tracedMappings );
	return map;
}
<commit_msg>handle missing line in sourcemap<commit_after>import { encode, decode } from 'sourcemap-codec';

function traceSegment ( loc, mappings ) {
	const line = loc[0];
	const column = loc[1];

	const segments = mappings[ line ];

	if ( !segments ) return null;

	for ( let i = 0; i < segments.length; i += 1 ) {
		const segment = segments[i];

		if ( segment[0] > column ) return null;

		if ( segment[0] === column ) {
			if ( segment[1] !== 0 ) {
				throw new Error( 'Bad sourcemap' );
			}

			return [ segment[2], segment[3] ];
		}
	}

	return null;
}

export default function collapseSourcemaps ( map, modules ) {
	const chains = modules.map( module => {
		return module.sourceMapChain.map( map => decode( map.mappings ) );
	});

	const decodedMappings = decode( map.mappings );

	const tracedMappings = decodedMappings.map( line => {
		let tracedLine = [];

		line.forEach( segment => {
			const sourceIndex = segment[1];
			const sourceCodeLine = segment[2];
			const sourceCodeColumn = segment[3];

			const chain = chains[ sourceIndex ];

			let i = chain.length;
			let traced = [ sourceCodeLine, sourceCodeColumn ];

			while ( i-- && traced ) {
				traced = traceSegment( traced, chain[i] );
			}

			if ( traced ) {
				tracedLine.push([
					segment[0],
					segment[1],
					traced[0],
					traced[1]
					// TODO name?
				]);
			}
		});

		return tracedLine;
	});

	map.mappings = encode( tracedMappings );
	return map;
}
<|endoftext|>


<commit_before>/*

sagews -- basic javascript client library for the sage workspace server

AUTHOR:
   - William Stein

*/

/* Namespace for the application */
var sagews = {};

sagews.test = function (mesg) {
    alert(mesg);
}

sagews.Client = function(server) {
  return {

  /* All Sessions */

    /* kill processes and delete everything about all sessions */
    close_all_sessions: function(callback) {
        $.getJSON(server + '/close_all_sessions', '',
            function(response, status) { callback(response) }
        )
    },

    /* much information about all sessions */
    sessions: function(callback) {
        $.getJSON(server + '/sessions', '',
            function(response, status) { callback(response) }
        )
    },

  /* Session Management: creating, interrupting, killing and deleting */

    /* create a new session */
    new_session: function(callback) {
        $.getJSON(server + '/new_session', '',
            function(response, status) { callback(response) }
        )
    },

    /* send interrupt signal to session with given id */
    sigint: function(session_id, callback) {

    },

    /* kill session with given id */
    sigkill: function(session_id, callback) {

    },

    /* kill process and remove all files for session with given id */
    close_session: function(session_id, callback) {

    },

  /* Session Information: */

    /* extensive information about a given session */
    session: function(session_id, callback) {

    },

    /* get status of session with given id: 'ready', 'running', 'dead' */
    status: function(session_id, callback) {

    },

    /* list of cells in a given session */
    cells: function(session_id, callback) {

    },

  /* Code execution */

    /* execute block of code */
    execute: function(session_id, code, callback) {
        $.post(server + '/execute/' + session_id, {'code':code},
            function(response, status) { callback(response) },
            'json'
        )
    },

    /* incremental output produced when executing code */
    output_messages: function(session_id, cell_id, number, callback) {

    },

  /* Files: uploading, downloading, deleting and listing */

    /* return list of all files in the given session */
    files: function(session_id, callback) {

    },

    /* put a file or files into the given session */
    put_file: function(session_id, files, callback) {

    },

    /* get a file from the given session */
    get_file: function(session_id, path, callback) {

    },

    /* delete a file from the session */
    delete_file: function(session_id, path, callback) {

    }

  };
}

<commit_msg>It seems that I easily implemented the entire client API in sagews.js.<commit_after>/*

sagews -- basic javascript client library for the sage workspace server

AUTHOR:
   - William Stein
*/

/* Namespace for the application */
var sagews = {};

sagews.test = function (mesg) {
    alert(mesg);
}

sagews.Client = function(server) {

  get = function(url, callback) {
      $.getJSON(server + '/' + url, '', function(response, status) { callback(response); });
  }

  post = function(url, vars, callback) {
      $.post(server + '/' + url, vars, function(response, status) { callback(response); }, 'json');
  }

  return {

  /* All Sessions */

    /* kill processes and delete everything about all sessions */
    close_all_sessions: function(callback) { get('close_all_sessions', callback); },

    /* much information about all sessions */
    sessions: function(callback) { get('sessions', callback); },

  /* Session Management: creating, interrupting, killing and deleting */

    /* create a new session */
    new_session: function(callback) { get('new_session', callback); },

    /* send interrupt signal to session with given id */
    sigint: function(session_id, callback) { get('sigint/' + session_id, callback); },

    /* kill session with given id */
    sigkill: function(session_id, callback) { get('sigkill/' + session_id, callback); },

    /* kill process and remove all files for session with given id */
    close_session: function(session_id, callback) { get('close_session/' + session_id, callback); },

  /* Session Information: */

    /* extensive information about a given session */
    session: function(session_id, callback) { get('session/' + session_id, callback); },

    /* get status of session with given id: 'ready', 'running', 'dead' */
    status: function(session_id, callback) { get('status/' + session_id, callback); },

    /* list of cells in a given session */
    cells: function(session_id, callback) { get('cells/' + session_id, callback); },

  /* Code execution */

    /* execute block of code */
    execute: function(session_id, code, callback) { post('execute/' + session_id, {'code':code}, callback); },

    /* incremental output produced when executing code */
    output_messages: function(session_id, cell_id, number, callback) {
        get('output_messages/' + session_id + '/' + cell_id + '/' + number, callback);
    },

  /* Files: uploading, downloading, deleting and listing */

    /* return list of all files in the given session */
    files: function(session_id, callback) { get('files/' + session_id, callback); },

    /* put a file or files into the given session */
    put_file: function(session_id, files, callback) { put('put_file/' + session_id, files, callback); },

    /* get a file from the given session */
    get_file: function(session_id, path, callback) { get('get_file/' + session_id + '/' + path, callback); },

    /* delete a file from the session */
    delete_file: function(session_id, path, callback) { get('delete_file/' + session_id + '/' + path, callback); }

  };
}

/*

Testing Javascript console code:

foo = sagews.Client('http://localhost:5000')

foo.sessions( function(m) { z = m.sessions; } )

z[0].status
"ready"
z.length

*/<|endoftext|>


<commit_before>MWF.xDesktop.requireApp("process.Xform", "$ElModule", null, false);
/** @class Elicon 基于Element UI的图标组件。
 * @o2cn 图标组件
 * @example
 * //可以在脚本中获取该组件
 * //方法1：
 * var icon = this.form.get("name"); //获取组件
 * //方法2
 * var icon = this.target; //在组件事件脚本中获取
 * @extends MWF.xApplication.process.Xform.$Module
 * @o2category FormComponents
 * @o2range {Process|CMS|Portal}
 * @hideconstructor
 * @see {@link https://element.eleme.cn/#/zh-CN/component/icon|Element UI Icon 图标}
 */
MWF.xApplication.process.Xform.Elicon = MWF.APPElicon =  new Class(
    /** @lends MWF.xApplication.process.Xform.Elicon# */
    {
    Implements: [Events],
    Extends: MWF.APP$ElModule,
    _appendVueData: function(){
        if (!this.json.icon) this.json.icon = "el-icon-platform-eleme";
        if (!this.json.iconSize) this.json.iconSize = "16";
        if (!this.json.iconColor) this.json.iconColor = "";
        if (!this.json.elStyles) this.json.elStyles = {};
    },
    _createElementHtml: function(){
        var html = "";
        return html;
    }
    // _loadVue: function(callback){
    //     if (!window.Vue){
    //         var vue = (o2.session.isDebugger) ? "vue_develop" : "vue";
    //         o2.loadAll({"css": "../o2_lib/vue/element/index.css", "js": [vue, "elementui"]}, { "sequence": true }, callback);
    //     }else{
    //         if (callback) callback();
    //     }
    // },
    // _queryLoaded: function(){
    //     this._loadVue();
    // },

    // load: function(){
    //     this._loadModuleEvents();
    //     if (this.fireEvent("queryLoad")){
    //         this._queryLoaded();
    //         this._loadUserInterface();
    //     }
    // },
    //
    // _loadUserInterface: function(){
    //     this.node.appendHTML(this._createElementHtml(), "before");
    //     var icon = this.node.getPrevious();
    //
    //     this.node.destroy();
    //     this.node = icon;
    //     this.node.set({
    //         "id": this.json.id,
    //         "MWFType": this.json.type
    //     });
    //     this._createVueApp();
    // },
    // _createVueApp: function(){
    //     debugger;
    //     if (!this.vm) this._loadVue(this._mountVueApp.bind(this));
    // },
    //
    // _loadVue: function(callback){
    //     if (!window.Vue){
    //         o2.loadAll({"css": "../o2_lib/vue/element/index.css", "js": ["vue", "elementui"]}, { "sequence": true }, callback);
    //     }else{
    //         if (callback) callback();
    //     }
    // },
    // _mountVueApp: function(){
    //     if (!this.vueApp) this.vueApp = this._createVueExtend();
    //
    //     /**
    //      * @summary Vue对象实例
    //      * @see https://vuejs.org/
    //      * @member {VueInstance}
    //      */
    //     this.vm = new Vue(this.vueApp).$mount(this.node);
    // },
    //
    // _createVueExtend: function(){
    //     var _self = this;
    //     return {
    //         data: this._createVueData(),
    //         mounted: function(){
    //             _self._afterMounted(this.$el);
    //         }
    //     };
    // },
    // _createVueData: function(){
    //     return this.json;
    // },
    // _afterMounted: function(el){
    //     this.node = el;
    //     this.node.set({
    //         "id": this.json.id,
    //         "MWFType": this.json.type
    //     });
    //     this._loadDomEvents();
    //     this._afterLoaded();
    //     this.fireEvent("postLoad");
    //     this.fireEvent("load");
    // },
    //     _createElementHtml: function(){
    //         var html = "<i";
    //         html += " :class=\"icon\"";
    //
    //         if (this.json.elProperties){
    //             Object.keys(this.json.elProperties).forEach(function(k){
    //                 if (this.json.elProperties[k]) html += " "+k+"=\""+this.json.elProperties[k]+"\"";
    //             }, this);
    //         }
    //
    //         // var styles = {};
    //         // if (this.json.iconSize) styles["font-size"] = this.json.iconSize+"px";
    //         // if (this.json.iconColor) styles["color"] = this.json.iconColor;
    //         // styles = Object.merge(styles, this.json.elStyles);
    //         //
    //         // if (styles){
    //         //     var style = "";
    //         //     Object.keys(styles).forEach(function(k){
    //         //         if (styles[k]) style += k+":"+styles[k]+";";
    //         //     }, this);
    //         //     html += " style=\""+style+"\"";
    //         // }
    //
    //         html += " :style=\"[elStyles, {fontSize: iconSize+'px', color: iconColor}]\"";
    //
    //
    //         html += ">";
    //         return html;
    //     }

});
<commit_msg>elicon<commit_after>MWF.xDesktop.requireApp("process.Xform", "$ElModule", null, false);
/** @class Elicon 基于Element UI的图标组件。
 * @o2cn 图标组件
 * @example
 * //可以在脚本中获取该组件
 * //方法1：
 * var icon = this.form.get("name"); //获取组件
 * //方法2
 * var icon = this.target; //在组件事件脚本中获取
 * @extends MWF.xApplication.process.Xform.$Module
 * @o2category FormComponents
 * @o2range {Process|CMS|Portal}
 * @hideconstructor
 * @see {@link https://element.eleme.cn/#/zh-CN/component/icon|Element UI Icon 图标}
 */
MWF.xApplication.process.Xform.Elicon = MWF.APPElicon =  new Class(
    /** @lends MWF.xApplication.process.Xform.Elicon# */
    {
    Implements: [Events],
    Extends: MWF.APP$ElModule,
    _appendVueData: function(){
        if (!this.json.icon) this.json.icon = "el-icon-platform-eleme";
        if (!this.json.iconSize) this.json.iconSize = "16";
        if (!this.json.iconColor) this.json.iconColor = "";
        if (!this.json.icon) this.json.icon = "";
        if (!this.json.elStyles) this.json.elStyles = {};
    },
    _createElementHtml: function(){
        var html = "";
        return html;
    }
    // _loadVue: function(callback){
    //     if (!window.Vue){
    //         var vue = (o2.session.isDebugger) ? "vue_develop" : "vue";
    //         o2.loadAll({"css": "../o2_lib/vue/element/index.css", "js": [vue, "elementui"]}, { "sequence": true }, callback);
    //     }else{
    //         if (callback) callback();
    //     }
    // },
    // _queryLoaded: function(){
    //     this._loadVue();
    // },

    // load: function(){
    //     this._loadModuleEvents();
    //     if (this.fireEvent("queryLoad")){
    //         this._queryLoaded();
    //         this._loadUserInterface();
    //     }
    // },
    //
    // _loadUserInterface: function(){
    //     this.node.appendHTML(this._createElementHtml(), "before");
    //     var icon = this.node.getPrevious();
    //
    //     this.node.destroy();
    //     this.node = icon;
    //     this.node.set({
    //         "id": this.json.id,
    //         "MWFType": this.json.type
    //     });
    //     this._createVueApp();
    // },
    // _createVueApp: function(){
    //     debugger;
    //     if (!this.vm) this._loadVue(this._mountVueApp.bind(this));
    // },
    //
    // _loadVue: function(callback){
    //     if (!window.Vue){
    //         o2.loadAll({"css": "../o2_lib/vue/element/index.css", "js": ["vue", "elementui"]}, { "sequence": true }, callback);
    //     }else{
    //         if (callback) callback();
    //     }
    // },
    // _mountVueApp: function(){
    //     if (!this.vueApp) this.vueApp = this._createVueExtend();
    //
    //     /**
    //      * @summary Vue对象实例
    //      * @see https://vuejs.org/
    //      * @member {VueInstance}
    //      */
    //     this.vm = new Vue(this.vueApp).$mount(this.node);
    // },
    //
    // _createVueExtend: function(){
    //     var _self = this;
    //     return {
    //         data: this._createVueData(),
    //         mounted: function(){
    //             _self._afterMounted(this.$el);
    //         }
    //     };
    // },
    // _createVueData: function(){
    //     return this.json;
    // },
    // _afterMounted: function(el){
    //     this.node = el;
    //     this.node.set({
    //         "id": this.json.id,
    //         "MWFType": this.json.type
    //     });
    //     this._loadDomEvents();
    //     this._afterLoaded();
    //     this.fireEvent("postLoad");
    //     this.fireEvent("load");
    // },
    //     _createElementHtml: function(){
    //         var html = "<i";
    //         html += " :class=\"icon\"";
    //
    //         if (this.json.elProperties){
    //             Object.keys(this.json.elProperties).forEach(function(k){
    //                 if (this.json.elProperties[k]) html += " "+k+"=\""+this.json.elProperties[k]+"\"";
    //             }, this);
    //         }
    //
    //         // var styles = {};
    //         // if (this.json.iconSize) styles["font-size"] = this.json.iconSize+"px";
    //         // if (this.json.iconColor) styles["color"] = this.json.iconColor;
    //         // styles = Object.merge(styles, this.json.elStyles);
    //         //
    //         // if (styles){
    //         //     var style = "";
    //         //     Object.keys(styles).forEach(function(k){
    //         //         if (styles[k]) style += k+":"+styles[k]+";";
    //         //     }, this);
    //         //     html += " style=\""+style+"\"";
    //         // }
    //
    //         html += " :style=\"[elStyles, {fontSize: iconSize+'px', color: iconColor}]\"";
    //
    //
    //         html += ">";
    //         return html;
    //     }

});
<|endoftext|>


<commit_before>import { uniqId, assign, normalizeRules, setDataAttribute, toggleClass, getInputEventName, debounce, isCallable, warn, toArray } from './utils';
import Generator from './generator';

const DEFAULT_OPTIONS = {
  targetOf: null,
  initial: false,
  scope: null,
  listen: true,
  name: null,
  active: true,
  required: false,
  rules: {},
  vm: null,
  classes: false,
  events: 'input|blur',
  delay: 0,
  classNames: {
    touched: 'touched', // the control has been blurred
    untouched: 'untouched', // the control hasn't been blurred
    valid: 'valid', // model is valid
    invalid: 'invalid', // model is invalid
    pristine: 'pristine', // control has not been interacted with
    dirty: 'dirty' // control has been interacted with
  }
};

/**
 * Generates the default flags for the field.
 * @param {Object} options
 */
const generateFlags = (options) => ({
  untouched: true,
  touched: false,
  dirty: false,
  pristine: true,
  valid: null,
  invalid: null,
  validated: false,
  pending: false,
  required: !!options.rules.required
});

export default class Field {
  constructor (el, options = {}) {
    this.id = uniqId();
    this.el = el;
    this.updated = false;
    this.dependencies = [];
    this.watchers = [];
    this.events = [];
    this.rules = {};
    if (!this.isHeadless && !(this.targetOf || options.targetOf)) {
      setDataAttribute(this.el, 'id', this.id); // cache field id if it is independent and has a root element.
    }
    options = assign({}, DEFAULT_OPTIONS, options);
    this.flags = generateFlags(options);
    this.vm = options.vm || this.vm;
    this.component = options.component || this.component;
    this.update(options);
    this.updated = false;
  }

  get isVue () {
    return !!this.component;
  }

  get validator () {
    if (!this.vm || !this.vm.$validator) {
      warn('No validator instance detected.');
      return { validate: () => {} };
    }

    return this.vm.$validator;
  }

  get isRequired () {
    return !!this.rules.required;
  }

  get isDisabled () {
    return (this.isVue && this.component.disabled) || (this.el && this.el.disabled);
  }

  get isHeadless () {
    return !this.el;
  }

  /**
   * Gets the display name (user-friendly name).
   * @return {String}
   */
  get displayName () {
    return this.alias;
  }

  /**
   * Gets the input value.
   * @return {*}
   */
  get value () {
    if (!isCallable(this.getter)) {
      return undefined;
    }

    return this.getter();
  }

  /**
   * If the field rejects false as a valid value for the required rule.
   */
  get rejectsFalse () {
    if (this.isVue || this.isHeadless) {
      return false;
    }

    return this.el.type === 'checkbox';
  }

  /**
   * Determines if the instance matches the options provided.
   * @param {Object} options The matching options.
   */
  matches (options) {
    if (options.id) {
      return this.id === options.id;
    }

    if (options.name === undefined && options.scope === undefined) {
      return true;
    }

    if (options.scope === undefined) {
      return this.name === options.name;
    }

    if (options.name === undefined) {
      return this.scope === options.scope;
    }

    return options.name === this.name && options.scope === this.scope;
  }

  /**
   *
   * @param {Object} options
   */
  update (options) {
    this.targetOf = options.targetOf || null;
    this.initial = options.initial || this.initial || false;

    // update errors scope if the field scope was changed.
    if (options.scope && options.scope !== this.scope && this.validator.errors && isCallable(this.validator.errors.update)) {
      this.validator.errors.update(this.id, { scope: this.scope });
    }

    this.scope = options.scope || this.scope || null;
    this.name = options.name || this.name || null;
    this.rules = options.rules ? normalizeRules(options.rules) : this.rules;
    this.model = options.model || this.model;
    this.listen = options.listen !== undefined ? options.listen : this.listen;
    this.classes = options.classes || this.classes || false;
    this.classNames = options.classNames || this.classNames || DEFAULT_OPTIONS.classNames;
    this.alias = options.alias || this.alias;
    this.getter = isCallable(options.getter) ? options.getter : this.getter;
    this.delay = options.delay || this.delay || 0;
    this.events = typeof options.events === 'string' && options.events.length ? options.events.split('|') : this.events;
    this.updateDependencies();
    this.addActionListeners();

    // validate if it was validated before and field was updated and there was a rules mutation.
    if (this.flags.validated && options.rules && this.updated) {
      this.validator.validate(`#${this.id}`);
    }

    this.updated = true;

    // no need to continue.
    if (this.isHeadless) {
      return;
    };

    this.updateClasses();
    this.addValueListeners();
    this.updateAriaAttrs();
  }

  /**
   * Determines if the field requires references to target fields.
  */
  updateDependencies () {
    // reset dependencies.
    this.dependencies.forEach(d => d.field.destroy());
    this.dependencies = [];

    // we get the selectors for each field.
    const fields = Object.keys(this.rules).reduce((prev, r) => {
      if (r === 'confirmed') {
        prev.push({ selector: this.rules[r][0] || `${this.name}_confirmation`, name: r });
      } else if (/after|before/.test(r)) {
        prev.push({ selector: this.rules[r][0], name: r });
      }

      return prev;
    }, []);

    if (!fields.length || !this.vm || !this.vm.$el) return;

    // must be contained within the same component, so we use the vm root element constrain our dom search.
    fields.forEach(({ selector, name }) => {
      let el = null;
      // vue ref selector.
      if (selector[0] === '$') {
        el = this.vm.$refs[selector.slice(1)];
      } else {
        // try a query selection.
        el = this.vm.$el.querySelector(selector);
      }

      if (!el) {
        // try a name selector
        el = this.vm.$el.querySelector(`input[name="${selector}"]`);
      }

      if (!el) {
        return;
      }

      const options = {
        vm: this.vm,
        classes: this.classes,
        classNames: this.classNames,
        delay: this.delay,
        scope: this.scope,
        events: this.events.join('|'),
        initial: this.initial,
        targetOf: this.id
      };

      // probably a component.
      if (isCallable(el.$watch)) {
        options.component = el;
        options.el = el.$el;
        options.getter = Generator.resolveGetter(el.$el, { child: el });
      } else {
        options.el = el;
        options.getter = Generator.resolveGetter(el, {});
      }

      this.dependencies.push({ name, field: new Field(options.el, options) });
    });
  }

  /**
   * Removes listeners.
   * @param {RegExp} tag
   */
  unwatch (tag) {
    if (!tag) {
      this.watchers.forEach(w => w.unwatch());
      this.watchers = [];
      return;
    }
    this.watchers.filter(w => tag.test(w.tag)).forEach(w => w.unwatch());
    this.watchers = this.watchers.filter(w => !tag.test(w.tag));
  }

  /**
   * Updates the element classes depending on each field flag status.
   */
  updateClasses () {
    if (!this.classes) return;

    toggleClass(this.el, this.classNames.dirty, this.flags.dirty);
    toggleClass(this.el, this.classNames.pristine, this.flags.pristine);
    toggleClass(this.el, this.classNames.valid, !!this.flags.valid);
    toggleClass(this.el, this.classNames.invalid, !!this.flags.invalid);
    toggleClass(this.el, this.classNames.touched, this.flags.touched);
    toggleClass(this.el, this.classNames.untouched, this.flags.untouched);
  }

  /**
   * Adds the listeners required for automatic classes and some flags.
   */
  addActionListeners () {
    // remove previous listeners.
    this.unwatch(/class/);

    const onBlur = () => {
      this.flags.touched = true;
      this.flags.untouched = false;
      if (this.classes) {
        toggleClass(this.el, this.classNames.touched, true);
        toggleClass(this.el, this.classNames.untouched, false);
      }

      // only needed once.
      this.unwatch(/^class_blur$/);
    };

    const inputEvent = getInputEventName(this.el);
    const onInput = () => {
      this.flags.dirty = true;
      this.flags.pristine = false;
      if (this.classes) {
        toggleClass(this.el, this.classNames.pristine, false);
        toggleClass(this.el, this.classNames.dirty, true);
      }

      // only needed once.
      this.unwatch(/^class_input$/);
    };

    if (this.isVue && isCallable(this.component.$once)) {
      this.component.$once('input', onInput);
      this.component.$once('blur', onBlur);
      this.watchers.push({
        tag: 'class_input',
        unwatch: () => {
          this.component.$off('input', onInput);
        }
      });
      this.watchers.push({
        tag: 'class_blur',
        unwatch: () => {
          this.component.$off('blur', onBlur);
        }
      });
      return;
    }

    if (this.isHeadless) return;

    this.el.addEventListener(inputEvent, onInput);
    // Checkboxes and radio buttons on Mac don't emit blur naturally, so we listen on click instead.
    const blurEvent = ['radio', 'checkbox'].indexOf(this.el.type) === -1 ? 'blur' : 'click';
    this.el.addEventListener(blurEvent, onBlur);
    this.watchers.push({
      tag: 'class_input',
      unwatch: () => {
        this.el.removeEventListener(inputEvent, onInput);
      }
    });

    this.watchers.push({
      tag: 'class_blur',
      unwatch: () => {
        this.el.removeEventListener(blurEvent, onBlur);
      }
    });
  }

  /**
   * Adds the listeners required for validation.
   */
  addValueListeners () {
    this.unwatch(/^input_.+/);
    if (!this.listen) return;

    let fn = null;
    if (this.targetOf) {
      fn = () => {
        this.validator.validate(`#${this.targetOf}`);
      };
    } else {
      fn = (...args) => {
        if (args.length === 0 || args[0] instanceof Event) {
          args[0] = this.value;
        }
        this.validator.validate(`#${this.id}`, args[0]);
      };
    }
    const validate = debounce(fn, this.delay);

    const inputEvent = getInputEventName(this.el);
    // replace input event with suitable one.
    let events = this.events.map(e => {
      return e === 'input' ? inputEvent : e;
    });

    // if there is a watchable model and an on input validation is requested.
    if (this.model && events.indexOf(inputEvent) !== -1) {
      const unwatch = this.vm.$watch(this.model, validate);
      this.watchers.push({
        tag: 'input_model',
        unwatch
      });
      // filter out input event as it is already handled by the watcher API.
      events = events.filter(e => e !== inputEvent);
    }

    // Add events.
    events.forEach(e => {
      if (this.isVue) {
        this.component.$on(e, validate);
        this.watchers.push({
          tag: 'input_vue',
          unwatch: () => {
            this.component.$off(e, validate);
          }
        });
        return;
      }

      if (~['radio', 'checkbox'].indexOf(this.el.type)) {
        const els = document.querySelectorAll(`input[name="${this.el.name}"]`);
        toArray(els).forEach(el => {
          el.addEventListener(e, validate);
          this.watchers.push({
            tag: 'input_native',
            unwatch: () => {
              el.removeEventListener(e, validate);
            }
          });
        });

        return;
      }

      this.el.addEventListener(e, validate);
      this.watchers.push({
        tag: 'input_native',
        unwatch: () => {
          this.el.removeEventListener(e, validate);
        }
      });
    });
  }

  /**
   * Updates aria attributes on the element.
   */
  updateAriaAttrs () {
    if (this.isHeadless || !isCallable(this.el.setAttribute)) return;

    this.el.setAttribute('aria-required', this.isRequired ? 'true' : 'false');
    this.el.setAttribute('aria-invalid', this.flags.invalid ? 'true' : 'false');
  }

  /**
   * Updates the custom validity for the field.
   */
  updateCustomValidity () {
    if (this.isHeadless || !isCallable(this.el.setCustomValidity)) return;

    this.el.setCustomValidity(this.flags.valid ? '' : (this.validator.errors.firstById(this.id) || ''));
  }

  /**
   * Removes all listeners.
   */
  destroy () {
    this.watchers.forEach(w => w.unwatch());
    this.watchers = [];
    this.dependencies.forEach(d => d.field.destroy());
    this.dependencies = [];
  }
}
<commit_msg>make sure event is a ctor closes #730<commit_after>import { uniqId, assign, normalizeRules, setDataAttribute, toggleClass, getInputEventName, debounce, isCallable, warn, toArray } from './utils';
import Generator from './generator';

const DEFAULT_OPTIONS = {
  targetOf: null,
  initial: false,
  scope: null,
  listen: true,
  name: null,
  active: true,
  required: false,
  rules: {},
  vm: null,
  classes: false,
  events: 'input|blur',
  delay: 0,
  classNames: {
    touched: 'touched', // the control has been blurred
    untouched: 'untouched', // the control hasn't been blurred
    valid: 'valid', // model is valid
    invalid: 'invalid', // model is invalid
    pristine: 'pristine', // control has not been interacted with
    dirty: 'dirty' // control has been interacted with
  }
};

/**
 * Generates the default flags for the field.
 * @param {Object} options
 */
const generateFlags = (options) => ({
  untouched: true,
  touched: false,
  dirty: false,
  pristine: true,
  valid: null,
  invalid: null,
  validated: false,
  pending: false,
  required: !!options.rules.required
});

export default class Field {
  constructor (el, options = {}) {
    this.id = uniqId();
    this.el = el;
    this.updated = false;
    this.dependencies = [];
    this.watchers = [];
    this.events = [];
    this.rules = {};
    if (!this.isHeadless && !(this.targetOf || options.targetOf)) {
      setDataAttribute(this.el, 'id', this.id); // cache field id if it is independent and has a root element.
    }
    options = assign({}, DEFAULT_OPTIONS, options);
    this.flags = generateFlags(options);
    this.vm = options.vm || this.vm;
    this.component = options.component || this.component;
    this.update(options);
    this.updated = false;
  }

  get isVue () {
    return !!this.component;
  }

  get validator () {
    if (!this.vm || !this.vm.$validator) {
      warn('No validator instance detected.');
      return { validate: () => {} };
    }

    return this.vm.$validator;
  }

  get isRequired () {
    return !!this.rules.required;
  }

  get isDisabled () {
    return (this.isVue && this.component.disabled) || (this.el && this.el.disabled);
  }

  get isHeadless () {
    return !this.el;
  }

  /**
   * Gets the display name (user-friendly name).
   * @return {String}
   */
  get displayName () {
    return this.alias;
  }

  /**
   * Gets the input value.
   * @return {*}
   */
  get value () {
    if (!isCallable(this.getter)) {
      return undefined;
    }

    return this.getter();
  }

  /**
   * If the field rejects false as a valid value for the required rule.
   */
  get rejectsFalse () {
    if (this.isVue || this.isHeadless) {
      return false;
    }

    return this.el.type === 'checkbox';
  }

  /**
   * Determines if the instance matches the options provided.
   * @param {Object} options The matching options.
   */
  matches (options) {
    if (options.id) {
      return this.id === options.id;
    }

    if (options.name === undefined && options.scope === undefined) {
      return true;
    }

    if (options.scope === undefined) {
      return this.name === options.name;
    }

    if (options.name === undefined) {
      return this.scope === options.scope;
    }

    return options.name === this.name && options.scope === this.scope;
  }

  /**
   *
   * @param {Object} options
   */
  update (options) {
    this.targetOf = options.targetOf || null;
    this.initial = options.initial || this.initial || false;

    // update errors scope if the field scope was changed.
    if (options.scope && options.scope !== this.scope && this.validator.errors && isCallable(this.validator.errors.update)) {
      this.validator.errors.update(this.id, { scope: this.scope });
    }

    this.scope = options.scope || this.scope || null;
    this.name = options.name || this.name || null;
    this.rules = options.rules ? normalizeRules(options.rules) : this.rules;
    this.model = options.model || this.model;
    this.listen = options.listen !== undefined ? options.listen : this.listen;
    this.classes = options.classes || this.classes || false;
    this.classNames = options.classNames || this.classNames || DEFAULT_OPTIONS.classNames;
    this.alias = options.alias || this.alias;
    this.getter = isCallable(options.getter) ? options.getter : this.getter;
    this.delay = options.delay || this.delay || 0;
    this.events = typeof options.events === 'string' && options.events.length ? options.events.split('|') : this.events;
    this.updateDependencies();
    this.addActionListeners();

    // validate if it was validated before and field was updated and there was a rules mutation.
    if (this.flags.validated && options.rules && this.updated) {
      this.validator.validate(`#${this.id}`);
    }

    this.updated = true;

    // no need to continue.
    if (this.isHeadless) {
      return;
    };

    this.updateClasses();
    this.addValueListeners();
    this.updateAriaAttrs();
  }

  /**
   * Determines if the field requires references to target fields.
  */
  updateDependencies () {
    // reset dependencies.
    this.dependencies.forEach(d => d.field.destroy());
    this.dependencies = [];

    // we get the selectors for each field.
    const fields = Object.keys(this.rules).reduce((prev, r) => {
      if (r === 'confirmed') {
        prev.push({ selector: this.rules[r][0] || `${this.name}_confirmation`, name: r });
      } else if (/after|before/.test(r)) {
        prev.push({ selector: this.rules[r][0], name: r });
      }

      return prev;
    }, []);

    if (!fields.length || !this.vm || !this.vm.$el) return;

    // must be contained within the same component, so we use the vm root element constrain our dom search.
    fields.forEach(({ selector, name }) => {
      let el = null;
      // vue ref selector.
      if (selector[0] === '$') {
        el = this.vm.$refs[selector.slice(1)];
      } else {
        // try a query selection.
        el = this.vm.$el.querySelector(selector);
      }

      if (!el) {
        // try a name selector
        el = this.vm.$el.querySelector(`input[name="${selector}"]`);
      }

      if (!el) {
        return;
      }

      const options = {
        vm: this.vm,
        classes: this.classes,
        classNames: this.classNames,
        delay: this.delay,
        scope: this.scope,
        events: this.events.join('|'),
        initial: this.initial,
        targetOf: this.id
      };

      // probably a component.
      if (isCallable(el.$watch)) {
        options.component = el;
        options.el = el.$el;
        options.getter = Generator.resolveGetter(el.$el, { child: el });
      } else {
        options.el = el;
        options.getter = Generator.resolveGetter(el, {});
      }

      this.dependencies.push({ name, field: new Field(options.el, options) });
    });
  }

  /**
   * Removes listeners.
   * @param {RegExp} tag
   */
  unwatch (tag) {
    if (!tag) {
      this.watchers.forEach(w => w.unwatch());
      this.watchers = [];
      return;
    }
    this.watchers.filter(w => tag.test(w.tag)).forEach(w => w.unwatch());
    this.watchers = this.watchers.filter(w => !tag.test(w.tag));
  }

  /**
   * Updates the element classes depending on each field flag status.
   */
  updateClasses () {
    if (!this.classes) return;

    toggleClass(this.el, this.classNames.dirty, this.flags.dirty);
    toggleClass(this.el, this.classNames.pristine, this.flags.pristine);
    toggleClass(this.el, this.classNames.valid, !!this.flags.valid);
    toggleClass(this.el, this.classNames.invalid, !!this.flags.invalid);
    toggleClass(this.el, this.classNames.touched, this.flags.touched);
    toggleClass(this.el, this.classNames.untouched, this.flags.untouched);
  }

  /**
   * Adds the listeners required for automatic classes and some flags.
   */
  addActionListeners () {
    // remove previous listeners.
    this.unwatch(/class/);

    const onBlur = () => {
      this.flags.touched = true;
      this.flags.untouched = false;
      if (this.classes) {
        toggleClass(this.el, this.classNames.touched, true);
        toggleClass(this.el, this.classNames.untouched, false);
      }

      // only needed once.
      this.unwatch(/^class_blur$/);
    };

    const inputEvent = getInputEventName(this.el);
    const onInput = () => {
      this.flags.dirty = true;
      this.flags.pristine = false;
      if (this.classes) {
        toggleClass(this.el, this.classNames.pristine, false);
        toggleClass(this.el, this.classNames.dirty, true);
      }

      // only needed once.
      this.unwatch(/^class_input$/);
    };

    if (this.isVue && isCallable(this.component.$once)) {
      this.component.$once('input', onInput);
      this.component.$once('blur', onBlur);
      this.watchers.push({
        tag: 'class_input',
        unwatch: () => {
          this.component.$off('input', onInput);
        }
      });
      this.watchers.push({
        tag: 'class_blur',
        unwatch: () => {
          this.component.$off('blur', onBlur);
        }
      });
      return;
    }

    if (this.isHeadless) return;

    this.el.addEventListener(inputEvent, onInput);
    // Checkboxes and radio buttons on Mac don't emit blur naturally, so we listen on click instead.
    const blurEvent = ['radio', 'checkbox'].indexOf(this.el.type) === -1 ? 'blur' : 'click';
    this.el.addEventListener(blurEvent, onBlur);
    this.watchers.push({
      tag: 'class_input',
      unwatch: () => {
        this.el.removeEventListener(inputEvent, onInput);
      }
    });

    this.watchers.push({
      tag: 'class_blur',
      unwatch: () => {
        this.el.removeEventListener(blurEvent, onBlur);
      }
    });
  }

  /**
   * Adds the listeners required for validation.
   */
  addValueListeners () {
    this.unwatch(/^input_.+/);
    if (!this.listen) return;

    let fn = null;
    if (this.targetOf) {
      fn = () => {
        this.validator.validate(`#${this.targetOf}`);
      };
    } else {
      fn = (...args) => {
        if (args.length === 0 || (isCallable(Event) && args[0] instanceof Event)) {
          args[0] = this.value;
        }
        this.validator.validate(`#${this.id}`, args[0]);
      };
    }
    const validate = debounce(fn, this.delay);

    const inputEvent = getInputEventName(this.el);
    // replace input event with suitable one.
    let events = this.events.map(e => {
      return e === 'input' ? inputEvent : e;
    });

    // if there is a watchable model and an on input validation is requested.
    if (this.model && events.indexOf(inputEvent) !== -1) {
      const unwatch = this.vm.$watch(this.model, validate);
      this.watchers.push({
        tag: 'input_model',
        unwatch
      });
      // filter out input event as it is already handled by the watcher API.
      events = events.filter(e => e !== inputEvent);
    }

    // Add events.
    events.forEach(e => {
      if (this.isVue) {
        this.component.$on(e, validate);
        this.watchers.push({
          tag: 'input_vue',
          unwatch: () => {
            this.component.$off(e, validate);
          }
        });
        return;
      }

      if (~['radio', 'checkbox'].indexOf(this.el.type)) {
        const els = document.querySelectorAll(`input[name="${this.el.name}"]`);
        toArray(els).forEach(el => {
          el.addEventListener(e, validate);
          this.watchers.push({
            tag: 'input_native',
            unwatch: () => {
              el.removeEventListener(e, validate);
            }
          });
        });

        return;
      }

      this.el.addEventListener(e, validate);
      this.watchers.push({
        tag: 'input_native',
        unwatch: () => {
          this.el.removeEventListener(e, validate);
        }
      });
    });
  }

  /**
   * Updates aria attributes on the element.
   */
  updateAriaAttrs () {
    if (this.isHeadless || !isCallable(this.el.setAttribute)) return;

    this.el.setAttribute('aria-required', this.isRequired ? 'true' : 'false');
    this.el.setAttribute('aria-invalid', this.flags.invalid ? 'true' : 'false');
  }

  /**
   * Updates the custom validity for the field.
   */
  updateCustomValidity () {
    if (this.isHeadless || !isCallable(this.el.setCustomValidity)) return;

    this.el.setCustomValidity(this.flags.valid ? '' : (this.validator.errors.firstById(this.id) || ''));
  }

  /**
   * Removes all listeners.
   */
  destroy () {
    this.watchers.forEach(w => w.unwatch());
    this.watchers = [];
    this.dependencies.forEach(d => d.field.destroy());
    this.dependencies = [];
  }
}
<|endoftext|>


<commit_before>const controlTypes = [
  'bool',
  'string',
  'number',
  'integer',
  'enum',
  'node',
  'avatar',
  'name',
  'func',
  'shape',
  'arrayOf',
];

export default controlTypes;
<commit_msg>Disable control types<commit_after>// Selecting arrays or objects is currently unsupported due to UI problems.
// Ref: https://github.com/pure-ui/carte-blanche/pull/210

const controlTypes = [
  'bool',
  'string',
  'number',
  'integer',
  'enum',
  'node',
  'avatar',
  'name',
  'func',
];

export default controlTypes;
<|endoftext|>


<commit_before>const normalDistribution = require("./normalDistribution");
const calculateArea = require("./calculateArea");

// Number -> Time -> Seed -> Integer
function goals(eloDifference, timeLength, seed) {
    const mean = 1.58;
    const f = graph(mean, 1.23, 0.00245, eloDifference, timeLength);
    const rand = updateSeed(seed, eloDifference, timeLength).decimal;

    return goals_(f, rand, mean, 0);
}

function goals_(f, rand, mean, goalCount) {
    const area = calculateArea(f, mean - 10, goalCount + 0.5);

    if (rand > area) {
        return goals_(f, rand, mean, goalCount + 1);
    }

    return goalCount;
}

// Number -> Number -> Number -> Number -> Time -> (Number -> Number)
function graph(mean, standardDeviation, constant, eloDifference, timeLength) {
    mean = mean + (constant * eloDifference);

    const decimal = timeLength.minutes / 90;

    const meanScaled = mean * decimal;
    const sdScaled = standardDeviation * decimal;

    return x => normalDistribution(meanScaled, sdScaled, x);
}

// Seed -> Number -> Time -> Seed
function updateSeed(seed, eloDifference, timeLength) {
    return seed
        .append(eloDifference)
        .append(timeLength.milliseconds);
}

module.exports = goals;
<commit_msg>Adds type annotation to goals_<commit_after>const normalDistribution = require("./normalDistribution");
const calculateArea = require("./calculateArea");

// Number -> Time -> Seed -> Integer
function goals(eloDifference, timeLength, seed) {
    const mean = 1.58;
    const f = graph(mean, 1.23, 0.00245, eloDifference, timeLength);
    const rand = updateSeed(seed, eloDifference, timeLength).decimal;

    return goals_(f, rand, mean, 0);
}

// (Number -> Number) -> Number -> Number -> Number -> Number
function goals_(f, rand, mean, goalCount) {
    const area = calculateArea(f, mean - 10, goalCount + 0.5);

    if (rand > area) {
        return goals_(f, rand, mean, goalCount + 1);
    }

    return goalCount;
}

// Number -> Number -> Number -> Number -> Time -> (Number -> Number)
function graph(mean, standardDeviation, constant, eloDifference, timeLength) {
    mean = mean + (constant * eloDifference);

    const decimal = timeLength.minutes / 90;

    const meanScaled = mean * decimal;
    const sdScaled = standardDeviation * decimal;

    return x => normalDistribution(meanScaled, sdScaled, x);
}

// Seed -> Number -> Time -> Seed
function updateSeed(seed, eloDifference, timeLength) {
    return seed
        .append(eloDifference)
        .append(timeLength.milliseconds);
}

module.exports = goals;
<|endoftext|>


<commit_before>// constructor
var Graph = function() {
  // car data and each individual car2go found in portland by license
  this.cars = window.cars;
  this.ids = Object.keys(this.cars);

  // the number of days tracked
  this.nDays = window.data.numDays;

  // the timestamp intervals in ms being tracked
  this.interval = 600000; // 10 minutes

  // the interval to update the graph with
  this.speed = 200;

  // the start of it
  this.start = new Date(data.start);
  // set the timestamp to be a clone of a date object at the first start
  this.timestamp = +this.start;
  this.end = data.end;

  // get elements
  this.gather();
  // attach to elements
  this.attach();

  // get configuration for mapreduce and calculate
  this.stats = this.defineStats();
};

// gather elements & element-related functions
Graph.prototype.gather = function() {
  // parent element
  this.container = d3.select('#map');
  // create an svg element
  this.svg = this.svg ||
    this.container.append('svg').attr('id', 'map-graph');

  // the count text
  this.count = d3.select('#count');

  // time elements
  this.day = {
    element: d3.select('#day'),
    // {day of week}
    format: d3.time.format('%A')
  };
  this.time = {
    element: d3.select('#time'),
    // {12hour,space padded:minute} {AM/PM}
    format: d3.time.format('%_I:%M %p')
  };

  // timeline element
  this.timeline = d3.select('#timeline');
  // height of the timeline element
  this.timeline.height = parseInt(
    this.timeline.style('height').replace(/px/, '')
  );

  // collect scales
  this.getScales();
};

// collect scales based on browser size
Graph.prototype.getScales = function() {
  this.xscale = d3.scale.linear()
    .domain([+this.start, this.end])
    .range([0, window.innerWidth]);
};

// attach events to handlers
Graph.prototype.attach = function() {
  var thiz = this;
  var timeline = this.timeline.node();

  timeline.addEventListener('mousemove', throttle(function(e) {
    var x = e.clientX;
    var timestamp = thiz.xscale.invert(x);
    thiz.timestamp = +new Date(timestamp).getTenth();

    thiz.stopAnimating = true;
    thiz.update(thiz.timestamp);
  }, 15));

  this.container.node().addEventListener('mouseover', function() {
    thiz.stopAnimating = false;
    thiz.animate();
  });

  window.addEventListener('resize', throttle(function() {
    // update the timeline
    thiz.getScales();
    thiz.drawTimepath();
    thiz.drawTimeline();
  }, 25));
};

// draws the axis and ticks under the map
Graph.prototype.drawTimeline = function() {
  // clear any ticks previously drawn
  this.timeline.select('.tick-container').remove();

  var start = +this.start;
  var end = this.end;

  var container = this.timeline.append('svg:g')
    .attr({
      'class': 'tick-container',
    })

  // number of ticks we need
  var ticks = (end - start) / this.interval;

  // iterate from the first interval to the number of ticks found
  // don't start at 0 to prevent drawing a tick on the left side of the page
  for (var i = 1; i < ticks; i++) {
    var attrs = {};

    // small tick = every % 6 (1 hour)
    // big tick = every % 36 (6 hours)
    // divider = every day % 144 (24 hours)
    if (i % 144 == 0) {
      attrs = {
        'class': 'divider',
        'y1': this.timeline.height - 30
      };
    }
    else if (i % 36 == 0) {
      attrs = {
        'class': 'big-tick',
        'y1': this.timeline.height - 15
      };
    }
    else if (i % 6 == 0) {
      attrs = {
        'class': 'small-tick',
        'y1': this.timeline.height - 5
      }
    }

    // if we found an interval for the tick
    if (attrs.y1 != null) {
      // get the x-coordinate from the tick scale
      var x = this.xscale(start + (i * this.interval));

      // append a tick mark
      container.append('svg:line')
        .attr(attrs)
        .attr({
          'y2': this.timeline.height,
          'x1': x,
          'x2': x
        })
    }
  }

  // draw a timeline marker
  var marker = this.timeline.append('svg:line')
    .attr({
      'class': 'time-marker',
      'y1': this.timeline.height - 20,
      'y2': this.timeline.height
    });
};

// draws the path element in the timeline
Graph.prototype.drawTimepath = function() {
  var thiz = this;

  // yscale from 0 number cars found to the max num of cars is the
  // total number found in the dataset
  var yscale = d3.scale.linear()
    .domain([100, this.ids.length])
    .range([this.timeline.height, 0]);

  // path function
  var path = d3.svg.line()
    .x(this.xscale)
    .y(function(d) {
      thiz.timestamp = d;
      return yscale(thiz.calculate().numCars);
    });

  // area function
  var area = d3.svg.area()
    .x(this.xscale)
    .y0(this.timeline.height)
    .y1(function(d) {
      thiz.timestamp = d;
      return yscale(thiz.calculate().numCars);
    });

  // generate a range of time intervals between the start and end
  var timestamps = d3.range(+this.start, this.end, this.interval);

  var generatePath = function(className, dFunction) {
    // remove any existing path first
    thiz.timeline.selectAll('path.' + className).remove();
    return thiz.timeline.selectAll('path.' + className)
      .data([timestamps])
      .enter()
      .append('svg:path')
      .attr({
        'class': className,
        'd': dFunction
      });
  };

  generatePath('count-area', area);
  // generatePath('count-path', path);

  // reset timestamp to beginning
  this.timestamp = +this.start;
};

Graph.prototype.animate = function(start, speed) {
  var thiz = this;
  // clear any existing animation loop
  if (this['animation'] != null)
    this.animation = clearTimeout(this.animation);

  this.timestamp = start || this.timestamp;
  speed = speed || this.speed;

  // draw all cars hidden to start
  this.circles = this.circles || this.svg.selectAll('.car')
    .data(this.ids)
    .enter()
    .append('svg:circle')
    .attr({
      'class': 'car hide',
      'r': 5,
      'data-id': function(car) {
        return car;
      }
    });

  // set a timeout that can be cleared if this is being called again
  var loop = function() {
    thiz.update(thiz.timestamp, speed);
    thiz.timestamp += thiz.interval;

    thiz.animation = thiz.stopAnimating == true ?
      // set the animation to undefined if told to stop udating
      clearTimeout(thiz.animation) :
      // otherwise, keep animating
      setTimeout(loop, speed);
  };
  loop();
};

// update the graph components and car positions
Graph.prototype.update = function(timestamp, speed) {
  timestamp = timestamp || this.timestamp;
  speed = speed || this.speed; // FIXME for 0

  var marker = this.timeline.select('.time-marker');

  // don't try to update if we're at a time beyond what we'll have data for
  if (timestamp > +Date.nowsTenth) return;

  // redraw all the cars with the current timestamp
  this.updateCars(timestamp);

  // update mapreduce stats
  this.data = this.calculate();

  // update text stats
  var date = new Date(timestamp);
  this.day.element.text(this.day.format(date));
  this.time.element.text(this.time.format(date));

  // update the position of the timeline marker
  var x = this.xscale(timestamp);

  // only animate when we're not updating FIXME
  var m = this.stopAnimating ?
    marker : marker.transition().duration(speed).ease('linear');

  m.attr({
    'x1': x,
    'x2': x
  });
}

// updates the car positions according to the given timestamp
Graph.prototype.updateCars = function(timestamp) {
  var thiz = this;
  timestamp = timestamp || +this.ago;

  // updates an individual car by it's id
  var update = function(car) {
    var loc = thiz.cars[car][timestamp];
    var circle = d3.select(this);

    // the car doesn't have a location for the timestamp
    if (loc == null) {
      // if we're not animating and the car doesn't have a location, hide it
      if (thiz.stopAnimating == true) {
        circle
          .classed('moving', false)
          .classed('hide', true);
        return;
      }
      // else if we're already moving, don't touch
      else if (circle.classed('moving')) return;

      // get an index of where the previous timestamp occured
      var prevTimestamp = circle.attr('data-timestamp');
      var index = thiz.cars[car].locations.indexOf(prevTimestamp);

      // find the next index
      var nextLocTimestamp = thiz.cars[car].locations[index + 1];
      // find the next location
      var nextLoc = thiz.cars[car][nextLocTimestamp];

      // if there is no next timestamp (ie, the car is unavailable and
      // there we are still looping), the car should be hidden
      if (nextLoc == null) {
        circle.classed('hide', true);
      }
      // if the next location found is over 2 hours away, prevent from
      // drifting the car slowly to it's next found location. it's not
      // available at the current timestamp so hide it until..
      else if (+nextLocTimestamp - +prevTimestamp >= (2*Date.hour)) {
        circle.classed('hide', true);
      }
      // transfer the car towards it's next location
      else {
        // start the car moving and show it
        circle.classed({ moving: true, hide: false });
        // the difference in intervals between the next found timestamp
        var intervals = (nextLocTimestamp - timestamp) / thiz.interval;
        // next coords
        var coords = thiz.getCoords(nextLoc);

        // start the animation to the next location
        circle.transition()
          .ease('linear')
          .duration(thiz.speed * intervals)
          .attr({
            cx: coords.x,
            cy: coords.y
          })
          .each('end', function() {
            d3.select(this).classed('moving', false);
          });
      }
    }
    else {
      // should only occur the first time the car is available, so
      // show it and put it in it's location
      var coords = thiz.getCoords(loc);
      // end any existing transitions
      circle.transition();
      // move the circle to it's location
      circle
        // show the car and remove the moving class
        .classed({ hide: false, moving: false })
        .attr({
          cx: coords.x,
          cy: coords.y,
          'data-timestamp': timestamp
        });
    }
  };

  this.circles.each(update);
};

// return an { x: x, y: y } object from a single location data
Graph.prototype.getCoords = function(location) {
  var coords = {
    lon: location[0],
    lat: location[1]
  };
  return window.map.locationPoint(coords);
};

// calculations on data
Graph.prototype.calculate = function(data, stats) {
  // default object of data to mapreduce on
  data = data || this.cars;
  // default stats object with mappings and reductions keys
  stats = stats || this.stats || this.defineStats();

  return mapreduce(data, stats.mappings, stats.reductions);
};

// a schema to mapreduce the dataset on
Graph.prototype.defineStats = function() {
  var thiz = this;

  return {
    mappings: {
      // boolean value if there was a location found for the current
      // configured timestamp
      numCars: function(car) {
        return car[thiz.timestamp] != null;
      }
    },

    reductions: {
      // return a count for the number of cars found with a location
      // at the current timestamp
      numCars: function(sum, haslocation) {
        sum = sum || 0;
        if (haslocation) sum += 1;
        return sum;
      }
    }
  }
}
<commit_msg>don't infinitely loop when complete<commit_after>// constructor
var Graph = function() {
  // car data and each individual car2go found in portland by license
  this.cars = window.cars;
  this.ids = Object.keys(this.cars);

  // the number of days tracked
  this.nDays = window.data.numDays;

  // the timestamp intervals in ms being tracked
  this.interval = 600000; // 10 minutes

  // the interval to update the graph with
  this.speed = 200;

  // the start of it
  this.start = new Date(data.start);
  // set the timestamp to be a clone of a date object at the first start
  this.timestamp = +this.start;
  this.end = data.end;

  // get elements
  this.gather();
  // attach to elements
  this.attach();

  // get configuration for mapreduce and calculate
  this.stats = this.defineStats();
};

// gather elements & element-related functions
Graph.prototype.gather = function() {
  // parent element
  this.container = d3.select('#map');
  // create an svg element
  this.svg = this.svg ||
    this.container.append('svg').attr('id', 'map-graph');

  // the count text
  this.count = d3.select('#count');

  // time elements
  this.day = {
    element: d3.select('#day'),
    // {day of week}
    format: d3.time.format('%A')
  };
  this.time = {
    element: d3.select('#time'),
    // {12hour,space padded:minute} {AM/PM}
    format: d3.time.format('%_I:%M %p')
  };

  // timeline element
  this.timeline = d3.select('#timeline');
  // height of the timeline element
  this.timeline.height = parseInt(
    this.timeline.style('height').replace(/px/, '')
  );

  // collect scales
  this.getScales();
};

// collect scales based on browser size
Graph.prototype.getScales = function() {
  this.xscale = d3.scale.linear()
    .domain([+this.start, this.end])
    .range([0, window.innerWidth]);
};

// attach events to handlers
Graph.prototype.attach = function() {
  var thiz = this;
  var timeline = this.timeline.node();

  timeline.addEventListener('mousemove', throttle(function(e) {
    var x = e.clientX;
    var timestamp = thiz.xscale.invert(x);
    thiz.timestamp = +new Date(timestamp).getTenth();

    thiz.stopAnimating = true;
    thiz.update(thiz.timestamp);
  }, 15));

  this.container.node().addEventListener('mouseover', function() {
    thiz.stopAnimating = false;
    thiz.animate();
  });

  window.addEventListener('resize', throttle(function() {
    // update the timeline
    thiz.getScales();
    thiz.drawTimepath();
    thiz.drawTimeline();
  }, 25));
};

// draws the axis and ticks under the map
Graph.prototype.drawTimeline = function() {
  // clear any ticks previously drawn
  this.timeline.select('.tick-container').remove();

  var start = +this.start;
  var end = this.end;

  var container = this.timeline.append('svg:g')
    .attr({
      'class': 'tick-container',
    })

  // number of ticks we need
  var ticks = (end - start) / this.interval;

  // iterate from the first interval to the number of ticks found
  // don't start at 0 to prevent drawing a tick on the left side of the page
  for (var i = 1; i < ticks; i++) {
    var attrs = {};

    // small tick = every % 6 (1 hour)
    // big tick = every % 36 (6 hours)
    // divider = every day % 144 (24 hours)
    if (i % 144 == 0) {
      attrs = {
        'class': 'divider',
        'y1': this.timeline.height - 30
      };
    }
    else if (i % 36 == 0) {
      attrs = {
        'class': 'big-tick',
        'y1': this.timeline.height - 15
      };
    }
    else if (i % 6 == 0) {
      attrs = {
        'class': 'small-tick',
        'y1': this.timeline.height - 5
      }
    }

    // if we found an interval for the tick
    if (attrs.y1 != null) {
      // get the x-coordinate from the tick scale
      var x = this.xscale(start + (i * this.interval));

      // append a tick mark
      container.append('svg:line')
        .attr(attrs)
        .attr({
          'y2': this.timeline.height,
          'x1': x,
          'x2': x
        })
    }
  }

  // draw a timeline marker
  var marker = this.timeline.append('svg:line')
    .attr({
      'class': 'time-marker',
      'y1': this.timeline.height - 20,
      'y2': this.timeline.height
    });
};

// draws the path element in the timeline
Graph.prototype.drawTimepath = function() {
  var thiz = this;

  // yscale from 0 number cars found to the max num of cars is the
  // total number found in the dataset
  var yscale = d3.scale.linear()
    .domain([100, this.ids.length])
    .range([this.timeline.height, 0]);

  // path function
  var path = d3.svg.line()
    .x(this.xscale)
    .y(function(d) {
      thiz.timestamp = d;
      return yscale(thiz.calculate().numCars);
    });

  // area function
  var area = d3.svg.area()
    .x(this.xscale)
    .y0(this.timeline.height)
    .y1(function(d) {
      thiz.timestamp = d;
      return yscale(thiz.calculate().numCars);
    });

  // generate a range of time intervals between the start and end
  var timestamps = d3.range(+this.start, this.end, this.interval);

  var generatePath = function(className, dFunction) {
    // remove any existing path first
    thiz.timeline.selectAll('path.' + className).remove();
    return thiz.timeline.selectAll('path.' + className)
      .data([timestamps])
      .enter()
      .append('svg:path')
      .attr({
        'class': className,
        'd': dFunction
      });
  };

  generatePath('count-area', area);
  // generatePath('count-path', path);

  // reset timestamp to beginning
  this.timestamp = +this.start;
};

Graph.prototype.animate = function(start, speed) {
  var thiz = this;
  // clear any existing animation loop
  if (this['animation'] != null)
    this.animation = clearTimeout(this.animation);

  this.timestamp = start || this.timestamp;
  speed = speed || this.speed;

  // draw all cars hidden to start
  this.circles = this.circles || this.svg.selectAll('.car')
    .data(this.ids)
    .enter()
    .append('svg:circle')
    .attr({
      'class': 'car hide',
      'r': 5,
      'data-id': function(car) {
        return car;
      }
    });

  // set a timeout that can be cleared if this is being called again
  var loop = function() {
    thiz.update(thiz.timestamp, speed);
    thiz.timestamp += thiz.interval;

    thiz.animation = thiz.stopAnimating == true ?
      // set the animation to undefined if told to stop udating
      clearTimeout(thiz.animation) :
      // if we're at a time greater than available, stop
      thiz.timestamp > thiz.end ? clearTimeout(thiz.animation) :
      // otherwise, keep animating
      setTimeout(loop, speed);
  };
  loop();
};

// update the graph components and car positions
Graph.prototype.update = function(timestamp, speed) {
  timestamp = timestamp || this.timestamp;
  speed = speed || this.speed; // FIXME for 0

  var marker = this.timeline.select('.time-marker');

  // don't try to update if we're at a time beyond what we'll have data for
  if (timestamp > this.end) return this.stop();

  // redraw all the cars with the current timestamp
  this.updateCars(timestamp);

  // update mapreduce stats
  this.data = this.calculate();

  // update text stats
  var date = new Date(timestamp);
  this.day.element.text(this.day.format(date));
  this.time.element.text(this.time.format(date));

  // update the position of the timeline marker
  var x = this.xscale(timestamp);

  // only animate when we're not updating FIXME
  var m = this.stopAnimating ?
    marker : marker.transition().duration(speed).ease('linear');

  m.attr({
    'x1': x,
    'x2': x
  });
}

// updates the car positions according to the given timestamp
Graph.prototype.updateCars = function(timestamp) {
  var thiz = this;
  if (timestamp == null) return;

  // updates an individual car by it's id
  var update = function(car) {
    var loc = thiz.cars[car][timestamp];
    var circle = d3.select(this);

    // the car doesn't have a location for the timestamp
    if (loc == null) {
      // if we're not animating and the car doesn't have a location, hide it
      if (thiz.stopAnimating == true) {
        circle
          .classed('moving', false)
          .classed('hide', true);
        return;
      }
      // else if we're already moving, don't touch
      else if (circle.classed('moving')) return;

      // get an index of where the previous timestamp occured
      var prevTimestamp = circle.attr('data-timestamp');
      var index = thiz.cars[car].locations.indexOf(prevTimestamp);

      // find the next index
      var nextLocTimestamp = thiz.cars[car].locations[index + 1];
      // find the next location
      var nextLoc = thiz.cars[car][nextLocTimestamp];

      // if there is no next timestamp (ie, the car is unavailable and
      // there we are still looping), the car should be hidden
      if (nextLoc == null) {
        circle.classed('hide', true);
      }
      // if the next location found is over 2 hours away, prevent from
      // drifting the car slowly to it's next found location. it's not
      // available at the current timestamp so hide it until..
      else if (+nextLocTimestamp - +prevTimestamp >= (2*Date.hour)) {
        circle.classed('hide', true);
      }
      // transfer the car towards it's next location
      else {
        // start the car moving and show it
        circle.classed({ moving: true, hide: false });
        // the difference in intervals between the next found timestamp
        var intervals = (nextLocTimestamp - timestamp) / thiz.interval;
        // next coords
        var coords = thiz.getCoords(nextLoc);

        // start the animation to the next location
        circle.transition()
          .ease('linear')
          .duration(thiz.speed * intervals)
          .attr({
            cx: coords.x,
            cy: coords.y
          })
          .each('end', function() {
            d3.select(this).classed('moving', false);
          });
      }
    }
    else {
      // should only occur the first time the car is available, so
      // show it and put it in it's location
      var coords = thiz.getCoords(loc);
      // end any existing transitions
      circle.transition();
      // move the circle to it's location
      circle
        // show the car and remove the moving class
        .classed({ hide: false, moving: false })
        .attr({
          cx: coords.x,
          cy: coords.y,
          'data-timestamp': timestamp
        });
    }
  };

  this.circles.each(update);
};

// ends any animation loop
Graph.prototype.stop = function() {
  if (this['animation'] != null)
    this.animation = clearTimeout(this.animation);
  return this;
};

// return an { x: x, y: y } object from a single location data
Graph.prototype.getCoords = function(location) {
  var coords = {
    lon: location[0],
    lat: location[1]
  };
  return window.map.locationPoint(coords);
};

// calculations on data
Graph.prototype.calculate = function(data, stats) {
  // default object of data to mapreduce on
  data = data || this.cars;
  // default stats object with mappings and reductions keys
  stats = stats || this.stats || this.defineStats();

  return mapreduce(data, stats.mappings, stats.reductions);
};

// a schema to mapreduce the dataset on
Graph.prototype.defineStats = function() {
  var thiz = this;

  return {
    mappings: {
      // boolean value if there was a location found for the current
      // configured timestamp
      numCars: function(car) {
        return car[thiz.timestamp] != null;
      }
    },

    reductions: {
      // return a count for the number of cars found with a location
      // at the current timestamp
      numCars: function(sum, haslocation) {
        sum = sum || 0;
        if (haslocation) sum += 1;
        return sum;
      }
    }
  }
}
<|endoftext|>


<commit_before>
var Graph = function(parameters) {};

Graph.longestPath = function(vNodes, vConns) {
  var log = asNEAT.Utils.log;

  // Clear any data from the last longest path search
  _.forEach(vNodes, function(vNode) {
    vNode.cleanParameters();
  });

  // Start at the output node
  var outNode = _.find(vNodes, function(e) {
    return e.asNEATNode instanceof asNEAT.OutNode;
  });

  log('found outNode: '+ outNode.asNEATNode.toString());

  var stack = [];
  stack = _.union(stack, Graph.getConnectionsGoingTo(outNode, vConns));

  var paths = [];
  var firstPath = new Path(outNode);
  outNode.longestPath = firstPath;
  outNode.numHops = firstPath.getLength();
  paths.push(firstPath);

  // traverse backwards along "enabled" connections and mark #hops on longest route
  while (stack.length) {
    var conn = stack.pop();

    var inVNode = conn.inVNode;
    var outVNode = conn.outVNode;

    if (inVNode.longestPath) {
      log('todo collision');
    }
    else {
      var path = outVNode.longestPath;

      // check outNode is the last node in path
      // if not, duplicate path from outNode
      if (!path.isLastNode(outVNode))
        path = path.duplicateFromNode(outNode);

      path.pushNode(inVNode);
      inVNode.numHops = path.getLength();
      inVNode.longestPath = path;
    }

    // push on next connections
    stack = _.union(stack, Graph.getConnectionsGoingTo(inVNode, vConns));
  }

  // foreach node increment # in hash w/ key of x & assign (h[x]-1) as YIndex;
  var hash = {};
  _.forEach(vNodes, function(vNode) {
    var key = vNode.getLocalX(),
        entry = hash[key];
    if (!entry) {
      entry = { value: 0 };
      hash[key] = entry;
    }

    vNode.hashElement = entry;
    vNode.yIndex = entry.value++;
  });
};

Graph.getConnectionsGoingTo = function(vNode, vConnections) {
  return _.filter(vConnections, function(e) {
    return e.asNEATConnection.enabled && e.outVNode === vNode;
  });
};

var Path = function(vNode) {
  this.nodes = [];
  if (vNode)
    this.pushNode(vNode);
};
Path.prototype.pushNode = function(vNode) {
  this.nodes.push(vNode);
};
Path.prototype.inPath = function(vNode) {
  return !!_.find(this.nodes, {'asNEATNode': vNode.asNEATNode});
};
/*
  @return the number of connections in the path
*/
Path.prototype.getLength = function() {
  return this.nodes.length-1;
};
Path.prototype.isLastNode = function(vNode) {
  return this.nodes[this.nodes.length-1]===vNode;
};
Path.prototype.duplicateFromNode = function(vNode) {
  var index = _.findIndex(this.nodes, function(node) {
    return node.asNEATNode === vNode.asNEATNode;
  });

  var newPath = new Path();
  newPath.nodes = this.nodes.slice(0, index);
  return newPath;
};

export default Graph;
<commit_msg>fixed path duplication not slicing correctly<commit_after>
var Graph = function(parameters) {};

Graph.longestPath = function(vNodes, vConns) {
  var log = asNEAT.Utils.log;

  // Clear any data from the last longest path search
  _.forEach(vNodes, function(vNode) {
    vNode.cleanParameters();
  });

  // Start at the output node
  var outNode = _.find(vNodes, function(e) {
    return e.asNEATNode instanceof asNEAT.OutNode;
  });

  log('found outNode: '+ outNode.asNEATNode.toString());

  var stack = [];
  stack = _.union(stack, Graph.getConnectionsGoingTo(outNode, vConns));

  var paths = [];
  var firstPath = new Path(outNode);
  outNode.longestPath = firstPath;
  outNode.numHops = firstPath.getLength();
  paths.push(firstPath);

  // traverse backwards along "enabled" connections and mark #hops on longest route
  while (stack.length) {
    var conn = stack.pop();

    var inVNode = conn.inVNode;
    var outVNode = conn.outVNode;

    if (inVNode.longestPath) {
      log('todo collision');
    }
    else {
      var path = outVNode.longestPath;

      // check outNode is the last node in path
      // if not, duplicate path from outNode
      if (!path.isLastNode(outVNode))
        path = path.duplicateFromNode(outNode);

      path.pushNode(inVNode);
      inVNode.numHops = path.getLength();
      inVNode.longestPath = path;
    }

    // push on next connections
    stack = _.union(stack, Graph.getConnectionsGoingTo(inVNode, vConns));
  }

  // foreach node increment # in hash w/ key of x & assign (h[x]-1) as YIndex;
  var hash = {};
  _.forEach(vNodes, function(vNode) {
    var key = vNode.getLocalX(),
        entry = hash[key];
    if (!entry) {
      entry = { value: 0 };
      hash[key] = entry;
    }

    vNode.hashElement = entry;
    vNode.yIndex = entry.value++;
  });
};

Graph.getConnectionsGoingTo = function(vNode, vConnections) {
  return _.filter(vConnections, function(e) {
    return e.asNEATConnection.enabled && e.outVNode === vNode;
  });
};

var Path = function(vNode) {
  this.nodes = [];
  if (vNode)
    this.pushNode(vNode);
};
Path.prototype.pushNode = function(vNode) {
  this.nodes.push(vNode);
};
Path.prototype.inPath = function(vNode) {
  return !!_.find(this.nodes, {'asNEATNode': vNode.asNEATNode});
};
/*
  @return the number of connections in the path
*/
Path.prototype.getLength = function() {
  return this.nodes.length-1;
};
Path.prototype.isLastNode = function(vNode) {
  return this.nodes[this.nodes.length-1]===vNode;
};
Path.prototype.duplicateFromNode = function(vNode) {
  var index = _.findIndex(this.nodes, function(node) {
    return node.asNEATNode === vNode.asNEATNode;
  });

  var newPath = new Path();
  newPath.nodes = this.nodes.slice(0, index+1);
  return newPath;
};

export default Graph;
<|endoftext|>