<commit_before>/**
 * sliphover v2.0.5
 * require jquery 1.7+
 * MIT License
 * for more info pls visit :https://github.com/wayou/SlipHover
 */

;
(function($, window, document, undefined) {

    // Create the defaults once
    var pluginName = "sliphover",
        defaults = {
            target: 'img', //the element that the overlay will attach to
            caption: 'title', //the caption that will display when hover
            duration: 'fast', //specify how long the animation will lasts in milliseconds
            fontColor: '#fff',
            textAlign: 'center', //display the caption left, center or right
            verticalMiddle: true, //display the caption vertical middle or not
            backgroundColor: 'rgba(0,0,0,.7)', //specify the background color and opacity using rgba
            backgroundColorAttr: null, //specify the attribute with background color value and opacity using rgba
            reverse: false, //reverse the direction
            height: '100%', //specify the height of the overlay
            withLink: true //if image is wraped with a link the overlay will be clickable, set false to disable click
        };

    function SlipHover(element, options) {
        this.element = element;
        this.settings = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;
        this.version = 'v2.0.5';
        this.init();
    }

    SlipHover.prototype = {
        init: function() {

            //disable on touch devices
            if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
                return;
            }

            var that = this,
                target = this.settings.target;

            //create the overlay container each time the mouse enters
            $(this.element).off('mouseenter.sliphover', target).on('mouseenter.sliphover', target, function(event) {
                //fix #9 https://github.com/wayou/SlipHover/issues/9
                //use this instead of event.target for sometimes the event.target is not retriving the right target we want
                //http://stackoverflow.com/questions/9838137/event-target-jquery-on-mousedown-up-is-not-giving-the-dom-specified-by-selecto
                var $element = $(this),
                    $overlayContainer = that.createContainer($element);

                $overlayContainer.off('mouseenter.sliphover mouseleave.sliphover').on('mouseenter.sliphover mouseleave.sliphover', function(event) {
                    //if (!$overlayContainer) return;
                    var direction = that.getDirection($(this), event);

                    //check if reverse option is on
                    direction = that.settings.reverse ? direction = (direction + 2) % 4 : direction;

                    if (event.type === 'mouseenter') {
                        //check if the previous overlay still exists before we create it
                        var $overlay = $overlayContainer.find('.sliphover-overlay');
                        if (!$overlay.length) {
                            $overlay = that.createOverlay(that, direction, $element);
                            //put the overlay into the container
                            $(this).html($overlay);
                        }
                        that.slideIn(that, $overlay);
                    } else {
                        //slide out based on the direction
                        that.removeOverlay(that, $(this), direction);
                    }
                });
            });
        },
        createContainer: function($element) {
            //get the properties of the target
            var top = $element.offset().top,
                left = $element.offset().left,
                //border = parseFloat($element.css("border-left-width")),
                width = $element.outerWidth(),
                height = $element.outerHeight();
            zIndex = $element.css("z-index");
            var $overlayContainer = $('<div>', {
                class: 'sliphover-container'
            }).css({
                pointerEvent: 'none',
                width: width,
                height: height,
                position: 'absolute',
                overflow: 'hidden',
                top: top,
                left: left,
                borderRadius: $element.css('border-radius'), //in case the target has a round border, this will make the overlay more nicer
                zIndex: zIndex == +zIndex ? (zIndex + 1) : 999 // if the z-index of the target is not number then use 999
            });

            $('body').append($overlayContainer);

            return $overlayContainer;
        },
        createOverlay: function(instance, direction, $element) {

            var bottom, left, $overlay, $overlayColor, content, $targetAParent;

            switch (direction) {
                case 0:
                    //from top
                    left = 0;
                    bottom = '100%';
                    break;
                case 1:
                    //from right
                    left = '100%';
                    bottom = 0;
                    break;
                case 2:
                    //from bottom
                    left = 0;
                    bottom = '-100%';
                    break;
                case 3:
                    //from left
                    left = '-100%';
                    bottom = 0;
                    break;
                default:
                    window.console.error('error when get direction of the mouse');
            };

            //if we want to display content vertical middle, we need to wrap the content into a div and set the style with display table-cell
            if (instance.settings.verticalMiddle) {
                content = $('<div>').css({
                    display: 'table-cell',
                    verticalAlign: 'middle'
                }).html($element.attr(instance.settings.caption));
            } else {
                content = $element.attr(instance.settings.caption);
            }
            $targetAParent = $element.parent('a');
            if ($targetAParent.length && instance.settings.withLink) {
                var url = $targetAParent.attr('href'),
                target=$targetAParent.attr('target');//fix issue#17
                $overlay = $('<a>', {
                    class: 'sliphover-overlay',
                    href: url || '#',
                    target: target||'_self'
                }).css({
                    textDecoration: 'none'
                });
            } else {
                $overlay = $('<div>', {
                    class: 'sliphover-overlay'
                });
            }

            $overlayColor = instance.settings.backgroundColorAttr ?
              $element.attr(instance.settings.backgroundColorAttr) : instance.settings.backgroundColor;

            $overlay.css({
                width: '100%',
                height: instance.settings.height,
                position: 'absolute',
                left: left,
                bottom: bottom,
                display: instance.settings.verticalMiddle ? 'table' : 'inline',
                textAlign: instance.settings.textAlign,
                color: instance.settings.fontColor,
                backgroundColor: $overlayColor
            })
                .html(content);
            return $overlay;
        },
        slideIn: function(instance, $overlay) {
            $overlay.stop().animate({
                left: 0,
                bottom: 0
            }, instance.settings.duration);
        },
        removeOverlay: function(instance, $overlayContainer, direction) {
            var finalState,
                $overlay = $overlayContainer.find('.sliphover-overlay');

            switch (direction) {
                case 0: //to top
                    finalState = {
                        bottom: '100%',
                        left: 0
                    };
                    break;
                case 1: //to right
                    finalState = {
                        bottom: 0,
                        left: '100%'
                    };
                    break;
                case 2: //to bottom
                    finalState = {
                        bottom: '-100%',
                        left: 0
                    };
                    break;
                case 3: //to left
                    finalState = {
                        bottom: 0,
                        left: '-100%'
                    };
                    break;
                default:
                    window.console.error('error when get direction of the mouse');
            };

            //slide out
            $overlay.stop().animate(finalState, instance.settings.duration, function() {
                $overlayContainer.remove();
            });
        },
        getDirection: function($target, event) {
            //reference: http://stackoverflow.com/questions/3627042/jquery-animation-for-a-hover-with-mouse-direction
            var w = $target.width(),
                h = $target.height(),
                x = (event.pageX - $target.offset().left - (w / 2)) * (w > h ? (h / w) : 1),
                y = (event.pageY - $target.offset().top - (h / 2)) * (h > w ? (w / h) : 1),
                direction = Math.round((((Math.atan2(y, x) * (180 / Math.PI)) + 180) / 90) + 3) % 4;
            return direction;
        }
    };

    $.fn[pluginName] = function(options) {
        this.each(function() {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new SlipHover(this, options));
            }
        });

        // chain jQuery functions
        return this;
    };

})(jQuery, window, document);
<commit_msg>update the version<commit_after>/**
 * sliphover v2.0.6
 * require jquery 1.7+
 * MIT License
 * for more info pls visit :https://github.com/wayou/SlipHover
 */

;
(function($, window, document, undefined) {

    // Create the defaults once
    var pluginName = "sliphover",
        defaults = {
            target: 'img', //the element that the overlay will attach to
            caption: 'title', //the caption that will display when hover
            duration: 'fast', //specify how long the animation will lasts in milliseconds
            fontColor: '#fff',
            textAlign: 'center', //display the caption left, center or right
            verticalMiddle: true, //display the caption vertical middle or not
            backgroundColor: 'rgba(0,0,0,.7)', //specify the background color and opacity using rgba
            backgroundColorAttr: null, //specify the attribute with background color value and opacity using rgba
            reverse: false, //reverse the direction
            height: '100%', //specify the height of the overlay
            withLink: true //if image is wraped with a link the overlay will be clickable, set false to disable click
        };

    function SlipHover(element, options) {
        this.element = element;
        this.settings = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;
        this.version = 'v2.0.5';
        this.init();
    }

    SlipHover.prototype = {
        init: function() {

            //disable on touch devices
            if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
                return;
            }

            var that = this,
                target = this.settings.target;

            //create the overlay container each time the mouse enters
            $(this.element).off('mouseenter.sliphover', target).on('mouseenter.sliphover', target, function(event) {
                //fix #9 https://github.com/wayou/SlipHover/issues/9
                //use this instead of event.target for sometimes the event.target is not retriving the right target we want
                //http://stackoverflow.com/questions/9838137/event-target-jquery-on-mousedown-up-is-not-giving-the-dom-specified-by-selecto
                var $element = $(this),
                    $overlayContainer = that.createContainer($element);

                $overlayContainer.off('mouseenter.sliphover mouseleave.sliphover').on('mouseenter.sliphover mouseleave.sliphover', function(event) {
                    //if (!$overlayContainer) return;
                    var direction = that.getDirection($(this), event);

                    //check if reverse option is on
                    direction = that.settings.reverse ? direction = (direction + 2) % 4 : direction;

                    if (event.type === 'mouseenter') {
                        //check if the previous overlay still exists before we create it
                        var $overlay = $overlayContainer.find('.sliphover-overlay');
                        if (!$overlay.length) {
                            $overlay = that.createOverlay(that, direction, $element);
                            //put the overlay into the container
                            $(this).html($overlay);
                        }
                        that.slideIn(that, $overlay);
                    } else {
                        //slide out based on the direction
                        that.removeOverlay(that, $(this), direction);
                    }
                });
            });
        },
        createContainer: function($element) {
            //get the properties of the target
            var top = $element.offset().top,
                left = $element.offset().left,
                //border = parseFloat($element.css("border-left-width")),
                width = $element.outerWidth(),
                height = $element.outerHeight();
            zIndex = $element.css("z-index");
            var $overlayContainer = $('<div>', {
                class: 'sliphover-container'
            }).css({
                pointerEvent: 'none',
                width: width,
                height: height,
                position: 'absolute',
                overflow: 'hidden',
                top: top,
                left: left,
                borderRadius: $element.css('border-radius'), //in case the target has a round border, this will make the overlay more nicer
                zIndex: zIndex == +zIndex ? (zIndex + 1) : 999 // if the z-index of the target is not number then use 999
            });

            $('body').append($overlayContainer);

            return $overlayContainer;
        },
        createOverlay: function(instance, direction, $element) {

            var bottom, left, $overlay, $overlayColor, content, $targetAParent;

            switch (direction) {
                case 0:
                    //from top
                    left = 0;
                    bottom = '100%';
                    break;
                case 1:
                    //from right
                    left = '100%';
                    bottom = 0;
                    break;
                case 2:
                    //from bottom
                    left = 0;
                    bottom = '-100%';
                    break;
                case 3:
                    //from left
                    left = '-100%';
                    bottom = 0;
                    break;
                default:
                    window.console.error('error when get direction of the mouse');
            };

            //if we want to display content vertical middle, we need to wrap the content into a div and set the style with display table-cell
            if (instance.settings.verticalMiddle) {
                content = $('<div>').css({
                    display: 'table-cell',
                    verticalAlign: 'middle'
                }).html($element.attr(instance.settings.caption));
            } else {
                content = $element.attr(instance.settings.caption);
            }
            $targetAParent = $element.parent('a');
            if ($targetAParent.length && instance.settings.withLink) {
                var url = $targetAParent.attr('href'),
                target=$targetAParent.attr('target');//fix issue#17
                $overlay = $('<a>', {
                    class: 'sliphover-overlay',
                    href: url || '#',
                    target: target||'_self'
                }).css({
                    textDecoration: 'none'
                });
            } else {
                $overlay = $('<div>', {
                    class: 'sliphover-overlay'
                });
            }

            $overlayColor = instance.settings.backgroundColorAttr ?
              $element.attr(instance.settings.backgroundColorAttr) : instance.settings.backgroundColor;

            $overlay.css({
                width: '100%',
                height: instance.settings.height,
                position: 'absolute',
                left: left,
                bottom: bottom,
                display: instance.settings.verticalMiddle ? 'table' : 'inline',
                textAlign: instance.settings.textAlign,
                color: instance.settings.fontColor,
                backgroundColor: $overlayColor
            })
                .html(content);
            return $overlay;
        },
        slideIn: function(instance, $overlay) {
            $overlay.stop().animate({
                left: 0,
                bottom: 0
            }, instance.settings.duration);
        },
        removeOverlay: function(instance, $overlayContainer, direction) {
            var finalState,
                $overlay = $overlayContainer.find('.sliphover-overlay');

            switch (direction) {
                case 0: //to top
                    finalState = {
                        bottom: '100%',
                        left: 0
                    };
                    break;
                case 1: //to right
                    finalState = {
                        bottom: 0,
                        left: '100%'
                    };
                    break;
                case 2: //to bottom
                    finalState = {
                        bottom: '-100%',
                        left: 0
                    };
                    break;
                case 3: //to left
                    finalState = {
                        bottom: 0,
                        left: '-100%'
                    };
                    break;
                default:
                    window.console.error('error when get direction of the mouse');
            };

            //slide out
            $overlay.stop().animate(finalState, instance.settings.duration, function() {
                $overlayContainer.remove();
            });
        },
        getDirection: function($target, event) {
            //reference: http://stackoverflow.com/questions/3627042/jquery-animation-for-a-hover-with-mouse-direction
            var w = $target.width(),
                h = $target.height(),
                x = (event.pageX - $target.offset().left - (w / 2)) * (w > h ? (h / w) : 1),
                y = (event.pageY - $target.offset().top - (h / 2)) * (h > w ? (w / h) : 1),
                direction = Math.round((((Math.atan2(y, x) * (180 / Math.PI)) + 180) / 90) + 3) % 4;
            return direction;
        }
    };

    $.fn[pluginName] = function(options) {
        this.each(function() {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new SlipHover(this, options));
            }
        });

        // chain jQuery functions
        return this;
    };

})(jQuery, window, document);
<|endoftext|>


<commit_before>(function(window)
{
    'use strict';
    class IMU
    {
        constructor( cockpit )
        {
            console.log('IMU Plugin running');

            var self = this;
            self.cockpit = cockpit;

            this.actions =
            {
                'plugin.imu.zeroYaw':
                {
                    description: 'Zero yaw/heading when in gyro mode',
                    controls:
                    {
                        button:
                        {
                            down: function()
                            {
                                self.zeroYaw();
                            }
                        }
                    }
                }
            };

            // Setup input handlers
            this.inputDefaults =
            {
                keyboard:
                {
                    "y": { type: "button", action: 'plugin.imu.zeroYaw' }
                }
            };
        };

        zeroRollPitch()
        {
            this.cockpit.rov.emit( "plugin.imu.zeroRollPitch" );
        }

        zeroYaw()
        {
            this.cockpit.rov.emit( "plugin.imu.zeroYaw" );
        }

        getTelemetryDefinitions()
        {
            return [];
        };

        listen()
        {
            var self = this;

            // zeroRollPitch
            this.cockpit.on('plugin.imu.zeroRollPitch', function()
            {
                self.zeroRollPitch();
            });

            // stepNegative
            this.cockpit.on('plugin.imu.zeroYaw', function()
            {
                self.zeroYaw();
            });
        };
    };

    var plugins = namespace('plugins');
    plugins.IMU = IMU;
    window.Cockpit.plugins.push( plugins.IMU );

}(window));<commit_msg>Changed zeroYaw key to alt+y<commit_after>(function(window)
{
    'use strict';
    class IMU
    {
        constructor( cockpit )
        {
            console.log('IMU Plugin running');

            var self = this;
            self.cockpit = cockpit;

            this.actions =
            {
                'plugin.imu.zeroYaw':
                {
                    description: 'Zero yaw/heading when in gyro mode',
                    controls:
                    {
                        button:
                        {
                            down: function()
                            {
                                self.zeroYaw();
                            }
                        }
                    }
                }
            };

            // Setup input handlers
            this.inputDefaults =
            {
                keyboard:
                {
                    "alt+y": { type: "button", action: 'plugin.imu.zeroYaw' }
                }
            };
        };

        zeroRollPitch()
        {
            this.cockpit.rov.emit( "plugin.imu.zeroRollPitch" );
        }

        zeroYaw()
        {
            this.cockpit.rov.emit( "plugin.imu.zeroYaw" );
        }

        getTelemetryDefinitions()
        {
            return [];
        };

        listen()
        {
            var self = this;

            // zeroRollPitch
            this.cockpit.on('plugin.imu.zeroRollPitch', function()
            {
                self.zeroRollPitch();
            });

            // zeroYaw
            this.cockpit.on('plugin.imu.zeroYaw', function()
            {
                self.zeroYaw();
            });
        };
    };

    var plugins = namespace('plugins');
    plugins.IMU = IMU;
    window.Cockpit.plugins.push( plugins.IMU );

}(window));<|endoftext|>


<commit_before>// Add persistent bindings between a selector and a model value.  Changes are propagated between the two.
//
// To use this, you should have a "bindings" element in your fluid defaults, as in:
//      bindings: [{
//          selector:    "input",
//          path:        "input"
//      }]
//
//
// The options are as follows:
// * selector:    A valid selector for your component.
//                Must be able to be resolved using that.locate(selector)
//
// * path:        A valid path for the model variable whose value will be watched.
//                Must be able to be resolved using fluid.get(path)
//
// To make use of this, you will need to start by sourcing this file after all fluid sources.
//
// In your component, you will need a valid selector, as in:
//
//  selectors: {
//      "input":    ".ptd-search-input"
//  }
//
// You will also need a valid model path, as in:
//
//  model: {
//      input:  ""
//  }
//
// Finally, you will need to invoke the binding applier where it makes sense, for example in response to an event.
// Here's an example that binds to the "onCreate" event:
//
//  listeners: {
//      onCreate: {
//        "funcName": "ctr.components.binder.applyBinding",
//        "args":     "{that}"
//      }
//  }
//
// Once you have done this, model changes should be passed to form controls and vice versa.
//
// Note that if you reload markup or otherwise generate new markup, you will need to reapply the set of bindings.
// The `templateAware` grade included with this package takes care of that for you by calling `fluid.initDomBinder`
// and then running `applyBinding` once the `onDomBind` event is fired.  See that component for more details.
//
// This code was originally written by Antranig Basman <amb26@ponder.org.uk> and was evolved by Tony Atkins
// <tony@raisingthefloor.org> to document its use and make it usable with a wider range of form element types.
//
/* global fluid, jQuery */
(function ($) {
    "use strict";
    fluid.registerNamespace("gpii.templates.binder");

    // The main function to create bindings between markup and model elements.  See above for usage details.
    gpii.templates.binder.applyBinding = function (that) {
        var bindings = that.options.bindings;
        fluid.each(bindings, function (binding) {
            var element = that.locate(binding.selector);

            var value = fluid.get(that.model, binding.path);

            fluid.value(element, value);

            // Update the model when the form changes
            element.change(function () {
                console.log("Changing model based on element update.");

                var value = fluid.value(element);
                that.applier.change(binding.path, value);
            });

            // Update the form elements when the model changes
            that.applier.modelChanged.addListener(binding.path, function (change) {
                console.log("Changing value based on model update.");

                fluid.value(element, change);
            });
        });
    };
})(jQuery);

<commit_msg>GPII-1175:  Updated binder to use `fluid.log` instead of `console.log`.<commit_after>// Add persistent bindings between a selector and a model value.  Changes are propagated between the two.
//
// To use this, you should have a "bindings" element in your fluid defaults, as in:
//      bindings: [{
//          selector:    "input",
//          path:        "input"
//      }]
//
//
// The options are as follows:
// * selector:    A valid selector for your component.
//                Must be able to be resolved using that.locate(selector)
//
// * path:        A valid path for the model variable whose value will be watched.
//                Must be able to be resolved using fluid.get(path)
//
// To make use of this, you will need to start by sourcing this file after all fluid sources.
//
// In your component, you will need a valid selector, as in:
//
//  selectors: {
//      "input":    ".ptd-search-input"
//  }
//
// You will also need a valid model path, as in:
//
//  model: {
//      input:  ""
//  }
//
// Finally, you will need to invoke the binding applier where it makes sense, for example in response to an event.
// Here's an example that binds to the "onCreate" event:
//
//  listeners: {
//      onCreate: {
//        "funcName": "ctr.components.binder.applyBinding",
//        "args":     "{that}"
//      }
//  }
//
// Once you have done this, model changes should be passed to form controls and vice versa.
//
// Note that if you reload markup or otherwise generate new markup, you will need to reapply the set of bindings.
// The `templateAware` grade included with this package takes care of that for you by calling `fluid.initDomBinder`
// and then running `applyBinding` once the `onDomBind` event is fired.  See that component for more details.
//
// This code was originally written by Antranig Basman <amb26@ponder.org.uk> and was evolved by Tony Atkins
// <tony@raisingthefloor.org> to document its use and make it usable with a wider range of form element types.
//
/* global fluid, jQuery */
(function ($) {
    "use strict";
    fluid.registerNamespace("gpii.templates.binder");

    // The main function to create bindings between markup and model elements.  See above for usage details.
    gpii.templates.binder.applyBinding = function (that) {
        var bindings = that.options.bindings;
        fluid.each(bindings, function (binding) {
            var element = that.locate(binding.selector);

            var value = fluid.get(that.model, binding.path);

            fluid.value(element, value);

            // Update the model when the form changes
            element.change(function () {
                fluid.log("Changing model based on element update.");

                var value = fluid.value(element);
                that.applier.change(binding.path, value);
            });

            // Update the form elements when the model changes
            that.applier.modelChanged.addListener(binding.path, function (change) {
                fluid.log("Changing value based on model update.");

                fluid.value(element, change);
            });
        });
    };
})(jQuery);

<|endoftext|>


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

import { Table } from 'material-ui/Table'
import { TableHeader } from 'material-ui/Table'
import { TableHeaderColumn } from 'material-ui/Table'
import { TableRow } from 'material-ui/Table'
import { TableRowColumn } from 'material-ui/Table'
import { TableBody } from 'material-ui/Table'

import baseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'
import getMuiTheme from 'material-ui/styles/getMuiTheme'

class DayTimeTable extends Component {
  getChildContext() {
    return {
      muiTheme: getMuiTheme(baseTheme)
    }
  }

  constructor (props) {
    super(props)
  }

  render () {
    const {
      caption,
      calcCellHeight,
      className,
      data,
      hideHeaders,
      interval,
      isActive,
      cellKey,
      max,
      min,
      showHeader,
      showCell,
      showTime,
      toolTip,
      timeText,
      ...other,
    } = this.props;

    var headers = data.map((day, ii) => {
      return <TableHeaderColumn>{showHeader(day)}</TableHeaderColumn>
    })

    var colNum = headers.length
    var rowNum = (max - min) / interval

    var grid = []
    var found = new Map()
    for (let ii = 0; ii < rowNum; ii++) {
      grid[ii] = []
      for (let jj = 0; jj < colNum; jj++) {
        grid[ii][jj] = 0;
        data[jj].info.map((cell) => {
          if (isActive(cell, ii)) {
            grid[ii][jj] = {
              height: calcCellHeight(cell),
              info: {...cell}
            }
            if (found.get(cellKey(cell))) {
              grid[ii][jj].skip = true
            } else {
              grid[ii][jj].first = true
              found.set(cellKey(cell), true)
            }
          }
        })
      }
    }

    return (
      <Table
        className={ className }
        muiTheme={this.context.muiTheme}
        selectable={false}
      >
        { !hideHeaders &&
          <TableHeader
            displaySelectAll={false}
            adjustForCheckbox={false}
          >
            { caption &&
              <TableRow>
                <TableHeaderColumn colSpan={colNum} tooltip={toolTip} style={{textAlign: 'center'}}>
                  {caption}
                </TableHeaderColumn>
              </TableRow>
            }
            <TableRow>
              <TableHeaderColumn>{timeText}</TableHeaderColumn>
              { headers }
            </TableRow>
          </TableHeader>
        }
        <TableBody
          displayRowCheckbox={false}
        >
        {
          grid.map((row, ii) => {
            return (
              <TableRow>
                <TableRowColumn
                  style={{
                    "border-right": "1px solid rgb(224, 224, 224)"
                  }}
                >{showTime(ii)}</TableRowColumn>
                {
                  row.map((xx) => {
                    if (xx.first) {
                      return (
                        <TableRowColumn
                          rowSpan={xx.height}
                          style={{
                            "border-left": "1px solid rgb(224, 224, 224)",
                          }}
                          {...xx.info.props}
                        >
                          {showCell(xx.info)}
                        </TableRowColumn>
                      )
                    }
                    else if (xx.skip) {
                      return
                    } else {
                      return <TableRowColumn
                        style={{
                          "border-left": "1px solid rgb(224, 224, 224)"
                        }}
                      />
                    }
                  })
                }
              </TableRow>
            )
          })
        }
        </TableBody>
      </Table>
    )
  }
}

DayTimeTable.propTypes = {
  caption: PropTypes.string,
  calcCellHeight: PropTypes.func,
  className: PropTypes.string,
  showCell: PropTypes.func,
  timeText: PropTypes.string,
  toolTip: PropTypes.string,
  title: PropTypes.string
}

DayTimeTable.defaultProps = {
  timeText: "Times",
  toolTip: null
}

DayTimeTable.childContextTypes = {
  muiTheme: PropTypes.object.isRequired,
}

export default DayTimeTable
<commit_msg>fix style warnings<commit_after>import React, { Component, PropTypes } from 'react'

import { Table } from 'material-ui/Table'
import { TableHeader } from 'material-ui/Table'
import { TableHeaderColumn } from 'material-ui/Table'
import { TableRow } from 'material-ui/Table'
import { TableRowColumn } from 'material-ui/Table'
import { TableBody } from 'material-ui/Table'

import baseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'
import getMuiTheme from 'material-ui/styles/getMuiTheme'

class DayTimeTable extends Component {
  getChildContext() {
    return {
      muiTheme: getMuiTheme(baseTheme)
    }
  }

  constructor (props) {
    super(props)
  }

  render () {
    const {
      caption,
      calcCellHeight,
      className,
      data,
      hideHeaders,
      interval,
      isActive,
      cellKey,
      max,
      min,
      showHeader,
      showCell,
      showTime,
      toolTip,
      timeText,
      ...other,
    } = this.props;

    var headers = data.map((day, ii) => {
      return <TableHeaderColumn>{showHeader(day)}</TableHeaderColumn>
    })

    var colNum = headers.length
    var rowNum = (max - min) / interval

    var grid = []
    var found = new Map()
    for (let ii = 0; ii < rowNum; ii++) {
      grid[ii] = []
      for (let jj = 0; jj < colNum; jj++) {
        grid[ii][jj] = 0;
        data[jj].info.map((cell) => {
          if (isActive(cell, ii)) {
            grid[ii][jj] = {
              height: calcCellHeight(cell),
              info: {...cell}
            }
            if (found.get(cellKey(cell))) {
              grid[ii][jj].skip = true
            } else {
              grid[ii][jj].first = true
              found.set(cellKey(cell), true)
            }
          }
        })
      }
    }

    return (
      <Table
        className={ className }
        muiTheme={this.context.muiTheme}
        selectable={false}
      >
        { !hideHeaders &&
          <TableHeader
            displaySelectAll={false}
            adjustForCheckbox={false}
          >
            { caption &&
              <TableRow>
                <TableHeaderColumn colSpan={colNum} tooltip={toolTip} style={{textAlign: 'center'}}>
                  {caption}
                </TableHeaderColumn>
              </TableRow>
            }
            <TableRow>
              <TableHeaderColumn>{timeText}</TableHeaderColumn>
              { headers }
            </TableRow>
          </TableHeader>
        }
        <TableBody
          displayRowCheckbox={false}
        >
        {
          grid.map((row, ii) => {
            return (
              <TableRow>
                <TableRowColumn
                  style={{
                    "borderRight": "1px solid rgb(224, 224, 224)"
                  }}
                >{showTime(ii)}</TableRowColumn>
                {
                  row.map((xx) => {
                    if (xx.first) {
                      return (
                        <TableRowColumn
                          rowSpan={xx.height}
                          style={{
                            "borderLeft": "1px solid rgb(224, 224, 224)",
                          }}
                          {...xx.info.props}
                        >
                          {showCell(xx.info)}
                        </TableRowColumn>
                      )
                    }
                    else if (xx.skip) {
                      return
                    } else {
                      return <TableRowColumn
                        style={{
                          "borderLeft": "1px solid rgb(224, 224, 224)"
                        }}
                      />
                    }
                  })
                }
              </TableRow>
            )
          })
        }
        </TableBody>
      </Table>
    )
  }
}

DayTimeTable.propTypes = {
  caption: PropTypes.string,
  calcCellHeight: PropTypes.func,
  className: PropTypes.string,
  showCell: PropTypes.func,
  timeText: PropTypes.string,
  toolTip: PropTypes.string,
  title: PropTypes.string
}

DayTimeTable.defaultProps = {
  timeText: "Times",
  toolTip: null
}

DayTimeTable.childContextTypes = {
  muiTheme: PropTypes.object.isRequired,
}

export default DayTimeTable
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import MediaQuery from 'react-responsive';

import CreateForm from './CreateForm';
import MobileCreateCard from './mobile-create-card/MobileCreateCard';

export default class Create extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            /* components/_form.scss */

                <MediaQuery minDeviceWidth={768} className="form-and-list">
                    <CreateForm {...this.props} />
                </MediaQuery>
                <MediaQuery maxDeviceWidth={768}>
                    <MobileCreateCard {...this.props} />
                </MediaQuery>

        );
    }
}<commit_msg>[refactor] using pure function to refactor presentational components<commit_after>import React from 'react';
import MediaQuery from 'react-responsive';

import CreateForm from './CreateForm';
import MobileCreateCard from './mobile-create-card/MobileCreateCard';

/* components/_form.scss */
const Create = (props) => (

        <MediaQuery minDeviceWidth={768} className="form-and-list">
            <CreateForm {...props} />
        </MediaQuery>
        <MediaQuery maxDeviceWidth={768}>
            <MobileCreateCard {...props} />
        </MediaQuery>

);

export default Create;
<|endoftext|>


<commit_before>import { debounce, throttle } from 'lodash';
import CodeMirror from 'codemirror';
import localforage from 'localforage';

import config from '../config';
import { initCodeMirror } from './codemirror';
import { parseYamlString } from './codemirror/yaml-tangram';
import { ParsedYAMLDocument } from './yaml-ast';
import { suppressAPIKeys } from './api-keys';
import { addHighlightEventListeners, getAllHighlightedLines } from './highlight';
import { replaceHistoryState } from '../tools/url-state';
import { loadScene } from '../map/map';
import { insertMarksInViewport } from '../editor/bookmarks';
import EventEmitter from '../components/event-emitter';

import store from '../store';
import { MARK_FILE_DIRTY, MARK_FILE_CLEAN } from '../store/actions';

const STORAGE_LAST_EDITOR_STATE = 'last-scene';
const EDITOR_REFRESH_THROTTLE = 20;

// Export an instantiated CodeMirror instance
/* eslint-disable import/no-mutable-exports */
export let editor;
export let parsedYAMLDocument;
/* eslint-enable import/no-mutable-exports */

// Timeout for saving things in memory
let localMemorySaveTimer;

/**
 * Imported by the <Editor> React component, to be called when it mounts.
 * It is here (instead of importing `initCodeMirror()` directly) so that this
 * module can continue to export the `editor` reference to the CodeMirror
 * instance. This may change in the future if this is confusing.
 *
 * @param {Node} el - reference to the editor's container DOM node.
 */
export function initEditor(el) {
  editor = initCodeMirror(el);

  // Debug
  window.editor = editor;

  // Turn on highlighting module
  addHighlightEventListeners();
}

/**
 * Utility function to refresh the editor layout - call it after the editor
 * size has changed dynamically. This function is throttled to prevent it from
 * executing too quickly.
 */
export const refreshEditor = throttle(() => {
  if (editor) {
    editor.refresh();
  }
}, EDITOR_REFRESH_THROTTLE);

// Sets or gets scene contents in the editor.
// =============================================================================

/**
 * Convenience function returning content of the editor.
 *
 * @public
 * @return {string} content
 */
export function getEditorContent() {
  return editor.getDoc().getValue();
}

/**
 * Creates a new CodeMirror document instance from a text string of file content,
 * scrubbing API keys if necessary.
 *
 * @public
 * @param {string} content - to display in editor
 * @returns {CodeMirror.Doc} doc - a CodeMirror document instance
 */
export function createCodeMirrorDoc(content) {
  // Remove any instances of Tangram Play's default API key
  const scrubbedContent = suppressAPIKeys(content, config.TILES.API_KEYS.SUPPRESSED);

  // Create a new instance of a CodeMirror document
  return new CodeMirror.Doc(scrubbedContent, 'yaml-tangram');
}

/**
 * Sets editor content with a given CodeMirror document instance.
 * This function is sometimes used to restore old editor
 * content that has not been saved (e.g. from local memory). Although it is
 * a new document instance, there are conditions from the old document that
 * must also be restored. (Note: we remember if a document in memory is "not
 * clean", but we cannot set this in CodeMirror -- it is always a clean
 * document when it is first created.)
 *
 * @public
 * @param {CodeMirror.Doc} doc - A CodeMirror document instance to set in editor
 * @param {Boolean} readOnly - whether the editor contents are read-only
 */
export function setEditorContent(doc, readOnly = false) {
  // Swap current content in CodeMirror document with the provided Doc instance.
  editor.swapDoc(doc);
  editor.setOption('readOnly', readOnly);

  // Parse the document
  const content = doc.getValue();
  parsedYAMLDocument = new ParsedYAMLDocument(content);

  // Debug access
  window.parsedYAMLDocument = parsedYAMLDocument;

  // Once the document is swapped in, if the content is not read-only,
  // add bookmarks back in if they're not added already.
  if (readOnly === false) {
    insertMarksInViewport();
  }
}

/**
 * Clears the current editor document, by creating a new, blank CodeMirror
 * document instance and swapping it from the current editor.
 *
 * @public
 */
export function clearEditorContent() {
  const clearedDoc = createCodeMirrorDoc('');
  setEditorContent(clearedDoc, false);
}

// If editor is updated, send it to the map.
function updateContent(content) {
  const url = URL.createObjectURL(new Blob([content]));
  loadScene(url);
}

// Wrap updateContent() in a debounce function to prevent rapid series of
// changes from continuously updating the map.
const debouncedUpdateContent = debounce(updateContent, 500);

function updateLocalMemory(content, doc, isClean) {
  // Bail if embedded
  if (window.isEmbedded) {
    return;
  }

  const scene = store.getState().scene;
  const activeFile = scene.activeFileIndex;

  scene.files[activeFile].contents = content;
  scene.files[activeFile].isClean = isClean;
  scene.files[activeFile].scrollInfo = editor.getScrollInfo();
  scene.files[activeFile].cursor = doc.getCursor();
  scene.files[activeFile].highlightedLines = getAllHighlightedLines();

  // Store in local memory
  localforage.setItem(STORAGE_LAST_EDITOR_STATE, scene);
}

// Wrap updateLocalMemory() in a debounce function. This actually does incur
// an extra significant processing overhead on every edit so we keep it from
// executing all the time.
const debouncedUpdateLocalMemory = debounce(updateLocalMemory, 500);

export function watchEditorForChanges() {
  const content = getEditorContent();
  const doc = editor.getDoc();
  const isClean = doc.isClean();

  parsedYAMLDocument.regenerate(content);

  // Update all the properties of the active file in local memory.
  // Localforage is async so it cannot be relied on to do this on the
  // window.beforeunload event; there is no guarantee the transaction is
  // completed before the page tears down. See here:
  // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB#Warning_About_Browser_Shutdown
  window.clearTimeout(localMemorySaveTimer);
  localMemorySaveTimer = window.setTimeout(debouncedUpdateLocalMemory,
    250, content, doc, isClean);

  // Send scene data to Tangram
  debouncedUpdateContent(content);

  // Update the page URL. When editor contents changes by user input
  // and the the editor state is not clean), we erase the ?scene= state
  // from the URL string. This prevents a situation where reloading (or
  // copy-pasting the URL) loads the scene file from an earlier state.
  if (isClean === false) {
    replaceHistoryState({
      scene: null,
    });

    // Also use this area to mark the state of the file in Redux store
    // TODO: These checks do not have to be debounced for Tangram.
    store.dispatch({
      type: MARK_FILE_DIRTY,
      fileIndex: 0,
    });
  } else {
    store.dispatch({
      type: MARK_FILE_CLEAN,
      fileIndex: 0,
    });
  }
}

// Getting parsed nodes.
// =============================================================================

/**
 * Returns an array of nodes recorded in a parsed line's state.
 * If there are no nodes for any reason, return an empty array.
 *
 * @public
 * @param {Number} line - the number of the line to check, 0-index.
 * @return {Array} nodes
 */
export function getNodesOfLine(line) {
  const lineHandle = editor.getLineHandle(line);

  // Return the nodes. If any property in the chain is not defined,
  // return an empty array.
  try {
    return lineHandle.stateAfter.nodes || [];
  } catch (e) {
    return [];
  }
}

/**
 * Returns an array of nodes in a given range. Calls getNodesOfLine()
 * repeatedly for each line in the range.
 *
 * @public
 * @param {Number} line - the number of the line to check, 0-index.
 * @return {Array} nodes
 */
export function getNodesInRange(from, to) {
  const nodes = [];

  if (from.line === to.line) {
    // If the searched nodes are in a same line
    const line = from.line;
    const inLineNodes = getNodesOfLine(line);

    for (const node of inLineNodes) {
      if (node.range.to.ch > from.ch || node.range.from.ch < to.ch) {
        nodes.push(node);
      }
    }
  } else {
    // If the searched nodes are in a range of lines
    for (let i = from.line; i <= to.line; i++) {
      const inLineNodes = getNodesOfLine(i);

      for (const node of inLineNodes) {
        if (node.range.from.line === from.line) {
          // Is in the beginning line
          if (node.range.to.ch > from.ch) {
            nodes.push(node);
          }
        } else if (node.range.to.line === to.line) {
          // is in the end line
          if (node.range.from.ch < to.ch) {
            nodes.push(node);
          }
        } else {
          // is in the sandwich lines
          nodes.push(node);
        }
      }
    }
  }
  return nodes;
}

/**
 * Gets an array of nodes for a given address (formatted `like:this`).
 *
 * @public
 * @param {string} address
 * @returns {Array} nodes
 */
export function getNodesForAddress(address) {
  // NOTE:
  // This is an expensive process because it needs to iterate through every
  // line until it finds the right address. Could be optimized if we store
  // addresses in a map... but then the question is how we keep it synced
  let lastState;
  for (let line = 0, size = editor.getDoc().size; line < size; line++) {
    const lineHandle = editor.getLineHandle(line);

    if (!lineHandle.stateAfter) {
      // If the line is NOT parsed.
      // ======================================================
      //
      // NOTE:
      // Manually parse it in a temporary buffer to avoid conflicts
      // with CodeMirror parser.
      // This means outside the Line Handle
      //
      // Copy the last parsed state
      let state = JSON.parse(JSON.stringify(lastState));
      state.line = line;

      // Parse the current state
      state = parseYamlString(lineHandle.text, state, 4);

      // Iterate through keys in this line
      for (const key of state.nodes) {
        if (key.address === address) {
          return key;
        }
      }
      // if nothing was found. Record the state and try again
      lastState = state;
      // TODO:
      // We might want to have two different parsers, a simpler one without
      // keys and just address for
      // the higliting and another more roboust that keep tracks of:
      // pairs (key/values), their ranges (from-to positions),
      // address and a some functions like getValue, setValue which could
      // be use by widgets or others addons to modify content
    } else {
      // it the line HAVE BEEN parsed (use the stateAfter)
      // ======================================================
      lastState = lineHandle.stateAfter;
      const keys = getNodesOfLine(line);
      for (const key of keys) {
        if (key.address === address) {
          return key;
        }
      }
    }
  }

  console.log('Fail searching', address);
  return null;
}

// If an inline node was changed, we'd like to reparse all the widgets in the line
// This function sends an event to our widgets-manager.js
function clearInlineNodes(fromPos) {
  EventEmitter.dispatch('editor:inlinenodes', { from: fromPos });
}

/**
 * Sets a bookmark's value. If a value is prepended with a YAML anchor, the
 * anchor is left in place.
 *
 * @public
 * @param {Object} bookmark - An object representing the bookmark whose value changes
 * @param {string} value - The new value to set to
 */
export function setCodeMirrorValue(bookmark, value) {
  // If editor is readonly, pass through bookmark unchanged.
  if (editor.isReadOnly()) {
    return bookmark;
  }

  // If an inline node is changed, we need to reparse all the other nodes in that line.
  let foundInlineNodes = null;

  const origin = '+value_change';

  // We should refresh the editor before the replacement
  // Believe this catches cases where we are parsing multiple colors that are in the viewport
  // TODO: probably not necessary if not using colorpalette probably
  editor.getStateAfter(bookmark.widgetPos.from.line, true);
  editor.getStateAfter(bookmark.widgetPos.to.line, true);

  const nodeArray = bookmark.lines[0].stateAfter.nodes;
  let node;

  // If only one node per line, always just fetch the node in Code Mirror's state after
  // This will reduce all sorts of errors because most cases are one-widget lines.
  if (nodeArray.length === 1) {
    node = nodeArray[0];
  } else {
    // If inline nodes
    for (const singleNode of nodeArray) {
      if (singleNode.range.from.ch === bookmark.widgetPos.from.ch) {
        node = singleNode;
        foundInlineNodes = node.range.from; // We found an inline node, log where it's at
        break;
      }
    }
  }

  // Force a space between the ':' and the value
  if (value === '') {
    value = ` ${value}`;
  }

  // Calculate beginning character of the value
  //               key:_[anchor]value
  //               ^ ^^^^
  //               | ||||__ + anchor.length
  //               | |||___ + 1
  //               | | `--- + 1
  //  range.from.ch  key.length
  const fromPos = {
    line: node.range.from.line,
    // Magic number: 2 refers to the colon + space between key and value
    ch: node.range.from.ch + node.key.length + 2 + node.anchor.length,
  };
  const toPos = node.range.to;

  doc.replaceRange(value, fromPos, toPos, origin);

  // If an inline node was changed, we'd like to reparse all the widgets in the line
  if (foundInlineNodes !== null) {
    setTimeout(clearInlineNodes(foundInlineNodes), 0);
  }

  return bookmark;
}

/* This section is for the shader widget links */

/**
 * Sets a text value within a shader block
 *
 * @public
 * @param {string} value - The new value to set
 * @param {Object} start - Start { line, ch }
 * @param {Object} end - End { line, ch }
 */
export function setCodeMirrorShaderValue(value, start, end) {
  // If editor is readonly, do nothing..
  if (editor.isReadOnly()) return;

  editor.replaceRange(value, start, end);
}

/**
 * Returns the left, bottom, right, top coordinates of the position we are clicking on
 *
 * @param {Object} linePos - position { line, ch } to lookup coordinates for
 */
export function getCoordinates(linePos) {
  return editor.charCoords(linePos);
}

/**
 * Set the CodeMirror cursor to a specific place within the editor
 *
 * @param {Number} line - line on which to set cursor
 * @param {Number} ch - ch on which to set cursor
 */
export function setCursor(line, ch) {
  const doc = editor.getDoc();
  doc.setCursor({ line, ch });
}
<commit_msg>Fix regression in `setCodeMirrorValue`<commit_after>import { debounce, throttle } from 'lodash';
import CodeMirror from 'codemirror';
import localforage from 'localforage';

import config from '../config';
import { initCodeMirror } from './codemirror';
import { parseYamlString } from './codemirror/yaml-tangram';
import { ParsedYAMLDocument } from './yaml-ast';
import { suppressAPIKeys } from './api-keys';
import { addHighlightEventListeners, getAllHighlightedLines } from './highlight';
import { replaceHistoryState } from '../tools/url-state';
import { loadScene } from '../map/map';
import { insertMarksInViewport } from '../editor/bookmarks';
import EventEmitter from '../components/event-emitter';

import store from '../store';
import { MARK_FILE_DIRTY, MARK_FILE_CLEAN } from '../store/actions';

const STORAGE_LAST_EDITOR_STATE = 'last-scene';
const EDITOR_REFRESH_THROTTLE = 20;

// Export an instantiated CodeMirror instance
/* eslint-disable import/no-mutable-exports */
export let editor;
export let parsedYAMLDocument;
/* eslint-enable import/no-mutable-exports */

// Timeout for saving things in memory
let localMemorySaveTimer;

/**
 * Imported by the <Editor> React component, to be called when it mounts.
 * It is here (instead of importing `initCodeMirror()` directly) so that this
 * module can continue to export the `editor` reference to the CodeMirror
 * instance. This may change in the future if this is confusing.
 *
 * @param {Node} el - reference to the editor's container DOM node.
 */
export function initEditor(el) {
  editor = initCodeMirror(el);

  // Debug
  window.editor = editor;

  // Turn on highlighting module
  addHighlightEventListeners();
}

/**
 * Utility function to refresh the editor layout - call it after the editor
 * size has changed dynamically. This function is throttled to prevent it from
 * executing too quickly.
 */
export const refreshEditor = throttle(() => {
  if (editor) {
    editor.refresh();
  }
}, EDITOR_REFRESH_THROTTLE);

// Sets or gets scene contents in the editor.
// =============================================================================

/**
 * Convenience function returning content of the editor.
 *
 * @public
 * @return {string} content
 */
export function getEditorContent() {
  return editor.getDoc().getValue();
}

/**
 * Creates a new CodeMirror document instance from a text string of file content,
 * scrubbing API keys if necessary.
 *
 * @public
 * @param {string} content - to display in editor
 * @returns {CodeMirror.Doc} doc - a CodeMirror document instance
 */
export function createCodeMirrorDoc(content) {
  // Remove any instances of Tangram Play's default API key
  const scrubbedContent = suppressAPIKeys(content, config.TILES.API_KEYS.SUPPRESSED);

  // Create a new instance of a CodeMirror document
  return new CodeMirror.Doc(scrubbedContent, 'yaml-tangram');
}

/**
 * Sets editor content with a given CodeMirror document instance.
 * This function is sometimes used to restore old editor
 * content that has not been saved (e.g. from local memory). Although it is
 * a new document instance, there are conditions from the old document that
 * must also be restored. (Note: we remember if a document in memory is "not
 * clean", but we cannot set this in CodeMirror -- it is always a clean
 * document when it is first created.)
 *
 * @public
 * @param {CodeMirror.Doc} doc - A CodeMirror document instance to set in editor
 * @param {Boolean} readOnly - whether the editor contents are read-only
 */
export function setEditorContent(doc, readOnly = false) {
  // Swap current content in CodeMirror document with the provided Doc instance.
  editor.swapDoc(doc);
  editor.setOption('readOnly', readOnly);

  // Parse the document
  const content = doc.getValue();
  parsedYAMLDocument = new ParsedYAMLDocument(content);

  // Debug access
  window.parsedYAMLDocument = parsedYAMLDocument;

  // Once the document is swapped in, if the content is not read-only,
  // add bookmarks back in if they're not added already.
  if (readOnly === false) {
    insertMarksInViewport();
  }
}

/**
 * Clears the current editor document, by creating a new, blank CodeMirror
 * document instance and swapping it from the current editor.
 *
 * @public
 */
export function clearEditorContent() {
  const clearedDoc = createCodeMirrorDoc('');
  setEditorContent(clearedDoc, false);
}

// If editor is updated, send it to the map.
function updateContent(content) {
  const url = URL.createObjectURL(new Blob([content]));
  loadScene(url);
}

// Wrap updateContent() in a debounce function to prevent rapid series of
// changes from continuously updating the map.
const debouncedUpdateContent = debounce(updateContent, 500);

function updateLocalMemory(content, doc, isClean) {
  // Bail if embedded
  if (window.isEmbedded) {
    return;
  }

  const scene = store.getState().scene;
  const activeFile = scene.activeFileIndex;

  scene.files[activeFile].contents = content;
  scene.files[activeFile].isClean = isClean;
  scene.files[activeFile].scrollInfo = editor.getScrollInfo();
  scene.files[activeFile].cursor = doc.getCursor();
  scene.files[activeFile].highlightedLines = getAllHighlightedLines();

  // Store in local memory
  localforage.setItem(STORAGE_LAST_EDITOR_STATE, scene);
}

// Wrap updateLocalMemory() in a debounce function. This actually does incur
// an extra significant processing overhead on every edit so we keep it from
// executing all the time.
const debouncedUpdateLocalMemory = debounce(updateLocalMemory, 500);

export function watchEditorForChanges() {
  const content = getEditorContent();
  const doc = editor.getDoc();
  const isClean = doc.isClean();

  parsedYAMLDocument.regenerate(content);

  // Update all the properties of the active file in local memory.
  // Localforage is async so it cannot be relied on to do this on the
  // window.beforeunload event; there is no guarantee the transaction is
  // completed before the page tears down. See here:
  // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB#Warning_About_Browser_Shutdown
  window.clearTimeout(localMemorySaveTimer);
  localMemorySaveTimer = window.setTimeout(debouncedUpdateLocalMemory,
    250, content, doc, isClean);

  // Send scene data to Tangram
  debouncedUpdateContent(content);

  // Update the page URL. When editor contents changes by user input
  // and the the editor state is not clean), we erase the ?scene= state
  // from the URL string. This prevents a situation where reloading (or
  // copy-pasting the URL) loads the scene file from an earlier state.
  if (isClean === false) {
    replaceHistoryState({
      scene: null,
    });

    // Also use this area to mark the state of the file in Redux store
    // TODO: These checks do not have to be debounced for Tangram.
    store.dispatch({
      type: MARK_FILE_DIRTY,
      fileIndex: 0,
    });
  } else {
    store.dispatch({
      type: MARK_FILE_CLEAN,
      fileIndex: 0,
    });
  }
}

// Getting parsed nodes.
// =============================================================================

/**
 * Returns an array of nodes recorded in a parsed line's state.
 * If there are no nodes for any reason, return an empty array.
 *
 * @public
 * @param {Number} line - the number of the line to check, 0-index.
 * @return {Array} nodes
 */
export function getNodesOfLine(line) {
  const lineHandle = editor.getLineHandle(line);

  // Return the nodes. If any property in the chain is not defined,
  // return an empty array.
  try {
    return lineHandle.stateAfter.nodes || [];
  } catch (e) {
    return [];
  }
}

/**
 * Returns an array of nodes in a given range. Calls getNodesOfLine()
 * repeatedly for each line in the range.
 *
 * @public
 * @param {Number} line - the number of the line to check, 0-index.
 * @return {Array} nodes
 */
export function getNodesInRange(from, to) {
  const nodes = [];

  if (from.line === to.line) {
    // If the searched nodes are in a same line
    const line = from.line;
    const inLineNodes = getNodesOfLine(line);

    for (const node of inLineNodes) {
      if (node.range.to.ch > from.ch || node.range.from.ch < to.ch) {
        nodes.push(node);
      }
    }
  } else {
    // If the searched nodes are in a range of lines
    for (let i = from.line; i <= to.line; i++) {
      const inLineNodes = getNodesOfLine(i);

      for (const node of inLineNodes) {
        if (node.range.from.line === from.line) {
          // Is in the beginning line
          if (node.range.to.ch > from.ch) {
            nodes.push(node);
          }
        } else if (node.range.to.line === to.line) {
          // is in the end line
          if (node.range.from.ch < to.ch) {
            nodes.push(node);
          }
        } else {
          // is in the sandwich lines
          nodes.push(node);
        }
      }
    }
  }
  return nodes;
}

/**
 * Gets an array of nodes for a given address (formatted `like:this`).
 *
 * @public
 * @param {string} address
 * @returns {Array} nodes
 */
export function getNodesForAddress(address) {
  // NOTE:
  // This is an expensive process because it needs to iterate through every
  // line until it finds the right address. Could be optimized if we store
  // addresses in a map... but then the question is how we keep it synced
  let lastState;
  for (let line = 0, size = editor.getDoc().size; line < size; line++) {
    const lineHandle = editor.getLineHandle(line);

    if (!lineHandle.stateAfter) {
      // If the line is NOT parsed.
      // ======================================================
      //
      // NOTE:
      // Manually parse it in a temporary buffer to avoid conflicts
      // with CodeMirror parser.
      // This means outside the Line Handle
      //
      // Copy the last parsed state
      let state = JSON.parse(JSON.stringify(lastState));
      state.line = line;

      // Parse the current state
      state = parseYamlString(lineHandle.text, state, 4);

      // Iterate through keys in this line
      for (const key of state.nodes) {
        if (key.address === address) {
          return key;
        }
      }
      // if nothing was found. Record the state and try again
      lastState = state;
      // TODO:
      // We might want to have two different parsers, a simpler one without
      // keys and just address for
      // the higliting and another more roboust that keep tracks of:
      // pairs (key/values), their ranges (from-to positions),
      // address and a some functions like getValue, setValue which could
      // be use by widgets or others addons to modify content
    } else {
      // it the line HAVE BEEN parsed (use the stateAfter)
      // ======================================================
      lastState = lineHandle.stateAfter;
      const keys = getNodesOfLine(line);
      for (const key of keys) {
        if (key.address === address) {
          return key;
        }
      }
    }
  }

  console.log('Fail searching', address);
  return null;
}

// If an inline node was changed, we'd like to reparse all the widgets in the line
// This function sends an event to our widgets-manager.js
function clearInlineNodes(fromPos) {
  EventEmitter.dispatch('editor:inlinenodes', { from: fromPos });
}

/**
 * Sets a bookmark's value. If a value is prepended with a YAML anchor, the
 * anchor is left in place.
 *
 * @public
 * @param {Object} bookmark - An object representing the bookmark whose value changes
 * @param {string} value - The new value to set to
 */
export function setCodeMirrorValue(bookmark, value) {
  // If editor is readonly, pass through bookmark unchanged.
  if (editor.isReadOnly()) {
    return bookmark;
  }

  // If an inline node is changed, we need to reparse all the other nodes in that line.
  let foundInlineNodes = null;

  const origin = '+value_change';

  // We should refresh the editor before the replacement
  // Believe this catches cases where we are parsing multiple colors that are in the viewport
  // TODO: probably not necessary if not using colorpalette probably
  editor.getStateAfter(bookmark.widgetPos.from.line, true);
  editor.getStateAfter(bookmark.widgetPos.to.line, true);

  const nodeArray = bookmark.lines[0].stateAfter.nodes;
  let node;

  // If only one node per line, always just fetch the node in Code Mirror's state after
  // This will reduce all sorts of errors because most cases are one-widget lines.
  if (nodeArray.length === 1) {
    node = nodeArray[0];
  } else {
    // If inline nodes
    for (const singleNode of nodeArray) {
      if (singleNode.range.from.ch === bookmark.widgetPos.from.ch) {
        node = singleNode;
        foundInlineNodes = node.range.from; // We found an inline node, log where it's at
        break;
      }
    }
  }

  const doc = editor.getDoc();

  // Force a space between the ':' and the value
  if (value === '') {
    value = ` ${value}`;
  }

  // Calculate beginning character of the value
  //               key:_[anchor]value
  //               ^ ^^^^
  //               | ||||__ + anchor.length
  //               | |||___ + 1
  //               | | `--- + 1
  //  range.from.ch  key.length
  const fromPos = {
    line: node.range.from.line,
    // Magic number: 2 refers to the colon + space between key and value
    ch: node.range.from.ch + node.key.length + 2 + node.anchor.length,
  };
  const toPos = node.range.to;

  doc.replaceRange(value, fromPos, toPos, origin);

  // If an inline node was changed, we'd like to reparse all the widgets in the line
  if (foundInlineNodes !== null) {
    setTimeout(clearInlineNodes(foundInlineNodes), 0);
  }

  return bookmark;
}

/* This section is for the shader widget links */

/**
 * Sets a text value within a shader block
 *
 * @public
 * @param {string} value - The new value to set
 * @param {Object} start - Start { line, ch }
 * @param {Object} end - End { line, ch }
 */
export function setCodeMirrorShaderValue(value, start, end) {
  editor.replaceRange(value, start, end);
}

/**
 * Returns the left, bottom, right, top coordinates of the position we are clicking on
 *
 * @param {Object} linePos - position { line, ch } to lookup coordinates for
 */
export function getCoordinates(linePos) {
  return editor.charCoords(linePos);
}

/**
 * Set the CodeMirror cursor to a specific place within the editor
 *
 * @param {Number} line - line on which to set cursor
 * @param {Number} ch - ch on which to set cursor
 */
export function setCursor(line, ch) {
  const doc = editor.getDoc();
  doc.setCursor({ line, ch });
}
<|endoftext|>


<commit_before>
fc.sourceNormalizers = [];
fc.sourceFetchers = [];

var ajaxDefaults = {
	dataType: 'json',
	cache: false
};

var eventGUID = 1;

function EventManager(options, _sources) {
	var t = this;

	// exports
	t.isFetchNeeded = isFetchNeeded;
	t.fetchEvents = fetchEvents;
	t.addEventSource = addEventSource;
	t.removeEventSource = removeEventSource;
	t.updateEvent = updateEvent;
	t.renderEvent = renderEvent;
	t.removeEvents = removeEvents;
	t.clientEvents = clientEvents;
	t.normalizeEvent = normalizeEvent;

	// imports
	var trigger = t.trigger;
	var getView = t.getView;
	var reportEvents = t.reportEvents;

	// locals
	var stickySource = { events: [] };
	var sources = [ stickySource ];
	var rangeStart, rangeEnd;
	var currentFetchID = 0;
	var pendingSourceCnt = 0;
	var loadingLevel = 0;
	var cache = [];

	for (var i=0; i<_sources.length; i++) {
		_addEventSource(_sources[i]);
	}

	/* Fetching
	-----------------------------------------------------------------------------*/

	function isFetchNeeded(start, end) {
		return !rangeStart || start < rangeStart || end > rangeEnd;
	}

	function fetchEvents(start, end) {
		rangeStart = start;
		rangeEnd = end;
		cache = [];
		var fetchID = ++currentFetchID;
		var len = sources.length;
		pendingSourceCnt = len;
		for (var i=0; i<len; i++) {
			fetchEventSource(sources[i], fetchID);
		}
	}

	function fetchEventSource(source, fetchID) {
		_fetchEventSource(source, function(events) {
			if (fetchID == currentFetchID) {
				if (events) {
					for (var i=0; i<events.length; i++) {
						events[i].source = source;
						normalizeEvent(events[i]);
					}
					cache = cache.concat(events);
				}
				pendingSourceCnt--;
				if (!pendingSourceCnt) {
					reportEvents(cache);
				}
			}
		});
	}

	function _fetchEventSource(source, callback) {
		var i;
		var fetchers = fc.sourceFetchers;
		var res;
		for (i=0; i<fetchers.length; i++) {
			res = fetchers[i](source, rangeStart, rangeEnd, callback);
			if (res === true) {
				// the fetcher is in charge. made its own async request
				return;
			}
			else if (typeof res == 'object') {
				// the fetcher returned a new source. process it
				_fetchEventSource(res, callback);
				return;
			}
		}
		var events = source.events;
		if (events) {
			if ($.isFunction(events)) {
				pushLoading();
				events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
					callback(events);
					popLoading();
				});
			}
			else if ($.isArray(events)) {
				callback(events);
			}
			else {
				callback();
			}
		}else{
			var url = source.url;
			if (url) {
				var success = source.success;
				var error = source.error;
				var complete = source.complete;
				var data = $.extend({}, source.data || {});
				var startParam = firstDefined(source.startParam, options.startParam);
				var endParam = firstDefined(source.endParam, options.endParam);
				if (startParam) {
					data[startParam] = Math.round(+rangeStart / 1000);
				}
				if (endParam) {
					data[endParam] = Math.round(+rangeEnd / 1000);
				}
				pushLoading();
				$.ajax($.extend({}, ajaxDefaults, source, {
					data: data,
					success: function(events) {
						events = events || [];
						var res = applyAll(success, this, arguments);
						if ($.isArray(res)) {
							events = res;
						}
						callback(events);
					},
					error: function() {
						applyAll(error, this, arguments);
						callback();
					},
					complete: function() {
						applyAll(complete, this, arguments);
						popLoading();
					}
				}));
			}else{
				callback();
			}
		}
	}

	/* Sources
	-----------------------------------------------------------------------------*/

	function addEventSource(source) {
		source = _addEventSource(source);
		if (source) {
			pendingSourceCnt++;
			fetchEventSource(source, currentFetchID); // will eventually call reportEvents
		}
	}

	function _addEventSource(source) {
		if ($.isFunction(source) || $.isArray(source)) {
			source = { events: source };
		}
		else if (typeof source == 'string') {
			source = { url: source };
		}
		if (typeof source == 'object') {
			normalizeSource(source);
			sources.push(source);
			return source;
		}
	}

	function removeEventSource(source) {
		sources = $.grep(sources, function(src) {
			return !isSourcesEqual(src, source);
		});
		// remove all client events from that source
		cache = $.grep(cache, function(e) {
			return !isSourcesEqual(e.source, source);
		});
		reportEvents(cache);
	}

	/* Manipulation
	-----------------------------------------------------------------------------*/

	function updateEvent(event) { // update an existing event
		var i, len = cache.length, e,
			defaultEventEnd = getView().defaultEventEnd, // getView???
			startDelta = event.start - event._start,
			endDelta = event.end ?
				(event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
				: 0;                                                      // was null and event was just resized
		for (i=0; i<len; i++) {
			e = cache[i];
			if (e._id == event._id && e != event) {
				e.start = new Date(+e.start + startDelta);
				if (event.end) {
					if (e.end) {
						e.end = new Date(+e.end + endDelta);
					}else{
						e.end = new Date(+defaultEventEnd(e) + endDelta);
					}
				}else{
					e.end = null;
				}
				e.title = event.title;
				e.url = event.url;
				e.allDay = event.allDay;
				e.className = event.className;
				e.editable = event.editable;
				e.color = event.color;
				e.backgroudColor = event.backgroudColor;
				e.borderColor = event.borderColor;
				e.textColor = event.textColor;
				normalizeEvent(e);
			}
		}
		normalizeEvent(event);
		reportEvents(cache);
	}

	function renderEvent(event, stick) {
		normalizeEvent(event);
		if (!event.source) {
			if (stick) {
				stickySource.events.push(event);
				event.source = stickySource;
			}
			cache.push(event);
		}
		reportEvents(cache);
	}

	function removeEvents(filter) {
		if (!filter) { // remove all
			cache = [];
			// clear all array sources
			for (var i=0; i<sources.length; i++) {
				if ($.isArray(sources[i].events)) {
					sources[i].events = [];
				}
			}
		}else{
			if (!$.isFunction(filter)) { // an event ID
				var id = filter + '';
				filter = function(e) {
					return e._id == id;
				};
			}
			cache = $.grep(cache, filter, true);
			// remove events from array sources
			for (var i=0; i<sources.length; i++) {
				if ($.isArray(sources[i].events)) {
					sources[i].events = $.grep(sources[i].events, filter, true);
				}
			}
		}
		reportEvents(cache);
	}

	function clientEvents(filter) {
		if ($.isFunction(filter)) {
			return $.grep(cache, filter);
		}
		else if (filter) { // an event ID
			filter += '';
			return $.grep(cache, function(e) {
				return e._id == filter;
			});
		}
		return cache; // else, return all
	}

	/* Loading State
	-----------------------------------------------------------------------------*/

	function pushLoading() {
		if (!loadingLevel++) {
			trigger('loading', null, true);
		}
	}

	function popLoading() {
		if (!--loadingLevel) {
			trigger('loading', null, false);
		}
	}

	/* Event Normalization
	-----------------------------------------------------------------------------*/

	function normalizeEvent(event) {
		var source = event.source || {};
		var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
		event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
		if (event.date) {
			if (!event.start) {
				event.start = event.date;
			}
			delete event.date;
		}
		event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
		event.end = parseDate(event.end, ignoreTimezone);
		if (event.end && event.end <= event.start) {
			event.end = null;
		}
		event._end = event.end ? cloneDate(event.end) : null;
		if (event.allDay === undefined) {
			event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
		}
		if (event.className) {
			if (typeof event.className == 'string') {
				event.className = event.className.split(/\s+/);
			}
		}else{
			event.className = [];
		}
		// TODO: if there is no start date, return false to indicate an invalid event
	}

	/* Utils
	------------------------------------------------------------------------------*/

	function normalizeSource(source) {
		if (source.className) {
			// TODO: repeat code, same code for event classNames
			if (typeof source.className == 'string') {
				source.className = source.className.split(/\s+/);
			}
		}else{
			source.className = [];
		}
		var normalizers = fc.sourceNormalizers;
		for (var i=0; i<normalizers.length; i++) {
			normalizers[i](source);
		}
	}

	function isSourcesEqual(source1, source2) {
		return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
	}

	function getSourcePrimitive(source) {
		return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
	}

}
<commit_msg>Added an option to the removeEventSource method to keep existing events on the calendar<commit_after>
fc.sourceNormalizers = [];
fc.sourceFetchers = [];

var ajaxDefaults = {
	dataType: 'json',
	cache: false
};

var eventGUID = 1;

function EventManager(options, _sources) {
	var t = this;

	// exports
	t.isFetchNeeded = isFetchNeeded;
	t.fetchEvents = fetchEvents;
	t.addEventSource = addEventSource;
	t.removeEventSource = removeEventSource;
	t.updateEvent = updateEvent;
	t.renderEvent = renderEvent;
	t.removeEvents = removeEvents;
	t.clientEvents = clientEvents;
	t.normalizeEvent = normalizeEvent;

	// imports
	var trigger = t.trigger;
	var getView = t.getView;
	var reportEvents = t.reportEvents;

	// locals
	var stickySource = { events: [] };
	var sources = [ stickySource ];
	var rangeStart, rangeEnd;
	var currentFetchID = 0;
	var pendingSourceCnt = 0;
	var loadingLevel = 0;
	var cache = [];

	for (var i=0; i<_sources.length; i++) {
		_addEventSource(_sources[i]);
	}

	/* Fetching
	-----------------------------------------------------------------------------*/

	function isFetchNeeded(start, end) {
		return !rangeStart || start < rangeStart || end > rangeEnd;
	}

	function fetchEvents(start, end) {
		rangeStart = start;
		rangeEnd = end;
		cache = [];
		var fetchID = ++currentFetchID;
		var len = sources.length;
		pendingSourceCnt = len;
		for (var i=0; i<len; i++) {
			fetchEventSource(sources[i], fetchID);
		}
	}

	function fetchEventSource(source, fetchID) {
		_fetchEventSource(source, function(events) {
			if (fetchID == currentFetchID) {
				if (events) {
					for (var i=0; i<events.length; i++) {
						events[i].source = source;
						normalizeEvent(events[i]);
					}
					cache = cache.concat(events);
				}
				pendingSourceCnt--;
				if (!pendingSourceCnt) {
					reportEvents(cache);
				}
			}
		});
	}

	function _fetchEventSource(source, callback) {
		var i;
		var fetchers = fc.sourceFetchers;
		var res;
		for (i=0; i<fetchers.length; i++) {
			res = fetchers[i](source, rangeStart, rangeEnd, callback);
			if (res === true) {
				// the fetcher is in charge. made its own async request
				return;
			}
			else if (typeof res == 'object') {
				// the fetcher returned a new source. process it
				_fetchEventSource(res, callback);
				return;
			}
		}
		var events = source.events;
		if (events) {
			if ($.isFunction(events)) {
				pushLoading();
				events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
					callback(events);
					popLoading();
				});
			}
			else if ($.isArray(events)) {
				callback(events);
			}
			else {
				callback();
			}
		}else{
			var url = source.url;
			if (url) {
				var success = source.success;
				var error = source.error;
				var complete = source.complete;
				var data = $.extend({}, source.data || {});
				var startParam = firstDefined(source.startParam, options.startParam);
				var endParam = firstDefined(source.endParam, options.endParam);
				if (startParam) {
					data[startParam] = Math.round(+rangeStart / 1000);
				}
				if (endParam) {
					data[endParam] = Math.round(+rangeEnd / 1000);
				}
				pushLoading();
				$.ajax($.extend({}, ajaxDefaults, source, {
					data: data,
					success: function(events) {
						events = events || [];
						var res = applyAll(success, this, arguments);
						if ($.isArray(res)) {
							events = res;
						}
						callback(events);
					},
					error: function() {
						applyAll(error, this, arguments);
						callback();
					},
					complete: function() {
						applyAll(complete, this, arguments);
						popLoading();
					}
				}));
			}else{
				callback();
			}
		}
	}

	/* Sources
	-----------------------------------------------------------------------------*/

	function addEventSource(source) {
		source = _addEventSource(source);
		if (source) {
			pendingSourceCnt++;
			fetchEventSource(source, currentFetchID); // will eventually call reportEvents
		}
	}

	function _addEventSource(source) {
		if ($.isFunction(source) || $.isArray(source)) {
			source = { events: source };
		}
		else if (typeof source == 'string') {
			source = { url: source };
		}
		if (typeof source == 'object') {
			normalizeSource(source);
			sources.push(source);
			return source;
		}
	}

	function removeEventSource(source, keepEvents) {
		keepEvents = (typeof keepEvents === "undefined") ? false : keepEvents;

		sources = $.grep(sources, function(src) {
			return !isSourcesEqual(src, source);
		});

		if (!keepEvents) {
			// remove all client events from that source
			cache = $.grep(cache, function(e) {
				return !isSourcesEqual(e.source, source);
			});
			reportEvents(cache);
		}
	}

	/* Manipulation
	-----------------------------------------------------------------------------*/

	function updateEvent(event) { // update an existing event
		var i, len = cache.length, e,
			defaultEventEnd = getView().defaultEventEnd, // getView???
			startDelta = event.start - event._start,
			endDelta = event.end ?
				(event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
				: 0;                                                      // was null and event was just resized
		for (i=0; i<len; i++) {
			e = cache[i];
			if (e._id == event._id && e != event) {
				e.start = new Date(+e.start + startDelta);
				if (event.end) {
					if (e.end) {
						e.end = new Date(+e.end + endDelta);
					}else{
						e.end = new Date(+defaultEventEnd(e) + endDelta);
					}
				}else{
					e.end = null;
				}
				e.title = event.title;
				e.url = event.url;
				e.allDay = event.allDay;
				e.className = event.className;
				e.editable = event.editable;
				e.color = event.color;
				e.backgroudColor = event.backgroudColor;
				e.borderColor = event.borderColor;
				e.textColor = event.textColor;
				normalizeEvent(e);
			}
		}
		normalizeEvent(event);
		reportEvents(cache);
	}

	function renderEvent(event, stick) {
		normalizeEvent(event);
		if (!event.source) {
			if (stick) {
				stickySource.events.push(event);
				event.source = stickySource;
			}
			cache.push(event);
		}
		reportEvents(cache);
	}

	function removeEvents(filter) {
		if (!filter) { // remove all
			cache = [];
			// clear all array sources
			for (var i=0; i<sources.length; i++) {
				if ($.isArray(sources[i].events)) {
					sources[i].events = [];
				}
			}
		}else{
			if (!$.isFunction(filter)) { // an event ID
				var id = filter + '';
				filter = function(e) {
					return e._id == id;
				};
			}
			cache = $.grep(cache, filter, true);
			// remove events from array sources
			for (var i=0; i<sources.length; i++) {
				if ($.isArray(sources[i].events)) {
					sources[i].events = $.grep(sources[i].events, filter, true);
				}
			}
		}
		reportEvents(cache);
	}

	function clientEvents(filter) {
		if ($.isFunction(filter)) {
			return $.grep(cache, filter);
		}
		else if (filter) { // an event ID
			filter += '';
			return $.grep(cache, function(e) {
				return e._id == filter;
			});
		}
		return cache; // else, return all
	}

	/* Loading State
	-----------------------------------------------------------------------------*/

	function pushLoading() {
		if (!loadingLevel++) {
			trigger('loading', null, true);
		}
	}

	function popLoading() {
		if (!--loadingLevel) {
			trigger('loading', null, false);
		}
	}

	/* Event Normalization
	-----------------------------------------------------------------------------*/

	function normalizeEvent(event) {
		var source = event.source || {};
		var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
		event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
		if (event.date) {
			if (!event.start) {
				event.start = event.date;
			}
			delete event.date;
		}
		event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
		event.end = parseDate(event.end, ignoreTimezone);
		if (event.end && event.end <= event.start) {
			event.end = null;
		}
		event._end = event.end ? cloneDate(event.end) : null;
		if (event.allDay === undefined) {
			event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
		}
		if (event.className) {
			if (typeof event.className == 'string') {
				event.className = event.className.split(/\s+/);
			}
		}else{
			event.className = [];
		}
		// TODO: if there is no start date, return false to indicate an invalid event
	}

	/* Utils
	------------------------------------------------------------------------------*/

	function normalizeSource(source) {
		if (source.className) {
			// TODO: repeat code, same code for event classNames
			if (typeof source.className == 'string') {
				source.className = source.className.split(/\s+/);
			}
		}else{
			source.className = [];
		}
		var normalizers = fc.sourceNormalizers;
		for (var i=0; i<normalizers.length; i++) {
			normalizers[i](source);
		}
	}

	function isSourcesEqual(source1, source2) {
		return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
	}

	function getSourcePrimitive(source) {
		return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
	}

}
<|endoftext|>


<commit_before>/*======================================================
************   Notifications   ************
======================================================*/
var _tempNotificationElement;
app.addNotification = function (params) {
    if (!params) return;

    if (typeof params.media === 'undefined') params.media = app.params.notificationMedia;
    if (typeof params.title === 'undefined') params.title = app.params.notificationTitle;
    if (typeof params.subtitle === 'undefined') params.subtitle = app.params.notificationSubtitle;
    if (typeof params.closeIcon === 'undefined') params.closeIcon = app.params.notificationCloseIcon;
    if (typeof params.hold === 'undefined') params.hold = app.params.notificationHold;
    if (typeof params.closeOnClick === 'undefined') params.closeOnClick = app.params.notificationCloseOnClick;

    if (!_tempNotificationElement) _tempNotificationElement = document.createElement('div');

    var container = $('.notifications');
    if (container.length === 0) {
        $('body').append('

');
        container = $('.notifications');
    }
    var list = container.children('ul');

    var itemHTML;
    if (params.custom) {
        itemHTML = '
' + params.custom + '
';
    }
    else {
        itemHTML = '

' +
                        (params.media ?
                        '
' +
                            params.media +
                        '
' : '') +
                        '
' +
                            '
' +
                                (params.title ? '
' + params.title + '
' : '') +
                                (params.closeIcon ? '

' : '') +
                            '
' +
                            (params.subtitle ? '
' + params.subtitle + '
' : '') +
                            (params.message ? '
' + params.message + '
' : '') +
                        '
' +
                    '

';
    }
    _tempNotificationElement.innerHTML = itemHTML;

    var item = $(_tempNotificationElement).children();

    item.on('click', function (e) {
        var close = false;
        if ($(e.target).is('.close-notification') || $(e.target).parents('.close-notification').length > 0) {
            close = true;
        }
        else {
            if (params.onClick) params.onClick(e, item[0]);
            if (params.closeOnClick) close = true;
        }
        if (close) app.closeNotification(item[0]);
    });
    if (params.onClose) {
        item.data('f7NotificationOnClose', function () {
            params.onClose(item[0]);
        });
    }
    if (params.additionalClass) {
        item.addClass(params.additionalClass);
    }
    if (params.hold) {
        setTimeout(function () {
            if (item.length > 0) app.closeNotification(item[0]);
        }, params.hold);
    }

    list.prepend(item[0]);
    container.show();

    var itemHeight = item.height();
    item.css('marginTop', -itemHeight + 'px');
    item.transition(0);

    var clientLeft = item[0].clientLeft;
    item.transition('');
    item.css('marginTop', '0px');

    container.transform('translate3d(0, 0,0)');
    item.removeClass('notification-hidden');

    return item[0];
};
app.closeNotification = function (item) {
    item = $(item);
    if (item.length === 0) return;
    if (item.hasClass('notification-item-removing')) return;
    var container = $('.notifications');

    var itemHeight = item.height();
    item.css('height', itemHeight + 'px').transition(0);
    var clientLeft = item[0].clientLeft;

    item.css('height', '0px').transition('').addClass('notification-item-removing');
    if (item.data('f7NotificationOnClose')) item.data('f7NotificationOnClose')();

    if (container.find('.notification-item:not(.notification-item-removing)').length === 0) {
        container.transform('');
    }

    item.addClass('notification-hidden').transitionEnd(function () {
        item.remove();
        if (container.find('.notification-item').length === 0) {
            container.hide();
        }
    });
};<commit_msg>Change height to outerHeight<commit_after>/*======================================================
************   Notifications   ************
======================================================*/
var _tempNotificationElement;
app.addNotification = function (params) {
    if (!params) return;

    if (typeof params.media === 'undefined') params.media = app.params.notificationMedia;
    if (typeof params.title === 'undefined') params.title = app.params.notificationTitle;
    if (typeof params.subtitle === 'undefined') params.subtitle = app.params.notificationSubtitle;
    if (typeof params.closeIcon === 'undefined') params.closeIcon = app.params.notificationCloseIcon;
    if (typeof params.hold === 'undefined') params.hold = app.params.notificationHold;
    if (typeof params.closeOnClick === 'undefined') params.closeOnClick = app.params.notificationCloseOnClick;

    if (!_tempNotificationElement) _tempNotificationElement = document.createElement('div');

    var container = $('.notifications');
    if (container.length === 0) {
        $('body').append('

');
        container = $('.notifications');
    }
    var list = container.children('ul');

    var itemHTML;
    if (params.custom) {
        itemHTML = '
' + params.custom + '
';
    }
    else {
        itemHTML = '

' +
                        (params.media ?
                        '
' +
                            params.media +
                        '
' : '') +
                        '
' +
                            '
' +
                                (params.title ? '
' + params.title + '
' : '') +
                                (params.closeIcon ? '

' : '') +
                            '
' +
                            (params.subtitle ? '
' + params.subtitle + '
' : '') +
                            (params.message ? '
' + params.message + '
' : '') +
                        '
' +
                    '

';
    }
    _tempNotificationElement.innerHTML = itemHTML;

    var item = $(_tempNotificationElement).children();

    item.on('click', function (e) {
        var close = false;
        if ($(e.target).is('.close-notification') || $(e.target).parents('.close-notification').length > 0) {
            close = true;
        }
        else {
            if (params.onClick) params.onClick(e, item[0]);
            if (params.closeOnClick) close = true;
        }
        if (close) app.closeNotification(item[0]);
    });
    if (params.onClose) {
        item.data('f7NotificationOnClose', function () {
            params.onClose(item[0]);
        });
    }
    if (params.additionalClass) {
        item.addClass(params.additionalClass);
    }
    if (params.hold) {
        setTimeout(function () {
            if (item.length > 0) app.closeNotification(item[0]);
        }, params.hold);
    }

    list.prepend(item[0]);
    container.show();

    var itemHeight = item.outerHeight();
    item.css('marginTop', -itemHeight + 'px');
    item.transition(0);

    var clientLeft = item[0].clientLeft;
    item.transition('');
    item.css('marginTop', '0px');

    container.transform('translate3d(0, 0,0)');
    item.removeClass('notification-hidden');

    return item[0];
};
app.closeNotification = function (item) {
    item = $(item);
    if (item.length === 0) return;
    if (item.hasClass('notification-item-removing')) return;
    var container = $('.notifications');

    var itemHeight = item.outerHeight();
    item.css('height', itemHeight + 'px').transition(0);
    var clientLeft = item[0].clientLeft;

    item.css('height', '0px').transition('').addClass('notification-item-removing');
    if (item.data('f7NotificationOnClose')) item.data('f7NotificationOnClose')();

    if (container.find('.notification-item:not(.notification-item-removing)').length === 0) {
        container.transform('');
    }

    item.addClass('notification-hidden').transitionEnd(function () {
        item.remove();
        if (container.find('.notification-item').length === 0) {
            container.hide();
        }
    });
};<|endoftext|>


<commit_before>// Page modules
var FastClick = require('fastclick')
var nav = require('./nav.js')
var urlParameter = require('./get-url-parameter')
var accordion = require('./accordion')
var socialShare = require('./social-share')
var sortBy = require('lodash/collection/sortBy')

nav.init()
FastClick.attach(document.body)

// Load and process data
require.ensure(['./api', './get-api-data', './category-endpoint', './template-render', 'spin.js', './analytics'], function (require) {
  var apiRoutes = require('./api')
  var getApiData = require('./get-api-data')
  var categoryEndpoint = require('./category-endpoint')
  var templating = require('./template-render')
  var Spinner = require('spin.js')
  var analytics = require('./analytics')

  // Spinner
  var spin = document.getElementById('spin')
  var loading = new Spinner().spin(spin)

  // Get category and create URL
  var theCategory = urlParameter.parameter('category')
  var theLocation = urlParameter.parameter('location')
  var categoryUrl = apiRoutes.categoryServiceProviders += theCategory

  buildList(categoryEndpoint.getEndpointUrl(categoryUrl, theLocation))

  function buildList (url) {
    // Get API data using promise
    getApiData.data(url).then(function (result) {
      var data = result.data

      // Get category name and edit page title
      var theTitle = data.name + ' - Street Support'
      document.title = theTitle

      data.subCategories = sortBy(data.subCategories, function (item) {
        return item.name
      })

      // Append object name for Hogan
      var theData = { organisations: data }
      var template = ''
      var callback = function () {}

      if (data.subCategories.length) {
        template = 'js-category-result-tpl'
        callback = function () {
          accordion.init()
        }
      } else {
        template = 'js-category-no-results-result-tpl'
      }

      templating.renderTemplate(template, theData, 'js-category-result-output', callback)

      loading.stop()
      analytics.init(theTitle)
      socialShare.init()
    })
  }
})
<commit_msg>category page on error, redirect to find-help<commit_after>// Page modules
var FastClick = require('fastclick')
var nav = require('./nav.js')
var urlParameter = require('./get-url-parameter')
var accordion = require('./accordion')
var socialShare = require('./social-share')
var sortBy = require('lodash/collection/sortBy')

nav.init()
FastClick.attach(document.body)

// Load and process data
require.ensure(['./api', './get-api-data', './category-endpoint', './template-render', 'spin.js', './analytics'], function (require) {
  var apiRoutes = require('./api')
  var getApiData = require('./get-api-data')
  var categoryEndpoint = require('./category-endpoint')
  var templating = require('./template-render')
  var Spinner = require('spin.js')
  var analytics = require('./analytics')

  // Spinner
  var spin = document.getElementById('spin')
  var loading = new Spinner().spin(spin)

  // Get category and create URL
  var theCategory = urlParameter.parameter('category')
  var theLocation = urlParameter.parameter('location')
  var categoryUrl = apiRoutes.categoryServiceProviders += theCategory

  buildList(categoryEndpoint.getEndpointUrl(categoryUrl, theLocation))

  function buildList (url) {
    // Get API data using promise
    getApiData.data(url).then(function (result) {
      if (result.status === 'error') {
        window.location.replace('/find-help.html')
      }
      var data = result.data

      // Get category name and edit page title
      var theTitle = data.name + ' - Street Support'
      document.title = theTitle

      data.subCategories = sortBy(data.subCategories, function (item) {
        return item.name
      })

      // Append object name for Hogan
      var theData = { organisations: data }
      var template = ''
      var callback = function () {}

      if (data.subCategories.length) {
        template = 'js-category-result-tpl'
        callback = function () {
          accordion.init()
        }
      } else {
        template = 'js-category-no-results-result-tpl'
      }

      templating.renderTemplate(template, theData, 'js-category-result-output', callback)

      loading.stop()
      analytics.init(theTitle)
      socialShare.init()
    })
  }
})
<|endoftext|>