<commit_before>$(function () {
  if ($('input[name=\'haveGutenberg\']')) {
    $('#gutenberg-modal').modal()
  }
  $('#actions').addClass('actions-scrub')

  $('.has-chevron').click(function () {
    $(this).find('span').toggleClass('down')
    $(this).next().collapse('toggle')
  })

  // display additional options on load
  const advancedOptions = $('#advanced-title')
  advancedOptions.find('.icon-arrow-right').addClass('showing')
  advancedOptions.siblings('.expansion').slideToggle(0)

  $('#swfileselect').change(function (ev) {
    let filename = ev.target.files[0].name
    if (filename.length > 25) { filename = filename.substring(0, 24) + '...' }
    $('#swfileselectbttnlabel').html(filename)
  })

  $('#lemfileselect').change(function (ev) {
    let filename = ev.target.files[0].name
    if (filename.length > 25) { filename = filename.substring(0, 24) + '...' }
    $('#lemfileselectbttnlabel').html(filename)
  })

  $('#consfileselect').change(function (ev) {
    let filename = ev.target.files[0].name
    if (filename.length > 25) { filename = filename.substring(0, 24) + '...' }
    $('#consfileselectbttnlabel').html(filename)
  })

  $('#scfileselect').change(function (ev) {
    let filename = ev.target.files[0].name
    if (filename.length > 25) { filename = filename.substring(0, 24) + '...' }
    $('#scfileselectbttnlabel').html(filename)
  })

  $('.bttnfilelabels').click(function () {
    // swfileselect, lemfileselect, consfileselect, scfileselect
    const filetype = $(this).attr('id').replace('bttnlabel', '')
    const usingCache = $('#usecache' + filetype).attr('disabled') != 'disabled'

    if ((usingCache) || ($(this).attr('id') != '')) {
      // $(this).siblings('.scrub-upload').attr('value', '');
      // Next two lines clear the file input; it's hard to find a cross-browser solution
      $('#' + filetype).val('')
      $('#' + filetype).replaceWith($('#' + filetype).clone(true))
      $('#usecache' + filetype).attr('disabled', 'disabled')
      $(this).text('')
    }

    // Do Ajax
    $.ajax({
      type: 'POST',
      url: '/removeUploadLabels',
      data: $(this).text().toString(),
      contentType: 'text/plain',
      headers: {'option': filetype + '[]'},
      beforeSend: function () {
        // alert('Sending...');
      },
      success: function (response) {
        // console.log(response);
      },
      error: function (jqXHR, textStatus, errorThrown) {
        console.log('Error: ' + errorThrown)
      }
    })
  })

  $('#whitespacebox').click(function () {
    if ($(this).is(':checked')) {
      $('#whitespace').removeClass('hidden')
    } else {
      $('#whitespace').addClass('hidden')
    }
  })
  $('#entityrules').change(function () {
    console.log($('#entityrules')[0].value)
    if ($('#entityrules')[0].value == 'MUFI-3' || $('#entityrules')[0].value == 'MUFI-4') {
      document.getElementById('MUFI-warning').style.display = 'inline-block'
      $('head').append('<link href=\'../static/lib/junicode/Junicode.woff\' rel=\'stylesheet\' type=\'text/css\'>')
      $('.filecontents').addClass('Junicode')
    } else {
      $('.filecontents').removeClass('Junicode')
      document.getElementById('MUFI-warning').style.display = 'none'
    }
  })

  $('#tagbox').click(function () {
    if ($(this).is(':checked')) {
      $('#tag').removeClass('hidden')
    } else {
      $('#tag').addClass('hidden')
    }
  })

  $(document).on('click', '#set-tags-button', function (event) {
    if ($('#allTags')) {
      let allTags = ($('#allTags')[0].value)
      allTags = JSON.stringify(allTags)
      $.ajax({
        type: 'POST',
        url: '/setAllTagsTable',
        data: allTags,
        'contentType': 'application/json; charset=utf-8',
        'dataType': 'json',
        beforeSend: function () {
          $('
', {
            id: 'xmlModalStatus',
            style: 'width:100px;margin:50px auto;z-index:1000;'
          }).appendTo('#xmlModalBody')
          $('#xmlModalStatus').append('')
        },
        success: function (response) {
          const selection = $('#allTags option:selected').val()
          $('#tagTable').empty().remove()
          const t = '

'
          $('#xmlModalBody').append(t)
          let select = '<select id="allTags" style="margin-top:3px;margin-right:5px;">'
          select += '<option value="remove-tag,allTags">Remove Tag Only</option>'
          select += '<option value="remove-element,allTags">Remove Element and All Its Contents</option>'
          select += '<option value="replace-element,allTags">Replace Element and Its Contents with Attribute Value</option>'
          select += '<option value="leave-alone,allTags">Leave Tag Alone</option>'
          select += '</select>'
          select += '<button id="set-tags-button" type="button" class="btn btn-primary"">Set All</button>'
          $('#tagTable').append('

Element	Action	' + select + '

')
          $('#tagTable').append('

')
          $('#tagTable tbody').append(response)
          $('#xmlModalStatus').remove()
          $('#allTags option[value=\'' + selection + '\']').prop('selected', true)
        },
        error: function (jqXHR, textStatus, errorThrown) {
          console.log('Error: ' + errorThrown)
        }
      })
    }
  })

  $('#punctbox').mousedown(function () {
    const timeToToggle = 300

    if ($('#aposhyph')[0].style.cssText == 'display: none;') {
      $('#aposhyph').fadeIn(timeToToggle)
    } else {
      $('#aposhyph').fadeOut(timeToToggle)
      $('#aposhyph')[0].style.cssText == 'display: none;'
    }
  })

  $('#xml-modal').on('show.bs.modal', function (e) {
    $.ajax({
      type: 'POST',
      url: '/getTagsTable',
      contentType: 'json',
      beforeSend: function () {
        $('
', {
          id: 'xmlModalStatus',
          style: 'width:100px;margin:50px auto;z-index:1000;'
        }).appendTo('#xmlModalBody')

        $('#xmlModalStatus').append('')
      },
      success: function (response) {
        j = JSON.parse(response)
        const t = '

'
        $('#xmlModalBody').append(t)
        let select = '<select id="allTags" style="margin-top:3px;margin-right:5px;">'
        select += '<option value="remove-tag,allTags">Remove Tag Only</option>'
        select += '<option value="remove-element,allTags">Remove Element and All Its Contents</option>'
        select += '<option value="replace-element,allTags">Replace Element and Its Contents with Attribute Value</option>'
        select += '<option value="leave-alone,allTags">Leave Tag Alone</option>'
        select += '</select>'
        select += '<button id="set-tags-button" type="button" class="btn btn-primary"">Set All</button>'
        $('#tagTable').append('

Element	Action	' + select + '

')
        $('#tagTable').append('

')
        $('#tagTable tbody').append(j['menu'])
        $('#xmlModalStatus').remove()
        if (j['selected-options'] != 'multiple') {
          $('#allTags option[value=\'' + j['selected-options'] + '\']').prop('selected', true)
        }
      },
      error: function (jqXHR, textStatus, errorThrown) {
        console.log('Error: ' + errorThrown)
      }
    })
  })

  $('#xml-modal').on('hidden.bs.modal', function () {
    $('#tagTable').empty().remove()
  })
})

function downloadScrubbing () {
  // Unfortunately, you can't trigger a download with an ajax request; calling a
  // Flask route seems to be the easiest method.
  window.location = '/downloadScrubbing'
}

function doScrubbing (action) {
  if ($('#num_active_files').val() == '0') {
    $('#error-modal').modal()
    return
  }

  $('#status-prepare').css({'visibility': 'visible'})

  $('#formAction').val(action)
  const formData = new FormData($('form')[0])

  $.ajax({
    url: '/doScrubbing',
    type: 'POST',
    processData: false, // important
    contentType: false, // important
    data: formData,
    error: function (jqXHR, textStatus, errorThrown) {
      $('#error-modal-message').html('Lexos could not apply the scrubbing actions.')
      $('#error-modal').modal()
      console.log('bad: ' + textStatus + ': ' + errorThrown)
    }
  }).done(function (response) {
    response = JSON.parse(response)
    $('#preview-body').empty()
    $.each(response['data'], function (i, item) {
      // const fileID = $(this)[0]
      const filename = $(this)[1]
      // const fileLabel = $(this)[2]
      const fileContents = $(this)[3]
      const fieldset = $('<fieldset></fieldset>')
      // CSS truncates the document name
      fieldset.append('<legend class="has-tooltip" style="color:#999; width:90%;margin: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">' + filename + '</legend>')
      fieldset.append('
' + fileContents + '
') // Keep this with no whitespace!
      $('#preview-body').append(fieldset)
      $('#status-prepare').css({'visibility': 'hidden'})
    })
  })
}
<commit_msg>made chevron rotate a separate function<commit_after>$(function () {
  if ($('input[name=\'haveGutenberg\']')) {
    $('#gutenberg-modal').modal()
  }
  $('#actions').addClass('actions-scrub')

  $('.has-chevron').click(rotateChevron())

  // display additional options on load
  const advancedOptions = $('#advanced-title')
  advancedOptions.find('.icon-arrow-right').addClass('showing')
  advancedOptions.siblings('.expansion').slideToggle(0)

  $('#swfileselect').change(function (ev) {
    let filename = ev.target.files[0].name
    if (filename.length > 25) { filename = filename.substring(0, 24) + '...' }
    $('#swfileselectbttnlabel').html(filename)
  })

  $('#lemfileselect').change(function (ev) {
    let filename = ev.target.files[0].name
    if (filename.length > 25) { filename = filename.substring(0, 24) + '...' }
    $('#lemfileselectbttnlabel').html(filename)
  })

  $('#consfileselect').change(function (ev) {
    let filename = ev.target.files[0].name
    if (filename.length > 25) { filename = filename.substring(0, 24) + '...' }
    $('#consfileselectbttnlabel').html(filename)
  })

  $('#scfileselect').change(function (ev) {
    let filename = ev.target.files[0].name
    if (filename.length > 25) { filename = filename.substring(0, 24) + '...' }
    $('#scfileselectbttnlabel').html(filename)
  })

  $('.bttnfilelabels').click(function () {
    // swfileselect, lemfileselect, consfileselect, scfileselect
    const filetype = $(this).attr('id').replace('bttnlabel', '')
    const usingCache = $('#usecache' + filetype).attr('disabled') != 'disabled'

    if ((usingCache) || ($(this).attr('id') != '')) {
      // $(this).siblings('.scrub-upload').attr('value', '');
      // Next two lines clear the file input; it's hard to find a cross-browser solution
      $('#' + filetype).val('')
      $('#' + filetype).replaceWith($('#' + filetype).clone(true))
      $('#usecache' + filetype).attr('disabled', 'disabled')
      $(this).text('')
    }

    // Do Ajax
    $.ajax({
      type: 'POST',
      url: '/removeUploadLabels',
      data: $(this).text().toString(),
      contentType: 'text/plain',
      headers: {'option': filetype + '[]'},
      beforeSend: function () {
        // alert('Sending...');
      },
      success: function (response) {
        // console.log(response);
      },
      error: function (jqXHR, textStatus, errorThrown) {
        console.log('Error: ' + errorThrown)
      }
    })
  })

  $('#whitespacebox').click(function () {
    if ($(this).is(':checked')) {
      $('#whitespace').removeClass('hidden')
    } else {
      $('#whitespace').addClass('hidden')
    }
  })
  $('#entityrules').change(function () {
    console.log($('#entityrules')[0].value)
    if ($('#entityrules')[0].value == 'MUFI-3' || $('#entityrules')[0].value == 'MUFI-4') {
      document.getElementById('MUFI-warning').style.display = 'inline-block'
      $('head').append('<link href=\'../static/lib/junicode/Junicode.woff\' rel=\'stylesheet\' type=\'text/css\'>')
      $('.filecontents').addClass('Junicode')
    } else {
      $('.filecontents').removeClass('Junicode')
      document.getElementById('MUFI-warning').style.display = 'none'
    }
  })

  $('#tagbox').click(function () {
    if ($(this).is(':checked')) {
      $('#tag').removeClass('hidden')
    } else {
      $('#tag').addClass('hidden')
    }
  })

  $(document).on('click', '#set-tags-button', function (event) {
    if ($('#allTags')) {
      let allTags = ($('#allTags')[0].value)
      allTags = JSON.stringify(allTags)
      $.ajax({
        type: 'POST',
        url: '/setAllTagsTable',
        data: allTags,
        'contentType': 'application/json; charset=utf-8',
        'dataType': 'json',
        beforeSend: function () {
          $('
', {
            id: 'xmlModalStatus',
            style: 'width:100px;margin:50px auto;z-index:1000;'
          }).appendTo('#xmlModalBody')
          $('#xmlModalStatus').append('')
        },
        success: function (response) {
          const selection = $('#allTags option:selected').val()
          $('#tagTable').empty().remove()
          const t = '

'
          $('#xmlModalBody').append(t)
          let select = '<select id="allTags" style="margin-top:3px;margin-right:5px;">'
          select += '<option value="remove-tag,allTags">Remove Tag Only</option>'
          select += '<option value="remove-element,allTags">Remove Element and All Its Contents</option>'
          select += '<option value="replace-element,allTags">Replace Element and Its Contents with Attribute Value</option>'
          select += '<option value="leave-alone,allTags">Leave Tag Alone</option>'
          select += '</select>'
          select += '<button id="set-tags-button" type="button" class="btn btn-primary"">Set All</button>'
          $('#tagTable').append('

Element	Action	' + select + '

')
          $('#tagTable').append('

')
          $('#tagTable tbody').append(response)
          $('#xmlModalStatus').remove()
          $('#allTags option[value=\'' + selection + '\']').prop('selected', true)
        },
        error: function (jqXHR, textStatus, errorThrown) {
          console.log('Error: ' + errorThrown)
        }
      })
    }
  })

  $('#punctbox').mousedown(function () {
    const timeToToggle = 300

    if ($('#aposhyph')[0].style.cssText == 'display: none;') {
      $('#aposhyph').fadeIn(timeToToggle)
    } else {
      $('#aposhyph').fadeOut(timeToToggle)
      $('#aposhyph')[0].style.cssText == 'display: none;'
    }
  })

  $('#xml-modal').on('show.bs.modal', function (e) {
    $.ajax({
      type: 'POST',
      url: '/getTagsTable',
      contentType: 'json',
      beforeSend: function () {
        $('
', {
          id: 'xmlModalStatus',
          style: 'width:100px;margin:50px auto;z-index:1000;'
        }).appendTo('#xmlModalBody')

        $('#xmlModalStatus').append('')
      },
      success: function (response) {
        j = JSON.parse(response)
        const t = '

'
        $('#xmlModalBody').append(t)
        let select = '<select id="allTags" style="margin-top:3px;margin-right:5px;">'
        select += '<option value="remove-tag,allTags">Remove Tag Only</option>'
        select += '<option value="remove-element,allTags">Remove Element and All Its Contents</option>'
        select += '<option value="replace-element,allTags">Replace Element and Its Contents with Attribute Value</option>'
        select += '<option value="leave-alone,allTags">Leave Tag Alone</option>'
        select += '</select>'
        select += '<button id="set-tags-button" type="button" class="btn btn-primary"">Set All</button>'
        $('#tagTable').append('

Element	Action	' + select + '

')
        $('#tagTable').append('

')
        $('#tagTable tbody').append(j['menu'])
        $('#xmlModalStatus').remove()
        if (j['selected-options'] != 'multiple') {
          $('#allTags option[value=\'' + j['selected-options'] + '\']').prop('selected', true)
        }
      },
      error: function (jqXHR, textStatus, errorThrown) {
        console.log('Error: ' + errorThrown)
      }
    })
  })

  $('#xml-modal').on('hidden.bs.modal', function () {
    $('#tagTable').empty().remove()
  })
})

function downloadScrubbing () {
  // Unfortunately, you can't trigger a download with an ajax request; calling a
  // Flask route seems to be the easiest method.
  window.location = '/downloadScrubbing'
}

function doScrubbing (action) {
  if ($('#num_active_files').val() == '0') {
    $('#error-modal').modal()
    return
  }

  $('#status-prepare').css({'visibility': 'visible'})

  $('#formAction').val(action)
  const formData = new FormData($('form')[0])

  $.ajax({
    url: '/doScrubbing',
    type: 'POST',
    processData: false, // important
    contentType: false, // important
    data: formData,
    error: function (jqXHR, textStatus, errorThrown) {
      $('#error-modal-message').html('Lexos could not apply the scrubbing actions.')
      $('#error-modal').modal()
      console.log('bad: ' + textStatus + ': ' + errorThrown)
    }
  }).done(function (response) {
    response = JSON.parse(response)
    $('#preview-body').empty()
    $.each(response['data'], function (i, item) {
      // const fileID = $(this)[0]
      const filename = $(this)[1]
      // const fileLabel = $(this)[2]
      const fileContents = $(this)[3]
      const fieldset = $('<fieldset></fieldset>')
      // CSS truncates the document name
      fieldset.append('<legend class="has-tooltip" style="color:#999; width:90%;margin: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">' + filename + '</legend>')
      fieldset.append('
' + fileContents + '
') // Keep this with no whitespace!
      $('#preview-body').append(fieldset)
      $('#status-prepare').css({'visibility': 'hidden'})
    })
  })
}

function rotateChevron () {
  $(this).find('span').toggleClass('down')
  $(this).next().collapse('toggle')
}
<|endoftext|>


<commit_before>import path from 'path';
import UglifyJSPlugin from 'uglifyjs-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';

const config = [
  {
    name: 'react-redux-spinner',
    entry: [
      './src/nprogress.css',
      './src/index.js'
    ],
    externals: {
      react: 'react',
      'react-dom': 'ReactDOM'
    },
    output: {
      path: path.join(__dirname, '..', 'dist'),
      filename: 'react-redux-spinner.js',
      library: 'react-redux-spinner',
      libraryTarget: 'umd',
      publicPath: '/dist/'
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          include: [ __dirname ],
          loader: 'babel-loader'
        },
        {
          test: /\.css$/,
          use: ExtractTextPlugin.extract({
            fallback: 'style-loader',
            use: {
              loader: 'css-loader',
              options: {
                minimize: true
              }
            }
          })
        }
      ]
    },
    plugins: [
      new UglifyJSPlugin(),
      new ExtractTextPlugin('react-redux-spinner.css')
    ]
  }
];

module.exports = config;
<commit_msg>Add prop-types as a peer dependency<commit_after>import path from 'path';
import UglifyJSPlugin from 'uglifyjs-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';

const config = [
  {
    name: 'react-redux-spinner',
    entry: [
      './src/nprogress.css',
      './src/index.js'
    ],
    externals: {
      react: 'react',
      'react-dom': 'ReactDOM',
      'prop-types': 'PropTypes'
    },
    output: {
      path: path.join(__dirname, '..', 'dist'),
      filename: 'react-redux-spinner.js',
      library: 'react-redux-spinner',
      libraryTarget: 'umd',
      publicPath: '/dist/'
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          include: [ __dirname ],
          loader: 'babel-loader'
        },
        {
          test: /\.css$/,
          use: ExtractTextPlugin.extract({
            fallback: 'style-loader',
            use: {
              loader: 'css-loader',
              options: {
                minimize: true
              }
            }
          })
        }
      ]
    },
    plugins: [
      new UglifyJSPlugin(),
      new ExtractTextPlugin('react-redux-spinner.css')
    ]
  }
];

module.exports = config;
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { TextInput, View, StyleSheet } from 'react-native';

import { APP_FONT_FAMILY } from '../globalStyles/index';
import { CircleButton } from './CircleButton';
import { AddIcon, MinusIcon } from './icons';

/**
 * Input component which will update a numerical value. The `value` passed will be
 * cast, so should return a valid number when cast to `Number`.
 *
 * Has increment and decrement buttons which increase the passed value by one.
 * The buttons can also be held, increase by one every 50ms. The increment amount
 * then grows exponentially every 500ms, reseting to one after releasing.
 *
 * Component uses state to update itself, and a ref for storing the current value as
 * this is far more responsive than updating the redux store and re-rendering every 50ms.
 *
 * @prop {String|Number} value          The current numerical value.
 * @prop {Func}          onChangeText   Callback for updating the value.
 * @prop {Number}        lowerLimit     The lower limit of the numerical range.
 * @prop {Number}        upperLimit     The upper limit of the numerical range.
 * @prop {Object}        textInputStyle Style object for the underlying TextInput.
 *
 */
export const StepperInput = ({ value, onChangeText, lowerLimit, upperLimit, textInputStyle }) => {
  const [, setCurrentValueState] = React.useState(Number(value));
  const currentValue = React.useRef(Number(value));
  const currentAdjustmentAmount = React.useRef(1);
  const valueAdjustmentInterval = React.useRef();
  const adjustmentIncreaseInterval = React.useRef();

  const parseNumber = number => Math.max(Math.min(number, upperLimit), lowerLimit);

  const onUpdate = newValue => {
    const updateValue = parseNumber(newValue);
    currentValue.current = updateValue;
    setCurrentValueState(updateValue);
    onChangeText(updateValue);
  };

  const decrementValue = () => {
    const updateValue = parseNumber(currentValue.current - currentAdjustmentAmount.current);
    currentValue.current = updateValue;
    setCurrentValueState(updateValue);
    onChangeText(updateValue);
  };

  const incrementValue = () => {
    const updateValue = parseNumber(currentValue.current + currentAdjustmentAmount.current);
    currentValue.current = updateValue;
    setCurrentValueState(updateValue);
    onChangeText(updateValue);
  };

  const inreaseIncrement = () => {
    currentAdjustmentAmount.current *= 2;
  };

  const onStartingLongPress = isIncrement => {
    if (!valueAdjustmentInterval.current) {
      valueAdjustmentInterval.current = setInterval(
        isIncrement ? incrementValue : decrementValue,
        50
      );
    }

    if (!adjustmentIncreaseInterval.current) {
      adjustmentIncreaseInterval.current = setInterval(inreaseIncrement, 500);
    }
  };

  const onEndLongPress = () => {
    clearInterval(valueAdjustmentInterval.current);
    clearInterval(adjustmentIncreaseInterval.current);

    valueAdjustmentInterval.current = null;
    adjustmentIncreaseInterval.current = null;
    currentAdjustmentAmount.current = 1;
  };

  const onStartDecrementPress = () => onStartingLongPress(false);
  const onStartIncrementPress = () => onStartingLongPress(true);

  return (
    <View style={localStyles.row}>
      <CircleButton
        IconComponent={MinusIcon}
        onPressIn={onStartDecrementPress}
        onPressOut={onEndLongPress}
      />
      <TextInput
        onChangeText={onUpdate}
        value={String(currentValue.current)}
        style={textInputStyle}
      />
      <CircleButton
        IconComponent={AddIcon}
        onPressIn={onStartIncrementPress}
        onPressOut={onEndLongPress}
      />
    </View>
  );
};

const localStyles = StyleSheet.create({
  row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
  textInput: {
    marginHorizontal: 25,
    width: 100,
    textAlign: 'center',
    fontSize: 24,

    fontFamily: APP_FONT_FAMILY,
  },
});

StepperInput.defaultProps = {
  lowerLimit: 0,
  upperLimit: 99999,
  textInputStyle: localStyles.textInput,
};

StepperInput.propTypes = {
  lowerLimit: PropTypes.number,
  upperLimit: PropTypes.number,
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  onChangeText: PropTypes.func.isRequired,
  textInputStyle: PropTypes.object,
};
<commit_msg>Add better increment and decrement functions<commit_after>/* eslint-disable react/forbid-prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { TextInput, View, StyleSheet } from 'react-native';

import { APP_FONT_FAMILY } from '../globalStyles/index';
import { CircleButton } from './CircleButton';
import { AddIcon, MinusIcon } from './icons';

/**
 * Input component which will update a numerical value. The `value` passed will be
 * cast, so should return a valid number when cast to `Number`.
 *
 * Has increment and decrement buttons which increase the passed value by one.
 * The buttons can also be held, increase by one every 50ms. The increment amount
 * then grows exponentially every 500ms, reseting to one after releasing.
 *
 * Component uses state to update itself, and a ref for storing the current value as
 * this is far more responsive than updating the redux store and re-rendering every 50ms.
 *
 * @prop {String|Number} value          The current numerical value.
 * @prop {Func}          onChangeText   Callback for updating the value.
 * @prop {Number}        lowerLimit     The lower limit of the numerical range.
 * @prop {Number}        upperLimit     The upper limit of the numerical range.
 * @prop {Object}        textInputStyle Style object for the underlying TextInput.
 *
 */
export const StepperInput = ({ value, onChangeText, lowerLimit, upperLimit, textInputStyle }) => {
  const [, setCurrentValueState] = React.useState(Number(value));
  const currentValue = React.useRef(Number(value));
  const currentAdjustmentAmount = React.useRef(1);
  const valueAdjustmentInterval = React.useRef();
  const adjustmentIncreaseInterval = React.useRef();

  const parseNumber = number => Math.max(Math.min(number, upperLimit), lowerLimit);

  const onUpdate = newValue => {
    const updateValue = parseNumber(newValue);
    currentValue.current = updateValue;
    setCurrentValueState(updateValue);
    onChangeText(updateValue);
  };

  const decrementValue = () => onUpdate(currentValue.current - currentAdjustmentAmount.current);
  const incrementValue = () => onUpdate(currentValue.current + currentAdjustmentAmount.current);

  const inreaseIncrement = () => {
    currentAdjustmentAmount.current *= 2;
  };

  const onStartingLongPress = isIncrement => {
    if (!valueAdjustmentInterval.current) {
      valueAdjustmentInterval.current = setInterval(
        isIncrement ? incrementValue : decrementValue,
        50
      );
    }

    if (!adjustmentIncreaseInterval.current) {
      adjustmentIncreaseInterval.current = setInterval(inreaseIncrement, 500);
    }
  };

  const onEndLongPress = () => {
    clearInterval(valueAdjustmentInterval.current);
    clearInterval(adjustmentIncreaseInterval.current);

    valueAdjustmentInterval.current = null;
    adjustmentIncreaseInterval.current = null;
    currentAdjustmentAmount.current = 1;
  };

  const onStartDecrementPress = () => onStartingLongPress(false);
  const onStartIncrementPress = () => onStartingLongPress(true);

  return (
    <View style={localStyles.row}>
      <CircleButton
        IconComponent={MinusIcon}
        onPressIn={onStartDecrementPress}
        onPressOut={onEndLongPress}
      />
      <TextInput
        onChangeText={onUpdate}
        value={String(currentValue.current)}
        style={textInputStyle}
      />
      <CircleButton
        IconComponent={AddIcon}
        onPressIn={onStartIncrementPress}
        onPressOut={onEndLongPress}
      />
    </View>
  );
};

const localStyles = StyleSheet.create({
  row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
  textInput: {
    marginHorizontal: 25,
    width: 100,
    textAlign: 'center',
    fontSize: 24,

    fontFamily: APP_FONT_FAMILY,
  },
});

StepperInput.defaultProps = {
  lowerLimit: 0,
  upperLimit: 99999,
  textInputStyle: localStyles.textInput,
};

StepperInput.propTypes = {
  lowerLimit: PropTypes.number,
  upperLimit: PropTypes.number,
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  onChangeText: PropTypes.func.isRequired,
  textInputStyle: PropTypes.object,
};
<|endoftext|>


<commit_before>/** @jsx React.DOM */
(function(Perseus) {

var answerTypes = {
    number: {name: "Numbers",
             forms: "integer, decimal, proper, improper, mixed"},
    decimal: {name: "Decimals", forms: "decimal"},
    integer: {name: "Integers", forms: "integer"},
    rational: {name: "Rationals", forms: "integer, proper, improper, mixed"},
    improper: {name: "Improper numbers", forms: "integer, proper, improper"},
    mixed: {name: "Mixed numbers", forms: "integer, proper, mixed"},
    percent: {name: "Percents",
              forms: "decimal, proper, improper, mixed, percent"},
    pi: {name: "Numbers with pi", forms: "pi"}
};

var formExamples = {
    "integer": function(options) { return $._("an integer, like $6$"); },
    "proper": function(options) {
        if (options.simplify === "optional") {
            return $._("a *simplified proper* fraction, like $3/5$");
        } else {
            return $._("a *proper* fraction, like $1/2$ or $6/10$");
        }
    },
    "improper": function(options) {
        if (options.simplify === "optional") {
            return $._("a *simplified improper* fraction, like $7/4$");
        } else {
            return $._("an *improper* fraction, like $10/7$ or $14/8$");
        }
    },
    "mixed": function(options) {
        return $._("a mixed number, like $1\\ 3/4$");
    },
    "decimal": function(options) {
        return $._("an *exact* decimal, like $0.75$");
    },
    "percent": function(options) {
        return $._("a percent, like $12.34\\%$");
    },
    "pi": function(options) {
        return $._("a multiple of pi, like $12\\ \\text{pi}$ or " +
                "$2/3\\ \\text{pi}$");
    }
};

var InputNumber = React.createClass({
    render: function() {
        var type = window.Modernizr && Modernizr.touch ? "number" : "text";
        return <input ref="input" type={type} className=
            {"perseus-input-size-" + (this.props.size || "normal")} />;
    },

    focus: function() {
        this.refs.input.getDOMNode().focus();
        return true;
    },

    toJSON: function(skipValidation) {
        return {
            value: this.refs.input.getDOMNode().value
        };
    },

    simpleValidate: function(rubric) {
        return InputNumber.validate(this.toJSON(), rubric);
    }
});

_.extend(InputNumber, {
    validate: function(state, rubric) {
        var val = Khan.answerTypes.number.createValidatorFunctional(
            rubric.value, {
                simplify: rubric.simplify,
                inexact: rubric.inexact,
                maxError: rubric.maxError,
                forms: answerTypes[rubric.answerType].forms
            });

        var result = val(state.value);

        if (state.value === "" || result === "") {
            return {
                type: "invalid",
                message: null
            };
        } else if (typeof result === "string") {
            return {
                type: "points",
                earned: 0,
                total: 1,
                message: result
            };
        } else {
            return {
                type: "points",
                earned: result === true ? 1 : 0,
                total: 1,
                message: null
            };
        }
    },

    examples: function(options) {
        var forms = answerTypes[options.answerType].forms.split(/\s*,\s*/);

        var examples = _.map(forms, function(form) {
            return formExamples[form](options);
        });

        return examples;
    }
});

var InputNumberEditor = React.createClass({
    getDefaultProps: function() {
        return {
            value: "0",
            simplify: "required",
            size: "normal",
            inexact: false,
            maxError: 0.1,
            answerType: "number"
        };
    },

    componentDidMount: function() {
        // TODO(alpert): How to do this at initialization instead of here?
        this.refs.size.getDOMNode().value = this.props.size;
    },

    render: function() {
        var answerTypeOptions = _.map(answerTypes, function(v, k) {
            return <option selected={k === this.props.answerType}
                    value={k}>{v.name}</option>;
        }, this);

        return

                Correct answer:
                <input type="text" ref="input" value={this.props.value}
                    onBlur={function(e) {
                        var ans = "" + (Perseus.Util.firstNumericalParse(
                                e.target.value) || 0);
                        e.target.value = ans;
                        this.props.onChange({value: ans});
                    }.bind(this)} />

                <input type="checkbox"
                    checked={this.props.simplify === "required"}
                    onChange={function(e) {
                        this.props.onChange({simplify: e.target.checked ?
                                "required" : "optional"});
                    }.bind(this)} />
                Require simplification

                <input type="checkbox"
                    checked={this.props.inexact}
                    onChange={function(e) {
                        this.props.onChange({inexact: e.target.checked});
                    }.bind(this)} />
                Allow inexact answers

            <input /* TODO(emily): don't use a hidden checkbox for alignment */
                type="checkbox" style={{visibility: "hidden"}} />
            Max error:
            <input type="text" disabled={!this.props.inexact}
                value={this.props.maxError}
                onBlur={function(e) {
                    var ans = "" + (Perseus.Util.firstNumericalParse(
                            e.target.value) || 0);
                    e.target.value = ans;
                    this.props.onChange({maxError: ans});
                }.bind(this)} />

            Answer type:
            <select onChange={function(e) {
                    this.props.onChange({answerType: e.target.value});
                }.bind(this)}>
                {answerTypeOptions}
            </select>

                Width
                <select ref="size"
                        onChange={function(e) {
                            this.props.onChange({size: e.target.value});
                        }.bind(this)}>
                    <option value="normal">Normal (80px)</option>
                    <option value="small">Small (40px)</option>
                </select>

;
    },

    focus: function() {
        this.refs.input.getDOMNode().focus();
        return true;
    },

    toJSON: function() {
        return _.pick(this.props,
                "value", "simplify", "size", "inexact", "maxError",
                "answerType");
    }
});

Perseus.Widgets.register("input-number", InputNumber);
Perseus.Widgets.register("input-number-editor", InputNumberEditor);

})(Perseus);
<commit_msg>Don't break if there isn't an answer type set<commit_after>/** @jsx React.DOM */
(function(Perseus) {

var answerTypes = {
    number: {name: "Numbers",
             forms: "integer, decimal, proper, improper, mixed"},
    decimal: {name: "Decimals", forms: "decimal"},
    integer: {name: "Integers", forms: "integer"},
    rational: {name: "Rationals", forms: "integer, proper, improper, mixed"},
    improper: {name: "Improper numbers", forms: "integer, proper, improper"},
    mixed: {name: "Mixed numbers", forms: "integer, proper, mixed"},
    percent: {name: "Percents",
              forms: "decimal, proper, improper, mixed, percent"},
    pi: {name: "Numbers with pi", forms: "pi"}
};

var formExamples = {
    "integer": function(options) { return $._("an integer, like $6$"); },
    "proper": function(options) {
        if (options.simplify === "optional") {
            return $._("a *simplified proper* fraction, like $3/5$");
        } else {
            return $._("a *proper* fraction, like $1/2$ or $6/10$");
        }
    },
    "improper": function(options) {
        if (options.simplify === "optional") {
            return $._("a *simplified improper* fraction, like $7/4$");
        } else {
            return $._("an *improper* fraction, like $10/7$ or $14/8$");
        }
    },
    "mixed": function(options) {
        return $._("a mixed number, like $1\\ 3/4$");
    },
    "decimal": function(options) {
        return $._("an *exact* decimal, like $0.75$");
    },
    "percent": function(options) {
        return $._("a percent, like $12.34\\%$");
    },
    "pi": function(options) {
        return $._("a multiple of pi, like $12\\ \\text{pi}$ or " +
                "$2/3\\ \\text{pi}$");
    }
};

var InputNumber = React.createClass({
    render: function() {
        var type = window.Modernizr && Modernizr.touch ? "number" : "text";
        return <input ref="input" type={type} className=
            {"perseus-input-size-" + (this.props.size || "normal")} />;
    },

    focus: function() {
        this.refs.input.getDOMNode().focus();
        return true;
    },

    toJSON: function(skipValidation) {
        return {
            value: this.refs.input.getDOMNode().value
        };
    },

    simpleValidate: function(rubric) {
        return InputNumber.validate(this.toJSON(), rubric);
    }
});

_.extend(InputNumber, {
    validate: function(state, rubric) {
        if (rubric.answerType == null) {
            rubric.answerType = "number";
        }
        var val = Khan.answerTypes.number.createValidatorFunctional(
            rubric.value, {
                simplify: rubric.simplify,
                inexact: rubric.inexact,
                maxError: rubric.maxError,
                forms: answerTypes[rubric.answerType].forms
            });

        var result = val(state.value);

        if (state.value === "" || result === "") {
            return {
                type: "invalid",
                message: null
            };
        } else if (typeof result === "string") {
            return {
                type: "points",
                earned: 0,
                total: 1,
                message: result
            };
        } else {
            return {
                type: "points",
                earned: result === true ? 1 : 0,
                total: 1,
                message: null
            };
        }
    },

    examples: function(options) {
        if (options.answerType == null) {
            options.answerType = "number";
        }
        var forms = answerTypes[options.answerType].forms.split(/\s*,\s*/);

        var examples = _.map(forms, function(form) {
            return formExamples[form](options);
        });

        return examples;
    }
});

var InputNumberEditor = React.createClass({
    getDefaultProps: function() {
        return {
            value: "0",
            simplify: "required",
            size: "normal",
            inexact: false,
            maxError: 0.1,
            answerType: "number"
        };
    },

    componentDidMount: function() {
        // TODO(alpert): How to do this at initialization instead of here?
        this.refs.size.getDOMNode().value = this.props.size;
    },

    render: function() {
        var answerTypeOptions = _.map(answerTypes, function(v, k) {
            return <option selected={k === this.props.answerType}
                    value={k}>{v.name}</option>;
        }, this);

        return

                Correct answer:
                <input type="text" ref="input" value={this.props.value}
                    onBlur={function(e) {
                        var ans = "" + (Perseus.Util.firstNumericalParse(
                                e.target.value) || 0);
                        e.target.value = ans;
                        this.props.onChange({value: ans});
                    }.bind(this)} />

                <input type="checkbox"
                    checked={this.props.simplify === "required"}
                    onChange={function(e) {
                        this.props.onChange({simplify: e.target.checked ?
                                "required" : "optional"});
                    }.bind(this)} />
                Require simplification

                <input type="checkbox"
                    checked={this.props.inexact}
                    onChange={function(e) {
                        this.props.onChange({inexact: e.target.checked});
                    }.bind(this)} />
                Allow inexact answers

            <input /* TODO(emily): don't use a hidden checkbox for alignment */
                type="checkbox" style={{visibility: "hidden"}} />
            Max error:
            <input type="text" disabled={!this.props.inexact}
                value={this.props.maxError}
                onBlur={function(e) {
                    var ans = "" + (Perseus.Util.firstNumericalParse(
                            e.target.value) || 0);
                    e.target.value = ans;
                    this.props.onChange({maxError: ans});
                }.bind(this)} />

            Answer type:
            <select onChange={function(e) {
                    this.props.onChange({answerType: e.target.value});
                }.bind(this)}>
                {answerTypeOptions}
            </select>

                Width
                <select ref="size"
                        onChange={function(e) {
                            this.props.onChange({size: e.target.value});
                        }.bind(this)}>
                    <option value="normal">Normal (80px)</option>
                    <option value="small">Small (40px)</option>
                </select>

;
    },

    focus: function() {
        this.refs.input.getDOMNode().focus();
        return true;
    },

    toJSON: function() {
        return _.pick(this.props,
                "value", "simplify", "size", "inexact", "maxError",
                "answerType");
    }
});

Perseus.Widgets.register("input-number", InputNumber);
Perseus.Widgets.register("input-number-editor", InputNumberEditor);

})(Perseus);
<|endoftext|>


<commit_before>(function() {
	// All known comparison-operators.
	// Please note that operators have to get more specific towards the end of
	// the array, an operator may not be containd by another that comes before
	// it. Negative example: ['=#=', '#=']
	var operators = ['=', '!=', '>', '<', '>=', '<=', '%=', '*='];
	// All Processwire-FormBuilder input-fields as jQuery array.
	var $fields = $('.Inputfield');

	// Structure this script uses to connect the field-relationships.
	var struct = {};
	// Whether to animate changes in visibility. This is not an option! Do not
	// change this value unless you know what you are doing!
	var usesAnimation = false;

	// Evaluates two given values using a comparison-operator.
	//
	// operator: String that can be found in the `operators` array.
	// v0: String extracted from the live HTML input/textarea/select field.
	// v1: String specified in the Processwire backend.
	//
	// returns: bool: `true` if the field with the value `v0` should be visible.
	function evaluateOperatorWithValues(operator, v0, v1) {
		switch (operator) {
		case '=': // is equal to
			return v0 == v1;
		case '!=': // is not equal to
			return v0 != v1;
		case '>': // is greater than
			return parseFloat(v0) > parseFloat(v1);
		case '<': // is less than
			return parseFloat(v0) < parseFloat(v1);
		case '>=': // is greater than or equal to
			return parseFloat(v0) >= parseFloat(v1);
		case '<=': // is less than or equal to
			return parseFloat(v0) <= parseFloat(v1);
		case '%=': // contains substring
		case '*=': // contains substring
			return v0.indexOf(v1) !== -1;
		default:
			console.error('The operator “'+operator+'” is not implemented in evaluateOperatorWithValues().');
		}
	}

	// Returns a value based on an field.
	function comparableValueForField(field) {
		var object = field.input;
		var value;

		if (object.tagName === 'SELECT') {
			// <select>: value of selected option
			value = object.options[object.selectedIndex].value || '';
		} else if (object.tagName === 'INPUT' && object.type === 'checkbox') {
			// <input type=checkbox>: 1 for checked, 0 for unchecked
			value = object.checked ? '1' : '0';
		} else if (object.tagName === 'INPUT' && object.type === 'radio') {
			// <input type=radio>: value of the selected input or ''
			var checked = document.querySelector('input[name='+object.name+']:checked');
			value = checked ? checked.value : '';
		} else {
			// <input>, <textarea>, etc.: value of the object
			value = object.value || '';
		}

		return value;
	}

	// Applies all conditions of a field to that field. This results in either
	// showing or hiding the field. Conditions are combined using the AND
	// operator, so they all have to evaluate to `true` to show the field.
	//
	// field: A field taken from the `struct` object.
	function applyConditions(field) {
		var show = true;

		for (var i = 0; i < field.conditions.length; i++) {
			var condition = field.conditions[i];
			var ref = struct[condition.ref];

			show &= ref.visible;
			show &= evaluateOperatorWithValues(
				condition.operator,
				comparableValueForField(ref),
				condition.value
			);
		}

		var $wrapper = $(field.wrapper);

		if (show) {
			$wrapper[(usesAnimation) ? 'slideDown' : 'show']();
			$wrapper.removeClass('fieldwire-hidden');
			field.$inputs.prop('disabled', false);
			field.visible = true;
		} else {
			$wrapper[(usesAnimation) ? 'slideUp' : 'hide']();
			$wrapper.addClass('fieldwire-hidden');
			field.$inputs.prop('disabled', true);
			field.visible = false;
		}

		// Notify all dependent fields of this change.
		evalueateDependents(field.wrapper.id);
	}

	// Evaluates the conditions for all fields that depend on a given field.
	// This function intentionally does not take a field object directly so
	// it always uses the most up-to-date version of a field.
	//
	// fieldId: The unique identifier used in the `struct` object.
	function evalueateDependents(fieldId) {
		var field = struct[fieldId];

		for (var i = 0; i < field.dependents.length; i++) {
			applyConditions(struct[field.dependents[i]]);
		}
	}

	// Create the basic `struct` structure.
	$fields.each(function() {
		var wrapper = this;
		// collect “input-like” elements
		var $inputs = $(this).find('input,textarea,select');

		struct[this.id] = {
			// the HTML node of the field
			wrapper: wrapper,
			// the HTML node of the first “input-like” element
			input: $inputs.get(0),
			// all “input-like” elements as a jQuery collection
			$inputs: $inputs,
			// this list will be filled after this $.each loop
			dependents: [],
			// fields are – by default – visible
			visible: true,
			// the condition as a string, empty string for no condition
			showIf: this.dataset.showIf || ''
		};

		// listen for opportunities to reevaluate all dependent fields
		$inputs.on('input change blur', function() {
			usesAnimation = true;
			evalueateDependents(wrapper.id);
		});
	});

	// Add the dependencies to `struct` as well as the conditions.
	$fields.each(function() {
		// example: 'key=value,age>18'
		var showIf = struct[this.id].showIf;
		// example: ["key=value", "age>18"]
		var rawConditions = showIf.split(',');
		var conditions = [];

		for (var i = 0; i < rawConditions.length; i++) {
			var rawCondition = rawConditions[i];
			var operator;

			// find the matching operator
			for (var index in operators) {
				if (rawCondition.split(operators[index]).length === 2) {
					operator = operators[index];
				} else { continue; }
			}

			if (operator === undefined) {
				// no operator for this condition
				struct[this.id].conditions = [];
				continue;
			}

			// example: ["fieldname", "value"] or ["fieldname.count", "value"]
			var splits = rawCondition.split(operator);
			// example: ["fieldname"] or ["fieldname", "count"]
			var lhs = splits[0].split('.');
			var fieldname = lhs[0].trim();

			conditions.push({
				// id of the filed this condition depends on (v0)
				ref: 'wrap_Inputfield_'+fieldname,
				// the operator used to compare the value of `ref` to `value`
				operator: operator,
				// the comparison-value defined in the Processwire backend (v1)
				value: splits[1].replace(/^\s*'?(.*)'\s*$/, '$1'),
				modifier: lhs[1] || null
			});

			// add this field as dependent to `conditions.ref`
			struct['wrap_Inputfield_'+fieldname].dependents.push(this.id);
		}

		struct[this.id].conditions = conditions;

		applyConditions(struct[this.id]);
	});
}());<commit_msg>Closes #2<commit_after>(function() {
	// All known comparison-operators.
	// Please note that operators have to get more specific towards the end of
	// the array, an operator may not be containd by another that comes before
	// it. Negative example: ['=#=', '#=']
	var operators = ['=', '!=', '>', '<', '>=', '<=', '%=', '*='];
	// All Processwire-FormBuilder input-fields as jQuery array.
	var $fields = $('.Inputfield');

	// Structure this script uses to connect the field-relationships.
	var struct = {};
	// Whether to animate changes in visibility. This is not an option! Do not
	// change this value unless you know what you are doing!
	var usesAnimation = false;

	// Evaluates two given values using a comparison-operator.
	//
	// operator: String that can be found in the `operators` array.
	// v0: String extracted from the live HTML input/textarea/select field.
	// v1: String specified in the Processwire backend.
	//
	// returns: bool: `true` if the field with the value `v0` should be visible.
	function evaluateOperatorWithValues(operator, v0, v1) {
		switch (operator) {
		case '=': // is equal to
			return v0 == v1;
		case '!=': // is not equal to
			return v0 != v1;
		case '>': // is greater than
			return parseFloat(v0) > parseFloat(v1);
		case '<': // is less than
			return parseFloat(v0) < parseFloat(v1);
		case '>=': // is greater than or equal to
			return parseFloat(v0) >= parseFloat(v1);
		case '<=': // is less than or equal to
			return parseFloat(v0) <= parseFloat(v1);
		case '%=': // contains substring
		case '*=': // contains substring
			return v0.indexOf(v1) !== -1;
		default:
			console.error('The operator “'+operator+'” is not implemented in evaluateOperatorWithValues().');
		}
	}

	// Returns a value based on an field.
	function comparableValueForField(field) {
		var object = field.input;
		var value;

		if (object.tagName === 'SELECT') {
			// <select>: value of selected option
			value = object.options[object.selectedIndex].value || '';
		} else if (object.tagName === 'INPUT' && object.type === 'checkbox') {
			// <input type=checkbox>: 1 for checked, 0 for unchecked
			value = object.checked ? '1' : '0';
		} else if (object.tagName === 'INPUT' && object.type === 'radio') {
			// <input type=radio>: value of the selected input or ''
			var checked = document.querySelector('input[name='+object.name+']:checked');
			value = checked ? checked.value : '';
		} else {
			// <input>, <textarea>, etc.: value of the object
			value = object.value || '';
		}

		return value;
	}

	// Applies all conditions of a field to that field. This results in either
	// showing or hiding the field. Conditions are combined using the AND
	// operator, so they all have to evaluate to `true` to show the field.
	//
	// field: A field taken from the `struct` object.
	function applyConditions(field) {
		var show = true;

		for (var i = 0; i < field.conditions.length; i++) {
			var condition = field.conditions[i];
			var ref = struct[condition.ref];

			show &= ref.visible;
			show &= evaluateOperatorWithValues(
				condition.operator,
				comparableValueForField(ref),
				condition.value
			);
		}

		var $wrapper = $(field.wrapper);

		if (show) {
			$wrapper[(usesAnimation) ? 'slideDown' : 'show']();
			$wrapper.removeClass('fieldwire-hidden');
			field.$inputs.prop('disabled', false);
			field.visible = true;
		} else {
			$wrapper[(usesAnimation) ? 'slideUp' : 'hide']();
			$wrapper.addClass('fieldwire-hidden');
			field.$inputs.prop('disabled', true);
			field.visible = false;
		}

		// Notify all dependent fields of this change.
		evalueateDependents(field.wrapper.id);
	}

	// Evaluates the conditions for all fields that depend on a given field.
	// This function intentionally does not take a field object directly so
	// it always uses the most up-to-date version of a field.
	//
	// fieldId: The unique identifier used in the `struct` object.
	function evalueateDependents(fieldId) {
		var field = struct[fieldId];

		for (var i = 0; i < field.dependents.length; i++) {
			applyConditions(struct[field.dependents[i]]);
		}
	}

	// Create the basic `struct` structure.
	$fields.each(function() {
		var wrapper = this;
		// collect “input-like” elements
		var $inputs = $(this).find('input,textarea,select');

		struct[this.id] = {
			// the HTML node of the field
			wrapper: wrapper,
			// the HTML node of the first “input-like” element
			input: $inputs.get(0),
			// all “input-like” elements as a jQuery collection
			$inputs: $inputs,
			// this list will be filled after this $.each loop
			dependents: [],
			// fields are – by default – visible
			visible: true,
			// the condition as a string, empty string for no condition
			showIf: $(this).data('showIf') || ''
		};

		// listen for opportunities to reevaluate all dependent fields
		$inputs.on('input change blur', function() {
			usesAnimation = true;
			evalueateDependents(wrapper.id);
		});
	});

	// Add the dependencies to `struct` as well as the conditions.
	$fields.each(function() {
		// example: 'key=value,age>18'
		var showIf = struct[this.id].showIf;
		// example: ["key=value", "age>18"]
		var rawConditions = showIf.split(',');
		var conditions = [];

		for (var i = 0; i < rawConditions.length; i++) {
			var rawCondition = rawConditions[i];
			var operator;

			// find the matching operator
			for (var index in operators) {
				if (rawCondition.split(operators[index]).length === 2) {
					operator = operators[index];
				} else { continue; }
			}

			if (operator === undefined) {
				// no operator for this condition
				struct[this.id].conditions = [];
				continue;
			}

			// example: ["fieldname", "value"] or ["fieldname.count", "value"]
			var splits = rawCondition.split(operator);
			// example: ["fieldname"] or ["fieldname", "count"]
			var lhs = splits[0].split('.');
			var fieldname = lhs[0].trim();

			conditions.push({
				// id of the filed this condition depends on (v0)
				ref: 'wrap_Inputfield_'+fieldname,
				// the operator used to compare the value of `ref` to `value`
				operator: operator,
				// the comparison-value defined in the Processwire backend (v1)
				value: splits[1].replace(/^\s*'?(.*)'\s*$/, '$1'),
				modifier: lhs[1] || null
			});

			// add this field as dependent to `conditions.ref`
			struct['wrap_Inputfield_'+fieldname].dependents.push(this.id);
		}

		struct[this.id].conditions = conditions;

		applyConditions(struct[this.id]);
	});
}());<|endoftext|>


<commit_before>const ipc = require('electron').ipcRenderer

const mobbersEl = document.getElementById('mobbers')
const minutesEl = document.getElementById('minutes')
const addEl = document.getElementById('add')
const addMobberForm = document.getElementById('addMobberForm')
const fullscreenSecondsEl = document.getElementById('fullscreen-seconds')

function createMobberEl(mobber) {
  const el = document.createElement('div')
  el.classList.add('mobber')
  el.innerHTML = mobber.name

  const nameEl = document.createElement('div')
  nameEl.classList.add('name')
  el.appendChild(nameEl)

  const rmBtn = document.createElement('button')
  rmBtn.classList.add('btn', 'rmBtn')
  rmBtn.innerHTML = 'Remove'
  el.appendChild(rmBtn)

  rmBtn.addEventListener('click', _ => ipc.send('removeMobber', mobber))

  return el
}

ipc.on('configUpdated', (event, data) => {
  minutesEl.value = Math.ceil(data.secondsPerTurn / 60)
  mobbersEl.innerHTML = ''
  const frag = document.createDocumentFragment()
  data.mobbers.map(mobber => {
    frag.appendChild(createMobberEl(mobber))
  })
  mobbersEl.appendChild(frag)
  fullscreenSecondsEl.value = data.secondsUntilFullscreen
})

minutesEl.addEventListener('change', _ => {
  console.log(minutesEl.value)
  ipc.send('setSecondsPerTurn', minutesEl.value * 60)
})

addMobberForm.addEventListener('submit', event => {
  event.preventDefault()
  let value = addEl.value.trim()
  if (!value) {
    return
  }
  ipc.send('addMobber', { name: value })
  addEl.value = ''
})

fullscreenSecondsEl.addEventListener('change', _ => {
  ipc.send('setSecondsUntilFullscreen', fullscreenSecondsEl.value * 1)
})

ipc.send('configWindowReady')
<commit_msg>Remove console.log<commit_after>const ipc = require('electron').ipcRenderer

const mobbersEl = document.getElementById('mobbers')
const minutesEl = document.getElementById('minutes')
const addEl = document.getElementById('add')
const addMobberForm = document.getElementById('addMobberForm')
const fullscreenSecondsEl = document.getElementById('fullscreen-seconds')

function createMobberEl(mobber) {
  const el = document.createElement('div')
  el.classList.add('mobber')
  el.innerHTML = mobber.name

  const nameEl = document.createElement('div')
  nameEl.classList.add('name')
  el.appendChild(nameEl)

  const rmBtn = document.createElement('button')
  rmBtn.classList.add('btn', 'rmBtn')
  rmBtn.innerHTML = 'Remove'
  el.appendChild(rmBtn)

  rmBtn.addEventListener('click', _ => ipc.send('removeMobber', mobber))

  return el
}

ipc.on('configUpdated', (event, data) => {
  minutesEl.value = Math.ceil(data.secondsPerTurn / 60)
  mobbersEl.innerHTML = ''
  const frag = document.createDocumentFragment()
  data.mobbers.map(mobber => {
    frag.appendChild(createMobberEl(mobber))
  })
  mobbersEl.appendChild(frag)
  fullscreenSecondsEl.value = data.secondsUntilFullscreen
})

minutesEl.addEventListener('change', _ => {
  ipc.send('setSecondsPerTurn', minutesEl.value * 60)
})

addMobberForm.addEventListener('submit', event => {
  event.preventDefault()
  let value = addEl.value.trim()
  if (!value) {
    return
  }
  ipc.send('addMobber', { name: value })
  addEl.value = ''
})

fullscreenSecondsEl.addEventListener('change', _ => {
  ipc.send('setSecondsUntilFullscreen', fullscreenSecondsEl.value * 1)
})

ipc.send('configWindowReady')
<|endoftext|>


<commit_before>/*
  nr is the prefix for this function space.
*/
function ddg_spice_zanran(zanran_results) {
  var outer_html = function(element){
    var container_div = d.createElement("div");
    container_div.appendChild(element);
    return container_div.innerHTML;
  };

  var create_link = function(result){
    var link = d.createElement("a");
    link.href = result.preview_url;
    link.title = result.title;
    return link;
  };

  var shorten_title = function(title, max_len){
    if(title.length > max_len) {
      var words = title.split(/\s+/);
      title = "";
      while(words.length && title.length + 1 + words[0].length < max_len) {
        title = title + " " + words.shift();
      }
      title += "\u2026";
    }
    return title;
  }

  var create_img_for_primary_result = function(result){
    var img_link = create_link(result);
    var img = d.createElement('img');
    img.src = "/iu/?u=" + result.preview_image;
    img_link.appendChild(img);
    YAHOO.util.Dom.addClass(img_link, 'zanran_preview_image');
    return img_link;
  };

  var create_dom_for_primary_result = function(result){
    var p1 = d.createElement("div");
    var source = d.createElement('i');
    p1.appendChild(source);
    source.appendChild(d.createTextNode("Source: "));
    p1.appendChild(d.createTextNode(result.site_name));

    var p2 = d.createElement("div");
    p2.appendChild(d.createTextNode(result.title));

    var link2 = create_link(result);
    link2.appendChild(d.createTextNode("[Link]"));

    var div = d.createElement("div");
    div.appendChild(p1);
    div.appendChild(p2);
    div.appendChild(link2);
    div.appendChild(d.createElement("br"));

    return div;
  };

  var create_dom_for_secondary_result = function(result){
    var source = d.createElement('i');
    source.appendChild(d.createTextNode("Source: "));
    source.appendChild(d.createTextNode(result.site_name));

    var title = shorten_title(result.title, 140);

    var link = create_link(result);
    link.appendChild(d.createTextNode("[Link]"));

    var div = d.createElement("p");
    div.appendChild(d.createTextNode(title));
    div.appendChild(link);
    div.appendChild(d.createTextNode(" | "));
    div.appendChild(source);

    return div;
  };

  if(YAHOO.env.ua.ie && YAHOO.env.ua.ie < 7) {
    return;
  }
  if(!zanran_results || !zanran_results.results || !zanran_results.results.length) {
    return;
  }

  var results = zanran_results.results;

  var items = [{
    a: outer_html(create_dom_for_primary_result(results[0])),
    i: outer_html(create_img_for_primary_result(results[0])),
    u: zanran_results.more,
    h: "Data & Statistics from Zanran (" + DDG.get_query() + ")",
    force_big_header: 1,
    s: 'Zanran',
    f: 1
  }];

  if(results.length > 1) {
    var extra_results = d.createElement("div");
    for(var i=1; i<results.length && i<5; i++) {
      extra_results.appendChild(create_dom_for_secondary_result(results[i]));
    }
    items.push({
      a: outer_html(extra_results),
      t: "More results",
      f: 1,
      s: 'Zanran',
      u: zanran_results.more
    });
  }
  nra(items);
}
<commit_msg>Removed query from header bar; Period after source; space before [Link]<commit_after>/*
  nr is the prefix for this function space.
*/
function ddg_spice_zanran(zanran_results) {
  var outer_html = function(element){
    var container_div = d.createElement("div");
    container_div.appendChild(element);
    return container_div.innerHTML;
  };

  var create_link = function(result){
    var link = d.createElement("a");
    link.href = result.preview_url;
    link.title = result.title;
    return link;
  };

  var shorten_title = function(title, max_len){
    if(title.length > max_len) {
      var words = title.split(/\s+/);
      title = "";
      while(words.length && title.length + 1 + words[0].length < max_len) {
        title = title + " " + words.shift();
      }
      title += "\u2026";
    }
    return title;
  }

  var create_img_for_primary_result = function(result){
    var img_link = create_link(result);
    var img = d.createElement('img');
    img.src = "/iu/?u=" + result.preview_image;
    img_link.appendChild(img);
    YAHOO.util.Dom.addClass(img_link, 'zanran_preview_image');
    return img_link;
  };

  var create_dom_for_primary_result = function(result){
    var p1 = d.createElement("div");
    var source = d.createElement('i');
    p1.appendChild(source);
    source.appendChild(d.createTextNode("Source: "));
    p1.appendChild(d.createTextNode(result.site_name + "."));

    var p2 = d.createElement("div");
    p2.appendChild(d.createTextNode(result.title));

    var link2 = create_link(result);
    link2.appendChild(d.createTextNode("[Link]"));

    var div = d.createElement("div");
    div.appendChild(p1);
    div.appendChild(p2);
    div.appendChild(link2);
    div.appendChild(d.createElement("br"));

    return div;
  };

  var create_dom_for_secondary_result = function(result){
    var source = d.createElement('i');
    source.appendChild(d.createTextNode("Source: "));
    source.appendChild(d.createTextNode(result.site_name));
    source.appendChild(d.createTextNode("."));

    var title = shorten_title(result.title, 140);

    var link = create_link(result);
    link.appendChild(d.createTextNode("[Link]"));

    var div = d.createElement("p");
    div.appendChild(d.createTextNode(title + " "));
    div.appendChild(link);
    div.appendChild(d.createTextNode(" | "));
    div.appendChild(source);

    return div;
  };

  if(YAHOO.env.ua.ie && YAHOO.env.ua.ie < 7) {
    return;
  }
  if(!zanran_results || !zanran_results.results || !zanran_results.results.length) {
    return;
  }

  var results = zanran_results.results;

  var items = [{
    a: outer_html(create_dom_for_primary_result(results[0])),
    i: outer_html(create_img_for_primary_result(results[0])),
    u: zanran_results.more,
    h: "Data & Statistics from Zanran",
    force_big_header: 1,
    s: 'Zanran',
    f: 1
  }];

  if(results.length > 1) {
    var extra_results = d.createElement("div");
    for(var i=1; i<results.length && i<5; i++) {
      extra_results.appendChild(create_dom_for_secondary_result(results[i]));
    }
    items.push({
      a: outer_html(extra_results),
      t: "More results",
      f: 1,
      s: 'Zanran',
      u: zanran_results.more
    });
  }
  nra(items);
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const Promise = require('promise');

/**
 * Google Sheets Snippets
 */
class SheetsSnippets {
  /**
   * Creates Sheets Snippets with a Google API Services
   * @param {GoogleAuth[]} service Authenticated Google Drive and Sheets Services
   */
  constructor([driveService, sheetsService]) {
    this.driveService = driveService;
    this.sheetsService = sheetsService;
  }

  /**
   * Creates a new Google Spreadsheet.
   * @param {string} title The spreadsheet title
   * @return {Promise<string>} The spreadsheet ID
   */
  async create(title) {
    return new Promise((resolve, reject) => {
      // [START sheets_create]
      const resource = {
        properties: {
          title,
        },
      };
      this.sheetsService.spreadsheets.create({
        resource,
        fields: 'spreadsheetId',
      }, (err, spreadsheet) =>{
        if (err) {
          // Handle error.
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          console.log(`Spreadsheet ID: ${spreadsheet.spreadsheetId}`);
          // [START_EXCLUDE silent]
          resolve(spreadsheet.spreadsheetId);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_create]
    });
  }

  /**
   * Updates the Spreadsheet title. Finds and replaces a string in the sheets.
   * @param {string} spreadsheetId The Spreadsheet to update
   * @param {string} title The new Spreadsheet title
   * @param {string} find The text to find
   * @param {string} replacement The text to replace
   * @return {Promise<BatchUpdateResponse>} The batch update response.
   */
  async batchUpdate(spreadsheetId, title, find, replacement) {
    return new Promise((resolve, reject) => {
      // [START sheets_batch_update]
      let requests = [];
      // Change the spreadsheet's title.
      requests.push({
        updateSpreadsheetProperties: {
          properties: {
            title,
          },
          fields: 'title',
        },
      });
      // Find and replace text.
      requests.push({
        findReplace: {
          find,
          replacement,
          allSheets: true,
        },
      });
      // Add additional requests (operations) ...
      const batchUpdateRequest = {requests};
      this.sheetsService.spreadsheets.batchUpdate({
        spreadsheetId,
        resource: batchUpdateRequest,
      }, (err, response) => {
        if (err) {
          // Handle error
          console.log(err);
          // [START_EXCLUDE silent]
          return reject(err);
          // [END_EXCLUDE]
        } else {
          const findReplaceResponse = response.replies[1].findReplace;
          console.log(`${findReplaceResponse.occurrencesChanged} replacements made.`);
        }
        // [START_EXCLUDE silent]
        resolve(response);
        // [END_EXCLUDE]
      });
      // [END sheets_batch_update]
    });
  }

  /**
   * Gets cell values from a Spreadsheet.
   * @param {string} spreadsheetId The spreadsheet ID.
   * @param {string} range The sheet range.
   * @return {Promise<ValueResponse>} The value response.
   */
  async getValues(spreadsheetId, range) {
    return new Promise((resolve, reject) => {
      // [START sheets_get_values]
      this.sheetsService.spreadsheets.values.get({
        spreadsheetId,
        range,
      }, (err, result) => {
        if (err) {
          // Handle error
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          const numRows = result.values ? result.values.length : 0;
          console.log(`${numRows} rows retrieved.`);
          // [START_EXCLUDE silent]
          resolve(result);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_get_values]
    });
  };

  /**
   * Batch gets cell values from a Spreadsheet.
   * @param {string} spreadsheetId The spreadsheet ID.
   * @param {string} _ranges The mock sheet range.
   * @return {Promise<ValueResponse>} The value response.
   */
  async batchGetValues(spreadsheetId, _ranges) {
    return new Promise((resolve, reject) => {
      // [START sheets_batch_get_values]
      let ranges = [
        // Range names ...
      ];
      // [START_EXCLUDE silent]
      ranges = _ranges;
      // [END_EXCLUDE]
      this.sheetsService.spreadsheets.values.batchGet({
        spreadsheetId,
        ranges,
      }, (err, result) => {
        if (err) {
          // Handle error
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          console.log(`${result.valueRanges.length} ranges retrieved.`);
          // [START_EXCLUDE silent]
          resolve(result);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_batch_get_values]
    });
  };

  /**
   * Updates values in a Spreadsheet.
   * @param {string} spreadsheetId The spreadsheet ID.
   * @param {string} range The range of values to update.
   * @param {object} valueInputOption Value update options.
   * @param {(string[])[]} _values A 2d array of values to update.
   * @return {Promise<ValueUpdateResponse>} The updated values response.
   */
  async updateValues(spreadsheetId, range, valueInputOption, _values) {
    return new Promise((resolve, reject) => {
      // [START sheets_update_values]
      let values = [
        [
          // Cell values ...
        ],
        // Additional rows ...
      ];
      // [START_EXCLUDE silent]
      values = _values;
      // [END_EXCLUDE]
      const resource = {
        values,
      };
      this.sheetsService.spreadsheets.values.update({
        spreadsheetId,
        range,
        valueInputOption,
        resource,
      }, (err, result) => {
        if (err) {
          // Handle error
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          console.log('%d cells updated.', result.updatedCells);
          // [START_EXCLUDE silent]
          resolve(result);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_update_values]
    });
  }

  /**
   * Batch Updates values in a Spreadsheet.
   * @param {string} spreadsheetId The spreadsheet ID.
   * @param {string} range The range of values to update.
   * @param {object} valueInputOption Value update options.
   * @param {(string[])[]} _values A 2d array of values to update.
   * @return {Promise<ValueUpdateResponse>} The updated values response.
   */
  async batchUpdateValues(spreadsheetId, range, valueInputOption, _values) {
    return new Promise((resolve, reject) => {
      // [START sheets_batch_update_values]
      let values = [
        [
          // Cell values ...
        ],
        // Additional rows ...
      ];
      // [START_EXCLUDE silent]
      values = _values;
      // [END_EXCLUDE]
      const data = [{
        range,
        values,
      }];
      // Additional ranges to update ...
      const resource = {
        data,
        valueInputOption,
      };
      this.sheetsService.spreadsheets.values.batchUpdate({
        spreadsheetId,
        resource,
      }, (err, result) => {
        if (err) {
          // Handle error
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          console.log('%d cells updated.', result.totalUpdatedCells);
          // [START_EXCLUDE silent]
          resolve(result);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_batch_update_values]
    });
  }

  /**
   * Appends values in a Spreadsheet.
   * @param {string} spreadsheetId The spreadsheet ID.
   * @param {string} range The range of values to append.
   * @param {object} valueInputOption Value input options.
   * @param {(string[])[]} _values A 2d array of values to append.
   * @return {Promise} The appended values response.
   */
  async appendValues(spreadsheetId, range, valueInputOption, _values) {
    return new Promise((resolve, reject) => {
      // [START sheets_append_values]
      let values = [
        [
          // Cell values ...
        ],
        // Additional rows ...
      ];
      // [START_EXCLUDE silent]
      values = _values;
      // [END_EXCLUDE]
      let resource = {
        values,
      };
      this.sheetsService.spreadsheets.values.append({
        spreadsheetId,
        range,
        valueInputOption,
        resource,
      }, (err, result) => {
        if (err) {
          // Handle error.
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          console.log(`${result.updates.updatedCells} cells appended.`);
          // [START_EXCLUDE silent]
          resolve(result);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_append_values]
    });
  }

  /**
   * Adds a pivot table to a spreadsheet.
   * @param {string} spreadsheetId The Spreadsheet to add the pivot table to.
   * @return {Promise<BatchUpdateResponse>} The batch update response.
   */
  async pivotTable(spreadsheetId) {
    return new Promise((resolve, reject) => {
      // Create two sheets for our pivot table
      const requests = [{
        addSheet: {},
      }, {
        addSheet: {},
      }];
      const resource = {requests};
      this.sheetsService.spreadsheets.batchUpdate({
        spreadsheetId,
        resource,
      }, (err, response) => {
        if (err) {
          // Handle error.
          console.log(err);
          // [START_EXCLUDE silent]
          return reject(err);
          // [END_EXCLUDE]
        } else {
          const sourceSheetId = response.replies[0].addSheet.properties.sheetId;
          const targetSheetId = response.replies[1].addSheet.properties.sheetId;
          // [START sheets_pivot_tables]
          const requests = [{
            updateCells: {
              rows: {
                values: [{
                    pivotTable: {
                      source: {
                        sheetId: sourceSheetId,
                        startRowIndex: 0,
                        startColumnIndex: 0,
                        endRowIndex: 20,
                        endColumnIndex: 7,
                      },
                      rows: [{
                        sourceColumnOffset: 1,
                        showTotals: true,
                        sortOrder: 'ASCENDING',
                      }],
                      columns: [{
                        sourceColumnOffset: 4,
                        sortOrder: 'ASCENDING',
                        showTotals: true,
                      }],
                      values: [{
                        summarizeFunction: 'COUNTA',
                        sourceColumnOffset: 4,
                      }],
                      valueLayout: 'HORIZONTAL',
                    },
                  },
                ],
              },
              start: {
                sheetId: targetSheetId,
                rowIndex: 0,
                columnIndex: 0,
              },
              fields: 'pivotTable',
            },
          }];

          const resource = {
            requests,
          };
          this.sheetsService.spreadsheets.batchUpdate({
            spreadsheetId,
            resource,
          }, (err, response) => {
            if (err) {
              // Handle error.
              console.log(err);
              // [START_EXCLUDE silent]
              return reject(err);
              // [END_EXCLUDE]
            } else {
              // [START_EXCLUDE silent]
              resolve(response);
              // [END_EXCLUDE]
            }
          });
          // [END sheets_pivot_tables]
        }
      });
    });
  }

  /**
   * Conditionally formats a Spreadsheet.
   * @param {string} spreadsheetId A Spreadsheet ID.
   * @return {Promise<BatchUpdateResponse>} The batch update response.
   */
  conditionalFormatting(spreadsheetId) {
    return new Promise((resolve, reject) => {
      // [START sheets_conditional_formatting]
      const myRange = {
        sheetId: 0,
        startRowIndex: 1,
        endRowIndex: 11,
        startColumnIndex: 0,
        endColumnIndex: 4,
      };
      const requests = [{
        addConditionalFormatRule: {
          rule: {
            ranges: [myRange],
            booleanRule: {
              condition: {
                type: 'CUSTOM_FORMULA',
                values: [{userEnteredValue: '=GT($D2,median($D$2:$D$11))'}],
              },
              format: {
                textFormat: {foregroundColor: {red: 0.8}},
              },
            },
          },
          index: 0,
        },
      }, {
        addConditionalFormatRule: {
          rule: {
            ranges: [myRange],
            booleanRule: {
              condition: {
                type: 'CUSTOM_FORMULA',
                values: [{userEnteredValue: '=LT($D2,median($D$2:$D$11))'}],
              },
              format: {
                backgroundColor: {red: 1, green: 0.4, blue: 0.4},
              },
            },
          },
          index: 0,
        },
      }];

      const resource = {
        requests,
      };
      this.sheetsService.spreadsheets.batchUpdate({
        spreadsheetId,
        resource,
      }, (err, response) => {
        if (err) {
          // Handle error.
          console.log(err);
          // [START_EXCLUDE silent]
          return reject(err);
          // [END_EXCLUDE]
        } else {
          console.log(`${response.replies.length} cells updated.`);
          // [START_EXCLUDE silent]
          resolve(response);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_conditional_formatting]
    });
  }
}

module.exports = SheetsSnippets;
<commit_msg>Fixed incorrect usage of not existing field `resource`<commit_after>/**
 * @license
 * Copyright Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const Promise = require('promise');

/**
 * Google Sheets Snippets
 */
class SheetsSnippets {
  /**
   * Creates Sheets Snippets with a Google API Services
   * @param {GoogleAuth[]} service Authenticated Google Drive and Sheets Services
   */
  constructor([driveService, sheetsService]) {
    this.driveService = driveService;
    this.sheetsService = sheetsService;
  }

  /**
   * Creates a new Google Spreadsheet.
   * @param {string} title The spreadsheet title
   * @return {Promise<string>} The spreadsheet ID
   */
  async create(title) {
    return new Promise((resolve, reject) => {
      // [START sheets_create]
      const requestBody = {
        properties: {
          title,
        },
      };
      this.sheetsService.spreadsheets.create({
        requestBody,
        fields: 'spreadsheetId',
      }, (err, spreadsheet) =>{
        if (err) {
          // Handle error.
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          console.log(`Spreadsheet ID: ${spreadsheet.spreadsheetId}`);
          // [START_EXCLUDE silent]
          resolve(spreadsheet.spreadsheetId);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_create]
    });
  }

  /**
   * Updates the Spreadsheet title. Finds and replaces a string in the sheets.
   * @param {string} spreadsheetId The Spreadsheet to update
   * @param {string} title The new Spreadsheet title
   * @param {string} find The text to find
   * @param {string} replacement The text to replace
   * @return {Promise<BatchUpdateResponse>} The batch update response.
   */
  async batchUpdate(spreadsheetId, title, find, replacement) {
    return new Promise((resolve, reject) => {
      // [START sheets_batch_update]
      let requests = [];
      // Change the spreadsheet's title.
      requests.push({
        updateSpreadsheetProperties: {
          properties: {
            title,
          },
          fields: 'title',
        },
      });
      // Find and replace text.
      requests.push({
        findReplace: {
          find,
          replacement,
          allSheets: true,
        },
      });
      // Add additional requests (operations) ...
      const batchUpdateRequest = {requests};
      this.sheetsService.spreadsheets.batchUpdate({
        spreadsheetId,
        requestBody: batchUpdateRequest,
      }, (err, response) => {
        if (err) {
          // Handle error
          console.log(err);
          // [START_EXCLUDE silent]
          return reject(err);
          // [END_EXCLUDE]
        } else {
          const findReplaceResponse = response.replies[1].findReplace;
          console.log(`${findReplaceResponse.occurrencesChanged} replacements made.`);
        }
        // [START_EXCLUDE silent]
        resolve(response);
        // [END_EXCLUDE]
      });
      // [END sheets_batch_update]
    });
  }

  /**
   * Gets cell values from a Spreadsheet.
   * @param {string} spreadsheetId The spreadsheet ID.
   * @param {string} range The sheet range.
   * @return {Promise<ValueResponse>} The value response.
   */
  async getValues(spreadsheetId, range) {
    return new Promise((resolve, reject) => {
      // [START sheets_get_values]
      this.sheetsService.spreadsheets.values.get({
        spreadsheetId,
        range,
      }, (err, result) => {
        if (err) {
          // Handle error
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          const numRows = result.values ? result.values.length : 0;
          console.log(`${numRows} rows retrieved.`);
          // [START_EXCLUDE silent]
          resolve(result);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_get_values]
    });
  };

  /**
   * Batch gets cell values from a Spreadsheet.
   * @param {string} spreadsheetId The spreadsheet ID.
   * @param {string} _ranges The mock sheet range.
   * @return {Promise<ValueResponse>} The value response.
   */
  async batchGetValues(spreadsheetId, _ranges) {
    return new Promise((resolve, reject) => {
      // [START sheets_batch_get_values]
      let ranges = [
        // Range names ...
      ];
      // [START_EXCLUDE silent]
      ranges = _ranges;
      // [END_EXCLUDE]
      this.sheetsService.spreadsheets.values.batchGet({
        spreadsheetId,
        ranges,
      }, (err, result) => {
        if (err) {
          // Handle error
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          console.log(`${result.valueRanges.length} ranges retrieved.`);
          // [START_EXCLUDE silent]
          resolve(result);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_batch_get_values]
    });
  };

  /**
   * Updates values in a Spreadsheet.
   * @param {string} spreadsheetId The spreadsheet ID.
   * @param {string} range The range of values to update.
   * @param {object} valueInputOption Value update options.
   * @param {(string[])[]} _values A 2d array of values to update.
   * @return {Promise<ValueUpdateResponse>} The updated values response.
   */
  async updateValues(spreadsheetId, range, valueInputOption, _values) {
    return new Promise((resolve, reject) => {
      // [START sheets_update_values]
      let values = [
        [
          // Cell values ...
        ],
        // Additional rows ...
      ];
      // [START_EXCLUDE silent]
      values = _values;
      // [END_EXCLUDE]
      const requestBody = {
        values,
      };
      this.sheetsService.spreadsheets.values.update({
        spreadsheetId,
        range,
        valueInputOption,
        requestBody,
      }, (err, result) => {
        if (err) {
          // Handle error
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          console.log('%d cells updated.', result.updatedCells);
          // [START_EXCLUDE silent]
          resolve(result);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_update_values]
    });
  }

  /**
   * Batch Updates values in a Spreadsheet.
   * @param {string} spreadsheetId The spreadsheet ID.
   * @param {string} range The range of values to update.
   * @param {object} valueInputOption Value update options.
   * @param {(string[])[]} _values A 2d array of values to update.
   * @return {Promise<ValueUpdateResponse>} The updated values response.
   */
  async batchUpdateValues(spreadsheetId, range, valueInputOption, _values) {
    return new Promise((resolve, reject) => {
      // [START sheets_batch_update_values]
      let values = [
        [
          // Cell values ...
        ],
        // Additional rows ...
      ];
      // [START_EXCLUDE silent]
      values = _values;
      // [END_EXCLUDE]
      const data = [{
        range,
        values,
      }];
      // Additional ranges to update ...
      const requestBody = {
        data,
        valueInputOption,
      };
      this.sheetsService.spreadsheets.values.batchUpdate({
        spreadsheetId,
        requestBody,
      }, (err, result) => {
        if (err) {
          // Handle error
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          console.log('%d cells updated.', result.totalUpdatedCells);
          // [START_EXCLUDE silent]
          resolve(result);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_batch_update_values]
    });
  }

  /**
   * Appends values in a Spreadsheet.
   * @param {string} spreadsheetId The spreadsheet ID.
   * @param {string} range The range of values to append.
   * @param {object} valueInputOption Value input options.
   * @param {(string[])[]} _values A 2d array of values to append.
   * @return {Promise} The appended values response.
   */
  async appendValues(spreadsheetId, range, valueInputOption, _values) {
    return new Promise((resolve, reject) => {
      // [START sheets_append_values]
      let values = [
        [
          // Cell values ...
        ],
        // Additional rows ...
      ];
      // [START_EXCLUDE silent]
      values = _values;
      // [END_EXCLUDE]
      let requestBody = {
        values,
      };
      this.sheetsService.spreadsheets.values.append({
        spreadsheetId,
        range,
        valueInputOption,
        requestBody,
      }, (err, result) => {
        if (err) {
          // Handle error.
          console.log(err);
          // [START_EXCLUDE silent]
          reject(err);
          // [END_EXCLUDE]
        } else {
          console.log(`${result.updates.updatedCells} cells appended.`);
          // [START_EXCLUDE silent]
          resolve(result);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_append_values]
    });
  }

  /**
   * Adds a pivot table to a spreadsheet.
   * @param {string} spreadsheetId The Spreadsheet to add the pivot table to.
   * @return {Promise<BatchUpdateResponse>} The batch update response.
   */
  async pivotTable(spreadsheetId) {
    return new Promise((resolve, reject) => {
      // Create two sheets for our pivot table
      const requests = [{
        addSheet: {},
      }, {
        addSheet: {},
      }];
      const requestBody = {requests};
      this.sheetsService.spreadsheets.batchUpdate({
        spreadsheetId,
        requestBody,
      }, (err, response) => {
        if (err) {
          // Handle error.
          console.log(err);
          // [START_EXCLUDE silent]
          return reject(err);
          // [END_EXCLUDE]
        } else {
          const sourceSheetId = response.replies[0].addSheet.properties.sheetId;
          const targetSheetId = response.replies[1].addSheet.properties.sheetId;
          // [START sheets_pivot_tables]
          const requests = [{
            updateCells: {
              rows: {
                values: [{
                    pivotTable: {
                      source: {
                        sheetId: sourceSheetId,
                        startRowIndex: 0,
                        startColumnIndex: 0,
                        endRowIndex: 20,
                        endColumnIndex: 7,
                      },
                      rows: [{
                        sourceColumnOffset: 1,
                        showTotals: true,
                        sortOrder: 'ASCENDING',
                      }],
                      columns: [{
                        sourceColumnOffset: 4,
                        sortOrder: 'ASCENDING',
                        showTotals: true,
                      }],
                      values: [{
                        summarizeFunction: 'COUNTA',
                        sourceColumnOffset: 4,
                      }],
                      valueLayout: 'HORIZONTAL',
                    },
                  },
                ],
              },
              start: {
                sheetId: targetSheetId,
                rowIndex: 0,
                columnIndex: 0,
              },
              fields: 'pivotTable',
            },
          }];

          const requestBody = {
            requests,
          };
          this.sheetsService.spreadsheets.batchUpdate({
            spreadsheetId,
            requestBody,
          }, (err, response) => {
            if (err) {
              // Handle error.
              console.log(err);
              // [START_EXCLUDE silent]
              return reject(err);
              // [END_EXCLUDE]
            } else {
              // [START_EXCLUDE silent]
              resolve(response);
              // [END_EXCLUDE]
            }
          });
          // [END sheets_pivot_tables]
        }
      });
    });
  }

  /**
   * Conditionally formats a Spreadsheet.
   * @param {string} spreadsheetId A Spreadsheet ID.
   * @return {Promise<BatchUpdateResponse>} The batch update response.
   */
  conditionalFormatting(spreadsheetId) {
    return new Promise((resolve, reject) => {
      // [START sheets_conditional_formatting]
      const myRange = {
        sheetId: 0,
        startRowIndex: 1,
        endRowIndex: 11,
        startColumnIndex: 0,
        endColumnIndex: 4,
      };
      const requests = [{
        addConditionalFormatRule: {
          rule: {
            ranges: [myRange],
            booleanRule: {
              condition: {
                type: 'CUSTOM_FORMULA',
                values: [{userEnteredValue: '=GT($D2,median($D$2:$D$11))'}],
              },
              format: {
                textFormat: {foregroundColor: {red: 0.8}},
              },
            },
          },
          index: 0,
        },
      }, {
        addConditionalFormatRule: {
          rule: {
            ranges: [myRange],
            booleanRule: {
              condition: {
                type: 'CUSTOM_FORMULA',
                values: [{userEnteredValue: '=LT($D2,median($D$2:$D$11))'}],
              },
              format: {
                backgroundColor: {red: 1, green: 0.4, blue: 0.4},
              },
            },
          },
          index: 0,
        },
      }];

      const requestBody = {
        requests,
      };
      this.sheetsService.spreadsheets.batchUpdate({
        spreadsheetId,
        requestBody,
      }, (err, response) => {
        if (err) {
          // Handle error.
          console.log(err);
          // [START_EXCLUDE silent]
          return reject(err);
          // [END_EXCLUDE]
        } else {
          console.log(`${response.replies.length} cells updated.`);
          // [START_EXCLUDE silent]
          resolve(response);
          // [END_EXCLUDE]
        }
      });
      // [END sheets_conditional_formatting]
    });
  }
}

module.exports = SheetsSnippets;
<|endoftext|>