<commit_before>/*global describe, it, ArangoAgency, after, afterEach, instanceInfo */

////////////////////////////////////////////////////////////////////////////////
/// @brief cluster collection creation tests
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Andreas Streichardt
/// @author Copyright 2017, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

'use strict';

const expect = require('chai').expect;

const internal = require('internal');
const db = internal.db;

let isCluster = instanceInfo.arangods.length > 1;
let endpoint = instanceInfo.arangods.filter(arangod => {
  return arangod.role === 'coordinator' || arangod.role === 'single';
})[0];

let download;
if (isCluster) {
  download = require('internal').clusterDownload;
} else {
  download = require('internal').download;
}

// this only tests the http api...there is a separate readonly test
describe('Readonly mode api', function() {
  afterEach(function() {
    // restore default server mode
    let resp = download(endpoint.url + '/_admin/server/mode', JSON.stringify({'mode': 'default'}), {
      method: 'put',
    });
  });

  after(function() {
    // wait 5 seconds so the "default" server mode has a chance to be picked up by all db servers
    // before we go on with other tests
    require("internal").wait(5, false);
  });

  it('outputs its current mode', function() {
    let resp = download(endpoint.url + '/_admin/server/mode');
    expect(resp.code).to.equal(200);
    let body = JSON.parse(resp.body);
    expect(body).to.have.property('mode', 'default');
  });

  it('can switch to readonly', function() {
    let resp = download(endpoint.url + '/_admin/server/mode', JSON.stringify({'mode': 'readonly'}), {
      method: 'put',
    });
    expect(resp.code).to.equal(200);
    let body = JSON.parse(resp.body);
    expect(body).to.have.property('mode', 'readonly');
  });

  it('throws an error when not passing an object', function() {
    let set = download(endpoint.url + '/_admin/server/mode', JSON.stringify('testi'), {
      method: 'put',
    });
    expect(set.code).to.equal(400);

    let resp = download(endpoint.url + '/_admin/server/mode');
    let body = JSON.parse(resp.body);
    expect(body).to.have.property('mode', 'default');
  });

  it('throws an error when passing an unknown mode', function() {
    let set = download(endpoint.url + '/_admin/server/mode', JSON.stringify({'mode': 'testi'}), {
      method: 'put',
    });
    expect(set.code).to.equal(400);

    let resp = download(endpoint.url + '/_admin/server/mode');
    let body = JSON.parse(resp.body);
    expect(body).to.have.property('mode', 'default');
  });

  it('the heartbeat should set readonly mode for all cluster nodes', function() {
    let resp = download(endpoint.url + '/_admin/server/mode', JSON.stringify({'mode': 'readonly'}), {
      method: 'put',
    });
    expect(resp.code).to.equal(200);
    // heartbeat thread will take some time
    internal.wait(0.5);

    let res = instanceInfo.arangods.filter(arangod => arangod.role === 'single' || arangod.role === 'coordinator' || arangod.role === 'primary')
    .every(arangod => {
      let resp = download(endpoint.url + '/_admin/server/mode');
      let body = JSON.parse(resp.body);
      expect(body).to.have.property('mode', 'readonly');
    });
  });
});
<commit_msg>increase timeout (#3859)<commit_after>/*global describe, it, ArangoAgency, after, afterEach, instanceInfo */

////////////////////////////////////////////////////////////////////////////////
/// @brief cluster collection creation tests
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Andreas Streichardt
/// @author Copyright 2017, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

'use strict';

const expect = require('chai').expect;

const internal = require('internal');
const db = internal.db;

let isCluster = instanceInfo.arangods.length > 1;
let endpoint = instanceInfo.arangods.filter(arangod => {
  return arangod.role === 'coordinator' || arangod.role === 'single';
})[0];

let download;
if (isCluster) {
  download = require('internal').clusterDownload;
} else {
  download = require('internal').download;
}

// this only tests the http api...there is a separate readonly test
describe('Readonly mode api', function() {
  afterEach(function() {
    // restore default server mode
    let resp = download(endpoint.url + '/_admin/server/mode', JSON.stringify({'mode': 'default'}), {
      method: 'put',
    });
  });

  after(function() {
    // wait 5 seconds so the "default" server mode has a chance to be picked up by all db servers
    // before we go on with other tests
    internal.wait(5, false);
  });

  it('outputs its current mode', function() {
    let resp = download(endpoint.url + '/_admin/server/mode');
    expect(resp.code).to.equal(200);
    let body = JSON.parse(resp.body);
    expect(body).to.have.property('mode', 'default');
  });

  it('can switch to readonly', function() {
    let resp = download(endpoint.url + '/_admin/server/mode', JSON.stringify({'mode': 'readonly'}), {
      method: 'put',
    });
    expect(resp.code).to.equal(200);
    let body = JSON.parse(resp.body);
    expect(body).to.have.property('mode', 'readonly');
  });

  it('throws an error when not passing an object', function() {
    let set = download(endpoint.url + '/_admin/server/mode', JSON.stringify('testi'), {
      method: 'put',
    });
    expect(set.code).to.equal(400);

    let resp = download(endpoint.url + '/_admin/server/mode');
    let body = JSON.parse(resp.body);
    expect(body).to.have.property('mode', 'default');
  });

  it('throws an error when passing an unknown mode', function() {
    let set = download(endpoint.url + '/_admin/server/mode', JSON.stringify({'mode': 'testi'}), {
      method: 'put',
    });
    expect(set.code).to.equal(400);

    let resp = download(endpoint.url + '/_admin/server/mode');
    let body = JSON.parse(resp.body);
    expect(body).to.have.property('mode', 'default');
  });

  it('the heartbeat should set readonly mode for all cluster nodes', function() {
    let resp = download(endpoint.url + '/_admin/server/mode', JSON.stringify({'mode': 'readonly'}), {
      method: 'put',
    });
    expect(resp.code).to.equal(200);
    // heartbeat thread will take some time
    internal.wait(5, false);

    let res = instanceInfo.arangods.filter(arangod => arangod.role === 'single' || arangod.role === 'coordinator' || arangod.role === 'primary')
    .every(arangod => {
      let resp = download(endpoint.url + '/_admin/server/mode');
      let body = JSON.parse(resp.body);
      expect(body).to.have.property('mode', 'readonly');
    });
  });
});
<|endoftext|>


<commit_before>/******************************************************************************
 * File: VideoController.js
 * Desc: Handles interaction with Netflix video player
 * Author: Fabrice Dugas
 *****************************************************************************/

function VideoController() {
  // Shortcuts to DOM elements
  this.player = document.getElementById('netflix-player');
  this.video = document.getElementsByTagName('video')[0];
  this.playButton = document.getElementsByClassName("player-control-button player-play-pause")[0];
  this.scrubber = document.getElementById('scrubber-component');
  this.handle = document.getElementsByClassName('player-scrubber-handle')[0];

  // Play button
  this.playButton.addEventListener('click', this.playButtonHandler.bind(this));

  // Video seeking
  this.scrubber.addEventListener('mouseup', this.scrubberHandler.bind(this));

  // Listen to Manager
  chrome.runtime.onMessage.addListener(this.messageHandler.bind(this));
}

VideoController.prototype.showControls = function() {
  // I don't think getting the actual position of the scrubber is necessary but whatever
  var rect = this.scrubber.getBoundingClientRect()
  var x = rect.left + rect.width/2
  var y = rect.top + rect.height/2
  var move = createFakeMouseEvent("mousemove", x, y);
  this.scrubber.dispatchEvent(move)
}

VideoController.prototype.hideControls = function() {
  var move = createFakeMouseEvent("mousemove", 0, 0);
  this.player.dispatchEvent(move);
}

VideoController.prototype.play = function() {
  var paused = this.video.paused;
  if (paused){
    var click = createFakeMouseEvent('click', 0, 0);
    this.playButton.dispatchEvent(click);
  }
}

VideoController.prototype.pause = function() {
  var paused = this.video.paused;
  if (!paused){
    var click = createFakeMouseEvent('click', 0, 0);
    this.playButton.dispatchEvent(click);
  }
}

VideoController.prototype.playButtonHandler = function(e) {
  // Verify that it is not a simulated click
  if (!e.fake) {
    var paused = this.video.paused;
    var state = (paused ? utils.state.PAUSED : utils.state.PLAYING);
    var time = this.video.currentTime;

    this.sendMessage(state, time);
  }
}

VideoController.prototype.scrubberHandler = function(e) {
  // Verify that it is not a simulated click
  if (!e.fake) {
    var paused = this.video.paused;
    var state = (paused ? utils.state.PAUSED : utils.state.PLAYING);
    var time = this.pos2time(e.clientX);

    console.log('Seeked at ' + time);

    this.sendMessage(state, time);
  }
}

// Convert time in seconds to a position on the scrubber
VideoController.prototype.time2pos = function(time) {
  var rect = this.scrubber.getBoundingClientRect();
  var videoLength = this.video.seekable.end(0);
  var pos = rect.left + time/videoLength*rect.width;

  // Limit to scrubber dimensions
  pos = pos >= rect.right ? rect.right : pos
  pos = pos <= rect.left  ? rect.left  : pos

  return pos;
}

// Convert a position on the scrubber to a time in seconds
VideoController.prototype.pos2time = function(posX) {
  var rect = this.scrubber.getBoundingClientRect();
  var offsetLeft = rect.left;
  var width = rect.width;
  var prct = (posX - offsetLeft)/width;
  var videoLength = this.video.seekable.end(0);
  var time = prct*videoLength;
  return time;
}

VideoController.prototype.seek = function(time) {
  console.log('Seeking to ' + time)

  this.showControls();

  var handle = this.handle;
  var rect = handle.getBoundingClientRect();
  var centerX = rect.left + rect.width / 2;
  var centerY = rect.top + rect.height / 2;

  // Calculate position to seek to
  var posX = this.time2pos(time);
  var posY = centerY;

  // Grab handle...
  var grab = createFakeMouseEvent("mousedown", centerX, centerY);
  handle.dispatchEvent(grab);

  // ... drag to seek position...
  var drag = createFakeMouseEvent("mousemove", posX, posY);
  handle.dispatchEvent(drag);

  // ... and finally drop
  var drop = createFakeMouseEvent("mouseup", posX, posY);
  handle.dispatchEvent(drop);

  this.hideControls();
}

VideoController.prototype.messageHandler = function(request, sender, sendResponse) {

  var currentTime = this.video.currentTime
  if (currentTime < request.time - 1 || currentTime > request.time + 1) {
    this.seek(request.time)
  }

  if (request.state == utils.state.PLAYING) {
    this.play()
  }

  else if (request.state == utils.state.PAUSED) {
    this.pause()
  }
}

VideoController.prototype.sendMessage = function(state, time) {
  chrome.runtime.sendMessage({state : state, time : time});
}

function initController(){
  var controller;
  var id = setInterval(function() {
    var controls = document.getElementsByClassName('player-control-bar')[0];
    if (controls) {
      console.log('Netflix controls are ready');
      controller = new VideoController();
      controller.pause();
      clearInterval(id);
    }
  }, 100);

  // Listen to key presses
  window.onkeydown = function (e) {
    var key = e.keyCode ? e.keyCode : e.which;

    // Space bar
    if (key == utils.keys.space) {
      var paused = controller.video.paused;
      var state = (paused ? utils.state.PAUSED : utils.state.PLAYING);
      var time = controller.video.currentTime;
      controller.sendMessage(state, time);
    }

  };

  // Listen to page refresh or exit
  window.addEventListener('unload', function() {
    controller.sendMessage(utils.state.UNLOADED);
  });

};

// Initialize the controller when the script is injected
console.log('videoController injected');
initController();<commit_msg>VideoController: Added timeout after showControls when seeking.<commit_after>/******************************************************************************
 * File: VideoController.js
 * Desc: Handles interaction with Netflix video player
 * Author: Fabrice Dugas
 *****************************************************************************/

function VideoController() {
  // Shortcuts to DOM elements
  this.player = document.getElementById('netflix-player');
  this.video = document.getElementsByTagName('video')[0];
  this.playButton = document.getElementsByClassName("player-control-button player-play-pause")[0];
  this.scrubber = document.getElementById('scrubber-component');
  this.handle = document.getElementsByClassName('player-scrubber-handle')[0];

  // Play button
  this.playButton.addEventListener('click', this.playButtonHandler.bind(this));

  // Video seeking
  this.scrubber.addEventListener('mouseup', this.scrubberHandler.bind(this));

  // Listen to Manager
  chrome.runtime.onMessage.addListener(this.messageHandler.bind(this));
}

VideoController.prototype.showControls = function() {
  // I don't think getting the actual position of the scrubber is necessary but whatever
  var rect = this.scrubber.getBoundingClientRect()
  var x = rect.left + rect.width/2
  var y = rect.top + rect.height/2
  var move = createFakeMouseEvent("mousemove", x, y);
  this.scrubber.dispatchEvent(move)
}

VideoController.prototype.hideControls = function() {
  var move = createFakeMouseEvent("mousemove", 0, 0);
  this.player.dispatchEvent(move);
}

VideoController.prototype.play = function() {
  var paused = this.video.paused;
  if (paused){
    var click = createFakeMouseEvent('click', 0, 0);
    this.playButton.dispatchEvent(click);
  }
}

VideoController.prototype.pause = function() {
  var paused = this.video.paused;
  if (!paused){
    var click = createFakeMouseEvent('click', 0, 0);
    this.playButton.dispatchEvent(click);
  }
}

VideoController.prototype.playButtonHandler = function(e) {
  // Verify that it is not a simulated click
  if (!e.fake) {
    var paused = this.video.paused;
    var state = (paused ? utils.state.PAUSED : utils.state.PLAYING);
    var time = this.video.currentTime;

    this.sendMessage(state, time);
  }
}

VideoController.prototype.scrubberHandler = function(e) {
  // Verify that it is not a simulated click
  if (!e.fake) {
    var paused = this.video.paused;
    var state = (paused ? utils.state.PAUSED : utils.state.PLAYING);
    var time = this.pos2time(e.clientX);

    console.log('Seeked at ' + time);

    this.sendMessage(state, time);
  }
}

// Convert time in seconds to a position on the scrubber
VideoController.prototype.time2pos = function(time) {
  var rect = this.scrubber.getBoundingClientRect();
  var videoLength = this.video.seekable.end(0);
  var pos = rect.left + time/videoLength*rect.width;

  // Limit to scrubber dimensions
  pos = pos >= rect.right ? rect.right : pos
  pos = pos <= rect.left  ? rect.left  : pos

  return pos;
}

// Convert a position on the scrubber to a time in seconds
VideoController.prototype.pos2time = function(posX) {
  var rect = this.scrubber.getBoundingClientRect();
  var offsetLeft = rect.left;
  var width = rect.width;
  var prct = (posX - offsetLeft)/width;
  var videoLength = this.video.seekable.end(0);
  var time = prct*videoLength;
  return time;
}

VideoController.prototype.seek = function(time) {
  console.log('Seeking to ' + time)

  this.showControls();

  setTimeout(function() {
    var handle = this.handle;
    var rect = handle.getBoundingClientRect();
    var centerX = rect.left + rect.width / 2;
    var centerY = rect.top + rect.height / 2;

    // Calculate position to seek to
    var posX = this.time2pos(time);
    var posY = centerY;

    // Grab handle...
    var grab = createFakeMouseEvent("mousedown", centerX, centerY);
    handle.dispatchEvent(grab);

    // ... drag to seek position...
    var drag = createFakeMouseEvent("mousemove", posX, posY);
    handle.dispatchEvent(drag);

    // ... and finally drop
    var drop = createFakeMouseEvent("mouseup", posX, posY);
    handle.dispatchEvent(drop);

    setTimeout(function() {
      this.hideControls();
    }.bind(this), 10);
  }.bind(this),10);
}

VideoController.prototype.messageHandler = function(request, sender, sendResponse) {

  var currentTime = this.video.currentTime
  if (currentTime < request.time - 0.5 || currentTime > request.time + 0.5) {
    this.seek(request.time)
  }

  if (request.state == utils.state.PLAYING) {
    this.play()
  }

  else if (request.state == utils.state.PAUSED) {
    this.pause()
  }
}

VideoController.prototype.sendMessage = function(state, time) {
  chrome.runtime.sendMessage({state : state, time : time});
}

function initController(){
  var controller;
  var id = setInterval(function() {
    var controls = document.getElementsByClassName('player-control-bar')[0];
    if (controls) {
      console.log('Netflix controls are ready');
      controller = new VideoController();
      controller.pause();
      clearInterval(id);
    }
  }, 100);

  // Listen to key presses
  window.onkeydown = function (e) {
    var key = e.keyCode ? e.keyCode : e.which;

    // Space bar
    if (key == utils.keys.space) {
      var paused = controller.video.paused;
      var state = (paused ? utils.state.PAUSED : utils.state.PLAYING);
      var time = controller.video.currentTime;
      controller.sendMessage(state, time);
    }

    if (key == utils.keys.a) {
      console.log('\'a\' pressed');
      setTimeout(function() {
        console.log('15 secs passed');
        controller.seek(300);
      }, 15000);
    }

  };

  // Listen to page refresh or exit
  window.addEventListener('unload', function() {
    controller.sendMessage(utils.state.UNLOADED);
  });

};

// Initialize the controller when the script is injected
console.log('videoController injected');
initController();<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import moment from 'moment';
import { findDOMNode } from 'react-dom';
import DateContainer from './DateContainer.js';
import Calendar from './Calendar.js';
import Clock from './Clock.js';
import { transitionEndDetect } from './utils/eventDetect.js';
import RootCloseWrapper from 'rsuite/lib/fixtures/RootCloseWrapper.js';

const DatePicker = React.createClass({
    propTypes: {
        defaultValue: PropTypes.instanceOf(Date),
        value: PropTypes.instanceOf(Date),
        minDate: PropTypes.instanceOf(Date),
        maxDate: PropTypes.instanceOf(Date),
        autoClose: PropTypes.bool,
        placeholder: PropTypes.string,
        dateFormat: PropTypes.string,
        onChange: PropTypes.func,
        dateFilter: PropTypes.func
    },

    contextTypes: {
        formGroup: PropTypes.object
    },
    childContextTypes: {
        messages: PropTypes.object
    },

    getDefaultProps() {
        return {
            dateFormat: 'YYYY-MM-DD',
            autoClose: true,
            placeholder: ''
        };
    },
    getChildContext() {
        return {
            messages: {
                week: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
            }
        };
    },

    getInitialState() {
        const { defaultValue, value } = this.props;
        const activeValue = value || defaultValue;
        let ret = transitionEndDetect();

        return {
            value: activeValue,
            pageDate: activeValue
                ? new Date(activeValue.getFullYear(), activeValue.getMonth())
                : this.getDefaultPageDate(),
            calendarState: 'HIDE',
            transitionSupport: ret
        };
    },
    handleTimeChange(v) {
        const { onChange: onFormChange } = this.getFormGroup();
        const { onChange } = this.props;
        const { value } = this.state;
        const { hours, minutes, seconds } = v;
        let nextValue = value
            ? new Date(value)
            : new Date();
        hours !== undefined && nextValue.setHours(hours);
        minutes !== undefined && nextValue.setMinutes(minutes);
        seconds !== undefined && nextValue.setSeconds(seconds);

        this.setState({ value: nextValue });
        onFormChange && onFormChange(nextValue);
        onChange && onChange(nextValue);
    },

    getTime() {
        const { dateFormat } = this.props;
        const { value } = this.state;
        let timeDate = value || new Date();
        let time = {};
        if (/(H|h)/.test(dateFormat)) {
            time.hours = timeDate.getHours();
        }
        if (/m/.test(dateFormat)) {
            time.minutes = timeDate.getMinutes();
        }
        if (/s/.test(dateFormat)) {
            time.seconds = timeDate.getSeconds();
        }
        return time;
    },

    shouldMountCalendar() {
        const { dateFormat } = this.props;
        return /(Y|M|D)/.test(dateFormat);
    },

    shouldMountClock() {
        const { dateFormat } = this.props;
        return /(H|h|m|s)/.test(dateFormat);
    },

    getFormGroup() {
        return this.context.formGroup || {};
    },

    getValue() {
        const { value: fieldValue } = this.getFormGroup();
        const { value } = this.props;

        if (fieldValue && !/Invalid|NaN/.test(new Date(fieldValue))) {
            return new Date(fieldValue);
        } else if (value) {
            return value;
        }

        return this.state.value || undefined;
    },

    reset() {
        this.setState({
            value: null,
            pageDate: this.getDefaultPageDate(),
            calendarState: 'HIDE'
        });
        const { onChange } = this.getFormGroup();
        onChange && onChange(null);
    },

    getDefaultPageDate() {
        const { minDate, maxDate } = this.props;
        let retDate = new Date();
        if (minDate && retDate.getTime() < minDate.getTime()) {
            retDate = minDate;
        }
        if (maxDate && retDate.getTime() > maxDate.getTime()) {
            retDate = maxDate;
        }
        return retDate;
    },

    getDateString() {
        const { placeholder } = this.props;
        const value = this.getValue();
        return value ? moment(value).format(this.props.dateFormat) : placeholder;
    },

    resetPageDate() {
        const value = this.getValue() || this.getDefaultPageDate();
        let pageDate = new Date(value.getFullYear(), value.getMonth());
        this.setState({ pageDate });
    },

    show() {
        this.resetPageDate();
        this.setState({ calendarState: 'SHOW' });
    },

    hide() {
        const { onBlur } = this.getFormGroup();
        onBlur && onBlur();
        this.setState({ calendarState: 'HIDE' });
    },

    toggle() {
        const { calendarState } = this.state;
        if (calendarState === 'SHOW') {
            this.hide();
        }
        if (calendarState === 'HIDE') {
            this.show();
        }
        if (calendarState === 'EDITING') {
            this.hide();
        }
    },

    showEditPanel() {
        this.setState({ calendarState: 'EDITING' });
    },

    hideEditPanel() {
        this.setState({ calendarState: 'SHOW' });
    },

    toggleEditPanel() {
        const { calendarState } = this.state;
        if (calendarState === 'EDITING') {
            this.hideEditPanel();
        }
        if (calendarState === 'SHOW') {
            this.showEditPanel();
        }
    },

    onMoveForword(nextPageDate) {
        const { transitionSupport } = this.state;
        if (!transitionSupport.supported) {
            this.setState({
                pageDate: nextPageDate
            });
            return;
        }
        this.setState({
            calendarState: 'SLIDING_L'
        });
    },

    onMoveBackward(nextPageDate) {
        const { transitionSupport } = this.state;
        if (!transitionSupport.supported) {
            this.setState({
                pageDate: nextPageDate
            });
            return;
        }
        this.setState({
            calendarState: 'SLIDING_R'
        });
    },

    onMoveDone() {
        const { calendarState, pageDate } = this.state;
        let pageChanges = 0;
        if (calendarState === 'SLIDING_L') {
            pageChanges = 1;
        }
        if (calendarState === 'SLIDING_R') {
            pageChanges = -1;
        }
        let nextPageDate = new Date(pageDate.getFullYear(), pageDate.getMonth() + pageChanges);
        this.setState({
            pageDate: nextPageDate,
            calendarState: 'SHOW'
        });
    },

    onChangePageDate(nextPageDate) {
        this.setState({
            pageDate: nextPageDate,
            calendarState: 'SHOW'
        });
    },

    setMinDate(date) {
        this.setState({ minDate: date });
    },

    setMaxDate(date) {
        this.setState({ maxDate: date });
    },

    handleSelect(day) {
        const { onChange, autoClose } = this.props;
        const { value } = this.state;
        const { onChange: onFormChange } = this.getFormGroup();

        if (autoClose) {
            this.hide();
        }

        let time = value || (new Date());
        let nextValue = new Date(day);

        // merge time into nextSelected
        nextValue.setHours(time.getHours());
        nextValue.setMinutes(time.getMinutes());
        nextValue.setSeconds(time.getSeconds());

        this.setState({ value: nextValue });
        onChange && onChange(day);
        onFormChange && onFormChange(day);
    },
    componentDidMount() {

        const { transitionSupport } = this.state;
        let el = findDOMNode(this.refs.calendar);
        if (transitionSupport.supported && el) {
            el.addEventListener(transitionSupport.event, e => {
                if (e.target.className === 'monthView-weeksWrapper'
                    && e.propertyName === 'left') {
                    this.onMoveDone();
                }
            });
        }
    },
    render() {
        const {
            minDate,
            maxDate,
            dateFilter
        } = this.props;

        const {
            calendarState,
            pageDate
        } = this.state;

        const value = this.getValue();
        const shouldMountCalendar = this.shouldMountCalendar();
        const shouldMountClock = this.shouldMountClock();

        return (
            <RootCloseWrapper onRootClose={this.hide}>

                    <DateContainer
                        placeholder={this.getDateString()}
                        onClick={this.toggle}
                        showCleanButton={!this.props.value && value}
                        onClean={value && this.reset}
                    />

                        {
                            shouldMountCalendar &&
                            <Calendar
                                calendarState={calendarState}
                                selectedDate={value}
                                pageDate={pageDate}
                                minDate={minDate}
                                maxDate={maxDate}
                                onMoveForword={this.onMoveForword}
                                onMoveBackward={this.onMoveBackward}
                                onSelect={this.handleSelect}
                                onClickTitle={this.toggleEditPanel}
                                onChangePageDate={this.onChangePageDate}
                                dateFilter={dateFilter}
                                ref="calendar"
                            />
                        }
                        {
                            shouldMountClock &&
                            <Clock
                                time={this.getTime()}
                                onChange={this.handleTimeChange}
                            />
                        }

            </RootCloseWrapper>
        );
    }
});

export default DatePicker;
<commit_msg>Fix: Failed propType: Invalid prop `showCleanButton` of type `date` supplied to `DateContainer`, expected `boolean`. Check the render method of `DatePicker`.<commit_after>import React, { PropTypes } from 'react';
import moment from 'moment';
import { findDOMNode } from 'react-dom';
import DateContainer from './DateContainer.js';
import Calendar from './Calendar.js';
import Clock from './Clock.js';
import { transitionEndDetect } from './utils/eventDetect.js';
import RootCloseWrapper from 'rsuite/lib/fixtures/RootCloseWrapper.js';

const DatePicker = React.createClass({
  propTypes: {
    defaultValue: PropTypes.instanceOf(Date),
    value: PropTypes.instanceOf(Date),
    minDate: PropTypes.instanceOf(Date),
    maxDate: PropTypes.instanceOf(Date),
    autoClose: PropTypes.bool,
    placeholder: PropTypes.string,
    dateFormat: PropTypes.string,
    onChange: PropTypes.func,
    dateFilter: PropTypes.func,
    messages: PropTypes.object
  },

  contextTypes: {
    formGroup: PropTypes.object
  },
  childContextTypes: {
    messages: PropTypes.object
  },

  getDefaultProps() {
    return {
      dateFormat: 'YYYY-MM-DD',
      autoClose: true,
      placeholder: '',
      messages: {
        week: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
      }
    };
  },
  getChildContext() {
    return {
      messages: this.props.messages
    };
  },

  getInitialState() {
    const { defaultValue, value } = this.props;
    const activeValue = value || defaultValue;
    let ret = transitionEndDetect();

    return {
      value: activeValue,
      pageDate: activeValue
        ? new Date(activeValue.getFullYear(), activeValue.getMonth())
        : this.getDefaultPageDate(),
      calendarState: 'HIDE',
      transitionSupport: ret
    };
  },
  handleTimeChange(v) {
    const { onChange: onFormChange } = this.getFormGroup();
    const { onChange } = this.props;
    const { value } = this.state;
    const { hours, minutes, seconds } = v;
    let nextValue = value
      ? new Date(value)
      : new Date();
    hours !== undefined && nextValue.setHours(hours);
    minutes !== undefined && nextValue.setMinutes(minutes);
    seconds !== undefined && nextValue.setSeconds(seconds);

    this.setState({ value: nextValue });
    onFormChange && onFormChange(nextValue);
    onChange && onChange(nextValue);
  },

  getTime() {
    const { dateFormat } = this.props;
    const { value } = this.state;
    let timeDate = value || new Date();
    let time = {};
    if (/(H|h)/.test(dateFormat)) {
      time.hours = timeDate.getHours();
    }
    if (/m/.test(dateFormat)) {
      time.minutes = timeDate.getMinutes();
    }
    if (/s/.test(dateFormat)) {
      time.seconds = timeDate.getSeconds();
    }
    return time;
  },

  shouldMountCalendar() {
    const { dateFormat } = this.props;
    return /(Y|M|D)/.test(dateFormat);
  },

  shouldMountClock() {
    const { dateFormat } = this.props;
    return /(H|h|m|s)/.test(dateFormat);
  },

  getFormGroup() {
    return this.context.formGroup || {};
  },

  getValue() {
    const { value: fieldValue } = this.getFormGroup();
    const { value } = this.props;

    if (fieldValue && !/Invalid|NaN/.test(new Date(fieldValue))) {
      return new Date(fieldValue);
    } else if (value) {
      return value;
    }

    return this.state.value || undefined;
  },

  reset() {
    this.setState({
      value: null,
      pageDate: this.getDefaultPageDate(),
      calendarState: 'HIDE'
    });
    const { onChange } = this.getFormGroup();
    onChange && onChange(null);
  },

  getDefaultPageDate() {
    const { minDate, maxDate } = this.props;
    let retDate = new Date();
    if (minDate && retDate.getTime() < minDate.getTime()) {
      retDate = minDate;
    }
    if (maxDate && retDate.getTime() > maxDate.getTime()) {
      retDate = maxDate;
    }
    return retDate;
  },

  getDateString() {
    const { placeholder } = this.props;
    const value = this.getValue();
    return value ? moment(value).format(this.props.dateFormat) : placeholder;
  },

  resetPageDate() {
    const value = this.getValue() || this.getDefaultPageDate();
    let pageDate = new Date(value.getFullYear(), value.getMonth());
    this.setState({ pageDate });
  },

  show() {
    this.resetPageDate();
    this.setState({ calendarState: 'SHOW' });
  },

  hide() {
    const { onBlur } = this.getFormGroup();
    onBlur && onBlur();
    this.setState({ calendarState: 'HIDE' });
  },

  toggle() {
    const { calendarState } = this.state;
    if (calendarState === 'SHOW') {
      this.hide();
    }
    if (calendarState === 'HIDE') {
      this.show();
    }
    if (calendarState === 'EDITING') {
      this.hide();
    }
  },

  showEditPanel() {
    this.setState({ calendarState: 'EDITING' });
  },

  hideEditPanel() {
    this.setState({ calendarState: 'SHOW' });
  },

  toggleEditPanel() {
    const { calendarState } = this.state;
    if (calendarState === 'EDITING') {
      this.hideEditPanel();
    }
    if (calendarState === 'SHOW') {
      this.showEditPanel();
    }
  },

  onMoveForword(nextPageDate) {
    const { transitionSupport } = this.state;
    if (!transitionSupport.supported) {
      this.setState({
        pageDate: nextPageDate
      });
      return;
    }
    this.setState({
      calendarState: 'SLIDING_L'
    });
  },

  onMoveBackward(nextPageDate) {
    const { transitionSupport } = this.state;
    if (!transitionSupport.supported) {
      this.setState({
        pageDate: nextPageDate
      });
      return;
    }
    this.setState({
      calendarState: 'SLIDING_R'
    });
  },

  onMoveDone() {
    const { calendarState, pageDate } = this.state;
    let pageChanges = 0;
    if (calendarState === 'SLIDING_L') {
      pageChanges = 1;
    }
    if (calendarState === 'SLIDING_R') {
      pageChanges = -1;
    }
    let nextPageDate = new Date(pageDate.getFullYear(), pageDate.getMonth() + pageChanges);
    this.setState({
      pageDate: nextPageDate,
      calendarState: 'SHOW'
    });
  },

  onChangePageDate(nextPageDate) {
    this.setState({
      pageDate: nextPageDate,
      calendarState: 'SHOW'
    });
  },

  setMinDate(date) {
    this.setState({ minDate: date });
  },

  setMaxDate(date) {
    this.setState({ maxDate: date });
  },

  handleSelect(day) {
    const { onChange, autoClose } = this.props;
    const { value } = this.state;
    const { onChange: onFormChange } = this.getFormGroup();

    if (autoClose) {
      this.hide();
    }

    let time = value || (new Date());
    let nextValue = new Date(day);

    // merge time into nextSelected
    nextValue.setHours(time.getHours());
    nextValue.setMinutes(time.getMinutes());
    nextValue.setSeconds(time.getSeconds());

    this.setState({ value: nextValue });
    onChange && onChange(day);
    onFormChange && onFormChange(day);
  },
  componentDidMount() {

    const { transitionSupport } = this.state;
    let el = findDOMNode(this.refs.calendar);
    if (transitionSupport.supported && el) {
      el.addEventListener(transitionSupport.event, e => {
        if (e.target.className === 'monthView-weeksWrapper'
          && e.propertyName === 'left') {
          this.onMoveDone();
        }
      });
    }
  },
  render() {
    const {
      minDate,
      maxDate,
      dateFilter
    } = this.props;

    const {
      calendarState,
      pageDate
    } = this.state;

    const value = this.getValue();
    const shouldMountCalendar = this.shouldMountCalendar();
    const shouldMountClock = this.shouldMountClock();

    return (
      <RootCloseWrapper onRootClose={this.hide}>

          <DateContainer
            placeholder={this.getDateString()}
            onClick={this.toggle}
            showCleanButton={!this.props.value && value}
            onClean={value && this.reset}
          />

            {
              shouldMountCalendar &&
              <Calendar
                calendarState={calendarState}
                selectedDate={value}
                pageDate={pageDate}
                minDate={minDate}
                maxDate={maxDate}
                onMoveForword={this.onMoveForword}
                onMoveBackward={this.onMoveBackward}
                onSelect={this.handleSelect}
                onClickTitle={this.toggleEditPanel}
                onChangePageDate={this.onChangePageDate}
                dateFilter={dateFilter}
                ref="calendar"
              />
            }
            {
              shouldMountClock &&
              <Clock
                time={this.getTime()}
                onChange={this.handleTimeChange}
              />
            }

      </RootCloseWrapper>
    );
  }
});

export default DatePicker;
<|endoftext|>


<commit_before>/**
 * Created by desver_f on 16/02/17.
 */
import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    ScrollView,
    Text,
    View,
    TouchableOpacity
} from 'react-native';
import {
    Icon
} from 'native-base';
import IconFA from 'react-native-vector-icons/FontAwesome';
import Accordion from 'react-native-collapsible/Accordion';

const sections = [
    {
        title: '   Deadline',
        icon: 'flag-checkered',
        content: '',
    },
    {
        title: '   Team',
        icon: 'user',
        content: '',
    },
    {
        title: '   Documents',
        icon: 'file-pdf-o',
        content: '',
    }
];

class ProjectDetails extends Component {

    constructor(props) {
        super(props);

        this._renderBanner = this._renderBanner.bind(this);
        this._renderHeader = this._renderHeader.bind(this);
        this._renderContent = this._renderContent.bind(this);
    }

    _renderHeader(sections) {
        return (
            <View style={{
                height: 70,
                backgroundColor: "#2c3e50",
                flexDirection: 'row',
                marginBottom: 2,
                borderBottomWidth: 0.5,
                borderTopWidth: 0.5,
            }}>
                <IconFA style={{
                    color: "#FFF",
                    fontSize: 24,
                    alignSelf: 'center',
                    flex: 0.09,
                    marginLeft: 20
                }} name={sections.icon} />
                    <Text style={{
                        color: "#FFF",
                        alignSelf: 'center',
                        flex: 0.91,
                }}>{sections.title}</Text>
            </View>
        )
    }

    _renderContent(sections) {
        return (
            <View>
                <Text></Text>
            </View>
        )
    }

    _renderBanner(registered) {

        const notification = registered ? "  You are registered to this project" : "  You are not registered for this project";

        return (
            <View style={{
                flex: 0.05,
                flexDirection: 'row',
                backgroundColor: registered ? "#62c462" : "#f44336",
                justifyContent: 'center'
            }}>
                <Icon style={{
                    color: "#FFF",
                }} name={ registered ? "ios-checkmark" : "ios-close"} />
                <Text style={{
                    fontWeight: "bold",
                    color: "#FFF",
                    fontSize: 12,
                    alignSelf: 'center'
                }}>{notification}</Text>
            </View>
        )
    }

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

        return (
            <View style={{
                flex: 1
            }}>

                {this._renderBanner(project.registered)}

                <View style={{
                    flex: 0.4,
                    backgroundColor: "#2c3e50",
                    elevation: 10,
                }}>

                    <View style={{
                        flexDirection: 'row',
                        marginLeft: 8,
                        marginTop: 12,
                    }}>
                        <IconFA style={{
                            color: "#FFF",
                            fontSize: 15,
                            margin: 1,}} name="info-circle" />
                        <Text style={{
                            fontWeight: "bold",
                            fontSize: 12,
                            fontFamily: "Nunito-Light",
                            color: "#FFF",
                        }}> Information</Text>
                    </View>

                    <View>
                        <Text style={{
                            color: "#FFF",
                            fontSize: 12,
                            marginLeft: 10,
                            marginTop: 5,
                            lineHeight: 20,
                            fontFamily: "Nunito-ExtraLight",
                        }}>
                            Create a logical machine manager. This classic topic will allow you to consolidate your bases in C ++
                        </Text>
                    </View>

                    <View style={{
                        marginLeft: 8,
                        marginTop: 12,
                    }}>
                        <Text style={{
                            fontWeight: "bold",
                            fontSize: 12,
                            fontFamily: "Nunito-Light",
                            color: "#FFF",
                        }}>Repository name :</Text>
                        <Text style={{
                            color: "#FFF",
                            fontSize: 12,
                            marginTop: 5,
                            lineHeight: 20,
                            fontFamily: "Nunito-ExtraLight",
                        }}>cpp_nanotekspice</Text>
                    </View>

                </View>

                <View style={{
                    flex: 0.55,
                    backgroundColor: "#42586E"
                }}>

                    <Accordion
                        sections={sections}
                        renderHeader={this._renderHeader}
                        renderContent={this._renderContent}
                    />

                </View>

            </View>
        );
    }
}

ProjectDetails.propTypes = {
    project: React.PropTypes.object,
};

export default ProjectDetails;
<commit_msg>fix-flex-details-project<commit_after>/**
 * Created by desver_f on 16/02/17.
 */
import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    ScrollView,
    Text,
    View,
    TouchableOpacity,
} from 'react-native';
import {
    Icon
} from 'native-base';
import IconFA from 'react-native-vector-icons/FontAwesome';
import Accordion from 'react-native-collapsible/Accordion';

const sections = [
    {
        title: '   Deadline',
        icon: 'flag-checkered',
        content: '',
    },
    {
        title: '   Team',
        icon: 'user',
        content: '',
    },
    {
        title: '   Documents',
        icon: 'file-pdf-o',
        content: '',
    }
];

class ProjectDetails extends Component {

    constructor(props) {
        super(props);

        this._renderBanner = this._renderBanner.bind(this);
        this._renderHeader = this._renderHeader.bind(this);
        this._renderContent = this._renderContent.bind(this);
        this._renderDescription = this._renderDescription.bind(this);
        this._renderRepositoryName = this._renderRepositoryName.bind(this);
    }

    _renderHeader(sections) {
        return (
            <View style={{
                height: 70,
                backgroundColor: "#2c3e50",
                flexDirection: 'row',
                marginBottom: 2,
                borderBottomWidth: 0.5,
                borderTopWidth: 0.5,
                alignItems: 'center',
            }}>
                <View style={{
                    flex: 0.05,
                }} />
                <IconFA style={{
                    color: "#FFF",
                    fontSize: 24,
                    flex: 0.1,
                }} name={sections.icon} />
                    <Text style={{
                        color: "#FFF",
                        flex: 0.6,
                }}>{sections.title}</Text>
            </View>
        )
    }

    _renderContent(sections) {
        return (
            <View>
                <Text></Text>
            </View>
        )
    }

    _renderBanner(registered) {
        const notification = registered ? "  You are registered to this project" : "  You are not registered for this project";
        return (
            <View style={{
                flex: 0.05,
                flexDirection: 'row',
                backgroundColor: registered ? "#62c462" : "#f44336",
                justifyContent: 'center'
            }}>
                <Icon style={{
                    color: "#FFF",
                }} name={ registered ? "ios-checkmark" : "ios-close"} />
                <Text style={{
                    fontWeight: "bold",
                    color: "#FFF",
                    fontSize: 12,
                    alignSelf: 'center'
                }}>{notification}</Text>
            </View>
        )
    }

    _renderDescription() {
        return (
            <View>
                <Text style={{
                    color: "#FFF",
                    fontSize: 12,
                    marginLeft: 10,
                    marginTop: 5,
                    fontFamily: "Nunito-ExtraLight",
                }}>Insert description</Text>
            </View>
        )
    }

    _renderRepositoryName() {
        return (
            <View>
                <Text style={{
                    color: "#FFF",
                    fontSize: 12,
                    marginTop: 5,
                    fontFamily: "Nunito-ExtraLight",
                }}>cpp_nanotekspice</Text>
            </View>
        )

    }

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

        return (
            <View style={{
                flex: 1
            }}>
                {this._renderBanner(project.registered)}
                <View style={{
                    flex: 0.4,
                    backgroundColor: "#2c3e50",
                    elevation: 10,
                }}>
                    <View style={{
                        flexDirection: 'row',
                        marginLeft: 8,
                        marginTop: 12,
                    }}>
                        <Text style={{
                            fontWeight: "bold",
                            fontSize: 12,
                            fontFamily: "Nunito-Light",
                            color: "#FFF",
                        }}> Information</Text>
                    </View>
                    {this._renderDescription()}
                    <View style={{
                        marginLeft: 10,
                        marginTop: 12,
                    }}>
                        <Text style={{
                            fontWeight: "bold",
                            fontSize: 12,
                            fontFamily: "Nunito-Light",
                            color: "#FFF",
                        }}>Repository name :</Text>
                        {this._renderRepositoryName()}
                    </View>
                </View>
                <View style={{
                    flex: 0.55,
                    backgroundColor: "#42586E"
                }}>
                    <ScrollView style={{ flex: 1,}}>
                        <Accordion
                            sections={sections}
                            renderHeader={this._renderHeader}
                            renderContent={this._renderContent}
                        />
                    </ScrollView>
                </View>
            </View>
        );
    }
}

ProjectDetails.propTypes = {
    project: React.PropTypes.object,
};

export default ProjectDetails;
<|endoftext|>


<commit_before>/**
 * @jsx React.DOM
 */

var BucketList = React.createClass({
  render: function() {
    var bucketNodes = this.props.dumps.map(function (bucketAndDumps) {
      var bucket = bucketAndDumps[0];
      var dumps = bucketAndDumps[1];
      return (<Bucket key={bucket.id} name={bucket.name} dumps={dumps}></Bucket>);
    });
    return (

        <UploadingFiles />
        <AnalyzingBuckets analyzingDumps={this.props.analyzingDumps}></AnalyzingBuckets>
        {bucketNodes}

    );
  }
});

var UploadingFiles = React.createClass({
  getInitialState: function() {
    return {uploads: []};
  },

  componentDidMount: function() {
    // Change this to the location of your server-side upload handler:
    var uploading = this;
    var url = '/uploadAsync';
    $('#holder').fileupload({
        url: url,
        dataType: 'json',
        submit: function(e, data) {
          var newUploads = uploading.state.uploads.concat(
            [{name: data.files[0].name, progress: 0}]);
          uploading.setState({uploads: newUploads});
          return true;
        },

        progress: function(e, data) {
          var progress = parseInt(data.loaded / data.total * 100, 10);
          var newUploads = uploading.state.uploads.map(function(entry) {
            if(entry.name === data.files[0].name) {
              return {name: entry.name, progress: progress};
            }
            else {
              return entry;
            }
          });
          uploading.setState({uploads: newUploads});
        },

        done: function (e, data) {
          var newUploads = uploading.state.uploads.filter(
            function(upload) { return upload.name !== data.files[0].name; });
          uploading.setState({uploads: newUploads});
        }
    }).prop('disabled', !$.support.fileInput)
        .parent().addClass($.support.fileInput ? undefined : 'disabled');

  },

  render: function() {
    var uploadingNodes = this.state.uploads.map(function(entry) {
      var width = {width: entry.progress + '%'};
      return (

{entry.name}

      );
    });
    return (

          Uploading...

        {uploadingNodes}

    );
  }
});

var AnalyzingBuckets = React.createClass({
  render: function() {
    var dumpNodes = this.props.analyzingDumps.map(function(dump) {
      return (

          {dump}

      );
    });

    return (

          Currently processing...

        {dumpNodes}

    );
  }
});

var Bucket = React.createClass({
  render: function() {
    var dumpNodes = this.props.dumps.map(function(dump) {
      return <Dump key={dump.id} dump={dump}></Dump>;
    });
    return (

          {this.props.name}

        {dumpNodes}

    );
  }
});

var Dump = React.createClass({
  getInitialState: function() {
    return {tags: this.props.dump.tags};
  },

  handleClickOnRemove: function() {
    this.handleAddTag('marked for deletion');
  },

  handleClickOnArchive: function() {
    this.handleAddTag('keep forever');
  },

  handleAddTag: function(tagName) {
    var tags = this.state.tags;
    if(!tags.some(function(tag) { return tag.name === tagName; })) {
      var newTags = tags.concat([{name: tagName}]);
      this.setState({tags: newTags});
    }
    $.ajax({
      type : 'POST',
      url : this.props.dump.addTagUrl + encodeURIComponent(tagName)
    });
  },

  handleRemoveTag: function(tagName) {
    var tags = this.state.tags;
    var newTags = tags.filter(function(elem) { return elem.name !== tagName; });
    this.setState({tags: newTags});
    $.ajax({
      type : 'POST',
      url : this.props.dump.removeTagUrl + encodeURIComponent(tagName)
    });
  },

  render: function() {
    var cx = React.addons.classSet;
    var classes = cx({
      'dmp': true,
      'new': this.props.dump.isNew
    });
    return (

              {this.props.dump.filename}

          <Tags
            tags = {this.state.tags}
            handleAddTag = {this.handleAddTag}
            handleRemoveTag = {this.handleRemoveTag} />
          <time>{this.props.dump.ageLabel}</time>

    );
  }
});

var Tags = React.createClass({
  getInitialState: function() {
    return {
      inputVisible: false,
      value: ''
    };
  },

  handleInputKeyPress: function(event) {
    if (event.keyCode == 13 || event.which == 13) {
      this.props.handleAddTag(this.state.value);
      this.setState({
        inputVisible: false,
        value: ''
      });
    }
  },

  handleInputBlur: function() {
    this.setState({
      inputVisible: false,
      value: ''
    });
  },

  handleInputChange: function(event) {
    this.setState({value: event.target.value});
  },

  handleAddTagClick: function() {
    this.setState({ inputVisible: true });
  },

  componentDidUpdate: function() {
    if(this.state.inputVisible) {
      this.refs.tagInput.getDOMNode().focus();
    }
  },

  render: function() {
    var removeTag = this.props.handleRemoveTag;
    var tagNodes = this.props.tags.map(function(tag) {
      return <Tag key={tag.name} tag={tag} handleRemoveTag={removeTag}></Tag>;
    });
    if(this.state.inputVisible) {
      return (

          {tagNodes}
          <input type="text"
            ref="tagInput"
            className="tag-input"
            list="tags"
            placeholder="  add a tag… "
            onKeyPress={this.handleInputKeyPress}
            onChange={this.handleInputChange}
            onBlur={this.handleInputBlur} >
          </input>

      );
    } else {
      return(

          {tagNodes}

            add a tag…

      );
    }
  }
});

var Tag = React.createClass({
  handleRemoveClick: function() {
    this.props.handleRemoveTag(this.props.tag.name);
  },

  render: function() {
    var cx = React.addons.classSet;
    var tagClass = this.props.tag.name.split(' ').join('-');
    var classes = cx({
      'tag': true,
      'removeable': true
    });
    classes = classes + ' ' + tagClass;
    return (

        {this.props.tag.name}

    );
  }
});

var Buckets = React.createClass({
  getInitialState: function() {
    return {dumps: [], analyzingDumps: []};
  },

  loadBucketsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({ dumps: data.buckets, analyzingDumps: data.analyzing });
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },

  componentWillMount: function() {
    this.loadBucketsFromServer();
    setInterval(this.loadBucketsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (

        <BucketList dumps={this.state.dumps} analyzingDumps={this.state.analyzingDumps} />

    );
  }
});

React.renderComponent(
  <Buckets url="dmpster/buckets.json" pollInterval={2000}/>,
  document.getElementById('content')
);<commit_msg>added optimistic updating of UI for processing dumps that were just uplaoded<commit_after>/**
 * @jsx React.DOM
 */

var BucketList = React.createClass({
  getInitialState: function() {
    return {analyzingDumps: this.props.analyzingDumps};
  },

  componentWillReceiveProps: function(nextProps) {
    this.setState({analyzingDumps: nextProps.analyzingDumps});
  },

  onFileUploaded: function(fileName) {
    var analyzing = this.state.analyzingDumps;
    if(!analyzing.some(function(a) { return a === fileName; })) {
      var newAnalyzing = analyzing.concat([fileName]);
      this.setState({analyzingDumps: newAnalyzing});
    }
  },

  render: function() {
    var bucketNodes = this.props.dumps.map(function (bucketAndDumps) {
      var bucket = bucketAndDumps[0];
      var dumps = bucketAndDumps[1];
      return (<Bucket key={bucket.id} name={bucket.name} dumps={dumps}></Bucket>);
    });
    return (

        <UploadingFiles onFileUploaded={this.onFileUploaded} />
        <AnalyzingBuckets analyzingDumps={this.state.analyzingDumps}></AnalyzingBuckets>
        {bucketNodes}

    );
  }
});

var UploadingFiles = React.createClass({
  getInitialState: function() {
    return {uploads: []};
  },

  componentDidMount: function() {
    // Change this to the location of your server-side upload handler:
    var uploading = this;
    var url = '/uploadAsync';
    $('#holder').fileupload({
        url: url,
        dataType: 'json',
        submit: function(e, data) {
          var newUploads = uploading.state.uploads.concat(
            [{name: data.files[0].name, progress: 0}]);
          uploading.setState({uploads: newUploads});
          return true;
        },

        progress: function(e, data) {
          var progress = parseInt(data.loaded / data.total * 100, 10);
          var newUploads = uploading.state.uploads.map(function(entry) {
            if(entry.name === data.files[0].name) {
              return {name: entry.name, progress: progress};
            }
            else {
              return entry;
            }
          });
          uploading.setState({uploads: newUploads});
        },

        done: function (e, data) {
          var finishedFile = data.files[0].name;
          var newUploads = uploading.state.uploads.filter(
            function(upload) { return upload.name !== finishedFile; });
          uploading.setState({uploads: newUploads});
          uploading.props.onFileUploaded(finishedFile);
        }
    }).prop('disabled', !$.support.fileInput)
        .parent().addClass($.support.fileInput ? undefined : 'disabled');

  },

  render: function() {
    var uploadingNodes = this.state.uploads.map(function(entry) {
      var width = {width: entry.progress + '%'};
      return (

{entry.name}

      );
    });
    return (

          Uploading...

        {uploadingNodes}

    );
  }
});

var AnalyzingBuckets = React.createClass({
  render: function() {
    var dumpNodes = this.props.analyzingDumps.map(function(dump) {
      return (

          {dump}

      );
    });

    return (

          Currently processing...

        {dumpNodes}

    );
  }
});

var Bucket = React.createClass({
  render: function() {
    var dumpNodes = this.props.dumps.map(function(dump) {
      return <Dump key={dump.id} dump={dump}></Dump>;
    });
    return (

          {this.props.name}

        {dumpNodes}

    );
  }
});

var Dump = React.createClass({
  getInitialState: function() {
    return {tags: this.props.dump.tags};
  },

  handleClickOnRemove: function() {
    this.handleAddTag('marked for deletion');
  },

  handleClickOnArchive: function() {
    this.handleAddTag('keep forever');
  },

  handleAddTag: function(tagName) {
    var tags = this.state.tags;
    if(!tags.some(function(tag) { return tag.name === tagName; })) {
      var newTags = tags.concat([{name: tagName}]);
      this.setState({tags: newTags});
    }
    $.ajax({
      type : 'POST',
      url : this.props.dump.addTagUrl + encodeURIComponent(tagName)
    });
  },

  handleRemoveTag: function(tagName) {
    var tags = this.state.tags;
    var newTags = tags.filter(function(elem) { return elem.name !== tagName; });
    this.setState({tags: newTags});
    $.ajax({
      type : 'POST',
      url : this.props.dump.removeTagUrl + encodeURIComponent(tagName)
    });
  },

  render: function() {
    var cx = React.addons.classSet;
    var classes = cx({
      'dmp': true,
      'new': this.props.dump.isNew
    });
    return (

              {this.props.dump.filename}

          <Tags
            tags = {this.state.tags}
            handleAddTag = {this.handleAddTag}
            handleRemoveTag = {this.handleRemoveTag} />
          <time>{this.props.dump.ageLabel}</time>

    );
  }
});

var Tags = React.createClass({
  getInitialState: function() {
    return {
      inputVisible: false,
      value: ''
    };
  },

  handleInputKeyPress: function(event) {
    if (event.keyCode == 13 || event.which == 13) {
      this.props.handleAddTag(this.state.value);
      this.setState({
        inputVisible: false,
        value: ''
      });
    }
  },

  handleInputBlur: function() {
    this.setState({
      inputVisible: false,
      value: ''
    });
  },

  handleInputChange: function(event) {
    this.setState({value: event.target.value});
  },

  handleAddTagClick: function() {
    this.setState({ inputVisible: true });
  },

  componentDidUpdate: function() {
    if(this.state.inputVisible) {
      this.refs.tagInput.getDOMNode().focus();
    }
  },

  render: function() {
    var removeTag = this.props.handleRemoveTag;
    var tagNodes = this.props.tags.map(function(tag) {
      return <Tag key={tag.name} tag={tag} handleRemoveTag={removeTag}></Tag>;
    });
    if(this.state.inputVisible) {
      return (

          {tagNodes}
          <input type="text"
            ref="tagInput"
            className="tag-input"
            list="tags"
            placeholder="  add a tag… "
            onKeyPress={this.handleInputKeyPress}
            onChange={this.handleInputChange}
            onBlur={this.handleInputBlur} >
          </input>

      );
    } else {
      return(

          {tagNodes}

            add a tag…

      );
    }
  }
});

var Tag = React.createClass({
  handleRemoveClick: function() {
    this.props.handleRemoveTag(this.props.tag.name);
  },

  render: function() {
    var cx = React.addons.classSet;
    var tagClass = this.props.tag.name.split(' ').join('-');
    var classes = cx({
      'tag': true,
      'removeable': true
    });
    classes = classes + ' ' + tagClass;
    return (

        {this.props.tag.name}

    );
  }
});

var Buckets = React.createClass({
  getInitialState: function() {
    return {dumps: [], analyzingDumps: []};
  },

  loadBucketsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({ dumps: data.buckets, analyzingDumps: data.analyzing });
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },

  componentWillMount: function() {
    this.loadBucketsFromServer();
    setInterval(this.loadBucketsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (

        <BucketList dumps={this.state.dumps} analyzingDumps={this.state.analyzingDumps} />

    );
  }
});

React.renderComponent(
  <Buckets url="dmpster/buckets.json" pollInterval={2000}/>,
  document.getElementById('content')
);<|endoftext|>


<commit_before>const _ = require('lodash');
const DebouncedCall = require('./DebouncedCall');
const { calculateDirty, getFieldName, isPromise, parseValue } = require('./helpers');

module.exports = class Field {
  constructor(pathToField, params, form, fieldStorage) {
    this._form = form;
    this._fieldStorage = fieldStorage;
    this._debouncedCall = new DebouncedCall(this._form.config.debounceTime);
    if (!_.isUndefined(params.debounceTime)) this.setDebounceTime(params.debounceTime);

    this._pathToField = pathToField;
    this._fieldName = getFieldName(pathToField);
    this._handlers = {
      onChange: undefined,
      onSave: undefined,
    };

    this._initState(params);

    this.handleChange = this.handleChange.bind(this);
    this.handleFocusIn = this.handleFocusIn.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
    this.handleEndEditing = this.handleEndEditing.bind(this);
    this.save = this.save.bind(this);
    this.clear = this.clear.bind(this);
    this.reset = this.reset.bind(this);
    this._doSave = this._doSave.bind(this);
    this._afterSaveEnd = this._afterSaveEnd.bind(this);
  }

  get form() {
    return this._form;
  }
  get savedValue() {
    return this._fieldStorage.getState(this._pathToField, 'savedValue');
  }
  get editedValue() {
    return this._fieldStorage.getState(this._pathToField, 'editedValue');
  }

  /**
   * Combined value
   * @return {*}
   */
  get value() {
    return this._fieldStorage.getCombinedValue(this._pathToField);
  }
  get name() {
    return this._fieldName;
  }
  get fullName() {
    return this._pathToField;
  }
  get dirty() {
    return this._fieldStorage.getState(this._pathToField, 'dirty');
  }
  get touched() {
    return this._fieldStorage.getState(this._pathToField, 'touched');
  }
  get valid() {
    return !this._fieldStorage.getState(this._pathToField, 'invalidMsg');
  }
  get invalidMsg() {
    return this._fieldStorage.getState(this._pathToField, 'invalidMsg');
  }
  get saving() {
    return this._fieldStorage.getState(this._pathToField, 'saving');
  }
  get savable() {
    return !this.canSave();
  }
  get focused() {
    return this._fieldStorage.getState(this._pathToField, 'focused');
  }
  get disabled() {
    return this._fieldStorage.getState(this._pathToField, 'disabled');
  }
  get defaultValue() {
    return this._fieldStorage.getState(this._pathToField, 'defaultValue');
  }
  get debounceTime() {
    return this._debouncedCall.delay;
  }

  /**
   * Check for field can be saved.
   * @return {string|undefined} - undefined means it can. Otherwise it returns a reason.
   */
  canSave() {
    // don't save invalid value
    if (!this.valid) return 'Field is invalid';

    // save only value which was modified.
    if (!this._fieldStorage.isFieldUnsaved(this._pathToField)) {
      return `Value hasn't modified`;
    }
  }

  /**
   * Set value silently(don't rise a change event).
   * It does:
   * * It set a new value to self instance and to storage
   * * It updates "dirty" and "valid" states.
   * * It rises storageChange event.
   *
   * It doesn't:
   * * It doesn't rise onChange callback (for user's events).
   * * It doesn't update "touched" state.
   * @param newValue
   */
  setValue(newValue) {
    const oldState = this._fieldStorage.getWholeState(this._pathToField);

    this.$setEditedValueSilent(newValue);
    this.form.validate();

    const newState = this._fieldStorage.getWholeState(this._pathToField);
    this._fieldStorage.emitStorageEvent(this._pathToField, 'update', newState, oldState);
  }

  /**
   * Set previously saved value. Usually it sets after server data has loading.
   * @param {*} newSavedValue
   */
  setSavedValue(newSavedValue) {
    const oldState = this._fieldStorage.getWholeState(this._pathToField);

    this.$setSavedValue(newSavedValue);
    this.form.validate();

    const newState = this._fieldStorage.getWholeState(this._pathToField);
    this._fieldStorage.emitStorageEvent(this._pathToField, 'update', newState, oldState);
  }

  setDisabled(value) {
    if (!_.isBoolean(value)) throw new Error(`Disabled has to be boolean`);
    this._setState({ disabled: value });
  }

  setDebounceTime(delay) {
    const toNumber = _.toNumber(delay);
    if (_.isNull(toNumber) || _.isNaN(toNumber)) throw new Error(`Bad debounceTime value`);
    // TODO: doesn't work
    this._debouncedCall.delay = toNumber;
  }

  /**
   * It's an onChange handler. It has to be placed to input's onChange attribute.
   * It sets a new value made by user and start saving.
   * It does:
   * * don't do anything if field is disabled
   * * don't save if value isn't changed
   * * update value
   * * update "touched" and "dirty" states
   * * validate form
   * * Rise a "change" events for field and form
   * * Run an onChange callback if it assigned.
   * * Start saving
   * @param {*} rawValue
   */
  handleChange(rawValue) {
    // don't do anything if disabled
    if (this.disabled) return;

    const newValue = parseValue(rawValue);
    // value is immutable
    const oldValue = this.value;
    const isChanged = !_.isEqual(oldValue, newValue);

    if (isChanged) {
      const oldState = this._fieldStorage.getWholeState(this._pathToField);
      // set editedValue and dirty state
      this.$setEditedValueSilent(newValue);
      this.form.validate();

      // set touched to true
      if (!this.touched) {
        this.$setStateSilent({ touched: true });
        this._form.$setStateSilent({ touched: true });
      }

      const newState = this._fieldStorage.getWholeState(this._pathToField);
      this._fieldStorage.emitStorageEvent(this._pathToField, 'update', newState, oldState);
    }

    // rise change event and save only changed value
    if (!this._form.config.allowSaveUnmodifiedField && !isChanged) return;

    // rise change by user event handlers and callbacks of form and field
    this._riseUserChangeEvent(this._pathToField, oldValue, newValue);
    // start save with debounced delay
    this._addSavingToQueue(false);
  }

  /**
   * Set field's "focused" prop to true.
   */
  handleFocusIn() {
    this._setState({ focused: true });
  }

  /**
   * Set field's "focused" prop to false.
   */
  handleBlur() {
    this._setState({ focused: false });
    // start save immediately
    this.save();
  }

  /**
   * bind it to your component to onEnter event.
   * It does:
   * * cancel previous save in queue
   * * immediately starts save
   */
  handleEndEditing() {
    if (this.disabled) return;
    // start save immediately
    this.save();
  }

  /**
   * Add one or more handlers on fields's event:
   * * change
   * * storage
   * * saveStart
   * * saveEnd
   * @param eventName
   * @param cb
   */
  on(eventName, cb) {
    this._fieldStorage.on(this._pathToField, eventName, cb);
  }

  off(eventName, cb) {
    this._fieldStorage.off(this._pathToField, eventName, cb);
  }

  /**
   * It rises a callback on field's value changes which has made by user.
   * @param {function} handler - callback. You can set only one callback per field.
   */
  onChange(handler) {
    this._handlers.onChange = handler;
  }

  /**
   * It rises with debounce delay on start saving.
   * @param {function} handler - callback. If it returns a promise - saving process will wait for it.
   *                             You can set only one callback per field.
   */
  onSave(handler) {
    this._handlers.onSave = handler;
  }

  /**
   * Start saving of field's value immediately.
   * @return {Promise}
   */
  save() {
    return this._addSavingToQueue(true);
  }

  /**
   * Clear value(user input) and set initial value.
   */
  clear() {
    this._updateStateAndValidate(() => {
      this.$clearSilent();
    });
  }

  /**
   * set saved value to current value.
   */
  revert() {
    this._updateStateAndValidate(() => {
      this.$revertSilent();
    });
  }

  /**
   * Reset to default value
   */
  reset() {
    this._updateStateAndValidate(() => {
      this.$resetSilent();
    });
  }

  /**
   * Cancel debounce waiting for saving
   */
  cancelSaving() {
    this._debouncedCall.cancel();
  }

  /**
   * Saving immediately
   */
  flushSaving() {
    this._debouncedCall.flush();
  }

  $clearSilent() {
    const initial = this._fieldStorage.getState(this._pathToField, 'initial');
    this.$setEditedValueSilent(initial);
  }

  $revertSilent() {
    this.$setEditedValueSilent(this.savedValue);
  }

  $resetSilent() {
    this.$setEditedValueSilent(this.defaultValue);
  }

  $destroyHandlers() {
    this._handlers = {};
  }

  $setEditedValueSilent(newValue) {
    // set top value layer
    this.$setStateSilent({
      editedValue: newValue,
      dirty: calculateDirty(newValue, this.savedValue),
    });
  }

  $setSavedValue(newSavedValue) {
    const newState = {
      savedValue: newSavedValue,
      editedValue: this.editedValue,
    };

    // update user input if field isn't on focus and set dirty to false.
    // of course if it allows in config.
    if (this._form.config.allowFocusedFieldUpdating || (!this._form.config.allowFocusedFieldUpdating && !this.focused)) {
      // clear top level
      newState.editedValue = undefined;
    }

    newState.dirty = calculateDirty(newState.editedValue, newState.savedValue);

    this.$setStateSilent(newState);
  }

  $setStateSilent(newPartlyState) {
    this._fieldStorage.setStateSilent(this._pathToField, newPartlyState);
  }

  $setValueAfterSave(savedValue) {
    // if value hasn't changed after submit was started - clear it
    if (savedValue === this.value) {
      this._fieldStorage.setStateSilent(this._pathToField, { editedValue: undefined });
    }

    this._fieldStorage.setStateSilent(this._pathToField, {
      savedValue,
      dirty: calculateDirty(this.editedValue, savedValue),
    });
  }

  _setState(partlyState) {
    this._fieldStorage.setState(this._pathToField, partlyState);
  }

  /**
   * Init field's state.
   * @param {object} params - params which was passed to form init.
   * @private
   */
  _initState({ initial, disabled, defaultValue }) {
    const parsedInitial = parseValue(initial);
    const parsedDefaultValue = parseValue(defaultValue);

    // set initial value otherwise default value
    const newValue = (_.isUndefined(parsedInitial)) ? parsedDefaultValue : parsedInitial;
    const initialState = _.omitBy({
      disabled,
      defaultValue: parsedDefaultValue,
      initial: parsedInitial,
      // set initial value to edited layer
      editedValue: (_.isUndefined(newValue)) ? undefined : newValue,
    }, _.isUndefined);

    // init state
    this._fieldStorage.initState(this._pathToField, initialState);
  }

  /**
   * It calls form field on value changed by user
   * It rises a "change" event.
   * It rises only if value changed by user.
   * @param {string} pathToField
   * @param {*} oldValue
   * @param {*} newValue
   */
  _riseUserChangeEvent(pathToField, oldValue, newValue) {
    const eventData = {
      fieldName: pathToField,
      oldValue,
      value: newValue,
      event: 'change',
    };

    // call field's onChange handler
    const fieldOnChangeHandler = this._handlers.onChange;
    if (fieldOnChangeHandler) fieldOnChangeHandler(eventData);
    // call forms's onChange handler
    this._form.$callHandler('onChange', { [pathToField]: newValue });

    // Rise events field's change handler
    this._fieldStorage.emit(pathToField, 'change', eventData);
    // run form's change handler
    this._form.$emit('change', { [pathToField]: newValue });
  }

  /**
   * Start saving field and form if they have a corresponding handlers.
   * @param {boolean} isImmediately
   *   * if true it will save immediately.
   *   * if false it will save with debounce delay
   * @private
   * @return {Promise} - promise of saving process or just resolve if field isn't savable.
   */
  _addSavingToQueue(isImmediately) {

    // TODO: проверить логику - должно либо встать в очередь либо отменить текущее сохранение
    /*
     * If "force" param is true it will cancel current saving process before start a new one.
     * If "force" param is false it add this saving process to queue.
     */

    // do nothing if field isn't savable
    if (!this.savable) return Promise.resolve();

    // do save after debounce
    return this._debouncedCall.exec(this._doSave, isImmediately);
  }

  /**
   * Do field saving process.
   * * set "saving" state to true
   * * rise "saveStart" event
   * * call "save" callback. If it returns a promise - wait for it
   * and after saving ends:
   * * set "saving" state to false
   * * rise "saveEnd" event
   * @return {Promise|undefined} - if "save" callback returns promise this method returns it.
   * @private
   */
  _doSave() {
    // value is immutable
    const valueToSave = this.value;

    if (this._handlers.onSave) {
      // run save callback
      const cbPromise = this._handlers.onSave(valueToSave);

      if (isPromise(cbPromise)) {
        // set saving to true and rise storage event
        this._setState({ saving: true });
        this._emitSaveStart();

        return cbPromise
          .then((result) => {
            // set saving to false and rise storage event
            this._setState({ saving: false });
            this._afterSaveEnd(valueToSave, result);

            return result;
          })
          .catch((error) => {
            // set saving to false and rise storage event
            this._setState({ saving: false });
            this._afterSaveEnd(valueToSave, { error });

            return Promise.reject(error);
          });
      }
    }

    // if save callback hasn't returned a promise
    // or if there isn't a save callback

    this.$setStateSilent({ saving: true });
    this._emitSaveStart();
    this.$setStateSilent({ saving: false });
    this._afterSaveEnd(valueToSave);
  }

  _emitSaveStart(data) {
    this._fieldStorage.emit(this._pathToField, 'saveStart', data);
    this._form.$emit('saveStart', { path: this._pathToField, data });
  }

  _afterSaveEnd(valueWhichSaved, result) {
    this._fieldStorage.emit(this._pathToField, 'saveEnd', result);
    this._form.$emit('saveEnd', {
      path: this._pathToField,
      result,
      isSuccess: !(result && result.error),
    });

    this.$setValueAfterSave(valueWhichSaved);
  }

  _updateStateAndValidate(cbWhichChangesState) {
    this._updateState(() => {
      if (cbWhichChangesState) cbWhichChangesState();
      this.form.validate();
    });
  }

  _updateState(cbWhichChangesState) {
    const oldState = this._fieldStorage.getWholeState(this._pathToField);

    if (cbWhichChangesState) cbWhichChangesState();

    const newState = this._fieldStorage.getWholeState(this._pathToField);
    this._fieldStorage.emitStorageEvent(this._pathToField, 'update', newState, oldState);
  }

};
<commit_msg>refactor<commit_after>const _ = require('lodash');
const DebouncedCall = require('./DebouncedCall');
const { calculateDirty, getFieldName, isPromise, parseValue } = require('./helpers');

module.exports = class Field {
  constructor(pathToField, params, form, fieldStorage) {
    this._form = form;
    this._fieldStorage = fieldStorage;
    this._debouncedCall = new DebouncedCall(this._form.config.debounceTime);
    if (!_.isUndefined(params.debounceTime)) this.setDebounceTime(params.debounceTime);

    this._pathToField = pathToField;
    this._fieldName = getFieldName(pathToField);
    this._handlers = {
      onChange: undefined,
      onSave: undefined,
    };

    this._initState(params);

    this.handleChange = this.handleChange.bind(this);
    this.handleFocusIn = this.handleFocusIn.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
    this.handleEndEditing = this.handleEndEditing.bind(this);
    this.save = this.save.bind(this);
    this.clear = this.clear.bind(this);
    this.reset = this.reset.bind(this);
    this._doSave = this._doSave.bind(this);
    this._afterSaveEnd = this._afterSaveEnd.bind(this);
  }

  get form() {
    return this._form;
  }
  get savedValue() {
    return this._fieldStorage.getState(this._pathToField, 'savedValue');
  }
  get editedValue() {
    return this._fieldStorage.getState(this._pathToField, 'editedValue');
  }

  /**
   * Combined value
   * @return {*}
   */
  get value() {
    return this._fieldStorage.getCombinedValue(this._pathToField);
  }
  get name() {
    return this._fieldName;
  }
  get fullName() {
    return this._pathToField;
  }
  get dirty() {
    return this._fieldStorage.getState(this._pathToField, 'dirty');
  }
  get touched() {
    return this._fieldStorage.getState(this._pathToField, 'touched');
  }
  get valid() {
    return !this._fieldStorage.getState(this._pathToField, 'invalidMsg');
  }
  get invalidMsg() {
    return this._fieldStorage.getState(this._pathToField, 'invalidMsg');
  }
  get saving() {
    return this._fieldStorage.getState(this._pathToField, 'saving');
  }
  get savable() {
    return !this.canSave();
  }
  get focused() {
    return this._fieldStorage.getState(this._pathToField, 'focused');
  }
  get disabled() {
    return this._fieldStorage.getState(this._pathToField, 'disabled');
  }
  get defaultValue() {
    return this._fieldStorage.getState(this._pathToField, 'defaultValue');
  }
  get debounceTime() {
    return this._debouncedCall.delay;
  }

  /**
   * Check for field can be saved.
   * @return {string|undefined} - undefined means it can. Otherwise it returns a reason.
   */
  canSave() {
    // don't save invalid value
    if (!this.valid) return 'Field is invalid';

    // save only value which was modified.
    if (!this._fieldStorage.isFieldUnsaved(this._pathToField)) {
      return `Value hasn't modified`;
    }
  }

  /**
   * Set value silently(don't rise a change event).
   * It does:
   * * It set a new value to self instance and to storage
   * * It updates "dirty" and "valid" states.
   * * It rises storageChange event.
   *
   * It doesn't:
   * * It doesn't rise onChange callback (for user's events).
   * * It doesn't update "touched" state.
   * @param newValue
   */
  setValue(newValue) {
    this._updateStateAndValidate(() => {
      this.$setEditedValueSilent(newValue);
    });
  }

  /**
   * Set previously saved value. Usually it sets after server data has loading.
   * @param {*} newSavedValue
   */
  setSavedValue(newSavedValue) {
    this._updateStateAndValidate(() => {
      this.$setSavedValue(newSavedValue);
    });
  }

  setDisabled(value) {
    if (!_.isBoolean(value)) throw new Error(`Disabled has to be boolean`);
    this._setState({ disabled: value });
  }

  setDebounceTime(delay) {
    const toNumber = _.toNumber(delay);
    if (_.isNull(toNumber) || _.isNaN(toNumber)) throw new Error(`Bad debounceTime value`);
    // TODO: doesn't work
    this._debouncedCall.delay = toNumber;
  }

  /**
   * It's an onChange handler. It has to be placed to input's onChange attribute.
   * It sets a new value made by user and start saving.
   * It does:
   * * don't do anything if field is disabled
   * * don't save if value isn't changed
   * * update value
   * * update "touched" and "dirty" states
   * * validate form
   * * Rise a "change" events for field and form
   * * Run an onChange callback if it assigned.
   * * Start saving
   * @param {*} rawValue
   */
  handleChange(rawValue) {
    // don't do anything if disabled
    if (this.disabled) return;

    const newValue = parseValue(rawValue);
    // value is immutable
    const oldValue = this.value;
    const isChanged = !_.isEqual(oldValue, newValue);

    if (isChanged) {
      this._updateState(() => {
        // set editedValue and dirty state
        this.$setEditedValueSilent(newValue);
        this.form.validate();

        // set touched to true
        if (!this.touched) {
          this.$setStateSilent({ touched: true });
          this._form.$setStateSilent({ touched: true });
        }
      });
    }

    // rise change event and save only changed value
    if (!this._form.config.allowSaveUnmodifiedField && !isChanged) return;

    // rise change by user event handlers and callbacks of form and field
    this._riseUserChangeEvent(this._pathToField, oldValue, newValue);
    // start save with debounced delay
    this._addSavingToQueue(false);
  }

  /**
   * Set field's "focused" prop to true.
   */
  handleFocusIn() {
    this._setState({ focused: true });
  }

  /**
   * Set field's "focused" prop to false.
   */
  handleBlur() {
    this._setState({ focused: false });
    // start save immediately
    this.save();
  }

  /**
   * bind it to your component to onEnter event.
   * It does:
   * * cancel previous save in queue
   * * immediately starts save
   */
  handleEndEditing() {
    if (this.disabled) return;
    // start save immediately
    this.save();
  }

  /**
   * Add one or more handlers on fields's event:
   * * change
   * * storage
   * * saveStart
   * * saveEnd
   * @param eventName
   * @param cb
   */
  on(eventName, cb) {
    this._fieldStorage.on(this._pathToField, eventName, cb);
  }

  off(eventName, cb) {
    this._fieldStorage.off(this._pathToField, eventName, cb);
  }

  /**
   * It rises a callback on field's value changes which has made by user.
   * @param {function} handler - callback. You can set only one callback per field.
   */
  onChange(handler) {
    this._handlers.onChange = handler;
  }

  /**
   * It rises with debounce delay on start saving.
   * @param {function} handler - callback. If it returns a promise - saving process will wait for it.
   *                             You can set only one callback per field.
   */
  onSave(handler) {
    this._handlers.onSave = handler;
  }

  /**
   * Start saving of field's value immediately.
   * @return {Promise}
   */
  save() {
    return this._addSavingToQueue(true);
  }

  /**
   * Clear value(user input) and set initial value.
   */
  clear() {
    this._updateStateAndValidate(() => {
      this.$clearSilent();
    });
  }

  /**
   * set saved value to current value.
   */
  revert() {
    this._updateStateAndValidate(() => {
      this.$revertSilent();
    });
  }

  /**
   * Reset to default value
   */
  reset() {
    this._updateStateAndValidate(() => {
      this.$resetSilent();
    });
  }

  /**
   * Cancel debounce waiting for saving
   */
  cancelSaving() {
    this._debouncedCall.cancel();
  }

  /**
   * Saving immediately
   */
  flushSaving() {
    this._debouncedCall.flush();
  }

  $clearSilent() {
    const initial = this._fieldStorage.getState(this._pathToField, 'initial');
    this.$setEditedValueSilent(initial);
  }

  $revertSilent() {
    this.$setEditedValueSilent(this.savedValue);
  }

  $resetSilent() {
    this.$setEditedValueSilent(this.defaultValue);
  }

  $destroyHandlers() {
    this._handlers = {};
  }

  $setEditedValueSilent(newValue) {
    // set top value layer
    this.$setStateSilent({
      editedValue: newValue,
      dirty: calculateDirty(newValue, this.savedValue),
    });
  }

  $setSavedValue(newSavedValue) {
    const newState = {
      savedValue: newSavedValue,
      editedValue: this.editedValue,
    };

    // update user input if field isn't on focus and set dirty to false.
    // of course if it allows in config.
    if (this._form.config.allowFocusedFieldUpdating || (!this._form.config.allowFocusedFieldUpdating && !this.focused)) {
      // clear top level
      newState.editedValue = undefined;
    }

    newState.dirty = calculateDirty(newState.editedValue, newState.savedValue);

    this.$setStateSilent(newState);
  }

  $setStateSilent(newPartlyState) {
    this._fieldStorage.setStateSilent(this._pathToField, newPartlyState);
  }

  $setValueAfterSave(savedValue) {
    // if value hasn't changed after submit was started - clear it
    if (savedValue === this.value) {
      this._fieldStorage.setStateSilent(this._pathToField, { editedValue: undefined });
    }

    this._fieldStorage.setStateSilent(this._pathToField, {
      savedValue,
      dirty: calculateDirty(this.editedValue, savedValue),
    });
  }

  _setState(partlyState) {
    this._fieldStorage.setState(this._pathToField, partlyState);
  }

  /**
   * Init field's state.
   * @param {object} params - params which was passed to form init.
   * @private
   */
  _initState({ initial, disabled, defaultValue }) {
    const parsedInitial = parseValue(initial);
    const parsedDefaultValue = parseValue(defaultValue);

    // set initial value otherwise default value
    const newValue = (_.isUndefined(parsedInitial)) ? parsedDefaultValue : parsedInitial;
    const initialState = _.omitBy({
      disabled,
      defaultValue: parsedDefaultValue,
      initial: parsedInitial,
      // set initial value to edited layer
      editedValue: (_.isUndefined(newValue)) ? undefined : newValue,
    }, _.isUndefined);

    // init state
    this._fieldStorage.initState(this._pathToField, initialState);
  }

  /**
   * It calls form field on value changed by user
   * It rises a "change" event.
   * It rises only if value changed by user.
   * @param {string} pathToField
   * @param {*} oldValue
   * @param {*} newValue
   */
  _riseUserChangeEvent(pathToField, oldValue, newValue) {
    const eventData = {
      fieldName: pathToField,
      oldValue,
      value: newValue,
      event: 'change',
    };

    // call field's onChange handler
    const fieldOnChangeHandler = this._handlers.onChange;
    if (fieldOnChangeHandler) fieldOnChangeHandler(eventData);
    // call forms's onChange handler
    this._form.$callHandler('onChange', { [pathToField]: newValue });

    // Rise events field's change handler
    this._fieldStorage.emit(pathToField, 'change', eventData);
    // run form's change handler
    this._form.$emit('change', { [pathToField]: newValue });
  }

  /**
   * Start saving field and form if they have a corresponding handlers.
   * @param {boolean} isImmediately
   *   * if true it will save immediately.
   *   * if false it will save with debounce delay
   * @private
   * @return {Promise} - promise of saving process or just resolve if field isn't savable.
   */
  _addSavingToQueue(isImmediately) {

    // TODO: проверить логику - должно либо встать в очередь либо отменить текущее сохранение
    /*
     * If "force" param is true it will cancel current saving process before start a new one.
     * If "force" param is false it add this saving process to queue.
     */

    // do nothing if field isn't savable
    if (!this.savable) return Promise.resolve();

    // do save after debounce
    return this._debouncedCall.exec(this._doSave, isImmediately);
  }

  /**
   * Do field saving process.
   * * set "saving" state to true
   * * rise "saveStart" event
   * * call "save" callback. If it returns a promise - wait for it
   * and after saving ends:
   * * set "saving" state to false
   * * rise "saveEnd" event
   * @return {Promise|undefined} - if "save" callback returns promise this method returns it.
   * @private
   */
  _doSave() {
    // value is immutable
    const valueToSave = this.value;

    if (this._handlers.onSave) {
      // run save callback
      const cbPromise = this._handlers.onSave(valueToSave);

      if (isPromise(cbPromise)) {
        // set saving to true and rise storage event
        this._setState({ saving: true });
        this._emitSaveStart();

        return cbPromise
          .then((result) => {
            // set saving to false and rise storage event
            this._setState({ saving: false });
            this._afterSaveEnd(valueToSave, result);

            return result;
          })
          .catch((error) => {
            // set saving to false and rise storage event
            this._setState({ saving: false });
            this._afterSaveEnd(valueToSave, { error });

            return Promise.reject(error);
          });
      }
    }

    // if save callback hasn't returned a promise
    // or if there isn't a save callback

    this.$setStateSilent({ saving: true });
    this._emitSaveStart();
    this.$setStateSilent({ saving: false });
    this._afterSaveEnd(valueToSave);
  }

  _emitSaveStart(data) {
    this._fieldStorage.emit(this._pathToField, 'saveStart', data);
    this._form.$emit('saveStart', { path: this._pathToField, data });
  }

  _afterSaveEnd(valueWhichSaved, result) {
    this._fieldStorage.emit(this._pathToField, 'saveEnd', result);
    this._form.$emit('saveEnd', {
      path: this._pathToField,
      result,
      isSuccess: !(result && result.error),
    });

    this.$setValueAfterSave(valueWhichSaved);
  }

  _updateStateAndValidate(cbWhichChangesState) {
    this._updateState(() => {
      if (cbWhichChangesState) cbWhichChangesState();
      this.form.validate();
    });
  }

  _updateState(cbWhichChangesState) {
    const oldState = this._fieldStorage.getWholeState(this._pathToField);

    if (cbWhichChangesState) cbWhichChangesState();

    const newState = this._fieldStorage.getWholeState(this._pathToField);
    this._fieldStorage.emitStorageEvent(this._pathToField, 'update', newState, oldState);
  }

};
<|endoftext|>