<commit_before>import {
  CustomSurface, platform,
  DefaultDOMElement as DOM, domHelpers, getRelativeBoundingRect,
  keys
} from 'substance'
import TableEditing from '../../article/TableEditing'
import {
  getCellRange, computeUpdatedSelection
} from '../../article/tableHelpers'
import TableClipboard from '../util/TableClipboard'
import TableCellComponent from './TableCellComponent'
import TableContextMenu from './TableContextMenu'

export default class TableComponent extends CustomSurface {

  constructor(...args) {
    super(...args)

    this._selectionData = {}
    this._tableEditing = new TableEditing(this.context.editorSession, this.props.node.id, this.getId())
    this._clipboard = new TableClipboard(this._tableEditing)
  }

  getChildContext() {
    return {
      surface: this,
      parentSurfaceId: this.getId(),
      // HACK: nulling this so that nested surfaces get an id that are relative to
      // this surface instead of the isolatedNodeComponent
      isolatedNodeComponent: null
    }
  }

  shouldRerender() {
    return false
  }

  didMount() {
    super.didMount()

    this._tableSha = this.props.node._getSha()

    // Disable reactive behaviour in read only mode
    // TODO: we may want to bring it back so the reader receives realtime updates
    if (!this.props.disabled) {
      this.context.editorSession.onRender('document', this._onDocumentChange, this)
      this.context.editorSession.onRender('selection', this._onSelectionChange, this)
      this._positionSelection(this._getSelectionData())
    }

  }

  dispose() {
    super.dispose()
    this.context.editorSession.off(this)
  }

  render($$) {
    let el = $$('div').addClass('sc-table')
    if (!this.props.disabled) {
      el.on('mousedown', this._onMousedown)
      .on('mouseup', this._onMouseup)
    }

    el.append(this._renderTable($$))
    // el.append(this._renderClickableOverlays($$))
    if (!this.props.disabled) {
      el.append(this._renderKeyTrap($$))
      el.append(this._renderUnclickableOverlays($$))
      el.append(this._renderContextMenu($$))
    }
    return el
  }

  _renderTable($$) {
    let table = $$('table').ref('table')
    let node = this.props.node
    let matrix = node.getCellMatrix()
    for (let i = 0; i < matrix.length; i++) {
      let cells = matrix[i]
      let tr = $$('tr')
      for (let j = 0; j < cells.length; j++) {
        if (cells[j].shadowed) continue
        let cell = cells[j]
        tr.append(
          $$(TableCellComponent, { node: cell, disabled: true })
            .ref(cell.id)
            .on('enter', this._onCellEnter)
            .on('tab', this._onCellTab)
            .on('escape', this._onCellEscape)
        )
      }
      table.append(tr)
    }
    table.on('mousemove', this._onMousemove)
      .on('dblclick', this._onDblclick)
      .on('contextmenu', this._onContextMenu)
      .on('contextmenuitemclick', this._onContextmenuitemclick)
    return table
  }

  _renderKeyTrap($$) {
    return $$('textarea').addClass('se-keytrap').ref('keytrap')
      .css({ position: 'absolute', width: 0, height: 0, opacity: 0 })
      .on('keydown', this._onKeydown)
      .on('input', this._onInput)
      .on('copy', this._onCopy)
      .on('paste', this._onPaste)
      .on('cut', this._onCut)
  }

  _renderUnclickableOverlays($$) {
    let el = $$('div').addClass('se-unclickable-overlays')
    el.append(
      this._renderSelectionOverlay($$)
    )
    el.append(
      this.props.unclickableOverlays
    )
    return el
  }

  _renderSelectionOverlay($$) {
    let el = $$('div').addClass('se-selection-overlay')
    el.append(
      $$('div').addClass('se-selection-anchor').ref('selAnchor').css('visibility', 'hidden'),
      $$('div').addClass('se-selection-range').ref('selRange').css('visibility', 'hidden')
    )
    return el
  }

  _renderContextMenu($$) {
    const configurator = this.context.configurator
    let contextMenu = $$(TableContextMenu, {
      toolPanel: configurator.getToolPanel('table-context-menu')
    }).ref('contextMenu')
      .addClass('se-context-menu')
      .css({ display: 'none' })
    return contextMenu
  }

  _onDocumentChange() {
    const table = this.props.node
    // Note: using a simplified way to detect when a table
    // has changed structurally
    // TableElementNode is detecting such changes and
    // updates an internal 'sha' that we can compare against
    if (table._hasShaChanged(this._tableSha)) {
      this.rerender()
      this._tableSha = table._getSha()
    }
  }

  _onSelectionChange(sel) {
    const self = this
    if (!sel || sel.isNull()) {
      _disableActiveCell()
      this._hideSelection()
    } else if (sel.isPropertySelection()) {
      let nodeId = sel.path[0]
      if (this._activeCell !== nodeId) {
        _disableActiveCell()
        let newCellEditor = this.refs[nodeId]
        if (newCellEditor) {
          // console.log('ENABLING CELL EDITOR', nodeId)
          newCellEditor.extendProps({ disabled: false })
          this._activeCell = nodeId
        }
      }
      if (this._activeCell) {
        // TODO: this could be simplified
        let doc = this.context.editorSession.getDocument()
        let cell = doc.get(this._activeCell)
        this._positionSelection({
          type: 'range',
          anchorCellId: cell.id,
          focusCellId: cell.id,
        }, true)
      } else {
        this._hideSelection()
      }
    } else if (sel.surfaceId !== this.getId()) {
      _disableActiveCell()
      this._hideSelection()
    } else {
      _disableActiveCell()
    }
    this._hideContextMenu()

    function _disableActiveCell() {
      const activeCellId = self._activeCell
      if(activeCellId) {
        let cellEditor = self.refs[activeCellId]
        if (cellEditor) {
          // console.log('DISABLING CELL EDITOR', activeCellId)
          cellEditor.extendProps({ disabled: true })
        }
        self._activeCell = null
      }
    }

  }

  _onMousedown(e) {
    e.stopPropagation()
    // TODO: do not update the selection if right-clicked and already having a selection
    if (platform.inBrowser) {
      DOM.wrap(window.document).on('mouseup', this._onMouseup, this, {
        once: true
      })
    }
    // console.log('_onMousedown', e)
    let selData = this._selectionData
    if (!selData) selData = this._selectionData = {}
    let target = this._getClickTargetForEvent(e)
    // console.log('target', target)
    if (!target) return

    let isRightButton = domHelpers.isRightButton(e)
    if (isRightButton) {
      // console.log('IS RIGHT BUTTON')
      // this will be handled by onContextMenu
      if (target.type === 'cell') {
        let targetCell = this.props.node.get(target.id)
        let _needSetSelection = true
        let _selData = this._getSelectionData()
        if (_selData && targetCell) {
          let { startRow, startCol, endRow, endCol } = getCellRange(this.props.node, _selData.anchorCellId, _selData.focusCellId)
          _needSetSelection = (
            targetCell.colIdx < startCol || targetCell.colIdx > endCol ||
            targetCell.rowIdx < startRow || targetCell.rowIdx > endRow
          )
        }
        if (_needSetSelection) {
          this._isSelecting = true
          selData.anchorCellId = target.id
          selData.focusCellId = target.id
          this._requestSelectionChange(this._tableEditing.createTableSelection(selData))
        }
      }
      return
    }
    if (target.type === 'cell') {
      this._isSelecting = true
      selData.focusCellId = target.id
      if (!e.shiftKey) {
        selData.anchorCellId = target.id
      }
      e.preventDefault()
      this._requestSelectionChange(this._tableEditing.createTableSelection(selData))
    }
  }

  _onMouseup(e) {
    e.stopPropagation()
    if (this._isSelecting) {
      e.preventDefault()
      this._isSelecting = false
    }
  }

  _onMousemove(e) {
    if (this._isSelecting) {
      const selData = this._selectionData
      let cellId = this._mapClientXYToCellId(e.clientX, e.clientY)
      if (cellId !== selData.focusCellId) {
        selData.focusCellId = cellId
        this._requestSelectionChange(this._tableEditing.createTableSelection(selData))
      }
    }
  }

  _onDblclick(e) {
    e.preventDefault()
    e.stopPropagation()
    this._requestEditCell()
  }

  _onKeydown(e) {
    let handled = false
    switch (e.keyCode) {
      case keys.LEFT:
        this._nav(0, -1, e.shiftKey)
        handled = true
        break
      case keys.RIGHT:
        this._nav(0, 1, e.shiftKey)
        handled = true
        break
      case keys.UP:
        this._nav(-1, 0, e.shiftKey)
        handled = true
        break
      case keys.DOWN:
        this._nav(1, 0, e.shiftKey)
        handled = true
        break
      case keys.ENTER: {
        this._requestEditCell()
        handled = true
        break
      }
      case keys.TAB: {
        this._nav(0, 1)
        handled = true
        break
      }
      case keys.DELETE:
      case keys.BACKSPACE: {
        this._clearSelection()
        handled = true
        break
      }
      default:
        //
    }
    // let an optional keyboard manager handle the key
    if (!handled) {
      const keyboardManager = this.context.keyboardManager
      if (keyboardManager) {
        handled = keyboardManager.onKeydown(e)
      }
    }
    if (handled) {
      e.preventDefault()
      e.stopPropagation()
    }
  }

  /*
    Type into cell (replacing the existing content)
  */
  _onInput() {
    const value = this.refs.keytrap.val()
    this._requestEditCell(value)
    // Clear keytrap after sending an action
    this.refs.keytrap.val('')
  }

  _onCellEnter(e) {
    e.stopPropagation()
    e.preventDefault()
    let cellEl = DOM.wrap(e.target).getParent()
    if (e.detail.shiftKey) {
      this._tableEditing.insertSoftBreak()
    } else {
      this._nav(1, 0, false, { anchorCellId: cellEl.id, focusCellId: cellEl.id })
    }
  }

  _onCellTab(e) {
    e.stopPropagation()
    e.preventDefault()
    let cellEl = DOM.wrap(e.target).getParent()
    this._nav(0, 1, false, { anchorCellId: cellEl.id, focusCellId: cellEl.id })
  }

  _onCellEscape(e) {
    e.stopPropagation()
    e.preventDefault()
    let cellEl = DOM.wrap(e.target).getParent()
    this._requestSelectionChange(this._tableEditing.createTableSelection({ anchorCellId: cellEl.id, focusCellId: cellEl.id }))
  }

  _onCopy(e) {
    this._clipboard.onCopy(e)
  }

  _onPaste(e) {
    this._clipboard.onPaste(e)
  }

  _onCut(e) {
    this._clipboard.onCut(e)
  }

  _onContextMenu(e) {
    e.preventDefault()
    e.stopPropagation()
    this._showContextMenu(e)
  }

  _onContextmenuitemclick(e) {
    e.preventDefault()
    e.stopPropagation()
    this._hideContextMenu()
  }

  _getSelection() {
    return this.context.editorSession.getSelection()
  }

  _getSelectionData() {
    let sel = this._getSelection()
    if (sel && sel.surfaceId === this.getId()) {
      return sel.data
    }
  }

  _requestEditCell(initialValue) {
    let selData = this._getSelectionData()
    if (selData) {
      this._tableEditing.editCell(selData.anchorCellId, initialValue)
    }
  }

  _requestSelectionChange(newSel) {
    // console.log('requesting selection change', newSel)
    if (newSel) newSel.surfaceId = this.getId()
    this.context.editorSession.setSelection(newSel)
  }

  _getClickTargetForEvent(e) {
    let target = DOM.wrap(e.target)
    let cellEl = domHelpers.findParent(target, 'td,th')
    if (cellEl) {
      return { type: 'cell', id: cellEl.id }
    }
  }

  _getRowCol(cellEl) {
    let rowIdx = parseInt(cellEl.getAttribute('data-row-idx'), 10)
    let colIdx = parseInt(cellEl.getAttribute('data-col-idx'), 10)
    return [rowIdx, colIdx]
  }

  _mapClientXYToCellId(x, y) {
    // TODO: this could be optimized using bisect search
    let cellEls = this.refs.table.el.findAll('th,td')
    for (let i = 0; i < cellEls.length; i++) {
      let cellEl = cellEls[i]
      let rect = domHelpers.getBoundingRect(cellEl)
      if (domHelpers.isXInside(x, rect) && domHelpers.isYInside(y, rect)) {
        return cellEl.id
      }
    }
  }

  _nav(dr, dc, expand, selData) {
    selData = selData || this._getSelectionData()
    if (selData) {
      let newSelData = computeUpdatedSelection(this.props.node, selData, dr, dc, expand)
      this._requestSelectionChange(this._tableEditing.createTableSelection(newSelData))
    }
  }

  _getCustomResourceId() {
    return this.props.node.id
  }

  _clearSelection() {
    let selData = this._getSelectionData()
    if (selData) {
      this._tableEditing.clearValues(selData.anchorCellId, selData.focusCellId)
    }
  }

  rerenderDOMSelection() {
    // console.log('SheetComponent.rerenderDOMSelection()')
    this._positionSelection(this._getSelectionData())
    // // put the native focus into the keytrap so that we
    // // receive keyboard events
    this.refs.keytrap.el.focus()
  }

  _positionSelection(selData, focused) {
    // TODO: find a better criteria for integrity checking
    if (!selData) {
      this._hideSelection()
      return
    }
    let { anchorCellId, focusCellId } = selData

    let anchorCellComp = this._getActualCellComp(anchorCellId)
    let anchorRect = getRelativeBoundingRect(anchorCellComp.el, this.el)
    this.refs.selAnchor.css(this._getStylesForRectangle(anchorRect))

    if (!focused) {
      let rangeRect
      if (focusCellId === anchorCellId) {
        rangeRect = anchorRect
      } else {
        let focusCellComp = this._getActualCellComp(focusCellId)
        let focusRect = getRelativeBoundingRect(focusCellComp.el, this.el)
        rangeRect = domHelpers.getBoundingRectForRects(anchorRect, focusRect)
      }
      this.refs.selRange.css(this._getStylesForRectangle(rangeRect))
    } else {
      this.refs.selRange.css('visibility', 'hidden')
    }
  }

  _getActualCellComp(cellId) {
    let table = this._tableEditing.getTable()
    let cell = table.get(cellId)
    if (cell.shadowed) cell = cell.masterCell
    return this.refs[cell.id]
  }

  _hideSelection() {
    this.refs.selAnchor.css('visibility', 'hidden')
    this.refs.selRange.css('visibility', 'hidden')
  }

  _hideContextMenu() {
    this.refs.contextMenu.addClass('sm-hidden')
  }

  _showContextMenu(e) {
    let contextMenu = this.refs.contextMenu
    let offset = this.el.getOffset()
    contextMenu.css({
      display: 'block',
      top: e.clientY - offset.top,
      left: e.clientX - offset.left
    })
    contextMenu.removeClass('sm-hidden')
  }

  _getStylesForRectangle(rect) {
    let styles = { visibility: 'hidden' }
    if (rect) {
      Object.assign(styles, rect)
      if (isFinite(rect.top) && isFinite(rect.left) &&
        isFinite(rect.width) && isFinite(rect.height)) {
        styles.visibility = 'visible'
      }
    }
    return styles
  }
}
<commit_msg>Rerender TableComponent when it gets enabled.<commit_after>import {
  CustomSurface, platform,
  DefaultDOMElement as DOM, domHelpers, getRelativeBoundingRect,
  keys
} from 'substance'
import TableEditing from '../../article/TableEditing'
import {
  getCellRange, computeUpdatedSelection
} from '../../article/tableHelpers'
import TableClipboard from '../util/TableClipboard'
import TableCellComponent from './TableCellComponent'
import TableContextMenu from './TableContextMenu'

export default class TableComponent extends CustomSurface {

  constructor(...args) {
    super(...args)

    this._selectionData = {}
    this._tableEditing = new TableEditing(this.context.editorSession, this.props.node.id, this.getId())
    this._clipboard = new TableClipboard(this._tableEditing)
  }

  getChildContext() {
    return {
      surface: this,
      parentSurfaceId: this.getId(),
      // HACK: nulling this so that nested surfaces get an id that are relative to
      // this surface instead of the isolatedNodeComponent
      isolatedNodeComponent: null
    }
  }

  shouldRerender(newProps) {
    return (newProps.node !== this.props.node || newProps.disabled !== this.props.disabled)
  }

  didMount() {
    super.didMount()

    this._tableSha = this.props.node._getSha()

    // Disable reactive behaviour in read only mode
    // TODO: we may want to bring it back so the reader receives realtime updates
    if (!this.props.disabled) {
      this.context.editorSession.onRender('document', this._onDocumentChange, this)
      this.context.editorSession.onRender('selection', this._onSelectionChange, this)
      this._positionSelection(this._getSelectionData())
    }

  }

  dispose() {
    super.dispose()
    this.context.editorSession.off(this)
  }

  render($$) {
    let el = $$('div').addClass('sc-table')
    if (!this.props.disabled) {
      el.on('mousedown', this._onMousedown)
      .on('mouseup', this._onMouseup)
    }

    el.append(this._renderTable($$))
    // el.append(this._renderClickableOverlays($$))
    if (!this.props.disabled) {
      el.append(this._renderKeyTrap($$))
      el.append(this._renderUnclickableOverlays($$))
      el.append(this._renderContextMenu($$))
    }
    return el
  }

  _renderTable($$) {
    let table = $$('table').ref('table')
    let node = this.props.node
    let matrix = node.getCellMatrix()
    for (let i = 0; i < matrix.length; i++) {
      let cells = matrix[i]
      let tr = $$('tr')
      for (let j = 0; j < cells.length; j++) {
        if (cells[j].shadowed) continue
        let cell = cells[j]
        tr.append(
          $$(TableCellComponent, { node: cell, disabled: true })
            .ref(cell.id)
            .on('enter', this._onCellEnter)
            .on('tab', this._onCellTab)
            .on('escape', this._onCellEscape)
        )
      }
      table.append(tr)
    }
    table.on('mousemove', this._onMousemove)
      .on('dblclick', this._onDblclick)
      .on('contextmenu', this._onContextMenu)
      .on('contextmenuitemclick', this._onContextmenuitemclick)
    return table
  }

  _renderKeyTrap($$) {
    return $$('textarea').addClass('se-keytrap').ref('keytrap')
      .css({ position: 'absolute', width: 0, height: 0, opacity: 0 })
      .on('keydown', this._onKeydown)
      .on('input', this._onInput)
      .on('copy', this._onCopy)
      .on('paste', this._onPaste)
      .on('cut', this._onCut)
  }

  _renderUnclickableOverlays($$) {
    let el = $$('div').addClass('se-unclickable-overlays')
    el.append(
      this._renderSelectionOverlay($$)
    )
    el.append(
      this.props.unclickableOverlays
    )
    return el
  }

  _renderSelectionOverlay($$) {
    let el = $$('div').addClass('se-selection-overlay')
    el.append(
      $$('div').addClass('se-selection-anchor').ref('selAnchor').css('visibility', 'hidden'),
      $$('div').addClass('se-selection-range').ref('selRange').css('visibility', 'hidden')
    )
    return el
  }

  _renderContextMenu($$) {
    const configurator = this.context.configurator
    let contextMenu = $$(TableContextMenu, {
      toolPanel: configurator.getToolPanel('table-context-menu')
    }).ref('contextMenu')
      .addClass('se-context-menu')
      .css({ display: 'none' })
    return contextMenu
  }

  _onDocumentChange() {
    const table = this.props.node
    // Note: using a simplified way to detect when a table
    // has changed structurally
    // TableElementNode is detecting such changes and
    // updates an internal 'sha' that we can compare against
    if (table._hasShaChanged(this._tableSha)) {
      this.rerender()
      this._tableSha = table._getSha()
    }
  }

  _onSelectionChange(sel) {
    const self = this
    if (!sel || sel.isNull()) {
      _disableActiveCell()
      this._hideSelection()
    } else if (sel.isPropertySelection()) {
      let nodeId = sel.path[0]
      if (this._activeCell !== nodeId) {
        _disableActiveCell()
        let newCellEditor = this.refs[nodeId]
        if (newCellEditor) {
          // console.log('ENABLING CELL EDITOR', nodeId)
          newCellEditor.extendProps({ disabled: false })
          this._activeCell = nodeId
        }
      }
      if (this._activeCell) {
        // TODO: this could be simplified
        let doc = this.context.editorSession.getDocument()
        let cell = doc.get(this._activeCell)
        this._positionSelection({
          type: 'range',
          anchorCellId: cell.id,
          focusCellId: cell.id,
        }, true)
      } else {
        this._hideSelection()
      }
    } else if (sel.surfaceId !== this.getId()) {
      _disableActiveCell()
      this._hideSelection()
    } else {
      _disableActiveCell()
    }
    this._hideContextMenu()

    function _disableActiveCell() {
      const activeCellId = self._activeCell
      if(activeCellId) {
        let cellEditor = self.refs[activeCellId]
        if (cellEditor) {
          // console.log('DISABLING CELL EDITOR', activeCellId)
          cellEditor.extendProps({ disabled: true })
        }
        self._activeCell = null
      }
    }

  }

  _onMousedown(e) {
    e.stopPropagation()
    // TODO: do not update the selection if right-clicked and already having a selection
    if (platform.inBrowser) {
      DOM.wrap(window.document).on('mouseup', this._onMouseup, this, {
        once: true
      })
    }
    // console.log('_onMousedown', e)
    let selData = this._selectionData
    if (!selData) selData = this._selectionData = {}
    let target = this._getClickTargetForEvent(e)
    // console.log('target', target)
    if (!target) return

    let isRightButton = domHelpers.isRightButton(e)
    if (isRightButton) {
      // console.log('IS RIGHT BUTTON')
      // this will be handled by onContextMenu
      if (target.type === 'cell') {
        let targetCell = this.props.node.get(target.id)
        let _needSetSelection = true
        let _selData = this._getSelectionData()
        if (_selData && targetCell) {
          let { startRow, startCol, endRow, endCol } = getCellRange(this.props.node, _selData.anchorCellId, _selData.focusCellId)
          _needSetSelection = (
            targetCell.colIdx < startCol || targetCell.colIdx > endCol ||
            targetCell.rowIdx < startRow || targetCell.rowIdx > endRow
          )
        }
        if (_needSetSelection) {
          this._isSelecting = true
          selData.anchorCellId = target.id
          selData.focusCellId = target.id
          this._requestSelectionChange(this._tableEditing.createTableSelection(selData))
        }
      }
      return
    }
    if (target.type === 'cell') {
      this._isSelecting = true
      selData.focusCellId = target.id
      if (!e.shiftKey) {
        selData.anchorCellId = target.id
      }
      e.preventDefault()
      this._requestSelectionChange(this._tableEditing.createTableSelection(selData))
    }
  }

  _onMouseup(e) {
    e.stopPropagation()
    if (this._isSelecting) {
      e.preventDefault()
      this._isSelecting = false
    }
  }

  _onMousemove(e) {
    if (this._isSelecting) {
      const selData = this._selectionData
      let cellId = this._mapClientXYToCellId(e.clientX, e.clientY)
      if (cellId !== selData.focusCellId) {
        selData.focusCellId = cellId
        this._requestSelectionChange(this._tableEditing.createTableSelection(selData))
      }
    }
  }

  _onDblclick(e) {
    e.preventDefault()
    e.stopPropagation()
    this._requestEditCell()
  }

  _onKeydown(e) {
    let handled = false
    switch (e.keyCode) {
      case keys.LEFT:
        this._nav(0, -1, e.shiftKey)
        handled = true
        break
      case keys.RIGHT:
        this._nav(0, 1, e.shiftKey)
        handled = true
        break
      case keys.UP:
        this._nav(-1, 0, e.shiftKey)
        handled = true
        break
      case keys.DOWN:
        this._nav(1, 0, e.shiftKey)
        handled = true
        break
      case keys.ENTER: {
        this._requestEditCell()
        handled = true
        break
      }
      case keys.TAB: {
        this._nav(0, 1)
        handled = true
        break
      }
      case keys.DELETE:
      case keys.BACKSPACE: {
        this._clearSelection()
        handled = true
        break
      }
      default:
        //
    }
    // let an optional keyboard manager handle the key
    if (!handled) {
      const keyboardManager = this.context.keyboardManager
      if (keyboardManager) {
        handled = keyboardManager.onKeydown(e)
      }
    }
    if (handled) {
      e.preventDefault()
      e.stopPropagation()
    }
  }

  /*
    Type into cell (replacing the existing content)
  */
  _onInput() {
    const value = this.refs.keytrap.val()
    this._requestEditCell(value)
    // Clear keytrap after sending an action
    this.refs.keytrap.val('')
  }

  _onCellEnter(e) {
    e.stopPropagation()
    e.preventDefault()
    let cellEl = DOM.wrap(e.target).getParent()
    if (e.detail.shiftKey) {
      this._tableEditing.insertSoftBreak()
    } else {
      this._nav(1, 0, false, { anchorCellId: cellEl.id, focusCellId: cellEl.id })
    }
  }

  _onCellTab(e) {
    e.stopPropagation()
    e.preventDefault()
    let cellEl = DOM.wrap(e.target).getParent()
    this._nav(0, 1, false, { anchorCellId: cellEl.id, focusCellId: cellEl.id })
  }

  _onCellEscape(e) {
    e.stopPropagation()
    e.preventDefault()
    let cellEl = DOM.wrap(e.target).getParent()
    this._requestSelectionChange(this._tableEditing.createTableSelection({ anchorCellId: cellEl.id, focusCellId: cellEl.id }))
  }

  _onCopy(e) {
    this._clipboard.onCopy(e)
  }

  _onPaste(e) {
    this._clipboard.onPaste(e)
  }

  _onCut(e) {
    this._clipboard.onCut(e)
  }

  _onContextMenu(e) {
    e.preventDefault()
    e.stopPropagation()
    this._showContextMenu(e)
  }

  _onContextmenuitemclick(e) {
    e.preventDefault()
    e.stopPropagation()
    this._hideContextMenu()
  }

  _getSelection() {
    return this.context.editorSession.getSelection()
  }

  _getSelectionData() {
    let sel = this._getSelection()
    if (sel && sel.surfaceId === this.getId()) {
      return sel.data
    }
  }

  _requestEditCell(initialValue) {
    let selData = this._getSelectionData()
    if (selData) {
      this._tableEditing.editCell(selData.anchorCellId, initialValue)
    }
  }

  _requestSelectionChange(newSel) {
    // console.log('requesting selection change', newSel)
    if (newSel) newSel.surfaceId = this.getId()
    this.context.editorSession.setSelection(newSel)
  }

  _getClickTargetForEvent(e) {
    let target = DOM.wrap(e.target)
    let cellEl = domHelpers.findParent(target, 'td,th')
    if (cellEl) {
      return { type: 'cell', id: cellEl.id }
    }
  }

  _getRowCol(cellEl) {
    let rowIdx = parseInt(cellEl.getAttribute('data-row-idx'), 10)
    let colIdx = parseInt(cellEl.getAttribute('data-col-idx'), 10)
    return [rowIdx, colIdx]
  }

  _mapClientXYToCellId(x, y) {
    // TODO: this could be optimized using bisect search
    let cellEls = this.refs.table.el.findAll('th,td')
    for (let i = 0; i < cellEls.length; i++) {
      let cellEl = cellEls[i]
      let rect = domHelpers.getBoundingRect(cellEl)
      if (domHelpers.isXInside(x, rect) && domHelpers.isYInside(y, rect)) {
        return cellEl.id
      }
    }
  }

  _nav(dr, dc, expand, selData) {
    selData = selData || this._getSelectionData()
    if (selData) {
      let newSelData = computeUpdatedSelection(this.props.node, selData, dr, dc, expand)
      this._requestSelectionChange(this._tableEditing.createTableSelection(newSelData))
    }
  }

  _getCustomResourceId() {
    return this.props.node.id
  }

  _clearSelection() {
    let selData = this._getSelectionData()
    if (selData) {
      this._tableEditing.clearValues(selData.anchorCellId, selData.focusCellId)
    }
  }

  rerenderDOMSelection() {
    // console.log('SheetComponent.rerenderDOMSelection()')
    this._positionSelection(this._getSelectionData())
    // // put the native focus into the keytrap so that we
    // // receive keyboard events
    this.refs.keytrap.el.focus()
  }

  _positionSelection(selData, focused) {
    // TODO: find a better criteria for integrity checking
    if (!selData) {
      this._hideSelection()
      return
    }
    let { anchorCellId, focusCellId } = selData

    let anchorCellComp = this._getActualCellComp(anchorCellId)
    let anchorRect = getRelativeBoundingRect(anchorCellComp.el, this.el)
    this.refs.selAnchor.css(this._getStylesForRectangle(anchorRect))

    if (!focused) {
      let rangeRect
      if (focusCellId === anchorCellId) {
        rangeRect = anchorRect
      } else {
        let focusCellComp = this._getActualCellComp(focusCellId)
        let focusRect = getRelativeBoundingRect(focusCellComp.el, this.el)
        rangeRect = domHelpers.getBoundingRectForRects(anchorRect, focusRect)
      }
      this.refs.selRange.css(this._getStylesForRectangle(rangeRect))
    } else {
      this.refs.selRange.css('visibility', 'hidden')
    }
  }

  _getActualCellComp(cellId) {
    let table = this._tableEditing.getTable()
    let cell = table.get(cellId)
    if (cell.shadowed) cell = cell.masterCell
    return this.refs[cell.id]
  }

  _hideSelection() {
    this.refs.selAnchor.css('visibility', 'hidden')
    this.refs.selRange.css('visibility', 'hidden')
  }

  _hideContextMenu() {
    this.refs.contextMenu.addClass('sm-hidden')
  }

  _showContextMenu(e) {
    let contextMenu = this.refs.contextMenu
    let offset = this.el.getOffset()
    contextMenu.css({
      display: 'block',
      top: e.clientY - offset.top,
      left: e.clientX - offset.left
    })
    contextMenu.removeClass('sm-hidden')
  }

  _getStylesForRectangle(rect) {
    let styles = { visibility: 'hidden' }
    if (rect) {
      Object.assign(styles, rect)
      if (isFinite(rect.top) && isFinite(rect.left) &&
        isFinite(rect.width) && isFinite(rect.height)) {
        styles.visibility = 'visible'
      }
    }
    return styles
  }
}
<|endoftext|>


<commit_before>/*
 * grunt-react
 * https://github.com/ericclemmons/grunt-react
 *
 * Copyright (c) 2013 Eric Clemmons
 * Licensed under the MIT license.
 */

'use strict';

/**
 * Module dependencies
 */

var fs      = require('fs');
var glob    = require('glob');
var mkdirp  = require('mkdirp');
var path    = require('path');

/**
 * React dependencies
 */

var visitors  = require('react-tools/vendor/fbtransform/visitors').transformVisitors;
var transform = require('react-tools/vendor/fbtransform/lib/transform').transform;

module.exports = function(grunt) {

  grunt.registerMultiTask('react', 'Compile Facebook React .jsx templates into .js', function() {
    var done    = grunt.task.current.async();
    var options = this.options({
      extension:    'js',
      ignoreMTime:  false
    });

    this.files.forEach(function(f) {
      var src = f.src.filter(function(dir) {
        if (!grunt.file.exists(dir)) {
          grunt.log.warn('Directory "' + dir + '" not found.');

          return false;
        } else if (!grunt.file.isDir(dir)) {
          grunt.log.warn(dir + ' is not a directory.');

          return false;
        }

        return true;
      }).map(function(dir) {

        var source  = path.resolve(dir);
        var target  = path.resolve(f.dest);

        // Find all of the files to transform.
        glob(path.join(source, '**', '*.' + options.extension), {}, function(er, files) {
          files.map(function(srcFile) {
            var destFile = target + srcFile.substring(source.length, srcFile.length - options.extension.length) + 'js';

            if (!options.ignoreMTime) {
              var srcStat = fs.statSync(srcFile);
              var destStat = fs.existsSync(destFile) && fs.statSync(destFile);

              if (destStat && Date.parse(srcStat.mtime) < Date.parse(destStat.mtime)) {
                return;
              }
            }

            var src     = fs.readFileSync(srcFile).toString();
            var newSrc  = transform(visitors.react, src).code;
            var destDir = path.dirname(destFile);

            mkdirp.sync(destDir);

            fs.writeFileSync(destFile, newSrc);
          });
          done();
        });
      });
    });
  });

};
<commit_msg>Report source and destination prior to compilation for better error reporting.<commit_after>/*
 * grunt-react
 * https://github.com/ericclemmons/grunt-react
 *
 * Copyright (c) 2013 Eric Clemmons
 * Licensed under the MIT license.
 */

'use strict';

/**
 * Module dependencies
 */

var fs      = require('fs');
var glob    = require('glob');
var mkdirp  = require('mkdirp');
var path    = require('path');

/**
 * React dependencies
 */

var visitors  = require('react-tools/vendor/fbtransform/visitors').transformVisitors;
var transform = require('react-tools/vendor/fbtransform/lib/transform').transform;

module.exports = function(grunt) {

  grunt.registerMultiTask('react', 'Compile Facebook React .jsx templates into .js', function() {
    var done    = grunt.task.current.async();
    var options = this.options({
      extension:    'js',
      ignoreMTime:  false
    });

    this.files.forEach(function(f) {
      var src = f.src.filter(function(dir) {
        if (!grunt.file.exists(dir)) {
          grunt.log.warn('Directory "' + dir + '" not found.');

          return false;
        } else if (!grunt.file.isDir(dir)) {
          grunt.log.warn(dir + ' is not a directory.');

          return false;
        }

        return true;
      }).map(function(dir) {

        var source  = path.resolve(dir);
        var target  = path.resolve(f.dest);

        // Find all of the files to transform.
        glob(path.join(source, '**', '*.' + options.extension), {}, function(er, files) {
          files.map(function(srcFile) {
            var destFile = target + srcFile.substring(source.length, srcFile.length - options.extension.length) + 'js';

            if (!options.ignoreMTime) {
              var srcStat = fs.statSync(srcFile);
              var destStat = fs.existsSync(destFile) && fs.statSync(destFile);

              if (destStat && Date.parse(srcStat.mtime) < Date.parse(destStat.mtime)) {
                return;
              }
            }

	    grunt.log.writeln("[react] "+srcFile+" --> "+destFile);

            var src     = fs.readFileSync(srcFile).toString();
            var newSrc  = transform(visitors.react, src).code;
            var destDir = path.dirname(destFile);

            mkdirp.sync(destDir);

            fs.writeFileSync(destFile, newSrc);
          });
          done();
        });
      });
    });
  });

};
<|endoftext|>


<commit_before>const moment = require('moment');

module.exports = (bot, message) => {
  bot.replyPublic(message, `⏰ 「${message.text}」やるぞー！`);
};
<commit_msg>add measuring of times<commit_after>const moment = require('moment');

const workPerTeamAndUser = {};

function getWork(message) {
  const key = message.team_id + '/' + message.user_id;
  if (!workPerTeamAndUser[key]) {
    workPerTeamAndUser[key] = {
      tasks: {},
      currentTask: undefined
    };
  }
  return workPerTeamAndUser[key];
}

// 実行中のタスクがあれば現時点の時刻まで作業したとして taskに計上する
function finishCurrentTask(message) {
  const work = getWork(message);
  const doneTask = work.currentTask;
  if (!doneTask) {
    return;
  }
  const nowTime = Date.now();
  if (!work.tasks[doneTask.name]) {
    work.tasks[doneTask.name] = {
      totalTime: 0
    };
  }
  work.tasks[doneTask.name].totalTime += nowTime - doneTask.startTime;
  work.currentTask = undefined;
}

function addTask(message) {
  // 現在のタスクを終了
  finishCurrentTask(message);

  const work = getWork(message);
  const nowTime = Date.now();

  // 新しいタスクを開始
  work.currentTask = {
    name: message.text,
    startTime: Date.now()
  };
}

function listupTasksForDisplay(message) {
  const work = getWork(message);
  const taskNames = Object.keys(work.tasks);
  if (taskNames.length === 0) {
    return '今日はまだ働いてないよ :sleeping:';
  }
  let result = [];
  let totalTime = 0;
  taskNames.forEach(taskName => {
    const task = work.tasks[taskName];
    totalTime += task.totalTime;
  });
  console.log('totalTime: ' + totalTime);
  taskNames.forEach(taskName => {
    const task = work.tasks[taskName];
    result.push([
      `"${taskName}"`,
      moment.duration(task.totalTime, 'millisecond').humanize(),
      '(' + Math.floor(task.totalTime / totalTime * 100) + '%)'
    ].join(' '));
  });
  return result.join('\n');
}

function clearTasks(message) {
  const work = getWork(message);
  work.tasks = [];
  work.currentTask = undefined;
}

module.exports = (bot, message) => {
  if (message.text === 'clear') {
    clearTasks(message);
    bot.replyPublic(message, 'なかったことにしたよ');
  }
  if (message.text === 'clock out') {
    finishCurrentTask(message);
    const listups = listupTasksForDisplay(message);
    clearTasks(message);
    bot.replyPublic(message, {
      text: 'おつかれさまー :honey_pot:',
      attachments: [{
        text: listups
      }]
    });
  } else if (message.text === '') {
    const work = getWork(message);
    if (work && work.currentTask) {
      bot.replyPublic(message, `いまは「 ${work.currentTask.name} 」をやっているよー `);
    } else {
      bot.replyPublic(message, ':question: そのうちヘルプがでるようになるよー');
    }
  } else {
    addTask(message);
    bot.replyPublic(message, `⏰ 「 ${message.text} 」やるぞー！`);
  }
};
<|endoftext|>


<commit_before>var path = require('path');

var utils = {
  /**
  * @description Check if the user has provided the required options
  * @param {object} options - No options are required
  * @return {bool} bool representing if the provided options are valid
  */
  optionsAreValid: function (options) {
    return typeof options === 'object';
  },

  /**
  * @description Trim the cwd from the filepath, so if cwd is foo and filepath is foo/bar/bacon.jam,
  *     trim off the foo and only return the bar/bacon.jam
  * @param {string} filepath - filepath we want to trim cwd from
  * @param {string} cwd - the cwd to remove from the beginning of the path
  * @return {string} filepath with the cwd removed or the provided path if the cwd is not valid
  */
  trimCwd: function (filepath, cwd) {
    if (typeof cwd === 'string' && filepath.substr(0, cwd.length) === cwd) {
      filepath = filepath.substr(cwd.length);
    }
    return path.posix.normalize(filepath);
  },

  /**
  * @description Takes an array of file objects and returns an array of file paths, This will need to do a few things
  *     it will need to trim cwd from paths, use optional relative destinations, and avoid duplicates
  * @param {string} basePath - Base path provided by options.dest
  * @param {string} srcBase - Source base for files, will be trimmed from files in creating destination
  * @param {object[]} files - Array of file objects found by grunt
  * @return {object[]} returns a complete array of file path objects, {src: '...', dest: '...'}
  */
  getFilePaths: function (basePath, srcBase, files) {
    var filePaths = [],
        destination;

    // Files must be of type array, if not, return an empty array
    if (Object.prototype.toString.call(files) !== '[object Array]') { return []; }

    files.forEach(function (file) {
      // For each src file we have
      file.info.forEach(function (info) {
        var filepath = info.path;
        // Make sure the path is normalized
        filepath = path.posix.normalize(filepath);
        // Trim the cwd from the path to prepare it for the destination
        destination = utils.trimCwd(filepath, file.orig.cwd);
        if (srcBase.length) {
          destination = utils.trimCwd(destination, srcBase);
        }
        // Set up the relative destination if one is provided
        if (file.orig.dest) {
          destination = path.posix.join(basePath, file.orig.dest, destination);
        } else {
          destination = path.posix.join(basePath, destination);
        }
        // Remove the filename from the destination
        destination = path.posix.dirname(destination);

        // If a the file src is not in the array, add the file, this matched on source
        if (!utils.arrayContainsSrcFile(filePaths, filepath)) {
          var filePath = utils.getPathWithDest(filePaths, destination);
          if (filePath) {
            filePath.src.push(filepath);
            filePath.isDir = info.isDir || filePath.isDir;
          } else {
            filePaths.push({
              src: [filepath],
              dest: destination,
              isDir: info.isDir
            });
          }
        }

      }); // Inner for-each

    }); // Outer for-each

    return filePaths;
  },

  /**
  * @description Takes an array [{src: ['..'], dest: "..", isDir: boolean} and a files source path and checks if the array contains it
  * @param {object[]} files - Array of FilePath Objects
  * @param {string} source - Source of the File
  * @return {boolean} whether or not the array of files contained a file with the same source
  */
  arrayContainsSrcFile: function (files, source) {
    return files.some(function (file) { return file.src.indexOf(source) !== -1; });
  },

  /**
   * @description Takes an array of filePaths and a destination path and returns the element containing it or undefined
   * @param {object[]} files - Array of FilePath objects
   * @param {string} destination - Destination path
   * return {object|undefined} element in the array containing the destination or undefined
   */
  getPathWithDest: function (files, destination) {
    return files.find(function(file) { return file.dest === destination; });
  },

  /**
   * @description Takes an options object and a array of file paths and creates the shell command
   * @param {object} options - ncftp_push options object
   * @param {array} files - array of file paths { src: '..', dest: '..' }
   * @return {string} the shell command to run
   */
  createShellCommand: function (options, files) {
    var command = [];
    files.forEach(function(file) {
      var recurse = (file.isDir) ? '-R' : '';
      command.push(options.ncftpPath + 'ncftpput ' + recurse
        + ' -m -f ' + options.authFile
        + ((options.redial) ? ' -r ' + options.redial : '')
        + ((options.debug) ? ' -d ' + options.debugFile : '')
        + ' ' + file.dest + ' ' + file.src.join(' '));
    });
    return command;
  }

};

module.exports = utils;
<commit_msg>Add some test code to see why travis build is failing<commit_after>var path = require('path');

var utils = {
  /**
  * @description Check if the user has provided the required options
  * @param {object} options - No options are required
  * @return {bool} bool representing if the provided options are valid
  */
  optionsAreValid: function (options) {
    return typeof options === 'object';
  },

  /**
  * @description Trim the cwd from the filepath, so if cwd is foo and filepath is foo/bar/bacon.jam,
  *     trim off the foo and only return the bar/bacon.jam
  * @param {string} filepath - filepath we want to trim cwd from
  * @param {string} cwd - the cwd to remove from the beginning of the path
  * @return {string} filepath with the cwd removed or the provided path if the cwd is not valid
  */
  trimCwd: function (filepath, cwd) {
    if (typeof cwd === 'string' && filepath.substr(0, cwd.length) === cwd) {
      filepath = filepath.substr(cwd.length);
    }
    return path.posix.normalize(filepath);
  },

  /**
  * @description Takes an array of file objects and returns an array of file paths, This will need to do a few things
  *     it will need to trim cwd from paths, use optional relative destinations, and avoid duplicates
  * @param {string} basePath - Base path provided by options.dest
  * @param {string} srcBase - Source base for files, will be trimmed from files in creating destination
  * @param {object[]} files - Array of file objects found by grunt
  * @return {object[]} returns a complete array of file path objects, {src: '...', dest: '...'}
  */
  getFilePaths: function (basePath, srcBase, files) {
    var filePaths = [],
        destination;

    // Files must be of type array, if not, return an empty array
    if (Object.prototype.toString.call(files) !== '[object Array]') { return []; }

    files.forEach(function (file) {
      // For each src file we have
      file.info.forEach(function (info) {
        var filepath = info.path;
        // Make sure the path is normalized
        filepath = path.posix.normalize(filepath);
        // Trim the cwd from the path to prepare it for the destination
        destination = utils.trimCwd(filepath, file.orig.cwd);
        if (srcBase.length) {
          destination = utils.trimCwd(destination, srcBase);
        }
        // Set up the relative destination if one is provided
        if (file.orig.dest) {
          destination = path.posix.join(basePath, file.orig.dest, destination);
        } else {
          destination = path.posix.join(basePath, destination);
        }
        // Remove the filename from the destination
        destination = path.posix.dirname(destination);

        // If a the file src is not in the array, add the file, this matched on source
        if (!utils.arrayContainsSrcFile(filePaths, filepath)) {
          var filePath = utils.getPathWithDest(filePaths, destination);
          if (filePath) {
            filePath.src.push(filepath);
            filePath.isDir = info.isDir || filePath.isDir;
          } else {
            filePaths.push({
              src: [filepath],
              dest: destination,
              isDir: info.isDir
            });
          }
        }

      }); // Inner for-each

    }); // Outer for-each

    return filePaths;
  },

  /**
  * @description Takes an array [{src: ['..'], dest: "..", isDir: boolean} and a files source path and checks if the array contains it
  * @param {object[]} files - Array of FilePath Objects
  * @param {string} source - Source of the File
  * @return {boolean} whether or not the array of files contained a file with the same source
  */
  arrayContainsSrcFile: function (files, source) {
    return files.some(function (file) { return file.src.indexOf(source) !== -1; });
  },

  /**
   * @description Takes an array of filePaths and a destination path and returns the element containing it or undefined
   * @param {object[]} files - Array of FilePath objects
   * @param {string} destination - Destination path
   * return {object|undefined} element in the array containing the destination or undefined
   */
  getPathWithDest: function (files, destination) {
    console.log(files,destination);
    return files.find(function(file) { return file.dest === destination; });
  },

  /**
   * @description Takes an options object and a array of file paths and creates the shell command
   * @param {object} options - ncftp_push options object
   * @param {array} files - array of file paths { src: '..', dest: '..' }
   * @return {string} the shell command to run
   */
  createShellCommand: function (options, files) {
    var command = [];
    files.forEach(function(file) {
      var recurse = (file.isDir) ? '-R' : '';
      command.push(options.ncftpPath + 'ncftpput ' + recurse
        + ' -m -f ' + options.authFile
        + ((options.redial) ? ' -r ' + options.redial : '')
        + ((options.debug) ? ' -d ' + options.debugFile : '')
        + ' ' + file.dest + ' ' + file.src.join(' '));
    });
    return command;
  }

};

module.exports = utils;
<|endoftext|>


<commit_before>/*
 * grunt-contrib-watch
 * http://gruntjs.com/
 *
 * Copyright (c) 2014 "Cowboy" Ben Alman, contributors
 * Licensed under the MIT license.
 */

var path = require('path');
var Gaze = require('gaze').Gaze;
var _ = require('lodash');
var waiting = 'Waiting...';
var changedFiles = Object.create(null);
var watchers = [];

module.exports = function(grunt) {
  'use strict';

  var taskrun = require('./lib/taskrunner')(grunt);

  // Default date format logged
  var dateFormat = function(time) {
    grunt.log.writeln(String(
      'Completed in ' +
      time.toFixed(3) +
      's at ' +
      (new Date()).toString()
    ).cyan + ' - ' + waiting);
  };

  // When task runner has started
  taskrun.on('start', function() {
    Object.keys(changedFiles).forEach(function(filepath) {
      // Log which file has changed, and how.
      grunt.log.ok('File "' + filepath + '" ' + changedFiles[filepath] + '.');
    });
    // Reset changedFiles
    changedFiles = Object.create(null);
  });

  // When task runner has ended
  taskrun.on('end', function(time) {
    if (time > 0) {
      dateFormat(time);
    }
  });

  // When a task run has been interrupted
  taskrun.on('interrupt', function() {
    grunt.log.writeln('').write('Scheduled tasks have been interrupted...'.yellow);
  });

  // When taskrun is reloaded
  taskrun.on('reload', function() {
    taskrun.clearRequireCache(Object.keys(changedFiles));
    grunt.log.writeln('').writeln('Reloading watch config...'.cyan);
  });

  grunt.registerTask('watch', 'Run predefined tasks whenever watched files change.', function(target) {
    var self = this;
    var name = self.name || 'watch';

    // Close any previously opened watchers
    watchers.forEach(function(watcher) {
      watcher.close();
    });
    watchers = [];

    // Never gonna give you up, never gonna let you down
    if (grunt.config([name, 'options', 'forever']) !== false) {
      taskrun.forever();
    }

    // If a custom dateFormat function
    var df = grunt.config([name, 'options', 'dateFormat']);
    if (typeof df === 'function') {
      dateFormat = df;
    }

    if (taskrun.running === false) { grunt.log.writeln(waiting); }

    // initialize taskrun
    var targets = taskrun.init(name, {target: target});

    targets.forEach(function(target, i) {
      if (typeof target.files === 'string') { target.files = [target.files]; }

      // Process into raw patterns
      var patterns = _.chain(target.files).flatten().map(function(pattern) {
        return grunt.config.process(pattern);
      }).value();

      // Validate the event option
      if (typeof target.options.event === 'string') {
        target.options.event = [target.options.event];
      }

      // Set cwd if options.cwd.file is set
      if (typeof target.options.cwd !== 'string' && target.options.cwd.files) {
        target.options.cwd = target.options.cwd.files;
      }

      // Create watcher per target
      watchers.push(new Gaze(patterns, target.options, function(err) {
        if (err) {
          if (typeof err === 'string') { err = new Error(err); }
          grunt.log.writeln('ERROR'.red);
          grunt.fatal(err);
          return taskrun.done();
        }

        // Log all watched files with --verbose set
        if (grunt.option('verbose')) {
          var watched = this.watched();
          Object.keys(watched).forEach(function(watchedDir) {
            watched[watchedDir].forEach(function(watchedFile) {
              grunt.log.writeln('Watching ' + path.relative(process.cwd(), watchedFile) + ' for changes.');
            });
          });
        }

        // On changed/added/deleted
        this.on('all', function(status, filepath) {

          // Skip events not specified
          if (!_.contains(target.options.event, 'all') &&
              !_.contains(target.options.event, status)) {
            return;
          }

          filepath = path.relative(process.cwd(), filepath);

          // Skip empty filepaths
          if (filepath === '') {
            return;
          }

          // If Gruntfile.js changed, reload self task
          if (/gruntfile\.(js|coffee)/i.test(filepath)) {
            taskrun.reload = true;
          }

          // Emit watch events if anyone is listening
          if (grunt.event.listeners('watch').length > 0) {
            grunt.event.emit('watch', status, filepath, target.name);
          }

          // Group changed files only for display
          changedFiles[filepath] = status;

          // Add changed files to the target
          if (taskrun.targets[target.name]) {
            if (!taskrun.targets[target.name].changedFiles) {
              taskrun.targets[target.name].changedFiles = Object.create(null);
            }
            taskrun.targets[target.name].changedFiles[filepath] = status;
          }

          // Queue the target
          if (taskrun.queue.indexOf(target.name) === -1) {
            taskrun.queue.push(target.name);
          }

          // Run the tasks
          taskrun.run();
        });

        // On watcher error
        this.on('error', function(err) {
          if (typeof err === 'string') { err = new Error(err); }
          grunt.log.error(err.message);
        });
      }));
    });

  });
};
<commit_msg>Added `options.reload` for reloading the grunt watch task when a configuration file changes<commit_after>/*
 * grunt-contrib-watch
 * http://gruntjs.com/
 *
 * Copyright (c) 2014 "Cowboy" Ben Alman, contributors
 * Licensed under the MIT license.
 */

var path = require('path');
var Gaze = require('gaze').Gaze;
var _ = require('lodash');
var waiting = 'Waiting...';
var changedFiles = Object.create(null);
var watchers = [];

module.exports = function(grunt) {
  'use strict';

  var taskrun = require('./lib/taskrunner')(grunt);

  // Default date format logged
  var dateFormat = function(time) {
    grunt.log.writeln(String(
      'Completed in ' +
      time.toFixed(3) +
      's at ' +
      (new Date()).toString()
    ).cyan + ' - ' + waiting);
  };

  // When task runner has started
  taskrun.on('start', function() {
    Object.keys(changedFiles).forEach(function(filepath) {
      // Log which file has changed, and how.
      grunt.log.ok('File "' + filepath + '" ' + changedFiles[filepath] + '.');
    });
    // Reset changedFiles
    changedFiles = Object.create(null);
  });

  // When task runner has ended
  taskrun.on('end', function(time) {
    if (time > 0) {
      dateFormat(time);
    }
  });

  // When a task run has been interrupted
  taskrun.on('interrupt', function() {
    grunt.log.writeln('').write('Scheduled tasks have been interrupted...'.yellow);
  });

  // When taskrun is reloaded
  taskrun.on('reload', function() {
    taskrun.clearRequireCache(Object.keys(changedFiles));
    grunt.log.writeln('').writeln('Reloading watch config...'.cyan);
  });

  grunt.registerTask('watch', 'Run predefined tasks whenever watched files change.', function(target) {
    var self = this;
    var name = self.name || 'watch';

    // Close any previously opened watchers
    watchers.forEach(function(watcher) {
      watcher.close();
    });
    watchers = [];

    // Never gonna give you up, never gonna let you down
    if (grunt.config([name, 'options', 'forever']) !== false) {
      taskrun.forever();
    }

    // If a custom dateFormat function
    var df = grunt.config([name, 'options', 'dateFormat']);
    if (typeof df === 'function') {
      dateFormat = df;
    }

    if (taskrun.running === false) { grunt.log.writeln(waiting); }

    // initialize taskrun
    var targets = taskrun.init(name, {target: target});

    targets.forEach(function(target, i) {
      if (typeof target.files === 'string') { target.files = [target.files]; }

      // Process into raw patterns
      var patterns = _.chain(target.files).flatten().map(function(pattern) {
        return grunt.config.process(pattern);
      }).value();

      // Validate the event option
      if (typeof target.options.event === 'string') {
        target.options.event = [target.options.event];
      }

      // Set cwd if options.cwd.file is set
      if (typeof target.options.cwd !== 'string' && target.options.cwd.files) {
        target.options.cwd = target.options.cwd.files;
      }

      // Create watcher per target
      watchers.push(new Gaze(patterns, target.options, function(err) {
        if (err) {
          if (typeof err === 'string') { err = new Error(err); }
          grunt.log.writeln('ERROR'.red);
          grunt.fatal(err);
          return taskrun.done();
        }

        // Log all watched files with --verbose set
        if (grunt.option('verbose')) {
          var watched = this.watched();
          Object.keys(watched).forEach(function(watchedDir) {
            watched[watchedDir].forEach(function(watchedFile) {
              grunt.log.writeln('Watching ' + path.relative(process.cwd(), watchedFile) + ' for changes.');
            });
          });
        }

        // On changed/added/deleted
        this.on('all', function(status, filepath) {

          // Skip events not specified
          if (!_.contains(target.options.event, 'all') &&
              !_.contains(target.options.event, status)) {
            return;
          }

          filepath = path.relative(process.cwd(), filepath);

          // Skip empty filepaths
          if (filepath === '') {
            return;
          }

          // If Gruntfile.js changed, reload self task
          if (target.options.reload || /gruntfile\.(js|coffee)/i.test(filepath)) {
            taskrun.reload = true;
          }

          // Emit watch events if anyone is listening
          if (grunt.event.listeners('watch').length > 0) {
            grunt.event.emit('watch', status, filepath, target.name);
          }

          // Group changed files only for display
          changedFiles[filepath] = status;

          // Add changed files to the target
          if (taskrun.targets[target.name]) {
            if (!taskrun.targets[target.name].changedFiles) {
              taskrun.targets[target.name].changedFiles = Object.create(null);
            }
            taskrun.targets[target.name].changedFiles[filepath] = status;
          }

          // Queue the target
          if (taskrun.queue.indexOf(target.name) === -1) {
            taskrun.queue.push(target.name);
          }

          // Run the tasks
          taskrun.run();
        });

        // On watcher error
        this.on('error', function(err) {
          if (typeof err === 'string') { err = new Error(err); }
          grunt.log.error(err.message);
        });
      }));
    });

  });
};
<|endoftext|>


<commit_before>/*
 * grunt-xcode
 * https://github.com/matiassingers/grunt-xcode
 *
 * Copyright (c) 2014 Matias Singers
 * Licensed under the MIT license.
 */
'use strict';

var Promise = require('bluebird');
var which = require('which');
var temporary = require('temporary');
var chalk = require('chalk');
var exec = require('child_process').exec;

String.prototype.format = function() {
    var formatted = this;
    for (var i = 0; i < arguments.length; i++) {
        formatted = formatted.replace(
            RegExp('\\{' + i + '\\}', 'g'), arguments[i]);
    }
    return formatted;
};

function safelyWrap(string){
  return JSON.stringify(string);
}

module.exports = function(grunt) {
  function executeCommand(args, silent){
    var command = 'xcodebuild ' + args.join(' ');
    grunt.verbose.writeln('Command:', chalk.yellow(command));

    return new Promise(function(resolve, reject){
      // poor man's progress indicator
      if(!silent){
        var progress = setInterval(function(){
          grunt.log.write(chalk.green('.'));
        }, 1000);
      }

      // See Sindre Sorhus' grunt-shell - https://github.com/sindresorhus/grunt-shell/blob/master/tasks/shell.js
      var cmd = exec(command, {maxBuffer: 1000*1024}, function(error){
        clearInterval(progress);
        grunt.log.write(' \n');

        if(error) {
          grunt.warn(error);

          reject(error);
        }

        resolve();
      });

      var captureOutput = function (child, output) {
        if (grunt.option('color') === false) {
          child.on('data', function (data) {
            output.write(chalk.stripColor(data));
          });
        } else {
          child.pipe(output);
        }
      };

      if (grunt.option('verbose')) {
        captureOutput(cmd.stdout, process.stdout);
      }

      if (grunt.option('verbose')) {
        captureOutput(cmd.stderr, process.stderr);
      }
    });
  }

  grunt.registerMultiTask('xcode', 'Build and export Xcode projects with Grunt', function() {
    var done = this.async();

    var options = this.options({
      clean: true,
      export: true,
      project: '',
      configuration: '',
      workspace: '',
      scheme: '',
      allTargets: true,
      target: '',
      archivePath: '',
      exportFormat: 'IPA',
      exportPath: process.cwd(),
      exportFilename: 'export.ipa',
      exportProvisioningProfile: '',
      exportSigningIdentity: '',
      exportInstallerIdentity: '',
      arch: '',
      sdk: ''
    });

    which('xcodebuild', function(err, path){
      if(err || !path){
        throw new Error('`xcodebuild` command is required for grunt-xcode to work, please install Xcode Tools from developer.apple.com');
      }
    });

    if(!options.project && !options.workspace){
      throw new Error('`options.project` or `options.workspace` is required');
    }

    if(options.workspace && !options.scheme){
      throw new Error('`options.scheme` is required when building a workspace');
    }

    if(!options.archivePath){
      var temporaryDirectory = new temporary.Dir();
      options.archivePath = temporaryDirectory.path;
    }

    runCleanCommand()
      .then(runArchiveCommand)
      .then(runExportCommand)
      .finally(cleanUp);

    function runCleanCommand(){
      if(!options.clean){
        return Promise.resolve();
      }

      var command = ['clean'];
      if(options.project) command.push('-project', safelyWrap(options.project));

      return executeCommand(command, true)
        .then(function(){
          grunt.verbose.ok('`xcodebuild clean` was successful');
        });
    }
    function runArchiveCommand(){
      if(!options.export) {
        return Promise.resolve();
      }

      var command = ['archive'];
      command.push('-archivePath', safelyWrap(options.archivePath));

      if(options.project) command.push('-project', safelyWrap(options.project));
      if(options.configuration) command.push('-configuration', safelyWrap(options.configuration));
      if(options.workspace) command.push('-workspace', safelyWrap(options.workspace));
      if(options.scheme) command.push('-scheme', safelyWrap(options.scheme));

      if(options.arch) command.push('-arch', safelyWrap(options.arch));
      if(options.sdk) command.push('-sdk', safelyWrap(options.sdk));

      if(!options.exportSigningIdentity) command.push('CODE_SIGN_IDENTITY=""', 'CODE_SIGN_ENTITLEMENTS=""', 'CODE_SIGNING_REQUIRED=NO');

      if(options.target){
        command.push('-target', safelyWrap(options.target));
      } else if(options.allTargets && !options.scheme) {
        command.push('-alltargets');
      }

      grunt.log.write('Archiving: ');
      return executeCommand(command)
        .then(function(){
          grunt.verbose.ok('`xcodebuild archive` was successful');
        });
    }
    function runExportCommand(){
      if(!options.export) {
        return Promise.resolve();
      }

      var command = ['-exportArchive'];
      command.push('-archivePath "{0}.xcarchive"'.format(options.archivePath));
      command.push('-exportPath "{0}/{1}"'.format(options.exportPath, options.exportFilename));

      command.push('-exportFormat', options.exportFormat);

      if(options.exportProvisioningProfile) command.push('-exportProvisioningProfile', safelyWrap(options.exportProvisioningProfile));
      if(options.exportSigningIdentity) command.push('-exportSigningIdentity', safelyWrap(options.exportSigningIdentity));
      if(options.exportInstallerIdentity) command.push('-exportInstallerIdentity', safelyWrap(options.exportInstallerIdentity));
      if(!options.exportSigningIdentity) command.push('-exportWithOriginalSigningIdentity');

      grunt.log.write('Exporting: ');
      return executeCommand(command)
        .then(function(){
          grunt.verbose.ok('`xcodebuild export` was successful');
        });
    }
    function cleanUp(){
      if(temporaryDirectory){
        temporaryDirectory.rmdir();
      }

      if(options.export){
        grunt.log.ok('Built and exported product to: {0}/{1}'.format(options.exportPath, options.exportFilename));
      }

      done();
    }
  });
};
<commit_msg>Change maxBuffer value to Infinity<commit_after>/*
 * grunt-xcode
 * https://github.com/matiassingers/grunt-xcode
 *
 * Copyright (c) 2014 Matias Singers
 * Licensed under the MIT license.
 */
'use strict';

var Promise = require('bluebird');
var which = require('which');
var temporary = require('temporary');
var chalk = require('chalk');
var exec = require('child_process').exec;

String.prototype.format = function() {
    var formatted = this;
    for (var i = 0; i < arguments.length; i++) {
        formatted = formatted.replace(
            RegExp('\\{' + i + '\\}', 'g'), arguments[i]);
    }
    return formatted;
};

function safelyWrap(string){
  return JSON.stringify(string);
}

module.exports = function(grunt) {
  function executeCommand(args, silent){
    var command = 'xcodebuild ' + args.join(' ');
    grunt.verbose.writeln('Command:', chalk.yellow(command));

    return new Promise(function(resolve, reject){
      // poor man's progress indicator
      if(!silent){
        var progress = setInterval(function(){
          grunt.log.write(chalk.green('.'));
        }, 1000);
      }

      // See Sindre Sorhus' grunt-shell - https://github.com/sindresorhus/grunt-shell/blob/master/tasks/shell.js
      var cmd = exec(command, {maxBuffer: Infinity}, function(error){
        clearInterval(progress);
        grunt.log.write(' \n');

        if(error) {
          grunt.warn(error);

          reject(error);
        }

        resolve();
      });

      var captureOutput = function (child, output) {
        if (grunt.option('color') === false) {
          child.on('data', function (data) {
            output.write(chalk.stripColor(data));
          });
        } else {
          child.pipe(output);
        }
      };

      if (grunt.option('verbose')) {
        captureOutput(cmd.stdout, process.stdout);
      }

      if (grunt.option('verbose')) {
        captureOutput(cmd.stderr, process.stderr);
      }
    });
  }

  grunt.registerMultiTask('xcode', 'Build and export Xcode projects with Grunt', function() {
    var done = this.async();

    var options = this.options({
      clean: true,
      export: true,
      project: '',
      configuration: '',
      workspace: '',
      scheme: '',
      allTargets: true,
      target: '',
      archivePath: '',
      exportFormat: 'IPA',
      exportPath: process.cwd(),
      exportFilename: 'export.ipa',
      exportProvisioningProfile: '',
      exportSigningIdentity: '',
      exportInstallerIdentity: '',
      arch: '',
      sdk: ''
    });

    which('xcodebuild', function(err, path){
      if(err || !path){
        throw new Error('`xcodebuild` command is required for grunt-xcode to work, please install Xcode Tools from developer.apple.com');
      }
    });

    if(!options.project && !options.workspace){
      throw new Error('`options.project` or `options.workspace` is required');
    }

    if(options.workspace && !options.scheme){
      throw new Error('`options.scheme` is required when building a workspace');
    }

    if(!options.archivePath){
      var temporaryDirectory = new temporary.Dir();
      options.archivePath = temporaryDirectory.path;
    }

    runCleanCommand()
      .then(runArchiveCommand)
      .then(runExportCommand)
      .finally(cleanUp);

    function runCleanCommand(){
      if(!options.clean){
        return Promise.resolve();
      }

      var command = ['clean'];
      if(options.project) command.push('-project', safelyWrap(options.project));

      return executeCommand(command, true)
        .then(function(){
          grunt.verbose.ok('`xcodebuild clean` was successful');
        });
    }
    function runArchiveCommand(){
      if(!options.export) {
        return Promise.resolve();
      }

      var command = ['archive'];
      command.push('-archivePath', safelyWrap(options.archivePath));

      if(options.project) command.push('-project', safelyWrap(options.project));
      if(options.configuration) command.push('-configuration', safelyWrap(options.configuration));
      if(options.workspace) command.push('-workspace', safelyWrap(options.workspace));
      if(options.scheme) command.push('-scheme', safelyWrap(options.scheme));

      if(options.arch) command.push('-arch', safelyWrap(options.arch));
      if(options.sdk) command.push('-sdk', safelyWrap(options.sdk));

      if(!options.exportSigningIdentity) command.push('CODE_SIGN_IDENTITY=""', 'CODE_SIGN_ENTITLEMENTS=""', 'CODE_SIGNING_REQUIRED=NO');

      if(options.target){
        command.push('-target', safelyWrap(options.target));
      } else if(options.allTargets && !options.scheme) {
        command.push('-alltargets');
      }

      grunt.log.write('Archiving: ');
      return executeCommand(command)
        .then(function(){
          grunt.verbose.ok('`xcodebuild archive` was successful');
        });
    }
    function runExportCommand(){
      if(!options.export) {
        return Promise.resolve();
      }

      var command = ['-exportArchive'];
      command.push('-archivePath "{0}.xcarchive"'.format(options.archivePath));
      command.push('-exportPath "{0}/{1}"'.format(options.exportPath, options.exportFilename));

      command.push('-exportFormat', options.exportFormat);

      if(options.exportProvisioningProfile) command.push('-exportProvisioningProfile', safelyWrap(options.exportProvisioningProfile));
      if(options.exportSigningIdentity) command.push('-exportSigningIdentity', safelyWrap(options.exportSigningIdentity));
      if(options.exportInstallerIdentity) command.push('-exportInstallerIdentity', safelyWrap(options.exportInstallerIdentity));
      if(!options.exportSigningIdentity) command.push('-exportWithOriginalSigningIdentity');

      grunt.log.write('Exporting: ');
      return executeCommand(command)
        .then(function(){
          grunt.verbose.ok('`xcodebuild export` was successful');
        });
    }
    function cleanUp(){
      if(temporaryDirectory){
        temporaryDirectory.rmdir();
      }

      if(options.export){
        grunt.log.ok('Built and exported product to: {0}/{1}'.format(options.exportPath, options.exportFilename));
      }

      done();
    }
  });
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.crunch.ui',
  name: 'CapableView',
  extends: 'foam.u2.View',
  documentation: 'A view for displaying capable objects',

  imports: [
    'crunchController',
    'notify',
    'subject',
    'userCapabilityJunctionDAO'
  ],

  requires: [
    'foam.log.LogLevel',
    'foam.nanos.crunch.AgentCapabilityJunction',
    'foam.nanos.crunch.UserCapabilityJunction',
    'foam.u2.detail.AbstractSectionedDetailView',
    'foam.u2.detail.SectionView'
  ],

  css: `
    ^ .foam-u2-detail-SectionedDetailPropertyView .foam-u2-CheckBox-label {
      top: 0px;
      position: relative;
    }
  `,

  properties: [
    {
      name: 'capableObj',
      documentation: 'a capable object'
    },
    {
      class: 'FObjectArray',
      of: 'foam.u2.wizard.BaseWizardlet',
      name: 'wizardlets',
      documentation: 'wizardlets for capable payloads',
      postSet: function() {
        this.listenOnPayloads();
      }
    },
    {
      class: 'Array',
      name: 'wizardletSectionsList',
      documentation: `
        sections for wizardlets
        wizardletSectionsList[i] stores sections for wizardlets[i]
      `,
      factory: function() {
        return this.wizardlets.map(wizardlet =>
          this.AbstractSectionedDetailView.create({
            of: wizardlet.of
          }).sections);
      }
    },
    {
      class: 'Boolean',
      name: 'showTitle'
    }
  ],

  methods: [
    async function initE() {
      this.SUPER();

      const self = this;

      // a flag for checking if the capable object has payloads
      const hasPayloads = this.capableObj.capablePayloads &&
        Object.keys(this.capableObj.capablePayloads).length > 0;

      // set capable payloads to capabilities if the capbilities don't have payloads yet
      // if ( ! hasPayloads ) {
      //     try {
      //       await this.capableObj.setRequirements(this.capableObj.capableRequirements);
      //     } catch (e) {
      //       this.notify(e.message, '', this.LogLevel.ERROR, true);
      //     }
      // }

      // set wizardlets based on the capableObj
      // note: payloads data are also set from getWizardletsFromCapable
      //       this is why we add listeners to payloads data after wizardlets are set
      this.wizardlets = this.capableObj ?
        await this.crunchController.getWizardletsFromCapable(this.capableObj) : [];

      this.start().addClass(this.myClass())
        .forEach(this.wizardletSectionsList, function(sections, index) {
          sections.map(section => (
            this.tag(self.SectionView, {
              section,
              data: self.wizardlets[index].data,
              showTitle: self.showTitle
            })
          ));
        }).end()
      .end();
    },

    // add listeners to payload data
    function listenOnPayloads() {
      for ( const payload of this.capableObj.capablePayloads ) {
        if ( payload.data ) payload.data.sub(this.clonePayloads);
      }
    }
  ],

  listeners: [
    {
      name: 'clonePayloads',
      documentation: `
        This listener reassgins capablePayloads array each time its elements get updated.
        The purpose of this is to listen to changes for payloads of a capable object
        that calls this view. (e.g., for bank accounts which are an capable object, we want to
        know if acceptance doc payloads are valid or not)
      `,
      code: function() {
        this.capableObj.capablePayloads = [...this.capableObj.capablePayloads];
      }
    }
  ]
});
<commit_msg>removed commented code<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.crunch.ui',
  name: 'CapableView',
  extends: 'foam.u2.View',
  documentation: 'A view for displaying capable objects',

  imports: [
    'crunchController',
    'notify',
    'subject',
    'userCapabilityJunctionDAO'
  ],

  requires: [
    'foam.log.LogLevel',
    'foam.nanos.crunch.AgentCapabilityJunction',
    'foam.nanos.crunch.UserCapabilityJunction',
    'foam.u2.detail.AbstractSectionedDetailView',
    'foam.u2.detail.SectionView'
  ],

  css: `
    ^ .foam-u2-detail-SectionedDetailPropertyView .foam-u2-CheckBox-label {
      top: 0px;
      position: relative;
    }
  `,

  properties: [
    {
      name: 'capableObj',
      documentation: 'a capable object'
    },
    {
      class: 'FObjectArray',
      of: 'foam.u2.wizard.BaseWizardlet',
      name: 'wizardlets',
      documentation: 'wizardlets for capable payloads',
      postSet: function() {
        this.listenOnPayloads();
      }
    },
    {
      class: 'Array',
      name: 'wizardletSectionsList',
      documentation: `
        sections for wizardlets
        wizardletSectionsList[i] stores sections for wizardlets[i]
      `,
      factory: function() {
        return this.wizardlets.map(wizardlet =>
          this.AbstractSectionedDetailView.create({
            of: wizardlet.of
          }).sections);
      }
    },
    {
      class: 'Boolean',
      name: 'showTitle'
    }
  ],

  methods: [
    async function initE() {
      this.SUPER();

      const self = this;

      // a flag for checking if the capable object has payloads
      const hasPayloads = this.capableObj.capablePayloads &&
        Object.keys(this.capableObj.capablePayloads).length > 0;

      // set wizardlets based on the capableObj
      // note: payloads data are also set from getWizardletsFromCapable
      //       this is why we add listeners to payloads data after wizardlets are set
      this.wizardlets = this.capableObj ?
        await this.crunchController.getWizardletsFromCapable(this.capableObj) : [];

      this.start().addClass(this.myClass())
        .forEach(this.wizardletSectionsList, function(sections, index) {
          sections.map(section => (
            this.tag(self.SectionView, {
              section,
              data: self.wizardlets[index].data,
              showTitle: self.showTitle
            })
          ));
        }).end()
      .end();
    },

    // add listeners to payload data
    function listenOnPayloads() {
      for ( const payload of this.capableObj.capablePayloads ) {
        if ( payload.data ) payload.data.sub(this.clonePayloads);
      }
    }
  ],

  listeners: [
    {
      name: 'clonePayloads',
      documentation: `
        This listener reassgins capablePayloads array each time its elements get updated.
        The purpose of this is to listen to changes for payloads of a capable object
        that calls this view. (e.g., for bank accounts which are an capable object, we want to
        know if acceptance doc payloads are valid or not)
      `,
      code: function() {
        this.capableObj.capablePayloads = [...this.capableObj.capablePayloads];
      }
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.dig',
  name: 'DigFileUploadView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.blob.BlobBlob',
    'foam.nanos.fs.File',
    'foam.u2.dialog.NotificationMessage',
    'foam.u2.tag.Input'
  ],

  imports: [
    'user',
    'blobService'
  ],

  exports: [
    'as data'
  ],

  css: `
    ^ .attachment-input {
      width: 0.1px;
      height: 0.1px;
      opacity: 0;
      overflow: hidden;
      position: absolute;
      z-index: -1;
    }
    ^ .attachment-filename {
      max-width: 342px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      float: left;
    }
    ^ .attachment-view {
      min-width: 700px;
      max-width: 275px;
      height: 40px;
      background-color: #ffffff;
      padding-left: 10px;
      padding-right: 10px;
      padding-top: 5px;
    }
    ^ .attachment-btn {
      margin: 10px 0;
    }
    ^ .uploadButtonContainer {
      height: 80px;
      display: inline-block;
      vertical-align: text-bottom;
      margin-left: 40px;
    }
    ^ .removeButtonContainer {
      display: inline-block;
      vertical-align: text-bottom;
      margin-left: 20px;
      vertical-align: top;
      margin-top: 5px;
    }
    ^ .uploadDescContainer{
      position: relative;
      left: 26%;
      bottom: 24%;
    }
    ^ .uploadDescription {
      margin-top: 9px;
      font-size: 14px;
      font-weight: 300;
      letter-spacing: 0.2px;
      color: /*%PRIMARY3%*/ #406dea;
    }
    ^ .boxless-for-drag-drop {
      border: dashed 4px #a4b3b8;
      width: 90%;
      height: 50px;
      padding: 10px 10px;
      position: relative;
      margin-bottom: 10px;
      margin-left: 5px;
      overflow: scroll;
    }
    ^ .attachment-footer {
      float: right;
    }
  `,

  properties: [
    {
      class: 'foam.nanos.fs.FileProperty',
      name: 'data'
    },
    {
      class: 'Boolean',
      name: 'dragActive',
      value: false
    },
    [ 'uploadHidden', false ],
    [ 'removeHidden', false ],
    [ 'boxHidden', false ],
    [ 'attachmentInputValue', null ]
  ],

  messages: [
    { name: 'UploadFileLabel', message: 'Choose File' },
    { name: 'RemoveImageLabel', message: 'Remove File' },
    { name: 'FileError', message: 'File required' },
    { name: 'FileTypeError', message: 'Wrong file format' },
    { name: 'ErrorMessage', message: 'Please upload an image less than 2MB' }
  ],

  methods: [
    function initE() {
      this.SUPER();
      var self = this;

      this
        .addClass(this.myClass())
        .start().addClass('attachment-btn').addClass('white-blue-button').addClass('btn')
          .add(this.UploadFileLabel)
          .on('click', this.onAddAttachmentClicked)
        .end()
        .start('div').addClass('boxless-for-drag-drop')
          .add(this.slot(function(data) {
            var e = this.E();

            if ( data ) {
              e.start('div').addClass('attachment-view')
                .start().addClass('attachment-filename')
                  .start('a')
                    .attrs({
                      href: this.data$.map(function(data) {
                        if ( data ) {
                          var blob = data.data;
                          var sessionId = localStorage['defaultSession'];

                          if ( self.BlobBlob.isInstance(blob) ) {
                            return URL.createObjectURL(blob.blob);
                          } else {
                            var url = '/service/httpFileService/' + data.id;
                            // attach session id if available
                            if ( sessionId )
                              url += '?sessionId=' + sessionId;
                          }
                          return url;
                        }
                      }),
                      target: '_self'
                    }) //attrs
                   .add(this.slot(function (filename) {
                      var len = filename.length;

                     return ( len > 35 ) ? (filename.substr(0, 20) +
                       '...' + filename.substr(len - 10, len)) : filename;
                   }, data.filename$))
                .end()
            .end()
            .start().addClass('attachment-footer')
             .start({ class: 'foam.u2.tag.Image', data: 'images/ic-delete.svg'}).hide(this.removeHidden).end()
             .on('click', function(e) {
               self.document.querySelector('.attachment-view').remove()
               self.data = null;
             })
           .end()
           .end()
         }
         return e;
         }, this.data$))
          .on('dragstart', this.onDragStart)
          .on('dragenter', this.onDragEnter)
          .on('dragover', this.onDragOver)
          .on('drop', this.onDrop)
          .start().addClass('uploadButtonContainer').hide(this.uploadHidden)
            .start('input').addClass('attachment-input')
              .attrs({
                type: 'file',
                accept: 'application/vnd.ms-excel, text/csv'
              })
              .on('change', this.onChange)
            .end()
          .end()
        .end()
    .end();
    }
  ],

  listeners: [
    function onAddAttachmentClicked(e) {
      this.document.querySelector('.attachment-input').click();
    },

    function onRemoveClicked(e) {
      this.dragActive = false;
      this.data = null;
      this.attachmentInputValue = null;
    },

    function onDragOver(e) {
      this.dragActive = true;
      e.preventDefault();
    },

    function onDrop(e) {
      e.preventDefault();
      this.dragActive = false;
      if ( this.uploadHidden ) return;

      var inputFile;
      if ( e.dataTransfer.items ) {
        inputFile = e.dataTransfer.items[0]
        if ( inputFile.kind === 'file' ) {
          var file = inputFile.getAsFile();
          if ( this.isFileType(file) )
            this.addFile(file);
          else
            this.add(this.NotificationMessage.create({message: this.FileTypeError, type: 'error'}));
        }
      } else if ( e.dataTransfer.files ) {
        var file = e.dataTransfer.files[0];
        if ( this.isFileType(file) )
          this.addFile(file);
        else
          this.add(this.NotificationMessage.create({message: this.FileTypeError, type: 'error'}));
      }
    },

    function isFileType(file) {
      if ( file.type === "application/vnd.ms-excel" || file.type === "text/csv" ) return true;

      return false;
    },

    function onChange(e) {
      this.dragActive = false;
      var file = e.target.files[0];
      this.addFile(file);
    },

    function addFile(file) {
      if ( file.size > ( 2 * 1024 * 1024 ) ) {
        this.add(this.NotificationMessage.create({ message: this.ErrorMessage, type: 'error' }));
        return;
      }

      this.data = this.File.create({
        owner: this.user.id,
        filename: file.name,
        filesize: file.size,
        mimeType: file.type,
        data: this.BlobBlob.create({
          blob: file
        })
      });
    }
  ]
});
<commit_msg>Remove URL sessionId from DigFileUploadView<commit_after>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.dig',
  name: 'DigFileUploadView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.blob.BlobBlob',
    'foam.nanos.fs.File',
    'foam.u2.dialog.NotificationMessage',
    'foam.u2.tag.Input'
  ],

  imports: [
    'user',
    'blobService'
  ],

  exports: [
    'as data'
  ],

  css: `
    ^ .attachment-input {
      width: 0.1px;
      height: 0.1px;
      opacity: 0;
      overflow: hidden;
      position: absolute;
      z-index: -1;
    }
    ^ .attachment-filename {
      max-width: 342px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      float: left;
    }
    ^ .attachment-view {
      min-width: 700px;
      max-width: 275px;
      height: 40px;
      background-color: #ffffff;
      padding-left: 10px;
      padding-right: 10px;
      padding-top: 5px;
    }
    ^ .attachment-btn {
      margin: 10px 0;
    }
    ^ .uploadButtonContainer {
      height: 80px;
      display: inline-block;
      vertical-align: text-bottom;
      margin-left: 40px;
    }
    ^ .removeButtonContainer {
      display: inline-block;
      vertical-align: text-bottom;
      margin-left: 20px;
      vertical-align: top;
      margin-top: 5px;
    }
    ^ .uploadDescContainer{
      position: relative;
      left: 26%;
      bottom: 24%;
    }
    ^ .uploadDescription {
      margin-top: 9px;
      font-size: 14px;
      font-weight: 300;
      letter-spacing: 0.2px;
      color: /*%PRIMARY3%*/ #406dea;
    }
    ^ .boxless-for-drag-drop {
      border: dashed 4px #a4b3b8;
      width: 90%;
      height: 50px;
      padding: 10px 10px;
      position: relative;
      margin-bottom: 10px;
      margin-left: 5px;
      overflow: scroll;
    }
    ^ .attachment-footer {
      float: right;
    }
  `,

  properties: [
    {
      class: 'foam.nanos.fs.FileProperty',
      name: 'data'
    },
    {
      class: 'Boolean',
      name: 'dragActive',
      value: false
    },
    [ 'uploadHidden', false ],
    [ 'removeHidden', false ],
    [ 'boxHidden', false ],
    [ 'attachmentInputValue', null ]
  ],

  messages: [
    { name: 'UploadFileLabel', message: 'Choose File' },
    { name: 'RemoveImageLabel', message: 'Remove File' },
    { name: 'FileError', message: 'File required' },
    { name: 'FileTypeError', message: 'Wrong file format' },
    { name: 'ErrorMessage', message: 'Please upload an image less than 2MB' }
  ],

  methods: [
    function initE() {
      this.SUPER();
      var self = this;

      this
        .addClass(this.myClass())
        .start().addClass('attachment-btn').addClass('white-blue-button').addClass('btn')
          .add(this.UploadFileLabel)
          .on('click', this.onAddAttachmentClicked)
        .end()
        .start('div').addClass('boxless-for-drag-drop')
          .add(this.slot(function(data) {
            var e = this.E();

            if ( data ) {
              e.start('div').addClass('attachment-view')
                .start().addClass('attachment-filename')
                  .start('a')
                    .attrs({
                      href: this.data$.map(function(data) {
                        if ( data ) {
                          var blob = data.data;
                          var sessionId = localStorage['defaultSession'];

                          if ( self.BlobBlob.isInstance(blob) ) {
                            return URL.createObjectURL(blob.blob);
                          } else {
                            var url = '/service/httpFileService/' + data.id;
                            return url;
                          }
                        }
                      }),
                      target: '_self'
                    }) //attrs
                   .add(this.slot(function (filename) {
                      var len = filename.length;

                     return ( len > 35 ) ? (filename.substr(0, 20) +
                       '...' + filename.substr(len - 10, len)) : filename;
                   }, data.filename$))
                .end()
            .end()
            .start().addClass('attachment-footer')
             .start({ class: 'foam.u2.tag.Image', data: 'images/ic-delete.svg'}).hide(this.removeHidden).end()
             .on('click', function(e) {
               self.document.querySelector('.attachment-view').remove()
               self.data = null;
             })
           .end()
           .end()
         }
         return e;
         }, this.data$))
          .on('dragstart', this.onDragStart)
          .on('dragenter', this.onDragEnter)
          .on('dragover', this.onDragOver)
          .on('drop', this.onDrop)
          .start().addClass('uploadButtonContainer').hide(this.uploadHidden)
            .start('input').addClass('attachment-input')
              .attrs({
                type: 'file',
                accept: 'application/vnd.ms-excel, text/csv'
              })
              .on('change', this.onChange)
            .end()
          .end()
        .end()
    .end();
    }
  ],

  listeners: [
    function onAddAttachmentClicked(e) {
      this.document.querySelector('.attachment-input').click();
    },

    function onRemoveClicked(e) {
      this.dragActive = false;
      this.data = null;
      this.attachmentInputValue = null;
    },

    function onDragOver(e) {
      this.dragActive = true;
      e.preventDefault();
    },

    function onDrop(e) {
      e.preventDefault();
      this.dragActive = false;
      if ( this.uploadHidden ) return;

      var inputFile;
      if ( e.dataTransfer.items ) {
        inputFile = e.dataTransfer.items[0]
        if ( inputFile.kind === 'file' ) {
          var file = inputFile.getAsFile();
          if ( this.isFileType(file) )
            this.addFile(file);
          else
            this.add(this.NotificationMessage.create({message: this.FileTypeError, type: 'error'}));
        }
      } else if ( e.dataTransfer.files ) {
        var file = e.dataTransfer.files[0];
        if ( this.isFileType(file) )
          this.addFile(file);
        else
          this.add(this.NotificationMessage.create({message: this.FileTypeError, type: 'error'}));
      }
    },

    function isFileType(file) {
      if ( file.type === "application/vnd.ms-excel" || file.type === "text/csv" ) return true;

      return false;
    },

    function onChange(e) {
      this.dragActive = false;
      var file = e.target.files[0];
      this.addFile(file);
    },

    function addFile(file) {
      if ( file.size > ( 2 * 1024 * 1024 ) ) {
        this.add(this.NotificationMessage.create({ message: this.ErrorMessage, type: 'error' }));
        return;
      }

      this.data = this.File.create({
        owner: this.user.id,
        filename: file.name,
        filesize: file.size,
        mimeType: file.type,
        data: this.BlobBlob.create({
          blob: file
        })
      });
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.INTERFACE({
  package: 'foam.strategy',
  name: 'StrategizerService',

  documentation: `
    A StrategizerService is something that can return a list of references to
    models that either implement a given interface or subclass a given (possibly
    abstract) class.

    We use the term "strategy" to refer to:

      1. A model that implements an interface. In this case, we say that the
         model is one of many possible strategies for that interface.
      2. A model that extends / is a subclass of a desired model. In this case,
         we refer to the subclass model as one of many possible strategies for
         the desired model.

    Hence, a StrategizerService will give you references to strategies for
    something you want, which we refer to as the "desired model". The desired
    model might be an interface, abstract base class, or a concrete class.

    Example of use of Strategizer on FObjectView:

    name: 'exampleProp',
    view: function(_, X) {
      let predicate = expr.AND(
          expr.EQ(foam.strategy.StrategyReference.DESIRED_MODEL_ID, 'foam.nanos.auth.User'),
          expr.IN(foam.strategy.StrategyReference.STRATEGY, [foam.lookup('foam.nanos.auth.SomeUserClass'), foam.lookup('foam.nanos.auth.AnotherUserClass') ])
      );
      return foam.u2.view.FObjectView.create({
        data: myData,
        of: foam.nanos.auth.User,
        persistantData: { website: X.data.user.website },
        predicate: predicate
      }, X);
    }

    - The FObjectView will persist X.data.user.website throughout any class selections.
    - Strategizer class options will include SomeUserClass and AnotherUserClass excluding all other user subclasses. Granted the user has permissions to the strategies and if those classes exist.
  `,

  methods: [
    {
      name: 'query',
      async: true,
      type: 'foam.strategy.StrategyReference[]',
      args: [
        {
          type: 'Context',
          name: 'x'
        },
        {
          type: 'String',
          name: 'desiredModelId',
          documentation: 'The full id of a model, abstract base class, or an interface that you desire a strategy for.'
        },
        {
          type: 'String',
          name: 'target',
          documentation: 'Optional. If specified, the Strategizer will include StrategyReferences that are not generally applicable, but are applicable to the target.'
        },
        {
          name: 'strategyPredicate',
          documentation: 'Provide a unique predicate to strategizer query. Useful when omitting or extending entries based on class types.'
        }
      ]
    }
  ]
});
<commit_msg>Update documentation<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.INTERFACE({
  package: 'foam.strategy',
  name: 'StrategizerService',

  documentation: `
    A StrategizerService is something that can return a list of references to
    models that either implement a given interface or subclass a given (possibly
    abstract) class.

    We use the term "strategy" to refer to:

      1. A model that implements an interface. In this case, we say that the
         model is one of many possible strategies for that interface.
      2. A model that extends / is a subclass of a desired model. In this case,
         we refer to the subclass model as one of many possible strategies for
         the desired model.

    Hence, a StrategizerService will give you references to strategies for
    something you want, which we refer to as the "desired model". The desired
    model might be an interface, abstract base class, or a concrete class.

    Example of use of Strategizer on FObjectView:

    name: 'exampleProp',
    view: function(_, X) {
      let predicate = expr.AND(
          expr.EQ(foam.strategy.StrategyReference.DESIRED_MODEL_ID, 'foam.nanos.auth.User'),
          expr.IN(foam.strategy.StrategyReference.STRATEGY, [foam.lookup('foam.nanos.auth.SomeUserClass'), foam.lookup('foam.nanos.auth.AnotherUserClass') ])
      );
      return foam.u2.view.FObjectView.create({
        data: X.data.exampleProp,
        of: foam.nanos.auth.User,
        persistantData: { website: X.data.user.website },
        predicate: predicate
      }, X);
    }

    - The FObjectView will persist X.data.user.website throughout any class selections.
    - Strategizer class options will include SomeUserClass and AnotherUserClass excluding all other user subclasses. Granted the user has permissions to the strategies and if those classes exist.
  `,

  methods: [
    {
      name: 'query',
      async: true,
      type: 'foam.strategy.StrategyReference[]',
      args: [
        {
          type: 'Context',
          name: 'x'
        },
        {
          type: 'String',
          name: 'desiredModelId',
          documentation: 'The full id of a model, abstract base class, or an interface that you desire a strategy for.'
        },
        {
          type: 'String',
          name: 'target',
          documentation: 'Optional. If specified, the Strategizer will include StrategyReferences that are not generally applicable, but are applicable to the target.'
        },
        {
          name: 'strategyPredicate',
          documentation: 'Provide a unique predicate to strategizer query. Useful when omitting or extending entries based on class types.'
        }
      ]
    }
  ]
});
<|endoftext|>