<commit_before>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback } from 'react';
import PropTypes from 'prop-types';

import currency from '../../localization/currency';

import { dataTableStyles, SUSSOL_ORANGE } from '../../globalStyles';

import Row from './Row';
import Cell from './Cell';

import CheckableCell from './CheckableCell';
import TouchableCell from './TouchableCell';
import DropDownCell from '../DropDownCell';

import { ExpiryDateInput } from '../ExpiryDateInput';

import {
  CheckedIcon,
  UncheckedIcon,
  DisabledCheckedIcon,
  DisabledUncheckedIcon,
  ChevronRightIcon,
  HistoryIcon,
  PencilIcon,
  HazardIcon,
  BookIcon,
} from '../icons';
import TextInputCell from './TextInputCell';

import { COLUMN_TYPES, COLUMN_KEYS } from '../../pages/dataTableUtilities';
import { generalStrings, tableStrings } from '../../localization/index';

import { formatStatus, formatDate } from '../../utilities';
import { formatType } from '../../utilities/formatStatus';

/**
 * Wrapper component for a mSupply DataTable page row.
 * Wraps Row component with a contained RenderCells, which
 * will generate the appropriate cell for a given columnKey.
 * Doesn't need to be used, but is a convenience component.
 *
 * @param {Object} rowData     Data object for a row i.e. ItemBatch object
 * @param {Object} rowState    State object for a row, see: Row.js
 * @param {Object} style       Style object the be passed to inner Row
 * @param {String} rowKey      Unique key for a row
 * @param {Array}  columns     Array of column objects, see: columns.js
 * @param {Bool}   isFinalised Boolean indicating if the DataTable page is finalised.
 * @param {Func}   rowIndex    index of this row.
 * @param {Func}   onPress     On press callback for the row itself.
 * @param {Func}   getCallback Function to return a cells callback
 *                             (colKey, propName) => callback
 */
const DataTableRow = React.memo(
  ({
    rowData,
    rowState,
    rowKey,
    columns,
    isFinalised,
    getCallback,
    onPress,
    rowIndex,
    getCellError,
    isValidated,
  }) => {
    const {
      cellText,
      cellContainer,
      warningCellContainer,
      errorCellContainer,
      touchableCellContainer,
      editableCellText,
      editableCellTextView,
      editableCellUnfocused,
      selectedRow: selectedRowStyle,
      alternateRow: alternateRowStyle,
      row: basicRowStyle,
      iconCell,
    } = dataTableStyles;

    const { isSelected = false } = rowState || {};
    // If the row is selected, use selectedRow style, otherwise alternate row style on index.

    const rowStyle = {
      ...(rowIndex % 2 === 0 ? alternateRowStyle : basicRowStyle),
      ...(isSelected && selectedRowStyle),
      ...(!isValidated && { borderColor: SUSSOL_ORANGE, borderWidth: 1, borderRadius: 5 }),
    };

    // Callback for rendering a row of cells.
    const renderCells = useCallback(
      () =>
        // Map each column to an appropriate cell for a given row.
        columns.map(({ key: columnKey, type, width, alignText, icon }, index) => {
          // Indicator if the right hand border should be removed from styles for this cell.
          const isLastCell = index === columns.length - 1;

          // This cell is disabled if:
          // - the page is finalised.
          // - the row has been explicitly set as disabled.
          // - the data is disabled (i.e. data is an invoice).
          // - the cell is a reason dropdown for a row with a difference of zero.
          const rowIsDisabled = rowState?.isDisabled ?? false;
          const dataIsDisabled = rowData?.isFinalised ?? false;
          const reasonIsDisabled =
            columnKey === COLUMN_KEYS.REASON_TITLE && rowData?.difference === 0;
          const isDisabled = isFinalised || rowIsDisabled || dataIsDisabled || reasonIsDisabled;

          // Alignment of this particular column. Default to left hand ide.
          const cellAlignment = alignText || 'left';

          switch (type) {
            case COLUMN_TYPES.EDITABLE_STRING:
            case COLUMN_TYPES.EDITABLE_NUMERIC: {
              const { isVaccine, hasBeenCounted } = rowData;

              // Placeholder values which are determined on a per row basis. For example, display
              // "Not counted" as a placeholder on a stocktake row. Placeholders are used under
              // the condition [placeholder && !value] which can be a source of bugs with JS
              // falsey values. I.e. if a value is 0, the placeholder will display rather than
              // the value.
              const extraPlaceholders = {
                [COLUMN_KEYS.COUNTED_TOTAL_QUANTITY]: hasBeenCounted
                  ? ''
                  : tableStrings.not_counted,
              };

              // Extra disabled conditions for columns which can be determined per row. For example,
              // only allow the doses column for vaccine items.
              const disabledCondition = {
                [COLUMN_KEYS.DOSES]: !isVaccine,
              };

              // Extra text which can be displayed for a column on a per row basis when the
              // cell is disabled. For example display N/A on a row for the doses column rather
              // than the underlying value "0".
              const disabledText = {
                [COLUMN_KEYS.DOSES]: isVaccine ? '' : generalStrings.not_available,
              };

              const value = disabledText[columnKey] ? disabledText[columnKey] : rowData[columnKey];
              const placeholder = extraPlaceholders[columnKey] ?? '';

              const inputIsDisabled = isDisabled || !!disabledCondition[columnKey];

              const cellErrors = getCellError?.(rowData, columnKey);
              const alternateCellStyleLookup = {
                warning: warningCellContainer,
                error: errorCellContainer,
              };
              const cellContainerStyle = alternateCellStyleLookup[cellErrors] ?? cellContainer;

              return (
                <TextInputCell
                  key={columnKey}
                  value={value}
                  rowKey={rowKey}
                  columnKey={columnKey}
                  onChangeText={getCallback(columnKey)}
                  isDisabled={inputIsDisabled}
                  width={width}
                  viewStyle={cellContainerStyle[cellAlignment]}
                  textViewStyle={editableCellTextView}
                  isLastCell={isLastCell}
                  keyboardType={type === COLUMN_TYPES.EDITABLE_NUMERIC ? 'numeric' : 'default'}
                  textInputStyle={cellText[cellAlignment]}
                  textStyle={editableCellUnfocused[cellAlignment]}
                  cellTextStyle={editableCellText}
                  rowIndex={rowIndex}
                  placeholder={placeholder}
                />
              );
            }
            case COLUMN_TYPES.EDITABLE_EXPIRY_DATE:
              return (
                <ExpiryDateInput
                  key={columnKey}
                  value={rowData[columnKey]}
                  rowKey={rowKey}
                  columnKey={columnKey}
                  onEndEditing={getCallback(columnKey)}
                  isDisabled={isDisabled}
                  width={width}
                  isLastCell={isLastCell}
                  rowIndex={rowIndex}
                />
              );

            case COLUMN_TYPES.CHECKABLE:
              return (
                <CheckableCell
                  key={columnKey}
                  rowKey={rowKey}
                  columnKey={columnKey}
                  isChecked={rowState && rowState.isSelected}
                  isDisabled={isDisabled}
                  CheckedComponent={CheckedIcon}
                  UncheckedComponent={UncheckedIcon}
                  DisabledCheckedComponent={DisabledCheckedIcon}
                  DisabledUncheckedComponent={DisabledUncheckedIcon}
                  onCheck={getCallback(columnKey, 'onCheck')}
                  onUncheck={getCallback(columnKey, 'onUncheck')}
                  containerStyle={touchableCellContainer}
                  width={width}
                  isLastCell={isLastCell}
                />
              );

            case COLUMN_TYPES.CURRENCY: {
              const value = currency(rowData[columnKey]).format(false);

              return (
                <Cell
                  key={columnKey}
                  value={value}
                  width={width}
                  viewStyle={cellContainer[cellAlignment]}
                  textStyle={cellText[cellAlignment]}
                  isLastCell={isLastCell}
                />
              );
            }

            case COLUMN_TYPES.STRING: {
              const value = rowData[columnKey];
              let displayValue = columnKey === 'status' ? formatStatus(value) : value;
              displayValue = columnKey === 'type' ? formatType(displayValue) : displayValue;

              return (
                <Cell
                  key={columnKey}
                  value={displayValue}
                  width={width}
                  viewStyle={cellContainer[cellAlignment]}
                  textStyle={cellText[cellAlignment]}
                  isLastCell={isLastCell}
                />
              );
            }

            case COLUMN_TYPES.NUMERIC: {
              // Special condition for stocktake difference cells.
              // Use the placeholder 'Not counted' when a stocktake item or batch
              // has not been counted yet.
              const value =
                columnKey === COLUMN_KEYS.DIFFERENCE && !rowData.hasBeenCounted
                  ? generalStrings.not_available
                  : Math.round(rowData[columnKey]);

              const cellErrors = getCellError?.(rowData, columnKey);
              const alternateCellStyleLookup = {
                warning: warningCellContainer,
                error: errorCellContainer,
              };
              const cellContainerStyle = alternateCellStyleLookup[cellErrors] ?? cellContainer;

              return (
                <Cell
                  key={columnKey}
                  value={value}
                  width={width}
                  viewStyle={cellContainerStyle[cellAlignment]}
                  textStyle={cellText[cellAlignment]}
                  isLastCell={isLastCell}
                />
              );
            }

            case COLUMN_TYPES.DATE:
              return (
                <Cell
                  key={columnKey}
                  value={formatDate(rowData[columnKey], 'll') ?? generalStrings.not_available}
                  width={width}
                  viewStyle={cellContainer[cellAlignment]}
                  textStyle={cellText[cellAlignment]}
                  isLastCell={isLastCell}
                />
              );

            case COLUMN_TYPES.ICON: {
              const { hasBreached, isVaccine } = rowData ?? {};

              const icons = {
                chevron_right: ChevronRightIcon,
                history: () => <HistoryIcon color={SUSSOL_ORANGE} />,
                pencil: () => <PencilIcon color={SUSSOL_ORANGE} />,
                breach: () => (hasBreached ? <HazardIcon color={SUSSOL_ORANGE} /> : null),
                book: () => <BookIcon color={SUSSOL_ORANGE} />,
              };

              const disabledConditions = {
                [COLUMN_KEYS.HAS_BREACHED]: !(hasBreached && isVaccine),
              };

              const isEditReadOnlyRecord =
                (columnKey === COLUMN_KEYS.PATIENT_EDIT ||
                  columnKey === COLUMN_KEYS.PRESCRIBER_EDIT) &&
                !rowData.isEditable;
              const iconComponent = isEditReadOnlyRecord ? icons.book : icons[icon];

              const isDisabledIcon =
                (isDisabled || disabledConditions[columnKey]) && columnKey !== COLUMN_KEYS.BATCH;

              return (
                <TouchableCell
                  key={columnKey}
                  renderChildren={iconComponent}
                  rowKey={rowKey}
                  columnKey={columnKey}
                  onPress={getCallback(columnKey)}
                  width={width}
                  isLastCell={isLastCell}
                  isDisabled={isDisabledIcon}
                  containerStyle={iconCell}
                />
              );
            }

            case COLUMN_TYPES.DROP_DOWN: {
              const { isVaccine = false } = rowData ?? {};

              const disabledConditions = {
                [COLUMN_KEYS.CURRENT_VVM_STATUS]: !isVaccine,
              };

              const disabledVVMStatus = disabledConditions[columnKey];

              return (
                <DropDownCell
                  key={columnKey}
                  isDisabled={isDisabled || disabledVVMStatus}
                  onPress={getCallback(columnKey)}
                  rowKey={rowKey}
                  columnKey={columnKey}
                  value={rowData[columnKey]}
                  isLastCell={isLastCell}
                  width={width}
                />
              );
            }

            default: {
              return (
                <Cell
                  key={columnKey}
                  value={rowData[columnKey]}
                  width={width}
                  viewStyle={cellContainer[cellAlignment]}
                  textStyle={cellText[cellAlignment]}
                  isLastCell={isLastCell}
                />
              );
            }
          }
        }),
      [isFinalised, rowState, rowData, rowIndex, columns]
    );

    return (
      <Row
        onPress={onPress}
        style={rowStyle}
        renderCells={renderCells}
        rowKey={rowKey}
        rowData={rowData}
        rowState={rowState}
        rowIndex={rowIndex}
      />
    );
  }
);

DataTableRow.defaultProps = {
  isFinalised: false,
  getCallback: null,
  onPress: null,
  rowState: null,
  getCellError: () => null,
  isValidated: true,
};

DataTableRow.propTypes = {
  onPress: PropTypes.func,
  rowData: PropTypes.object.isRequired,
  rowState: PropTypes.object,
  rowKey: PropTypes.string.isRequired,
  columns: PropTypes.array.isRequired,
  isFinalised: PropTypes.bool,
  getCallback: PropTypes.func,
  rowIndex: PropTypes.number.isRequired,
  getCellError: PropTypes.func,
  isValidated: PropTypes.bool,
};

export default DataTableRow;
<commit_msg>Add memoizing of DataTableRow style<commit_after>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';

import currency from '../../localization/currency';

import { dataTableStyles, SUSSOL_ORANGE } from '../../globalStyles';

import Row from './Row';
import Cell from './Cell';

import CheckableCell from './CheckableCell';
import TouchableCell from './TouchableCell';
import DropDownCell from '../DropDownCell';

import { ExpiryDateInput } from '../ExpiryDateInput';

import {
  CheckedIcon,
  UncheckedIcon,
  DisabledCheckedIcon,
  DisabledUncheckedIcon,
  ChevronRightIcon,
  HistoryIcon,
  PencilIcon,
  HazardIcon,
  BookIcon,
} from '../icons';
import TextInputCell from './TextInputCell';

import { COLUMN_TYPES, COLUMN_KEYS } from '../../pages/dataTableUtilities';
import { generalStrings, tableStrings } from '../../localization/index';

import { formatStatus, formatDate } from '../../utilities';
import { formatType } from '../../utilities/formatStatus';

/**
 * Wrapper component for a mSupply DataTable page row.
 * Wraps Row component with a contained RenderCells, which
 * will generate the appropriate cell for a given columnKey.
 * Doesn't need to be used, but is a convenience component.
 *
 * @param {Object} rowData     Data object for a row i.e. ItemBatch object
 * @param {Object} rowState    State object for a row, see: Row.js
 * @param {Object} style       Style object the be passed to inner Row
 * @param {String} rowKey      Unique key for a row
 * @param {Array}  columns     Array of column objects, see: columns.js
 * @param {Bool}   isFinalised Boolean indicating if the DataTable page is finalised.
 * @param {Func}   rowIndex    index of this row.
 * @param {Func}   onPress     On press callback for the row itself.
 * @param {Func}   getCallback Function to return a cells callback
 *                             (colKey, propName) => callback
 */
const DataTableRow = React.memo(
  ({
    rowData,
    rowState,
    rowKey,
    columns,
    isFinalised,
    getCallback,
    onPress,
    rowIndex,
    getCellError,
    isValidated,
  }) => {
    const {
      cellText,
      cellContainer,
      warningCellContainer,
      errorCellContainer,
      touchableCellContainer,
      editableCellText,
      editableCellTextView,
      editableCellUnfocused,
      selectedRow: selectedRowStyle,
      alternateRow: alternateRowStyle,
      row: basicRowStyle,
      iconCell,
    } = dataTableStyles;

    const { isSelected = false } = rowState || {};
    // If the row is selected, use selectedRow style, otherwise alternate row style on index.

    const rowStyle = useMemo(
      () => ({
        ...(rowIndex % 2 === 0 ? alternateRowStyle : basicRowStyle),
        ...(isSelected && selectedRowStyle),
        ...(!isValidated && { borderColor: SUSSOL_ORANGE, borderWidth: 1, borderRadius: 5 }),
      }),
      [rowIndex, isSelected, isValidated]
    );

    // Callback for rendering a row of cells.
    const renderCells = useCallback(
      () =>
        // Map each column to an appropriate cell for a given row.
        columns.map(({ key: columnKey, type, width, alignText, icon }, index) => {
          // Indicator if the right hand border should be removed from styles for this cell.
          const isLastCell = index === columns.length - 1;

          // This cell is disabled if:
          // - the page is finalised.
          // - the row has been explicitly set as disabled.
          // - the data is disabled (i.e. data is an invoice).
          // - the cell is a reason dropdown for a row with a difference of zero.
          const rowIsDisabled = rowState?.isDisabled ?? false;
          const dataIsDisabled = rowData?.isFinalised ?? false;
          const reasonIsDisabled =
            columnKey === COLUMN_KEYS.REASON_TITLE && rowData?.difference === 0;
          const isDisabled = isFinalised || rowIsDisabled || dataIsDisabled || reasonIsDisabled;

          // Alignment of this particular column. Default to left hand ide.
          const cellAlignment = alignText || 'left';

          switch (type) {
            case COLUMN_TYPES.EDITABLE_STRING:
            case COLUMN_TYPES.EDITABLE_NUMERIC: {
              const { isVaccine, hasBeenCounted } = rowData;

              // Placeholder values which are determined on a per row basis. For example, display
              // "Not counted" as a placeholder on a stocktake row. Placeholders are used under
              // the condition [placeholder && !value] which can be a source of bugs with JS
              // falsey values. I.e. if a value is 0, the placeholder will display rather than
              // the value.
              const extraPlaceholders = {
                [COLUMN_KEYS.COUNTED_TOTAL_QUANTITY]: hasBeenCounted
                  ? ''
                  : tableStrings.not_counted,
              };

              // Extra disabled conditions for columns which can be determined per row. For example,
              // only allow the doses column for vaccine items.
              const disabledCondition = {
                [COLUMN_KEYS.DOSES]: !isVaccine,
              };

              // Extra text which can be displayed for a column on a per row basis when the
              // cell is disabled. For example display N/A on a row for the doses column rather
              // than the underlying value "0".
              const disabledText = {
                [COLUMN_KEYS.DOSES]: isVaccine ? '' : generalStrings.not_available,
              };

              const value = disabledText[columnKey] ? disabledText[columnKey] : rowData[columnKey];
              const placeholder = extraPlaceholders[columnKey] ?? '';

              const inputIsDisabled = isDisabled || !!disabledCondition[columnKey];

              const cellErrors = getCellError?.(rowData, columnKey);
              const alternateCellStyleLookup = {
                warning: warningCellContainer,
                error: errorCellContainer,
              };
              const cellContainerStyle = alternateCellStyleLookup[cellErrors] ?? cellContainer;

              return (
                <TextInputCell
                  key={columnKey}
                  value={value}
                  rowKey={rowKey}
                  columnKey={columnKey}
                  onChangeText={getCallback(columnKey)}
                  isDisabled={inputIsDisabled}
                  width={width}
                  viewStyle={cellContainerStyle[cellAlignment]}
                  textViewStyle={editableCellTextView}
                  isLastCell={isLastCell}
                  keyboardType={type === COLUMN_TYPES.EDITABLE_NUMERIC ? 'numeric' : 'default'}
                  textInputStyle={cellText[cellAlignment]}
                  textStyle={editableCellUnfocused[cellAlignment]}
                  cellTextStyle={editableCellText}
                  rowIndex={rowIndex}
                  placeholder={placeholder}
                />
              );
            }
            case COLUMN_TYPES.EDITABLE_EXPIRY_DATE:
              return (
                <ExpiryDateInput
                  key={columnKey}
                  value={rowData[columnKey]}
                  rowKey={rowKey}
                  columnKey={columnKey}
                  onEndEditing={getCallback(columnKey)}
                  isDisabled={isDisabled}
                  width={width}
                  isLastCell={isLastCell}
                  rowIndex={rowIndex}
                />
              );

            case COLUMN_TYPES.CHECKABLE:
              return (
                <CheckableCell
                  key={columnKey}
                  rowKey={rowKey}
                  columnKey={columnKey}
                  isChecked={rowState && rowState.isSelected}
                  isDisabled={isDisabled}
                  CheckedComponent={CheckedIcon}
                  UncheckedComponent={UncheckedIcon}
                  DisabledCheckedComponent={DisabledCheckedIcon}
                  DisabledUncheckedComponent={DisabledUncheckedIcon}
                  onCheck={getCallback(columnKey, 'onCheck')}
                  onUncheck={getCallback(columnKey, 'onUncheck')}
                  containerStyle={touchableCellContainer}
                  width={width}
                  isLastCell={isLastCell}
                />
              );

            case COLUMN_TYPES.CURRENCY: {
              const value = currency(rowData[columnKey]).format(false);

              return (
                <Cell
                  key={columnKey}
                  value={value}
                  width={width}
                  viewStyle={cellContainer[cellAlignment]}
                  textStyle={cellText[cellAlignment]}
                  isLastCell={isLastCell}
                />
              );
            }

            case COLUMN_TYPES.STRING: {
              const value = rowData[columnKey];
              let displayValue = columnKey === 'status' ? formatStatus(value) : value;
              displayValue = columnKey === 'type' ? formatType(displayValue) : displayValue;

              return (
                <Cell
                  key={columnKey}
                  value={displayValue}
                  width={width}
                  viewStyle={cellContainer[cellAlignment]}
                  textStyle={cellText[cellAlignment]}
                  isLastCell={isLastCell}
                />
              );
            }

            case COLUMN_TYPES.NUMERIC: {
              // Special condition for stocktake difference cells.
              // Use the placeholder 'Not counted' when a stocktake item or batch
              // has not been counted yet.
              const value =
                columnKey === COLUMN_KEYS.DIFFERENCE && !rowData.hasBeenCounted
                  ? generalStrings.not_available
                  : Math.round(rowData[columnKey]);

              const cellErrors = getCellError?.(rowData, columnKey);
              const alternateCellStyleLookup = {
                warning: warningCellContainer,
                error: errorCellContainer,
              };
              const cellContainerStyle = alternateCellStyleLookup[cellErrors] ?? cellContainer;

              return (
                <Cell
                  key={columnKey}
                  value={value}
                  width={width}
                  viewStyle={cellContainerStyle[cellAlignment]}
                  textStyle={cellText[cellAlignment]}
                  isLastCell={isLastCell}
                />
              );
            }

            case COLUMN_TYPES.DATE:
              return (
                <Cell
                  key={columnKey}
                  value={formatDate(rowData[columnKey], 'll') ?? generalStrings.not_available}
                  width={width}
                  viewStyle={cellContainer[cellAlignment]}
                  textStyle={cellText[cellAlignment]}
                  isLastCell={isLastCell}
                />
              );

            case COLUMN_TYPES.ICON: {
              const { hasBreached, isVaccine } = rowData ?? {};

              const icons = {
                chevron_right: ChevronRightIcon,
                history: () => <HistoryIcon color={SUSSOL_ORANGE} />,
                pencil: () => <PencilIcon color={SUSSOL_ORANGE} />,
                breach: () => (hasBreached ? <HazardIcon color={SUSSOL_ORANGE} /> : null),
                book: () => <BookIcon color={SUSSOL_ORANGE} />,
              };

              const disabledConditions = {
                [COLUMN_KEYS.HAS_BREACHED]: !(hasBreached && isVaccine),
              };

              const isEditReadOnlyRecord =
                (columnKey === COLUMN_KEYS.PATIENT_EDIT ||
                  columnKey === COLUMN_KEYS.PRESCRIBER_EDIT) &&
                !rowData.isEditable;
              const iconComponent = isEditReadOnlyRecord ? icons.book : icons[icon];

              const isDisabledIcon =
                (isDisabled || disabledConditions[columnKey]) && columnKey !== COLUMN_KEYS.BATCH;

              return (
                <TouchableCell
                  key={columnKey}
                  renderChildren={iconComponent}
                  rowKey={rowKey}
                  columnKey={columnKey}
                  onPress={getCallback(columnKey)}
                  width={width}
                  isLastCell={isLastCell}
                  isDisabled={isDisabledIcon}
                  containerStyle={iconCell}
                />
              );
            }

            case COLUMN_TYPES.DROP_DOWN: {
              const { isVaccine = false } = rowData ?? {};

              const disabledConditions = {
                [COLUMN_KEYS.CURRENT_VVM_STATUS]: !isVaccine,
              };

              const disabledVVMStatus = disabledConditions[columnKey];

              return (
                <DropDownCell
                  key={columnKey}
                  isDisabled={isDisabled || disabledVVMStatus}
                  onPress={getCallback(columnKey)}
                  rowKey={rowKey}
                  columnKey={columnKey}
                  value={rowData[columnKey]}
                  isLastCell={isLastCell}
                  width={width}
                />
              );
            }

            default: {
              return (
                <Cell
                  key={columnKey}
                  value={rowData[columnKey]}
                  width={width}
                  viewStyle={cellContainer[cellAlignment]}
                  textStyle={cellText[cellAlignment]}
                  isLastCell={isLastCell}
                />
              );
            }
          }
        }),
      [isFinalised, rowState, rowData, rowIndex, columns]
    );

    return (
      <Row
        onPress={onPress}
        style={rowStyle}
        renderCells={renderCells}
        rowKey={rowKey}
        rowData={rowData}
        rowState={rowState}
        rowIndex={rowIndex}
        debug
      />
    );
  }
);

DataTableRow.defaultProps = {
  isFinalised: false,
  getCallback: null,
  onPress: null,
  rowState: null,
  getCellError: () => null,
  isValidated: true,
};

DataTableRow.propTypes = {
  onPress: PropTypes.func,
  rowData: PropTypes.object.isRequired,
  rowState: PropTypes.object,
  rowKey: PropTypes.string.isRequired,
  columns: PropTypes.array.isRequired,
  isFinalised: PropTypes.bool,
  getCallback: PropTypes.func,
  rowIndex: PropTypes.number.isRequired,
  getCellError: PropTypes.func,
  isValidated: PropTypes.bool,
};

export default DataTableRow;
<|endoftext|>


<commit_before>(function()
{
    "use strict";

    // post & delete oldest file

    var tumblr = require('tumblr.js');
    var R = require('ramda');
    var fs = require('fs');
    var fsq = require('../functal/fsq');

    // tokens are environment variables

    // Authenticate via OAuth
    var client = tumblr.createClient(
    {
        consumer_key: process.env.tumblr_consumer_key,
        consumer_secret: process.env.tumblr_consumer_secret,
        token: process.env.tumblr_token,
        token_secret: process.env.tumblr_token_secret
    });

    var isDev = (process.env.TERM_PROGRAM === 'Apple_Terminal');

    var functalsFolder = process.env.HOME + '/Dropbox/functals';

    var folder = functalsFolder + '/medium/';

    fsq.readdir(folder).then(function(files)
    {
        var file = R.find(function(f)
        {
            return /\.png$/.test(f);
        }, files);

        if (file)
        {
            file = folder + file;

            console.log(file);

            // Make the request
            client.userInfo(function(err, data)
            {
                if (err)
                {
                    console.log(err);
                }

                // console.log(JSON.stringify(data, null, 4));

                // client.userInfo(function(err, data)
                //         {
                //             R.forEach(function(blog)
                //             {
                //                 console.log(blog.name);
                //             }, data.user.blogs);
                // });

                // get the blog
                var df = R.find(R.propEq('name', 'functal'), data.user.blogs);
                // console.log(df);

                var options = {
                    caption: 'Fractal',
                    tags: 'fractal,functal',
                    link: 'https://functal.tumblr.com', // something required or post fails with 401
                    data: file
                };

                client.photo('functal', options, function(err, data)
                {
                    console.log(err);
                    console.log(data);

                    // delete file & json
                    fsq.unlink(file).then(function()
                    {
                        try
                        {
                            fsq.unlink(file.replace(/\.png/, '.json'));
                        }
                        catch (ex)
                        {}
                    });

                });
            });
        }
        else
        {
            console.log('no file');
        }
    });
}());
<commit_msg>make markdown<commit_after>(function()
{
    "use strict";

    // post & delete oldest file

    var tumblr = require('tumblr.js');
    var R = require('ramda');
    var fs = require('fs');
    var fsq = require('../functal/fsq');

    // tokens are environment variables

    // Authenticate via OAuth
    var client = tumblr.createClient(
    {
        consumer_key: process.env.tumblr_consumer_key,
        consumer_secret: process.env.tumblr_consumer_secret,
        token: process.env.tumblr_token,
        token_secret: process.env.tumblr_token_secret
    });

    var isDev = (process.env.TERM_PROGRAM === 'Apple_Terminal');

    var functalsFolder = process.env.HOME + '/Dropbox/functals';

    var folder = functalsFolder + '/medium/';

    fsq.readdir(folder).then(function(files)
    {
        var file = R.find(function(f)
        {
            return /\.png$/.test(f);
        }, files);

        if (file)
        {
            file = folder + file;

            console.log(file);

            // Make the request
            client.userInfo(function(err, data)
            {
                if (err)
                {
                    console.log(err);
                }

                // console.log(JSON.stringify(data, null, 4));

                // client.userInfo(function(err, data)
                //         {
                //             R.forEach(function(blog)
                //             {
                //                 console.log(blog.name);
                //             }, data.user.blogs);
                // });

                // get the blog
                var df = R.find(R.propEq('name', 'functal'), data.user.blogs);
                // console.log(df);

                var options = {
                    caption: 'Fractal',
                    tags: 'fractal,functal',
                    format: 'markdown',
                    link: 'https://functal.tumblr.com', // something required or post fails with 401
                    data: file
                };

                client.photo('functal', options, function(err, data)
                {
                    console.log(err);
                    console.log(data);

                    // delete file & json
                    fsq.unlink(file).then(function()
                    {
                        try
                        {
                            fsq.unlink(file.replace(/\.png/, '.json'));
                        }
                        catch (ex)
                        {}
                    });

                });
            });
        }
        else
        {
            console.log('no file');
        }
    });
}());
<|endoftext|>


<commit_before>import React from 'react';

import Analyzer from 'Parser/Core/Analyzer';
import Combatants from 'Parser/Core/Modules/Combatants';

import SPELLS from 'common/SPELLS';
import StatisticBox from "Main/StatisticBox";
import SpellIcon from "common/SpellIcon";
import { formatNumber } from "common/format";
import SpellLink from "common/SpellLink";
import { formatPercentage } from "../../../../../common/format";

//generally accepted rule is to save crows if boss is below 25% health.
const CROWS_SAVE_PERCENT = 0.25;
const EXECUTE_PERCENT = 0.2;

class AMurderOfCrows extends Analyzer {

  static dependencies = {
    combatants: Combatants,
  };

  bossIDs = [];
  damage = 0;
  shouldHaveSaved = 0;
  bossHP = 0;

  on_initialized() {
    this.active = this.combatants.selected.hasTalent(SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id);
    this.owner.report.enemies.forEach(enemy => {
      enemy.fights.forEach(fight => {
        if (fight.id === this.owner.fight.id && enemy.type === "Boss") this.bossIDs.push(enemy.id);
      });
    });
  }

  on_byPlayer_damage(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.A_MURDER_OF_CROWS_SPELL.id) {
      return;
    }
    this.damage += event.amount;
  }

  on_byPlayer_cast(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.A_MURDER_OF_CROWS_SPELL.id) {
      return;
    }
    if (event.maxHitPoints && this.bossIDs.includes(event.targetID)) {
      if ((event.hitPoints / event.maxHitPoints) <= CROWS_SAVE_PERCENT && (event.hitPoints / event.maxHitPoints) > EXECUTE_PERCENT) {
        this.shouldHaveSaved += 1;
        this.bossHP = (event.hitPoints/event.maxHitPoints);
      }
    }
  }

  statistic() {
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id} />}
        value={`${formatNumber(this.damage)}`}
        label={this.owner.formatItemDamageDone(this.damage)}
      />
    );
  }
  suggestions(when) {
    when(this.shouldHaveSaved).isGreaterThan(0)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(You should generally save <SpellLink id={SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id} /> when the boss has under 25% hp so that it is ready to use when the boss hits 20% and you can start getting <SpellLink id={SPELLS.BULLSEYE_TRAIT.id}/> quicker.)
          .icon(SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.icon)
          .actual(`You cast crows while boss ${formatPercentage(this.bossHP)}% HP.`)
          .recommended(`0 casts when boss has between 20 and 25% hp is recommended`)
          .average(recommended);
      });

  }
}

export default AMurderOfCrows;
<commit_msg>explanation for EXECUTE_PERCENT<commit_after>import React from 'react';

import Analyzer from 'Parser/Core/Analyzer';
import Combatants from 'Parser/Core/Modules/Combatants';

import SPELLS from 'common/SPELLS';
import StatisticBox from "Main/StatisticBox";
import SpellIcon from "common/SpellIcon";
import { formatNumber } from "common/format";
import SpellLink from "common/SpellLink";
import { formatPercentage } from "../../../../../common/format";

//generally accepted rule is to save crows if boss is below 25% health.
const CROWS_SAVE_PERCENT = 0.25;
//when we enter execute and Bullseye starts racking up
const EXECUTE_PERCENT = 0.2;

class AMurderOfCrows extends Analyzer {

  static dependencies = {
    combatants: Combatants,
  };

  bossIDs = [];
  damage = 0;
  shouldHaveSaved = 0;
  bossHP = 0;

  on_initialized() {
    this.active = this.combatants.selected.hasTalent(SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id);
    this.owner.report.enemies.forEach(enemy => {
      enemy.fights.forEach(fight => {
        if (fight.id === this.owner.fight.id && enemy.type === "Boss") this.bossIDs.push(enemy.id);
      });
    });
  }

  on_byPlayer_damage(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.A_MURDER_OF_CROWS_SPELL.id) {
      return;
    }
    this.damage += event.amount;
  }

  on_byPlayer_cast(event) {
    const spellId = event.ability.guid;
    if (spellId !== SPELLS.A_MURDER_OF_CROWS_SPELL.id) {
      return;
    }
    if (event.maxHitPoints && this.bossIDs.includes(event.targetID)) {
      if ((event.hitPoints / event.maxHitPoints) <= CROWS_SAVE_PERCENT && (event.hitPoints / event.maxHitPoints) > EXECUTE_PERCENT) {
        this.shouldHaveSaved += 1;
        this.bossHP = (event.hitPoints/event.maxHitPoints);
      }
    }
  }

  statistic() {
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id} />}
        value={`${formatNumber(this.damage)}`}
        label={this.owner.formatItemDamageDone(this.damage)}
      />
    );
  }
  suggestions(when) {
    when(this.shouldHaveSaved).isGreaterThan(0)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(You should generally save <SpellLink id={SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.id} /> when the boss has under 25% hp so that it is ready to use when the boss hits 20% and you can start getting <SpellLink id={SPELLS.BULLSEYE_TRAIT.id}/> quicker.)
          .icon(SPELLS.A_MURDER_OF_CROWS_TALENT_SHARED.icon)
          .actual(`You cast crows while boss ${formatPercentage(this.bossHP)}% HP.`)
          .recommended(`0 casts when boss has between 20 and 25% hp is recommended`)
          .average(recommended);
      });

  }
}

export default AMurderOfCrows;
<|endoftext|>


<commit_before>require("./etg.client").loadcards(function(Cards, CardCodes, Targeting) {
	var credits = [
		[["andretimpa", "http://andretimpa.deviantart.com"], ["Element Icons", "assets/esheet.png"], ["Rarity Icons", "assets/raritysheet.png"],
			["4sa", "4si", "4sk", "4sl", "4sn", "4so", "4sp", "4sq", "4ss", "4st", "4su", "4vr", "55s", "6ub"]],
		[["jarozaoz", "http://elementscommunity.org/forum/profile/?u=6364"],
			["4t3", "4vn", "500", "532", "52s", "534", "55k", "568", "59c", "5ff", "5fk", "5fe", "5io", "5le", "5lf", "5ls", "5la", "5li", "5oq", "5op", "5rh", "5s4", "5v8", "62c"]],
		[["mega plini", "http://elementscommunity.org/forum/profile/?u=202"], ["5ig"]],
		[["OdinVanguard", "http://elementscommunity.org/forum/profile/?u=232"],
			["4se", "4sf", "4t4", "4td", "4vf", "4vk", "4vo", "52i", "52m", "52o", "52p", "530", "55t", "565", "594", "5c5", "5cr", "5cs", "5f4", "5f6", "5fg", "5fj", "5i8", "5lq", "5oi", "5on", "5or", "5ri", "5ru", "5s1", "5up",
			"5ur", "5us", "5v0", "5v1", "61u", "623", "628", "629", "716"]],
		[["pepokish", "http://theowlettenest.com"],
			["52g", "58o", "5bv", "5f0", "5i4", "5ie", "5l8", "5lb", "5oj"]],
		[["Ravizant", "http://elementscommunity.org/forum/profile/?u=8037"], ["Element Icons", "assets/esheet.png"], ["Card Backgrounds", "assets/backsheet.png"],
			["4sc", "4vi", "4vj", "4vq", "4vt", "4vu", "50a", "50u", "53e", "542", "55q", "55v", "564", "56i", "576", "58q", "599", "59m", "5c3", "5cb", "5ce", "5cf", "5cg", "5cq", "5de", "5f3", "5fd", "5fi", "5fu", "5gi", "5ia",
			"5im", "5j2", "5jm", "5l9", "5m6", "5mq", "5ou", "5pa", "5pu", "5rq", "5se", "5t2", "5uq", "5uu", "5vi", "61p", "61q", "61v", "620", "627", "62m", "6u3", "77a", "77p", "7au", "7av", "7dj", "7e2", "7qa", "7ta", "809", "80a"]],
		[["serprex", "http://fiction.wikia.com/wiki/User:Serprex"], ["622", "62a"]],
		[["vrt", "http://vrt-designs.com"], ["Donation thread", "http://elementscommunity.org/forum/card-art/help-support-an-artist"],
			["4vc", "4ve", "4vh", "52t", "55l", "55o", "55r", "560", "562", "563", "591", "5c1", "5c9", "5f2", "5fc", "5i5", "5i7", "5ij", "5ll", "5oc", "5rk", "5rs", "5rt", "5uk", "5ul", "5um", "5ut", "5uv", "5v3", "61o", "61t", "624", "625", "626"]],
		[["willowdream", "http://willowdream.carbonmade.com"], ["Status Icons", "assets/statussheet.png"], ["Status Borders", "assets/statusborders.png"],
			["5fb", "5ib", "5il", "5iq", "5od", "5oe", "5ok", "5om", "5oo", "5os", "5p0", "5uo", "5v2", "7gr", "7h5"]],
	];
	var str = "
";
	for(var i=0; i<credits.length; i++){
		var credit = credits[i];
		for(var j=0; j<credit.length-1; j++){
			str += ""+credit[j][0]+" ";
		}
		var codes = credit[credit.length-1];
		if (codes.length){
			codes.sort();
			str += "

";
			for(var j=0; j<codes.length; j++){
				var code=codes[j];
				str += ""+CardCodes[code].name+"	";
				if ((j&7)==7)str += "

";
			}
			str += "

";
		}else str += "

";
	}
	document.getElementById("codecreds").innerHTML = str;
});<commit_msg>Added Orochi art<commit_after>require("./etg.client").loadcards(function(Cards, CardCodes, Targeting) {
	var credits = [
		[["andretimpa", "http://andretimpa.deviantart.com"], ["Element Icons", "assets/esheet.png"], ["Rarity Icons", "assets/raritysheet.png"],
			["4sa", "4si", "4sk", "4sl", "4sn", "4so", "4sp", "4sq", "4ss", "4st", "4su", "4vr", "55s", "6ub"]],
		[["Drake_XIV", "http://elementscommunity.org/forum/profile/?u=107"],
			["59b"]],
		[["jarozaoz", "http://elementscommunity.org/forum/profile/?u=6364"],
			["4t3", "4vn", "500", "532", "52s", "534", "55k", "568", "59c", "5ff", "5fk", "5fe", "5io", "5le", "5lf", "5ls", "5la", "5li", "5oq", "5op", "5rh", "5s4", "5v8", "62c"]],
		[["mega plini", "http://elementscommunity.org/forum/profile/?u=202"], ["5ig"]],
		[["OdinVanguard", "http://elementscommunity.org/forum/profile/?u=232"],
			["4se", "4sf", "4t4", "4td", "4vf", "4vk", "4vo", "52i", "52m", "52o", "52p", "530", "55t", "565", "594", "5c5", "5cr", "5cs", "5f4", "5f6", "5fg", "5fj", "5i8", "5lq", "5oi", "5on", "5or", "5ri", "5ru", "5s1", "5up",
			"5ur", "5us", "5v0", "5v1", "61u", "623", "628", "629", "716"]],
		[["pepokish", "http://theowlettenest.com"],
			["52g", "58o", "5bv", "5f0", "5i4", "5ie", "5l8", "5lb", "5oj"]],
		[["Ravizant", "http://elementscommunity.org/forum/profile/?u=8037"], ["Element Icons", "assets/esheet.png"], ["Card Backgrounds", "assets/backsheet.png"],
			["4sc", "4vi", "4vj", "4vq", "4vt", "4vu", "50a", "50u", "53e", "542", "55q", "55v", "564", "56i", "576", "58q", "599", "59m", "5c3", "5cb", "5ce", "5cf", "5cg", "5cq", "5de", "5f3", "5fd", "5fi", "5fu", "5gi", "5ia",
			"5im", "5j2", "5jm", "5l9", "5m6", "5mq", "5ou", "5pa", "5pu", "5rq", "5se", "5t2", "5uq", "5uu", "5vi", "61p", "61q", "61v", "620", "627", "62m", "6u3", "77a", "77p", "7au", "7av", "7dj", "7e2", "7qa", "7ta", "809", "80a"]],
		[["serprex", "http://fiction.wikia.com/wiki/User:Serprex"], ["622", "62a"]],
		[["vrt", "http://vrt-designs.com"], ["Donation thread", "http://elementscommunity.org/forum/card-art/help-support-an-artist"],
			["4vc", "4ve", "4vh", "52t", "55l", "55o", "55r", "560", "562", "563", "591", "5c1", "5c9", "5f2", "5fc", "5i5", "5i7", "5ij", "5ll", "5oc", "5rk", "5rs", "5rt", "5uk", "5ul", "5um", "5ut", "5uv", "5v3", "61o", "61t", "624", "625", "626"]],
		[["willowdream", "http://willowdream.carbonmade.com"], ["Status Icons", "assets/statussheet.png"], ["Status Borders", "assets/statusborders.png"],
			["5fb", "5ib", "5il", "5iq", "5od", "5oe", "5ok", "5om", "5oo", "5os", "5p0", "5uo", "5v2", "7gr", "7h5"]],
	];
	var str = "
";
	for(var i=0; i<credits.length; i++){
		var credit = credits[i];
		for(var j=0; j<credit.length-1; j++){
			str += ""+credit[j][0]+" ";
		}
		var codes = credit[credit.length-1];
		if (codes.length){
			codes.sort();
			str += "

";
			for(var j=0; j<codes.length; j++){
				var code=codes[j];
				str += ""+CardCodes[code].name+"	";
				if ((j&7)==7)str += "

";
			}
			str += "

";
		}else str += "

";
	}
	document.getElementById("codecreds").innerHTML = str;
});<|endoftext|>


<commit_before>
uiModule.gis = function() {
  if (!(this instanceof uiModule.gis)) {
    return new uiModule.gis();
  }

  return this;
};

/**
 * Create a placeholder to display current layers
 *
 * @param rootId
 * @param heading
 */
uiModule.gis.createGisLayerList = function(rootId, heading) {
  var listRoot = document.getElementById(rootId);
  var itemRoot = document.createElement("div");
  itemRoot.setAttribute("class", "accordion-group");
  listRoot.appendChild(itemRoot);

  var itemHeading = document.createElement("div");
  itemHeading.setAttribute("class", "accordion-heading");
  itemRoot.appendChild(itemHeading);

  var itemCollection = document.createElement("a");
  itemCollection.setAttribute("class", "accordion-toggle");
  itemCollection.setAttribute("data-toggle", "collapse");
  itemCollection.setAttribute("data-parent", "#"+rootId);
  itemCollection.setAttribute("href", "#collapse-"+rootId);
  itemCollection.appendChild(document.createTextNode(heading));
  itemHeading.appendChild(itemCollection);

  var subItemsRoot = document.createElement("div");
  subItemsRoot.setAttribute("class", "accordion-body collapse in");
  subItemsRoot.setAttribute("id", "collapse-"+rootId);
  itemRoot.appendChild(subItemsRoot);
  var subItemsList = document.createElement("div");
  subItemsList.setAttribute("class", "accordion-inner");
  subItemsRoot.appendChild(subItemsList);

  var tableRoot = document.createElement("table");
  tableRoot.setAttribute("id", rootId + '-table');
  subItemsList.appendChild(tableRoot);

  var tbody = document.createElement("tbody");
  tableRoot.appendChild(tbody);
};

/**
 * Create a list of documents that could be added as a layer
 *
 * @param rootId
 * @param heading
 * @param layersRootId
 * @param data
 */
uiModule.gis.createGisDataList = function(rootId, heading, layersRootId, data, callback) {
  var listRoot = $(document.getElementById(rootId));

  var itemRoot = $(document.createElement('div'));
  itemRoot.attr('class', 'accordion-group');
  listRoot.append(itemRoot);

  var itemHeading = $(document.createElement('div'));
  itemHeading.attr('class', 'accordion-heading');
  itemRoot.append(itemHeading);

  var itemCollection = $(document.createElement('a'));
  itemCollection.attr('class', 'accordion-toggle');
  itemCollection.attr('data-toggle', 'collapse');
  itemCollection.attr('data-parent', '#'+rootId);
  itemCollection.attr('href', '#collapse-'+rootId);
  itemCollection.append(document.createTextNode(heading));
  itemHeading.append(itemCollection);

  var subItemsRoot = $(document.createElement('div'));
  subItemsRoot.attr('class', 'accordion-body collapse in');
  subItemsRoot.attr('id', 'collapse-'+rootId);
  itemRoot.append(subItemsRoot);

  var subItemsList = $(document.createElement('div'));
  subItemsList.attr('class', 'accordion-inner');
  subItemsRoot.append(subItemsList);

  var tableRoot = $(document.createElement('table'));
  tableRoot.attr('class', 'table-hover');
  subItemsList.append(tableRoot);

  $.each(data, function(i, item) {
    var row = $(document.createElement('tr'));
    row.attr('class', 'success');
    tableRoot.append(row);

    var col = $(document.createElement('td'));
    var he = $(document.createElement('h4'));
    he.html(item.basename);
    col.append(he);
    row.append(col);

    // Add drop-down so that users can select a variable
    col = $(document.createElement('td'));
    var select = $(document.createElement('select'));
    select.attr('class', 'combobox');
    col.append(select);
    for (var k = 0; k < item.variables.length; ++k) {
      var varname = item.variables[k].name;
      var option = $(document.createElement('option'));
      option.attr('value', varname);
      option.html(varname);
      select.append(option);
    }
    row.append(col);

    // Add 'add' button
    col = $(document.createElement('td'));
    var button = $(document.createElement('button'));
    button.attr('type', 'button');
    button.attr('class', 'btn btn-primary');
    button.attr('id', 'btn-add-' + item.name);
    button.attr('_id', item._id);
    button.attr('name', item.name);
    button.attr('basename', item.basename);
    button.attr('data-toggle', 'button');
    button.append($(document.createTextNode('Add')));
    col.append(button);
    row.append(col);

    if (callback != undefined) {
      $(button).on("click", callback);
    }
  });

  $('.combobox').width(Math.max.apply(Math,
                                       $('.combobox').map(function(){
                                         return $(this).outerWidth();}).get()));
  $('.combobox').select2();
};

/**
 * Add a document as a layer to the list
 *
 * @todo Actually load a layer
 *
 * @param layersRootId
 * @param elem
 */
uiModule.gis.addLayer = function(object, layersRootId, elem, selectfunc, togglefunc,
  removefunc, callback) {

  var rootId = "#" + layersRootId;
  var _id = $(elem).attr("_id");
  if (_id !== null) {
    var tbody = $(rootId).find('tbody');
    var basename = $(elem).attr("name");
    $(elem).removeClass("btn-primary");
    $(elem).addClass("btn-success");
    $(elem).addClass("disabled");
    var layerId = basename;

    var tr = $(document.createElement('tr'));
    tr.attr('id', layerId);
    $(tbody).append(tr);

    // Name of the layer
    var td = $(document.createElement('td'));
    td.append($(document.createElement('h4')).html(basename));
    tr.append(td);

    // Select button
    td = $(document.createElement('td'));
    td.attr('class', 'td-btn-layer');
    var button = $(document.createElement('button'));
    button.attr('class', 'btn-layer btn-select-layer btn btn-primary disabled');
    button.attr('disabled', 'disabled');
    button.html('Select');
    button.click(layerId, function() {
      selectfunc(this, layerId);
    });
    td.append(button);

    // Toggle button
    button = $(document.createElement('button'));
    button.attr('class', 'btn-layer btn-toggle-layer btn btn-warning disabled');
    button.attr('disabled', 'disabled');
    button.html('Toggle');
    button.click(layerId, function() {
      togglefunc(this, layerId);
    });
    td.append(button);

    // Remove button
    button = $(document.createElement('button'));
    button.attr('class', 'btn-layer btn-remove-layer btn btn-danger disabled');
    button.attr('disabled', 'disabled');
    button.html('Remove');
    button.click(layerId, function() {
      removefunc(this, layerId);
    });
    td.append(button);
    tr.append(td);

    $('.btn-layer').width(Math.max.apply(Math,
                                            $('.btn-layer').map(function(){
                                              return $(this).outerWidth();}).get()));
    }
    // @todo Just calling fadeIn does not work. It has to be set invisible
    // and then only it works. We need to check if this is expected.
    $('#'+layerId).fadeOut(0);
    $('#'+layerId).fadeIn('slow', callback);
};

/**
 * Remove a layer from the list
 *
 * @param elem
 * @returns {Boolean}
 */
uiModule.gis.removeLayer = function(elem, layerId) {
  var buttonId = $('#btn-add-'+layerId);
  var button = $(buttonId);

  if (button !== null || button !== undefined) {
    button.removeClass('disabled');
    button.removeClass('btn-success');
    button.removeClass('active');
    button.addClass('btn-primary');

    $('#' + layerId).fadeOut(function() {
      $('#' + layerId).remove();
    });

    return true;
  }

  return false;
};

uiModule.gis.toggleLayer = function(elem, layerId) {
  // Do nothing
};

/**
 * Update UI to when a select is selected
 */
uiModule.gis.selectLayer = function(target, layerId) {
  $('btn-select-layer').siblings().removeClass('active');
  $(target).addClass('active');
  return true;
};
<commit_msg>Work in progress to add view options to the control panel<commit_after>
uiModule.gis = function() {
  if (!(this instanceof uiModule.gis)) {
    return new uiModule.gis();
  }

  return this;
};

/**
 * Create a placeholder to display current layers
 *
 * @param rootId
 * @param heading
 */
uiModule.gis.createList = function(rootId, heading) {
  var listRoot = document.getElementById(rootId);
  var itemRoot = document.createElement("div");
  itemRoot.setAttribute("class", "accordion-group");
  listRoot.appendChild(itemRoot);

  var itemHeading = document.createElement("div");
  itemHeading.setAttribute("class", "accordion-heading");
  itemRoot.appendChild(itemHeading);

  var itemCollection = document.createElement("a");
  itemCollection.setAttribute("class", "accordion-toggle");
  itemCollection.setAttribute("data-toggle", "collapse");
  itemCollection.setAttribute("data-parent", "#"+rootId);
  itemCollection.setAttribute("href", "#collapse-"+rootId);
  itemCollection.appendChild(document.createTextNode(heading));
  itemHeading.appendChild(itemCollection);

  var subItemsRoot = document.createElement("div");
  subItemsRoot.setAttribute("class", "accordion-body collapse in");
  subItemsRoot.setAttribute("id", "collapse-"+rootId);
  itemRoot.appendChild(subItemsRoot);
  var subItemsList = document.createElement("div");
  subItemsList.setAttribute("class", "accordion-inner");
  subItemsRoot.appendChild(subItemsList);

  var tableRoot = document.createElement("table");
  tableRoot.setAttribute("id", rootId + '-table');
  subItemsList.appendChild(tableRoot);

  var tbody = document.createElement("tbody");
  tableRoot.appendChild(tbody);
};

/**
 * Create a list of documents that could be added as a layer
 *
 * @param rootId
 * @param heading
 * @param layersRootId
 * @param data
 */
uiModule.gis.createDataList = function(rootId, heading, layersRootId, data, callback) {
  var listRoot = $(document.getElementById(rootId));

  var itemRoot = $(document.createElement('div'));
  itemRoot.attr('class', 'accordion-group');
  listRoot.append(itemRoot);

  var itemHeading = $(document.createElement('div'));
  itemHeading.attr('class', 'accordion-heading');
  itemRoot.append(itemHeading);

  var itemCollection = $(document.createElement('a'));
  itemCollection.attr('class', 'accordion-toggle');
  itemCollection.attr('data-toggle', 'collapse');
  itemCollection.attr('data-parent', '#'+rootId);
  itemCollection.attr('href', '#collapse-'+rootId);
  itemCollection.append(document.createTextNode(heading));
  itemHeading.append(itemCollection);

  var subItemsRoot = $(document.createElement('div'));
  subItemsRoot.attr('class', 'accordion-body collapse in');
  subItemsRoot.attr('id', 'collapse-'+rootId);
  itemRoot.append(subItemsRoot);

  var subItemsList = $(document.createElement('div'));
  subItemsList.attr('class', 'accordion-inner');
  subItemsRoot.append(subItemsList);

  var tableRoot = $(document.createElement('table'));
  tableRoot.attr('class', 'table-hover');
  subItemsList.append(tableRoot);

  $.each(data, function(i, item) {
    var row = $(document.createElement('tr'));
    row.attr('class', 'success');
    tableRoot.append(row);

    var col = $(document.createElement('td'));
    var he = $(document.createElement('h4'));
    he.html(item.basename);
    col.append(he);
    row.append(col);

    // Add drop-down so that users can select a variable
    col = $(document.createElement('td'));
    var select = $(document.createElement('select'));
    select.attr('class', 'combobox');
    col.append(select);
    for (var k = 0; k < item.variables.length; ++k) {
      var varname = item.variables[k].name;
      var option = $(document.createElement('option'));
      option.attr('value', varname);
      option.html(varname);
      select.append(option);
    }
    row.append(col);

    // Add 'add' button
    col = $(document.createElement('td'));
    var button = $(document.createElement('button'));
    button.attr('type', 'button');
    button.attr('class', 'btn btn-primary');
    button.attr('id', 'btn-add-' + item.name);
    button.attr('_id', item._id);
    button.attr('name', item.name);
    button.attr('basename', item.basename);
    button.attr('data-toggle', 'button');
    button.append($(document.createTextNode('Add')));
    col.append(button);
    row.append(col);

    if (callback != undefined) {
      $(button).on("click", callback);
    }
  });

  $('.combobox').width(Math.max.apply(Math,
                                       $('.combobox').map(function(){
                                         return $(this).outerWidth();}).get()));
  $('.combobox').select2();
};

/**
 * Add a document as a layer to the list
 *
 * @todo Actually load a layer
 *
 * @param layersRootId
 * @param elem
 */
uiModule.gis.addLayer = function(object, layersRootId, elem, selectfunc, togglefunc,
  removefunc, callback) {

  var rootId = "#" + layersRootId;
  var _id = $(elem).attr("_id");
  if (_id !== null) {
    var tbody = $(rootId).find('tbody');
    var basename = $(elem).attr("name");
    $(elem).removeClass("btn-primary");
    $(elem).addClass("btn-success");
    $(elem).addClass("disabled");
    var layerId = basename;

    var tr = $(document.createElement('tr'));
    tr.attr('id', layerId);
    $(tbody).append(tr);

    // Name of the layer
    var td = $(document.createElement('td'));
    td.append($(document.createElement('h4')).html(basename));
    tr.append(td);

    // Select button
    td = $(document.createElement('td'));
    td.attr('class', 'td-btn-layer');
    var button = $(document.createElement('button'));
    button.attr('class', 'btn-layer btn-select-layer btn btn-primary disabled');
    button.attr('disabled', 'disabled');
    button.html('Select');
    button.click(layerId, function() {
      selectfunc(this, layerId);
    });
    td.append(button);

    // Toggle button
    button = $(document.createElement('button'));
    button.attr('class', 'btn-layer btn-toggle-layer btn btn-warning disabled');
    button.attr('disabled', 'disabled');
    button.html('Toggle');
    button.click(layerId, function() {
      togglefunc(this, layedId);
    });
    td.append(button);

    // Remove button
    button = $(document.createElement('button'));
    button.attr('class', 'btn-layer btn-remove-layer btn btn-danger disabled');
    button.attr('disabled', 'disabled');
    button.html('Remove');
    button.click(layerId, function() {
      removefunc(this, layedId);
    });
    td.append(button);
    tr.append(td);

    $('.btn-layer').width(Math.max.apply(Math,
                                            $('.btn-layer').map(function(){
                                              return $(this).outerWidth();}).get()));
    }
    // @todo Just calling fadeIn does not work. It has to be set invisible
    // and then only it works. We need to check if this is expected.
    $('#'+layerId).fadeOut(0);
    $('#'+layerId).fadeIn('slow', callback);
};

/**
 * Remove a layer from the list
 *
 * @param elem
 * @returns {Boolean}
 */
uiModule.gis.removeLayer = function(elem, layerId) {
  var buttonId = $('#btn-add-'+layerId);
  var button = $(buttonId);

  if (button !== null || button !== undefined) {
    button.removeClass('disabled');
    button.removeClass('btn-success');
    button.removeClass('active');
    button.addClass('btn-primary');

    $('#' + layerId).fadeOut(function() {
      $('#' + layerId).remove();
    });

    return true;
  }

  return false;
};

uiModule.gis.toggleLayer = function(elem, layerId) {
  // Do nothing
};

/**
 * Update UI to when a select is selected
 */
uiModule.gis.selectLayer = function(target, layerId) {
  $('btn-select-layer').siblings().removeClass('active');
  $(target).addClass('active');
  return true;
};
<|endoftext|>


<commit_before>/*
    update.js <https://github.com/davidfig/update>
    License: MIT license <https://github.com/davidfig/update/license>
    Author: David Figatner
    Copyright (c) 2016 YOPEY YOPEY LLC
*/ (function(){

var list = [];
var FPS = 60;
var FPSActual;
var pause = false;
var pauseRegistrations = [];
var pauseElapsed = 0;

var startTime = 0;
var frameNumber = 0;
var lastUpdate = 0;
var lastFPS = '--';

var tolerance = 1;

var maxChange = 100;

var panels = {fps: null, meter: null, percent: null};
var percentageList = [];

// this creates a rolling average using 500 entries to smooth the debug percentages
var rollingAverage = 500;

// initialize Update
function init()
{
    checkVisibility();
    FPSActual = 1000 / FPS;
    if (Debug)
    {
        debugInit();
    }
}

// register functions to call after Update pauses or resumes
function registerPause(pause, resume)
{
    pauseRegistrations.push({ pause: pause, resume: resume});
}

function pauseGame()
{
    if (pause !== true)
    {
        pause = true;
        pauseElapsed = performance.now() - lastUpdate;
        updateOff = false;
        if (Debug)
        {
            debugPause();
        }
        for (var i = 0; i < pauseRegistrations.length; i++)
        {
            pauseRegistrations[i].pause();
        }
    }
}

function resumeGame()
{
    if (pause === true)
    {
        pause = false;
        lastUpdate = performance.now() - pauseElapsed;
        fpsReset();
        for (var i = 0; i < pauseRegistrations.length; i++)
        {
            pauseRegistrations[i].resume();
        }
        if (updateOff === true)
        {
            update();
        }
    }
}

function fpsReset()
{
    startTime = 0;
    frameNumber = 0;
    lastUpdate = 0;
    lastFPS = '--';
}

function updateOther(elapsed)
{
    var i = 0, _i = list.length;
    var total = 0, other = 0, updates = [];
    while (i < _i)
    {
        var update = list[i++];
        if (update.pause)
        {
            continue;
        }
        if (update.duration !== 0)
        {
            update.elapsed += elapsed;
            if (update.elapsed < update.duration)
            {
                if (update.params.percent)
                {
                    var change = percentageList[update.params.percent];
                    change.amounts[change.current++] = 0;
                    if (change.current === rollingAverage)
                    {
                        change.current = 0;
                    }
                }
                continue;
            }
            else
            {
                update.elapsed = 0;
            }
        }
        var start;
        if (Debug && panels.percent)
        {
            start = performance.now();
            update.callback(elapsed, update.params);
            current = performance.now() - start;
            if (update.params.percent)
            {
                var change = percentageList[update.params.percent];
                change.amounts[change.current++] = current;
                if (change.current === rollingAverage)
                {
                    change.current = 0;
                }
            }
            else
            {
                other += current;
            }
            total += current;
        }
        else
        {
            update.callback(elapsed, update.params);
        }
        if (update.once)
        {
            i--;
            _i--;
            list.splice(i, 1);
        }
    }
    if (Debug && panels.percent)
    {
        debugPercent(other);
    }
}

// Add a function to the update loop with a desired FPS
// params:
//   once: true or false -- call function only once
//   percent: track percentages in a davidfig/Debug panel
function addFPS(funct, fps, params)
{
    return add(funct, 1000 / fps, params);
}

// Add a function to the update loop ever time MS
// params:
//   once: true or false -- call function only once
//   percent: track percentages in a davidfig/Debug panel
function add(funct, time, params)
{
    time = time || 0;
    params = params || {};
    var update = {callback: funct, params: params, duration: time, elapsed: 0, once: params.once, pause: false};
    list.push(update);
    if (Debug && params.percent)
    {
        if (!Debug.get('percentages'))
        {
            panels.percent = Debug.add('percentages', {style: {textAlign: 'right'}});
            percentageList['Other'] = {current: 0, amounts: []};
        }
        percentageList[params.percent] = {current: 0, amounts: []};
        var test = '';
        for (var key in percentageList)
        {
            test += key + ': --%
';
        }
        test += 'Other: --';
        debugOne(test, {name: 'percentages'});
        Debug.resize();
    }
    return update;
}

// removes an update from the loop
// update is the object returned by Update.add() or Update.addFPS()
function remove(update)
{
    for (var i = 0, _i = list.length; i < _i; i++)
    {
        if (list[i] === update)
        {
            list.splice(i, 1);
            return;
        }
    }
}

// starts the update loop
function update()
{
    if (pause === true)
    {
        updateOff = true;
        return;
    }
    var current = performance.now();
    var elapsed;
    if (lastUpdate === 0)
    {
        elapsed = 0;
    }
    else
    {
        elapsed = current - lastUpdate;
        elapsed = elapsed > maxChange ? maxChange : elapsed;
    }
    if (FPS === 60 || elapsed === 0 || elapsed >= FPSActual)
    {
        if (list.length)
        {
            updateOther(elapsed);
        }
        lastUpdate = current;
        if (Debug)
        {
            debugUpdate(current);
        }
    }
    if (typeof requestAnimationFrame === 'function')
    {
        requestAnimationFrame(update);
    }
    else
    {
        setTimeout(update, FPSActual);
    }
}

//
// Starts a page visibility event listener running, or window.onpagehide/onpageshow if not supported by the browser.
// Also listens for window.onblur and window.onfocus.
// based on Phaser: https://github.com/photonstorm/phaser (MIT license)
function checkVisibility()
{
    var hiddenVar;
    if (document.webkitHidden !== undefined)
    {
        hiddenVar = 'webkitvisibilitychange';
    }
    else if (document.mozHidden !== undefined)
    {
        hiddenVar = 'mozvisibilitychange';
    }
    else if (document.msHidden !== undefined)
    {
        hiddenVar = 'msvisibilitychange';
    }
    else if (document.hidden !== undefined)
    {
        hiddenVar = 'visibilitychange';
    }
    else
    {
        hiddenVar = null;
    }

    //  Does browser support it? If not (like in IE9 or old Android) we need to fall back to blur/focus
    if (hiddenVar)
    {
        document.addEventListener(hiddenVar, visibilityChange, false);
    }

    window.onblur = visibilityChange;
    window.onfocus = visibilityChange;

    window.onpagehide = visibilityChange;
    window.onpageshow = visibilityChange;
}

// Callback for checkVisibility()
// based on Phaser: https://github.com/photonstorm/phaser (MIT license)
function visibilityChange(event)
{
    if (event.type === 'pagehide' || event.type === 'blur' || event.type === 'pageshow' || event.type === 'focus')
    {
        if (event.type === 'pagehide' || event.type === 'blur')
        {
            pauseGame();
        }
        else if (event.type === 'pageshow' || event.type === 'focus')
        {
            resumeGame(event);
        }
        return;
    }

    if (document.hidden || document.mozHidden || document.msHidden || document.webkitHidden || event.type === "pause")
    {
        pauseGame(event);
    }
}

// add an FPS panel and meter
function debugInit()
{
    panels.fps = Debug.add('FPS', {text: '-- FPS'});
    panels.meter = Debug.addMeter('panel');
}

function debugPause()
{
    debugOne('-- FPS', {panel: panels.fps});
}

function debugUpdate(current)
{
    frameNumber++;
    var currentTime = performance.now() - startTime;

    // skip the first half second to get rid of garbage
    if (currentTime > 500)
    {
        if (startTime !== 0)
        {
            lastFPS = Math.floor(frameNumber / (currentTime / 1000));
            if (lastFPS >= 60 - tolerance && lastFPS <= 60 + tolerance)
            {
                lastFPS = 60;
            }
        }
        startTime = performance.now();
        frameNumber = 0;
    }
    debugOne(lastFPS + ' FPS', {panel: panels.fps});
    var time = performance.now() - current;

// TODO: 16.7 is hard coded for 60 FPS. Probably should figure out the real amount for the current FPS
    var expected = (16.7 - time) / 16.7;
    Debug.meter(expected, {panel: panels.meter});
}

function debugPercent(other)
{
    var change = percentageList['Other'];
    change.amounts[change.current++] = other;
    change.current %= rollingAverage;
    var updates = [], all = 0;
    for (var name in percentageList)
    {
        var change = percentageList[name];
        var total = 0;
        for (var i = 0; i < change.amounts.length; i++)
        {
            total += change.amounts[i];
        }
        total /= change.amounts.length;
        all += total;
        updates.push({name: name, total: total});
    }
    var result = '';
    for (var i = 1; i < updates.length; i++)
    {
        var update = updates[i];
        result += update.name + ': ' + Math.round(update.total / all * 100) + '%
';
    }
    var update = updates[0];
    result += update.name + ': ' + Math.round(update.total / all * 100) + '%
';
    debugOne(result, {panel: panels.percent});
}

// exports
var Update = {
    init: init,
    add: add,
    update: update,
    remove: remove,
    registerPause: registerPause,
    pauseGame: pauseGame,
    resumeGame: resumeGame
};

// add support for AMD (Asynchronous Module Definition) libraries such as require.js.
if (typeof define === 'function' && define.amd)
{
    define(function()
    {
        return {
            Update: Update
        };
    });
}

// add support for CommonJS libraries such as browserify.
if (typeof exports !== 'undefined')
{
    exports.Update = Update;
}

// define globally in case AMD is not available or available but not used
if (typeof window !== 'undefined')
{
    window.Update = Update;
} })();<commit_msg>Added debug panel for number of update calls<commit_after>/*
    update.js <https://github.com/davidfig/update>
    License: MIT license <https://github.com/davidfig/update/license>
    Author: David Figatner
    Copyright (c) 2016 YOPEY YOPEY LLC
*/ (function(){

var list = [];
var FPS = 60;
var FPSActual;
var pause = false;
var pauseRegistrations = [];
var pauseElapsed = 0;

var startTime = 0;
var frameNumber = 0;
var lastUpdate = 0;
var lastFPS = '--';

var tolerance = 1;

var maxChange = 100;

var panels = {fps: null, meter: null, percent: null};
var percentageList = [];
var lastCount;

// this creates a rolling average using 500 entries to smooth the debug percentages
var rollingAverage = 500;

// initialize Update
function init()
{
    checkVisibility();
    FPSActual = 1000 / FPS;
    if (Debug)
    {
        debugInit();
    }
}

// register functions to call after Update pauses or resumes
function registerPause(pause, resume)
{
    pauseRegistrations.push({ pause: pause, resume: resume});
}

function pauseGame()
{
    if (pause !== true)
    {
        pause = true;
        pauseElapsed = performance.now() - lastUpdate;
        updateOff = false;
        if (Debug)
        {
            debugPause();
        }
        for (var i = 0; i < pauseRegistrations.length; i++)
        {
            pauseRegistrations[i].pause();
        }
    }
}

function resumeGame()
{
    if (pause === true)
    {
        pause = false;
        lastUpdate = performance.now() - pauseElapsed;
        fpsReset();
        for (var i = 0; i < pauseRegistrations.length; i++)
        {
            pauseRegistrations[i].resume();
        }
        if (updateOff === true)
        {
            update();
        }
    }
}

function fpsReset()
{
    startTime = 0;
    frameNumber = 0;
    lastUpdate = 0;
    lastFPS = '--';
}

function updateOther(elapsed)
{
    var i = 0, _i = list.length;
    var total = 0, other = 0, updates = [], count = 0;
    while (i < _i)
    {
        var update = list[i++];
        if (update.pause)
        {
            continue;
        }
        if (update.duration !== 0)
        {
            update.elapsed += elapsed;
            if (update.elapsed < update.duration)
            {
                if (update.params.percent)
                {
                    var change = percentageList[update.params.percent];
                    change.amounts[change.current++] = 0;
                    if (change.current === rollingAverage)
                    {
                        change.current = 0;
                    }
                }
                continue;
            }
            else
            {
                update.elapsed = 0;
            }
        }
        var start, result;
        if (Debug && panels.percent)
        {
            start = performance.now();
            result = update.callback(elapsed, update.params);
            current = performance.now() - start;
            if (update.params.percent)
            {
                var change = percentageList[update.params.percent];
                change.amounts[change.current++] = current;
                if (change.current === rollingAverage)
                {
                    change.current = 0;
                }
            }
            else
            {
                other += current;
            }
            total += current;
        }
        else
        {
            result = update.callback(elapsed, update.params);
        }
        if (update.once || result)
        {
            i--;
            _i--;
            list.splice(i, 1);
        }
        count++;
    }
    if (Debug)
    {
        if (lastCount !== count)
        {
            debugOne(count + ' updates', {panel: panels.count});
            lastCount = count;
        }
        if (panels.percent)
        {
            debugPercent(other);
        }
    }
}

// Add a function to the update loop with a desired FPS
// params:
//   once: true or false -- call function only once
//   percent: track percentages in a davidfig/Debug panel
function addFPS(funct, fps, params)
{
    return add(funct, 1000 / fps, params);
}

// Add a function to the update loop ever time MS
// params:
//   once: true or false -- call function only once
//   percent: track percentages in a davidfig/Debug panel
function add(funct, time, params)
{
    time = time || 0;
    params = params || {};
    var update = {callback: funct, params: params, duration: time, elapsed: 0, once: params.once, pause: false};
    list.push(update);
    if (Debug && params.percent)
    {
        if (!Debug.get('percentages'))
        {
            panels.percent = Debug.add('percentages', {style: {textAlign: 'right'}});
            percentageList['Other'] = {current: 0, amounts: []};
        }
        percentageList[params.percent] = {current: 0, amounts: []};
        var test = '';
        for (var key in percentageList)
        {
            test += key + ': --%
';
        }
        test += 'Other: --';
        debugOne(test, {name: 'percentages'});
        Debug.resize();
    }
    return update;
}

// removes an update from the loop
// update is the object returned by Update.add() or Update.addFPS()
function remove(update)
{
    for (var i = 0, _i = list.length; i < _i; i++)
    {
        if (list[i] === update)
        {
            list.splice(i, 1);
            return;
        }
    }
}

// starts the update loop
function update()
{
    if (pause === true)
    {
        updateOff = true;
        return;
    }
    var current = performance.now();
    var elapsed;
    if (lastUpdate === 0)
    {
        elapsed = 0;
    }
    else
    {
        elapsed = current - lastUpdate;
        elapsed = elapsed > maxChange ? maxChange : elapsed;
    }
    if (FPS === 60 || elapsed === 0 || elapsed >= FPSActual)
    {
        if (list.length)
        {
            updateOther(elapsed);
        }
        lastUpdate = current;
        if (Debug)
        {
            debugUpdate(current);
        }
    }
    if (typeof requestAnimationFrame === 'function')
    {
        requestAnimationFrame(update);
    }
    else
    {
        setTimeout(update, FPSActual);
    }
}

//
// Starts a page visibility event listener running, or window.onpagehide/onpageshow if not supported by the browser.
// Also listens for window.onblur and window.onfocus.
// based on Phaser: https://github.com/photonstorm/phaser (MIT license)
function checkVisibility()
{
    var hiddenVar;
    if (document.webkitHidden !== undefined)
    {
        hiddenVar = 'webkitvisibilitychange';
    }
    else if (document.mozHidden !== undefined)
    {
        hiddenVar = 'mozvisibilitychange';
    }
    else if (document.msHidden !== undefined)
    {
        hiddenVar = 'msvisibilitychange';
    }
    else if (document.hidden !== undefined)
    {
        hiddenVar = 'visibilitychange';
    }
    else
    {
        hiddenVar = null;
    }

    //  Does browser support it? If not (like in IE9 or old Android) we need to fall back to blur/focus
    if (hiddenVar)
    {
        document.addEventListener(hiddenVar, visibilityChange, false);
    }

    window.onblur = visibilityChange;
    window.onfocus = visibilityChange;

    window.onpagehide = visibilityChange;
    window.onpageshow = visibilityChange;
}

// Callback for checkVisibility()
// based on Phaser: https://github.com/photonstorm/phaser (MIT license)
function visibilityChange(event)
{
    if (event.type === 'pagehide' || event.type === 'blur' || event.type === 'pageshow' || event.type === 'focus')
    {
        if (event.type === 'pagehide' || event.type === 'blur')
        {
            pauseGame();
        }
        else if (event.type === 'pageshow' || event.type === 'focus')
        {
            resumeGame(event);
        }
        return;
    }

    if (document.hidden || document.mozHidden || document.msHidden || document.webkitHidden || event.type === "pause")
    {
        pauseGame(event);
    }
}

// add an FPS panel and meter
function debugInit()
{
    panels.fps = Debug.add('FPS', {text: '-- FPS'});
    panels.meter = Debug.addMeter('panel');
    panels.count = Debug.add('Updates', {text: '0 updates'});
}

function debugPause()
{
    debugOne('-- FPS', {panel: panels.fps});
}

function debugUpdate(current)
{
    frameNumber++;
    var currentTime = performance.now() - startTime;

    // skip the first half second to get rid of garbage
    if (currentTime > 500)
    {
        if (startTime !== 0)
        {
            lastFPS = Math.floor(frameNumber / (currentTime / 1000));
            if (lastFPS >= 60 - tolerance && lastFPS <= 60 + tolerance)
            {
                lastFPS = 60;
            }
        }
        startTime = performance.now();
        frameNumber = 0;
    }
    debugOne(lastFPS + ' FPS', {panel: panels.fps});
    var time = performance.now() - current;

// TODO: 16.7 is hard coded for 60 FPS. Probably should figure out the real amount for the current FPS
    var expected = (16.7 - time) / 16.7;
    Debug.meter(expected, {panel: panels.meter});
}

function debugPercent(other)
{
    var change = percentageList['Other'];
    change.amounts[change.current++] = other;
    change.current %= rollingAverage;
    var updates = [], all = 0;
    for (var name in percentageList)
    {
        var change = percentageList[name];
        var total = 0;
        for (var i = 0; i < change.amounts.length; i++)
        {
            total += change.amounts[i];
        }
        total /= change.amounts.length;
        all += total;
        updates.push({name: name, total: total});
    }
    var result = '';
    for (var i = 1; i < updates.length; i++)
    {
        var update = updates[i];
        result += update.name + ': ' + Math.round(update.total / all * 100) + '%
';
    }
    var update = updates[0];
    result += update.name + ': ' + Math.round(update.total / all * 100) + '%
';
    debugOne(result, {panel: panels.percent});
}

// exports
var Update = {
    init: init,
    add: add,
    update: update,
    remove: remove,
    registerPause: registerPause,
    pauseGame: pauseGame,
    resumeGame: resumeGame
};

// add support for AMD (Asynchronous Module Definition) libraries such as require.js.
if (typeof define === 'function' && define.amd)
{
    define(function()
    {
        return {
            Update: Update
        };
    });
}

// add support for CommonJS libraries such as browserify.
if (typeof exports !== 'undefined')
{
    exports.Update = Update;
}

// define globally in case AMD is not available or available but not used
if (typeof window !== 'undefined')
{
    window.Update = Update;
} })();<|endoftext|>


<commit_before>import { encode as buildQueryString } from 'querystring';
import merge from 'lodash/merge';
import spinner from './spinner';
import redraw from './utils/redraw';

export const apiVersion = 2;

const baseUrl = window.lichess.apiEndPoint;

export function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  } else {
    const error = new Error(response.statusText);
    error.response = response;
    throw error;
  }
}

export function parseJSON(response) {
  return response.json();
}

function addQuerystring(url, querystring) {
  const prefix = url.indexOf('?') < 0 ? '?' : '&';
  let res = url + prefix + querystring;
  return res;
}

function request(url, opts, feedback, uncache) {

  function onSuccess(data) {
    if (feedback) spinner.stop();
    redraw();
    return data;
  }

  function onError(error) {
    console.error(error);
    if (feedback) spinner.stop();
    redraw();
    throw error;
  }

  const cfg = {
    method: 'GET',
    credentials: 'include',
    headers: {
      'X-Requested-With': 'XMLHttpRequest',
      'Accept': 'application/vnd.lichess.v' + apiVersion + '+json'
    }
  };

  merge(cfg, opts);

  // by default POST and PUT send json except if defined otherwise in caller
  if ((cfg.method === 'POST' || cfg.method === 'PUT') &&
    cfg.headers['Content-Type'] === undefined
  ) {
    cfg.headers['Content-Type'] = 'application/json; charset=UTF-8';
  }

  if (uncache) {
    url = addQuerystring(url, `_=${Date.now()}`);
  }

  if (opts && opts.query) {
    const query = buildQueryString(opts.query);
    if (query !== '') {
      url = addQuerystring(url, query);
    }
  }

  const fullUrl = url.indexOf('http') > -1 ? url : baseUrl + url;

  const promise = Promise.race([
    fetch(fullUrl, cfg),
    new Promise((resolve, reject) =>
      setTimeout(() => reject(new Error('Request timeout.')), 8000)
    )
  ]);

  if (feedback) {
    spinner.spin(document.body);
  }

  return promise
    .then(checkStatus)
    .then(onSuccess)
    .catch(onError);
}

export function fetchJSON(url, opts, feedback, uncache) {
  return request(url, opts, feedback, uncache)
  .then(parseJSON);
}

export function fetchText(url, opts, feedback, uncache) {
  return request(url, opts, feedback, uncache)
  .then(r => r.text());
}
<commit_msg>Remove useless console.error<commit_after>import { encode as buildQueryString } from 'querystring';
import merge from 'lodash/merge';
import spinner from './spinner';
import redraw from './utils/redraw';

export const apiVersion = 2;

const baseUrl = window.lichess.apiEndPoint;

export function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  } else {
    const error = new Error(response.statusText);
    error.response = response;
    throw error;
  }
}

export function parseJSON(response) {
  return response.json();
}

function addQuerystring(url, querystring) {
  const prefix = url.indexOf('?') < 0 ? '?' : '&';
  let res = url + prefix + querystring;
  return res;
}

function request(url, opts, feedback, uncache) {

  function onSuccess(data) {
    if (feedback) spinner.stop();
    redraw();
    return data;
  }

  function onError(error) {
    if (feedback) spinner.stop();
    redraw();
    throw error;
  }

  const cfg = {
    method: 'GET',
    credentials: 'include',
    headers: {
      'X-Requested-With': 'XMLHttpRequest',
      'Accept': 'application/vnd.lichess.v' + apiVersion + '+json'
    }
  };

  merge(cfg, opts);

  // by default POST and PUT send json except if defined otherwise in caller
  if ((cfg.method === 'POST' || cfg.method === 'PUT') &&
    cfg.headers['Content-Type'] === undefined
  ) {
    cfg.headers['Content-Type'] = 'application/json; charset=UTF-8';
  }

  if (uncache) {
    url = addQuerystring(url, `_=${Date.now()}`);
  }

  if (opts && opts.query) {
    const query = buildQueryString(opts.query);
    if (query !== '') {
      url = addQuerystring(url, query);
    }
  }

  const fullUrl = url.indexOf('http') > -1 ? url : baseUrl + url;

  const promise = Promise.race([
    fetch(fullUrl, cfg),
    new Promise((resolve, reject) =>
      setTimeout(() => reject(new Error('Request timeout.')), 8000)
    )
  ]);

  if (feedback) {
    spinner.spin(document.body);
  }

  return promise
    .then(checkStatus)
    .then(onSuccess)
    .catch(onError);
}

export function fetchJSON(url, opts, feedback, uncache) {
  return request(url, opts, feedback, uncache)
  .then(parseJSON);
}

export function fetchText(url, opts, feedback, uncache) {
  return request(url, opts, feedback, uncache)
  .then(r => r.text());
}
<|endoftext|>


<commit_before>/**
 * A simple component that shows the spell value in the most plain way possible.
 * Use this only as the very last resort.
 */
import React from 'react';
import PropTypes from 'prop-types';

import ItemIcon from 'common/ItemIcon';
import ItemLink from 'common/ItemLink';

const BoringItemValue = ({ item, children, className }) => (
  <div className={`pad boring-text ${className || ''}`}>

      <ItemIcon id={item.id} /> <ItemLink id={item.id} icon={false} />

      {children}

);
BoringItemValue.propTypes = {
  item: PropTypes.shape({
    id: PropTypes.number.isRequired,
  }).isRequired,
  children: PropTypes.node.isRequired,
  className: PropTypes.string,
};

export default BoringItemValue;
<commit_msg>Correct component names<commit_after>/**
 * A simple component that shows the spell value in the most plain way possible.
 * Use this only as the very last resort.
 */
import React from 'react';
import PropTypes from 'prop-types';

import ItemIcon from 'common/ItemIcon';
import ItemLink from 'common/ItemLink';

const BoringItemValueText = ({ item, children, className }) => (
  <div className={`pad boring-text ${className || ''}`}>

      <ItemIcon id={item.id} /> <ItemLink id={item.id} icon={false} />

      {children}

);
BoringItemValueText.propTypes = {
  item: PropTypes.shape({
    id: PropTypes.number.isRequired,
  }).isRequired,
  children: PropTypes.node.isRequired,
  className: PropTypes.string,
};

export default BoringItemValueText;
<|endoftext|>


<commit_before>const path = require('path')
const browserify = require('browserify')
const browserifyInc = require('browserify-incremental')
const fs = require('fs')
const mkdirp = require('mkdirp')
const watchify = require('watchify')
const multistream = require('multistream')

const allDeps = []

module.exports = function (opts) {
  opts = opts || {}
  if (!opts.rootDir) { opts.rootDir = process.cwd() }
  if (!opts.cacheFile) { opts.cacheFile = '.bcache.json' }
  if (typeof opts.inc === 'undefined') { opts.inc = true }

  if (!opts.vendor) {
    console.error('Missing vendor config')
    process.exit(1)
  }

  const modules = opts.vendor
  opts.outFile = 'dist/vendor.js'

  var b
  if (opts.watch) {
    b = watchify(browserify(watchify.args))
  } else if (opts.inc) {
    b = browserify(browserifyInc.args)
    browserifyInc(b, { cacheFile: opts.cacheFile })
  } else {
    b = browserify()
  }

  var bundle = require('./lib/build')(b, opts)

  // make sure the output directory exists
  var outDir = path.dirname(opts.outFile)
  mkdirp.sync(outDir)

  b.require(modules)

  const onFinish = function () {
    if (!opts.watch) {
      process.exit()
    }
  }

  bundle(onFinish)
}
<commit_msg>pack.js creates dist/vendor.js in the dir of the pack file<commit_after>const path = require('path')
const browserify = require('browserify')
const browserifyInc = require('browserify-incremental')
const fs = require('fs')
const mkdirp = require('mkdirp')
const watchify = require('watchify')

module.exports = function (opts) {
  opts = opts || {}

  if (!opts.cacheFile) { opts.cacheFile = '.bcache.json' }
  if (typeof opts.inc === 'undefined') { opts.inc = true }

  if (!opts.vendor) {
    console.error('Missing vendor config')
    process.exit(1)
  }

  const outDir = path.join(path.dirname(opts._path), 'dist')
  const modules = opts.vendor
  opts.outFile = path.join(outDir, 'vendor.js')

  var b
  if (opts.watch) {
    b = watchify(browserify(watchify.args))
  } else if (opts.inc) {
    b = browserify(browserifyInc.args)
    browserifyInc(b, { cacheFile: opts.cacheFile })
  } else {
    b = browserify()
  }

  var bundle = require('./lib/build')(b, opts)

  // make sure the output directory exists
  mkdirp.sync(outDir)

  b.require(modules)

  const onFinish = function () {
    if (!opts.watch) {
      process.exit()
    }
  }

  bundle(onFinish)
}
<|endoftext|>


<commit_before>"use strict";

// you can use: drawLine(ctx, x1, y1, x2, y2);
// you can use: drawLine(ctx, x1, y1, x2, y2);
// you can use: drawCircle(ctx, x, y, radius);

// Export only these methods
var drawCurve, drawConstruction, drawBezierNodes;

(function () {

    function calcBspline(t, nodes, knots) {
        var k = 0;
        for (var l = 3; l < (knots.length - 2); l++) {
            if (knots[l].val() >= t) break;
            k++;
        }
        var u1 = knots[k + 0].val();
        var u2 = knots[k + 1].val();
        var u3 = knots[k + 2].val();
        var u4 = knots[k + 3].val();
        var u5 = knots[k + 4].val();
        var u6 = knots[k + 5].val();

        var p10 = nodes[k + 0].pos();
        var p20 = nodes[k + 1].pos();
        var p30 = nodes[k + 2].pos();
        var p40 = nodes[k + 3].pos();

        var p21 = p10.multiply((u4 - t) / (u4 - u1)).add(p20.multiply((t - u1) / (u4 - u1)));
        var p31 = p20.multiply((u5 - t) / (u5 - u2)).add(p30.multiply((t - u2) / (u5 - u2)));
        var p41 = p30.multiply((u6 - t) / (u6 - u3)).add(p40.multiply((t - u3) / (u6 - u3)));

        var p32 = p21.multiply((u4 - t) / (u4 - u2)).add(p31.multiply((t - u2) / (u4 - u2)));
        var p42 = p31.multiply((u5 - t) / (u5 - u3)).add(p41.multiply((t - u3) / (u5 - u3)));

        var p43 = p32.multiply((u4 - t) / (u4 - u3)).add(p42.multiply((t - u3) / (u4 - u3)));
        return  p43;
    }

    drawCurve = function (ctx, curve) {
        if (curve.nodes.length < 4) return;

        var lknot = curve.knots[2].val();
        var rknot = curve.knots[curve.knots.length - 3].val();

        var samples = 1000;
        var l = calcBspline(lknot, curve.nodes, curve.knots);

        for (var i = 1; i < samples; i++) {
            var t = lknot + (rknot - lknot) / samples * i;
            var r = calcBspline(t, curve.nodes, curve.knots);
            drawLine(ctx, l.x, l.y, r.x, r.y);
            l = r;
        }
    };

    drawConstruction = function (ctx, curve) {
        var n = curve.nodes.length;
        var m = curve.knots.length;
        var t = curve.timeKnot.val();
        var k = 0;
        for (var l = 3; l < (m - 2); l++) {
            if (curve.knots[l].val() >= t) {
                break;
            }
            k++;
        }

        if (t >= curve.knots[2].val() && t < curve.knots[m - 3].val()) {
            var u1 = curve.knots[k + 0].val();
            var u2 = curve.knots[k + 1].val();
            var u3 = curve.knots[k + 2].val();
            var u4 = curve.knots[k + 3].val();
            var u5 = curve.knots[k + 4].val();
            var u6 = curve.knots[k + 5].val();

            var p10 = curve.nodes[k + 0].pos();
            var p20 = curve.nodes[k + 1].pos();
            var p30 = curve.nodes[k + 2].pos();
            var p40 = curve.nodes[k + 3].pos();

            setColors(ctx, 'yellow', 'cyan');
            drawCircle(ctx, p10.x, p10.y, 4);
            drawCircle(ctx, p20.x, p20.y, 4);
            drawCircle(ctx, p30.x, p30.y, 4);
            drawCircle(ctx, p40.x, p40.y, 4);
            drawLine(ctx, p10.x, p10.y, p20.x, p20.y);
            drawLine(ctx, p20.x, p20.y, p30.x, p30.y);
            drawLine(ctx, p30.x, p30.y, p40.x, p40.y);

            var p21 = p10.multiply((u4 - t) / (u4 - u1)).add(p20.multiply((t - u1) / (u4 - u1)));
            var p31 = p20.multiply((u5 - t) / (u5 - u2)).add(p30.multiply((t - u2) / (u5 - u2)));
            var p41 = p30.multiply((u6 - t) / (u6 - u3)).add(p40.multiply((t - u3) / (u6 - u3)));

            setColors(ctx, 'red', 'orange');
            drawCircle(ctx, p21.x, p21.y, 3);
            drawCircle(ctx, p31.x, p31.y, 3);
            drawCircle(ctx, p41.x, p41.y, 3);
            drawLine(ctx, p21.x, p21.y, p31.x, p31.y);
            drawLine(ctx, p31.x, p31.y, p41.x, p41.y);

            var p32 = p21.multiply((u4 - t) / (u4 - u2)).add(p31.multiply((t - u2) / (u4 - u2)));
            var p42 = p31.multiply((u5 - t) / (u5 - u3)).add(p41.multiply((t - u3) / (u5 - u3)));

            setColors(ctx, 'violet', 'pink');
            drawCircle(ctx, p32.x, p32.y, 2);
            drawCircle(ctx, p42.x, p42.y, 2);
            drawLine(ctx, p32.x, p32.y, p42.x, p42.y);

            var p43 = p32.multiply((u4 - t) / (u4 - u3)).add(p42.multiply((t - u3) / (u4 - u3)));
            setColors(ctx, 'green', 'darkgreen');
            drawCircle(ctx, p43.x, p43.y, 4);
        }
    };

    drawBezierNodes = function (ctx, curve) {
        // TODO: implement this
    };

})();
<commit_msg>refactoring<commit_after>"use strict";

// you can use: drawLine(ctx, x1, y1, x2, y2);
// you can use: drawLine(ctx, x1, y1, x2, y2);
// you can use: drawCircle(ctx, x, y, radius);

// Export only these methods
var drawCurve, drawConstruction, drawBezierNodes;

(function () {

    function calcDrawBspline(t, curve, ctx) {
        var m = curve.knots.length;
        var k = 0;
        for (var l = 3; l < (m - 2); l++) {
            if (curve.knots[l].val() >= t) break;
            k++;
        }

        if (t >= curve.knots[2].val() && t < curve.knots[m - 3].val()) {
            var u1 = curve.knots[k + 0].val();
            var u2 = curve.knots[k + 1].val();
            var u3 = curve.knots[k + 2].val();
            var u4 = curve.knots[k + 3].val();
            var u5 = curve.knots[k + 4].val();
            var u6 = curve.knots[k + 5].val();

            var p10 = curve.nodes[k + 0].pos();
            var p20 = curve.nodes[k + 1].pos();
            var p30 = curve.nodes[k + 2].pos();
            var p40 = curve.nodes[k + 3].pos();

            var p21 = p10.multiply((u4 - t) / (u4 - u1)).add(p20.multiply((t - u1) / (u4 - u1)));
            var p31 = p20.multiply((u5 - t) / (u5 - u2)).add(p30.multiply((t - u2) / (u5 - u2)));
            var p41 = p30.multiply((u6 - t) / (u6 - u3)).add(p40.multiply((t - u3) / (u6 - u3)));

            var p32 = p21.multiply((u4 - t) / (u4 - u2)).add(p31.multiply((t - u2) / (u4 - u2)));
            var p42 = p31.multiply((u5 - t) / (u5 - u3)).add(p41.multiply((t - u3) / (u5 - u3)));

            var p43 = p32.multiply((u4 - t) / (u4 - u3)).add(p42.multiply((t - u3) / (u4 - u3)));

            if (ctx) {
                setColors(ctx, 'yellow', 'cyan');
                drawCircle(ctx, p10.x, p10.y, 4);
                drawCircle(ctx, p20.x, p20.y, 4);
                drawCircle(ctx, p30.x, p30.y, 4);
                drawCircle(ctx, p40.x, p40.y, 4);
                drawLine(ctx, p10.x, p10.y, p20.x, p20.y);
                drawLine(ctx, p20.x, p20.y, p30.x, p30.y);
                drawLine(ctx, p30.x, p30.y, p40.x, p40.y);

                setColors(ctx, 'red', 'orange');
                drawCircle(ctx, p21.x, p21.y, 3);
                drawCircle(ctx, p31.x, p31.y, 3);
                drawCircle(ctx, p41.x, p41.y, 3);
                drawLine(ctx, p21.x, p21.y, p31.x, p31.y);
                drawLine(ctx, p31.x, p31.y, p41.x, p41.y);

                setColors(ctx, 'violet', 'pink');
                drawCircle(ctx, p32.x, p32.y, 2);
                drawCircle(ctx, p42.x, p42.y, 2);
                drawLine(ctx, p32.x, p32.y, p42.x, p42.y);

                setColors(ctx, 'green', 'darkgreen');
                drawCircle(ctx, p43.x, p43.y, 4);
            }

            return p43;
        }
    }

    drawCurve = function (ctx, curve) {
        if (curve.nodes.length < 4) return;

        var lknot = curve.knots[2].val();
        var rknot = curve.knots[curve.knots.length - 3].val();

        var samples = 1000;
        var l = calcDrawBspline(lknot, curve);

        for (var i = 1; i < samples; i++) {
            var t = lknot + (rknot - lknot) / samples * i;
            var r = calcDrawBspline(t, curve);
            drawLine(ctx, l.x, l.y, r.x, r.y);
            l = r;
        }
    };

    drawConstruction = function (ctx, curve) {
        calcDrawBspline(curve.timeKnot.val(), curve, ctx);
    };

    drawBezierNodes = function (ctx, curve) {
        // TODO: implement this
    };

})();
<|endoftext|>