<commit_before>import {$, addClass, append, css, hasClass, on, once, Promise, removeClass, toMs, width, within} from 'uikit-util';
import Class from './class';
import Container from './container';
import Togglable from './togglable';

let active;

export default {

    mixins: [Class, Container, Togglable],

    props: {
        selPanel: String,
        selClose: String,
        escClose: Boolean,
        bgClose: Boolean,
        stack: Boolean
    },

    data: {
        cls: 'uk-open',
        escClose: true,
        bgClose: true,
        overlay: true,
        stack: false
    },

    computed: {

        panel({selPanel}, $el) {
            return $(selPanel, $el);
        },

        transitionElement() {
            return this.panel;
        },

        transitionDuration() {
            return toMs(css(this.transitionElement, 'transitionDuration'));
        }

    },

    events: [

        {

            name: 'click',

            delegate() {
                return this.selClose;
            },

            handler(e) {
                e.preventDefault();
                this.hide();
            }

        },

        {

            name: 'toggle',

            self: true,

            handler(e) {

                if (e.defaultPrevented) {
                    return;
                }

                e.preventDefault();
                this.toggle();
            }

        },

        {
            name: 'beforeshow',

            self: true,

            handler(e) {

                const prev = active && active !== this && active;

                active = this;

                if (prev) {
                    if (this.stack) {
                        this.prev = prev;
                    } else {
                        prev.hide().then(this.show);
                        e.preventDefault();
                        return;
                    }
                }

                registerEvents();

            }

        },

        {
            name: 'beforehide',

            self: true,

            handler() {

                active = active && active !== this && active || this.prev;

                if (!active) {
                    deregisterEvents();
                }

            }

        },

        {

            name: 'show',

            self: true,

            handler() {

                if (!hasClass(document.documentElement, this.clsPage)) {
                    this.scrollbarWidth = width(window) - width(document);
                    css(document.body, 'overflowY', this.scrollbarWidth && this.overlay ? 'scroll' : '');
                }

                addClass(document.documentElement, this.clsPage);

            }

        },

        {

            name: 'hidden',

            self: true,

            handler() {

                let found, {prev} = this;

                while (prev) {

                    if (prev.clsPage === this.clsPage) {
                        found = true;
                        break;
                    }

                    prev = prev.prev;

                }

                if (!found) {
                    removeClass(document.documentElement, this.clsPage);

                }

                !this.prev && css(document.body, 'overflowY', '');
            }

        }

    ],

    methods: {

        toggle() {
            return this.isToggled() ? this.hide() : this.show();
        },

        show() {

            if (this.isToggled()) {
                return;
            }

            if (this.container && this.$el.parentNode !== this.container) {
                append(this.container, this.$el);
                this._callConnected();
            }

            return this.toggleNow(this.$el, true);
        },

        hide() {
            if (this.isToggled()) {
                return this.toggleNow(this.$el, false);
            }
        },

        getActive() {
            return active;
        },

        _toggleImmediate(el, show) {
            return new Promise(resolve =>
                requestAnimationFrame(() => {
                    this._toggle(el, show);

                    if (this.transitionDuration) {
                        once(this.transitionElement, 'transitionend', resolve, false, e => e.target === this.transitionElement);
                    } else {
                        resolve();
                    }
                })
            );
        }

    }

};

let events;

function registerEvents() {

    if (events) {
        return;
    }

    events = [
        on(document, 'click', ({target, defaultPrevented}) => {
            if (active && active.bgClose && !defaultPrevented && (!active.overlay || within(target, active.$el)) && (!active.panel || !within(target, active.panel))) {
                active.hide();
            }
        }),
        on(document, 'keydown', e => {
            if (e.keyCode === 27 && active && active.escClose) {
                e.preventDefault();
                active.hide();
            }
        })
    ];
}

function deregisterEvents() {
    events && events.forEach(unbind => unbind());
    events = null;
}
<commit_msg>update modal<commit_after>import {$, addClass, append, css, hasClass, on, once, Promise, removeClass, toMs, width, within} from 'uikit-util';
import Class from './class';
import Container from './container';
import Togglable from './togglable';

let active;

export default {

    mixins: [Class, Container, Togglable],

    props: {
        selPanel: String,
        selClose: String,
        escClose: Boolean,
        bgClose: Boolean,
        stack: Boolean
    },

    data: {
        cls: 'uk-open',
        escClose: true,
        bgClose: true,
        overlay: true,
        stack: false
    },

    computed: {

        panel({selPanel}, $el) {
            return $(selPanel, $el);
        },

        transitionElement() {
            return this.panel;
        },

        transitionDuration() {
            return toMs(css(this.transitionElement, 'transitionDuration'));
        }

    },

    events: [

        {

            name: 'click',

            delegate() {
                return this.selClose;
            },

            handler(e) {
                e.preventDefault();
                this.hide();
            }

        },

        {

            name: 'toggle',

            self: true,

            handler(e) {

                if (e.defaultPrevented) {
                    return;
                }

                e.preventDefault();
                this.toggle();
            }

        },

        {
            name: 'beforeshow',

            self: true,

            handler(e) {

                const prev = active && active !== this && active;

                active = this;

                if (prev) {
                    if (this.stack) {
                        this.prev = prev;
                    } else {
                        prev.hide().then(this.show);
                        e.preventDefault();
                        return;
                    }
                }

                registerEvents();

            }

        },

        {
            name: 'beforehide',

            self: true,

            handler() {

                active = active && active !== this && active || this.prev;

                if (!active) {
                    deregisterEvents();
                }

            }

        },

        {

            name: 'show',

            self: true,

            handler() {

                if (!hasClass(document.documentElement, this.clsPage)) {
                    this.scrollbarWidth = width(window) - width(document);
                    css(document.body, 'overflowY', this.scrollbarWidth && this.overlay ? 'scroll' : '');
                }

                addClass(document.documentElement, this.clsPage);

            }

        },

        {

            name: 'hidden',

            self: true,

            handler() {

                let found, {prev} = this;

                while (prev) {

                    if (prev.clsPage === this.clsPage) {
                        found = true;
                        break;
                    }

                    prev = prev.prev;

                }

                if (!found) {
                    removeClass(document.documentElement, this.clsPage);

                }

                !this.prev && css(document.body, 'overflowY', '');
            }

        }

    ],

    methods: {

        toggle() {
            return this.isToggled() ? this.hide() : this.show();
        },

        show() {

            if (this.isToggled()) {
                return Promise.resolve();
            }

            if (this.container && this.$el.parentNode !== this.container) {
                append(this.container, this.$el);
                this._callConnected();
            }

            return this.toggleNow(this.$el, true);
        },

        hide() {
            return this.isToggled()
                ? this.toggleNow(this.$el, false)
                : Promise.resolve();
        },

        getActive() {
            return active;
        },

        _toggleImmediate(el, show) {
            return new Promise(resolve =>
                requestAnimationFrame(() => {
                    this._toggle(el, show);

                    if (this.transitionDuration) {
                        once(this.transitionElement, 'transitionend', resolve, false, e => e.target === this.transitionElement);
                    } else {
                        resolve();
                    }
                })
            );
        }

    }

};

let events;

function registerEvents() {

    if (events) {
        return;
    }

    events = [
        on(document, 'click', ({target, defaultPrevented}) => {
            if (active && active.bgClose && !defaultPrevented && (!active.overlay || within(target, active.$el)) && (!active.panel || !within(target, active.panel))) {
                active.hide();
            }
        }),
        on(document, 'keydown', e => {
            if (e.keyCode === 27 && active && active.escClose) {
                e.preventDefault();
                active.hide();
            }
        })
    ];
}

function deregisterEvents() {
    events && events.forEach(unbind => unbind());
    events = null;
}
<|endoftext|>


<commit_before>(function(Factlink, $, _, easyXDM, window, undefined) {

Factlink.Fact = function() {
  var elements;
  // This is to scare Mark:
  var timeout;
  var highlight_timeout;

  // The balloon which corresponds to this Fact
  var balloon;

  var _obj;
  // For use inside other function scopes
  var self = this;
  // Internal object which will hold all bound event handlers
  var _bound_events = {};
  // If you want to support more events add them to this variable:
  var _events = ["focus", "blur", "click", "update"];

  function initialize(id, elems, opinions) {
    elements = elems;

    _obj = {
      "id": id,
      "opinions": opinions
    };

    createEventHandlers(_events);

    highlight();

    balloon = new Factlink.Balloon(id, self);

    // Bind the own events
    $(elements)
      .bind('mouseenter', self.focus)
      .bind('mouseleave', self.blur)
      .bind('click', self.click);

    bindFocus();

    bindClick(id);

    stopHighlighting(1500);
  }

  // This may look like some magic, but here we expose the Fact.blur/focus/click
  // methods
  function createEventHandlers(events) {
    for ( var i = 0; i < events.length; i++ ) {
      _bound_events[events[i]] = [];

      self[events[i]] = (function(i) {
        var e = events[i];

        return function() {
          var args = [e].concat(Array.prototype.slice.call(arguments));

          if ( $.isFunction( args[1] ) ) {
            bind.apply(this, args);
          } else {
            trigger.apply(this, args);
          }
        };
      })(i);
    }
  }

  function bind(type, fn) {
    _bound_events[type].push(fn);
  }

  function trigger(type) {
    var args = Array.prototype.slice.call(arguments);

    args.shift();

    for (var i = 0; i < _bound_events[type].length; i++) {
      _bound_events[type][i].apply(this, args);
    }
  }

  function highlight() {
    clearTimeout(highlight_timeout);

    $( elements ).addClass('fl-active');
  }

  function stopHighlighting(timer) {
    clearTimeout(highlight_timeout);

    if ( timer ) {
      highlight_timeout = setTimeout(function() {
        $( elements ).removeClass('fl-active');
      }, timer);
    } else {
      $( elements ).removeClass('fl-active');
    }
  }

  function bindFocus() {
    self.focus(function(e) {
      clearTimeout(timeout);

      highlight();

      if ( ! balloon.isVisible() ) {
        // Need to call a direct .hide() here to make sure not two popups are
        // open at a time
        Factlink.el.find('div.fl-popup').hide();
        balloon.show($(e.target).offset().top, e.pageX, e.show_fast);
      }
    });

    self.blur(function(e) {
      clearTimeout(timeout);

      stopHighlighting();

      timeout = setTimeout(function(){
        balloon.hide();
      }, 300);
    });
  }

  function bindClick(id) {
    self.click(function(e) {
      var self = this;
      // A custom switch-like module
      var modusHandler = (function() {
        return {
          "default": function() {
            Factlink.showInfo(id);
          },
          addToFact: function() {
            Factlink.prepare.show(e.pageX, e.pageY);
            Factlink.prepare.setFactId(id);
          }
        };
      })();
      modusHandler[FactlinkConfig.modus]();
    });
  }

  this.getObject = function() {
    return _obj;
  };

  this.getElements = function() {
    return elements;
  };

  this.destroy = function() {
    for ( var i = 0; i < elements.length; i++ ) {
      var $el = $(elements[i]);

      var html = $el.html();

      if ( ! $el.is('.fl-first') ) {
        $el.before(html);
      }

      $el.remove();
    }

    balloon.destroy();
  };

  initialize.apply(this, arguments);
};

})(window.Factlink, Factlink.$, Factlink._, Factlink.easyXDM, Factlink.global);
<commit_msg>use native offsetTop since .offset() does not work together with margin's<commit_after>(function(Factlink, $, _, easyXDM, window, undefined) {

Factlink.Fact = function() {
  var elements;
  // This is to scare Mark:
  var timeout;
  var highlight_timeout;

  // The balloon which corresponds to this Fact
  var balloon;

  var _obj;
  // For use inside other function scopes
  var self = this;
  // Internal object which will hold all bound event handlers
  var _bound_events = {};
  // If you want to support more events add them to this variable:
  var _events = ["focus", "blur", "click", "update"];

  function initialize(id, elems, opinions) {
    elements = elems;

    _obj = {
      "id": id,
      "opinions": opinions
    };

    createEventHandlers(_events);

    highlight();

    balloon = new Factlink.Balloon(id, self);

    // Bind the own events
    $(elements)
      .bind('mouseenter', self.focus)
      .bind('mouseleave', self.blur)
      .bind('click', self.click);

    bindFocus();

    bindClick(id);

    stopHighlighting(1500);
  }

  // This may look like some magic, but here we expose the Fact.blur/focus/click
  // methods
  function createEventHandlers(events) {
    for ( var i = 0; i < events.length; i++ ) {
      _bound_events[events[i]] = [];

      self[events[i]] = (function(i) {
        var e = events[i];

        return function() {
          var args = [e].concat(Array.prototype.slice.call(arguments));

          if ( $.isFunction( args[1] ) ) {
            bind.apply(this, args);
          } else {
            trigger.apply(this, args);
          }
        };
      })(i);
    }
  }

  function bind(type, fn) {
    _bound_events[type].push(fn);
  }

  function trigger(type) {
    var args = Array.prototype.slice.call(arguments);

    args.shift();

    for (var i = 0; i < _bound_events[type].length; i++) {
      _bound_events[type][i].apply(this, args);
    }
  }

  function highlight() {
    clearTimeout(highlight_timeout);

    $( elements ).addClass('fl-active');
  }

  function stopHighlighting(timer) {
    clearTimeout(highlight_timeout);

    if ( timer ) {
      highlight_timeout = setTimeout(function() {
        $( elements ).removeClass('fl-active');
      }, timer);
    } else {
      $( elements ).removeClass('fl-active');
    }
  }

  function bindFocus() {
    self.focus(function(e) {
      clearTimeout(timeout);

      highlight();

      if ( ! balloon.isVisible() ) {
        // Need to call a direct .hide() here to make sure not two popups are
        // open at a time
        Factlink.el.find('div.fl-popup').hide();
        balloon.show($(e.target)[0].offsetTop, e.pageX, e.show_fast);
      }
    });

    self.blur(function(e) {
      clearTimeout(timeout);

      stopHighlighting();

      timeout = setTimeout(function(){
        balloon.hide();
      }, 300);
    });
  }

  function bindClick(id) {
    self.click(function(e) {
      var self = this;
      // A custom switch-like module
      var modusHandler = (function() {
        return {
          "default": function() {
            Factlink.showInfo(id);
          },
          addToFact: function() {
            Factlink.prepare.show(e.pageX, e.pageY);
            Factlink.prepare.setFactId(id);
          }
        };
      })();
      modusHandler[FactlinkConfig.modus]();
    });
  }

  this.getObject = function() {
    return _obj;
  };

  this.getElements = function() {
    return elements;
  };

  this.destroy = function() {
    for ( var i = 0; i < elements.length; i++ ) {
      var $el = $(elements[i]);

      var html = $el.html();

      if ( ! $el.is('.fl-first') ) {
        $el.before(html);
      }

      $el.remove();
    }

    balloon.destroy();
  };

  initialize.apply(this, arguments);
};

})(window.Factlink, Factlink.$, Factlink._, Factlink.easyXDM, Factlink.global);
<|endoftext|>


<commit_before>import React from "react";
import WordlistStore from "../stores/WordlistStore";
import _ from "lodash";
import Notes from "../components/verb/Notes.js";
import Debouncer from "../components/Debouncer.js";

export default React.createClass({
  getInitialState: function() {
    return {
      words: undefined
    };
  },

  loadSample: function(sampleSize) {

   const captionMapping = {
      'imperfect_singular': 'Imperfectum singular',
      'imperfect_plural': 'Imperfectum plural',
      'perfect': 'Perfectum',
    };

    const wordToStateful = (w) => {
    };
    const words =
      _.sampleSize(WordlistStore.getAll(), sampleSize)
      .map((w)=>{
        const verb = _.pick(w, 'infinitive', 'notes');
        verb.forms = _.mapValues(w.forms, (form, key)=>{
          return {
            key,
            expected: form,
            actual: '',
            caption: captionMapping[key],
          };
        });
        return verb;
      });

    return words;
  },

  onGo: function(size) {
    this.setState({words: this.loadSample(size)})
  },

  componentWillReceiveProps: function(newProps) {
    this.setState(this.getInitialState());
  },

  render: function() {
    if (this.state.words) {
      return <RandomRow words={this.state.words} onDone={this.componentWillReceiveProps.bind(this)} />
    } else {
      return <SizeInput onGo={this.onGo}/>
    }
  }

});

const SizeInput = React.createClass({
  getInitialState: function() {return {value: 15};},
  setValue: function(newValue) {
    this.setState({value: parseInt(newValue)});
  },
  onGoClick: function() {
    if (this.state.value > 0) {
      this.props.onGo(this.state.value);
    }
  },
  render: function() {
    const valueLink = {
      value: this.state.value,
      requestChange: this.setValue
    };
    return (
      <form class="form-inline">

          Sample size
          <input type="number" class="form-control" id="sampleSize" autoFocus valueLink={valueLink}/>

        <button onClick={this.onGoClick} type="submit" class="btn btn-default">Randomize</button>
      </form>
    );
  }
});

const RandomRow = React.createClass({

  getInitialState: function() {
    return {
      position: 0,
      showAnswers : false,
    };
  },

  getCurrentWord: function() {
    return this.props.words[this.state.position];
  },

  onNextClick: function() {
    if (this.isEof()) {
      this.props.onDone();
    } else {
      this.setState({
        position: this.state.position + 1,
        showAnswers: false,
      });
    }
  },

  onHelpClickHandler: function() {
    this.setState({showAnswers: true});
  },

  isEof: function() {
    return this.state.position + 1 >= this.props.words.length;
  },

  render: function() {
    const verb = this.getCurrentWord();
    return  <VerbEntryForm key={verb.infinitive} verb={verb} showAnswers={this.state.showAnswers}>
              <button type="submit" class="btn btn-default" onClick={this.onNextClick}>{this.isEof() ? 'Opnieuw' : 'Next'}</button>
              <button type="submit" class="btn" onClick={this.onHelpClickHandler}>Help!</button>
            </VerbEntryForm>
  }
});

const VerbEntryForm = React.createClass({

  getInitialState: function() {
    return {
      verb: this.props.verb,
    };
  },

  render: function() {
    const {showAnswers} = this.props;
    const {verb} = this.state;
    const that = this;
    function onChangeHandlerFactory (key) {
      return function(e) {
        const newValue = e.target.value.toLowerCase();
        verb.forms[key].actual = newValue;
        that.setState({verb});
      }
    }

    const inputFields = ['imperfect_singular', 'imperfect_plural', 'perfect'].map((key, index) => {
        const form = verb.forms[key];
        return <FormGroup key={key} id={key} caption={form.caption} autoFocus={index == 0}
          text={form.expected} showAnswer={showAnswers} placeholder={verb.infinitive}
          value={form.actual} onChange={onChangeHandlerFactory(key)} />
    });

    return (
      <form onKeyDown={this._onKeyDown}>

{verb.infinitive}

<Notes text={verb.notes}/>

        {inputFields}
        {this.props.children}
      </form>
    );
  },

  _allAnsweredCorrectly: function() {
    const {verb} = this.state;
    const anyFalse = _.find(verb.forms, (form) => {
      return form.actual != form.expected;
    });
    return !anyFalse;
  },

  _onKeyDown : function(event) {
    const ENTER_KEY_CODE = 13;
    if (event.keyCode == ENTER_KEY_CODE && !this._allAnsweredCorrectly()) {
      event.preventDefault();
      return false;
    }
    return false;
  },

});

const FormGroup = React.createClass({
  mixins: [Debouncer(function(){this.reEvaluate()})],

  getInitialState: function() {return {evaluationClass: ''}},

  evaluateAnswer: function() {
     return (this.props.value == this.props.text) ? 'has-success' : 'has-error';
  },

  reEvaluate: function() {
    this.setValidationStatus(this.evaluateAnswer());
  },

  withdrawEvaluation: function() {
    this.setValidationStatus('');
  },

  setValidationStatus: function(evaluationClass) {
    this.setState({evaluationClass});
  },

  componentWillReceiveProps: function(newProps) {
    if (this.props.text != newProps.text || this.props.value != newProps.value) {
      this.withdrawEvaluation();
      this.debounce();
    }
  },

  render: function() {
    const {id, caption, showAnswer, ...rest} = this.props;

    return

            {caption}{showAnswer ? ' -> ' + this.props.text : ''}
            <input {...rest}
              id={id}
              className="form-control input-lg"
              type="text"
              autoComplete="off"
              autoCapitalize="none"
              autoCorrect="off"
              onBlur={this.reEvaluate}
              />

  }
});<commit_msg>cleanup<commit_after>import React from "react";
import WordlistStore from "../stores/WordlistStore";
import _ from "lodash";
import Notes from "../components/verb/Notes.js";
import Debouncer from "../components/Debouncer.js";

export default React.createClass({
  getInitialState: function() {
    return {
      words: undefined
    };
  },

  loadSample: function(sampleSize) {

   const captionMapping = {
      'imperfect_singular': 'Imperfectum singular',
      'imperfect_plural': 'Imperfectum plural',
      'perfect': 'Perfectum',
    };

    const words =
      _.sampleSize(WordlistStore.getAll(), sampleSize)
      .map((w)=>{
        const verb = _.pick(w, 'infinitive', 'notes');
        verb.forms = _.mapValues(w.forms, (form, key)=>{
          return {
            key,
            expected: form,
            actual: '',
            caption: captionMapping[key],
          };
        });
        return verb;
      });

    return words;
  },

  onGo: function(size) {
    this.setState({words: this.loadSample(size)})
  },

  componentWillReceiveProps: function(newProps) {
    this.setState(this.getInitialState());
  },

  render: function() {
    if (this.state.words) {
      return <RandomRow words={this.state.words} onDone={this.componentWillReceiveProps.bind(this)} />
    } else {
      return <SizeInput onGo={this.onGo}/>
    }
  }

});

const SizeInput = React.createClass({
  getInitialState: function() {return {value: 15};},
  setValue: function(newValue) {
    this.setState({value: parseInt(newValue)});
  },
  onGoClick: function() {
    if (this.state.value > 0) {
      this.props.onGo(this.state.value);
    }
  },
  render: function() {
    const valueLink = {
      value: this.state.value,
      requestChange: this.setValue
    };
    return (
      <form class="form-inline">

          Sample size
          <input type="number" class="form-control" id="sampleSize" autoFocus valueLink={valueLink}/>

        <button onClick={this.onGoClick} type="submit" class="btn btn-default">Randomize</button>
      </form>
    );
  }
});

const RandomRow = React.createClass({

  getInitialState: function() {
    return {
      position: 0,
      showAnswers : false,
    };
  },

  getCurrentWord: function() {
    return this.props.words[this.state.position];
  },

  onNextClick: function() {
    if (this.isEof()) {
      this.props.onDone();
    } else {
      this.setState({
        position: this.state.position + 1,
        showAnswers: false,
      });
    }
  },

  onHelpClickHandler: function() {
    this.setState({showAnswers: true});
  },

  isEof: function() {
    return this.state.position + 1 >= this.props.words.length;
  },

  render: function() {
    const verb = this.getCurrentWord();
    return  <VerbEntryForm key={verb.infinitive} verb={verb} showAnswers={this.state.showAnswers}>
              <button type="submit" class="btn btn-default" onClick={this.onNextClick}>{this.isEof() ? 'Opnieuw' : 'Next'}</button>
              <button type="submit" class="btn" onClick={this.onHelpClickHandler}>Help!</button>
            </VerbEntryForm>
  }
});

const VerbEntryForm = React.createClass({

  getInitialState: function() {
    return {
      verb: this.props.verb,
    };
  },

  render: function() {
    const {showAnswers} = this.props;
    const {verb} = this.state;
    const that = this;
    function onChangeHandlerFactory (key) {
      return function(e) {
        const newValue = e.target.value.toLowerCase();
        verb.forms[key].actual = newValue;
        that.setState({verb});
      }
    }

    const inputFields = ['imperfect_singular', 'imperfect_plural', 'perfect'].map((key, index) => {
        const form = verb.forms[key];
        return <FormGroup key={key} id={key} caption={form.caption} autoFocus={index == 0}
          text={form.expected} showAnswer={showAnswers} placeholder={verb.infinitive}
          value={form.actual} onChange={onChangeHandlerFactory(key)} />
    });

    return (
      <form onKeyDown={this._onKeyDown}>

{verb.infinitive}

<Notes text={verb.notes}/>

        {inputFields}
        {this.props.children}
      </form>
    );
  },

  _allAnsweredCorrectly: function() {
    const {verb} = this.state;
    const anyFalse = _.find(verb.forms, (form) => {
      return form.actual != form.expected;
    });
    return !anyFalse;
  },

  _onKeyDown : function(event) {
    const ENTER_KEY_CODE = 13;
    if (event.keyCode == ENTER_KEY_CODE && !this._allAnsweredCorrectly()) {
      event.preventDefault();
      return false;
    }
    return false;
  },

});

const FormGroup = React.createClass({
  mixins: [Debouncer(function(){this.reEvaluate()})],

  getInitialState: function() {return {evaluationClass: ''}},

  evaluateAnswer: function() {
     return (this.props.value == this.props.text) ? 'has-success' : 'has-error';
  },

  reEvaluate: function() {
    this.setValidationStatus(this.evaluateAnswer());
  },

  withdrawEvaluation: function() {
    this.setValidationStatus('');
  },

  setValidationStatus: function(evaluationClass) {
    this.setState({evaluationClass});
  },

  componentWillReceiveProps: function(newProps) {
    if (this.props.text != newProps.text || this.props.value != newProps.value) {
      this.withdrawEvaluation();
      this.debounce();
    }
  },

  render: function() {
    const {id, caption, showAnswer, ...rest} = this.props;

    return

            {caption}{showAnswer ? ' -> ' + this.props.text : ''}
            <input {...rest}
              id={id}
              className="form-control input-lg"
              type="text"
              autoComplete="off"
              autoCapitalize="none"
              autoCorrect="off"
              onBlur={this.reEvaluate}
              />

  }
});<|endoftext|>


<commit_before>import React from "react";
import WordlistStore from "../stores/WordlistStore";
import _ from "lodash";
import Notes from "../components/verb/Notes.js";
import Debouncer from "../components/Debouncer.js";

export default React.createClass({
  getInitialState: function() {
    return {
      words: undefined
    };
  },

  loadSample: function(sampleSize) {
    const wordToStateful = (w) => {
      return {
        expected: w,
        actual: '',
      };
    };
    const words = _.sampleSize(WordlistStore.getAll(), sampleSize)
      .map((w)=>{
        const verb = _.extend({}, w);
        verb.imperfect.singular = wordToStateful(verb.imperfect.singular);
        verb.imperfect.plural = wordToStateful(verb.imperfect.plural);
        verb.perfect = wordToStateful(verb.perfect);
        return verb;
    });

    return words;
  },

  onGo: function(size) {
    this.setState({words: this.loadSample(size)})
  },

  componentWillReceiveProps: function(newProps) {
    this.setState(this.getInitialState());
  },

  render: function() {
    if (this.state.words) {
      return <RandomRow words={this.state.words} onDone={this.componentWillReceiveProps.bind(this)} />
    } else {
      return <SizeInput onGo={this.onGo}/>
    }
  }

});

const SizeInput = React.createClass({
  getInitialState: function() {return {value: 15};},
  setValue: function(newValue) {
    this.setState({value: parseInt(newValue)});
  },
  onGoClick: function() {
    if (this.state.value > 0) {
      this.props.onGo(this.state.value);
    }
  },
  render: function() {
    const valueLink = {
      value: this.state.value,
      requestChange: this.setValue
    };
    return (
      <form class="form-inline">

          Sample size
          <input type="number" class="form-control" id="sampleSize" autoFocus valueLink={valueLink}/>

        <button onClick={this.onGoClick} type="submit" class="btn btn-default">Randomize</button>
      </form>
    );
  }
});

const RandomRow = React.createClass({

  getInitialState: function() {
    return {
      position: 0,
      showAnswers : false,
    };
  },

  getCurrentWord: function() {
    return this.props.words[this.state.position];
  },

  onNextClick: function() {
    if (this.isEof()) {
      this.props.onDone();
    } else {
      this.setState({
        position: this.state.position + 1,
        showAnswers: false,
      });
    }
  },

  onHelpClickHandler: function() {
    this.setState({showAnswers: true});
  },

  isEof: function() {
    return this.state.position + 1 >= this.props.words.length;
  },

  render: function() {
    const verb = this.getCurrentWord();
    return  <VerbEntryForm key={verb.infinitive} verb={verb} showAnswers={this.state.showAnswers}>
              <button type="submit" class="btn btn-default" onClick={this.onNextClick}>{this.isEof() ? 'Opnieuw' : 'Next'}</button>
              <button type="submit" class="btn" onClick={this.onHelpClickHandler}>Help!</button>
            </VerbEntryForm>
  }
});

const VerbEntryForm = React.createClass({

  getInitialState: function() {
    return {
      verb: this.props.verb,
    };
  },

  render: function() {
    const {showAnswers} = this.props;
    const {verb} = this.state;
    const that = this;
    function onChangeHandlerFactory (key) {
      return function(e) {
        const newValue = e.target.value.toLowerCase();
        verb[key].actual = newValue;
        that.setState({verb});
      }
    }
    return (
      <form>

{verb.infinitive}

<Notes text={verb.notes}/>

        <FormGroup id="impf.s" caption="Imperfectum singular" autoFocus
          text={verb.imperfect.singular.expected} showAnswer={showAnswers} />
        <FormGroup id="impf.p" caption="Imperfectum plural"
          text={verb.imperfect.plural.expected} showAnswer={showAnswers} />
        <FormGroup id="impf.s" caption="Perfectum"
          text={verb.perfect.expected} showAnswer={showAnswers} value={verb.perfect.actual} onChange={onChangeHandlerFactory('perfect')}/>
        {this.props.children}
      </form>
    );
  }
});

const FormGroup = React.createClass({
  mixins: [Debouncer(function(validationClass){this.setValidationStatus(validationClass)})],

  getInitialState: function() {return {validationClass:'', value: ''}},

  setValidationStatus: function (validationClass) {
    this.setState({validationClass});
  },

  onValueChangeHandler: function(e) {
    if (this.props.onChange) {
      this.props.onChange(e);
    }
    const value = e.target.value.toLowerCase();
    this.setState({value});
    this.setValidationStatus('');
    this.debounce((value == this.props.text) ? 'has-success' : 'has-error')
  },

  render: function() {
    const {id, caption, showAnswer, ...rest} = this.props;

    var value;
    if (rest.onChange) {
      value = rest.value;
    } else {
      value = this.state.value;
    }

    return

            {caption}{showAnswer ? ' -> ' + this.props.text : ''}
            <input {...rest}
              id={id}
              className="form-control input-lg"
              type="text"
              autoComplete="off"
              autoCapitalize="none"
              autoCorrect="off"
              placeholder={caption}
              value={value}
              onChange={this.onValueChangeHandler}/>

  }
});<commit_msg>flattened structure<commit_after>import React from "react";
import WordlistStore from "../stores/WordlistStore";
import _ from "lodash";
import Notes from "../components/verb/Notes.js";
import Debouncer from "../components/Debouncer.js";

export default React.createClass({
  getInitialState: function() {
    return {
      words: undefined
    };
  },

  loadSample: function(sampleSize) {
    const wordToStateful = (w) => {
      return {
        expected: w,
        actual: '',
      };
    };
    const words = _.sampleSize(WordlistStore.getAll(), sampleSize)
      .map((w)=>{
        const verb = _.extend({}, w);
        verb.imperfect_singular = wordToStateful(verb.imperfect.singular);
        verb.imperfect_plural = wordToStateful(verb.imperfect.plural);
        verb.perfect = wordToStateful(verb.perfect);
        return verb;
    });

    return words;
  },

  onGo: function(size) {
    this.setState({words: this.loadSample(size)})
  },

  componentWillReceiveProps: function(newProps) {
    this.setState(this.getInitialState());
  },

  render: function() {
    if (this.state.words) {
      return <RandomRow words={this.state.words} onDone={this.componentWillReceiveProps.bind(this)} />
    } else {
      return <SizeInput onGo={this.onGo}/>
    }
  }

});

const SizeInput = React.createClass({
  getInitialState: function() {return {value: 15};},
  setValue: function(newValue) {
    this.setState({value: parseInt(newValue)});
  },
  onGoClick: function() {
    if (this.state.value > 0) {
      this.props.onGo(this.state.value);
    }
  },
  render: function() {
    const valueLink = {
      value: this.state.value,
      requestChange: this.setValue
    };
    return (
      <form class="form-inline">

          Sample size
          <input type="number" class="form-control" id="sampleSize" autoFocus valueLink={valueLink}/>

        <button onClick={this.onGoClick} type="submit" class="btn btn-default">Randomize</button>
      </form>
    );
  }
});

const RandomRow = React.createClass({

  getInitialState: function() {
    return {
      position: 0,
      showAnswers : false,
    };
  },

  getCurrentWord: function() {
    return this.props.words[this.state.position];
  },

  onNextClick: function() {
    if (this.isEof()) {
      this.props.onDone();
    } else {
      this.setState({
        position: this.state.position + 1,
        showAnswers: false,
      });
    }
  },

  onHelpClickHandler: function() {
    this.setState({showAnswers: true});
  },

  isEof: function() {
    return this.state.position + 1 >= this.props.words.length;
  },

  render: function() {
    const verb = this.getCurrentWord();
    return  <VerbEntryForm key={verb.infinitive} verb={verb} showAnswers={this.state.showAnswers}>
              <button type="submit" class="btn btn-default" onClick={this.onNextClick}>{this.isEof() ? 'Opnieuw' : 'Next'}</button>
              <button type="submit" class="btn" onClick={this.onHelpClickHandler}>Help!</button>
            </VerbEntryForm>
  }
});

const VerbEntryForm = React.createClass({

  getInitialState: function() {
    return {
      verb: this.props.verb,
    };
  },

  render: function() {
    const {showAnswers} = this.props;
    const {verb} = this.state;
    const that = this;
    function onChangeHandlerFactory (key) {
      return function(e) {
        const newValue = e.target.value.toLowerCase();
        verb[key].actual = newValue;
        that.setState({verb});
      }
    }
    return (
      <form>

{verb.infinitive}

<Notes text={verb.notes}/>

        <FormGroup id="impf.s" caption="Imperfectum singular" autoFocus
          text={verb.imperfect_singular.expected} showAnswer={showAnswers}
          value={verb.imperfect_singular.actual} onChange={onChangeHandlerFactory('imperfect_singular')} />
        <FormGroup id="impf.p" caption="Imperfectum plural"
          text={verb.imperfect_plural.expected} showAnswer={showAnswers}
          value={verb.imperfect_plural.actual} onChange={onChangeHandlerFactory('imperfect_plural')} />
        <FormGroup id="impf.s" caption="Perfectum"
          text={verb.perfect.expected} showAnswer={showAnswers}
          value={verb.perfect.actual} onChange={onChangeHandlerFactory('perfect')} />
        {this.props.children}
      </form>
    );
  }
});

const FormGroup = React.createClass({
  mixins: [Debouncer(function(validationClass){this.setValidationStatus(validationClass)})],

  getInitialState: function() {return {validationClass:'', value: ''}},

  setValidationStatus: function (validationClass) {
    this.setState({validationClass});
  },

  onValueChangeHandler: function(e) {
    if (this.props.onChange) {
      this.props.onChange(e);
    }
    const value = e.target.value.toLowerCase();
    this.setState({value});
    this.setValidationStatus('');
    this.debounce((value == this.props.text) ? 'has-success' : 'has-error')
  },

  render: function() {
    const {id, caption, showAnswer, ...rest} = this.props;

    var value;
    if (rest.onChange) {
      value = rest.value;
    } else {
      value = this.state.value;
    }

    return

            {caption}{showAnswer ? ' -> ' + this.props.text : ''}
            <input {...rest}
              id={id}
              className="form-control input-lg"
              type="text"
              autoComplete="off"
              autoCapitalize="none"
              autoCorrect="off"
              placeholder={caption}
              value={value}
              onChange={this.onValueChangeHandler}/>

  }
});<|endoftext|>


<commit_before>/*!
 * Panels Util
 * For both styleguide and viewer
 *
 * Copyright (c) 2013-16 Brad Frost, http://bradfrostweb.com & Dave Olsen, http://dmolsen.com
 * Licensed under the MIT license
 *
 * @requires url-handler.js
 *
 */

var panelsUtil = {

  addClickEvents: function(templateRendered, patternPartial) {

    var els = templateRendered.querySelectorAll('#sg-'+patternPartial+'-tabs li');
    for (var i = 0; i < els.length; ++i) {
      els[i].onclick = (function() {
        var patternPartial = this.getAttribute('data-patternpartial');
        var panelID = this.getAttribute('data-panelid');
        panelsUtil.show(patternPartial, panelID);
      });
    }

    return templateRendered;

  },

  show: function(patternPartial, panelID) {

    var els;

    // turn off all of the active tabs
    els = document.querySelectorAll('sg-'+patternPartial+'-tabs li');
    for (i = 0; i < els.length; ++i) {
      els[i].classList.remove('sg-code-title-active');
    }

    // hide all of the panels
    els = document.querySelectorAll('sg-'+patternPartial+'-tabs li');
    for (i = 0; i < els.length; ++i) {
      els[i].style.display = 'none';
    }

    // add active tab class
    document.getElementById('sg-'+patternPartial+'-'+panelID+'-tab').classList.add('sg-tab-title-active');

    // show the panel
    document.getElementById('sg-'+patternPartial+'-'+panelID+'-panel').style.display = 'block';

    /*
    if (codeViewer.copyOnInit) {
      codeViewer.selectCode();
      codeViewer.copyOnInit = false;
    }
    */

  }

};
<commit_msg>adding smarting handle of click event<commit_after>/*!
 * Panels Util
 * For both styleguide and viewer
 *
 * Copyright (c) 2013-16 Brad Frost, http://bradfrostweb.com & Dave Olsen, http://dmolsen.com
 * Licensed under the MIT license
 *
 * @requires url-handler.js
 *
 */

var panelsUtil = {

  addClickEvents: function(templateRendered, patternPartial) {

    var els = templateRendered.querySelectorAll('#sg-'+patternPartial+'-tabs li');
    for (var i = 0; i < els.length; ++i) {
      els[i].onclick = function(e) {
        e.preventDefault();
        var patternPartial = this.getAttribute('data-patternpartial');
        var panelID = this.getAttribute('data-panelid');
        panelsUtil.show(patternPartial, panelID);
      };
    }

    return templateRendered;

  },

  show: function(patternPartial, panelID) {

    var els;

    // turn off all of the active tabs
    els = document.querySelectorAll('#sg-'+patternPartial+'-tabs li');
    for (i = 0; i < els.length; ++i) {
      els[i].classList.remove('sg-tab-title-active');
    }

    // hide all of the panels
    els = document.querySelectorAll('#sg-'+patternPartial+'-panels div.sg-tabs-panel');
    for (i = 0; i < els.length; ++i) {
      els[i].style.display = 'none';
    }

    // add active tab class
    document.getElementById('sg-'+patternPartial+'-'+panelID+'-tab').classList.add('sg-tab-title-active');

    // show the panel
    document.getElementById('sg-'+patternPartial+'-'+panelID+'-panel').style.display = 'block';

    /*
    if (codeViewer.copyOnInit) {
      codeViewer.selectCode();
      codeViewer.copyOnInit = false;
    }
    */

  }

};
<|endoftext|>


<commit_before>/*
 * Electronic Logistics Management Information System (eLMIS) is a supply chain management system for health commodities in a developing country setting.
 *
 * Copyright (C) 2015  John Snow, Inc (JSI). This program was produced for the U.S. Agency for International Development (USAID). It was prepared under the USAID | DELIVER PROJECT, Task Order 4.
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
function StockedOutController($scope, $window, StockedOutReport, $routeParams) {

    $scope.OnFilterChanged = function () {
        // clear old data if there was any

        $scope.data = $scope.datarows = [];
        $scope.filter.max = 10000;
        $scope.filter.showStockeout = 'N';
        StockedOutReport.get($scope.getSanitizedParameter(), function (data) {
            if (data.pages !== undefined && data.pages.rows !== undefined) {
                $scope.data = data.pages.rows;
                $scope.paramsChanged($scope.tableParams);
            }
        });
    };
    if (!utils.isEmpty($routeParams.reportType)) {
        var reportTypes = $routeParams.reportType.split(',');
        $scope.reportTypes = {};
        reportTypes.forEach(function (reportType) {
            $scope.reportTypes[reportType] = true;
        });

    }else{
        $scope.reportTypes = {};
        $scope.reportTypes["RE"] = true;
    }
    ( function init(){

        $routeParams.reportType="RE";

    })();
    $scope.onToggleReportTypeAll = function () {
        if ($scope.reportTypes === undefined) {
            $scope.reportTypes = {};
        }

        $scope.reportTypes.EM = $scope.reportTypes.RE = $scope.allReportType;
        $scope.onReportTypeCheckboxChanged();
    };
    $scope.onReportTypeCheckboxChanged = function () {

        var reportType = getReportType();

        $scope.applyUrl();
        $scope.OnFilterChanged();
    };
    $scope.exportReport = function (type) {
        $scope.filter.pdformat = 1;
        var params = jQuery.param($scope.getSanitizedParameter());
        var url = '/reports/download/stocked_out/' + type + '?' + params;
        $window.open(url, '_BLANK');
    };

    function getReportType() {
        var reportType = null;
        _.keys($scope.reportTypes).forEach(function (key) {
            var value = $scope.reportTypes[key];
            if (value === true && (key === 'EM' || key === 'RE')) {
                utils.isNullOrUndefined(reportType) ? reportType = key : reportType += "," + key;
            } else if (value === false) {
                $scope.allReportType = false;
            }
        });
        if ($scope.filter === undefined) {
            $scope.filter = {reportType: reportType};
        } else {
            $scope.filter.reportType = reportType;
        }
        return reportType;
    }
}
<commit_msg>fix js error<commit_after>/*
 * Electronic Logistics Management Information System (eLMIS) is a supply chain management system for health commodities in a developing country setting.
 *
 * Copyright (C) 2015  John Snow, Inc (JSI). This program was produced for the U.S. Agency for International Development (USAID). It was prepared under the USAID | DELIVER PROJECT, Task Order 4.
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
function StockedOutController($scope, $window, StockedOutReport, $routeParams) {

    $scope.OnFilterChanged = function () {
        // clear old data if there was any

        $scope.data = $scope.datarows = [];
        $scope.filter.max = 10000;
        $scope.filter.showStockeout = 'N';
        StockedOutReport.get($scope.getSanitizedParameter(), function (data) {
            if (data.pages !== undefined && data.pages.rows !== undefined) {
                $scope.data = data.pages.rows;
                $scope.paramsChanged($scope.tableParams);
            }
        });
    };
    if (!utils.isEmpty($routeParams.reportType)) {
        var reportTypes = $routeParams.reportType.split(',');
        $scope.reportTypes = {};
        reportTypes.forEach(function (reportType) {
            $scope.reportTypes[reportType] = true;
        });

    }else{
        $scope.reportTypes = {};
        $scope.reportTypes.RE = true;
    }
    ( function init(){

        $routeParams.reportType="RE";

    })();
    $scope.onToggleReportTypeAll = function () {
        if ($scope.reportTypes === undefined) {
            $scope.reportTypes = {};
        }

        $scope.reportTypes.EM = $scope.reportTypes.RE = $scope.allReportType;
        $scope.onReportTypeCheckboxChanged();
    };
    $scope.onReportTypeCheckboxChanged = function () {

        var reportType = getReportType();

        $scope.applyUrl();
        $scope.OnFilterChanged();
    };
    $scope.exportReport = function (type) {
        $scope.filter.pdformat = 1;
        var params = jQuery.param($scope.getSanitizedParameter());
        var url = '/reports/download/stocked_out/' + type + '?' + params;
        $window.open(url, '_BLANK');
    };

    function getReportType() {
        var reportType = null;
        _.keys($scope.reportTypes).forEach(function (key) {
            var value = $scope.reportTypes[key];
            if (value === true && (key === 'EM' || key === 'RE')) {
                utils.isNullOrUndefined(reportType) ? reportType = key : reportType += "," + key;
            } else if (value === false) {
                $scope.allReportType = false;
            }
        });
        if ($scope.filter === undefined) {
            $scope.filter = {reportType: reportType};
        } else {
            $scope.filter.reportType = reportType;
        }
        return reportType;
    }
}
<|endoftext|>


<commit_before>/**
 * Renders and allows to control a single slide
 */

import {
  createElement,
  setTransform,
  equalizePoints,
  roundPoint,
  toTransformString,
  clamp,
  LOAD_STATE,
  setWidthHeight,
} from '../util/util.js';

import PanBounds from './pan-bounds.js';
import ZoomLevel from './zoom-level.js';
import { getPanAreaSize } from '../util/viewport-size.js';
import Placeholder from './placeholder.js';

class Slide {
  constructor(data, index, pswp) {
    this.data = data;
    this.index = index;
    this.pswp = pswp;
    this.isActive = (index === pswp.currIndex);
    this.currentResolution = 0;
    this.panAreaSize = {};

    this.isFirstSlide = (this.isActive && !pswp.opener.isOpen);

    this.zoomLevels = new ZoomLevel(pswp.options, data, index, pswp);

    this.pswp.dispatch('gettingData', {
      slide: this,
      data: this.data,
      index
    });

    this.pan = {
      x: 0,
      y: 0
    };

    this.content = this.pswp.contentLoader.getContentBySlide(this);

    this.currZoomLevel = 1;
    this.width = this.content.width;
    this.height = this.content.height;

    this.bounds = new PanBounds(this);

    this.prevDisplayedWidth = -1;
    this.prevDisplayedHeight = -1;

    this.pswp.dispatch('slideInit', { slide: this });
  }

  /**
   * If this slide is active/current/visible
   *
   * @param {Boolean} isActive
   */
  setIsActive(isActive) {
    if (isActive && !this.isActive) {
      // slide just became active
      this.activate();
    } else if (!isActive && this.isActive) {
      // slide just became non-active
      this.deactivate();
    }
  }

  /**
   * Appends slide content to DOM
   */
  append(holderElement) {
    this.holderElement = holderElement;

    // Slide appended to DOM
    if (!this.data) {
      this.holderElement.innerHTML = '';
      return;
    }

    this.calculateSize();

    this.container = createElement('pswp__zoom-wrap');
    this.container.transformOrigin = '0 0';

    this.load();
    this.appendHeavy();
    this.updateContentSize();

    this.holderElement.innerHTML = '';
    this.holderElement.appendChild(this.container);

    this.zoomAndPanToInitial();

    this.pswp.dispatch('firstZoomPan', { slide: this });

    this.applyCurrentZoomPan();

    this.pswp.dispatch('afterSetContent', { slide: this });

    if (this.isActive) {
      this.activate();
    }
  }

  removePlaceholder() {
    if (this.placeholder && this.content && !this.content.keepPlaceholder()) {
      // With delay, as image might be loaded, but not decoded
      setTimeout(() => {
        if (this.placeholder) {
          this.placeholder.destroy();
          this.placeholder = null;
        }
      }, 500);
    }
  }

  load() {
    if (this.usePlaceholder() && !this.placeholder) {
      const useImagePlaceholder = this.data.msrc && this.isFirstSlide;
      this.placeholder = new Placeholder(
        useImagePlaceholder ? this.data.msrc : false,
        this.container
      );
    }

    this.content.load();
    this.pswp.dispatch('slideLoad', { slide: this });
  }

  /**
   * Append "heavy" DOM elements
   *
   * This may depend on a type of slide,
   * but generally these are large images.
   */
  appendHeavy() {
    const { pswp } = this;
    const appendHeavyNearby = true;

    // Avoid appending heavy elements during animations
    if (this.heavyAppended
        || !pswp.opener.isOpen
        || pswp.mainScroll.isShifted()
        || (!this.isActive && !appendHeavyNearby)) {
      return;
    }

    if (this.pswp.dispatch('appendHeavy', { slide: this }).defaultPrevented) {
      return;
    }

    this.heavyAppended = true;

    if (this.content.state === LOAD_STATE.ERROR) {
      this.displayError();
    } else {
      this.content.appendTo(this.container);
      if (this.placeholder) {
        this.removePlaceholder();
      }
    }

    this.pswp.dispatch('appendHeavyContent', { slide: this });
  }

  /**
   * Append HTML content to slide container
   * (usually item.html or error message)
   *
   * @param {DOMElement} containerEl
   * @param {String} html
   */
  setSlideHTML(html) {
    const { container } = this;
    if (html.tagName) {
      container.appendChild(html);
    } else {
      container.innerHTML = html;
    }
  }

  displayError() {
    const errorElement = this.content.getErrorElement();
    errorElement.style.position = 'absolute';
    errorElement.style.left = 0;
    errorElement.style.top = 0;
    this.activeErrorElement = errorElement;
    this.setSlideHTML(errorElement);
    this.updateContentSize(true);
  }

  /**
   * Triggered when this slide is active (selected).
   *
   * If it's part of opening/closing transition -
   * activate() will trigger after the transition is ended.
   */
  activate() {
    this.isActive = true;
    this.appendHeavy();
    this.content.activate();
    this.pswp.dispatch('slideActivate', { slide: this });
  }

  /**
   * Triggered when this slide becomes inactive.
   *
   * Slide can become inactive only after it was active.
   */
  deactivate() {
    this.isActive = false;
    this.content.deactivate();

    // reset zoom level
    this.currentResolution = 0;
    this.zoomAndPanToInitial();
    this.applyCurrentZoomPan();
    this.updateContentSize();

    this.pswp.dispatch('slideDeactivate', { slide: this });
  }

  /**
   * The slide should destroy itself, it will never be used again.
   * (unbind all events and destroy internal components)
   */
  destroy() {
    this.content.remove();
    this.pswp.dispatch('slideDestroy', { slide: this });
  }

  resize() {
    if (this.currZoomLevel === this.zoomLevels.initial || !this.isActive) {
      // Keep initial zoom level if it was before the resize,
      // as well as when this slide is not active

      // Reset position and scale to original state
      this.calculateSize();
      this.currentResolution = 0;
      this.zoomAndPanToInitial();
      this.applyCurrentZoomPan();
      this.updateContentSize();
    } else {
      // readjust pan position if it's beyond the bounds
      this.calculateSize();
      this.bounds.update(this.currZoomLevel);
      this.panTo(this.pan.x, this.pan.y);
    }
  }

  /**
   * Apply size to current slide content,
   * based on the current resolution and scale.
   *
   * @param {Boolean} force if size should be updated even if dimensions weren't changed
   */
  updateContentSize(force) {
    // Use initial zoom level
    // if resolution is not defined (user didn't zoom yet)
    const scaleMultiplier = this.currentResolution || this.zoomLevels.initial;

    if (!scaleMultiplier) {
      return;
    }

    const width = Math.round(this.width * scaleMultiplier) || this.pswp.viewportSize.x;
    const height = Math.round(this.height * scaleMultiplier) || this.pswp.viewportSize.y;

    if (!this.sizeChanged(width, height) && !force) {
      return;
    }

    if (this.placeholder) {
      this.placeholder.setDisplayedSize(width, height);
    }

    if (this.activeErrorElement) {
      setWidthHeight(this.activeErrorElement, width, height);
    }

    this.content.setDisplayedSize(width, height);
  }

  sizeChanged(width, height) {
    if (width !== this.prevDisplayedWidth
        || height !== this.prevDisplayedHeight) {
      this.prevDisplayedWidth = width;
      this.prevDisplayedHeight = height;
      return true;
    }

    return false;
  }

  getPlaceholderElement() {
    if (this.placeholder) {
      return this.placeholder.element;
    }
  }

  /**
   * Zoom current slide image to...
   *
   * @param  {Number} destZoomLevel      Destination zoom level.
   * @param  {Object|false} centerPoint  Transform origin center point,
   *                                     or false if viewport center should be used.
   * @param  {Number} transitionDuration Transition duration, may be set to 0.
   * @param  {Boolean|null} ignoreBounds Minimum and maximum zoom levels will be ignored.
   * @return {Boolean|null}              Returns true if animated.
   */
  zoomTo(destZoomLevel, centerPoint, transitionDuration, ignoreBounds) {
    const { pswp } = this;
    if (!this.isZoomable()
        || pswp.mainScroll.isShifted()) {
      return;
    }

    pswp.dispatch('beforeZoomTo', {
      destZoomLevel, centerPoint, transitionDuration
    });

    // stop all pan and zoom transitions
    pswp.animations.stopAllPan();

    // if (!centerPoint) {
    //   centerPoint = pswp.getViewportCenterPoint();
    // }

    const prevZoomLevel = this.currZoomLevel;

    if (!ignoreBounds) {
      destZoomLevel = clamp(destZoomLevel, this.zoomLevels.min, this.zoomLevels.max);
    }

    // if (transitionDuration === undefined) {
    //   transitionDuration = this.pswp.options.zoomAnimationDuration;
    // }

    this.setZoomLevel(destZoomLevel);
    this.pan.x = this.calculateZoomToPanOffset('x', centerPoint, prevZoomLevel);
    this.pan.y = this.calculateZoomToPanOffset('y', centerPoint, prevZoomLevel);
    roundPoint(this.pan);

    const finishTransition = () => {
      this._setResolution(destZoomLevel);
      this.applyCurrentZoomPan();
    };

    if (!transitionDuration) {
      finishTransition();
    } else {
      pswp.animations.startTransition({
        isPan: true,
        name: 'zoomTo',
        target: this.container,
        transform: this.getCurrentTransform(),
        onComplete: finishTransition,
        duration: transitionDuration,
        easing: pswp.options.easing
      });
    }
  }

  toggleZoom(centerPoint) {
    this.zoomTo(
      this.currZoomLevel === this.zoomLevels.initial
        ? this.zoomLevels.secondary : this.zoomLevels.initial,
      centerPoint,
      this.pswp.options.zoomAnimationDuration
    );
  }

  /**
   * Updates zoom level property and recalculates new pan bounds,
   * unlike zoomTo it does not apply transform (use applyCurrentZoomPan)
   *
   * @param {Number} currZoomLevel
   */
  setZoomLevel(currZoomLevel) {
    this.currZoomLevel = currZoomLevel;
    this.bounds.update(this.currZoomLevel);
  }

  /**
   * Get pan position after zoom at a given `point`.
   *
   * Always call setZoomLevel(newZoomLevel) beforehand to recalculate
   * pan bounds according to the new zoom level.
   *
   * @param {String} axis
   * @param {Object|null} centerPoint point based on which zoom is performed,
   *                                  usually refers to the current mouse position,
   *                                  if false - viewport center will be used.
   * @param {Number|null} prevZoomLevel Zoom level before new zoom was applied.
   */
  calculateZoomToPanOffset(axis, point, prevZoomLevel) {
    const totalPanDistance = this.bounds.max[axis] - this.bounds.min[axis];
    if (totalPanDistance === 0) {
      return this.bounds.center[axis];
    }

    if (!point) {
      point = this.pswp.getViewportCenterPoint();
    }

    const zoomFactor = this.currZoomLevel / prevZoomLevel;
    return this.bounds.correctPan(
      axis,
      (this.pan[axis] - point[axis]) * zoomFactor + point[axis]
    );
  }

  /**
   * Apply pan and keep it within bounds.
   *
   * @param {Number} panX
   * @param {Number} panY
   */
  panTo(panX, panY) {
    this.pan.x = this.bounds.correctPan('x', panX);
    this.pan.y = this.bounds.correctPan('y', panY);
    this.applyCurrentZoomPan();
  }

  /**
   * If the slide in the current state can be panned by the user
   */
  isPannable() {
    return this.width && (this.currZoomLevel > this.zoomLevels.fit);
  }

  /**
   * If the slide can be zoomed
   */
  isZoomable() {
    return this.width && this.content.isZoomable();
  }

  usePlaceholder() {
    return this.content.usePlaceholder();
  }

  /**
   * Apply transform and scale based on
   * the current pan position (this.pan) and zoom level (this.currZoomLevel)
   */
  applyCurrentZoomPan() {
    this._applyZoomTransform(this.pan.x, this.pan.y, this.currZoomLevel);
    if (this === this.pswp.currSlide) {
      this.pswp.dispatch('zoomPanUpdate', { slide: this });
    }
  }

  zoomAndPanToInitial() {
    this.currZoomLevel = this.zoomLevels.initial;

    // pan according to the zoom level
    this.bounds.update(this.currZoomLevel);
    equalizePoints(this.pan, this.bounds.center);
    this.pswp.dispatch('initialZoomPan', { slide: this });
  }

  /**
   * Set translate and scale based on current resolution
   *
   * @param {Number} x
   * @param {Number} y
   * @param {Number} zoom
   */
  _applyZoomTransform(x, y, zoom) {
    zoom /= this.currentResolution || this.zoomLevels.initial;
    setTransform(this.container, x, y, zoom);
  }

  calculateSize() {
    // this.zoomLevels.fit = 1;
    // this.zoomLevels.vFill = 1;
    // this.zoomLevels.initial = 1;

    const { pswp } = this;

    equalizePoints(
      this.panAreaSize,
      getPanAreaSize(pswp.options, pswp.viewportSize, pswp)
    );

    this.zoomLevels.update(this.width, this.height, this.panAreaSize);

    pswp.dispatch('calcSlideSize', {
      slide: this
    });
  }

  getCurrentTransform() {
    const scale = this.currZoomLevel / (this.currentResolution || this.zoomLevels.initial);
    return toTransformString(this.pan.x, this.pan.y, scale);
  }

  /**
   * Set resolution and re-render the image.
   *
   * For example, if the real image size is 2000x1500,
   * and resolution is 0.5 - it will be rendered as 1000x750.
   *
   * Image with zoom level 2 and resolution 0.5 is
   * the same as image with zoom level 1 and resolution 1.
   *
   * Used to optimize animations and make
   * sure that browser renders image in highest quality.
   * Also used by responsive images to load the correct one.
   *
   * @param {Number} newResolution
   */
  _setResolution(newResolution) {
    if (newResolution === this.currentResolution) {
      return;
    }

    this.currentResolution = newResolution;
    this.updateContentSize();

    this.pswp.dispatch('resolutionChanged');
  }
}

export default Slide;
<commit_msg>Do not remove placeholder if slide isn't loaded<commit_after>/**
 * Renders and allows to control a single slide
 */

import {
  createElement,
  setTransform,
  equalizePoints,
  roundPoint,
  toTransformString,
  clamp,
  LOAD_STATE,
  setWidthHeight,
} from '../util/util.js';

import PanBounds from './pan-bounds.js';
import ZoomLevel from './zoom-level.js';
import { getPanAreaSize } from '../util/viewport-size.js';
import Placeholder from './placeholder.js';

class Slide {
  constructor(data, index, pswp) {
    this.data = data;
    this.index = index;
    this.pswp = pswp;
    this.isActive = (index === pswp.currIndex);
    this.currentResolution = 0;
    this.panAreaSize = {};

    this.isFirstSlide = (this.isActive && !pswp.opener.isOpen);

    this.zoomLevels = new ZoomLevel(pswp.options, data, index, pswp);

    this.pswp.dispatch('gettingData', {
      slide: this,
      data: this.data,
      index
    });

    this.pan = {
      x: 0,
      y: 0
    };

    this.content = this.pswp.contentLoader.getContentBySlide(this);

    this.currZoomLevel = 1;
    this.width = this.content.width;
    this.height = this.content.height;

    this.bounds = new PanBounds(this);

    this.prevDisplayedWidth = -1;
    this.prevDisplayedHeight = -1;

    this.pswp.dispatch('slideInit', { slide: this });
  }

  /**
   * If this slide is active/current/visible
   *
   * @param {Boolean} isActive
   */
  setIsActive(isActive) {
    if (isActive && !this.isActive) {
      // slide just became active
      this.activate();
    } else if (!isActive && this.isActive) {
      // slide just became non-active
      this.deactivate();
    }
  }

  /**
   * Appends slide content to DOM
   */
  append(holderElement) {
    this.holderElement = holderElement;

    // Slide appended to DOM
    if (!this.data) {
      this.holderElement.innerHTML = '';
      return;
    }

    this.calculateSize();

    this.container = createElement('pswp__zoom-wrap');
    this.container.transformOrigin = '0 0';

    this.load();
    this.appendHeavy();
    this.updateContentSize();

    this.holderElement.innerHTML = '';
    this.holderElement.appendChild(this.container);

    this.zoomAndPanToInitial();

    this.pswp.dispatch('firstZoomPan', { slide: this });

    this.applyCurrentZoomPan();

    this.pswp.dispatch('afterSetContent', { slide: this });

    if (this.isActive) {
      this.activate();
    }
  }

  removePlaceholder() {
    if (this.placeholder && this.content && !this.content.keepPlaceholder()) {
      // With delay, as image might be loaded, but not decoded
      setTimeout(() => {
        if (this.placeholder) {
          this.placeholder.destroy();
          this.placeholder = null;
        }
      }, 500);
    }
  }

  load() {
    if (this.usePlaceholder() && !this.placeholder) {
      const useImagePlaceholder = this.data.msrc && this.isFirstSlide;
      this.placeholder = new Placeholder(
        useImagePlaceholder ? this.data.msrc : false,
        this.container
      );
    }

    this.content.load();
    this.pswp.dispatch('slideLoad', { slide: this });
  }

  /**
   * Append "heavy" DOM elements
   *
   * This may depend on a type of slide,
   * but generally these are large images.
   */
  appendHeavy() {
    const { pswp } = this;
    const appendHeavyNearby = true;

    // Avoid appending heavy elements during animations
    if (this.heavyAppended
        || !pswp.opener.isOpen
        || pswp.mainScroll.isShifted()
        || (!this.isActive && !appendHeavyNearby)) {
      return;
    }

    if (this.pswp.dispatch('appendHeavy', { slide: this }).defaultPrevented) {
      return;
    }

    this.heavyAppended = true;

    if (this.content.state === LOAD_STATE.ERROR) {
      this.displayError();
    } else {
      this.content.appendTo(this.container);
      if (this.placeholder && this.content.state === LOAD_STATE.LOADED) {
        this.removePlaceholder();
      }
    }

    this.pswp.dispatch('appendHeavyContent', { slide: this });
  }

  /**
   * Append HTML content to slide container
   * (usually item.html or error message)
   *
   * @param {DOMElement} containerEl
   * @param {String} html
   */
  setSlideHTML(html) {
    const { container } = this;
    if (html.tagName) {
      container.appendChild(html);
    } else {
      container.innerHTML = html;
    }
  }

  displayError() {
    const errorElement = this.content.getErrorElement();
    errorElement.style.position = 'absolute';
    errorElement.style.left = 0;
    errorElement.style.top = 0;
    this.activeErrorElement = errorElement;
    this.setSlideHTML(errorElement);
    this.updateContentSize(true);
  }

  /**
   * Triggered when this slide is active (selected).
   *
   * If it's part of opening/closing transition -
   * activate() will trigger after the transition is ended.
   */
  activate() {
    this.isActive = true;
    this.appendHeavy();
    this.content.activate();
    this.pswp.dispatch('slideActivate', { slide: this });
  }

  /**
   * Triggered when this slide becomes inactive.
   *
   * Slide can become inactive only after it was active.
   */
  deactivate() {
    this.isActive = false;
    this.content.deactivate();

    // reset zoom level
    this.currentResolution = 0;
    this.zoomAndPanToInitial();
    this.applyCurrentZoomPan();
    this.updateContentSize();

    this.pswp.dispatch('slideDeactivate', { slide: this });
  }

  /**
   * The slide should destroy itself, it will never be used again.
   * (unbind all events and destroy internal components)
   */
  destroy() {
    this.content.remove();
    this.pswp.dispatch('slideDestroy', { slide: this });
  }

  resize() {
    if (this.currZoomLevel === this.zoomLevels.initial || !this.isActive) {
      // Keep initial zoom level if it was before the resize,
      // as well as when this slide is not active

      // Reset position and scale to original state
      this.calculateSize();
      this.currentResolution = 0;
      this.zoomAndPanToInitial();
      this.applyCurrentZoomPan();
      this.updateContentSize();
    } else {
      // readjust pan position if it's beyond the bounds
      this.calculateSize();
      this.bounds.update(this.currZoomLevel);
      this.panTo(this.pan.x, this.pan.y);
    }
  }

  /**
   * Apply size to current slide content,
   * based on the current resolution and scale.
   *
   * @param {Boolean} force if size should be updated even if dimensions weren't changed
   */
  updateContentSize(force) {
    // Use initial zoom level
    // if resolution is not defined (user didn't zoom yet)
    const scaleMultiplier = this.currentResolution || this.zoomLevels.initial;

    if (!scaleMultiplier) {
      return;
    }

    const width = Math.round(this.width * scaleMultiplier) || this.pswp.viewportSize.x;
    const height = Math.round(this.height * scaleMultiplier) || this.pswp.viewportSize.y;

    if (!this.sizeChanged(width, height) && !force) {
      return;
    }

    if (this.placeholder) {
      this.placeholder.setDisplayedSize(width, height);
    }

    if (this.activeErrorElement) {
      setWidthHeight(this.activeErrorElement, width, height);
    }

    this.content.setDisplayedSize(width, height);
  }

  sizeChanged(width, height) {
    if (width !== this.prevDisplayedWidth
        || height !== this.prevDisplayedHeight) {
      this.prevDisplayedWidth = width;
      this.prevDisplayedHeight = height;
      return true;
    }

    return false;
  }

  getPlaceholderElement() {
    if (this.placeholder) {
      return this.placeholder.element;
    }
  }

  /**
   * Zoom current slide image to...
   *
   * @param  {Number} destZoomLevel      Destination zoom level.
   * @param  {Object|false} centerPoint  Transform origin center point,
   *                                     or false if viewport center should be used.
   * @param  {Number} transitionDuration Transition duration, may be set to 0.
   * @param  {Boolean|null} ignoreBounds Minimum and maximum zoom levels will be ignored.
   * @return {Boolean|null}              Returns true if animated.
   */
  zoomTo(destZoomLevel, centerPoint, transitionDuration, ignoreBounds) {
    const { pswp } = this;
    if (!this.isZoomable()
        || pswp.mainScroll.isShifted()) {
      return;
    }

    pswp.dispatch('beforeZoomTo', {
      destZoomLevel, centerPoint, transitionDuration
    });

    // stop all pan and zoom transitions
    pswp.animations.stopAllPan();

    // if (!centerPoint) {
    //   centerPoint = pswp.getViewportCenterPoint();
    // }

    const prevZoomLevel = this.currZoomLevel;

    if (!ignoreBounds) {
      destZoomLevel = clamp(destZoomLevel, this.zoomLevels.min, this.zoomLevels.max);
    }

    // if (transitionDuration === undefined) {
    //   transitionDuration = this.pswp.options.zoomAnimationDuration;
    // }

    this.setZoomLevel(destZoomLevel);
    this.pan.x = this.calculateZoomToPanOffset('x', centerPoint, prevZoomLevel);
    this.pan.y = this.calculateZoomToPanOffset('y', centerPoint, prevZoomLevel);
    roundPoint(this.pan);

    const finishTransition = () => {
      this._setResolution(destZoomLevel);
      this.applyCurrentZoomPan();
    };

    if (!transitionDuration) {
      finishTransition();
    } else {
      pswp.animations.startTransition({
        isPan: true,
        name: 'zoomTo',
        target: this.container,
        transform: this.getCurrentTransform(),
        onComplete: finishTransition,
        duration: transitionDuration,
        easing: pswp.options.easing
      });
    }
  }

  toggleZoom(centerPoint) {
    this.zoomTo(
      this.currZoomLevel === this.zoomLevels.initial
        ? this.zoomLevels.secondary : this.zoomLevels.initial,
      centerPoint,
      this.pswp.options.zoomAnimationDuration
    );
  }

  /**
   * Updates zoom level property and recalculates new pan bounds,
   * unlike zoomTo it does not apply transform (use applyCurrentZoomPan)
   *
   * @param {Number} currZoomLevel
   */
  setZoomLevel(currZoomLevel) {
    this.currZoomLevel = currZoomLevel;
    this.bounds.update(this.currZoomLevel);
  }

  /**
   * Get pan position after zoom at a given `point`.
   *
   * Always call setZoomLevel(newZoomLevel) beforehand to recalculate
   * pan bounds according to the new zoom level.
   *
   * @param {String} axis
   * @param {Object|null} centerPoint point based on which zoom is performed,
   *                                  usually refers to the current mouse position,
   *                                  if false - viewport center will be used.
   * @param {Number|null} prevZoomLevel Zoom level before new zoom was applied.
   */
  calculateZoomToPanOffset(axis, point, prevZoomLevel) {
    const totalPanDistance = this.bounds.max[axis] - this.bounds.min[axis];
    if (totalPanDistance === 0) {
      return this.bounds.center[axis];
    }

    if (!point) {
      point = this.pswp.getViewportCenterPoint();
    }

    const zoomFactor = this.currZoomLevel / prevZoomLevel;
    return this.bounds.correctPan(
      axis,
      (this.pan[axis] - point[axis]) * zoomFactor + point[axis]
    );
  }

  /**
   * Apply pan and keep it within bounds.
   *
   * @param {Number} panX
   * @param {Number} panY
   */
  panTo(panX, panY) {
    this.pan.x = this.bounds.correctPan('x', panX);
    this.pan.y = this.bounds.correctPan('y', panY);
    this.applyCurrentZoomPan();
  }

  /**
   * If the slide in the current state can be panned by the user
   */
  isPannable() {
    return this.width && (this.currZoomLevel > this.zoomLevels.fit);
  }

  /**
   * If the slide can be zoomed
   */
  isZoomable() {
    return this.width && this.content.isZoomable();
  }

  usePlaceholder() {
    return this.content.usePlaceholder();
  }

  /**
   * Apply transform and scale based on
   * the current pan position (this.pan) and zoom level (this.currZoomLevel)
   */
  applyCurrentZoomPan() {
    this._applyZoomTransform(this.pan.x, this.pan.y, this.currZoomLevel);
    if (this === this.pswp.currSlide) {
      this.pswp.dispatch('zoomPanUpdate', { slide: this });
    }
  }

  zoomAndPanToInitial() {
    this.currZoomLevel = this.zoomLevels.initial;

    // pan according to the zoom level
    this.bounds.update(this.currZoomLevel);
    equalizePoints(this.pan, this.bounds.center);
    this.pswp.dispatch('initialZoomPan', { slide: this });
  }

  /**
   * Set translate and scale based on current resolution
   *
   * @param {Number} x
   * @param {Number} y
   * @param {Number} zoom
   */
  _applyZoomTransform(x, y, zoom) {
    zoom /= this.currentResolution || this.zoomLevels.initial;
    setTransform(this.container, x, y, zoom);
  }

  calculateSize() {
    // this.zoomLevels.fit = 1;
    // this.zoomLevels.vFill = 1;
    // this.zoomLevels.initial = 1;

    const { pswp } = this;

    equalizePoints(
      this.panAreaSize,
      getPanAreaSize(pswp.options, pswp.viewportSize, pswp)
    );

    this.zoomLevels.update(this.width, this.height, this.panAreaSize);

    pswp.dispatch('calcSlideSize', {
      slide: this
    });
  }

  getCurrentTransform() {
    const scale = this.currZoomLevel / (this.currentResolution || this.zoomLevels.initial);
    return toTransformString(this.pan.x, this.pan.y, scale);
  }

  /**
   * Set resolution and re-render the image.
   *
   * For example, if the real image size is 2000x1500,
   * and resolution is 0.5 - it will be rendered as 1000x750.
   *
   * Image with zoom level 2 and resolution 0.5 is
   * the same as image with zoom level 1 and resolution 1.
   *
   * Used to optimize animations and make
   * sure that browser renders image in highest quality.
   * Also used by responsive images to load the correct one.
   *
   * @param {Number} newResolution
   */
  _setResolution(newResolution) {
    if (newResolution === this.currentResolution) {
      return;
    }

    this.currentResolution = newResolution;
    this.updateContentSize();

    this.pswp.dispatch('resolutionChanged');
  }
}

export default Slide;
<|endoftext|>


<commit_before>/**
 * Add a little christmas spirit
 */
(function () {
  var headElem = document.querySelector('head');
  var snowStyles = '<link rel="stylesheet" href="//frippz-se.s3.amazonaws.com/christmas.css">';
  var header = document.querySelector('#header');

  headElem.insertAdjacentHTML('beforeend', snowStyles);
  header.classList.add('snow');
})();
<commit_msg>Rename christmas.css to snow.css<commit_after>/**
 * Add a little christmas spirit
 */
(function () {
  var headElem = document.querySelector('head');
  var snowStyles = '<link rel="stylesheet" href="//frippz-se.s3.amazonaws.com/snow.css">';
  var header = document.querySelector('#header');

  headElem.insertAdjacentHTML('beforeend', snowStyles);
  header.classList.add('snow');
})();
<|endoftext|>