<commit_before>import {
  platform, DefaultDOMElement, AbstractEditor,
  Toolbar, EditorSession, DOMSelection,
  Configurator
} from 'substance'
import ContextSection from '../shared/ContextSection'
import SheetStatusBar from './SheetStatusBar'
import FormulaBar from './FormulaBar'
import FormulaEditor from './FormulaEditor'
import FunctionUsageCommand from '../shared/FunctionUsageCommand'
import FunctionUsageTool from '../shared/FunctionUsageTool'
import CodeEditorPackage from '../shared/CodeEditorPackage'
import analyseCode from '../shared/analyseCode'
import { getCellLabel } from './sheetHelpers'

export default class SheetEditor extends AbstractEditor {

  constructor(...args) {
    super(...args)
    // a context for FormulaBar and FormulaEditor
    this._formulaEditorContext = this._createFormulaEditorContext()
    // true when the cursor is either in the FormularBar or the FormulaEditor
    this._isEditing = false

    this.handleActions({
      'updateCell': this._updateCell,
      'cancelCellEditing': this._cancelCellEditing,
      'editCell': this._editCell,
      'requestSelectionChange': this._requestSelectionChange,
      'selectAll': this._selectAll,
      'executeCommand': this._executeCommand,
      'help': this._openDocumentation,
    })
  }

  getChildContext() {
    const editorSession = this.props.editorSession
    const keyboardManager = this.keyboardManager
    const configurator = editorSession.getConfigurator()
    const issueManager = editorSession.getManager('issue-manager')
    const host = this.context.host
    return Object.assign({}, super.getChildContext(), {
      configurator,
      host,
      issueManager,
      keyboardManager
    })
  }

  getInitialState() {
    let sheetState = this._getSheetState()
    return {
      showContext: false,
      contextId: null,
      cellId: null,
      displayMode: sheetState.displayMode
    }
  }

  didMount() {
    super.didMount()

    const editorSession = this.props.editorSession
    if (platform.inBrowser) {
      DefaultDOMElement.wrap(window).on('resize', this._onResize, this)
    }
    editorSession.onUpdate('selection', this._onSelectionChange, this)
    editorSession.onUpdate('document', this._onSheetStateChange, this, {
      path: ['sheet.state']
    })

    this._formulaEditorContext.editorSession.onUpdate('selection', this._onCellEditorSelectionChange, this)

    this.rerender()

    // set the selection into the first cell
    // Doing this delayed to be in a new flow
    setTimeout(() => {
      editorSession.setSelection(
        this.getSheetComponent().selectFirstCell()
      )
    }, 0)
  }

  dispose() {
    super.dispose()
    const editorSession = this.props.editorSession
    if (platform.inBrowser) {
      DefaultDOMElement.wrap(window).off(this)
    }
    editorSession.off(this)
    this._formulaEditorContext.editorSession.off(this)
  }

  render($$) {
    let el = $$('div').addClass('sc-sheet-editor')
    el.addClass('sm-display-mode-'+this.state.displayMode)
    el.on('keydown', super.onKeyDown)
    el.append(
      $$('div').addClass('se-main-section').append(
        this._renderToolpane($$),
        this._renderContent($$),
        this._renderStatusbar($$)
      )
    )
    if(this.state.showContext) {
      el.append(
        $$(ContextSection, {
          contextId: this.state.contextId,
          cellId: this.state.cellId,
          sectionId: this.state.sectionId
        }).ref('context')
      )
    }
    return el
  }

  _renderToolpane($$) {
    const configurator = this.getConfigurator()
    let el = $$('div').addClass('se-tool-pane')
    el.append(
      $$(FormulaBar, {
        node: this._formulaEditorContext.node,
        context: this._formulaEditorContext
      }),
      $$(Toolbar, {
        toolPanel: configurator.getToolPanel('toolbar')
      }).ref('toolbar')
    )
    return el
  }

  _renderContent($$) {
    let el = $$('div').addClass('se-body')
    el.append(
      this._renderSheet($$)
    )
    return el
  }

  _renderSheet($$) {
    const sheet = this.getDocument()
    const formulaEditorContext = this._formulaEditorContext
    // only rendering the sheet when mounted
    // so that we have real width and height
    if (this.isMounted()) {
      const SheetComponent = this.getComponent('sheet')
      return $$(SheetComponent, {
        sheet,
        overlays: [
          $$(FormulaEditor, {
            context: formulaEditorContext
          }).ref('formulaEditor')
            .css({
              position: 'absolute',
              display: 'none'
            })
        ]
      }).ref('sheet')
        // LEGACY
        // TODO: the displayMode is app specific
        // so it should be set on the sc-sheet-editor
        // and the CSS should be reflect this
        .addClass('sm-mode-'+this.state.displayMode)
    } else {
      return $$('div')
    }
  }

  _renderStatusbar($$) {
    return $$(SheetStatusBar, {}).ref('sheet-statusbar')
  }

  getWidth() {
    if (this.el) {
      return this.el.getWidth()
    } else {
      return 1000
    }
  }

  getHeight() {
    if (this.el) {
      return this.el.getHeight()
    } else {
      return 750
    }
  }

  getSheetComponent() {
    return this.refs.sheet
  }

  setSelectionOnCell(cellId) {
    const sheet = this.getDocument()
    let cell = sheet.get(cellId)
    let row = cell.parentNode
    let colIdx = row._childNodes.indexOf(cell.id)
    let rowIdx = row.parentNode._childNodes.indexOf(row.id)
    let selData = {
      type: 'range',
      anchorRow: rowIdx,
      focusRow: rowIdx,
      anchorCol: colIdx,
      focusCol: colIdx
    }

    this.props.editorSession.setSelection({
      type: 'custom',
      customType: 'sheet',
      data: selData,
      surfaceId: this.refs.sheet.getId()
    })
  }

  toggleContext(contextId, cellId) {
    if(cellId === null && !this.state.showContext) return
    if (this.state.showContext && this.state.contextId === contextId && cellId === undefined) {
      this.setState({
        showContext: false
      })
    } else {
      this.setState({
        showContext: true,
        contextId,
        cellId
      })
    }
  }

  // a context propagated by FormulaBar and FormulaEditor
  _createFormulaEditorContext() {
    const configurator = new Configurator()
    configurator.import(CodeEditorPackage)
    // TODO: let's see if we can generalize this, so that it can
    // go into the CodeEditorPackage
    configurator.addCommand('function-usage', FunctionUsageCommand, {
      commandGroup: 'prompt'
    })
    configurator.addTool('function-usage', FunctionUsageTool)
    configurator.addToolPanel('prompt', [
      {
        name: 'prompt',
        type: 'tool-group',
        showDisabled: false,
        commandGroups: ['prompt']
      }
    ])

    // a document with only one node used by cell editors
    // i.e. expression bar, or popover editor on enter
    let cellEditorDoc = configurator.createDocument()
    let node = cellEditorDoc.create({
      id: 'cell',
      type: 'cell',
      content: ''
    })
    let editorSession = new EditorSession(cellEditorDoc, { configurator })
    const self = this
    // provide an adapter for DOMSelection
    // TODO: either use a helper to create the DOMSelection or change DOMSelection's ctor to be better usable
    let domSelection = new DOMSelection({
      getDocument() { return cellEditorDoc },
      getSurfaceManager() { return editorSession.surfaceManager },
      getElement() { return self.getElement() }
    })
    return {
      configurator,
      editorSession,
      domSelection,
      node,
      markersManager: editorSession.markersManager,
      surfaceManager: editorSession.surfaceManager,
      commandManager: editorSession.commandManager,
      commandGroups: configurator.getCommandGroups(),
      tools: configurator.getTools(),
      functionManager: this.context.host.functionManager
    }
  }

  _onResize() {
    if (platform.inBrowser) {
      if (!this._rafId) {
        this._rafId = window.requestAnimationFrame(() => {
          this._rafId = null
          this.refs.sheet.forceUpdate()
        })
      }
    }
  }

  _getSheetSelection() {
    return this.getEditorSession().getSelection().data || {}
  }

  _onSelectionChange(sel) {
    // TODO: what to do if the sheet seleciton is null?
    if (!sel) return

    let formulaEditorSession = this._formulaEditorContext.editorSession
    let cell = this._getAnchorCell()
    if (cell) {
      this._setFormula(cell.textContent)
    }
    if (this._isEditing) {
      this._isEditing = false
      this._hideFormulaEditor()
      formulaEditorSession.setSelection(null)
    }
  }

  _onCellEditorSelectionChange(sel) {
    let sheetSel = this._getSheetSelection()
    let formulaEditorSession = this._formulaEditorContext.editorSession
    if (!sel.isNull() && !this._isEditing) {
      this._isEditing = true
      this._currentSelection = this.getEditorSession().getSelection()
      this._showFormulaEditor(sheetSel.anchorRow, sheetSel.anchorCol)
      formulaEditorSession.resetHistory()
    }

    const commandState = formulaEditorSession.getCommandStates()
    const functionUsageState = commandState['function-usage']
    const position = functionUsageState.position
    if(position !== undefined) {
      const cellState = formulaEditorSession.getDocument().get(['cell','state'])
      const tokens = cellState.tokens
    }
  }

  _onSheetStateChange() {
    const sheet = this.props.editorSession.getDocument()
    const sheetState = sheet.getState()
    if (this.state.displayMode !== sheetState.displayMode) {
      this.getSheetComponent().forceUpdate()
      this.extendState({
        displayMode: sheetState.displayMode
      })
    }
  }

  /*
    This gets called when the user starts editing a cell
    At this time it should be sure that the table cell
    is already rendered.
  */
  _showFormulaEditor(rowIdx, colIdx) {
    const formulaEditor = this.refs.formulaEditor
    const sheetComponent = this.getSheetComponent()
    // only show if we actually get a rectangle
    // e.g. this is null if the cell is not in the
    // viewport
    let rect = sheetComponent.getCellRect(rowIdx, colIdx)
    if (rect) {
      formulaEditor.css({
        display: 'block',
        position: 'absolute',
        top: rect.top,
        left: rect.left,
        "min-width": rect.width+'px',
        "min-height": rect.height+'px'
      })
    } else {
      formulaEditor.css({
        display: 'none'
      })
    }
  }

  _hideFormulaEditor() {
    const formulaEditor = this.refs.formulaEditor
    formulaEditor.css({
      display: 'none',
      top: 0, left: 0
    })
  }

  _setFormula(val, sel) {
    let context = this._formulaEditorContext
    let node = context.node
    let formulaEditorSession = this._formulaEditorContext.editorSession
    formulaEditorSession.transaction(tx => {
      tx.set(node.getPath(), val)
      tx.setSelection(sel)
    })
  }

  _cancelCellEditing() {
    // just renew the the selection
    let editorSession = this.getEditorSession()
    editorSession.setSelection(editorSession.getSelection())
  }

  /*
    Request inline editor
  */
  _editCell(initialValue) {
    const formulaEditorSession = this._formulaEditorContext.editorSession
    const formulaNode = this._formulaEditorContext.node
    if (initialValue) {
      this._setFormula(initialValue)
    }
    const path = formulaNode.getPath()
    const text = formulaNode.getText()
    const startOffset = text.length
    formulaEditorSession.setSelection({
      type: 'property',
      path,
      startOffset,
      surfaceId: 'formula-editor'
    })
  }

  _replaceEditorToken(from, to) {
    const formulaEditorSession = this._formulaEditorContext.editorSession
    const selection = formulaEditorSession.getSelection().toJSON()
    const commandState = formulaEditorSession.getCommandStates()
    const functionUsageState = commandState['function-usage']
    formulaEditorSession.transaction(tx => {
      if(functionUsageState.position) {
        selection.startOffset = functionUsageState.position.start
        selection.endOffset = functionUsageState.position.end
        tx.setSelection(selection)
      }
      const range = from + ':' + to
      tx.insertText(range)
      if(!functionUsageState.position) {
        if(selection.startOffset === selection.endOffset) {
          selection.endOffset += range.length
        }
        tx.setSelection(selection)
      }
    })
  }

  _requestSelectionChange(newSelection) {
    let editorSession = this.getEditorSession()
    if (this._isEditing) {
      //console.info('TODO: Implement range selector', newSelection)
      //const formulaEditorSession = this._formulaEditorContext.editorSession
      const formulaEditorSession = this._formulaEditorContext.editorSession
      const commandState = formulaEditorSession.getCommandStates()
      const functionUsageState = commandState['function-usage']
      if(functionUsageState.functionName) {
        const selData = newSelection.data
        const fromCell = getCellLabel(selData.anchorRow, selData.anchorCol)
        const toCell = getCellLabel(selData.focusRow, selData.focusCol)
        this._replaceEditorToken(fromCell, toCell)
        const sheetComp = this.getSheetComponent()
        sheetComp._positionRangeSelection(newSelection)
      }
      //sheetComp._hideSelection(newSelection)
    } else {
      editorSession.setSelection(newSelection)
    }
  }

  _updateCell() {
    let editorSession = this.getEditorSession()
    let cell = this._getAnchorCell()
    let oldValue = cell.getText()
    let newValue = this._formulaEditorContext.node.getText()

    let newSel = this.refs.sheet.shiftSelection(1, 0, false)
    // skip if there is no change
    if (oldValue !== newValue) {
      // collapsing the selection to the anchor cell
      // so that on undo/redo only the change cell is selected
      let selBefore = this._currentSelection
      selBefore.data.focusRow = selBefore.data.anchorRow
      selBefore.data.focusCol = selBefore.data.anchorCol
      // HACK: need to set the selection 'silently' so that
      // this works fine with undo/redo (-> before state)
      editorSession._setSelection(selBefore)
      editorSession.transaction(tx => {
        tx.set(cell.getPath(), newValue)
      })
    }
    // setting the selection in the transaction
    // leads to an inintuitiv undo/redo behavior
    // thus we are updating the selection in an extra update here
    editorSession.setSelection(newSel)
  }

  _getAnchorCell() {
    let sel = this._getSheetSelection()
    return this.getDocument().getCell(sel.anchorRow, sel.anchorCol)
  }

  _getSheetState() {
    const editorSession = this.props.editorSession
    const sheet = editorSession.getDocument()
    return sheet.getState()
  }

  _selectAll() {
    this._executeCommand('sheet:select-all')
  }

  _executeCommand(commandName, params) {
    // TODO: soon we will pull out CommandManager from EditorSession
    let commandManager = this.commandManager
    commandManager.executeCommand(commandName, params)
  }

  _openDocumentation(helpPath) {
    const path = helpPath.split(':')
    const contextId = path[0]
    const sectionId = path[1]
    this.setState({
      showContext: true,
      contextId,
      sectionId
    })
  }
}
<commit_msg>Complete bidirectional reference selecions.<commit_after>import {
  platform, DefaultDOMElement, AbstractEditor,
  Toolbar, EditorSession, DOMSelection,
  Configurator
} from 'substance'
import ContextSection from '../shared/ContextSection'
import SheetStatusBar from './SheetStatusBar'
import FormulaBar from './FormulaBar'
import FormulaEditor from './FormulaEditor'
import FunctionUsageCommand from '../shared/FunctionUsageCommand'
import FunctionUsageTool from '../shared/FunctionUsageTool'
import CodeEditorPackage from '../shared/CodeEditorPackage'
import { getCellLabel } from './sheetHelpers'
import { getRowCol } from '../shared/cellHelpers'

export default class SheetEditor extends AbstractEditor {

  constructor(...args) {
    super(...args)
    // a context for FormulaBar and FormulaEditor
    this._formulaEditorContext = this._createFormulaEditorContext()
    // true when the cursor is either in the FormularBar or the FormulaEditor
    this._isEditing = false

    this.handleActions({
      'updateCell': this._updateCell,
      'cancelCellEditing': this._cancelCellEditing,
      'editCell': this._editCell,
      'requestSelectionChange': this._requestSelectionChange,
      'selectAll': this._selectAll,
      'executeCommand': this._executeCommand,
      'help': this._openDocumentation,
    })
  }

  getChildContext() {
    const editorSession = this.props.editorSession
    const keyboardManager = this.keyboardManager
    const configurator = editorSession.getConfigurator()
    const issueManager = editorSession.getManager('issue-manager')
    const host = this.context.host
    return Object.assign({}, super.getChildContext(), {
      configurator,
      host,
      issueManager,
      keyboardManager
    })
  }

  getInitialState() {
    let sheetState = this._getSheetState()
    return {
      showContext: false,
      contextId: null,
      cellId: null,
      displayMode: sheetState.displayMode
    }
  }

  didMount() {
    super.didMount()

    const editorSession = this.props.editorSession
    if (platform.inBrowser) {
      DefaultDOMElement.wrap(window).on('resize', this._onResize, this)
    }
    editorSession.onUpdate('selection', this._onSelectionChange, this)
    editorSession.onUpdate('document', this._onSheetStateChange, this, {
      path: ['sheet.state']
    })

    this._formulaEditorContext.editorSession.onUpdate('selection', this._onCellEditorSelectionChange, this)

    this.rerender()

    // set the selection into the first cell
    // Doing this delayed to be in a new flow
    setTimeout(() => {
      editorSession.setSelection(
        this.getSheetComponent().selectFirstCell()
      )
    }, 0)
  }

  dispose() {
    super.dispose()
    const editorSession = this.props.editorSession
    if (platform.inBrowser) {
      DefaultDOMElement.wrap(window).off(this)
    }
    editorSession.off(this)
    this._formulaEditorContext.editorSession.off(this)
  }

  render($$) {
    let el = $$('div').addClass('sc-sheet-editor')
    el.addClass('sm-display-mode-'+this.state.displayMode)
    el.on('keydown', super.onKeyDown)
    el.append(
      $$('div').addClass('se-main-section').append(
        this._renderToolpane($$),
        this._renderContent($$),
        this._renderStatusbar($$)
      )
    )
    if(this.state.showContext) {
      el.append(
        $$(ContextSection, {
          contextId: this.state.contextId,
          cellId: this.state.cellId,
          sectionId: this.state.sectionId
        }).ref('context')
      )
    }
    return el
  }

  _renderToolpane($$) {
    const configurator = this.getConfigurator()
    let el = $$('div').addClass('se-tool-pane')
    el.append(
      $$(FormulaBar, {
        node: this._formulaEditorContext.node,
        context: this._formulaEditorContext
      }),
      $$(Toolbar, {
        toolPanel: configurator.getToolPanel('toolbar')
      }).ref('toolbar')
    )
    return el
  }

  _renderContent($$) {
    let el = $$('div').addClass('se-body')
    el.append(
      this._renderSheet($$)
    )
    return el
  }

  _renderSheet($$) {
    const sheet = this.getDocument()
    const formulaEditorContext = this._formulaEditorContext
    // only rendering the sheet when mounted
    // so that we have real width and height
    if (this.isMounted()) {
      const SheetComponent = this.getComponent('sheet')
      return $$(SheetComponent, {
        sheet,
        overlays: [
          $$(FormulaEditor, {
            context: formulaEditorContext
          }).ref('formulaEditor')
            .css({
              position: 'absolute',
              display: 'none'
            })
        ]
      }).ref('sheet')
        // LEGACY
        // TODO: the displayMode is app specific
        // so it should be set on the sc-sheet-editor
        // and the CSS should be reflect this
        .addClass('sm-mode-'+this.state.displayMode)
    } else {
      return $$('div')
    }
  }

  _renderStatusbar($$) {
    return $$(SheetStatusBar, {}).ref('sheet-statusbar')
  }

  getWidth() {
    if (this.el) {
      return this.el.getWidth()
    } else {
      return 1000
    }
  }

  getHeight() {
    if (this.el) {
      return this.el.getHeight()
    } else {
      return 750
    }
  }

  getSheetComponent() {
    return this.refs.sheet
  }

  setSelectionOnCell(cellId) {
    const sheet = this.getDocument()
    let cell = sheet.get(cellId)
    let row = cell.parentNode
    let colIdx = row._childNodes.indexOf(cell.id)
    let rowIdx = row.parentNode._childNodes.indexOf(row.id)
    let selData = {
      type: 'range',
      anchorRow: rowIdx,
      focusRow: rowIdx,
      anchorCol: colIdx,
      focusCol: colIdx
    }

    this.props.editorSession.setSelection({
      type: 'custom',
      customType: 'sheet',
      data: selData,
      surfaceId: this.refs.sheet.getId()
    })
  }

  toggleContext(contextId, cellId) {
    if(cellId === null && !this.state.showContext) return
    if (this.state.showContext && this.state.contextId === contextId && cellId === undefined) {
      this.setState({
        showContext: false
      })
    } else {
      this.setState({
        showContext: true,
        contextId,
        cellId
      })
    }
  }

  // a context propagated by FormulaBar and FormulaEditor
  _createFormulaEditorContext() {
    const configurator = new Configurator()
    configurator.import(CodeEditorPackage)
    // TODO: let's see if we can generalize this, so that it can
    // go into the CodeEditorPackage
    configurator.addCommand('function-usage', FunctionUsageCommand, {
      commandGroup: 'prompt'
    })
    configurator.addTool('function-usage', FunctionUsageTool)
    configurator.addToolPanel('prompt', [
      {
        name: 'prompt',
        type: 'tool-group',
        showDisabled: false,
        commandGroups: ['prompt']
      }
    ])

    // a document with only one node used by cell editors
    // i.e. expression bar, or popover editor on enter
    let cellEditorDoc = configurator.createDocument()
    let node = cellEditorDoc.create({
      id: 'cell',
      type: 'cell',
      content: ''
    })
    let editorSession = new EditorSession(cellEditorDoc, { configurator })
    const self = this
    // provide an adapter for DOMSelection
    // TODO: either use a helper to create the DOMSelection or change DOMSelection's ctor to be better usable
    let domSelection = new DOMSelection({
      getDocument() { return cellEditorDoc },
      getSurfaceManager() { return editorSession.surfaceManager },
      getElement() { return self.getElement() }
    })
    return {
      configurator,
      editorSession,
      domSelection,
      node,
      markersManager: editorSession.markersManager,
      surfaceManager: editorSession.surfaceManager,
      commandManager: editorSession.commandManager,
      commandGroups: configurator.getCommandGroups(),
      tools: configurator.getTools(),
      functionManager: this.context.host.functionManager
    }
  }

  _onResize() {
    if (platform.inBrowser) {
      if (!this._rafId) {
        this._rafId = window.requestAnimationFrame(() => {
          this._rafId = null
          this.refs.sheet.forceUpdate()
        })
      }
    }
  }

  _getSheetSelection() {
    return this.getEditorSession().getSelection().data || {}
  }

  _onSelectionChange(sel) {
    // TODO: what to do if the sheet seleciton is null?
    if (!sel) return

    let formulaEditorSession = this._formulaEditorContext.editorSession
    let cell = this._getAnchorCell()
    if (cell) {
      this._setFormula(cell.textContent)
    }
    if (this._isEditing) {
      this._isEditing = false
      this._hideFormulaEditor()
      formulaEditorSession.setSelection(null)
    }
  }

  _setReferenceSelection(reference) {
    const from = reference.split(':')[0]
    const to = reference.split(':')[1]
    const [startRow, startCol] = getRowCol(from)
    const [endRow, endCol] = to ? getRowCol(to) : [startRow, startCol]

    const selData = {
      type: 'range',
      anchorRow: startRow,
      focusRow: endRow ? endRow : startRow,
      anchorCol: startCol,
      focusCol: endCol ? endCol: startCol
    }

    const sheetComp = this.getSheetComponent()
    sheetComp._positionRangeSelection({
      type: 'custom',
      customType: 'sheet',
      data: selData,
      surfaceId: this.refs.sheet.getId()
    })
  }

  _onCellEditorSelectionChange(sel) {
    let sheetSel = this._getSheetSelection()
    let formulaEditorSession = this._formulaEditorContext.editorSession
    if (!sel.isNull() && !this._isEditing) {
      this._isEditing = true
      this._currentSelection = this.getEditorSession().getSelection()
      this._showFormulaEditor(sheetSel.anchorRow, sheetSel.anchorCol)
      formulaEditorSession.resetHistory()
    }

    const commandState = formulaEditorSession.getCommandStates()
    const functionUsageState = commandState['function-usage']
    const position = functionUsageState.position
    if(position !== undefined) {
      const formulaSelection = formulaEditorSession.getSelection()
      if(formulaSelection.type !== 'null') {
        const cursorOffset = formulaSelection.start.offset
        const cellState = formulaEditorSession.getDocument().get(['cell','state'])
        const tokens = cellState.tokens
        const activeToken = tokens.find(token => {
          return token.type === 'cell' && cursorOffset >= token.start && cursorOffset <= token.end
        })

        if(activeToken) {
          const cellReference = activeToken.text
          this._setReferenceSelection(cellReference)
        } else {
          const sheetComp = this.getSheetComponent()
          sheetComp._hideSelection()
        }
      }
    }
  }

  _onSheetStateChange() {
    const sheet = this.props.editorSession.getDocument()
    const sheetState = sheet.getState()
    if (this.state.displayMode !== sheetState.displayMode) {
      this.getSheetComponent().forceUpdate()
      this.extendState({
        displayMode: sheetState.displayMode
      })
    }
  }

  /*
    This gets called when the user starts editing a cell
    At this time it should be sure that the table cell
    is already rendered.
  */
  _showFormulaEditor(rowIdx, colIdx) {
    const formulaEditor = this.refs.formulaEditor
    const sheetComponent = this.getSheetComponent()
    // only show if we actually get a rectangle
    // e.g. this is null if the cell is not in the
    // viewport
    let rect = sheetComponent.getCellRect(rowIdx, colIdx)
    if (rect) {
      formulaEditor.css({
        display: 'block',
        position: 'absolute',
        top: rect.top,
        left: rect.left,
        "min-width": rect.width+'px',
        "min-height": rect.height+'px'
      })
    } else {
      formulaEditor.css({
        display: 'none'
      })
    }
  }

  _hideFormulaEditor() {
    const formulaEditor = this.refs.formulaEditor
    formulaEditor.css({
      display: 'none',
      top: 0, left: 0
    })
  }

  _setFormula(val, sel) {
    let context = this._formulaEditorContext
    let node = context.node
    let formulaEditorSession = this._formulaEditorContext.editorSession
    formulaEditorSession.transaction(tx => {
      tx.set(node.getPath(), val)
      tx.setSelection(sel)
    })
  }

  _cancelCellEditing() {
    // just renew the the selection
    let editorSession = this.getEditorSession()
    editorSession.setSelection(editorSession.getSelection())
  }

  /*
    Request inline editor
  */
  _editCell(initialValue) {
    const formulaEditorSession = this._formulaEditorContext.editorSession
    const formulaNode = this._formulaEditorContext.node
    if (initialValue) {
      this._setFormula(initialValue)
    }
    const path = formulaNode.getPath()
    const text = formulaNode.getText()
    const startOffset = text.length
    formulaEditorSession.setSelection({
      type: 'property',
      path,
      startOffset,
      surfaceId: 'formula-editor'
    })
  }

  _replaceEditorToken(from, to) {
    const formulaEditorSession = this._formulaEditorContext.editorSession
    const selection = formulaEditorSession.getSelection().toJSON()
    const commandState = formulaEditorSession.getCommandStates()
    const functionUsageState = commandState['function-usage']
    formulaEditorSession.transaction(tx => {
      if(functionUsageState.position) {
        selection.startOffset = functionUsageState.position.start
        selection.endOffset = functionUsageState.position.end
        tx.setSelection(selection)
      }
      const range = from + ':' + to
      tx.insertText(range)
      if(!functionUsageState.position) {
        if(selection.startOffset === selection.endOffset) {
          selection.endOffset += range.length
        }
        tx.setSelection(selection)
      }
    })
  }

  _requestSelectionChange(newSelection) {
    let editorSession = this.getEditorSession()
    if (this._isEditing) {
      //console.info('TODO: Implement range selector', newSelection)
      //const formulaEditorSession = this._formulaEditorContext.editorSession
      const formulaEditorSession = this._formulaEditorContext.editorSession
      const commandState = formulaEditorSession.getCommandStates()
      const functionUsageState = commandState['function-usage']
      if(functionUsageState.functionName) {
        const selData = newSelection.data
        const fromCell = getCellLabel(selData.anchorRow, selData.anchorCol)
        const toCell = getCellLabel(selData.focusRow, selData.focusCol)
        this._replaceEditorToken(fromCell, toCell)
        const sheetComp = this.getSheetComponent()
        sheetComp._positionRangeSelection(newSelection)
      }
      //sheetComp._hideSelection(newSelection)
    } else {
      editorSession.setSelection(newSelection)
    }
  }

  _updateCell() {
    let editorSession = this.getEditorSession()
    let cell = this._getAnchorCell()
    let oldValue = cell.getText()
    let newValue = this._formulaEditorContext.node.getText()

    let newSel = this.refs.sheet.shiftSelection(1, 0, false)
    // skip if there is no change
    if (oldValue !== newValue) {
      // collapsing the selection to the anchor cell
      // so that on undo/redo only the change cell is selected
      let selBefore = this._currentSelection
      selBefore.data.focusRow = selBefore.data.anchorRow
      selBefore.data.focusCol = selBefore.data.anchorCol
      // HACK: need to set the selection 'silently' so that
      // this works fine with undo/redo (-> before state)
      editorSession._setSelection(selBefore)
      editorSession.transaction(tx => {
        tx.set(cell.getPath(), newValue)
      })
    }
    // setting the selection in the transaction
    // leads to an inintuitiv undo/redo behavior
    // thus we are updating the selection in an extra update here
    editorSession.setSelection(newSel)
  }

  _getAnchorCell() {
    let sel = this._getSheetSelection()
    return this.getDocument().getCell(sel.anchorRow, sel.anchorCol)
  }

  _getSheetState() {
    const editorSession = this.props.editorSession
    const sheet = editorSession.getDocument()
    return sheet.getState()
  }

  _selectAll() {
    this._executeCommand('sheet:select-all')
  }

  _executeCommand(commandName, params) {
    // TODO: soon we will pull out CommandManager from EditorSession
    let commandManager = this.commandManager
    commandManager.executeCommand(commandName, params)
  }

  _openDocumentation(helpPath) {
    const path = helpPath.split(':')
    const contextId = path[0]
    const sectionId = path[1]
    this.setState({
      showContext: true,
      contextId,
      sectionId
    })
  }
}
<|endoftext|>


<commit_before>/**
 *
 *  IntersectionQuery.js
 *
 *  @copyright 2017 Kevin Lindsey
 *
 */

/**
 *
 */
var IntersectionQuery = {};

/**
 *  pointInCircle
 *
 *  @param {Point2D} point
 *  @param {Point2D} center
 *  @param {Number} radius
 *  @return {Boolean}
 */
IntersectionQuery.pointInCircle = function(point, center, radius) {
    var v = Vector.fromPoints(center, point);

    return v.length() <= radius;
};

/**
 *  pointInEllipse
 *
 *  @param {Point2D} point
 *  @param {Point2D} center
 *  @param {Number} radiusX
 *  @param {Number} radiusY
 *  @return {Boolean}
 */
IntersectionQuery.pointInEllipse = function(point, center, radiusX, radiusY) {
    var len = point.subtract(center);

    return ( len.x * len.x ) / ( radiusX * radiusX ) + ( len.y * len.y ) / ( radiusY * radiusY ) <= 1;
};

/**
 *  pointInPolyline
 *
 *  @param {Point2D} point
 *  @param {Array<Point2D>} points
 */
IntersectionQuery.pointInPolyline = function(point, points) {
    var length  = points.length;
    var counter = 0;
    var x_inter;

    var p1 = points[0];

    for ( var i = 1; i <= length; i++ ) {
        var p2 = points[i%length];
        var minY = Math.min(p1.y, p2.y);
        var maxY = Math.max(p1.y, p2.y);
        var maxX = Math.max(p1.x, p2.x);

        if ( p1.y != p2.y && minY < point.y && point.y <= maxY && point.x <= maxX ) {
            x_inter = (point.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;

            if ( p1.x == p2.x || point.x <= x_inter ) {
                counter++;
            }
        }

        p1 = p2;
    }

    return ( counter % 2 == 1 );
};

/**
 *  pointInPolyline
 *
 *  @param {Point2D} point
 *  @param {Array<Point2D>} points
 */
IntersectionQuery.pointInPolygon = IntersectionQuery.pointInPolyline;

/**
 *  pointInRectangle
 *
 *  @param {Point2D} point
 *  @param {Point2D} topLeft
 *  @param {Point2D} bottomRight
 *  @return {Boolean}
 */
IntersectionQuery.pointInRectangle = function(point, topLeft, bottomRight) {
    return (
        topLeft.x <= point.x && point.x < bottomRight.x &&
        topleft.y <= point.y && point.y < bottomRight.y
    );
};

if (typeof module !== "undefined") {
    module.exports = IntersectionQuery;
}
<commit_msg>Fix class reference<commit_after>/**
 *
 *  IntersectionQuery.js
 *
 *  @copyright 2017 Kevin Lindsey
 *
 */

/**
 *
 */
var IntersectionQuery = {};

/**
 *  pointInCircle
 *
 *  @param {Point2D} point
 *  @param {Point2D} center
 *  @param {Number} radius
 *  @return {Boolean}
 */
IntersectionQuery.pointInCircle = function(point, center, radius) {
    var v = Vector2D.fromPoints(center, point);

    return v.length() <= radius;
};

/**
 *  pointInEllipse
 *
 *  @param {Point2D} point
 *  @param {Point2D} center
 *  @param {Number} radiusX
 *  @param {Number} radiusY
 *  @return {Boolean}
 */
IntersectionQuery.pointInEllipse = function(point, center, radiusX, radiusY) {
    var len = point.subtract(center);

    return ( len.x * len.x ) / ( radiusX * radiusX ) + ( len.y * len.y ) / ( radiusY * radiusY ) <= 1;
};

/**
 *  pointInPolyline
 *
 *  @param {Point2D} point
 *  @param {Array<Point2D>} points
 */
IntersectionQuery.pointInPolyline = function(point, points) {
    var length  = points.length;
    var counter = 0;
    var x_inter;

    var p1 = points[0];

    for ( var i = 1; i <= length; i++ ) {
        var p2 = points[i%length];
        var minY = Math.min(p1.y, p2.y);
        var maxY = Math.max(p1.y, p2.y);
        var maxX = Math.max(p1.x, p2.x);

        if ( p1.y != p2.y && minY < point.y && point.y <= maxY && point.x <= maxX ) {
            x_inter = (point.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;

            if ( p1.x == p2.x || point.x <= x_inter ) {
                counter++;
            }
        }

        p1 = p2;
    }

    return ( counter % 2 == 1 );
};

/**
 *  pointInPolyline
 *
 *  @param {Point2D} point
 *  @param {Array<Point2D>} points
 */
IntersectionQuery.pointInPolygon = IntersectionQuery.pointInPolyline;

/**
 *  pointInRectangle
 *
 *  @param {Point2D} point
 *  @param {Point2D} topLeft
 *  @param {Point2D} bottomRight
 *  @return {Boolean}
 */
IntersectionQuery.pointInRectangle = function(point, topLeft, bottomRight) {
    return (
        topLeft.x <= point.x && point.x < bottomRight.x &&
        topleft.y <= point.y && point.y < bottomRight.y
    );
};

if (typeof module !== "undefined") {
    module.exports = IntersectionQuery;
}
<|endoftext|>


<commit_before>(function() {
  // extend swal with a function for adding forms
  swal.withForm = function() {
    // initialize with field values supplied on `swal.withForm` call
    var swalForm = new SwalForm(arguments[0].formFields)
    // prevent successive calls to add duplicated form fields
    swalForm.removeSwalForm()
    // make form values inserted by the user available at `doneFunction`
    swalForm.addWayToGetFormValuesInDoneFunction(arguments)

    // forward arguments
    swal.apply({}, arguments)

    var htmlForm = swalForm.generateHtmlForm()
    swalForm.insertFormInSwalModal(htmlForm)
    swalForm.allowClickingDirectlyOnInputs()
    swalForm.focusOnFirstInput()
    swalForm.markFirstRadioButtons()
  }

  // constructor for helper object
  function SwalForm(formFields) {
    this.formFields = formFields
  }

  // helper methods
  extend(SwalForm.prototype, {
    formClass: 'swal-form',
    generateHtmlForm: function() {
      var form = {
        clazz: this.formClass,
        innerHtml: this.formFields.map(toFormTag.bind(this)).reduce(toSingleString)
      }

      return t("
{innerHtml}
", form)

      function toFormTag(field) {
        var input = Input(field)
        // to separate groups of checkboxes and radiobuttons in different lines
        var conditionalLineBreak = (input.isRadioOrCheckbox() && this.lastFieldName != field.name) ? '
' : ''
        this.lastFieldName = field.name

        return conditionalLineBreak + input.toHtml()
      }
    },
    addWayToGetFormValuesInDoneFunction: function(swalArgs) {
      var swalFormInstance = this
      var doneFunction = swalArgs[1]
      swalArgs[1] = function() {
        // make form values available at `this` variable inside doneFunction
        this.swalForm = swalFormInstance.getFormValues()
        doneFunction.apply(this, arguments)
      }
    },
    getFormValues: function() {
      var inputHtmlCollection = document.querySelector('div.' + this.formClass).getElementsByTagName('input')
      var inputArray = [].slice.call(inputHtmlCollection)

      return inputArray
              .filter(uncheckedRadiosAndCheckboxes)
              .map(toValuableAttrs)
              .reduce(toSingleObject,{})

      function uncheckedRadiosAndCheckboxes(tag) {
        return (isRadioOrCheckbox(tag) ? tag.checked : true)
      }

      function toValuableAttrs(tag) {
        var attr = {}
        attr[tag.id || tag.name] = tag.value
        return attr
      }

      function toSingleObject(obj1, obj2) {
        return extendPreventingOverrides(obj1, obj2)

        // for checkboxes we want to obtain all selected values in an array
        function extendPreventingOverrides(a, b) {
          Object.keys(b).forEach(addContentFromBtoA)
          return a

          function addContentFromBtoA(key) {
            if (a.hasOwnProperty(key)) {
              mergeIntoAnArray(a, b, key)
            } else {
              a[key] = b[key]
            }
          }
        }

        function mergeIntoAnArray(a, b, key) {
          if (Array.isArray(a[key])) {
            a[key].push(b[key])
          } else {
            a[key] = [a[key], b[key]]
          }
        }
      }
    },
    insertFormInSwalModal: function(htmlFormString) {
      var formTag = stringToTag(htmlFormString)
      var sweetAlertModal = document.querySelector('.sweet-alert')
      var buttonContainerTag = sweetAlertModal.querySelector('.sa-button-container') || sweetAlertModal.querySelector('.cancel')
      // insert form before swal bottom buttons
      sweetAlertModal.insertBefore(formTag, buttonContainerTag)

      function stringToTag(string) {
        var div = document.createElement('div')
        div.innerHTML = string
        return div.firstChild
      }
    },
    removeSwalForm: function() {
      var formTag = document.querySelector('.' + this.formClass)
      formTag && document.querySelector('.sweet-alert').removeChild(formTag)
    },
    allowClickingDirectlyOnInputs: function() {
      // sweet-alert attaches an onblur handler which prevents clicks on of non
      // button elements until click is made on the modal
      document.querySelector('.sweet-alert button.confirm').onblur = function() {}
      document.querySelector('.sweet-alert button.cancel').onblur = function() {}
    },
    getSelector: function() {
      var firstField = this.formFields[0]
      return (firstField.id ? t('#{id}', firstField) : t("[name='{name}']", firstField))
    },
    focusOnFirstInput: function() {
      setTimeout(focus.bind(this))

      function focus() {
        document.querySelector(this.getSelector()).focus();
      }
    },
    markFirstRadioButtons: function() {
      setTimeout(markAsChecked.bind(this))

      function markAsChecked() {
        document.querySelector(this.getSelector()).checked = true
      }
    }
  })

  function isRadioOrCheckbox(tag) {
    return tag.type == 'radio' || tag.type == 'checkbox'
  }

  function extend(o1, o2){
    for (var key in o2) {
      if (o2.hasOwnProperty(key)) {
        o1[key] = o2[key]
      }
    }
    return o1
  }

  function Input(field) {
    var input = {
      id: field.id || '',
      name: field.name || '',
      placeholder: field.placeholder || camelCaseToHuman(field.id),
      value: field.value || '',
      type: field.type || 'text',
      options: field.options || [],
      isRadioOrCheckbox: function() {
        return isRadioOrCheckbox(input)
      },
      toHtml: function() {
        if (input.type!=="select"){
            var inputTag = t("<input id='{id}' class='{clazz}' type='{type}' name='{name}'" +
                 " value='{value}' title='{placeholder}' placeholder='{placeholder}'>", input);
        }else{
            var inputTag = t("<select id='{id}' class='{clazz}' name='{name}'" +
                 " value='{value}' title='{placeholder}' style='width:100%'>", input);
            input.options.forEach(function(option){
                option.value=option.value || "";
                option.text=option.text || "";
                inputTag+=t("<option value='{value}'>{text}</option>",option);
            });
            inputTag+="</select>"
        }
        var labelTag  = t("{label}",input);

        return inputTag+labelTag;
      }
    }
    input.label = input.isRadioOrCheckbox() ? input.value : ''
    input.clazz = !input.isRadioOrCheckbox() ? 'nice-input' : 'patch-swal-styles-for-inputs'

    return input

    function camelCaseToHuman(arg) {
      if (arg) {
        return arg
          .replace(/([A-Z])/g, ' $1') // insert a space before all caps
          .replace(/^./, function(str){ return str.toUpperCase() }) // uppercase the first character
      } else {
        return ''
      }
    }
  }

  // string interpolation hack
  function t(template, data) {
    for (var key in data) {
      template = template.replace(new RegExp('{' + key + '}', 'g'), data[key])
    }
    return template
  }

  function toSingleString(s1, s2) {
    return s1 + s2
  }
})()
<commit_msg>solved the issue #5<commit_after>(function() {
  // extend swal with a function for adding forms
  swal.withForm = function() {
    // initialize with field values supplied on `swal.withForm` call
    var swalForm = new SwalForm(arguments[0].formFields)
    // prevent successive calls to add duplicated form fields
    swalForm.removeSwalForm()
    // make form values inserted by the user available at `doneFunction`
    swalForm.addWayToGetFormValuesInDoneFunction(arguments)

    // forward arguments
    swal.apply({}, arguments)

    var htmlForm = swalForm.generateHtmlForm()
    swalForm.insertFormInSwalModal(htmlForm)
    swalForm.allowClickingDirectlyOnInputs()
    swalForm.focusOnFirstInput()
    swalForm.markFirstRadioButtons()
  }

  // constructor for helper object
  function SwalForm(formFields) {
    this.formFields = formFields
  }

  // helper methods
  extend(SwalForm.prototype, {
    formClass: 'swal-form',
    generateHtmlForm: function() {
      var form = {
        clazz: this.formClass,
        innerHtml: this.formFields.map(toFormTag.bind(this)).reduce(toSingleString)
      }

      return t("
{innerHtml}
", form)

      function toFormTag(field) {
        var input = Input(field)
        // to separate groups of checkboxes and radiobuttons in different lines
        var conditionalLineBreak = (input.isRadioOrCheckbox() && this.lastFieldName != field.name) ? '
' : ''
        this.lastFieldName = field.name

        return conditionalLineBreak + input.toHtml()
      }
    },
    addWayToGetFormValuesInDoneFunction: function(swalArgs) {
      var swalFormInstance = this
      var doneFunction = swalArgs[1]
      swalArgs[1] = function(isConfirm) {
        // make form values available at `this` variable inside doneFunction
        this.swalForm = swalFormInstance.getFormValues()
        doneFunction.apply(this, arguments)

        //clean form to not interfere in normals sweet alerts
        document.querySelector('.swal-form').innerHTML=""
      }
    },
    getFormValues: function() {
      var inputHtmlCollection = document.querySelector('div.' + this.formClass).getElementsByTagName('input')
      var inputArray = [].slice.call(inputHtmlCollection)

      return inputArray
              .filter(uncheckedRadiosAndCheckboxes)
              .map(toValuableAttrs)
              .reduce(toSingleObject,{})

      function uncheckedRadiosAndCheckboxes(tag) {
        return (isRadioOrCheckbox(tag) ? tag.checked : true)
      }

      function toValuableAttrs(tag) {
        var attr = {}
        attr[tag.id || tag.name] = tag.value
        return attr
      }

      function toSingleObject(obj1, obj2) {
        return extendPreventingOverrides(obj1, obj2)

        // for checkboxes we want to obtain all selected values in an array
        function extendPreventingOverrides(a, b) {
          Object.keys(b).forEach(addContentFromBtoA)
          return a

          function addContentFromBtoA(key) {
            if (a.hasOwnProperty(key)) {
              mergeIntoAnArray(a, b, key)
            } else {
              a[key] = b[key]
            }
          }
        }

        function mergeIntoAnArray(a, b, key) {
          if (Array.isArray(a[key])) {
            a[key].push(b[key])
          } else {
            a[key] = [a[key], b[key]]
          }
        }
      }
    },
    insertFormInSwalModal: function(htmlFormString) {
      var formTag = stringToTag(htmlFormString)
      var sweetAlertModal = document.querySelector('.sweet-alert')
      var buttonContainerTag = sweetAlertModal.querySelector('.sa-button-container') || sweetAlertModal.querySelector('.cancel')
      // insert form before swal bottom buttons
      sweetAlertModal.insertBefore(formTag, buttonContainerTag)

      function stringToTag(string) {
        var div = document.createElement('div')
        div.innerHTML = string
        return div.firstChild
      }
    },
    removeSwalForm: function() {
      var formTag = document.querySelector('.' + this.formClass)
      formTag && document.querySelector('.sweet-alert').removeChild(formTag)
    },
    allowClickingDirectlyOnInputs: function() {
      // sweet-alert attaches an onblur handler which prevents clicks on of non
      // button elements until click is made on the modal
      document.querySelector('.sweet-alert button.confirm').onblur = function() {}
      document.querySelector('.sweet-alert button.cancel').onblur = function() {}
    },
    getSelector: function() {
      var firstField = this.formFields[0]
      return (firstField.id ? t('#{id}', firstField) : t("[name='{name}']", firstField))
    },
    focusOnFirstInput: function() {
      setTimeout(focus.bind(this))

      function focus() {
        document.querySelector(this.getSelector()).focus();
      }
    },
    markFirstRadioButtons: function() {
      setTimeout(markAsChecked.bind(this))

      function markAsChecked() {
        document.querySelector(this.getSelector()).checked = true
      }
    }
  })

  function isRadioOrCheckbox(tag) {
    return tag.type == 'radio' || tag.type == 'checkbox'
  }

  function extend(o1, o2){
    for (var key in o2) {
      if (o2.hasOwnProperty(key)) {
        o1[key] = o2[key]
      }
    }
    return o1
  }

  function Input(field) {
    var input = {
      id: field.id || '',
      name: field.name || '',
      placeholder: field.placeholder || camelCaseToHuman(field.id),
      value: field.value || '',
      type: field.type || 'text',
      options: field.options || [],
      isRadioOrCheckbox: function() {
        return isRadioOrCheckbox(input)
      },
      toHtml: function() {
        if (input.type!=="select"){
            var inputTag = t("<input id='{id}' class='{clazz}' type='{type}' name='{name}'" +
                 " value='{value}' title='{placeholder}' placeholder='{placeholder}'>", input);
        }else{
            var inputTag = t("<select id='{id}' class='{clazz}' name='{name}'" +
                 " value='{value}' title='{placeholder}' style='width:100%'>", input);
            input.options.forEach(function(option){
                option.value=option.value || "";
                option.text=option.text || "";
                inputTag+=t("<option value='{value}'>{text}</option>",option);
            });
            inputTag+="</select>"
        }
        var labelTag  = t("{label}",input);

        return inputTag+labelTag;
      }
    }
    input.label = input.isRadioOrCheckbox() ? input.value : ''
    input.clazz = !input.isRadioOrCheckbox() ? 'nice-input' : 'patch-swal-styles-for-inputs'

    return input

    function camelCaseToHuman(arg) {
      if (arg) {
        return arg
          .replace(/([A-Z])/g, ' $1') // insert a space before all caps
          .replace(/^./, function(str){ return str.toUpperCase() }) // uppercase the first character
      } else {
        return ''
      }
    }
  }

  // string interpolation hack
  function t(template, data) {
    for (var key in data) {
      template = template.replace(new RegExp('{' + key + '}', 'g'), data[key])
    }
    return template
  }

  function toSingleString(s1, s2) {
    return s1 + s2
  }
})()
<|endoftext|>


<commit_before>import React from 'react';
import { shallow } from 'enzyme';

import Navbar from '../src/Navbar';

describe('<Navbar />', () => {
  it('should have default props', () => {
    const navbar = <Navbar />;
    expect(navbar.props.dir).toBe('ltr');
    expect(navbar.props.showPreviousButton).toBe(true);
    expect(navbar.props.showNextButton).toBe(true);
  });
  it('should have the right class name', () => {
    const wrapper = shallow(<Navbar className="DayPicker-NavBar" />);
    expect(wrapper).toHaveClassName('DayPicker-NavBar');
  });
  it('should render the aria labels for buttons', () => {
    const wrapper = shallow(<Navbar />);
    expect(wrapper.find('.DayPicker-NavButton--prev')).toHaveProp(
      'aria-label',
      'Previous Month'
    );
    expect(wrapper.find('.DayPicker-NavButton--next')).toHaveProp(
      'aria-label',
      'Next Month'
    );
  });
  it('should render custom aria labels', () => {
    const wrapper = shallow(
      <Navbar
        labels={{
          nextMonth: 'Successivo',
          previousMonth: 'Precedente',
        }}
      />
    );
    expect(wrapper.find('.DayPicker-NavButton--prev')).toHaveProp(
      'aria-label',
      'Precedente'
    );
    expect(wrapper.find('.DayPicker-NavButton--next')).toHaveProp(
      'aria-label',
      'Successivo'
    );
  });
  it('should have the navigation buttons classes', () => {
    const wrapper = shallow(<Navbar />);
    expect(wrapper.find('.DayPicker-NavButton').at(0)).toHaveClassName(
      'DayPicker-NavButton--prev'
    );
    expect(wrapper.find('.DayPicker-NavButton').at(1)).toHaveClassName(
      'DayPicker-NavButton--next'
    );
  });
  it('should invert buttons position for RTL', () => {
    const wrapper = shallow(<Navbar dir="rtl" />);
    expect(wrapper.find('.DayPicker-NavButton').at(0)).toHaveClassName(
      'DayPicker-NavButton--next'
    );
    expect(wrapper.find('.DayPicker-NavButton').at(1)).toHaveClassName(
      'DayPicker-NavButton--prev'
    );
  });
  it('should disable interaction with the previous button', () => {
    const wrapper = shallow(<Navbar showPreviousButton={false} />);
    expect(wrapper.find('.DayPicker-NavButton--prev')).toHaveLength(1);
  });
  it('should disable interaction with the next button', () => {
    const wrapper = shallow(<Navbar showNextButton={false} />);
    expect(wrapper.find('.DayPicker-NavButton--next')).toHaveLength(1);
  });
  it('should call `onNextClick` when clicking the next button', () => {
    const handleNextClick = jest.fn();
    const wrapper = shallow(<Navbar onNextClick={handleNextClick} />);
    wrapper.find('.DayPicker-NavButton--next').simulate('click');
    expect(handleNextClick).toHaveBeenCalledTimes(1);
  });
  it('should call `onPreviousClick` when clicking the prev button', () => {
    const handlePreviousClick = jest.fn();
    const wrapper = shallow(<Navbar onPreviousClick={handlePreviousClick} />);
    wrapper.find('.DayPicker-NavButton--prev').simulate('click');
    expect(handlePreviousClick).toHaveBeenCalledTimes(1);
  });
  it('should call `onNextClick` when clicking the prev button for RTL', () => {
    const handleNextClick = jest.fn();
    const wrapper = shallow(<Navbar dir="rtl" onNextClick={handleNextClick} />);
    wrapper.find('.DayPicker-NavButton--prev').simulate('click');
    expect(handleNextClick).toHaveBeenCalledTimes(1);
  });
  it('should call `onPreviousClick` when clicking the next button for RTL', () => {
    const handlePreviousClick = jest.fn();
    const wrapper = shallow(
      <Navbar dir="rtl" onPreviousClick={handlePreviousClick} />
    );
    wrapper.find('.DayPicker-NavButton--next').simulate('click');
    expect(handlePreviousClick).toHaveBeenCalledTimes(1);
  });
});
<commit_msg>Check nav button to have .DayPicker-NavButton--interactionDisabled<commit_after>import React from 'react';
import { shallow } from 'enzyme';

import Navbar from '../src/Navbar';

describe('<Navbar />', () => {
  it('should have default props', () => {
    const navbar = <Navbar />;
    expect(navbar.props.dir).toBe('ltr');
    expect(navbar.props.showPreviousButton).toBe(true);
    expect(navbar.props.showNextButton).toBe(true);
  });
  it('should have the right class name', () => {
    const wrapper = shallow(<Navbar className="DayPicker-NavBar" />);
    expect(wrapper).toHaveClassName('DayPicker-NavBar');
  });
  it('should render the aria labels for buttons', () => {
    const wrapper = shallow(<Navbar />);
    expect(wrapper.find('.DayPicker-NavButton--prev')).toHaveProp(
      'aria-label',
      'Previous Month'
    );
    expect(wrapper.find('.DayPicker-NavButton--next')).toHaveProp(
      'aria-label',
      'Next Month'
    );
  });
  it('should render custom aria labels', () => {
    const wrapper = shallow(
      <Navbar
        labels={{
          nextMonth: 'Successivo',
          previousMonth: 'Precedente',
        }}
      />
    );
    expect(wrapper.find('.DayPicker-NavButton--prev')).toHaveProp(
      'aria-label',
      'Precedente'
    );
    expect(wrapper.find('.DayPicker-NavButton--next')).toHaveProp(
      'aria-label',
      'Successivo'
    );
  });
  it('should have the navigation buttons classes', () => {
    const wrapper = shallow(<Navbar />);
    expect(wrapper.find('.DayPicker-NavButton').at(0)).toHaveClassName(
      'DayPicker-NavButton--prev'
    );
    expect(wrapper.find('.DayPicker-NavButton').at(1)).toHaveClassName(
      'DayPicker-NavButton--next'
    );
  });
  it('should invert buttons position for RTL', () => {
    const wrapper = shallow(<Navbar dir="rtl" />);
    expect(wrapper.find('.DayPicker-NavButton').at(0)).toHaveClassName(
      'DayPicker-NavButton--next'
    );
    expect(wrapper.find('.DayPicker-NavButton').at(1)).toHaveClassName(
      'DayPicker-NavButton--prev'
    );
  });
  it('should disable interaction with the previous button', () => {
    const wrapper = shallow(<Navbar showPreviousButton={false} />);
    expect(wrapper.find('.DayPicker-NavButton--prev')).toHaveClassName(
      '.DayPicker-NavButton--interactionDisabled'
    );
  });
  it('should disable interaction with the next button', () => {
    const wrapper = shallow(<Navbar showNextButton={false} />);
    expect(wrapper.find('.DayPicker-NavButton--next')).toHaveClassName(
      '.DayPicker-NavButton--interactionDisabled'
    );
  });
  it('should call `onNextClick` when clicking the next button', () => {
    const handleNextClick = jest.fn();
    const wrapper = shallow(<Navbar onNextClick={handleNextClick} />);
    wrapper.find('.DayPicker-NavButton--next').simulate('click');
    expect(handleNextClick).toHaveBeenCalledTimes(1);
  });
  it('should call `onPreviousClick` when clicking the prev button', () => {
    const handlePreviousClick = jest.fn();
    const wrapper = shallow(<Navbar onPreviousClick={handlePreviousClick} />);
    wrapper.find('.DayPicker-NavButton--prev').simulate('click');
    expect(handlePreviousClick).toHaveBeenCalledTimes(1);
  });
  it('should call `onNextClick` when clicking the prev button for RTL', () => {
    const handleNextClick = jest.fn();
    const wrapper = shallow(<Navbar dir="rtl" onNextClick={handleNextClick} />);
    wrapper.find('.DayPicker-NavButton--prev').simulate('click');
    expect(handleNextClick).toHaveBeenCalledTimes(1);
  });
  it('should call `onPreviousClick` when clicking the next button for RTL', () => {
    const handlePreviousClick = jest.fn();
    const wrapper = shallow(
      <Navbar dir="rtl" onPreviousClick={handlePreviousClick} />
    );
    wrapper.find('.DayPicker-NavButton--next').simulate('click');
    expect(handlePreviousClick).toHaveBeenCalledTimes(1);
  });
});
<|endoftext|>


<commit_before>import React from 'react';
import {shallow, mount} from 'enzyme';

import {QueryBuilder, RuleGroup} from './index';

describe('<QueryBuilder />', ()=> {

    it('should exist', ()=> {
        expect(QueryBuilder).to.exist;
    });

    describe('when rendered', ()=> {
        it('should render the root RuleGroup', ()=> {
            const dom = shallow(<QueryBuilder />);
            expect(dom.find('RuleGroup')).to.have.length(1);
        });

        it('should show the list of combinators in the RuleGroup', ()=> {
            const dom = mount(<QueryBuilder />);
            const options = dom.find('select option');
            expect(options).to.have.length(2); // and, or
        });

    });

    describe('when initial query is provided', ()=> {
        let dom;

        beforeEach(()=> {
            const fields = [
                {name: 'firstName', label: 'First Name'},
                {name: 'lastName', label: 'Last Name'},
                {name: 'age', label: 'Age'},
            ];

            const query = {
                combinator: 'and',
                id: '111',
                rules: [
                    {
                        id: '222',
                        field: 'firstName',
                        value: 'Test',
                        operator: '='
                    }
                ]
            };

            dom = mount(<QueryBuilder query={query} fields={fields}/>);
        });

        it('should contain a <Rule />', ()=> {

            const rule = dom.find('Rule');
            expect(rule).to.have.length(1);
        });

        it('should have the Rule with the correct props', ()=> {
            const rule = dom.find('Rule');

            expect(rule.props().field).to.equal('firstName');
            expect(rule.props().value).to.equal('Test');
            expect(rule.props().operator).to.equal('=');
        });

        it('should have a select control with the provided fields', ()=> {
            const rule = dom.find('Rule');

            expect(rule.find('.rule-fields option')).to.have.length(3);
        });

        it('should have an field selector with the correct field', ()=> {
            const rule = dom.find('Rule');

            expect(rule.find('.rule-fields').props().value).to.equal('firstName');
        });

        it('should have an operator selector with the correct operator', ()=> {
            const rule = dom.find('Rule');

            expect(rule.find('.rule-operators').props().value).to.equal('=');
        });

        it('should have an input control with the correct value', ()=> {
            const rule = dom.find('Rule');

            expect(rule.find('input').props().value).to.equal('Test');
        });

    });

});
<commit_msg>fix failing unit tests<commit_after>import React from 'react';
import {shallow, mount} from 'enzyme';

import QueryBuilder from './QueryBuilder';

describe('<QueryBuilder />', ()=> {

    it('should exist', ()=> {
        expect(QueryBuilder).to.exist;
    });

    describe('when rendered', ()=> {
        it('should render the root RuleGroup', ()=> {
            const dom = shallow(<QueryBuilder />);
            expect(dom.find('RuleGroup')).to.have.length(1);
        });

        it('should show the list of combinators in the RuleGroup', ()=> {
            const dom = mount(<QueryBuilder />);
            const options = dom.find('select option');
            expect(options).to.have.length(2); // and, or
        });

    });

    describe('when initial query is provided', ()=> {
        let dom;

        beforeEach(()=> {
            const fields = [
                {name: 'firstName', label: 'First Name'},
                {name: 'lastName', label: 'Last Name'},
                {name: 'age', label: 'Age'},
            ];

            const query = {
                combinator: 'and',
                id: '111',
                rules: [
                    {
                        id: '222',
                        field: 'firstName',
                        value: 'Test',
                        operator: '='
                    }
                ]
            };

            dom = mount(<QueryBuilder query={query} fields={fields}/>);
        });

        it('should contain a <Rule />', ()=> {

            const rule = dom.find('Rule');
            expect(rule).to.have.length(1);
        });

        it('should have the Rule with the correct props', ()=> {
            const rule = dom.find('Rule');

            expect(rule.props().field).to.equal('firstName');
            expect(rule.props().value).to.equal('Test');
            expect(rule.props().operator).to.equal('=');
        });

        it('should have a select control with the provided fields', ()=> {
            const rule = dom.find('Rule');

            expect(rule.find('.rule-fields option')).to.have.length(3);
        });

        it('should have an field selector with the correct field', ()=> {
            const rule = dom.find('Rule');

            expect(rule.find('.rule-fields').props().value).to.equal('firstName');
        });

        it('should have an operator selector with the correct operator', ()=> {
            const rule = dom.find('Rule');

            expect(rule.find('.rule-operators').props().value).to.equal('=');
        });

        it('should have an input control with the correct value', ()=> {
            const rule = dom.find('Rule');

            expect(rule.find('input').props().value).to.equal('Test');
        });

    });

});
<|endoftext|>


<commit_before>var timeoverride = null;

window.onerror = function(message, url, lineNumber) {
  document.getElementById("loading").innerHTML = "Error loading page
"+message+" Line "+lineNumber;
  return true;
};

document.addEventListener('DOMContentLoaded', function() {

document.getElementById("goButton").addEventListener("click", function(){
 RunForestRun()
});

});

// //on DOM load

// });

function getSearchParameters() {
    var prmstr = window.location.search.substr(1);
    return prmstr != null && prmstr != "" ? transformToAssocArray(prmstr) : {};
}

function transformToAssocArray(prmstr) {
    var params = {};
    var prmarr = prmstr.split("&");
    for (var i = 0; i < prmarr.length; i++) {
        var tmparr = prmarr[i].split("=");
        params[tmparr[0]] = tmparr[1];
    }
    return params;
}

var timeperiod;
var unitname = "";

var params = getSearchParameters();

//Get times vars for the call
function RunForestRun() {

            document.getElementById("loading").style.visibility = 'visible';

    if (timeoverride !== null) { //we are using a time override

        var end = new Date();

        var start = new Date();
        start.setDate(start.getDate() - (timeoverride / 24));

        starttime = start.toISOString();
        endtime = end.toISOString();

        console.log(starttime);
        console.log(endtime);

        params.start = starttime;
        params.end = endtime;

    }

    //IF TRAIN BEACON

    // if (params.host == "trainbeacon.ses.nsw.gov.au")
    // {
    //     document.body.style.backgroundColor = "green";
    // }

    if (unitname == "") {

        console.log("firstrun...will fetch vars");

        if (typeof params.hq !== 'undefined') {

        if (params.hq.split(",").length == 1)
            {

            GetUnitNamefromBeacon(params.hq, params.host, function(returnedunitname) {
                unitname = returnedunitname;
                HackTheMatrix(params.hq, params.host, returnedunitname);
            });

            } else {
        console.log("passed array of units");
        unitname = "group selection";
        HackTheMatrix(params.hq, params.host, unitname);
    }

        } else { //no hq was sent, get them all
            unitname = "NSW";
            HackTheMatrix(null, params.host, unitname);
        }

    } else {
        console.log("rerun...will NOT fetch vars");
        if (typeof params.hq == 'undefined') {
            HackTheMatrix(null, unitname);
        } else {
            HackTheMatrix(params.hq, params.host, unitname);
        }

    }

}

//make the call to beacon
function HackTheMatrix(id,host, unit) {

    document.title = unitname + " Export";

    var start = new Date(decodeURIComponent(params.start));
    var end = new Date(decodeURIComponent(params.end));

    GetJSONfromBeacon(id, host, start, end, function(jobs) {

        var exports = jobs.Results.map(function(d){
        var rawdate = new Date(d.JobReceived);
        d.JobReceivedFixed = new Date(rawdate.getTime() + ( rawdate.getTimezoneOffset() * 60000 ));

        var tags = d.Tags.map(function(d){return d.Name}).join(",");

        var rObj = {};

        if (document.getElementById("Id").checked) {rObj["Id"] = d.Identifier};
        if (document.getElementById("Received").checked) {rObj["JobReceivedFixed"] = d.JobReceivedFixed};
        if (document.getElementById("Priority").checked) {rObj["JobPriorityType"] = d.JobPriorityType.Name};
        if (document.getElementById("Type").checked) {rObj["Type"] = d.JobType.Name};
        if (document.getElementById("Status").checked) {rObj["Status"] = d.JobStatusType.Name};
        if (document.getElementById("SituationOnScene").checked) {rObj["SituationOnScene"] = d.SituationOnScene};
        if (document.getElementById("PermissionToEnterPremises").checked) {rObj["PermissionToEnterPremises"] = d.PermissionToEnterPremises};

        if (document.getElementById("Tags").checked) {rObj["Tags"] = tags};
        if (document.getElementById("HQ").checked) {rObj["HQ"] = d.EntityAssignedTo.Code};
        if (document.getElementById("Region").checked) {rObj["Region"] = d.CallerFirstName};
        if (document.getElementById("Event").checked) {rObj["Event"] = d.Event.Identifier};

        if (document.getElementById("Level").checked) {rObj["Level"] = d.Address.Level};
        if (document.getElementById("StreetNumber").checked) {rObj["StreetNumber"] = d.Address.StreetNumber};
        if (document.getElementById("Street").checked) {rObj["Street"] = d.Address.Street};
        if (document.getElementById("Locality").checked) {rObj["Locality"] = d.Address.Locality};
        if (document.getElementById("PostCode").checked) {rObj["PostCode"] = d.Address.PostCode};
        if (document.getElementById("PrettyAddress").checked) {rObj["PrettyAddress"] = d.Address.PrettyAddress};
        if (document.getElementById("AdditionalAddressInfo").checked) {rObj["AdditionalAddressInfo"] = d.Address.AdditionalAddressInfo};
        if (document.getElementById("Latitude").checked) {rObj["Latitude"] = d.Address.Latitude};
        if (document.getElementById("Longitude").checked) {rObj["Longitude"] = d.Address.Longitude};

        if (document.getElementById("CallerFirstName").checked) {rObj["CallerFirstName"] = d.CallerFirstName};
        if (document.getElementById("CallerLastName").checked) {rObj["CallerLastName"] = d.CallerLastName};
        if (document.getElementById("CallerPhoneNumber").checked) {rObj["CallerPhoneNumber"] = d.CallerPhoneNumber};
        if (document.getElementById("ContactCalled").checked) {rObj["ContactCalled"] = d.ContactCalled};
        if (document.getElementById("ContactFirstName").checked) {rObj["ContactFirstName"] = d.ContactFirstName};
        if (document.getElementById("ContactLastName").checked) {rObj["ContactLastName"] = d.ContactLastName};
        if (document.getElementById("ContactPhoneNumber").checked) {rObj["ContactPhoneNumber"] = d.ContactPhoneNumber};

        return rObj

        });

        downloadCSV("LighthouseExport.csv",exports);

                    document.getElementById("loading").style.visibility = 'hidden';

});
}

function convertArrayOfObjectsToCSV(data) {
        var result, ctr, keys, columnDelimiter, lineDelimiter, data;

        if (data == null || !data.length) {
            return null;
        }

        columnDelimiter =  ',';
        lineDelimiter = '\n';

        keys = Object.keys(data[0]);

        result = '';
        result += keys.join(columnDelimiter);
        result += lineDelimiter;

        data.forEach(function(item) {
            ctr = 0;
            keys.forEach(function(key) {
                if (item[key] === null) {item[key] = ""};
                if (ctr > 0) result += columnDelimiter;

                result += "\""+item[key]+"\"";
                ctr++;
            });
            result += lineDelimiter;
        });

        return result;
    }

    function downloadCSV(file,dataIn) {
        var csv = convertArrayOfObjectsToCSV(dataIn);
        if (csv == null) return;

        filename = file;

        if (!csv.match(/^data:text\/csv/i)) {
            csv = 'data:text/csv;charset=utf-8,' + csv;
        }
        data = encodeURI(csv);

        link = document.createElement('a');
        link.setAttribute('href', data);
        link.setAttribute('download', filename);
        link.click();
    }
<commit_msg>fixed null event ID bug<commit_after>var timeoverride = null;

window.onerror = function(message, url, lineNumber) {
  document.getElementById("loading").innerHTML = "Error loading page
"+message+" Line "+lineNumber;
  return true;
};

document.addEventListener('DOMContentLoaded', function() {

document.getElementById("goButton").addEventListener("click", function(){
 RunForestRun()
});

});

// //on DOM load

// });

function getSearchParameters() {
    var prmstr = window.location.search.substr(1);
    return prmstr != null && prmstr != "" ? transformToAssocArray(prmstr) : {};
}

function transformToAssocArray(prmstr) {
    var params = {};
    var prmarr = prmstr.split("&");
    for (var i = 0; i < prmarr.length; i++) {
        var tmparr = prmarr[i].split("=");
        params[tmparr[0]] = tmparr[1];
    }
    return params;
}

var timeperiod;
var unitname = "";

var params = getSearchParameters();

//Get times vars for the call
function RunForestRun() {

            document.getElementById("loading").style.visibility = 'visible';

    if (timeoverride !== null) { //we are using a time override

        var end = new Date();

        var start = new Date();
        start.setDate(start.getDate() - (timeoverride / 24));

        starttime = start.toISOString();
        endtime = end.toISOString();

        console.log(starttime);
        console.log(endtime);

        params.start = starttime;
        params.end = endtime;

    }

    //IF TRAIN BEACON

    // if (params.host == "trainbeacon.ses.nsw.gov.au")
    // {
    //     document.body.style.backgroundColor = "green";
    // }

    if (unitname == "") {

        console.log("firstrun...will fetch vars");

        if (typeof params.hq !== 'undefined') {

        if (params.hq.split(",").length == 1)
            {

            GetUnitNamefromBeacon(params.hq, params.host, function(returnedunitname) {
                unitname = returnedunitname;
                HackTheMatrix(params.hq, params.host, returnedunitname);
            });

            } else {
        console.log("passed array of units");
        unitname = "group selection";
        HackTheMatrix(params.hq, params.host, unitname);
    }

        } else { //no hq was sent, get them all
            unitname = "NSW";
            HackTheMatrix(null, params.host, unitname);
        }

    } else {
        console.log("rerun...will NOT fetch vars");
        if (typeof params.hq == 'undefined') {
            HackTheMatrix(null, unitname);
        } else {
            HackTheMatrix(params.hq, params.host, unitname);
        }

    }

}

//make the call to beacon
function HackTheMatrix(id,host, unit) {

    document.title = unitname + " Export";

    var start = new Date(decodeURIComponent(params.start));
    var end = new Date(decodeURIComponent(params.end));

    GetJSONfromBeacon(id, host, start, end, function(jobs) {

        var exports = jobs.Results.map(function(d){
        var rawdate = new Date(d.JobReceived);
        d.JobReceivedFixed = new Date(rawdate.getTime() + ( rawdate.getTimezoneOffset() * 60000 ));

        var tags = d.Tags.map(function(d){return d.Name}).join(",");

        var rObj = {};

        if (document.getElementById("Id").checked) {rObj["Id"] = d.Identifier};
        if (document.getElementById("Received").checked) {rObj["JobReceivedFixed"] = d.JobReceivedFixed};
        if (document.getElementById("Priority").checked) {rObj["JobPriorityType"] = d.JobPriorityType.Name};
        if (document.getElementById("Type").checked) {rObj["Type"] = d.JobType.Name};
        if (document.getElementById("Status").checked) {rObj["Status"] = d.JobStatusType.Name};
        if (document.getElementById("SituationOnScene").checked) {rObj["SituationOnScene"] = d.SituationOnScene};
        if (document.getElementById("PermissionToEnterPremises").checked) {rObj["PermissionToEnterPremises"] = d.PermissionToEnterPremises};

        if (document.getElementById("Tags").checked) {rObj["Tags"] = tags};
        if (document.getElementById("HQ").checked) {rObj["HQ"] = d.EntityAssignedTo.Code};
        if (document.getElementById("Region").checked) {rObj["Region"] = d.CallerFirstName};

        if (document.getElementById("Event").checked)
            {
                if (d.Event !== null)
                {
                    rObj["Event"] = d.Event.Identifier;

            } else
            {
               rObj["Event"] = "";
            }
            };

        if (document.getElementById("Level").checked) {rObj["Level"] = d.Address.Level};
        if (document.getElementById("StreetNumber").checked) {rObj["StreetNumber"] = d.Address.StreetNumber};
        if (document.getElementById("Street").checked) {rObj["Street"] = d.Address.Street};
        if (document.getElementById("Locality").checked) {rObj["Locality"] = d.Address.Locality};
        if (document.getElementById("PostCode").checked) {rObj["PostCode"] = d.Address.PostCode};
        if (document.getElementById("PrettyAddress").checked) {rObj["PrettyAddress"] = d.Address.PrettyAddress};
        if (document.getElementById("AdditionalAddressInfo").checked) {rObj["AdditionalAddressInfo"] = d.Address.AdditionalAddressInfo};
        if (document.getElementById("Latitude").checked) {rObj["Latitude"] = d.Address.Latitude};
        if (document.getElementById("Longitude").checked) {rObj["Longitude"] = d.Address.Longitude};

        if (document.getElementById("CallerFirstName").checked) {rObj["CallerFirstName"] = d.CallerFirstName};
        if (document.getElementById("CallerLastName").checked) {rObj["CallerLastName"] = d.CallerLastName};
        if (document.getElementById("CallerPhoneNumber").checked) {rObj["CallerPhoneNumber"] = d.CallerPhoneNumber};
        if (document.getElementById("ContactCalled").checked) {rObj["ContactCalled"] = d.ContactCalled};
        if (document.getElementById("ContactFirstName").checked) {rObj["ContactFirstName"] = d.ContactFirstName};
        if (document.getElementById("ContactLastName").checked) {rObj["ContactLastName"] = d.ContactLastName};
        if (document.getElementById("ContactPhoneNumber").checked) {rObj["ContactPhoneNumber"] = d.ContactPhoneNumber};

        return rObj

        });
console.log(exports);

        downloadCSV("LighthouseExport.csv",exports);

                    document.getElementById("loading").style.visibility = 'hidden';

});
}

function convertArrayOfObjectsToCSV(data) {
        var result, ctr, keys, columnDelimiter, lineDelimiter, data;

        if (data == null || !data.length) {
            return null;
        }

        columnDelimiter =  ',';
        lineDelimiter = '\n';

        keys = Object.keys(data[0]);

        result = '';
        result += keys.join(columnDelimiter);
        result += lineDelimiter;

        data.forEach(function(item) {
            ctr = 0;
            keys.forEach(function(key) {
                if (item[key] === null) {item[key] = ""};
                if (ctr > 0) result += columnDelimiter;

                result += "\""+item[key]+"\"";
                ctr++;
            });
            result += lineDelimiter;
        });

        return result;
    }

    function downloadCSV(file,dataIn) {
        var csv = convertArrayOfObjectsToCSV(dataIn);
        if (csv == null) return;

        filename = file;

        if (!csv.match(/^data:text\/csv/i)) {
            csv = 'data:text/csv;charset=utf-8,' + csv;
        }
        data = encodeURI(csv);

        link = document.createElement('a');
        link.setAttribute('href', data);
        link.setAttribute('download', filename);
        link.click();
    }
<|endoftext|>