<commit_before>Template.page.rendered = function() {
    /**
     * Display the mouse position for dev marker placement
     */
    $('.wetlands').click(function(e){
        var parentOffset = $(this).parent().offset();
        var relX = e.pageX - parentOffset.left;
        $('.mouse-x').text(relX);
        var relY = e.pageY - parentOffset.top;
        $('.mouse-y').text(relY);
    });

    /**
     * Animate objects once the page is rendered
     */
    $('.container-map').addClass('animated fadeIn');
    $('.markers').addClass('animated fadeIn');
    if ( $('.body-copy.rare-climate-zone').length || $('.body-copy.our-wetlands').length ) {
        $('.body-copy').addClass('animated fadeInUp');
    }
    else {
        $('.body-copy').addClass('animated fadeInLeft');
    }
    $('.body-copy').css('-webkit-animation-duration', '400ms');
    $('.body-copy').css('animation-duration', '400ms');
    $('.subpage-button, h3').addClass('animated fadeInDown ');
    $('.subpage-button ').css('animation-duration', '300ms');
    $('.canyoneer-natural-walks').css('animation-duration', '400ms');
    $('h3').css('animation-duration', '500ms');

    /**
     * SVG Markers for Pacific Flyway
     */
    var selector;
    if ($('.map-wet-migration').length) {
        selector = '.map-wet-migration .marker-svg';
    }
    else if ($('.map-wet-wetlands')){
        selector = '.map-wet-wetlands .marker-svg';
    }

    if ($(selector).length) {
        var svgWidth = 1366;
        var svgHeight = 768;
        var svg = d3.select(selector)
            .append('svg')
            .attr('width', svgWidth)
            .attr('height', svgHeight);
    }

    $(selector).each(function() {

        // Get data about the marker from the DOM
        var centerX = $(this).data('dot-left');
        var centerY = $(this).data('dot-top');
        var arrowX = $(this).data('arrow-left');
        var arrowY = $(this).data('arrow-top');
        var markerNumber = $(this).data('marker-number');
        var markerLabel = $(this).data('marker-label');

        // Marker group
        var lineGroup = svg.append('g');

        // Pointer line
        var lineData = [ { 'x': centerX, 'y': centerY }, { 'x': arrowX,  'y': arrowY} ];
        var lineFunction = d3.svg.line()
            .x(function(d) { return d.x; })
            .y(function(d) { return d.y; })
            .interpolate('linear');
        lineGroup.append('path')
            .attr('d', lineFunction(lineData))
            .attr('class', 'marker-line');

        // Marker
        var markerCircle = svg
            .append('circle')
            .attr('r', 20)
            .attr('class', 'marker-circle marker-circle-' + markerNumber)
            .attr('cx', centerX)
            .attr('cy', centerY);

        // Text
        var textX;
        var textY;
        // One digit
        if (markerNumber.toString().length < 2) {
            textX = centerX - 6;
            textY = (centerY + 8);
        }
        // Two digits
        else {
            textX = centerX - 12;
            textY = (centerY + 8);
        }
        svg
            .append('svg:text')
            .attr('class', 'marker-numbers')
            .attr('x', textX)
            .attr('y', textY)
            .text(markerNumber);

        // Click area
        //
        // We don't use this on some maps because of overlap.
        var clickArea = svg
            .append('circle')
            .attr('fill', 'transparent')
            .attr('class', 'marker-click-area')
            .attr('data-dot-left', arrowX)
            .attr('data-dot-top', arrowY)
            .attr('data-marker-number', markerNumber)
            .attr('data-marker-label', markerLabel)
            .attr('cx', centerX)
            .attr('cy', centerY);
        if ($('.map-wet-migration').length) {
            clickArea.attr('r', 50);
        }
        else {
            clickArea.attr('r', 20);
        }

    });

    /**
     * Then/Now slider for comparison pages
     */
    if ($('.map-wet-then-now').length) {
        selector = '.map-wet-migration .marker-svg';
        if ($(selector).length) {
            var svgWidth = 1366;
            var svgHeight = 768;
            var svg = d3.select(selector)
                .append('svg')
                .attr('width', svgWidth)
                .attr('height', svgHeight);
        }

        /**
         * Starting handle position
         */
        $('.top-image').css('width', '75%');
        var canvasHeight = $('#handle').height();
        var canvasWidth = $('#handle').width();
        var canvasLeft = ((1366 / 4) * 3) - (canvasWidth / 2);
        var canvasTop = ((768 / 2) - (canvasHeight / 2));
        $('#handle').css('top', canvasTop + 'px');
        $('#handle').css('left',  canvasLeft + 'px');

        $('.before-after-slider').mousemove(
            function(e) {
            // get the mouse x (horizontal) position and offset of the div
            var offset =  $(this).offset();
            var iTopWidth = (e.pageX - offset.left);

            // set width of bottomimage div
            $(this).find('.top-image').width(iTopWidth);
            $('#handle').css('left', iTopWidth - (canvasWidth / 2));
        }
        );
        /**
         * Draw the little handle and arrows for the top image
         */
        var handleCanvas = document.getElementById('handle');
        var context = handleCanvas.getContext('2d');

        context.beginPath();
        context.moveTo(10, ((handleCanvas.height / 2) - 10));
        context.lineTo(10, ((handleCanvas.height / 2) + 10));
        context.lineTo(0, ( handleCanvas.height / 2 ));
        context.lineTo(10, ((handleCanvas.height / 2) - 10));
        context.fill();
        var boxWidth = 5.5;
        context.rect(((handleCanvas.width / 2) - (boxWidth / 2)), (handleCanvas.height / 3), boxWidth, (handleCanvas.height / 3));
        context.fillStyle = 'white';
        context.fill();
        context.moveTo((handleCanvas.width - 10), ((handleCanvas.height / 2) - 10));
        context.lineTo((handleCanvas.width - 10), ((handleCanvas.height / 2) + 10));
        context.lineTo(handleCanvas.width, ( handleCanvas.height / 2 ));
        context.lineTo((handleCanvas.width - 10), ((handleCanvas.height / 2) - 10));
        context.fill();
        context.lineWidth = 1;
        context.strokeStyle = '#888';
        context.stroke();
    }

};

/**
 * Custom actions for page links
 */
Template.page.events({
    /**
     * Close button for popup layers
     */
    'mousedown .marker-pop-up .btn-close': function() {
        $('.marker-pop-up.popActive').removeClass('fadeInLeft popActive').addClass('animated fadeOutLeft');
        d3.selectAll('.marker-clicked').classed('marker-clicked', false);
    },

    // Swap out marker states
    'mousedown .marker-svg circle.marker-click-area': function(e) {
        var clickedImage  = $(e.currentTarget).data('marker-number');
        d3.selectAll('.marker-clicked').classed('marker-clicked', false);
        d3.selectAll('.marker-circle-' + clickedImage).classed('marker-clicked', true);
    },

    // Wetlands data toggling
    'mousedown .map-wet-wetlands .marker-svg circle.marker-click-area': function(e) {
        var clickedLeft  = $(e.currentTarget).data('dot-left');
        console.log('clickedLeft - ', clickedLeft);
        var clickedTop  = $(e.currentTarget).data('dot-top');
        console.log('clickedTop - ', clickedTop);
        var clickedImage  = $(e.currentTarget).data('marker-number');
        var clickedLabel  = $(e.currentTarget).data('marker-label');
        $('.wetland-name').remove();
        $('.map-wet-wetlands').append( '
' + clickedLabel + '
' );
        $('.wetland-name').css({
            'left': (clickedLeft + 4),
            'top': clickedTop - 50});
        console.log('clickedImage - ', clickedImage);
        console.log('clickedLabel - ', clickedLabel);
    },

    // Pacific Flyway toggling
    'mousedown .map-wet-migration .marker-svg circle.marker-click-area': function(e) {
        var clickedImage  = $(e.currentTarget).data('marker-number');
        $('.marker-pop-up.popActive').removeClass('fadeInLeft popActive').addClass('animated fadeOutLeft');
        $('.marker-pop-up.' + clickedImage ).removeClass('fadeOutLeft popActive').addClass('animated fadeInLeft popActive');
    },

    'mousedown .marker-order': function(e) {
        e.preventDefault();

        var fadeMarkersOut = function() {
            $('.clicked').siblings('canvas').removeClass('fadeMarkerIn');
            $('.clicked').siblings('canvas').addClass('animated fadeMarkerOut');
            $('.clicked').removeClass('fadeIn');
            $('.clicked').addClass('animated fadeOut');
        };

        var fadeMarkerIn = function() {
            $('.clicked').removeClass('fadeOut clicked');
            $(e.target).siblings('.marker-label').addClass('clicked animated fadeIn');
            $(e.target).siblings('canvas').addClass('animated fadeMarkerIn');
        };

        fadeMarkersOut();
        window.setTimeout(function() {
            fadeMarkerIn();
        }, 300);
    },

    /**
     * Subpage buttons
     *
     * Explore and scientists pages
     */
    'click .subpage-button': function(e) {
        e.preventDefault();
        colorPalate = this.colorPalate;
        subPageType = this.subPageType;
        link = this.link;

        /**
         * Animate the page before we leave. We're mostly removing
         * the layers on top of the map
         *
         * TODO - Make this more DRY
         */
        var animateContentOut = function() {
            if ($('.subpage-selector').length) {
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.subpage-button-1').css('-webkit-animation-delay', '.15s');
                $('.subpage-button-2').css('-webkit-animation-delay', '.1s');
                $('.subpage-button-3').css('-webkit-animation-delay', '.05s');
                $('.subpage-button-4').css('-webkit-animation-delay', '.0s');
                $('.subpage-button').addClass('animated fadeOutUp');
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.btn-home').addClass('animated fadeOutDownCustom');
            }
            else {
                $('.body-copy').addClass('animated fadeOutLeft');
                $('.container-map').addClass('animated fadeOut');
            }
        }
        animateContentOut();

        window.setTimeout(function() {
            goDestination();
        }, 600);
        function goDestination() {
            Router.go('subPage', {colorPalate: colorPalate, subPageType: subPageType, link: link});
        }
    },

    /**
     * Home button
     */
    'click .btn-home': function(e) {

        e.preventDefault();
        destination = this.colorPalate;

        /**
         * Animate the page before we leave. We're mostly removing
         * the layers on top of the map
         */
        var animateContentOut = function() {
            // Animate all overlays
            $('.container-map').addClass('animated fadeOut');
            $('.markers').addClass('animated fadeOut');
            if ($('.body-copy.horizontal').length) {
                $('.body-copy').addClass('animated fadeOutDownCustom');
            }
            else if ($('.subpage-selector').length) {
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.subpage-button-1').css('-webkit-animation-delay', '.15s');
                $('.subpage-button-2').css('-webkit-animation-delay', '.1s');
                $('.subpage-button-3').css('-webkit-animation-delay', '.05s');
                $('.subpage-button-4').css('-webkit-animation-delay', '.0s');
                $('.subpage-button').addClass('animated fadeOutUp');
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.btn-home').addClass('animated fadeOutDownCustom');
            }
            else {
                $('.body-copy').addClass('animated fadeOutLeft');
            }
        }
        animateContentOut();

        window.setTimeout(function() {
            goDestination();
        }, 600);
        function goDestination() {
            Router.go('componentPage', {link: destination});
        }
    }
});

Template.page.helpers({
    subPages: function() {
        data = Pages.find({parentId: this._id}, {sort: {order: 1}});
        if (data.count()) {
            return data;
        }
        else {
            return false;
        }
    }
});
<commit_msg>Adding animation for watersheds map<commit_after>Template.page.rendered = function() {
    /**
     * Display the mouse position for dev marker placement
     */
    $('.wetlands').click(function(e){
        var parentOffset = $(this).parent().offset();
        var relX = e.pageX - parentOffset.left;
        $('.mouse-x').text(relX);
        var relY = e.pageY - parentOffset.top;
        $('.mouse-y').text(relY);
    });

    /**
     * Animate objects once the page is rendered
     */
    $('.container-map').addClass('animated fadeIn');
    $('.markers').addClass('animated fadeIn');
    if (
        $('.body-copy.rare-climate-zone').length ||
        $('.body-copy.our-wetlands').length ||
        $('.body-copy.watersheds').length) {

        $('.body-copy').addClass('animated fadeInUp');
    }
    else {
        $('.body-copy').addClass('animated fadeInLeft');
    }
    $('.body-copy').css('-webkit-animation-duration', '400ms');
    $('.body-copy').css('animation-duration', '400ms');
    $('.subpage-button, h3').addClass('animated fadeInDown ');
    $('.subpage-button ').css('animation-duration', '300ms');
    $('.canyoneer-natural-walks').css('animation-duration', '400ms');
    $('h3').css('animation-duration', '500ms');

    /**
     * SVG Markers for Pacific Flyway
     */
    var selector;
    if ($('.map-wet-migration').length) {
        selector = '.map-wet-migration .marker-svg';
    }
    else if ($('.map-wet-wetlands')){
        selector = '.map-wet-wetlands .marker-svg';
    }

    if ($(selector).length) {
        var svgWidth = 1366;
        var svgHeight = 768;
        var svg = d3.select(selector)
            .append('svg')
            .attr('width', svgWidth)
            .attr('height', svgHeight);
    }

    $(selector).each(function() {

        // Get data about the marker from the DOM
        var centerX = $(this).data('dot-left');
        var centerY = $(this).data('dot-top');
        var arrowX = $(this).data('arrow-left');
        var arrowY = $(this).data('arrow-top');
        var markerNumber = $(this).data('marker-number');
        var markerLabel = $(this).data('marker-label');

        // Marker group
        var lineGroup = svg.append('g');

        // Pointer line
        var lineData = [ { 'x': centerX, 'y': centerY }, { 'x': arrowX,  'y': arrowY} ];
        var lineFunction = d3.svg.line()
            .x(function(d) { return d.x; })
            .y(function(d) { return d.y; })
            .interpolate('linear');
        lineGroup.append('path')
            .attr('d', lineFunction(lineData))
            .attr('class', 'marker-line');

        // Marker
        var markerCircle = svg
            .append('circle')
            .attr('r', 20)
            .attr('class', 'marker-circle marker-circle-' + markerNumber)
            .attr('cx', centerX)
            .attr('cy', centerY);

        // Text
        var textX;
        var textY;
        // One digit
        if (markerNumber.toString().length < 2) {
            textX = centerX - 6;
            textY = (centerY + 8);
        }
        // Two digits
        else {
            textX = centerX - 12;
            textY = (centerY + 8);
        }
        svg
            .append('svg:text')
            .attr('class', 'marker-numbers')
            .attr('x', textX)
            .attr('y', textY)
            .text(markerNumber);

        // Click area
        //
        // We don't use this on some maps because of overlap.
        var clickArea = svg
            .append('circle')
            .attr('fill', 'transparent')
            .attr('class', 'marker-click-area')
            .attr('data-dot-left', arrowX)
            .attr('data-dot-top', arrowY)
            .attr('data-marker-number', markerNumber)
            .attr('data-marker-label', markerLabel)
            .attr('cx', centerX)
            .attr('cy', centerY);
        if ($('.map-wet-migration').length) {
            clickArea.attr('r', 50);
        }
        else {
            clickArea.attr('r', 20);
        }

    });

    /**
     * Then/Now slider for comparison pages
     */
    if ($('.map-wet-then-now').length) {
        selector = '.map-wet-migration .marker-svg';
        if ($(selector).length) {
            var svgWidth = 1366;
            var svgHeight = 768;
            var svg = d3.select(selector)
                .append('svg')
                .attr('width', svgWidth)
                .attr('height', svgHeight);
        }

        /**
         * Starting handle position
         */
        $('.top-image').css('width', '75%');
        var canvasHeight = $('#handle').height();
        var canvasWidth = $('#handle').width();
        var canvasLeft = ((1366 / 4) * 3) - (canvasWidth / 2);
        var canvasTop = ((768 / 2) - (canvasHeight / 2));
        $('#handle').css('top', canvasTop + 'px');
        $('#handle').css('left',  canvasLeft + 'px');

        $('.before-after-slider').mousemove(
            function(e) {
            // get the mouse x (horizontal) position and offset of the div
            var offset =  $(this).offset();
            var iTopWidth = (e.pageX - offset.left);

            // set width of bottomimage div
            $(this).find('.top-image').width(iTopWidth);
            $('#handle').css('left', iTopWidth - (canvasWidth / 2));
        }
        );
        /**
         * Draw the little handle and arrows for the top image
         */
        var handleCanvas = document.getElementById('handle');
        var context = handleCanvas.getContext('2d');

        context.beginPath();
        context.moveTo(10, ((handleCanvas.height / 2) - 10));
        context.lineTo(10, ((handleCanvas.height / 2) + 10));
        context.lineTo(0, ( handleCanvas.height / 2 ));
        context.lineTo(10, ((handleCanvas.height / 2) - 10));
        context.fill();
        var boxWidth = 5.5;
        context.rect(((handleCanvas.width / 2) - (boxWidth / 2)), (handleCanvas.height / 3), boxWidth, (handleCanvas.height / 3));
        context.fillStyle = 'white';
        context.fill();
        context.moveTo((handleCanvas.width - 10), ((handleCanvas.height / 2) - 10));
        context.lineTo((handleCanvas.width - 10), ((handleCanvas.height / 2) + 10));
        context.lineTo(handleCanvas.width, ( handleCanvas.height / 2 ));
        context.lineTo((handleCanvas.width - 10), ((handleCanvas.height / 2) - 10));
        context.fill();
        context.lineWidth = 1;
        context.strokeStyle = '#888';
        context.stroke();
    }

};

/**
 * Custom actions for page links
 */
Template.page.events({
    /**
     * Close button for popup layers
     */
    'mousedown .marker-pop-up .btn-close': function() {
        $('.marker-pop-up.popActive').removeClass('fadeInLeft popActive').addClass('animated fadeOutLeft');
        d3.selectAll('.marker-clicked').classed('marker-clicked', false);
    },

    // Swap out marker states
    'mousedown .marker-svg circle.marker-click-area': function(e) {
        var clickedImage  = $(e.currentTarget).data('marker-number');
        d3.selectAll('.marker-clicked').classed('marker-clicked', false);
        d3.selectAll('.marker-circle-' + clickedImage).classed('marker-clicked', true);
    },

    // Wetlands data toggling
    'mousedown .map-wet-wetlands .marker-svg circle.marker-click-area': function(e) {
        var clickedLeft  = $(e.currentTarget).data('dot-left');
        console.log('clickedLeft - ', clickedLeft);
        var clickedTop  = $(e.currentTarget).data('dot-top');
        console.log('clickedTop - ', clickedTop);
        var clickedImage  = $(e.currentTarget).data('marker-number');
        var clickedLabel  = $(e.currentTarget).data('marker-label');
        $('.wetland-name').remove();
        $('.map-wet-wetlands').append( '
' + clickedLabel + '
' );
        $('.wetland-name').css({
            'left': (clickedLeft + 4),
            'top': clickedTop - 50});
        console.log('clickedImage - ', clickedImage);
        console.log('clickedLabel - ', clickedLabel);
    },

    // Pacific Flyway toggling
    'mousedown .map-wet-migration .marker-svg circle.marker-click-area': function(e) {
        var clickedImage  = $(e.currentTarget).data('marker-number');
        $('.marker-pop-up.popActive').removeClass('fadeInLeft popActive').addClass('animated fadeOutLeft');
        $('.marker-pop-up.' + clickedImage ).removeClass('fadeOutLeft popActive').addClass('animated fadeInLeft popActive');
    },

    'mousedown .marker-order': function(e) {
        e.preventDefault();

        var fadeMarkersOut = function() {
            $('.clicked').siblings('canvas').removeClass('fadeMarkerIn');
            $('.clicked').siblings('canvas').addClass('animated fadeMarkerOut');
            $('.clicked').removeClass('fadeIn');
            $('.clicked').addClass('animated fadeOut');
        };

        var fadeMarkerIn = function() {
            $('.clicked').removeClass('fadeOut clicked');
            $(e.target).siblings('.marker-label').addClass('clicked animated fadeIn');
            $(e.target).siblings('canvas').addClass('animated fadeMarkerIn');
        };

        fadeMarkersOut();
        window.setTimeout(function() {
            fadeMarkerIn();
        }, 300);
    },

    /**
     * Subpage buttons
     *
     * Explore and scientists pages
     */
    'click .subpage-button': function(e) {
        e.preventDefault();
        colorPalate = this.colorPalate;
        subPageType = this.subPageType;
        link = this.link;

        /**
         * Animate the page before we leave. We're mostly removing
         * the layers on top of the map
         *
         * TODO - Make this more DRY
         */
        var animateContentOut = function() {
            if ($('.subpage-selector').length) {
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.subpage-button-1').css('-webkit-animation-delay', '.15s');
                $('.subpage-button-2').css('-webkit-animation-delay', '.1s');
                $('.subpage-button-3').css('-webkit-animation-delay', '.05s');
                $('.subpage-button-4').css('-webkit-animation-delay', '.0s');
                $('.subpage-button').addClass('animated fadeOutUp');
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.btn-home').addClass('animated fadeOutDownCustom');
            }
            else {
                $('.body-copy').addClass('animated fadeOutLeft');
                $('.container-map').addClass('animated fadeOut');
            }
        }
        animateContentOut();

        window.setTimeout(function() {
            goDestination();
        }, 600);
        function goDestination() {
            Router.go('subPage', {colorPalate: colorPalate, subPageType: subPageType, link: link});
        }
    },

    /**
     * Home button
     */
    'click .btn-home': function(e) {

        e.preventDefault();
        destination = this.colorPalate;

        /**
         * Animate the page before we leave. We're mostly removing
         * the layers on top of the map
         */
        var animateContentOut = function() {
            // Animate all overlays
            $('.container-map').addClass('animated fadeOut');
            $('.markers').addClass('animated fadeOut');
            if ($('.body-copy.horizontal').length) {
                $('.body-copy').addClass('animated fadeOutDownCustom');
            }
            else if ($('.subpage-selector').length) {
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.subpage-button-1').css('-webkit-animation-delay', '.15s');
                $('.subpage-button-2').css('-webkit-animation-delay', '.1s');
                $('.subpage-button-3').css('-webkit-animation-delay', '.05s');
                $('.subpage-button-4').css('-webkit-animation-delay', '.0s');
                $('.subpage-button').addClass('animated fadeOutUp');
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.btn-home').addClass('animated fadeOutDownCustom');
            }
            else {
                $('.body-copy').addClass('animated fadeOutLeft');
            }
        }
        animateContentOut();

        window.setTimeout(function() {
            goDestination();
        }, 600);
        function goDestination() {
            Router.go('componentPage', {link: destination});
        }
    }
});

Template.page.helpers({
    subPages: function() {
        data = Pages.find({parentId: this._id}, {sort: {order: 1}});
        if (data.count()) {
            return data;
        }
        else {
            return false;
        }
    }
});
<|endoftext|>


<commit_before>const AppContext = require('../../../AppContext');

const C = require('./Countable');
const DateCountable = C.DateCountable;

const fs = require('fs');

// Max number of days that we keep history for an iterator
const MAX_HISTORY_RETAIN_DAYS = 14;

class Iterator {
    static createDateCounter(iteratorType, iteratorId, maxValue, immutable) {
        if (iteratorType === 'Linear') {
            return new Iterator(iteratorId, maxValue, DateCountable, immutable);
        } else if (iteratorType == 'Cyclic') {
            return new CyclicIterator(iteratorId, maxValue, DateCountable, immutable);
        }
    }

    constructor(iteratorId, maxValue, CountableType, immutable = true) {
        this._iteratorId = iteratorId;
        this._maxValue = maxValue;
        this._immutable = immutable;
        this._CountableType = CountableType;

        this._iteratorFilePath =
            AppContext.getInstance().CWD +
            '/run/iterator/' +
            this._iteratorId +
            '.iterator';

        this.reset();
        this._tag = this._CountableType.MinCountable;
        this._history = '';

        this.loadIterator();
    }

    loadIterator() {
        if (fs.existsSync(this._iteratorFilePath)) {
            let data = JSON.parse(fs.readFileSync(this._iteratorFilePath, 'utf-8'));

            this._iteratorPos = parseInt(data.iteratorPos);
            this._tag = new this._CountableType(data.tag);
            this._history = data.history ? data.history : '';
        }
    }

    /*
    * The tag helps us repeat the generation for the
    * same day (in case of a problem), without risking the iterator move forward
    */
    next(requesterTagValue, offset) {
        if (requesterTagValue == undefined) {
            throw Error('Tag should be provided for all counter requests');
        }

        if (!this._maxValue || this._maxValue == 0) {
            return null;
        }

        let requesterTag = new this._CountableType(requesterTagValue);

        // Easy case, same as before
        if (this._tag.equals(requesterTag)) {
            return this._iteratorPos;
        }

        if (requesterTag.diff(this._tag) > 0) {
            // New tag is ahead of us. Easy! Increase iterator and update history
            return this.doMoveAhead(requesterTag, offset);
        } else {
            // We are asking for a value from past, we must rollback
            return this.rollback(requesterTag, offset);
        }

        return null;
    }

    // Overriden in subclasses
    doMoveAhead(requesterTag, offset) {
        if (this.hasNext(offset)) {
            this._history = this.pushTagToHistory(requesterTag);
            this._iteratorPos++;
            this._tag = requesterTag;

            this.persist();

            return this.adjustByOffset(this._iteratorPos, offset);
        }
        return null;
    }

    rollback(requesterTag, offset) {
        // Here are the steps:
        // 1- Check if we have enough history to rollback
        // 2- Calculate the offset based on the history
        // 3- Adjust by offset, update values and history (obviously a cropped one now)

        let diff = this._tag.diff(requesterTag); // We expect this to be a positive int
        if (diff > this._history.length) {
            throw Error(
                `Not enough history to rollback, required ${diff}` +
                ` but history length was ${this._history.length}.`
            );
        }

        // Count number of '1's in the history substring
        let rollbackOffset = (this._history.substring(0, diff).match(/1/g) || []).length;

        // From the location of rolling back to the end of string
        this._history = this._history.substring(diff + 1);
        this._iteratorPos = this.adjustByOffset(
            this._iteratorPos,
            rollbackOffset * (-1)
        );
        this._tag = requesterTag; // Rollback the tag value as well

        if (!this.isInBounds()) {
            throw Error('Requested iterator position found not in bound ' +
                        'when rolling back. This should not happen unless underlying ' +
                        'have been changed. We cannot proceed!');
        }

        this.persist();

        if (this.isInBounds(offset)) {
            return this.adjustByOffset(this._iteratorPos, offset);
        }

        return null;
    }

    pushTagToHistory(requesterTag) {
        // We expect this number to be positive integer
        let diff = requesterTag.diff(this._tag);
        // Pad the history with zero (for tag values no one requested a value on)
        let newHistory = '1' + '0'.repeat(diff - 1) + this._history;
        // Limit the length of history we keep
        return newHistory.substring(0, MAX_HISTORY_RETAIN_DAYS);
    }

    // Overriden by subclasses
    adjustByOffset(base, offset) {
        if (offset) {
            return base + offset;
        } else {
            return base;
        }
    }

    // Overriden by subclasses
    isInBounds(offset = 0) {
        if (this._iteratorPos + offset < this._maxValue) {
            return true;
        }
        return false;
    }

    hasNext(offset = 0) {
        if (this.isInBounds(offset + 1)) {
            return true;
        }
        return false;
    }

    reset() {
        this._iteratorPos = -1;
    }

    persist() {
        if (!this._immutable) {
            let result = {
                'tag': this._tag.Value,
                'iteratorPos': this._iteratorPos,
                'history': this._history,
            };

            fs.writeFileSync(this._iteratorFilePath, JSON.stringify(result, null, 2));
        }
    }

    get Tag() {
        return this._tag;
    }

    get IteratorPos() {
        return this._iteratorPos;
    }

    get History() {
        return this._history;
    }
}

class CyclicIterator extends Iterator {
    constructor(counterId, maxValue, CountableType, immutable = true) {
        super(counterId, maxValue, CountableType, immutable);
    }

    doMoveAhead(requesterTag, offset) {
        if (this.linearIteratorHasNext()) {
            this._history = this.pushTagToHistory(requesterTag);
            this._iteratorPos++;
            this._tag = requesterTag;

            this.persist();

            return this.adjustByOffset(this._iteratorPos, offset);
        } else {
            this.reset();
            // Offset will be taken into consideration
            // after resetting the iterator
            return this.doMoveAhead(requesterTag, offset);
        }
    }

    adjustByOffset(base, offset) {
        if (offset) {
            return (base + offset) % this._maxValue;
        } else {
            return base;
        }
    }

    linearIteratorHasNext() {
        return Iterator.prototype.hasNext.call(this);
    }

    // always true unless the maxValue is not set correctly
    hasNext() {
        if (this._maxValue && this._maxValue > 0) {
            return true;
        }
    }
}

module.exports = {
    'Iterator': Iterator,
    'CyclicIterator': CyclicIterator,
};
<commit_msg>bug fix in iterator<commit_after>const AppContext = require('../../../AppContext');

const C = require('./Countable');
const DateCountable = C.DateCountable;

const fs = require('fs');

// Max number of days that we keep history for an iterator
const MAX_HISTORY_RETAIN_DAYS = 14;

class Iterator {
    static createDateCounter(iteratorType, iteratorId, maxValue, immutable) {
        if (iteratorType === 'Linear') {
            return new Iterator(iteratorId, maxValue, DateCountable, immutable);
        } else if (iteratorType == 'Cyclic') {
            return new CyclicIterator(iteratorId, maxValue, DateCountable, immutable);
        }
    }

    constructor(iteratorId, maxValue, CountableType, immutable = true) {
        this._iteratorId = iteratorId;
        this._maxValue = maxValue;
        this._immutable = immutable;
        this._CountableType = CountableType;

        this._iteratorFilePath =
            AppContext.getInstance().CWD +
            '/run/iterator/' +
            this._iteratorId +
            '.iterator';

        this.reset();
        this._tag = this._CountableType.MinCountable;
        this._history = '';

        this.loadIterator();
    }

    loadIterator() {
        if (fs.existsSync(this._iteratorFilePath)) {
            let data = JSON.parse(fs.readFileSync(this._iteratorFilePath, 'utf-8'));

            this._iteratorPos = parseInt(data.iteratorPos);
            this._tag = new this._CountableType(data.tag);
            this._history = data.history ? data.history : '';
        }
    }

    /*
    * The tag helps us repeat the generation for the
    * same day (in case of a problem), without risking the iterator move forward
    */
    next(requesterTagValue, offset) {
        if (requesterTagValue == undefined) {
            throw Error('Tag should be provided for all counter requests');
        }

        if (!this._maxValue || this._maxValue == 0) {
            return null;
        }

        let requesterTag = new this._CountableType(requesterTagValue);

        // Easy case, same as before
        if (this._tag.equals(requesterTag)) {
            return this._iteratorPos;
        }

        if (requesterTag.diff(this._tag) > 0) {
            // New tag is ahead of us. Easy! Increase iterator and update history
            return this.doMoveAhead(requesterTag, offset);
        } else {
            // We are asking for a value from past, we must rollback
            return this.rollback(requesterTag, offset);
        }

        return null;
    }

    // Overriden in subclasses
    doMoveAhead(requesterTag, offset) {
        if (this.hasNext(offset)) {
            this._history = this.pushTagToHistory(requesterTag);
            this._iteratorPos++;
            this._tag = requesterTag;

            this.persist();

            return this.adjustByOffset(this._iteratorPos, offset);
        }
        return null;
    }

    rollback(requesterTag, offset) {
        // Here are the steps:
        // 1- Check if we have enough history to rollback
        // 2- Calculate the offset based on the history
        // 3- Adjust by offset, update values and history (obviously a cropped one now)

        let diff = this._tag.diff(requesterTag); // We expect this to be a positive int
        if (diff > this._history.length) {
            throw Error(
                `Not enough history to rollback, required ${diff}` +
                ` but history length was ${this._history.length}.`
            );
        }

        // Count number of '1's in the history substring
        let rollbackOffset = (this._history.substring(0, diff).match(/1/g) || []).length;

        // From the location of rolling back to the end of string
        this._history = this._history.substring(diff);
        this._iteratorPos = this.adjustByOffset(
            this._iteratorPos,
            rollbackOffset * (-1)
        );
        this._tag = requesterTag; // Rollback the tag value as well

        if (!this.isInBounds()) {
            throw Error('Requested iterator position found not in bound ' +
                        'when rolling back. This should not happen unless underlying ' +
                        'have been changed. We cannot proceed!');
        }

        this.persist();

        if (this.isInBounds(offset)) {
            return this.adjustByOffset(this._iteratorPos, offset);
        }

        return null;
    }

    pushTagToHistory(requesterTag) {
        // We expect this number to be positive integer
        let diff = requesterTag.diff(this._tag);
        // Pad the history with zero (for tag values no one requested a value on)
        let newHistory = '1' + '0'.repeat(diff - 1) + this._history;
        // Limit the length of history we keep
        return newHistory.substring(0, MAX_HISTORY_RETAIN_DAYS);
    }

    // Overriden by subclasses
    adjustByOffset(base, offset) {
        if (offset) {
            return base + offset;
        } else {
            return base;
        }
    }

    // Overriden by subclasses
    isInBounds(offset = 0) {
        if (this._iteratorPos + offset < this._maxValue) {
            return true;
        }
        return false;
    }

    hasNext(offset = 0) {
        if (this.isInBounds(offset + 1)) {
            return true;
        }
        return false;
    }

    reset() {
        this._iteratorPos = -1;
    }

    persist() {
        if (!this._immutable) {
            let result = {
                'tag': this._tag.Value,
                'iteratorPos': this._iteratorPos,
                'history': this._history,
            };

            fs.writeFileSync(this._iteratorFilePath, JSON.stringify(result, null, 2));
        }
    }

    get Tag() {
        return this._tag;
    }

    get IteratorPos() {
        return this._iteratorPos;
    }

    get History() {
        return this._history;
    }
}

class CyclicIterator extends Iterator {
    constructor(counterId, maxValue, CountableType, immutable = true) {
        super(counterId, maxValue, CountableType, immutable);
    }

    doMoveAhead(requesterTag, offset) {
        if (this.linearIteratorHasNext()) {
            this._history = this.pushTagToHistory(requesterTag);
            this._iteratorPos++;
            this._tag = requesterTag;

            this.persist();

            return this.adjustByOffset(this._iteratorPos, offset);
        } else {
            this.reset();
            // Offset will be taken into consideration
            // after resetting the iterator
            return this.doMoveAhead(requesterTag, offset);
        }
    }

    adjustByOffset(base, offset) {
        if (offset) {
            return (base + offset) % this._maxValue;
        } else {
            return base;
        }
    }

    linearIteratorHasNext() {
        return Iterator.prototype.hasNext.call(this);
    }

    // always true unless the maxValue is not set correctly
    hasNext() {
        if (this._maxValue && this._maxValue > 0) {
            return true;
        }
    }
}

module.exports = {
    'Iterator': Iterator,
    'CyclicIterator': CyclicIterator,
};
<|endoftext|>


<commit_before>/* @flow */

import { TABLES, ROLES } from '../../lib/schema';
import { bus, cache } from '../../core-server';
import * as Constants from '../../lib/Constants';
import log from 'winston';
import User from '../../models/user';
import Counter from '../../lib/counter';
// import ThreadRel from '../../models/threadrel';
// import RoomRel from '../../models/roomrel';

bus.on('change', (changes, next) => {
	if (!changes.entities) {
		next();
		return;
	}
	const counter = new Counter();
	for (const id in changes.entities) {
		const entity = changes.entities[id];

		// 1. Increment children counts on parents
		if (
			entity.type === Constants.TYPE_TEXT ||
			entity.type === Constants.TYPE_THREAD
		) {
			if (!entity.createTime || entity.createTime !== entity.updateTime || (!entity.parents || !entity.parents.length)) {
				continue;
			}
			let inc;
			log.info('Count module reached: ', entity);
			if (entity.createTime === entity.updateTime) {
				log.info('increase count');
				inc = 1;
			} else if (entity.deleteTime) {
				inc = -1;
			}

			const parent = changes.entities[entity.parents[0]] || {};
			parent.counts = parent.counts || {};
			parent.counts.children = [ inc, '$add' ];

			parent.id = entity.parents[0];
			parent.type = (entity.type === Constants.TYPE_TEXT) ?
				Constants.TYPE_THREAD : Constants.TYPE_ROOM;

			// if (entity.type === Constants.TYPE_TEXT) {
			//
			// }
			// console.log('parents count module: ', parent);
			changes.entities[entity.parents[0]] = parent;

			// 2. Increment text/thread count of user

			const user = changes.entities[entity.creator] || new User({ id: entity.creator });

			user.counts = user.counts || {};
			user.counts[TABLES[entity.type]] = [ inc, '$add' ];
			user.id = entity.creator;
			changes.entities[entity.creator] = user;
		}

		 // 3. Increment related counts on items

		if (
			entity.type === Constants.TYPE_TEXTREL ||
			entity.type === Constants.TYPE_THREADREL ||
			entity.type === Constants.TYPE_ROOMREL ||
			entity.type === Constants.TYPE_PRIVREL ||
			entity.type === Constants.TYPE_TOPICREL
		) {
			if (!entity.id) entity.id = entity.user + '_' + entity.item;
			console.log('roomrel: , entity: ', entity);
			// if (entity.roles.length === 0) {
			// 	decrementCount(changes, entity);
			// 	continue;
			// }
			counter.inc();
			cache.getEntity(entity.id, (err, result) => {
				let exist = [], inc = 1;
				if (err) {
					counter.err(err);
					return;
				}
				console.log("result: ", result);
				if (result) {
					entity.roles.forEach((role) => {
						if (result.roles.indexOf(role) === -1) {
							exist.push(role);
						}
					});

					if (entity.roles.length === 0) {
						console.log('got roles empty');
						inc = -1;
						exist = result.roles;
					}

					if (exist.length === 0) {
						counter.dec();
						return;
					}
				} else {
					exist = entity.roles;
				}

				const item = changes.entities[entity.item] || {};

				item.counts = item.counts || {};
				// if (entity.__op__ && entity.__op__.role && entity.__op__.roles[0] === 'union') {
				// 	const rem = entity.__op__.roles[0].slice(1);
				//
				// 	rem.forEach((role) => {
				// 		if (ROLES[role]) {
				// 			item.counts[ROLES[role]] = -1;
				// 			item.counts.__op__[ROLES[role]] = 'inc';
				// 		}
				// 	});
				// }
				// console.log("exist: entity.roles: ", exist, entity.roles );
				exist.forEach((role) => {
					if (ROLES[role]) {
						item.counts[ROLES[role]] = [ inc, '$add' ];
					}
				});

				item.id = entity.item;

				switch (entity.type) {
				case Constants.TYPE_TEXTREL:
					item.type = Constants.TYPE_TEXT;
					break;
				case Constants.TYPE_THREADREL:
					item.type = Constants.TYPE_THREAD;
					break;
				case Constants.TYPE_ROOMREL:
					item.type = Constants.TYPE_ROOM;
					break;
				case Constants.TYPE_PRIVREL:
					item.type = Constants.TYPE_PRIV;
					break;
				case Constants.TYPE_TOPICREL:
					item.type = Constants.TYPE_TOPIC;
					break;
				}
				changes.entities[entity.item] = item;
				console.log("item count module: ", item);
				counter.dec();
			});
		}
	}
	counter.then(next);
}, Constants.APP_PRIORITIES.COUNT);
log.info('Count module ready.');
<commit_msg>remove console statements<commit_after>/* @flow */

import { TABLES, ROLES } from '../../lib/schema';
import { bus, cache } from '../../core-server';
import * as Constants from '../../lib/Constants';
import log from 'winston';
import User from '../../models/user';
import Counter from '../../lib/counter';
// import ThreadRel from '../../models/threadrel';
// import RoomRel from '../../models/roomrel';

bus.on('change', (changes, next) => {
	if (!changes.entities) {
		next();
		return;
	}
	const counter = new Counter();
	for (const id in changes.entities) {
		const entity = changes.entities[id];

		// 1. Increment children counts on parents
		if (
			entity.type === Constants.TYPE_TEXT ||
			entity.type === Constants.TYPE_THREAD
		) {
			if (!entity.createTime || entity.createTime !== entity.updateTime || (!entity.parents || !entity.parents.length)) {
				continue;
			}
			let inc;
			log.info('Count module reached: ', entity);
			if (entity.createTime === entity.updateTime) {
				log.info('increase count');
				inc = 1;
			} else if (entity.deleteTime) {
				inc = -1;
			}

			const parent = changes.entities[entity.parents[0]] || {};
			parent.counts = parent.counts || {};
			parent.counts.children = [ inc, '$add' ];

			parent.id = entity.parents[0];
			parent.type = (entity.type === Constants.TYPE_TEXT) ?
				Constants.TYPE_THREAD : Constants.TYPE_ROOM;

			// if (entity.type === Constants.TYPE_TEXT) {
			//
			// }
			// console.log('parents count module: ', parent);
			changes.entities[entity.parents[0]] = parent;

			// 2. Increment text/thread count of user

			const user = changes.entities[entity.creator] || new User({ id: entity.creator });

			user.counts = user.counts || {};
			user.counts[TABLES[entity.type]] = [ inc, '$add' ];
			user.id = entity.creator;
			changes.entities[entity.creator] = user;
		}

		 // 3. Increment related counts on items

		if (
			entity.type === Constants.TYPE_TEXTREL ||
			entity.type === Constants.TYPE_THREADREL ||
			entity.type === Constants.TYPE_ROOMREL ||
			entity.type === Constants.TYPE_PRIVREL ||
			entity.type === Constants.TYPE_TOPICREL
		) {
			if (!entity.id) entity.id = entity.user + '_' + entity.item;
			// console.log('roomrel: , entity: ', entity);
			// if (entity.roles.length === 0) {
			// 	decrementCount(changes, entity);
			// 	continue;
			// }
			counter.inc();
			cache.getEntity(entity.id, (err, result) => {
				let exist = [], inc = 1;
				if (err) {
					counter.err(err);
					return;
				}
				// console.log("result: ", result);
				if (result) {
					entity.roles.forEach((role) => {
						if (result.roles.indexOf(role) === -1) {
							exist.push(role);
						}
					});

					if (entity.roles.length === 0) {
						// console.log('got roles empty');
						inc = -1;
						exist = result.roles;
					}

					if (exist.length === 0) {
						counter.dec();
						return;
					}
				} else {
					exist = entity.roles;
				}

				const item = changes.entities[entity.item] || {};

				item.counts = item.counts || {};
				// if (entity.__op__ && entity.__op__.role && entity.__op__.roles[0] === 'union') {
				// 	const rem = entity.__op__.roles[0].slice(1);
				//
				// 	rem.forEach((role) => {
				// 		if (ROLES[role]) {
				// 			item.counts[ROLES[role]] = -1;
				// 			item.counts.__op__[ROLES[role]] = 'inc';
				// 		}
				// 	});
				// }
				// console.log("exist: entity.roles: ", exist, entity.roles );
				exist.forEach((role) => {
					if (ROLES[role]) {
						item.counts[ROLES[role]] = [ inc, '$add' ];
					}
				});

				item.id = entity.item;

				switch (entity.type) {
				case Constants.TYPE_TEXTREL:
					item.type = Constants.TYPE_TEXT;
					break;
				case Constants.TYPE_THREADREL:
					item.type = Constants.TYPE_THREAD;
					break;
				case Constants.TYPE_ROOMREL:
					item.type = Constants.TYPE_ROOM;
					break;
				case Constants.TYPE_PRIVREL:
					item.type = Constants.TYPE_PRIV;
					break;
				case Constants.TYPE_TOPICREL:
					item.type = Constants.TYPE_TOPIC;
					break;
				}
				changes.entities[entity.item] = item;
				console.log("item count module: ", item);
				counter.dec();
			});
		}
	}
	counter.then(next);
}, Constants.APP_PRIORITIES.COUNT);
log.info('Count module ready.');
<|endoftext|>


<commit_before>import React from 'react';
import SprkTextInput from './SprkTextInput/SprkTextInput';
import SprkInput from './SprkInput/SprkInput';
import SprkLabel from './SprkLabel/SprkLabel';
import SprkInputContainer from './SprkInputContainer/SprkInputContainer';
import SprkErrorContainer from './SprkErrorContainer/SprkErrorContainer';
import { markdownDocumentationLinkBuilder } from '../../../../storybook-utilities/markdownDocumentationLinkBuilder';

export default {
  title: 'Components/Input/Monetary',
  decorators: [(story) =>
{story()}
],
  component: SprkInput,
  parameters: {
    jest: ['SprkInputIconCheck'],
    info: `
${markdownDocumentationLinkBuilder('input')}
- The value of this field may contain special characters
(,) which you may need to remove before submitting the form.
- Requires Additional Engineering:
  - Validation - The \`valid\` prop controls this input’s
  validation state. This boolean is determined by your own
  validation logic. Validation happens before reformatting.
  - Reformatting to monetary pattern (##.##) - On blur, it
  should reformat to add commas and decimals to the hundredth
  place. Reformat user input with the \`formatter\` prop.
  This prop receives a function that takes the current value
  and returns the reformatted value.
`,
  },
};

export const monetaryInput = () => (
  <SprkInputContainer>

      <SprkLabel isMonetary>Payment</SprkLabel>
      <SprkInput
        additionalClasses="sprk-b-TextInput--has-text-icon sprk-u-Width-100"
        placeholder="0.00"
      />

  </SprkInputContainer>
);

monetaryInput.story = {
  name: 'Default',
  parameters: {
    jest: ['SprkInput', 'SprkLabel', 'SprkInputContainer'],
  },
};

export const invalidMonetaryInput = () => (
  <SprkInputContainer>

      <SprkLabel isMonetary>Payment</SprkLabel>
      <SprkInput
        additionalClasses="sprk-b-TextInput--has-text-icon sprk-u-Width-100"
        placeholder="0.00"
        isValid={false}
      />

    <SprkErrorContainer
      id="invalid-monetary"
      message="Update this story once error container is done"
    >
      There is an error on this field.
    </SprkErrorContainer>
  </SprkInputContainer>
);

invalidMonetaryInput.story = {
  name: 'Invalid',
  parameters: {
    jest: [
      'SprkInput',
      'SprkLabel',
      'SprkInputContainer',
      'SprkErrorContainer',
    ],
  },
};

export const disabledMonetaryInput = () => (
  <SprkInputContainer>

      <SprkLabel isMonetary isDisabled>
        Payment
      </SprkLabel>
      <SprkInput
        additionalClasses="sprk-b-TextInput--has-text-icon sprk-u-Width-100"
        placeholder="0.00"
        isDisabled
      />

  </SprkInputContainer>
);

disabledMonetaryInput.story = {
  name: 'Disabled',
  parameters: {
    jest: ['SprkInput', 'SprkLabel', 'SprkInputContainer'],
  },
};

export const legacyMonetaryInput = () => (
  <SprkTextInput label="Payment" textIcon name="monetary" placeholder="0.00" />
);

legacyMonetaryInput.story = {
  name: 'Legacy (Deprecated)',
  parameters: {
    jest: ['SprkTextInput'],
  },
};

export const legacyInvalidMonetaryInput = () => (
  <SprkTextInput
    label="Payment"
    textIcon
    name="monetary"
    placeholder="0.00"
    valid={false}
    errorMessage="There is an error on this field."
  />
);

legacyInvalidMonetaryInput.story = {
  name: 'Legacy Invalid (Deprecated)',
  parameters: {
    jest: ['SprkTextInput'],
  },
};

export const legacyDisabledMonetaryInput = () => (
  <SprkTextInput
    label="Payment"
    textIcon
    name="monetary"
    placeholder="0.00"
    disabled
  />
);

legacyDisabledMonetaryInput.story = {
  name: 'Legacy Disabled (Deprecated)',
  parameters: {
    jest: ['SprkTextInput'],
  },
};
<commit_msg>update monetary stories<commit_after>import React from 'react';
import SprkTextInput from './SprkTextInput/SprkTextInput';
import SprkInput from './SprkInput/SprkInput';
import SprkLabel from './SprkLabel/SprkLabel';
import SprkFieldError from './SprkFieldError/SprkFieldError';
import SprkIcon from '../../components/icons/SprkIcon';
import SprkInputContainer from './SprkInputContainer/SprkInputContainer';
import { markdownDocumentationLinkBuilder } from '../../../../storybook-utilities/markdownDocumentationLinkBuilder';

export default {
  title: 'Components/Input/Monetary',
  decorators: [(story) =>
{story()}
],
  component: SprkInput,
  parameters: {
    jest: ['SprkInput'],
    info: `
${markdownDocumentationLinkBuilder('input')}
- The value of this field may contain special characters
(,) which you may need to remove before submitting the form.
- Requires Additional Engineering:
  - Validation - The \`valid\` prop controls this input’s
  validation state. This boolean is determined by your own
  validation logic. Validation happens before reformatting.
  - Reformatting to monetary pattern (##.##) - On blur, it
  should reformat to add commas and decimals to the hundredth
  place. Reformat user input with the \`formatter\` prop.
  This prop receives a function that takes the current value
  and returns the reformatted value.
`,
  },
};

export const monetaryInput = () => (
  <SprkInputContainer>

      <SprkLabel isMonetary htmlFor="monetary-1">
        Payment
      </SprkLabel>
      <SprkInput
        id="monetary-1"
        additionalClasses="sprk-b-TextInput--has-text-icon sprk-u-Width-100"
        placeholder="0.00"
      />

  </SprkInputContainer>
);

monetaryInput.story = {
  name: 'Default',
  parameters: {
    jest: ['SprkInput', 'SprkLabel', 'SprkInputContainer'],
  },
};

export const invalidMonetaryInput = () => (
  <SprkInputContainer>

      <SprkLabel isMonetary htmlFor="monetary-2">
        Payment
      </SprkLabel>
      <SprkInput
        id="monetary-2"
        ariaDescribedBy="invalid-monetary"
        additionalClasses="sprk-b-TextInput--has-text-icon sprk-u-Width-100"
        placeholder="0.00"
        isValid={false}
      />

    <SprkFieldError id="invalid-monetary">
      <SprkIcon
        iconName="exclamation-filled"
        additionalClasses="sprk-b-ErrorIcon"
      />

There is an error on this field.

    </SprkFieldError>
  </SprkInputContainer>
);

invalidMonetaryInput.story = {
  name: 'Invalid',
  parameters: {
    jest: [
      'SprkInput',
      'SprkLabel',
      'SprkInputContainer',
      'SprkFieldError',
      'SprkIcon',
    ],
  },
};

export const disabledMonetaryInput = () => (
  <SprkInputContainer>

      <SprkLabel isMonetary isDisabled htmlFor="monetary-3">
        Payment
      </SprkLabel>
      <SprkInput
        id="monetary-3"
        additionalClasses="sprk-b-TextInput--has-text-icon sprk-u-Width-100"
        placeholder="0.00"
        isDisabled
      />

  </SprkInputContainer>
);

disabledMonetaryInput.story = {
  name: 'Disabled',
  parameters: {
    jest: ['SprkInput', 'SprkLabel', 'SprkInputContainer'],
  },
};

export const legacyMonetaryInput = () => (
  <SprkTextInput label="Payment" textIcon name="monetary" placeholder="0.00" />
);

legacyMonetaryInput.story = {
  name: 'Legacy (Deprecated)',
  parameters: {
    jest: ['SprkTextInput'],
  },
};

export const legacyInvalidMonetaryInput = () => (
  <SprkTextInput
    label="Payment"
    textIcon
    name="monetary"
    placeholder="0.00"
    valid={false}
    errorMessage="There is an error on this field."
  />
);

legacyInvalidMonetaryInput.story = {
  name: 'Legacy Invalid (Deprecated)',
  parameters: {
    jest: ['SprkTextInput'],
  },
};

export const legacyDisabledMonetaryInput = () => (
  <SprkTextInput
    label="Payment"
    textIcon
    name="monetary"
    placeholder="0.00"
    disabled
  />
);

legacyDisabledMonetaryInput.story = {
  name: 'Legacy Disabled (Deprecated)',
  parameters: {
    jest: ['SprkTextInput'],
  },
};
<|endoftext|>


<commit_before>var karyo = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null},
			'c2': {'genome_id': 1, 'length': 1000, 'seq': null}
		}
};
var filters = {'karyo': {
		'order': ['c1', 'c2'],
		'genome_order': [0, 1],
		'chromosomes': {
			'c1': {'reverse': false, 'visible': true},
			'c2': {'reverse': false, 'visible': true}
		}
	}
}
var karyo2 = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null},
			'c2': {'genome_id': 1, 'length': 1000, 'seq': null},
			'c3': {'genome_id': 1, 'length': 1000, 'seq': null}
		}
};
var filters2 = {'karyo': {
		'order': ['c1', 'c2', 'c3'],
		'genome_order': [0, 1],
		'chromosomes': {
			'c1': {'reverse': false, 'visible': true},
			'c2': {'reverse': false, 'visible': true},
			'c3': {'reverse': false, 'visible': true}
		}
	}
}
var karyo3 = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null},
			'c2': {'genome_id': 1, 'length': 1000, 'seq': null},
			'c3': {'genome_id': 2, 'length': 1000, 'seq': null}
		}
};
var filters3 = {'karyo': {
		'order': ['c1', 'c2', 'c3'],
		'genome_order': [0, 1, 2],
		'chromosomes': {
			'c1': {'reverse': false, 'visible': true},
			'c2': {'reverse': false, 'visible': true},
			'c3': {'reverse': false, 'visible': true}
		}
	}
};
var karyo4 = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null},
			'c2': {'genome_id': 1, 'length': 1000, 'seq': null},
			'c3': {'genome_id': 1, 'length': 1000, 'seq': null},
			'c4': {'genome_id': 2, 'length': 1000, 'seq': null}
		}
};
var karyo5 = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null},
			'c2': {'genome_id': 1, 'length': 1000, 'seq': null},
			'c4': {'genome_id': 2, 'length': 1000, 'seq': null}
		}
};
var karyo6 = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null}
		}
};
var filters4 = {'karyo': {
		'order': ['c1', 'c2', 'c3', 'c4'],
		'genome_order': [0, 1, 2],
		'chromosomes': {
			'c1': {'reverse': false, 'visible': null},
			'c2': {'reverse': false, 'visible': null},
			'c3': {'reverse': false, 'visible': null},
			'c4': {'reverse': false, 'visible': null}
		}
	}
};
var filters5 = {'karyo': {
	'order': ['c1', 'c2', 'c4'],
	'genome_order': [0, 1, 2],
	'chromosomes': {
		'c1': {'reverse': false, 'visible': null},
		'c2': {'reverse': false, 'visible': null},
		'c4': {'reverse': false, 'visible': null}
	}
}
};
var filters4_reverse = {'karyo': {
	'order': ['c1', 'c2', 'c3', 'c4'],
	'genome_order': [0, 1, 2],
	'chromosomes': {
		'c1': {'reverse': false, 'visible': null},
		'c2': {'reverse': true, 'visible': null},
		'c3': {'reverse': false, 'visible': null},
		'c4': {'reverse': true, 'visible': null}
	}
}
};
var filters6 = {'karyo': {
	'order': ['c1'],
	'genome_order': [0],
	'chromosomes': {
		'c1': {'reverse': false, 'visible': true}
	}
}
}
var features = {
		'f1': {'karyo': 'c1', 'start': 300, 'end': 800},
		'f2': {'karyo': 'c2', 'start': 100, 'end': 600}
};
var features2 = {
		'f1': {'karyo': 'c1', 'start': 300, 'end': 800},
		'f2': {'karyo': 'c2', 'start': 100, 'end': 600},
		'f3': {'karyo': 'c4', 'start': 400, 'end': 900},
		'f4': {'karyo': 'c3', 'start': 900, 'end': 800},
		'f5': {'karyo': 'c1', 'start': 1800, 'end': 1900}
};
var features3 = {
		'f1': {'karyo': 'c1', 'start': 300, 'end': 800},
		'f2': {'karyo': 'c2', 'start': 100, 'end': 600},
		'f3': {'karyo': 'c4', 'start': 400, 'end': 900}
};
var features4 = {
		'f1': {'karyo': 'c1', 'start': 300, 'end': 800},
		'f2': {'karyo': 'c2', 'start': 100, 'end': 600},
		'f3': {'karyo': 'c4', 'start': 400, 'end': 900},
		'f4': {'karyo': 'c1', 'start': 1800, 'end': 1900}
}
var links = {
            	 "l1": {'source': 'f1', 'target': 'f2', 'identity': 90}
			 };
var links2 = {
            	 "l1": {'source': 'f1', 'target': 'f2', 'identity': 90},
				 "l2": {'source': 'f3', 'target': 'f2', 'identity': 86}
			 };
var links3 = {
             	 "l1": {'source': 'f1', 'target': 'f2', 'identity': 90},
 				 "l2": {'source': 'f5', 'target': 'f4', 'identity': 86}
 			 };
var links4 = {
             	 "l1": {'source': 'f1', 'target': 'f2', 'identity': 90},
 				 "l2": {'source': 'f2', 'target': 'f3', 'identity': 86},
 				 "l3": {'source': 'f1', 'target': 'f3', 'identity': 94}
 			 };
var links5 = {
		"l1": {'source': 'f1', 'target': 'f2', 'identity': 90},
		"l2": {'source': 'f2', 'target': 'f3', 'identity': 86}
};
var links6 = {
		"l3": {'source': 'f1', 'target': 'f3', 'identity': 90},
		"l5": {'source': 'f4', 'target': 'f2', 'identity': 86}
};

var data = {'karyo': karyo, 'features': features, 'links': links};
var data2 = {'karyo': karyo2, 'features': features, 'links': links};
var data3 = {'karyo': karyo3, 'features': features, 'links': links};
var data4 = {'karyo': karyo4, 'features': features, 'links': links};<commit_msg>Add new test data for testing filterLinksByIdentity<commit_after>var karyo = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null},
			'c2': {'genome_id': 1, 'length': 1000, 'seq': null}
		}
};
var filters = {'karyo': {
		'order': ['c1', 'c2'],
		'genome_order': [0, 1],
		'chromosomes': {
			'c1': {'reverse': false, 'visible': true},
			'c2': {'reverse': false, 'visible': true}
		}
	}
}
var karyo2 = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null},
			'c2': {'genome_id': 1, 'length': 1000, 'seq': null},
			'c3': {'genome_id': 1, 'length': 1000, 'seq': null}
		}
};
var filters2 = {'karyo': {
		'order': ['c1', 'c2', 'c3'],
		'genome_order': [0, 1],
		'chromosomes': {
			'c1': {'reverse': false, 'visible': true},
			'c2': {'reverse': false, 'visible': true},
			'c3': {'reverse': false, 'visible': true}
		}
	}
}
var karyo3 = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null},
			'c2': {'genome_id': 1, 'length': 1000, 'seq': null},
			'c3': {'genome_id': 2, 'length': 1000, 'seq': null}
		}
};
var filters3 = {'karyo': {
		'order': ['c1', 'c2', 'c3'],
		'genome_order': [0, 1, 2],
		'chromosomes': {
			'c1': {'reverse': false, 'visible': true},
			'c2': {'reverse': false, 'visible': true},
			'c3': {'reverse': false, 'visible': true}
		}
	}
};
var karyo4 = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null},
			'c2': {'genome_id': 1, 'length': 1000, 'seq': null},
			'c3': {'genome_id': 1, 'length': 1000, 'seq': null},
			'c4': {'genome_id': 2, 'length': 1000, 'seq': null}
		}
};
var karyo5 = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null},
			'c2': {'genome_id': 1, 'length': 1000, 'seq': null},
			'c4': {'genome_id': 2, 'length': 1000, 'seq': null}
		}
};
var karyo6 = {
		'chromosomes': {
			'c1': {'genome_id': 0, 'length': 2000, 'seq': null}
		}
};
var filters4 = {'karyo': {
		'order': ['c1', 'c2', 'c3', 'c4'],
		'genome_order': [0, 1, 2],
		'chromosomes': {
			'c1': {'reverse': false, 'visible': null},
			'c2': {'reverse': false, 'visible': null},
			'c3': {'reverse': false, 'visible': null},
			'c4': {'reverse': false, 'visible': null}
		}
	}
};
var filters5 = {'karyo': {
	'order': ['c1', 'c2', 'c4'],
	'genome_order': [0, 1, 2],
	'chromosomes': {
		'c1': {'reverse': false, 'visible': null},
		'c2': {'reverse': false, 'visible': null},
		'c4': {'reverse': false, 'visible': null}
	}
}
};
var filters4_reverse = {'karyo': {
	'order': ['c1', 'c2', 'c3', 'c4'],
	'genome_order': [0, 1, 2],
	'chromosomes': {
		'c1': {'reverse': false, 'visible': null},
		'c2': {'reverse': true, 'visible': null},
		'c3': {'reverse': false, 'visible': null},
		'c4': {'reverse': true, 'visible': null}
	}
}
};
var filters6 = {'karyo': {
	'order': ['c1'],
	'genome_order': [0],
	'chromosomes': {
		'c1': {'reverse': false, 'visible': true}
	}
}
}
var features = {
		'f1': {'karyo': 'c1', 'start': 300, 'end': 800},
		'f2': {'karyo': 'c2', 'start': 100, 'end': 600}
};
var features2 = {
		'f1': {'karyo': 'c1', 'start': 300, 'end': 800},
		'f2': {'karyo': 'c2', 'start': 100, 'end': 600},
		'f3': {'karyo': 'c4', 'start': 400, 'end': 900},
		'f4': {'karyo': 'c3', 'start': 900, 'end': 800},
		'f5': {'karyo': 'c1', 'start': 1800, 'end': 1900}
};
var features3 = {
		'f1': {'karyo': 'c1', 'start': 300, 'end': 800},
		'f2': {'karyo': 'c2', 'start': 100, 'end': 600},
		'f3': {'karyo': 'c4', 'start': 400, 'end': 900}
};
var features4 = {
		'f1': {'karyo': 'c1', 'start': 300, 'end': 800},
		'f2': {'karyo': 'c2', 'start': 100, 'end': 600},
		'f3': {'karyo': 'c4', 'start': 400, 'end': 900},
		'f4': {'karyo': 'c1', 'start': 1800, 'end': 1900}
}
var links = {
            	 "l1": {'source': 'f1', 'target': 'f2', 'identity': 90}
			 };
var links2 = {
            	 "l1": {'source': 'f1', 'target': 'f2', 'identity': 90},
				 "l2": {'source': 'f3', 'target': 'f2', 'identity': 86}
			 };
var links3 = {
             	 "l1": {'source': 'f1', 'target': 'f2', 'identity': 90},
 				 "l2": {'source': 'f5', 'target': 'f4', 'identity': 86}
 			 };
var links4 = {
             	 "l1": {'source': 'f1', 'target': 'f2', 'identity': 90},
 				 "l2": {'source': 'f2', 'target': 'f3', 'identity': 86},
 				 "l3": {'source': 'f1', 'target': 'f3', 'identity': 94}
 			 };
var links5 = {
		"l1": {'source': 'f1', 'target': 'f2', 'identity': 90},
		"l2": {'source': 'f2', 'target': 'f3', 'identity': 86}
};
var links6 = {
		"l3": {'source': 'f1', 'target': 'f3', 'identity': 90},
		"l5": {'source': 'f4', 'target': 'f2', 'identity': 86}
};
var links7 = {
		"l1": {'source': 'f1', 'target': 'f3', 'identity': 40},
		"l2": {'source': 'f2', 'target': 'f3', 'identity': 50},
		"l3": {'source': 'f2', 'target': 'f4', 'identity': 60},
		"l4": {'source': 'f1', 'target': 'f4', 'identity': 70},
		"l5": {'source': 'f4', 'target': 'f2', 'identity': 90}
};

var data = {'karyo': karyo, 'features': features, 'links': links};
var data2 = {'karyo': karyo2, 'features': features, 'links': links};
var data3 = {'karyo': karyo3, 'features': features, 'links': links};
var data4 = {'karyo': karyo4, 'features': features, 'links': links};<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');

// TemplateEngine
// Copyright (C) 2013 Krasimir Tsonev
// http://krasimirtsonev.com/blog/article/Javascript-template-engine-in-just-20-line
// Released under the MIT license.
var TemplateEngine = function(text, options) {
    var re = /<%([^%>]+)?%>/g,
        reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
        code = 'var r=[];\n',
        cursor = 0,
        match;
    var add = function(line, js) {
        js ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
            (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"').replace(/\n/g, '\\n') + '");\n' : '');
        return add;
    }
    while (match = re.exec(text)) {
        add(text.slice(cursor, match.index))(match[1], true);
        cursor = match.index + match[0].length;
    }
    add(text.substr(cursor, text.length - cursor));
    code += 'return r.join("");';
    return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
}

var templates = {
    "RequireJS_config_plugins.js": '//Do not modify this file, it is automatically generated.\n' +

        'if(process._RJS_isBrowser){var p=[],c=require.config;require.config=function(k){c.apply(require,arguments);var n=k.packages;'+
        'n&&(p=n),require.config=c,window.setTimeout(function(){require(p.map(function(n){return n.name}),function(){console.log("Plugins loaded.")})},0)}}\n' +

        'require.config({packages:[\n' +

        '<%for(var i in this.plugins) {%>' +
        '{\n' +
        'name: "readium_plugin_<%this.plugins[i]%>",\n' +
        'location: process._RJS_rootDir(1) + "/plugins/<%this.plugins[i]%>",\n' +
        'main: "main"\n' +
        '},\n' +
        '<%}%>' +

        ']});\n',

    "RequireJS_config_plugins_multiple-bundles.js": '//Do not modify this file, it is automatically generated.\n' +
        'require.config({modules:[\n' +

        '<%for(var i in this.plugins) {%>' +
        '{\n' +
        'name: "readium-plugin-<%this.plugins[i]%>",\n' +
        'create: true,\n' +
        'include: ["readium_plugin_<%this.plugins[i]%>"],\n' +
        'exclude: ["readium-external-libs", "readium-cfi-js", "readium-shared-js"],\n' +
        'insertRequire: ["readium_plugin_<%this.plugins[i]%>"]\n' +
        '},\n' +
        '<%}%>' +

        ']});\n',

    "RequireJS_config_plugins_single-bundle.js": '//Do not modify this file, it is automatically generated.\n' +
        'require.config({include:[\n' +

        '<%for(var i in this.plugins) {%>' +
        '"readium_plugin_<%this.plugins[i]%>",\n' +
        '<%}%>' +

        '],insertRequire:[\n' +

        '<%for(var i in this.plugins) {%>' +
        '"readium_plugin_<%this.plugins[i]%>",\n' +
        '<%}%>' +

        ']});\n'
};

var plugins = ["annotations", "example"];

var dir = path.join(process.cwd(), 'build-config');

Object.keys(templates).forEach(function(key) {
    var filePath = path.join(dir, key);
    fs.writeFileSync(filePath, TemplateEngine(templates[key], {plugins: plugins}));
});
<commit_msg>[build-tools] Lookup which plugins need generated configs. This is done by parsing a plugins.json file in the plugins folder of readium-shared-js<commit_after>var fs = require('fs');
var path = require('path');

console.log("========>");
console.log("========> Plugins bootstrap ...");
console.log("========>");

// TemplateEngine
// Copyright (C) 2013 Krasimir Tsonev
// http://krasimirtsonev.com/blog/article/Javascript-template-engine-in-just-20-line
// Released under the MIT license.
var TemplateEngine = function(text, options) {
    var re = /<%([^%>]+)?%>/g,
        reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
        code = 'var r=[];\n',
        cursor = 0,
        match;
    var add = function(line, js) {
        js ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
            (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"').replace(/\n/g, '\\n') + '");\n' : '');
        return add;
    }
    while (match = re.exec(text)) {
        add(text.slice(cursor, match.index))(match[1], true);
        cursor = match.index + match[0].length;
    }
    add(text.substr(cursor, text.length - cursor));
    code += 'return r.join("");';
    return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
}

var templates = {
    "RequireJS_config_plugins.js": '//Do not modify this file, it is automatically generated.\n' +

        'if(process._RJS_isBrowser){var p=[],c=require.config;require.config=function(k){c.apply(require,arguments);var n=k.packages;' +
        'n&&(p=n),require.config=c,window.setTimeout(function(){require(p.map(function(n){return n.name}),function(){console.log("Plugins loaded.")})},0)}}\n' +

        'require.config({packages:[\n' +

        '<%for(var i in this.plugins) {%>' +
        '{\n' +
        'name: "readium_plugin_<%this.plugins[i]%>",\n' +
        'location: process._RJS_rootDir(1) + "/plugins/<%this.plugins[i]%>",\n' +
        'main: "main"\n' +
        '},\n' +
        '<%}%>' +

        ']});\n',

    "RequireJS_config_plugins_multiple-bundles.js": '//Do not modify this file, it is automatically generated.\n' +
        'require.config({modules:[\n' +

        '<%for(var i in this.plugins) {%>' +
        '{\n' +
        'name: "readium-plugin-<%this.plugins[i]%>",\n' +
        'create: true,\n' +
        'include: ["readium_plugin_<%this.plugins[i]%>"],\n' +
        'exclude: ["readium-external-libs", "readium-cfi-js", "readium-shared-js"],\n' +
        'insertRequire: ["readium_plugin_<%this.plugins[i]%>"]\n' +
        '},\n' +
        '<%}%>' +

        ']});\n',

    "RequireJS_config_plugins_single-bundle.js": '//Do not modify this file, it is automatically generated.\n' +
        'require.config({include:[\n' +

        '<%for(var i in this.plugins) {%>' +
        '"readium_plugin_<%this.plugins[i]%>",\n' +
        '<%}%>' +

        '],insertRequire:[\n' +

        '<%for(var i in this.plugins) {%>' +
        '"readium_plugin_<%this.plugins[i]%>",\n' +
        '<%}%>' +

        ']});\n'
};

var pluginsDir = path.join(process.cwd(), 'plugins');

var pluginsJsonPath = path.join(pluginsDir, 'plugins.json');
var pluginsJson = fs.readFileSync(pluginsJsonPath, {encoding: "utf8"});
pluginsJson = JSON.parse(pluginsJson);

var includedPlugins = pluginsJson["include"];
console.log("Included plugins: ", includedPlugins);

var excludedPlugins = pluginsJson["exclude"];
console.log("Excluded plugins: ", excludedPlugins);

includedPlugins.forEach(function(pluginName) {
    // Check for the existance of main.js inside a plugin's folder
    // This will throw an error if the path does not exist or is unaccessable
    try {
        fs.accessSync(path.join(pluginsDir, pluginName, 'main.js'));
    } catch (ex) {
        console.error('Error: Does the plugin \'' + pluginName + '\' exist?');
        throw ex;
    }
});

var dir = path.join(process.cwd(), 'build-config');

console.log("Generated plugin config files: ");

Object.keys(templates).forEach(function(key) {
    var filePath = path.join(dir, key);
    fs.writeFileSync(filePath, TemplateEngine(templates[key], {
        plugins: includedPlugins
    }));
    console.log(filePath);
});

console.log("========> End of plugins bootstrap.");
<|endoftext|>


<commit_before>/*
 * Ext JS Connect
 * Copyright(c) 2010 Sencha Inc.
 * MIT Licensed
 *
 * Modified by Yahoo!
 * Copyright (c) 2011-2012, Yahoo! Inc.  All rights reserved.
 * Yahoo! Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

/*
 * Connect staticProvider middleware adapted for Mojito *
 ********************************************************
 * This was modified to allow load all files from the
 * Mojito development environment instead of one static
 * directory.
 ********************************************************
 */

/*jslint node:true, anon:true, sloppy:true, nomen:true, todo:true */

/*
 * Module dependencies.
 */
var fs = require('fs'),
    mime = require('mime'),
    pa = require('path'),
    parseUrl = require('url').parse,
    logger,
    NAME = 'StaticHandler';

/*
 * File buffer cache.
 */
var _cache = {};

/*
 * Check if `req` and response `headers`.
 *
 * @param {IncomingMessage} req
 * @param {Object} headers
 * @return {Boolean}
 * @api private
 */
function modified(req, headers) {
    var modifiedSince = req.headers['if-modified-since'],
        lastModified = headers['Last-Modified'],
        noneMatch = req.headers['if-none-match'],
        etag = headers.ETag;

    // Check If-None-Match
    if (noneMatch && etag && noneMatch === etag) {
        return false;
    }

    // Check If-Modified-Since
    if (modifiedSince && lastModified) {
        modifiedSince = new Date(modifiedSince);
        lastModified = new Date(lastModified);
        // Ignore invalid dates
        if (!isNaN(modifiedSince.getTime())) {
            if (lastModified <= modifiedSince) {
                return false;
            }
        }
    }

    return true;
}

/*
 * Check if `req` is a conditional GET request.
 *
 * @method conditionalGET
 * @param {IncomingMessage} req
 * @return {Boolean}
 * @api private
 */
function conditionalGET(req) {
    return req.headers['if-modified-since'] ||
        req.headers['if-none-match'];
}

/*
 * Return an ETag in the form of size-mtime.
 *
 * @method etag
 * @param {Object} stat
 * @return {String}
 * @api private
 */
function etag(stat) {
    return stat.size + '-' + Number(stat.mtime);
}

/*
 * Respond with 304 "Not Modified".
 *
 * @method notModified
 * @param {ServerResponse} res
 * @param {Object} headers
 * @api private
 */
function notModified(res, headers) {
    // Strip Content-* headers
    Object.keys(headers).forEach(function(field) {
        if (0 === field.indexOf('Content')) {
            delete headers[field];
        }
    });
    res.writeHead(304, headers);
    res.end();
}

/*
 * Respond with 403 "Forbidden".
 *
 * @method forbidden
 * @param {ServerResponse} res
 * @api private
 */
function forbidden(res) {
    var body = 'Forbidden';
    res.writeHead(403, {
        'Content-Type': 'text/plain',
        'Content-Length': body.length
    });
    res.end(body);
}

/*
 * Clear the memory cache for `key` or the entire cache.
 *
 * @method clearCache
 * @param {String} key
 * @api public
 */
function clearCache(key) {
    if (key) {
        delete _cache[key];
    } else {
        _cache = {};
    }
}

/*
 * Static file server.
 *
 * Options:
 *
 *   - `root`     Root path from which to serve static files.
 *   - `maxAge`   Browser cache maxAge in milliseconds, defaults to 0
 *   - `cache`    When true cache files in memory indefinitely,
 *                until invalidated by a conditional GET request.
 *                When given, maxAge will be derived from this value.
 *
 * @param {Object} options
 * @return {Function}
 * @api public
 */
function staticProvider(store, globalLogger) {
    logger = globalLogger;
    var appConfig = store.getStaticAppConfig(),
        options = appConfig.staticHandling || {},
        cache = options.cache,
        maxAge = options.maxAge,
        urls = store.getAllURLs();

    if (cache && !maxAge) {
        maxAge = cache;
    }
    maxAge = maxAge || 0;

    return function(req, res, next) {
        if (req.method !== 'GET' && req.method !== 'HEAD') {
            return next();
        }

        var url = parseUrl(req.url),
            path = url.pathname,
            filename,
            hit,
            head = (req.method === 'HEAD');

        // TODO: [Issue 87] we should be able to just remove this, because
        // Mojito closes all bad URLs down.
        // Potentially malicious path
        if (path.indexOf('..') !== -1) {
            return forbidden(res);
        }

        logger.log('serving static path: ' + path, 'debug', 'static-handler');

        // Use the resource store as a URI "rewriter" here.
        // /favicon.ico is sent to ./my_app_folder/assets/favicon.ico
        // /robots.txt is sent to ./my_app_folder/assets/robots.txt
        filename = urls[path];
        // TODO: [Issue 80] remove this for performance
        if (!filename &&
                (path === '/favicon.ico' ||
                 path === '/robots.txt')) {
            filename = pa.join(store._config.root, 'assets', path);
        }

        if (!filename) {
            return next();
        }

        //logger.log('static GET ' + filename, 'debug', 'static-handler');

        // Cache hit
        //if (cache && !conditionalGET(req) && (hit = _cache[path])) {
        //    res.writeHead(200, hit.headers);
        //    res.end(head ? undefined : hit.body);
        //    return;
        //}

        // DEBUG -- setTimeout(function() {

        // FUTURE [Issue 89] stat cache?
        fs.stat(filename, function(err, stat) {
            if (err) {
                logger.log('err finding: ' + filename, 'warn', NAME);
                // TODO: [Issue 90] send next an error?
                return next();
            }
            if (stat.isDirectory()) {
                // TODO: [Issue 90] send next an error?
                return next();
            }

            // Serve the file directly using buffers
            function onRead(err, data) {
                if (err) {
                    logger.log('NOT FOUND: ' + filename, 'warn');
                    return next(err);
                }
                var mimetype = mime.lookup(filename),
                    charset = mime.charsets.lookup(mimetype),
                    // Response headers
                    headers = {
                        'Content-Type': mimetype + (charset ? '; charset=' +
                            charset : ''),
                        'Content-Length': stat.size,
                        'Last-Modified': options.forceUpdate ? new Date().toUTCString() : stat.ctime.toUTCString(),
                        'Cache-Control': 'public max-age=' + (maxAge / 1000),
                        'ETag': etag(stat)
                    };

                if (!options.forceUpdate && !modified(req, headers)) {
                    return notModified(res, headers);
                }

                // logger.log('(staticProvider) serving static file: ' +
                //     filename);
                res.writeHead(200, headers);
                res.end(head ? undefined : data);

                // TODO: [Issue 92] This is not even being used here...
                // remove and revisit (the _cache use is commented out below)
                // Cache support
                if (cache) {
                    _cache[path] = {
                        headers: headers,
                        body: data
                    };
                }
            }
            fs.readFile(filename, onRead);
        });

        // DEBUG -- }, 500);

    };
}

/**
 * Export function to create the static handler.
 * @param {Object} config The configuration data for the handler.
 * @return {Object} A static handler.
 */
module.exports = function(config) {
    return staticProvider(config.store, config.logger);
};
<commit_msg>Added support for crossdomain.xml file<commit_after>/*
 * Ext JS Connect
 * Copyright(c) 2010 Sencha Inc.
 * MIT Licensed
 *
 * Modified by Yahoo!
 * Copyright (c) 2011-2012, Yahoo! Inc.  All rights reserved.
 * Yahoo! Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

/*
 * Connect staticProvider middleware adapted for Mojito *
 ********************************************************
 * This was modified to allow load all files from the
 * Mojito development environment instead of one static
 * directory.
 ********************************************************
 */

/*jslint node:true, anon:true, sloppy:true, nomen:true, todo:true */

/*
 * Module dependencies.
 */
var fs = require('fs'),
    mime = require('mime'),
    pa = require('path'),
    parseUrl = require('url').parse,
    logger,
    NAME = 'StaticHandler';

/*
 * File buffer cache.
 */
var _cache = {};

/*
 * Check if `req` and response `headers`.
 *
 * @param {IncomingMessage} req
 * @param {Object} headers
 * @return {Boolean}
 * @api private
 */
function modified(req, headers) {
    var modifiedSince = req.headers['if-modified-since'],
        lastModified = headers['Last-Modified'],
        noneMatch = req.headers['if-none-match'],
        etag = headers.ETag;

    // Check If-None-Match
    if (noneMatch && etag && noneMatch === etag) {
        return false;
    }

    // Check If-Modified-Since
    if (modifiedSince && lastModified) {
        modifiedSince = new Date(modifiedSince);
        lastModified = new Date(lastModified);
        // Ignore invalid dates
        if (!isNaN(modifiedSince.getTime())) {
            if (lastModified <= modifiedSince) {
                return false;
            }
        }
    }

    return true;
}

/*
 * Check if `req` is a conditional GET request.
 *
 * @method conditionalGET
 * @param {IncomingMessage} req
 * @return {Boolean}
 * @api private
 */
function conditionalGET(req) {
    return req.headers['if-modified-since'] ||
        req.headers['if-none-match'];
}

/*
 * Return an ETag in the form of size-mtime.
 *
 * @method etag
 * @param {Object} stat
 * @return {String}
 * @api private
 */
function etag(stat) {
    return stat.size + '-' + Number(stat.mtime);
}

/*
 * Respond with 304 "Not Modified".
 *
 * @method notModified
 * @param {ServerResponse} res
 * @param {Object} headers
 * @api private
 */
function notModified(res, headers) {
    // Strip Content-* headers
    Object.keys(headers).forEach(function(field) {
        if (0 === field.indexOf('Content')) {
            delete headers[field];
        }
    });
    res.writeHead(304, headers);
    res.end();
}

/*
 * Respond with 403 "Forbidden".
 *
 * @method forbidden
 * @param {ServerResponse} res
 * @api private
 */
function forbidden(res) {
    var body = 'Forbidden';
    res.writeHead(403, {
        'Content-Type': 'text/plain',
        'Content-Length': body.length
    });
    res.end(body);
}

/*
 * Clear the memory cache for `key` or the entire cache.
 *
 * @method clearCache
 * @param {String} key
 * @api public
 */
function clearCache(key) {
    if (key) {
        delete _cache[key];
    } else {
        _cache = {};
    }
}

/*
 * Static file server.
 *
 * Options:
 *
 *   - `root`     Root path from which to serve static files.
 *   - `maxAge`   Browser cache maxAge in milliseconds, defaults to 0
 *   - `cache`    When true cache files in memory indefinitely,
 *                until invalidated by a conditional GET request.
 *                When given, maxAge will be derived from this value.
 *
 * @param {Object} options
 * @return {Function}
 * @api public
 */
function staticProvider(store, globalLogger) {
    logger = globalLogger;
    var appConfig = store.getStaticAppConfig(),
        options = appConfig.staticHandling || {},
        cache = options.cache,
        maxAge = options.maxAge,
        urls = store.getAllURLs();

    if (cache && !maxAge) {
        maxAge = cache;
    }
    maxAge = maxAge || 0;

    return function(req, res, next) {
        if (req.method !== 'GET' && req.method !== 'HEAD') {
            return next();
        }

        var url = parseUrl(req.url),
            path = url.pathname,
            filename,
            hit,
            head = (req.method === 'HEAD');

        // TODO: [Issue 87] we should be able to just remove this, because
        // Mojito closes all bad URLs down.
        // Potentially malicious path
        if (path.indexOf('..') !== -1) {
            return forbidden(res);
        }

        logger.log('serving static path: ' + path, 'debug', 'static-handler');

        // Use the resource store as a URI "rewriter" here.
        // /favicon.ico is sent to ./my_app_folder/assets/favicon.ico
        // /robots.txt is sent to ./my_app_folder/assets/robots.txt
        // /crossdomain.xml is sent to ./my_app_folder/assets/crossdomain.xml
        filename = urls[path];
        // TODO: [Issue 80] remove this for performance
        if (!filename &&
                (path === '/favicon.ico' ||
                 path === '/robots.txt'  ||
                 path === '/crossdomain.xml')) {
            filename = pa.join(store._config.root, 'assets', path);
        }

        if (!filename) {
            return next();
        }

        //logger.log('static GET ' + filename, 'debug', 'static-handler');

        // Cache hit
        //if (cache && !conditionalGET(req) && (hit = _cache[path])) {
        //    res.writeHead(200, hit.headers);
        //    res.end(head ? undefined : hit.body);
        //    return;
        //}

        // DEBUG -- setTimeout(function() {

        // FUTURE [Issue 89] stat cache?
        fs.stat(filename, function(err, stat) {
            if (err) {
                logger.log('err finding: ' + filename, 'warn', NAME);
                // TODO: [Issue 90] send next an error?
                return next();
            }
            if (stat.isDirectory()) {
                // TODO: [Issue 90] send next an error?
                return next();
            }

            // Serve the file directly using buffers
            function onRead(err, data) {
                if (err) {
                    logger.log('NOT FOUND: ' + filename, 'warn');
                    return next(err);
                }
                var mimetype = mime.lookup(filename),
                    charset = mime.charsets.lookup(mimetype),
                    // Response headers
                    headers = {
                        'Content-Type': mimetype + (charset ? '; charset=' +
                            charset : ''),
                        'Content-Length': stat.size,
                        'Last-Modified': options.forceUpdate ? new Date().toUTCString() : stat.ctime.toUTCString(),
                        'Cache-Control': 'public max-age=' + (maxAge / 1000),
                        'ETag': etag(stat)
                    };

                if (!options.forceUpdate && !modified(req, headers)) {
                    return notModified(res, headers);
                }

                // logger.log('(staticProvider) serving static file: ' +
                //     filename);
                res.writeHead(200, headers);
                res.end(head ? undefined : data);

                // TODO: [Issue 92] This is not even being used here...
                // remove and revisit (the _cache use is commented out below)
                // Cache support
                if (cache) {
                    _cache[path] = {
                        headers: headers,
                        body: data
                    };
                }
            }
            fs.readFile(filename, onRead);
        });

        // DEBUG -- }, 500);

    };
}

/**
 * Export function to create the static handler.
 * @param {Object} config The configuration data for the handler.
 * @return {Object} A static handler.
 */
module.exports = function(config) {
    return staticProvider(config.store, config.logger);
};
<|endoftext|>


<commit_before>Bloob.BodyToJsonConverter = function() {};
Bloob.BodyToJsonConverter.bodyToJson = function(body) {
	return body.toJson();

	// first, use default blueprint
	resultJson.bluePrints.defaultBluePrint = {
		"class": "Body"
	};

	var world = map.world;
	for(var bodyIndex in world.mBodies) {
		var body = world.mBodies[bodyIndex];

		// generate base shape
		var shapeId = shapeIdGenerator.nextId();

		// build body information
		var bodyId = bodyIdGenerator.nextId();
		resultJson.bodies[bodyId] = {
			"shape": shapeId,
			"pointMasses": 0,
			"translate": body.mDerivedPos.copy(),
			"rotate": body.mDerivedAngle,
			"scale": {"x": 9, "y": 1.5},
			"isKinematic": body.mKinematic
		};
	}

	return resultJson;
};

Body.prototype.toJson = function() {
	return {
		"class": "Body",
		"shape": "i",
		"pointMasses": 1,
		"translate": {"x": 0, "y": 4},
		"rotate": 0,
		"isKinematic": false
	};
};

SpringBody.prototype.toJson = function() {
	var json = Body.prototype.toJson.apply(this, arguments);
	json.class = "SpringBody";
	json.edgeSpringK = 150.0;
	json.edgeSpringDamp = 5.0;
	json.shapeSpringK = 300.0;
	json.shapeSpringDamp = 15.0;
	json.internalSprings = [
		{
			"pointA": 0,
			"pointB": 14,
			"springK": 300,
			"damping": 10
		},
		{
			"pointA": 1,
			"pointB": 14,
			"springK": 300.0,
			"damping": 10.0
		}
	];
	return json;
};

PressureBody.prototype.toJson = function() {
	var json = SpringBody.prototype.toJson.apply(this, arguments);
	json.class = "PressureBody";
	json.gasPressure = 0.0;
	return json;
};
<commit_msg>convert real gasPressure<commit_after>Bloob.BodyToJsonConverter = function() {};
Bloob.BodyToJsonConverter.bodyToJson = function(body) {
	return body.toJson();

	// first, use default blueprint
	resultJson.bluePrints.defaultBluePrint = {
		"class": "Body"
	};

	var world = map.world;
	for(var bodyIndex in world.mBodies) {
		var body = world.mBodies[bodyIndex];

		// generate base shape
		var shapeId = shapeIdGenerator.nextId();

		// build body information
		var bodyId = bodyIdGenerator.nextId();
		resultJson.bodies[bodyId] = {
			"shape": shapeId,
			"pointMasses": 0,
			"translate": body.mDerivedPos.copy(),
			"rotate": body.mDerivedAngle,
			"scale": {"x": 9, "y": 1.5},
			"isKinematic": body.mKinematic
		};
	}

	return resultJson;
};

Body.prototype.toJson = function() {
	return {
		"class": "Body",
		"shape": "i",
		"pointMasses": 1,
		"translate": {"x": 0, "y": 4},
		"rotate": 0,
		"isKinematic": false
	};
};

SpringBody.prototype.toJson = function() {
	var json = Body.prototype.toJson.apply(this, arguments);
	json.class = "SpringBody";
	json.edgeSpringK = 150.0;
	json.edgeSpringDamp = 5.0;
	json.shapeSpringK = 300.0;
	json.shapeSpringDamp = 15.0;
	json.internalSprings = [
		{
			"pointA": 0,
			"pointB": 14,
			"springK": 300,
			"damping": 10
		},
		{
			"pointA": 1,
			"pointB": 14,
			"springK": 300.0,
			"damping": 10.0
		}
	];
	return json;
};

PressureBody.prototype.toJson = function() {
	var json = SpringBody.prototype.toJson.apply(this, arguments);

	// adjustments for pressure bodies
	json.class = "PressureBody";
	json.gasPressure = this.getGasPressure();

	return json;
};
<|endoftext|>