<commit_before>var Navbar = ReactBootstrap.Navbar;
var Nav = ReactBootstrap.Nav;
var NavItemLink = ReactRouterBootstrap.NavItemLink;
var ButtonLink = ReactRouterBootstrap.ButtonLink;
var ModalTrigger = ReactBootstrap.ModalTrigger;
var Modal = ReactBootstrap.Modal;
var Button = ReactBootstrap.Button;
var Input = ReactBootstrap.Input;
var Grid = ReactBootstrap.Grid;
var Row = ReactBootstrap.Row;
var Col = ReactBootstrap.Col;
var Well = ReactBootstrap.Well;
var Panel = ReactBootstrap.Panel;
var ListGroup = ReactBootstrap.ListGroup;
var ListGroupItem = ReactBootstrap.ListGroupItem;
var Table = ReactBootstrap.Table;
var State = ReactRouter.State;
var Navigation = ReactRouter.Navigation;
var RouteHandler = ReactRouter.RouteHandler;
var Route = ReactRouter.Route;
var Router = ReactRouter.Router;
var Link = ReactRouter.Link;
var DefaultRoute = ReactRouter.DefaultRoute;
var NotFoundRoute = ReactRouter.NotFoundRoute;

var Dataset = React.createClass({
  render: function() {
    var dataset = this.props.data;
    return (
      <Modal {...this.props} title="View" animation={false}>

{dataset.tag.name}

          <Button onClick={this.props.onRequestHide}>Close</Button>

      </Modal>
    )
  }
});

var Datasets = React.createClass({
  mixins: [State],

  waterfall: function() {
    if (this.cache.datasets.length === 0) {
      return;
    }
    jQuery("#waterfall").waterfall({
      selector: ".dataset",
    });
  },

  loadDatasets: function() {
    var self = this;
    var query = this.getQuery();
    var params = this.getParams();
    var max = query.max || '';
    var limit = Number(query.limit) || 50;
    var tag = query.tag || '';
    this.limit = limit;
    var dataType = params.dataType || 'all';
    jQuery.get('/api/datasets/?max=' + max + '&limit=' + limit + '&data_type=' + dataType + '&tag=' + tag, function(data) {
      self.setState(data);
    });
  },

  getInitialState: function() {
    this.cache = this.cache || {};
    return {datasets: []};
  },

  shouldLoadDatasets: function() {
    var path = this.getPath();
    if (this.cache.path !== path) {
      this.cache.path = path;
      return true;
    }
    return false;
  },

  shouldCleanDatasets: function() {
    var pathname = this.getPathname();
    var query = this.getQuery();
    if (this.cache.pathname !== pathname || this.cache.tag !== query.tag || !query.max) {
      this.cache.pathname = pathname;
      this.cache.tag = query.tag;
      this.cache.scroll = true;
      return true;
    }
    return false;
  },

  cleanDatasets: function() {
    this.cache.datasets = [];
  },

  componentDidMount: function() {
    this.cache.datasets = this.cache.datasets || [];
    this.componentDidUpdate();
  },

  componentDidUpdate: function() {
    if (this.shouldLoadDatasets()) {
      if (this.shouldCleanDatasets()) {
        this.cleanDatasets();
      }
      this.loadDatasets();
    } else {
      if (this.cache.scroll) {
        this.cache.scroll = false;
        window.scroll(0, 0);
      }
      this.waterfall();
    }
  },

  render: function() {
    var datasets = this.state.datasets || [];
    var loadMore;
    if (datasets.length >= this.limit) {
      var query = this.getQuery();
      query = query || {};
      query.max = datasets[datasets.length - 1].dataset_id;
      loadMore = (

          <ButtonLink bsStyle="info" bsSize="large"
              params={this.getParams()} to="datasets" query={query} block>加载更多...</ButtonLink>

      );
    }
    if (this.cache.datasets && this.cache.datasets.length > 0) {
      var oldLastDataset = this.cache.datasets[this.cache.datasets.length - 1];
      var lastDataset = datasets[datasets.length - 1];
      if (oldLastDataset.dataset_id !== lastDataset.dataset_id) {
        this.cache.datasets = this.cache.datasets.concat(datasets);
      }
    } else {
      this.cache.datasets = datasets;
    }
    var elems = this.cache.datasets.map(function(dataset) {
      var width = 192;
      var height = width / dataset.file.width * dataset.file.height;
      if (height > 600) {
        height = 600;
      }
      return (
        <ModalTrigger modal={<Dataset data={dataset} title={dataset.tag.name} />}>

{dataset.tag.name}

        </ModalTrigger>
      );
    });
    return (

          {elems}

        {loadMore}

    );
  }
});

var SearchForm = React.createClass({
  mixins: [State],

  getInitialState: function() {
    var query = this.getQuery();
    this.cache = this.cache || {};

    return {
      value: query.tag || '',
      tags: []
    };
  },

  getHint: function(word) {
    var self = this;
    jQuery.get('/api/tags/hint?word=' + word, function(data) {
      self.setState(data);
    });
  },

  shouldCleanHint: function() {
    var path = this.getPath();
    if (this.cache.path === path) {
      return false;
    }
    this.cache.path = path;
    return true;

  },

  cleanHint: function() {
    this.setState({tags: []});
  },

  componentDidUpdate: function() {
    if (this.cache.changed) {
      if (this.shouldCleanHint()) {
        this.cache.changed = false;
        this.cleanHint();
      }
      return;
    }
    var query = this.getQuery();
    if (query.tag && query.tag !== this.state.value) {
      this.setState({
        value: query.tag
      });
    } else if (!query.tag && this.state.value) {
      this.setState({value: ''});
    }
    this.cache.path = this.getPath();
  },

  handleChange: function() {
    this.cache.changed = true;
    this.getHint(this.refs.tag.getValue());
    this.setState({
      value: this.refs.tag.getValue()
    });
  },

  handleSubmit: function(evt) {
    evt.preventDefault();
    this.cache.changed = false;
    this.props.onSubmit(this.state.value);
  },

  handleListClick: function(eventKey, href, target) {
    this.cache.changed = false;
    this.props.onSubmit(target);
    this.setState({tags: [], value: target});
  },

  render: function() {
    var list = this.state.tags.map(function(tag) {
      return <ListGroupItem target={tag.name}> {tag.name}</ListGroupItem>;
    });
    return (
      <form className="navbar-form navbar-right" onSubmit={this.handleSubmit}>
        <Input type="text"
          name="tag"
          value={this.state.value}
          ref="tag"
          placeholder="Search..."
          onChange={this.handleChange} />

          <ListGroup onClick={this.handleListClick}>
            {list}
          </ListGroup>

      </form>
    );
  }
});

var App = React.createClass({
  mixins: [State, Navigation],

  getInitialState: function() {
    var tag = this.getQuery().tag || '';
    return {tag: tag}
  },

  handleSubmit: function(tag) {
    var query = this.getQuery();
    var params = this.getParams();
    params.dataType = params.dataType || 'all';
    query.tag = tag;
    delete query.max;
    var href = this.makeHref('datasets', params, query);
    window.location.href = href;
    this.setState({tag: tag || ''});
  },

  render: function() {
    return (

        <Navbar fixedTop inverse fluid brand="Huabot Brain">

            <NavItemLink to="dashboard">Dashboard</NavItemLink>

          <SearchForm onSubmit={this.handleSubmit} />
        </Navbar>
        <Grid fluid>
          <Row>
            <Col sm={3} md={2} className="sidebar">

                <NavItemLink to="datasets" params={{dataType: 'all'}}>所有数据</NavItemLink>
                <NavItemLink to="datasets" params={{dataType: 'train'}}>训练数据</NavItemLink>
                <NavItemLink to="datasets" params={{dataType: 'val'}}>验证数据</NavItemLink>
                <NavItemLink to="demo">DEMO</NavItemLink>

            </Col>
            <Col sm={9} smOffset={3} md={10} mdOffset={2}>
              <RouteHandler />
            </Col>
          </Row>
        </Grid>

    );
  }
});

var Dashboard = React.createClass({
  mixins: [State],

  loadTags: function() {
    var self = this;
    var query = this.getQuery();
    var max = query.max || '';
    var limit = Number(query.limit) || 20;
    this.limit = limit;
    jQuery.get('/api/tags/?max=' + max + '&limit=' + limit, function(data) {
      self.setState(data);
    });

  },

  getInitialState: function() {
    this.cache = this.cache || {};
    return {tags: []};
  },

  shouldLoadTags: function() {
    var path = this.getPath();
    if (this.cache.path !== path) {
      this.cache.path = path;
      return true;
    }
    return false;
  },

  shouldCleanTags: function() {
    if (!this.getQuery().max) {
      return true;
    }
    return false;
  },

  cleanTags: function() {
    this.cache.tags = [];
  },

  componentDidMount: function() {
    this.cache.tags = this.cache.tags || [];
    this.componentDidUpdate();
  },

  componentDidUpdate: function() {
    if (this.shouldLoadTags()) {
      if (this.shouldCleanTags()) {
        this.cache.scroll = true;
        this.cleanTags();
      }
      this.loadTags();
    } else {
      if (this.cache.scroll) {
        this.cache.scroll = false;
        window.scroll(0, 0);
      }
    }
  },

  render: function() {
    var tags = this.state.tags || [];
    var loadMore;
    if (tags.length >= this.limit) {
      var query = this.getQuery();
      query = query || {};
      query.max = tags[tags.length - 1].tag_id;
      loadMore = (

          <ButtonLink bsStyle="info" bsSize="large"
              params={this.getParams()} to="dashboard" query={query} block>加载更多...</ButtonLink>

      );
    }
    if (this.cache.tags && this.cache.tags.length > 0) {
      var oldLastTag = this.cache.tags[this.cache.tags.length - 1];
      var lastTag = tags[tags.length - 1];
      if (oldLastTag.tag_id !== lastTag.tag_id) {
        this.cache.tags = this.cache.tags.concat(tags);
      }
    } else {
      this.cache.tags = tags;
    }
    var elems = this.cache.tags.map(function(tag) {
      return (

          {tag.tag_id}
          <Link to="datasets" params={{dataType: 'all'}} query={{tag: tag.name}}>{tag.name}</Link>
          {tag.train_count}
          {tag.test_count}

      );
    });
    return (

 Dashboard

        <Row className="train-status">
          <Col xs={6}>

          </Col>
          <Col xs={6}>

          </Col>
        </Row>

Tags

              #
              Name
              Train
              Test

            {elems}

        {loadMore}

    );
  }
});

var DEMO = React.createClass({
  mixins: [State, Navigation],

  predict: function(img_url) {
    var self = this;
    jQuery.post('/api/predict', {img_url: img_url}, function(data) {
      self.setState(data);
    });

  },

  getInitialState: function() {
    this.cache = {};
    var query = this.getQuery();
    var img_url = query.img_url || '';
    return {bet_result: [], time: null, err: null};
  },

  componentDidMount: function() {
    this.componentDidUpdate();
  },

  componentDidUpdate: function() {
    var query = this.getQuery();
    if (query.img_url && query.img_url !== this.cache.imgUrl) {
      this.cache.imgUrl = query.img_url;
      this.predict(this.cache.imgUrl);
    }
  },

  handleSubmit: function(evt) {
    evt.preventDefault();
    var imgUrl = this.refs.imgUrl.getValue();
    if (imgUrl === this.cache.imgUrl) {
      return;
    }
    this.cache.imgUrl = imgUrl;
    this.predict(imgUrl);
    var href = this.makeHref('demo', {}, {img_url: imgUrl});
    window.location.href = href;
  },

  render: function() {
    var elems = this.state.bet_result.map(function(result) {
      var tag = result.tag;
      return (

          {tag.tag_id}
          <Link to="datasets" params={{dataType: 'all'}} query={{tag: tag.name}}>{tag.name}</Link>
          {result.score}

      );
    });

    var image;
    if (this.cache.imgUrl) {
      image = ;
    }

    var time = 'loading...';
    if (this.state.time) {
      time = this.state.time + ' s';
    }

    return (

        <Well>

Enter an image url then predict tags.

          <form onSubmit={this.handleSubmit}>
            <Input type="url" name="img_url" ref="imgUrl" />
          </form>
        </Well>
        <Row className="result">
          <Col xs={6}>
            {image}
          </Col>
          <Col xs={6}>
            <Panel> Spend time: {time} </Panel>

                  #
                  Tag
                  Score

                {elems}

          </Col>
        </Row>

    );
  }
});

var routes = (
  <Route handler={App} path="/">
    <DefaultRoute handler={Dashboard} />
    <Route name="dashboard" handler={Dashboard} />
    <Route name="demo" handler={DEMO} />
    <Route name="datasets" handler={Datasets} path="/ds/:dataType"/>
    <NotFoundRoute handler={Datasets} />
  </Route>
);

ReactRouter.run(routes, function (Handler, state) {
  React.render(<Handler />, document.body);
});
<commit_msg>Add default predict images<commit_after>var Navbar = ReactBootstrap.Navbar;
var Nav = ReactBootstrap.Nav;
var NavItemLink = ReactRouterBootstrap.NavItemLink;
var ButtonLink = ReactRouterBootstrap.ButtonLink;
var ModalTrigger = ReactBootstrap.ModalTrigger;
var Modal = ReactBootstrap.Modal;
var Button = ReactBootstrap.Button;
var Input = ReactBootstrap.Input;
var Grid = ReactBootstrap.Grid;
var Row = ReactBootstrap.Row;
var Col = ReactBootstrap.Col;
var Well = ReactBootstrap.Well;
var Panel = ReactBootstrap.Panel;
var ListGroup = ReactBootstrap.ListGroup;
var ListGroupItem = ReactBootstrap.ListGroupItem;
var Table = ReactBootstrap.Table;
var State = ReactRouter.State;
var Navigation = ReactRouter.Navigation;
var RouteHandler = ReactRouter.RouteHandler;
var Route = ReactRouter.Route;
var Router = ReactRouter.Router;
var Link = ReactRouter.Link;
var DefaultRoute = ReactRouter.DefaultRoute;
var NotFoundRoute = ReactRouter.NotFoundRoute;

var Dataset = React.createClass({
  render: function() {
    var dataset = this.props.data;
    return (
      <Modal {...this.props} title="View" animation={false}>

{dataset.tag.name}

          <Button onClick={this.props.onRequestHide}>Close</Button>

      </Modal>
    )
  }
});

var Datasets = React.createClass({
  mixins: [State],

  waterfall: function() {
    if (this.cache.datasets.length === 0) {
      return;
    }
    jQuery("#waterfall").waterfall({
      selector: ".dataset",
    });
  },

  loadDatasets: function() {
    var self = this;
    var query = this.getQuery();
    var params = this.getParams();
    var max = query.max || '';
    var limit = Number(query.limit) || 50;
    var tag = query.tag || '';
    this.limit = limit;
    var dataType = params.dataType || 'all';
    jQuery.get('/api/datasets/?max=' + max + '&limit=' + limit + '&data_type=' + dataType + '&tag=' + tag, function(data) {
      self.setState(data);
    });
  },

  getInitialState: function() {
    this.cache = this.cache || {};
    return {datasets: []};
  },

  shouldLoadDatasets: function() {
    var path = this.getPath();
    if (this.cache.path !== path) {
      this.cache.path = path;
      return true;
    }
    return false;
  },

  shouldCleanDatasets: function() {
    var pathname = this.getPathname();
    var query = this.getQuery();
    if (this.cache.pathname !== pathname || this.cache.tag !== query.tag || !query.max) {
      this.cache.pathname = pathname;
      this.cache.tag = query.tag;
      this.cache.scroll = true;
      return true;
    }
    return false;
  },

  cleanDatasets: function() {
    this.cache.datasets = [];
  },

  componentDidMount: function() {
    this.cache.datasets = this.cache.datasets || [];
    this.componentDidUpdate();
  },

  componentDidUpdate: function() {
    if (this.shouldLoadDatasets()) {
      if (this.shouldCleanDatasets()) {
        this.cleanDatasets();
      }
      this.loadDatasets();
    } else {
      if (this.cache.scroll) {
        this.cache.scroll = false;
        window.scroll(0, 0);
      }
      this.waterfall();
    }
  },

  render: function() {
    var datasets = this.state.datasets || [];
    var loadMore;
    if (datasets.length >= this.limit) {
      var query = this.getQuery();
      query = query || {};
      query.max = datasets[datasets.length - 1].dataset_id;
      loadMore = (

          <ButtonLink bsStyle="info" bsSize="large"
              params={this.getParams()} to="datasets" query={query} block>加载更多...</ButtonLink>

      );
    }
    if (this.cache.datasets && this.cache.datasets.length > 0) {
      var oldLastDataset = this.cache.datasets[this.cache.datasets.length - 1];
      var lastDataset = datasets[datasets.length - 1];
      if (oldLastDataset.dataset_id !== lastDataset.dataset_id) {
        this.cache.datasets = this.cache.datasets.concat(datasets);
      }
    } else {
      this.cache.datasets = datasets;
    }
    var elems = this.cache.datasets.map(function(dataset) {
      var width = 192;
      var height = width / dataset.file.width * dataset.file.height;
      if (height > 600) {
        height = 600;
      }
      return (
        <ModalTrigger modal={<Dataset data={dataset} title={dataset.tag.name} />}>

{dataset.tag.name}

        </ModalTrigger>
      );
    });
    return (

          {elems}

        {loadMore}

    );
  }
});

var SearchForm = React.createClass({
  mixins: [State],

  getInitialState: function() {
    var query = this.getQuery();
    this.cache = this.cache || {};

    return {
      value: query.tag || '',
      tags: []
    };
  },

  getHint: function(word) {
    var self = this;
    jQuery.get('/api/tags/hint?word=' + word, function(data) {
      self.setState(data);
    });
  },

  shouldCleanHint: function() {
    var path = this.getPath();
    if (this.cache.path === path) {
      return false;
    }
    this.cache.path = path;
    return true;

  },

  cleanHint: function() {
    this.setState({tags: []});
  },

  componentDidUpdate: function() {
    if (this.cache.changed) {
      if (this.shouldCleanHint()) {
        this.cache.changed = false;
        this.cleanHint();
      }
      return;
    }
    var query = this.getQuery();
    if (query.tag && query.tag !== this.state.value) {
      this.setState({
        value: query.tag
      });
    } else if (!query.tag && this.state.value) {
      this.setState({value: ''});
    }
    this.cache.path = this.getPath();
  },

  handleChange: function() {
    this.cache.changed = true;
    this.getHint(this.refs.tag.getValue());
    this.setState({
      value: this.refs.tag.getValue()
    });
  },

  handleSubmit: function(evt) {
    evt.preventDefault();
    this.cache.changed = false;
    this.props.onSubmit(this.state.value);
  },

  handleListClick: function(eventKey, href, target) {
    this.cache.changed = false;
    this.props.onSubmit(target);
    this.setState({tags: [], value: target});
  },

  render: function() {
    var list = this.state.tags.map(function(tag) {
      return <ListGroupItem target={tag.name}> {tag.name}</ListGroupItem>;
    });
    return (
      <form className="navbar-form navbar-right" onSubmit={this.handleSubmit}>
        <Input type="text"
          name="tag"
          value={this.state.value}
          ref="tag"
          placeholder="Search..."
          onChange={this.handleChange} />

          <ListGroup onClick={this.handleListClick}>
            {list}
          </ListGroup>

      </form>
    );
  }
});

var App = React.createClass({
  mixins: [State, Navigation],

  getInitialState: function() {
    var tag = this.getQuery().tag || '';
    return {tag: tag}
  },

  handleSubmit: function(tag) {
    var query = this.getQuery();
    var params = this.getParams();
    params.dataType = params.dataType || 'all';
    query.tag = tag;
    delete query.max;
    var href = this.makeHref('datasets', params, query);
    window.location.href = href;
    this.setState({tag: tag || ''});
  },

  render: function() {
    return (

        <Navbar fixedTop inverse fluid brand="Huabot Brain">

            <NavItemLink to="dashboard">Dashboard</NavItemLink>

          <SearchForm onSubmit={this.handleSubmit} />
        </Navbar>
        <Grid fluid>
          <Row>
            <Col sm={3} md={2} className="sidebar">

                <NavItemLink to="datasets" params={{dataType: 'all'}}>所有数据</NavItemLink>
                <NavItemLink to="datasets" params={{dataType: 'train'}}>训练数据</NavItemLink>
                <NavItemLink to="datasets" params={{dataType: 'val'}}>验证数据</NavItemLink>
                <NavItemLink to="demo">DEMO</NavItemLink>

            </Col>
            <Col sm={9} smOffset={3} md={10} mdOffset={2}>
              <RouteHandler />
            </Col>
          </Row>
        </Grid>

    );
  }
});

var Dashboard = React.createClass({
  mixins: [State],

  loadTags: function() {
    var self = this;
    var query = this.getQuery();
    var max = query.max || '';
    var limit = Number(query.limit) || 20;
    this.limit = limit;
    jQuery.get('/api/tags/?max=' + max + '&limit=' + limit, function(data) {
      self.setState(data);
    });

  },

  getInitialState: function() {
    this.cache = this.cache || {};
    return {tags: []};
  },

  shouldLoadTags: function() {
    var path = this.getPath();
    if (this.cache.path !== path) {
      this.cache.path = path;
      return true;
    }
    return false;
  },

  shouldCleanTags: function() {
    if (!this.getQuery().max) {
      return true;
    }
    return false;
  },

  cleanTags: function() {
    this.cache.tags = [];
  },

  componentDidMount: function() {
    this.cache.tags = this.cache.tags || [];
    this.componentDidUpdate();
  },

  componentDidUpdate: function() {
    if (this.shouldLoadTags()) {
      if (this.shouldCleanTags()) {
        this.cache.scroll = true;
        this.cleanTags();
      }
      this.loadTags();
    } else {
      if (this.cache.scroll) {
        this.cache.scroll = false;
        window.scroll(0, 0);
      }
    }
  },

  render: function() {
    var tags = this.state.tags || [];
    var loadMore;
    if (tags.length >= this.limit) {
      var query = this.getQuery();
      query = query || {};
      query.max = tags[tags.length - 1].tag_id;
      loadMore = (

          <ButtonLink bsStyle="info" bsSize="large"
              params={this.getParams()} to="dashboard" query={query} block>加载更多...</ButtonLink>

      );
    }
    if (this.cache.tags && this.cache.tags.length > 0) {
      var oldLastTag = this.cache.tags[this.cache.tags.length - 1];
      var lastTag = tags[tags.length - 1];
      if (oldLastTag.tag_id !== lastTag.tag_id) {
        this.cache.tags = this.cache.tags.concat(tags);
      }
    } else {
      this.cache.tags = tags;
    }
    var elems = this.cache.tags.map(function(tag) {
      return (

          {tag.tag_id}
          <Link to="datasets" params={{dataType: 'all'}} query={{tag: tag.name}}>{tag.name}</Link>
          {tag.train_count}
          {tag.test_count}

      );
    });
    return (

 Dashboard

        <Row className="train-status">
          <Col xs={6}>

          </Col>
          <Col xs={6}>

          </Col>
        </Row>

Tags

              #
              Name
              Train
              Test

            {elems}

        {loadMore}

    );
  }
});

var DEMO = React.createClass({
  mixins: [State, Navigation],

  predict: function(img_url) {
    var self = this;
    jQuery.post('/api/predict', {img_url: img_url}, function(data) {
      self.setState(data);
    });

  },

  getInitialState: function() {
    this.cache = {};
    var query = this.getQuery();
    var img_url = query.img_url || '';
    return {bet_result: [], time: null, err: null};
  },

  componentDidMount: function() {
    this.cache.imgUrl = 'http://img.hb.aicdn.com/d3a5039f151ddf451b95ea1b9b7e6af73f189c3a5f23-MMFBG3_fw320';
    this.predict(this.cache.imgUrl);
    this.componentDidUpdate();
  },

  componentDidUpdate: function() {
    var query = this.getQuery();
    if (query.img_url && query.img_url !== this.cache.imgUrl) {
      this.cache.imgUrl = query.img_url;
      this.predict(this.cache.imgUrl);
    }
  },

  handleSubmit: function(evt) {
    evt.preventDefault();
    var imgUrl = this.refs.imgUrl.getValue();
    if (imgUrl === this.cache.imgUrl) {
      return;
    }
    this.cache.imgUrl = imgUrl;
    this.predict(imgUrl);
    var href = this.makeHref('demo', {}, {img_url: imgUrl});
    window.location.href = href;
  },

  render: function() {
    var elems = this.state.bet_result.map(function(result) {
      var tag = result.tag;
      return (

          {tag.tag_id}
          <Link to="datasets" params={{dataType: 'all'}} query={{tag: tag.name}}>{tag.name}</Link>
          {result.score}

      );
    });

    var image;
    if (this.cache.imgUrl) {
      image = ;
    }

    var time = 'loading...';
    if (this.state.time) {
      time = this.state.time + ' s';
    }

    return (

        <Well>

Enter an image url then predict tags.

          <form onSubmit={this.handleSubmit}>
            <Input type="url" name="img_url" ref="imgUrl" />
          </form>
        </Well>
        <Row className="result">
          <Col xs={6}>
            {image}
          </Col>
          <Col xs={6}>
            <Panel> Spend time: {time} </Panel>

                  #
                  Tag
                  Score

                {elems}

          </Col>
        </Row>

    );
  }
});

var routes = (
  <Route handler={App} path="/">
    <DefaultRoute handler={Dashboard} />
    <Route name="dashboard" handler={Dashboard} />
    <Route name="demo" handler={DEMO} />
    <Route name="datasets" handler={Datasets} path="/ds/:dataType"/>
    <NotFoundRoute handler={Datasets} />
  </Route>
);

ReactRouter.run(routes, function (Handler, state) {
  React.render(<Handler />, document.body);
});
<|endoftext|>


<commit_before>Crypto.MARC4 = function () {

	// Shortcut
	var util = Crypto.util;

	// IV length in bytes
	var ivLen = 16;

	// Drop n keystream bytes
	var drop = 1536;

	return {

		/**
		 * Public API
		 */

		encrypt: function (message, key) {

			// Convert to bytes
			var M = util.string_bytes(message),
			    K = util.string_bytes(key);

			// Attach random IV
			for (var IV = [], i = 0; i < ivLen; i++)
				IV.push(Math.floor(Math.random() * 256));
			K = K.concat(IV);

			// Encrypt
			this._MARC4(M, K, drop);

			// Return ciphertext
			return util.bytes_base64(IV.concat(M));

		},

		decrypt: function (ciphertext, key) {

			// Convert to bytes
			var C = util.base64_bytes(ciphertext),
			    K = util.string_bytes(key);

			// Separate IV and message
			var IV = C.splice(0, ivLen);

			// Attach IV
			K = K.concat(IV);

			// Decrypt
			this._MARC4(C, K, drop);

			// Return plaintext
			return util.bytes_string(C);

		},

		/**
		 * Internal methods
		 */

		// The core
		_MARC4: function (M, K, drop) {

			// State variables
			var i, j, S;

			// Key setup
			for (i = 0, S = []; i < 256; i++) S[i] = i;
			for (i = 0, j = 0;  i < 256; i++) {

				j = (j + S[i] + K[i % K.length]) % 256;

				// Swap
				S[i] ^= S[j];
				S[j] ^= S[i];
				S[i] ^= S[j];

			}

			// Clear counters
			i = j = 0;

			// Encryption
			for (var k = 0 - drop; k < M.length; k++) {

				i = (i + 1) % 256;
				j = (j + S[i]) % 256;

				// Swap
				S[i] ^= S[j];
				S[j] ^= S[i];
				S[i] ^= S[j];

				// Stop here if we're still dropping keystream
				if (k < 0) continue;

				// Encrypt
				M[k] ^= S[(S[i] + S[j]) % 256];

			}

		}

	};

}();
<commit_msg>Cleanup.<commit_after>Crypto.MARC4 = function () {

	// Shortcut
	var util = Crypto.util;

	return {

		/**
		 * Public API
		 */

		encrypt: function (message, key) {

			// Convert to bytes
			var M = util.string_bytes(message),
			    K = util.string_bytes(key);

			// Attach random IV
			for (var IV = [], i = 0; i < 16; i++)
				IV.push(Math.floor(Math.random() * 256));
			K = K.concat(IV);

			// Encrypt
			this._MARC4(M, K, 1536);

			// Return ciphertext
			return util.bytes_base64(IV.concat(M));

		},

		decrypt: function (ciphertext, key) {

			// Convert to bytes
			var C = util.base64_bytes(ciphertext),
			    K = util.string_bytes(key);

			// Separate IV and message
			var IV = C.splice(0, 16);

			// Attach IV
			K = K.concat(IV);

			// Decrypt
			this._MARC4(C, K, 1536);

			// Return plaintext
			return util.bytes_string(C);

		},

		/**
		 * Internal methods
		 */

		// The core
		_MARC4: function (M, K, drop) {

			// State variables
			var i, j, S;

			// Key setup
			for (i = 0, S = []; i < 256; i++) S[i] = i;
			for (i = 0, j = 0;  i < 256; i++) {

				j = (j + S[i] + K[i % K.length]) % 256;

				// Swap
				S[i] ^= S[j];
				S[j] ^= S[i];
				S[i] ^= S[j];

			}

			// Clear counters
			i = j = 0;

			// Encryption
			for (var k = 0 - drop; k < M.length; k++) {

				i = (i + 1) % 256;
				j = (j + S[i]) % 256;

				// Swap
				S[i] ^= S[j];
				S[j] ^= S[i];
				S[i] ^= S[j];

				// Stop here if we're still dropping keystream
				if (k < 0) continue;

				// Encrypt
				M[k] ^= S[(S[i] + S[j]) % 256];

			}

		}

	};

}();
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var errors = require('./errors.js');

var TOMBSTONE_TTL_OFFSET = 500;

module.exports = Operations;

function Operations(opts) {
    var self = this;

    self.timers = opts.timers;
    self.logger = opts.logger;
    self.random = opts.random;
    self.timeoutCheckInterval = opts.timeoutCheckInterval;
    self.timeoutFuzz = opts.timeoutFuzz;
    self.initTimeout = opts.initTimeout;
    self.connectionStalePeriod = opts.connectionStalePeriod;

    self.connection = opts.connection;
    self.startTime = self.timers.now();
    self.timer = null;
    self.destroyed = false;

    self.tombstones = {
        out: []
    };
    self.requests = {
        in: Object.create(null),
        out: Object.create(null)
    };
    self.pending = {
        in: 0,
        out: 0
    };
    self.lastTimeoutTime = 0;
}

function OperationTombstone(id, time, timeout) {
    var self = this;

    self.id = id;
    self.time = time;
    self.timeout = timeout;
}

Operations.prototype.startTimeoutTimer =
function startTimeoutTimer() {
    var self = this;

    self.timer = self.timers.setTimeout(
        onChannelTimeout, self._getTimeoutDelay()
    );

    function onChannelTimeout() {
        self._onTimeoutCheck();
    }
};

Operations.prototype.getRequests = function getRequests() {
    var self = this;

    return self.requests;
};

Operations.prototype.getPending = function getPending() {
    var self = this;

    return self.pending;
};

Operations.prototype.getOutReq = function getOutReq(id) {
    var self = this;

    return self.requests.out[id];
};

Operations.prototype.getInReq = function getInReq(id) {
    var self = this;

    return self.requests.in[id];
};

Operations.prototype.addOutReq = function addOutReq(req) {
    var self = this;

    self.requests.out[req.id] = req;
    self.pending.out++;

    return req;
};

Operations.prototype.addInReq = function addInReq(req) {
    var self = this;

    self.requests.in[req.id] = req;
    self.pending.in++;

    return req;
};

Operations.prototype.popOutReq = function popOutReq(id, context) {
    var self = this;

    var req = self.requests.out[id];
    if (!req) {
        self.logMissingOutRequest(id, context);
        return null;
    }

    delete self.requests.out[id];

    var now = self.timers.now();
    var timeout = now + TOMBSTONE_TTL_OFFSET + req.ttl +
        self._getTimeoutFuzz();

    self.tombstones.out.push(new OperationTombstone(
        req.id, self.timers.now(), timeout
    ));
    self.pending.out--;

    return req;
};

Operations.prototype.logMissingOutRequest =
function logMissingOutRequest(id, context) {
    var self = this;

    var tombstones = self.tombstones.out;
    var isStale = false;

    for (var i = 0; i < tombstones.length; i++) {
        if (tombstones[i].id === id) {
            isStale = true;
            break;
        }
    }

    // If this id has been timed out then just return
    if (isStale) {
        return null;
    }

    // context is err or res
    if (context.originalId) {
        context = {
            error: context,
            id: context.originalId,
            info: 'got error frame for unknown id'
        };
    } else if (context.id) {
        context = {
            responseId: context.id,
            code: context.code,
            arg1: Buffer.isBuffer(context.arg1) ?
                String(context.arg1) : 'streamed-arg1',
            info: 'got call response for unknown id'
        };
    }

    // This could be because of a confused / corrupted server.
    self.logger.info('popOutReq received for unknown or lost id', {
        context: context,
        socketRemoteAddr: self.connection.socketRemoteAddr,
        direction: self.connection.direction
    });
};

Operations.prototype.popInReq = function popInReq(id) {
    var self = this;

    var req = self.requests.in[id];
    if (!req) {
        // TODO warn ?
        return null;
    }

    delete self.requests.in[id];
    self.pending.in--;

    return req;
};

Operations.prototype.removeReq = function removeReq(id) {
    var self = this;

    if (id in self.requests.in) {
        delete self.requests.in[id];
    } else if (id in self.requests.out) {
        delete self.requests.out[id];
    }
};

Operations.prototype.clear = function clear() {
    var self = this;

    self.pending.in = 0;
    self.pending.out = 0;
    self.tombstones.out = [];
};

Operations.prototype.destroy = function destroy() {
    var self = this;

    self.destroyed = true;

    if (self.timer) {
        self.timers.clearTimeout(self.timer);
        self.timer = null;
    }
};

// timeout check runs every timeoutCheckInterval +/- some random fuzz. Range is from
//   base - fuzz/2 to base + fuzz/2
Operations.prototype._getTimeoutDelay =
function _getTimeoutDelay() {
    var self = this;

    return self.timeoutCheckInterval + self._getTimeoutFuzz();
};

Operations.prototype._getTimeoutFuzz =
function _getTimeoutFuzz() {
    var self = this;

    var fuzz = self.timeoutFuzz;
    if (!fuzz) {
        return 0;
    }

    return Math.round(Math.floor(self.random() * fuzz)) - (fuzz / 2);
};

// If the connection has some success and some timeouts, we should probably leave it up,
// but if everything is timing out, then we should kill the connection.
Operations.prototype._onTimeoutCheck =
function _onTimeoutCheck() {
    var self = this;
    if (self.destroyed) {
        return;
    }

    var isInitialized = self.connection.remoteName;
    if (!isInitialized) {
        var elapsed = self.timers.now() - self.startTime;
        if (elapsed >= self.initTimeout) {
            self.connection.timedOutEvent
                .emit(self, errors.ConnectionTimeoutError({
                    start: self.startTime,
                    elapsed: elapsed,
                    timeout: self.initTimeout
                }));
            return;
        }
    }

    self._checkTimeout(self.requests.out, 'out');
    self._checkTimeout(self.requests.in, 'in');

    var now = self.timers.now();
    var tombstones = [];
    for (var i = 0; i < self.tombstones.out.length; i++) {
        var tombstone = self.tombstones.out[i];
        if (now < tombstone.timeout) {
            tombstones.push(tombstone);
        }
    }
    self.tombstones.out = tombstones;

    self.startTimeoutTimer();
};

Operations.prototype._checkTimeout =
function _checkTimeout(ops, direction) {
    var self = this;
    var opKeys = Object.keys(ops);
    for (var i = 0; i < opKeys.length; i++) {
        var id = opKeys[i];
        var req = ops[id];
        if (req === undefined) {
            self.logger.warn('unexpected undefined request', {
                direction: direction,
                id: id
            });
        } else if (req.timedOut) {
            self.logger.warn('lingering timed-out request', {
                direction: direction,
                id: id
            });

            if (direction === 'in') {
                self.popInReq(id);
            } else if (direction === 'out') {
                self.popOutReq(id);
            }
        } else if (req.checkTimeout()) {
            if (direction === 'out') {
                var now = self.timers.now();
                if (self.lastTimeoutTime &&
                    now > self.lastTimeoutTime + self.connectionStalePeriod
                ) {
                    var err = errors.ConnectionStaleTimeoutError({
                        lastTimeoutTime: self.lastTimeoutTime
                    });
                    self.connection.timedOutEvent
                        .emit(self, err);
                } else if (!self.lastTimeoutTime) {
                    self.lastTimeoutTime = self.timers.now();
                }

            }
            // else
            //     req.res.sendError // XXX may need to build
            if (direction === 'in') {
                self.popInReq(id);
            } else if (direction === 'out') {
                self.popOutReq(id);
            }
        }
    }
};
<commit_msg>[BUG] gaurd against context undefined<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var errors = require('./errors.js');

var TOMBSTONE_TTL_OFFSET = 500;

module.exports = Operations;

function Operations(opts) {
    var self = this;

    self.timers = opts.timers;
    self.logger = opts.logger;
    self.random = opts.random;
    self.timeoutCheckInterval = opts.timeoutCheckInterval;
    self.timeoutFuzz = opts.timeoutFuzz;
    self.initTimeout = opts.initTimeout;
    self.connectionStalePeriod = opts.connectionStalePeriod;

    self.connection = opts.connection;
    self.startTime = self.timers.now();
    self.timer = null;
    self.destroyed = false;

    self.tombstones = {
        out: []
    };
    self.requests = {
        in: Object.create(null),
        out: Object.create(null)
    };
    self.pending = {
        in: 0,
        out: 0
    };
    self.lastTimeoutTime = 0;
}

function OperationTombstone(id, time, timeout) {
    var self = this;

    self.id = id;
    self.time = time;
    self.timeout = timeout;
}

Operations.prototype.startTimeoutTimer =
function startTimeoutTimer() {
    var self = this;

    self.timer = self.timers.setTimeout(
        onChannelTimeout, self._getTimeoutDelay()
    );

    function onChannelTimeout() {
        self._onTimeoutCheck();
    }
};

Operations.prototype.getRequests = function getRequests() {
    var self = this;

    return self.requests;
};

Operations.prototype.getPending = function getPending() {
    var self = this;

    return self.pending;
};

Operations.prototype.getOutReq = function getOutReq(id) {
    var self = this;

    return self.requests.out[id];
};

Operations.prototype.getInReq = function getInReq(id) {
    var self = this;

    return self.requests.in[id];
};

Operations.prototype.addOutReq = function addOutReq(req) {
    var self = this;

    self.requests.out[req.id] = req;
    self.pending.out++;

    return req;
};

Operations.prototype.addInReq = function addInReq(req) {
    var self = this;

    self.requests.in[req.id] = req;
    self.pending.in++;

    return req;
};

Operations.prototype.popOutReq = function popOutReq(id, context) {
    var self = this;

    var req = self.requests.out[id];
    if (!req) {
        self.logMissingOutRequest(id, context);
        return null;
    }

    delete self.requests.out[id];

    var now = self.timers.now();
    var timeout = now + TOMBSTONE_TTL_OFFSET + req.ttl +
        self._getTimeoutFuzz();

    self.tombstones.out.push(new OperationTombstone(
        req.id, self.timers.now(), timeout
    ));
    self.pending.out--;

    return req;
};

Operations.prototype.logMissingOutRequest =
function logMissingOutRequest(id, context) {
    var self = this;

    var tombstones = self.tombstones.out;
    var isStale = false;

    for (var i = 0; i < tombstones.length; i++) {
        if (tombstones[i].id === id) {
            isStale = true;
            break;
        }
    }

    // If this id has been timed out then just return
    if (isStale) {
        return null;
    }

    // context is err or res
    if (context && context.originalId) {
        context = {
            error: context,
            id: context.originalId,
            info: 'got error frame for unknown id'
        };
    } else if (context && context.id) {
        context = {
            responseId: context.id,
            code: context.code,
            arg1: Buffer.isBuffer(context.arg1) ?
                String(context.arg1) : 'streamed-arg1',
            info: 'got call response for unknown id'
        };
    }

    // This could be because of a confused / corrupted server.
    self.logger.info('popOutReq received for unknown or lost id', {
        context: context,
        socketRemoteAddr: self.connection.socketRemoteAddr,
        direction: self.connection.direction
    });
};

Operations.prototype.popInReq = function popInReq(id) {
    var self = this;

    var req = self.requests.in[id];
    if (!req) {
        // TODO warn ?
        return null;
    }

    delete self.requests.in[id];
    self.pending.in--;

    return req;
};

Operations.prototype.removeReq = function removeReq(id) {
    var self = this;

    if (id in self.requests.in) {
        delete self.requests.in[id];
    } else if (id in self.requests.out) {
        delete self.requests.out[id];
    }
};

Operations.prototype.clear = function clear() {
    var self = this;

    self.pending.in = 0;
    self.pending.out = 0;
    self.tombstones.out = [];
};

Operations.prototype.destroy = function destroy() {
    var self = this;

    self.destroyed = true;

    if (self.timer) {
        self.timers.clearTimeout(self.timer);
        self.timer = null;
    }
};

// timeout check runs every timeoutCheckInterval +/- some random fuzz. Range is from
//   base - fuzz/2 to base + fuzz/2
Operations.prototype._getTimeoutDelay =
function _getTimeoutDelay() {
    var self = this;

    return self.timeoutCheckInterval + self._getTimeoutFuzz();
};

Operations.prototype._getTimeoutFuzz =
function _getTimeoutFuzz() {
    var self = this;

    var fuzz = self.timeoutFuzz;
    if (!fuzz) {
        return 0;
    }

    return Math.round(Math.floor(self.random() * fuzz)) - (fuzz / 2);
};

// If the connection has some success and some timeouts, we should probably leave it up,
// but if everything is timing out, then we should kill the connection.
Operations.prototype._onTimeoutCheck =
function _onTimeoutCheck() {
    var self = this;
    if (self.destroyed) {
        return;
    }

    var isInitialized = self.connection.remoteName;
    if (!isInitialized) {
        var elapsed = self.timers.now() - self.startTime;
        if (elapsed >= self.initTimeout) {
            self.connection.timedOutEvent
                .emit(self, errors.ConnectionTimeoutError({
                    start: self.startTime,
                    elapsed: elapsed,
                    timeout: self.initTimeout
                }));
            return;
        }
    }

    self._checkTimeout(self.requests.out, 'out');
    self._checkTimeout(self.requests.in, 'in');

    var now = self.timers.now();
    var tombstones = [];
    for (var i = 0; i < self.tombstones.out.length; i++) {
        var tombstone = self.tombstones.out[i];
        if (now < tombstone.timeout) {
            tombstones.push(tombstone);
        }
    }
    self.tombstones.out = tombstones;

    self.startTimeoutTimer();
};

Operations.prototype._checkTimeout =
function _checkTimeout(ops, direction) {
    var self = this;
    var opKeys = Object.keys(ops);
    for (var i = 0; i < opKeys.length; i++) {
        var id = opKeys[i];
        var req = ops[id];
        if (req === undefined) {
            self.logger.warn('unexpected undefined request', {
                direction: direction,
                id: id
            });
        } else if (req.timedOut) {
            self.logger.warn('lingering timed-out request', {
                direction: direction,
                id: id
            });

            if (direction === 'in') {
                self.popInReq(id);
            } else if (direction === 'out') {
                self.popOutReq(id);
            }
        } else if (req.checkTimeout()) {
            if (direction === 'out') {
                var now = self.timers.now();
                if (self.lastTimeoutTime &&
                    now > self.lastTimeoutTime + self.connectionStalePeriod
                ) {
                    var err = errors.ConnectionStaleTimeoutError({
                        lastTimeoutTime: self.lastTimeoutTime
                    });
                    self.connection.timedOutEvent
                        .emit(self, err);
                } else if (!self.lastTimeoutTime) {
                    self.lastTimeoutTime = self.timers.now();
                }

            }
            // else
            //     req.res.sendError // XXX may need to build
            if (direction === 'in') {
                self.popInReq(id);
            } else if (direction === 'out') {
                self.popOutReq(id);
            }
        }
    }
};
<|endoftext|>


<commit_before>import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit, click, fillIn, triggerEvent } from '@ember/test-helpers';

function findTriggerElementWithLabel(labelRegex) {
  return [...this.element.querySelectorAll('.cs-toolbox-section label')].find(element => labelRegex.test(element.textContent));
}

function findInputWithValue(value) {
  return Array.from(this.element.querySelectorAll('input'))
    .find(element => element.value === value);
}

module('Acceptance | tools', function(hooks) {
  setupApplicationTest(hooks);

  hooks.beforeEach(function() {
    delete localStorage['cardstack-tools'];
  });

  hooks.afterEach(function() {
    delete localStorage['cardstack-tools'];
  });

  test('activate tools', async function(assert) {
    await visit('/1');
    await click('.cardstack-tools-launcher');

    let element = findTriggerElementWithLabel.call(this, /Title/);
    await click(element);
    let matching = findInputWithValue.call(this, 'hello world');
    assert.ok(matching, 'found field editor for title');

    element = findTriggerElementWithLabel.call(this, /Body/);
    await click(element);
    matching = findInputWithValue.call(this, 'Look behind you, a Three-Headed Monkey!');
    assert.ok(matching, 'found field editor for comment body');

    element = findTriggerElementWithLabel.call(this, /Name/);
    await click(element);
    matching = findInputWithValue.call(this, 'Guybrush Threepwood');
    assert.ok(matching, 'found field editor for comment author name');
  });

  test('show validation error', async function(assert) {
    await visit('/1');
    await click('.cardstack-tools-launcher');

    let element = findTriggerElementWithLabel.call(this, /Title/);
    await click(element);

    let titleEditor = findInputWithValue.call(this, 'hello world');
    await fillIn(titleEditor, '');
    await triggerEvent(titleEditor, 'blur');

    assert.dom('.field-editor--error-message').hasText('title must not be empty');
  });
});
<commit_msg>Add test for validating owned relationship field<commit_after>import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { visit, click, fillIn, triggerEvent } from '@ember/test-helpers';

function findTriggerElementWithLabel(labelRegex) {
  return [...this.element.querySelectorAll('.cs-toolbox-section label')].find(element => labelRegex.test(element.textContent));
}

function findInputWithValue(value) {
  return Array.from(this.element.querySelectorAll('input'))
    .find(element => element.value === value);
}

module('Acceptance | tools', function(hooks) {
  setupApplicationTest(hooks);

  hooks.beforeEach(function() {
    delete localStorage['cardstack-tools'];
  });

  hooks.afterEach(function() {
    delete localStorage['cardstack-tools'];
  });

  test('activate tools', async function(assert) {
    await visit('/1');
    await click('.cardstack-tools-launcher');

    let element = findTriggerElementWithLabel.call(this, /Title/);
    await click(element);
    let matching = findInputWithValue.call(this, 'hello world');
    assert.ok(matching, 'found field editor for title');

    element = findTriggerElementWithLabel.call(this, /Body/);
    await click(element);
    matching = findInputWithValue.call(this, 'Look behind you, a Three-Headed Monkey!');
    assert.ok(matching, 'found field editor for comment body');

    element = findTriggerElementWithLabel.call(this, /Name/);
    await click(element);
    matching = findInputWithValue.call(this, 'Guybrush Threepwood');
    assert.ok(matching, 'found field editor for comment author name');
  });

  test('show validation error', async function(assert) {
    await visit('/1');
    await click('.cardstack-tools-launcher');

    let element = findTriggerElementWithLabel.call(this, /Title/);
    await click(element);

    let titleEditor = findInputWithValue.call(this, 'hello world');
    await fillIn(titleEditor, '');
    await triggerEvent(titleEditor, 'blur');
    assert.dom('.field-editor--error-message').hasText('title must not be empty');

    element = findTriggerElementWithLabel.call(this, /Body/);
    await click(element);

    let commentBodyEditor = findInputWithValue.call(this, 'Look behind you, a Three-Headed Monkey!');
    await fillIn(commentBodyEditor, '');
    await triggerEvent(commentBodyEditor, 'blur');
    assert.dom('.field-editor--error-message').hasText('body must not be empty');
  });
});
<|endoftext|>


<commit_before>/**
 * @classdesc 指令存在于某个组件域中,表现为一个自定义属性。
 * 组件的生命周期：
 *

 *

 *
onCreate：当指令被创建时触发

 *
onInit：当指令初始化时触发

 *
onActive: 当指令激活时触发

 *
onUpdate: 当指令条件变更时触发

 *
onDestroy：当指令被销毁时触发

 *

 *

 * @class
 */
function Directive (name,value) {
	View.call(this);
	/**
	 * 指令的字面值
	 */
	this.value = value;
	/**
	 * 指令名称
	 */
	this.name = name;
	/**
	 * 参数列表
	 * @type {Array}
	 */
	this.params;
	/**
	 * 过滤函数
	 * @type {Function}
	 */
	this.filter;
	/**
	 * 指令所在的组件
	 */
	this.component;

	/**
	 * 是否终结

	 * 终结指令会告诉扫描器不对该组件的内部进行扫描，包括表达式，指令，子组件都不会生成
	 * @type {Boolean}
	 * @default false
	 */
	this.final = false;
	/**
	 * 指令优先级用于定义同类指令的执行顺序。最大999
	 * @type {Number}
	 */
	this.priority = 0;
}
Util.inherits(Directive,View);
Util.ext(Directive.prototype,{
	init:function(){
		if(this.__state == Component.state.inited){
			return;
		}
		//预处理自定义标签中的表达式
		var expNode = Scanner.getExpNode(this.value,this.component);
		var calcVal = expNode && Renderer.calcExpNode(expNode);
		if(calcVal)this.value = calcVal;

		LOGGER.log(this,'inited');

		if(this.onInit){
			this.onInit();
		}

		this.__state = Component.state.inited;
	},
	//component invoke only
	active:function(){
		//do observe
		if(this.onUpdate){
			var expObj = lexer(this.value);
			for(var varStr in expObj.varTree){
				var varObj = expObj.varTree[varStr];

				var aon = new AttrObserveNode(this,expObj);

				//监控变量
				if(this.component)
				Builder.buildExpModel(this.component,varObj,aon);
			}

			var rs = Renderer.evalExp(this.component,expObj);
			this.onUpdate(rs);
		}

		this.onActive && this.onActive();

	},
	/**
	 * 销毁指令
	 */
	destroy:function(){
		LOGGER.log(this,'destroy');

		this.__destroy(this);

		this.component =
		this.params =
		this.filter =
		this.onUpdate = null;

		this.onDestroy && this.onDestroy();
	}
});<commit_msg>add parameter when call onActive<commit_after>/**
 * @classdesc 指令存在于某个组件域中,表现为一个自定义属性。
 * 组件的生命周期：
 *

 *

 *
onCreate：当指令被创建时触发

 *
onInit：当指令初始化时触发

 *
onActive: 当指令激活时触发

 *
onUpdate: 当指令条件变更时触发

 *
onDestroy：当指令被销毁时触发

 *

 *

 * @class
 */
function Directive (name,value) {
	View.call(this);
	/**
	 * 指令的字面值
	 */
	this.value = value;
	/**
	 * 指令名称
	 */
	this.name = name;
	/**
	 * 参数列表
	 * @type {Array}
	 */
	this.params;
	/**
	 * 过滤函数
	 * @type {Function}
	 */
	this.filter;
	/**
	 * 指令所在的组件
	 */
	this.component;

	/**
	 * 是否终结

	 * 终结指令会告诉扫描器不对该组件的内部进行扫描，包括表达式，指令，子组件都不会生成
	 * @type {Boolean}
	 * @default false
	 */
	this.final = false;
	/**
	 * 指令优先级用于定义同类指令的执行顺序。最大999
	 * @type {Number}
	 */
	this.priority = 0;
}
Util.inherits(Directive,View);
Util.ext(Directive.prototype,{
	init:function(){
		if(this.__state == Component.state.inited){
			return;
		}
		//预处理自定义标签中的表达式
		var expNode = Scanner.getExpNode(this.value,this.component);
		var calcVal = expNode && Renderer.calcExpNode(expNode);
		if(calcVal)this.value = calcVal;

		LOGGER.log(this,'inited');

		if(this.onInit){
			this.onInit();
		}

		this.__state = Component.state.inited;
	},
	//component invoke only
	active:function(){
		//do observe
		if(this.onUpdate){
			var expObj = lexer(this.value);
			for(var varStr in expObj.varTree){
				var varObj = expObj.varTree[varStr];

				var aon = new AttrObserveNode(this,expObj);

				//监控变量
				if(this.component)
				Builder.buildExpModel(this.component,varObj,aon);
			}

			var rs = Renderer.evalExp(this.component,expObj);
			this.onUpdate(rs);
		}

		this.onActive && this.onActive(rs);

	},
	/**
	 * 销毁指令
	 */
	destroy:function(){
		LOGGER.log(this,'destroy');

		this.__destroy(this);

		this.component =
		this.params =
		this.filter =
		this.onUpdate = null;

		this.onDestroy && this.onDestroy();
	}
});<|endoftext|>


<commit_before>const express = require('express');
const request = require('request');
const router = express.Router();
// --------------------------------------------------------------------------------------
// get invalid records for specific date
// --------------------------------------------------------------------------------------
router.get('/:date', function(req, res, next) {
  if(isNaN(Date.parse(req.params.date))) {  // validate date
    res.status(500).send({ error : "invalid date: " + req.params.date});        // send error status and message
    return;
  }

  var date = new Date(req.params.date);
  date.setHours(0);
  date.setMinutes(0);
  date.setSeconds(0);
  date.setMilliseconds(0);  // set to 00:00:00:000

  req.db.invalid_records.find({timestamp : date}, { _id : 0, timestamp : 0 }, function (err, items) {
    respond(err, items, res)
  });
});
// --------------------------------------------------------------------------------------
// filter invalid records data to gen only one entry per pair [ pn, csi ]
// --------------------------------------------------------------------------------------
//var filter_data = function(data)
function filter_data(data)
{
  var filter = [];   // filtering "rules"
  var ret = [];

  for(var item in data) {       // create filtering rules
    var fields = data[item].split("#");
    var csi = fields[4];
    var pn = fields[5];
    var id = csi + "#" + pn;

    // item already in array, do not add it again
    if(filter.includes(id))
      continue;

    filter.push(id);     // combined id from csi and pn
  }

// --------------------------------------------------------------------------------------
// filter by pair [ pn, csi ]
// both pn and csi may be empty at the same time - in that case, we want all the records including this pair
// => filtering is exclusion based

  for(var item in filter) {
    var cnt = 0;

    for(var line in data) {
      if(data[line].indexOf(filter[item]) != -1) {  // pair present in line
        if(cnt == 0)    // first occurence
          ret.push(data[line]);     // add to result

        cnt++;
      }
    }
  }

  return ret;
}
// --------------------------------------------------------------------------------------
// get invalid records and perform filtering
// --------------------------------------------------------------------------------------
router.get('/filtered/:date/', function(req, res, next) {
  var url_base = 'https://etlog.cesnet.cz:8443';
  var url = "/invalid_records/";

  request.get({
    url: url_base + url + req.params.date,
  }, function (error, response, body) {
    if(error || (response.status != undefined && response.status != 200)) {
      if(error) {   // handle error
        console.log(error);
        res.end(error);
      }
      else {    // handle status code
        console.log(JSON.parse(body).error);
        res.end(JSON.parse(body).error);
      }
    }
    else {
      if(JSON.parse(body).length == 0) {   // handle empty data
        res.json([]);
      }
      else {    // non empty response
        res.json(filter_data(JSON.parse(body)[0]['records']));      // body contains array which contains dict, the key "records" holds the data
      }
    }
  });

});
// --------------------------------------------------------------------------------------
// send data to user
// --------------------------------------------------------------------------------------
function respond(err, items, res) {
  if(err) {
    console.log(err);
    res.send(err);
    return;
  }

  res.json(items);
}
// --------------------------------------------------------------------------------------
module.exports = router;
<commit_msg>update returned response<commit_after>const express = require('express');
const request = require('request');
const router = express.Router();
// --------------------------------------------------------------------------------------
// get invalid records for specific date
// --------------------------------------------------------------------------------------
router.get('/:date', function(req, res, next) {
  if(isNaN(Date.parse(req.params.date))) {  // validate date
    res.status(500).send({ error : "invalid date: " + req.params.date});        // send error status and message
    return;
  }

  var date = new Date(req.params.date);
  date.setHours(0);
  date.setMinutes(0);
  date.setSeconds(0);
  date.setMilliseconds(0);  // set to 00:00:00:000

  req.db.invalid_records.find({timestamp : date}, { _id : 0, timestamp : 0 }, function (err, items) {
    if(items.length != 0)       // database returned data
      items = items[0]['records']; // convert [{"records":[ ... ]}] to [ ... ]

    respond(err, items, res)
  });
});
// --------------------------------------------------------------------------------------
// filter invalid records data to gen only one entry per pair [ pn, csi ]
// --------------------------------------------------------------------------------------
//var filter_data = function(data)
function filter_data(data)
{
  var filter = [];   // filtering "rules"
  var ret = [];

  for(var item in data) {       // create filtering rules
    var fields = data[item].split("#");
    var csi = fields[4];
    var pn = fields[5];
    var id = csi + "#" + pn;

    // item already in array, do not add it again
    if(filter.includes(id))
      continue;

    filter.push(id);     // combined id from csi and pn
  }

// --------------------------------------------------------------------------------------
// filter by pair [ pn, csi ]
// both pn and csi may be empty at the same time - in that case, we want all the records including this pair
// => filtering is exclusion based

  for(var item in filter) {
    var cnt = 0;

    for(var line in data) {
      if(data[line].indexOf(filter[item]) != -1) {  // pair present in line
        if(cnt == 0)    // first occurence
          ret.push(data[line]);     // add to result

        cnt++;
      }
    }
  }

  return ret;
}
// --------------------------------------------------------------------------------------
// get invalid records and perform filtering
// --------------------------------------------------------------------------------------
router.get('/filtered/:date/', function(req, res, next) {
  var url_base = 'https://etlog.cesnet.cz:8443';
  var url = "/invalid_records/";

  request.get({
    url: url_base + url + req.params.date,
  }, function (error, response, body) {
    if(error || (response.status != undefined && response.status != 200)) {
      if(error) {   // handle error
        console.log(error);
        res.end(error);
      }
      else {    // handle status code
        console.log(JSON.parse(body).error);
        res.end(JSON.parse(body).error);
      }
    }
    else {
      res.json(filter_data(JSON.parse(body)));  // send filtered data
    }
  });

});
// --------------------------------------------------------------------------------------
// send data to user
// --------------------------------------------------------------------------------------
function respond(err, items, res) {
  if(err) {
    console.log(err);
    res.send(err);
    return;
  }

  res.json(items);
}
// --------------------------------------------------------------------------------------
module.exports = router;
<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import warning from 'warning';

import { Map as ImmutableMap } from 'immutable';

import {
    List,
} from '@ichef/gypcrete';

import Option, {
    valueType,
    TYPE_SYMBOL as OPTION_TYPE_SYMBOL,
} from './SelectOption';

import parseSelectOptions from './utils/parseSelectOptions';
import getElementTypeSymbol from './utils/getElementTypeSymbol';

function getInitialCheckedState(selectedValue, multiple = true) {
    const checkedState = new ImmutableMap();

    return checkedState.withMutations((map) => {
        const valueArray = (multiple) ? selectedValue : [selectedValue];
        valueArray.forEach(optionValue => map.set(optionValue, true));
    });
}

/**
 * A List providing multiple options for user to pick from.
 * It can be in either **single** or **multiple** response mode.
 */

class SelectList extends PureComponent {
    static propTypes = {
        multiple: PropTypes.bool,
        showCheckAll: PropTypes.bool,
        minCheck: PropTypes.number,
        checkAllLabel: PropTypes.node,
        value: PropTypes.oneOfType([
            valueType,
            PropTypes.arrayOf(valueType),
        ]),
        defaultValue: PropTypes.oneOfType([
            valueType,
            PropTypes.arrayOf(valueType),
        ]),
        onChange: PropTypes.func,
        title: PropTypes.string,
        desc: PropTypes.node,
    };

    static defaultProps = {
        multiple: false,
        showCheckAll: true,
        minCheck: 0,
        checkAllLabel: 'All',
        value: undefined,
        defaultValue: undefined,
        onChange: () => {},
        title: undefined,
        desc: undefined,
    };

    state = {
        checkedState: getInitialCheckedState(
            this.getInitialValue(),
            this.props.multiple
        ),
    };

    componentWillReceiveProps(nextProps) {
        warning(
            this.getIsControlled(this.props) === this.getIsControlled(nextProps),
            '<SelectList> should not switch from controlled to uncontrolled (or vice versa).'
        );

        if (this.getIsControlled(nextProps)) {
            this.setState({
                checkedState: getInitialCheckedState(nextProps.value, nextProps.multiple),
            });
        } else if (this.props.multiple !== nextProps.multiple) {
            warning(false, '<SelectList>: you should not change `multiple` prop while it is uncontrolled. Its value will be reset now.');
            this.setState({
                checkedState: getInitialCheckedState([])
            });
        }
    }

    getInitialValue() {
        const { value, defaultValue, multiple } = this.props;

        if (value !== undefined) {
            return value;
        }

        if (multiple && defaultValue === undefined) {
            return [];
        }

        return defaultValue;
    }

    getIsControlled(fromProps = this.props) {
        return fromProps.value !== undefined;
    }

    getIsAllChecked() {
        const { checkedState } = this.state;
        const allOptions = this.getOptions();

        return allOptions.every(option => checkedState.get(option.value));
    }

    getOptions(fromProps = this.props) {
        return parseSelectOptions(fromProps.children);
    }

    // eslint-disable-next-line react/destructuring-assignment
    getValues(fromCheckedState = this.state.checkedState) {
        const allOptions = this.getOptions();

        return allOptions
            .filter(option => fromCheckedState.get(option.value))
            .map(option => option.value);
    }

    handleChange(nextCheckedState) {
        const { onChange, multiple } = this.props;
        const nextValues = this.getValues(nextCheckedState);

        if (!this.getIsControlled()) {
            this.setState({ checkedState: nextCheckedState });
        }
        onChange(multiple ? nextValues : nextValues[0]);
    }

    handleOptionChange = (optionValue, isChecked) => {
        const { multiple, minCheck } = this.props;
        const { checkedState } = this.state;

        let nextState = checkedState;

        if (multiple) {
            nextState = nextState.set(optionValue, isChecked);

            const nextCheckedSize = nextState.filter(value => value).size;

            if (nextCheckedSize < minCheck) {
                // Cancel this operation
                return;
            }
        } else {
            const currentCheckedOptionValue = checkedState.findKey(value => value);

            if (optionValue === currentCheckedOptionValue) {
                // Does not consider a change
                return;
            }
            nextState = nextState
                .set(currentCheckedOptionValue, false)
                .set(optionValue, isChecked);
        }

        this.handleChange(nextState);
    }

    handleCheckAllOptionChange = (ignoreThis, isChecked) => {
        const { minCheck } = this.props;
        const { checkedState } = this.state;

        const variableOptions = this.getOptions()
            .filter(option => !option.readOnly);

        const nextState = checkedState.withMutations((map) => {
            variableOptions.forEach(option => map.set(option.value, isChecked));

            const nextCheckedSize = map.filter(value => value).size;
            const checksNeeded = minCheck - nextCheckedSize;

            // Check options until matching minCheck
            if (checksNeeded > 0) {
                variableOptions.slice(0, checksNeeded)
                    .forEach(option => map.set(option.value, true));
            }
        });

        this.handleChange(nextState);
    }

    renderOptions(children = this.props.children) {
        const { checkedState } = this.state;

        return React.Children.map(children, (child) => {
            if (getElementTypeSymbol(child) === OPTION_TYPE_SYMBOL) {
                return React.cloneElement(child, {
                    checked: checkedState.get(child.props.value),
                    onChange: this.handleOptionChange,
                });
            }

            if (child && child.type === React.Fragment) {
                return this.renderOptions(child.props.children);
            }

            return child;
        });
    }

    renderCheckAllOption() {
        const { checkAllLabel } = this.props;
        const isAllChecked = this.getIsAllChecked();

        return (
            <Option
                label={checkAllLabel}
                value={null}
                checked={isAllChecked}
                onChange={this.handleCheckAllOptionChange} />
        );
    }

    render() {
        const {
            multiple,
            showCheckAll,
            title,
            desc,
            ...wrapperProps
        } = this.props;

        return (
            <List title={title} desc={desc} {...wrapperProps}>
                {multiple && showCheckAll && this.renderCheckAllOption()}
                {this.renderOptions()}
            </List>
        );
    }
}

export default SelectList;
<commit_msg>Fix SelectList wrapperProps<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import warning from 'warning';

import { Map as ImmutableMap } from 'immutable';

import {
    List,
} from '@ichef/gypcrete';
import getRemainingProps from '@ichef/gypcrete/lib/utils/getRemainingProps';

import Option, {
    valueType,
    TYPE_SYMBOL as OPTION_TYPE_SYMBOL,
} from './SelectOption';

import parseSelectOptions from './utils/parseSelectOptions';
import getElementTypeSymbol from './utils/getElementTypeSymbol';

function getInitialCheckedState(selectedValue, multiple = true) {
    const checkedState = new ImmutableMap();

    return checkedState.withMutations((map) => {
        const valueArray = (multiple) ? selectedValue : [selectedValue];
        valueArray.forEach(optionValue => map.set(optionValue, true));
    });
}

/**
 * A List providing multiple options for user to pick from.
 * It can be in either **single** or **multiple** response mode.
 */

class SelectList extends PureComponent {
    static propTypes = {
        multiple: PropTypes.bool,
        showCheckAll: PropTypes.bool,
        minCheck: PropTypes.number,
        checkAllLabel: PropTypes.node,
        value: PropTypes.oneOfType([
            valueType,
            PropTypes.arrayOf(valueType),
        ]),
        defaultValue: PropTypes.oneOfType([
            valueType,
            PropTypes.arrayOf(valueType),
        ]),
        onChange: PropTypes.func,
        title: PropTypes.string,
        desc: PropTypes.node,
    };

    static defaultProps = {
        multiple: false,
        showCheckAll: true,
        minCheck: 0,
        checkAllLabel: 'All',
        value: undefined,
        defaultValue: undefined,
        onChange: () => {},
        title: undefined,
        desc: undefined,
    };

    state = {
        checkedState: getInitialCheckedState(
            this.getInitialValue(),
            this.props.multiple
        ),
    };

    componentWillReceiveProps(nextProps) {
        warning(
            this.getIsControlled(this.props) === this.getIsControlled(nextProps),
            '<SelectList> should not switch from controlled to uncontrolled (or vice versa).'
        );

        if (this.getIsControlled(nextProps)) {
            this.setState({
                checkedState: getInitialCheckedState(nextProps.value, nextProps.multiple),
            });
        } else if (this.props.multiple !== nextProps.multiple) {
            warning(false, '<SelectList>: you should not change `multiple` prop while it is uncontrolled. Its value will be reset now.');
            this.setState({
                checkedState: getInitialCheckedState([])
            });
        }
    }

    getInitialValue() {
        const { value, defaultValue, multiple } = this.props;

        if (value !== undefined) {
            return value;
        }

        if (multiple && defaultValue === undefined) {
            return [];
        }

        return defaultValue;
    }

    getIsControlled(fromProps = this.props) {
        return fromProps.value !== undefined;
    }

    getIsAllChecked() {
        const { checkedState } = this.state;
        const allOptions = this.getOptions();

        return allOptions.every(option => checkedState.get(option.value));
    }

    getOptions(fromProps = this.props) {
        return parseSelectOptions(fromProps.children);
    }

    // eslint-disable-next-line react/destructuring-assignment
    getValues(fromCheckedState = this.state.checkedState) {
        const allOptions = this.getOptions();

        return allOptions
            .filter(option => fromCheckedState.get(option.value))
            .map(option => option.value);
    }

    handleChange(nextCheckedState) {
        const { onChange, multiple } = this.props;
        const nextValues = this.getValues(nextCheckedState);

        if (!this.getIsControlled()) {
            this.setState({ checkedState: nextCheckedState });
        }
        onChange(multiple ? nextValues : nextValues[0]);
    }

    handleOptionChange = (optionValue, isChecked) => {
        const { multiple, minCheck } = this.props;
        const { checkedState } = this.state;

        let nextState = checkedState;

        if (multiple) {
            nextState = nextState.set(optionValue, isChecked);

            const nextCheckedSize = nextState.filter(value => value).size;

            if (nextCheckedSize < minCheck) {
                // Cancel this operation
                return;
            }
        } else {
            const currentCheckedOptionValue = checkedState.findKey(value => value);

            if (optionValue === currentCheckedOptionValue) {
                // Does not consider a change
                return;
            }
            nextState = nextState
                .set(currentCheckedOptionValue, false)
                .set(optionValue, isChecked);
        }

        this.handleChange(nextState);
    }

    handleCheckAllOptionChange = (ignoreThis, isChecked) => {
        const { minCheck } = this.props;
        const { checkedState } = this.state;

        const variableOptions = this.getOptions()
            .filter(option => !option.readOnly);

        const nextState = checkedState.withMutations((map) => {
            variableOptions.forEach(option => map.set(option.value, isChecked));

            const nextCheckedSize = map.filter(value => value).size;
            const checksNeeded = minCheck - nextCheckedSize;

            // Check options until matching minCheck
            if (checksNeeded > 0) {
                variableOptions.slice(0, checksNeeded)
                    .forEach(option => map.set(option.value, true));
            }
        });

        this.handleChange(nextState);
    }

    renderOptions(children = this.props.children) {
        const { checkedState } = this.state;

        return React.Children.map(children, (child) => {
            if (getElementTypeSymbol(child) === OPTION_TYPE_SYMBOL) {
                return React.cloneElement(child, {
                    checked: checkedState.get(child.props.value),
                    onChange: this.handleOptionChange,
                });
            }

            if (child && child.type === React.Fragment) {
                return this.renderOptions(child.props.children);
            }

            return child;
        });
    }

    renderCheckAllOption() {
        const { checkAllLabel } = this.props;
        const isAllChecked = this.getIsAllChecked();

        return (
            <Option
                label={checkAllLabel}
                value={null}
                checked={isAllChecked}
                onChange={this.handleCheckAllOptionChange} />
        );
    }

    render() {
        const {
            multiple,
            showCheckAll,
            title,
            desc,
        } = this.props;
        const wrapperProps = getRemainingProps(this.props, SelectList.propTypes);

        return (
            <List title={title} desc={desc} {...wrapperProps}>
                {multiple && showCheckAll && this.renderCheckAllOption()}
                {this.renderOptions()}
            </List>
        );
    }
}

export default SelectList;
<|endoftext|>


<commit_before>define('mas-editor', [
  'editor',
  'editor-plugin-sticky-toolbar'
], function (
  Editor,
  editorPluginStickyToolbar
) {
  'use strict';
  return {
    /**
     * Setups options and instantiates Editor instance
     * @param  {Object} options Options
     * @return {Object} this
     */
    init: function(options) {
      if(typeof options !== 'object') return;

      this.options = options;
      this.cmsFormNode = this.options.cmsFormNode;
      this.toolbarNode = this.options.toolbarNode;
      this.editorContainer = this.options.editorContainer;
      this.htmlEditorNode = this.options.htmlEditorNode;
      this.htmlEditorContentNode = this.options.htmlEditorContentNode;
      this.markdownEditorNode = this.options.markdownEditorNode;
      this.markdownEditorContentNode = this.options.markdownEditorContentNode;
      this.switchModeButtonNodes = this.options.switchModeButtonNodes;
      this.editor = new Editor(
        this.htmlEditorContentNode,
        this.markdownEditorContentNode,
        this.toolbarNode
      );
      this.editor.use(editorPluginStickyToolbar(this.toolbarNode));
      this.classActive = this.options.classActive || this.editor.constants.CLASSES.ACTIVE;
      this.mode = this.options.mode || this.editor.config.defaultEditingMode;

      this.addUIEvents();
      this.setupAppEvents();
    },

    /**
     * Bind DOM events
     * @return {Object} this
     */
    addUIEvents: function() {
      var _this = this;

      // Catches form submit and delegates to a handler function
      this.cmsFormNode.addEventListener('submit', function(event) {
        event.preventDefault();
        _this.handleSubmit();
      });

      this.setupModeButton();

      return this;
    },

    /**
     * [setupAppEvents description]
     * @return {[type]} [description]
     */
    setupAppEvents: function() {
      this.editor.events.subscribe('mode:changed', this.handleChangeModeEvent, this);
      return this;
    },

    /**
     * Setups mode switching buttons
     * @return {Object} this
     */
    setupModeButton: function() {
      var i = this.switchModeButtonNodes.length,
          _this = this;

      while(i--) {
        (function(button) {
          button.addEventListener('click', function() {
            _this.changeMode(button.dataset.mode);
          });
        })(this.switchModeButtonNodes[i]);
      }
      return this;
    },

    /**
     * Handles Mode button click
     * @param  {Object} button Button DOM node
     * @return {[type]}        [description]
     */
    handleChangeModeEvent: function(mode) {
      Array.prototype.map.call(this.switchModeButtonNodes, function(buttonNode) {
        buttonNode.classList.remove(this.classActive);
        if(buttonNode.dataset.mode === mode) {
          buttonNode.classList.add(this.classActive);
        }
      }.bind(this));
    },

    /**
     * Converts HTML input into Markdown then submits form
     * @return {Object} this
     */
    handleSubmit: function() {
      if(this.mode === this.editor.constants.MODES.HTML) {
        this.editor.changeEditingMode(this.editor.constants.MODES.MARKDOWN);
      }
      this.cmsFormNode.submit();
      return this;
    },

    /**
     * Handles switch between HTML and Markdown mode
     * @param  {String} mode html|markdown
     * @return {Object} this
     */
    changeMode: function(mode) {
      if(mode === this.mode) return;

      this.mode = mode;

      switch(mode) {
        case this.editor.constants.MODES.HTML:
          this.show(this.htmlEditorNode).hide(this.markdownEditorNode);
          break;
        case this.editor.constants.MODES.MARKDOWN:
          this.show(this.markdownEditorNode).hide(this.htmlEditorNode);
          break;
        default:
          this.show(this.htmlEditorNode).hide(this.markdownEditorNode);
          break;
      }

      this.editor.changeEditingMode(this.mode);

      return this;
    },

    /**
     * Adds an active class to a DOM node
     * @param  {Object} node Target DOM node
     * @return {Object} this
     */
    show: function(node) {
      node.classList.add(this.classActive);
      return this;
    },

    /**
     * Removes active class from a DOM node
     * @param  {Object} node Target DOM node
     * @return {Object} this
     */
    hide: function(node) {
      node.classList.remove(this.classActive);
      return this;
    }
  };
});
<commit_msg>Move sticky toolbar plugin out of mas-editor class and into instantiation code<commit_after>define('mas-editor', [
  'editor'
], function (
  Editor
) {
  'use strict';
  return {
    /**
     * Setups options and instantiates Editor instance
     * @param  {Object} options Options
     * @return {Object} this
     */
    init: function(options) {
      if(typeof options !== 'object') return;

      this.options = options;
      this.cmsFormNode = this.options.cmsFormNode;
      this.toolbarNode = this.options.toolbarNode;
      this.editorContainer = this.options.editorContainer;
      this.htmlEditorNode = this.options.htmlEditorNode;
      this.htmlEditorContentNode = this.options.htmlEditorContentNode;
      this.markdownEditorNode = this.options.markdownEditorNode;
      this.markdownEditorContentNode = this.options.markdownEditorContentNode;
      this.switchModeButtonNodes = this.options.switchModeButtonNodes;
      this.editor = new Editor(
        this.htmlEditorContentNode,
        this.markdownEditorContentNode,
        this.toolbarNode
      );
      this.classActive = this.options.classActive || this.editor.constants.CLASSES.ACTIVE;
      this.mode = this.options.mode || this.editor.config.defaultEditingMode;

      this.addUIEvents();
      this.setupAppEvents();
    },

    /**
     * Bind DOM events
     * @return {Object} this
     */
    addUIEvents: function() {
      var _this = this;

      // Catches form submit and delegates to a handler function
      this.cmsFormNode.addEventListener('submit', function(event) {
        event.preventDefault();
        _this.handleSubmit();
      });

      this.setupModeButton();

      return this;
    },

    /**
     * [setupAppEvents description]
     * @return {[type]} [description]
     */
    setupAppEvents: function() {
      this.editor.events.subscribe('mode:changed', this.handleChangeModeEvent, this);
      return this;
    },

    /**
     * Setups mode switching buttons
     * @return {Object} this
     */
    setupModeButton: function() {
      var i = this.switchModeButtonNodes.length,
          _this = this;

      while(i--) {
        (function(button) {
          button.addEventListener('click', function() {
            _this.changeMode(button.dataset.mode);
          });
        })(this.switchModeButtonNodes[i]);
      }
      return this;
    },

    /**
     * Handles Mode button click
     * @param  {Object} button Button DOM node
     * @return {[type]}        [description]
     */
    handleChangeModeEvent: function(mode) {
      Array.prototype.map.call(this.switchModeButtonNodes, function(buttonNode) {
        buttonNode.classList.remove(this.classActive);
        if(buttonNode.dataset.mode === mode) {
          buttonNode.classList.add(this.classActive);
        }
      }.bind(this));
    },

    /**
     * Converts HTML input into Markdown then submits form
     * @return {Object} this
     */
    handleSubmit: function() {
      if(this.mode === this.editor.constants.MODES.HTML) {
        this.editor.changeEditingMode(this.editor.constants.MODES.MARKDOWN);
      }
      this.cmsFormNode.submit();
      return this;
    },

    /**
     * Handles switch between HTML and Markdown mode
     * @param  {String} mode html|markdown
     * @return {Object} this
     */
    changeMode: function(mode) {
      if(mode === this.mode) return;

      this.mode = mode;

      switch(mode) {
        case this.editor.constants.MODES.HTML:
          this.show(this.htmlEditorNode).hide(this.markdownEditorNode);
          break;
        case this.editor.constants.MODES.MARKDOWN:
          this.show(this.markdownEditorNode).hide(this.htmlEditorNode);
          break;
        default:
          this.show(this.htmlEditorNode).hide(this.markdownEditorNode);
          break;
      }

      this.editor.changeEditingMode(this.mode);

      return this;
    },

    /**
     * Adds an active class to a DOM node
     * @param  {Object} node Target DOM node
     * @return {Object} this
     */
    show: function(node) {
      node.classList.add(this.classActive);
      return this;
    },

    /**
     * Removes active class from a DOM node
     * @param  {Object} node Target DOM node
     * @return {Object} this
     */
    hide: function(node) {
      node.classList.remove(this.classActive);
      return this;
    }
  };
});
<|endoftext|>