<commit_before>/**
 * @file BaseTable component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import ColGroup from '../_ColGroup';
import Thead from '../_Thead';
import Tbody from '../_Tbody';
import Tfoot from '../_Tfoot';

import TableFragment from '../_statics/TableFragment';
import HorizontalAlign from '../_statics/HorizontalAlign';
import SelectMode from '../_statics/SelectMode';
import SelectAllMode from '../_statics/SelectAllMode';
import SortingType from '../_statics/SortingType';

import Util from '../_vendors/Util';
import TableCalculation from '../_vendors/TableCalculation';

class BaseTable extends Component {

    static Fragment = TableFragment;
    static Align = HorizontalAlign;
    static SelectMode = SelectMode;
    static SelectAllMode = SelectAllMode;
    static SortingType = SortingType;

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    render() {

        const {
            className, style, fixed, fragment, columns, headColumns, bodyColumns, isHeadHidden, isFootHidden,
            ...restProps
        } = this.props;

        return (

                <ColGroup columns={TableCalculation.getColumnsWithSpan(fragment, bodyColumns || columns)}/>

                {
                    !isHeadHidden && (fragment === TableFragment.HEAD || (!fixed && !fragment)) ?

                        :
                        null
                }

                {
                    !fragment ?

                        :
                        null
                }

                {/** render foot if a footRenderer exists in columns */}
                {
                    !isFootHidden && (fragment === TableFragment.FOOT || (!fixed && !fragment)) ?

                        :
                        null
                }

        );

    }
}

BaseTable.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The select mode of table.
     */
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    /**
     * The select all mode of table, all or current page.
     */
    selectAllMode: PropTypes.oneOf(Util.enumerateValue(SelectAllMode)),

    fixed: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(Util.enumerateValue(HorizontalAlign))]),

    fragment: PropTypes.oneOf(Util.enumerateValue(TableFragment)),

    /**
     * Children passed into table header.
     */
    columns: PropTypes.arrayOf(PropTypes.shape({

        /**
         * width of column
         */
        width: PropTypes.number,

        /**
         * minimum width of column
         */
        minWidth: PropTypes.number,

        /**
         * The class name of header.
         */
        headClassName: PropTypes.string,

        /**
         * Override the styles of header.
         */
        headStyle: PropTypes.object,

        /**
         * The render content in header.
         * (1) string，example： 'id'
         * (2) callback，example：function (colIndex) {return colIndex;}
         */
        headRenderer: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

        /**
         * align of table header cell
         */
        headAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * column span of table header
         */
        headSpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * The class name of td.
         */
        bodyClassName: PropTypes.string,

        /**
         * Override the styles of td.
         */
        bodyStyle: PropTypes.object,

        /**
         * The render content in table.
         * (1) data key，example： 'id'
         * (2) data key tamplate，example：'${id} - ${name}'
         * (3) callback，example：function (rowData, rowIndex, colIndex) {return rowData.id;}
         */
        bodyRenderer: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

        /**
         * align of table body cell
         */
        bodyAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * column span of table body
         */
        bodySpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * The class name of footer.
         */
        footClassName: PropTypes.string,

        /**
         * Override the styles of footer.
         */
        footStyle: PropTypes.object,

        /**
         * The render content in footer.
         * (1) string，example： 'id'
         * (2) callback，example：function (colIndex) {return colIndex;}
         */
        footRenderer: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

        /**
         * align of table footer cell
         */
        footAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * column span of table foot
         */
        footSpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * If true,this column can be sorted.
         */
        sortable: PropTypes.bool,

        /**
         * Sorting property.
         */
        sortingProp: PropTypes.string,

        defaultSortingType: PropTypes.oneOf(Util.enumerateValue(SortingType))

    })),
    headColumns: PropTypes.array,
    bodyColumns: PropTypes.array,

    data: PropTypes.array,
    baseColIndex: PropTypes.number,
    disabled: PropTypes.bool,
    expandRows: PropTypes.array,

    /**
     * hidden
     */
    isHeadHidden: PropTypes.bool,
    isFootHidden: PropTypes.bool,

    /**
     * sorting
     */
    sorting: PropTypes.shape({
        prop: PropTypes.string,
        type: PropTypes.oneOf(Util.enumerateValue(SortingType))
    }),
    defaultSortingType: PropTypes.oneOf(Util.enumerateValue(SortingType)),
    sortingAscIconCls: PropTypes.string,
    sortingDescIconCls: PropTypes.string,
    sortingFunc: PropTypes.func,

    /**
     * callback
     */
    onSortChange: PropTypes.func,
    onHeadClick: PropTypes.func,
    onRowClick: PropTypes.func,
    onCellClick: PropTypes.func,
    onFootClick: PropTypes.func,
    onExpandChange: PropTypes.func

};

BaseTable.defaultProps = {

    baseColIndex: 0,
    disabled: false,
    expandRows: [],

    isHeadHidden: false,
    isFootHidden: false,

    defaultSortingType: SortingType.ASC,
    sortingAscIconCls: 'fas fa-sort-up',
    sortingDescIconCls: 'fas fa-sort-down'

};

export default BaseTable;
<commit_msg>update BaseTable<commit_after>/**
 * @file BaseTable component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import ColGroup from '../_ColGroup';
import Thead from '../_Thead';
import Tbody from '../_Tbody';
import Tfoot from '../_Tfoot';

import TableFragment from '../_statics/TableFragment';
import HorizontalAlign from '../_statics/HorizontalAlign';
import SelectMode from '../_statics/SelectMode';
import SelectAllMode from '../_statics/SelectAllMode';
import SortingType from '../_statics/SortingType';

import Util from '../_vendors/Util';
import TableCalculation from '../_vendors/TableCalculation';

class BaseTable extends Component {

    static Fragment = TableFragment;
    static Align = HorizontalAlign;
    static SelectMode = SelectMode;
    static SelectAllMode = SelectAllMode;
    static SortingType = SortingType;

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    render() {

        const {
            className, style, fixed, fragment, columns, headColumns, bodyColumns, isHeadHidden, isFootHidden,
            ...restProps
        } = this.props;

        return (

                <ColGroup columns={TableCalculation.getColumnsWithSpan(fragment, bodyColumns || columns)}/>

                {
                    !isHeadHidden && (fragment === TableFragment.HEAD || (!fixed && !fragment)) ?

                        :
                        null
                }

                {
                    !fragment ?

                        :
                        null
                }

                {/** render foot if a footRenderer exists in columns */}
                {
                    !isFootHidden && (fragment === TableFragment.FOOT || (!fixed && !fragment)) ?

                        :
                        null
                }

        );

    }
}

BaseTable.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The select mode of table.
     */
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    /**
     * The select all mode of table, all or current page.
     */
    selectAllMode: PropTypes.oneOf(Util.enumerateValue(SelectAllMode)),

    fixed: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(Util.enumerateValue(HorizontalAlign))]),

    fragment: PropTypes.oneOf(Util.enumerateValue(TableFragment)),

    /**
     * Children passed into table header.
     */
    columns: PropTypes.arrayOf(PropTypes.shape({

        /**
         * fixed position of column ( true / 'left' / 'right' )
         */
        fixed: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(Util.enumerateValue(HorizontalAlign))]),

        /**
         * width of column
         */
        width: PropTypes.number,

        /**
         * minimum width of column
         */
        minWidth: PropTypes.number,

        /**
         * align of current column
         */
        align: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * The class name of header.
         */
        headClassName: PropTypes.string,

        /**
         * Override the styles of header.
         */
        headStyle: PropTypes.object,

        /**
         * align of table header cell
         */
        headAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * The render content in table head.
         *  (1) callback:
         *      function (tableData, colIndex) {
         *          return colIndex;
         *      }
         *
         *  (2) others:
         *      render whatever you pass
         */
        headRenderer: PropTypes.any,

        /**
         * column span of table header
         */
        headSpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * The class name of td.
         */
        bodyClassName: PropTypes.string,

        /**
         * Override the styles of td.
         */
        bodyStyle: PropTypes.object,

        /**
         * align of table body cell
         */
        bodyAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * The render content in table body.
         *  (1) callback:
         *      function (rowData, rowIndex, colIndex) {
         *          return rowData.id;
         *      }
         *
         *  (2) others:
         *      render whatever you pass
         */
        bodyRenderer: PropTypes.any,

        /**
         * column span of table body
         */
        bodySpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * The class name of footer.
         */
        footClassName: PropTypes.string,

        /**
         * Override the styles of footer.
         */
        footStyle: PropTypes.object,

        /**
         * align of table footer cell
         */
        footAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * The render content in table foot.
         *  (1) callback:
         *      function (tableData, colIndex) {
         *          return colIndex;
         *      }
         *
         *  (2) others:
         *      render whatever you pass
         */
        footRenderer: PropTypes.any,

        /**
         * column span of table foot
         */
        footSpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * If true,this column can be sorted.
         */
        sortable: PropTypes.bool,

        /**
         * Sorting property.
         */
        sortingProp: PropTypes.string,

        defaultSortingType: PropTypes.oneOf(Util.enumerateValue(SortingType))

    })),
    headColumns: PropTypes.array,
    bodyColumns: PropTypes.array,

    data: PropTypes.array,
    baseColIndex: PropTypes.number,
    disabled: PropTypes.bool,
    expandRows: PropTypes.array,

    /**
     * hidden
     */
    isHeadHidden: PropTypes.bool,
    isFootHidden: PropTypes.bool,

    /**
     * sorting
     */
    sorting: PropTypes.shape({
        prop: PropTypes.string,
        type: PropTypes.oneOf(Util.enumerateValue(SortingType))
    }),
    defaultSortingType: PropTypes.oneOf(Util.enumerateValue(SortingType)),
    sortingAscIconCls: PropTypes.string,
    sortingDescIconCls: PropTypes.string,
    sortingFunc: PropTypes.func,

    /**
     * callback
     */
    onSortChange: PropTypes.func,
    onHeadClick: PropTypes.func,
    onRowClick: PropTypes.func,
    onCellClick: PropTypes.func,
    onFootClick: PropTypes.func,
    onExpandChange: PropTypes.func

};

BaseTable.defaultProps = {

    baseColIndex: 0,
    disabled: false,
    expandRows: [],

    isHeadHidden: false,
    isFootHidden: false,

    defaultSortingType: SortingType.ASC,
    sortingAscIconCls: 'fas fa-sort-up',
    sortingDescIconCls: 'fas fa-sort-down'

};

export default BaseTable;
<|endoftext|>


<commit_before>import fromMask from './creator/fromMask';
import fromMask2 from './creator/fromMask2';
import fromExtrema from './creator/fromExtrema';
import fromWaterShed from './creator/fromWaterShed';
import fromCoordinates from './creator/fromPixels';
import createROI from './createROI';
import extendObject from 'extend';
import Image from '../image';
import ROIMap from './ROIMap';

/**
 * A manager of Regions of Interest. A ROIManager is related to a specific Image
 * and may contain multiple layers. Each layer is characterized by a label whose is
 * name by default 'default'
 * @class ROIManager
 * @param {Image} image
 * @param {object} [options]
 */
export default class ROIManager {
    constructor(image, options = {}) {
        this._image = image;
        this._options = options;
        if (!this._options.label) this._options.label = 'default';
        this._layers = {};
        this._painted = null;
    }

    generateROIFromExtrema(options = {}) {
        let opt = extendObject({}, this._options, options);
        let roiMap = fromExtrema.call(this._image, options);
        this._layers[opt.label] = new ROILayer(roiMap, opt);
    }

    /**
     * @param {[[number]]} pixels - an array of pixels
     * @param {object} options
     */
    putPixels(pixels, options = {}) {
        let opt = extendObject({}, this._options, options);
        let roiMap = fromCoordinates.call(this._image, pixels, options);
        this._layers[opt.label] = new ROILayer(roiMap, opt);
        return this;
    }

    /**
     * @param {number[]} roiMap
     * @param options
     */
    putMap(roiMap, options = {}) {
        let map = new ROIMap(this._image, roiMap);
        let opt = extendObject({}, this._options, options);
        this._layers[opt.label] = new ROILayer(map, opt);
        return this;
    }

    generateROIFromWaterShed(options = {}) {
        let opt = extendObject({}, this._options, options);
        let roiMap = fromWaterShed.call(this._image, options);
        this._layers[opt.label] = new ROILayer(roiMap, opt);
    }

    putMask(mask, options = {}) {
        let opt = extendObject({}, this._options, options);
        let roiMap = fromMask.call(this._image, mask, options);
        this._layers[opt.label] = new ROILayer(roiMap, opt);
        return this;
    }

    putMask2(mask, options = {}) {
        let opt = extendObject({}, this._options, options);
        let roiMap = fromMask2.call(this._image, mask, options);
        this._layers[opt.label] = new ROILayer(roiMap, opt);
        return this;
    }

    getMap(options = {}) {
        let opt = extendObject({}, this._options, options);
        if (!this._layers[opt.label]) return;
        return this._layers[opt.label].roiMap;
    }

    getROIIDs(options = {}) {
        let rois = this.getROI(options);
        if (!rois) return;
        let ids = new Array(rois.length);
        for (let i = 0; i < rois.length; i++) {
            ids[i] = rois[i].id;
        }
        return ids;
    }

    getROI({
        label = this._options.label,
        positive = true,
        negative = true,
        minSurface = 0,
        maxSurface = Number.POSITIVE_INFINITY
        } = {}) {

        let allROIs = this._layers[label].roi;

        let rois = new Array(allROIs.length);
        let ptr = 0;
        for (let i = 0; i < allROIs.length; i++) {
            let roi = allROIs[i];
            if (((roi.id < 0 && negative) || roi.id > 0 && positive)
                && roi.surface >= minSurface
                && roi.surface <= maxSurface) {
                rois[ptr++] = roi;
            }
        }
        rois.length = ptr;
        return rois;
    }

    getROIMasks(options = {}) {
        let rois = this.getROI(options);

        let masks = new Array(rois.length);
        for (let i = 0; i < rois.length; i++) {
            masks[i] = rois[i].mask;
        }
        return masks;
    }

    getPixels(options = {}) {
        let opt = extendObject({}, this._options, options);
        if (this._layers[opt.label]) {
            return this._layers[opt.label].roiMap.pixels;
        }
    }

    /**
     * Paint the ROI on a copy of the image adn return this image.
     * @param options
     * @param color {array} [$1.color=[max,0,0]] - Array of 3 elements (R, G, B), default is red.
     * @param alpha Value from 0 to 255 to specify the alpha. Will be used if it is unspecified
     * @param colors {array} Array of Array of 3 elements (R, G, B) for each color of each mask.
     * @param randomColors If we we would like to paint each mask with a random color
     * @param distinctColors If we we would like to paint each mask with a different color (default: false);
     * @param showLabels Paint the masks ID on the image (default: false). Requires a RGBA image !
     * @param labelColor Define the color to paint the labels (default : 'blue')
     *  id: true / false
     *  color
     * @returns {*|null}
     */

    paint(options = {}) {
        let showLabels = options.showLabels;
        let labelColor = options.labelColor || 'blue';

        if (!this._painted) this._painted = this._image.rgba8();
        let masks = this.getROIMasks(options);
        this._painted.paintMasks(masks, options);

        if (showLabels) {
            let canvas = this._painted.getCanvas({originalData: true});
            let ctx = canvas.getContext('2d');
            ctx.fillStyle = labelColor;
            let rois = this.getROI();
            for (let i = 0; i < rois.length; i++) {
                ctx.fillText(rois[i].id, rois[i].meanX - 3, rois[i].meanY + 3);
            }
            this._painted.data = ctx.getImageData(0, 0, this._painted.width, this._painted.height).data;
        }
        return this._painted;
    }

    // return a mask corresponding to all the selected masks
    getMask(options = {}) {
        let mask = new Image(this._image.width, this._image.height, {kind:'BINARY'});
        let masks = this.getROIMasks(options);

        for (let i = 0; i < masks.length; i++) {
            let roi = masks[i];
            // we need to find the parent image to calculate the relative position

            for (let x = 0; x < roi.width; x++) {
                for (let y = 0; y < roi.height; y++) {
                    if (roi.getBitXY(x, y)) {
                        mask.setBitXY(x + roi.position[0], y + roi.position[1]);
                    }
                }
            }
        }
        return mask;
    }

    resetPainted(image) {
        this._painted = image;
    }

    /**
     *  Return a new roiMAP changed with the fusion of certain ROIs.
     * @param rois is an array of ROIs which shares the same roiMAP.
     * @param algorithm ; algorithm used to decide which ROIs are merged.
     * @param value is an integer, determine the strength of the merging.
     * @returns {*}
     */

    mergeROI(options = {}) {
        let opt = extendObject({}, this._options, options);
        let algorithm = opt.algorithm || 'commonBorder';
        let minCommonBorderLength = opt.minCommonBorderLength || 5;
        let rois = this.getROI(opt);
        let toMerge = new Set();
        switch (algorithm.toLowerCase()) {
            //Algorithms. We can add more algorithm to create other types of merging.
            case 'commonborder' :
                for (let i = 0; i < rois.length; i++) {
                    for (let k = 0; k < rois[i].borderIDs.length; k++) {
                        //If the length of wall of the current region and his neighbour is big enough, we join the rois.
                        if (rois[i].borderIDs[k] !== 0 && rois[i].borderIDs[k] < rois[i].id && rois[i].borderLengths[k] * minCommonBorderLength >= rois[i].external) {
                            toMerge.add([rois[i].id, rois[i].borderIDs[k]]);
                        }
                    }
                }
                break;
        }

        //Now we can modify the roiMap by merging each region determined before
        let pixels = this.getMap(opt).pixels;
        for (let index = 0; index < pixels.length; index++) {
            if (pixels[index] !== 0) {
                for (let array of toMerge) {
                    if (pixels[index] === array[0]) {
                        pixels[index] = array[1];
                    }
                }
            }
        }
        this.putMap(pixels, opt);
    }
}

class ROILayer {
    constructor(roiMap, options) {
        this.roiMap = roiMap;
        this.options = options;
        this.roi = createROI(this.roiMap);
    }
}
<commit_msg>Correct paintMasks<commit_after>import fromMask from './creator/fromMask';
import fromMask2 from './creator/fromMask2';
import fromExtrema from './creator/fromExtrema';
import fromWaterShed from './creator/fromWaterShed';
import fromCoordinates from './creator/fromPixels';
import createROI from './createROI';
import extendObject from 'extend';
import Image from '../image';
import ROIMap from './ROIMap';

/**
 * A manager of Regions of Interest. A ROIManager is related to a specific Image
 * and may contain multiple layers. Each layer is characterized by a label whose is
 * name by default 'default'
 * @class ROIManager
 * @param {Image} image
 * @param {object} [options]
 */
export default class ROIManager {
    constructor(image, options = {}) {
        this._image = image;
        this._options = options;
        if (!this._options.label) this._options.label = 'default';
        this._layers = {};
        this._painted = null;
    }

    generateROIFromExtrema(options = {}) {
        let opt = extendObject({}, this._options, options);
        let roiMap = fromExtrema.call(this._image, options);
        this._layers[opt.label] = new ROILayer(roiMap, opt);
    }

    /**
     * @param {[[number]]} pixels - an array of pixels
     * @param {object} options
     */
    putPixels(pixels, options = {}) {
        let opt = extendObject({}, this._options, options);
        let roiMap = fromCoordinates.call(this._image, pixels, options);
        this._layers[opt.label] = new ROILayer(roiMap, opt);
        return this;
    }

    /**
     * @param {number[]} roiMap
     * @param options
     */
    putMap(roiMap, options = {}) {
        let map = new ROIMap(this._image, roiMap);
        let opt = extendObject({}, this._options, options);
        this._layers[opt.label] = new ROILayer(map, opt);
        return this;
    }

    generateROIFromWaterShed(options = {}) {
        let opt = extendObject({}, this._options, options);
        let roiMap = fromWaterShed.call(this._image, options);
        this._layers[opt.label] = new ROILayer(roiMap, opt);
    }

    putMask(mask, options = {}) {
        let opt = extendObject({}, this._options, options);
        let roiMap = fromMask.call(this._image, mask, options);
        this._layers[opt.label] = new ROILayer(roiMap, opt);
        return this;
    }

    putMask2(mask, options = {}) {
        let opt = extendObject({}, this._options, options);
        let roiMap = fromMask2.call(this._image, mask, options);
        this._layers[opt.label] = new ROILayer(roiMap, opt);
        return this;
    }

    getMap(options = {}) {
        let opt = extendObject({}, this._options, options);
        if (!this._layers[opt.label]) return;
        return this._layers[opt.label].roiMap;
    }

    getROIIDs(options = {}) {
        let rois = this.getROI(options);
        if (!rois) return;
        let ids = new Array(rois.length);
        for (let i = 0; i < rois.length; i++) {
            ids[i] = rois[i].id;
        }
        return ids;
    }

    getROI({
        label = this._options.label,
        positive = true,
        negative = true,
        minSurface = 0,
        maxSurface = Number.POSITIVE_INFINITY
        } = {}) {

        let allROIs = this._layers[label].roi;

        let rois = new Array(allROIs.length);
        let ptr = 0;
        for (let i = 0; i < allROIs.length; i++) {
            let roi = allROIs[i];
            if (((roi.id < 0 && negative) || roi.id > 0 && positive)
                && roi.surface >= minSurface
                && roi.surface <= maxSurface) {
                rois[ptr++] = roi;
            }
        }
        rois.length = ptr;
        return rois;
    }

    getROIMasks(options = {}) {
        let rois = this.getROI(options);

        let masks = new Array(rois.length);
        for (let i = 0; i < rois.length; i++) {
            masks[i] = rois[i].mask;
        }
        return masks;
    }

    getPixels(options = {}) {
        let opt = extendObject({}, this._options, options);
        if (this._layers[opt.label]) {
            return this._layers[opt.label].roiMap.pixels;
        }
    }

    /**
     * Paint the ROI on a copy of the image adn return this image.
     * @param options
     * @param color {array} [$1.color=[max,0,0]] - Array of 3 elements (R, G, B), default is red.
     * @param alpha Value from 0 to 255 to specify the alpha. Will be used if it is unspecified
     * @param colors {array} Array of Array of 3 elements (R, G, B) for each color of each mask.
     * @param randomColors If we we would like to paint each mask with a random color
     * @param distinctColors If we we would like to paint each mask with a different color (default: false);
     * @param showLabels Paint the masks ID on the image (default: false). Requires a RGBA image !
     * @param labelColor Define the color to paint the labels (default : 'blue')
     *  id: true / false
     *  color
     * @returns {*|null}
     */

    paint(options = {}) {
        let showLabels = options.showLabels;
        let labelColor = options.labelColor || 'blue';

        if (!this._painted) this._painted = this._image.rgba8();
        let masks = this.getROIMasks(options);
        this._painted.paintMasks(masks, options);

        if (showLabels) {
            let canvas = this._painted.getCanvas({originalData: true});
            let ctx = canvas.getContext('2d');
            ctx.fillStyle = labelColor;
            let rois = this.getROI(options);
            for (let i = 0; i < rois.length; i++) {
                ctx.fillText(rois[i].id, rois[i].meanX - 3, rois[i].meanY + 3);
            }
            this._painted.data = ctx.getImageData(0, 0, this._painted.width, this._painted.height).data;
        }
        return this._painted;
    }

    // return a mask corresponding to all the selected masks
    getMask(options = {}) {
        let mask = new Image(this._image.width, this._image.height, {kind:'BINARY'});
        let masks = this.getROIMasks(options);

        for (let i = 0; i < masks.length; i++) {
            let roi = masks[i];
            // we need to find the parent image to calculate the relative position

            for (let x = 0; x < roi.width; x++) {
                for (let y = 0; y < roi.height; y++) {
                    if (roi.getBitXY(x, y)) {
                        mask.setBitXY(x + roi.position[0], y + roi.position[1]);
                    }
                }
            }
        }
        return mask;
    }

    resetPainted(image) {
        this._painted = image;
    }

    /**
     *  Return a new roiMAP changed with the fusion of certain ROIs.
     * @param rois is an array of ROIs which shares the same roiMAP.
     * @param algorithm ; algorithm used to decide which ROIs are merged.
     * @param value is an integer, determine the strength of the merging.
     * @returns {*}
     */

    mergeROI(options = {}) {
        let opt = extendObject({}, this._options, options);
        let algorithm = opt.algorithm || 'commonBorder';
        let minCommonBorderLength = opt.minCommonBorderLength || 5;
        let rois = this.getROI(opt);
        let toMerge = new Set();
        switch (algorithm.toLowerCase()) {
            //Algorithms. We can add more algorithm to create other types of merging.
            case 'commonborder' :
                for (let i = 0; i < rois.length; i++) {
                    for (let k = 0; k < rois[i].borderIDs.length; k++) {
                        //If the length of wall of the current region and his neighbour is big enough, we join the rois.
                        if (rois[i].borderIDs[k] !== 0 && rois[i].borderIDs[k] < rois[i].id && rois[i].borderLengths[k] * minCommonBorderLength >= rois[i].external) {
                            toMerge.add([rois[i].id, rois[i].borderIDs[k]]);
                        }
                    }
                }
                break;
        }

        //Now we can modify the roiMap by merging each region determined before
        let pixels = this.getMap(opt).pixels;
        for (let index = 0; index < pixels.length; index++) {
            if (pixels[index] !== 0) {
                for (let array of toMerge) {
                    if (pixels[index] === array[0]) {
                        pixels[index] = array[1];
                    }
                }
            }
        }
        this.putMap(pixels, opt);
    }
}

class ROILayer {
    constructor(roiMap, options) {
        this.roiMap = roiMap;
        this.options = options;
        this.roi = createROI(this.roiMap);
    }
}
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * String plugin, loads a string using a syntax like:
 * var title = require( 'string!JOHN_TRAVOLTAGE/johnTravoltage.name' );
 *
 * The reasons we need our own string plugin:
 * So we can only include the (possibly) strings that are needed for a sim
 * So we can enumerate all of the used strings, for purposed of a translation utility
 * For uniformity with image and audio plugin
 *
 * TODO: exclude the plugin itself from build file.  RequireJS docs said there is an easy way to do so
 * TODO: Currently hard coded to use English.  Provide support for any language.
 * TODO: Should we build all strings into the final HTML file, or separate HTML file per language?
 * TODO: Provide fallbacks to higher languages for missing strings
 * TODO: Could lump strings together into one script block--perhaps it would be readable by the translation utility and the runtime.
 *       This could facilitate rewriting the html in place.
 * @author Sam Reid
 */
define( function() {

  //Keep track of the strings that are used during dependency resolution so they can be looked up at build time
  var buildMap = {};

  var locale = 'en';

  return {
    load: function( name, parentRequire, onload, config ) {

      var url = parentRequire.toUrl( name );
      console.log( 'found url: ' + url );
      var question = url.lastIndexOf( '?' );
      var key = question < 0 ? url.substring( url.lastIndexOf( '/' ) + 1 ) : url.substring( url.lastIndexOf( '/' ) + 1, question );

      var project = name.substring( 0, name.indexOf( '/' ) );

      var stringPath = project.toLowerCase().split( '_' ).join( '-' ) + '-strings_' + locale;

      if ( config.isBuild ) {
        buildMap[name] = url;
        onload( null );
      }
      else {
        //Load it through the module system
//        debugger;
        parentRequire( [project + '/../nls/' + stringPath], function( stringFile ) {
          console.log( 'loaded through module system: ' + stringFile );
          console.log( 'checking query parameter:', key );
          var queryParameterValue = window.phetcommon.getQueryParameter( key );
          if ( queryParameterValue ) {
            onload( queryParameterValue );
          }
          else {
            onload( stringFile[key] );
          }
        } );
      }
    },

    //write method based on RequireJS official text plugin by James Burke
    //https://github.com/jrburke/requirejs/blob/master/text.js
    write: function( pluginName, moduleName, write ) {
      if ( moduleName in buildMap ) {
        var filename = buildMap[moduleName];
        var project = moduleName.substring( 0, moduleName.indexOf( '/' ) );
        var stringPath = project.toLowerCase().split( '_' ).join( '-' ) + '-strings_' + locale;
        var file = filename.substring( 0, filename.lastIndexOf( '/' ) ) + '/../nls/' + stringPath + '.js';

        //Load the string file, and evaluate with eval().  TODO: should these be JSON?
        //TODO: This may be inefficient at build time, since the same file may be loaded many times (one per string at worst)
        //TODO: We could cache those file loads

        //Load the file from the file system
        //TODO: What if it is UTF-16 or something?
        var loadedFile = fs.readFileSync( file, 'utf8' );

        //TODO: if the file doesn't use define({}), this will not work
        loadedFile = loadedFile.substring( loadedFile.indexOf( 'define' ) + 'define'.length );
        var evaled = eval( loadedFile );
        var key = moduleName.substring( moduleName.lastIndexOf( '/' ) + 1 );
        var value = evaled[key];

        console.log( 'pluginName: ' + pluginName );
        console.log( 'moduleName: ' + moduleName );
        console.log( 'found filename: ' + filename );
        console.log( 'file', file );
        console.log( 'loaded file', loadedFile );
        console.log( 'evaled', evaled );

        console.log( key );
        console.log( value );

//        define("string!ENERGY_SKATE_PARK/tab.introduction", function(){ return window.phetcommon.getQueryParameter( key ) || "Intro";};});

        //TODO: Will this harm performance on startup?
        //TODO: Do we need to encodeURIComponent on the key here?  Or decode the value?
        //TODO: why is the string coming out lower case
        var expression = 'window.phetcommon.getQueryParameter( "' + key + '" ) || "' + value + '";';

        //Write code that will load the image and register with a global `phetImages` to make sure everything loaded, see SimLauncher.js
//        write( 'define("' + pluginName + '!' + moduleName + '", function(){ return "' + value + '";});\n' );
        write( 'define("' + pluginName + '!' + moduleName + '", function(){ return ' + expression + ';});\n' );

        //Enumerate all of the strings used by the sim, with no false positives
        //TODO: A better way to do this without globals?  Perhaps the export value of this function?
        //TODO: if we stick with globals, make sure the globalStrings array is clean (undefined) when we start and delete it when we are done with it.
        global.globalStrings = global.globalStrings || [];
        global.globalStrings.push( {key: moduleName, value: value} );
      }
    }
  };
} );<commit_msg>added docs<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * String plugin, loads a string using a syntax like:
 * var title = require( 'string!JOHN_TRAVOLTAGE/johnTravoltage.name' );
 *
 * The reasons we need our own string plugin:
 * So we can only include the (possibly) strings that are needed for a sim
 * So we can enumerate all of the used strings, for purposed of a translation utility
 * For uniformity with image and audio plugin
 *
 * TODO: exclude the plugin itself from build file.  RequireJS docs said there is an easy way to do so
 * TODO: Currently hard coded to use English.  Provide support for any language.
 * TODO: Should we build all strings into the final HTML file, or separate HTML file per language?
 * TODO: Provide fallbacks to higher languages for missing strings
 * TODO: Could lump strings together into one script block--perhaps it would be readable by the translation utility and the runtime.
 *       This could facilitate rewriting the html in place.
 * @author Sam Reid
 */
define( function() {

  //Keep track of the strings that are used during dependency resolution so they can be looked up at build time
  var buildMap = {};

  var locale = 'en';

  return {
    load: function( name, parentRequire, onload, config ) {

      var url = parentRequire.toUrl( name );
      console.log( 'found url: ' + url );
      var question = url.lastIndexOf( '?' );
      var key = question < 0 ? url.substring( url.lastIndexOf( '/' ) + 1 ) : url.substring( url.lastIndexOf( '/' ) + 1, question );

      var project = name.substring( 0, name.indexOf( '/' ) );

      var stringPath = project.toLowerCase().split( '_' ).join( '-' ) + '-strings_' + locale;

      if ( config.isBuild ) {
        buildMap[name] = url;
        onload( null );
      }
      else {
        //Load it through the module system
//        debugger;
        parentRequire( [project + '/../nls/' + stringPath], function( stringFile ) {
          console.log( 'loaded through module system: ' + stringFile );
          console.log( 'checking query parameter:', key );
          var queryParameterValue = window.phetcommon.getQueryParameter( key );
          if ( queryParameterValue ) {
            onload( queryParameterValue );
          }
          else {
            onload( stringFile[key] );
          }
        } );
      }
    },

    //write method based on RequireJS official text plugin by James Burke
    //https://github.com/jrburke/requirejs/blob/master/text.js
    write: function( pluginName, moduleName, write ) {
      if ( moduleName in buildMap ) {
        var filename = buildMap[moduleName];
        var project = moduleName.substring( 0, moduleName.indexOf( '/' ) );
        var stringPath = project.toLowerCase().split( '_' ).join( '-' ) + '-strings_' + locale;
        var file = filename.substring( 0, filename.lastIndexOf( '/' ) ) + '/../nls/' + stringPath + '.js';

        //Load the string file, and evaluate with eval().  TODO: should these be JSON?
        //TODO: This may be inefficient at build time, since the same file may be loaded many times (one per string at worst)
        //TODO: We could cache those file loads

        //Load the file from the file system
        //TODO: What if it is UTF-16 or something?
        var loadedFile = fs.readFileSync( file, 'utf8' );

        //TODO: if the file doesn't use define({}), this will not work
        loadedFile = loadedFile.substring( loadedFile.indexOf( 'define' ) + 'define'.length );

        //TODO: is it safe to just eval a loaded file?  What if there is some insecure code there?
        var evaled = eval( loadedFile );
        var key = moduleName.substring( moduleName.lastIndexOf( '/' ) + 1 );
        var value = evaled[key];

        console.log( 'pluginName: ' + pluginName );
        console.log( 'moduleName: ' + moduleName );
        console.log( 'found filename: ' + filename );
        console.log( 'file', file );
        console.log( 'loaded file', loadedFile );
        console.log( 'evaled', evaled );

        console.log( key );
        console.log( value );

//        define("string!ENERGY_SKATE_PARK/tab.introduction", function(){ return window.phetcommon.getQueryParameter( key ) || "Intro";};});

        //TODO: Will this harm performance on startup?
        //TODO: Do we need to encodeURIComponent on the key here?  Or decode the value?
        //TODO: why is the string coming out lower case
        var expression = 'window.phetcommon.getQueryParameter( "' + key + '" ) || "' + value + '";';

        //Write code that will load the image and register with a global `phetImages` to make sure everything loaded, see SimLauncher.js
//        write( 'define("' + pluginName + '!' + moduleName + '", function(){ return "' + value + '";});\n' );
        write( 'define("' + pluginName + '!' + moduleName + '", function(){ return ' + expression + ';});\n' );

        //Enumerate all of the strings used by the sim, with no false positives
        //TODO: A better way to do this without globals?  Perhaps the export value of this function?
        //TODO: if we stick with globals, make sure the globalStrings array is clean (undefined) when we start and delete it when we are done with it.
        global.globalStrings = global.globalStrings || [];
        global.globalStrings.push( {key: moduleName, value: value} );
      }
    }
  };
} );<|endoftext|>


<commit_before>/*globals EmberDev */
import EmberView from "ember-views/views/view";
import Container from 'container/container';
import run from "ember-metal/run_loop";
import jQuery from "ember-views/system/jquery";

var view, originalLookup;

var container = {
  lookupFactory: function() { }
};

function viewClass(options) {
  options.container = options.container || container;
  return EmberView.extend(options);
}

QUnit.module("Handlebars {{#view}} helper", {
  setup: function() {
    originalLookup = Ember.lookup;
  },

  teardown: function() {
    Ember.lookup = originalLookup;

    if (view) {
      run(view, 'destroy');
    }
  }
});

test("By default view:toplevel is used", function() {
  var DefaultView = viewClass({
    elementId: 'toplevel-view',
    template: Ember.Handlebars.compile('hello world')
  });

  function lookupFactory(fullName) {
    equal(fullName, 'view:toplevel');

    return DefaultView;
  }

  var container = {
    lookupFactory: lookupFactory
  };

  view = EmberView.extend({
    template: Ember.Handlebars.compile('{{view}}'),
    container: container
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#toplevel-view').text(), 'hello world');
});

test("By default, without a container, EmberView is used", function() {
  view = EmberView.extend({
    template: Ember.Handlebars.compile('{{view tagName="span"}}')
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  ok(jQuery('#qunit-fixture').html().toUpperCase().match(/<SPAN/), 'contains view with span');
});

test("View lookup - App.FuView (DEPRECATED)", function() {
  Ember.lookup = {
    App: {
      FuView: viewClass({
        elementId: "fu",
        template: Ember.Handlebars.compile("bro")
      })
    }
  };

  view = viewClass({
    template: Ember.Handlebars.compile("{{view App.FuView}}")
  }).create();

  expectDeprecation(function(){
    run(view, 'appendTo', '#qunit-fixture');
  }, /Resolved the view "App.FuView" on the global context/);

  equal(jQuery('#fu').text(), 'bro');
});

test("View lookup - 'fu'", function() {
  var FuView = viewClass({
    elementId: "fu",
    template: Ember.Handlebars.compile("bro")
  });

  function lookupFactory(fullName) {
    equal(fullName, 'view:fu');

    return FuView;
  }

  var container = {
    lookupFactory: lookupFactory
  };

  view = EmberView.extend({
    template: Ember.Handlebars.compile("{{view 'fu'}}"),
    container: container
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#fu').text(), 'bro');
});

test("View lookup - view.computed", function() {
  var FuView = viewClass({
    elementId: "fu",
    template: Ember.Handlebars.compile("bro")
  });

  function lookupFactory(fullName) {
    equal(fullName, 'view:fu');

    return FuView;
  }

  var container = {
    lookupFactory: lookupFactory
  };

  view = EmberView.extend({
    template: Ember.Handlebars.compile("{{view view.computed}}"),
    container: container,
    computed: 'fu'
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#fu').text(), 'bro');
});

test("id bindings downgrade to one-time property lookup", function() {
  view = EmberView.extend({
    template: Ember.Handlebars.compile("{{#view id=view.meshuggah}}{{view.parentView.meshuggah}}{{/view}}"),
    meshuggah: 'stengah'
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#stengah').text(), 'stengah', "id binding performed property lookup");
  run(view, 'set', 'meshuggah', 'omg');
  equal(jQuery('#stengah').text(), 'omg', "id didn't change");
});

test("mixing old and new styles of property binding fires a warning, treats value as if it were quoted", function() {
  if (EmberDev && EmberDev.runningProdBuild){
    ok(true, 'Logging does not occur in production builds');
    return;
  }

  expect(2);

  var oldWarn = Ember.warn;

  Ember.warn = function(msg) {
    equal(msg, "You're attempting to render a view by passing borfBinding=view.snork to a view helper, but this syntax is ambiguous. You should either surround view.snork in quotes or remove `Binding` from borfBinding.");
  };

  view = EmberView.extend({
    template: Ember.Handlebars.compile("{{#view borfBinding=view.snork}}
{{view.borf}}
{{/view}}"),
    snork: "nerd"
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#lol').text(), "nerd", "awkward mixed syntax treated like binding");

  Ember.warn = oldWarn;
});

test("allows you to pass attributes that will be assigned to the class instance, like class=\"foo\"", function() {
  expect(4);

  var container = new Container();
  container.register('view:toplevel', EmberView.extend());

  view = EmberView.extend({
    template: Ember.Handlebars.compile('{{view id="foo" tagName="h1" class="foo"}}{{#view id="bar" class="bar"}}Bar{{/view}}'),
    container: container
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  ok(jQuery('#foo').hasClass('foo'));
  ok(jQuery('#foo').is('h1'));
  ok(jQuery('#bar').hasClass('bar'));
  equal(jQuery('#bar').text(), 'Bar');
});

test("Should apply class without condition always", function() {
  view = EmberView.create({
    context: [],
    controller: Ember.Object.create(),
    template: Ember.Handlebars.compile('{{#view id="foo" classBinding=":foo"}} Foo{{/view}}')
  });

  run(view, 'appendTo', '#qunit-fixture');

  ok(jQuery('#foo').hasClass('foo'), "Always applies classbinding without condition");
});
<commit_msg>View name resolution regression tests<commit_after>/*globals EmberDev */
import EmberView from "ember-views/views/view";
import Container from 'container/container';
import run from "ember-metal/run_loop";
import jQuery from "ember-views/system/jquery";

var view, originalLookup;

var container = {
  lookupFactory: function() { }
};

function viewClass(options) {
  options.container = options.container || container;
  return EmberView.extend(options);
}

QUnit.module("Handlebars {{#view}} helper", {
  setup: function() {
    originalLookup = Ember.lookup;
  },

  teardown: function() {
    Ember.lookup = originalLookup;

    if (view) {
      run(view, 'destroy');
    }
  }
});

test("By default view:toplevel is used", function() {
  var DefaultView = viewClass({
    elementId: 'toplevel-view',
    template: Ember.Handlebars.compile('hello world')
  });

  function lookupFactory(fullName) {
    equal(fullName, 'view:toplevel');

    return DefaultView;
  }

  var container = {
    lookupFactory: lookupFactory
  };

  view = EmberView.extend({
    template: Ember.Handlebars.compile('{{view}}'),
    container: container
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#toplevel-view').text(), 'hello world');
});

test("By default, without a container, EmberView is used", function() {
  view = EmberView.extend({
    template: Ember.Handlebars.compile('{{view tagName="span"}}')
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  ok(jQuery('#qunit-fixture').html().toUpperCase().match(/<SPAN/), 'contains view with span');
});

test("View lookup - App.FuView (DEPRECATED)", function() {
  Ember.lookup = {
    App: {
      FuView: viewClass({
        elementId: "fu",
        template: Ember.Handlebars.compile("bro")
      })
    }
  };

  view = viewClass({
    template: Ember.Handlebars.compile("{{view App.FuView}}")
  }).create();

  expectDeprecation(function(){
    run(view, 'appendTo', '#qunit-fixture');
  }, /Resolved the view "App.FuView" on the global context/);

  equal(jQuery('#fu').text(), 'bro');
});

test("View lookup - 'fu'", function() {
  var FuView = viewClass({
    elementId: "fu",
    template: Ember.Handlebars.compile("bro")
  });

  function lookupFactory(fullName) {
    equal(fullName, 'view:fu');

    return FuView;
  }

  var container = {
    lookupFactory: lookupFactory
  };

  view = EmberView.extend({
    template: Ember.Handlebars.compile("{{view 'fu'}}"),
    container: container
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#fu').text(), 'bro');
});

test("View lookup - 'fu' when fu is a property and a view name", function() {
  var FuView = viewClass({
    elementId: "fu",
    template: Ember.Handlebars.compile("bro")
  });

  function lookupFactory(fullName) {
    equal(fullName, 'view:fu');

    return FuView;
  }

  var container = {
    lookupFactory: lookupFactory
  };

  view = EmberView.extend({
    template: Ember.Handlebars.compile("{{view 'fu'}}"),
    context: {fu: 'boom!'},
    container: container
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#fu').text(), 'bro');
});

test("View lookup - view.computed", function() {
  var FuView = viewClass({
    elementId: "fu",
    template: Ember.Handlebars.compile("bro")
  });

  function lookupFactory(fullName) {
    equal(fullName, 'view:fu');

    return FuView;
  }

  var container = {
    lookupFactory: lookupFactory
  };

  view = EmberView.extend({
    template: Ember.Handlebars.compile("{{view view.computed}}"),
    container: container,
    computed: 'fu'
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#fu').text(), 'bro');
});

test("id bindings downgrade to one-time property lookup", function() {
  view = EmberView.extend({
    template: Ember.Handlebars.compile("{{#view id=view.meshuggah}}{{view.parentView.meshuggah}}{{/view}}"),
    meshuggah: 'stengah'
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#stengah').text(), 'stengah', "id binding performed property lookup");
  run(view, 'set', 'meshuggah', 'omg');
  equal(jQuery('#stengah').text(), 'omg', "id didn't change");
});

test("mixing old and new styles of property binding fires a warning, treats value as if it were quoted", function() {
  if (EmberDev && EmberDev.runningProdBuild){
    ok(true, 'Logging does not occur in production builds');
    return;
  }

  expect(2);

  var oldWarn = Ember.warn;

  Ember.warn = function(msg) {
    equal(msg, "You're attempting to render a view by passing borfBinding=view.snork to a view helper, but this syntax is ambiguous. You should either surround view.snork in quotes or remove `Binding` from borfBinding.");
  };

  view = EmberView.extend({
    template: Ember.Handlebars.compile("{{#view borfBinding=view.snork}}
{{view.borf}}
{{/view}}"),
    snork: "nerd"
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  equal(jQuery('#lol').text(), "nerd", "awkward mixed syntax treated like binding");

  Ember.warn = oldWarn;
});

test("allows you to pass attributes that will be assigned to the class instance, like class=\"foo\"", function() {
  expect(4);

  var container = new Container();
  container.register('view:toplevel', EmberView.extend());

  view = EmberView.extend({
    template: Ember.Handlebars.compile('{{view id="foo" tagName="h1" class="foo"}}{{#view id="bar" class="bar"}}Bar{{/view}}'),
    container: container
  }).create();

  run(view, 'appendTo', '#qunit-fixture');

  ok(jQuery('#foo').hasClass('foo'));
  ok(jQuery('#foo').is('h1'));
  ok(jQuery('#bar').hasClass('bar'));
  equal(jQuery('#bar').text(), 'Bar');
});

test("Should apply class without condition always", function() {
  view = EmberView.create({
    context: [],
    controller: Ember.Object.create(),
    template: Ember.Handlebars.compile('{{#view id="foo" classBinding=":foo"}} Foo{{/view}}')
  });

  run(view, 'appendTo', '#qunit-fixture');

  ok(jQuery('#foo').hasClass('foo'), "Always applies classbinding without condition");
});
<|endoftext|>


<commit_before>var modelClass, model, moduleOpts = {
  setup: function() {
    modelClass = Ember.Object.extend(Ember.Validations);
    model = modelClass.create({

      validations: {

        name: {

          customPresence: {
            validator: function(obj, attr, val) {
              if (!val) {
                obj.get('validationErrors').add(attr, "isEmpty");
              }
            }
          }

        }

      }

    });
  },
  teardown: function() {
    modelClass = null;
    model = null;
  }
};
module("Ember.Validations",moduleOpts);

test("should set 'error' property", function() {
  ok(Ember.ValidationErrors.detectInstance(model.get('validationErrors')), "'error' property should be an Ember.ValidationErrors");
});

test("#validate should call #validate validator method", function() {
  model.validate();
  var nameErrorsKeys = model.get('validationErrors.name.keys');
  deepEqual(nameErrorsKeys, ["isEmpty"], "should call #validate validator method");
});

test("#validate should set 'isValid' property to false when invalid", function() {
  model.validate();
  equal(model.get('isValid'), false, "should set 'isValid' to false");
});

test("#validate should set 'isValid' property to true when valid", function() {
  model.set('name', 'ember');
  model.validate();
  equal(model.get('isValid'), true, "should set 'isValid' to true");
});

test("#validate should return false when invalid", function() {
  equal(model.validate(), false, "should return false");
});

test("#validate should return true when valid", function() {
  model.set('name', 'ember');
  equal(model.validate(), true, "should return true");
});

test("#validate should remove previous errors", function() {
  model.get('validationErrors').add('name', 'blank');
  model.set('name', 'ember');
  model.validate();
  equal(model.get('validationErrors').get('length'), 0, "should have no error");
});

test("#validation should notify errors property changed", function() {
  model.reopen({
    observer: Ember.observer(function() {
      ok(true, "errors has changed");
    }, 'validationErrors')
  });
  model.validate();
  expect(1);
});<commit_msg>Clean Validations tests<commit_after>var modelClass, model, moduleOpts = {
  setup: function() {
    modelClass = Ember.Object.extend(Ember.Validations);

    var validations = {
      name: {
        customPresence: {
          validator: function(obj, attr, val) {
            if (!val) {
              obj.get('validationErrors').add(attr, "isEmpty");
            }
          }
        }
      }
    };

    model = modelClass.create({validations: validations});
  },
  teardown: function() {
    modelClass = null;
    model = null;
  }
};
module("Ember.Validations",moduleOpts);

test("should set 'error' property", function() {
  ok(Ember.ValidationErrors.detectInstance(model.get('validationErrors')), "'error' property should be an Ember.ValidationErrors");
});

test("#validate should call #validate validator method", function() {
  model.validate();
  var nameErrorsKeys = model.get('validationErrors.name.keys');
  deepEqual(nameErrorsKeys, ["isEmpty"], "should call #validate validator method");
});

test("#validate should set 'isValid' property to false when invalid", function() {
  model.validate();
  equal(model.get('isValid'), false, "should set 'isValid' to false");
});

test("#validate should set 'isValid' property to true when valid", function() {
  model.set('name', 'ember');
  model.validate();
  equal(model.get('isValid'), true, "should set 'isValid' to true");
});

test("#validate should return false when invalid", function() {
  equal(model.validate(), false, "should return false");
});

test("#validate should return true when valid", function() {
  model.set('name', 'ember');
  equal(model.validate(), true, "should return true");
});

test("#validate should remove previous errors", function() {
  model.get('validationErrors').add('name', 'blank');
  model.set('name', 'ember');
  model.validate();
  equal(model.get('validationErrors').get('length'), 0, "should have no error");
});

test("#validation should notify errors property changed", function() {
  model.reopen({
    observer: Ember.observer(function() {
      ok(true, "errors has changed");
    }, 'validationErrors')
  });
  model.validate();
  expect(1);
});<|endoftext|>


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

import {
  Row,
  Col,
  FormGroup,
  ControlLabel,
  FormControl,
  InputGroup,
  Glyphicon,
  Checkbox,
  Radio,
  Panel,
  ButtonToolbar,
  Button,
  Well,
  HelpBlock,
  Modal,
  Alert
} from 'react-bootstrap'
import Select from 'react-select'

import classnames from 'classnames'
import find from 'lodash/find'
import some from 'lodash/some'
import get from 'lodash/get'

import ArrayEditor from './ArrayEditor'
import QuestionsEditor from './QuestionsEditor'
import QuestionsBulkImport from './QuestionsBulkImport'
import style from './style.scss'

const getInputValue = input => {
  switch (input.type) {
    case 'radio':
      const els = [].slice.call(document.getElementsByName(input.name))
      for (const el of els) {
        if (el.checked) {
          return el.value
        }
      }
      return null
    case 'checkbox':
      return input.checked
    default:
      return input.value
  }
}

const cleanupQuestions = questions => questions.map(q => q.trim()).filter(Boolean)

const ACTIONS = {
  TEXT: 'text',
  REDIRECT: 'redirect',
  TEXT_REDIRECT: 'text_redirect'
}

export default class QnaAdmin extends Component {
  constructor(props) {
    super(props)
    this.csvDownloadableLink = React.createRef()
  }

  createEmptyQuestion() {
    return {
      id: null,
      data: {
        questions: [''],
        answer: '',
        redirectFlow: '',
        redirectNode: '',
        action: ACTIONS.TEXT,
        enabled: true
      }
    }
  }

  state = {
    newItem: this.createEmptyQuestion(),
    items: [],
    flows: null,
    filter: '',
    showBulkImport: undefined
  }

  shouldAutofocus = true

  fetchFlows() {
    this.props.bp.axios.get('/api/flows/all').then(({ data }) => {
      this.setState({ flows: data })
    })
  }

  fetchData() {
    this.props.bp.axios.get('/api/botpress-qna/').then(({ data }) => {
      this.setState({ items: data })
    })
  }

  componentDidMount() {
    this.fetchData()
    this.fetchFlows()
  }

  onCreate = value => {
    const data = {
      ...value.data,
      questions: cleanupQuestions(value.data.questions)
    }
    return this.props.bp.axios.post('/api/botpress-qna/', data).then(({ data: id }) => ({
      // update the value with the retrieved ID
      id,
      // and the cleaned data
      data
    }))
  }

  onEdit = index => {
    const value = this.state.items[index]
    this.props.bp.axios.put(`/api/botpress-qna/${value.id}`, value.data)
  }

  onDelete = value => {
    this.props.bp.axios.delete(`/api/botpress-qna/${value.id}`)
  }

  onInputChange = (index, prop, onChange) => event =>
    this.onPropChange(index, prop, onChange)(getInputValue(event.target))

  onSelectChange = (index, prop, onChange) => ({ value }) => this.onPropChange(index, prop, onChange)(value)

  onPropChange = (index, prop, onChange) => propValue => {
    const value = index == null ? this.state.newItem : this.state.items[index]
    onChange(
      {
        ...value,
        data: {
          ...value.data,
          [prop]: propValue
        }
      },
      index
    )
  }

  onQuestionsChanged = (index, onChange) => questions => {
    const value = index == null ? this.state.newItem : this.state.items[index]
    onChange(
      {
        ...value,
        data: {
          ...value.data,
          questions
        }
      },
      index
    )
  }

  updateState = newState => this.setState(newState)

  getFormControlId = (index, suffix) => `form-${index != null ? index : 'new'}-${suffix}`

  canSave = data =>
    !!cleanupQuestions(data.questions).length &&
    (data.action === ACTIONS.TEXT ? !!data.answer : !!data.redirectFlow && !!data.redirectNode)

  renderTextAndRedirectSelect(index, onChange) {
    return (

        {this.renderTextInput(index, onChange)}
        {this.renderRedirectSelect(index, onChange)}

    )
  }

  renderTextInput = (index, onChange) => {
    const item = index === null ? 'newItem' : `items.${index}`
    const answer = get(this.state, `${item}.data.answer`, '')

    return (
      <FormGroup controlId={this.getFormControlId(index, 'answer')}>
        <ControlLabel>Answer:</ControlLabel>
        <FormControl
          componentClass="textarea"
          placeholder="Answer"
          value={answer}
          onChange={this.onInputChange(index, 'answer', onChange)}
        />
      </FormGroup>
    )
  }

  renderRedirectSelect(index, onChange) {
    const { flows } = this.state
    if (!flows) {
      return null
    }
    const flowOptions = flows.map(({ name }) => ({ label: name, value: name }))

    const { data } = index == null ? this.state.newItem : this.state.items[index]
    const { redirectFlow } = data

    const nodeOptions = !redirectFlow
      ? []
      : find(flows, { name: redirectFlow }).nodes.map(({ name }) => ({ label: name, value: name }))

    return (

        <Row>
          <Col sm={6} md={2}>
            Flow:
          </Col>
          <Col sm={6} md={4}>
            <Select
              value={redirectFlow}
              options={flowOptions}
              onChange={this.onSelectChange(index, 'redirectFlow', onChange)}
            />
          </Col>

          <Col sm={6} md={2}>
            Node:
          </Col>
          <Col sm={6} md={4}>
            <Select
              value={data.redirectNode}
              options={nodeOptions}
              onChange={this.onSelectChange(index, 'redirectNode', onChange)}
            />
          </Col>
        </Row>

    )
  }

  doQuestionsBulkImport = (index, onChange) => newQuestions => {
    if (index === undefined) {
      return
    }
    this.setState({ showBulkImport: undefined })

    const value = index == null ? this.state.newItem : this.state.items[index]

    const oldQuestions = value.data.questions.map(s => s.trim()).filter(Boolean)

    onChange(
      {
        ...value,
        data: {
          ...value.data,
          questions: newQuestions.concat(oldQuestions)
        }
      },
      index
    )
  }

  showQuestionsBulkImportModal = index => () => {
    this.setState({ showBulkImport: index })
  }

  renderBulkImportModal(index, onChange) {
    return (
      <QuestionsBulkImport
        onSubmit={this.doQuestionsBulkImport(index, onChange)}
        onCancel={() => {
          this.setState({ showBulkImport: undefined })
        }}
      />
    )
  }

  renderForm = ({ data }, index, { isDirty, onCreate, onEdit, onReset, onDelete, onChange }) => {
    const { shouldAutofocus } = this
    this.shouldAutofocus = false

    const { showBulkImport } = this.state
    const saveSign = `${isDirty ? '* ' : ''}Save`

    return (
      <Well bsSize="small" bsClass={classnames('well', style.qna, { [style.pale]: !data.enabled })}>
        {index == null &&
New Q&A
}

        <Checkbox
          checked={data.enabled}
          onChange={this.onInputChange(index, 'enabled', onChange)}
          bsClass={classnames('checkbox', { [style.strong]: data.enabled })}
        >
          Enabled
        </Checkbox>

        <Panel>
          <Panel.Heading>
            Questions
            <Button bsSize="xs" bsStyle="link" onClick={this.showQuestionsBulkImportModal(index)}>
              bulk import
            </Button>
            {showBulkImport === index && this.renderBulkImportModal(index, onChange)}
          </Panel.Heading>
          <Panel.Body>
            <QuestionsEditor
              autofocus={shouldAutofocus && index == null}
              items={data.questions}
              onChange={this.onQuestionsChanged(index, onChange)}
            />
          </Panel.Body>
        </Panel>

        <FormGroup>
          Reply with:
          <Radio
            name={this.getFormControlId(index, 'action')}
            value={ACTIONS.TEXT}
            checked={data.action === ACTIONS.TEXT}
            onChange={this.onInputChange(index, 'action', onChange)}
            inline
          >
            text answer
          </Radio>
          <Radio
            name={this.getFormControlId(index, 'action')}
            value={ACTIONS.REDIRECT}
            checked={data.action === ACTIONS.REDIRECT}
            onChange={this.onInputChange(index, 'action', onChange)}
            inline
          >
            redirect to flow node
          </Radio>
          <Radio
            name={this.getFormControlId(index, 'action')}
            value={ACTIONS.TEXT_REDIRECT}
            checked={data.action === ACTIONS.TEXT_REDIRECT}
            onChange={this.onInputChange(index, 'action', onChange)}
            inline
          >
            text answer and redirect to flow node
          </Radio>
        </FormGroup>

        {data.action === ACTIONS.TEXT && this.renderTextInput(index, onChange)}
        {data.action === ACTIONS.REDIRECT && this.renderRedirectSelect(index, onChange)}
        {data.action === ACTIONS.TEXT_REDIRECT && this.renderTextAndRedirectSelect(index, onChange)}

        <ButtonToolbar>
          <Button type="button" onClick={() => onReset(index)} disabled={!isDirty}>
            Reset
          </Button>
          {index != null && (
            <Button type="button" bsStyle="danger" onClick={() => onDelete(index)}>
              Delete
            </Button>
          )}
          <Button
            type="button"
            bsStyle="success"
            onClick={() => (index != null ? onEdit(index) : onCreate())}
            disabled={!isDirty || !this.canSave(data)}
          >
            {index != null ? saveSign : 'Create'}
          </Button>
        </ButtonToolbar>
      </Well>
    )
  }

  onFilterChange = event => {
    this.setState({ filter: event.target.value })
  }

  questionMatches = filter => ({ data: { questions, answer, action } }, index) => {
    if (index == null || !filter) {
      return true
    }

    if (action === ACTIONS.TEXT && answer.indexOf(filter) >= 0) {
      return true
    }

    return some(questions, q => q.indexOf(filter) >= 0)
  }

  uploadCsv = () => {
    const formData = new FormData()
    formData.set('isReplace', this.state.isCsvUploadReplace)
    formData.append('csv', this.state.csvToUpload)
    const headers = { 'Content-Type': 'multipart/form-data' }
    this.props.bp.axios
      .post('/api/botpress-qna/csv', formData, { headers })
      .then(() => {
        this.setState({ importCsvModalShow: false })
        this.fetchData()
      })
      .catch(({ response: { data: csvUploadError } }) => this.setState({ csvUploadError }))
  }

  downloadCsv = () =>
    // We can't just download file directly due to security restrictions
    this.props.bp.axios({ url: '/api/botpress-qna/csv', responseType: 'blob' }).then(response => {
      this.setState(
        {
          csvDownloadableLinkHref: window.URL.createObjectURL(new Blob([response.data])),
          csvDownloadableFileName: /filename=(.*\.csv)/.exec(response.headers['content-disposition'])[1]
        },
        () => this.csvDownloadableLink.current.click()
      )
    })

  render() {
    return (
      <Panel>

        <Panel.Body>
          <FormGroup>
            <ButtonToolbar>
              <Button
                bsStyle="success"
                onClick={() =>
                  this.setState({
                    importCsvModalShow: true,
                    csvToUpload: null,
                    csvUploadError: null,
                    isCsvUploadReplace: false
                  })}
                type="button"
              >
                <Glyphicon glyph="upload" />   Import from CSV
              </Button>
              <Modal show={this.state.importCsvModalShow} onHide={() => this.setState({ importCsvModalShow: false })}>
                <Modal.Header closeButton>
                  <Modal.Title>Import CSV</Modal.Title>
                </Modal.Header>
                <Modal.Body>
                  {this.state.csvUploadError && (
                    <Alert bsStyle="danger" onDismiss={() => this.setState({ csvUploadError: null })}>

{this.state.csvUploadError}

                    </Alert>
                  )}
                  <form>
                    <FormGroup>
                      <ControlLabel>CSV file</ControlLabel>
                      <FormControl
                        type="file"
                        accept=".csv"
                        onChange={e => this.setState({ csvToUpload: e.target.files[0] })}
                      />
                      <HelpBlock>CSV should be formatted "question,answer_type,answer"</HelpBlock>
                    </FormGroup>
                    <FormGroup>
                      <Checkbox
                        checked={this.state.isCsvUploadReplace}
                        onChange={e => this.setState({ isCsvUploadReplace: e.target.checked })}
                      >
                        Replace existing FAQs
                      </Checkbox>
                      <HelpBlock>Deletes existing FAQs and then uploads new ones from the file</HelpBlock>
                    </FormGroup>
                  </form>
                </Modal.Body>
                <Modal.Footer>
                  <Button bsStyle="primary" onClick={this.uploadCsv} disabled={!Boolean(this.state.csvToUpload)}>
                    Upload
                  </Button>
                </Modal.Footer>
              </Modal>

              <Button bsStyle="success" onClick={this.downloadCsv} type="button">
                <Glyphicon glyph="download" />  Export to CSV
              </Button>
            </ButtonToolbar>
          </FormGroup>
          <FormGroup>
            <InputGroup>
              <FormControl placeholder="Filter questions" value={this.state.filter} onChange={this.onFilterChange} />
              <InputGroup.Addon>
                <Glyphicon glyph="search" />
              </InputGroup.Addon>
            </InputGroup>
          </FormGroup>

          <ArrayEditor
            items={this.state.items}
            shouldShowItem={this.questionMatches(this.state.filter)}
            newItem={this.state.newItem}
            renderItem={this.renderForm}
            onCreate={this.onCreate}
            onEdit={this.onEdit}
            onDelete={this.onDelete}
            updateState={this.updateState}
            createNewItem={this.createEmptyQuestion}
          />
        </Panel.Body>
      </Panel>
    )
  }
}
<commit_msg>fix(qna): on flow deletion or rename, QNA still works<commit_after>import React, { Component } from 'react'

import {
  Row,
  Col,
  FormGroup,
  ControlLabel,
  FormControl,
  InputGroup,
  Glyphicon,
  Checkbox,
  Radio,
  Panel,
  ButtonToolbar,
  Button,
  Well,
  HelpBlock,
  Modal,
  Alert
} from 'react-bootstrap'
import Select from 'react-select'

import classnames from 'classnames'
import find from 'lodash/find'
import some from 'lodash/some'
import get from 'lodash/get'

import ArrayEditor from './ArrayEditor'
import QuestionsEditor from './QuestionsEditor'
import QuestionsBulkImport from './QuestionsBulkImport'
import style from './style.scss'

const getInputValue = input => {
  switch (input.type) {
    case 'radio':
      const els = [].slice.call(document.getElementsByName(input.name))
      for (const el of els) {
        if (el.checked) {
          return el.value
        }
      }
      return null
    case 'checkbox':
      return input.checked
    default:
      return input.value
  }
}

const cleanupQuestions = questions => questions.map(q => q.trim()).filter(Boolean)

const ACTIONS = {
  TEXT: 'text',
  REDIRECT: 'redirect',
  TEXT_REDIRECT: 'text_redirect'
}

export default class QnaAdmin extends Component {
  constructor(props) {
    super(props)
    this.csvDownloadableLink = React.createRef()
  }

  createEmptyQuestion() {
    return {
      id: null,
      data: {
        questions: [''],
        answer: '',
        redirectFlow: '',
        redirectNode: '',
        action: ACTIONS.TEXT,
        enabled: true
      }
    }
  }

  state = {
    newItem: this.createEmptyQuestion(),
    items: [],
    flows: null,
    filter: '',
    showBulkImport: undefined
  }

  shouldAutofocus = true

  fetchFlows() {
    this.props.bp.axios.get('/api/flows/all').then(({ data }) => {
      this.setState({ flows: data })
    })
  }

  fetchData() {
    this.props.bp.axios.get('/api/botpress-qna/').then(({ data }) => {
      this.setState({ items: data })
    })
  }

  componentDidMount() {
    this.fetchData()
    this.fetchFlows()
  }

  onCreate = value => {
    const data = {
      ...value.data,
      questions: cleanupQuestions(value.data.questions)
    }
    return this.props.bp.axios.post('/api/botpress-qna/', data).then(({ data: id }) => ({
      // update the value with the retrieved ID
      id,
      // and the cleaned data
      data
    }))
  }

  onEdit = index => {
    const value = this.state.items[index]
    this.props.bp.axios.put(`/api/botpress-qna/${value.id}`, value.data)
  }

  onDelete = value => {
    this.props.bp.axios.delete(`/api/botpress-qna/${value.id}`)
  }

  onInputChange = (index, prop, onChange) => event =>
    this.onPropChange(index, prop, onChange)(getInputValue(event.target))

  onSelectChange = (index, prop, onChange) => ({ value }) => this.onPropChange(index, prop, onChange)(value)

  onPropChange = (index, prop, onChange) => propValue => {
    const value = index == null ? this.state.newItem : this.state.items[index]
    onChange(
      {
        ...value,
        data: {
          ...value.data,
          [prop]: propValue
        }
      },
      index
    )
  }

  onQuestionsChanged = (index, onChange) => questions => {
    const value = index == null ? this.state.newItem : this.state.items[index]
    onChange(
      {
        ...value,
        data: {
          ...value.data,
          questions
        }
      },
      index
    )
  }

  updateState = newState => this.setState(newState)

  getFormControlId = (index, suffix) => `form-${index != null ? index : 'new'}-${suffix}`

  canSave = data =>
    !!cleanupQuestions(data.questions).length &&
    (data.action === ACTIONS.TEXT ? !!data.answer : !!data.redirectFlow && !!data.redirectNode)

  renderTextAndRedirectSelect(index, onChange) {
    return (

        {this.renderTextInput(index, onChange)}
        {this.renderRedirectSelect(index, onChange)}

    )
  }

  renderTextInput = (index, onChange) => {
    const item = index === null ? 'newItem' : `items.${index}`
    const answer = get(this.state, `${item}.data.answer`, '')

    return (
      <FormGroup controlId={this.getFormControlId(index, 'answer')}>
        <ControlLabel>Answer:</ControlLabel>
        <FormControl
          componentClass="textarea"
          placeholder="Answer"
          value={answer}
          onChange={this.onInputChange(index, 'answer', onChange)}
        />
      </FormGroup>
    )
  }

  renderRedirectSelect(index, onChange) {
    const { flows } = this.state
    if (!flows) {
      return null
    }
    const flowOptions = flows.map(({ name }) => ({ label: name, value: name }))

    const { data } = index == null ? this.state.newItem : this.state.items[index]
    const { redirectFlow } = data

    const nodeOptions = !redirectFlow
      ? []
      : get(find(flows, { name: redirectFlow }), 'nodes', []).map(({ name }) => ({ label: name, value: name }))

    return (

        <Row>
          <Col sm={6} md={2}>
            Flow:
          </Col>
          <Col sm={6} md={4}>
            <Select
              value={redirectFlow}
              options={flowOptions}
              onChange={this.onSelectChange(index, 'redirectFlow', onChange)}
            />
          </Col>

          <Col sm={6} md={2}>
            Node:
          </Col>
          <Col sm={6} md={4}>
            <Select
              value={data.redirectNode}
              options={nodeOptions}
              onChange={this.onSelectChange(index, 'redirectNode', onChange)}
            />
          </Col>
        </Row>

    )
  }

  doQuestionsBulkImport = (index, onChange) => newQuestions => {
    if (index === undefined) {
      return
    }
    this.setState({ showBulkImport: undefined })

    const value = index == null ? this.state.newItem : this.state.items[index]

    const oldQuestions = value.data.questions.map(s => s.trim()).filter(Boolean)

    onChange(
      {
        ...value,
        data: {
          ...value.data,
          questions: newQuestions.concat(oldQuestions)
        }
      },
      index
    )
  }

  showQuestionsBulkImportModal = index => () => {
    this.setState({ showBulkImport: index })
  }

  renderBulkImportModal(index, onChange) {
    return (
      <QuestionsBulkImport
        onSubmit={this.doQuestionsBulkImport(index, onChange)}
        onCancel={() => {
          this.setState({ showBulkImport: undefined })
        }}
      />
    )
  }

  renderForm = ({ data }, index, { isDirty, onCreate, onEdit, onReset, onDelete, onChange }) => {
    const { shouldAutofocus } = this
    this.shouldAutofocus = false

    const { showBulkImport } = this.state
    const saveSign = `${isDirty ? '* ' : ''}Save`

    return (
      <Well bsSize="small" bsClass={classnames('well', style.qna, { [style.pale]: !data.enabled })}>
        {index == null &&
New Q&A
}

        <Checkbox
          checked={data.enabled}
          onChange={this.onInputChange(index, 'enabled', onChange)}
          bsClass={classnames('checkbox', { [style.strong]: data.enabled })}
        >
          Enabled
        </Checkbox>

        <Panel>
          <Panel.Heading>
            Questions
            <Button bsSize="xs" bsStyle="link" onClick={this.showQuestionsBulkImportModal(index)}>
              bulk import
            </Button>
            {showBulkImport === index && this.renderBulkImportModal(index, onChange)}
          </Panel.Heading>
          <Panel.Body>
            <QuestionsEditor
              autofocus={shouldAutofocus && index == null}
              items={data.questions}
              onChange={this.onQuestionsChanged(index, onChange)}
            />
          </Panel.Body>
        </Panel>

        <FormGroup>
          Reply with:
          <Radio
            name={this.getFormControlId(index, 'action')}
            value={ACTIONS.TEXT}
            checked={data.action === ACTIONS.TEXT}
            onChange={this.onInputChange(index, 'action', onChange)}
            inline
          >
            text answer
          </Radio>
          <Radio
            name={this.getFormControlId(index, 'action')}
            value={ACTIONS.REDIRECT}
            checked={data.action === ACTIONS.REDIRECT}
            onChange={this.onInputChange(index, 'action', onChange)}
            inline
          >
            redirect to flow node
          </Radio>
          <Radio
            name={this.getFormControlId(index, 'action')}
            value={ACTIONS.TEXT_REDIRECT}
            checked={data.action === ACTIONS.TEXT_REDIRECT}
            onChange={this.onInputChange(index, 'action', onChange)}
            inline
          >
            text answer and redirect to flow node
          </Radio>
        </FormGroup>

        {data.action === ACTIONS.TEXT && this.renderTextInput(index, onChange)}
        {data.action === ACTIONS.REDIRECT && this.renderRedirectSelect(index, onChange)}
        {data.action === ACTIONS.TEXT_REDIRECT && this.renderTextAndRedirectSelect(index, onChange)}

        <ButtonToolbar>
          <Button type="button" onClick={() => onReset(index)} disabled={!isDirty}>
            Reset
          </Button>
          {index != null && (
            <Button type="button" bsStyle="danger" onClick={() => onDelete(index)}>
              Delete
            </Button>
          )}
          <Button
            type="button"
            bsStyle="success"
            onClick={() => (index != null ? onEdit(index) : onCreate())}
            disabled={!isDirty || !this.canSave(data)}
          >
            {index != null ? saveSign : 'Create'}
          </Button>
        </ButtonToolbar>
      </Well>
    )
  }

  onFilterChange = event => {
    this.setState({ filter: event.target.value })
  }

  questionMatches = filter => ({ data: { questions, answer, action } }, index) => {
    if (index == null || !filter) {
      return true
    }

    if (action === ACTIONS.TEXT && answer.indexOf(filter) >= 0) {
      return true
    }

    return some(questions, q => q.indexOf(filter) >= 0)
  }

  uploadCsv = () => {
    const formData = new FormData()
    formData.set('isReplace', this.state.isCsvUploadReplace)
    formData.append('csv', this.state.csvToUpload)
    const headers = { 'Content-Type': 'multipart/form-data' }
    this.props.bp.axios
      .post('/api/botpress-qna/csv', formData, { headers })
      .then(() => {
        this.setState({ importCsvModalShow: false })
        this.fetchData()
      })
      .catch(({ response: { data: csvUploadError } }) => this.setState({ csvUploadError }))
  }

  downloadCsv = () =>
    // We can't just download file directly due to security restrictions
    this.props.bp.axios({ url: '/api/botpress-qna/csv', responseType: 'blob' }).then(response => {
      this.setState(
        {
          csvDownloadableLinkHref: window.URL.createObjectURL(new Blob([response.data])),
          csvDownloadableFileName: /filename=(.*\.csv)/.exec(response.headers['content-disposition'])[1]
        },
        () => this.csvDownloadableLink.current.click()
      )
    })

  render() {
    return (
      <Panel>

        <Panel.Body>
          <FormGroup>
            <ButtonToolbar>
              <Button
                bsStyle="success"
                onClick={() =>
                  this.setState({
                    importCsvModalShow: true,
                    csvToUpload: null,
                    csvUploadError: null,
                    isCsvUploadReplace: false
                  })}
                type="button"
              >
                <Glyphicon glyph="upload" />   Import from CSV
              </Button>
              <Modal show={this.state.importCsvModalShow} onHide={() => this.setState({ importCsvModalShow: false })}>
                <Modal.Header closeButton>
                  <Modal.Title>Import CSV</Modal.Title>
                </Modal.Header>
                <Modal.Body>
                  {this.state.csvUploadError && (
                    <Alert bsStyle="danger" onDismiss={() => this.setState({ csvUploadError: null })}>

{this.state.csvUploadError}

                    </Alert>
                  )}
                  <form>
                    <FormGroup>
                      <ControlLabel>CSV file</ControlLabel>
                      <FormControl
                        type="file"
                        accept=".csv"
                        onChange={e => this.setState({ csvToUpload: e.target.files[0] })}
                      />
                      <HelpBlock>CSV should be formatted "question,answer_type,answer"</HelpBlock>
                    </FormGroup>
                    <FormGroup>
                      <Checkbox
                        checked={this.state.isCsvUploadReplace}
                        onChange={e => this.setState({ isCsvUploadReplace: e.target.checked })}
                      >
                        Replace existing FAQs
                      </Checkbox>
                      <HelpBlock>Deletes existing FAQs and then uploads new ones from the file</HelpBlock>
                    </FormGroup>
                  </form>
                </Modal.Body>
                <Modal.Footer>
                  <Button bsStyle="primary" onClick={this.uploadCsv} disabled={!Boolean(this.state.csvToUpload)}>
                    Upload
                  </Button>
                </Modal.Footer>
              </Modal>

              <Button bsStyle="success" onClick={this.downloadCsv} type="button">
                <Glyphicon glyph="download" />  Export to CSV
              </Button>
            </ButtonToolbar>
          </FormGroup>
          <FormGroup>
            <InputGroup>
              <FormControl placeholder="Filter questions" value={this.state.filter} onChange={this.onFilterChange} />
              <InputGroup.Addon>
                <Glyphicon glyph="search" />
              </InputGroup.Addon>
            </InputGroup>
          </FormGroup>

          <ArrayEditor
            items={this.state.items}
            shouldShowItem={this.questionMatches(this.state.filter)}
            newItem={this.state.newItem}
            renderItem={this.renderForm}
            onCreate={this.onCreate}
            onEdit={this.onEdit}
            onDelete={this.onDelete}
            updateState={this.updateState}
            createNewItem={this.createEmptyQuestion}
          />
        </Panel.Body>
      </Panel>
    )
  }
}
<|endoftext|>


<commit_before>var Rock = MapEntity.extend(
{
	Sprites : ['rock1', 'rock2']
});

var MapTree = MapEntity.extend(
{
	Sprites : ['deadCherryTree0', 'deadCherryTree1'],
	TreeID: 0,

	initialize: function()
	{
		this.TreeID = Crafty.math.randomInt(0, this.Sprites.length - 1);
		var entity = Crafty.e("2D, Canvas, Body, Static, " + this.Sprites[this.TreeID])
			.attr({z:2, TileWidth: 1, TileHeight: 1});
		this.set({'entity' : entity });
	},

	Appear: function(world, x, y)
	{
		var TreeSize = [{w: 4, h: 4}, {w: 3, h: 4}];

		var w = TreeSize[this.TreeID].w;
		var h = TreeSize[this.TreeID].h;

		this.getEntity().Appear(world, x - Math.floor(w / 2), y - h);
		return this;
	},
});

var TreeManager = Class({
	constructor: function() {
		// var trees = ["cherryTree0", "cherryTree1"];
		                    // treetrunk1: [1, 0]
		// var trees = ["deadTree", "cherryTree0", "cherryTree1"];
		var trees = ["deadTree", "treeStump"];
		this.treeSprites = [];
		for (var i = 0; i < trees.length; i++) {
			this.treeSprites.push(Crafty.e(trees[i]));
		}
	},

	draw: function(context, treeID, x, y) {
		var treeSprite = this.treeSprites[treeID];
		var srcX = treeSprite.__coord[0];
		var srcY = treeSprite.__coord[1];
		var w = treeSprite.__coord[2];
		var h = treeSprite.__coord[3];
		var destX = x - Math.floor(w / 2);
		var destY = y - h;
		// Draw the tree
		context.drawImage(treeSprite.img,
			srcX, srcY, w, h,
			destX, destY, w, h);
	}
});
<commit_msg>Fixed the tree placement<commit_after>var Rock = MapEntity.extend(
{
	Sprites : ['rock1', 'rock2']
});

var MapTree = MapEntity.extend(
{
	Sprites : ['deadCherryTree0', 'deadCherryTree1'],
	TreeID: 0,

	initialize: function()
	{
		this.TreeID = Crafty.math.randomInt(0, this.Sprites.length - 1);
		var entity = Crafty.e("2D, Canvas, Body, Static, " + this.Sprites[this.TreeID])
			.attr({z:2, TileWidth: 1, TileHeight: 1});
		this.set({'entity' : entity });
	},

	Appear: function(world, x, y)
	{
		var TreeSize = [{w: 4, h: 4}, {w: 3, h: 4}];

		var w = TreeSize[this.TreeID].w;
		var h = TreeSize[this.TreeID].h;

		this.getEntity().Appear(world, x - Math.floor(w / 2), y);
		return this;
	},
});

var TreeManager = Class({
	constructor: function() {
		// var trees = ["cherryTree0", "cherryTree1"];
		                    // treetrunk1: [1, 0]
		// var trees = ["deadTree", "cherryTree0", "cherryTree1"];
		var trees = ["deadTree", "treeStump"];
		this.treeSprites = [];
		for (var i = 0; i < trees.length; i++) {
			this.treeSprites.push(Crafty.e(trees[i]));
		}
	},

	draw: function(context, treeID, x, y) {
		var treeSprite = this.treeSprites[treeID];
		var srcX = treeSprite.__coord[0];
		var srcY = treeSprite.__coord[1];
		var w = treeSprite.__coord[2];
		var h = treeSprite.__coord[3];
		var destX = x - Math.floor(w / 2);
		var destY = y - h;
		// Draw the tree
		context.drawImage(treeSprite.img,
			srcX, srcY, w, h,
			destX, destY, w, h);
	}
});
<|endoftext|>


<commit_before>/**
 * Created by ate on 27.09.16.
 */
angular.module('ubirchAdminCrudApp')
  .constant('settings', {
    UBIRCH_API_HOST : "http://52.166.200.188:8080/",
    UBIRCH_AUTH_SERVICE_API_HOST : "${UBIRCH_AUTH_SERVICE_API_HOST}",
    DEFAULT_AUTH_EXPIRED_SECS: -1,
    DEFAULT_DEVICE_TYPE_KEY: "unknownDeviceType"
  });
<commit_msg>removed trailing slash<commit_after>/**
 * Created by ate on 27.09.16.
 */
angular.module('ubirchAdminCrudApp')
  .constant('settings', {
    UBIRCH_API_HOST : "http://52.166.200.188:8080",
    UBIRCH_AUTH_SERVICE_API_HOST : "${UBIRCH_AUTH_SERVICE_API_HOST}",
    DEFAULT_AUTH_EXPIRED_SECS: -1,
    DEFAULT_DEVICE_TYPE_KEY: "unknownDeviceType"
  });
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// 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.

'use strict';

/** Google Analytics Data API sample quickstart application.
 This application demonstrates the usage of the Analytics Data API using
 service account credentials.

 Before you start the application, please review the comments starting with
 "TODO(developer)" and update the code to use correct values.

 Usage:
 npm install
 node quickstart.js
 */

function main(propertyId = 'YOUR-GA4-PROPERTY-ID') {
  // [START google_analytics_data_quickstart]
  /**
   * TODO(developer): Uncomment this variable and replace with your
   *   Google Analytics 4 property ID before running the sample.
   */
  // propertyId = 'YOUR-GA4-PROPERTY-ID';

  // [START google_analytics_data_initialize]
  // Imports the Google Analytics Data API client library.
  const {BetaAnalyticsDataClient} = require('@google-analytics/data');

  // Using a default constructor instructs the client to use the credentials
  // specified in GOOGLE_APPLICATION_CREDENTIALS environment variable.
  const analyticsDataClient = new BetaAnalyticsDataClient();
  // [END google_analytics_data_initialize]

  // Runs a simple report.
  async function runReport() {
    // [START google_analytics_data_run_report]
    const [response] = await analyticsDataClient.runReport({
      property: `properties/${propertyId}`,
      dateRanges: [
        {
          startDate: '2020-03-31',
          endDate: 'today',
        },
      ],
      dimensions: [
        {
          name: 'city',
        },
      ],
      metrics: [
        {
          name: 'activeUsers',
        },
      ],
    });
    // [END google_analytics_data_run_report]

    // [START google_analytics_data_run_report_response]
    console.log('Report result:');
    response.rows.forEach(row => {
      console.log(row.dimensionValues[0], row.metricValues[0]);
    });
    // [END google_analytics_data_run_report_response]
  }

  runReport();
  // [END google_analytics_data_quickstart]
}

process.on('unhandledRejection', err => {
  console.error(err.message);
  process.exitCode = 1;
});
main(...process.argv.slice(2));
<commit_msg>docs: update region tag names to match the convention (#83)<commit_after>// Copyright 2021 Google LLC
//
// 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.

'use strict';

/** Google Analytics Data API sample quickstart application.
 This application demonstrates the usage of the Analytics Data API using
 service account credentials.

 Before you start the application, please review the comments starting with
 "TODO(developer)" and update the code to use correct values.

 Usage:
 npm install
 node quickstart.js
 */

function main(propertyId = 'YOUR-GA4-PROPERTY-ID') {
  // [START analyticsdata_quickstart]
  /**
   * TODO(developer): Uncomment this variable and replace with your
   *   Google Analytics 4 property ID before running the sample.
   */
  // propertyId = 'YOUR-GA4-PROPERTY-ID';

  // [START analyticsdata_run_report_initialize]
  // Imports the Google Analytics Data API client library.
  const {BetaAnalyticsDataClient} = require('@google-analytics/data');

  // Using a default constructor instructs the client to use the credentials
  // specified in GOOGLE_APPLICATION_CREDENTIALS environment variable.
  const analyticsDataClient = new BetaAnalyticsDataClient();
  // [END analyticsdata_run_report_initialize]

  // Runs a simple report.
  async function runReport() {
    // [START analyticsdata_run_report]
    const [response] = await analyticsDataClient.runReport({
      property: `properties/${propertyId}`,
      dateRanges: [
        {
          startDate: '2020-03-31',
          endDate: 'today',
        },
      ],
      dimensions: [
        {
          name: 'city',
        },
      ],
      metrics: [
        {
          name: 'activeUsers',
        },
      ],
    });
    // [END analyticsdata_run_report]

    // [START analyticsdata_run_report_response]
    console.log('Report result:');
    response.rows.forEach(row => {
      console.log(row.dimensionValues[0], row.metricValues[0]);
    });
    // [END analyticsdata_run_report_response]
  }

  runReport();
  // [END analyticsdata_quickstart]
}

process.on('unhandledRejection', err => {
  console.error(err.message);
  process.exitCode = 1;
});
main(...process.argv.slice(2));
<|endoftext|>


<commit_before>(function() {
  'use strict';
  /*
   * A trendline allows you to associate a line with a numerical value.
   *
   * @name trendLine
   */
  d4.feature('trendLine', function(name) {
    return {
      accessors: {
        text: function(d) {
          return d3.format('').call(this, d[1]);
        },

        textX: function() {
          return this.x(this.width);
        },

        textY: function() {
          return this.x(this.height);
        },

        x1: function() {
          return this.x(0);
        },

        x2: function() {
          return this.x(this.width);
        },

        y1: function() {
          return this.y(0);
        },

        y2: function() {
          return this.y(this.height);
        },

      },
      render: function(scope, data, selection) {
        var defs = this.svg.select('defs');

        d4.appendOnce(defs, 'marker#' + name + '-start')
          .attr('viewBox', '0 0 10 10')
          .attr('refX', 10)
          .attr('refY', 5)
          .attr('markerWidth', -6)
          .attr('markerHeight', 6)
          .attr('orient', 'auto')
          .append('path')
          .attr('d', 'M 0 0 L 10 5 L 0 10 z');

        d4.appendOnce(selection, 'g.' + name);
        var trendLine = d4.appendOnce(this.svg.select('.' + name), 'line.line')
          .attr('x1', d4.functor(scope.accessors.x1).bind(this))
          .attr('x2', d4.functor(scope.accessors.x2).bind(this))
          .attr('y1', d4.functor(scope.accessors.y1).bind(this))
          .attr('y2', d4.functor(scope.accessors.y2).bind(this))
          .attr('marker-end', 'url(#' + name + '-start)');

        d4.appendOnce(this.svg.select('.' + name), 'text.trendLine-label')
          .text(d4.functor(scope.accessors.text).bind(this))
          .attr('x', d4.functor(scope.accessors.textX).bind(this))
          .attr('y', d4.functor(scope.accessors.textY).bind(this));
        return trendLine;
      }
    };
  });
}).call(this);
<commit_msg>Fix an error with the arrow point.<commit_after>(function() {
  'use strict';
  /*
   * A trendline allows you to associate a line with a numerical value.
   *
   * @name trendLine
   */
  d4.feature('trendLine', function(name) {
    return {
      accessors: {
        text: function(d) {
          return d3.format('').call(this, d[1]);
        },

        textX: function() {
          return this.x(this.width);
        },

        textY: function() {
          return this.x(this.height);
        },

        x1: function() {
          return this.x(0);
        },

        x2: function() {
          return this.x(this.width);
        },

        y1: function() {
          return this.y(0);
        },

        y2: function() {
          return this.y(this.height);
        },

      },
      render: function(scope, data, selection) {
        var defs = this.svg.select('defs');

        d4.appendOnce(defs, 'marker#' + name + '-start')
          .attr('viewBox', '0 0 10 10')
          .attr('refX', 10)
          .attr('refY', 5)
          .attr('markerWidth', 6)
          .attr('markerHeight', 6)
          .attr('orient', 'auto')
          .append('path')
          .attr('d', 'M 0 0 L 10 5 L 0 10 z');

        d4.appendOnce(selection, 'g.' + name);
        var trendLine = d4.appendOnce(this.svg.select('.' + name), 'line.line')
          .attr('x1', d4.functor(scope.accessors.x1).bind(this))
          .attr('x2', d4.functor(scope.accessors.x2).bind(this))
          .attr('y1', d4.functor(scope.accessors.y1).bind(this))
          .attr('y2', d4.functor(scope.accessors.y2).bind(this))
          .attr('marker-end', 'url(#' + name + '-start)');

        d4.appendOnce(this.svg.select('.' + name), 'text.trendLine-label')
          .text(d4.functor(scope.accessors.text).bind(this))
          .attr('x', d4.functor(scope.accessors.textX).bind(this))
          .attr('y', d4.functor(scope.accessors.textY).bind(this));
        return trendLine;
      }
    };
  });
}).call(this);
<|endoftext|>