<commit_before>import { bindAll } from 'underscore';
import { on, off, getUnitFromValue} from 'utils/mixins';

const ToolbarView = require('dom_components/view/ToolbarView');
const Toolbar = require('dom_components/model/Toolbar');
const key = require('keymaster');
const Backbone = require('backbone');
let showOffsets;
const $ = Backbone.$;

module.exports = {

  init(o) {
    bindAll(this, 'onHover', 'onOut', 'onClick', 'onKeyPress',
      'copyComp', 'pasteComp', 'onFrameScroll');
  },

  enable() {
    this.frameOff = this.canvasOff = this.adjScroll = null;
    var config  = this.config.em.get('Config');
    this.startSelectComponent();
    this.toggleClipboard(config.copyPaste);
    var em = this.config.em;
    showOffsets = 1;

    em.on('component:update', this.updateAttached, this);
    em.on('change:canvasOffset', this.updateAttached, this);
  },

  /**
   * Toggle clipboard function
   * @param  {Boolean} active
   * @return {this}
   * @private
   */
  toggleClipboard(active) {
    var en = active || 0;
    if(en){
      key('⌘+c, ctrl+c', this.copyComp);
      key('⌘+v, ctrl+v', this.pasteComp);
    }else{
      key.unbind('⌘+c, ctrl+c');
      key.unbind('⌘+v, ctrl+v');
    }
  },

  /**
   * Copy component to the clipboard
   * @private
   */
  copyComp() {
    var el = this.editorModel.get('selectedComponent');
    if(el && el.get('copyable'))
      this.editorModel.set('clipboard', el);
  },

  /**
   * Paste component from clipboard
   * @private
   */
  pasteComp() {
    var clp = this.editorModel.get('clipboard'),
        sel = this.editorModel.get('selectedComponent');
    if(clp && sel && sel.collection){
      var index = sel.collection.indexOf(sel),
          clone = clp.clone();
      sel.collection.add(clone, { at: index + 1 });
    }
  },

  /**
   * Start select component event
   * @private
   * */
  startSelectComponent() {
   this.toggleSelectComponent(1);
  },

  /**
   * Stop select component event
   * @private
   * */
  stopSelectComponent() {
   this.toggleSelectComponent();
  },

  /**
   * Toggle select component event
   * @private
   * */
  toggleSelectComponent(enable) {
    const em = this.em;
    const method = enable ? 'on' : 'off';
    const methods = {on, off};
    const body = this.getCanvasBody();
    const win = this.getContentWindow();
    methods[method](body, 'mouseover', this.onHover);
    methods[method](body, 'mouseout', this.onOut);
    methods[method](body, 'click', this.onClick);
    methods[method](win, 'scroll', this.onFrameScroll);
    methods[method](win, 'keydown', this.onKeyPress);
    em[method]('change:selectedComponent', this.onSelect, this);
  },

  /**
   * On key press event
   * @private
   * */
  onKeyPress(e) {
    var key = e.which || e.keyCode;
    var comp = this.editorModel.get('selectedComponent');
    var focused = this.frameEl.contentDocument.activeElement.tagName !== 'BODY';

    // On CANC (46) or Backspace (8)
    if(key == 8 || key == 46) {
      if(!focused)
        e.preventDefault();
      if(comp && !focused) {
        if(!comp.get('removable'))
          return;
        comp.set('status','');
        comp.destroy();
        this.hideBadge();
        this.clean();
        this.hideHighlighter();
        this.editorModel.set('selectedComponent', null);
      }
    }
  },

  /**
   * Hover command
   * @param {Object}  e
   * @private
   */
  onHover(e) {
    e.stopPropagation();
    var trg = e.target;

    // Adjust tools scroll top
    if (!this.adjScroll) {
      this.adjScroll = 1;
      this.onFrameScroll(e);
      this.updateAttached();
    }

    var pos = this.getElementPos(trg);
    this.updateBadge(trg, pos);
    this.updateHighlighter(trg, pos);
    this.showElementOffset(trg, pos);
  },

  /**
   * Out command
   * @param {Object}  e
   * @private
   */
  onOut(e) {
    e.stopPropagation();
    this.hideBadge();
    this.hideHighlighter();
    this.hideElementOffset();
  },

  /**
   * Show element offset viewer
   * @param {HTMLElement}  el
   * @param {Object} pos
   */
  showElementOffset(el, pos) {
    var $el = $(el);
    var model = $el.data('model');

    if ( (model && model.get('status') == 'selected') ||
        !showOffsets){
      return;
    }

    this.editor.runCommand('show-offset', {
      el,
      elPos: pos,
    });
  },

  /**
   * Hide element offset viewer
   * @param {HTMLElement}  el
   * @param {Object} pos
   */
  hideElementOffset(el, pos) {
    this.editor.stopCommand('show-offset');
  },

  /**
   * Show fixed element offset viewer
   * @param {HTMLElement}  el
   * @param {Object} pos
   */
  showFixedElementOffset(el, pos) {
    this.editor.runCommand('show-offset', {
      el,
      elPos: pos,
      state: 'Fixed',
    });
  },

  /**
   * Hide fixed element offset viewer
   * @param {HTMLElement}  el
   * @param {Object} pos
   */
  hideFixedElementOffset(el, pos) {
    if(this.editor)
      this.editor.stopCommand('show-offset', {state: 'Fixed'});
  },

  /**
   * Hide Highlighter element
   */
  hideHighlighter() {
    this.canvas.getHighlighter().style.display = 'none';
  },

  /**
   * On element click
   * @param {Event}  e
   * @private
   */
  onClick(e) {
    e.stopPropagation();
    const model = $(e.target).data('model');
    model && this.editor.select(model);
  },

  /**
   * Update badge for the component
   * @param {Object} Component
   * @param {Object} pos Position object
   * @private
   * */
  updateBadge(el, pos) {
    var $el = $(el);
    var canvas = this.canvas;
    var config = canvas.getConfig();
    var customeLabel = config.customBadgeLabel;
    this.cacheEl = el;
    var model = $el.data("model");
    if(!model || !model.get('badgable'))
      return;
    var badge = this.getBadge();
    var badgeLabel = model.getIcon() + model.getName();
    badgeLabel = customeLabel ? customeLabel(model) : badgeLabel;
    badge.innerHTML = badgeLabel;
    var bStyle = badge.style;
    var u = 'px';
    bStyle.display = 'block';
    var canvasPos = canvas.getCanvasView().getPosition();
    var badgeH = badge ? badge.offsetHeight : 0;
    var badgeW = badge ? badge.offsetWidth : 0;
    var top = pos.top - badgeH < canvasPos.top ? canvasPos.top : pos.top - badgeH;
    var left = pos.left + badgeW < canvasPos.left ? canvasPos.left : pos.left;
    bStyle.top = top + u;
    bStyle.left = left + u;
  },

  /**
   * Update highlighter element
   * @param {HTMLElement} el
   * @param {Object} pos Position object
   * @private
   */
  updateHighlighter(el, pos) {
    var $el = $(el);
    var model = $el.data('model');

    if(!model || !model.get("hoverable") || model.get('status') == 'selected') {
      return;
    }

    var hlEl = this.canvas.getHighlighter();
    var hlStyle = hlEl.style;
    var unit = 'px';
    hlStyle.left = pos.left + unit;
    hlStyle.top = pos.top + unit;
    hlStyle.height = pos.height + unit;
    hlStyle.width = pos.width + unit;
    hlStyle.display = 'block';
  },

  /**
   * Say what to do after the component was selected
   * @param {Object}  e
   * @param {Object}  el
   * @private
   * */
  onSelect() {
    const editor = this.editor;
    const model = this.em.getSelected();
    this.updateToolbar(model);

    if (model) {
      const el = model.view.el;
      this.showFixedElementOffset(el);
      this.hideElementOffset();
      this.hideHighlighter();
      this.initResize(el);
    } else {
      editor.stopCommand('resize');
    }
  },

  /**
   * Init resizer on the element if possible
   * @param  {HTMLElement} el
   * @private
   */
  initResize(el) {
    var em = this.em;
    var editor = em ? em.get('Editor') : '';
    var config = em ? em.get('Config') : '';
    var pfx = config.stylePrefix || '';
    var attrName = `data-${pfx}handler`;
    var resizeClass = `${pfx}resizing`;
    var model = em.get('selectedComponent');
    var resizable = model.get('resizable');
    var options = {};
    var modelToStyle;

    var toggleBodyClass = (method, e, opts) => {
      const docs = opts.docs;
      docs && docs.forEach(doc => {
        const body = doc.body;
        const cls = body.className || '';
        body.className = (method == 'add' ?
          `${cls} ${resizeClass}` : cls.replace(resizeClass, '')).trim();
      });
    };

    if (editor && resizable) {
      options = {
        // Here the resizer is updated with the current element height and width
        onStart(e, opts = {}) {
          const { el, config, resizer } = opts;
          const { keyHeight, keyWidth, currentUnit } = config;
          toggleBodyClass('add', e, opts);
          modelToStyle = em.get('StyleManager').getModelToStyle(model);
          const computedStyle = getComputedStyle(el);
          const modelStyle = modelToStyle.getStyle();
          const currentWidth = modelStyle[keyWidth] || computedStyle[keyWidth];
          const currentHeight = modelStyle[keyHeight] || computedStyle[keyHeight];
          resizer.startDim.w = parseFloat(currentWidth);
          resizer.startDim.h = parseFloat(currentHeight);
          showOffsets = 0;

          if (currentUnit) {
            config.unitHeight = getUnitFromValue(currentHeight);
            config.unitWidth = getUnitFromValue(currentWidth);
          }
        },

        // Update all positioned elements (eg. component toolbar)
        onMove() {
          editor.trigger('change:canvasOffset');
        },

        onEnd(e, opts) {
          toggleBodyClass('remove', e, opts);
          editor.trigger('change:canvasOffset');
          showOffsets = 1;
        },

        updateTarget(el, rect, options = {}) {
          if (!modelToStyle) {
            return;
          }

          const { store, selectedHandler, config} = options;
          const { keyHeight, keyWidth } = config;
          const onlyHeight = ['tc', 'bc'].indexOf(selectedHandler) >= 0;
          const onlyWidth = ['cl', 'cr'].indexOf(selectedHandler) >= 0;
          const style = modelToStyle.getStyle();

          if (!onlyHeight) {
            style[keyWidth] = rect.w + config.unitWidth;
          }

          if (!onlyWidth) {
            style[keyHeight] = rect.h + config.unitHeight;
          }

          modelToStyle.setStyle(style, {avoidStore: 1});
          const updateEvent = `update:component:style`;
          em && em.trigger(`${updateEvent}:${keyHeight} ${updateEvent}:${keyWidth}`);

          if (store) {
            modelToStyle.trigger('change:style', modelToStyle, style, {});
          }
        }
      };

      if (typeof resizable == 'object') {
        options = { ...options, ...resizable };
      }

      editor.runCommand('resize', {el, options});

      // On undo/redo the resizer rect is not updating, need somehow to call
      // this.updateRect on undo/redo action
    }
  },

  /**
   * Update toolbar if the component has one
   * @param {Object} mod
   */
  updateToolbar(mod) {
    var em = this.config.em;
    var model = mod == em ? em.get('selectedComponent') : mod;
    var toolbarEl = this.canvas.getToolbarEl();
    var toolbarStyle = toolbarEl.style;

    if (!model) {
      // By putting `toolbarStyle.display = 'none'` will cause kind
      // of freezed effect with component selection (probably by iframe
      // switching)
      toolbarStyle.opacity = 0;
      return;
    }

    var toolbar = model.get('toolbar');
    var ppfx = this.ppfx;
    var showToolbar = em.get('Config').showToolbar;

    if (showToolbar && toolbar && toolbar.length) {
      toolbarStyle.opacity = '';
      toolbarStyle.display = '';
      if(!this.toolbar) {
        toolbarEl.innerHTML = '';
        this.toolbar = new Toolbar(toolbar);
        var toolbarView = new ToolbarView({
          collection: this.toolbar,
          editor: this.editor
        });
        toolbarEl.appendChild(toolbarView.render().el);
      }

      this.toolbar.reset(toolbar);
      var view = model.view;

      if(view) {
        this.updateToolbarPos(view.el);
      }
    } else {
      toolbarStyle.display = 'none';
    }
  },

  /**
   * Update toolbar positions
   * @param {HTMLElement} el
   * @param {Object} pos
   */
  updateToolbarPos(el, elPos) {
    var unit = 'px';
    var toolbarEl = this.canvas.getToolbarEl();
    var toolbarStyle = toolbarEl.style;
    var pos = this.canvas.getTargetToElementDim(toolbarEl, el, {
      elPos,
      event: 'toolbarPosUpdate',
    });
    var leftPos = pos.left + pos.elementWidth - pos.targetWidth;
    toolbarStyle.top = pos.top + unit;
    toolbarStyle.left = leftPos + unit;
  },

  /**
   * Return canvas dimensions and positions
   * @return {Object}
   */
  getCanvasPosition() {
    return this.canvas.getCanvasView().getPosition();
  },

  /**
   * Removes all highlighting effects on components
   * @private
   * */
  clean() {
    if(this.selEl)
      this.selEl.removeClass(this.hoverClass);
  },

  /**
   * Returns badge element
   * @return {HTMLElement}
   * @private
   */
  getBadge() {
    return this.canvas.getBadgeEl();
  },

  /**
   * On frame scroll callback
   * @private
   */
  onFrameScroll(e) {
    var el = this.cacheEl;
    if (el) {
      var elPos = this.getElementPos(el);
      this.updateBadge(el, elPos);
      var model = this.em.get('selectedComponent');

      if (model) {
        this.updateToolbarPos(model.view.el);
      }
    }
  },

  /**
   * Update attached elements, eg. component toolbar
   * @return {[type]} [description]
   */
  updateAttached() {
    var model = this.em.get('selectedComponent');
    if (model) {
      var view = model.view;
      this.updateToolbarPos(view.el);
      this.showFixedElementOffset(view.el);
    }
  },

  /**
   * Returns element's data info
   * @param {HTMLElement} el
   * @return {Object}
   * @private
   */
  getElementPos(el, badge) {
    return this.canvas.getCanvasView().getElementPos(el);
  },

  /**
   * Hide badge
   * @private
   * */
  hideBadge() {
    this.getBadge().style.display = 'none';
  },

  /**
   * Clean previous model from different states
   * @param {Component} model
   * @private
   */
  cleanPrevious(model) {
    model && model.set({
      status: '',
      state: '',
    });
  },

  /**
   * Returns content window
   * @private
   */
  getContentWindow() {
    return this.frameEl.contentWindow;
  },

  run(editor) {
    this.editor = editor && editor.get('Editor');
    this.enable();
    this.onSelect();
  },

  stop(editor, sender, opts = {}) {
    const em = this.em;
    this.stopSelectComponent();
    !opts.preserveSelected && em.setSelected(null);
    this.clean();
    this.toggleClipboard();
    this.hideBadge();
    this.hideFixedElementOffset();
    this.canvas.getToolbarEl().style.display = 'none';

    em.off('component:update', this.updateAttached, this);
    em.off('change:canvasOffset', this.updateAttached, this);
    em.off('change:selectedComponent', this.updateToolbar, this);
  }
};
<commit_msg>Fix toolbar positioning issues<commit_after>import { bindAll } from 'underscore';
import { on, off, getUnitFromValue} from 'utils/mixins';

const ToolbarView = require('dom_components/view/ToolbarView');
const Toolbar = require('dom_components/model/Toolbar');
const key = require('keymaster');
const Backbone = require('backbone');
let showOffsets;
const $ = Backbone.$;

module.exports = {

  init(o) {
    bindAll(this, 'onHover', 'onOut', 'onClick', 'onKeyPress',
      'copyComp', 'pasteComp', 'onFrameScroll');
  },

  enable() {
    this.frameOff = this.canvasOff = this.adjScroll = null;
    var config  = this.config.em.get('Config');
    this.startSelectComponent();
    this.toggleClipboard(config.copyPaste);
    var em = this.config.em;
    showOffsets = 1;

    em.on('component:update', this.updateAttached, this);
    em.on('change:canvasOffset', this.updateAttached, this);
  },

  /**
   * Toggle clipboard function
   * @param  {Boolean} active
   * @return {this}
   * @private
   */
  toggleClipboard(active) {
    var en = active || 0;
    if(en){
      key('⌘+c, ctrl+c', this.copyComp);
      key('⌘+v, ctrl+v', this.pasteComp);
    }else{
      key.unbind('⌘+c, ctrl+c');
      key.unbind('⌘+v, ctrl+v');
    }
  },

  /**
   * Copy component to the clipboard
   * @private
   */
  copyComp() {
    var el = this.editorModel.get('selectedComponent');
    if(el && el.get('copyable'))
      this.editorModel.set('clipboard', el);
  },

  /**
   * Paste component from clipboard
   * @private
   */
  pasteComp() {
    var clp = this.editorModel.get('clipboard'),
        sel = this.editorModel.get('selectedComponent');
    if(clp && sel && sel.collection){
      var index = sel.collection.indexOf(sel),
          clone = clp.clone();
      sel.collection.add(clone, { at: index + 1 });
    }
  },

  /**
   * Start select component event
   * @private
   * */
  startSelectComponent() {
   this.toggleSelectComponent(1);
  },

  /**
   * Stop select component event
   * @private
   * */
  stopSelectComponent() {
   this.toggleSelectComponent();
  },

  /**
   * Toggle select component event
   * @private
   * */
  toggleSelectComponent(enable) {
    const em = this.em;
    const method = enable ? 'on' : 'off';
    const methods = {on, off};
    const body = this.getCanvasBody();
    const win = this.getContentWindow();
    methods[method](body, 'mouseover', this.onHover);
    methods[method](body, 'mouseout', this.onOut);
    methods[method](body, 'click', this.onClick);
    methods[method](win, 'scroll', this.onFrameScroll);
    methods[method](win, 'keydown', this.onKeyPress);
    em[method]('change:selectedComponent', this.onSelect, this);
  },

  /**
   * On key press event
   * @private
   * */
  onKeyPress(e) {
    var key = e.which || e.keyCode;
    var comp = this.editorModel.get('selectedComponent');
    var focused = this.frameEl.contentDocument.activeElement.tagName !== 'BODY';

    // On CANC (46) or Backspace (8)
    if(key == 8 || key == 46) {
      if(!focused)
        e.preventDefault();
      if(comp && !focused) {
        if(!comp.get('removable'))
          return;
        comp.set('status','');
        comp.destroy();
        this.hideBadge();
        this.clean();
        this.hideHighlighter();
        this.editorModel.set('selectedComponent', null);
      }
    }
  },

  /**
   * Hover command
   * @param {Object}  e
   * @private
   */
  onHover(e) {
    e.stopPropagation();
    var trg = e.target;

    // Adjust tools scroll top
    if (!this.adjScroll) {
      this.adjScroll = 1;
      this.onFrameScroll(e);
      this.updateAttached();
    }

    var pos = this.getElementPos(trg);
    this.updateBadge(trg, pos);
    this.updateHighlighter(trg, pos);
    this.showElementOffset(trg, pos);
  },

  /**
   * Out command
   * @param {Object}  e
   * @private
   */
  onOut(e) {
    e.stopPropagation();
    this.hideBadge();
    this.hideHighlighter();
    this.hideElementOffset();
  },

  /**
   * Show element offset viewer
   * @param {HTMLElement}  el
   * @param {Object} pos
   */
  showElementOffset(el, pos) {
    var $el = $(el);
    var model = $el.data('model');

    if ( (model && model.get('status') == 'selected') ||
        !showOffsets){
      return;
    }

    this.editor.runCommand('show-offset', {
      el,
      elPos: pos,
    });
  },

  /**
   * Hide element offset viewer
   * @param {HTMLElement}  el
   * @param {Object} pos
   */
  hideElementOffset(el, pos) {
    this.editor.stopCommand('show-offset');
  },

  /**
   * Show fixed element offset viewer
   * @param {HTMLElement}  el
   * @param {Object} pos
   */
  showFixedElementOffset(el, pos) {
    this.editor.runCommand('show-offset', {
      el,
      elPos: pos,
      state: 'Fixed',
    });
  },

  /**
   * Hide fixed element offset viewer
   * @param {HTMLElement}  el
   * @param {Object} pos
   */
  hideFixedElementOffset(el, pos) {
    if(this.editor)
      this.editor.stopCommand('show-offset', {state: 'Fixed'});
  },

  /**
   * Hide Highlighter element
   */
  hideHighlighter() {
    this.canvas.getHighlighter().style.display = 'none';
  },

  /**
   * On element click
   * @param {Event}  e
   * @private
   */
  onClick(e) {
    e.stopPropagation();
    const model = $(e.target).data('model');
    model && this.editor.select(model);
  },

  /**
   * Update badge for the component
   * @param {Object} Component
   * @param {Object} pos Position object
   * @private
   * */
  updateBadge(el, pos) {
    var $el = $(el);
    var canvas = this.canvas;
    var config = canvas.getConfig();
    var customeLabel = config.customBadgeLabel;
    this.cacheEl = el;
    var model = $el.data("model");
    if(!model || !model.get('badgable'))
      return;
    var badge = this.getBadge();
    var badgeLabel = model.getIcon() + model.getName();
    badgeLabel = customeLabel ? customeLabel(model) : badgeLabel;
    badge.innerHTML = badgeLabel;
    var bStyle = badge.style;
    var u = 'px';
    bStyle.display = 'block';
    var canvasPos = canvas.getCanvasView().getPosition();
    var badgeH = badge ? badge.offsetHeight : 0;
    var badgeW = badge ? badge.offsetWidth : 0;
    var top = pos.top - badgeH < canvasPos.top ? canvasPos.top : pos.top - badgeH;
    var left = pos.left + badgeW < canvasPos.left ? canvasPos.left : pos.left;
    bStyle.top = top + u;
    bStyle.left = left + u;
  },

  /**
   * Update highlighter element
   * @param {HTMLElement} el
   * @param {Object} pos Position object
   * @private
   */
  updateHighlighter(el, pos) {
    var $el = $(el);
    var model = $el.data('model');

    if(!model || !model.get("hoverable") || model.get('status') == 'selected') {
      return;
    }

    var hlEl = this.canvas.getHighlighter();
    var hlStyle = hlEl.style;
    var unit = 'px';
    hlStyle.left = pos.left + unit;
    hlStyle.top = pos.top + unit;
    hlStyle.height = pos.height + unit;
    hlStyle.width = pos.width + unit;
    hlStyle.display = 'block';
  },

  /**
   * Say what to do after the component was selected
   * @param {Object}  e
   * @param {Object}  el
   * @private
   * */
  onSelect() {
    const editor = this.editor;
    const model = this.em.getSelected();
    this.updateToolbar(model);

    if (model) {
      const el = model.view.el;
      this.showFixedElementOffset(el);
      this.hideElementOffset();
      this.hideHighlighter();
      this.initResize(el);
    } else {
      editor.stopCommand('resize');
    }
  },

  /**
   * Init resizer on the element if possible
   * @param  {HTMLElement} el
   * @private
   */
  initResize(el) {
    var em = this.em;
    var editor = em ? em.get('Editor') : '';
    var config = em ? em.get('Config') : '';
    var pfx = config.stylePrefix || '';
    var attrName = `data-${pfx}handler`;
    var resizeClass = `${pfx}resizing`;
    var model = em.get('selectedComponent');
    var resizable = model.get('resizable');
    var options = {};
    var modelToStyle;

    var toggleBodyClass = (method, e, opts) => {
      const docs = opts.docs;
      docs && docs.forEach(doc => {
        const body = doc.body;
        const cls = body.className || '';
        body.className = (method == 'add' ?
          `${cls} ${resizeClass}` : cls.replace(resizeClass, '')).trim();
      });
    };

    if (editor && resizable) {
      options = {
        // Here the resizer is updated with the current element height and width
        onStart(e, opts = {}) {
          const { el, config, resizer } = opts;
          const { keyHeight, keyWidth, currentUnit } = config;
          toggleBodyClass('add', e, opts);
          modelToStyle = em.get('StyleManager').getModelToStyle(model);
          const computedStyle = getComputedStyle(el);
          const modelStyle = modelToStyle.getStyle();
          const currentWidth = modelStyle[keyWidth] || computedStyle[keyWidth];
          const currentHeight = modelStyle[keyHeight] || computedStyle[keyHeight];
          resizer.startDim.w = parseFloat(currentWidth);
          resizer.startDim.h = parseFloat(currentHeight);
          showOffsets = 0;

          if (currentUnit) {
            config.unitHeight = getUnitFromValue(currentHeight);
            config.unitWidth = getUnitFromValue(currentWidth);
          }
        },

        // Update all positioned elements (eg. component toolbar)
        onMove() {
          editor.trigger('change:canvasOffset');
        },

        onEnd(e, opts) {
          toggleBodyClass('remove', e, opts);
          editor.trigger('change:canvasOffset');
          showOffsets = 1;
        },

        updateTarget(el, rect, options = {}) {
          if (!modelToStyle) {
            return;
          }

          const { store, selectedHandler, config} = options;
          const { keyHeight, keyWidth } = config;
          const onlyHeight = ['tc', 'bc'].indexOf(selectedHandler) >= 0;
          const onlyWidth = ['cl', 'cr'].indexOf(selectedHandler) >= 0;
          const style = modelToStyle.getStyle();

          if (!onlyHeight) {
            style[keyWidth] = rect.w + config.unitWidth;
          }

          if (!onlyWidth) {
            style[keyHeight] = rect.h + config.unitHeight;
          }

          modelToStyle.setStyle(style, {avoidStore: 1});
          const updateEvent = `update:component:style`;
          em && em.trigger(`${updateEvent}:${keyHeight} ${updateEvent}:${keyWidth}`);

          if (store) {
            modelToStyle.trigger('change:style', modelToStyle, style, {});
          }
        }
      };

      if (typeof resizable == 'object') {
        options = { ...options, ...resizable };
      }

      editor.runCommand('resize', {el, options});

      // On undo/redo the resizer rect is not updating, need somehow to call
      // this.updateRect on undo/redo action
    }
  },

  /**
   * Update toolbar if the component has one
   * @param {Object} mod
   */
  updateToolbar(mod) {
    var em = this.config.em;
    var model = mod == em ? em.get('selectedComponent') : mod;
    var toolbarEl = this.canvas.getToolbarEl();
    var toolbarStyle = toolbarEl.style;

    if (!model) {
      // By putting `toolbarStyle.display = 'none'` will cause kind
      // of freezed effect with component selection (probably by iframe
      // switching)
      toolbarStyle.opacity = 0;
      return;
    }

    var toolbar = model.get('toolbar');
    var ppfx = this.ppfx;
    var showToolbar = em.get('Config').showToolbar;

    if (showToolbar && toolbar && toolbar.length) {
      toolbarStyle.opacity = '';
      toolbarStyle.display = '';
      if(!this.toolbar) {
        toolbarEl.innerHTML = '';
        this.toolbar = new Toolbar(toolbar);
        var toolbarView = new ToolbarView({
          collection: this.toolbar,
          editor: this.editor
        });
        toolbarEl.appendChild(toolbarView.render().el);
      }

      this.toolbar.reset(toolbar);
      const view = model.view;
      view && this.updateToolbarPos(view.el);
    } else {
      toolbarStyle.display = 'none';
    }
  },

  /**
   * Update toolbar positions
   * @param {HTMLElement} el
   * @param {Object} pos
   */
  updateToolbarPos(el, elPos) {
    var unit = 'px';
    var toolbarEl = this.canvas.getToolbarEl();
    var toolbarStyle = toolbarEl.style;
    toolbarStyle.display = 'block';
    var pos = this.canvas.getTargetToElementDim(toolbarEl, el, {
      elPos,
      event: 'toolbarPosUpdate',
    });
    var leftPos = pos.left + pos.elementWidth - pos.targetWidth;
    toolbarStyle.top = pos.top + unit;
    toolbarStyle.left = leftPos + unit;
    toolbarStyle.display = '';
  },

  /**
   * Return canvas dimensions and positions
   * @return {Object}
   */
  getCanvasPosition() {
    return this.canvas.getCanvasView().getPosition();
  },

  /**
   * Removes all highlighting effects on components
   * @private
   * */
  clean() {
    if(this.selEl)
      this.selEl.removeClass(this.hoverClass);
  },

  /**
   * Returns badge element
   * @return {HTMLElement}
   * @private
   */
  getBadge() {
    return this.canvas.getBadgeEl();
  },

  /**
   * On frame scroll callback
   * @private
   */
  onFrameScroll(e) {
    var el = this.cacheEl;
    if (el) {
      var elPos = this.getElementPos(el);
      this.updateBadge(el, elPos);
      var model = this.em.get('selectedComponent');

      if (model) {
        this.updateToolbarPos(model.view.el);
      }
    }
  },

  /**
   * Update attached elements, eg. component toolbar
   * @return {[type]} [description]
   */
  updateAttached(updated) {
    const model = this.em.get('selectedComponent');

    if (model && updated === model) {
      var view = model.view;
      this.updateToolbarPos(view.el);
      this.showFixedElementOffset(view.el);
    }
  },

  /**
   * Returns element's data info
   * @param {HTMLElement} el
   * @return {Object}
   * @private
   */
  getElementPos(el, badge) {
    return this.canvas.getCanvasView().getElementPos(el);
  },

  /**
   * Hide badge
   * @private
   * */
  hideBadge() {
    this.getBadge().style.display = 'none';
  },

  /**
   * Clean previous model from different states
   * @param {Component} model
   * @private
   */
  cleanPrevious(model) {
    model && model.set({
      status: '',
      state: '',
    });
  },

  /**
   * Returns content window
   * @private
   */
  getContentWindow() {
    return this.frameEl.contentWindow;
  },

  run(editor) {
    this.editor = editor && editor.get('Editor');
    this.enable();
    this.onSelect();
  },

  stop(editor, sender, opts = {}) {
    const em = this.em;
    this.stopSelectComponent();
    !opts.preserveSelected && em.setSelected(null);
    this.clean();
    this.toggleClipboard();
    this.hideBadge();
    this.hideFixedElementOffset();
    this.canvas.getToolbarEl().style.display = 'none';

    em.off('component:update', this.updateAttached, this);
    em.off('change:canvasOffset', this.updateAttached, this);
    em.off('change:selectedComponent', this.updateToolbar, this);
  }
};
<|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/>. */

// @flow

import React, { Component } from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { formatKeyShortcut } from "../../utils/text";
import actions from "../../actions";
import {
  getSources,
  getActiveSearch,
  getSelectedPrimaryPaneTab
} from "../../selectors";
import { features } from "../../utils/prefs";
import "./Sources.css";
import classnames from "classnames";

import Outline from "./Outline";
import SourcesTree from "./SourcesTree";

import type { SourcesMap } from "../../reducers/types";

type Props = {
  selectedTab: string,
  setPrimaryPaneTab: string => void,
  sources: SourcesMap,
  selectLocation: Object => void,
  horizontal: boolean,
  setActiveSearch: string => void,
  closeActiveSearch: () => void,
  sourceSearchOn: boolean
};

class PrimaryPanes extends Component<Props> {
  renderShortcut: Function;
  selectedPane: String;
  showPane: Function;
  renderTabs: Function;
  renderChildren: Function;

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

    this.renderShortcut = this.renderShortcut.bind(this);
    this.showPane = this.showPane.bind(this);
    this.renderTabs = this.renderTabs.bind(this);
  }

  showPane(selectedPane: string) {
    this.props.setPrimaryPaneTab(selectedPane);
  }

  renderOutlineTabs() {
    if (!features.outline) {
      return;
    }

    const sources = formatKeyShortcut(L10N.getStr("sources.header"));

    const outline = formatKeyShortcut(L10N.getStr("outline.header"));

    return [

 this.showPane("sources")}
        key="sources-tab"
      >
        {sources}

,

 this.showPane("outline")}
        key="outline-tab"
      >
        {outline}

    ];
  }

  renderTabs() {
    return (

{this.renderOutlineTabs()}

    );
  }

  renderShortcut() {
    if (this.props.horizontal) {
      const onClick = () => {
        if (this.props.sourceSearchOn) {
          return this.props.closeActiveSearch();
        }
        this.props.setActiveSearch("source");
      };
      return (

          {L10N.getFormatStr(
            "sources.search",
            formatKeyShortcut(L10N.getStr("sources.search.key2"))
          )}

      );
    }
  }

  renderOutline() {
    const { selectLocation } = this.props;

    const outlineComp = features.outline ? (
      <Outline selectLocation={selectLocation} />
    ) : null;

    return outlineComp;
  }

  renderSources() {
    const { sources, selectLocation } = this.props;
    return <SourcesTree sources={sources} selectLocation={selectLocation} />;
  }

  render() {
    const { selectedTab } = this.props;

    return (

        {this.renderTabs()}
        {selectedTab === "sources" ? <SourcesTree /> : <Outline />}

    );
  }
}

export default connect(
  state => ({
    selectedTab: getSelectedPrimaryPaneTab(state),
    sources: getSources(state),
    sourceSearchOn: getActiveSearch(state) === "source"
  }),
  dispatch => bindActionCreators(actions, dispatch)
)(PrimaryPanes);
<commit_msg>Remove functions we no longer use (#5392)<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/>. */

// @flow

import React, { Component } from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { formatKeyShortcut } from "../../utils/text";
import actions from "../../actions";
import {
  getSources,
  getActiveSearch,
  getSelectedPrimaryPaneTab
} from "../../selectors";
import { features } from "../../utils/prefs";
import "./Sources.css";
import classnames from "classnames";

import Outline from "./Outline";
import SourcesTree from "./SourcesTree";

import type { SourcesMap } from "../../reducers/types";

type Props = {
  selectedTab: string,
  setPrimaryPaneTab: string => void,
  sources: SourcesMap,
  selectLocation: Object => void,
  horizontal: boolean,
  setActiveSearch: string => void,
  closeActiveSearch: () => void,
  sourceSearchOn: boolean
};

class PrimaryPanes extends Component<Props> {
  renderShortcut: Function;
  selectedPane: String;
  showPane: Function;
  renderTabs: Function;
  renderChildren: Function;

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

    this.renderShortcut = this.renderShortcut.bind(this);
    this.showPane = this.showPane.bind(this);
    this.renderTabs = this.renderTabs.bind(this);
  }

  showPane(selectedPane: string) {
    this.props.setPrimaryPaneTab(selectedPane);
  }

  renderOutlineTabs() {
    if (!features.outline) {
      return;
    }

    const sources = formatKeyShortcut(L10N.getStr("sources.header"));

    const outline = formatKeyShortcut(L10N.getStr("outline.header"));

    return [

 this.showPane("sources")}
        key="sources-tab"
      >
        {sources}

,

 this.showPane("outline")}
        key="outline-tab"
      >
        {outline}

    ];
  }

  renderTabs() {
    return (

{this.renderOutlineTabs()}

    );
  }

  renderShortcut() {
    if (this.props.horizontal) {
      const onClick = () => {
        if (this.props.sourceSearchOn) {
          return this.props.closeActiveSearch();
        }
        this.props.setActiveSearch("source");
      };
      return (

          {L10N.getFormatStr(
            "sources.search",
            formatKeyShortcut(L10N.getStr("sources.search.key2"))
          )}

      );
    }
  }

  render() {
    const { selectedTab } = this.props;

    return (

        {this.renderTabs()}
        {selectedTab === "sources" ? <SourcesTree /> : <Outline />}

    );
  }
}

export default connect(
  state => ({
    selectedTab: getSelectedPrimaryPaneTab(state),
    sources: getSources(state),
    sourceSearchOn: getActiveSearch(state) === "source"
  }),
  dispatch => bindActionCreators(actions, dispatch)
)(PrimaryPanes);
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import ProgressBar from './../../progressbar/progressbar';

var Seek = React.createClass({

    propTypes: {
        copyKeys: React.PropTypes.object,
        seek: React.PropTypes.func,
        percentageBuffered: React.PropTypes.number,
        percentagePlayed: React.PropTypes.number,
        duration: React.PropTypes.number
    },

    getInitialState() {
        return {
            // When the child range input becomes focused,
            // we need to set this custom seek bar to look
            // 'focused' with the correct styles. Need to
            // do this via a class.
            focused: false
        };
    },

    /**
     * Calculates the seek time based on change of input.
     * @param  {object} e Event object
     * @return {undefined}
     */
    seek(e) {
        this.props.seek(e.target.value * this.props.duration / 100);
    },

    onFocus() {
        this.setState({
            focused: true
        });
    },

    onBlur() {
        this.setState({
            focused: false
        });
    },

    render() {
        return (

                    <ProgressBar
                        step={5}
                        onBlur={this.onBlur}
                        onFocus={this.onFocus}
                        onChange={this.seek}
                        progress={this.props.percentagePlayed} />

        );
    }
});

export default Seek;
<commit_msg>Allow more granular video seeking<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import ProgressBar from './../../progressbar/progressbar';

var Seek = React.createClass({

    propTypes: {
        copyKeys: React.PropTypes.object,
        seek: React.PropTypes.func,
        percentageBuffered: React.PropTypes.number,
        percentagePlayed: React.PropTypes.number,
        duration: React.PropTypes.number
    },

    getInitialState() {
        return {
            // When the child range input becomes focused,
            // we need to set this custom seek bar to look
            // 'focused' with the correct styles. Need to
            // do this via a class.
            focused: false
        };
    },

    /**
     * Calculates the seek time based on change of input.
     * @param  {object} e Event object
     * @return {undefined}
     */
    seek(e) {
        this.props.seek(e.target.value * this.props.duration / 100);
    },

    onFocus() {
        this.setState({
            focused: true
        });
    },

    onBlur() {
        this.setState({
            focused: false
        });
    },

    render() {
        return (

                    <ProgressBar
                        onBlur={this.onBlur}
                        onFocus={this.onFocus}
                        onChange={this.seek}
                        progress={this.props.percentagePlayed} />

        );
    }
});

export default Seek;
<|endoftext|>


<commit_before>import {connect} from 'react-redux';
import React, {PropTypes} from 'react';
import {merge} from 'ramda';
import {redo, undo} from '../../actions/index.js';
import Radium from 'radium';

function UnconnectedToolbar(props) {
    const {dispatch, history} = props;
    const styles={
        parentSpanStyle: {
            display: 'inline-block'
        },
        iconStyle: {
            fontSize: 20
        },
        labelStyle: {
            fontSize: 15
        }
    }

    const undoLink = (
         dispatch(undo())}
        >

                {'↺'}

                undo

    );

    const redoLink = (
         dispatch(redo())}
        >

                {'↻'}

                redo

    );

    return (

                {history.past.length > 0 ? undoLink : null}
                {history.future.length > 0 ? redoLink : null}

    );
}

UnconnectedToolbar.propTypes = {
    history: PropTypes.object,
    dispatch: PropTypes.function
};

const Toolbar = connect(
    state => ({
        history: state.history
    }),
    dispatch => ({dispatch})
)(Radium(UnconnectedToolbar));

export default Toolbar;
<commit_msg>apply hover styles on a single icon, not both<commit_after>import {connect} from 'react-redux';
import React, {PropTypes} from 'react';
import {merge} from 'ramda';
import {redo, undo} from '../../actions/index.js';
import Radium from 'radium';

function UnconnectedToolbar(props) {
    const {dispatch, history} = props;
    const styles={
        parentSpanStyle: {
            display: 'inline-block',
            'opacity': '0.2',
            ':hover': {
                'opacity': 1
            }
        },
        iconStyle: {
            fontSize: 20
        },
        labelStyle: {
            fontSize: 15
        }
    }

    const undoLink = (
         dispatch(undo())}
        >

                {'↺'}

                undo

    );

    const redoLink = (
         dispatch(redo())}
        >

                {'↻'}

                redo

    );

    return (

                {history.past.length > 0 ? undoLink : null}
                {history.future.length > 0 ? redoLink : null}

    );
}

UnconnectedToolbar.propTypes = {
    history: PropTypes.object,
    dispatch: PropTypes.function
};

const Toolbar = connect(
    state => ({
        history: state.history
    }),
    dispatch => ({dispatch})
)(Radium(UnconnectedToolbar));

export default Toolbar;
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as courseActions from '../../actions/courseActions';

class CoursesPage extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.state = {
      course: { title: '' }
    };

    // For performance, bind in the constructor. Binding in the render
    // function will create a new instance of the function, and impacts
    // performance.
    this.onTitleChange = this.onTitleChange.bind(this);
    this.onClickSave = this.onClickSave.bind(this);
  }

  onTitleChange(event) {
    const course = this.state.course;
    course.title = event.target.value;
    this.setState({ course: course });
  }

  onClickSave() {
    this.props.actions.createCourse(this.state.course);

    this.setState({
      course: { title: '' }
    });
  }

  courseRow(course, index) {
    return
{course.title}
;
  }

  render() {
    return (

Courses

        {this.props.courses.map(this.courseRow)}

Add Course

        <input
          type="text"
          onChange={this.onTitleChange}
          value={this.state.course.title}/>

        <input
          type="submit"
          value="Save"
          onClick={this.onClickSave}/>

    );
  }
}

function mapStateToProps(state, ownProps) {
  return {
    courses: state.courses
  };
}

function matchDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(courseActions, dispatch)
  };
}

CoursesPage.propTypes = {
  actions: PropTypes.object.isRequired,
  courses: PropTypes.array.isRequired
};

export default connect(mapStateToProps, matchDispatchToProps)(CoursesPage);
<commit_msg>Remove course management concerns from course page<commit_after>import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as courseActions from '../../actions/courseActions';

class CoursesPage extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  courseRow(course, index) {
    return
{course.title}
;
  }

  render() {
    return (

Courses

        {this.props.courses.map(this.courseRow)}

    );
  }
}

function mapStateToProps(state, ownProps) {
  return {
    courses: state.courses
  };
}

function matchDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(courseActions, dispatch)
  };
}

CoursesPage.propTypes = {
  actions: PropTypes.object.isRequired,
  courses: PropTypes.array.isRequired
};

export default connect(mapStateToProps, matchDispatchToProps)(CoursesPage);
<|endoftext|>


<commit_before>import React from 'react';
import { Navbar } from '../../components/navbar';
import { fetchDevice, sendCommand, changeStepSize } from '../../actions/bot_actions';
import { ToggleButton } from './toggle_button';
import { store } from '../../store';
import { DirectionButton } from './direction_button';

export class StepSizeSelector extends React.Component {
  cssForIndex(num) {
    var choices = this.props.choices;
    var css = "move-amount no-radius ";
    if(num === _.first(choices)) { css += "leftmost " }
    if(num === _.last(choices)) { css += "rightmost " }
    if(num === this.props.selected ) { css += "move-amount-selected " }
    return css;
  }

  render() {
    return (

              {
                this.props.choices.map(
                  (item, inx) => <button
                              className={ this.cssForIndex(item) }
                              onClick={ () => this.props.selector(item) }
                              key={ inx } >{ item }</button>
                )
              }

)
  }
}

export class Controls extends React.Component {
render() {
  var bot = store.getState().bot || {};

  return (

        <Navbar/>

                        <button
                          className="red button-like widget-control"
                          type="button"
                          onClick={ () => this.props.dispatch(sendCommand({name: "emergencyStop" })) } >

                          E-STOP

                        </button>

Move

                          MOVE AMOUNT (mm) Busy

                              <StepSizeSelector
                                choices={ [1,10,100,1000,10000] }
                                selector={ (num) => this.props.dispatch(changeStepSize(num)) }
                                selected={ bot.stepSize } />

                                    <DirectionButton axis="y" direction="up" steps={ bot.stepSize || 1000 } {...this.props} />

                                    <DirectionButton axis="z" direction="up" steps={ bot.stepSize || 1000 } {...this.props} />

                                    <button
                                     className="button-like i fa fa-home arrow-button"
                                     onClick={ () => this.props.dispatch(sendCommand({name: "homeAll", speed: (bot.hardware.s || 100) })) } />

                                    <DirectionButton axis="x" direction="left" steps={ bot.stepSize || 1000 } {...this.props} />

                                    <DirectionButton axis="y" direction="down" steps={ bot.stepSize || 1000 } {...this.props} />

                                    <DirectionButton axis="x" direction="right" steps={ bot.stepSize || 1000 } {...this.props} />

                                    <DirectionButton axis="z" direction="down" steps={ bot.stepSize || 1000 } {...this.props} />

                              GANTRY (X)

                              <input className="move-input" type="text" value={ bot.hardware.x } />

                              CROSS-SLIDE (Y)

                              <input className="move-input" type="text" value={ bot.hardware.y } />

                              Z-AXIS (Z)

                              <input className="move-input" type="text" value={ bot.hardware.z } />

                              <button className="full-width green button-like" ng_click="manualMovement()">GO</button>

Tool Control

                              VACUUM PUMP

                              <ToggleButton toggleval={ bot.hardware.pin0 } />

                              WATER VALVE

                              <ToggleButton toggleval={ bot.hardware.pin12 } />

                              LED

                              <ToggleButton toggleval={ bot.hardware.pin13 } />

Camera

  );
}
};
<commit_msg>[STABLE] JSX Tidy<commit_after>import React from 'react';
import { Navbar } from '../../components/navbar';
import { fetchDevice, sendCommand, changeStepSize } from '../../actions/bot_actions';
import { ToggleButton } from './toggle_button';
import { store } from '../../store';
import { DirectionButton } from './direction_button';
export class StepSizeSelector extends React.Component {
  cssForIndex(num) {
    var choices = this.props.choices;
    var css = "move-amount no-radius ";
    if(num === _.first(choices)) {
      css += "leftmost "
    }
    if(num === _.last(choices)) {
      css += "rightmost "
    }
    if(num === this.props.selected) {
      css += "move-amount-selected "
    }
    return css;
  }

  render() {
    return(

              {
                this.props.choices.map(
                  (item, inx) => <button
                              className={ this.cssForIndex(item) }
                              onClick={ () => this.props.selector(item) }
                              key={ inx } >{ item }</button>
                )
              }

)
  }
}

export class Controls extends React.Component {

  render() {
    var bot = store.getState()
      .bot || {};

    return(

        <Navbar/>

                        <button
                          className="red button-like widget-control"
                          type="button"
                          onClick={ () => this.props.dispatch(sendCommand({name: "emergencyStop" })) } >

                          E-STOP

                        </button>

Move

                          MOVE AMOUNT (mm) Busy

                              <StepSizeSelector
                                choices={ [1,10,100,1000,10000] }
                                selector={ (num) => this.props.dispatch(changeStepSize(num)) }
                                selected={ bot.stepSize } />

                                    <DirectionButton axis="y" direction="up" steps={ bot.stepSize || 1000 } {...this.props} />

                                    <DirectionButton axis="z" direction="up" steps={ bot.stepSize || 1000 } {...this.props} />

                                    <button
                                     className="button-like i fa fa-home arrow-button"
                                     onClick={ () => this.props.dispatch(sendCommand({name: "homeAll", speed: (bot.hardware.s || 100) })) } />

                                    <DirectionButton axis="x" direction="left" steps={ bot.stepSize || 1000 } {...this.props} />

                                    <DirectionButton axis="y" direction="down" steps={ bot.stepSize || 1000 } {...this.props} />

                                    <DirectionButton axis="x" direction="right" steps={ bot.stepSize || 1000 } {...this.props} />

                                    <DirectionButton axis="z" direction="down" steps={ bot.stepSize || 1000 } {...this.props} />

                              GANTRY (X)

                              <input className="move-input" type="text" value={ bot.hardware.x } />

                              CROSS-SLIDE (Y)

                              <input className="move-input" type="text" value={ bot.hardware.y } />

                              Z-AXIS (Z)

                              <input className="move-input" type="text" value={ bot.hardware.z } />

                              <button className="full-width green button-like" ng_click="manualMovement()">GO</button>

Tool Control

                              VACUUM PUMP

                              <ToggleButton toggleval={ bot.hardware.pin0 } />

                              WATER VALVE

                              <ToggleButton toggleval={ bot.hardware.pin12 } />

                              LED

                              <ToggleButton toggleval={ bot.hardware.pin13 } />

Camera

    );
  }
};
<|endoftext|>


<commit_before>import React from 'react';
import {findDOMNode} from 'react-dom';
import cn from 'classnames';
import dates from './utils/dates';
import localizer from './localizer';
import chunk from 'lodash/chunk';
import collection from 'lodash/collection';

import {navigate} from './utils/constants';
import {notify} from './utils/helpers';
import getHeight from 'dom-helpers/query/height';
import getPosition from 'dom-helpers/query/position';
import raf from 'dom-helpers/util/requestAnimationFrame';
import {parse, isDateBetween, isDatesSame, isDateBefore} from '../../util/date-utils';

import EventRow from './EventRow';
import EventEndingRow from './EventEndingRow';
import Popup from './Popup';
import Overlay from 'react-overlays/lib/Overlay';
import BackgroundCells from './BackgroundCells';

import {dateFormat} from './utils/propTypes';
import {
    segStyle, inRange, eventSegments
    , endOfRange, eventLevels, sortEvents
} from './utils/eventLevels';

let eventsForWeek = (evts, start, end, props) =>
    evts.filter((e) => inRange(e, start, end, props));

let isSegmentInSlot = (seg, slot) => seg.left <= slot && seg.right >= slot;

const PROPERTY_TYPES = {
    culture: React.PropTypes.string,

    date: React.PropTypes.instanceOf(Date),

    min: React.PropTypes.instanceOf(Date),
    max: React.PropTypes.instanceOf(Date),

    dateFormat,

    weekdayFormat: dateFormat,

    popup: React.PropTypes.bool,

    popupOffset: React.PropTypes.oneOfType([
        React.PropTypes.number,
        React.PropTypes.shape({
            x: React.PropTypes.number,
            y: React.PropTypes.number
        })
    ]),

    onSelectEvent: React.PropTypes.func,
    onSelectSlot: React.PropTypes.func
};

class MonthView extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            rowLimit: 5,
            needLimitMeasure: true,
            startDate: null,
            endDate: null,
            dropSelection: false
        };

        this.renderWeek = this.renderWeek.bind(this);
        this.renderBackground = this.renderBackground.bind(this);
        this.renderRowLevel = this.renderRowLevel.bind(this);
        this.renderShowMore = this.renderShowMore.bind(this);
        this._dates = this._dates.bind(this);
        this._headers = this._headers.bind(this);
        this._renderMeasureRows = this._renderMeasureRows.bind(this);
        this._renderOverlay = this._renderOverlay.bind(this);
        this._measureRowLimit = this._measureRowLimit.bind(this);
        this._dateClick = this._dateClick.bind(this);
        this._selectEvent = this._selectEvent.bind(this);
        this._selectDates = this._selectDates.bind(this);
        this._showMore = this._showMore.bind(this);
        this.clearSelection = this.clearSelection.bind(this);
    }

    componentWillMount() {
        this._bgRows = [];
        this._pendingSelection = [];
    }

    componentWillReceiveProps({date}) {
        this.setState({
            needLimitMeasure: !dates.eq(date, this.props.date)
        });
    }

    componentDidMount() {
        let running;

        if (this.state.needLimitMeasure)
            this._measureRowLimit(this.props);

        window.addEventListener('click', this._dropSelection = (e) => {
            if (!e.target.closest('.rbc-calendar')) {
                this._reset();
            }
        });

        window.addEventListener('resize', this._resizeListener = () => {
            if (!running) {
                raf(() => {
                    running = false;
                    this.setState({needLimitMeasure: true}); //eslint-disable-line
                });
            }
        }, false);
    }

    componentDidUpdate() {
        if (this.state.needLimitMeasure)
            this._measureRowLimit(this.props);
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this._resizeListener, false);
        window.removeEventListener('resize', this._dropSelection, false);
    }

    renderWeek(week, weekIdx, content) {
        let {first, last} = endOfRange(week);
        let evts = eventsForWeek(this.props.events, week[0], week[week.length - 1], this.props);
        let nonWorkingDays = this.props.nonWorkingDays;
        evts.sort((a, b) => sortEvents(a, b, this.props));

        let segments = evts = evts.map((evt) => eventSegments(evt, first, last, this.props));
        let limit = (this.state.rowLimit - 1) || 1;

        let {levels, extra} = eventLevels(segments, limit);

        content = content || ((lvls, wk) => lvls.map((lvl, idx) => this.renderRowLevel(lvl, wk, idx)));

        return (

 this._firstRow = r)}
            >
                {
                    this.renderBackground(week, weekIdx)
                }

 this._firstDateRow = r)}
                    >
                        { this._dates(week, nonWorkingDays) }

                    {
                        content(levels, week, weekIdx)
                    }
                    {
                        !!extra.length &&
                        this.renderShowMore(segments, extra, week, weekIdx, levels.length)
                    }

                { this.props.popup
                && this._renderOverlay()
                }

        );
    }

    _setDate(date) {
        if (this._getStartDate()) {
            this.setState({
                endDate: date
            });
        } else {
            this.setState({
                startDate: date
            });
        }

        if (this._isStartEndDateSelected()) {
            let start = this._getStartDate();
            let end = this._getEndDate();

            if (!isDateBefore(start, end)) {
                let temp = end;
                end = start;
                start = temp;
            }

            notify(this.props.onRangeSelected, {
                start,
                end
            });

            this._reset();
        }
    }

    _getStartDate() {
        return this.state.startDate && parse(this.state.startDate);
    }

    _isStartEndDateSelected() {
        return this._getStartDate() && this._getEndDate();
    }

    _getEndDate() {
        return this.state.endDate && parse(this.state.endDate);
    }

    _getHoveredDate() {
        return this.state.hoveredDate && parse(this.state.hoveredDate);
    }

    _reset() {
        this.setState({
            endDate: null,
            startDate: null
        });
    }

    renderBackground(row, idx) {
        let self = this;

        function getCurrentDate(index) {
            let date = row.slice(index, index + 1)[0];
            return parse(date);
        }

        function onSelectSlot({start, end}) {
            let selectedDate = self._pendingSelection.concat(row.slice(start, end + 1));
            self._setDate(selectedDate[0]);

            self._pendingSelection = selectedDate;

            clearTimeout(self._selectTimer);
            self._selectTimer = setTimeout(() => self._selectDates());

        }

        function isEndDate(dateCell) {
            let date = getCurrentDate(dateCell);

            return isDatesSame(date, self._hoveredDate) && self._getStartDate();
        }

        function isHovered(dateCell) {
            let date = getCurrentDate(dateCell);

            return isDatesSame(date, self._hoveredDate) && !self._getStartDate();
        }

        function onHoverSlot(dateCell) {
            let date = getCurrentDate(dateCell);

            if (isDatesSame(date, self._hoveredDate)) {
                return;
            }

            self._hoveredDate = date;
            self.setState({
                hoveredDate: date
            });
        }

        function highlight(cell) {
            let date = getCurrentDate(cell);
            let startDate = self._getStartDate();
            let hoveredDate = self._getHoveredDate();

            return hoveredDate && startDate && date
                && isDateBetween(startDate, hoveredDate, date, '()')
                || isDateBetween(hoveredDate, startDate, date, '()');

        }

        function isSelected(cell) {
            let startDate = self._getStartDate();
            let date = getCurrentDate(cell);

            return isDatesSame(date, startDate);
        }

        return (
            <BackgroundCells
                highlight={highlight}
                isSelected={isSelected}
                isHovered={isHovered}
                isEndDate={isEndDate}
                onHover={onHoverSlot}
                rowIndex
                index={idx}
                container={() => findDOMNode(this)}
                draggableSelect={this.props.draggableSelect}
                selectable={this.props.selectable}
                slots={7}
                ref={(r) => this._bgRows[idx] = r}
                onSelectSlot={onSelectSlot}
            />
        );
    }

    renderRowLevel(segments, week, idx) {
        let {first, last} = endOfRange(week);

        return (
            <EventRow
                {...this.props}
                eventComponent={this.props.components.event}
                onSelect={this._selectEvent}
                key={idx}
                segments={segments}
                start={first}
                end={last}
            />
        );
    }

    renderShowMore(segments, extraSegments, week, weekIdx) {
        let {first, last} = endOfRange(week);

        let onClick = (slot) => this._showMore(segments, week[slot - 1], weekIdx, slot);

        return (
            <EventEndingRow
                {...this.props}
                eventComponent={this.props.components.event}
                onSelect={this._selectEvent}
                onShowMore={onClick}
                key={'last_row_' + weekIdx}
                segments={extraSegments}
                start={first}
                end={last}
            />
        );
    }

    _dates(row, nonWorkingDays) {
        return row.map((day, colIdx) => {
            let offRange = dates.month(day) !== dates.month(this.props.date);
            let isNonWorkDay = collection.find(nonWorkingDays, function same(nonWorkDay) {
                return dates.isSameDate(nonWorkDay, day);
            });
            return (
                <div
                    key={'header_' + colIdx}
                    style={segStyle(1, 7)}
                    className={cn('rbc-date-cell', {
                        'rbc-off-range': offRange && !isNonWorkDay,
                        'rbc-off-range-non-work': offRange && isNonWorkDay,
                        'rbc-off-non-work': !offRange && isNonWorkDay,
                        'rbc-now': dates.eq(day, new Date(), 'day'),
                        'rbc-current': dates.eq(day, this.props.date, 'day')
                    })}
                >

                        { localizer.format(day, this.props.dateFormat, this.props.culture) }

            );
        });
    }

    _headers(row, format, culture) {
        let first = row[0];
        let last = row[row.length - 1];

        return dates.range(first, last, 'day').map((day, idx) =>

                { localizer.format(day, format, culture) }

        );
    }

    _renderMeasureRows(levels, row, idx) {
        let first = idx === 0;

        return first ? (

 this._measureEvent = r} className={cn('rbc-event')}>

        ) : ;
    }

    _renderOverlay() {
        let overlay = (this.state && this.state.overlay) || {};

        return (
            <Overlay
                rootClose
                placement="bottom"
                container={this}
                show={!!overlay.position}
                onHide={() => this.setState({overlay: null})}
            >
                <Popup
                    {...this.props}
                    position={overlay.position}
                    events={overlay.events}
                    slotStart={overlay.date}
                    slotEnd={overlay.end}
                    onSelect={this._selectEvent}
                />
            </Overlay>
        );
    }

    _measureRowLimit() {
        let eventHeight = getHeight(this._measureEvent);
        let labelHeight = getHeight(this._firstDateRow);
        let eventSpace = getHeight(this._firstRow) - labelHeight;

        this._needLimitMeasure = false;

        this.setState({
            needLimitMeasure: false,
            rowLimit: Math.max(
                Math.floor(eventSpace / eventHeight), 1)
        });
    }

    _dateClick(date, e) {
        e.preventDefault();
        this.clearSelection();
        notify(this.props.onNavigate, [navigate.DATE, date]);
    }

    _selectEvent(...args) {
        //cancel any pending selections so only the event click goes through.
        this.clearSelection();

        notify(this.props.onSelectEvent, args);
    }

    _selectDates(e) {
        let slots = this._pendingSelection.slice();

        this._pendingSelection = [];

        slots.sort((a, b) => +a - +b);

        notify(this.props.onSelectSlot, {
            slots,
            start: slots[0],
            end: slots[slots.length - 1]
        });
    }

    _showMore(segments, date, weekIdx, slot) {
        let cell = findDOMNode(this._bgRows[weekIdx]).children[slot - 1];

        let events = segments
            .filter((seg) => isSegmentInSlot(seg, slot))
            .map((seg) => seg.event);

        //cancel any pending selections so only the event click goes through.
        this.clearSelection();

        if (this.props.popup) {
            let position = getPosition(cell, findDOMNode(this));

            this.setState({
                overlay: {date, events, position}
            });
        }
        else {
            notify(this.props.onNavigate, [navigate.DATE, date]);
        }

        notify(this.props.onShowMore, [events, date, slot]);
    }

    clearSelection() {
        clearTimeout(this._selectTimer);
        this._pendingSelection = [];
    }

    render() {
        let {date, culture, weekdayFormat} = this.props
            , month = dates.visibleDays(date, culture)
            , weeks = chunk(month, 7);

        let measure = this.state.needLimitMeasure;

        this._weekCount = weeks.length;

        return (

                    {this._headers(weeks[0], weekdayFormat, culture)}

                { weeks.map((week, idx) =>
                    this.renderWeek(week, idx, measure && this._renderMeasureRows))
                }

        );
    }

}

MonthView.navigate = (date, action) => {
    switch (action) {
        case navigate.PREVIOUS:
            return dates.add(date, -1, 'month');

        case navigate.NEXT:
            return dates.add(date, 1, 'month');

        default:
            return date;
    }
};

MonthView.range = (date, {culture}) => {
    let start = dates.firstVisibleDay(date, culture);
    let end = dates.lastVisibleDay(date, culture);
    return {start, end};
};

MonthView.propTypes = PROPERTY_TYPES;

export default MonthView;
<commit_msg>minor fix<commit_after>import React from 'react';
import {findDOMNode} from 'react-dom';
import cn from 'classnames';
import dates from './utils/dates';
import localizer from './localizer';
import chunk from 'lodash/chunk';
import collection from 'lodash/collection';

import {navigate} from './utils/constants';
import {notify} from './utils/helpers';
import getHeight from 'dom-helpers/query/height';
import getPosition from 'dom-helpers/query/position';
import raf from 'dom-helpers/util/requestAnimationFrame';
import {parse, isDateBetween, isDatesSame, isDateBefore} from '../../util/date-utils';

import EventRow from './EventRow';
import EventEndingRow from './EventEndingRow';
import Popup from './Popup';
import Overlay from 'react-overlays/lib/Overlay';
import BackgroundCells from './BackgroundCells';

import {dateFormat} from './utils/propTypes';
import {
    segStyle, inRange, eventSegments
    , endOfRange, eventLevels, sortEvents
} from './utils/eventLevels';

let eventsForWeek = (evts, start, end, props) =>
    evts.filter((e) => inRange(e, start, end, props));

let isSegmentInSlot = (seg, slot) => seg.left <= slot && seg.right >= slot;

const PROPERTY_TYPES = {
    culture: React.PropTypes.string,

    date: React.PropTypes.instanceOf(Date),

    min: React.PropTypes.instanceOf(Date),
    max: React.PropTypes.instanceOf(Date),

    dateFormat,

    weekdayFormat: dateFormat,

    popup: React.PropTypes.bool,

    popupOffset: React.PropTypes.oneOfType([
        React.PropTypes.number,
        React.PropTypes.shape({
            x: React.PropTypes.number,
            y: React.PropTypes.number
        })
    ]),

    onSelectEvent: React.PropTypes.func,
    onSelectSlot: React.PropTypes.func
};

class MonthView extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            rowLimit: 5,
            needLimitMeasure: true,
            startDate: null,
            endDate: null,
            dropSelection: false
        };

        this.renderWeek = this.renderWeek.bind(this);
        this.renderBackground = this.renderBackground.bind(this);
        this.renderRowLevel = this.renderRowLevel.bind(this);
        this.renderShowMore = this.renderShowMore.bind(this);
        this._dates = this._dates.bind(this);
        this._headers = this._headers.bind(this);
        this._renderMeasureRows = this._renderMeasureRows.bind(this);
        this._renderOverlay = this._renderOverlay.bind(this);
        this._measureRowLimit = this._measureRowLimit.bind(this);
        this._dateClick = this._dateClick.bind(this);
        this._selectEvent = this._selectEvent.bind(this);
        this._selectDates = this._selectDates.bind(this);
        this._showMore = this._showMore.bind(this);
        this.clearSelection = this.clearSelection.bind(this);
    }

    componentWillMount() {
        this._bgRows = [];
        this._pendingSelection = [];
    }

    componentWillReceiveProps({date}) {
        this.setState({
            needLimitMeasure: !dates.eq(date, this.props.date)
        });
    }

    componentDidMount() {
        let running;

        if (this.state.needLimitMeasure)
            this._measureRowLimit(this.props);

        window.addEventListener('click', this._dropSelection = (e) => {
            if (!e.target.closest('.rbc-calendar')) {
                this._reset();
            }
        });

        window.addEventListener('resize', this._resizeListener = () => {
            if (!running) {
                raf(() => {
                    running = false;
                    this.setState({needLimitMeasure: true}); //eslint-disable-line
                });
            }
        }, false);
    }

    componentDidUpdate() {
        if (this.state.needLimitMeasure)
            this._measureRowLimit(this.props);
    }

    componentWillUnmount() {
        window.removeEventListener('resize', this._resizeListener, false);
        window.removeEventListener('click', this._dropSelection, false);
    }

    renderWeek(week, weekIdx, content) {
        let {first, last} = endOfRange(week);
        let evts = eventsForWeek(this.props.events, week[0], week[week.length - 1], this.props);
        let nonWorkingDays = this.props.nonWorkingDays;
        evts.sort((a, b) => sortEvents(a, b, this.props));

        let segments = evts = evts.map((evt) => eventSegments(evt, first, last, this.props));
        let limit = (this.state.rowLimit - 1) || 1;

        let {levels, extra} = eventLevels(segments, limit);

        content = content || ((lvls, wk) => lvls.map((lvl, idx) => this.renderRowLevel(lvl, wk, idx)));

        return (

 this._firstRow = r)}
            >
                {
                    this.renderBackground(week, weekIdx)
                }

 this._firstDateRow = r)}
                    >
                        { this._dates(week, nonWorkingDays) }

                    {
                        content(levels, week, weekIdx)
                    }
                    {
                        !!extra.length &&
                        this.renderShowMore(segments, extra, week, weekIdx, levels.length)
                    }

                { this.props.popup
                && this._renderOverlay()
                }

        );
    }

    _setDate(date) {
        if (this._getStartDate()) {
            this.setState({
                endDate: date
            });
        } else {
            this.setState({
                startDate: date
            });
        }

        if (this._isStartEndDateSelected()) {
            let start = this._getStartDate();
            let end = this._getEndDate();

            if (!isDateBefore(start, end)) {
                let temp = end;
                end = start;
                start = temp;
            }

            notify(this.props.onRangeSelected, {
                start,
                end
            });

            this._reset();
        }
    }

    _getStartDate() {
        return this.state.startDate && parse(this.state.startDate);
    }

    _isStartEndDateSelected() {
        return this._getStartDate() && this._getEndDate();
    }

    _getEndDate() {
        return this.state.endDate && parse(this.state.endDate);
    }

    _getHoveredDate() {
        return this.state.hoveredDate && parse(this.state.hoveredDate);
    }

    _reset() {
        this.setState({
            endDate: null,
            startDate: null
        });
    }

    renderBackground(row, idx) {
        let self = this;

        function getCurrentDate(index) {
            let date = row.slice(index, index + 1)[0];
            return parse(date);
        }

        function onSelectSlot({start, end}) {
            let selectedDate = self._pendingSelection.concat(row.slice(start, end + 1));
            self._setDate(selectedDate[0]);

            self._pendingSelection = selectedDate;

            clearTimeout(self._selectTimer);
            self._selectTimer = setTimeout(() => self._selectDates());

        }

        function isEndDate(dateCell) {
            let date = getCurrentDate(dateCell);

            return isDatesSame(date, self._hoveredDate) && self._getStartDate();
        }

        function isHovered(dateCell) {
            let date = getCurrentDate(dateCell);

            return isDatesSame(date, self._hoveredDate) && !self._getStartDate();
        }

        function onHoverSlot(dateCell) {
            let date = getCurrentDate(dateCell);

            if (isDatesSame(date, self._hoveredDate)) {
                return;
            }

            self._hoveredDate = date;
            self.setState({
                hoveredDate: date
            });
        }

        function highlight(cell) {
            let date = getCurrentDate(cell);
            let startDate = self._getStartDate();
            let hoveredDate = self._getHoveredDate();

            return hoveredDate && startDate && date
                && isDateBetween(startDate, hoveredDate, date, '()')
                || isDateBetween(hoveredDate, startDate, date, '()');

        }

        function isSelected(cell) {
            let startDate = self._getStartDate();
            let date = getCurrentDate(cell);

            return isDatesSame(date, startDate);
        }

        return (
            <BackgroundCells
                highlight={highlight}
                isSelected={isSelected}
                isHovered={isHovered}
                isEndDate={isEndDate}
                onHover={onHoverSlot}
                rowIndex
                index={idx}
                container={() => findDOMNode(this)}
                draggableSelect={this.props.draggableSelect}
                selectable={this.props.selectable}
                slots={7}
                ref={(r) => this._bgRows[idx] = r}
                onSelectSlot={onSelectSlot}
            />
        );
    }

    renderRowLevel(segments, week, idx) {
        let {first, last} = endOfRange(week);

        return (
            <EventRow
                {...this.props}
                eventComponent={this.props.components.event}
                onSelect={this._selectEvent}
                key={idx}
                segments={segments}
                start={first}
                end={last}
            />
        );
    }

    renderShowMore(segments, extraSegments, week, weekIdx) {
        let {first, last} = endOfRange(week);

        let onClick = (slot) => this._showMore(segments, week[slot - 1], weekIdx, slot);

        return (
            <EventEndingRow
                {...this.props}
                eventComponent={this.props.components.event}
                onSelect={this._selectEvent}
                onShowMore={onClick}
                key={'last_row_' + weekIdx}
                segments={extraSegments}
                start={first}
                end={last}
            />
        );
    }

    _dates(row, nonWorkingDays) {
        return row.map((day, colIdx) => {
            let offRange = dates.month(day) !== dates.month(this.props.date);
            let isNonWorkDay = collection.find(nonWorkingDays, function same(nonWorkDay) {
                return dates.isSameDate(nonWorkDay, day);
            });
            return (
                <div
                    key={'header_' + colIdx}
                    style={segStyle(1, 7)}
                    className={cn('rbc-date-cell', {
                        'rbc-off-range': offRange && !isNonWorkDay,
                        'rbc-off-range-non-work': offRange && isNonWorkDay,
                        'rbc-off-non-work': !offRange && isNonWorkDay,
                        'rbc-now': dates.eq(day, new Date(), 'day'),
                        'rbc-current': dates.eq(day, this.props.date, 'day')
                    })}
                >

                        { localizer.format(day, this.props.dateFormat, this.props.culture) }

            );
        });
    }

    _headers(row, format, culture) {
        let first = row[0];
        let last = row[row.length - 1];

        return dates.range(first, last, 'day').map((day, idx) =>

                { localizer.format(day, format, culture) }

        );
    }

    _renderMeasureRows(levels, row, idx) {
        let first = idx === 0;

        return first ? (

 this._measureEvent = r} className={cn('rbc-event')}>

        ) : ;
    }

    _renderOverlay() {
        let overlay = (this.state && this.state.overlay) || {};

        return (
            <Overlay
                rootClose
                placement="bottom"
                container={this}
                show={!!overlay.position}
                onHide={() => this.setState({overlay: null})}
            >
                <Popup
                    {...this.props}
                    position={overlay.position}
                    events={overlay.events}
                    slotStart={overlay.date}
                    slotEnd={overlay.end}
                    onSelect={this._selectEvent}
                />
            </Overlay>
        );
    }

    _measureRowLimit() {
        let eventHeight = getHeight(this._measureEvent);
        let labelHeight = getHeight(this._firstDateRow);
        let eventSpace = getHeight(this._firstRow) - labelHeight;

        this._needLimitMeasure = false;

        this.setState({
            needLimitMeasure: false,
            rowLimit: Math.max(
                Math.floor(eventSpace / eventHeight), 1)
        });
    }

    _dateClick(date, e) {
        e.preventDefault();
        this.clearSelection();
        notify(this.props.onNavigate, [navigate.DATE, date]);
    }

    _selectEvent(...args) {
        //cancel any pending selections so only the event click goes through.
        this.clearSelection();

        notify(this.props.onSelectEvent, args);
    }

    _selectDates(e) {
        let slots = this._pendingSelection.slice();

        this._pendingSelection = [];

        slots.sort((a, b) => +a - +b);

        notify(this.props.onSelectSlot, {
            slots,
            start: slots[0],
            end: slots[slots.length - 1]
        });
    }

    _showMore(segments, date, weekIdx, slot) {
        let cell = findDOMNode(this._bgRows[weekIdx]).children[slot - 1];

        let events = segments
            .filter((seg) => isSegmentInSlot(seg, slot))
            .map((seg) => seg.event);

        //cancel any pending selections so only the event click goes through.
        this.clearSelection();

        if (this.props.popup) {
            let position = getPosition(cell, findDOMNode(this));

            this.setState({
                overlay: {date, events, position}
            });
        }
        else {
            notify(this.props.onNavigate, [navigate.DATE, date]);
        }

        notify(this.props.onShowMore, [events, date, slot]);
    }

    clearSelection() {
        clearTimeout(this._selectTimer);
        this._pendingSelection = [];
    }

    render() {
        let {date, culture, weekdayFormat} = this.props
            , month = dates.visibleDays(date, culture)
            , weeks = chunk(month, 7);

        let measure = this.state.needLimitMeasure;

        this._weekCount = weeks.length;

        return (

                    {this._headers(weeks[0], weekdayFormat, culture)}

                { weeks.map((week, idx) =>
                    this.renderWeek(week, idx, measure && this._renderMeasureRows))
                }

        );
    }

}

MonthView.navigate = (date, action) => {
    switch (action) {
        case navigate.PREVIOUS:
            return dates.add(date, -1, 'month');

        case navigate.NEXT:
            return dates.add(date, 1, 'month');

        default:
            return date;
    }
};

MonthView.range = (date, {culture}) => {
    let start = dates.firstVisibleDay(date, culture);
    let end = dates.lastVisibleDay(date, culture);
    return {start, end};
};

MonthView.propTypes = PROPERTY_TYPES;

export default MonthView;
<|endoftext|>


<commit_before>// dependencies
//
import React from 'react';
import uniqueId from 'lodash/uniqueId';
import isArray from 'lodash/isArray';

/*

        Cycles

                <input type="checkbox" value="1">
                1

                <input type="checkbox" value="2">
                2

*/

class CheckboxGroup extends React.Component {
    constructor(props) {
        super(props);

        this.state = { value: [] };
    }

    render() {
        return (

                {this.props.label
                    ? {this.props.label}
                    : null
                }
                {this.props.options.map((opt) =>

                            <input type="checkbox" value={opt.value} />
                            {opt.name}

                )}

        );
    }
}

export default CheckboxGroup;
<commit_msg>added code to initialize state for component<commit_after>// dependencies
//
import React from 'react';
import uniqueId from 'lodash/uniqueId';
import isArray from 'lodash/isArray';

/*

        Cycles

                <input type="checkbox" value="1">
                1

                <input type="checkbox" value="2">
                2

*/

class CheckboxGroup extends React.Component {
    constructor(props) {
        super(props);

        let value = [];

        if (this.props.value) {
            value = isArray(this.props.value) ? this.props.value : [this.props.value];
        }

        this.state = { value };
    }

    render() {
        return (

                {this.props.label
                    ? {this.props.label}
                    : null
                }
                {this.props.options.map((opt) =>

                            <input type="checkbox" value={opt.value} />
                            {opt.name}

                )}

        );
    }
}

export default CheckboxGroup;
<|endoftext|>


<commit_before><commit_msg>add test for disableLimit Select option<commit_after><|endoftext|>