<commit_before>/*
 * Copyright (C) 2018 Deciso B.V.
 * Copyright (C) 2018 Franco Fichtner <franco@opnsense.org>
 * Copyright (C) 2018 René Muhr <rene@team-rebellion.net>
 * Copyright (C) 2018 Fabian Franz
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/* hook-in additional theme functionality */

$(document).ready(function () {
    // traverse loaded css files
    var toggle_sidebar_loaded = false;
    var $window = $(window);
    var winheight = $(window).height();
    var nomouse = "mouseenter mouseleave";
    var layer1_a = $('#mainmenu > div > a');
    var layer1_div = $('#mainmenu > div > div');
    var layer2_a = $('#mainmenu > div > div > a');
    var layer2_div = $('#mainmenu > div > div > div');
    var navigation = $("#navigation");
    var events = {
        mouseenter: function () {
            $("#navigation.col-sidebar-left").css("width", "415px");
            var that = $(this);
            if (that.next("div").hasClass("in")) {
                /* no action needed */
            } else if ((that.next().is("a")) || (that.is("a:last-child"))) {
                activeremove(this);
            } else {
                var divtop = that.offset().top - $window.scrollTop();
                var divheight = that.next("div").height();
                var currentheight = (divtop + divheight);
                that.trigger("click");
                if (currentheight > winheight) {
                    that.next("div").css("margin-top", -divheight - (that.is(layer1_a) ? 3 : 0));
                }
            }
        },
        mouseleave: function () {
            $("#navigation.col-sidebar-left").css("width", "70px");
        },
        mousedown: function () {
            $(this).trigger("click");
        },
        mouseup: function () {
            $(this).blur();
        }
    };
    var events2 = {
        mouseenter: function () {
            $("#navigation.col-sidebar-left").css("width", "415px");
            $(this).trigger("click");
        },
        mouseleave: function () {
            $("#navigation.col-sidebar-left").css("width", "70px");
        }
    };

    $.each(document.styleSheets, function (sheetIndex, sheet) {
        if (sheet.href !== null && sheet.href.match(/main\.css(\?v=\w+$)?/gm)) {
            $.each(sheet.cssRules || sheet.rules, function (ruleIndex, rule) {
                if (rule.cssText.indexOf('toggle-sidebar') >= 0) {
                    toggle_sidebar_loaded = true;
                }
            });
        }
    });

    /* disable mouseevents on toggle and resize */
    function mouse_events_off() {
        layer1_a.off(nomouse);
        layer2_a.off(nomouse);
        layer1_div.off(nomouse);
        layer2_div.off(nomouse);
    }

    /* trigger mouseevents on startup */
    function trigger_sidebar() {
        layer1_a.first().trigger("mouseenter").trigger("mouseleave");
        layer1_div.removeClass("in");
        layer2_div.removeClass("in");
    }

    /* transition duration - time */
    function transition_duration(time) {
        $.fn.collapse.Constructor.TRANSITION_DURATION = time;
    }

    function activeremove(e) {
        $(e).nextAll("a").addClass("collapsed").attr("aria-expanded", "false");
        $(e).prevAll("a").addClass("collapsed").attr("aria-expanded", "false");
        $(e).next("a").addClass("collapsed").attr("aria-expanded", "false");
        $(e).prev("a").addClass("collapsed").attr("aria-expanded", "false");
        $(e).nextAll("div").removeClass("in").attr("aria-expanded", "false");
        $(e).prevAll("div").removeClass("in").attr("aria-expanded", "false");
        $(e).next("div").removeClass("in").attr("aria-expanded", "false");
        $(e).prev("div").removeClass("in").attr("aria-expanded", "false");
        $(e).trigger("click");
    }

    function opnsense_sidebar_toggle(store) {
        navigation.toggleClass("col-sidebar-left");
        $("main").toggleClass("col-sm-9 col-sm-push-3 col-lg-10 col-lg-push-2 col-lg-12");
        $(".toggle-sidebar > i").toggleClass("fa-chevron-right fa-chevron-left");
        if (navigation.hasClass("col-sidebar-left")) {
            $(".brand-logo").css("display", "none");
            $(".brand-icon").css("display", "inline-block");
            trigger_sidebar();
            if (store && window.sessionStorage) {
                sessionStorage.setItem('toggle_sidebar_preset', 1);
                transition_duration(0);
            }
        } else {
            $(".brand-icon").css("display", "none");
            $(".brand-logo").css("display", "inline-block");
            $("#navigation.page-side.col-xs-12.col-sm-3.col-lg-2.hidden-xs").css("width", "");
            if (store && window.sessionStorage) {
                sessionStorage.setItem('toggle_sidebar_preset', 0);
                mouse_events_off();
                transition_duration(350);
            }
        }
    }

    if (toggle_sidebar_loaded) {
        var toggle_btn = $(".toggle-sidebar");
        /* navigation toggle */
        toggle_btn.click(function () {
            opnsense_sidebar_toggle(true);
            $(this).blur();
        });

        /* sidebar mouseenter */
        navigation.mouseenter(function () {
            if (navigation.hasClass("col-sidebar-left")) {
                transition_duration(0);
                layer1_a.on(events);
                layer2_a.on(events);
                layer1_div.on(events2);
                layer2_div.on(events2);
            }
        });

        /* sidebar mouseleave */
        $("#mainmenu").mouseleave(function () {
            if ($("#navigation").hasClass("col-sidebar-left")) {
                layer1_a.attr("aria-expanded", "false").next("div").removeClass("in");
                layer2_a.attr("aria-expanded", "false").next("div").removeClass("in");
                layer1_div.removeAttr("style");
                layer2_div.removeAttr("style");
            }
        });

        /* on resize - toggle sidebar / main navigation */
        $(window).on('resize', function () {
            var win = $(this);
            winheight = win.height();
            if ((win.height() < 675 || win.width() < 760) && navigation.not("col-sidebar-hidden")) {
                navigation.addClass("col-sidebar-hidden");
                mouse_events_off();
                if (navigation.hasClass("col-sidebar-left")) {
                    opnsense_sidebar_toggle(false);
                    mouse_events_off();
                    transition_duration(350);
                }
            } else if ((win.height() >= 675 && win.width() >= 760) && navigation.hasClass("col-sidebar-hidden")) {
                $("#navigation").removeClass("col-sidebar-hidden");
                transition_duration(0);
                if (window.sessionStorage && sessionStorage.getItem('toggle_sidebar_preset') == 1) {
                    opnsense_sidebar_toggle(false);
                }
            }
        });

        /* only show toggle button when style is loaded */
        toggle_btn.show();

        /* auto-collapse if previously requested */
        if (window.sessionStorage && sessionStorage.getItem('toggle_sidebar_preset') == 1) {
            opnsense_sidebar_toggle(false);
        }
    }
});
<commit_msg>sidebar - performance & optimisation; closes #2938<commit_after>/*
 * Copyright (C) 2018 Deciso B.V.
 * Copyright (C) 2018 Franco Fichtner <franco@opnsense.org>
 * Copyright (C) 2018 René Muhr <rene@team-rebellion.net>
 * Copyright (C) 2018 Fabian Franz
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/* hook-in additional theme functionality */

$(document).ready(function () {
    // traverse loaded css files
    var toggle_sidebar_loaded = false,
    $window = $(window),
    winHeight = $(window).height(),
    mouse = 'mouseenter mouseleave',
    layer1_a = $('#mainmenu > div > a'),
    layer1_div = $('#mainmenu > div > div'),
    layer2_a = $('#mainmenu > div > div > a'),
    layer2_div = $('#mainmenu > div > div > div'),
    navigation = $('#navigation'),
    mainmenu = $('#mainmenu'),
    events = {
        mouseenter: function () {
            $('#navigation.col-sidebar-left').css('width', '415px');
            var that = $(this);
            if (that.next('div').hasClass('in')) {
                /* no action needed */
            } else if ((that.next().is('a')) || (that.is('a:last-child'))) {
                activeremove(this);
            } else {
                var offsetTop = that.offset().top;
                var winscrTop = $window.scrollTop();
                var divHeight = that.next('div').height();
                var divTop = (offsetTop - winscrTop);
                var currentHeight = (divTop + divHeight);
                var thatTrigger = that.trigger('click');
                if (currentHeight > winHeight) {
                    var result = that.next('div').css('margin-top', -divHeight - (that.is(layer1_a) ? 3 : 0));
                }
            }
        },
        mouseleave: function () {
            $('#navigation.col-sidebar-left').css('width', '70px');
            layer1_a.off(events).on(events);
        },
        mousedown: function () {
            $(this).trigger('click');
        },
        mouseup: function () {
            $(this).blur();
        }
    },
    events2 = {
        mouseenter: function () {
            $('#navigation.col-sidebar-left').css('width', '415px');
            $(this).trigger('click');
        },
        mouseleave: function () {
            $('#navigation.col-sidebar-left').css('width', '70px');
        }
    };

    $.each(document.styleSheets, function (sheetIndex, sheet) {
        if (sheet.href !== null && sheet.href.match(/main\.css(\?v=\w+$)?/gm)) {
            $.each(sheet.cssRules || sheet.rules, function (ruleIndex, rule) {
                if (rule.cssText.indexOf('toggle-sidebar') >= 0) {
                    toggle_sidebar_loaded = true;
                }
            });
        }
    });

    /* disable mouseevents on toggle and resize */
    function mouse_events_off() {
        layer1_a.off(mouse);
        layer2_a.off(mouse);
        layer1_div.off(mouse);
        layer2_div.off(mouse);
    }

    /* trigger mouseevents on startup */
    function trigger_sidebar() {
        layer1_a.first().trigger('mouseenter').trigger('mouseleave');
        layer1_div.removeClass('in');
        layer2_div.removeClass('in');
    }

    /* transition duration - time */
    function transition_duration(time) {
        $.fn.collapse.Constructor.TRANSITION_DURATION = time;
    }

    /* remove open submenu */
    function activeremove(r) {
        $(r).nextAll('a').addClass('collapsed').attr('aria-expanded', 'false');
        $(r).prevAll('a').addClass('collapsed').attr('aria-expanded', 'false');
        $(r).nextAll('div').removeClass('in').attr('aria-expanded', 'false');
        $(r).prevAll('div').removeClass('in').attr('aria-expanded', 'false');
    }

    function opnsense_sidebar_toggle(store) {
        navigation.toggleClass('col-sidebar-left');
        $('main').toggleClass('col-sm-9 col-sm-push-3 col-lg-10 col-lg-push-2 col-lg-12');
        $('.toggle-sidebar > i').toggleClass('fa-chevron-right fa-chevron-left');
        if (navigation.hasClass('col-sidebar-left')) {
            $('.brand-logo').css('display', 'none');
            $('.brand-icon').css('display', 'inline-block');
            trigger_sidebar();
            if (store && window.sessionStorage) {
                sessionStorage.setItem('toggle_sidebar_preset', 1);
                transition_duration(0);
            }
        } else {
            $('.brand-icon').css('display', 'none');
            $('.brand-logo').css('display', 'inline-block');
            $('#navigation.page-side.col-xs-12.col-sm-3.col-lg-2.hidden-xs').css('width', '');
            if (store && window.sessionStorage) {
                sessionStorage.setItem('toggle_sidebar_preset', 0);
                mouse_events_off();
                transition_duration(350);
            }
        }
    }

    if (toggle_sidebar_loaded) {
        var toggle_btn = $('.toggle-sidebar');
        /* navigation toggle */
        toggle_btn.click(function () {
            opnsense_sidebar_toggle(true);
            $(this).blur();
        });

        /* sidebar mouseenter */
        mainmenu.mouseenter(function () {
            if (navigation.hasClass('col-sidebar-left')) {
                transition_duration(0);
                layer1_a.on(events);
                layer2_a.on(events);
                layer1_div.on(events2);
                layer2_div.on(events2);
            }
        });

        /* sidebar mouseleave */
        mainmenu.mouseleave(function () {
            if ($('#navigation').hasClass('col-sidebar-left')) {
                layer1_a.attr('aria-expanded', 'false').next('div').removeClass('in');
                layer2_a.attr('aria-expanded', 'false').next('div').removeClass('in');
                layer1_div.removeAttr('style');
                layer2_div.removeAttr('style');
                layer2_a.off(events);
                layer1_div.off(events2);
                layer2_div.off(events2);
            }
        });

        /* on resize - toggle sidebar / main navigation */
        $(window).on('resize', function () {
            var win = $(this);
            winHeight = win.height();
            if ((win.height() < 675 || win.width() < 760) && navigation.not('col-sidebar-hidden')) {
                navigation.addClass('col-sidebar-hidden');
                mouse_events_off();
                if (navigation.hasClass('col-sidebar-left')) {
                    opnsense_sidebar_toggle(false);
                    mouse_events_off();
                    transition_duration(350);
                }
            } else if ((win.height() >= 675 && win.width() >= 760) && navigation.hasClass('col-sidebar-hidden')) {
                navigation.removeClass('col-sidebar-hidden');
                transition_duration(0);
                if (window.sessionStorage && sessionStorage.getItem('toggle_sidebar_preset') == 1) {
                    opnsense_sidebar_toggle(false);
                }
            }
        });

        /* only show toggle button when style is loaded */
        toggle_btn.show();

        /* auto-collapse if previously requested */
        if (window.sessionStorage && sessionStorage.getItem('toggle_sidebar_preset') == 1) {
            opnsense_sidebar_toggle(false);
        }
    }
});
<|endoftext|>


<commit_before>define(function (require) {

    var zrUtil = require('zrender/core/util');
    var AxisBuilder = require('./AxisBuilder');
    var BrushController = require('../helper/BrushController');

    var elementList = ['axisLine', 'axisLabel', 'axisTick', 'axisName'];

    var AxisView = require('../../echarts').extendComponentView({

        type: 'parallelAxis',

        /**
         * @override
         */
        init: function (ecModel, api) {
            AxisView.superApply(this, 'init', arguments);

            /**
             * @type {module:echarts/component/helper/BrushController}
             */
            (this._brushController = new BrushController(api.getZr()))
                .on('brush', zrUtil.bind(this._onBrush, this));
        },

        /**
         * @override
         */
        render: function (axisModel, ecModel, api, payload) {
            if (fromAxisAreaSelect(axisModel, ecModel, payload)) {
                return;
            }

            this.axisModel = axisModel;
            this.api = api;

            this.group.removeAll();

            if (!axisModel.get('show')) {
                return;
            }

            var coordSys = ecModel.getComponent(
                'parallel', axisModel.get('parallelIndex')
            ).coordinateSystem;

            var areaSelectStyle = axisModel.getAreaSelectStyle();
            var areaWidth = areaSelectStyle.width;

            var dim = axisModel.axis.dim;
            var axisLayout = coordSys.getAxisLayout(dim);

            // Fetch from axisModel by default.
            var axisNameTruncateLength;
            var axisNameTruncateEllipsis;
            var axisLabelShow;
            var axisIndex = zrUtil.indexOf(coordSys.dimensions, dim);

            var axisExpandWindow = axisLayout.axisExpandWindow;
            if (axisExpandWindow
                && (axisIndex <= axisExpandWindow[0] || axisIndex >= axisExpandWindow[1])
            ) {
                // edge axis do not show detail info, otherwise overlap.
                axisNameTruncateLength = 1;
                axisNameTruncateEllipsis = '';
                axisLabelShow = false;
            }

            var builderOpt = zrUtil.extend(
                {
                    axisLabelShow: axisLabelShow,
                    axisNameTruncateLength: axisNameTruncateLength,
                    axisNameTruncateEllipsis: axisNameTruncateEllipsis,
                    strokeContainThreshold: areaWidth,
                    // lineWidth === 0 or no value.
                    axisLineSilent: !(areaWidth > 0) // jshint ignore:line
                },
                axisLayout
            );

            var axisBuilder = new AxisBuilder(axisModel, builderOpt);

            zrUtil.each(elementList, axisBuilder.add, axisBuilder);

            var axisGroup = axisBuilder.getGroup();

            this.group.add(axisGroup);

            this._refreshBrushController(builderOpt, areaSelectStyle, axisModel, areaWidth);
        },

        _refreshBrushController: function (builderOpt, areaSelectStyle, axisModel, areaWidth) {
            // After filtering, axis may change, select area needs to be update.
            var axis = axisModel.axis;
            var coverInfoList = zrUtil.map(axisModel.activeIntervals, function (interval) {
                return {
                    brushType: 'lineX',
                    panelId: 'pl',
                    range: [
                        axis.dataToCoord(interval[0], true),
                        axis.dataToCoord(interval[1], true)
                    ]
                };
            });

            var extent = axis.getExtent();
            var extra = 30; // Arbitrary value.
            var rect = {
                x: extent[0] - extra,
                y: -areaWidth / 2,
                width: extent[1] - extent[0] + 2 * extra,
                height: areaWidth
            };

            this._brushController
                .mount({
                    enableGlobalPan: true,
                    rotation: builderOpt.rotation,
                    position: builderOpt.position
                })
                .setPanels([{
                    panelId: 'pl',
                    rect: rect
                }])
                .enableBrush({
                    brushType: 'lineX',
                    brushStyle: areaSelectStyle,
                    removeOnClick: true
                })
                .updateCovers(coverInfoList);
        },

        _onBrush: function (coverInfoList, opt) {
            // Do not cache these object, because the mey be changed.
            var axisModel = this.axisModel;
            var axis = axisModel.axis;

            var intervals = zrUtil.map(coverInfoList, function (coverInfo) {
                return [
                    axis.coordToData(coverInfo.range[0], true),
                    axis.coordToData(coverInfo.range[1], true)
                ];
            });

            // If realtime is true, action is not dispatched on drag end, because
            // the drag end emits the same params with the last drag move event,
            // and may have some delay when using touch pad.
            if (!axisModel.option.realtime === opt.isEnd || opt.removeOnClick) { // jshint ignore:line
                this.api.dispatchAction({
                    type: 'axisAreaSelect',
                    parallelAxisId: axisModel.id,
                    intervals: intervals
                });
            }
        },

        /**
         * @override
         */
        dispose: function () {
            this._brushController.dispose();
        }
    });

    function fromAxisAreaSelect(axisModel, ecModel, payload) {
        return payload
            && payload.type === 'axisAreaSelect'
            && ecModel.findComponents(
                {mainType: 'parallelAxis', query: payload}
            )[0] === axisModel;
    }

    return AxisView;
});<commit_msg>Parallel axis animation<commit_after>define(function (require) {

    var zrUtil = require('zrender/core/util');
    var AxisBuilder = require('./AxisBuilder');
    var BrushController = require('../helper/BrushController');
    var graphic = require('../../util/graphic');

    var elementList = ['axisLine', 'axisLabel', 'axisTick', 'axisName'];

    var AxisView = require('../../echarts').extendComponentView({

        type: 'parallelAxis',

        /**
         * @override
         */
        init: function (ecModel, api) {
            AxisView.superApply(this, 'init', arguments);

            /**
             * @type {module:echarts/component/helper/BrushController}
             */
            (this._brushController = new BrushController(api.getZr()))
                .on('brush', zrUtil.bind(this._onBrush, this));
        },

        /**
         * @override
         */
        render: function (axisModel, ecModel, api, payload) {
            if (fromAxisAreaSelect(axisModel, ecModel, payload)) {
                return;
            }

            this.axisModel = axisModel;
            this.api = api;

            this.group.removeAll();

            var oldAxisGroup = this._axisGroup;
            this._axisGroup = new graphic.Group();
            this.group.add(this._axisGroup);

            if (!axisModel.get('show')) {
                return;
            }

            var coordSys = ecModel.getComponent(
                'parallel', axisModel.get('parallelIndex')
            ).coordinateSystem;

            var areaSelectStyle = axisModel.getAreaSelectStyle();
            var areaWidth = areaSelectStyle.width;

            var dim = axisModel.axis.dim;
            var axisLayout = coordSys.getAxisLayout(dim);

            // Fetch from axisModel by default.
            var axisNameTruncateLength;
            var axisNameTruncateEllipsis;
            var axisLabelShow;
            var axisIndex = zrUtil.indexOf(coordSys.dimensions, dim);

            var axisExpandWindow = axisLayout.axisExpandWindow;
            if (axisExpandWindow
                && (axisIndex <= axisExpandWindow[0] || axisIndex >= axisExpandWindow[1])
            ) {
                // edge axis do not show detail info, otherwise overlap.
                axisNameTruncateLength = 1;
                axisNameTruncateEllipsis = '';
                axisLabelShow = false;
            }

            var builderOpt = zrUtil.extend(
                {
                    axisLabelShow: axisLabelShow,
                    axisNameTruncateLength: axisNameTruncateLength,
                    axisNameTruncateEllipsis: axisNameTruncateEllipsis,
                    strokeContainThreshold: areaWidth,
                    // lineWidth === 0 or no value.
                    axisLineSilent: !(areaWidth > 0) // jshint ignore:line
                },
                axisLayout
            );

            var axisBuilder = new AxisBuilder(axisModel, builderOpt);

            zrUtil.each(elementList, axisBuilder.add, axisBuilder);

            this._axisGroup.add(axisBuilder.getGroup());

            this._refreshBrushController(builderOpt, areaSelectStyle, axisModel, areaWidth);

            graphic.groupTransition(oldAxisGroup, this._axisGroup, axisModel);
        },

        _refreshBrushController: function (builderOpt, areaSelectStyle, axisModel, areaWidth) {
            // After filtering, axis may change, select area needs to be update.
            var axis = axisModel.axis;
            var coverInfoList = zrUtil.map(axisModel.activeIntervals, function (interval) {
                return {
                    brushType: 'lineX',
                    panelId: 'pl',
                    range: [
                        axis.dataToCoord(interval[0], true),
                        axis.dataToCoord(interval[1], true)
                    ]
                };
            });

            var extent = axis.getExtent();
            var extra = 30; // Arbitrary value.
            var rect = {
                x: extent[0] - extra,
                y: -areaWidth / 2,
                width: extent[1] - extent[0] + 2 * extra,
                height: areaWidth
            };

            this._brushController
                .mount({
                    enableGlobalPan: true,
                    rotation: builderOpt.rotation,
                    position: builderOpt.position
                })
                .setPanels([{
                    panelId: 'pl',
                    rect: rect
                }])
                .enableBrush({
                    brushType: 'lineX',
                    brushStyle: areaSelectStyle,
                    removeOnClick: true
                })
                .updateCovers(coverInfoList);
        },

        _onBrush: function (coverInfoList, opt) {
            // Do not cache these object, because the mey be changed.
            var axisModel = this.axisModel;
            var axis = axisModel.axis;

            var intervals = zrUtil.map(coverInfoList, function (coverInfo) {
                return [
                    axis.coordToData(coverInfo.range[0], true),
                    axis.coordToData(coverInfo.range[1], true)
                ];
            });

            // If realtime is true, action is not dispatched on drag end, because
            // the drag end emits the same params with the last drag move event,
            // and may have some delay when using touch pad.
            if (!axisModel.option.realtime === opt.isEnd || opt.removeOnClick) { // jshint ignore:line
                this.api.dispatchAction({
                    type: 'axisAreaSelect',
                    parallelAxisId: axisModel.id,
                    intervals: intervals
                });
            }
        },

        /**
         * @override
         */
        dispose: function () {
            this._brushController.dispose();
        }
    });

    function fromAxisAreaSelect(axisModel, ecModel, payload) {
        return payload
            && payload.type === 'axisAreaSelect'
            && ecModel.findComponents(
                {mainType: 'parallelAxis', query: payload}
            )[0] === axisModel;
    }

    return AxisView;
});<|endoftext|>


<commit_before>// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information.

/**
 * Nav Bar Plugin
 */
(function ($) {
  $.fn.NavBar = function () {

    /** Go through each nav bar we've been given. */
    return this.each(function () {

      var $navBar = $(this);

      // Open the nav bar on mobile.
      $navBar.on('click', '.js-openMenu', function(event) {
        event.stopPropagation();
        $navBar.toggleClass('is-open');
      });

      // Close the nav bar on mobile.
      $navBar.click(function() {
        if ($navBar.hasClass('is-open')) {
          $navBar.removeClass('is-open');
        }
      });

      // Set selected states and open/close menus.
      $navBar.on('click', '.ms-NavBar-item:not(.is-disabled)', function(event) {
        event.stopPropagation();

        // Prevent default actions from firing if links are not found.
        if ($(this).children('.ms-NavBar-link').length === 0) {
          event.preventDefault();
        }

        // Deselect all of the items.
        $(this).siblings('.ms-NavBar-item').removeClass('is-selected');

        // Close and blur the search box if it doesn't have text.
        if ($navBar.find('.ms-NavBar-item.ms-NavBar-item--search .ms-TextField-field').val().length === 0) {
          $('.ms-NavBar-item.ms-NavBar-item--search').removeClass('is-open').find('.ms-TextField-field').blur();
        }

        // Does the selected item have a menu?
        if ($(this).hasClass('ms-NavBar-item--hasMenu')) {

          // Toggle 'is-open' to open or close it.
          $(this).children('.ms-ContextualMenu:first').toggleClass('is-open');

          // Toggle 'is-selected' to indicate whether it is active.
          $(this).toggleClass('is-selected');
        } else {
          // Doesn't have a menu, so just select the item.
          $(this).addClass('is-selected');

          // Close the submenu and any open contextual menus.
          $navBar.removeClass('is-open').find('.ms-ContextualMenu').removeClass('is-open');
        }

        // Is this the search box? Open it up and focus on the search field.
        if ($(this).hasClass('ms-NavBar-item--search')) {
          $(this).addClass('is-open');
          $(this).find('.ms-TextField-field').focus();

          // Close any open menus.
          $navBar.find('.ms-ContextualMenu:first').removeClass('is-open');
        }
      });

      // Prevent contextual menus from being hidden when clicking on them.
      $navBar.on('click', '.ms-NavBar-item .ms-ContextualMenu', function(event) {
        event.stopPropagation();

        // Collapse the mobile "panel" for nav items.
        $(this).removeClass('is-open');
        $navBar.removeClass('is-open').find('.ms-NavBar-item--hasMenu').removeClass('is-selected');
      });

      // Hide any menus and close the search box when clicking anywhere in the document.
      $(document).on('click', 'html', function(event) {
        $navBar.find('.ms-NavBar-item').removeClass('is-selected').find('.ms-ContextualMenu').removeClass('is-open');

        // Close and blur the search box if it doesn't have text.
        if ($navBar.find('.ms-NavBar-item.ms-NavBar-item--search .ms-TextField-field').val().length === 0) {
          $navBar.find('.ms-NavBar-item.ms-NavBar-item--search').removeClass('is-open').find('.ms-TextField-field').blur();
        }
      });
    });
  };
})(jQuery);
<commit_msg>Fix: Click on navBar crashed with null reference when nav bar doesn't have a search box.<commit_after>// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information.

/**
 * Nav Bar Plugin
 */
(function ($) {
  $.fn.NavBar = function () {

    /** Go through each nav bar we've been given. */
    return this.each(function () {

      var $navBar = $(this);

      // Open the nav bar on mobile.
      $navBar.on('click', '.js-openMenu', function(event) {
        event.stopPropagation();
        $navBar.toggleClass('is-open');
      });

      // Close the nav bar on mobile.
      $navBar.click(function() {
        if ($navBar.hasClass('is-open')) {
          $navBar.removeClass('is-open');
        }
      });

      // Set selected states and open/close menus.
      $navBar.on('click', '.ms-NavBar-item:not(.is-disabled)', function(event) {
        var $searchBox = $navBar.find('.ms-NavBar-item.ms-NavBar-item--search .ms-TextField-field');
        event.stopPropagation();

        // Prevent default actions from firing if links are not found.
        if ($(this).children('.ms-NavBar-link').length === 0) {
          event.preventDefault();
        }

        // Deselect all of the items.
        $(this).siblings('.ms-NavBar-item').removeClass('is-selected');

        // Close and blur the search box if it doesn't have text.
        if ($searchBox.length > 0 && $searchBox.val().length === 0) {
          $('.ms-NavBar-item.ms-NavBar-item--search').removeClass('is-open').find('.ms-TextField-field').blur();
        }

        // Does the selected item have a menu?
        if ($(this).hasClass('ms-NavBar-item--hasMenu')) {

          // Toggle 'is-open' to open or close it.
          $(this).children('.ms-ContextualMenu:first').toggleClass('is-open');

          // Toggle 'is-selected' to indicate whether it is active.
          $(this).toggleClass('is-selected');
        } else {
          // Doesn't have a menu, so just select the item.
          $(this).addClass('is-selected');

          // Close the submenu and any open contextual menus.
          $navBar.removeClass('is-open').find('.ms-ContextualMenu').removeClass('is-open');
        }

        // Is this the search box? Open it up and focus on the search field.
        if ($(this).hasClass('ms-NavBar-item--search')) {
          $(this).addClass('is-open');
          $(this).find('.ms-TextField-field').focus();

          // Close any open menus.
          $navBar.find('.ms-ContextualMenu:first').removeClass('is-open');
        }
      });

      // Prevent contextual menus from being hidden when clicking on them.
      $navBar.on('click', '.ms-NavBar-item .ms-ContextualMenu', function(event) {
        event.stopPropagation();

        // Collapse the mobile "panel" for nav items.
        $(this).removeClass('is-open');
        $navBar.removeClass('is-open').find('.ms-NavBar-item--hasMenu').removeClass('is-selected');
      });

      // Hide any menus and close the search box when clicking anywhere in the document.
      $(document).on('click', 'html', function(event) {
		var $searchBox = $navBar.find('.ms-NavBar-item.ms-NavBar-item--search .ms-TextField-field');
        $navBar.find('.ms-NavBar-item').removeClass('is-selected').find('.ms-ContextualMenu').removeClass('is-open');

        // Close and blur the search box if it doesn't have text.
        if ($searchBox.length > 0 && $searchBox.val().length === 0) {
          $navBar.find('.ms-NavBar-item.ms-NavBar-item--search').removeClass('is-open').find('.ms-TextField-field').blur();
        }
      });
    });
  };
})(jQuery);
<|endoftext|>


<commit_before>import './SectionDate.less';
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment-timezone';
import { DEFAULT_DATE_TIME_FORMAT, PARSING_STRING_WITH_TIME_ZONE } from '../../constants/Constants';

export function dateToMoment(date) {
  if (!date) {
    return 'N/A';
  }

  let result;

  result = moment(date, PARSING_STRING_WITH_TIME_ZONE);
  if (result.isValid()) {
    return result;
  }

  result = moment(date);
  if (result.isValid()) {
    return result;
  }

  // fallback to raw object
  return date;
}

const SectionDate = ({ date, style, format, isPrefixRequired = true }) => {
  const dateTime = dateToMoment(date);
  return (

      {isPrefixRequired && Date:}

        {moment.isMoment(dateTime) ? dateTime.tz(moment.tz.guess())
          .format(format || DEFAULT_DATE_TIME_FORMAT) : dateTime}

);
};
SectionDate.propTypes = {
  date: PropTypes.string,
  style: PropTypes.object,
  format: PropTypes.string,
  isPrefixRequired: PropTypes.bool
};

export default SectionDate;
<commit_msg>random commit<commit_after>import './SectionDate.less';
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment-timezone';
import { DEFAULT_DATE_TIME_FORMAT, PARSING_STRING_WITH_TIME_ZONE } from '../../constants/Constants';

export function dateToMoment(date) {
  if (!date) {
    return 'N/A';
  }
  let result;

  result = moment(date, PARSING_STRING_WITH_TIME_ZONE);
  if (result.isValid()) {
    return result;
  }

  result = moment(date);
  if (result.isValid()) {
    return result;
  }

  // fallback to raw object
  return date;
}

const SectionDate = ({ date, style, format, isPrefixRequired = true }) => {
  const dateTime = dateToMoment(date);
  return (

      {isPrefixRequired && Date:}

        {moment.isMoment(dateTime) ? dateTime.tz(moment.tz.guess())
          .format(format || DEFAULT_DATE_TIME_FORMAT) : dateTime}

);
};
SectionDate.propTypes = {
  date: PropTypes.string,
  style: PropTypes.object,
  format: PropTypes.string,
  isPrefixRequired: PropTypes.bool
};

export default SectionDate;
<|endoftext|>


<commit_before>import '../../js/services/apikey';
import 'angular';
import 'firebase';
import 'angularfire';

export default angular.module('sol-backend', ['firebase', 'api-key'])
.service('solBackend',
        ['$q', '$firebaseAuth', '$firebaseArray', '$firebaseObject', 'ApiKey', '$rootScope',
        function ($q, $firebaseAuth, $firebaseArray, $firebaseObject, apiKey, $rootScope) {
    const connector = $q((resolve, reject) => {
        let key = apiKey.get('firebase');

        if (key) {
            let url = `https://${key}.firebaseio.com`;
            let ref = new Firebase(url);
            resolve(ref);
        }
        else {
            $rootScope.$on('firebase-apikey', function(e, key) {
                let url = `https://${key}.firebaseio.com`;
                let ref = new Firebase(url);
                resolve(ref);
            });
        }
    });

    Object.assign(this, {
        authenticateWithPopup () {
            return this.getAuth().then(($auth) => {
                return $auth.$authWithOAuthPopup('google');
            });
        },

        unauthenticate () {
            return this.getAuth().then(($auth) => {
                $auth.$unauth();
            });
        },

        getAuth () {
            return connector.then((firebase) => {
                return $firebaseAuth(firebase);
            });
        },

        getAuthObject () {
            return this.getAuth().then(($auth) => {
                return $auth.$getAuth();
            });
        },

        fetchPlaylist (refKey) {
            return connector.then((firebase) => {
                return $firebaseObject(firebase.child('playlists').child(refKey));
            }).then((record) => {
                return $q((resolve, reject) => {
                    record.$ref().once('value', (snap) => {
                        let val = snap.val();
                        if (val !== null && Array.isArray(val.playlist))
                            resolve(val.playlist);
                        else
                            reject();
                    });
                });
            });
        },

        fetchUserPlaylists (uid) {
            if (typeof uid !== 'string' || !uid) return $q.reject();

            return connector.then((firebase) => {
                let playlists = firebase.child('playlists');
                return $firebaseArray(
                    playlists.orderByChild('uid').equalTo(uid));
            });
        },

        publishPlaylist (playlist) {
            return connector.then((firebase) => {
                return $firebaseArray(firebase.child('playlists'));
            }).then((record) => {
                return this.getAuthObject().then((auth) => {
                    let uid = auth === null ? null : auth.uid;
                    let data = { uid, playlist };

                    return record.$add(data);
                });
            }).then((ref) => {
                return ref.key();
            });
        }
    });

}]);
<commit_msg>Rewrite solBackend.publishPlaylist so it will also write metadata<commit_after>import '../../js/services/apikey';
import 'angular';
import 'firebase';
import 'angularfire';

export default angular.module('sol-backend', ['firebase', 'api-key'])
.service('solBackend',
        ['$q', '$firebaseAuth', '$firebaseArray', '$firebaseObject', 'ApiKey', '$rootScope',
        function ($q, $firebaseAuth, $firebaseArray, $firebaseObject, apiKey, $rootScope) {
    const connector = $q((resolve, reject) => {
        let key = apiKey.get('firebase');

        if (key) {
            let url = `https://${key}.firebaseio.com`;
            let ref = new Firebase(url);
            resolve(ref);
        }
        else {
            $rootScope.$on('firebase-apikey', function(e, key) {
                let url = `https://${key}.firebaseio.com`;
                let ref = new Firebase(url);
                resolve(ref);
            });
        }
    });

    Object.assign(this, {
        authenticateWithPopup () {
            return this.getAuth().then(($auth) => {
                return $auth.$authWithOAuthPopup('google');
            });
        },

        unauthenticate () {
            return this.getAuth().then(($auth) => {
                $auth.$unauth();
            });
        },

        getAuth () {
            return connector.then((firebase) => {
                return $firebaseAuth(firebase);
            });
        },

        getAuthData () {
            return this.getAuth().then(($auth) => {
                return $auth.$getAuth();
            });
        },

        fetchPlaylist (refKey) {
            return connector.then((firebase) => {
                return $firebaseObject(firebase.child('playlists').child(refKey));
            }).then((record) => {
                return $q((resolve, reject) => {
                    record.$ref().once('value', (snap) => {
                        let val = snap.val();
                        if (val !== null && Array.isArray(val.playlist))
                            resolve(val.playlist);
                        else
                            reject();
                    });
                });
            });
        },

        fetchUserPlaylists (uid) {
            if (typeof uid !== 'string' || !uid) return $q.reject();

            return connector.then((firebase) => {
                let playlists = firebase.child('playlists/metadata');
                let ref = playlists.orderByChild('uid').equalTo(uid);
                return $firebaseArray(ref);
            });
        },

        publishPlaylist (playlist, playlistMetadata = {}) {
            let firebase;
            let auth;

            return connector.then((_firebase) => {
                firebase = _firebase;
            }).then(() => {
                return this.getAuthData();
            }).then((_auth) => {
                auth = _auth;
            }).then(() => {
                let playlists = firebase.child('playlists');
                let data = { playlist };

                return playlists.push(data);
            }).then((ref) => {
                let metadata = firebase.child('playlists/metadata');
                let uid = auth === null ? null : auth.uid;
                let data = {
                    uid,
                    playlist: ref.key(),
                    name: playlistMetadata.name || null,
                    public: playlistMetadata.public || null
                };

                return metadata.push(data);
            }).then((ref) => {
                return $q((resolve) => {
                    ref.child('playlist').once('value', (snapshot) => {
                        let playlist = snapshot.val();
                        resolve(playlist);
                    });
                });
            });
        }
    });

}]);
<|endoftext|>


<commit_before>import React from 'react';

export default class ImagePreview extends React.Component {
  constructor(props) {
    super(props);
    this.handleImageLoaded = this.handleImageLoaded.bind(this);
  }

  handleImageLoaded() {
    window.URL.revokeObjectURL(this.props.value);
  }

  render() {
    console.log(this.props)
    const { value } = this.props;
    return value ?  : null;
  }
}
<commit_msg>render props.value straight<commit_after>import React from 'react';

export default class ImagePreview extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const { value } = this.props;
    return value ?  : null;
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled, { css } from 'react-emotion';
import { defaultCode } from '../utils/playground.default-code';

import FullscreenButton from './fullscreen-button';

import {
  requestFullscreen,
  exitFullscreen,
  getFullscreenElement
} from '../utils/fullscreen';

import {
  LiveProvider,
  LiveEditor,
  LiveError,
  LivePreview
} from 'react-live';

export const PlaygroundProvider = styled(LiveProvider)`
  border-radius: 0 0 6px 6px;
  height: 100%;
  width: 100%;
  border-radius: 6px;
  overflow: hidden;
`;

const PlaygroundPreview = styled(({ className }) => (
  <LivePreview className={className} />
))`
  padding: 0.5rem;
  min-height: 100%;

  background: ${p => p.previewBackgroundColor || '#fff'};
`;

const PlaygroundEditor = styled(({ syntaxStyles: _, prismTheme: __, ...rest }) => <LiveEditor {...rest} />)`
  && {
    ${props => props.syntaxStyles}
    min-height: 100%;
    font-size: 1.25vw;

    &.builtin-prism-theme {
      ${props => props.prismTheme}
    }
  }
`;

const PlaygroundRow = styled.div`
  display: flex;
  justify-content: stretch;
  align-items: center;
  width: 100%;

  /* NOTE: Comma separation doesn't seem to work here */

  &:-webkit-full-screen { height: 100%; }
  &:-moz-full-screen { height: 100%; }
  &:-ms-fullscreen { height: 100%; }
  &:fullscreen { height: 100%; }

  &:-webkit-full-screen > * { height: 100%; }
  &:-moz-full-screen > * { height: 100%; }
  &:-ms-fullscreen > * { height: 100%; }
  &:fullscreen > * { height: 100%; }
`;

const Title = styled.div`
  position: relative;
  flex: 1;
  background: #ddd;
  border-bottom: 1px solid #999;
  color: #424242;
  display: block;
  font-family: 'Roboto Mono', 'Menlo', 'Andale Mono', monospace;
  font-size: 1.15vw;
  padding: 0.4rem 0;
  text-transform: uppercase;

  &:last-child {
    border-left: 1px solid #999;
  }

  > button {
    position: absolute;
    right: 1em;
    margin-top: -0.1em;
  }

  ${props => props.useDarkTheme && css`
    background: #272822;
    border-bottom: 1px solid #000;
    color: #fff;
  `}
`;

const PlaygroundColumn = styled.div`
  flex: 1;
  font-size: 1.25vw;
  margin: 0;
  position: relative;
  height: 60vh;
  overflow-y: scroll;
  &:last-child {
    border-left: 1px solid #999;
  }
`;

const PlaygroundError = styled(LiveError)`
  position: absolute;
  bottom: 0;
  width: 100%;
  text-align: left;
  white-space: pre-wrap;
  background: rgba(255, 35, 36, 0.8);
  color: white;
  padding: 0.5rem;
`;

const STORAGE_KEY = 'spectacle-playground';

class ComponentPlayground extends Component {
  constructor(props) {
    super(props);

    this.onRef = this.onRef.bind(this);
    this.onEditorChange = this.onEditorChange.bind(this);
    this.requestFullscreen = this.requestFullscreen.bind(this);
    this.syncCode = this.syncCode.bind(this);

    this.state = {
      code: (this.props.code || defaultCode).trim()
    };
  }

  componentDidMount() {
    localStorage.setItem(STORAGE_KEY, this.state.code);
    window.addEventListener('storage', this.syncCode);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.code !== this.props.code) {
      const code = (this.props.code || defaultCode).trim();
      this.setState({ code });
    }
  }

  componentWillUnmount() {
    window.removeEventListener('storage', this.syncCode);
  }

  onKeyUp(evt) {
    evt.stopPropagation();

    // Esc: When entering the editor or an input element the default esc-to-exit might not work anymore
    if (evt.keyCode === 27 && getFullscreenElement()) {
      exitFullscreen();
    }
  }

  onKeyDown(evt) {
    evt.stopPropagation();
  }

  onRef(node) {
    this.node = node;
  }

  onEditorChange(code) {
    this.setState({ code });
    localStorage.setItem(STORAGE_KEY, code);
  }

  requestFullscreen() {
    requestFullscreen(this.node);
  }

  syncCode({ key, newValue }) {
    if (key === STORAGE_KEY) {
      this.setState({ code: newValue });
    }
  }

  render() {
    const {
      previewBackgroundColor,
      scope = {},
      theme = 'dark'
    } = this.props;

    const useDarkTheme = theme === 'dark';
    const externalPrismTheme = this.props.theme === 'external';
    const className = `language-jsx ${externalPrismTheme ? '' : 'builtin-prism-theme'}`;

    return (
      <PlaygroundProvider
        mountStylesheet={false}
        code={this.state.code}
        scope={{ Component, ...scope }}
        noInline
      >
        <PlaygroundRow>
          <Title>Live Preview</Title>
          <Title useDarkTheme={useDarkTheme}>
            Source Code
            <FullscreenButton onClick={this.requestFullscreen} />
          </Title>
        </PlaygroundRow>

        <PlaygroundRow
          innerRef={this.onRef}
          onKeyUp={this.onKeyUp}
          onKeyDown={this.onKeyDown}
        >
          <PlaygroundColumn>
            <PlaygroundPreview
              previewBackgroundColor={previewBackgroundColor}
            />
            <PlaygroundError />
          </PlaygroundColumn>

          <PlaygroundColumn>
            <PlaygroundEditor
              className={className}
              syntaxStyles={this.context.styles.components.syntax}
              prismTheme={this.context.styles.prism[useDarkTheme ? 'dark' : 'light']}
              onChange={this.onEditorChange}
            />
          </PlaygroundColumn>
        </PlaygroundRow>
      </PlaygroundProvider>
    );
  }
}

ComponentPlayground.contextTypes = {
  styles: PropTypes.object,
  store: PropTypes.object
};

ComponentPlayground.propTypes = {
  code: PropTypes.string,
  previewBackgroundColor: PropTypes.string,
  scope: PropTypes.object,
  theme: PropTypes.oneOf(['dark', 'light', 'external']),
};

ComponentPlayground.defaultProps = {
  theme: 'dark',
};

export default ComponentPlayground;
<commit_msg>Expose LiveProvider's transformCode prop (#482)<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled, { css } from 'react-emotion';
import { defaultCode } from '../utils/playground.default-code';

import FullscreenButton from './fullscreen-button';

import {
  requestFullscreen,
  exitFullscreen,
  getFullscreenElement
} from '../utils/fullscreen';

import {
  LiveProvider,
  LiveEditor,
  LiveError,
  LivePreview
} from 'react-live';

export const PlaygroundProvider = styled(LiveProvider)`
  border-radius: 0 0 6px 6px;
  height: 100%;
  width: 100%;
  border-radius: 6px;
  overflow: hidden;
`;

const PlaygroundPreview = styled(({ className }) => (
  <LivePreview className={className} />
))`
  padding: 0.5rem;
  min-height: 100%;

  background: ${p => p.previewBackgroundColor || '#fff'};
`;

const PlaygroundEditor = styled(({ syntaxStyles: _, prismTheme: __, ...rest }) => <LiveEditor {...rest} />)`
  && {
    ${props => props.syntaxStyles}
    min-height: 100%;
    font-size: 1.25vw;

    &.builtin-prism-theme {
      ${props => props.prismTheme}
    }
  }
`;

const PlaygroundRow = styled.div`
  display: flex;
  justify-content: stretch;
  align-items: center;
  width: 100%;

  /* NOTE: Comma separation doesn't seem to work here */

  &:-webkit-full-screen { height: 100%; }
  &:-moz-full-screen { height: 100%; }
  &:-ms-fullscreen { height: 100%; }
  &:fullscreen { height: 100%; }

  &:-webkit-full-screen > * { height: 100%; }
  &:-moz-full-screen > * { height: 100%; }
  &:-ms-fullscreen > * { height: 100%; }
  &:fullscreen > * { height: 100%; }
`;

const Title = styled.div`
  position: relative;
  flex: 1;
  background: #ddd;
  border-bottom: 1px solid #999;
  color: #424242;
  display: block;
  font-family: 'Roboto Mono', 'Menlo', 'Andale Mono', monospace;
  font-size: 1.15vw;
  padding: 0.4rem 0;
  text-transform: uppercase;

  &:last-child {
    border-left: 1px solid #999;
  }

  > button {
    position: absolute;
    right: 1em;
    margin-top: -0.1em;
  }

  ${props => props.useDarkTheme && css`
    background: #272822;
    border-bottom: 1px solid #000;
    color: #fff;
  `}
`;

const PlaygroundColumn = styled.div`
  flex: 1;
  font-size: 1.25vw;
  margin: 0;
  position: relative;
  height: 60vh;
  overflow-y: scroll;
  &:last-child {
    border-left: 1px solid #999;
  }
`;

const PlaygroundError = styled(LiveError)`
  position: absolute;
  bottom: 0;
  width: 100%;
  text-align: left;
  white-space: pre-wrap;
  background: rgba(255, 35, 36, 0.8);
  color: white;
  padding: 0.5rem;
`;

const STORAGE_KEY = 'spectacle-playground';

class ComponentPlayground extends Component {
  constructor(props) {
    super(props);

    this.onRef = this.onRef.bind(this);
    this.onEditorChange = this.onEditorChange.bind(this);
    this.requestFullscreen = this.requestFullscreen.bind(this);
    this.syncCode = this.syncCode.bind(this);

    this.state = {
      code: (this.props.code || defaultCode).trim()
    };
  }

  componentDidMount() {
    localStorage.setItem(STORAGE_KEY, this.state.code);
    window.addEventListener('storage', this.syncCode);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.code !== this.props.code) {
      const code = (this.props.code || defaultCode).trim();
      this.setState({ code });
    }
  }

  componentWillUnmount() {
    window.removeEventListener('storage', this.syncCode);
  }

  onKeyUp(evt) {
    evt.stopPropagation();

    // Esc: When entering the editor or an input element the default esc-to-exit might not work anymore
    if (evt.keyCode === 27 && getFullscreenElement()) {
      exitFullscreen();
    }
  }

  onKeyDown(evt) {
    evt.stopPropagation();
  }

  onRef(node) {
    this.node = node;
  }

  onEditorChange(code) {
    this.setState({ code });
    localStorage.setItem(STORAGE_KEY, code);
  }

  requestFullscreen() {
    requestFullscreen(this.node);
  }

  syncCode({ key, newValue }) {
    if (key === STORAGE_KEY) {
      this.setState({ code: newValue });
    }
  }

  render() {
    const {
      previewBackgroundColor,
      scope = {},
      theme = 'dark',
      transformCode
    } = this.props;

    const useDarkTheme = theme === 'dark';
    const externalPrismTheme = this.props.theme === 'external';
    const className = `language-jsx ${externalPrismTheme ? '' : 'builtin-prism-theme'}`;

    return (
      <PlaygroundProvider
        mountStylesheet={false}
        code={this.state.code}
        scope={{ Component, ...scope }}
        transformCode={transformCode}
        noInline
      >
        <PlaygroundRow>
          <Title>Live Preview</Title>
          <Title useDarkTheme={useDarkTheme}>
            Source Code
            <FullscreenButton onClick={this.requestFullscreen} />
          </Title>
        </PlaygroundRow>

        <PlaygroundRow
          innerRef={this.onRef}
          onKeyUp={this.onKeyUp}
          onKeyDown={this.onKeyDown}
        >
          <PlaygroundColumn>
            <PlaygroundPreview
              previewBackgroundColor={previewBackgroundColor}
            />
            <PlaygroundError />
          </PlaygroundColumn>

          <PlaygroundColumn>
            <PlaygroundEditor
              className={className}
              syntaxStyles={this.context.styles.components.syntax}
              prismTheme={this.context.styles.prism[useDarkTheme ? 'dark' : 'light']}
              onChange={this.onEditorChange}
            />
          </PlaygroundColumn>
        </PlaygroundRow>
      </PlaygroundProvider>
    );
  }
}

ComponentPlayground.contextTypes = {
  styles: PropTypes.object,
  store: PropTypes.object
};

ComponentPlayground.propTypes = {
  code: PropTypes.string,
  previewBackgroundColor: PropTypes.string,
  scope: PropTypes.object,
  theme: PropTypes.oneOf(['dark', 'light', 'external']),
};

ComponentPlayground.defaultProps = {
  theme: 'dark',
};

export default ComponentPlayground;
<|endoftext|>


<commit_before>import TouchPan from '../../directives/touch-pan'
import { cssTransform } from '../../utils/dom'
import { between } from '../../utils/format'
import { QResizeObservable } from '../observables'
import ModelToggleMixin from '../../mixins/model-toggle'

const
  bodyClassBelow = 'with-layout-drawer-opened',
  bodyClassAbove = 'with-layout-drawer-opened-above',
  duration = 150

export default {
  name: 'q-layout-drawer',
  inject: {
    layout: {
      default () {
        console.error('QLayoutDrawer needs to be child of QLayout')
      }
    }
  },
  mixins: [ModelToggleMixin],
  directives: {
    TouchPan
  },
  props: {
    overlay: Boolean,
    side: {
      type: String,
      default: 'left',
      validator: v => ['left', 'right'].includes(v)
    },
    breakpoint: {
      type: Number,
      default: 992
    },
    behavior: {
      type: String,
      validator: v => ['default', 'desktop', 'mobile'].includes(v),
      default: 'default'
    },
    contentStyle: Object,
    contentClass: [String, Object, Array],
    noSwipeOpen: Boolean,
    noSwipeClose: Boolean
  },
  data () {
    const
      largeScreenState = this.value !== void 0 ? this.value : true,
      showing = this.behavior !== 'mobile' && this.breakpoint < this.layout.width && !this.overlay
        ? largeScreenState
        : false

    if (this.value !== void 0 && this.value !== showing) {
      this.$emit('input', showing)
    }

    return {
      showing,
      belowBreakpoint: (
        this.behavior === 'mobile' ||
        (this.behavior !== 'desktop' && this.breakpoint >= this.layout.width)
      ),
      largeScreenState,
      mobileOpened: false,

      size: 300,
      inTransit: false,
      position: 0,
      percentage: 0
    }
  },
  watch: {
    belowBreakpoint (val, old) {
      if (this.mobileOpened) {
        return
      }

      if (val) { // from lg to xs
        if (!this.overlay) {
          this.largeScreenState = this.showing
        }
        // ensure we close it for small screen
        this.hide()
      }
      else if (!this.overlay) { // from xs to lg
        this[this.largeScreenState ? 'show' : 'hide']()
      }
    },
    behavior (val) {
      this.__updateLocal('belowBreakpoint', (
        val === 'mobile' ||
        (val !== 'desktop' && this.breakpoint >= this.layout.width)
      ))
    },
    breakpoint (val) {
      this.__updateLocal('belowBreakpoint', (
        this.behavior === 'mobile' ||
        (this.behavior !== 'desktop' && val >= this.layout.width)
      ))
    },
    'layout.width' (val) {
      this.__updateLocal('belowBreakpoint', (
        this.behavior === 'mobile' ||
        (this.behavior !== 'desktop' && this.breakpoint >= val)
      ))
    },
    offset (val) {
      this.__update('offset', val)
    },
    onScreenOverlay () {
      if (this.animateOverlay) {
        this.layout.__animate()
      }
    },
    onLayout (val) {
      this.__update('space', val)
      this.layout.__animate()
    },
    $route () {
      if (this.mobileOpened || this.onScreenOverlay) {
        this.hide()
      }
    }
  },
  computed: {
    rightSide () {
      return this.side === 'right'
    },
    offset () {
      return this.showing && !this.mobileOpened
        ? this.size
        : 0
    },
    fixed () {
      return this.overlay || this.layout.view.indexOf(this.rightSide ? 'R' : 'L') > -1
    },
    onLayout () {
      return this.showing && !this.mobileView && !this.overlay
    },
    onScreenOverlay () {
      return this.showing && !this.mobileView && this.overlay
    },
    backdropClass () {
      return {
        'q-layout-backdrop-transition': !this.inTransit,
        'no-pointer-events': !this.inTransit && !this.showing
      }
    },
    mobileView () {
      return this.belowBreakpoint || this.mobileOpened
    },
    headerSlot () {
      return this.overlay
        ? false
        : (this.rightSide
          ? this.layout.rows.top[2] === 'r'
          : this.layout.rows.top[0] === 'l'
        )
    },
    footerSlot () {
      return this.overlay
        ? false
        : (this.rightSide
          ? this.layout.rows.bottom[2] === 'r'
          : this.layout.rows.bottom[0] === 'l'
        )
    },
    backdropStyle () {
      return { backgroundColor: `rgba(0,0,0,${this.percentage * 0.4})` }
    },
    belowClass () {
      return {
        'fixed': true,
        'on-top': true,
        'on-screen': this.showing,
        'off-screen': !this.showing,
        'transition-generic': !this.inTransit,
        'top-padding': true
      }
    },
    belowStyle () {
      if (this.inTransit) {
        return cssTransform(`translateX(${this.position}px)`)
      }
    },
    aboveClass () {
      const onScreen = this.onLayout || this.onScreenOverlay
      return {
        'off-screen': !onScreen,
        'on-screen': onScreen,
        'fixed': this.fixed || !this.onLayout,
        'top-padding': this.headerSlot
      }
    },
    aboveStyle () {
      const css = {}

      if (this.layout.header.space && !this.headerSlot) {
        if (this.fixed) {
          css.top = `${this.layout.header.offset}px`
        }
        else if (this.layout.header.space) {
          css.top = `${this.layout.header.size}px`
        }
      }

      if (this.layout.footer.space && !this.footerSlot) {
        if (this.fixed) {
          css.bottom = `${this.layout.footer.offset}px`
        }
        else if (this.layout.footer.space) {
          css.bottom = `${this.layout.footer.size}px`
        }
      }

      return css
    },
    computedStyle () {
      return [this.contentStyle, this.mobileView ? this.belowStyle : this.aboveStyle]
    },
    computedClass () {
      return [this.contentClass, this.mobileView ? this.belowClass : this.aboveClass]
    }
  },
  render (h) {
    const child = []

    if (this.mobileView) {
      if (!this.noSwipeOpen) {
        child.push(h('div', {
          staticClass: `q-layout-drawer-opener fixed-${this.side}`,
          directives: [{
            name: 'touch-pan',
            modifiers: { horizontal: true },
            value: this.__openByTouch
          }]
        }))
      }
      child.push(h('div', {
        staticClass: 'fullscreen q-layout-backdrop',
        'class': this.backdropClass,
        style: this.backdropStyle,
        on: { click: this.hide },
        directives: [{
          name: 'touch-pan',
          modifiers: { horizontal: true },
          value: this.__closeByTouch
        }]
      }))
    }

    return h('div', { staticClass: 'q-drawer-container' }, child.concat([
      h('aside', {
        staticClass: `q-layout-drawer q-layout-drawer-${this.side} scroll q-layout-transition`,
        'class': this.computedClass,
        style: this.computedStyle,
        attrs: this.$attrs,
        listeners: this.$listeners,
        directives: this.mobileView && !this.noSwipeClose ? [{
          name: 'touch-pan',
          modifiers: { horizontal: true },
          value: this.__closeByTouch
        }] : null
      }, [
        this.$slots.default,
        h(QResizeObservable, {
          on: { resize: this.__onResize }
        })
      ])
    ]))
  },
  created () {
    if (this.onLayout) {
      this.__update('space', true)
      this.__update('offset', this.offset)
    }

    this.$nextTick(() => {
      this.animateOverlay = true
    })
  },
  beforeDestroy () {
    clearTimeout(this.timer)
    this.__update('size', 0)
    this.__update('space', false)
  },
  methods: {
    __openByTouch (evt) {
      if (!this.belowBreakpoint) {
        return
      }
      const
        width = this.size,
        position = between(evt.distance.x, 0, width)

      if (evt.isFinal) {
        const opened = position >= Math.min(75, width)
        this.inTransit = false
        if (opened) { this.show() }
        else { this.percentage = 0 }
        return
      }

      this.position = this.rightSide
        ? Math.max(width - position, 0)
        : Math.min(0, position - width)

      this.percentage = between(position / width, 0, 1)

      if (evt.isFirst) {
        document.body.classList.add(bodyClassBelow)
        this.inTransit = true
      }
    },
    __closeByTouch (evt) {
      if (!this.mobileOpened) {
        return
      }

      const
        width = this.size,
        position = evt.direction === this.side
          ? between(evt.distance.x, 0, width)
          : 0

      if (evt.isFinal) {
        const opened = Math.abs(position) < Math.min(75, width)
        this.inTransit = false
        if (opened) { this.percentage = 1 }
        else { this.hide() }
        return
      }

      this.position = (this.rightSide ? 1 : -1) * position
      this.percentage = between(1 - position / width, 0, 1)

      if (evt.isFirst) {
        this.inTransit = true
      }
    },
    __show () {
      if (this.belowBreakpoint) {
        this.mobileOpened = true
        this.percentage = 1
      }

      document.body.classList.add(this.belowBreakpoint ? bodyClassBelow : bodyClassAbove)

      clearTimeout(this.timer)
      this.timer = setTimeout(() => {
        if (this.showPromise) {
          this.showPromise.then(() => {
            document.body.classList.remove(bodyClassAbove)
          })
          this.showPromiseResolve()
        }
      }, duration)
    },
    __hide () {
      this.mobileOpened = false
      this.percentage = 0

      document.body.classList.remove(bodyClassAbove)
      document.body.classList.remove(bodyClassBelow)

      clearTimeout(this.timer)
      this.timer = setTimeout(() => {
        this.hidePromise && this.hidePromiseResolve()
      }, duration)
    },

    __onResize ({ width }) {
      this.__update('size', width)
      this.__updateLocal('size', width)
    },
    __update (prop, val) {
      if (this.layout[this.side][prop] !== val) {
        this.layout[this.side][prop] = val
      }
    },
    __updateLocal (prop, val) {
      if (this[prop] !== val) {
        this[prop] = val
      }
    }
  }
}
<commit_msg>feat(RTL): Drawer swipe support<commit_after>import TouchPan from '../../directives/touch-pan'
import { cssTransform } from '../../utils/dom'
import { between } from '../../utils/format'
import { QResizeObservable } from '../observables'
import ModelToggleMixin from '../../mixins/model-toggle'

const
  bodyClassBelow = 'with-layout-drawer-opened',
  bodyClassAbove = 'with-layout-drawer-opened-above',
  duration = 150

export default {
  name: 'q-layout-drawer',
  inject: {
    layout: {
      default () {
        console.error('QLayoutDrawer needs to be child of QLayout')
      }
    }
  },
  mixins: [ModelToggleMixin],
  directives: {
    TouchPan
  },
  props: {
    overlay: Boolean,
    side: {
      type: String,
      default: 'left',
      validator: v => ['left', 'right'].includes(v)
    },
    breakpoint: {
      type: Number,
      default: 992
    },
    behavior: {
      type: String,
      validator: v => ['default', 'desktop', 'mobile'].includes(v),
      default: 'default'
    },
    contentStyle: Object,
    contentClass: [String, Object, Array],
    noSwipeOpen: Boolean,
    noSwipeClose: Boolean
  },
  data () {
    const
      largeScreenState = this.value !== void 0 ? this.value : true,
      showing = this.behavior !== 'mobile' && this.breakpoint < this.layout.width && !this.overlay
        ? largeScreenState
        : false

    if (this.value !== void 0 && this.value !== showing) {
      this.$emit('input', showing)
    }

    return {
      showing,
      belowBreakpoint: (
        this.behavior === 'mobile' ||
        (this.behavior !== 'desktop' && this.breakpoint >= this.layout.width)
      ),
      largeScreenState,
      mobileOpened: false,

      size: 300,
      inTransit: false,
      position: 0,
      percentage: 0
    }
  },
  watch: {
    belowBreakpoint (val, old) {
      if (this.mobileOpened) {
        return
      }

      if (val) { // from lg to xs
        if (!this.overlay) {
          this.largeScreenState = this.showing
        }
        // ensure we close it for small screen
        this.hide()
      }
      else if (!this.overlay) { // from xs to lg
        this[this.largeScreenState ? 'show' : 'hide']()
      }
    },
    behavior (val) {
      this.__updateLocal('belowBreakpoint', (
        val === 'mobile' ||
        (val !== 'desktop' && this.breakpoint >= this.layout.width)
      ))
    },
    breakpoint (val) {
      this.__updateLocal('belowBreakpoint', (
        this.behavior === 'mobile' ||
        (this.behavior !== 'desktop' && val >= this.layout.width)
      ))
    },
    'layout.width' (val) {
      this.__updateLocal('belowBreakpoint', (
        this.behavior === 'mobile' ||
        (this.behavior !== 'desktop' && this.breakpoint >= val)
      ))
    },
    offset (val) {
      this.__update('offset', val)
    },
    onScreenOverlay () {
      if (this.animateOverlay) {
        this.layout.__animate()
      }
    },
    onLayout (val) {
      this.__update('space', val)
      this.layout.__animate()
    },
    $route () {
      if (this.mobileOpened || this.onScreenOverlay) {
        this.hide()
      }
    }
  },
  computed: {
    rightSide () {
      return this.side === 'right'
    },
    offset () {
      return this.showing && !this.mobileOpened
        ? this.size
        : 0
    },
    fixed () {
      return this.overlay || this.layout.view.indexOf(this.rightSide ? 'R' : 'L') > -1
    },
    onLayout () {
      return this.showing && !this.mobileView && !this.overlay
    },
    onScreenOverlay () {
      return this.showing && !this.mobileView && this.overlay
    },
    backdropClass () {
      return {
        'q-layout-backdrop-transition': !this.inTransit,
        'no-pointer-events': !this.inTransit && !this.showing
      }
    },
    mobileView () {
      return this.belowBreakpoint || this.mobileOpened
    },
    headerSlot () {
      return this.overlay
        ? false
        : (this.rightSide
          ? this.layout.rows.top[2] === 'r'
          : this.layout.rows.top[0] === 'l'
        )
    },
    footerSlot () {
      return this.overlay
        ? false
        : (this.rightSide
          ? this.layout.rows.bottom[2] === 'r'
          : this.layout.rows.bottom[0] === 'l'
        )
    },
    backdropStyle () {
      return { backgroundColor: `rgba(0,0,0,${this.percentage * 0.4})` }
    },
    belowClass () {
      return {
        'fixed': true,
        'on-top': true,
        'on-screen': this.showing,
        'off-screen': !this.showing,
        'transition-generic': !this.inTransit,
        'top-padding': true
      }
    },
    belowStyle () {
      if (this.inTransit) {
        return cssTransform(`translateX(${this.position}px)`)
      }
    },
    aboveClass () {
      const onScreen = this.onLayout || this.onScreenOverlay
      return {
        'off-screen': !onScreen,
        'on-screen': onScreen,
        'fixed': this.fixed || !this.onLayout,
        'top-padding': this.headerSlot
      }
    },
    aboveStyle () {
      const css = {}

      if (this.layout.header.space && !this.headerSlot) {
        if (this.fixed) {
          css.top = `${this.layout.header.offset}px`
        }
        else if (this.layout.header.space) {
          css.top = `${this.layout.header.size}px`
        }
      }

      if (this.layout.footer.space && !this.footerSlot) {
        if (this.fixed) {
          css.bottom = `${this.layout.footer.offset}px`
        }
        else if (this.layout.footer.space) {
          css.bottom = `${this.layout.footer.size}px`
        }
      }

      return css
    },
    computedStyle () {
      return [this.contentStyle, this.mobileView ? this.belowStyle : this.aboveStyle]
    },
    computedClass () {
      return [this.contentClass, this.mobileView ? this.belowClass : this.aboveClass]
    }
  },
  render (h) {
    const child = []

    if (this.mobileView) {
      if (!this.noSwipeOpen) {
        child.push(h('div', {
          staticClass: `q-layout-drawer-opener fixed-${this.side}`,
          directives: [{
            name: 'touch-pan',
            modifiers: { horizontal: true },
            value: this.__openByTouch
          }]
        }))
      }
      child.push(h('div', {
        staticClass: 'fullscreen q-layout-backdrop',
        'class': this.backdropClass,
        style: this.backdropStyle,
        on: { click: this.hide },
        directives: [{
          name: 'touch-pan',
          modifiers: { horizontal: true },
          value: this.__closeByTouch
        }]
      }))
    }

    return h('div', { staticClass: 'q-drawer-container' }, child.concat([
      h('aside', {
        staticClass: `q-layout-drawer q-layout-drawer-${this.side} scroll q-layout-transition`,
        'class': this.computedClass,
        style: this.computedStyle,
        attrs: this.$attrs,
        listeners: this.$listeners,
        directives: this.mobileView && !this.noSwipeClose ? [{
          name: 'touch-pan',
          modifiers: { horizontal: true },
          value: this.__closeByTouch
        }] : null
      }, [
        this.$slots.default,
        h(QResizeObservable, {
          on: { resize: this.__onResize }
        })
      ])
    ]))
  },
  created () {
    if (this.onLayout) {
      this.__update('space', true)
      this.__update('offset', this.offset)
    }

    this.$nextTick(() => {
      this.animateOverlay = true
    })
  },
  beforeDestroy () {
    clearTimeout(this.timer)
    this.__update('size', 0)
    this.__update('space', false)
  },
  methods: {
    __openByTouch (evt) {
      if (!this.belowBreakpoint) {
        return
      }
      const
        width = this.size,
        position = between(evt.distance.x, 0, width)

      if (evt.isFinal) {
        const opened = position >= Math.min(75, width)
        this.inTransit = false
        if (opened) { this.show() }
        else { this.percentage = 0 }
        return
      }

      this.position = (this.$q.i18n.rtl ? !this.rightSide : this.rightSide)
        ? Math.max(width - position, 0)
        : Math.min(0, position - width)

      this.percentage = between(position / width, 0, 1)

      if (evt.isFirst) {
        document.body.classList.add(bodyClassBelow)
        this.inTransit = true
      }
    },
    __closeByTouch (evt) {
      if (!this.mobileOpened) {
        return
      }

      const
        width = this.size,
        dir = evt.direction === this.side,
        position = (this.$q.i18n.rtl ? !dir : dir)
          ? between(evt.distance.x, 0, width)
          : 0

      if (evt.isFinal) {
        const opened = Math.abs(position) < Math.min(75, width)
        this.inTransit = false
        if (opened) { this.percentage = 1 }
        else { this.hide() }
        return
      }

      this.position = (this.$q.i18n.rtl ? -1 : 1) * (this.rightSide ? 1 : -1) * position
      this.percentage = between(1 - position / width, 0, 1)

      if (evt.isFirst) {
        this.inTransit = true
      }
    },
    __show () {
      if (this.belowBreakpoint) {
        this.mobileOpened = true
        this.percentage = 1
      }

      document.body.classList.add(this.belowBreakpoint ? bodyClassBelow : bodyClassAbove)

      clearTimeout(this.timer)
      this.timer = setTimeout(() => {
        if (this.showPromise) {
          this.showPromise.then(() => {
            document.body.classList.remove(bodyClassAbove)
          })
          this.showPromiseResolve()
        }
      }, duration)
    },
    __hide () {
      this.mobileOpened = false
      this.percentage = 0

      document.body.classList.remove(bodyClassAbove)
      document.body.classList.remove(bodyClassBelow)

      clearTimeout(this.timer)
      this.timer = setTimeout(() => {
        this.hidePromise && this.hidePromiseResolve()
      }, duration)
    },

    __onResize ({ width }) {
      this.__update('size', width)
      this.__updateLocal('size', width)
    },
    __update (prop, val) {
      if (this.layout[this.side][prop] !== val) {
        this.layout[this.side][prop] = val
      }
    },
    __updateLocal (prop, val) {
      if (this[prop] !== val) {
        this[prop] = val
      }
    }
  }
}
<|endoftext|>


<commit_before>// @flow
import React, {Component, PropTypes} from 'react';
import isEmpty from 'lodash/isEmpty';
import SMIcon from '../../home/components/SMIcon';
import OSMIcon from '../../home/components/OSMIcon';
import {View} from './View';
import Logo from '../../home/components/Logo';
import {Map, TileLayer, ZoomControl} from 'react-leaflet';
import Control from '../../map/components/Control';
import {mobileBreakpoint} from '../../common/constants';
import {languages} from '../../language/constants';
import {MAP_URL, DEFAULT_ZOOM, MIN_ZOOM, BOUNDARIES} from '../../map/constants';
import {latLngToArray} from '../../map/helpers';
import {getUnitPosition} from '../helpers';
import UnitsOnMap from './UnitsOnMap';
import UserLocationMarker from '../../map/components/UserLocationMarker';
import {translate} from 'react-i18next';

class MapView extends Component {
  static propTypes = {
    position: PropTypes.array.isRequired,
    units: PropTypes.array
  };

  state: {
    isMobile: boolean,
    zoomLevel: number,
    menuOpen: boolean
  };

  constructor(props: Object) {
    super(props);

    this.state = {
      isMobile: window.innerWidth < mobileBreakpoint,
      menuOpen: false,
      modalOpen: false,
      zoomLevel: DEFAULT_ZOOM
    };

    this.setLocation = this.setLocation.bind(this);
    this.locateUser = this.locateUser.bind(this);
    this.updateIsMobile = this.updateIsMobile.bind(this);
    this.handleClick = this.handleClick.bind(this);
    this.toggleMenu = this.toggleMenu.bind(this);
    this.toggleModal = this.toggleModal.bind(this);
    this.openModal = this.openModal.bind(this);
    this.closeModal = this.closeModal.bind(this);
    this.handleZoom = this.handleZoom.bind(this);
    this.setView = this.setView.bind(this);
  }

  componentDidMount() {
    window.addEventListener('resize', this.updateIsMobile);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.updateIsMobile);
  }

  componentWillReceiveProps(nextProps) {
    const {selectedUnit} = this.props;
    if (nextProps.selectedUnit &&
      (!selectedUnit || selectedUnit.id !== nextProps.selectedUnit.id ) ) {
      this.centerMapToUnit(nextProps.selectedUnit);
    }
  }

  centerMapToUnit(unit: Object) {
    if (this.state.isMobile) {
      let location = getUnitPosition(unit);
      location[0] = location[0] + 0.02;
      //For some reason could not use reverse here so had to do this weird way.
      this.refs.map.leafletElement.setView(location, DEFAULT_ZOOM);
    }
    else {
      let location = getUnitPosition(unit);
      location[1] = location[1] - 0.04;

      this.refs.map.leafletElement.setView(location, DEFAULT_ZOOM);
    }
  }

  handleZoom() {
    this.setState({zoomLevel: this.refs.map.leafletElement.getZoom()});
  }

  updateIsMobile() {
    this.setState({isMobile: window.innerWidth < mobileBreakpoint});
  }

  locateUser() {
    this.refs.map.leafletElement.locate({setView: true});
  }

  handleClick(event: Object) {
    // Click events from info menu and language changer hit this. Don't
    // do anything for those events.
    if(event.originalEvent.target.className.includes('leaflet')) {
      this.setLocation(event);
    }
  }

  setLocation(event: Object) {
    this.props.setLocation(latLngToArray(event.latlng));
  }

  toggleMenu() {
    if(this.state.menuOpen) {
      this.setState({menuOpen: false});
    } else {
      this.setState({menuOpen: true});
    }
  }

  toggleModal() {
    if(this.state.modalOpen) {
      this.setState({modalOpen: false});
    } else {
      this.setState({modalOpen: true});
    }
  }

  setView(coordinates) {
    this.refs.map.leafletElement.setView(coordinates);
  }

  openModal() {
    this.setState({modalOpen: true});
  }

  closeModal() {
    this.setState({modalOpen: false});
  }

  render() {
    const {position, selectedUnit, units, selected, activeLanguage, openUnit, changeLanguage, t} = this.props;
    const {isMobile, zoomLevel, menuOpen} = this.state;

    return (
      <View id="map-view" className="map-view" isSelected={selected}>
        <Map ref="map"
          zoomControl={false}
          attributionControl={false}
          center={position}
          maxBounds={BOUNDARIES}
          zoom={DEFAULT_ZOOM}
          minZoom={MIN_ZOOM}
          onClick={this.handleClick}
          onLocationfound={this.setLocation}
          onZoomend={this.handleZoom}>
          <TileLayer
            url={MAP_URL}
            attribution='© OpenStreetMap contributors'
          />
          <UserLocationMarker />
          <UnitsOnMap units={units} zoomLevel={zoomLevel} selectedUnit={selectedUnit} openUnit={openUnit}/>
          {!isMobile && <ZoomControl position="bottomright" />}
          <Control handleClick={this.locateUser} className="leaflet-control-locate" position="bottomright">
            <OSMIcon icon="locate" />
          </Control>
          <LanguageChanger activeLanguage={activeLanguage} changeLanguage={changeLanguage} />
          {menuOpen ? <InfoMenu t={t} openModal={this.openModal} /> : null}
          <Control handleClick={this.toggleMenu} className="leaflet-control-info" position={isMobile ? 'bottomleft' : 'topright'}>
            <SMIcon icon="info" />
          </Control>
        </Map>
        <Logo/>
        {this.state.modalOpen ? <AboutModal closeModal={this.closeModal} t={t}/> : null}
      </View>
    );
  }
}

export default translate(null, {withRef: true})(MapView);

const LanguageChanger = ({changeLanguage, activeLanguage}) =>

    {Object.keys(languages).filter((language) => languages[language] !== activeLanguage).map((languageKey, index) => (

         changeLanguage(languages[languageKey])}>
          {languageKey}

        {index < Object.keys(languages).length - 2
          ?
|

          : null}

)
    )}

;

const InfoMenu = ({openModal, t}) =>

    <InfoMenuItem icon='info' t={t}>
      {t('MAP.INFO_MENU.GIVE_FEEDBACK')}
    </InfoMenuItem>
    <InfoMenuItem icon='info' handleClick={openModal}>
      {t('MAP.INFO_MENU.ABOUT_SERVICE')}
    </InfoMenuItem>
    <InfoMenuItem handleClick={() => null}>
      © {t('MAP.ATTRIBUTION')}
    </InfoMenuItem>

;

const InfoMenuItem = ({children, handleClick, icon}) =>

    {icon ? <SMIcon icon={icon} style={{paddingRight: 2}}/> : null}
    {children}

;

const AboutModal = ({closeModal, t}) =>

        <SMIcon icon="close" onClick={() => closeModal()} />

        {t('MAP.ABOUT')}

;
<commit_msg>Add placeholder feedback modal<commit_after>// @flow
import React, {Component, PropTypes} from 'react';
import isEmpty from 'lodash/isEmpty';
import SMIcon from '../../home/components/SMIcon';
import OSMIcon from '../../home/components/OSMIcon';
import {View} from './View';
import Logo from '../../home/components/Logo';
import {Map, TileLayer, ZoomControl} from 'react-leaflet';
import Control from '../../map/components/Control';
import {mobileBreakpoint} from '../../common/constants';
import {languages} from '../../language/constants';
import {MAP_URL, DEFAULT_ZOOM, MIN_ZOOM, BOUNDARIES} from '../../map/constants';
import {latLngToArray} from '../../map/helpers';
import {getUnitPosition} from '../helpers';
import UnitsOnMap from './UnitsOnMap';
import UserLocationMarker from '../../map/components/UserLocationMarker';
import {translate} from 'react-i18next';

class MapView extends Component {
  static propTypes = {
    position: PropTypes.array.isRequired,
    units: PropTypes.array
  };

  state: {
    isMobile: boolean,
    zoomLevel: number,
    menuOpen: boolean
  };

  constructor(props: Object) {
    super(props);

    this.state = {
      isMobile: window.innerWidth < mobileBreakpoint,
      menuOpen: false,
      aboutModalOpen: false,
      feedbackModalOpen: false,
      zoomLevel: DEFAULT_ZOOM
    };

    this.setLocation = this.setLocation.bind(this);
    this.locateUser = this.locateUser.bind(this);
    this.updateIsMobile = this.updateIsMobile.bind(this);
    this.handleClick = this.handleClick.bind(this);
    this.toggleMenu = this.toggleMenu.bind(this);
    this.openAboutModal = this.openAboutModal.bind(this);
    this.closeAboutModal = this.closeAboutModal.bind(this);
    this.openFeedbackModal = this.openFeedbackModal.bind(this);
    this.closeFeedbackModal = this.closeFeedbackModal.bind(this);
    this.handleZoom = this.handleZoom.bind(this);
    this.setView = this.setView.bind(this);
  }

  componentDidMount() {
    window.addEventListener('resize', this.updateIsMobile);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.updateIsMobile);
  }

  componentWillReceiveProps(nextProps) {
    const {selectedUnit} = this.props;
    if (nextProps.selectedUnit &&
      (!selectedUnit || selectedUnit.id !== nextProps.selectedUnit.id ) ) {
      this.centerMapToUnit(nextProps.selectedUnit);
    }
  }

  centerMapToUnit(unit: Object) {
    if (this.state.isMobile) {
      let location = getUnitPosition(unit);
      location[0] = location[0] + 0.02;
      //For some reason could not use reverse here so had to do this weird way.
      this.refs.map.leafletElement.setView(location, DEFAULT_ZOOM);
    }
    else {
      let location = getUnitPosition(unit);
      location[1] = location[1] - 0.04;

      this.refs.map.leafletElement.setView(location, DEFAULT_ZOOM);
    }
  }

  handleZoom() {
    this.setState({zoomLevel: this.refs.map.leafletElement.getZoom()});
  }

  updateIsMobile() {
    this.setState({isMobile: window.innerWidth < mobileBreakpoint});
  }

  locateUser() {
    this.refs.map.leafletElement.locate({setView: true});
  }

  handleClick(event: Object) {
    // Click events from info menu and language changer hit this. Don't
    // do anything for those events.
    if(event.originalEvent.target.className.includes('leaflet')) {
      this.setLocation(event);
    }
  }

  setLocation(event: Object) {
    this.props.setLocation(latLngToArray(event.latlng));
  }

  toggleMenu() {
    if(this.state.menuOpen) {
      this.setState({menuOpen: false});
    } else {
      this.setState({menuOpen: true});
    }
  }

  setView(coordinates) {
    this.refs.map.leafletElement.setView(coordinates);
  }

  openAboutModal() {
    this.setState({aboutModalOpen: true});
  }

  closeAboutModal() {
    this.setState({aboutModalOpen: false});
  }

  openFeedbackModal() {
    this.setState({feedbackModalOpen: true});
  }

  closeFeedbackModal() {
    this.setState({feedbackModalOpen: false});
  }

  render() {
    const {position, selectedUnit, units, selected, activeLanguage, openUnit, changeLanguage, t} = this.props;
    const {isMobile, zoomLevel, menuOpen} = this.state;

    return (
      <View id="map-view" className="map-view" isSelected={selected}>
        <Map ref="map"
          zoomControl={false}
          attributionControl={false}
          center={position}
          maxBounds={BOUNDARIES}
          zoom={DEFAULT_ZOOM}
          minZoom={MIN_ZOOM}
          onClick={this.handleClick}
          onLocationfound={this.setLocation}
          onZoomend={this.handleZoom}>
          <TileLayer
            url={MAP_URL}
            attribution='© OpenStreetMap contributors'
          />
          <UserLocationMarker />
          <UnitsOnMap units={units} zoomLevel={zoomLevel} selectedUnit={selectedUnit} openUnit={openUnit}/>
          {!isMobile && <ZoomControl position="bottomright" />}
          <Control handleClick={this.locateUser} className="leaflet-control-locate" position="bottomright">
            <OSMIcon icon="locate" />
          </Control>
          <LanguageChanger activeLanguage={activeLanguage} changeLanguage={changeLanguage} />
          {menuOpen ? <InfoMenu t={t} openAboutModal={this.openAboutModal} openFeedbackModal={this.openFeedbackModal} /> : null}
          <Control handleClick={this.toggleMenu} className="leaflet-control-info" position={isMobile ? 'bottomleft' : 'topright'}>
            <SMIcon icon="info" />
          </Control>
        </Map>
        <Logo/>
        {this.state.aboutModalOpen ? <AboutModal closeModal={this.closeAboutModal} t={t}/> : null}
        {this.state.feedbackModalOpen ? <FeedbackModal closeModal={this.closeFeedbackModal} t={t}/> : null}
      </View>
    );
  }
}

export default translate(null, {withRef: true})(MapView);

const LanguageChanger = ({changeLanguage, activeLanguage}) =>

    {Object.keys(languages).filter((language) => languages[language] !== activeLanguage).map((languageKey, index) => (

         changeLanguage(languages[languageKey])}>
          {languageKey}

        {index < Object.keys(languages).length - 2
          ?
|

          : null}

)
    )}

;

const InfoMenu = ({openAboutModal, openFeedbackModal, t}) =>

    <InfoMenuItem icon='info' handleClick={openFeedbackModal} t={t}>
      {t('MAP.INFO_MENU.GIVE_FEEDBACK')}
    </InfoMenuItem>
    <InfoMenuItem icon='info' handleClick={openAboutModal}>
      {t('MAP.INFO_MENU.ABOUT_SERVICE')}
    </InfoMenuItem>
    <InfoMenuItem handleClick={() => null}>
      © {t('MAP.ATTRIBUTION')}
    </InfoMenuItem>

;

const InfoMenuItem = ({children, handleClick, icon}) =>

    {icon ? <SMIcon icon={icon} style={{paddingRight: 2}}/> : null}
    {children}

;

const AboutModal = ({closeModal, t}) =>

        <SMIcon icon="close" onClick={() => closeModal()} />

        {t('MAP.ABOUT')}

;

const FeedbackModal = ({closeModal, t}) =>

        <SMIcon icon="close" onClick={() => closeModal()} />

{t('MAP.GIVE_FEEDBACK')}

        <form>

<textarea type="text" placeholder="Message" />

              <input type="checkbox" />
              Haluan palautetta sähköpostiin

          <button>Send</button>
        </form>

;
<|endoftext|>