<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require('react');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");

module.exports = React.createClass({
    displayName: 'RightPanel',

    Phase : {
        MemberList: 'MemberList',
        FileList: 'FileList',
        MemberInfo: 'MemberInfo',
    },

    componentWillMount: function() {
        this.dispatcherRef = dis.register(this.onAction);
        var cli = MatrixClientPeg.get();
        cli.on("RoomState.members", this.onRoomStateMember);
    },

    componentWillUnmount: function() {
        dis.unregister(this.dispatcherRef);
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
        }
    },

    getInitialState: function() {
        return {
            phase : this.Phase.MemberList
        }
    },

    onMemberListButtonClick: function() {
        if (this.props.collapsed) {
            this.setState({ phase: this.Phase.MemberList });
            dis.dispatch({
                action: 'show_right_panel',
            });
        }
        else {
            dis.dispatch({
                action: 'hide_right_panel',
            });
        }
    },

    onRoomStateMember: function(ev, state, member) {
        // redraw the badge on the membership list
        if (this.state.phase == this.Phase.MemberList && member.roomId === this.props.roomId) {
            this.forceUpdate();
        }
        else if (this.state.phase === this.Phase.MemberInfo && member.roomId === this.props.roomId &&
                member.userId === this.state.member.userId) {
            // refresh the member info (e.g. new power level)
            this.forceUpdate();
        }
    },

    onAction: function(payload) {
        if (payload.action === "view_user") {
            if (payload.member) {
                this.setState({
                    phase: this.Phase.MemberInfo,
                    member: payload.member,
                });
            }
            else {
                this.setState({
                    phase: this.Phase.MemberList
                });
            }
        }
        if (payload.action === "view_room") {
            if (this.state.phase === this.Phase.MemberInfo) {
                this.setState({
                    phase: this.Phase.MemberList
                });
            }
        }
    },

    render: function() {
        var MemberList = sdk.getComponent('rooms.MemberList');
        var TintableSvg = sdk.getComponent("elements.TintableSvg");
        var buttonGroup;
        var panel;

        var filesHighlight;
        var membersHighlight;
        if (!this.props.collapsed) {
            if (this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) {
                membersHighlight =

;
            }
            else if (this.state.phase == this.Phase.FileList) {
                filesHighlight =

;
            }
        }

        var membersBadge;
        if ((this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) && this.props.roomId) {
            var cli = MatrixClientPeg.get();
            var room = cli.getRoom(this.props.roomId);
            if (room) {
                membersBadge =
{ room.getJoinedMembers().length }
;
            }
        }

        if (this.props.roomId) {
            buttonGroup =

                            <TintableSvg src="img/members.svg" width="17" height="22"/>
                            { membersBadge }
                            { membersHighlight }

                            <TintableSvg src="img/files.svg" width="17" height="22"/>
                            { filesHighlight }

;

            if (!this.props.collapsed) {
                if(this.state.phase == this.Phase.MemberList) {
                    panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
                }
                else if(this.state.phase == this.Phase.MemberInfo) {
                    var MemberInfo = sdk.getComponent('rooms.MemberInfo');
                    panel = <MemberInfo roomId={this.props.roomId} member={this.state.member} key={this.props.roomId} />
                }
            }

        }

        var classes = "mx_RightPanel";
        if (this.props.collapsed) {
            classes += " collapsed";
        }

        return (

                    { buttonGroup }

                { panel }

        );
    }
});

<commit_msg>Rate limit UI updates to avoid browser death<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require('react');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc');

module.exports = React.createClass({
    displayName: 'RightPanel',

    Phase : {
        MemberList: 'MemberList',
        FileList: 'FileList',
        MemberInfo: 'MemberInfo',
    },

    componentWillMount: function() {
        this.dispatcherRef = dis.register(this.onAction);
        var cli = MatrixClientPeg.get();
        cli.on("RoomState.members", this.onRoomStateMember);
    },

    componentWillUnmount: function() {
        dis.unregister(this.dispatcherRef);
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
        }
    },

    getInitialState: function() {
        return {
            phase : this.Phase.MemberList
        }
    },

    onMemberListButtonClick: function() {
        if (this.props.collapsed) {
            this.setState({ phase: this.Phase.MemberList });
            dis.dispatch({
                action: 'show_right_panel',
            });
        }
        else {
            dis.dispatch({
                action: 'hide_right_panel',
            });
        }
    },

    onRoomStateMember: function(ev, state, member) {
        // redraw the badge on the membership list
        if (this.state.phase == this.Phase.MemberList && member.roomId === this.props.roomId) {
            this._delayedUpdate();
        }
        else if (this.state.phase === this.Phase.MemberInfo && member.roomId === this.props.roomId &&
                member.userId === this.state.member.userId) {
            // refresh the member info (e.g. new power level)
            this._delayedUpdate();
        }
    },

    _delayedUpdate: new rate_limited_func(function() {
        this.forceUpdate()
    }, 500),

    onAction: function(payload) {
        if (payload.action === "view_user") {
            if (payload.member) {
                this.setState({
                    phase: this.Phase.MemberInfo,
                    member: payload.member,
                });
            }
            else {
                this.setState({
                    phase: this.Phase.MemberList
                });
            }
        }
        if (payload.action === "view_room") {
            if (this.state.phase === this.Phase.MemberInfo) {
                this.setState({
                    phase: this.Phase.MemberList
                });
            }
        }
    },

    render: function() {
        var MemberList = sdk.getComponent('rooms.MemberList');
        var TintableSvg = sdk.getComponent("elements.TintableSvg");
        var buttonGroup;
        var panel;

        var filesHighlight;
        var membersHighlight;
        if (!this.props.collapsed) {
            if (this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) {
                membersHighlight =

;
            }
            else if (this.state.phase == this.Phase.FileList) {
                filesHighlight =

;
            }
        }

        var membersBadge;
        if ((this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) && this.props.roomId) {
            var cli = MatrixClientPeg.get();
            var room = cli.getRoom(this.props.roomId);
            if (room) {
                membersBadge =
{ room.getJoinedMembers().length }
;
            }
        }

        if (this.props.roomId) {
            buttonGroup =

                            <TintableSvg src="img/members.svg" width="17" height="22"/>
                            { membersBadge }
                            { membersHighlight }

                            <TintableSvg src="img/files.svg" width="17" height="22"/>
                            { filesHighlight }

;

            if (!this.props.collapsed) {
                if(this.state.phase == this.Phase.MemberList) {
                    panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
                }
                else if(this.state.phase == this.Phase.MemberInfo) {
                    var MemberInfo = sdk.getComponent('rooms.MemberInfo');
                    panel = <MemberInfo roomId={this.props.roomId} member={this.state.member} key={this.props.roomId} />
                }
            }

        }

        var classes = "mx_RightPanel";
        if (this.props.collapsed) {
            classes += " collapsed";
        }

        return (

                    { buttonGroup }

                { panel }

        );
    }
});

<|endoftext|>


<commit_before>import * as utils from 'base/utils';
import Component from 'base/component';

var precision = 1;

//constants
var class_playing = "vzb-playing";
var class_loading = "vzb-ts-loading";
var class_hide_play = "vzb-ts-hide-play-button";
var class_dragging = "vzb-ts-dragging";
var class_axis_aligned = "vzb-ts-axis-aligned";
var class_show_value = "vzb-ts-show-value";
var class_show_value_when_drag_play = "vzb-ts-show-value-when-drag-play";

//margins for slider
var profiles = {
  small: {
    margin: {
      top: 7,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 8,
    label_spacing: 10
  },
  medium: {
    margin: {
      top: 16,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 9,
    label_spacing: 12
  },
  large: {
    margin: {
      top: 14,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 11,
    label_spacing: 14
  }
};

var presentationProfileChanges = {
  "medium": {
    margin: {
      top: 9
    }
  },
  "large": {
    margin: {
    }
  }
}

var TimeSlider = Component.extend({
  /**
   * Initializes the timeslider.
   * Executed once before any template is rendered.
   * @param model The model passed to the component
   * @param context The component's parent
   */
  init: function(model, context) {

    this.name = "gapminder-timeslider";
    this.template = this.template || "timeslider.html";
    this.prevPosition = null;
    //define expected models/hooks for this component
    this.model_expects = [{
      name: "time",
      type: "time"
    }];

    var _this = this;

    //starts as splash if this is the option
    this._splash = model.ui.splash;

    //binds methods to this model
    this.model_binds = {
      'change:time': function(evt, path) {

        //TODO: readyOnce CANNOT be run twice
        if(_this._splash !== _this.model.time.splash) {
          _this._splash = _this.model.time.splash;
          _this.readyOnce();
          _this.ready();
        }

        if(!_this._splash) {

          if((['time.start', 'time.end']).indexOf(path) !== -1) {
            _this.changeLimits();
          }
          _this._optionClasses();
        }
      },
      'change:time.value': function(evt, path) {
        if(!_this._splash) {
          //only set handle position if change is external
          if(!_this.model.time.dragging) _this._setHandle(_this.model.time.playing);
        }
      },
      'change:time.start': function(evt, path) {
        if(!_this._splash) {
          //only set handle position if change is external
          if(!_this.model.time.dragging) _this._setHandle(_this.model.time.playing);
        }
      },
      'change:time.end': function(evt, path) {
        if(!_this._splash) {
          //only set handle position if change is external
          if(!_this.model.time.dragging) _this._setHandle(_this.model.time.playing);
        }
      }
    };

    this.ui = utils.extend({
      show_limits: false,
      show_value: false,
      show_value_when_drag_play: true,
      show_button: true,
      class_axis_aligned: false
    }, model.ui, this.ui);

    // Same constructor as the superclass
    this._super(model, context);

    //defaults
    this.width = 0;
    this.height = 0;

    this.getValueWidth = utils.memoize(this.getValueWidth);
    this._setTime = utils.throttle(this._setTime, 50);
  },

  //template is ready
  readyOnce: function () {

    if(this._splash) return;

    var _this = this;

    //DOM to d3
    //TODO: remove this ugly hack
    this.element = utils.isArray(this.element) ? this.element : d3.select(this.element);
    this.element.classed(class_loading, false);

    //html elements
    this.slider_outer = this.element.select(".vzb-ts-slider");
    this.slider = this.slider_outer.select("g");
    this.axis = this.element.select(".vzb-ts-slider-axis");
    this.slide = this.element.select(".vzb-ts-slider-slide");
    this.handle = this.slide.select(".vzb-ts-slider-handle");
    this.valueText = this.slide.select('.vzb-ts-slider-value');
    //Scale
    this.xScale = d3.time.scale.utc()
      .clamp(true);

    //Axis
    this.xAxis = d3.svg.axis()
      .orient("bottom")
      .tickSize(0);
    //Value
    this.valueText.attr("text-anchor", "middle").attr("dy", "-0.7em");

    var brushed = _this._getBrushed(),
      brushedEnd = _this._getBrushedEnd();

    //Brush for dragging
    this.brush = d3.svg.brush()
      .x(this.xScale)
      .extent([0, 0])
      .on("brush", function () {
        brushed.call(this);
      })
      .on("brushend", function () {
        brushedEnd.call(this);
      });

    //Slide
    this.slide.call(this.brush);

    this.slider_outer.on("mousewheel", function () {
        //do nothing and dont pass the event on if we are currently dragging the slider
        if(_this.model.time.dragging){
            d3.event.stopPropagation();
            d3.event.preventDefault();
            d3.event.returnValue = false;
            return false;
        }
    });

    this.slide.selectAll(".extent,.resize")
      .remove();

    this.parent.on('myEvent', function (evt, arg) {
      var layoutProfile = _this.getLayoutProfile();

      if (arg.profile && arg.profile.margin) {
        profiles[layoutProfile].margin = arg.profile.margin;
      }

      // set the right margin that depends on longest label width
      _this.element.select(".vzb-ts-slider-wrapper")
        .style("right", (arg.mRight - profiles[layoutProfile].margin.right) + "px");

      _this.xScale.range([0, arg.rangeMax]);
      _this.resize();
    });
  },

  //template and model are ready
  ready: function () {
    if(this._splash) return;

    var play = this.element.select(".vzb-ts-btn-play");
    var pause = this.element.select(".vzb-ts-btn-pause");
    var _this = this;
    var time = this.model.time;

    play.on('click', function () {

      _this.model.time.play();
    });

    pause.on('click', function () {
      _this.model.time.pause("soft");
    });

    this.changeLimits();
    this.changeTime();
    this.resize();

  },

  changeLimits: function() {
    var minValue = this.model.time.start;
    var maxValue = this.model.time.end;
    //scale
    this.xScale.domain([minValue, maxValue]);
    //axis
    this.xAxis.tickValues([minValue, maxValue])
      .tickFormat(this.model.time.timeFormat);
  },

  changeTime: function() {
    this.ui.format = this.model.time.unit;
    //time slider should always receive a time model
    var time = this.model.time.value;
    //special classes
    this._optionClasses();
  },

  /**
   * Executes everytime the container or vizabi is resized
   * Ideally,it contains only operations related to size
   */
  resize: function () {

    this.model.time.pause();

    this.profile = this.getActiveProfile(profiles, presentationProfileChanges);

    var slider_w = parseInt(this.slider_outer.style("width"), 10);
    var slider_h = parseInt(this.slider_outer.style("height"), 10);
    this.width = slider_w - this.profile.margin.left - this.profile.margin.right;
    this.height = slider_h - this.profile.margin.bottom - this.profile.margin.top;
    var _this = this;

    //translate according to margins
    this.slider.attr("transform", "translate(" + this.profile.margin.left + "," + this.profile.margin.top + ")");

    //adjust scale width if it was not set manually before
    if (this.xScale.range()[1] = 1) this.xScale.range([0, this.width]);

    //adjust axis with scale
    this.xAxis = this.xAxis.scale(this.xScale)
      .tickPadding(this.profile.label_spacing);

    this.axis.attr("transform", "translate(0," + this.height / 2 + ")")
      .call(this.xAxis);

    this.slide.select(".background")
      .attr("height", this.height);

    //size of handle
    this.handle.attr("transform", "translate(0," + this.height / 2 + ")")
      .attr("r", this.profile.radius);

    this.sliderWidth = _this.slider.node().getBoundingClientRect().width;

    this._setHandle();

  },

  /**
   * Returns width of slider text value.
   * Parameters in this function needed for memoize function, so they are not redundant.
   */
  getValueWidth: function(layout, value) {
    return this.valueText.node().getBoundingClientRect().width;
  },

  /**
   * Gets brushed function to be executed when dragging
   * @returns {Function} brushed function
   */
  _getBrushed: function() {
    var _this = this;
    return function() {

      if (_this.model.time.playing)
        _this.model.time.pause();

      _this._optionClasses();
      _this.element.classed(class_dragging, true);

      var value = _this.brush.extent()[0];

      //set brushed properties

      if(d3.event.sourceEvent) {
        _this.model.time.dragStart();
        var posX = utils.roundStep(Math.round(d3.mouse(this)[0]), precision);
        value = _this.xScale.invert(posX);
        var maxPosX = _this.width;

        if(posX > maxPosX) {
          posX = maxPosX;
        } else if(posX < 0) {
          posX = 0;
        }

        //set handle position
        _this.handle.attr("cx", posX);
        _this.valueText.attr("transform", "translate(" + posX + "," + (_this.height / 2) + ")");
        _this.valueText.text(_this.model.time.timeFormat(value));
      }

      //set time according to dragged position
      if(value - _this.model.time.value !== 0) {
        _this._setTime(value);
      }
    };
  },

  /**
   * Gets brushedEnd function to be executed when dragging ends
   * @returns {Function} brushedEnd function
   */
  _getBrushedEnd: function() {
    var _this = this;
    return function() {
      _this._setTime.recallLast();
      _this.element.classed(class_dragging, false);
      _this.model.time.dragStop();
      _this.model.time.snap();
    };
  },

  /**
   * Sets the handle to the correct position
   * @param {Boolean} transition whether to use transition or not
   */
  _setHandle: function(transition) {
    var _this = this;
    var value = this.model.time.value;
    this.slide.call(this.brush.extent([value, value]));
//    this.valueText.text(this.model.time.timeFormat(value));

//    var old_pos = this.handle.attr("cx");
    var new_pos = this.xScale(value);
    if(_this.prevPosition == null) _this.prevPosition = new_pos;
    var delayAnimations = new_pos > _this.prevPosition ? this.model.time.delayAnimations : 0;
    if(transition) {
      this.handle.attr("cx", _this.prevPosition)
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("cx", new_pos);

      this.valueText.attr("transform", "translate(" + _this.prevPosition + "," + (this.height / 2) + ")")
        .transition('text')
        .delay(delayAnimations)
        .text(this.model.time.timeFormat(value));
      this.valueText
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")");
    } else {
      this.handle
        //cancel active transition
        .interrupt()
        .attr("cx", new_pos);

      this.valueText
        //cancel active transition
        .interrupt()
        .interrupt('text')
        .transition('text');
      this.valueText
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")")
        .text(this.model.time.timeFormat(value));
    }
    _this.prevPosition = new_pos;

  },

  /**
   * Sets the current time model to time
   * @param {number} time The time
   */
  _setTime: function(time) {
    //update state
    var _this = this;
    //  frameRate = 50;

    //avoid updating more than once in "frameRate"
    //var now = new Date();
    //if (this._updTime != null && now - this._updTime < frameRate) return;
    //this._updTime = now;
    var persistent = !this.model.time.dragging && !this.model.time.playing;
    _this.model.time.getModelObject('value').set(time, false, persistent); // non persistent
  },

  /**
   * Applies some classes to the element according to options
   */
  _optionClasses: function() {
    //show/hide classes

    var show_limits = this.ui.show_limits;
    var show_value = this.ui.show_value;
    var show_value_when_drag_play = this.ui.show_value_when_drag_play;
    var axis_aligned = this.ui.axis_aligned;
    var show_play = (this.ui.show_button) && (this.model.time.playable);

    if(!show_limits) {
      this.xAxis.tickValues([]).ticks(0);
    }

    this.element.classed(class_hide_play, !show_play);
    this.element.classed(class_playing, this.model.time.playing);
    this.element.classed(class_show_value, show_value);
    this.element.classed(class_show_value_when_drag_play, show_value_when_drag_play);
    this.element.classed(class_axis_aligned, axis_aligned);
  }
});

export default TimeSlider;
<commit_msg>Issue #1340: Block page scrolling when dragging<commit_after>import * as utils from 'base/utils';
import Component from 'base/component';

var precision = 1;

//constants
var class_playing = "vzb-playing";
var class_loading = "vzb-ts-loading";
var class_hide_play = "vzb-ts-hide-play-button";
var class_dragging = "vzb-ts-dragging";
var class_axis_aligned = "vzb-ts-axis-aligned";
var class_show_value = "vzb-ts-show-value";
var class_show_value_when_drag_play = "vzb-ts-show-value-when-drag-play";

//margins for slider
var profiles = {
  small: {
    margin: {
      top: 7,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 8,
    label_spacing: 10
  },
  medium: {
    margin: {
      top: 16,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 9,
    label_spacing: 12
  },
  large: {
    margin: {
      top: 14,
      right: 15,
      bottom: 10,
      left: 15
    },
    radius: 11,
    label_spacing: 14
  }
};

var presentationProfileChanges = {
  "medium": {
    margin: {
      top: 9
    }
  },
  "large": {
    margin: {
    }
  }
}

var TimeSlider = Component.extend({
  /**
   * Initializes the timeslider.
   * Executed once before any template is rendered.
   * @param model The model passed to the component
   * @param context The component's parent
   */
  init: function(model, context) {

    this.name = "gapminder-timeslider";
    this.template = this.template || "timeslider.html";
    this.prevPosition = null;
    //define expected models/hooks for this component
    this.model_expects = [{
      name: "time",
      type: "time"
    }];

    var _this = this;

    //starts as splash if this is the option
    this._splash = model.ui.splash;

    //binds methods to this model
    this.model_binds = {
      'change:time': function(evt, path) {

        //TODO: readyOnce CANNOT be run twice
        if(_this._splash !== _this.model.time.splash) {
          _this._splash = _this.model.time.splash;
          _this.readyOnce();
          _this.ready();
        }

        if(!_this._splash) {

          if((['time.start', 'time.end']).indexOf(path) !== -1) {
            _this.changeLimits();
          }
          _this._optionClasses();
        }
      },
      'change:time.value': function(evt, path) {
        if(!_this._splash) {
          //only set handle position if change is external
          if(!_this.model.time.dragging) _this._setHandle(_this.model.time.playing);
        }
      },
      'change:time.start': function(evt, path) {
        if(!_this._splash) {
          //only set handle position if change is external
          if(!_this.model.time.dragging) _this._setHandle(_this.model.time.playing);
        }
      },
      'change:time.end': function(evt, path) {
        if(!_this._splash) {
          //only set handle position if change is external
          if(!_this.model.time.dragging) _this._setHandle(_this.model.time.playing);
        }
      }
    };

    this.ui = utils.extend({
      show_limits: false,
      show_value: false,
      show_value_when_drag_play: true,
      show_button: true,
      class_axis_aligned: false
    }, model.ui, this.ui);

    // Same constructor as the superclass
    this._super(model, context);

    //defaults
    this.width = 0;
    this.height = 0;

    this.getValueWidth = utils.memoize(this.getValueWidth);
    this._setTime = utils.throttle(this._setTime, 50);
  },

  //template is ready
  readyOnce: function () {

    if(this._splash) return;

    var _this = this;

    //DOM to d3
    //TODO: remove this ugly hack
    this.element = utils.isArray(this.element) ? this.element : d3.select(this.element);
    this.element.classed(class_loading, false);

    //html elements
    this.slider_outer = this.element.select(".vzb-ts-slider");
    this.slider = this.slider_outer.select("g");
    this.axis = this.element.select(".vzb-ts-slider-axis");
    this.slide = this.element.select(".vzb-ts-slider-slide");
    this.handle = this.slide.select(".vzb-ts-slider-handle");
    this.valueText = this.slide.select('.vzb-ts-slider-value');
    //Scale
    this.xScale = d3.time.scale.utc()
      .clamp(true);

    //Axis
    this.xAxis = d3.svg.axis()
      .orient("bottom")
      .tickSize(0);
    //Value
    this.valueText.attr("text-anchor", "middle").attr("dy", "-0.7em");

    var brushed = _this._getBrushed(),
      brushedEnd = _this._getBrushedEnd();

    //Brush for dragging
    this.brush = d3.svg.brush()
      .x(this.xScale)
      .extent([0, 0])
      .on("brush", function () {
        brushed.call(this);
      })
      .on("brushend", function () {
        brushedEnd.call(this);
      });

    //Slide
    this.slide.call(this.brush);

    this.slider_outer.on("mousewheel", function () {
        //do nothing and dont pass the event on if we are currently dragging the slider
        if(_this.model.time.dragging){
            d3.event.stopPropagation();
            d3.event.preventDefault();
            d3.event.returnValue = false;
            return false;
        }
    });

    this.slide.selectAll(".extent,.resize")
      .remove();

    this.parent.on('myEvent', function (evt, arg) {
      var layoutProfile = _this.getLayoutProfile();

      if (arg.profile && arg.profile.margin) {
        profiles[layoutProfile].margin = arg.profile.margin;
      }

      // set the right margin that depends on longest label width
      _this.element.select(".vzb-ts-slider-wrapper")
        .style("right", (arg.mRight - profiles[layoutProfile].margin.right) + "px");

      _this.xScale.range([0, arg.rangeMax]);
      _this.resize();
    });
  },

  //template and model are ready
  ready: function () {
    if(this._splash) return;

    var play = this.element.select(".vzb-ts-btn-play");
    var pause = this.element.select(".vzb-ts-btn-pause");
    var _this = this;
    var time = this.model.time;

    play.on('click', function () {

      _this.model.time.play();
    });

    pause.on('click', function () {
      _this.model.time.pause("soft");
    });

    this.changeLimits();
    this.changeTime();
    this.resize();

  },

  changeLimits: function() {
    var minValue = this.model.time.start;
    var maxValue = this.model.time.end;
    //scale
    this.xScale.domain([minValue, maxValue]);
    //axis
    this.xAxis.tickValues([minValue, maxValue])
      .tickFormat(this.model.time.timeFormat);
  },

  changeTime: function() {
    this.ui.format = this.model.time.unit;
    //time slider should always receive a time model
    var time = this.model.time.value;
    //special classes
    this._optionClasses();
  },

  /**
   * Executes everytime the container or vizabi is resized
   * Ideally,it contains only operations related to size
   */
  resize: function () {

    this.model.time.pause();

    this.profile = this.getActiveProfile(profiles, presentationProfileChanges);

    var slider_w = parseInt(this.slider_outer.style("width"), 10);
    var slider_h = parseInt(this.slider_outer.style("height"), 10);
    this.width = slider_w - this.profile.margin.left - this.profile.margin.right;
    this.height = slider_h - this.profile.margin.bottom - this.profile.margin.top;
    var _this = this;

    //translate according to margins
    this.slider.attr("transform", "translate(" + this.profile.margin.left + "," + this.profile.margin.top + ")");

    //adjust scale width if it was not set manually before
    if (this.xScale.range()[1] = 1) this.xScale.range([0, this.width]);

    //adjust axis with scale
    this.xAxis = this.xAxis.scale(this.xScale)
      .tickPadding(this.profile.label_spacing);

    this.axis.attr("transform", "translate(0," + this.height / 2 + ")")
      .call(this.xAxis);

    this.slide.select(".background")
      .attr("height", this.height);

    //size of handle
    this.handle.attr("transform", "translate(0," + this.height / 2 + ")")
      .attr("r", this.profile.radius);

    this.sliderWidth = _this.slider.node().getBoundingClientRect().width;

    this._setHandle();

  },

  /**
   * Returns width of slider text value.
   * Parameters in this function needed for memoize function, so they are not redundant.
   */
  getValueWidth: function(layout, value) {
    return this.valueText.node().getBoundingClientRect().width;
  },

  /**
   * Gets brushed function to be executed when dragging
   * @returns {Function} brushed function
   */
  _getBrushed: function() {
    var _this = this;
    return function() {

      if (_this.model.time.playing)
        _this.model.time.pause();

      _this._optionClasses();
      _this.element.classed(class_dragging, true);

      var value = _this.brush.extent()[0];

      //set brushed properties

      if(d3.event.sourceEvent) {
        // Prevent window scrolling on cursor drag in Chrome/Chromium.
        d3.event.sourceEvent.preventDefault();

        _this.model.time.dragStart();
        var posX = utils.roundStep(Math.round(d3.mouse(this)[0]), precision);
        value = _this.xScale.invert(posX);
        var maxPosX = _this.width;

        if(posX > maxPosX) {
          posX = maxPosX;
        } else if(posX < 0) {
          posX = 0;
        }

        //set handle position
        _this.handle.attr("cx", posX);
        _this.valueText.attr("transform", "translate(" + posX + "," + (_this.height / 2) + ")");
        _this.valueText.text(_this.model.time.timeFormat(value));
      }

      //set time according to dragged position
      if(value - _this.model.time.value !== 0) {
        _this._setTime(value);
      }
    };
  },

  /**
   * Gets brushedEnd function to be executed when dragging ends
   * @returns {Function} brushedEnd function
   */
  _getBrushedEnd: function() {
    var _this = this;
    return function() {
      _this._setTime.recallLast();
      _this.element.classed(class_dragging, false);
      _this.model.time.dragStop();
      _this.model.time.snap();
    };
  },

  /**
   * Sets the handle to the correct position
   * @param {Boolean} transition whether to use transition or not
   */
  _setHandle: function(transition) {
    var _this = this;
    var value = this.model.time.value;
    this.slide.call(this.brush.extent([value, value]));
//    this.valueText.text(this.model.time.timeFormat(value));

//    var old_pos = this.handle.attr("cx");
    var new_pos = this.xScale(value);
    if(_this.prevPosition == null) _this.prevPosition = new_pos;
    var delayAnimations = new_pos > _this.prevPosition ? this.model.time.delayAnimations : 0;
    if(transition) {
      this.handle.attr("cx", _this.prevPosition)
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("cx", new_pos);

      this.valueText.attr("transform", "translate(" + _this.prevPosition + "," + (this.height / 2) + ")")
        .transition('text')
        .delay(delayAnimations)
        .text(this.model.time.timeFormat(value));
      this.valueText
        .transition()
        .duration(delayAnimations)
        .ease("linear")
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")");
    } else {
      this.handle
        //cancel active transition
        .interrupt()
        .attr("cx", new_pos);

      this.valueText
        //cancel active transition
        .interrupt()
        .interrupt('text')
        .transition('text');
      this.valueText
        .attr("transform", "translate(" + new_pos + "," + (this.height / 2) + ")")
        .text(this.model.time.timeFormat(value));
    }
    _this.prevPosition = new_pos;

  },

  /**
   * Sets the current time model to time
   * @param {number} time The time
   */
  _setTime: function(time) {
    //update state
    var _this = this;
    //  frameRate = 50;

    //avoid updating more than once in "frameRate"
    //var now = new Date();
    //if (this._updTime != null && now - this._updTime < frameRate) return;
    //this._updTime = now;
    var persistent = !this.model.time.dragging && !this.model.time.playing;
    _this.model.time.getModelObject('value').set(time, false, persistent); // non persistent
  },

  /**
   * Applies some classes to the element according to options
   */
  _optionClasses: function() {
    //show/hide classes

    var show_limits = this.ui.show_limits;
    var show_value = this.ui.show_value;
    var show_value_when_drag_play = this.ui.show_value_when_drag_play;
    var axis_aligned = this.ui.axis_aligned;
    var show_play = (this.ui.show_button) && (this.model.time.playable);

    if(!show_limits) {
      this.xAxis.tickValues([]).ticks(0);
    }

    this.element.classed(class_hide_play, !show_play);
    this.element.classed(class_playing, this.model.time.playing);
    this.element.classed(class_show_value, show_value);
    this.element.classed(class_show_value_when_drag_play, show_value_when_drag_play);
    this.element.classed(class_axis_aligned, axis_aligned);
  }
});

export default TimeSlider;
<|endoftext|>


<commit_before>import React from 'react';
import classNames from 'classnames';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
import PropTypes from 'prop-types';
import Icon from '../icon/icon';
import tagComponent from '../../utils/helpers/tags/tags';
import Portal from '../portal/portal';
import {
  ToastStyle, ToastTypeStyle, ToastContentStyle, ToastWrapper
} from './toast.style';
import OptionsHelper from '../../utils/helpers/options-helper';
import DismissButton from '../dismiss-button';

class Toast extends React.Component {
  /** Classes to be applied to the component. */
  get componentClasses() {
    return classNames(
      this.props.className,
    );
  }

  /** Content rendered for dismiss X */
  get dismissIcon() {
    if (this.props.onDismiss) {
      return (
        <DismissButton
          data-element='close'
          onDismiss={ this.props.onDismiss }
          type='close'
          variant={ this.props.variant || this.props.as }
        />
      );
    }
    return null;
  }

  /** Content rendered for the toast. */
  toastContent() {
    if (this.props.open) {
      return (
        <ToastStyle
          isCenter={ this.props.isCenter }
          variant={ this.props.variant || this.props.as }
          className={ this.componentClasses }
          { ...tagComponent('toast', this.props) }
        >
          <ToastTypeStyle variant={ this.props.variant || this.props.as }>
            <Icon type={ this.props.variant || this.props.as } />
          </ToastTypeStyle>
          <ToastContentStyle
            variant={ this.props.variant || this.props.as }
            isDismiss={ this.props.onDismiss }
          >
            { this.props.children }
          </ToastContentStyle>
          { this.dismissIcon }
        </ToastStyle>
      );
    }
    return null;
  }

  /** Renders the component. */
  render() {
    return (
      <Portal>
        <ToastWrapper isCenter={ this.props.isCenter }>
          <CSSTransitionGroup
            component='div'
            transitionAppear
            transitionName='toast'
            transitionAppearTimeout={ 1600 }
            transitionEnterTimeout={ 1500 }
            transitionLeaveTimeout={ 500 }
          >

            { this.toastContent() }

          </CSSTransitionGroup>
        </ToastWrapper>
      </Portal>
    );
  }
}

Toast.propTypes = {
  /** Customizes the appearance in the DLS theme */
  variant: PropTypes.oneOf(OptionsHelper.colors),
  /** Customizes the appearance in a legacy theme through colour see the 'iconColorSets' for possible values) */
  as: PropTypes.oneOf(OptionsHelper.colors),
  /** Custom className */
  className: PropTypes.string,
  /** The rendered children of the component. */
  children: PropTypes.node,
  /** Determines if the toast is open. */
  open: PropTypes.bool,
  /** Callback for when dismissed. */
  onDismiss: PropTypes.func,
  /** props used with flash component. Allow to center a component */
  isCenter: PropTypes.bool
};

Toast.defaultProps = {
  as: 'warning',
  onDismiss: null,
  open: true,
  isCenter: false
};

export default Toast;
<commit_msg>clean code<commit_after>import React from 'react';
import classNames from 'classnames';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
import PropTypes from 'prop-types';
import Icon from '../icon/icon';
import tagComponent from '../../utils/helpers/tags/tags';
import Portal from '../portal/portal';
import {
  ToastStyle, ToastTypeStyle, ToastContentStyle, ToastWrapper
} from './toast.style';
import OptionsHelper from '../../utils/helpers/options-helper';
import DismissButton from '../dismiss-button';

class Toast extends React.Component {
  /** Classes to be applied to the component. */
  get componentClasses() {
    return classNames(
      this.props.className,
    );
  }

  /** Content rendered for dismiss X */
  get dismissIcon() {
    if (this.props.onDismiss) {
      return (
        <DismissButton
          data-element='close'
          onDismiss={ this.props.onDismiss }
          type='close'
          variant={ this.props.variant || this.props.as }
        />
      );
    }
    return null;
  }

  /** Content rendered for the toast. */
  toastContent() {
    if (this.props.open) {
      return (
        <ToastStyle
          isCenter={ this.props.isCenter }
          variant={ this.props.variant || this.props.as }
          className={ this.componentClasses }
          { ...tagComponent('toast', this.props) }
        >
          <ToastTypeStyle variant={ this.props.variant || this.props.as }>
            <Icon type={ this.props.variant || this.props.as } />
          </ToastTypeStyle>
          <ToastContentStyle
            variant={ this.props.variant || this.props.as }
            isDismiss={ this.props.onDismiss }
          >
            { this.props.children }
          </ToastContentStyle>
          { this.dismissIcon }
        </ToastStyle>
      );
    }
    return null;
  }

  render() {
    return (
      <Portal>
        <ToastWrapper isCenter={ this.props.isCenter }>
          <CSSTransitionGroup
            component='div'
            transitionAppear
            transitionName='toast'
            transitionAppearTimeout={ 1600 }
            transitionEnterTimeout={ 1500 }
            transitionLeaveTimeout={ 500 }
          >
            { this.toastContent() }
          </CSSTransitionGroup>
        </ToastWrapper>
      </Portal>
    );
  }
}

Toast.propTypes = {
  /** Customizes the appearance in the DLS theme */
  variant: PropTypes.oneOf(OptionsHelper.colors),
  /** Customizes the appearance in a legacy theme through colour see the 'iconColorSets' for possible values) */
  as: PropTypes.oneOf(OptionsHelper.colors),
  /** Custom className */
  className: PropTypes.string,
  /** The rendered children of the component. */
  children: PropTypes.node,
  /** Determines if the toast is open. */
  open: PropTypes.bool,
  /** Callback for when dismissed. */
  onDismiss: PropTypes.func,
  /** props used with flash component. Allow to center a component */
  isCenter: PropTypes.bool
};

Toast.defaultProps = {
  as: 'warning',
  onDismiss: null,
  open: true,
  isCenter: false
};

export default Toast;
<|endoftext|>


<commit_before>// TODO:
// move typedWord from state to redux store, and possibly other
// state variables as well

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { wpm } from '../helpers/wpm';
import TypingTest from '../components/typing-test';
import * as actions from '../actions/action-creators';

const corpus =
  "the be to of and a in that have I it for not on with" +
  " he as you do at this but his by from they we say her she or an will my one" +
  " all would there their what so up out if about who get which go me when" +
  " make can like time no just him know take people into year your good some" +
  " could them see other than then now look only come its over think also" +
  " back after use two how our work first well way even new want because any" +
  " these give day most us";

const wordList = corpus.split(" ");

class TypingTestContainer extends Component {
  constructor() {
    super();
    this.start = this.start.bind(this);
    this.stop = this.stop.bind(this);
    this.handleKeyPress = this.handleKeyPress.bind(this);
    this.getRandomText = this.getRandomText.bind(this);
    this.wpm = this.wpm.bind(this);

    /* We keep in local state everything related to the actual act of typing.
     * Anything else (typing test level and word-level things)
     * will be in redux store.
     *
     * TODO: move raw text to redux store
     */
    this.state = {
      words: [],        // The text as an array of words
      typedWord: "",    // The current word as typed by the user
      typedLine: [""],  // Whole words typed on this line so far
      totalWords: 0,    // Total number of words typed
      currentChar: 0,   // Index of current char in current word
      correctCharCount: 0,
      wrongCharCount: 0,
    };
  }

  componentDidMount() {
    // Generate random words
    const text = this.getRandomText(50);
    this.props.setText(text, 50);

    // Transform the text into on array of words
    this.setState({ words: text.split(" ") });
  }

  // Generates a string with n randomly selected words separated by spaces
  getRandomText(n) {
    const rand = (min, max) => Math.floor(Math.random() * (max - min)) + min;
    let text = [];
    for (let i = 0; i < n; i++) {
      text.push(wordList[rand(0, wordList.length)]);
    }
    return text.join(" ");
  }

  start() {
    this.props.start(Date.now());
  }

  stop() {
    const endTime = Date.now();
    this.props.stop(endTime,
                    wpm(this.state.correctCharCount, this.state.wrongCharCount,
                    endTime - this.props.typingTest.startTime));
  }

  wpm() {
    return 0;
  }

  handleKeyPress(e) {
    if (this.props.typingTest.finished)
      return;

    let {
      typedWord,
      typedLine,
      totalWords,
      currentChar,
      correctCharCount,
      wrongCharCount } = this.state;

    // Get the word that's currently being typed
    const word = this.state.words[totalWords];

    if (e.key === "Tab")
      e.preventDefault();

    if (e.key === 'Shift' || e.key === 'Ctrl' || e.key === 'Alt')
      return;

    // Start when typing starts
    if (!this.props.typingTest.inProgress &&
      e.key !== 'Shift' &&
      e.key !== 'Tab')
      this.start();

    // Handle backspace
    if (e.key === 'Backspace') {
      // Already at the beginning of the current word?
      if (currentChar === 0)
        return;

      // Is this a correct char?
      if (this.state.typedWord[currentChar - 1] === word[currentChar - 1])
        correctCharCount--;
      else
        wrongCharCount--;

      currentChar--;
      typedWord = typedWord.slice(0, typedWord.length - 1);
    } else if (e.key === ' ' || e.key === 'Enter') {
      // Count as incorrectly typed characters any letters that were
      // left to type
      wrongCharCount += word.length - currentChar;

      // If this was the last word, then stop the typing test,
      // otherwise move on to next word
      typedLine[this.props.typingTest.word] = typedWord;
      typedWord = "";
      totalWords++;
      currentChar = 0;
      if (totalWords === this.state.words.length)
        this.stop();
      else
        this.props.wordTyped();
    } else {
      // Handle other keys than backspace, space or enter
      if (e.key.length > 1)
        return; // Discard non-alphanumeric

      if (e.key === word[currentChar])
        correctCharCount++;
      else
        wrongCharCount++;

      currentChar++;
      typedWord += e.key;

      // Stop if all the text was typed (the last character being correct)
      if (this.state.totalWords === this.state.words.length - 1 &&
        typedWord.length === word.length &&
        typedWord.slice(-1) === word.slice(-1)) {

          this.stop();
        }
    }
    this.setState({
      correctCharCount,
      wrongCharCount,
      currentChar,
      typedWord,
      typedLine,
      totalWords,
    });
  }

  render() {
    return(
      <TypingTest
        typingTest={this.props.typingTest}
        typedWord={this.state.typedWord}
        typedLine={this.state.typedLine}
        correctChars={this.state.correctCharCount}
        wrongChars={this.state.wrongCharCount}
        onKeyPress={this.handleKeyPress}/>
    );
  }
}

function mapStateToProps(state) {
  return { typingTest: state.typingTest };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(actions, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(TypingTestContainer);
<commit_msg>Cleanup<commit_after>// TODO:
// move typedWord from state to redux store, and possibly other
// state variables as well

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { wpm } from '../helpers/wpm';
import TypingTest from '../components/typing-test';
import * as actions from '../actions/action-creators';

const corpus =
  "the be to of and a in that have I it for not on with" +
  " he as you do at this but his by from they we say her she or an will my one" +
  " all would there their what so up out if about who get which go me when" +
  " make can like time no just him know take people into year your good some" +
  " could them see other than then now look only come its over think also" +
  " back after use two how our work first well way even new want because any" +
  " these give day most us";

const wordList = corpus.split(" ");

class TypingTestContainer extends Component {
  constructor() {
    super();
    this.start = this.start.bind(this);
    this.stop = this.stop.bind(this);
    this.handleKeyPress = this.handleKeyPress.bind(this);
    this.getRandomText = this.getRandomText.bind(this);

    /* We keep in local state everything related to the actual act of typing.
     * Anything else (typing test level and word-level things)
     * will be in redux store.
     *
     * TODO: move raw text to redux store
     */
    this.state = {
      words: [],        // The text as an array of words
      typedWord: "",    // The current word as typed by the user
      typedLine: [""],  // Whole words typed on this line so far
      totalWords: 0,    // Total number of words typed
      currentChar: 0,   // Index of current char in current word
      correctCharCount: 0,
      wrongCharCount: 0,
    };
  }

  componentDidMount() {
    // Generate random words
    const text = this.getRandomText(50);
    this.props.setText(text, 50);

    // Transform the text into on array of words
    this.setState({ words: text.split(" ") });
  }

  // Generates a string with n randomly selected words separated by spaces
  getRandomText(n) {
    const rand = (min, max) => Math.floor(Math.random() * (max - min)) + min;
    let text = [];
    for (let i = 0; i < n; i++) {
      text.push(wordList[rand(0, wordList.length)]);
    }
    return text.join(" ");
  }

  start() {
    this.props.start(Date.now());
  }

  stop() {
    const endTime = Date.now();
    this.props.stop(endTime,
                    wpm(this.state.correctCharCount, this.state.wrongCharCount,
                    endTime - this.props.typingTest.startTime));
  }

  handleKeyPress(e) {
    if (this.props.typingTest.finished)
      return;

    let {
      typedWord,
      typedLine,
      totalWords,
      currentChar,
      correctCharCount,
      wrongCharCount } = this.state;

    // Get the word that's currently being typed
    const word = this.state.words[totalWords];

    if (e.key === "Tab")
      e.preventDefault();

    if (e.key === 'Shift' || e.key === 'Ctrl' || e.key === 'Alt')
      return;

    // Start when typing starts
    if (!this.props.typingTest.inProgress &&
      e.key !== 'Shift' &&
      e.key !== 'Tab')
      this.start();

    // Handle backspace
    if (e.key === 'Backspace') {
      // Already at the beginning of the current word?
      if (currentChar === 0)
        return;

      // Is this a correct char?
      if (this.state.typedWord[currentChar - 1] === word[currentChar - 1])
        correctCharCount--;
      else
        wrongCharCount--;

      currentChar--;
      typedWord = typedWord.slice(0, typedWord.length - 1);
    } else if (e.key === ' ' || e.key === 'Enter') {
      // Count as incorrectly typed characters any letters that were
      // left to type
      wrongCharCount += word.length - currentChar;

      // If this was the last word, then stop the typing test,
      // otherwise move on to next word
      typedLine[this.props.typingTest.word] = typedWord;
      typedWord = "";
      totalWords++;
      currentChar = 0;
      if (totalWords === this.state.words.length)
        this.stop();
      else
        this.props.wordTyped();
    } else {
      // Handle other keys than backspace, space or enter
      if (e.key.length > 1)
        return; // Discard non-alphanumeric

      if (e.key === word[currentChar])
        correctCharCount++;
      else
        wrongCharCount++;

      currentChar++;
      typedWord += e.key;

      // Stop if all the text was typed (the last character being correct)
      if (this.state.totalWords === this.state.words.length - 1 &&
        typedWord.length === word.length &&
        typedWord.slice(-1) === word.slice(-1)) {

          this.stop();
        }
    }
    this.setState({
      correctCharCount,
      wrongCharCount,
      currentChar,
      typedWord,
      typedLine,
      totalWords,
    });
  }

  render() {
    return(
      <TypingTest
        typingTest={this.props.typingTest}
        typedWord={this.state.typedWord}
        typedLine={this.state.typedLine}
        correctChars={this.state.correctCharCount}
        wrongChars={this.state.wrongCharCount}
        onKeyPress={this.handleKeyPress}/>
    );
  }
}

function mapStateToProps(state) {
  return { typingTest: state.typingTest };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(actions, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(TypingTestContainer);
<|endoftext|>


<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This is a fork/based on Small Tabs, written by ロシア,
 * https://addons.mozilla.org/en-US/firefox/addon/small-tabs/
 */

"use strict";

var EXPORTED_SYMBOLS = [ "ThinBookmarks" ];

Components.utils.import("resource://gre/modules/Services.jsm");

Components.utils.import("chrome://thinbookmarks/content/javascript/DynamicStyleSheets.js");

var ThinBookmarks = {
	preferences : null,
	styleSheet : null,
	styleSheetService : null,

	getWindows : function() {
		var windows = [];

		var browserWindows = Services.wm.getEnumerator("navigator:browser");

		while (browserWindows.hasMoreElements()) {
			var browserWindow = browserWindows.getNext();
			browserWindow.QueryInterface(Components.interfaces.nsIDOMWindow);
			windows.push(browserWindow);
		}

		return windows;
	},

	handleEvent : function(event) {
		var document = event.target;
		var window = document.defaultView;

		window.removeEventListener("load", this, true);

		if (document.documentElement.getAttribute("windowtype") != "navigator:browser") {
			return;
		}

		this.loadScript(window);
	},

	loadStyle : function() {
		if (!this.styleSheetService.sheetRegistered(this.styleSheet, this.styleSheetService.USER_SHEET)) {
			this.styleSheetService.loadAndRegisterSheet(this.styleSheet, this.styleSheetService.USER_SHEET);
		}
	},

	unloadStyle : function() {
		if (this.styleSheetService.sheetRegistered(this.styleSheet, this.styleSheetService.USER_SHEET)) {
			this.styleSheetService.unregisterSheet(this.styleSheet, this.styleSheetService.USER_SHEET);
		}
	},

	loadScript : function(window) {
	},

	unloadScript : function(window) {
	},

	onOpenWindow : function(window) {
		var domWindow = window.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(
				Components.interfaces.nsIDOMWindow);
		domWindow.addEventListener("load", this, true);
	},

	onCloseWindow : function(window) {
	},

	onWindowTitleChange : function(window, title) {
	},

	observe : function(subject, topic, data) {
		if (topic != "nsPref:changed") {
			return;
		}

		this.refreshPreference(data);
	},

	refreshPreference : function(name) {
		switch (name) {
			case "bookmarks.icon.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(name,
									"#PlacesToolbarItems > .bookmark-item:not([type]) > .toolbarbutton-icon { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "bookmarks.text.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(name,
									"#PlacesToolbarItems > .bookmark-item:not([type]) > .toolbarbutton-text { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "dropdown.hideopenallintabs":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(
									name,
									"menuitem[class=\"openintabs-menuitem\"], menuseparator[class=\"bookmarks-actions-menuseparator\"] { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "dropdown.minwidth":
				var minimumWidth = this.preferences.getIntPref(name);

				DynamicStyleSheets.register(name, "#PlacesToolbarItems scrollbox { min-width: " + minimumWidth
						+ "px !important; }");
				break;

			case "dropdown.scrollbar":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(
									name,
									"#PlacesToolbarItems scrollbox { overflow-y: auto !important; } #PlacesToolbarItems autorepeatbutton { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "folders.dropdown.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(name,
									"#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-menu-dropmarker { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "folders.icon.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(name,
									"#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-icon { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "folders.text.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(name,
									"#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-text { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "height":
				var height = this.preferences.getIntPref(name);
				var itemHeight = height - this.preferences.getIntPref("item.height.difference");

				DynamicStyleSheets.register(name, "#PersonalToolbar, #PlacesToolbar { height: " + height
						+ "px !important; max-height: " + height
						+ "px !important; } #PersonalToolbar > *, #PlacesToolbarItems > * { height: " + itemHeight
						+ "px !important; max-height: " + itemHeight + "px !important;}");
				break;

			case "items.icon.padding.bottom":
				var itemsIconPaddingBottom = this.preferences.getIntPref(name);

				if (itemsIconPaddingBottom > 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon { padding-bottom: "
									+ itemsIconPaddingBottom + "px !important; }");
				} else if (itemsIconPaddingBottom < 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon { margin-bottom: "
									+ itemsIconPaddingBottom + "px !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}

				break;

			case "items.icon.padding.top":
				var itemsIconPaddingTop = this.preferences.getIntPref(name);

				if (itemsIconPaddingTop > 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon { padding-top: "
									+ itemsIconPaddingTop + "px !important; }");
				} else if (itemsIconPaddingTop < 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon { margin-top: "
									+ itemsIconPaddingTop + "px !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}

				break;

			case "items.text.padding.top":
				var itemsTextPaddingTop = this.preferences.getIntPref(name);

				if (itemsTextPaddingTop > 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-text { padding-top: "
									+ itemsTextPaddingTop + "px !important; }");
				} else if (itemsTextPaddingTop < 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-text { margin-top: "
									+ itemsTextPaddingTop + "px !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}

				break;
		}
	},

	setDefaultPreferences : function() {
		var defaultPreferences = Components.classes["@mozilla.org/preferences-service;1"].getService(
				Components.interfaces.nsIPrefService).getDefaultBranch("extensions.org.bonsaimind.thinbookmarks.");

		defaultPreferences.setBoolPref("bookmarks.icon.hide", false);
		this.refreshPreference("bookmarks.icon.hide");
		defaultPreferences.setBoolPref("bookmarks.text.hide", true);
		this.refreshPreference("bookmarks.text.hide");

		defaultPreferences.setBoolPref("dropdown.hideopenallintabs", false);
		this.refreshPreference("dropdown.hideopenallintabs");
		defaultPreferences.setIntPref("dropdown.minwidth", 0);
		this.refreshPreference("dropdown.minwidth");
		defaultPreferences.setBoolPref("dropdown.scrollbar", false);
		this.refreshPreference("dropdown.scrollbar");

		defaultPreferences.setBoolPref("folders.dropdown.hide", true);
		this.refreshPreference("folders.dropdown.hide");
		defaultPreferences.setBoolPref("folders.icon.hide", true);
		this.refreshPreference("folders.icon.hide");
		defaultPreferences.setBoolPref("folders.text.hide", false);
		this.refreshPreference("folders.text.hide");

		defaultPreferences.setIntPref("height", 24);
		defaultPreferences.setIntPref("item.height.difference", 2);
		this.refreshPreference("height");

		defaultPreferences.setIntPref("items.icon.padding.bottom", -7);
		this.refreshPreference("items.icon.padding.bottom");
		defaultPreferences.setIntPref("items.icon.padding.top", -7);
		this.refreshPreference("items.icon.padding.top");
		defaultPreferences.setIntPref("items.text.padding.top", -1);
		this.refreshPreference("items.text.padding.top");
	},

	init : function() {
		this.preferences = Components.classes["@mozilla.org/preferences-service;1"].getService(
				Components.interfaces.nsIPrefService).getBranch("extensions.org.bonsaimind.thinbookmarks.");
		this.preferences.QueryInterface(Components.interfaces.nsIPrefBranch2);
		this.preferences.addObserver("", this, false);
		this.styleSheet = Services.io.newURI("resource://thinbookmarks/thinbookmarks.css", null, null);
		this.styleSheetService = Components.classes["@mozilla.org/content/style-sheet-service;1"]
				.getService(Components.interfaces.nsIStyleSheetService);

		this.setDefaultPreferences();
		this.loadStyle();

		Services.wm.addListener(this);

		this.getWindows().forEach(function(window) {
			this.loadScript(window);
		}, this);
	},

	uninit : function() {
		this.unloadStyle();

		DynamicStyleSheets.unregisterAll();

		Services.wm.removeListener(this);

		this.getWindows().forEach(function(window) {
			this.unloadScript(window);
		}, this);
	}
};
<commit_msg>Made sure that only the PlacesToolbar is effected.<commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This is a fork/based on Small Tabs, written by ロシア,
 * https://addons.mozilla.org/en-US/firefox/addon/small-tabs/
 */

"use strict";

var EXPORTED_SYMBOLS = [ "ThinBookmarks" ];

Components.utils.import("resource://gre/modules/Services.jsm");

Components.utils.import("chrome://thinbookmarks/content/javascript/DynamicStyleSheets.js");

var ThinBookmarks = {
	preferences : null,
	styleSheet : null,
	styleSheetService : null,

	getWindows : function() {
		var windows = [];

		var browserWindows = Services.wm.getEnumerator("navigator:browser");

		while (browserWindows.hasMoreElements()) {
			var browserWindow = browserWindows.getNext();
			browserWindow.QueryInterface(Components.interfaces.nsIDOMWindow);
			windows.push(browserWindow);
		}

		return windows;
	},

	handleEvent : function(event) {
		var document = event.target;
		var window = document.defaultView;

		window.removeEventListener("load", this, true);

		if (document.documentElement.getAttribute("windowtype") != "navigator:browser") {
			return;
		}

		this.loadScript(window);
	},

	loadStyle : function() {
		if (!this.styleSheetService.sheetRegistered(this.styleSheet, this.styleSheetService.USER_SHEET)) {
			this.styleSheetService.loadAndRegisterSheet(this.styleSheet, this.styleSheetService.USER_SHEET);
		}
	},

	unloadStyle : function() {
		if (this.styleSheetService.sheetRegistered(this.styleSheet, this.styleSheetService.USER_SHEET)) {
			this.styleSheetService.unregisterSheet(this.styleSheet, this.styleSheetService.USER_SHEET);
		}
	},

	loadScript : function(window) {
	},

	unloadScript : function(window) {
	},

	onOpenWindow : function(window) {
		var domWindow = window.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(
				Components.interfaces.nsIDOMWindow);
		domWindow.addEventListener("load", this, true);
	},

	onCloseWindow : function(window) {
	},

	onWindowTitleChange : function(window, title) {
	},

	observe : function(subject, topic, data) {
		if (topic != "nsPref:changed") {
			return;
		}

		this.refreshPreference(data);
	},

	refreshPreference : function(name) {
		switch (name) {
			case "bookmarks.icon.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(name,
									"#PlacesToolbarItems > .bookmark-item:not([type]) > .toolbarbutton-icon { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "bookmarks.text.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(name,
									"#PlacesToolbarItems > .bookmark-item:not([type]) > .toolbarbutton-text { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "dropdown.hideopenallintabs":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(
									name,
									"#PlacesToolbarItems menuitem[class=\"openintabs-menuitem\"], #PlacesToolbarItems menuseparator[class=\"bookmarks-actions-menuseparator\"] { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "dropdown.minwidth":
				var minimumWidth = this.preferences.getIntPref(name);

				DynamicStyleSheets.register(name, "#PlacesToolbarItems scrollbox { min-width: " + minimumWidth
						+ "px !important; }");
				break;

			case "dropdown.scrollbar":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(
									name,
									"#PlacesToolbarItems scrollbox { overflow-y: auto !important; } #PlacesToolbarItems autorepeatbutton { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "folders.dropdown.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(name,
									"#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-menu-dropmarker { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "folders.icon.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(name,
									"#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-icon { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "folders.text.hide":
				if (this.preferences.getBoolPref(name)) {
					DynamicStyleSheets
							.register(name,
									"#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-text { display: none !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}
				break;

			case "height":
				var height = this.preferences.getIntPref(name);
				var itemHeight = height - this.preferences.getIntPref("item.height.difference");

				DynamicStyleSheets.register(name, "#PersonalToolbar, #PlacesToolbar { height: " + height
						+ "px !important; max-height: " + height
						+ "px !important; } #PersonalToolbar > *, #PlacesToolbarItems > * { height: " + itemHeight
						+ "px !important; max-height: " + itemHeight + "px !important;}");
				break;

			case "items.icon.padding.bottom":
				var itemsIconPaddingBottom = this.preferences.getIntPref(name);

				if (itemsIconPaddingBottom > 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon { padding-bottom: "
									+ itemsIconPaddingBottom + "px !important; }");
				} else if (itemsIconPaddingBottom < 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon { margin-bottom: "
									+ itemsIconPaddingBottom + "px !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}

				break;

			case "items.icon.padding.top":
				var itemsIconPaddingTop = this.preferences.getIntPref(name);

				if (itemsIconPaddingTop > 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon { padding-top: "
									+ itemsIconPaddingTop + "px !important; }");
				} else if (itemsIconPaddingTop < 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon { margin-top: "
									+ itemsIconPaddingTop + "px !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}

				break;

			case "items.text.padding.top":
				var itemsTextPaddingTop = this.preferences.getIntPref(name);

				if (itemsTextPaddingTop > 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-text { padding-top: "
									+ itemsTextPaddingTop + "px !important; }");
				} else if (itemsTextPaddingTop < 0) {
					DynamicStyleSheets.register(name,
							"#PlacesToolbarItems > .bookmark-item > .toolbarbutton-text { margin-top: "
									+ itemsTextPaddingTop + "px !important; }");
				} else {
					DynamicStyleSheets.unregister(name);
				}

				break;
		}
	},

	setDefaultPreferences : function() {
		var defaultPreferences = Components.classes["@mozilla.org/preferences-service;1"].getService(
				Components.interfaces.nsIPrefService).getDefaultBranch("extensions.org.bonsaimind.thinbookmarks.");

		defaultPreferences.setBoolPref("bookmarks.icon.hide", false);
		this.refreshPreference("bookmarks.icon.hide");
		defaultPreferences.setBoolPref("bookmarks.text.hide", true);
		this.refreshPreference("bookmarks.text.hide");

		defaultPreferences.setBoolPref("dropdown.hideopenallintabs", false);
		this.refreshPreference("dropdown.hideopenallintabs");
		defaultPreferences.setIntPref("dropdown.minwidth", 0);
		this.refreshPreference("dropdown.minwidth");
		defaultPreferences.setBoolPref("dropdown.scrollbar", false);
		this.refreshPreference("dropdown.scrollbar");

		defaultPreferences.setBoolPref("folders.dropdown.hide", true);
		this.refreshPreference("folders.dropdown.hide");
		defaultPreferences.setBoolPref("folders.icon.hide", true);
		this.refreshPreference("folders.icon.hide");
		defaultPreferences.setBoolPref("folders.text.hide", false);
		this.refreshPreference("folders.text.hide");

		defaultPreferences.setIntPref("height", 24);
		defaultPreferences.setIntPref("item.height.difference", 2);
		this.refreshPreference("height");

		defaultPreferences.setIntPref("items.icon.padding.bottom", -7);
		this.refreshPreference("items.icon.padding.bottom");
		defaultPreferences.setIntPref("items.icon.padding.top", -7);
		this.refreshPreference("items.icon.padding.top");
		defaultPreferences.setIntPref("items.text.padding.top", -1);
		this.refreshPreference("items.text.padding.top");
	},

	init : function() {
		this.preferences = Components.classes["@mozilla.org/preferences-service;1"].getService(
				Components.interfaces.nsIPrefService).getBranch("extensions.org.bonsaimind.thinbookmarks.");
		this.preferences.QueryInterface(Components.interfaces.nsIPrefBranch2);
		this.preferences.addObserver("", this, false);
		this.styleSheet = Services.io.newURI("resource://thinbookmarks/thinbookmarks.css", null, null);
		this.styleSheetService = Components.classes["@mozilla.org/content/style-sheet-service;1"]
				.getService(Components.interfaces.nsIStyleSheetService);

		this.setDefaultPreferences();
		this.loadStyle();

		Services.wm.addListener(this);

		this.getWindows().forEach(function(window) {
			this.loadScript(window);
		}, this);
	},

	uninit : function() {
		this.unloadStyle();

		DynamicStyleSheets.unregisterAll();

		Services.wm.removeListener(this);

		this.getWindows().forEach(function(window) {
			this.unloadScript(window);
		}, this);
	}
};
<|endoftext|>


<commit_before>import tagList from './tagList';
import $ from 'jquery';
$.fn.modal= require('semantic-ui-modal');
$.fn.dropdown = require('semantic-ui-dropdown');
$.fn.transition = require('semantic-ui-transition');
$.fn.dimmer = require('semantic-ui-dimmer');

export default class WWLTWModal {
    constructor() {
        let modalContainer = `

What did you learn while working on this story?

<input type="button" value="Skip"
                                                                 class="ui deny button negative basic right floated">

                <form id="wwltw-form" class="ui form">

                                Body (markdown supported)
                                <textarea name="learning-body" id="learning-body"></textarea>

                                Tags (optional)
                                <select name="tags" multiple="" id="learning-tags" class="ui fluid search dropdown">
                                    <option value="">e.g. java, rails, tdd</option>
                                    ${this.populateTags()}
                                </select>

                                <input type="submit" value="Add to WWLTW story" class="ui submit button right floated">

                </form>

`;

        this.addElementToBody(modalContainer);
    }

    populateTags() {
        return tagList.map((tag) => {
            return `<option value="${tag}">${tag}</option>`;
        }).join('');
    }

    initialize() {
        $('.ui.fluid.dropdown').dropdown();

        this.wwltwForm = document.querySelector("#wwltw-form");
        this.learningBody = document.querySelector("#learning-body");
        this.$modal = $('.ui.modal').modal({
            onHide: () => {
                this.clearForm()
            }
        });
        return this;
    }

    clearForm() {
        this.learningBody.value = "";
        $('.ui.fluid.dropdown').dropdown('restore defaults');
    };

    bindFormSubmission(thingToDoOnSubmit) {
        this.wwltwForm.addEventListener('submit', (e) => {
            e.preventDefault();

            thingToDoOnSubmit(this.extractSubmittedTags(), this.learningBody.value);

            this.clearForm();
            this.$modal.modal('hide');
        });
    }

    extractSubmittedTags() {
        const learningTags = this.wwltwForm.querySelector("#learning-tags");
        const tags = $(learningTags).find('option:selected')
            .map(function (i, option) {
                return option.value;
            });

        return Array.prototype.join.call(tags, ', ');
    };

    addElementToBody(innerHtml) {
        let modalContainer = document.createElement("div");
        modalContainer.innerHTML = innerHtml;
        document.querySelector('body').appendChild(modalContainer);
    }
}<commit_msg>Add placeholder text for learning body to promote standardized format<commit_after>import tagList from './tagList';
import $ from 'jquery';
$.fn.modal= require('semantic-ui-modal');
$.fn.dropdown = require('semantic-ui-dropdown');
$.fn.transition = require('semantic-ui-transition');
$.fn.dimmer = require('semantic-ui-dimmer');

export default class WWLTWModal {
    constructor() {
        let modalContent = `

What did you learn while working on this story?

<input type="button" value="Skip"
                                                                 class="ui deny button negative basic right floated">

                <form id="wwltw-form" class="ui form">

                                Content (markdown supported)
                                <textarea name="learning-body" id="learning-body"></textarea>

                                Tags (optional)
                                <select name="tags" multiple="" id="learning-tags" class="ui fluid search dropdown">
                                    <option value="">e.g. java, rails, tdd</option>
                                    ${this.populateTags()}
                                </select>

                                <input type="submit" value="Add to WWLTW story" class="ui submit button right floated">

                </form>

`;

        let modalContainer = this.addElementToBody(modalContent);
        let learningBody = modalContainer.querySelector("#learning-body");
        learningBody.placeholder = "### A Header for your Learning (recommended)\n\n";
        learningBody.placeholder += "Body of learning. You can use all the markdown you want\n\n";
        learningBody.placeholder += "```\n    codeBlocks.areSupported();\n```";
    }

    populateTags() {
        return tagList.map((tag) => {
            return `<option value="${tag}">${tag}</option>`;
        }).join('');
    }

    initialize() {
        $('.ui.fluid.dropdown').dropdown();

        this.wwltwForm = document.querySelector("#wwltw-form");
        this.learningBody = document.querySelector("#learning-body");
        this.$modal = $('.ui.modal').modal({
            onHide: () => {
                this.clearForm()
            }
        });
        return this;
    }

    clearForm() {
        this.learningBody.value = "";
        $('.ui.fluid.dropdown').dropdown('restore defaults');
    };

    bindFormSubmission(thingToDoOnSubmit) {
        this.wwltwForm.addEventListener('submit', (e) => {
            e.preventDefault();

            thingToDoOnSubmit(this.extractSubmittedTags(), this.learningBody.value);

            this.clearForm();
            this.$modal.modal('hide');
        });
    }

    extractSubmittedTags() {
        const learningTags = this.wwltwForm.querySelector("#learning-tags");
        const tags = $(learningTags).find('option:selected')
            .map(function (i, option) {
                return option.value;
            });

        return Array.prototype.join.call(tags, ', ');
    };

    addElementToBody(innerHtml) {
        let elementContainer = document.createElement("div");
        elementContainer.innerHTML = innerHtml;
        document.querySelector('body').appendChild(elementContainer);
        return elementContainer;
    }
}<|endoftext|>