<commit_before>const path = require('path')
const os = require('os')

const GeneralError = require('../utilities/general-error')
const ComponentConverterUnknown = require('./component-converter-errors').ComponentConverterUnknown
const ComponentStorerUnknown = require('./component-storer-errors').ComponentStorerUnknown

const ComponentGithubStorer = require('./ComponentGithubStorer')
const ComponentHttpStorer = require('./ComponentHttpStorer')

/**
 * The abstract base class for all Stencila components
 */
class Component {

  /**
   * Construct a component
   *
   * @param {string} address - Address of component
   */
  constructor (address) {
    this.address = address
  }

  /**
   * Get the long form of a component address
   *
   * @see Component.short
   * @see address
   *
   * @example
   *
   * Component.long('+document')
   * 'new://document'
   *
   * Component.long('gh:stencila/stencila/README.md')
   * 'gh://stencila/stencila/README.md'
   *
   * Component.long('./report/intro.md')
   * 'file:///current/directory/report/intro.md'
   *
   * Component.long('stats/t-test.md')
   * 'lib://stats/t-test.md'
   *
   * Component.long()
   * 'id://fa4cf2c5cff5b576990feb96f25c98e6111990c873010855a53bcba979583836'
   *
   * @param {string} address - The address to lengthen
   * @return {string} - The long form of the address
   */
  static long (address) {
    if (address.match(/^(new|id|name|lib|file|http|https|git|gh):\/\//)) {
      return address
    } else if (address[0] === '+') {
      return 'new://' + address.substring(1)
    } else if (address[0] === '*') {
      return 'name://' + address.substring(1)
    } else if (address[0] === '.' || address[0] === '/' || address[0] === '~') {
      if (address[0] === '~') address = os.homedir() + address.substring(1)
      return 'file://' + path.resolve(address)
    } else {
      let match = address.match(/^([a-z]+)(:\/?\/?)(.+)$/)
      if (match) {
        let alias = match[1]
        let path = match[3]
        if (alias === 'file') {
          // Only arrive here with `file:/foo` since with
          // `file:` with two or more slashes is already "long"
          return `file:///${path}`
        } else if (alias === 'http' || alias === 'https') {
          return `${alias}://${path}`
        } else if (alias === 'gh' || alias === 'github') {
          return `gh://${path}`
        } else {
          throw new GeneralError('Unknown scheme alias.', {alias: alias})
        }
      } else {
        return 'lib://' + address
      }
    }
  }

  /**
   * Get the long form of this component's address
   *
   * @see Component#long
   *
   * @return {string} - The long form of the address
   */
  long () {
    return Component.long(this.address)
  }

  /**
   * Get the short form of a component address
   *
   * This method is the inverse of `long()`. It shortens an address tp
   * a smaller, more aeshetically pleasing form, that is useful in URLs
   * an other places.
   *
   * @see Component.long
   *
   * @example
   *
   * Component.short('new://document')
   * '+document'
   *
   * Component.short('file:///some/directory/my-doc.md')
   * 'file:/some/directory/my-doc.md'
   *
   * Component.short()
   * '*fa4cf2c5cff5b576990feb96f25c98e6111990c873010855a53bcba979583836'
   *
   * @param {string} address - The address to shorten
   * @return {string} - The short form of the address
   */
  static short (address) {
    address = Component.long(address)
    if (address.substring(0, 6) === 'new://') {
      return '+' + address.substring(6)
    } else if (address.substring(0, 7) === 'name://') {
      return '*' + address.substring(7)
    } else if (address.substring(0, 7) === 'file://') {
      return 'file:' + address.substring(7)
    } else if (address.substring(0, 5) === 'lib://') {
      return address.substring(5)
    } else if (address.substring(0, 5) === 'gh://') {
      return 'gh:' + address.substring(5)
    } else {
      let match = address.match(/([a-z]+):\/\/(.+)$/)
      return `${match[1]}:${match[2]}`
    }
  }

  /**
   * Get the short form of this component's address
   *
   * @see Component#short
   *
   * @return {string} - The short form of the address
   */
  short () {
    return Component.short(this.address)
  }

  /**
   * Split a component address into its parts
   *
   * @param {string} address - The address to split
   * @return {object} - An object with a property for each part of the address
   */
  static split (address) {
    address = Component.long(address)
    let matches = address.match(/([a-z]+):\/\/([\w\-\./]+)(@([\w\-\.]+))?/) // eslint-disable-line no-useless-escape
    if (matches) {
      return {
        scheme: matches[1],
        path: matches[2],
        format: path.extname(matches[2]).substring(1) || null,
        version: matches[4] || null
      }
    } else {
      throw new GeneralError('Unable to split address', {address: address})
    }
  }

  /**
   * Split this component's address into its parts
   *
   * @see Component.long
   *
   * @return {object} - An object with a property for each part of the addres
   */
  split () {
    return Component.split(this.address)
  }

  static scheme (address) {
    return Component.split(address).scheme
  }

  get scheme () {
    return this.constructor.scheme(this.address)
  }

  static path (address) {
    return Component.split(address).path
  }

  get path () {
    return this.constructor.path(this.address)
  }

  static format (address) {
    return Component.split(address).format
  }

  get format () {
    return this.constructor.format(this.address)
  }

  static version (address) {
    return Component.split(address).version
  }

  get version () {
    return this.constructor.version(this.address)
  }

  /**
   * Get a default value for this component class
   *
   * @param  {String} name - Name of value default is wanted for
   * @return {Object} - Default value
   */
  static default (name) {
    return {}[name] || null
  }

  /**
   * Get a default value for this component instance
   *
   * @param  {String} name - Name of value default is wanted for
   * @return {Object} - Default value
   */
  default (name) {
    return this.constructor.default(name)
  }

  /**
   * Get the converter for a format for this component class
   *
   * @param {string} format The format e.g. `'html'`, `'md'`
   */
  static converter (format) {
    throw new ComponentConverterUnknown(format)
  }

  /**
   * Get the converter for a format for this component instance
   *
   * @param {string} format The format e.g. `'html'`, `'md'`
   * @return {converter} A component converter
   */
  converter (format) {
    format = format || this.format
    return this.constructor.converter(format)
  }

  /**
  * Load content in a specified format
  *
  * @param {string} content - content of the document
  * @param {string} format - format of the content
  * @param {object} options - options that are passed to the converter
  **/
  load (content, format, options) {
    format = format || this.constructor.default(format)
    options = options || {}

    this.converter(format).load(this, content, options)
  }

  /**
  * dump content in a specified format
  *
  * @param {string} format - format of the content
  * @param {object} options - options that are passed to the converter
  * @returns {converter} - Content of the document
  **/
  dump (format, options) {
    format = format || this.constructor.default(format)
    options = options || {}

    return this.converter(format).dump(this, options)
  }

  /**
   * Get the storer for a scheme for this component class
   *
   * @param {string} scheme - A component address scheme
   * @return {storer} A component storer
   */
  static storer (scheme) {
    let Storer = {
      'gh': ComponentGithubStorer,
      'http': ComponentHttpStorer,
      'https': ComponentHttpStorer
    }[scheme]
    if (!Storer) throw new ComponentStorerUnknown(scheme)
    return new Storer()
  }

  storer (scheme) {
    scheme = scheme || this.scheme
    return this.constructor.storer(scheme)
  }

  read () {
    let {scheme, path, format, version} = this.split() // eslint-disable-line no-unused-vars
    let content = this.storer(scheme).read(this.address)
    this.load(content, format)
  }

  write () {
    let {scheme, path, format, version} = this.split() // eslint-disable-line no-unused-vars
    let content = this.dump(format)
    this.storer(scheme).write(this.address, content)
  }

}

module.exports = Component
<commit_msg>Fix bug :bug:<commit_after>const path = require('path')
const os = require('os')

const GeneralError = require('../utilities/general-error')
const ComponentConverterUnknown = require('./component-converter-errors').ComponentConverterUnknown
const ComponentStorerUnknown = require('./component-storer-errors').ComponentStorerUnknown

const ComponentGithubStorer = require('./ComponentGithubStorer')
const ComponentHttpStorer = require('./ComponentHttpStorer')

/**
 * The abstract base class for all Stencila components
 */
class Component {

  /**
   * Construct a component
   *
   * @param {string} address - Address of component
   */
  constructor (address) {
    this.address = address
  }

  /**
   * Get the long form of a component address
   *
   * @see Component.short
   * @see address
   *
   * @example
   *
   * Component.long('+document')
   * 'new://document'
   *
   * Component.long('gh:stencila/stencila/README.md')
   * 'gh://stencila/stencila/README.md'
   *
   * Component.long('./report/intro.md')
   * 'file:///current/directory/report/intro.md'
   *
   * Component.long('stats/t-test.md')
   * 'lib://stats/t-test.md'
   *
   * Component.long()
   * 'id://fa4cf2c5cff5b576990feb96f25c98e6111990c873010855a53bcba979583836'
   *
   * @param {string} address - The address to lengthen
   * @return {string} - The long form of the address
   */
  static long (address) {
    if (address.match(/^(new|id|name|lib|file|http|https|git|gh):\/\//)) {
      return address
    } else if (address[0] === '+') {
      return 'new://' + address.substring(1)
    } else if (address[0] === '*') {
      return 'name://' + address.substring(1)
    } else if (address[0] === '.' || address[0] === '/' || address[0] === '~') {
      if (address[0] === '~') address = os.homedir() + address.substring(1)
      return 'file://' + path.resolve(address)
    } else {
      let match = address.match(/^([a-z]+)(:\/?\/?)(.+)$/)
      if (match) {
        let alias = match[1]
        let path = match[3]
        if (alias === 'file') {
          // Only arrive here with `file:/foo` since with
          // `file:` with two or more slashes is already "long"
          return `file:///${path}`
        } else if (alias === 'http' || alias === 'https') {
          return `${alias}://${path}`
        } else if (alias === 'gh' || alias === 'github') {
          return `gh://${path}`
        } else {
          throw new GeneralError('Unknown scheme alias.', {alias: alias})
        }
      } else {
        return 'lib://' + address
      }
    }
  }

  /**
   * Get the long form of this component's address
   *
   * @see Component#long
   *
   * @return {string} - The long form of the address
   */
  long () {
    return Component.long(this.address)
  }

  /**
   * Get the short form of a component address
   *
   * This method is the inverse of `long()`. It shortens an address tp
   * a smaller, more aeshetically pleasing form, that is useful in URLs
   * an other places.
   *
   * @see Component.long
   *
   * @example
   *
   * Component.short('new://document')
   * '+document'
   *
   * Component.short('file:///some/directory/my-doc.md')
   * 'file:/some/directory/my-doc.md'
   *
   * Component.short()
   * '*fa4cf2c5cff5b576990feb96f25c98e6111990c873010855a53bcba979583836'
   *
   * @param {string} address - The address to shorten
   * @return {string} - The short form of the address
   */
  static short (address) {
    address = Component.long(address)
    if (address.substring(0, 6) === 'new://') {
      return '+' + address.substring(6)
    } else if (address.substring(0, 7) === 'name://') {
      return '*' + address.substring(7)
    } else if (address.substring(0, 7) === 'file://') {
      return 'file:' + address.substring(7)
    } else if (address.substring(0, 6) === 'lib://') {
      return address.substring(5)
    } else if (address.substring(0, 5) === 'gh://') {
      return 'gh:' + address.substring(5)
    } else {
      let match = address.match(/([a-z]+):\/\/(.+)$/)
      return `${match[1]}:${match[2]}`
    }
  }

  /**
   * Get the short form of this component's address
   *
   * @see Component#short
   *
   * @return {string} - The short form of the address
   */
  short () {
    return Component.short(this.address)
  }

  /**
   * Split a component address into its parts
   *
   * @param {string} address - The address to split
   * @return {object} - An object with a property for each part of the address
   */
  static split (address) {
    address = Component.long(address)
    let matches = address.match(/([a-z]+):\/\/([\w\-\./]+)(@([\w\-\.]+))?/) // eslint-disable-line no-useless-escape
    if (matches) {
      return {
        scheme: matches[1],
        path: matches[2],
        format: path.extname(matches[2]).substring(1) || null,
        version: matches[4] || null
      }
    } else {
      throw new GeneralError('Unable to split address', {address: address})
    }
  }

  /**
   * Split this component's address into its parts
   *
   * @see Component.long
   *
   * @return {object} - An object with a property for each part of the addres
   */
  split () {
    return Component.split(this.address)
  }

  static scheme (address) {
    return Component.split(address).scheme
  }

  get scheme () {
    return this.constructor.scheme(this.address)
  }

  static path (address) {
    return Component.split(address).path
  }

  get path () {
    return this.constructor.path(this.address)
  }

  static format (address) {
    return Component.split(address).format
  }

  get format () {
    return this.constructor.format(this.address)
  }

  static version (address) {
    return Component.split(address).version
  }

  get version () {
    return this.constructor.version(this.address)
  }

  /**
   * Get a default value for this component class
   *
   * @param  {String} name - Name of value default is wanted for
   * @return {Object} - Default value
   */
  static default (name) {
    return {}[name] || null
  }

  /**
   * Get a default value for this component instance
   *
   * @param  {String} name - Name of value default is wanted for
   * @return {Object} - Default value
   */
  default (name) {
    return this.constructor.default(name)
  }

  /**
   * Get the converter for a format for this component class
   *
   * @param {string} format The format e.g. `'html'`, `'md'`
   */
  static converter (format) {
    throw new ComponentConverterUnknown(format)
  }

  /**
   * Get the converter for a format for this component instance
   *
   * @param {string} format The format e.g. `'html'`, `'md'`
   * @return {converter} A component converter
   */
  converter (format) {
    format = format || this.format
    return this.constructor.converter(format)
  }

  /**
  * Load content in a specified format
  *
  * @param {string} content - content of the document
  * @param {string} format - format of the content
  * @param {object} options - options that are passed to the converter
  **/
  load (content, format, options) {
    format = format || this.constructor.default(format)
    options = options || {}

    this.converter(format).load(this, content, options)
  }

  /**
  * dump content in a specified format
  *
  * @param {string} format - format of the content
  * @param {object} options - options that are passed to the converter
  * @returns {converter} - Content of the document
  **/
  dump (format, options) {
    format = format || this.constructor.default(format)
    options = options || {}

    return this.converter(format).dump(this, options)
  }

  /**
   * Get the storer for a scheme for this component class
   *
   * @param {string} scheme - A component address scheme
   * @return {storer} A component storer
   */
  static storer (scheme) {
    let Storer = {
      'gh': ComponentGithubStorer,
      'http': ComponentHttpStorer,
      'https': ComponentHttpStorer
    }[scheme]
    if (!Storer) throw new ComponentStorerUnknown(scheme)
    return new Storer()
  }

  storer (scheme) {
    scheme = scheme || this.scheme
    return this.constructor.storer(scheme)
  }

  read () {
    let {scheme, path, format, version} = this.split() // eslint-disable-line no-unused-vars
    let content = this.storer(scheme).read(this.address)
    this.load(content, format)
  }

  write () {
    let {scheme, path, format, version} = this.split() // eslint-disable-line no-unused-vars
    let content = this.dump(format)
    this.storer(scheme).write(this.address, content)
  }

}

module.exports = Component
<|endoftext|>


<commit_before><commit_msg>fix, always use component base class method<commit_after><|endoftext|>


<commit_before>/* @flow */

// for debugging resize handler:
// import $ from 'jquery'
import * as React from 'react'
import * as _ from 'lodash'
import { Slick, Plugins } from 'slickgrid-es6'
import WindowSizeListener from 'react-window-size-listener'
import * as reltab from '../reltab'
import * as actions from '../actions'
import LoadingModal from './LoadingModal'
import PagedDataView from '../PagedDataView'
import ViewParams from '../ViewParams'
import * as util from '../util'
import {clipboard} from 'electron'
import csv from 'fast-csv'
import * as he from 'he'

const { CellRangeSelector, CellSelectionModel, CellCopyManager } = Plugins

const container = '#epGrid' // for now

const gridOptions = {
  multiColumnSort: true
}

const INDENT_PER_LEVEL = 15 // pixels

const calcIndent = (depth: number): number => (INDENT_PER_LEVEL * depth)

/*
 * Formatter for cells in pivot column
 */
const groupCellFormatter = (row, cell, value, columnDef, item) => {
  const toggleCssClass = 'slick-group-toggle'
  const toggleExpandedCssClass = 'expanded'
  const toggleCollapsedCssClass = 'collapsed'
  const groupTitleCssClass = 'slick-group-title'

  var indentation = calcIndent(item._depth) + 'px'

  var pivotStr = item._pivot || ''

  const expandClass = ((!item._isLeaf) ? (item._isOpen ? toggleExpandedCssClass : toggleCollapsedCssClass) : '')
  const ret = `

${pivotStr}`
  return ret
}

// scan table data to make best effort at initial column widths
const MINCOLWIDTH = 80
const MAXCOLWIDTH = 300

// TODO: use real font metrics:
const measureStringWidth = (s: string): number => 8 + (5.5 * s.length)
const measureHeaderStringWidth = (s: string): number => 24 + (5.5 * s.length)

// get column width for specific column:
const getColWidth = (dataView: PagedDataView, cnm: string) => {
  let colWidth
  const offset = dataView.getOffset()
  const limit = offset + dataView.getItemCount()
  for (var i = offset; i < limit; i++) {
    var row = dataView.getItem(i)
    var cellVal = row[ cnm ]
    var cellWidth = MINCOLWIDTH
    if (cellVal) {
      cellWidth = measureStringWidth(cellVal.toString())
    }
    if (cnm === '_pivot') {
      cellWidth += calcIndent(row._depth + 2)
    }
    colWidth = Math.min(MAXCOLWIDTH,
      Math.max(colWidth || MINCOLWIDTH, cellWidth))
  }
  const displayName = dataView.schema.displayName(cnm)
  const headerStrWidth = measureHeaderStringWidth(displayName)
  colWidth = Math.min(MAXCOLWIDTH,
    Math.max(colWidth || MINCOLWIDTH, headerStrWidth))
  return colWidth
}

type ColWidthMap = {[cid: string]: number}

function getInitialColWidthsMap (dataView: Object): ColWidthMap {
  // let's approximate the column width:
  var colWidths = {}
  var nRows = dataView.getLength()
  if (nRows === 0) {
    return {}
  }
  const initRow = dataView.getItem(0)
  for (let cnm in initRow) {
    colWidths[cnm] = getColWidth(dataView, cnm)
  }

  return colWidths
}

/*
 * Construct map of SlickGrid column descriptors from base schema
 * and column width info
 *
 * Map should contain entries for all column ids
 */
const mkSlickColMap = (schema: reltab.Schema, viewParams: ViewParams, colWidths: ColWidthMap) => {
  let slickColMap = {}

  // hidden columns:
  slickColMap['_id'] = { id: '_id', field: '_id', name: '_id' }
  slickColMap['_parentId'] = { id: '_parentId', field: '_parentId', name: '_parentId' }
  for (let colId of schema.columns) {
    let cmd = schema.columnMetadata[ colId ]
    if (!cmd) {
      console.error('could not find column metadata for ', colId, schema)
    }
    let ci: any = { id: colId, field: colId, cssClass: '', name: '', formatter: null }
    if (colId === '_pivot') {
      const pivotNames = viewParams.vpivots.map(cid => schema.displayName(cid))
      const leafCid = viewParams.pivotLeafColumn
      let leafPivotStr = leafCid ? (' > ' + schema.displayName(leafCid)) : ''
      const pivotDisplayName = 'Pivot: ' + pivotNames.join(' > ') + leafPivotStr
      ci.cssClass = 'pivot-column'
      ci.name = he.encode(pivotDisplayName)
      ci.toolTip = pivotDisplayName
      ci.formatter = groupCellFormatter
    } else {
      var displayName = cmd.displayName || colId
      ci.name = he.encode(displayName)
      ci.toolTip = he.encode(displayName)
      ci.sortable = true
      const ff = viewParams.getColumnFormat(schema, colId).getFormatter()
      ci.formatter = (row, cell, value, columnDef, item) => ff(value)
    }
    ci.width = colWidths[ colId ]
    slickColMap[ colId ] = ci
  }
  return slickColMap
}

/**
 * React component wrapper around SlickGrid
 *
 */
export default class GridPane extends React.Component {
  grid: Object
  colWidthsMap: ColWidthMap
  slickColMap: Object

  isPivoted () {
    const viewParams = this.props.viewState.viewParams
    return (viewParams.vpivots.length > 0)
  }

  onGridClick (e: any, args: any) {
    const viewParams = this.props.viewState.viewParams
    var item = this.grid.getDataItem(args.row)
    if (item._isLeaf) {
      return
    }
    const vpivots = viewParams.vpivots
    let path = []
    for (let i = 0; i < vpivots.length; i++) {
      let pathItem = item['_path' + i]
      if (pathItem == null) {
        break
      }
      path.push(item['_path' + i])
    }
    if (item._isOpen) {
      actions.closePath(path, this.props.stateRefUpdater)
    } else {
      actions.openPath(path, this.props.stateRefUpdater)
    }
  }

  // Get grid columns based on current column visibility settings:
  getGridCols (dataView: ?Object = null) {
    const viewParams = this.props.viewState.viewParams
    const showHiddenCols = viewParams.showHiddenCols
    const displayCols = viewParams.displayColumns

    let gridCols = displayCols.map(cid => this.slickColMap[cid])
    if (this.isPivoted()) {
      this.updateColWidth(dataView, '_pivot')
      let pivotCol = this.slickColMap['_pivot']
      gridCols.unshift(pivotCol)
    }
    if (showHiddenCols) {
      const hiddenColIds = _.difference(_.keys(this.slickColMap), gridCols.map(gc => gc.field))
      const hiddenCols = hiddenColIds.map(cid => this.slickColMap[cid])
      gridCols = gridCols.concat(hiddenCols)
    }
    return gridCols
  }

  /* Create grid from the specified set of columns */
  createGrid (columns: any, data: any) {
    this.grid = new Slick.Grid(container, data, columns, gridOptions)

    const selectionModel = new CellSelectionModel()
    this.grid.setSelectionModel(selectionModel)
    selectionModel.onSelectedRangesChanged.subscribe((e, args) => {
      // TODO: could store this in app state and show some
      // stats about selected range
    })

    const copyManager = new CellCopyManager()
    this.grid.registerPlugin(copyManager)

    copyManager.onCopyCells.subscribe((e, args) => {
      const range = args.ranges[0]
      let copyData = []
      const gridCols = this.grid.getColumns()
      const gridData = this.grid.getData()
      for (let row = range.fromRow; row <= range.toRow; row++) {
        const rowData = gridData.getItem(row)
        const copyRow = []
        for (let col = range.fromCell; col <= range.toCell; col++) {
          const cid = gridCols[col].id
          copyRow.push(rowData[cid])
        }
        copyData.push(copyRow)
      }
      csv.writeToString(copyData, {headers: false}, (err, data) => {
        if (err) {
          console.error('error converting copied data to CSV: ', err)
          return
        }
        clipboard.writeText(data)
      })
    })

    const rangeSelector = new CellRangeSelector()

    this.grid.registerPlugin(rangeSelector)

    const updateViewportDebounced = _.debounce(() => {
      const vp = this.grid.getViewport()
      actions.updateViewport(vp.top, vp.bottom, this.props.stateRefUpdater)
    }, 100)

    this.grid.onViewportChanged.subscribe((e, args) => {
      updateViewportDebounced()
    })

    this.grid.onSort.subscribe((e, args) => {
      // convert back from slickGrid format: */
      const sortKey = args.sortCols.map(sc => [sc.sortCol.field, sc.sortAsc])
      actions.setSortKey(sortKey, this.props.stateRefUpdater)
    })

    this.grid.onClick.subscribe((e, args) => this.onGridClick(e, args))

    this.grid.onColumnsReordered.subscribe((e, args) => {
      const cols = this.grid.getColumns()
      const displayColIds = cols.map(c => c.field).filter(cid => cid[0] !== '_')
      actions.setColumnOrder(displayColIds, this.props.stateRefUpdater)
    })

    // load the first page
    this.grid.onViewportChanged.notify()

    if (this.props.onSlickGridCreated) {
      this.props.onSlickGridCreated(this.grid)
    }
  }

  updateColWidth (dataView: any, colId: string) {
    const colWidth = getColWidth(dataView, colId)
    this.colWidthsMap[ colId ] = colWidth
    this.slickColMap[ colId ].width = colWidth
  }

  /*
   * update grid from dataView
   */
  updateGrid (dataView: any) {
    const viewParams = this.props.viewState.viewParams
    // console.log('updateGrid: dataView: offset: ', dataView.getOffset(), 'length: ', dataView.getLength())
    if (!this.colWidthsMap) {
      this.colWidthsMap = getInitialColWidthsMap(dataView)
    }
    this.slickColMap = mkSlickColMap(dataView.schema, viewParams, this.colWidthsMap)
    const gridCols = this.getGridCols(dataView)
    if (!this.grid) {
      // console.log('updateGrid: initial update, creating grid...')
      this.createGrid(gridCols, dataView)
      this.grid.resizeCanvas()
    } else {
      this.grid.setColumns(gridCols)
      this.grid.setData(dataView)
    }
    // update sort columns:
    const vpSortKey = viewParams.sortKey.map(([columnId, sortAsc]) => ({columnId, sortAsc}))
    this.grid.setSortColumns(vpSortKey)
    this.grid.invalidateAllRows()
    this.grid.updateRowCount()
    this.grid.render()
  }

  shouldComponentUpdate (nextProps: any, nextState: any) {
    const viewState = this.props.viewState
    const nextViewState = nextProps.viewState
    const omitPred = (val: any, key: string, obj: Object) => key.startsWith('viewport')
    // N.B.: We use toObject rather than toJS because we only want a
    // shallow conversion
    const vs = _.omitBy(viewState.toObject(), omitPred)
    const nvs = _.omitBy(nextViewState.toObject(), omitPred)
    const ret = !util.shallowEqual(vs, nvs)
    return ret
  }

  handleWindowResize (e: any) {
    if (this.grid) {
      /*
      const $container = $(container)
      console.log('$container: ', $container)
      const pvh = $.css($container[0], 'height', true)
      console.log('viewport height before resize:', pvh)
      */
      this.grid.resizeCanvas()
      /*
      console.log('viewport height after resize:', $.css($container[0], 'height', true))
      console.log('gridPane.handleWindowResize: done with resize and render')
      */
    }
  }

  render () {
    const viewState = this.props.viewState
    const lt = viewState.loadingTimer
    // Only show loading modal if we've been loading more than 500 ms
    const lm = (lt.running && (lt.elapsed > 500)) ? <LoadingModal /> : null
    return (

        <WindowSizeListener onResize={e => this.handleWindowResize(e)} />

        {lm}

    )
  }

  componentDidUpdate (prevProps: any, prevState: any) {
    const dataView = this.props.viewState.dataView
    if ((dataView !== prevProps.viewState.dataView) && dataView != null) {
      this.updateGrid(dataView)
    }
  }
 }
<commit_msg>Use depth instead of null to limit path length; fixes #123<commit_after>/* @flow */

// for debugging resize handler:
// import $ from 'jquery'
import * as React from 'react'
import * as _ from 'lodash'
import { Slick, Plugins } from 'slickgrid-es6'
import WindowSizeListener from 'react-window-size-listener'
import * as reltab from '../reltab'
import * as actions from '../actions'
import LoadingModal from './LoadingModal'
import PagedDataView from '../PagedDataView'
import ViewParams from '../ViewParams'
import * as util from '../util'
import {clipboard} from 'electron'
import csv from 'fast-csv'
import * as he from 'he'

const { CellRangeSelector, CellSelectionModel, CellCopyManager } = Plugins

const container = '#epGrid' // for now

const gridOptions = {
  multiColumnSort: true
}

const INDENT_PER_LEVEL = 15 // pixels

const calcIndent = (depth: number): number => (INDENT_PER_LEVEL * depth)

/*
 * Formatter for cells in pivot column
 */
const groupCellFormatter = (row, cell, value, columnDef, item) => {
  const toggleCssClass = 'slick-group-toggle'
  const toggleExpandedCssClass = 'expanded'
  const toggleCollapsedCssClass = 'collapsed'
  const groupTitleCssClass = 'slick-group-title'

  var indentation = calcIndent(item._depth) + 'px'

  var pivotStr = item._pivot || ''

  const expandClass = ((!item._isLeaf) ? (item._isOpen ? toggleExpandedCssClass : toggleCollapsedCssClass) : '')
  const ret = `

${pivotStr}`
  return ret
}

// scan table data to make best effort at initial column widths
const MINCOLWIDTH = 80
const MAXCOLWIDTH = 300

// TODO: use real font metrics:
const measureStringWidth = (s: string): number => 8 + (5.5 * s.length)
const measureHeaderStringWidth = (s: string): number => 24 + (5.5 * s.length)

// get column width for specific column:
const getColWidth = (dataView: PagedDataView, cnm: string) => {
  let colWidth
  const offset = dataView.getOffset()
  const limit = offset + dataView.getItemCount()
  for (var i = offset; i < limit; i++) {
    var row = dataView.getItem(i)
    var cellVal = row[ cnm ]
    var cellWidth = MINCOLWIDTH
    if (cellVal) {
      cellWidth = measureStringWidth(cellVal.toString())
    }
    if (cnm === '_pivot') {
      cellWidth += calcIndent(row._depth + 2)
    }
    colWidth = Math.min(MAXCOLWIDTH,
      Math.max(colWidth || MINCOLWIDTH, cellWidth))
  }
  const displayName = dataView.schema.displayName(cnm)
  const headerStrWidth = measureHeaderStringWidth(displayName)
  colWidth = Math.min(MAXCOLWIDTH,
    Math.max(colWidth || MINCOLWIDTH, headerStrWidth))
  return colWidth
}

type ColWidthMap = {[cid: string]: number}

function getInitialColWidthsMap (dataView: Object): ColWidthMap {
  // let's approximate the column width:
  var colWidths = {}
  var nRows = dataView.getLength()
  if (nRows === 0) {
    return {}
  }
  const initRow = dataView.getItem(0)
  for (let cnm in initRow) {
    colWidths[cnm] = getColWidth(dataView, cnm)
  }

  return colWidths
}

/*
 * Construct map of SlickGrid column descriptors from base schema
 * and column width info
 *
 * Map should contain entries for all column ids
 */
const mkSlickColMap = (schema: reltab.Schema, viewParams: ViewParams, colWidths: ColWidthMap) => {
  let slickColMap = {}

  // hidden columns:
  slickColMap['_id'] = { id: '_id', field: '_id', name: '_id' }
  slickColMap['_parentId'] = { id: '_parentId', field: '_parentId', name: '_parentId' }
  for (let colId of schema.columns) {
    let cmd = schema.columnMetadata[ colId ]
    if (!cmd) {
      console.error('could not find column metadata for ', colId, schema)
    }
    let ci: any = { id: colId, field: colId, cssClass: '', name: '', formatter: null }
    if (colId === '_pivot') {
      const pivotNames = viewParams.vpivots.map(cid => schema.displayName(cid))
      const leafCid = viewParams.pivotLeafColumn
      let leafPivotStr = leafCid ? (' > ' + schema.displayName(leafCid)) : ''
      const pivotDisplayName = 'Pivot: ' + pivotNames.join(' > ') + leafPivotStr
      ci.cssClass = 'pivot-column'
      ci.name = he.encode(pivotDisplayName)
      ci.toolTip = pivotDisplayName
      ci.formatter = groupCellFormatter
    } else {
      var displayName = cmd.displayName || colId
      ci.name = he.encode(displayName)
      ci.toolTip = he.encode(displayName)
      ci.sortable = true
      const ff = viewParams.getColumnFormat(schema, colId).getFormatter()
      ci.formatter = (row, cell, value, columnDef, item) => ff(value)
    }
    ci.width = colWidths[ colId ]
    slickColMap[ colId ] = ci
  }
  return slickColMap
}

/**
 * React component wrapper around SlickGrid
 *
 */
export default class GridPane extends React.Component {
  grid: Object
  colWidthsMap: ColWidthMap
  slickColMap: Object

  isPivoted () {
    const viewParams = this.props.viewState.viewParams
    return (viewParams.vpivots.length > 0)
  }

  onGridClick (e: any, args: any) {
    const viewParams = this.props.viewState.viewParams
    var item = this.grid.getDataItem(args.row)
    if (item._isLeaf) {
      return
    }
    const vpivots = viewParams.vpivots
    const depth = item._depth
    let path = []
    for (let i = 0; i < vpivots.length && i < depth; i++) {
      let pathItem = item['_path' + i]
      path.push(item['_path' + i])
    }
    if (item._isOpen) {
      actions.closePath(path, this.props.stateRefUpdater)
    } else {
      actions.openPath(path, this.props.stateRefUpdater)
    }
  }

  // Get grid columns based on current column visibility settings:
  getGridCols (dataView: ?Object = null) {
    const viewParams = this.props.viewState.viewParams
    const showHiddenCols = viewParams.showHiddenCols
    const displayCols = viewParams.displayColumns

    let gridCols = displayCols.map(cid => this.slickColMap[cid])
    if (this.isPivoted()) {
      this.updateColWidth(dataView, '_pivot')
      let pivotCol = this.slickColMap['_pivot']
      gridCols.unshift(pivotCol)
    }
    if (showHiddenCols) {
      const hiddenColIds = _.difference(_.keys(this.slickColMap), gridCols.map(gc => gc.field))
      const hiddenCols = hiddenColIds.map(cid => this.slickColMap[cid])
      gridCols = gridCols.concat(hiddenCols)
    }
    return gridCols
  }

  /* Create grid from the specified set of columns */
  createGrid (columns: any, data: any) {
    this.grid = new Slick.Grid(container, data, columns, gridOptions)

    const selectionModel = new CellSelectionModel()
    this.grid.setSelectionModel(selectionModel)
    selectionModel.onSelectedRangesChanged.subscribe((e, args) => {
      // TODO: could store this in app state and show some
      // stats about selected range
    })

    const copyManager = new CellCopyManager()
    this.grid.registerPlugin(copyManager)

    copyManager.onCopyCells.subscribe((e, args) => {
      const range = args.ranges[0]
      let copyData = []
      const gridCols = this.grid.getColumns()
      const gridData = this.grid.getData()
      for (let row = range.fromRow; row <= range.toRow; row++) {
        const rowData = gridData.getItem(row)
        const copyRow = []
        for (let col = range.fromCell; col <= range.toCell; col++) {
          const cid = gridCols[col].id
          copyRow.push(rowData[cid])
        }
        copyData.push(copyRow)
      }
      csv.writeToString(copyData, {headers: false}, (err, data) => {
        if (err) {
          console.error('error converting copied data to CSV: ', err)
          return
        }
        clipboard.writeText(data)
      })
    })

    const rangeSelector = new CellRangeSelector()

    this.grid.registerPlugin(rangeSelector)

    const updateViewportDebounced = _.debounce(() => {
      const vp = this.grid.getViewport()
      actions.updateViewport(vp.top, vp.bottom, this.props.stateRefUpdater)
    }, 100)

    this.grid.onViewportChanged.subscribe((e, args) => {
      updateViewportDebounced()
    })

    this.grid.onSort.subscribe((e, args) => {
      // convert back from slickGrid format: */
      const sortKey = args.sortCols.map(sc => [sc.sortCol.field, sc.sortAsc])
      actions.setSortKey(sortKey, this.props.stateRefUpdater)
    })

    this.grid.onClick.subscribe((e, args) => this.onGridClick(e, args))

    this.grid.onColumnsReordered.subscribe((e, args) => {
      const cols = this.grid.getColumns()
      const displayColIds = cols.map(c => c.field).filter(cid => cid[0] !== '_')
      actions.setColumnOrder(displayColIds, this.props.stateRefUpdater)
    })

    // load the first page
    this.grid.onViewportChanged.notify()

    if (this.props.onSlickGridCreated) {
      this.props.onSlickGridCreated(this.grid)
    }
  }

  updateColWidth (dataView: any, colId: string) {
    const colWidth = getColWidth(dataView, colId)
    this.colWidthsMap[ colId ] = colWidth
    this.slickColMap[ colId ].width = colWidth
  }

  /*
   * update grid from dataView
   */
  updateGrid (dataView: any) {
    const viewParams = this.props.viewState.viewParams
    // console.log('updateGrid: dataView: offset: ', dataView.getOffset(), 'length: ', dataView.getLength())
    if (!this.colWidthsMap) {
      this.colWidthsMap = getInitialColWidthsMap(dataView)
    }
    this.slickColMap = mkSlickColMap(dataView.schema, viewParams, this.colWidthsMap)
    const gridCols = this.getGridCols(dataView)
    if (!this.grid) {
      // console.log('updateGrid: initial update, creating grid...')
      this.createGrid(gridCols, dataView)
      this.grid.resizeCanvas()
    } else {
      this.grid.setColumns(gridCols)
      this.grid.setData(dataView)
    }
    // update sort columns:
    const vpSortKey = viewParams.sortKey.map(([columnId, sortAsc]) => ({columnId, sortAsc}))
    this.grid.setSortColumns(vpSortKey)
    this.grid.invalidateAllRows()
    this.grid.updateRowCount()
    this.grid.render()
  }

  shouldComponentUpdate (nextProps: any, nextState: any) {
    const viewState = this.props.viewState
    const nextViewState = nextProps.viewState
    const omitPred = (val: any, key: string, obj: Object) => key.startsWith('viewport')
    // N.B.: We use toObject rather than toJS because we only want a
    // shallow conversion
    const vs = _.omitBy(viewState.toObject(), omitPred)
    const nvs = _.omitBy(nextViewState.toObject(), omitPred)
    const ret = !util.shallowEqual(vs, nvs)
    return ret
  }

  handleWindowResize (e: any) {
    if (this.grid) {
      /*
      const $container = $(container)
      console.log('$container: ', $container)
      const pvh = $.css($container[0], 'height', true)
      console.log('viewport height before resize:', pvh)
      */
      this.grid.resizeCanvas()
      /*
      console.log('viewport height after resize:', $.css($container[0], 'height', true))
      console.log('gridPane.handleWindowResize: done with resize and render')
      */
    }
  }

  render () {
    const viewState = this.props.viewState
    const lt = viewState.loadingTimer
    // Only show loading modal if we've been loading more than 500 ms
    const lm = (lt.running && (lt.elapsed > 500)) ? <LoadingModal /> : null
    return (

        <WindowSizeListener onResize={e => this.handleWindowResize(e)} />

        {lm}

    )
  }

  componentDidUpdate (prevProps: any, prevState: any) {
    const dataView = this.props.viewState.dataView
    if ((dataView !== prevProps.viewState.dataView) && dataView != null) {
      this.updateGrid(dataView)
    }
  }
 }
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import _ from 'lodash';

import * as Actions from '../actions/playingIndex';
import { playCurrentNote, getReplaySpeedForNote } from '../util/audio';
import { expandedTracksSelector } from '../util/selectors';

class Playback extends Component {
  constructor(props) {
    super(props);
    this.timers = [];
  }

  shouldComponentUpdate() {
    return false;
  }

  componentWillMount() {
    this.startPlayback();
  }

  componentWillUnmount() {
    this.timers.map((timer) => {
      cancelAnimationFrame(timer);
    });
  }

  loopThroughSong = (startTimestamp, playingIndex, track, visibleTrackIndex, trackIndex) => {
    const { measureIndex, noteIndex } = playingIndex;
    const { measures } = track;
    const measureToPlay = measures[measureIndex];

    const currentTimestamp = Date.now();
    const replaySpeed = getReplaySpeedForNote(measureToPlay.notes, noteIndex, measureToPlay.bpm);

    if(currentTimestamp - startTimestamp >= replaySpeed) {
      if(measureIndex === measures.length - 1 && noteIndex >= measureToPlay.notes.length - 1) {
        this.props.actions.setPlayingIndex(null);
      } else if(measureIndex !== measures.length - 1 && noteIndex >= measureToPlay.notes.length - 1) {
        const newPlayingIndex = {
          measureIndex: measureIndex + 1,
          noteIndex: 0
        };
        playCurrentNote(track, newPlayingIndex, this.props.buffers[track.instrument]);
        if(visibleTrackIndex === trackIndex) {
          this.updateUI(measures, measureToPlay, noteIndex, measureIndex);
        }
        this.timers[trackIndex] = requestAnimationFrame(() => {
          this.loopThroughSong(currentTimestamp, newPlayingIndex, track, visibleTrackIndex, trackIndex);
        });
      } else if(noteIndex < measureToPlay.notes.length - 1){
        const newPlayingIndex = {
          measureIndex,
          noteIndex: noteIndex + 1
        };
        playCurrentNote(track, newPlayingIndex, this.props.buffers[track.instrument]);
        if(visibleTrackIndex === trackIndex) {
          this.updateUI(measures, measureToPlay, noteIndex, measureIndex);
        }
        this.timers[trackIndex] = requestAnimationFrame(() => {
          this.loopThroughSong(currentTimestamp, newPlayingIndex, track, visibleTrackIndex, trackIndex);
        });
      }
    } else {
      this.timers[trackIndex] = requestAnimationFrame(() => {
        this.loopThroughSong(startTimestamp, playingIndex, track, visibleTrackIndex, trackIndex);
      });
    }
  };

  updateUI = (measures, measure, noteIndex, playbackMeasureIndex) => {
    if(measure.measureIndex !== measures.length - 1 && noteIndex >= measure.notes.length - 1) {
      this.updateNote({
        measureIndex: measures[playbackMeasureIndex + 1].measureIndex,
        noteIndex: 0
      });
    } else {
      this.updateNote({
        measureIndex: measure.measureIndex,
        noteIndex: noteIndex + 1
      });
    }
  };

  getRepeatingSection = (measures, repeatIndex) => {
    return repeatIndex === -1 ? [] : measures.slice(0, repeatIndex + 1);
  };

  mapMeasureIndices = (measures) => {
    return measures.map((measure, i) => {
      return {
        ...measure,
        measureIndex: i
      };
    });
  };

  startPlayback = () => {
    const { buffers, tracks, currentTrackIndex, playingIndex, expandedTracks } = this.props;

    expandedTracks.forEach((track) => {
      playCurrentNote(track, playingIndex, buffers[track.instrument]);
    });

    expandedTracks.forEach((track, i) => {
      this.timers[i] = requestAnimationFrame(() => {
        this.loopThroughSong(Date.now(), playingIndex, track, currentTrackIndex, i);
      });
    });
  };

  updateNote = (playingIndex) => {
    _.defer(() => {
      this.props.actions.setPlayingIndex(playingIndex);
    });
  };

  render() {
    return

;
  }
}

function mapStateToProps(state) {
  return {
    tracks: state.tracks,
    currentTrackIndex: state.currentTrackIndex,
    playingIndex: state.playingIndex
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  };
}

export default connect(expandedTracksSelector, mapDispatchToProps)(Playback);
<commit_msg>remove unused argument and mapStateToProps method<commit_after>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import _ from 'lodash';

import * as Actions from '../actions/playingIndex';
import { playCurrentNote, getReplaySpeedForNote } from '../util/audio';
import { expandedTracksSelector } from '../util/selectors';

class Playback extends Component {
  constructor(props) {
    super(props);
    this.timers = [];
  }

  shouldComponentUpdate() {
    return false;
  }

  componentWillMount() {
    this.startPlayback();
  }

  componentWillUnmount() {
    this.timers.map((timer) => {
      cancelAnimationFrame(timer);
    });
  }

  loopThroughSong = (startTimestamp, playingIndex, track, visibleTrackIndex, trackIndex) => {
    const { measureIndex, noteIndex } = playingIndex;
    const { measures } = track;
    const measureToPlay = measures[measureIndex];

    const currentTimestamp = Date.now();
    const replaySpeed = getReplaySpeedForNote(measureToPlay.notes, noteIndex, measureToPlay.bpm);

    if(currentTimestamp - startTimestamp >= replaySpeed) {
      if(measureIndex === measures.length - 1 && noteIndex >= measureToPlay.notes.length - 1) {
        this.props.actions.setPlayingIndex(null);
      } else if(measureIndex !== measures.length - 1 && noteIndex >= measureToPlay.notes.length - 1) {
        const newPlayingIndex = {
          measureIndex: measureIndex + 1,
          noteIndex: 0
        };
        playCurrentNote(track, newPlayingIndex, this.props.buffers[track.instrument]);
        if(visibleTrackIndex === trackIndex) {
          this.updateUI(measures, measureToPlay, noteIndex, measureIndex);
        }
        this.timers[trackIndex] = requestAnimationFrame(() => {
          this.loopThroughSong(currentTimestamp, newPlayingIndex, track, visibleTrackIndex, trackIndex);
        });
      } else if(noteIndex < measureToPlay.notes.length - 1){
        const newPlayingIndex = {
          measureIndex,
          noteIndex: noteIndex + 1
        };
        playCurrentNote(track, newPlayingIndex, this.props.buffers[track.instrument]);
        if(visibleTrackIndex === trackIndex) {
          this.updateUI(measures, measureToPlay, noteIndex, measureIndex);
        }
        this.timers[trackIndex] = requestAnimationFrame(() => {
          this.loopThroughSong(currentTimestamp, newPlayingIndex, track, visibleTrackIndex, trackIndex);
        });
      }
    } else {
      this.timers[trackIndex] = requestAnimationFrame(() => {
        this.loopThroughSong(startTimestamp, playingIndex, track, visibleTrackIndex, trackIndex);
      });
    }
  };

  updateUI = (measures, measure, noteIndex, playbackMeasureIndex) => {
    if(measure.measureIndex !== measures.length - 1 && noteIndex >= measure.notes.length - 1) {
      this.updateNote({
        measureIndex: measures[playbackMeasureIndex + 1].measureIndex,
        noteIndex: 0
      });
    } else {
      this.updateNote({
        measureIndex: measure.measureIndex,
        noteIndex: noteIndex + 1
      });
    }
  };

  getRepeatingSection = (measures, repeatIndex) => {
    return repeatIndex === -1 ? [] : measures.slice(0, repeatIndex + 1);
  };

  mapMeasureIndices = (measures) => {
    return measures.map((measure, i) => {
      return {
        ...measure,
        measureIndex: i
      };
    });
  };

  startPlayback = () => {
    const { buffers, currentTrackIndex, playingIndex, expandedTracks } = this.props;

    expandedTracks.forEach((track) => {
      playCurrentNote(track, playingIndex, buffers[track.instrument]);
    });

    expandedTracks.forEach((track, i) => {
      this.timers[i] = requestAnimationFrame(() => {
        this.loopThroughSong(Date.now(), playingIndex, track, currentTrackIndex, i);
      });
    });
  };

  updateNote = (playingIndex) => {
    _.defer(() => {
      this.props.actions.setPlayingIndex(playingIndex);
    });
  };

  render() {
    return

;
  }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  };
}

export default connect(expandedTracksSelector, mapDispatchToProps)(Playback);
<|endoftext|>


<commit_before>import React, { Component } from 'react';

class Projects extends Component {
	constructor(props) {
		super(props);
		this.state = { active: 0 };
	}

	render() {
		return (

						<button className="btn modal-close">×</button>

Projects

									Covid-19 Dashboard:  Artifical Intelligence model that uses predictor sliders to see how
									increasing or decreasing restrictions affects the number of covid-19 cases.  Collaborated with Evolutionary Artifical Intelligence
									group to integrate their existing model into cognizant.com and improve the UI/UX.

									Built with: Dash

											LIVE DEMO

									Next Gen Smile Care:  Responsive website for dental practice, featuring a list of services, testimonials, and contact form.  Worked closely with client and designer on information architecture and UX prior to development. Utitlized Sketch to develop pixel-perfect website. Also developed animated banner ads and email.

									Built with:  React, React-Boostrap, EmailJS, JQuery

									APIs: Google Maps

										LIVE DEMO
									  |
										GITHUB

									Animated Banner Ad

										LIVE DEMO
									  |
										GITHUB

										LIVE DEMO
									  |
										GITHUB

									 Email

										LIVE DEMO
									  |
										GITHUB

									Wanderlust:  Search for the cheapest flights
									based on origin city, date and trip duration. This list of flights
									is provided by the Amadeus API. You will have the ability to save
									flight information.

									Built with:  React-Redux, MongoDB, ExpressJS,
									NodeJS and Material-UI

									APIs: Google Maps and Amadeus

											LIVE DEMO
										  |

											GITHUB

									Mail Monkey:  Allows users to send and track
									surveys via email. A profile page displays a history of the
									emails sent, along with a bar chart of how their customers
									responded.

									Built with:  React-Redux, MongoDB, ExpressJS,
									NodeJS and MaterializeCSS

									APIs: Google Oauth, Stripe and Sendgrid

											LIVE DEMO
										  |
											GITHUB

									Vinyl Records:  A content management system
									that allows users to purchase records. An admin panel allows
									the site owner to add, edit, or delete inventory on the
									website.

									Built with:  React-Redux, MongoDB, ExpressJS,
									NodeJS and React-Boostrap

											LIVE DEMO
										  |
											GITHUB

		);
	}
}

export default Projects;
<commit_msg>update about, netlify ubuntu update<commit_after>import React, { Component } from "react";

class Projects extends Component {
  constructor(props) {
    super(props);
    this.state = { active: 0 };
  }

  render() {
    return (

            <button className="btn modal-close">×</button>

Projects

                Covid-19 Dashboard:  Artifical Intelligence
                model that uses predictor sliders to see how increasing or
                decreasing restrictions affects the number of covid-19 cases.
                Collaborated with Evolutionary Artifical Intelligence group to
                integrate their existing model into cognizant.com and improve
                the UI/UX.

                Built with: Dash

                    LIVE DEMO

                  Next Gen Smile Care:  Responsive website for
                  dental practice, featuring a list of services, testimonials,
                  and contact form. Worked closely with client and designer on
                  information architecture and UX prior to development.
                  Utitlized Sketch to develop pixel-perfect website. Also
                  developed animated banner ads and email.

                  Built with:  React, React-Boostrap, EmailJS,
                  JQuery

                  APIs: Google Maps

                    LIVE DEMO

                    |

                    GITHUB

                  Animated Banner Ad

                    LIVE DEMO

                    |

                    GITHUB

                    LIVE DEMO

                    |

                    GITHUB

                   Email

                    LIVE DEMO

                    |

                    GITHUB

                Wanderlust:  Search for the cheapest flights
                based on origin city, date and trip duration. This list of
                flights is provided by the Amadeus API. You will have the
                ability to save flight information.

                Built with:  React-Redux, MongoDB, ExpressJS,
                NodeJS and Material-UI

                APIs: Google Maps and Amadeus

                    LIVE DEMO

                    |

                    GITHUB

              Mail Monkey:  Allows users to send and track
              surveys via email. A profile page displays a history of the emails
              sent, along with a bar chart of how their customers responded.

              Built with:  React-Redux, MongoDB, ExpressJS,
              NodeJS and MaterializeCSS

              APIs: Google Oauth, Stripe and Sendgrid

                  LIVE DEMO

                  |

                  GITHUB

                Vinyl Records:  A content management system
                that allows users to purchase records. An admin panel allows the
                site owner to add, edit, or delete inventory on the website.

                Built with:  React-Redux, MongoDB, ExpressJS,
                NodeJS and React-Boostrap

                    LIVE DEMO

                    |

                    GITHUB

    );
  }
}

export default Projects;
<|endoftext|>


<commit_before>/*
 * IBM Confidential - OCO Source Materials
 * Copyright (c) IBM Corp. 1992, 2016
 * Copyright (c) Internet Security Systems, Inc. 1992-2006
 * The source code for this program is not published or otherwise divested of its trade secrets,
 * irrespective of what has been deposited with the U.S. Copyright Office.
 */

var React = require('react');
var SpinnerWave = require('./SpinnerWave');

var RepoList = function(props){

    if (!props.dataToShow){
      return
 No repositories found... Try a new keyword!

    }

    if (!props.keyword){
      return
 Search a list with a keyword!

    }

    if (!props.repositoryList.length){
      return (

          <SpinnerWave />

      );
    }

    return(

        {
          props.repositoryList.map(function(repo,i){
            return (

{repo}

            )
          })}

    );
}

module.exports = RepoList;<commit_msg>Update RepoList.js<commit_after>var React = require('react');
var SpinnerWave = require('./SpinnerWave');

var RepoList = function(props){

    if (!props.dataToShow){
      return
 No repositories found... Try a new keyword!

    }

    if (!props.keyword){
      return
 Search a list with a keyword!

    }

    if (!props.repositoryList.length){
      return (

          <SpinnerWave />

      );
    }

    return(

        {
          props.repositoryList.map(function(repo,i){
            return (

{repo}

            )
          })}

    );
}

module.exports = RepoList;
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import history from '../history';
import _ from 'lodash';

const Settings = ({
  pivotalToken,
  pivotalProjectId,
  gitHubAuthorized,
  selectedRepo,
  repos,
  herokuAuthorized,
  onSettingsChange,
  onRepoQueryChange
}) => {
  return (

Pivotal Swimlanes Settings

      <form>
        Pivotal Token:

        <input
          type='text'
          value={pivotalToken}
          onChange={e => onSettingsChange({ pivotalToken: e.target.value })} />

        Pivotal Project ID:

        <input
          type='text'
          value={pivotalProjectId}
          onChange={e => onSettingsChange({ pivotalProjectId: e.target.value })} />

        GitHub Repo:

        {gitHubAuthorized ? (

            <input
              type='text'
              placeholder='Search Repos'
              onChange={_.debounce(e => onRepoQueryChange(e.target.value), 500)} />
            {_.any(selectedRepo) ?
Selected: {selectedRepo}
 : null}

              {_.map(repos, (repo, i) =>

 onSettingsChange({ selectedRepo: repo })}
                >
                  {repo}

              )}

        ) : (

              Authorize GitHub Account

        )}

        Heroku Account:

        {herokuAuthorized ? (

Account connected

        ) : (

              Authorize Heroku Account

        )}

        <button onClick={() => history.pushState(null, '/')}>Continue</button>
      </form>

  );
};

Settings.propTypes = {
  pivotalToken: PropTypes.string,
  pivotalProjectId: PropTypes.string,
  selectedRepo: PropTypes.string,
  repos: PropTypes.arrayOf(PropTypes.string),
  herokuAuthorized: PropTypes.bool
};

export default Settings;
<commit_msg>Show message when no repos found for query<commit_after>import React, { PropTypes } from 'react';
import history from '../history';
import _ from 'lodash';

const Settings = ({
  pivotalToken,
  pivotalProjectId,
  gitHubAuthorized,
  selectedRepo,
  repos,
  herokuAuthorized,
  onSettingsChange,
  onRepoQueryChange
}) => {
  return (

Pivotal Swimlanes Settings

      <form>
        Pivotal Token:

        <input
          type='text'
          value={pivotalToken}
          onChange={e => onSettingsChange({ pivotalToken: e.target.value })} />

        Pivotal Project ID:

        <input
          type='text'
          value={pivotalProjectId}
          onChange={e => onSettingsChange({ pivotalProjectId: e.target.value })} />

        GitHub Repo:

        {gitHubAuthorized ? (

            <input
              type='text'
              placeholder='Search Repos'
              onChange={_.debounce(e => onRepoQueryChange(e.target.value), 500)} />
            {_.any(selectedRepo) ?
Selected: {selectedRepo}
 : null}
            {_.any(repos) ? (

                {_.map(repos, (repo, i) =>

 onSettingsChange({ selectedRepo: repo })}
                  >
                    {repo}

                )}

            ) : (

No repos found.

            )}

        ) : (

              Authorize GitHub Account

        )}

        Heroku Account:

        {herokuAuthorized ? (

Account connected

        ) : (

              Authorize Heroku Account

        )}

        <button onClick={() => history.pushState(null, '/')}>Continue</button>
      </form>

  );
};

Settings.propTypes = {
  pivotalToken: PropTypes.string,
  pivotalProjectId: PropTypes.string,
  selectedRepo: PropTypes.string,
  repos: PropTypes.arrayOf(PropTypes.string),
  herokuAuthorized: PropTypes.bool
};

export default Settings;
<|endoftext|>


<commit_before>import React, { Component } from 'react';

import { Button, Input } from 'semantic-ui-react';

export class Add extends Component {
  constructor(props) {
    super(props);

    this.state = {
      primary: '',
      accent: ''
    };

    this.onAdd = this.onAdd.bind(this);
  }

  onAdd() {
    const { primary, accent } = this.state;

    if (primary && accent) {
      this.props.actions.onAddItem(primary, accent);
      this.setState({ primary: '', accent: '' });
    }
  }

  render() {
    return (

        <Input value={ this.state.primary } onChange={ event => this.setState({ primary: event.target.value }) } />
        { ' - ' }
        <Input value={ this.state.accent } onChange={ event => this.setState({ accent: event.target.value }) } />
        { ' ' }
        <Button basic color="black" onClick={ this.onAdd }>Add</Button>

    );
  }
}
<commit_msg>32nd Commit<commit_after>import React from 'react';

import { Button, Input } from 'semantic-ui-react';

export const Add = ({ actions }) => {
  let primary, accent;

  const onAdd = (event) => {
    event.preventDefault();

    if (primary.inputRef.value && accent.inputRef.value) {
      actions.onAddItem(primary.inputRef.value, accent.inputRef.value);
      primary.inputRef.value = '';
      accent.inputRef.value = '';
    }
  };

  return (

      <form onSubmit={ onAdd }>
        <Input ref={ node => primary = node } />
        { ' - ' }
        <Input ref={ node => accent = node } />
        { ' ' }
        <Button basic color="black" type="submit">Add</Button>
      </form>

  );
}

// import React, { Component } from 'react';
//
// import { Button, Input } from 'semantic-ui-react';
//
// export class Add extends Component {
//   constructor(props) {
//     super(props);
//
//     this.state = {
//       primary: '',
//       accent: ''
//     };
//
//     this.onAdd = this.onAdd.bind(this);
//   }
//
//   onAdd() {
//     const { primary, accent } = this.state;
//
//     if (primary && accent) {
//       this.props.actions.onAddItem(primary, accent);
//       this.setState({ primary: '', accent: '' });
//     }
//   }
//
//   render() {
//     return (
//

//         <Input value={ this.state.primary } onChange={ event => this.setState({ primary: event.target.value }) } />
//         { ' - ' }
//         <Input value={ this.state.accent } onChange={ event => this.setState({ accent: event.target.value }) } />
//         { ' ' }
//         <Button basic color="black" onClick={ this.onAdd }>Add</Button>
//

//     );
//   }
// }
<|endoftext|>


<commit_before>/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Cockpit is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Cockpit is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";
import { superuser } from "superuser";

import '../lib/patternfly/patternfly-cockpit.scss';

const _ = cockpit.gettext;

var sos_task;
var sos_archive_url;
var sos_archive_files;

function sos_init() {
    // Start right away
    sos_create();
}

function sos_error(message, extra) {
    document.getElementById("sos-alert").setAttribute("hidden", "hidden");
    document.getElementById("sos-progress").setAttribute("hidden", "hidden");
    document.getElementById("sos-download").setAttribute("hidden", "hidden");
    document.querySelector("#sos-error .alert-message").textContent = message;

    const e_extra = document.getElementById("sos-error-extra");
    if (extra) {
        e_extra.textContent = extra;
        e_extra.removeAttribute("hidden");
    } else
        e_extra.setAttribute("hidden", "hidden");
    document.getElementById("sos-cancel").textContent = _("Close");
    document.getElementById("sos-error").removeAttribute("hidden");
}

function sos_create() {
    document.querySelector("#sos-progress .progress-bar").style.width = "0%";
    document.getElementById("sos-download").setAttribute("hidden", "hidden");
    document.getElementById("sos-error").setAttribute("hidden", "hidden");
    document.getElementById("sos-cancel").textContent = _("Cancel");

    sos_archive_url = null;
    sos_archive_files = [];

    var task = cockpit.spawn(["sosreport", "--batch"],
                             { superuser: true, err: "out", pty: true });
    sos_task = task;

    // TODO - Use a real API instead of scraping stdout once such
    //        an API exists.

    var output = "";
    var plugins_count = 0;
    var progress_regex = /Running ([0-9]+)\/([0-9]+):/; // Only for sos < 3.6
    var finishing_regex = /Finishing plugins.*\[Running: (.*)\]/;
    var starting_regex = /Starting ([0-9]+)\/([0-9]+).*\[Running: (.*)\]/;
    var archive_regex = /Your sosreport has been generated and saved in:\s+(\/[^\r\n]+)/;

    task.stream(function (text) {
        if (sos_task == task) {
            var m, p;
            p = 0;

            output += text;
            var lines = output.split("\n");
            for (var i = lines.length - 1; i >= 0; i--) {
                if ((m = starting_regex.exec(lines[i]))) {
                    plugins_count = parseInt(m[2], 10);
                    p = ((parseInt(m[1], 10) - m[3].split(" ").length) / plugins_count) * 100;
                    break;
                } else if ((m = finishing_regex.exec(lines[i]))) {
                    if (!plugins_count)
                        p = 100;
                    else
                        p = ((plugins_count - m[1].split(" ").length) / plugins_count) * 100;
                    break;
                } else if ((m = progress_regex.exec(lines[i]))) {
                    p = (parseInt(m[1], 10) / parseInt(m[2], 10)) * 100;
                    break;
                }
            }
            document.getElementById("sos-alert").removeAttribute("hidden");
            document.getElementById("sos-progress").removeAttribute("hidden");
            document.querySelector("#sos-progress .progress-bar").style.width = p.toString() + "%";
        }
    });
    task.done(function () {
        if (sos_task == task) {
            var m = archive_regex.exec(output);
            if (m) {
                var archive = m[1];
                var basename = archive.replace(/.*\//, "");

                // When running sosreport in a container on the
                // Atomics, the archive path needs to be adjusted.
                //
                if (archive.indexOf("/host") === 0)
                    archive = archive.substr(5);

                sos_archive_files = [archive, archive + ".md5"];

                var query = window.btoa(JSON.stringify({
                    payload: "fsread1",
                    binary: "raw",
                    path: archive,
                    superuser: true,
                    max_read_size: 150 * 1024 * 1024,
                    external: {
                        "content-disposition": 'attachment; filename="' + basename + '"',
                        "content-type": "application/x-xz, application/octet-stream"
                    }
                }));
                var prefix = (new URL(cockpit.transport.uri("channel/" + cockpit.transport.csrf_token))).pathname;
                sos_archive_url = prefix + '?' + query;
                document.getElementById("sos-progress").setAttribute("hidden", "hidden");
                document.getElementById("sos-error").setAttribute("hidden", "hidden");
                document.getElementById("sos-alert").removeAttribute("hidden");
                document.getElementById("sos-download").removeAttribute("hidden");
                document.getElementById("sos-cancel").textContent = _("Close");
            } else {
                sos_error(_("No archive has been created."), output);
            }
            sos_task = null;
        }
    });
    task.fail(function (error) {
        if (sos_task == task) {
            sos_error(error.toString(), output);
            sos_task = null;
        }
    });
}

function sos_cancel() {
    if (sos_task) {
        sos_task.close("cancelled");
        sos_task = null;
    }
    if (sos_archive_files.length > 0) {
        cockpit.spawn(["rm"].concat(sos_archive_files), { superuser: true, err: "message" })
                .fail(function (error) {
                    console.log("failed to remove", sos_archive_files, error);
                });
    }
    sos_archive_url = null;
    sos_archive_files = [];
    document.getElementById("sos").setAttribute("hidden", "hidden");
}

function sos_download() {
    // We download via a hidden iframe to get better control over
    // the error cases.
    const iframe = document.createElement("iframe");
    iframe.setAttribute("src", sos_archive_url);
    iframe.setAttribute("hidden", "hidden");
    iframe.addEventListener("load", () => {
        const title = iframe.contentDocument.title;
        if (title)
            sos_error(title);
    });
    document.body.appendChild(iframe);
}

document.addEventListener("DOMContentLoaded", () => {
    document.getElementById("create-button").addEventListener("click", () => {
        document.getElementById("sos").removeAttribute("hidden");
        sos_init();
    });
    document.getElementById("sos-cancel").addEventListener("click", sos_cancel);
    document.querySelector("#sos-download button").addEventListener("click", sos_download);

    cockpit.translate();
    document.body.removeAttribute("hidden");

    function update_admin_allowed() {
        document.getElementById("switch-instructions").style.display = superuser.allowed === false ? "block" : "none";
        if (superuser.allowed)
            document.getElementById("create-button").removeAttribute("hidden");
        else
            document.getElementById("create-button").setAttribute("hidden", "hidden");
    }

    superuser.addEventListener("changed", update_admin_allowed);
    update_admin_allowed();

    // Send a 'init' message.  This tells the tests that we
    // are ready to go.
    //
    cockpit.transport.wait(() => { });
});
<commit_msg>sosreport: Eliminate `var` keyword<commit_after>/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Cockpit is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Cockpit is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";
import { superuser } from "superuser";

import '../lib/patternfly/patternfly-cockpit.scss';

const _ = cockpit.gettext;

let sos_task;
let sos_archive_url;
let sos_archive_files;

function sos_init() {
    // Start right away
    sos_create();
}

function sos_error(message, extra) {
    document.getElementById("sos-alert").setAttribute("hidden", "hidden");
    document.getElementById("sos-progress").setAttribute("hidden", "hidden");
    document.getElementById("sos-download").setAttribute("hidden", "hidden");
    document.querySelector("#sos-error .alert-message").textContent = message;

    const e_extra = document.getElementById("sos-error-extra");
    if (extra) {
        e_extra.textContent = extra;
        e_extra.removeAttribute("hidden");
    } else
        e_extra.setAttribute("hidden", "hidden");
    document.getElementById("sos-cancel").textContent = _("Close");
    document.getElementById("sos-error").removeAttribute("hidden");
}

function sos_create() {
    document.querySelector("#sos-progress .progress-bar").style.width = "0%";
    document.getElementById("sos-download").setAttribute("hidden", "hidden");
    document.getElementById("sos-error").setAttribute("hidden", "hidden");
    document.getElementById("sos-cancel").textContent = _("Cancel");

    sos_archive_url = null;
    sos_archive_files = [];

    const task = cockpit.spawn(["sosreport", "--batch"],
                               { superuser: true, err: "out", pty: true });
    sos_task = task;

    // TODO - Use a real API instead of scraping stdout once such
    //        an API exists.

    let output = "";
    let plugins_count = 0;
    const progress_regex = /Running ([0-9]+)\/([0-9]+):/; // Only for sos < 3.6
    const finishing_regex = /Finishing plugins.*\[Running: (.*)\]/;
    const starting_regex = /Starting ([0-9]+)\/([0-9]+).*\[Running: (.*)\]/;
    const archive_regex = /Your sosreport has been generated and saved in:\s+(\/[^\r\n]+)/;

    task.stream(function (text) {
        if (sos_task == task) {
            let p = 0;
            let m;

            output += text;
            const lines = output.split("\n");
            for (let i = lines.length - 1; i >= 0; i--) {
                if ((m = starting_regex.exec(lines[i]))) {
                    plugins_count = parseInt(m[2], 10);
                    p = ((parseInt(m[1], 10) - m[3].split(" ").length) / plugins_count) * 100;
                    break;
                } else if ((m = finishing_regex.exec(lines[i]))) {
                    if (!plugins_count)
                        p = 100;
                    else
                        p = ((plugins_count - m[1].split(" ").length) / plugins_count) * 100;
                    break;
                } else if ((m = progress_regex.exec(lines[i]))) {
                    p = (parseInt(m[1], 10) / parseInt(m[2], 10)) * 100;
                    break;
                }
            }
            document.getElementById("sos-alert").removeAttribute("hidden");
            document.getElementById("sos-progress").removeAttribute("hidden");
            document.querySelector("#sos-progress .progress-bar").style.width = p.toString() + "%";
        }
    });
    task.done(function () {
        if (sos_task == task) {
            const m = archive_regex.exec(output);
            if (m) {
                let archive = m[1];
                const basename = archive.replace(/.*\//, "");

                // When running sosreport in a container on the
                // Atomics, the archive path needs to be adjusted.
                //
                if (archive.indexOf("/host") === 0)
                    archive = archive.substr(5);

                sos_archive_files = [archive, archive + ".md5"];

                const query = window.btoa(JSON.stringify({
                    payload: "fsread1",
                    binary: "raw",
                    path: archive,
                    superuser: true,
                    max_read_size: 150 * 1024 * 1024,
                    external: {
                        "content-disposition": 'attachment; filename="' + basename + '"',
                        "content-type": "application/x-xz, application/octet-stream"
                    }
                }));
                const prefix = (new URL(cockpit.transport.uri("channel/" + cockpit.transport.csrf_token))).pathname;
                sos_archive_url = prefix + '?' + query;
                document.getElementById("sos-progress").setAttribute("hidden", "hidden");
                document.getElementById("sos-error").setAttribute("hidden", "hidden");
                document.getElementById("sos-alert").removeAttribute("hidden");
                document.getElementById("sos-download").removeAttribute("hidden");
                document.getElementById("sos-cancel").textContent = _("Close");
            } else {
                sos_error(_("No archive has been created."), output);
            }
            sos_task = null;
        }
    });
    task.fail(function (error) {
        if (sos_task == task) {
            sos_error(error.toString(), output);
            sos_task = null;
        }
    });
}

function sos_cancel() {
    if (sos_task) {
        sos_task.close("cancelled");
        sos_task = null;
    }
    if (sos_archive_files.length > 0) {
        cockpit.spawn(["rm"].concat(sos_archive_files), { superuser: true, err: "message" })
                .fail(function (error) {
                    console.log("failed to remove", sos_archive_files, error);
                });
    }
    sos_archive_url = null;
    sos_archive_files = [];
    document.getElementById("sos").setAttribute("hidden", "hidden");
}

function sos_download() {
    // We download via a hidden iframe to get better control over
    // the error cases.
    const iframe = document.createElement("iframe");
    iframe.setAttribute("src", sos_archive_url);
    iframe.setAttribute("hidden", "hidden");
    iframe.addEventListener("load", () => {
        const title = iframe.contentDocument.title;
        if (title)
            sos_error(title);
    });
    document.body.appendChild(iframe);
}

document.addEventListener("DOMContentLoaded", () => {
    document.getElementById("create-button").addEventListener("click", () => {
        document.getElementById("sos").removeAttribute("hidden");
        sos_init();
    });
    document.getElementById("sos-cancel").addEventListener("click", sos_cancel);
    document.querySelector("#sos-download button").addEventListener("click", sos_download);

    cockpit.translate();
    document.body.removeAttribute("hidden");

    function update_admin_allowed() {
        document.getElementById("switch-instructions").style.display = superuser.allowed === false ? "block" : "none";
        if (superuser.allowed)
            document.getElementById("create-button").removeAttribute("hidden");
        else
            document.getElementById("create-button").setAttribute("hidden", "hidden");
    }

    superuser.addEventListener("changed", update_admin_allowed);
    update_admin_allowed();

    // Send a 'init' message.  This tells the tests that we
    // are ready to go.
    //
    cockpit.transport.wait(() => { });
});
<|endoftext|>


<commit_before>//es5
function link1(height, color, url) {
    var height = height || 50;
    var color = color || "red";
    var url = url || "http://www.abc.com";
    console.log(height, color, url);
}
link1(null, "Blue");
link1(undefined, "", "http://www.xyz.com");
//es6
function link2(height = 50, color = "red", url = "http://www.abc.com") {
    console.log(height, color, url);
}
link2(undefined, "Blue");
link2(undefined, "", "http://www.xyz.com");<commit_msg>dev 40<commit_after>//es5
function link1(height, color, url) {
    var height = height !== undefined ? height : 50;
    var color = color !== undefined ? color : "red";
    var url = url !== undefined ? url : "http://www.abc.com";
    console.log(height, color, url);
}
link1(null, "Blue");
link1(undefined, "", "http://www.xyz.com");
//es6
function link2(height = 50, color = "red", url = "http://www.abc.com") {
    console.log(height, color, url);
}
link2(undefined, "Blue");
link2(undefined, "", "http://www.xyz.com");<|endoftext|>


<commit_before>/* global Promise */
var registerComponent = require('../core/register-component').registerComponent;
var srcLoader = require('../utils/src-loader');
var THREE = require('../../lib/three');
var utils = require('../vr-utils');

var CubeLoader = new THREE.CubeTextureLoader();
var texturePromises = {};

/**
 * Material component.
 *
 * @namespace material
 * @params {string} color - Diffuse color.
 * @params {string} envMap - To load a environment cubemap. Takes a selector
           to an element containing six img elements, or a comma-separated
           string of direct url()s.
 * @params {number} height - Height to render texture.
 * @params {number} metalness - Parameter for physical/standard material.
 * @params {number} opacity - [0-1].
 * @params {boolean} receiveLight - Determines whether the material is shaded.
 * @params {number} reflectivity - Parameter for physical/standard material.
 * @params {string} repeat - X and Y value for size of texture repeating
           (in UV units).
 * @params {number} roughness - Parameter for physical/standard material.
 * @params {string} src - To load a texture. takes a selector to an img/video
           element or a direct url().
 * @params {boolean} transparent - Whether to render transparent the alpha
           channel of a texture (e.g., .png).
 * @params {number} width - Width to render texture.
 */
module.exports.Component = registerComponent('material', {
  defaults: {
    value: {
      color: '#FFF',
      envMap: '',
      height: 360,
      metalness: 0.0,
      opacity: 1.0,
      receiveLight: true,
      reflectivity: 1.0,
      repeat: '',
      roughness: 0.5,
      src: '',
      transparent: false,
      width: 640
    }
  },

  init: {
    value: function () {
      this.textureSrc = null;
      this.isLoadingEnvMap = false;
      this.material = null;
    }
  },

  /**
   * Update or create material.
   * Returned material type depends on receiveLight.
   *   receiveLight: false - MeshBasicMaterial.
   *   receiveLight: true - MeshStandardMaterial.
   *
   * @return {object} material
   */
  update: {
    value: function () {
      var data = this.data;
      var material;

      if (data.receiveLight) {
        // Physical material.
        material = this.updateOrCreateMaterialHelper({
          color: new THREE.Color(data.color),
          side: this.getSides(),
          opacity: data.opacity,
          transparent: data.transparent,
          metalness: data.metalness,
          reflectivity: data.reflectivity,
          roughness: data.roughness
        }, 'MeshStandardMaterial');
        // Environment cubemaps.
        if (data.envMap && !this.isLoadingEnvMap) {
          this.loadEnvMap(material, data.envMap);
        } else {
          material.envMap = null;
          material.needsUpdate = true;
        }
      } else {
        material = this.updateOrCreateMaterialHelper({
          // Basic material.
          color: new THREE.Color(data.color),
          side: this.getSides(),
          opacity: data.opacity,
          transparent: data.transparent
        }, 'MeshBasicMaterial');
      }

      this.el.object3D.material = this.material = material;

      // Textures.
      var src = data.src;
      if (src) {
        if (src !== this.textureSrc) {
          // Texture added or changed.
          this.textureSrc = src;
          srcLoader.validateSrc(src, this.loadImage.bind(this),
                                this.loadVideo.bind(this));
        }
      } else {
        // Texture removed.
        material.map = null;
        material.needsUpdate = true;
      }
    }
  },

  /**
   * Remove material on remove (callback).
   */
  remove: {
    value: function () {
      var object3D = this.el.object3D;
      if (object3D) { object3D.material = null; }
    }
  },

  /**
   * Updates this.material using data, creates new material if this.material
   * doesn't yet exist.
   *
   * @params {object} data - Attributes to set on the material.
   * @params {string} type - Type of material to create (if necessary).
   * @returns {object} material - three.js material based on `type`.
   */
  updateOrCreateMaterialHelper: {
    value: function (data, type) {
      var material = this.material;
      var reuseMaterial = material && material.type === type;
      if (reuseMaterial) {
        Object.keys(data).forEach(function (key) {
          material[key] = data[key];
        });
      } else {
        material = new THREE[type](data);
      }
      return material;
    }
  },

  /**
   * Returns an integer for which new material face sides will be rendered.
   *
   * @returns {Integer} `THREE.DoubleSide` (`0`) or `THREE.FrontSide` (`2`)
   */
  getSides: {
    value: function () {
      var geometry = this.el.components.geometry;
      if (geometry && geometry.data.openEnded) {
        // For performance reasons, we special case open-ended cylinders
        // for rendering both faces.
        return THREE.DoubleSide;
      }
      return THREE.FrontSide;
    }
  },

  /**
   * Handle environment cubemap. Textures are cached in texturePromises.
   *
   * @param {object} material - three.js material.
   * @param {string} envMap - Query selector or comma-separated list of url()s.
   */
  loadEnvMap: {
    value: function (material, envMap) {
      var self = this;
      self.isLoadingEnvMap = true;
      if (texturePromises[envMap]) {
        // Another material is already loading this texture. Wait on promise.
        texturePromises[envMap].then(function (cube) {
          self.isLoadingEnvMap = false;
          material.envMap = cube;
          material.needsUpdate = true;
        });
      } else {
        // Material is first to load this texture. Load and resolve texture.
        texturePromises[envMap] = new Promise(function (resolve) {
          srcLoader.validateCubemapSrc(envMap, function loadEnvMap (urls) {
            CubeLoader.load(urls, function (cube) {
              // Texture loaded.
              self.isLoadingEnvMap = false;
              material.envMap = cube;
              resolve(cube);
            });
          });
        });
      }
    }
  },

  /**
   * Sets image texture on material as map.
   *
   * @params {string|object} src - An <img> element or url to an image file.
   */
  loadImage: {
    value: function (src) {
      var repeat = this.data.repeat;
      var repeatXY;
      var texture;
      var isEl = typeof src !== 'string';
      if (isEl) {
        texture = new THREE.Texture(src);
        texture.needsUpdate = true;
      } else {
        texture = THREE.ImageUtils.loadTexture(src);
      }
      if (repeat) {
        repeatXY = repeat.split(' ');
        if (repeatXY.length === 2) {
          texture.wrapS = THREE.RepeatWrapping;
          texture.wrapT = THREE.RepeatWrapping;
          texture.repeat.set(parseInt(repeatXY[0], 10),
                             parseInt(repeatXY[1], 10));
        }
      }
      this.material.needsUpdate = true;
      this.material.map = texture;
    }
  },

  /**
   * Creates a video element to be used as a texture.
   *
   * @params {string} src - the url pointing to the video file.
   */
  createVideoEl: {
    value: function (src) {
      var el = this.videoEl || document.createElement('video');
      function onError () {
        utils.warn('The url "$s" is not a valid image or video', src);
      }
      el.width = this.data.width;
      el.height = this.data.height;
      // Attach event listeners if brand new video element.
      if (el !== this.videoEl) {
        el.autoplay = true;
        el.loop = true;
        el.crossOrigin = true;
        el.addEventListener('error', onError, true);
        this.videoEl = el;
      }
      el.src = src;
      return el;
    }
  },

  /**
   * Sets video texture on material as map.
   *
   * @params {string|object} src - A <video> element or url to a video file.
   */
  loadVideo: {
    value: function (src) {
      // three.js video texture loader requires a <video>.
      var videoEl = typeof src !== 'string' ? src : this.createVideoEl(src);
      var texture = new THREE.VideoTexture(videoEl);
      texture.minFilter = THREE.LinearFilter;
      texture.needsUpdate = true;
      this.material.map = texture;
      this.material.needsUpdate = true;
    }
  }
});
<commit_msg>Refactors the update method of materials.<commit_after>/* global Promise */
var registerComponent = require('../core/register-component').registerComponent;
var srcLoader = require('../utils/src-loader');
var THREE = require('../../lib/three');
var utils = require('../vr-utils');

var CubeLoader = new THREE.CubeTextureLoader();
var texturePromises = {};

/**
 * Material component.
 *
 * @namespace material
 * @params {string} color - Diffuse color.
 * @params {string} envMap - To load a environment cubemap. Takes a selector
           to an element containing six img elements, or a comma-separated
           string of direct url()s.
 * @params {number} height - Height to render texture.
 * @params {number} metalness - Parameter for physical/standard material.
 * @params {number} opacity - [0-1].
 * @params {boolean} receiveLight - Determines whether the material is shaded.
 * @params {number} reflectivity - Parameter for physical/standard material.
 * @params {string} repeat - X and Y value for size of texture repeating
           (in UV units).
 * @params {number} roughness - Parameter for physical/standard material.
 * @params {string} src - To load a texture. takes a selector to an img/video
           element or a direct url().
 * @params {boolean} transparent - Whether to render transparent the alpha
           channel of a texture (e.g., .png).
 * @params {number} width - Width to render texture.
 */
module.exports.Component = registerComponent('material', {
  defaults: {
    value: {
      color: '#FFF',
      envMap: '',
      height: 360,
      metalness: 0.0,
      opacity: 1.0,
      receiveLight: true,
      reflectivity: 1.0,
      repeat: '',
      roughness: 0.5,
      src: '',
      transparent: false,
      width: 640
    }
  },

  init: {
    value: function () {
      this.textureSrc = null;
      this.isLoadingEnvMap = false;
      this.material = null;
    }
  },

  /**
   * Update or create material.
   * Returned material type depends on receiveLight.
   *   receiveLight: false - MeshBasicMaterial.
   *   receiveLight: true - MeshStandardMaterial.
   *
   * @return {object} material
   */
  update: {
    value: function () {
      var data = this.data;
      var src = data.src;
      var standardMaterial = data.receiveLight;
      var materialType = standardMaterial ? 'MeshStandardMaterial' : 'MeshBasicMaterial';
      var materialData = {
        color: new THREE.Color(data.color),
        side: this.getSides(),
        opacity: data.opacity,
        transparent: data.transparent
      };

      // Physical material extra parameters
      if (standardMaterial) {
        materialData.metalness = data.metalness;
        materialData.reflectivity = data.reflectivity;
        materialData.roughness = data.roughness;
      }

      // Create or reuse an existing material
      this.material = this.updateOrCreateMaterialHelper(materialData, materialType);
      this.el.object3D.material = this.material;

      // Load textures and/or cubmaps
      if (standardMaterial) { this.updateEnvMap(); }
      this.updateTexture(src);
    }
  },

  /*
   * Updates texture applied to the material
   *
   * @params {string|object} src - An <img> / <video> element or url to an image/video file.
   */
  updateTexture: {
    value: function (src) {
      var data = this.data;
      var material = this.material;
      if (src) {
        if (src !== this.textureSrc) {
          // Texture added or changed.
          this.textureSrc = src;
          srcLoader.validateSrc(src, loadImage, loadVideo);
        }
      } else {
        // Texture removed.
        material.map = null;
        material.needsUpdate = true;
      }
      function loadImage (src) { loadImageTexture(material, src, data.repeat); }
      function loadVideo (src) { loadVideoTexture(material, src, data.width, data.height); }
    }
  },

  /**
   * Remove material on remove (callback).
   */
  remove: {
    value: function () {
      var object3D = this.el.object3D;
      if (object3D) { object3D.material = null; }
    }
  },

  /**
   * Updates this.material using data, creates new material if this.material
   * doesn't yet exist.
   *
   * @params {object} data - Attributes to set on the material.
   * @params {string} type - Type of material to create (if necessary).
   * @returns {object} material - three.js material based on `type`.
   */
  updateOrCreateMaterialHelper: {
    value: function (data, type) {
      var material = this.material;
      var reuseMaterial = material && material.type === type;
      if (reuseMaterial) {
        Object.keys(data).forEach(function (key) {
          material[key] = data[key];
        });
      } else {
        material = new THREE[type](data);
      }
      return material;
    }
  },

  /**
   * Returns an integer for which new material face sides will be rendered.
   *
   * @returns {Integer} `THREE.DoubleSide` (`0`) or `THREE.FrontSide` (`2`)
   */
  getSides: {
    value: function () {
      var geometry = this.el.components.geometry;
      if (geometry && geometry.data.openEnded) {
        // For performance reasons, we special case open-ended cylinders
        // for rendering both faces.
        return THREE.DoubleSide;
      }
      return THREE.FrontSide;
    }
  },

  /**
   * Handle environment cubemap. Textures are cached in texturePromises.
   *
  */
  updateEnvMap: {
    value: function () {
      var self = this;
      var material = this.material;
      var envMap = this.data.envMap;
      // Environment cubemaps.
      if (!envMap || this.isLoadingEnvMap) {
        material.envMap = null;
        material.needsUpdate = true;
        return;
      }
      this.isLoadingEnvMap = true;
      if (texturePromises[envMap]) {
        // Another material is already loading this texture. Wait on promise.
        texturePromises[envMap].then(function (cube) {
          self.isLoadingEnvMap = false;
          material.envMap = cube;
          material.needsUpdate = true;
        });
      } else {
        // Material is first to load this texture. Load and resolve texture.
        texturePromises[envMap] = new Promise(function (resolve) {
          srcLoader.validateCubemapSrc(envMap, function loadEnvMap (urls) {
            CubeLoader.load(urls, function (cube) {
              // Texture loaded.
              self.isLoadingEnvMap = false;
              material.envMap = cube;
              resolve(cube);
            });
          });
        });
      }
    }
  }
});

/**
 * Sets image texture on material as map.
 *
 * @params {object} material - three.js material.
 * @params {string|object} src - An <img> element or url to an image file.
 * @params {string} repeat - X and Y value for size of texture repeating (in UV units).
 */
function loadImageTexture (material, src, repeat) {
  var repeatXY;
  var texture;
  var isEl = typeof src !== 'string';
  if (isEl) {
    texture = new THREE.Texture(src);
    texture.needsUpdate = true;
  } else {
    texture = THREE.ImageUtils.loadTexture(src);
  }
  if (repeat) {
    repeatXY = repeat.split(' ');
    if (repeatXY.length === 2) {
      texture.wrapS = THREE.RepeatWrapping;
      texture.wrapT = THREE.RepeatWrapping;
      texture.repeat.set(parseInt(repeatXY[0], 10),
                         parseInt(repeatXY[1], 10));
    }
  }
  material.needsUpdate = true;
  material.map = texture;
}

/**
 * Creates a video element to be used as a texture.
 *
 * @params {object} material - three.js material.
 * @params {string} src - url to a video file.
 * @params {number} width - width of the video.
 * @params {number} height - height of the video.
 */
function createVideoEl (material, src, width, height) {
  var el = material.videoEl || document.createElement('video');
  function onError () {
    utils.warn('The url "$s" is not a valid image or video', src);
  }
  el.width = width;
  el.height = height;
  // Attach event listeners if brand new video element.
  if (el !== this.videoEl) {
    el.autoplay = true;
    el.loop = true;
    el.crossOrigin = true;
    el.addEventListener('error', onError, true);
    material.videoEl = el;
  }
  el.src = src;
  return el;
}

/**
 * Sets video texture on material as map.
 * @params {object} material - three.js material.
 * @params {string} src - url to a video file.
 * @params {number} width - width of the video.
 * @params {number} height - height of the video.
 *
*/
function loadVideoTexture (material, src, height, width) {
  // three.js video texture loader requires a <video>.
  var videoEl = typeof src !== 'string' ? src : createVideoEl(material, src, height, width);
  var texture = new THREE.VideoTexture(videoEl);
  texture.minFilter = THREE.LinearFilter;
  texture.needsUpdate = true;
  material.map = texture;
  material.needsUpdate = true;
}
<|endoftext|>