<commit_before>/**
 * @module echarts/component/toolbox/feature/DataView
 */

define(function (require) {

    var zrUtil = require('zrender/core/util');
    var eventTool = require('zrender/core/event');

    var BLOCK_SPLITER = new Array(60).join('-');
    var ITEM_SPLITER = '\t';
    /**
     * Group series into two types
     *  1. on category axis, like line, bar
     *  2. others, like scatter, pie
     * @param {module:echarts/model/Global} ecModel
     * @return {Object}
     * @inner
     */
    function groupSeries(ecModel) {
        var seriesGroupByCategoryAxis = {};
        var otherSeries = [];
        var meta = [];
        ecModel.eachRawSeries(function (seriesModel) {
            var coordSys = seriesModel.coordinateSystem;

            if (coordSys && (coordSys.type === 'cartesian2d' || coordSys.type === 'polar')) {
                var baseAxis = coordSys.getBaseAxis();
                if (baseAxis.type === 'category') {
                    var key = baseAxis.dim + '_' + baseAxis.index;
                    if (!seriesGroupByCategoryAxis[key]) {
                        seriesGroupByCategoryAxis[key] = {
                            categoryAxis: baseAxis,
                            valueAxis: coordSys.getOtherAxis(baseAxis),
                            series: []
                        };
                        meta.push({
                            axisDim: baseAxis.dim,
                            axisIndex: baseAxis.index
                        });
                    }
                    seriesGroupByCategoryAxis[key].series.push(seriesModel);
                }
                else {
                    otherSeries.push(seriesModel);
                }
            }
            else {
                otherSeries.push(seriesModel);
            }
        });

        return {
            seriesGroupByCategoryAxis: seriesGroupByCategoryAxis,
            other: otherSeries,
            meta: meta
        };
    }

    /**
     * Assemble content of series on cateogory axis
     * @param {Array.<module:echarts/model/Series>} series
     * @return {string}
     * @inner
     */
    function assembleSeriesWithCategoryAxis(series) {
        var tables = [];
        zrUtil.each(series, function (group, key) {
            var categoryAxis = group.categoryAxis;
            var valueAxis = group.valueAxis;
            var valueAxisDim = valueAxis.dim;

            var headers = [' '].concat(zrUtil.map(group.series, function (series) {
                return series.name;
            }));
            var columns = [categoryAxis.model.getCategories()];
            zrUtil.each(group.series, function (series) {
                columns.push(series.getRawData().mapArray(valueAxisDim, function (val) {
                    return val;
                }));
            });
            // Assemble table content
            var lines = [headers.join(ITEM_SPLITER)];
            for (var i = 0; i < columns[0].length; i++) {
                var items = [];
                for (var j = 0; j < columns.length; j++) {
                    items.push(columns[j][i]);
                }
                lines.push(items.join(ITEM_SPLITER));
            }
            tables.push(lines.join('\n'));
        });
        return tables.join('\n\n' +  BLOCK_SPLITER + '\n\n');
    }

    /**
     * Assemble content of other series
     * @param {Array.<module:echarts/model/Series>} series
     * @return {string}
     * @inner
     */
    function assembleOtherSeries(series) {
        return zrUtil.map(series, function (series) {
            var data = series.getRawData();
            var lines = [series.name];
            var vals = [];
            data.each(data.dimensions, function () {
                var argLen = arguments.length;
                var dataIndex = arguments[argLen - 1];
                var name = data.getName(dataIndex);
                for (var i = 0; i < argLen - 1; i++) {
                    vals[i] = arguments[i];
                }
                lines.push((name ? (name + ITEM_SPLITER) : '') + vals.join(ITEM_SPLITER));
            });
            return lines.join('\n');
        }).join('\n\n' + BLOCK_SPLITER + '\n\n');
    }

    /**
     * @param {module:echarts/model/Global}
     * @return {string}
     * @inner
     */
    function getContentFromModel(ecModel) {

        var result = groupSeries(ecModel);

        return {
            value: zrUtil.filter([
                    assembleSeriesWithCategoryAxis(result.seriesGroupByCategoryAxis),
                    assembleOtherSeries(result.other)
                ], function (str) {
                    return str.replace(/[\n\t\s]/g, '');
                }).join('\n\n' + BLOCK_SPLITER + '\n\n'),

            meta: result.meta
        };
    }

    function trim(str) {
        return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    }
    /**
     * If a block is tsv format
     */
    function isTSVFormat(block) {
        // Simple method to find out if a block is tsv format
        var firstLine = block.slice(0, block.indexOf('\n'));
        if (firstLine.indexOf(ITEM_SPLITER) >= 0) {
            return true;
        }
    }

    var itemSplitRegex = new RegExp('[' + ITEM_SPLITER + '| ]+', 'g');
    /**
     * @param {string} tsv
     * @return {Array.<Object>}
     */
    function parseTSVContents(tsv) {
        var tsvLines = tsv.split(/\n+/g);
        var headers = trim(tsvLines.shift()).split(itemSplitRegex);

        var categories = [];
        var series = zrUtil.map(headers, function (header) {
            return {
                name: header,
                data: []
            };
        });
        for (var i = 0; i < tsvLines.length; i++) {
            var items = trim(tsvLines[i]).split(itemSplitRegex);
            categories.push(items.shift());
            for (var j = 0; j < items.length; j++) {
                series[j] && (series[j].data[i] = items[j]);
            }
        }
        return {
            series: series,
            categories: categories
        };
    }

    /**
     * @param {string} str
     * @return {Array.<Object>}
     * @inner
     */
    function parseListContents(str) {
        var lines = str.split(/\n+/g);
        var seriesName = trim(lines.shift());

        var data = [];
        for (var i = 0; i < lines.length; i++) {
            var items = trim(lines[i]).split(itemSplitRegex);
            data[i] = [];
            for (var j = 0; j < items.length; j++) {
                var item = items[j];
                data[i].push(item);
            }
        }

        return {
            name: seriesName,
            data: data
        };
    }

    /**
     * @param {string} str
     * @param {Array.<Object>} blockMetaList
     * @return {Object}
     * @inner
     */
    function parseContents(str, blockMetaList) {
        var blocks = str.split(new RegExp('\n*' + BLOCK_SPLITER + '\n*', 'g'));
        var newOption = {
            series: []
        };
        zrUtil.each(blocks, function (block, idx) {
            if (isTSVFormat(block)) {
                var result = parseTSVContents(block);
                var blockMeta = blockMetaList[idx];
                var axisKey = blockMeta.axisDim + 'Axis';

                if (blockMeta) {
                    newOption[axisKey] = newOption[axisKey] || [];
                    newOption[axisKey][blockMeta.axisIndex] = {
                        data: result.categories
                    };
                    newOption.series = newOption.series.concat(result.series);
                }
            }
            else {
                var result = parseListContents(block);
                newOption.series.push(result);
            }
        });
        return newOption;
    }

    /**
     * @alias {module:echarts/component/toolbox/feature/DataView}
     * @constructor
     * @param {module:echarts/model/Model} model
     */
    function DataView(model) {

        this._dom = null;

        this.model = model;
    }

    DataView.defaultOption = {
        show: true,
        readOnly: false,
        icon: 'M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28',
        title: '数据视图',
        lang: ['数据视图', '关闭', '刷新'],
        buttonColor: '#c23531',
        buttonTextColor: '#fff'
    };

    DataView.prototype.onclick = function (ecModel, api) {
        var container = api.getDom();
        var model = this.model;
        if (this._dom) {
            container.removeChild(this._dom);
        }
        var root = document.createElement('div');
        root.style.cssText = 'position:absolute;left:5px;top:5px;bottom:5px;right:5px;'
            + 'background-color: white';

        // Create elements
        var header = document.createElement('h4');
        var lang = model.get('lang') || [];
        header.innerHTML = lang[0] || model.get('title');
        header.style.cssText = 'margin: 10px 20px;';
        var textarea = document.createElement('textarea');
        textarea.style.cssText = 'display:block;width:100%;font-size:14px;line-height:1.6rem;font-family:Monaco,Consolas,Courier New';
        textarea.readOnly = model.get('readOnly');

        var result = getContentFromModel(ecModel);
        textarea.value = result.value;
        var blockMetaList = result.meta;

        var buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;';

        var buttonStyle = 'float:right;margin-right:20px;border:none;'
            + 'cursor:pointer;padding:4px 8px;font-size:12px;border-radius:3px';
        var closeButton = document.createElement('div');
        var refreshButton = document.createElement('div');

        buttonStyle += ';background-color:' + model.get('buttonColor');
        buttonStyle += ';color:' + model.get('buttonTextColor');

        var self = this;

        function close() {
            container.removeChild(root);
            self._dom = null;
        }
        eventTool.addEventListener(closeButton, 'click', close);

        eventTool.addEventListener(refreshButton, 'click', function () {
            var newOption;
            try {
                newOption = parseContents(textarea.value, blockMetaList);
            }
            catch (e) {
                close();
                throw new Error('Data view format error ' + e);
            }
            api.dispatchAction({
                type: 'changeDataView',
                newOption: newOption
            });

            close();
        });

        closeButton.innerHTML = lang[1];
        refreshButton.innerHTML = lang[2];
        refreshButton.style.cssText = buttonStyle;
        closeButton.style.cssText = buttonStyle;

        buttonContainer.appendChild(refreshButton);
        buttonContainer.appendChild(closeButton);

        // http://stackoverflow.com/questions/6637341/use-tab-to-indent-in-textarea
        eventTool.addEventListener(textarea, 'keydown', function (e) {
            if ((e.keyCode || e.which) === 9) {
                // get caret position/selection
                var val = this.value;
                var start = this.selectionStart;
                var end = this.selectionEnd;

                // set textarea value to: text before caret + tab + text after caret
                this.value = val.substring(0, start) + ITEM_SPLITER + val.substring(end);

                // put caret at right position again
                this.selectionStart = this.selectionEnd = start + 1;

                // prevent the focus lose
                eventTool.stop(e);
            }
        });

        root.appendChild(header);
        root.appendChild(textarea);
        root.appendChild(buttonContainer);

        textarea.style.height = (container.clientHeight - 80) + 'px';

        container.appendChild(root);
        this._dom = root;
    };

    DataView.prototype.remove = function (ecModel, api) {
        this._dom && api.getDom().removeChild(this._dom);
    };

    DataView.prototype.dispose = function (ecModel, api) {
        this.remove(ecModel, api);
    };

    /**
     * @inner
     */
    function tryMergeDataOption(newData, originalData) {
        return zrUtil.map(newData, function (newVal, idx) {
            var original = originalData && originalData[idx];
            if (zrUtil.isObject(original) && !zrUtil.isArray(original)) {
                return zrUtil.defaults({
                    value: newVal
                }, original);
            }
            else {
                return newVal;
            }
        });
    }

    require('../featureManager').register('dataView', DataView);

    require('../../../echarts').registerAction({
        type: 'changeDataView',
        event: 'dataViewChanged',
        update: 'prepareAndUpdate'
    }, function (payload, ecModel) {
        var newSeriesOptList = [];
        zrUtil.each(payload.newOption.series, function (seriesOpt) {
            var seriesModel = ecModel.getSeriesByName(seriesOpt.name)[0];
            if (!seriesModel) {
                // New created series
                // Geuss the series type
                newSeriesOptList.push(zrUtil.extend({
                    // Default is scatter
                    type: 'scatter'
                }, seriesOpt));
            }
            else {
                var originalData = seriesModel.get('data');
                newSeriesOptList.push({
                    name: seriesOpt.name,
                    data: tryMergeDataOption(seriesOpt.data, originalData)
                });
            }
        });

        ecModel.mergeOption(zrUtil.defaults({
            series: newSeriesOptList
        }, payload.newOption));
    });

    return DataView;
});<commit_msg>DataView 样式设置<commit_after>/**
 * @module echarts/component/toolbox/feature/DataView
 */

define(function (require) {

    var zrUtil = require('zrender/core/util');
    var eventTool = require('zrender/core/event');

    var BLOCK_SPLITER = new Array(60).join('-');
    var ITEM_SPLITER = '\t';
    /**
     * Group series into two types
     *  1. on category axis, like line, bar
     *  2. others, like scatter, pie
     * @param {module:echarts/model/Global} ecModel
     * @return {Object}
     * @inner
     */
    function groupSeries(ecModel) {
        var seriesGroupByCategoryAxis = {};
        var otherSeries = [];
        var meta = [];
        ecModel.eachRawSeries(function (seriesModel) {
            var coordSys = seriesModel.coordinateSystem;

            if (coordSys && (coordSys.type === 'cartesian2d' || coordSys.type === 'polar')) {
                var baseAxis = coordSys.getBaseAxis();
                if (baseAxis.type === 'category') {
                    var key = baseAxis.dim + '_' + baseAxis.index;
                    if (!seriesGroupByCategoryAxis[key]) {
                        seriesGroupByCategoryAxis[key] = {
                            categoryAxis: baseAxis,
                            valueAxis: coordSys.getOtherAxis(baseAxis),
                            series: []
                        };
                        meta.push({
                            axisDim: baseAxis.dim,
                            axisIndex: baseAxis.index
                        });
                    }
                    seriesGroupByCategoryAxis[key].series.push(seriesModel);
                }
                else {
                    otherSeries.push(seriesModel);
                }
            }
            else {
                otherSeries.push(seriesModel);
            }
        });

        return {
            seriesGroupByCategoryAxis: seriesGroupByCategoryAxis,
            other: otherSeries,
            meta: meta
        };
    }

    /**
     * Assemble content of series on cateogory axis
     * @param {Array.<module:echarts/model/Series>} series
     * @return {string}
     * @inner
     */
    function assembleSeriesWithCategoryAxis(series) {
        var tables = [];
        zrUtil.each(series, function (group, key) {
            var categoryAxis = group.categoryAxis;
            var valueAxis = group.valueAxis;
            var valueAxisDim = valueAxis.dim;

            var headers = [' '].concat(zrUtil.map(group.series, function (series) {
                return series.name;
            }));
            var columns = [categoryAxis.model.getCategories()];
            zrUtil.each(group.series, function (series) {
                columns.push(series.getRawData().mapArray(valueAxisDim, function (val) {
                    return val;
                }));
            });
            // Assemble table content
            var lines = [headers.join(ITEM_SPLITER)];
            for (var i = 0; i < columns[0].length; i++) {
                var items = [];
                for (var j = 0; j < columns.length; j++) {
                    items.push(columns[j][i]);
                }
                lines.push(items.join(ITEM_SPLITER));
            }
            tables.push(lines.join('\n'));
        });
        return tables.join('\n\n' +  BLOCK_SPLITER + '\n\n');
    }

    /**
     * Assemble content of other series
     * @param {Array.<module:echarts/model/Series>} series
     * @return {string}
     * @inner
     */
    function assembleOtherSeries(series) {
        return zrUtil.map(series, function (series) {
            var data = series.getRawData();
            var lines = [series.name];
            var vals = [];
            data.each(data.dimensions, function () {
                var argLen = arguments.length;
                var dataIndex = arguments[argLen - 1];
                var name = data.getName(dataIndex);
                for (var i = 0; i < argLen - 1; i++) {
                    vals[i] = arguments[i];
                }
                lines.push((name ? (name + ITEM_SPLITER) : '') + vals.join(ITEM_SPLITER));
            });
            return lines.join('\n');
        }).join('\n\n' + BLOCK_SPLITER + '\n\n');
    }

    /**
     * @param {module:echarts/model/Global}
     * @return {string}
     * @inner
     */
    function getContentFromModel(ecModel) {

        var result = groupSeries(ecModel);

        return {
            value: zrUtil.filter([
                    assembleSeriesWithCategoryAxis(result.seriesGroupByCategoryAxis),
                    assembleOtherSeries(result.other)
                ], function (str) {
                    return str.replace(/[\n\t\s]/g, '');
                }).join('\n\n' + BLOCK_SPLITER + '\n\n'),

            meta: result.meta
        };
    }

    function trim(str) {
        return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    }
    /**
     * If a block is tsv format
     */
    function isTSVFormat(block) {
        // Simple method to find out if a block is tsv format
        var firstLine = block.slice(0, block.indexOf('\n'));
        if (firstLine.indexOf(ITEM_SPLITER) >= 0) {
            return true;
        }
    }

    var itemSplitRegex = new RegExp('[' + ITEM_SPLITER + '| ]+', 'g');
    /**
     * @param {string} tsv
     * @return {Array.<Object>}
     */
    function parseTSVContents(tsv) {
        var tsvLines = tsv.split(/\n+/g);
        var headers = trim(tsvLines.shift()).split(itemSplitRegex);

        var categories = [];
        var series = zrUtil.map(headers, function (header) {
            return {
                name: header,
                data: []
            };
        });
        for (var i = 0; i < tsvLines.length; i++) {
            var items = trim(tsvLines[i]).split(itemSplitRegex);
            categories.push(items.shift());
            for (var j = 0; j < items.length; j++) {
                series[j] && (series[j].data[i] = items[j]);
            }
        }
        return {
            series: series,
            categories: categories
        };
    }

    /**
     * @param {string} str
     * @return {Array.<Object>}
     * @inner
     */
    function parseListContents(str) {
        var lines = str.split(/\n+/g);
        var seriesName = trim(lines.shift());

        var data = [];
        for (var i = 0; i < lines.length; i++) {
            var items = trim(lines[i]).split(itemSplitRegex);
            data[i] = [];
            for (var j = 0; j < items.length; j++) {
                var item = items[j];
                data[i].push(item);
            }
        }

        return {
            name: seriesName,
            data: data
        };
    }

    /**
     * @param {string} str
     * @param {Array.<Object>} blockMetaList
     * @return {Object}
     * @inner
     */
    function parseContents(str, blockMetaList) {
        var blocks = str.split(new RegExp('\n*' + BLOCK_SPLITER + '\n*', 'g'));
        var newOption = {
            series: []
        };
        zrUtil.each(blocks, function (block, idx) {
            if (isTSVFormat(block)) {
                var result = parseTSVContents(block);
                var blockMeta = blockMetaList[idx];
                var axisKey = blockMeta.axisDim + 'Axis';

                if (blockMeta) {
                    newOption[axisKey] = newOption[axisKey] || [];
                    newOption[axisKey][blockMeta.axisIndex] = {
                        data: result.categories
                    };
                    newOption.series = newOption.series.concat(result.series);
                }
            }
            else {
                var result = parseListContents(block);
                newOption.series.push(result);
            }
        });
        return newOption;
    }

    /**
     * @alias {module:echarts/component/toolbox/feature/DataView}
     * @constructor
     * @param {module:echarts/model/Model} model
     */
    function DataView(model) {

        this._dom = null;

        this.model = model;
    }

    DataView.defaultOption = {
        show: true,
        readOnly: false,
        icon: 'M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28',
        title: '数据视图',
        lang: ['数据视图', '关闭', '刷新'],
        backgroundColor: '#fff',
        textColor: '#000',
        textareaColor: '#fff',
        textareaBorderColor: '#333',
        buttonColor: '#c23531',
        buttonTextColor: '#fff'
    };

    DataView.prototype.onclick = function (ecModel, api) {
        var container = api.getDom();
        var model = this.model;
        if (this._dom) {
            container.removeChild(this._dom);
        }
        var root = document.createElement('div');
        root.style.cssText = 'position:absolute;left:5px;top:5px;bottom:5px;right:5px;';
        root.style.backgroundColor = model.get('backgroundColor') || '#fff';

        // Create elements
        var header = document.createElement('h4');
        var lang = model.get('lang') || [];
        header.innerHTML = lang[0] || model.get('title');
        header.style.cssText = 'margin: 10px 20px;';

        var textarea = document.createElement('textarea');
        // Textarea style
        textarea.style.cssText = 'display:block;width:100%;font-size:14px;line-height:1.6rem;font-family:Monaco,Consolas,Courier New';
        textarea.readOnly = model.get('readOnly');
        textarea.style.color = model.get('textColor');
        textarea.style.borderColor = model.get('textareaBorderColor');
        textarea.style.backgroundColor = model.get('textareaColor');

        var result = getContentFromModel(ecModel);
        textarea.value = result.value;
        var blockMetaList = result.meta;

        var buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;';

        var buttonStyle = 'float:right;margin-right:20px;border:none;'
            + 'cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px';
        var closeButton = document.createElement('div');
        var refreshButton = document.createElement('div');

        buttonStyle += ';background-color:' + model.get('buttonColor');
        buttonStyle += ';color:' + model.get('buttonTextColor');

        var self = this;

        function close() {
            container.removeChild(root);
            self._dom = null;
        }
        eventTool.addEventListener(closeButton, 'click', close);

        eventTool.addEventListener(refreshButton, 'click', function () {
            var newOption;
            try {
                newOption = parseContents(textarea.value, blockMetaList);
            }
            catch (e) {
                close();
                throw new Error('Data view format error ' + e);
            }
            api.dispatchAction({
                type: 'changeDataView',
                newOption: newOption
            });

            close();
        });

        closeButton.innerHTML = lang[1];
        refreshButton.innerHTML = lang[2];
        refreshButton.style.cssText = buttonStyle;
        closeButton.style.cssText = buttonStyle;

        buttonContainer.appendChild(refreshButton);
        buttonContainer.appendChild(closeButton);

        // http://stackoverflow.com/questions/6637341/use-tab-to-indent-in-textarea
        eventTool.addEventListener(textarea, 'keydown', function (e) {
            if ((e.keyCode || e.which) === 9) {
                // get caret position/selection
                var val = this.value;
                var start = this.selectionStart;
                var end = this.selectionEnd;

                // set textarea value to: text before caret + tab + text after caret
                this.value = val.substring(0, start) + ITEM_SPLITER + val.substring(end);

                // put caret at right position again
                this.selectionStart = this.selectionEnd = start + 1;

                // prevent the focus lose
                eventTool.stop(e);
            }
        });

        root.appendChild(header);
        root.appendChild(textarea);
        root.appendChild(buttonContainer);

        textarea.style.height = (container.clientHeight - 80) + 'px';

        container.appendChild(root);
        this._dom = root;
    };

    DataView.prototype.remove = function (ecModel, api) {
        this._dom && api.getDom().removeChild(this._dom);
    };

    DataView.prototype.dispose = function (ecModel, api) {
        this.remove(ecModel, api);
    };

    /**
     * @inner
     */
    function tryMergeDataOption(newData, originalData) {
        return zrUtil.map(newData, function (newVal, idx) {
            var original = originalData && originalData[idx];
            if (zrUtil.isObject(original) && !zrUtil.isArray(original)) {
                return zrUtil.defaults({
                    value: newVal
                }, original);
            }
            else {
                return newVal;
            }
        });
    }

    require('../featureManager').register('dataView', DataView);

    require('../../../echarts').registerAction({
        type: 'changeDataView',
        event: 'dataViewChanged',
        update: 'prepareAndUpdate'
    }, function (payload, ecModel) {
        var newSeriesOptList = [];
        zrUtil.each(payload.newOption.series, function (seriesOpt) {
            var seriesModel = ecModel.getSeriesByName(seriesOpt.name)[0];
            if (!seriesModel) {
                // New created series
                // Geuss the series type
                newSeriesOptList.push(zrUtil.extend({
                    // Default is scatter
                    type: 'scatter'
                }, seriesOpt));
            }
            else {
                var originalData = seriesModel.get('data');
                newSeriesOptList.push({
                    name: seriesOpt.name,
                    data: tryMergeDataOption(seriesOpt.data, originalData)
                });
            }
        });

        ecModel.mergeOption(zrUtil.defaults({
            series: newSeriesOptList
        }, payload.newOption));
    });

    return DataView;
});<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Link from '../Link';

const CoursesList = ({ courses }) => (

    {courses.map(({ id, title }) => (

        <Link to={`/courses/${id}`}>{title} </Link>

    ))}

);

CoursesList.propTypes = {
  courses: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.string,
      title: PropTypes.string,
    }),
  ).isRequired,
  user: PropTypes.shape({
    isAdmin: PropTypes.bool,
  }).isRequired,
};

const mapStateToProps = state => ({
  user: state.user,
});

export default connect(mapStateToProps)(CoursesList);
<commit_msg>remove redux related code from CourseList<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Link from '../Link';

const CoursesList = ({ courses }) => (

    {courses.map(({ id, title }) => (

        <Link to={`/courses/${id}`}>{title}</Link>

    ))}

);

CoursesList.propTypes = {
  courses: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.string.isRequired,
      title: PropTypes.string.isRequired,
    }),
  ).isRequired,
};

export default CoursesList;
<|endoftext|>


<commit_before>import cx from 'clsx';
import React from 'react';
import PropTypes from 'prop-types';
import { FixedSizeList } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import AutoSizer from 'react-virtualized-auto-sizer';
import itemSelection from 'item-selection/immutable';
import useDirection from '../../hooks/useDirection';
import LoadingRow from './LoadingRow';

const {
  useCallback,
  useEffect,
  useRef,
  useState,
} = React;

/**
 * Check if two media lists are different, taking into account
 * pagination. If the new media list contains items where the previous
 * list doesn't, but every other item is identical, we assume
 * the new list has just loaded a page that wasn't loaded in the
 * previous one, and decide that the list is not really different.
 */
function didMediaChange(prev, next) {
  return prev.some((item, i) => item && next[i] && item._id !== next[i]._id);
}

const defaultMakeActions = () => null;

function BaseMediaList({
  className,
  media,
  listComponent: ListComponent,
  rowComponent: RowComponent,
  rowProps = {},
  onRequestPage,
  onOpenPreviewMediaDialog,
  // The `size` property is only necessary for lazy loading.
  size = null,
  makeActions = defaultMakeActions,
}) {
  const lastMediaRef = useRef(media);
  const [selection, setSelection] = useState(() => itemSelection(media));
  const direction = useDirection();

  useEffect(() => {
    const lastMedia = lastMediaRef.current;
    if (lastMedia !== media) {
      lastMediaRef.current = media;
      const selectedIndices = selection.getIndices();
      const mediaChanged = didMediaChange(lastMedia, media);
      setSelection(mediaChanged ? itemSelection(media) : itemSelection(media, selectedIndices));
    }
  }, [media, selection]);

  const selectItem = useCallback((index, event) => {
    event.preventDefault();

    if (event.shiftKey) {
      setSelection(selection.selectRange(index));
    } else if (event.ctrlKey) {
      setSelection(selection.selectToggle(index));
    } else {
      setSelection(selection.select(index));
    }
  }, [selection]);

  const renderRow = useCallback(({ index, style }) => {
    const selected = selection.isSelectedIndex(index);
    if (!media[index]) {
      return (
        <LoadingRow
          key={index}
          className="MediaList-row"
          style={style}
          selected={selected}
        />
      );
    }

    return (
      <RowComponent
        key={media[index] ? media[index]._id : index}
        {...rowProps}
        style={style}
        className="MediaList-row"
        media={media[index]}
        selected={selected}
        selection={selection.get()}
        onClick={(event) => selectItem(index, event)}
        onOpenPreviewMediaDialog={onOpenPreviewMediaDialog}
        makeActions={() => makeActions(media[index], selection, index)}
      />
    );
    // `RowComponent` should really be in this list but then react-hooks/exhaustive-deps complains.
    // We don't change it on the fly ever I think and shouldn't, but if we ever did have a reason
    // to do it, this might break :)
  }, [selection, media, rowProps, onOpenPreviewMediaDialog, makeActions, selectItem]);

  const mediaLength = media.length;
  const innerList = ({ height, onItemsRendered, ref }) => (
    <FixedSizeList
      itemCount={size || mediaLength}
      itemSize={56}
      height={height}
      onItemsRendered={onItemsRendered}
      ref={ref}
      width="100%"
      direction={direction}
      rerenderOnUpdate={selection}
    >
      {renderRow}
    </FixedSizeList>
  );

  // This is not used as a real React component.
  // eslint-disable-next-line react/prop-types
  const lazyLoading = (makeList) => ({ height }) => {
    const isItemLoaded = (index) => media[index] != null;
    const loadMoreItems = (start) => {
      const page = Math.floor(start / 50);
      onRequestPage(page);
    };

    const inner = ({ onItemsRendered, ref }) => makeList({ onItemsRendered, ref, height });
    return (
      <InfiniteLoader
        isItemLoaded={isItemLoaded}
        itemCount={size || mediaLength}
        loadMoreItems={loadMoreItems}
      >
        {inner}
      </InfiniteLoader>
    );
  };

  const customWrapper = (makeList) => (props) => (
    <ListComponent>
      {makeList(props)}
    </ListComponent>
  );

  const autoSizing = (makeList) => () => {
    const inner = ({ height }) => makeList({ height });
    return (
      <AutoSizer disableWidth>
        {inner}
      </AutoSizer>
    );
  };

  let listRenderer = innerList;
  if (onRequestPage) {
    listRenderer = lazyLoading(listRenderer);
  }
  listRenderer = customWrapper(listRenderer);
  listRenderer = autoSizing(listRenderer);

  return (

      {listRenderer()}

  );
}

BaseMediaList.propTypes = {
  className: PropTypes.string,
  media: PropTypes.array,
  size: PropTypes.number,
  onRequestPage: PropTypes.func,
  listComponent: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.func,
  ]).isRequired,
  rowComponent: PropTypes.func.isRequired,
  rowProps: PropTypes.object,

  onOpenPreviewMediaDialog: PropTypes.func,
  makeActions: PropTypes.func,
};

export default BaseMediaList;
<commit_msg>[Hotfix] Throttling requests sent from infinite scrolling behavior (#1873)<commit_after>import cx from 'clsx';
import React from 'react';
import PropTypes from 'prop-types';
import { FixedSizeList } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import AutoSizer from 'react-virtualized-auto-sizer';
import itemSelection from 'item-selection/immutable';
import useDirection from '../../hooks/useDirection';
import LoadingRow from './LoadingRow';

const {
  useCallback,
  useEffect,
  useRef,
  useState,
} = React;

/**
 * Check if two media lists are different, taking into account
 * pagination. If the new media list contains items where the previous
 * list doesn't, but every other item is identical, we assume
 * the new list has just loaded a page that wasn't loaded in the
 * previous one, and decide that the list is not really different.
 */
function didMediaChange(prev, next) {
  return prev.some((item, i) => item && next[i] && item._id !== next[i]._id);
}

const defaultMakeActions = () => null;

function BaseMediaList({
  className,
  media,
  listComponent: ListComponent,
  rowComponent: RowComponent,
  rowProps = {},
  onRequestPage,
  onOpenPreviewMediaDialog,
  // The `size` property is only necessary for lazy loading.
  size = null,
  makeActions = defaultMakeActions,
}) {
  const lastMediaRef = useRef(media);
  const [selection, setSelection] = useState(() => itemSelection(media));
  const inFlightPageRequests = useRef({});
  const direction = useDirection();

  useEffect(() => {
    const lastMedia = lastMediaRef.current;
    if (lastMedia !== media) {
      lastMediaRef.current = media;
      const selectedIndices = selection.getIndices();
      const mediaChanged = didMediaChange(lastMedia, media);
      setSelection(mediaChanged ? itemSelection(media) : itemSelection(media, selectedIndices));
    }
  }, [media, selection]);

  const selectItem = useCallback((index, event) => {
    event.preventDefault();

    if (event.shiftKey) {
      setSelection(selection.selectRange(index));
    } else if (event.ctrlKey) {
      setSelection(selection.selectToggle(index));
    } else {
      setSelection(selection.select(index));
    }
  }, [selection]);

  const renderRow = useCallback(({ index, style }) => {
    const selected = selection.isSelectedIndex(index);
    if (!media[index]) {
      return (
        <LoadingRow
          key={index}
          className="MediaList-row"
          style={style}
          selected={selected}
        />
      );
    }

    return (
      <RowComponent
        key={media[index] ? media[index]._id : index}
        {...rowProps}
        style={style}
        className="MediaList-row"
        media={media[index]}
        selected={selected}
        selection={selection.get()}
        onClick={(event) => selectItem(index, event)}
        onOpenPreviewMediaDialog={onOpenPreviewMediaDialog}
        makeActions={() => makeActions(media[index], selection, index)}
      />
    );
    // `RowComponent` should really be in this list but then react-hooks/exhaustive-deps complains.
    // We don't change it on the fly ever I think and shouldn't, but if we ever did have a reason
    // to do it, this might break :)
  }, [selection, media, rowProps, onOpenPreviewMediaDialog, makeActions, selectItem]);

  const mediaLength = media.length;
  const innerList = ({ height, onItemsRendered, ref }) => (
    <FixedSizeList
      itemCount={size || mediaLength}
      itemSize={56}
      height={height}
      onItemsRendered={onItemsRendered}
      ref={ref}
      width="100%"
      direction={direction}
      rerenderOnUpdate={selection}
    >
      {renderRow}
    </FixedSizeList>
  );

  // This is not used as a real React component.
  // eslint-disable-next-line react/prop-types
  const lazyLoading = (makeList) => ({ height }) => {
    const isItemLoaded = (index) => media[index] != null;
    const loadMoreItems = (start) => {
      const page = Math.floor(start / 50);
      if (inFlightPageRequests.current[page]) return Promise.resolve(null);
      inFlightPageRequests.current[page] = 1;

      return onRequestPage(page).finally(() => {
        // Without the timeout we can still get duplicate requests.
        // That is *probably* because a rerender is triggered by some
        // redux action on request completion, just *before* the new
        // playlist items are actually stored in state.
        setTimeout(() => {
          delete inFlightPageRequests.current[page];
        }, 200);
      });
    };

    const inner = ({ onItemsRendered, ref }) => makeList({ onItemsRendered, ref, height });
    return (
      <InfiniteLoader
        isItemLoaded={isItemLoaded}
        itemCount={size || mediaLength}
        loadMoreItems={loadMoreItems}
      >
        {inner}
      </InfiniteLoader>
    );
  };

  const customWrapper = (makeList) => (props) => (
    <ListComponent>
      {makeList(props)}
    </ListComponent>
  );

  const autoSizing = (makeList) => () => {
    const inner = ({ height }) => makeList({ height });
    return (
      <AutoSizer disableWidth>
        {inner}
      </AutoSizer>
    );
  };

  let listRenderer = innerList;
  if (onRequestPage) {
    listRenderer = lazyLoading(listRenderer);
  }
  listRenderer = customWrapper(listRenderer);
  listRenderer = autoSizing(listRenderer);

  return (

      {listRenderer()}

  );
}

BaseMediaList.propTypes = {
  className: PropTypes.string,
  media: PropTypes.array,
  size: PropTypes.number,
  onRequestPage: PropTypes.func,
  listComponent: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.func,
  ]).isRequired,
  rowComponent: PropTypes.func.isRequired,
  rowProps: PropTypes.object,

  onOpenPreviewMediaDialog: PropTypes.func,
  makeActions: PropTypes.func,
};

export default BaseMediaList;
<|endoftext|>


<commit_before>// Components
import VDatePickerTitle from './VDatePickerTitle'
import VDatePickerHeader from './VDatePickerHeader'
import VDatePickerDateTable from './VDatePickerDateTable'
import VDatePickerMonthTable from './VDatePickerMonthTable'
import VDatePickerYears from './VDatePickerYears'

// Mixins
import Picker from '../../mixins/picker'

// Utils
import { pad, createNativeLocaleFormatter } from './util'
import isDateAllowed from './util/isDateAllowed'
import { consoleWarn } from '../../util/console'

/* @vue/component */
export default {
  name: 'v-date-picker',

  mixins: [Picker],

  props: {
    allowedDates: Function,
    // Function formatting the day in date picker table
    dayFormat: {
      type: Function,
      default: null
    },
    events: {
      type: [Array, Object, Function],
      default: () => null
    },
    eventColor: {
      type: [String, Function, Object],
      default: 'warning'
    },
    firstDayOfWeek: {
      type: [String, Number],
      default: 0
    },
    // Function formatting the tableDate in the day/month table header
    headerDateFormat: {
      type: Function,
      default: null
    },
    locale: {
      type: String,
      default: 'en-us'
    },
    max: String,
    min: String,
    // Function formatting month in the months table
    monthFormat: {
      type: Function,
      default: null
    },
    multiple: Boolean,
    nextIcon: {
      type: String,
      default: '$vuetify.icons.next'
    },
    pickerDate: String,
    prevIcon: {
      type: String,
      default: '$vuetify.icons.prev'
    },
    reactive: Boolean,
    readonly: Boolean,
    scrollable: Boolean,
    showCurrent: {
      type: [Boolean, String],
      default: true
    },
    // Function formatting currently selected date in the picker title
    titleDateFormat: {
      type: Function,
      default: null
    },
    type: {
      type: String,
      default: 'date',
      validator: type => ['date', 'month'].includes(type) // TODO: year
    },
    value: [Array, String],
    // Function formatting the year in table header and pickup title
    yearFormat: {
      type: Function,
      default: null
    },
    yearIcon: String
  },

  data () {
    const now = new Date()
    return {
      activePicker: this.type.toUpperCase(),
      defaultColor: 'accent',
      inputDay: null,
      inputMonth: null,
      inputYear: null,
      isReversing: false,
      now,
      // tableDate is a string in 'YYYY' / 'YYYY-M' format (leading zero for month is not required)
      tableDate: (() => {
        if (this.pickerDate) {
          return this.pickerDate
        }

        const date = (this.multiple ? this.value[this.value.length - 1] : this.value) ||
          `${now.getFullYear()}-${now.getMonth() + 1}`
        const type = this.type === 'date' ? 'month' : 'year'
        return this.sanitizeDateString(date, type)
      })()
    }
  },

  computed: {
    lastValue () {
      return this.multiple ? this.value[this.value.length - 1] : this.value
    },
    selectedMonths () {
      if (!this.value || !this.value.length || this.type === 'month') {
        return this.value
      } else if (this.multiple) {
        return this.value.map(val => val.substr(0, 7))
      } else {
        return this.value.substr(0, 7)
      }
    },
    current () {
      if (this.showCurrent === true) {
        return this.sanitizeDateString(`${this.now.getFullYear()}-${this.now.getMonth() + 1}-${this.now.getDate()}`, this.type)
      }

      return this.showCurrent || null
    },
    inputDate () {
      return this.type === 'date'
        ? `${this.inputYear}-${pad(this.inputMonth + 1)}-${pad(this.inputDay)}`
        : `${this.inputYear}-${pad(this.inputMonth + 1)}`
    },
    tableMonth () {
      return (this.pickerDate || this.tableDate).split('-')[1] - 1
    },
    tableYear () {
      return (this.pickerDate || this.tableDate).split('-')[0] * 1
    },
    minMonth () {
      return this.min ? this.sanitizeDateString(this.min, 'month') : null
    },
    maxMonth () {
      return this.max ? this.sanitizeDateString(this.max, 'month') : null
    },
    minYear () {
      return this.min ? this.sanitizeDateString(this.min, 'year') : null
    },
    maxYear () {
      return this.max ? this.sanitizeDateString(this.max, 'year') : null
    },
    formatters () {
      return {
        year: this.yearFormat || createNativeLocaleFormatter(this.locale, { year: 'numeric', timeZone: 'UTC' }, { length: 4 }),
        titleDate: this.titleDateFormat || (this.multiple ? this.defaultTitleMultipleDateFormatter : this.defaultTitleDateFormatter)
      }
    },
    defaultTitleMultipleDateFormatter () {
      if (this.value.length < 2) {
        return dates => dates.length ? this.defaultTitleDateFormatter(dates[0]) : '0 selected'
      }

      return dates => `${dates.length} selected`
    },
    defaultTitleDateFormatter () {
      const titleFormats = {
        year: { year: 'numeric', timeZone: 'UTC' },
        month: { month: 'long', timeZone: 'UTC' },
        date: { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC' }
      }

      const titleDateFormatter = createNativeLocaleFormatter(this.locale, titleFormats[this.type], {
        start: 0,
        length: { date: 10, month: 7, year: 4 }[this.type]
      })

      const landscapeFormatter = date => titleDateFormatter(date)
        .replace(/([^\d\s])([\d])/g, (match, nonDigit, digit) => `${nonDigit} ${digit}`)
        .replace(', ', ',
')

      return this.landscape ? landscapeFormatter : titleDateFormatter
    }
  },

  watch: {
    tableDate (val, prev) {
      // Make a ISO 8601 strings from val and prev for comparision, otherwise it will incorrectly
      // compare for example '2000-9' and '2000-10'
      const sanitizeType = this.type === 'month' ? 'year' : 'month'
      this.isReversing = this.sanitizeDateString(val, sanitizeType) < this.sanitizeDateString(prev, sanitizeType)
      this.$emit('update:pickerDate', val)
    },
    pickerDate (val) {
      if (val) {
        this.tableDate = val
      } else if (this.lastValue && this.type === 'date') {
        this.tableDate = this.sanitizeDateString(this.lastValue, 'month')
      } else if (this.lastValue && this.type === 'month') {
        this.tableDate = this.sanitizeDateString(this.lastValue, 'year')
      }
    },
    value () {
      this.checkMultipleProp()
      this.setInputDate()

      if (this.value && !this.pickerDate) {
        this.tableDate = this.sanitizeDateString(this.inputDate, this.type === 'month' ? 'year' : 'month')
      }
    },
    type (type) {
      this.activePicker = type.toUpperCase()

      if (this.value && this.value.length) {
        const output = (this.multiple ? this.value : [this.value])
          .map(val => this.sanitizeDateString(val, type))
          .filter(this.isDateAllowed)
        this.$emit('input', this.multiple ? output : output[0])
      }
    }
  },

  created () {
    this.checkMultipleProp()

    if (this.pickerDate !== this.tableDate) {
      this.$emit('update:pickerDate', this.tableDate)
    }
    this.setInputDate()
  },

  methods: {
    emitInput (newInput) {
      const output = this.multiple
        ? (
          this.value.indexOf(newInput) === -1
            ? this.value.concat([newInput])
            : this.value.filter(x => x !== newInput)
        )
        : newInput

      this.$emit('input', output)
      this.multiple || this.$emit('change', newInput)
    },
    checkMultipleProp () {
      if (this.value == null) return
      const valueType = this.value.constructor.name
      const expected = this.multiple ? 'Array' : 'String'
      if (valueType !== expected) {
        consoleWarn(`Value must be ${this.multiple ? 'an' : 'a'} ${expected}, got ${valueType}`, this)
      }
    },
    isDateAllowed (value) {
      return isDateAllowed(value, this.min, this.max, this.allowedDates)
    },
    yearClick (value) {
      this.inputYear = value
      if (this.type === 'month') {
        this.tableDate = `${value}`
      } else {
        this.tableDate = `${value}-${pad(this.tableMonth + 1)}`
      }
      this.activePicker = 'MONTH'
      this.reactive && !this.multiple && this.isDateAllowed(this.inputDate) && this.$emit('input', this.inputDate)
    },
    monthClick (value) {
      this.inputYear = parseInt(value.split('-')[0], 10)
      this.inputMonth = parseInt(value.split('-')[1], 10) - 1
      if (this.type === 'date') {
        this.tableDate = value
        this.activePicker = 'DATE'
        this.reactive && !this.multiple && this.isDateAllowed(this.inputDate) && this.$emit('input', this.inputDate)
      } else {
        this.emitInput(this.inputDate)
      }
    },
    dateClick (value) {
      this.inputYear = parseInt(value.split('-')[0], 10)
      this.inputMonth = parseInt(value.split('-')[1], 10) - 1
      this.inputDay = parseInt(value.split('-')[2], 10)
      this.emitInput(this.inputDate)
    },
    genPickerTitle () {
      return this.$createElement(VDatePickerTitle, {
        props: {
          date: this.value ? this.formatters.titleDate(this.value) : '',
          selectingYear: this.activePicker === 'YEAR',
          year: this.formatters.year(`${this.inputYear}`),
          yearIcon: this.yearIcon,
          value: this.multiple ? this.value[0] : this.value
        },
        slot: 'title',
        style: this.readonly ? {
          'pointer-events': 'none'
        } : undefined,
        on: {
          'update:selectingYear': value => this.activePicker = value ? 'YEAR' : this.type.toUpperCase()
        }
      })
    },
    genTableHeader () {
      return this.$createElement(VDatePickerHeader, {
        props: {
          nextIcon: this.nextIcon,
          color: this.color,
          dark: this.dark,
          disabled: this.readonly,
          format: this.headerDateFormat,
          light: this.light,
          locale: this.locale,
          min: this.activePicker === 'DATE' ? this.minMonth : this.minYear,
          max: this.activePicker === 'DATE' ? this.maxMonth : this.maxYear,
          prevIcon: this.prevIcon,
          value: this.activePicker === 'DATE' ? `${this.tableYear}-${pad(this.tableMonth + 1)}` : `${this.tableYear}`
        },
        on: {
          toggle: () => this.activePicker = (this.activePicker === 'DATE' ? 'MONTH' : 'YEAR'),
          input: value => this.tableDate = value
        }
      })
    },
    genDateTable () {
      return this.$createElement(VDatePickerDateTable, {
        props: {
          allowedDates: this.allowedDates,
          color: this.color,
          current: this.current,
          dark: this.dark,
          disabled: this.readonly,
          events: this.events,
          eventColor: this.eventColor,
          firstDayOfWeek: this.firstDayOfWeek,
          format: this.dayFormat,
          light: this.light,
          locale: this.locale,
          min: this.min,
          max: this.max,
          tableDate: `${this.tableYear}-${pad(this.tableMonth + 1)}`,
          scrollable: this.scrollable,
          value: this.value
        },
        ref: 'table',
        on: {
          input: this.dateClick,
          tableDate: value => this.tableDate = value
        }
      })
    },
    genMonthTable () {
      return this.$createElement(VDatePickerMonthTable, {
        props: {
          allowedDates: this.type === 'month' ? this.allowedDates : null,
          color: this.color,
          current: this.current ? this.sanitizeDateString(this.current, 'month') : null,
          dark: this.dark,
          disabled: this.readonly,
          format: this.monthFormat,
          light: this.light,
          locale: this.locale,
          min: this.minMonth,
          max: this.maxMonth,
          scrollable: this.scrollable,
          value: this.selectedMonths,
          tableDate: `${this.tableYear}`
        },
        ref: 'table',
        on: {
          input: this.monthClick,
          tableDate: value => this.tableDate = value
        }
      })
    },
    genYears () {
      return this.$createElement(VDatePickerYears, {
        props: {
          color: this.color,
          format: this.yearFormat,
          locale: this.locale,
          min: this.minYear,
          max: this.maxYear,
          value: `${this.tableYear}`
        },
        on: {
          input: this.yearClick
        }
      })
    },
    genPickerBody () {
      const children = this.activePicker === 'YEAR' ? [
        this.genYears()
      ] : [
        this.genTableHeader(),
        this.activePicker === 'DATE' ? this.genDateTable() : this.genMonthTable()
      ]

      return this.$createElement('div', {
        key: this.activePicker,
        style: this.readonly ? {
          'pointer-events': 'none'
        } : undefined
      }, children)
    },
    // Adds leading zero to month/day if necessary, returns 'YYYY' if type = 'year',
    // 'YYYY-MM' if 'month' and 'YYYY-MM-DD' if 'date'
    sanitizeDateString (dateString, type) {
      const [year, month = 1, date = 1] = dateString.split('-')
      return `${year}-${pad(month)}-${pad(date)}`.substr(0, { date: 10, month: 7, year: 4 }[type])
    },
    setInputDate () {
      if (this.lastValue) {
        const array = this.lastValue.split('-')
        this.inputYear = parseInt(array[0], 10)
        this.inputMonth = parseInt(array[1], 10) - 1
        if (this.type === 'date') {
          this.inputDay = parseInt(array[2], 10)
        }
      } else {
        this.inputYear = this.inputYear || this.now.getFullYear()
        this.inputMonth = this.inputMonth == null ? this.inputMonth : this.now.getMonth()
        this.inputDay = this.inputDay || this.now.getDate()
      }
    }
  },

  render () {
    return this.genPicker('v-picker--date')
  }
}
<commit_msg>fix: don't change month autmatically when deselecting date<commit_after>// Components
import VDatePickerTitle from './VDatePickerTitle'
import VDatePickerHeader from './VDatePickerHeader'
import VDatePickerDateTable from './VDatePickerDateTable'
import VDatePickerMonthTable from './VDatePickerMonthTable'
import VDatePickerYears from './VDatePickerYears'

// Mixins
import Picker from '../../mixins/picker'

// Utils
import { pad, createNativeLocaleFormatter } from './util'
import isDateAllowed from './util/isDateAllowed'
import { consoleWarn } from '../../util/console'

/* @vue/component */
export default {
  name: 'v-date-picker',

  mixins: [Picker],

  props: {
    allowedDates: Function,
    // Function formatting the day in date picker table
    dayFormat: {
      type: Function,
      default: null
    },
    events: {
      type: [Array, Object, Function],
      default: () => null
    },
    eventColor: {
      type: [String, Function, Object],
      default: 'warning'
    },
    firstDayOfWeek: {
      type: [String, Number],
      default: 0
    },
    // Function formatting the tableDate in the day/month table header
    headerDateFormat: {
      type: Function,
      default: null
    },
    locale: {
      type: String,
      default: 'en-us'
    },
    max: String,
    min: String,
    // Function formatting month in the months table
    monthFormat: {
      type: Function,
      default: null
    },
    multiple: Boolean,
    nextIcon: {
      type: String,
      default: '$vuetify.icons.next'
    },
    pickerDate: String,
    prevIcon: {
      type: String,
      default: '$vuetify.icons.prev'
    },
    reactive: Boolean,
    readonly: Boolean,
    scrollable: Boolean,
    showCurrent: {
      type: [Boolean, String],
      default: true
    },
    // Function formatting currently selected date in the picker title
    titleDateFormat: {
      type: Function,
      default: null
    },
    type: {
      type: String,
      default: 'date',
      validator: type => ['date', 'month'].includes(type) // TODO: year
    },
    value: [Array, String],
    // Function formatting the year in table header and pickup title
    yearFormat: {
      type: Function,
      default: null
    },
    yearIcon: String
  },

  data () {
    const now = new Date()
    return {
      activePicker: this.type.toUpperCase(),
      defaultColor: 'accent',
      inputDay: null,
      inputMonth: null,
      inputYear: null,
      isReversing: false,
      now,
      // tableDate is a string in 'YYYY' / 'YYYY-M' format (leading zero for month is not required)
      tableDate: (() => {
        if (this.pickerDate) {
          return this.pickerDate
        }

        const date = (this.multiple ? this.value[this.value.length - 1] : this.value) ||
          `${now.getFullYear()}-${now.getMonth() + 1}`
        const type = this.type === 'date' ? 'month' : 'year'
        return this.sanitizeDateString(date, type)
      })()
    }
  },

  computed: {
    lastValue () {
      return this.multiple ? this.value[this.value.length - 1] : this.value
    },
    selectedMonths () {
      if (!this.value || !this.value.length || this.type === 'month') {
        return this.value
      } else if (this.multiple) {
        return this.value.map(val => val.substr(0, 7))
      } else {
        return this.value.substr(0, 7)
      }
    },
    current () {
      if (this.showCurrent === true) {
        return this.sanitizeDateString(`${this.now.getFullYear()}-${this.now.getMonth() + 1}-${this.now.getDate()}`, this.type)
      }

      return this.showCurrent || null
    },
    inputDate () {
      return this.type === 'date'
        ? `${this.inputYear}-${pad(this.inputMonth + 1)}-${pad(this.inputDay)}`
        : `${this.inputYear}-${pad(this.inputMonth + 1)}`
    },
    tableMonth () {
      return (this.pickerDate || this.tableDate).split('-')[1] - 1
    },
    tableYear () {
      return (this.pickerDate || this.tableDate).split('-')[0] * 1
    },
    minMonth () {
      return this.min ? this.sanitizeDateString(this.min, 'month') : null
    },
    maxMonth () {
      return this.max ? this.sanitizeDateString(this.max, 'month') : null
    },
    minYear () {
      return this.min ? this.sanitizeDateString(this.min, 'year') : null
    },
    maxYear () {
      return this.max ? this.sanitizeDateString(this.max, 'year') : null
    },
    formatters () {
      return {
        year: this.yearFormat || createNativeLocaleFormatter(this.locale, { year: 'numeric', timeZone: 'UTC' }, { length: 4 }),
        titleDate: this.titleDateFormat || (this.multiple ? this.defaultTitleMultipleDateFormatter : this.defaultTitleDateFormatter)
      }
    },
    defaultTitleMultipleDateFormatter () {
      if (this.value.length < 2) {
        return dates => dates.length ? this.defaultTitleDateFormatter(dates[0]) : '0 selected'
      }

      return dates => `${dates.length} selected`
    },
    defaultTitleDateFormatter () {
      const titleFormats = {
        year: { year: 'numeric', timeZone: 'UTC' },
        month: { month: 'long', timeZone: 'UTC' },
        date: { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC' }
      }

      const titleDateFormatter = createNativeLocaleFormatter(this.locale, titleFormats[this.type], {
        start: 0,
        length: { date: 10, month: 7, year: 4 }[this.type]
      })

      const landscapeFormatter = date => titleDateFormatter(date)
        .replace(/([^\d\s])([\d])/g, (match, nonDigit, digit) => `${nonDigit} ${digit}`)
        .replace(', ', ',
')

      return this.landscape ? landscapeFormatter : titleDateFormatter
    }
  },

  watch: {
    tableDate (val, prev) {
      // Make a ISO 8601 strings from val and prev for comparision, otherwise it will incorrectly
      // compare for example '2000-9' and '2000-10'
      const sanitizeType = this.type === 'month' ? 'year' : 'month'
      this.isReversing = this.sanitizeDateString(val, sanitizeType) < this.sanitizeDateString(prev, sanitizeType)
      this.$emit('update:pickerDate', val)
    },
    pickerDate (val) {
      if (val) {
        this.tableDate = val
      } else if (this.lastValue && this.type === 'date') {
        this.tableDate = this.sanitizeDateString(this.lastValue, 'month')
      } else if (this.lastValue && this.type === 'month') {
        this.tableDate = this.sanitizeDateString(this.lastValue, 'year')
      }
    },
    value (newValue, oldValue) {
      this.checkMultipleProp()
      this.setInputDate()

      if (!this.multiple && this.value && !this.pickerDate) {
        this.tableDate = this.sanitizeDateString(this.inputDate, this.type === 'month' ? 'year' : 'month')
      } else if (this.multiple && this.value.length && !oldValue.length && !this.pickerDate) {
        this.tableDate = this.sanitizeDateString(this.inputDate, this.type === 'month' ? 'year' : 'month')
      }
    },
    type (type) {
      this.activePicker = type.toUpperCase()

      if (this.value && this.value.length) {
        const output = (this.multiple ? this.value : [this.value])
          .map(val => this.sanitizeDateString(val, type))
          .filter(this.isDateAllowed)
        this.$emit('input', this.multiple ? output : output[0])
      }
    }
  },

  created () {
    this.checkMultipleProp()

    if (this.pickerDate !== this.tableDate) {
      this.$emit('update:pickerDate', this.tableDate)
    }
    this.setInputDate()
  },

  methods: {
    emitInput (newInput) {
      const output = this.multiple
        ? (
          this.value.indexOf(newInput) === -1
            ? this.value.concat([newInput])
            : this.value.filter(x => x !== newInput)
        )
        : newInput

      this.$emit('input', output)
      this.multiple || this.$emit('change', newInput)
    },
    checkMultipleProp () {
      if (this.value == null) return
      const valueType = this.value.constructor.name
      const expected = this.multiple ? 'Array' : 'String'
      if (valueType !== expected) {
        consoleWarn(`Value must be ${this.multiple ? 'an' : 'a'} ${expected}, got ${valueType}`, this)
      }
    },
    isDateAllowed (value) {
      return isDateAllowed(value, this.min, this.max, this.allowedDates)
    },
    yearClick (value) {
      this.inputYear = value
      if (this.type === 'month') {
        this.tableDate = `${value}`
      } else {
        this.tableDate = `${value}-${pad(this.tableMonth + 1)}`
      }
      this.activePicker = 'MONTH'
      this.reactive && !this.multiple && this.isDateAllowed(this.inputDate) && this.$emit('input', this.inputDate)
    },
    monthClick (value) {
      this.inputYear = parseInt(value.split('-')[0], 10)
      this.inputMonth = parseInt(value.split('-')[1], 10) - 1
      if (this.type === 'date') {
        this.tableDate = value
        this.activePicker = 'DATE'
        this.reactive && !this.multiple && this.isDateAllowed(this.inputDate) && this.$emit('input', this.inputDate)
      } else {
        this.emitInput(this.inputDate)
      }
    },
    dateClick (value) {
      this.inputYear = parseInt(value.split('-')[0], 10)
      this.inputMonth = parseInt(value.split('-')[1], 10) - 1
      this.inputDay = parseInt(value.split('-')[2], 10)
      this.emitInput(this.inputDate)
    },
    genPickerTitle () {
      return this.$createElement(VDatePickerTitle, {
        props: {
          date: this.value ? this.formatters.titleDate(this.value) : '',
          selectingYear: this.activePicker === 'YEAR',
          year: this.formatters.year(`${this.inputYear}`),
          yearIcon: this.yearIcon,
          value: this.multiple ? this.value[0] : this.value
        },
        slot: 'title',
        style: this.readonly ? {
          'pointer-events': 'none'
        } : undefined,
        on: {
          'update:selectingYear': value => this.activePicker = value ? 'YEAR' : this.type.toUpperCase()
        }
      })
    },
    genTableHeader () {
      return this.$createElement(VDatePickerHeader, {
        props: {
          nextIcon: this.nextIcon,
          color: this.color,
          dark: this.dark,
          disabled: this.readonly,
          format: this.headerDateFormat,
          light: this.light,
          locale: this.locale,
          min: this.activePicker === 'DATE' ? this.minMonth : this.minYear,
          max: this.activePicker === 'DATE' ? this.maxMonth : this.maxYear,
          prevIcon: this.prevIcon,
          value: this.activePicker === 'DATE' ? `${this.tableYear}-${pad(this.tableMonth + 1)}` : `${this.tableYear}`
        },
        on: {
          toggle: () => this.activePicker = (this.activePicker === 'DATE' ? 'MONTH' : 'YEAR'),
          input: value => this.tableDate = value
        }
      })
    },
    genDateTable () {
      return this.$createElement(VDatePickerDateTable, {
        props: {
          allowedDates: this.allowedDates,
          color: this.color,
          current: this.current,
          dark: this.dark,
          disabled: this.readonly,
          events: this.events,
          eventColor: this.eventColor,
          firstDayOfWeek: this.firstDayOfWeek,
          format: this.dayFormat,
          light: this.light,
          locale: this.locale,
          min: this.min,
          max: this.max,
          tableDate: `${this.tableYear}-${pad(this.tableMonth + 1)}`,
          scrollable: this.scrollable,
          value: this.value
        },
        ref: 'table',
        on: {
          input: this.dateClick,
          tableDate: value => this.tableDate = value
        }
      })
    },
    genMonthTable () {
      return this.$createElement(VDatePickerMonthTable, {
        props: {
          allowedDates: this.type === 'month' ? this.allowedDates : null,
          color: this.color,
          current: this.current ? this.sanitizeDateString(this.current, 'month') : null,
          dark: this.dark,
          disabled: this.readonly,
          format: this.monthFormat,
          light: this.light,
          locale: this.locale,
          min: this.minMonth,
          max: this.maxMonth,
          scrollable: this.scrollable,
          value: this.selectedMonths,
          tableDate: `${this.tableYear}`
        },
        ref: 'table',
        on: {
          input: this.monthClick,
          tableDate: value => this.tableDate = value
        }
      })
    },
    genYears () {
      return this.$createElement(VDatePickerYears, {
        props: {
          color: this.color,
          format: this.yearFormat,
          locale: this.locale,
          min: this.minYear,
          max: this.maxYear,
          value: `${this.tableYear}`
        },
        on: {
          input: this.yearClick
        }
      })
    },
    genPickerBody () {
      const children = this.activePicker === 'YEAR' ? [
        this.genYears()
      ] : [
        this.genTableHeader(),
        this.activePicker === 'DATE' ? this.genDateTable() : this.genMonthTable()
      ]

      return this.$createElement('div', {
        key: this.activePicker,
        style: this.readonly ? {
          'pointer-events': 'none'
        } : undefined
      }, children)
    },
    // Adds leading zero to month/day if necessary, returns 'YYYY' if type = 'year',
    // 'YYYY-MM' if 'month' and 'YYYY-MM-DD' if 'date'
    sanitizeDateString (dateString, type) {
      const [year, month = 1, date = 1] = dateString.split('-')
      return `${year}-${pad(month)}-${pad(date)}`.substr(0, { date: 10, month: 7, year: 4 }[type])
    },
    setInputDate () {
      if (this.lastValue) {
        const array = this.lastValue.split('-')
        this.inputYear = parseInt(array[0], 10)
        this.inputMonth = parseInt(array[1], 10) - 1
        if (this.type === 'date') {
          this.inputDay = parseInt(array[2], 10)
        }
      } else {
        this.inputYear = this.inputYear || this.now.getFullYear()
        this.inputMonth = this.inputMonth == null ? this.inputMonth : this.now.getMonth()
        this.inputDay = this.inputDay || this.now.getDate()
      }
    }
  },

  render () {
    return this.genPicker('v-picker--date')
  }
}
<|endoftext|>


<commit_before>/*
 This file is a part of libertysoil.org website
 Copyright (C) 2016  Loki Education (Social Enterprise)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
import React, { cloneElement, Component, PropTypes } from 'react';
import { throttle, isArray, compact } from 'lodash';

export default class Breadcrumbs extends Component {
  static displayName = 'Breadcrumbs';

  static propTypes = {
    children: PropTypes.node,
    className: PropTypes.string,
    title: PropTypes.node
  };

  constructor(props) {
    super(props);

    this.state = {
      visibleCrumbs: null
    };
  }

  componentDidMount() {
    window.addEventListener('resize', this.resetVisibleCrumbs);
    document.addEventListener('DOMContentLoaded', this.resetVisibleCrumbs);
  }

  componentWillReceiveProps() {
    this.resetVisibleCrumbs();
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.resetVisibleCrumbs);
    document.removeEventListener('DOMContentLoaded', this.resetVisibleCrumbs);
  }

  resetVisibleCrumbs = throttle(() => {
    const { children } = this.props;
    let childrenCount = 0;

    if (children) {
      childrenCount = children.length;
    }

    this.setState({
      visibleCrumbs: childrenCount
    }, this.updateVisibleCrumbs);
  }, 100);

  updateVisibleCrumbs = () => {
    const breadcrumbsWidth = this._breadcrumbs.offsetWidth;
    const bodyWidth = this._body.offsetWidth;

    if (bodyWidth >= breadcrumbsWidth) {
      if (this.state.visibleCrumbs > 0) {
        this.setState({
          visibleCrumbs: this.state.visibleCrumbs - 1
        }, this.updateVisibleCrumbs);
      }
    }
  };

  renderCrumbs(crumbs = []) {
    let visibleCrumbs = this.state.visibleCrumbs;
    let isCollapsed = [];

    if (!isArray(crumbs)) {
      crumbs = [crumbs];
    }

    isCollapsed = crumbs.map(() => ((visibleCrumbs-- <= 0))).reverse();

    return compact(crumbs).map((crumb, i) => {
      const props = {};
      if (crumb.type instanceof Function) {
        props.collapsed = isCollapsed[i];
      }

      return (

          {cloneElement(crumb, props)}

      );
    });
  }

  renderTitle(title) {
    let titleComponent = null;

    if (title) {
      titleComponent =
{title}
;
    }

    return titleComponent;
  }

  render() {
    const {
      children,
      className,
      title,
      ...props
    } = this.props;

    let cn = 'breadcrumbs header__breadcrumbs';
    if (className) {
      cn += ` ${className}`;
    }

    return (

 this._breadcrumbs = c} {...props}>

 this._body = c}>
          {this.renderCrumbs(children)}
          {this.renderTitle(title)}

    );
  }
}
<commit_msg>Breadcrumbs: don't pass 'collapsed' if it's reduntant<commit_after>/*
 This file is a part of libertysoil.org website
 Copyright (C) 2016  Loki Education (Social Enterprise)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
import React, { cloneElement, Component, PropTypes } from 'react';
import { throttle, isArray, compact, keys } from 'lodash';

export default class Breadcrumbs extends Component {
  static displayName = 'Breadcrumbs';

  static propTypes = {
    children: PropTypes.node,
    className: PropTypes.string,
    title: PropTypes.node
  };

  constructor(props) {
    super(props);

    this.state = {
      visibleCrumbs: null
    };
  }

  componentDidMount() {
    window.addEventListener('resize', this.resetVisibleCrumbs);
    document.addEventListener('DOMContentLoaded', this.resetVisibleCrumbs);
  }

  componentWillReceiveProps() {
    this.resetVisibleCrumbs();
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.resetVisibleCrumbs);
    document.removeEventListener('DOMContentLoaded', this.resetVisibleCrumbs);
  }

  resetVisibleCrumbs = throttle(() => {
    const { children } = this.props;
    let childrenCount = 0;

    if (children) {
      childrenCount = children.length;
    }

    this.setState({
      visibleCrumbs: childrenCount
    }, this.updateVisibleCrumbs);
  }, 100);

  updateVisibleCrumbs = () => {
    const breadcrumbsWidth = this._breadcrumbs.offsetWidth;
    const bodyWidth = this._body.offsetWidth;

    if (bodyWidth >= breadcrumbsWidth) {
      if (this.state.visibleCrumbs > 0) {
        this.setState({
          visibleCrumbs: this.state.visibleCrumbs - 1
        }, this.updateVisibleCrumbs);
      }
    }
  };

  renderCrumbs(crumbs = []) {
    let visibleCrumbs = this.state.visibleCrumbs;
    let isCollapsed = [];

    if (!isArray(crumbs)) {
      crumbs = [crumbs];
    }

    isCollapsed = crumbs.map(() => ((visibleCrumbs-- <= 0))).reverse();

    const checkType = (typeDef) => {
      return (
        (typeof typeDef.propTypes === 'object') &&
        keys(typeDef.propTypes).find(v => v === 'collapsed')
      );
    };

    return compact(crumbs).map((crumb, i) => {
      const props = {};
      const { type: typeDef } = crumb;

      if ((typeDef instanceof Function) && checkType(typeDef)) {
        props.collapsed = isCollapsed[i];
      }

      return (

          {cloneElement(crumb, props)}

      );
    });
  }

  renderTitle(title) {
    let titleComponent = null;

    if (title) {
      titleComponent =
{title}
;
    }

    return titleComponent;
  }

  render() {
    const {
      children,
      className,
      title,
      ...props
    } = this.props;

    let cn = 'breadcrumbs header__breadcrumbs';
    if (className) {
      cn += ` ${className}`;
    }

    return (

 this._breadcrumbs = c} {...props}>

 this._body = c}>
          {this.renderCrumbs(children)}
          {this.renderTitle(title)}

    );
  }
}
<|endoftext|>


<commit_before>import React, {Component} from 'react'
import Icon from 'react-fa'

import numberShow from 'lib/numberShower/numberShower'

const PrecisionNumber = ({value, precision, number=numberShow(value, precision)}) => (

    {number.value}{number.symbol}
    {number.power && ({`\u00b710`}{number.power})}

)

const RangeDisplay = ({low, high}) => (

<PrecisionNumber value={low}/> to <PrecisionNumber value={high}/>

)

const ResultSection = ({length, mean, percentiles}) => (
  length === 1 ? <PrecisionNumber value={mean} precision={6}/> : <RangeDisplay low={percentiles[5]} high={percentiles[95]}/>
)

const AnalyticsSection = (stats) => {
  return (

      {`According to the model, this value has a 95% chance of being above `}
      <PrecisionNumber value={stats.percentiles[5]}/>
      {` and a 95% chance of being below `}
      <PrecisionNumber value={stats.percentiles[95]}/>
      {'.'}
      {` The mean value is `}
      <PrecisionNumber value={stats.mean}/>
      {` and the median is `}
      <PrecisionNumber value={stats.percentiles[50]}/>
      {`.`}

  )
}

export class Output extends Component {
  state = {
    showAnalysis: false
  }

  showAnalysis(show=true) {
    this.setState({showAnalysis: show})
  }

  render() {
    const {metric: {name, simulation}} = this.props
    const {showAnalysis} = this.state
    return (

              {name}

            <div className={`result-section${!_.isEmpty(_.get(simulation, 'sample.errors')) ? ' has-errors' : ''}`}>
              {_.has(simulation, 'stats') && <ResultSection {...simulation.stats} />}

              {!showAnalysis && _.has(simulation, 'stats.percentiles.5') &&

 this.showAnalysis(true)}> ?

              }
              {showAnalysis &&

 this.showAnalysis(false)}> <i className={`ion-md-close`}/>

              }

              {showAnalysis && _.has(simulation, 'stats.percentiles.5') &&
                <AnalyticsSection {...simulation.stats} />
              }

    )
  }
}
<commit_msg>Fix from CR<commit_after>import React, {Component} from 'react'
import Icon from 'react-fa'

import numberShow from 'lib/numberShower/numberShower'

const PrecisionNumber = ({value, precision, number=numberShow(value, precision)}) => (

    {number.value}{number.symbol}
    {number.power && ({`\u00b710`}{number.power})}

)

const RangeDisplay = ({range: [low, high]}) => (

<PrecisionNumber value={low}/> to <PrecisionNumber value={high}/>

)

const ResultSection = ({length, mean, adjustedConfidenceInterval}) => (
  length === 1 ? <PrecisionNumber value={mean} precision={6}/> : <RangeDisplay range={adjustedConfidenceInterval}/>
)

const AnalyticsSection = (stats) => (

    {`According to the model, this value has a 90% chance of being between `}
    <PrecisionNumber value={stats.adjustedConfidenceInterval[0]}/>
    {` and `}
    <PrecisionNumber value={stats.adjustedConfidenceInterval[1]}/>
    {'.'}
    {` The mean value is `}
    <PrecisionNumber value={stats.mean}/>
    {` and the median is `}
    <PrecisionNumber value={stats.percentiles[50]}/>
    {`.`}

)

export class Output extends Component {
  state = {
    showAnalysis: false
  }

  toggleAnalysis() {
    const showAnalysis = this.state.showAnalysis
    this.setState({showAnalysis: !showAnalysis})
  }

  render() {
    const {metric: {name, simulation}} = this.props
    const {showAnalysis} = this.state
    console.log(simulation.stats)
    return (

              {name}

            <div className={`result-section${!_.isEmpty(_.get(simulation, 'sample.errors')) ? ' has-errors' : ''}`}>
              {_.has(simulation, 'stats') && <ResultSection {...simulation.stats} />}

              {!showAnalysis && _.has(simulation, 'stats.percentiles.5') &&

 ?

              }
              {showAnalysis &&

 <i className={`ion-md-close`}/>

              }

              {showAnalysis && _.has(simulation, 'stats.percentiles.5') &&
                <AnalyticsSection {...simulation.stats} />
              }

    )
  }
}
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as courseActions from '../../actions/courseActions';
import CourseForm from './CourseForm';

class ManageCoursePage extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      course : Object.assign({}, this.props.course),
      errors : {}
    }
  }

  updateCourseState = (event) => {
    const field = event.target.name;
    let course = Object.assign({}, this.state.course);
    course[field] = event.target.value;
    return this.setState({ course : course })
  };

  saveCourse = (event) => {
    event.preventDefault();
    this.props.actions.saveCourse(this.state.course);
    this.context.router.push('/courses');
  };

  render() {
    return (

Manage Course

        <CourseForm
          onSave={this.saveCourse}
          onChange={this.updateCourseState}
          course={this.state.course}
          allAuthors={this.props.authors}
          errors={this.state.errors}
        />

    );
  }
}

ManageCoursePage.propTypes = {
  course: PropTypes.object.isRequired,
  authors : PropTypes.array.isRequired,
  actions : PropTypes.object.isRequired
};

ManageCoursePage.contextTypes = {
  router: PropTypes.func.isRequired
};

const mapStateToProps = (state, ownProps) => {
  const courseId = ownProps.params.id;
  let course = {
    id: "",
    title: "",
    watchHref: "",
    authorId: "",
    length: "",
    category: ""
  };

  const authorsFormattedForDropdown = state.authors.map(author => {
    return {
      key : author.id,
      value : author.id,
      text: `${author.firstName} ${author.lastName}`
    }
  });

  return {
    course: course,
    authors : authorsFormattedForDropdown
  }
};
const mapDispatchToProps = (dispatch) => {
  return {
    actions: bindActionCreators(courseActions, dispatch)
  }
};
export default connect(mapStateToProps, mapDispatchToProps)(ManageCoursePage);
<commit_msg>1. populate ManageCoursePage when the course is available 2. update course state when there's new course props coming in componentWillReceiveProps<commit_after>import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as courseActions from '../../actions/courseActions';
import CourseForm from './CourseForm';

class ManageCoursePage extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      course : Object.assign({}, this.props.course),
      errors : {}
    }
  }

  componentWillReceiveProps(nextProps){
    if(this.props.course.id != nextProps.course.id){
      //Necessary to populate form when existing course is loaded directly
      this.setState({
        course : Object.assign({}, nextProps.course)
      });
    }
  };

  updateCourseState = (event) => {
    const field = event.target.name;
    let course = Object.assign({}, this.state.course);
    course[field] = event.target.value;
    return this.setState({ course : course })
  };

  saveCourse = (event) => {
    event.preventDefault();
    this.props.actions.saveCourse(this.state.course);
    this.context.router.push('/courses');
  };

  render() {
    return (

Manage Course

        <CourseForm
          onSave={this.saveCourse}
          onChange={this.updateCourseState}
          course={this.state.course}
          allAuthors={this.props.authors}
          errors={this.state.errors}
        />

    );
  }
}

ManageCoursePage.propTypes = {
  course: PropTypes.object.isRequired,
  authors : PropTypes.array.isRequired,
  actions : PropTypes.object.isRequired
};

ManageCoursePage.contextTypes = {
  router: PropTypes.func.isRequired
};

const getCourseById = (courses, id) => {
  const course = courses.filter(course => course.id === id);
  if(course) return courses[0];
  return null;
};

const mapStateToProps = (state, ownProps) => {
  const courseId = ownProps.params.id;
  let course = { id: "", title: "", watchHref: "", authorId: "", length: "", category: "" };

  //need to validate if courses is already loaded (because there's a latency in server around 1000ms (mock data))
  if(courseId && state.courses.length > 0){
    course = getCourseById(state.courses, courseId);
  }

  const authorsFormattedForDropdown = state.authors.map(author => {
    return {
      key : author.id,
      value : author.id,
      text: `${author.firstName} ${author.lastName}`
    }
  });

  return {
    course: course,
    authors : authorsFormattedForDropdown
  }
};
const mapDispatchToProps = (dispatch) => {
  return {
    actions: bindActionCreators(courseActions, dispatch)
  }
};
export default connect(mapStateToProps, mapDispatchToProps)(ManageCoursePage);
<|endoftext|>


<commit_before>/**
 * Message mediation scenario. Send the message defined in the SOAP
 * WS-Addressing EPR value.
 */

var SUPPORT_LIBS = '../lib/support/';
var logger = require('../lib/logger');

exports.executeSample = function(zenithMessage, callback){
	//@TODO path name should be extracted from the SOAP header
	var serviceURL = 'http://localhost:9000/services/SimpleStockQuoteService';
	logger.debug('SampleConfig', 'EPR: ' + serviceURL);

	var pathName = zenithMessage.transportHeaders.url.pathname;

	var option = {
			url : serviceURL
		};

	var endpoint = require(SUPPORT_LIBS + 'ws_endpoint');

	endpoint.callService(zenithMessage, option, function(err,message){
			callback(null, message);
		});
}<commit_msg>extract address from the soap header<commit_after>/**
 * Message mediation scenario. Send the message defined in the SOAP
 * WS-Addressing EPR value.
 */

var SUPPORT_LIBS = '../lib/support/';
var logger = require('../lib/logger');
var saxProcessor = require(SUPPORT_LIBS + 'xml/sax_processor');

exports.executeSample = function(zenithMessage, callback){

	var serviceURL;

	//this defines the header field values in the soap message.
	var soapHeaderElement = 'To';
	var soapHeaderElementNmSpcURI = 'http://www.w3.org/2005/08/addressing';

	var saxProcessor = require(SUPPORT_LIBS + 'xml/sax_processor');

	saxProcessor.getElementValue(zenithMessage.body,
			soapHeaderElementNmSpcURI, soapHeaderElement, function(err, value){

		if(err == null){ // no error
			serviceURL = value[0];
			logger.debug('SampleConfig', 'EPR: ' + serviceURL);

			var option = {
					url : serviceURL
				};

			var endpoint = require(SUPPORT_LIBS + 'ws_endpoint');

			endpoint.callService(zenithMessage, option, function(err,message){
					callback(null, message);
			});

		} else {
				//set the message body to the error message
				var errMsg = 'EPR value is not defined.';
				zenithMessage.body = soapErrorMsg.getSOAP11Fault(errMsg);
				callback(null, zenithMessage);
		}

	});

}<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react'

import TextArea from 'react-textarea-autosize'

import './style.css'

export default class MetricName extends Component {
  displayName: 'MetricName'

  static propTypes = {
    name: PropTypes.string,
    inSelectedCell: PropTypes.bool.isRequired,
    onChange: PropTypes.func.isRequired,
  }

  state = {
    value: this.props.name,
    editing: false,
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.name === this.state.value) {this.setState({value: nextProps.name})}
  }

  shouldComponentUpdate(nextProps, nextState) {
    return ((nextProps.name !== this.props.name) ||
            (nextProps.inSelectedCell !== this.props.inSelectedCell) ||
            (nextState.value !== this.state.value) ||
            (nextState.editing !== this.state.editing))
  }

  handleSubmit() {
    if (this._hasChanged()){
      this.props.onChange({name: this.state.value})
    }
    this.setState({editing: false})
  }

  _hasChanged() {
    return (this.state.value != this.props.name)
  }

  hasContent() {
    return !_.isEmpty(this.state.value)
  }

  componentWillUnmount() {
    this.handleSubmit()
  }

  onChange(e) {
    this.setState({value: e.target.value})
  }

  handleKeyDown(e) {
    const ENTER = (e) => ((e.keyCode === 13) && !e.shiftKey)
    if (ENTER(e)){
      e.stopPropagation()
      this.props.jumpSection()
    }
  }

  render() {
    return (

        {this.state.editing &&
          <TextArea
            onBlur={this.handleSubmit.bind(this)}
            onChange={this.onChange.bind(this)}
            onKeyDown={this.handleKeyDown.bind(this)}
            onMouseLeave={() => {if (!this._hasChanged()) {this.setState({editing: false})}}}
            placeholder={'name'}
            ref={'input'}
            tabIndex={2}
            value={this.state.value}
          />
        }
        {!this.state.editing &&
          <div className={`static${!this.state.value ? ' default-value' : ''}`}
            onMouseEnter={() => {this.setState({editing: true})}}
          >
            {this.state.value || 'name'}

        }

    )
  }
}
<commit_msg>click means you definitely want to edit<commit_after>import React, {Component, PropTypes} from 'react'

import TextArea from 'react-textarea-autosize'

import './style.css'

export default class MetricName extends Component {
  displayName: 'MetricName'

  static propTypes = {
    name: PropTypes.string,
    inSelectedCell: PropTypes.bool.isRequired,
    onChange: PropTypes.func.isRequired,
  }

  state = {
    value: this.props.name,
    editing: false,
    persistEditing: false,
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.name === this.state.value) {this.setState({value: nextProps.name})}
  }

  shouldComponentUpdate(nextProps, nextState) {
    return ((nextProps.name !== this.props.name) ||
            (nextProps.inSelectedCell !== this.props.inSelectedCell) ||
            (nextState.value !== this.state.value) ||
            (nextState.editing !== this.state.editing))
  }

  handleSubmit() {
    if (this._hasChanged()){
      this.props.onChange({name: this.state.value})
    }
    this.setState({editing: false, persistEditing: false})
  }

  _hasChanged() {
    return (this.state.value != this.props.name)
  }

  _handleMouseLeave() {
    if (!(this.state.persistEditing || this._hasChanged())) {
      this.setState({editing: false})
    }
  }

  hasContent() {
    return !_.isEmpty(this.state.value)
  }

  componentWillUnmount() {
    this.handleSubmit()
  }

  onChange(e) {
    this.setState({value: e.target.value})
  }

  handleKeyDown(e) {
    const ENTER = (e) => ((e.keyCode === 13) && !e.shiftKey)
    if (ENTER(e)){
      e.stopPropagation()
      this.props.jumpSection()
    }
  }

  render() {
    return (

        {this.state.editing &&
          <TextArea
            onBlur={this.handleSubmit.bind(this)}
            onChange={this.onChange.bind(this)}
            onKeyDown={this.handleKeyDown.bind(this)}
            onMouseLeave={() => {}}
            placeholder={'name'}
            ref={'input'}
            tabIndex={2}
            value={this.state.value}
          />
        }
        {!this.state.editing &&
          <div className={`static${!this.state.value ? ' default-value' : ''}`}
            onMouseEnter={() => {this.setState({editing: true})}}
            onClick={() => {this.setState({persistEditing: true})}}
          >
            {this.state.value || 'name'}

        }

    )
  }
}
<|endoftext|>