<commit_before>/**
 * @file Data zoom model
 */
define(function(require) {

    var zrUtil = require('zrender/core/util');
    var env = require('zrender/core/env');
    var echarts = require('../../echarts');
    var modelUtil = require('../../util/model');
    var visualDefault = require('../../visual/visualDefault');
    var VisualMapping = require('../../visual/VisualMapping');
    var mapVisual = VisualMapping.mapVisual;
    var eachVisual = VisualMapping.eachVisual;
    var numberUtil = require('../../util/number');
    var isArray = zrUtil.isArray;
    var each = zrUtil.each;
    var asc = numberUtil.asc;
    var linearMap = numberUtil.linearMap;

    return echarts.extendComponentModel({

        type: 'visualMap',

        dependencies: ['series'],

        /**
         * [lowerBound, upperBound]
         *
         * @readOnly
         * @type {Array.<number>}
         */
        dataBound: [-Infinity, Infinity],

        /**
         * @readOnly
         * @type {Array.<string>}
         */
        stateList: ['inRange', 'outOfRange'],

        /**
         * @protected
         */
        defaultOption: {
            show: true,

            zlevel: 0,
            z: 4,

                                    // set min: 0, max: 200, only for campatible with ec2.
                                    // In fact min max should not have default value.
            min: 0,                 // min value, must specified if pieces is not specified.
            max: 200,               // max value, must specified if pieces is not specified.

            dimension: null,
            inRange: null,          // 'color', 'colorHue', 'colorSaturation', 'colorLightness', 'colorAlpha',
                                    // 'symbol', 'symbolSize'
            outOfRange: null,       // 'color', 'colorHue', 'colorSaturation',
                                    // 'colorLightness', 'colorAlpha',
                                    // 'symbol', 'symbolSize'

            left: 0,                // 'center' ¦ 'left' ¦ 'right' ¦ {number} (px)
            right: null,            // The same as left.
            top: null,              // 'top' ¦ 'bottom' ¦ 'center' ¦ {number} (px)
            bottom: 0,              // The same as top.
            itemWidth: null,
            itemHeight: null,
            inverse: false,
            orient: 'vertical',        // 'horizontal' ¦ 'vertical'

            seriesIndex: null,          // 所控制的series indices，默认所有有value的series.
            backgroundColor: 'rgba(0,0,0,0)',
            borderColor: '#ccc',       // 值域边框颜色
            contentColor: '#5793f3',
            inactiveColor: '#aaa',
            borderWidth: 0,            // 值域边框线宽，单位px，默认为0（无边框）
            padding: 5,                // 值域内边距，单位px，默认各方向内边距为5，
                                       // 接受数组分别设定上右下左边距，同css
            textGap: 10,               //
            precision: 0,              // 小数精度，默认为0，无小数点
            color: ['#bf444c', '#d88273', '#f6efa6'],//颜色（deprecated，兼容ec2，对应数值由高到低）

            formatter: null,
            text: null,                // 文本，如['高', '低']，兼容ec2，text[0]对应高值，text[1]对应低值
            textStyle: {
                color: '#333'          // 值域文字颜色
            }
        },

        /**
         * @protected
         */
        init: function (option, parentModel, ecModel) {
            /**
             * @private
             * @type {boolean}
             */
            this._autoSeriesIndex = false;

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

            /**
             * @readOnly
             */
            this.controllerVisuals = {};

            /**
             * @readOnly
             */
            this.targetVisuals = {};

            /**
             * @readOnly
             */
            this.textStyleModel;

            /**
             * [width, height]
             * @readOnly
             * @type {Array.<number>}
             */
            this.itemSize;

            this.mergeDefaultAndTheme(option, ecModel);
            this.mergeOption({}, true);
        },

        /**
         * @override
         */
        baseMergeOption: function (newOption) {
            var thisOption = this.option;

            newOption && zrUtil.merge(thisOption, newOption);

            if (newOption.text && isArray(newOption.text)) {
                thisOption.text = newOption.text.slice();
            }

            // FIXME
            // necessary?
            // Disable realtime view update if canvas is not supported.
            if (!env.canvasSupported) {
                thisOption.realtime = false;
            }

            this.textStyleModel = this.getModel('textStyle');

            this.resetItemSize();

            this.completeVisualOption();
        },

        /**
         * @example
         * this.formatValueText(someVal); // format single numeric value to text.
         * this.formatValueText(someVal, true); // format single category value to text.
         * this.formatValueText([min, max]); // format numeric min-max to text.
         * this.formatValueText([this.dataBound[0], max]); // using data lower bound.
         * this.formatValueText([min, this.dataBound[1]]); // using data upper bound.
         *
         * @param {number|Array.<number>} value Real value, or this.dataBound[0 or 1].
         * @param {boolean} [isCategory=false] Only available when value is number.
         * @return {string}
         * @protected
         */
        formatValueText: function(value, isCategory) {
            var option = this.option;
            var precision = option.precision;
            var dataBound = this.dataBound;
            var formatter = option.formatter;
            var isMinMax;
            var textValue;

            if (zrUtil.isArray(value)) {
                value = value.slice();
                isMinMax = true;
            }

            textValue = isCategory
                ? value
                : (isMinMax
                    ? [toFixed(value[0]), toFixed(value[1])]
                    : toFixed(value)
                );

            if (zrUtil.isString(formatter)) {
                return formatter
                    .replace('{value}', isMinMax ? textValue[0] : textValue)
                    .replace('{value2}', isMinMax ? textValue[1] : textValue);
            }
            else if (zrUtil.isFunction(formatter)) {
                return isMinMax
                    ? formatter(value[0], value[1])
                    : formatter(value);
            }

            if (isMinMax) {
                if (value[0] === dataBound[0]) {
                    return '< ' + textValue[1];
                }
                else if (value[1] === dataBound[1]) {
                    return '> ' + textValue[0];
                }
                else {
                    return textValue[0] + ' - ' + textValue[1];
                }
            }
            else { // Format single value (includes category case).
                return textValue;
            }

            function toFixed(val) {
                return val === dataBound[0]
                    ? 'min'
                    : val === dataBound[1]
                    ? 'max'
                    : (+val).toFixed(precision);
            }
        },

        /**
         * @protected
         */
        resetTargetSeries: function (newOption, isInit) {
            var thisOption = this.option;
            var autoSeriesIndex = this._autoSeriesIndex =
                (isInit ? thisOption : newOption).seriesIndex == null;
            thisOption.seriesIndex = autoSeriesIndex
                ? [] : modelUtil.normalizeToArray(thisOption.seriesIndex);

            autoSeriesIndex && this.ecModel.eachSeries(function (seriesModel, index) {
                var data = seriesModel.getData();
                // FIXME
                // 只考虑了list，还没有考虑map等。

                // FIXME
                // 这里可能应该这么判断：data.dimensions中有超出其所属coordSystem的量。
                if (data.type === 'list') {
                    thisOption.seriesIndex.push(index);
                }
            });
        },

        /**
         * @protected
         */
        resetExtent: function () {
            var thisOption = this.option;

            // Can not calculate data extent by data here.
            // Because series and data may be modified in processing stage.
            // So we do not support the feature "auto min/max".

            var extent = asc([thisOption.min, thisOption.max]);

            this._dataExtent = extent;
        },

        /**
         * @protected
         */
        getDataDimension: function (list) {
            var optDim = this.option.dimension;
            return optDim != null
                ? optDim : list.dimensions.length - 1;
        },

        /**
         * @public
         * @override
         */
        getExtent: function () {
            return this._dataExtent.slice();
        },

        /**
         * @protected
         */
        resetVisual: function (fillVisualOption) {
            var dataExtent = this.getExtent();

            doReset.call(this, 'controller', this.controllerVisuals);
            doReset.call(this, 'target', this.targetVisuals);

            function doReset(baseAttr, visualMappings) {
                each(this.stateList, function (state) {
                    var mappings = visualMappings[state] || (visualMappings[state] = {});
                    var visaulOption = this.option[baseAttr][state] || {};
                    each(visaulOption, function (visualData, visualType) {
                        if (!VisualMapping.isValidType(visualType)) {
                            return;
                        }
                        var mappingOption = {
                            type: visualType,
                            dataExtent: dataExtent,
                            visual: visualData
                        };
                        fillVisualOption && fillVisualOption.call(this, mappingOption, state);
                        mappings[visualType] = new VisualMapping(mappingOption);
                    }, this);
                }, this);
            }
        },

        /**
         * @protected
         */
        completeVisualOption: function () {
            var thisOption = this.option;
            var base = {inRange: thisOption.inRange, outOfRange: thisOption.outOfRange};

            var target = thisOption.target || (thisOption.target = {});
            var controller = thisOption.controller || (thisOption.controller = {});

            zrUtil.merge(target, base); // Do not override
            zrUtil.merge(controller, base); // Do not override

            var isCategory = this.isCategory();

            completeSingle.call(this, target);
            completeSingle.call(this, controller);
            completeInactive.call(this, target, 'inRange', 'outOfRange');
            completeInactive.call(this, target, 'outOfRange', 'inRange');
            completeController.call(this, controller);

            function completeSingle(base) {
                // Compatible with ec2 dataRange.color.
                // The mapping order of dataRange.color is: [high value, ..., low value]
                // whereas inRange.color and outOfRange.color is [low value, ..., high value]
                if (isArray(thisOption.color)
                    // If there has been inRange: {symbol: ...}, adding color is a mistake.
                    // So adding color only when no inRange defined.
                    && !base.inRange
                ) {
                    base.inRange = {color: thisOption.color.slice().reverse()};
                }

                // If using shortcut like: {inRange: 'symbol'}, complete default value.
                each(this.stateList, function (state) {
                    var visualType = base[state];

                    if (zrUtil.isString(visualType)) {
                        var defa = visualDefault.get(visualType, 'active', isCategory);
                        if (defa) {
                            base[state] = {};
                            base[state][visualType] = defa;
                        }
                        else {
                            // Mark as not specified.
                            delete base[state];
                        }
                    }
                }, this);
            }

            function completeInactive(base, stateExist, stateAbsent) {
                var optExist = base[stateExist];
                var optAbsent = base[stateAbsent];

                if (optExist && !optAbsent) {
                    optAbsent = base[stateAbsent] = {};
                    each(optExist, function (visualData, visualType) {
                        var defa = visualDefault.get(visualType, 'inactive', isCategory);
                        if (VisualMapping.isValidType(visualType) && defa) {
                            optAbsent[visualType] = defa;
                        }
                    });
                }
            }

            function completeController(controller) {
                var symbolExists = (controller.inRange || {}).symbol
                    || (controller.outOfRange || {}).symbol;
                var symbolSizeExists = (controller.inRange || {}).symbolSize
                    || (controller.outOfRange || {}).symbolSize;
                var inactiveColor = this.get('inactiveColor');

                each(this.stateList, function (state) {

                    var itemSize = this.itemSize;
                    var visuals = controller[state];

                    // Set inactive color for controller if no other color attr (like colorAlpha) specified.
                    if (!visuals) {
                        visuals = controller[state] = {
                            color: isCategory ? inactiveColor : [inactiveColor]
                        };
                    }

                    // Consistent symbol and symbolSize if not specified.
                    if (!visuals.symbol) {
                        visuals.symbol = symbolExists
                            && zrUtil.clone(symbolExists)
                            || (isCategory ? 'roundRect' : ['roundRect']);
                    }
                    if (!visuals.symbolSize) {
                        visuals.symbolSize = symbolSizeExists
                            && zrUtil.clone(symbolSizeExists)
                            || (isCategory ? itemSize[0] : [itemSize[0], itemSize[0]]);
                    }

                    // Filter square and none.
                    visuals.symbol = mapVisual(visuals.symbol, function (symbol) {
                        return (symbol === 'none' || symbol === 'square') ? 'roundRect' : symbol;
                    });

                    // Normalize symbolSize
                    var symbolSize = visuals.symbolSize;

                    if (symbolSize) {
                        var max = -Infinity;
                        // symbolSize can be object when categories defined.
                        eachVisual(symbolSize, function (value) {
                            value > max && (max = value);
                        });
                        visuals.symbolSize = mapVisual(symbolSize, function (value) {
                            return linearMap(value, [0, max], [0, itemSize[0]], true);
                        });
                    }

                }, this);
            }
        },

        /**
         * @public
         */
        eachTargetSeries: function (callback, context) {
            zrUtil.each(this.option.seriesIndex, function (seriesIndex) {
                callback.call(context, this.ecModel.getSeriesByIndex(seriesIndex));
            }, this);
        },

        /**
         * @public
         */
        isCategory: function () {
            return !!this.option.categories;
        },

        /**
         * @protected
         */
        resetItemSize: function () {
            this.itemSize = [
                parseFloat(this.get('itemWidth')),
                parseFloat(this.get('itemHeight'))
            ];
        },

        /**
         * @public
         * @abstract
         */
        setSelected: zrUtil.noop,

        /**
         * @public
         * @abstract
         */
        getValueState: zrUtil.noop

    });

});<commit_msg>tweak<commit_after>/**
 * @file Data zoom model
 */
define(function(require) {

    var zrUtil = require('zrender/core/util');
    var env = require('zrender/core/env');
    var echarts = require('../../echarts');
    var modelUtil = require('../../util/model');
    var visualDefault = require('../../visual/visualDefault');
    var VisualMapping = require('../../visual/VisualMapping');
    var mapVisual = VisualMapping.mapVisual;
    var eachVisual = VisualMapping.eachVisual;
    var numberUtil = require('../../util/number');
    var isArray = zrUtil.isArray;
    var each = zrUtil.each;
    var asc = numberUtil.asc;
    var linearMap = numberUtil.linearMap;

    return echarts.extendComponentModel({

        type: 'visualMap',

        dependencies: ['series'],

        /**
         * [lowerBound, upperBound]
         *
         * @readOnly
         * @type {Array.<number>}
         */
        dataBound: [-Infinity, Infinity],

        /**
         * @readOnly
         * @type {Array.<string>}
         */
        stateList: ['inRange', 'outOfRange'],

        /**
         * @protected
         */
        defaultOption: {
            show: true,

            zlevel: 0,
            z: 4,

                                    // set min: 0, max: 200, only for campatible with ec2.
                                    // In fact min max should not have default value.
            min: 0,                 // min value, must specified if pieces is not specified.
            max: 200,               // max value, must specified if pieces is not specified.

            dimension: null,
            inRange: null,          // 'color', 'colorHue', 'colorSaturation', 'colorLightness', 'colorAlpha',
                                    // 'symbol', 'symbolSize'
            outOfRange: null,       // 'color', 'colorHue', 'colorSaturation',
                                    // 'colorLightness', 'colorAlpha',
                                    // 'symbol', 'symbolSize'

            left: 0,                // 'center' ¦ 'left' ¦ 'right' ¦ {number} (px)
            right: null,            // The same as left.
            top: null,              // 'top' ¦ 'bottom' ¦ 'center' ¦ {number} (px)
            bottom: 0,              // The same as top.
            itemWidth: null,
            itemHeight: null,
            inverse: false,
            orient: 'vertical',        // 'horizontal' ¦ 'vertical'

            seriesIndex: null,          // 所控制的series indices，默认所有有value的series.
            backgroundColor: 'rgba(0,0,0,0)',
            borderColor: '#ccc',       // 值域边框颜色
            contentColor: '#5793f3',
            inactiveColor: '#aaa',
            borderWidth: 0,            // 值域边框线宽，单位px，默认为0（无边框）
            padding: 5,                // 值域内边距，单位px，默认各方向内边距为5，
                                       // 接受数组分别设定上右下左边距，同css
            textGap: 10,               //
            precision: 0,              // 小数精度，默认为0，无小数点
            color: ['#bf444c', '#d88273', '#f6efa6'], //颜色（deprecated，兼容ec2，顺序同pieces，不同于inRange/outOfRange）

            formatter: null,
            text: null,                // 文本，如['高', '低']，兼容ec2，text[0]对应高值，text[1]对应低值
            textStyle: {
                color: '#333'          // 值域文字颜色
            }
        },

        /**
         * @protected
         */
        init: function (option, parentModel, ecModel) {
            /**
             * @private
             * @type {boolean}
             */
            this._autoSeriesIndex = false;

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

            /**
             * @readOnly
             */
            this.controllerVisuals = {};

            /**
             * @readOnly
             */
            this.targetVisuals = {};

            /**
             * @readOnly
             */
            this.textStyleModel;

            /**
             * [width, height]
             * @readOnly
             * @type {Array.<number>}
             */
            this.itemSize;

            this.mergeDefaultAndTheme(option, ecModel);
            this.mergeOption({}, true);
        },

        /**
         * @override
         */
        baseMergeOption: function (newOption) {
            var thisOption = this.option;

            newOption && zrUtil.merge(thisOption, newOption);

            if (newOption.text && isArray(newOption.text)) {
                thisOption.text = newOption.text.slice();
            }

            // FIXME
            // necessary?
            // Disable realtime view update if canvas is not supported.
            if (!env.canvasSupported) {
                thisOption.realtime = false;
            }

            this.textStyleModel = this.getModel('textStyle');

            this.resetItemSize();

            this.completeVisualOption();
        },

        /**
         * @example
         * this.formatValueText(someVal); // format single numeric value to text.
         * this.formatValueText(someVal, true); // format single category value to text.
         * this.formatValueText([min, max]); // format numeric min-max to text.
         * this.formatValueText([this.dataBound[0], max]); // using data lower bound.
         * this.formatValueText([min, this.dataBound[1]]); // using data upper bound.
         *
         * @param {number|Array.<number>} value Real value, or this.dataBound[0 or 1].
         * @param {boolean} [isCategory=false] Only available when value is number.
         * @return {string}
         * @protected
         */
        formatValueText: function(value, isCategory) {
            var option = this.option;
            var precision = option.precision;
            var dataBound = this.dataBound;
            var formatter = option.formatter;
            var isMinMax;
            var textValue;

            if (zrUtil.isArray(value)) {
                value = value.slice();
                isMinMax = true;
            }

            textValue = isCategory
                ? value
                : (isMinMax
                    ? [toFixed(value[0]), toFixed(value[1])]
                    : toFixed(value)
                );

            if (zrUtil.isString(formatter)) {
                return formatter
                    .replace('{value}', isMinMax ? textValue[0] : textValue)
                    .replace('{value2}', isMinMax ? textValue[1] : textValue);
            }
            else if (zrUtil.isFunction(formatter)) {
                return isMinMax
                    ? formatter(value[0], value[1])
                    : formatter(value);
            }

            if (isMinMax) {
                if (value[0] === dataBound[0]) {
                    return '< ' + textValue[1];
                }
                else if (value[1] === dataBound[1]) {
                    return '> ' + textValue[0];
                }
                else {
                    return textValue[0] + ' - ' + textValue[1];
                }
            }
            else { // Format single value (includes category case).
                return textValue;
            }

            function toFixed(val) {
                return val === dataBound[0]
                    ? 'min'
                    : val === dataBound[1]
                    ? 'max'
                    : (+val).toFixed(precision);
            }
        },

        /**
         * @protected
         */
        resetTargetSeries: function (newOption, isInit) {
            var thisOption = this.option;
            var autoSeriesIndex = this._autoSeriesIndex =
                (isInit ? thisOption : newOption).seriesIndex == null;
            thisOption.seriesIndex = autoSeriesIndex
                ? [] : modelUtil.normalizeToArray(thisOption.seriesIndex);

            autoSeriesIndex && this.ecModel.eachSeries(function (seriesModel, index) {
                var data = seriesModel.getData();
                // FIXME
                // 只考虑了list，还没有考虑map等。

                // FIXME
                // 这里可能应该这么判断：data.dimensions中有超出其所属coordSystem的量。
                if (data.type === 'list') {
                    thisOption.seriesIndex.push(index);
                }
            });
        },

        /**
         * @protected
         */
        resetExtent: function () {
            var thisOption = this.option;

            // Can not calculate data extent by data here.
            // Because series and data may be modified in processing stage.
            // So we do not support the feature "auto min/max".

            var extent = asc([thisOption.min, thisOption.max]);

            this._dataExtent = extent;
        },

        /**
         * @protected
         */
        getDataDimension: function (list) {
            var optDim = this.option.dimension;
            return optDim != null
                ? optDim : list.dimensions.length - 1;
        },

        /**
         * @public
         * @override
         */
        getExtent: function () {
            return this._dataExtent.slice();
        },

        /**
         * @protected
         */
        resetVisual: function (fillVisualOption) {
            var dataExtent = this.getExtent();

            doReset.call(this, 'controller', this.controllerVisuals);
            doReset.call(this, 'target', this.targetVisuals);

            function doReset(baseAttr, visualMappings) {
                each(this.stateList, function (state) {
                    var mappings = visualMappings[state] || (visualMappings[state] = {});
                    var visaulOption = this.option[baseAttr][state] || {};
                    each(visaulOption, function (visualData, visualType) {
                        if (!VisualMapping.isValidType(visualType)) {
                            return;
                        }
                        var mappingOption = {
                            type: visualType,
                            dataExtent: dataExtent,
                            visual: visualData
                        };
                        fillVisualOption && fillVisualOption.call(this, mappingOption, state);
                        mappings[visualType] = new VisualMapping(mappingOption);
                    }, this);
                }, this);
            }
        },

        /**
         * @protected
         */
        completeVisualOption: function () {
            var thisOption = this.option;
            var base = {inRange: thisOption.inRange, outOfRange: thisOption.outOfRange};

            var target = thisOption.target || (thisOption.target = {});
            var controller = thisOption.controller || (thisOption.controller = {});

            zrUtil.merge(target, base); // Do not override
            zrUtil.merge(controller, base); // Do not override

            var isCategory = this.isCategory();

            completeSingle.call(this, target);
            completeSingle.call(this, controller);
            completeInactive.call(this, target, 'inRange', 'outOfRange');
            completeInactive.call(this, target, 'outOfRange', 'inRange');
            completeController.call(this, controller);

            function completeSingle(base) {
                // Compatible with ec2 dataRange.color.
                // The mapping order of dataRange.color is: [high value, ..., low value]
                // whereas inRange.color and outOfRange.color is [low value, ..., high value]
                // Notice: ec2 has no inverse.
                if (isArray(thisOption.color)
                    // If there has been inRange: {symbol: ...}, adding color is a mistake.
                    // So adding color only when no inRange defined.
                    && !base.inRange
                ) {
                    base.inRange = {color: thisOption.color.slice().reverse()};
                }

                // If using shortcut like: {inRange: 'symbol'}, complete default value.
                each(this.stateList, function (state) {
                    var visualType = base[state];

                    if (zrUtil.isString(visualType)) {
                        var defa = visualDefault.get(visualType, 'active', isCategory);
                        if (defa) {
                            base[state] = {};
                            base[state][visualType] = defa;
                        }
                        else {
                            // Mark as not specified.
                            delete base[state];
                        }
                    }
                }, this);
            }

            function completeInactive(base, stateExist, stateAbsent) {
                var optExist = base[stateExist];
                var optAbsent = base[stateAbsent];

                if (optExist && !optAbsent) {
                    optAbsent = base[stateAbsent] = {};
                    each(optExist, function (visualData, visualType) {
                        var defa = visualDefault.get(visualType, 'inactive', isCategory);
                        if (VisualMapping.isValidType(visualType) && defa) {
                            optAbsent[visualType] = defa;
                        }
                    });
                }
            }

            function completeController(controller) {
                var symbolExists = (controller.inRange || {}).symbol
                    || (controller.outOfRange || {}).symbol;
                var symbolSizeExists = (controller.inRange || {}).symbolSize
                    || (controller.outOfRange || {}).symbolSize;
                var inactiveColor = this.get('inactiveColor');

                each(this.stateList, function (state) {

                    var itemSize = this.itemSize;
                    var visuals = controller[state];

                    // Set inactive color for controller if no other color attr (like colorAlpha) specified.
                    if (!visuals) {
                        visuals = controller[state] = {
                            color: isCategory ? inactiveColor : [inactiveColor]
                        };
                    }

                    // Consistent symbol and symbolSize if not specified.
                    if (!visuals.symbol) {
                        visuals.symbol = symbolExists
                            && zrUtil.clone(symbolExists)
                            || (isCategory ? 'roundRect' : ['roundRect']);
                    }
                    if (!visuals.symbolSize) {
                        visuals.symbolSize = symbolSizeExists
                            && zrUtil.clone(symbolSizeExists)
                            || (isCategory ? itemSize[0] : [itemSize[0], itemSize[0]]);
                    }

                    // Filter square and none.
                    visuals.symbol = mapVisual(visuals.symbol, function (symbol) {
                        return (symbol === 'none' || symbol === 'square') ? 'roundRect' : symbol;
                    });

                    // Normalize symbolSize
                    var symbolSize = visuals.symbolSize;

                    if (symbolSize) {
                        var max = -Infinity;
                        // symbolSize can be object when categories defined.
                        eachVisual(symbolSize, function (value) {
                            value > max && (max = value);
                        });
                        visuals.symbolSize = mapVisual(symbolSize, function (value) {
                            return linearMap(value, [0, max], [0, itemSize[0]], true);
                        });
                    }

                }, this);
            }
        },

        /**
         * @public
         */
        eachTargetSeries: function (callback, context) {
            zrUtil.each(this.option.seriesIndex, function (seriesIndex) {
                callback.call(context, this.ecModel.getSeriesByIndex(seriesIndex));
            }, this);
        },

        /**
         * @public
         */
        isCategory: function () {
            return !!this.option.categories;
        },

        /**
         * @protected
         */
        resetItemSize: function () {
            this.itemSize = [
                parseFloat(this.get('itemWidth')),
                parseFloat(this.get('itemHeight'))
            ];
        },

        /**
         * @public
         * @abstract
         */
        setSelected: zrUtil.noop,

        /**
         * @public
         * @abstract
         */
        getValueState: zrUtil.noop

    });

});<|endoftext|>


<commit_before>var CrudView = {
    bulkActionForm: function (selector) {
        var bulkActionForm = $(selector);
        if (bulkActionForm.length) {
            bulkActionForm.submit(function (e) {
                var action = $('.bulk-action-submit select', bulkActionForm).val();
                if (!action) {
                    return e.preventDefault();
                }

                bulkActionForm.attr('action', action);
            });
        }
    },

    flatpickr: function (selector) {
        $(selector).flatpickr();
    },

    selectize: function (selector) {
        $(selector).selectize({plugins: ['remove_button']});
    },

    select2: function (selector) {
        $(selector).each(function () {
            var $this = $(this),
                config = {theme: 'bootstrap4'};

            if (!$this.prop('multiple') && $this.find('option:first').val() === '') {
                config.allowClear = true;
                config.placeholder = '';
            }

            $(this).select2(config);
        });
    },

    autocomplete: function (selector) {
        $(selector).each(function (i, ele) {
            var $ele = $(ele);

            $ele.select2({
                theme: 'bootstrap4',
                minimumInputLength: 1,
                ajax: {
                    delay: 250,
                    url: $ele.data('url'),
                    dataType: 'json',
                    data: function (params) {
                        var query = {};
                        query[$ele.data('filter-field') || $ele.attr('name')] = params.term;

                        if ($ele.data('dependent-on') && $('#' + $ele.data('dependent-on')).val()) {
                            data[$ele.data('dependent-on-field')] = $('#' + $ele.data('dependent-on')).val();
                        }

                        return query;
                    },
                    processResults: function (data, params) {
                        var results = [];
                        var inputType = $ele.data('inputType');

                        if (data.data) {
                            $.each(data.data, function(id, text) {
                                if (text.indexOf(params.term) > -1) {
                                    results.push({
                                        id: inputType === 'text' ? text : id,
                                        text: text
                                    });
                                }
                            });
                        }

                        return {
                            results: results
                        };
                    }
                }
            });
        });
    },

    dirtyForms: function () {
        $.DirtyForms.dialog = false;
        $('form[data-dirty-check=1]').dirtyForms();
    },

    dropdown: function () {
        $('.dropdown-toggle').dropdown();

        // recommended hack to get dropdowns correctly work inside responsive table
        $('.table-responsive').on('show.bs.dropdown', function () {
            $('.table-responsive').css( "overflow", "inherit" );
        });
        $('.table-responsive').on('hide.bs.dropdown', function () {
            $('.table-responsive').css( "overflow", "auto" );
        })
    },

    initialize: function () {
        this.bulkActionForm('.bulk-actions');
        this.flatpickr('.flatpickr');
        this.select2('select[multiple]:not(.no-select2), .select2');
        this.autocomplete('input.autocomplete, select.autocomplete');
        this.dirtyForms();
        this.dropdown();
    }
};

$(function () {
    CrudView.initialize();
});
<commit_msg>Use case insensitive match<commit_after>var CrudView = {
    bulkActionForm: function (selector) {
        var bulkActionForm = $(selector);
        if (bulkActionForm.length) {
            bulkActionForm.submit(function (e) {
                var action = $('.bulk-action-submit select', bulkActionForm).val();
                if (!action) {
                    return e.preventDefault();
                }

                bulkActionForm.attr('action', action);
            });
        }
    },

    flatpickr: function (selector) {
        $(selector).flatpickr();
    },

    selectize: function (selector) {
        $(selector).selectize({plugins: ['remove_button']});
    },

    select2: function (selector) {
        $(selector).each(function () {
            var $this = $(this),
                config = {theme: 'bootstrap4'};

            if (!$this.prop('multiple') && $this.find('option:first').val() === '') {
                config.allowClear = true;
                config.placeholder = '';
            }

            $(this).select2(config);
        });
    },

    autocomplete: function (selector) {
        $(selector).each(function (i, ele) {
            var $ele = $(ele);

            $ele.select2({
                theme: 'bootstrap4',
                minimumInputLength: 1,
                ajax: {
                    delay: 250,
                    url: $ele.data('url'),
                    dataType: 'json',
                    data: function (params) {
                        var query = {};
                        query[$ele.data('filter-field') || $ele.attr('name')] = params.term;

                        if ($ele.data('dependent-on') && $('#' + $ele.data('dependent-on')).val()) {
                            data[$ele.data('dependent-on-field')] = $('#' + $ele.data('dependent-on')).val();
                        }

                        return query;
                    },
                    processResults: function (data, params) {
                        var results = [],
                            inputType = $ele.data('inputType'),
                            term = params.term.toLowerCase();

                        if (data.data) {
                            $.each(data.data, function(id, text) {
                                if (text.toLowerCase().indexOf(term) > -1) {
                                    results.push({
                                        id: inputType === 'text' ? text : id,
                                        text: text
                                    });
                                }
                            });
                        }

                        return {
                            results: results
                        };
                    }
                }
            });
        });
    },

    dirtyForms: function () {
        $.DirtyForms.dialog = false;
        $('form[data-dirty-check=1]').dirtyForms();
    },

    dropdown: function () {
        $('.dropdown-toggle').dropdown();

        // recommended hack to get dropdowns correctly work inside responsive table
        $('.table-responsive').on('show.bs.dropdown', function () {
            $('.table-responsive').css( "overflow", "inherit" );
        });
        $('.table-responsive').on('hide.bs.dropdown', function () {
            $('.table-responsive').css( "overflow", "auto" );
        })
    },

    initialize: function () {
        this.bulkActionForm('.bulk-actions');
        this.flatpickr('.flatpickr');
        this.select2('select[multiple]:not(.no-select2), .select2');
        this.autocomplete('input.autocomplete, select.autocomplete');
        this.dirtyForms();
        this.dropdown();
    }
};

$(function () {
    CrudView.initialize();
});
<|endoftext|>


<commit_before>class System {
  constructor(p_xSizeAndPos){
    this.sizeAndPosition = p_xSizeAndPos;
    this.optionsSet = new OptionsSet(this.optionsMetadata());
    this.settings = this.optionsSet.settings;
    this.scale =  this.settings.scale;

    this.internalTicksPerFrame = 1;
    this.speedIdx = 1;
    this.paused = false;
    this.tickCount = 0;
    this.initFireRisks();
    this.fireRiskThreshold = 0.1;
    this.cellViewer = new CellViewer();
    this.grid = new CellGrid(this.sizeAndPosition,
                             this,
                             this.settings.cellWidth,
                             this.cellViewer
                             );
    this.grid.initCells();

    this.firePropagationMatrix = System.BASE_INFLUENCE_MATRIX;
  }

  // Return a list of Options, specific to this sketch,
  // that can be tweaked via the URL parameters
  // separate from the default P5JsSettings that are supported
  //
  // supported types: integer, float, string
  optionsMetadata(){
    return [
      { name: "cellWidth", type: "integer", default: 20},
      { name: "scale", type: "float", default: 0.02},
      // { name: "varname3", type: "float", default: 0.6}
    ];
  }

  createCell(tmpRow, tmpCol, i){
    const tmpCell = new Cell(tmpRow, tmpCol, i, this);
    tmpCell.setType(this.terrainTypeForPos(tmpRow, tmpCol));
    if (tmpCell.terrainType == System.TERRAIN_FOLLIAGE){
      tmpCell.fuelAmount = 100;
    }
    return tmpCell;
  }

  get x(){ return this.sizeAndPosition.x; }
  get y(){ return this.sizeAndPosition.y; }
  containsXY(x, y){ return this.sizeAndPosition.containsXY(x, y); }

  static get TERRAIN_SOIL(){ return 0; }
  static get TERRAIN_WATER(){ return 1; }
  static get TERRAIN_FOLLIAGE(){ return 2; }
  static get TERRAIN_BURNING(){ return 3; }
  static get TERRAIN_ENGULFED(){ return 4; }
  static get TERRAIN_SMOLDERING(){ return 5; }
  static get TERRAIN_BURNT(){ return 6; }
  static get TERRAIN_PARTIAL_BURN(){ return 7; }

  static get BASE_INFLUENCE_MATRIX(){
    return [
        0.02,
        0.05,
        0.02,

        0.05,
        // cell
        0.05,

        0.02,
        0.05,
        0.02
      ];
  }

  initFireRisks(){
    this.terrainFireRisks = [];
    this.terrainFireRisks[System.TERRAIN_SOIL]       = 0;
    this.terrainFireRisks[System.TERRAIN_WATER]      = -0.1;
    this.terrainFireRisks[System.TERRAIN_FOLLIAGE]   = 0;
    this.terrainFireRisks[System.TERRAIN_BURNING]    = 1.0;
    this.terrainFireRisks[System.TERRAIN_ENGULFED]   = 1.5;
    this.terrainFireRisks[System.TERRAIN_SMOLDERING] = 0.3;
    this.terrainFireRisks[System.TERRAIN_BURNT]      = 0.05;
    this.terrainFireRisks[System.TERRAIN_PARTIAL_BURN] = 0;
  }

  togglePause(){
    this.paused = !this.paused;
    this.setSpeed(max(1, this.speedIdx));
  }

  slowDown(){ this.setSpeed(this.speedIdx - 1) }
  speedUp(){ this.setSpeed(this.speedIdx + 1) }

  setSpeed(newIndex){
    const speedLookup = [0,
      1,
      2,
      4
    ];

    this.speedIdx = constrain(newIndex, 0, speedLookup.length - 1);
    this.internalTicksPerFrame = speedLookup[this.speedIdx];
  }

  terrainTypeForPos(x, y){
    const waterOrLand = noise(this.scale * x, this.scale * y);

    if (waterOrLand < 0.5){
      return System.TERRAIN_WATER;

    } else {
      const largeOffset = 100000;
      const landOrFolliage = noise(this.scale * (x + largeOffset),
                                   this.scale * (y + largeOffset));
      if (landOrFolliage < 0.6) {
        return System.TERRAIN_SOIL;
      } else {
        return System.TERRAIN_FOLLIAGE;
      }
    }
  }

  knockDownAt(x, y){
    const cell = this.grid.cellForXY(x, y);
    if (!cell.isBurning()){
      return;
    }
    cell.fireIntensity = 0;
    cell.setType(System.TERRAIN_SMOLDERING);
  }

  fireBreakAt(x, y){
    const cell = this.grid.cellForXY(x, y);

    if (!cell.isBurning() && cell.terrainType == System.TERRAIN_FOLLIAGE){
      cell.setType(System.TERRAIN_SOIL);
      cell.fuelAmount = 0;
    }
  }

  lightningAt(x, y){
    const cell = this.grid.cellForXY(x, y);

    if (!cell.isBurning() && cell.fuelAmount > 0){
      cell.startBurning();
    }
  }

  lightningStrike(){
    let firstFolliage = this.grid.cells.find(c => c.terrainType == System.TERRAIN_FOLLIAGE);
    if (firstFolliage) {
      firstFolliage.startBurning();
    }
  }

  tick(){
    // console.log("tock");
    if (this.paused){
      return;
    }

    for (var i = 0; i < this.internalTicksPerFrame; i++){
      this.internalTick();
    }
  }

  internalTick(){
    this.tickCount += 1;

    if (this.tickCount % 5 == 0) {
      this.calcNextCellTypes();
      this.assignNextTypes();
    }
    this.grid.cells.forEach(cell => cell.tick());
  }

  assignNextTypes(){
    this.grid.cells.filter(c => c.nextFrameType != undefined)
                   .forEach((c) => {
                      c.setType(c.nextFrameType);
                      c.nextFrameType = undefined;
                    });
  }

  calcNextCellTypes(){
    this.grid.cells.filter(c => !c.isBurning() && c.fuelAmount > 0)
                   .forEach(c => this.setNextTypeForCell(c));
  }

  setNextTypeForCell(cell){
    if(this.fireRiskFromNeighbors(cell) > this.fireRiskThreshold){
      cell.nextFrameType = System.TERRAIN_BURNING;
    }
  }

  fireRiskFromNeighbors(cell){
    const neighbors = this.grid.cellNeighborsOfIdx(cell._idx);

    return  neighbors.map(this.fireRisk, this)
                     .reduce((total, riskFactor) => total + riskFactor, 0);
  }

  fireRisk(cell, neighborIdx){
    if (cell == undefined) {
      return 0;
    }

    return this.terrainFireRisks[cell.terrainType]
              * this.firePropagationMatrix[neighborIdx];
  }

  render(){
    noStroke();
    this.grid.renderViewsAsNeeded();
  }
}
<commit_msg>p5js/forest-fires-02 - Add slower speeds<commit_after>class System {
  constructor(p_xSizeAndPos){
    this.sizeAndPosition = p_xSizeAndPos;
    this.optionsSet = new OptionsSet(this.optionsMetadata());
    this.settings = this.optionsSet.settings;
    this.scale =  this.settings.scale;

    this.internalTicksPerFrame = 1;
    this.speedIdx = 1;
    this.paused = false;
    this.tickCount = 0;
    this.initFireRisks();
    this.fireRiskThreshold = 0.1;
    this.cellViewer = new CellViewer();
    this.grid = new CellGrid(this.sizeAndPosition,
                             this,
                             this.settings.cellWidth,
                             this.cellViewer
                             );
    this.grid.initCells();

    this.firePropagationMatrix = System.BASE_INFLUENCE_MATRIX;
  }

  // Return a list of Options, specific to this sketch,
  // that can be tweaked via the URL parameters
  // separate from the default P5JsSettings that are supported
  //
  // supported types: integer, float, string
  optionsMetadata(){
    return [
      { name: "cellWidth", type: "integer", default: 20},
      { name: "scale", type: "float", default: 0.02},
      // { name: "varname3", type: "float", default: 0.6}
    ];
  }

  createCell(tmpRow, tmpCol, i){
    const tmpCell = new Cell(tmpRow, tmpCol, i, this);
    tmpCell.setType(this.terrainTypeForPos(tmpRow, tmpCol));
    if (tmpCell.terrainType == System.TERRAIN_FOLLIAGE){
      tmpCell.fuelAmount = 100;
    }
    return tmpCell;
  }

  get x(){ return this.sizeAndPosition.x; }
  get y(){ return this.sizeAndPosition.y; }
  containsXY(x, y){ return this.sizeAndPosition.containsXY(x, y); }

  static get TERRAIN_SOIL(){ return 0; }
  static get TERRAIN_WATER(){ return 1; }
  static get TERRAIN_FOLLIAGE(){ return 2; }
  static get TERRAIN_BURNING(){ return 3; }
  static get TERRAIN_ENGULFED(){ return 4; }
  static get TERRAIN_SMOLDERING(){ return 5; }
  static get TERRAIN_BURNT(){ return 6; }
  static get TERRAIN_PARTIAL_BURN(){ return 7; }

  static get BASE_INFLUENCE_MATRIX(){
    return [
        0.02,
        0.05,
        0.02,

        0.05,
        // cell
        0.05,

        0.02,
        0.05,
        0.02
      ];
  }

  initFireRisks(){
    this.terrainFireRisks = [];
    this.terrainFireRisks[System.TERRAIN_SOIL]       = 0;
    this.terrainFireRisks[System.TERRAIN_WATER]      = -0.1;
    this.terrainFireRisks[System.TERRAIN_FOLLIAGE]   = 0;
    this.terrainFireRisks[System.TERRAIN_BURNING]    = 1.0;
    this.terrainFireRisks[System.TERRAIN_ENGULFED]   = 1.5;
    this.terrainFireRisks[System.TERRAIN_SMOLDERING] = 0.3;
    this.terrainFireRisks[System.TERRAIN_BURNT]      = 0.05;
    this.terrainFireRisks[System.TERRAIN_PARTIAL_BURN] = 0;
  }

  togglePause(){
    this.paused = !this.paused;
    this.setSpeed(max(1, this.speedIdx));
  }

  slowDown(){ this.setSpeed(this.speedIdx - 1) }
  speedUp(){ this.setSpeed(this.speedIdx + 1) }

  setSpeed(newIndex){
    const speedLookup = [0,
      0.25,
      0.5,
      1,
      2,
      4
    ];

    this.speedIdx = constrain(newIndex, 0, speedLookup.length - 1);
    this.internalTicksPerFrame = speedLookup[this.speedIdx];
  }

  terrainTypeForPos(x, y){
    const waterOrLand = noise(this.scale * x, this.scale * y);

    if (waterOrLand < 0.5){
      return System.TERRAIN_WATER;

    } else {
      const largeOffset = 100000;
      const landOrFolliage = noise(this.scale * (x + largeOffset),
                                   this.scale * (y + largeOffset));
      if (landOrFolliage < 0.6) {
        return System.TERRAIN_SOIL;
      } else {
        return System.TERRAIN_FOLLIAGE;
      }
    }
  }

  knockDownAt(x, y){
    const cell = this.grid.cellForXY(x, y);
    if (!cell.isBurning()){
      return;
    }
    cell.fireIntensity = 0;
    cell.setType(System.TERRAIN_SMOLDERING);
  }

  fireBreakAt(x, y){
    const cell = this.grid.cellForXY(x, y);

    if (!cell.isBurning() && cell.terrainType == System.TERRAIN_FOLLIAGE){
      cell.setType(System.TERRAIN_SOIL);
      cell.fuelAmount = 0;
    }
  }

  lightningAt(x, y){
    const cell = this.grid.cellForXY(x, y);

    if (!cell.isBurning() && cell.fuelAmount > 0){
      cell.startBurning();
    }
  }

  lightningStrike(){
    let firstFolliage = this.grid.cells.find(c => c.terrainType == System.TERRAIN_FOLLIAGE);
    if (firstFolliage) {
      firstFolliage.startBurning();
    }
  }

  tick(){
    // console.log("tock");
    if (this.paused){
      return;
    }

    if (this.internalTicksPerFrame < 1) {
      const onlyRunOnModulo = 1 / this.internalTicksPerFrame;
      if (frameCount % onlyRunOnModulo == 0){
        this.internalTick();
      }
    } else {
      for (var i = 0; i < this.internalTicksPerFrame; i++){
        this.internalTick();
      }
    }
  }

  internalTick(){
    this.tickCount += 1;

    if (this.tickCount % 5 == 0) {
      this.calcNextCellTypes();
      this.assignNextTypes();
    }
    this.grid.cells.forEach(cell => cell.tick());
  }

  assignNextTypes(){
    this.grid.cells.filter(c => c.nextFrameType != undefined)
                   .forEach((c) => {
                      c.setType(c.nextFrameType);
                      c.nextFrameType = undefined;
                    });
  }

  calcNextCellTypes(){
    this.grid.cells.filter(c => !c.isBurning() && c.fuelAmount > 0)
                   .forEach(c => this.setNextTypeForCell(c));
  }

  setNextTypeForCell(cell){
    if(this.fireRiskFromNeighbors(cell) > this.fireRiskThreshold){
      cell.nextFrameType = System.TERRAIN_BURNING;
    }
  }

  fireRiskFromNeighbors(cell){
    const neighbors = this.grid.cellNeighborsOfIdx(cell._idx);

    return  neighbors.map(this.fireRisk, this)
                     .reduce((total, riskFactor) => total + riskFactor, 0);
  }

  fireRisk(cell, neighborIdx){
    if (cell == undefined) {
      return 0;
    }

    return this.terrainFireRisks[cell.terrainType]
              * this.firePropagationMatrix[neighborIdx];
  }

  render(){
    noStroke();
    this.grid.renderViewsAsNeeded();
  }
}
<|endoftext|>


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

const styles = require('./style.scss');

export default class Track extends Component {
  static propTypes = {
    progress: PropTypes.number.isRequired,
    onTrackChange: PropTypes.func,
    simple: PropTypes.bool,
    style: PropTypes.any
  };

  handleClick = (event) => {
    const { onTrackChange, simple } = this.props;

    if (!simple) {
      const fraction = (
        event.nativeEvent.offsetX /
        this.refs.container.getBoundingClientRect().width
      );

      return onTrackChange(fraction);
    }
  }

  render() {
    const { progress, simple, style } = this.props;

    return (

        <div className={`${styles.progress} ${simple ? styles.simple : ''}`} style={{width: `${progress}%`}}/>

    );
  }
}
<commit_msg>clean up<commit_after>import React, { Component, PropTypes } from 'react';

const styles = require('./style.scss');

export default class Track extends Component {
  static propTypes = {
    progress: PropTypes.number.isRequired,
    onTrackChange: PropTypes.func,
    simple: PropTypes.bool,
    style: PropTypes.object
  };

  handleClick = (event) => {
    const { onTrackChange } = this.props;

    const fraction = (
      event.nativeEvent.offsetX /
      this.refs.container.getBoundingClientRect().width
    );

    return onTrackChange(fraction);
  }

  render() {
    const { progress, simple, style } = this.props;

    return (

        <div className={`${styles.progress} ${simple ? styles.simple : ''}`} style={{ width: `${progress}%` }} />

    );
  }
}
<|endoftext|>


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

const {fbContent, fbInternalOnly} = require('internaldocs-fb-helpers');
module.exports = {
  mainSidebar: {
    // TODO: update content
    'What is Litho?': ['intro/motivation', 'intro/built-with-litho'],
    'Tutorial': [
      'tutorial/overview',
      // TODO: add kotlin dep
      'tutorial/project-setup',
      'tutorial/first-components',
      'tutorial/adding-state',
      'tutorial/building-lists',
    ],
    'Main Concepts': [
      'mainconcepts/components-basics',
      'mainconcepts/props',
      {
        'Hooks and State': [
          'mainconcepts/hooks-intro',
          'mainconcepts/use-state',
          'mainconcepts/use-effect',
          'mainconcepts/use-ref',
          'mainconcepts/use-error-boundary',
        ],
      },
      'mainconcepts/flexbox-yoga',
      {
        // TODO: remove/replace 'Coordinating State and Actions'
        'Coordinating State and Actions': [
          'mainconcepts/coordinate-state-actions/hoisting-state',
          'mainconcepts/coordinate-state-actions/communicating-between-components',
          'mainconcepts/coordinate-state-actions/componenttree',
          'mainconcepts/coordinate-state-actions/visibility-handling',
          'mainconcepts/coordinate-state-actions/keys-and-identity',
        ],
      },
    ],
    'Building lists': [
      'sections/start',
      'sections/recycler-collection-component',
      'sections/best-practices',
      'sections/hscrolls',
      'sections/api-overview',
      'sections/working-ranges',
      'sections/services',
      'sections/view-support',
      'sections/diff-sections',
      'sections/architecture',
    ],
    // TODO: revisit wording to make it clear it's not just the 'kotlin' testing API
    'Testing': [
      'kotlin/testing-getting-started',
      'kotlin/testing-assertions',
      'kotlin/testing-actions',
    ],
    // TODO: de-dupe content with main concepts
    ...fbInternalOnly({
      'Migrating to the Kotlin API': [
        'kotlin/kotlin-intro',
        'kotlin/kotlin-api-basics',
        'kotlin/hooks-for-spec-developers',
        'kotlin/kotlin-flexbox-containers',
        'kotlin/collections',
        'kotlin/event-handling',
        'kotlin/kotlin-api-cheatsheet',
      ],
    }),
    // TODO: clean this section up, add intro page
    'Codegen APIs': [
      'codegen/layout-specs',
      'codegen/mount-specs',
      {
        'Passing Data To Components': [
          'codegen/passing-data-to-components/spec-props',
          'codegen/passing-data-to-components/treeprops',
        ],
      },
      'codegen/state-for-specs',
      'codegen/events-for-specs',
      'codegen/trigger-events',
    ],
    'Animations': [
      'animations/transition-basics',
      'animations/transition-types',
      'animations/transition-all-layout',
      'animations/transition-choreography',
      'animations/transition-definitions',
      'animations/transition-key-types',
      'animations/dynamic-props',
    ],
    'Widgets': [
      'widgets/builtin-widgets',
      ...fbInternalOnly(['fb/widgets/design-components']),
    ],
    'Accessibility': ['accessibility/accessibility-overview'],
    // TODO: clean this section up, add intro page
    'Tooling': [
      {
        'Debugging': [
          'debugging/debugging-tips',
          'debugging/debugging-sections',
          ...fbInternalOnly(['debugging/fb/debugging-time-travel']),
        ],
        'Developer Tools': [
          'devtools/android-studio-plugin',
          'devtools/flipper-plugins',
        ],
      },
      'performance/analysing-performance',
    ],
    'Best Practices': [
      'best-practices/immutability',
      'best-practices/props-vs-state',
      'best-practices/coding-style',
    ],
    ...fbInternalOnly({
      'Contributing to the Documentation': [
        'fb/documentation/contributing-documentation',
        'fb/documentation/formatting-tips',
        'fb/documentation/writing-guide',
      ],
    }),
    ...fbInternalOnly({
      '[Internal]': [
        'fb/internal-litho',
        'fb/video-lessons',
        {
          Architecture: [
            'fb/architecture-sections-in-a-fragment-or-activity',
            'fb/architecture-thread-safety',
            'fb/architecture-litho-tricks',
          ],
        },
        'fb/dependency-injection',
        {
          'Analysing Performance': [
            'fb/analysing-performance-qpl',
            'fb/analysing-performance-spotting-performance-issues',
            'fb/analysing-performance-ttrc',
          ],
        },
        {
          'Error Handling': [
            'fb/error-boundaries',
            'fb/error-handling',
            'fb/error-handling-setting-a-default-error-event-handler',
          ],
        },
        'fb/experimentation',
        {
          'Open Source': [
            'fb/open-source',
            'fb/open-source-using-the-open-source-repo',
            'fb/open-source-releasing-litho',
          ],
        },
        'fb/sample-app',
      ],
    }),

    //
    // Begin unused content
    //

    ...fbInternalOnly({
      '[Old Not Reused Content]': [
        {
          'Introducing Litho': ['intro', 'uses'],
          'Quick Start': [
            'getting-started',
            'tutorial',
            'writing-components',
            'using-components',
          ],
          'Reference': ['common-props', 'cached-values'],
          'Handling Events': ['events-touch-handling'],
          'Sections': ['sections-tutorial', 'communicating-with-the-ui'],
          'Common use cases': [
            'updating-ui',
            'borders',
            'tooltips',
            'saving-state',
          ],
          'Compatibility': ['styles'],
          'Advanced Guides': [
            'architecture-overview',
            'custom-layout',
            'onattached-ondetached',
          ],
          'Architecture': [
            'codegen',
            'asynchronous-layout',
            'view-flattening',
            'recycling',
          ],
          'Experimental': ['mount-extensions'],
          'Additional Resources': ['faq', 'glossary'],
          'Contributing': [
            'contributing',
            'community-showcase',
            'repo-structure',
          ],
          'Testing': [
            'testing/testing-overview',
            {
              'Unit Tests': [
                'testing/unit-testing',
                ...fbInternalOnly(['testing/fb/unit-testing-at-facebook']),
                'testing/subcomponent-testing',
                'testing/prop-matching',
                'testing/testing-treeprops',
                'testing/injectprop-matching',
                'testing/event-handler-testing',
                'testing/sections-testing',
              ],
            },
            {
              'UI Tests': [
                'testing/espresso-testing',
                ...fbInternalOnly(['testing/fb/buddy-tests-at-facebook']),
              ],
            },
            ...fbInternalOnly([
              {
                'Benchmark Tests': [
                  'testing/fb/litho-benchmark-tests',
                  {
                    'MobileLab Tests': [
                      'testing/fb/mobilelab-benchmark-tests/mobilelab-tests',
                      'testing/fb/mobilelab-benchmark-tests/getting-started',
                      'testing/fb/mobilelab-benchmark-tests/memory-benchmarks',
                      'testing/fb/mobilelab-benchmark-tests/mobilelab-integration',
                      'testing/fb/mobilelab-benchmark-tests/profiling-benchmarks',
                    ],
                  },
                ],
              },
            ]),
            'testing/tests-in-android-studio',
          ],
          'Deep Dive': [
            {
              Reconciliation: [
                'deep-dive/reconciliation',
                'deep-dive/reconciliation/enabling-reconciliation',
                ...fbInternalOnly([
                  'deep-dive/reconciliation/fb/when-to-use-reconciliation',
                ]),
              ],
            },
            'deep-dive/incremental-mount',
            {
              Debugging: ['annotation-processor-debugging'],
            },
          ],
        },
      ],
    }),
  },
};
<commit_msg>Last nav cleanup of mainconcepts and sidebar<commit_after>/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @format
 */

const {fbContent, fbInternalOnly} = require('internaldocs-fb-helpers');
module.exports = {
  mainSidebar: {
    // TODO: update content
    'What is Litho?': ['intro/motivation', 'intro/built-with-litho'],
    'Tutorial': [
      'tutorial/overview',
      // TODO: add kotlin dep
      'tutorial/project-setup',
      'tutorial/first-components',
      'tutorial/adding-state',
      'tutorial/building-lists',
    ],
    'Main Concepts': [
      'mainconcepts/components-basics',
      'mainconcepts/props',
      {
        'Hooks and State': [
          'mainconcepts/hooks-intro',
          'mainconcepts/use-state',
          'mainconcepts/use-effect',
          'mainconcepts/use-ref',
          'mainconcepts/use-error-boundary',
        ],
      },
      'mainconcepts/flexbox-yoga',
      // TODO: Kotlin-ify,
      'mainconcepts/coordinate-state-actions/keys-and-identity',
    ],
    // TODO: Clean up/update for Collection
    'Building Lists': [
      'sections/start',
      'sections/recycler-collection-component',
      'sections/best-practices',
      'sections/hscrolls',
      'sections/api-overview',
      'sections/working-ranges',
      'sections/services',
      'sections/view-support',
      'sections/diff-sections',
      'sections/architecture',
    ],
    // TODO: Kotlin-ify this section
    'Animations': [
      'animations/transition-basics',
      'animations/transition-types',
      'animations/transition-all-layout',
      'animations/transition-choreography',
      'animations/transition-definitions',
      'animations/transition-key-types',
      'animations/dynamic-props',
    ],
    'Visibility': [
      // TODO: Kotlin-ify
      'mainconcepts/coordinate-state-actions/visibility-handling',
    ],
    'Accessibility': ['accessibility/accessibility-overview'],
    // TODO: revisit wording to make it clear it's not just the 'kotlin' testing API
    'Testing': [
      'kotlin/testing-getting-started',
      'kotlin/testing-assertions',
      'kotlin/testing-actions',
    ],
    'Widgets': [
      'widgets/builtin-widgets',
      ...fbInternalOnly(['fb/widgets/design-components']),
    ],
    // TODO: clean this section up, add intro page
    'Codegen APIs': [
      'codegen/layout-specs',
      'codegen/mount-specs',
      {
        'Passing Data To Components': [
          'codegen/passing-data-to-components/spec-props',
          'codegen/passing-data-to-components/treeprops',
        ],
      },
      'codegen/state-for-specs',
      'codegen/events-for-specs',
      'codegen/trigger-events',
    ],
    // TODO: de-dupe content with main concepts
    ...fbInternalOnly({
      'Migrating to the Kotlin API': [
        'kotlin/kotlin-intro',
        'kotlin/kotlin-api-basics',
        'kotlin/hooks-for-spec-developers',
        'kotlin/kotlin-flexbox-containers',
        'kotlin/collections',
        'kotlin/event-handling',
        'kotlin/kotlin-api-cheatsheet',
      ],
    }),
    // TODO: clean this section up, add intro page
    'Tooling': [
      {
        'Debugging': [
          'debugging/debugging-tips',
          'debugging/debugging-sections',
          ...fbInternalOnly(['debugging/fb/debugging-time-travel']),
        ],
        'Developer Tools': [
          'devtools/android-studio-plugin',
          'devtools/flipper-plugins',
        ],
      },
      'performance/analysing-performance',
    ],
    'Best Practices': [
      'best-practices/immutability',
      'best-practices/props-vs-state',
      'mainconcepts/coordinate-state-actions/hoisting-state',
      'mainconcepts/coordinate-state-actions/communicating-between-components',
      'best-practices/coding-style',
    ],
    ...fbInternalOnly({
      'Contributing to the Documentation': [
        'fb/documentation/contributing-documentation',
        'fb/documentation/formatting-tips',
        'fb/documentation/writing-guide',
      ],
    }),
    ...fbInternalOnly({
      '[Internal]': [
        'fb/internal-litho',
        'fb/video-lessons',
        {
          Architecture: [
            'fb/architecture-sections-in-a-fragment-or-activity',
            'fb/architecture-thread-safety',
            'fb/architecture-litho-tricks',
          ],
        },
        'fb/dependency-injection',
        {
          'Analysing Performance': [
            'fb/analysing-performance-qpl',
            'fb/analysing-performance-spotting-performance-issues',
            'fb/analysing-performance-ttrc',
          ],
        },
        {
          'Error Handling': [
            'fb/error-boundaries',
            'fb/error-handling',
            'fb/error-handling-setting-a-default-error-event-handler',
          ],
        },
        'fb/experimentation',
        {
          'Open Source': [
            'fb/open-source',
            'fb/open-source-using-the-open-source-repo',
            'fb/open-source-releasing-litho',
          ],
        },
        'fb/sample-app',
      ],
    }),

    //
    // Begin unused content
    //

    ...fbInternalOnly({
      '[Old Not Reused Content]': [
        {
          'Introducing Litho': ['intro', 'uses'],
          'Quick Start': [
            'getting-started',
            'tutorial',
            'writing-components',
            'using-components',
          ],
          'Reference': ['common-props', 'cached-values'],
          'Handling Events': ['events-touch-handling'],
          'Sections': ['sections-tutorial', 'communicating-with-the-ui'],
          'Common use cases': [
            'updating-ui',
            'borders',
            'tooltips',
            'saving-state',
          ],
          'Compatibility': ['styles'],
          'Advanced Guides': [
            'mainconcepts/coordinate-state-actions/componenttree',
            'architecture-overview',
            'custom-layout',
            'onattached-ondetached',
          ],
          'Architecture': [
            'codegen',
            'asynchronous-layout',
            'view-flattening',
            'recycling',
          ],
          'Experimental': ['mount-extensions'],
          'Additional Resources': ['faq', 'glossary'],
          'Contributing': [
            'contributing',
            'community-showcase',
            'repo-structure',
          ],
          'Testing': [
            'testing/testing-overview',
            {
              'Unit Tests': [
                'testing/unit-testing',
                ...fbInternalOnly(['testing/fb/unit-testing-at-facebook']),
                'testing/subcomponent-testing',
                'testing/prop-matching',
                'testing/testing-treeprops',
                'testing/injectprop-matching',
                'testing/event-handler-testing',
                'testing/sections-testing',
              ],
            },
            {
              'UI Tests': [
                'testing/espresso-testing',
                ...fbInternalOnly(['testing/fb/buddy-tests-at-facebook']),
              ],
            },
            ...fbInternalOnly([
              {
                'Benchmark Tests': [
                  'testing/fb/litho-benchmark-tests',
                  {
                    'MobileLab Tests': [
                      'testing/fb/mobilelab-benchmark-tests/mobilelab-tests',
                      'testing/fb/mobilelab-benchmark-tests/getting-started',
                      'testing/fb/mobilelab-benchmark-tests/memory-benchmarks',
                      'testing/fb/mobilelab-benchmark-tests/mobilelab-integration',
                      'testing/fb/mobilelab-benchmark-tests/profiling-benchmarks',
                    ],
                  },
                ],
              },
            ]),
            'testing/tests-in-android-studio',
          ],
          'Deep Dive': [
            {
              Reconciliation: [
                'deep-dive/reconciliation',
                'deep-dive/reconciliation/enabling-reconciliation',
                ...fbInternalOnly([
                  'deep-dive/reconciliation/fb/when-to-use-reconciliation',
                ]),
              ],
            },
            'deep-dive/incremental-mount',
            {
              Debugging: ['annotation-processor-debugging'],
            },
          ],
        },
      ],
    }),
  },
};
<|endoftext|>


<commit_before>import React from 'react';

export
default React.createClass({
    render() {
        return (

                        <form className="pure-form pure-form-aligned" onsubmit="urlFormAction(); return false;" id="magnetForm">
                            <fieldset>

                                    <input id="magnetLink" type="text" name="magnet" placeholder="Magnet/Torrent URI or Video URL" />
                                    <button id="magnetSubmit" type="submit" className="pure-button pure-button-primary">Stream</button>

                            </fieldset>
                        </form>

        );
    }
});<commit_msg>Do stuff on URL submit!<commit_after>import React from 'react';

import utils from '../../../utils/util';

export
default React.createClass({

    handelURLAdd() {
        if (this.refs.urlInput.value.length > 0) {
            console.log('value:', this.refs.urlInput.value)
            console.log('detected:', utils.parseURL(this.refs.urlInput.value))
        }
    },

    render() {
        return (

                    <form className="pure-form pure-form-aligned" onSubmit={this.handelURLAdd} >
                        <fieldset>

                                <input ref="urlInput" type="text" placeholder="Magnet/Torrent URI or Video URL" />
                                <button type="submit" className="pure-button pure-button-primary">Stream</button>

                        </fieldset>
                    </form>

        );
    }
});<|endoftext|>


<commit_before>const Metalsmith = require('metalsmith');
const inquirer = require('inquirer');
const Handlebars = require('handlebars');
const renderRaw = require('consolidate').handlebars.render;
const path = require('path');
const multimatch = require('multimatch');
const getOptions = require('./options.js');
const logger = require('../logger.js');
const utils = require('../utils');
const render = utils.promisify(renderRaw, renderRaw);

Handlebars.registerHelper('author', function(res) {
  return new Handlebars.SafeString(res.data.root.author);
});

module.exports = function({name, templateName, cacheTemplatePath, targetPath, clone, isMultiModule}, done) {
  const src = cacheTemplatePath || templateName;
  const metadata = getOptions(name, src);

  Metalsmith(path.join(src, 'template'))
    .source('.')
    .destination(targetPath)
    .use(ask(metadata.prompts, isMultiModule))
    .use(filesignore(metadata.filesignore))
    .use(template(metadata.skipCompile))
    .use(multiModule(metadata.multiModule, isMultiModule))
    .use(insertCliInfoToPackage({name, templateName, cacheTemplatePath, clone, isMultiModule}))
    .build(done);
};

/**
 * Prompt plugin.
 *
 * @param {Object} prompts
 */

function ask(prompts, isMultiModule) {
  prompts = Object.keys(prompts).map(key => Object.assign({name: key}, prompts[key]));
  if (isMultiModule) {
    prompts.push({
      name: 'defaultModule',
      type: 'string',
      message: 'Please enter a default module name',
      default: 'home'
    });
  }

  return function(files, metalsmith, done) {
    const metadata = metalsmith.metadata();
    inquirer.prompt(prompts).then((answers) => {
      for (var key in answers) {
        metadata[key] = answers[key];
      }
      done();
    });
  };
}

/**
 * Template in place plugin.
 *
 * @param {Object} skip compile
 */

function template(skipCompile) {
  skipCompile = typeof skipCompile === 'string'
    ? [skipCompile]
    : skipCompile;

  return function(files, metalsmith, done) {
    const metadata = metalsmith.metadata();

    const promises = Object.keys(files).map(file => {
      if (skipCompile && multimatch([file], skipCompile, { dot: true }).length) {
        return Promise.resolve();
      }

      const str = files[file].contents.toString();
      return render(str, metadata)
        .then(res => {
          files[file].contents = Buffer.from(res);
        })
        .catch(_ => {
          logger.error('"%s" file render failed. Please add the file to the skipCompile key in the metadata.js', file);
        });
    });

    Promise.all(promises).then(() => {
      done(null);
    }, done);
  };
}

function filesignore(filesIgnore) {
  return function(files, metalsmith, done) {
    const reg = /(\.tpl\.js)$/;
    for (const file in files) {
      if (reg.test(file)) {
        delete files[file];
        continue;
      }

      for (let i = 0; i < filesIgnore.length; i++) {
        const regPrefixPath = new RegExp('^' + filesIgnore[i]);
        if (regPrefixPath.test(file)) {
          delete files[file];
        }
      }
    }

    done(null);
  }
}

/**
 * MultiModule plugin.
 * In multi module mode, multi module project is generated
 *
 * @param {Object} multi module map
 * @param {Boolean} isMultiModule
 */

function multiModule(multiModuleMap, isMultiModule) {
  return function(files, metalsmith, done) {
    if (!isMultiModule) return done(null);

    const defaultModule = metalsmith.metadata().defaultModule;

    // Add multi module project structure
    for (const file in files) {
      for (let i = 0; i < multiModuleMap.length; i++) {
        const tmpPrefixPath = multiModuleMap[i][0];
        const targetPrefixPath = multiModuleMap[i][1].replace(/(\[defaultModule\])/g, defaultModule);
        const reg = new RegExp('^' + tmpPrefixPath);
        if (!reg.test(file)) continue;
        const finalPath = file.replace(reg, targetPrefixPath);
        files[finalPath] = files[file];
      }
    }

    // Clear single module project structure
    for (const file in files) {
      for (let j = 0; j < multiModuleMap.length; j++) {
        const tmpPrefixPath = multiModuleMap[j][0];
        if (!new RegExp('^' + tmpPrefixPath).test(file)) continue;
        delete files[file];
      }
    }
    done();
  };
}

function insertCliInfoToPackage({name: projectName, templateName, cacheTemplatePath, clone, isMultiModule}) {
  return function(files, metalsmith, done) {
    if (!files['package.json']) return done();
    const metadata = metalsmith.metadata();
    const defaultModule = metadata.defaultModule;
    const str = files['package.json'].contents.toString();
    const json = JSON.parse(str);

    json.thinkjs = Object.assign(json.thinkjs || {}, {
      metadata,
      projectName,
      templateName,
      cacheTemplatePath,
      defaultModule,
      clone,
      isMultiModule
    });

    files['package.json'].contents = Buffer.from(JSON.stringify(json, null, '  '), 'binary');
    done();
  };
}
<commit_msg>lint-fix<commit_after>const Metalsmith = require('metalsmith');
const inquirer = require('inquirer');
const Handlebars = require('handlebars');
const renderRaw = require('consolidate').handlebars.render;
const path = require('path');
const multimatch = require('multimatch');
const getOptions = require('./options.js');
const logger = require('../logger.js');
const utils = require('../utils');
const render = utils.promisify(renderRaw, renderRaw);

Handlebars.registerHelper('author', function(res) {
  return new Handlebars.SafeString(res.data.root.author);
});

module.exports = function({name, templateName, cacheTemplatePath, targetPath, clone, isMultiModule}, done) {
  const src = cacheTemplatePath || templateName;
  const metadata = getOptions(name, src);

  Metalsmith(path.join(src, 'template'))
    .source('.')
    .destination(targetPath)
    .use(ask(metadata.prompts, isMultiModule))
    .use(filesignore(metadata.filesignore))
    .use(template(metadata.skipCompile))
    .use(multiModule(metadata.multiModule, isMultiModule))
    .use(insertCliInfoToPackage({name, templateName, cacheTemplatePath, clone, isMultiModule}))
    .build(done);
};

/**
 * Prompt plugin.
 *
 * @param {Object} prompts
 */

function ask(prompts, isMultiModule) {
  prompts = Object.keys(prompts).map(key => Object.assign({name: key}, prompts[key]));
  if (isMultiModule) {
    prompts.push({
      name: 'defaultModule',
      type: 'string',
      message: 'Please enter a default module name',
      default: 'home'
    });
  }

  return function(files, metalsmith, done) {
    const metadata = metalsmith.metadata();
    inquirer.prompt(prompts).then((answers) => {
      for (var key in answers) {
        metadata[key] = answers[key];
      }
      done();
    });
  };
}

/**
 * Template in place plugin.
 *
 * @param {Object} skip compile
 */

function template(skipCompile) {
  skipCompile = typeof skipCompile === 'string'
    ? [skipCompile]
    : skipCompile;

  return function(files, metalsmith, done) {
    const metadata = metalsmith.metadata();

    const promises = Object.keys(files).map(file => {
      if (skipCompile && multimatch([file], skipCompile, { dot: true }).length) {
        return Promise.resolve();
      }

      const str = files[file].contents.toString();
      return render(str, metadata)
        .then(res => {
          files[file].contents = Buffer.from(res);
        })
        .catch(_ => {
          logger.error('"%s" file render failed. Please add the file to the skipCompile key in the metadata.js', file);
        });
    });

    Promise.all(promises).then(() => {
      done(null);
    }, done);
  };
}

function filesignore(filesIgnore) {
  return function(files, metalsmith, done) {
    const reg = /(\.tpl\.js)$/;
    for (const file in files) {
      if (reg.test(file)) {
        delete files[file];
        continue;
      }

      for (let i = 0; i < filesIgnore.length; i++) {
        const regPrefixPath = new RegExp('^' + filesIgnore[i]);
        if (regPrefixPath.test(file)) {
          delete files[file];
        }
      }
    }

    done(null);
  };
}

/**
 * MultiModule plugin.
 * In multi module mode, multi module project is generated
 *
 * @param {Object} multi module map
 * @param {Boolean} isMultiModule
 */

function multiModule(multiModuleMap, isMultiModule) {
  return function(files, metalsmith, done) {
    if (!isMultiModule) return done(null);

    const defaultModule = metalsmith.metadata().defaultModule;

    // Add multi module project structure
    for (const file in files) {
      for (let i = 0; i < multiModuleMap.length; i++) {
        const tmpPrefixPath = multiModuleMap[i][0];
        const targetPrefixPath = multiModuleMap[i][1].replace(/(\[defaultModule\])/g, defaultModule);
        const reg = new RegExp('^' + tmpPrefixPath);
        if (!reg.test(file)) continue;
        const finalPath = file.replace(reg, targetPrefixPath);
        files[finalPath] = files[file];
      }
    }

    // Clear single module project structure
    for (const file in files) {
      for (let j = 0; j < multiModuleMap.length; j++) {
        const tmpPrefixPath = multiModuleMap[j][0];
        if (!new RegExp('^' + tmpPrefixPath).test(file)) continue;
        delete files[file];
      }
    }
    done();
  };
}

function insertCliInfoToPackage({name: projectName, templateName, cacheTemplatePath, clone, isMultiModule}) {
  return function(files, metalsmith, done) {
    if (!files['package.json']) return done();
    const metadata = metalsmith.metadata();
    const defaultModule = metadata.defaultModule;
    const str = files['package.json'].contents.toString();
    const json = JSON.parse(str);

    json.thinkjs = Object.assign(json.thinkjs || {}, {
      metadata,
      projectName,
      templateName,
      cacheTemplatePath,
      defaultModule,
      clone,
      isMultiModule
    });

    files['package.json'].contents = Buffer.from(JSON.stringify(json, null, '  '), 'binary');
    done();
  };
}
<|endoftext|>


<commit_before>const request = require('superagent')

export default class Authentication {
  constructor (opts = {}) {
    let { authUrl, clientId, storage, loginUrl, logoutUrl, userFetchUrl, accessCheckUrl } = opts

    this.authUrl = authUrl || 'https://moauth.fagbokforlaget.no'
    this.currentUser = undefined
    this.token = undefined
    this.clientId = clientId
    this.storage = storage || window.localStorage
    this.loginUrl = loginUrl || this.authUrl + '/_auth/login'
    this.logoutUrl = logoutUrl
    this.userFetchUrl = userFetchUrl || this.authUrl + '/_auth/user?token='
    this.accessCheckUrl = accessCheckUrl || this.authUrl + '/_auth/access'
  }

  _loginUrl (redirectUrl, scope = undefined) {
    if (!this.loginUrl.includes('?')) {
      this.loginUrl += '?'
    }
    return this.loginUrl + '&client_id=' + (this.clientId || 'generic') + '&redirect_url=' + encodeURIComponent(redirectUrl) + '&scope=' + (scope || 'dbok')
  }

  _parseQueryString (loc) {
    const pl = /\+/g
    const search = /([^&=]+)=?([^&]*)/g
    const decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')) }
    let urlParams = {}
    let query = loc.substring(1)

    if (/\?/.test(query)) {
      query = query.split('?')[1]
    }

    while (1) {
      const match = search.exec(query)

      if (!match) {
        break
      }
      urlParams[decode(match[1])] = decode(match[2])
    }

    return urlParams
  }

  authorize (obj = {}) {
    let { redirectUrl, scope } = obj

    window.location = this._loginUrl(redirectUrl || window.location, scope)
  }

  getUser () {
    let storeUser = this.storage.getItem('user')

    if (this.currentUser) {
      return this.currentUser
    }
    if (storeUser) {
      return JSON.parse(storeUser)
    }
    return undefined
  }

  checkToken (loc = window.location.search) {
    let params = this._parseQueryString(loc)
    let self = this

    self.token = params.token || params.access_token || this.storage.getItem('token') || undefined

    return new Promise((resolve, reject) => {
      if (self.isAuthenticated() && self.token && typeof self.token !== 'undefined') {
        resolve(self.getUser())
      }

      if (self.token && typeof self.token !== 'undefined') {
        if (window) {
          window.history.replaceState({}, '', window.location.pathname + window.location.hash || '')
        }
        self.storage.setItem('token', self.token)
        self.fetchUser(this.userFetchUrl + encodeURIComponent(self.token))
          .then((user) => {
            resolve(user)
          })
          .catch((err) => {
            reject(err)
          })
      } else {
        reject(new Error('access token not found'))
      }
    })
  }

  checkAccess (productIds, loc = window.location.search) {
    const params = this._parseQueryString(loc)
    const token = params.token || params.access_token || this.storage.getItem('token') || undefined
    const products = Array.isArray(productIds) ? productIds : [productIds]

    return new Promise(async (resolve, reject) => {
      if (token && typeof token !== 'undefined') {
        let access

        try {
          access = await this.fetchAccess(this.accessCheckUrl, { token: token, productIds: products })
        } catch (err) {
          reject(err)
        }
        try {
          const user = await this.checkToken(loc)
          user.access = access.products || []
          resolve(user)
        } catch (err) {
          reject(err)
        }
      } else {
        reject(new Error('access token not found'))
      }
    })
  }

  fetchUser (url) {
    let self = this

    return new Promise((resolve, reject) => {
      request
        .get(url)
        .then(response => {
          if (response.statusCode === 200 && response.body) {
            let resp = response.body
            let user = resp.user || resp.objects[0]

            self.storage.setItem('user', JSON.stringify(user))
            resolve(user)
          } else {
            reject(new Error('authentication failed: Invalid response'))
          }
        })
        .catch(err => {
          reject(err)
        })
    })
  }

  fetchAccess (url, body) {
    return new Promise((resolve, reject) => {
      request
        .post(url)
        .send(body)
        .set('Accept', 'application/json')
        .then(response => {
          if (response.statusCode === 200 && response.body) {
            if (response.body.success) {
              resolve(response.body.product)
            } else {
              reject(new Error('This user does not have access to this product'))
            }
          }
        })
        .catch(error => {
          reject(error)
        })
    })
  }

  async logout (url) {
    this.storage.removeItem('user')
    this.storage.removeItem('token')

    url = url || this.logoutUrl

    if (url) window.location = url
  }

  isAuthenticated () {
    let user = this.getUser()

    if (user) {
      return true
    }
    return false
  }
}
<commit_msg>fix: access default value<commit_after>const request = require('superagent')

export default class Authentication {
  constructor (opts = {}) {
    let { authUrl, clientId, storage, loginUrl, logoutUrl, userFetchUrl, accessCheckUrl } = opts

    this.authUrl = authUrl || 'https://moauth.fagbokforlaget.no'
    this.currentUser = undefined
    this.token = undefined
    this.clientId = clientId
    this.storage = storage || window.localStorage
    this.loginUrl = loginUrl || this.authUrl + '/_auth/login'
    this.logoutUrl = logoutUrl
    this.userFetchUrl = userFetchUrl || this.authUrl + '/_auth/user?token='
    this.accessCheckUrl = accessCheckUrl || this.authUrl + '/_auth/access'
  }

  _loginUrl (redirectUrl, scope = undefined) {
    if (!this.loginUrl.includes('?')) {
      this.loginUrl += '?'
    }
    return this.loginUrl + '&client_id=' + (this.clientId || 'generic') + '&redirect_url=' + encodeURIComponent(redirectUrl) + '&scope=' + (scope || 'dbok')
  }

  _parseQueryString (loc) {
    const pl = /\+/g
    const search = /([^&=]+)=?([^&]*)/g
    const decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')) }
    let urlParams = {}
    let query = loc.substring(1)

    if (/\?/.test(query)) {
      query = query.split('?')[1]
    }

    while (1) {
      const match = search.exec(query)

      if (!match) {
        break
      }
      urlParams[decode(match[1])] = decode(match[2])
    }

    return urlParams
  }

  authorize (obj = {}) {
    let { redirectUrl, scope } = obj

    window.location = this._loginUrl(redirectUrl || window.location, scope)
  }

  getUser () {
    let storeUser = this.storage.getItem('user')

    if (this.currentUser) {
      return this.currentUser
    }
    if (storeUser) {
      return JSON.parse(storeUser)
    }
    return undefined
  }

  checkToken (loc = window.location.search) {
    let params = this._parseQueryString(loc)
    let self = this

    self.token = params.token || params.access_token || this.storage.getItem('token') || undefined

    return new Promise((resolve, reject) => {
      if (self.isAuthenticated() && self.token && typeof self.token !== 'undefined') {
        resolve(self.getUser())
      }

      if (self.token && typeof self.token !== 'undefined') {
        if (window) {
          window.history.replaceState({}, '', window.location.pathname + window.location.hash || '')
        }
        self.storage.setItem('token', self.token)
        self.fetchUser(this.userFetchUrl + encodeURIComponent(self.token))
          .then((user) => {
            resolve(user)
          })
          .catch((err) => {
            reject(err)
          })
      } else {
        reject(new Error('access token not found'))
      }
    })
  }

  checkAccess (productIds, loc = window.location.search) {
    const params = this._parseQueryString(loc)
    const token = params.token || params.access_token || this.storage.getItem('token') || undefined
    const products = Array.isArray(productIds) ? productIds : [productIds]
    let access = {products: []}

    return new Promise(async (resolve, reject) => {
      if (token && typeof token !== 'undefined') {

        try {
          access = await this.fetchAccess(this.accessCheckUrl, { token: token, productIds: products })
        } catch (err) {
          reject(err)
        }
        try {
          const user = await this.checkToken(loc)
          user.access = access.products
          resolve(user)
        } catch (err) {
          reject(err)
        }
      } else {
        reject(new Error('access token not found'))
      }
    })
  }

  fetchUser (url) {
    let self = this

    return new Promise((resolve, reject) => {
      request
        .get(url)
        .then(response => {
          if (response.statusCode === 200 && response.body) {
            let resp = response.body
            let user = resp.user || resp.objects[0]

            self.storage.setItem('user', JSON.stringify(user))
            resolve(user)
          } else {
            reject(new Error('authentication failed: Invalid response'))
          }
        })
        .catch(err => {
          reject(err)
        })
    })
  }

  fetchAccess (url, body) {
    return new Promise((resolve, reject) => {
      request
        .post(url)
        .send(body)
        .set('Accept', 'application/json')
        .then(response => {
          if (response.statusCode === 200 && response.body) {
            if (response.body.success) {
              resolve(response.body.product)
            } else {
              reject(new Error('This user does not have access to this product'))
            }
          }
        })
        .catch(error => {
          reject(error)
        })
    })
  }

  async logout (url) {
    this.storage.removeItem('user')
    this.storage.removeItem('token')

    url = url || this.logoutUrl

    if (url) window.location = url
  }

  isAuthenticated () {
    let user = this.getUser()

    if (user) {
      return true
    }
    return false
  }
}
<|endoftext|>