<commit_before>/**
 * @file Axis operator
 */
define(function(require) {

    var zrUtil = require('zrender/core/util');
    var numberUtil = require('../../util/number');
    var each = zrUtil.each;
    var asc = numberUtil.asc;

    /**
     * Operate single axis.
     * One axis can only operated by one axis operator.
     * Different dataZoomModels may be defined to operate the same axis.
     * (i.e. 'inside' data zoom and 'slider' data zoom components)
     * So dataZoomModels share one axisProxy in that case.
     *
     * @class
     */
    var AxisProxy = function (dimName, axisIndex, dataZoomModel, ecModel) {

        /**
         * @private
         * @type {string}
         */
        this._dimName = dimName;

        /**
         * @private
         */
        this._axisIndex = axisIndex;

        /**
         * @private
         * @type {Array.<number>}
         */
        this._valueWindow;

        /**
         * @private
         * @type {Array.<number>}
         */
        this._percentWindow;

        /**
         * @private
         * @type {Array.<number>}
         */
        this._dataExtent;

        /**
         * @readOnly
         * @type {module: echarts/model/Global}
         */
        this.ecModel = ecModel;

        /**
         * @private
         * @type {module: echarts/component/dataZoom/DataZoomModel}
         */
        this._dataZoomModel = dataZoomModel;
    };

    AxisProxy.prototype = {

        constructor: AxisProxy,

        /**
         * Whether the axisProxy is hosted by dataZoomModel.
         *
         * @public
         * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
         * @return {boolean}
         */
        hostedBy: function (dataZoomModel) {
            return this._dataZoomModel === dataZoomModel;
        },

        /**
         * @return {Array.<number>}
         */
        getDataExtent: function () {
            return this._dataExtent.slice();
        },

        /**
         * @return {Array.<number>}
         */
        getDataValueWindow: function () {
            return this._valueWindow.slice();
        },

        /**
         * @return {Array.<number>}
         */
        getDataPercentWindow: function () {
            return this._percentWindow.slice();
        },

        /**
         * @public
         * @param {number} axisIndex
         * @return {Array} seriesModels
         */
        getTargetSeriesModels: function () {
            var seriesModels = [];

            this.ecModel.eachSeries(function (seriesModel) {
                if (this._axisIndex === seriesModel.get(this._dimName + 'AxisIndex')) {
                    seriesModels.push(seriesModel);
                }
            }, this);

            return seriesModels;
        },

        getAxisModel: function () {
            return this.ecModel.getComponent(this._dimName + 'Axis', this._axisIndex);
        },

        getOtherAxisModel: function () {
            var axisDim = this._dimName;
            var ecModel = this.ecModel;
            var axisModel = this.getAxisModel();
            var isCartesian = axisDim === 'x' || axisDim === 'y';
            var otherAxisDim;
            var coordSysIndexName;
            if (isCartesian) {
                coordSysIndexName = 'gridIndex';
                otherAxisDim = axisDim === 'x' ? 'y' : 'x';
            }
            else {
                coordSysIndexName = 'polarIndex';
                otherAxisDim = axisDim === 'angle' ? 'radius' : 'angle';
            }
            var foundOtherAxisModel;
            ecModel.eachComponent(otherAxisDim + 'Axis', function (otherAxisModel) {
                if ((otherAxisModel.get(coordSysIndexName) || 0)
                    === (axisModel.get(coordSysIndexName) || 0)
                ) {
                    foundOtherAxisModel = otherAxisModel;
                }
            });
            return foundOtherAxisModel;
        },

        /**
         * Notice: reset should not be called before series.restoreData() called,
         * so it is recommanded to be called in "process stage" but not "model init
         * stage".
         *
         * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
         */
        reset: function (dataZoomModel) {
            if (dataZoomModel !== this._dataZoomModel) {
                return;
            }

            // Culculate data window and data extent, and record them.
            var dataExtent = this._dataExtent = calculateDataExtent(
                this._dimName, this.getTargetSeriesModels()
            );
            var dataWindow = calculateDataWindow(
                dataZoomModel.option, dataExtent, this
            );
            this._valueWindow = dataWindow.valueWindow;
            this._percentWindow = dataWindow.percentWindow;

            // Update axis setting then.
            setAxisModel(this);
        },

        /**
         * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
         */
        restore: function (dataZoomModel) {
            if (dataZoomModel !== this._dataZoomModel) {
                return;
            }

            this._valueWindow = this._percentWindow = null;
            setAxisModel(this, true);
        },

        /**
         * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
         */
        filterData: function (dataZoomModel) {
            if (dataZoomModel !== this._dataZoomModel) {
                return;
            }

            var axisDim = this._dimName;
            var seriesModels = this.getTargetSeriesModels();
            var filterMode = dataZoomModel.get('filterMode');
            var valueWindow = this._valueWindow;

            // FIXME
            // Toolbox may has dataZoom injected. And if there are stacked bar chart
            // with NaN data, NaN will be filtered and stack will be wrong.
            // So we need to force the mode to be set empty.
            // In fect, it is not a big deal that do not support filterMode-'filter'
            // when using toolbox#dataZoom, utill tooltip#dataZoom support "single axis
            // selection" some day, which might need "adapt to data extent on the
            // otherAxis", which is disabled by filterMode-'empty'.
            var otherAxisModel = this.getOtherAxisModel();
            if (dataZoomModel.get('$fromToolbox')
                && otherAxisModel
                && otherAxisModel.get('type') === 'category'
            ) {
                filterMode = 'empty';
            }

            // Process series data
            each(seriesModels, function (seriesModel) {
                var seriesData = seriesModel.getData();

                seriesData && each(seriesModel.coordDimToDataDim(axisDim), function (dim) {
                    if (filterMode === 'empty') {
                        seriesModel.setData(
                            seriesData.map(dim, function (value) {
                                return !isInWindow(value) ? NaN : value;
                            })
                        );
                    }
                    else {
                        seriesData.filterSelf(dim, isInWindow);
                    }
                });
            });

            function isInWindow(value) {
                return value >= valueWindow[0] && value <= valueWindow[1];
            }
        }
    };

    function calculateDataExtent(axisDim, seriesModels) {
        var dataExtent = [Infinity, -Infinity];

        each(seriesModels, function (seriesModel) {
            var seriesData = seriesModel.getData();
            if (seriesData) {
                each(seriesModel.coordDimToDataDim(axisDim), function (dim) {
                    var seriesExtent = seriesData.getDataExtent(dim);
                    seriesExtent[0] < dataExtent[0] && (dataExtent[0] = seriesExtent[0]);
                    seriesExtent[1] > dataExtent[1] && (dataExtent[1] = seriesExtent[1]);
                });
            }
        }, this);

        return dataExtent;
    }

    function calculateDataWindow(opt, dataExtent, axisProxy) {
        var axisModel = axisProxy.getAxisModel();
        var scale = axisModel.axis.scale;
        var percentExtent = [0, 100];
        var percentWindow = [
            opt.start,
            opt.end
        ];
        var valueWindow = [];

        // In percent range is used and axis min/max/scale is set,
        // window should be based on min/max/0, but should not be
        // based on the extent of filtered data.
        dataExtent = dataExtent.slice();
        fixExtendByAxis(dataExtent, axisModel, scale);

        each(['startValue', 'endValue'], function (prop) {
            valueWindow.push(
                opt[prop] != null
                    ? scale.parse(opt[prop])
                    : null
            );
        });

        // Normalize bound.
        each([0, 1], function (idx) {
            var boundValue = valueWindow[idx];
            var boundPercent = percentWindow[idx];

            // start/end has higher priority over startValue/endValue,
            // because start/end can be consistent among different type
            // of axis but startValue/endValue not.

            if (boundPercent != null || boundValue == null) {
                if (boundPercent == null) {
                    boundPercent = percentExtent[idx];
                }
                // Use scale.parse to math round for category or time axis.
                boundValue = scale.parse(numberUtil.linearMap(
                    boundPercent, percentExtent, dataExtent, true
                ));
            }
            else { // boundPercent == null && boundValue != null
                boundPercent = numberUtil.linearMap(
                    boundValue, dataExtent, percentExtent, true
                );
            }
            // valueWindow[idx] = round(boundValue);
            // percentWindow[idx] = round(boundPercent);
            valueWindow[idx] = boundValue;
            percentWindow[idx] = boundPercent;
        });

        return {
            valueWindow: asc(valueWindow),
            percentWindow: asc(percentWindow)
        };
    }

    function fixExtendByAxis(dataExtent, axisModel, scale) {
        each(['min', 'max'], function (minMax, index) {
            var axisMax = axisModel.get(minMax, true);
            // Consider 'dataMin', 'dataMax'
            if (axisMax != null && (axisMax + '').toLowerCase() !== 'data' + minMax) {
                dataExtent[index] = scale.parse(axisMax);
            }
        });

        if (!axisModel.get('scale', true)) {
            dataExtent[0] > 0 && (dataExtent[0] = 0);
            dataExtent[1] < 0 && (dataExtent[1] = 0);
        }

        return dataExtent;
    }

    function setAxisModel(axisProxy, isRestore) {
        var axisModel = axisProxy.getAxisModel();

        var percentWindow = axisProxy._percentWindow;
        var valueWindow = axisProxy._valueWindow;

        if (!percentWindow) {
            return;
        }

        var isFull = isRestore || (percentWindow[0] === 0 && percentWindow[1] === 100);
        // [0, 500]: arbitrary value, guess axis extent.
        var precision = !isRestore && numberUtil.getPixelPrecision(valueWindow, [0, 500]);
        // toFixed() digits argument must be between 0 and 20
        var invalidPrecision = !isRestore && !(precision < 20 && precision >= 0);

        var useOrigin = isRestore || isFull || invalidPrecision;

        axisModel.setRange && axisModel.setRange(
            useOrigin ? null : +valueWindow[0].toFixed(precision),
            useOrigin ? null : +valueWindow[1].toFixed(precision)
        );
    }

    return AxisProxy;

});<commit_msg>[dataZoom] compatible that xAxisIndex is set as [0] erroneously<commit_after>/**
 * @file Axis operator
 */
define(function(require) {

    var zrUtil = require('zrender/core/util');
    var numberUtil = require('../../util/number');
    var each = zrUtil.each;
    var asc = numberUtil.asc;

    /**
     * Operate single axis.
     * One axis can only operated by one axis operator.
     * Different dataZoomModels may be defined to operate the same axis.
     * (i.e. 'inside' data zoom and 'slider' data zoom components)
     * So dataZoomModels share one axisProxy in that case.
     *
     * @class
     */
    var AxisProxy = function (dimName, axisIndex, dataZoomModel, ecModel) {

        /**
         * @private
         * @type {string}
         */
        this._dimName = dimName;

        /**
         * @private
         */
        this._axisIndex = axisIndex;

        /**
         * @private
         * @type {Array.<number>}
         */
        this._valueWindow;

        /**
         * @private
         * @type {Array.<number>}
         */
        this._percentWindow;

        /**
         * @private
         * @type {Array.<number>}
         */
        this._dataExtent;

        /**
         * @readOnly
         * @type {module: echarts/model/Global}
         */
        this.ecModel = ecModel;

        /**
         * @private
         * @type {module: echarts/component/dataZoom/DataZoomModel}
         */
        this._dataZoomModel = dataZoomModel;
    };

    AxisProxy.prototype = {

        constructor: AxisProxy,

        /**
         * Whether the axisProxy is hosted by dataZoomModel.
         *
         * @public
         * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
         * @return {boolean}
         */
        hostedBy: function (dataZoomModel) {
            return this._dataZoomModel === dataZoomModel;
        },

        /**
         * @return {Array.<number>}
         */
        getDataExtent: function () {
            return this._dataExtent.slice();
        },

        /**
         * @return {Array.<number>}
         */
        getDataValueWindow: function () {
            return this._valueWindow.slice();
        },

        /**
         * @return {Array.<number>}
         */
        getDataPercentWindow: function () {
            return this._percentWindow.slice();
        },

        /**
         * @public
         * @param {number} axisIndex
         * @return {Array} seriesModels
         */
        getTargetSeriesModels: function () {
            var seriesModels = [];

            this.ecModel.eachSeries(function (seriesModel) {
                // Legacy problem: some one wrote xAxisIndex as [0] following the wrong way in example.
                if (this._axisIndex === +seriesModel.get(this._dimName + 'AxisIndex')) {
                    seriesModels.push(seriesModel);
                }
            }, this);

            return seriesModels;
        },

        getAxisModel: function () {
            return this.ecModel.getComponent(this._dimName + 'Axis', this._axisIndex);
        },

        getOtherAxisModel: function () {
            var axisDim = this._dimName;
            var ecModel = this.ecModel;
            var axisModel = this.getAxisModel();
            var isCartesian = axisDim === 'x' || axisDim === 'y';
            var otherAxisDim;
            var coordSysIndexName;
            if (isCartesian) {
                coordSysIndexName = 'gridIndex';
                otherAxisDim = axisDim === 'x' ? 'y' : 'x';
            }
            else {
                coordSysIndexName = 'polarIndex';
                otherAxisDim = axisDim === 'angle' ? 'radius' : 'angle';
            }
            var foundOtherAxisModel;
            ecModel.eachComponent(otherAxisDim + 'Axis', function (otherAxisModel) {
                if ((otherAxisModel.get(coordSysIndexName) || 0)
                    === (axisModel.get(coordSysIndexName) || 0)
                ) {
                    foundOtherAxisModel = otherAxisModel;
                }
            });
            return foundOtherAxisModel;
        },

        /**
         * Notice: reset should not be called before series.restoreData() called,
         * so it is recommanded to be called in "process stage" but not "model init
         * stage".
         *
         * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
         */
        reset: function (dataZoomModel) {
            if (dataZoomModel !== this._dataZoomModel) {
                return;
            }

            // Culculate data window and data extent, and record them.
            var dataExtent = this._dataExtent = calculateDataExtent(
                this._dimName, this.getTargetSeriesModels()
            );
            var dataWindow = calculateDataWindow(
                dataZoomModel.option, dataExtent, this
            );
            this._valueWindow = dataWindow.valueWindow;
            this._percentWindow = dataWindow.percentWindow;

            // Update axis setting then.
            setAxisModel(this);
        },

        /**
         * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
         */
        restore: function (dataZoomModel) {
            if (dataZoomModel !== this._dataZoomModel) {
                return;
            }

            this._valueWindow = this._percentWindow = null;
            setAxisModel(this, true);
        },

        /**
         * @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
         */
        filterData: function (dataZoomModel) {
            if (dataZoomModel !== this._dataZoomModel) {
                return;
            }

            var axisDim = this._dimName;
            var seriesModels = this.getTargetSeriesModels();
            var filterMode = dataZoomModel.get('filterMode');
            var valueWindow = this._valueWindow;

            // FIXME
            // Toolbox may has dataZoom injected. And if there are stacked bar chart
            // with NaN data, NaN will be filtered and stack will be wrong.
            // So we need to force the mode to be set empty.
            // In fect, it is not a big deal that do not support filterMode-'filter'
            // when using toolbox#dataZoom, utill tooltip#dataZoom support "single axis
            // selection" some day, which might need "adapt to data extent on the
            // otherAxis", which is disabled by filterMode-'empty'.
            var otherAxisModel = this.getOtherAxisModel();
            if (dataZoomModel.get('$fromToolbox')
                && otherAxisModel
                && otherAxisModel.get('type') === 'category'
            ) {
                filterMode = 'empty';
            }

            // Process series data
            each(seriesModels, function (seriesModel) {
                var seriesData = seriesModel.getData();

                seriesData && each(seriesModel.coordDimToDataDim(axisDim), function (dim) {
                    if (filterMode === 'empty') {
                        seriesModel.setData(
                            seriesData.map(dim, function (value) {
                                return !isInWindow(value) ? NaN : value;
                            })
                        );
                    }
                    else {
                        seriesData.filterSelf(dim, isInWindow);
                    }
                });
            });

            function isInWindow(value) {
                return value >= valueWindow[0] && value <= valueWindow[1];
            }
        }
    };

    function calculateDataExtent(axisDim, seriesModels) {
        var dataExtent = [Infinity, -Infinity];

        each(seriesModels, function (seriesModel) {
            var seriesData = seriesModel.getData();
            if (seriesData) {
                each(seriesModel.coordDimToDataDim(axisDim), function (dim) {
                    var seriesExtent = seriesData.getDataExtent(dim);
                    seriesExtent[0] < dataExtent[0] && (dataExtent[0] = seriesExtent[0]);
                    seriesExtent[1] > dataExtent[1] && (dataExtent[1] = seriesExtent[1]);
                });
            }
        }, this);

        return dataExtent;
    }

    function calculateDataWindow(opt, dataExtent, axisProxy) {
        var axisModel = axisProxy.getAxisModel();
        var scale = axisModel.axis.scale;
        var percentExtent = [0, 100];
        var percentWindow = [
            opt.start,
            opt.end
        ];
        var valueWindow = [];

        // In percent range is used and axis min/max/scale is set,
        // window should be based on min/max/0, but should not be
        // based on the extent of filtered data.
        dataExtent = dataExtent.slice();
        fixExtendByAxis(dataExtent, axisModel, scale);

        each(['startValue', 'endValue'], function (prop) {
            valueWindow.push(
                opt[prop] != null
                    ? scale.parse(opt[prop])
                    : null
            );
        });

        // Normalize bound.
        each([0, 1], function (idx) {
            var boundValue = valueWindow[idx];
            var boundPercent = percentWindow[idx];

            // start/end has higher priority over startValue/endValue,
            // because start/end can be consistent among different type
            // of axis but startValue/endValue not.

            if (boundPercent != null || boundValue == null) {
                if (boundPercent == null) {
                    boundPercent = percentExtent[idx];
                }
                // Use scale.parse to math round for category or time axis.
                boundValue = scale.parse(numberUtil.linearMap(
                    boundPercent, percentExtent, dataExtent, true
                ));
            }
            else { // boundPercent == null && boundValue != null
                boundPercent = numberUtil.linearMap(
                    boundValue, dataExtent, percentExtent, true
                );
            }
            // valueWindow[idx] = round(boundValue);
            // percentWindow[idx] = round(boundPercent);
            valueWindow[idx] = boundValue;
            percentWindow[idx] = boundPercent;
        });

        return {
            valueWindow: asc(valueWindow),
            percentWindow: asc(percentWindow)
        };
    }

    function fixExtendByAxis(dataExtent, axisModel, scale) {
        each(['min', 'max'], function (minMax, index) {
            var axisMax = axisModel.get(minMax, true);
            // Consider 'dataMin', 'dataMax'
            if (axisMax != null && (axisMax + '').toLowerCase() !== 'data' + minMax) {
                dataExtent[index] = scale.parse(axisMax);
            }
        });

        if (!axisModel.get('scale', true)) {
            dataExtent[0] > 0 && (dataExtent[0] = 0);
            dataExtent[1] < 0 && (dataExtent[1] = 0);
        }

        return dataExtent;
    }

    function setAxisModel(axisProxy, isRestore) {
        var axisModel = axisProxy.getAxisModel();

        var percentWindow = axisProxy._percentWindow;
        var valueWindow = axisProxy._valueWindow;

        if (!percentWindow) {
            return;
        }

        var isFull = isRestore || (percentWindow[0] === 0 && percentWindow[1] === 100);
        // [0, 500]: arbitrary value, guess axis extent.
        var precision = !isRestore && numberUtil.getPixelPrecision(valueWindow, [0, 500]);
        // toFixed() digits argument must be between 0 and 20
        var invalidPrecision = !isRestore && !(precision < 20 && precision >= 0);

        var useOrigin = isRestore || isFull || invalidPrecision;

        axisModel.setRange && axisModel.setRange(
            useOrigin ? null : +valueWindow[0].toFixed(precision),
            useOrigin ? null : +valueWindow[1].toFixed(precision)
        );
    }

    return AxisProxy;

});<|endoftext|>


<commit_before>/** @jsx hJSX */
import Cycle from '@cycle/core'
import {Rx} from '@cycle/core'
import {hJSX} from '@cycle/dom'
import Class from 'classnames'
let merge = Rx.Observable.merge

import {preventDefault,isTextNotEmpty,formatData,exists} from '../utils/obsUtils'
import EditableItem from './EditableItem'

function commentsList (comments) {
  let listElements = comments.map(function(comment){
    const iconSvg = `<svg className="icon" version="1.1" id="Pencil" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
             viewBox="0 0 20 20" enable-background="new 0 0 20 20" >
          <path fill="#FFFFFF" d="M14.69,2.661c-1.894-1.379-3.242-1.349-3.754-1.266c-0.144,0.023-0.265,0.106-0.35,0.223l-4.62,6.374
            l-2.263,3.123c-0.277,0.382-0.437,0.836-0.462,1.307l-0.296,5.624c-0.021,0.405,0.382,0.698,0.76,0.553l5.256-2.01
            c0.443-0.17,0.828-0.465,1.106-0.849l1.844-2.545l5.036-6.949c0.089-0.123,0.125-0.273,0.1-0.423
            C16.963,5.297,16.56,4.021,14.69,2.661z M8.977,15.465l-2.043,0.789c-0.08,0.031-0.169,0.006-0.221-0.062
            c-0.263-0.335-0.576-0.667-1.075-1.03c-0.499-0.362-0.911-0.558-1.31-0.706c-0.08-0.03-0.131-0.106-0.126-0.192l0.122-2.186
            l0.549-0.755c0,0,1.229-0.169,2.833,0.998c1.602,1.166,1.821,2.388,1.821,2.388L8.977,15.465z"/>
          </svg>`

    return

          {comment.author} commented 0.5h ago

          <EditableItem data={comment.text} editable={false}/>

  })
  return

    {listElements}

}

function createComment (newComment,  changeHandler){
  const iconSvg = `
    <svg className="icon" version="1.1" id="Message" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
       viewBox="0 0 20 20" enable-background="new 0 0 20 20" >
      <path fill="#FFFFFF" d="M18,6v7c0,1.1-0.9,2-2,2h-4v3l-4-3H4c-1.101,0-2-0.9-2-2V6c0-1.1,0.899-2,2-2h12C17.1,4,18,4.9,18,6z"/>
    </svg>`

  return  (

          Leave a comment

        <EditableItem id="newComment"
          data={newComment.text}
          placeholder="what are your thoughts..."
          multiline="true"
          changeHandler={changeHandler}
        />

      <button className="add">Add comment</button>

  )
}

//FIXME : uppercased to avoid conflict with comments "model"
function renderComments(interactions, toggled, comments, entity, newComment, changeHandler){
  let commentDetails = null
  let commentsData = []

  let key = [undefined,undefined] //at design level ie :no entity
  if(entity){
    key = [entity.iuid,entity.typeUid]
  }

  if(toggled){
    commentsData = comments.bykey[key]
    if(!commentsData)
      commentsData = []

    commentDetails =

        { commentsList(commentsData) }
        { createComment(newComment, changeHandler) }

  }

  const iconSvg =  `
    <svg className="icon" version="1.1" id="Message" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
       viewBox="0 0 20 20" enable-background="new 0 0 20 20" onClick={interactions.subject('toggle').onEvent}>
      <path fill="#FFFFFF" d="M18,6v7c0,1.1-0.9,2-2,2h-4v3l-4-3H4c-1.101,0-2-0.9-2-2V6c0-1.1,0.899-2,2-2h12C17.1,4,18,4.9,18,6z"/>
    </svg>`

  return (

            See/add comments

        {commentDetails}

  )
}

//helper function, tor return uids (type/instance)
function getIds(entity){
  console.log("getIds")
  if(entity){
    return {typeUid:entity.typeUid, iuid:entity.iuid}
  }
  return {typeUid:undefined, iuid:undefined}
}

function Comments({DOM,props$}) {
  let comments$   = props$.pluck('comments')
  let entity$     = props$.pluck('entity')

  let addComment$ = DOM.select(".add").events("click")
  let toggled$ =  DOM.select(".comments").events("toggle")
    .map(true)
    .startWith(false)
    .scan((acc,val)=>!acc)

  //stream containing new comment, if any
  let newComment$ = interactions.subject('newCommentContent$')
    .map(e=>e.target.value)
    .startWith(undefined)
    .map(e=>{ return {text:e} })
    .shareReplay(1)

  //stream of new comments
  addComment$ = addComment$
    .withLatestFrom(
      newComment$
      ,entity$.map(getIds)
      ,function(a,commentText,entityData){
        return { text:commentText.text, target:entityData}
      })
    .shareReplay(1)

  newComment$ =
    merge(
      newComment$,
      addComment$.map({text:undefined})
    )

  addComment$.subscribe(e=>console.log("addComment",e))

  let vtree$ = Rx.Observable
    .combineLatest(
      comments$
      ,entity$
      ,newComment$
      ,toggled$
      ,function(comments,entity,newComment, toggled){

        //FIXME: temp hack
        function changeHandler(fieldName, index, event){
          interactions.subject('newCommentContent$').onEvent(event)
        }
        let _changeHandler = changeHandler.bind(null,"comment",undefined)

        return renderComments(interactions, toggled, comments, entity, newComment, _changeHandler)
      }
    )

  return {
    DOM: vtree$,
    events:{
      addComment$
    }
  }
}

export default Comments
<commit_msg>- updated comments component:  * removed react centric elements  * cleanups  * wip<commit_after>/** @jsx hJSX */
import Cycle from '@cycle/core'
import {Rx} from '@cycle/core'
import {hJSX} from '@cycle/dom'
import Class from 'classnames'
let merge = Rx.Observable.merge

import {preventDefault,isTextNotEmpty,formatData,exists} from '../../utils/obsUtils'
//import EditableItem from './EditableItem'

function commentsList (comments) {
  let listElements = comments.map(function(comment){
    const iconSvg = `<svg className="icon" version="1.1" id="Pencil" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
             viewBox="0 0 20 20" enable-background="new 0 0 20 20" >
          <path fill="#FFFFFF" d="M14.69,2.661c-1.894-1.379-3.242-1.349-3.754-1.266c-0.144,0.023-0.265,0.106-0.35,0.223l-4.62,6.374
            l-2.263,3.123c-0.277,0.382-0.437,0.836-0.462,1.307l-0.296,5.624c-0.021,0.405,0.382,0.698,0.76,0.553l5.256-2.01
            c0.443-0.17,0.828-0.465,1.106-0.849l1.844-2.545l5.036-6.949c0.089-0.123,0.125-0.273,0.1-0.423
            C16.963,5.297,16.56,4.021,14.69,2.661z M8.977,15.465l-2.043,0.789c-0.08,0.031-0.169,0.006-0.221-0.062
            c-0.263-0.335-0.576-0.667-1.075-1.03c-0.499-0.362-0.911-0.558-1.31-0.706c-0.08-0.03-0.131-0.106-0.126-0.192l0.122-2.186
            l0.549-0.755c0,0,1.229-0.169,2.833,0.998c1.602,1.166,1.821,2.388,1.821,2.388L8.977,15.465z"/>
          </svg>`

    return

          {comment.author} commented 0.5h ago

          <EditableItem data={comment.text} editable={false}/>

  })
  return

    {listElements}

}

/*<EditableItem id="newComment"
          data={newComment.text}
          placeholder="what are your thoughts..."
          multiline="true"
          changeHandler={changeHandler}
        />*/

function createComment (newComment){
  const iconSvg = `
    <svg className="icon" version="1.1" id="Message" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
       viewBox="0 0 20 20" enable-background="new 0 0 20 20" >
      <path fill="#FFFFFF" d="M18,6v7c0,1.1-0.9,2-2,2h-4v3l-4-3H4c-1.101,0-2-0.9-2-2V6c0-1.1,0.899-2,2-2h12C17.1,4,18,4.9,18,6z"/>
    </svg>`

  return  (

          Leave a comment

      <button className="add">Add comment</button>

  )
}

//FIXME : uppercased to avoid conflict with comments "model"
function renderComments(toggled, comments, entity, newComment){
  let commentDetails = null
  let commentsData = []

  let key = [undefined,undefined] //at design level ie :no entity
  if(entity){
    key = [entity.iuid,entity.typeUid]
  }

  if(toggled){
    commentsData = comments.bykey[key]
    if(!commentsData)
      commentsData = []

    commentDetails =

        { commentsList(commentsData) }
        { createComment(newComment) }

  }
  //onClick={interactions.subject('toggle').onEvent}

  const iconSvg =  `
    <svg class="icon" version="1.1" id="Message" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
       viewBox="0 0 20 20" enable-background="new 0 0 20 20" >
      <path fill="#FFFFFF" d="M18,6v7c0,1.1-0.9,2-2,2h-4v3l-4-3H4c-1.101,0-2-0.9-2-2V6c0-1.1,0.899-2,2-2h12C17.1,4,18,4.9,18,6z"/>
    </svg>`

   /*

            See/add comments

        */

  return (

        {commentDetails}

  )
}

//helper function, tor return uids (type/instance)
function getIds(entity){
  console.log("getIds")
  if(entity){
    return {typeUid:entity.typeUid, iuid:entity.id}
  }
  return {typeUid:undefined, iuid:undefined}
}

function Comments({DOM,props$}) {
  let comments$   = props$.pluck('comments')
  let entity$     = props$.pluck('entity')

  let addComment$ = DOM.select(".add").events("click")
  let toggled$    = DOM.select(".comments").events("toggle")
    .map(true)
    .startWith(false)
    .scan((acc,val)=>!acc)

  //stream containing new comment, if any
  let newComment$ = Rx.Observable.just("foo") /*interactions.subject('newCommentContent$')
    .map(e=>e.target.value)
    .startWith(undefined)
    .map(e=>{ return {text:e} })
    .shareReplay(1)*/

  //stream of new comments
  addComment$ = addComment$
    .withLatestFrom(
      newComment$
      ,entity$.map(getIds)
      ,function(a,commentText,entityData){
        return { text:commentText.text, target:entityData}
      })
    .shareReplay(1)

  newComment$ =
    merge(
      newComment$,
      addComment$.map({text:undefined})
    )

  addComment$.subscribe(e=>console.log("addComment",e))

  let vtree$ = Rx.Observable
    .combineLatest(
      comments$
      ,entity$
      ,newComment$
      ,toggled$
      ,function(comments,entity,newComment, toggled){
        //FIXME: temp hack
        /*function changeHandler(fieldName, index, event){
          interactions.subject('newCommentContent$').onEvent(event)
        }
        let _changeHandler = changeHandler.bind(null,"comment",undefined)*/
        return renderComments(toggled, comments, entity, newComment)
      }
    )

  return {
    DOM: vtree$,
    events:{
      addComment$
    }
  }
}

export default Comments
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Select from 'react-select';
import { includes, isArray, isEmpty } from 'lodash';
import XLSX from 'xlsx';
import moment from 'moment';

const CLASSIFICATION_ATTRIBUTES = [
  { attribute: 'code', name: 'Koodi', type: 'classification' },
  { attribute: 'name', name: 'Nimi', type: 'classification' },
  { attribute: 'description', name: 'Kuvaus', type: 'classification' },
  { attribute: 'descriptionInternal', name: 'Sisäinen kuvaus', type: 'classification' },
  { attribute: 'relatedClassification', name: 'Liittyvä tehtäväluokka', type: 'classification' },
  { attribute: 'additionalInformation', name: 'Lisätiedot', type: 'classification' }
];

export const EXPORT_OPTIONS = [
  { value: 0, label: 'Yhdelle välilehdelle' },
  { value: 1, label: 'Omille välilehdille' }
];

const getChildren = (array, item) => {
  if (isArray(item.children)) {
    return item.children.reduce(getChildren, array);
  }
  const exportItem = {
    additionalInformation: item.additional_information,
    code: item.code,
    description: item.description,
    descriptionInternal: item.description_internal,
    name: item.title,
    relatedClassification: item.related_classification,
    attributes: item.function_attributes,
    phases: item.phases
  };

  array.push(exportItem);
  return array;
};

/**
 * Get all attributes (phases, actions, records) for specific function
 * @param {object} item
 */
const getAllAttributes = (item) => {
  const allAttributes = Object.keys(item.attributes || {});

  item.phases.forEach((phase) => {
    allAttributes.push(...Object.keys(phase.attributes || {}));
    phase.actions.forEach((action) => {
      allAttributes.push(...Object.keys(action.attributes || {}));
      action.records.forEach((record) => {
        allAttributes.push(...Object.keys(record.attributes || {}));
      });
    });
  });

  // // Add two blank, merge attributes of each items & sort alphabetically
  return [ null, null, ...allAttributes.reduce((a, b) => [...new Set([...a, b])], []).sort() ];
};

/**
 * Set data for row
 * @param {object} item
 * @param {array} headers
 * @param {name} name
 */
const setRowData = (item, headers, name = 'käsittelyprosessi') => {
  const rowData = [name, `${item.code || ''} ${item.name}`];
  Object.keys(item.attributes || {}).forEach((attrName) => {
    const indexInHeaders = headers.indexOf(attrName);
    rowData[indexInHeaders] = item.attributes[attrName];
  });

  return rowData;
};

const createWorkBook = (attributeTypes, filename, items) => {
  const workBook = XLSX.utils.book_new();

  items.forEach((item) => {
    const headers = getAllAttributes(item);
    const names = headers.map(header => {
      return attributeTypes && attributeTypes.hasOwnProperty(header)
        ? attributeTypes[header].name || null
        : null;
    });
    const rows = [];

    // Function-level data
    rows.push(setRowData(item, headers));

    item.phases.forEach((phase) => {
      rows.push(setRowData(phase, headers, 'käsittelyvaihe'));
      phase.actions.forEach((action) => {
        rows.push(setRowData(action, headers, 'toimenpide'));
        action.records.forEach((record) => {
          rows.push(setRowData(record, headers, 'asiakirja'));
        });
      });
    });

    const workSheet = XLSX.utils.aoa_to_sheet([headers, names, ...rows]);
    XLSX.utils.book_append_sheet(workBook, workSheet, item.code);
  });

  XLSX.writeFile(workBook, filename);
};

const getAttributes = (attributeTypes, type) => {
  return Object.keys(attributeTypes || {}).reduce((acc, attribute) => {
    if (attributeTypes.hasOwnProperty(attribute) && attributeTypes[attribute].allowedIn && includes(attributeTypes[attribute].allowedIn, type)) {
      acc.push({
        attribute,
        index: attributeTypes[attribute].index || 100,
        name: attributeTypes[attribute].name,
        type
      });
    }
    return acc;
  }, []).sort((a, b) => a.index - b.index);
};

const getItemAttributes = (attributes, item) => {
  const values = [];
  attributes.forEach(attr => {
    const value = item.attributes && item.attributes.hasOwnProperty(attr.attribute)
      ? item.attributes[attr.attribute]
      : null;
    values.push(isArray(value) ? value.join(', ') : value);
  });
  return values;
};

const createSingleSheetWorkBook = (attributeTypes, filename, items) => {
  const workBook = XLSX.utils.book_new();
  const functionAttributes = getAttributes(attributeTypes, 'function');
  const phaseAttributes = getAttributes(attributeTypes, 'phase');
  const actionAttributes = getAttributes(attributeTypes, 'action');
  const recordAttributes = getAttributes(attributeTypes, 'record');
  const attributes = [];
  const titles = [];

  [...CLASSIFICATION_ATTRIBUTES, ...functionAttributes, ...phaseAttributes, ...actionAttributes, ...recordAttributes].forEach(attr => {
    attributes.push(`${attr.type}.${attr.attribute}`);
    titles.push(attr.name);
  });

  const workSheet = XLSX.utils.aoa_to_sheet([attributes, titles]);

  items.forEach((item) => {
    const cols = [];

    CLASSIFICATION_ATTRIBUTES.forEach(attr => {
      cols.push(item.hasOwnProperty(attr.attribute) ? item[attr.attribute] : null);
    });

    cols.push(...getItemAttributes(functionAttributes, item));

    item.phases.forEach((phase) => {
      const phaseCols = getItemAttributes(phaseAttributes, phase);

      phase.actions.forEach((action) => {
        const actionCols = getItemAttributes(actionAttributes, action);

        action.records.forEach((record) => {
          const recordCols = getItemAttributes(recordAttributes, record);
          XLSX.utils.sheet_add_aoa(workSheet, [[...cols, ...phaseCols, ...actionCols, ...recordCols]], { origin: -1 });
        });

        if (isEmpty(action.records)) {
          XLSX.utils.sheet_add_aoa(workSheet, [[...cols, ...phaseCols, ...actionCols]], { origin: -1 });
        }
      });

      if (isEmpty(phase.actions)) {
        XLSX.utils.sheet_add_aoa(workSheet, [[...cols, ...phaseCols]], { origin: -1 });
      }
    });

    if (isEmpty(item.phases)) {
      XLSX.utils.sheet_add_aoa(workSheet, [[...cols]], { origin: -1 });
    }
  });

  XLSX.utils.book_append_sheet(workBook, workSheet, 'Tiedonohjaus');
  XLSX.writeFile(workBook, filename);
};

const Exporter = ({ attributeTypes, data, className, isVisible }) => {
  if (!isVisible) {
    return null;
  }

  const exportData = data.reduce(getChildren, []);
  const fileName = `helerm-export_${moment().format('DD.MM.YYYY')}.xlsx`;

  return (

      <Select
        autoBlur={false}
        clearable={false}
        value={null}
        onChange={(e) => {
          if (e.value === 0) {
            createSingleSheetWorkBook(attributeTypes, fileName, exportData);
          } else {
            createWorkBook(attributeTypes, fileName, exportData);
          }
        }}
        autoFocus={true}
        options={EXPORT_OPTIONS}
        placeholder={`Vie hakutulokset (${exportData.length})`}
      />

  );
};

Exporter.propTypes = {
  attributeTypes: PropTypes.object,
  className: PropTypes.string,
  data: PropTypes.array,
  isVisible: PropTypes.bool
};

Exporter.defaultProps = {
  isVisible: true
};

export default Exporter;
<commit_msg>Excel exporter button label<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Select from 'react-select';
import { includes, isArray, isEmpty } from 'lodash';
import XLSX from 'xlsx';
import moment from 'moment';

const CLASSIFICATION_ATTRIBUTES = [
  { attribute: 'code', name: 'Koodi', type: 'classification' },
  { attribute: 'name', name: 'Nimi', type: 'classification' },
  { attribute: 'description', name: 'Kuvaus', type: 'classification' },
  { attribute: 'descriptionInternal', name: 'Sisäinen kuvaus', type: 'classification' },
  { attribute: 'relatedClassification', name: 'Liittyvä tehtäväluokka', type: 'classification' },
  { attribute: 'additionalInformation', name: 'Lisätiedot', type: 'classification' }
];

export const EXPORT_OPTIONS = [
  { value: 0, label: 'Yhdelle välilehdelle' },
  { value: 1, label: 'Omille välilehdille' }
];

const getChildren = (array, item) => {
  if (isArray(item.children)) {
    return item.children.reduce(getChildren, array);
  }
  const exportItem = {
    additionalInformation: item.additional_information,
    code: item.code,
    description: item.description,
    descriptionInternal: item.description_internal,
    name: item.title,
    relatedClassification: item.related_classification,
    attributes: item.function_attributes,
    phases: item.phases
  };

  array.push(exportItem);
  return array;
};

/**
 * Get all attributes (phases, actions, records) for specific function
 * @param {object} item
 */
const getAllAttributes = (item) => {
  const allAttributes = Object.keys(item.attributes || {});

  item.phases.forEach((phase) => {
    allAttributes.push(...Object.keys(phase.attributes || {}));
    phase.actions.forEach((action) => {
      allAttributes.push(...Object.keys(action.attributes || {}));
      action.records.forEach((record) => {
        allAttributes.push(...Object.keys(record.attributes || {}));
      });
    });
  });

  // // Add two blank, merge attributes of each items & sort alphabetically
  return [ null, null, ...allAttributes.reduce((a, b) => [...new Set([...a, b])], []).sort() ];
};

/**
 * Set data for row
 * @param {object} item
 * @param {array} headers
 * @param {name} name
 */
const setRowData = (item, headers, name = 'käsittelyprosessi') => {
  const rowData = [name, `${item.code || ''} ${item.name}`];
  Object.keys(item.attributes || {}).forEach((attrName) => {
    const indexInHeaders = headers.indexOf(attrName);
    rowData[indexInHeaders] = item.attributes[attrName];
  });

  return rowData;
};

const createWorkBook = (attributeTypes, filename, items) => {
  const workBook = XLSX.utils.book_new();

  items.forEach((item) => {
    const headers = getAllAttributes(item);
    const names = headers.map(header => {
      return attributeTypes && attributeTypes.hasOwnProperty(header)
        ? attributeTypes[header].name || null
        : null;
    });
    const rows = [];

    // Function-level data
    rows.push(setRowData(item, headers));

    item.phases.forEach((phase) => {
      rows.push(setRowData(phase, headers, 'käsittelyvaihe'));
      phase.actions.forEach((action) => {
        rows.push(setRowData(action, headers, 'toimenpide'));
        action.records.forEach((record) => {
          rows.push(setRowData(record, headers, 'asiakirja'));
        });
      });
    });

    const workSheet = XLSX.utils.aoa_to_sheet([headers, names, ...rows]);
    XLSX.utils.book_append_sheet(workBook, workSheet, item.code);
  });

  XLSX.writeFile(workBook, filename);
};

const getAttributes = (attributeTypes, type) => {
  return Object.keys(attributeTypes || {}).reduce((acc, attribute) => {
    if (attributeTypes.hasOwnProperty(attribute) && attributeTypes[attribute].allowedIn && includes(attributeTypes[attribute].allowedIn, type)) {
      acc.push({
        attribute,
        index: attributeTypes[attribute].index || 100,
        name: attributeTypes[attribute].name,
        type
      });
    }
    return acc;
  }, []).sort((a, b) => a.index - b.index);
};

const getItemAttributes = (attributes, item) => {
  const values = [];
  attributes.forEach(attr => {
    const value = item.attributes && item.attributes.hasOwnProperty(attr.attribute)
      ? item.attributes[attr.attribute]
      : null;
    values.push(isArray(value) ? value.join(', ') : value);
  });
  return values;
};

const createSingleSheetWorkBook = (attributeTypes, filename, items) => {
  const workBook = XLSX.utils.book_new();
  const functionAttributes = getAttributes(attributeTypes, 'function');
  const phaseAttributes = getAttributes(attributeTypes, 'phase');
  const actionAttributes = getAttributes(attributeTypes, 'action');
  const recordAttributes = getAttributes(attributeTypes, 'record');
  const attributes = [];
  const titles = [];

  [...CLASSIFICATION_ATTRIBUTES, ...functionAttributes, ...phaseAttributes, ...actionAttributes, ...recordAttributes].forEach(attr => {
    attributes.push(`${attr.type}.${attr.attribute}`);
    titles.push(attr.name);
  });

  const workSheet = XLSX.utils.aoa_to_sheet([attributes, titles]);

  items.forEach((item) => {
    const cols = [];

    CLASSIFICATION_ATTRIBUTES.forEach(attr => {
      cols.push(item.hasOwnProperty(attr.attribute) ? item[attr.attribute] : null);
    });

    cols.push(...getItemAttributes(functionAttributes, item));

    item.phases.forEach((phase) => {
      const phaseCols = getItemAttributes(phaseAttributes, phase);

      phase.actions.forEach((action) => {
        const actionCols = getItemAttributes(actionAttributes, action);

        action.records.forEach((record) => {
          const recordCols = getItemAttributes(recordAttributes, record);
          XLSX.utils.sheet_add_aoa(workSheet, [[...cols, ...phaseCols, ...actionCols, ...recordCols]], { origin: -1 });
        });

        if (isEmpty(action.records)) {
          XLSX.utils.sheet_add_aoa(workSheet, [[...cols, ...phaseCols, ...actionCols]], { origin: -1 });
        }
      });

      if (isEmpty(phase.actions)) {
        XLSX.utils.sheet_add_aoa(workSheet, [[...cols, ...phaseCols]], { origin: -1 });
      }
    });

    if (isEmpty(item.phases)) {
      XLSX.utils.sheet_add_aoa(workSheet, [[...cols]], { origin: -1 });
    }
  });

  XLSX.utils.book_append_sheet(workBook, workSheet, 'Tiedonohjaus');
  XLSX.writeFile(workBook, filename);
};

const Exporter = ({ attributeTypes, data, className, isVisible }) => {
  if (!isVisible) {
    return null;
  }

  const exportData = data.reduce(getChildren, []);
  const fileName = `helerm-export_${moment().format('DD.MM.YYYY')}.xlsx`;

  return (

      <Select
        autoBlur={false}
        clearable={false}
        value={null}
        onChange={(e) => {
          if (e.value === 0) {
            createSingleSheetWorkBook(attributeTypes, fileName, exportData);
          } else {
            createWorkBook(attributeTypes, fileName, exportData);
          }
        }}
        autoFocus={true}
        options={EXPORT_OPTIONS}
        placeholder={`Vie hakutulokset Exceliin (${exportData.length})`}
      />

  );
};

Exporter.propTypes = {
  attributeTypes: PropTypes.object,
  className: PropTypes.string,
  data: PropTypes.array,
  isVisible: PropTypes.bool
};

Exporter.defaultProps = {
  isVisible: true
};

export default Exporter;
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import FontAwesome from 'react-fontawesome';
import { Row, Col } from 'reactstrap';

import EditLinkForm from '../../components/Forms/EditLinkForm';

import './linkPost.scss';

export default class LinkPost extends Component {

  static propTypes = {
    upVote: PropTypes.func,
    downVote: PropTypes.func,
    onEditSubmit: PropTypes.func,
    onDelete: PropTypes.func,
    owner: PropTypes.bool,
    link: PropTypes.object,
    author: PropTypes.object
  }

  state = {
    isEditMode: false
  }

  toggleEditMode(){
    this.setState({
      isEditMode: !this.state.isEditMode,
    });
  }

  render() {

    const {
      onUpvote,
      onDownvote,
      onEditSubmit,
      onDelete,
      owner
    } = this.props;

    const {
      id,
      title,
      directory,
      url,
      description,
      image,
      created_at,
      updated_at,
      votes,
      upvoted,
      downvoted,
      comment_count
    } = this.props.link;

    const {
      username,
      avatar
    } = this.props.author;

    const {
      isEditMode
    } = this.state;

    return (
      <Row className="linkpost">
        <Col xs="12" sm="3" lg="2" className="side">

<Link to={`/u/${this.props.author.id}/${username}`}>{ username }</Link>

<button onClick={ onUpvote } disabled={ upvoted }><FontAwesome name="arrow-up" /></button>

{ votes }

<button onClick={ onDownvote } disabled={ downvoted }><FontAwesome name="arrow-down" /></button>

            {owner && (
<button onClick={ this.toggleEditMode.bind(this) }>Edit</button>
 )}
            {owner && (
<button onClick={ onDelete }>Remove</button>
 )}

        </Col>
        <Col xs="12" sm="9" lg="10">

{ title }

            { created_at }

              { isEditMode && (
                <EditLinkForm
                  onSubmit={ values => onEditSubmit(values).then(response => this.toggleEditMode())}
                  initialValues={{ description }}
                  showCancel={ true }
                  onCancelClick={ this.toggleEditMode.bind(this) }
                />
              ) || description}

Comments: { comment_count }

Edited: { created_at }

        </Col>
      </Row>
    );
  }
}
<commit_msg>Updated LinkPost to support deleted link<commit_after>import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import FontAwesome from 'react-fontawesome';
import { Row, Col } from 'reactstrap';

import EditLinkForm from '../../components/Forms/EditLinkForm';

import './linkPost.scss';

export default class LinkPost extends Component {

  static propTypes = {
    upVote: PropTypes.func,
    downVote: PropTypes.func,
    onEditSubmit: PropTypes.func,
    onDelete: PropTypes.func,
    owner: PropTypes.bool,
    link: PropTypes.object,
    author: PropTypes.object
  }

  state = {
    isEditMode: false
  }

  toggleEditMode(){
    this.setState({
      isEditMode: !this.state.isEditMode,
    });
  }

  render() {

    const {
      onUpvote,
      onDownvote,
      onEditSubmit,
      onDelete,
      owner
    } = this.props;

    const {
      id,
      title,
      directory,
      url,
      description,
      image,
      created_at,
      updated_at,
      votes,
      upvoted,
      downvoted,
      comment_count,
      deleted
    } = this.props.link;

    const {
      username,
      avatar
    } = this.props.author;

    const {
      isEditMode
    } = this.state;

    return (
      <Row className="linkpost">
        <Col xs="12" sm="3" lg="2" className="side">
          {!deleted && (  )}

<Link to={`/u/${this.props.author.id}/${username}`}>{ username }</Link>

<button onClick={ onUpvote } disabled={ deleted || upvoted }><FontAwesome name="arrow-up" /></button>

{ votes }

<button onClick={ onDownvote } disabled={ deleted || downvoted }><FontAwesome name="arrow-down" /></button>

            {!deleted && owner && (
<button onClick={ this.toggleEditMode.bind(this) }>Edit</button>
 )}
            {!deleted && owner && (
<button onClick={ onDelete }>Remove</button>
 )}

        </Col>
        <Col xs="12" sm="9" lg="10">

{ title }

            { created_at }

              {deleted && (Deleted...)}
              { !deleted && isEditMode && (
                <EditLinkForm
                  onSubmit={ values => onEditSubmit(values).then(response => this.toggleEditMode())}
                  initialValues={{ description }}
                  showCancel={ true }
                  onCancelClick={ this.toggleEditMode.bind(this) }
                />
              ) || description}

Comments: { comment_count }

Edited: { created_at }

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


<commit_before>import React, { PureComponent } from 'react';
import {
  Image as RNImage,
  Text as RNText,
  View as RNView,
} from 'react-native';
import PropTypes from 'prop-types';
import withStyles from '../../withStyles';
import RNTouchableOpacity from '../RNTouchableOpacity';
import ListItemImage from './ListItemImage';
import ListItemContentView from './ListItemContentView';
import ListItemTitle from './ListItemTitle';
import ListItemDetailText from './ListItemDetailText';
import ListItemDivider from './ListItemDivider';

const { propTypes: RNImageProps } = RNImage;
const { propTypes: RNTextProps } = RNText;

const accessoryTypes = {
  None: 'none',
  DisclosureIndicator: 'disclosureIndicator',
};

const propTypes = {
  renderImage: PropTypes.func,
  renderTitle: PropTypes.func,
  renderDetailText: PropTypes.func,
  renderAccessoryView: PropTypes.func,
  imageSource: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.object,
  ]),
  imageStyle: RNImageProps.style,
  title: PropTypes.string,
  titleStyle: RNTextProps.style,
  detailText: PropTypes.string,
  detailTextStyle: RNTextProps.style,
  accessoryType: PropTypes.string,
  onPress: PropTypes.func,
};
const defaultProps = {
  renderImage: null,
  renderTitle: null,
  renderDetailText: null,
  renderAccessoryView: null,
  imageSource: null,
  imageStyle: null,
  title: null,
  titleStyle: null,
  detailText: null,
  detailTextStyle: null,
  accessoryType: accessoryTypes.None,
  onPress: null,
};
const mapPropToStyles = [
  'activeOpacity',
];

class ListItem extends PureComponent {
  constructor(props) {
    super(props);

    this.renderListItemImage = this.renderListItemImage.bind(this);
    this.renderListItemTitle = this.renderListItemTitle.bind(this);
    this.renderListItemDetailText = this.renderListItemDetailText.bind(this);
  }

  renderListItemImage() {
    let {
      renderImage,
      imageSource,
      imageStyle,
    } = this.props;

    if (renderImage) {
      return renderImage();
    }

    if (!imageSource && !imageStyle) {
      return null;
    }
    if (imageStyle && typeof imageStyle === 'number') {
      imageStyle = StyleSheet.flatten(imageStyle);
    }

    return (
      <ListItemImage
        source={imageSource}
        style={imageStyle}
      />
    );
  }

  renderListItemTitle() {
    let {
      renderTitle,
      title,
      titleStyle,
    } = this.props;

    if (renderTitle) {
      return renderTitle();
    }
    if (!title) {
      return null;
    }
    if (titleStyle && typeof titleStyle === 'number') {
      titleStyle = StyleSheet.flatten(titleStyle);
    }
    return (
      <ListItemTitle style={titleStyle}>
        {title}
      </ListItemTitle>
    );
  }

  renderListItemDetailText() {
    let {
      renderDetailText,
      detailText,
      detailTextStyle,
    } = this.props;

    if (renderDetailText) {
      return renderDetailText();
    }
    if (!detailText) {
      return null;
    }
    if (detailTextStyle && typeof detailTextStyle === 'number') {
      detailTextStyle = StyleSheet.flatten(detailTextStyle);
    }
    return (
      <ListItemDetailText style={detailTextStyle}>
        {detailText}
      </ListItemDetailText>
    );
  }

  render() {
    /* eslint-disable prefer-const */
    let {
      accessoryView,
      accessoryType,
      cellStyle,
      onPress,
      ...restProps
    } = this.props;

    if (accessoryType) {
      switch (accessoryType) {
        case 'disclosureIndicator':
          // accessoryView = (
          //   <Image
          //     source={imgCommonNext}
          //     style={{ width: 6, height: 12, marginLeft: 6 }}
          //   />
          // );
          break;
        default:
          break;
      }
    }

    const imageView = this.renderListItemImage();
    const titleView = this.renderListItemTitle();
    const detailTextView = this.renderListItemDetailText();

    const Container = onPress ? RNTouchableOpacity : RNView;

    return (
      <Container
        {...restProps}
        onPress={onPress}
      >
        {imageView}
        <ListItemContentView
          cellStyle={cellStyle}
        >
          {titleView}
          {detailTextView}
        </ListItemContentView>
        {accessoryView}
      </Container>
    );
  }
}

ListItem.propTypes = propTypes;
ListItem.defaultProps = defaultProps;
// 自定义类型
ListItem.accessoryTypes = accessoryTypes;

ListItem.ContentView = ListItemContentView;
ListItem.Title = ListItemTitle;
ListItem.DetailText = ListItemDetailText;
ListItem.Divider = ListItemDivider;

export default withStyles('ListItem', mapPropToStyles)(ListItem);
<commit_msg>ListItem add accessoryView support<commit_after>import React, { PureComponent } from 'react';
import {
  Image as RNImage,
  Text as RNText,
  View as RNView,
} from 'react-native';
import PropTypes from 'prop-types';
import withStyles from '../../withStyles';
import RNTouchableOpacity from '../RNTouchableOpacity';
import Image from '../Image';
import ListItemImage from './ListItemImage';
import ListItemContentView from './ListItemContentView';
import ListItemTitle from './ListItemTitle';
import ListItemDetailText from './ListItemDetailText';
import ListItemDivider from './ListItemDivider';

const disclosureIndicatorImageBase64String = `data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAAa0lEQVR4Ae3RAQaAQBhE4cACEHvQ
RQBpAehwQdjb1I/AEEIa7XsMAB8z0N+bY9UFU2LHtckHY4BKsaagr1EZFKgXULsbagQFqhfUdody
A1Wny1Z3DBgw2Q3TwIB5UHLAaMUDIygHjLYIhugEZkaqyaxtmsIAAAAASUVORK5CYII=
`.replace(/\n/g, '');

const { propTypes: RNImageProps } = RNImage;
const { propTypes: RNTextProps } = RNText;

const accessoryTypes = {
  None: 'none',
  DisclosureIndicator: 'disclosureIndicator',
};

const propTypes = {
  renderImage: PropTypes.func,
  renderTitle: PropTypes.func,
  renderDetailText: PropTypes.func,
  renderAccessoryView: PropTypes.func,
  imageSource: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.object,
  ]),
  imageStyle: RNImageProps.style,
  title: PropTypes.string,
  titleStyle: RNTextProps.style,
  detailText: PropTypes.string,
  detailTextStyle: RNTextProps.style,
  accessoryType: PropTypes.string,
  onPress: PropTypes.func,
};
const defaultProps = {
  renderImage: null,
  renderTitle: null,
  renderDetailText: null,
  renderAccessoryView: null,
  imageSource: null,
  imageStyle: null,
  title: null,
  titleStyle: null,
  detailText: null,
  detailTextStyle: null,
  accessoryType: accessoryTypes.None,
  onPress: null,
};
const mapPropToStyles = [
  'activeOpacity',
];

class ListItem extends PureComponent {
  constructor(props) {
    super(props);

    this.renderListItemImage = this.renderListItemImage.bind(this);
    this.renderListItemTitle = this.renderListItemTitle.bind(this);
    this.renderListItemDetailText = this.renderListItemDetailText.bind(this);
    this.renderListItemAccessoryView = this.renderListItemAccessoryView.bind(this);
  }

  renderListItemImage() {
    let {
      renderImage,
      imageSource,
      imageStyle,
    } = this.props;

    if (renderImage) {
      return renderImage();
    }

    if (!imageSource && !imageStyle) {
      return null;
    }
    if (imageStyle && typeof imageStyle === 'number') {
      imageStyle = StyleSheet.flatten(imageStyle);
    }

    return (
      <ListItemImage
        source={imageSource}
        style={imageStyle}
      />
    );
  }

  renderListItemTitle() {
    let {
      renderTitle,
      title,
      titleStyle,
    } = this.props;

    if (renderTitle) {
      return renderTitle();
    }
    if (!title) {
      return null;
    }
    if (titleStyle && typeof titleStyle === 'number') {
      titleStyle = StyleSheet.flatten(titleStyle);
    }
    return (
      <ListItemTitle style={titleStyle}>
        {title}
      </ListItemTitle>
    );
  }

  renderListItemDetailText() {
    let {
      renderDetailText,
      detailText,
      detailTextStyle,
    } = this.props;

    if (renderDetailText) {
      return renderDetailText();
    }
    if (!detailText) {
      return null;
    }
    if (detailTextStyle && typeof detailTextStyle === 'number') {
      detailTextStyle = StyleSheet.flatten(detailTextStyle);
    }
    return (
      <ListItemDetailText style={detailTextStyle}>
        {detailText}
      </ListItemDetailText>
    );
  }

  renderListItemAccessoryView() {
    let {
      renderAccessoryView,
      accessoryType,
    } = this.props;
    if (renderAccessoryView) {
      return renderAccessoryView();
    }

    switch (accessoryType) {
      case accessoryTypes.DisclosureIndicator:
        return (
          <Image
            source={{ uri: disclosureIndicatorImageBase64String }}
            style={{ width: 6, height: 12, marginLeft: 6 }}
          />
        );
      default:
        return null;
    }
  }

  render() {
    /* eslint-disable prefer-const */
    let {
      onPress,
      ...restProps
    } = this.props;

    const imageView = this.renderListItemImage();
    const titleView = this.renderListItemTitle();
    const detailTextView = this.renderListItemDetailText();
    const accessoryView = this.renderListItemAccessoryView();

    const Container = onPress ? RNTouchableOpacity : RNView;

    return (
      <Container
        {...restProps}
        onPress={onPress}
      >
        {imageView}
        <ListItemContentView>
          {titleView}
          {detailTextView}
        </ListItemContentView>
        {accessoryView}
      </Container>
    );
  }
}

ListItem.propTypes = propTypes;
ListItem.defaultProps = defaultProps;
// 自定义类型
ListItem.accessoryTypes = accessoryTypes;

ListItem.ContentView = ListItemContentView;
ListItem.Title = ListItemTitle;
ListItem.DetailText = ListItemDetailText;
ListItem.Divider = ListItemDivider;

export default withStyles('ListItem', mapPropToStyles)(ListItem);
<|endoftext|>


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

import { TYPE_TO_CLASS } from '../../utils/constants';

class LogTable extends Component {
	renderLogTableRows() {
		return this.props.logs.map(log => {
			return (
				<tr key={`log-item-${log.time}`} className={TYPE_TO_CLASS[log.type]}>
					{log.time}
					{log.type}
					{log.message}

			);
		});
	}

	render() {
		if(this.props.selected.application.id === '') {
			return null;
		}

		return (

Logs

										Time
										Type
										Message

									{this.renderLogTableRows()}

		);
	}
}

function mapStateToProps(state) {
	return {
		logs: state.logs[state.selected.application.id] || [],
		selected: state.selected
	};
}

export default connect(
	mapStateToProps,
	null
)(LogTable);
<commit_msg>Added a helpful message in the initial home screen.<commit_after>import React, { Component } from 'react';
import { connect } from 'react-redux';

import { TYPE_TO_CLASS } from '../../utils/constants';

class LogTable extends Component {
	renderLogTableRows() {
		return this.props.logs.map(log => {
			return (
				<tr key={`log-item-${log.time}`} className={TYPE_TO_CLASS[log.type]}>
					{log.time}
					{log.type}
					{log.message}

			);
		});
	}

	render() {
		if(this.props.selected.application.id === '') {
			return (

Welcome to the SocketIO Log Viewer

Select an application in the sidebar to view logs in real time.

			);
		}

		return (

Logs

										Time
										Type
										Message

									{this.renderLogTableRows()}

		);
	}
}

function mapStateToProps(state) {
	return {
		logs: state.logs[state.selected.application.id] || [],
		selected: state.selected
	};
}

export default connect(
	mapStateToProps,
	null
)(LogTable);
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import themeable from 'instructure-ui/lib/themeable';
import Typography from 'instructure-ui/lib/components/Typography';
import Checkbox from 'instructure-ui/lib/components/Checkbox';
import Link from 'instructure-ui/lib/components/Link';
import ScreenReaderContent from 'instructure-ui/lib/components/ScreenReaderContent';
import Assignment from 'instructure-icons/react/Line/IconAssignmentLine';
import Quiz from 'instructure-icons/react/Line/IconQuizLine';
import Announcement from 'instructure-icons/react/Line/IconAnnouncementLine';
import Discussion from 'instructure-icons/react/Line/IconDiscussionLine';
import Note from 'instructure-icons/react/Line/IconNoteLightLine';
import Calendar from 'instructure-icons/react/Line/IconCalendarMonthLine';
import Page from 'instructure-icons/react/Line/IconMsWordLine';
import styles from './styles.css';
import theme from './theme.js';
import {bool, instanceOf, number, string, func} from 'prop-types';
import formatMessage from '../../format-message';

class PlannerItem extends Component {
  static propTypes = {
    color: string.isRequired,
    id: number.isRequired,
    title: string.isRequired,
    points: number,
    date: instanceOf(Date),
    completed: bool,
    associated_item: string,
    toggleCompletion: func.isRequired,
  }

  constructor (props) {
    super(props);
    this.state = {
      completed: props.completed,
    };
  }

  componentWillReceiveProps (nextProps) {
    this.setState({
      completed: nextProps.completed
    });
  }

  toggleCompletion = (e) => {
    this.props.toggleCompletion(this.props.id);
    this.setState({
      completed: !this.state.completed,
    });
  }

  renderIcon = () => {
    switch(this.props.associated_item) {
        case "Assignment":
          return <Assignment />;
        case "Quiz":
          return <Quiz />;
        case "Discussion":
          return <Discussion />;
        case "Announcement":
          return <Announcement />;
        case "Calendar Event":
          return <Calendar />;
        case "Page":
          return <Page />;
        default:
          return <Note />;

    }
  }

  render () {
    let checkBoxLabel = this.state.completed
      ? formatMessage('Task {title} is complete', { title: this.props.title })
      : formatMessage('Task {title} is incomplete', { title: this.props.title });
    return (

          <Checkbox
            label={<ScreenReaderContent>{checkBoxLabel}</ScreenReaderContent>}
            checked={this.state.completed}
            onChange={this.toggleCompletion}
          />

          {this.renderIcon()}

              <Typography size="x-small" color="secondary">
                {this.props.associated_item}
              </Typography>

              <Link href="#">{this.props.title}</Link>

Pills (sep. component?)

                <Typography color="secondary">
                  <Typography size="large">{this.props.points}</Typography>
                  <Typography size="x-small">
                    { this.props.points
                        ? formatMessage('pts')
                        : null
                    }
                  </Typography>
                </Typography>

                <Typography color="secondary" size="x-small">
                    { this.props.date
                        ? formatMessage(`DUE: {date, time, short}`, {date: this.props.date})
                        : null
                    }
                </Typography>

    );
  }
}

export default themeable(theme, styles)(PlannerItem);
<commit_msg>refactor render method of planneritem<commit_after>import React, { Component } from 'react';
import themeable from 'instructure-ui/lib/themeable';
import Typography from 'instructure-ui/lib/components/Typography';
import Checkbox from 'instructure-ui/lib/components/Checkbox';
import Link from 'instructure-ui/lib/components/Link';
import ScreenReaderContent from 'instructure-ui/lib/components/ScreenReaderContent';
import Assignment from 'instructure-icons/react/Line/IconAssignmentLine';
import Quiz from 'instructure-icons/react/Line/IconQuizLine';
import Announcement from 'instructure-icons/react/Line/IconAnnouncementLine';
import Discussion from 'instructure-icons/react/Line/IconDiscussionLine';
import Note from 'instructure-icons/react/Line/IconNoteLightLine';
import Calendar from 'instructure-icons/react/Line/IconCalendarMonthLine';
import Page from 'instructure-icons/react/Line/IconMsWordLine';
import styles from './styles.css';
import theme from './theme.js';
import {bool, instanceOf, number, string, func} from 'prop-types';
import formatMessage from '../../format-message';

class PlannerItem extends Component {
  static propTypes = {
    color: string.isRequired,
    id: number.isRequired,
    title: string.isRequired,
    points: number,
    date: instanceOf(Date),
    completed: bool,
    associated_item: string,
    toggleCompletion: func.isRequired,
  }

  constructor (props) {
    super(props);
    this.state = {
      completed: props.completed,
    };
  }

  componentWillReceiveProps (nextProps) {
    this.setState({
      completed: nextProps.completed
    });
  }

  toggleCompletion = (e) => {
    this.props.toggleCompletion(this.props.id);
    this.setState({
      completed: !this.state.completed,
    });
  }

  renderIcon = () => {
    switch(this.props.associated_item) {
        case "Assignment":
          return <Assignment />;
        case "Quiz":
          return <Quiz />;
        case "Discussion":
          return <Discussion />;
        case "Announcement":
          return <Announcement />;
        case "Calendar Event":
          return <Calendar />;
        case "Page":
          return <Page />;
        default:
          return <Note />;

    }
  }

  renderBadges = () => {
    //We need to replace this with the badge
    //Component and configure how many badges to render
    return  (

Pills (sep. component?)

    );
  }

  renderItemMetrics = () => {
    return (

        {this.renderBadges()}

            <Typography color="secondary">
              <Typography size="large">{this.props.points}</Typography>
              <Typography size="x-small">
                { this.props.points
                    ? formatMessage('pts')
                    : null
                }
              </Typography>
            </Typography>

            <Typography color="secondary" size="x-small">
                { this.props.date
                    ? formatMessage(`DUE: {date, time, short}`, {date: this.props.date})
                    : null
                }
            </Typography>

    );
  }

  renderItemDetails = () => {
    return (

          <Typography size="x-small" color="secondary">
            {this.props.associated_item}
          </Typography>

          <Link href="#">{this.props.title}</Link>

    );
  }

  render () {
    let checkBoxLabel = this.state.completed
      ? formatMessage('Task {title} is complete', { title: this.props.title })
      : formatMessage('Task {title} is incomplete', { title: this.props.title });
    return (

          <Checkbox
            label={<ScreenReaderContent>{checkBoxLabel}</ScreenReaderContent>}
            checked={this.state.completed}
            onChange={this.toggleCompletion}
          />

          {this.renderIcon()}

          {this.renderItemDetails()}
          {this.renderItemMetrics()}

    );
  }
}

export default themeable(theme, styles)(PlannerItem);
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import Table from './Table';

const defaultValueRenderer = (val) =>
  _.isString(val) ?
    val :
    (
      _.isArray(val) ?
        `[${val.map(defaultValueRenderer).join(',')}]` :
        JSON.stringify(val)
    );

class SimpleTable extends React.Component {
  static propTypes = {
    /**
     * An array of data items to use for the rows of the table.
     */
    items: PropTypes.array.isRequired,
    /**
     * Renders a Table.Row that represents a particular item. Row must include an onClick prop for details to work.
     * (item) => <Row />
     */
    renderRow: PropTypes.func,
    /**
     * Renders the detail view of any item when that item is selected.
     */
    renderDetails: PropTypes.func,
    /**
     * An array of column descriptions. Column name should correspond to a key in the data items.
     */
    columns: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string.isRequired,
      sortable: PropTypes.bool,
    })),
    /**
     * Called when the user changes the sort state of a column. Column name and sort order are passed as params.
     * (columnName, sortOrder) => void
     */
    onSortChanged: PropTypes.func,
    /**
     * Indicates whether the table should render a loading state.
     */
    loading: PropTypes.bool,
  };

  static defaultProps = {
    renderRow: (item) => (

        {Object.values(item).map((val) =>
{defaultValueRenderer(val)}
)}

    ),
    renderDetails: null,
    columns: null,
    onSortChanged: () => null,
    loading: false,
  };

  state = {
    sortOrders: {},
    detailsItem: null,
  };

  blankSortOrders = () => this.props.columns.reduce((orders, { name }) => ({ ...orders, [name]: 0 }), {});

  createHeaderClickHandler = (headerName) =>
    this.props.onSortChanged ?
      (sortOrder) => {
        const newSortOrder = sortOrder || -this.state.sortOrders[headerName] || 1;
        this.setState({
          sortOrders: {
            ...this.blankSortOrders(),
            [headerName]: newSortOrder,
          },
        });
        this.props.onSortChanged(headerName, newSortOrder);
      } :
      null;

  createRowClickHandler = (item) => () => {
    if (this.state.detailsItem === item) {
      this.setState({ detailsItem: null });
    } else {
      this.setState({ detailsItem: item });
    }
  };

  renderDetails = (item) => {
    const { renderDetails } = this.props;
    return (

        {renderDetails(item)}

    );
  };

  renderRows = () => {
    const { items, renderRow } = this.props;
    return items.reduce((elements, item) => {
      const rendered = React.cloneElement(renderRow(item), { onClick: this.createRowClickHandler(item) });
      if (item === this.state.detailsItem) {
        return [
          ...elements,
          rendered,
          this.renderDetails(item),
        ];
      }
      return [...elements, rendered];
    }, []);
  };

  renderColumnHeaders = () => {
    const { columns, onSortChanged } = this.props;
    const { sortOrders } = this.state;
    if (!columns) {
      return null;
    }

    return (

        {columns.map((column, idx) => (

            {column.name}

        ))}

    );
  }

  render() {
    return (

        {this.renderRows()}

    );
  }
}

SimpleTable.usage = `
Provides a more straighforward interface for creating a Table which may suit most use cases.

\`\`\`

\`\`\`
`;

export default SimpleTable;
<commit_msg>Improve SimpleTable API<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import Table from './Table';

const defaultValueRenderer = (val) =>
  _.isString(val) ?
    val :
    (
      _.isArray(val) ?
        `[${val.map(defaultValueRenderer).join(',')}]` :
        JSON.stringify(val)
    );

class SimpleTable extends React.Component {
  static propTypes = {
    /**
     * An array of data items to use for the rows of the table.
     */
    items: PropTypes.array.isRequired,
    /**
     * Renders a Table.Row that represents a particular item. Row must include an onClick prop for details to work.
     * (item) => <Row />
     */
    renderRow: PropTypes.func,
    /**
     * Renders the detail view of any item when that item is selected.
     */
    renderDetails: PropTypes.func,
    /**
     * An array of column descriptions. Column name should correspond to a key in the data items. displayName is what is actually rendered (defaults to name).
     */
    columns: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string.isRequired,
      displayName: PropTypes.string.isRequired,
      sortable: PropTypes.bool,
    })),
    /**
     * Called when the user changes the sort state of a column. Column name and sort order are passed as params.
     * (columnName, sortOrder) => void
     */
    onSortChanged: PropTypes.func,
    /**
     * Indicates whether the table should render a loading state.
     */
    loading: PropTypes.bool,
  };

  static defaultProps = {
    renderRow: (item) => (

        {Object.values(item).map((val) =>
{defaultValueRenderer(val)}
)}

    ),
    renderDetails: null,
    columns: null,
    onSortChanged: () => null,
    loading: false,
  };

  state = {
    sortOrders: {},
    detailsItem: null,
  };

  blankSortOrders = () => this.props.columns.reduce((orders, { name }) => ({ ...orders, [name]: 0 }), {});

  createHeaderClickHandler = (headerName) =>
    this.props.onSortChanged ?
      (sortOrder) => {
        const newSortOrder = sortOrder || -this.state.sortOrders[headerName] || 1;
        this.setState({
          sortOrders: {
            ...this.blankSortOrders(),
            [headerName]: newSortOrder,
          },
        });
        this.props.onSortChanged(headerName, newSortOrder);
      } :
      null;

  createRowClickHandler = (item) => () => {
    if (this.state.detailsItem === item) {
      this.setState({ detailsItem: null });
    } else {
      this.setState({ detailsItem: item });
    }
  };

  renderDetails = (item) => {
    const { renderDetails } = this.props;
    return (

        {renderDetails(item)}

    );
  };

  renderRows = () => {
    const { items, renderRow } = this.props;
    return items.reduce((elements, item) => {
      const rendered = React.cloneElement(renderRow(item), { onClick: this.createRowClickHandler(item) });
      if (item === this.state.detailsItem) {
        return [
          ...elements,
          rendered,
          this.renderDetails(item),
        ];
      }
      return [...elements, rendered];
    }, []);
  };

  renderColumnHeaders = () => {
    const { columns, onSortChanged } = this.props;
    const { sortOrders } = this.state;
    if (!columns) {
      return null;
    }

    return (

        {columns.map((column, idx) => (

            {column.displayName || column.name}

        ))}

    );
  }

  render() {
    return (

        {this.renderRows()}

    );
  }
}

SimpleTable.usage = `
Provides a more straighforward interface for creating a Table which may suit most use cases.

\`\`\`

\`\`\`
`;

export default SimpleTable;
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import styles from 'components/Track/Track.scss';

export default class Track extends React.Component {

  render() {
    return (

            {this.props.name}

            {this.props.description}

    )
  }

}

Track.propTypes = {
  name: PropTypes.string.isRequired,
  image: PropTypes.string,
  description: PropTypes.string.isRequired
}
<commit_msg>Add back Track component image<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import styles from 'components/Track/Track.scss';

export default class Track extends React.Component {

  render() {
    return (

            {this.props.name}

            {this.props.description}

    )
  }

}

Track.propTypes = {
  name: PropTypes.string.isRequired,
  image: PropTypes.string.isRequired,
  description: PropTypes.string.isRequired
}
<|endoftext|>


<commit_before>import styled from 'styled-components';
import StyledIconButton from '../icon-button/icon-button.style';

const AlertStyle = styled.div`
  ${StyledIconButton} {
    position: absolute;
    top: 25px !important;
  }
`;

export default AlertStyle;
<commit_msg>refactor(alert): using styled-components specificity<commit_after>import styled from 'styled-components';
import StyledIconButton from '../icon-button/icon-button.style';

const AlertStyle = styled.div`
  && ${StyledIconButton} {
    position: absolute;
    top: 25px;
  }
`;

export default AlertStyle;
<|endoftext|>


<commit_before>import React from 'react';
import currencyData from '../data/currencies.js';
import listener from '../lib/listener.js';
import form from '../lib/form.js';
import reactGA from 'react-ga';

module.exports = React.createClass({
  getInitialState: function() {
    return {
      currency: {}
    };
  },
  componentDidMount: function() {
    listener.on("stateUpdated", this.onStateUpdated);
  },
  componentWillUnmount: function() {
    listener.off("stateUpdated", this.onStateUpdated);
  },
  onStateUpdated: function(e) {
    var detail = e.detail;
    var state = detail.state;
    var value = detail.value;
    if (state === "currency") {
      this.setState({
        currency: value
      });
    }
  },
  onChange: function(e) {
    var currency = currencyData[e.currentTarget.value];
    if (currency) {
      form.updateState("currency", currency);
    }
    reactGA.event({
      category: "User Flow",
      action: "Changed Currency",
      label: currency.code
    });
  },
  render: function() {
    return (
      <select onChange={this.onChange} className="currency-dropdown" value={this.state.currency.code}>
        {Object.keys(currencyData).map(function(currency, i) {
          return (
            <option value={currency} key={i}>
              {currencyData[currency].symbol}   {currency}
            </option>
          );
        })}
      </select>
    );
  }
});
<commit_msg>Fixing failing test case.<commit_after>import React from 'react';
import currencyData from '../data/currencies.js';
import listener from '../lib/listener.js';
import form from '../lib/form.js';
import reactGA from 'react-ga';

module.exports = React.createClass({
  getInitialState: function() {
    return {
      currency: {}
    };
  },
  componentDidMount: function() {
    listener.on("stateUpdated", this.onStateUpdated);
  },
  componentWillUnmount: function() {
    listener.off("stateUpdated", this.onStateUpdated);
  },
  onStateUpdated: function(e) {
    var detail = e.detail;
    var state = detail.state;
    var value = detail.value;
    if (state === "currency") {
      this.setState({
        currency: value
      });
    }
  },
  onChange: function(e) {
    var currency = currencyData[e.currentTarget.value];
    if (currency) {
      form.updateState("currency", currency);
      reactGA.event({
        category: "User Flow",
        action: "Changed Currency",
        label: currency.code
      });
    }
  },
  render: function() {
    return (
      <select onChange={this.onChange} className="currency-dropdown" value={this.state.currency.code}>
        {Object.keys(currencyData).map(function(currency, i) {
          return (
            <option value={currency} key={i}>
              {currencyData[currency].symbol}   {currency}
            </option>
          );
        })}
      </select>
    );
  }
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import propTypes from 'prop-types';
import {
  setStyleProperty,
  removeStyleProperty,
  getRoot,
  isValidProperty,
} from '../utilities';

class CustomProperties extends Component {
  constructor(props) {
    super(props);

    this.container = null;
    this.containerRef = this.containerRef.bind(this);
    this.getContainer = this.getContainer.bind(this);
    this.handleNewProperties = this.handleNewProperties.bind(this);
  }

  componentDidMount() {
    const { properties } = this.props;
    const keys = Object.keys(properties);

    keys.forEach(key => {
      setStyleProperty(this.getContainer(), key, properties[key]);
    });
  }

  componentWillReceiveProps(nextProps) {
    const { properties } = this.props;

    if (nextProps.properties !== properties) {
      this.handleNewProperties(nextProps.properties, properties);
    }
  }

  componentWillUnmount() {
    const { global, properties } = this.props;

    if (!global) {
      return;
    }

    const keys = Object.keys(properties);

    keys.forEach(key => {
      removeStyleProperty(this.getContainer(), key);
    });
  }

  containerRef(element) {
    this.container = element;
  }

  getContainer() {
    const { global } = this.props;

    return global ? getRoot() : this.container;
  }

  handleNewProperties(next, previous) {
    const nextKeys = Object.keys(next);
    const previousKeys = Object.keys(previous);
    const removedKeys = previousKeys
      .filter(key => typeof next[key] === 'undefined');

    nextKeys
      .filter(key => next[key] !== previous[key])
      .forEach(key => {
        setStyleProperty(this.getContainer(), key, next[key]);
      });

    removedKeys.forEach(key => {
      removeStyleProperty(this.getContainer(), key);
    });
  }

  render() {
    return (

        {this.props.children}

    );
  }
}

CustomProperties.propTypes = {
  global: propTypes.bool,
  properties: propTypes.objectOf((value, key, componentName) => {
    if (!isValidProperty(key)) {
      return new Error(`
<${componentName} /> could not set the property "${key}: ${value[key]};".
Custom Property names must be a string starting with two dashes, for example "--theme-background".
      `.trim());
    }
  }),
};

CustomProperties.defaultProps = {
  global: false,
  properties: {},
};

export default CustomProperties;
<commit_msg>Only use container element when needed.<commit_after>import React, { Component } from 'react';
import propTypes from 'prop-types';
import {
  setStyleProperty,
  removeStyleProperty,
  getRoot,
  isValidProperty,
} from '../utilities';

class CustomProperties extends Component {
  constructor(props) {
    super(props);

    this.container = null;
    this.containerRef = this.containerRef.bind(this);
    this.getContainer = this.getContainer.bind(this);
    this.handleNewProperties = this.handleNewProperties.bind(this);
  }

  componentDidMount() {
    const { properties } = this.props;
    const keys = Object.keys(properties);

    keys.forEach(key => {
      setStyleProperty(this.getContainer(), key, properties[key]);
    });
  }

  componentWillReceiveProps(nextProps) {
    const { properties } = this.props;

    if (nextProps.properties !== properties) {
      this.handleNewProperties(nextProps.properties, properties);
    }
  }

  componentWillUnmount() {
    const { global, properties } = this.props;

    if (!global) {
      return;
    }

    const keys = Object.keys(properties);

    keys.forEach(key => {
      removeStyleProperty(this.getContainer(), key);
    });
  }

  containerRef(element) {
    this.container = element;
  }

  getContainer() {
    const { global } = this.props;

    return global ? getRoot() : this.container;
  }

  handleNewProperties(next, previous) {
    const nextKeys = Object.keys(next);
    const previousKeys = Object.keys(previous);
    const removedKeys = previousKeys
      .filter(key => typeof next[key] === 'undefined');

    nextKeys
      .filter(key => next[key] !== previous[key])
      .forEach(key => {
        setStyleProperty(this.getContainer(), key, next[key]);
      });

    removedKeys.forEach(key => {
      removeStyleProperty(this.getContainer(), key);
    });
  }

  render() {
    return !this.props.global ? (

        {this.props.children}

    ) : (this.props.children || null);
  }
}

CustomProperties.propTypes = {
  global: propTypes.bool,
  properties: propTypes.objectOf((value, key, componentName) => {
    if (!isValidProperty(key)) {
      return new Error(`
<${componentName} /> could not set the property "${key}: ${value[key]};".
Custom Property names must be a string starting with two dashes, for example "--theme-background".
      `.trim());
    }
  }),
};

CustomProperties.defaultProps = {
  global: false,
  properties: {},
};

export default CustomProperties;
<|endoftext|>