<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Button, Icon, Modal, Input, message, Menu, Row, Col } from 'antd'
import { autobind } from 'core-decorators';
import axios from 'axios';
import { withRouter } from 'react-router';
const { TextArea } = Input;
const Search = Input.Search;
const confirm = Modal.confirm;
const TYPE_EDIT = 'edit';

import {
  fetchGroupList,
  setCurrGroup,
  setGroupList
} from '../../../actions/group.js'

import './GroupList.scss'

@connect(
  state => ({
    groupList: state.group.groupList,
    currGroup: state.group.currGroup
  }),
  {
    fetchGroupList,
    setCurrGroup,
    setGroupList
  }
)
@withRouter
export default class GroupList extends Component {

  static propTypes = {
    groupList: PropTypes.array,
    currGroup: PropTypes.object,
    fetchGroupList: PropTypes.func,
    setCurrGroup: PropTypes.func,
    setGroupList: PropTypes.func,
    match: PropTypes.object,
    history: PropTypes.object
  }

  state = {
    addGroupModalVisible: false,
    editGroupModalVisible: false,
    newGroupName: '',
    newGroupDesc: '',
    currGroupName: '',
    currGroupDesc: '',
    groupList: []
  }

  constructor(props) {
    super(props)
  }

  componentWillMount() {
    const groupName = this.props.match.params.groupName;
    this.props.fetchGroupList().then(() => {
      let currGroup = this.props.groupList[0] || { group_name: '', group_desc: '' };
      if(this.props.groupList.length && groupName){
        for(let i = 0;i<this.props.groupList.length;i++){
          if(this.props.groupList[i].group_name === groupName){
            currGroup = this.props.groupList[i];
          }else{
            this.props.history.push(`${currGroup.group_name}`);
          }
        }
      }
      console.log(currGroup);
      this.setState({groupList: this.props.groupList});
      this.props.setCurrGroup(currGroup)
    });
  }

  @autobind
  showModal(type) {
    if (type === 'edit') {
      const { currGroup } = this.props;
      this.setState({
        currGroupName: currGroup.group_name,
        currGroupDesc: currGroup.group_desc,
        editGroupModalVisible: true
      });
    } else {
      this.setState({
        addGroupModalVisible: true
      });
    }
  }
  @autobind
  hideModal(type) {
    if (type === TYPE_EDIT) {
      this.setState({
        editGroupModalVisible: false
      });
    } else {
      this.setState({
        addGroupModalVisible: false
      });
    }
  }
  @autobind
  addGroup() {
    const { newGroupName: group_name, newGroupDesc: group_desc } = this.state;
    axios.post('/group/add', { group_name, group_desc }).then(res => {
      if (res.data.errcode) {
        message.error(res.data.errmsg);
      } else {
        this.setState({
          addGroupModalVisible: false
        });
        this.props.fetchGroupList().then(() => {
          this.setState({groupList: this.props.groupList});
        })
        this.props.setCurrGroup(res.data.data)
      }
    });
  }
  @autobind
  editGroup() {
    const { currGroupName: group_name, currGroupDesc: group_desc } = this.state;
    const id = this.props.currGroup._id;
    axios.post('/group/up', { group_name, group_desc, id }).then(res => {
      if (res.data.errcode) {
        message.error(res.data.errmsg);
      } else {
        this.setState({
          editGroupModalVisible: false
        });
        this.props.setCurrGroup({ group_name, group_desc, _id: id });
      }
    });
  }
  @autobind
  inputNewGroupName(e, type) {
    if (type === TYPE_EDIT) {
      this.setState({ currGroupName: e.target.value})
    } else {
      this.setState({newGroupName: e.target.value});
    }
  }
  @autobind
  inputNewGroupDesc(e, type) {
    if (type === TYPE_EDIT) {
      this.setState({ currGroupDesc: e.target.value})
    } else {
      this.setState({newGroupDesc: e.target.value});
    }
  }

  @autobind
  selectGroup(e) {
    const groupId = e.key;
    const currGroup = this.props.groupList.find((group) => { return +group._id === +groupId });
    this.props.setCurrGroup(currGroup);
    this.props.history.push(`${currGroup.group_name}`);
  }

  @autobind
  deleteGroup() {
    const self = this;
    const { currGroup } = self.props;
    confirm({
      title: `你确定要删除分组 ${currGroup.group_name}？`,
      content: `分组简介：${currGroup.group_desc}`,
      onOk() {
        axios.post('/group/del', {id: currGroup._id}).then(res => {
          if (res.data.errcode) {
            message.error(res.data.errmsg);
          } else {
            message.success('删除成功');
            self.props.fetchGroupList().then(() => {
              const currGroup = self.props.groupList[0] || { group_name: '', group_desc: '' };
              self.setState({groupList: self.props.groupList});
              self.props.setCurrGroup(currGroup)
            });
          }
        });
      }
    });
  }

  @autobind
  searchGroup(e, value) {
    const v = value || e.target.value;
    const { groupList } = this.props;
    if (v === '') {
      this.setState({groupList})
    } else {
      this.setState({groupList: groupList.filter(group => new RegExp(v, 'i').test(group.group_name))})
    }
  }

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

    return (

              {currGroup.group_name}
              <Icon className="edit-group" type="edit" title="编辑分组" onClick={() => this.showModal(TYPE_EDIT)}/>
              <Icon className="delete-group" type="delete" title="删除分组" onClick={this.deleteGroup}/>

简介：{currGroup.group_desc}

              <Search onChange={this.searchGroup} onSearch={(v) => this.searchGroup(null, v)}/>

            <Button type="primary" onClick={this.showModal}>添加分组</Button>

          <Menu
            className="group-list"
            mode="inline"
            onClick={this.selectGroup}
            selectedKeys={[`${currGroup._id}`]}
          >
            {
              this.state.groupList.map((group) => (
                <Menu.Item key={`${group._id}`} className="group-item">
                  <Icon type="folder-open" />{group.group_name}
                </Menu.Item>
              ))
            }
          </Menu>

        <Modal
          title="添加分组"
          visible={this.state.addGroupModalVisible}
          onOk={this.addGroup}
          onCancel={this.hideModal}
          className="add-group-modal"
        >
          <Row gutter={6} className="modal-input">
            <Col span="5">
分组名：
</Col>
            <Col span="15">
              <Input placeholder="请输入分组名称" onChange={this.inputNewGroupName}></Input>
            </Col>
          </Row>
          <Row gutter={6} className="modal-input">
            <Col span="5">
简介：
</Col>
            <Col span="15">
              <TextArea rows = {3} placeholder="请输入分组描述" onChange={this.inputNewGroupDesc}></TextArea>
            </Col>
          </Row>
        </Modal>
        <Modal
          title="编辑分组"
          visible={this.state.editGroupModalVisible}
          onOk={this.editGroup}
          onCancel={() => this.hideModal(TYPE_EDIT)}
          className="add-group-modal"
        >
          <Row gutter={6} className="modal-input">
            <Col span="5">
分组名：
</Col>
            <Col span="15">
              <Input placeholder="请输入分组名称" value={this.state.currGroupName} onChange={(e) => this.inputNewGroupName(e, TYPE_EDIT)}></Input>
            </Col>
          </Row>
          <Row gutter={6} className="modal-input">
            <Col span="5">
简介：
</Col>
            <Col span="15">
              <Input placeholder="请输入分组描述" value={this.state.currGroupDesc} onChange={(e) => this.inputNewGroupDesc(e, TYPE_EDIT)}></Input>
            </Col>
          </Row>
        </Modal>

    )
  }
}
<commit_msg>fix: 修改history导致的warm<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Button, Icon, Modal, Input, message, Menu, Row, Col } from 'antd'
import { autobind } from 'core-decorators';
import axios from 'axios';
import { withRouter } from 'react-router';
const { TextArea } = Input;
const Search = Input.Search;
const confirm = Modal.confirm;
const TYPE_EDIT = 'edit';

import {
  fetchGroupList,
  setCurrGroup,
  setGroupList
} from '../../../actions/group.js'

import './GroupList.scss'

@connect(
  state => ({
    groupList: state.group.groupList,
    currGroup: state.group.currGroup
  }),
  {
    fetchGroupList,
    setCurrGroup,
    setGroupList
  }
)
@withRouter
export default class GroupList extends Component {

  static propTypes = {
    groupList: PropTypes.array,
    currGroup: PropTypes.object,
    fetchGroupList: PropTypes.func,
    setCurrGroup: PropTypes.func,
    setGroupList: PropTypes.func,
    match: PropTypes.object,
    history: PropTypes.object
  }

  state = {
    addGroupModalVisible: false,
    editGroupModalVisible: false,
    newGroupName: '',
    newGroupDesc: '',
    currGroupName: '',
    currGroupDesc: '',
    groupList: []
  }

  constructor(props) {
    super(props)
  }

  componentWillMount() {
    const groupName = this.props.match.params.groupName;
    this.props.fetchGroupList().then(() => {
      let currGroup = this.props.groupList[0] || { group_name: '', group_desc: '' };
      if(this.props.groupList.length && groupName){
        for(let i = 0;i<this.props.groupList.length;i++){
          if(this.props.groupList[i].group_name === groupName){
            currGroup = this.props.groupList[i];
          }else{
            this.props.history.replace(`${currGroup.group_name}`);
          }
        }
      }
      console.log(currGroup);
      this.setState({groupList: this.props.groupList});
      this.props.setCurrGroup(currGroup)
    });
  }

  @autobind
  showModal(type) {
    if (type === 'edit') {
      const { currGroup } = this.props;
      this.setState({
        currGroupName: currGroup.group_name,
        currGroupDesc: currGroup.group_desc,
        editGroupModalVisible: true
      });
    } else {
      this.setState({
        addGroupModalVisible: true
      });
    }
  }
  @autobind
  hideModal(type) {
    if (type === TYPE_EDIT) {
      this.setState({
        editGroupModalVisible: false
      });
    } else {
      this.setState({
        addGroupModalVisible: false
      });
    }
  }
  @autobind
  addGroup() {
    const { newGroupName: group_name, newGroupDesc: group_desc } = this.state;
    axios.post('/group/add', { group_name, group_desc }).then(res => {
      if (res.data.errcode) {
        message.error(res.data.errmsg);
      } else {
        this.setState({
          addGroupModalVisible: false
        });
        this.props.fetchGroupList().then(() => {
          this.setState({groupList: this.props.groupList});
        })
        this.props.setCurrGroup(res.data.data)
      }
    });
  }
  @autobind
  editGroup() {
    const { currGroupName: group_name, currGroupDesc: group_desc } = this.state;
    const id = this.props.currGroup._id;
    axios.post('/group/up', { group_name, group_desc, id }).then(res => {
      if (res.data.errcode) {
        message.error(res.data.errmsg);
      } else {
        this.setState({
          editGroupModalVisible: false
        });
        this.props.setCurrGroup({ group_name, group_desc, _id: id });
      }
    });
  }
  @autobind
  inputNewGroupName(e, type) {
    if (type === TYPE_EDIT) {
      this.setState({ currGroupName: e.target.value})
    } else {
      this.setState({newGroupName: e.target.value});
    }
  }
  @autobind
  inputNewGroupDesc(e, type) {
    if (type === TYPE_EDIT) {
      this.setState({ currGroupDesc: e.target.value})
    } else {
      this.setState({newGroupDesc: e.target.value});
    }
  }

  @autobind
  selectGroup(e) {
    const groupId = e.key;
    const currGroup = this.props.groupList.find((group) => { return +group._id === +groupId });
    this.props.setCurrGroup(currGroup);
    this.props.history.replace(`${currGroup.group_name}`);
  }

  @autobind
  deleteGroup() {
    const self = this;
    const { currGroup } = self.props;
    confirm({
      title: `你确定要删除分组 ${currGroup.group_name}？`,
      content: `分组简介：${currGroup.group_desc}`,
      onOk() {
        axios.post('/group/del', {id: currGroup._id}).then(res => {
          if (res.data.errcode) {
            message.error(res.data.errmsg);
          } else {
            message.success('删除成功');
            self.props.fetchGroupList().then(() => {
              const currGroup = self.props.groupList[0] || { group_name: '', group_desc: '' };
              self.setState({groupList: self.props.groupList});
              self.props.setCurrGroup(currGroup)
            });
          }
        });
      }
    });
  }

  @autobind
  searchGroup(e, value) {
    const v = value || e.target.value;
    const { groupList } = this.props;
    if (v === '') {
      this.setState({groupList})
    } else {
      this.setState({groupList: groupList.filter(group => new RegExp(v, 'i').test(group.group_name))})
    }
  }

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

    return (

              {currGroup.group_name}
              <Icon className="edit-group" type="edit" title="编辑分组" onClick={() => this.showModal(TYPE_EDIT)}/>
              <Icon className="delete-group" type="delete" title="删除分组" onClick={this.deleteGroup}/>

简介：{currGroup.group_desc}

              <Search onChange={this.searchGroup} onSearch={(v) => this.searchGroup(null, v)}/>

            <Button type="primary" onClick={this.showModal}>添加分组</Button>

          <Menu
            className="group-list"
            mode="inline"
            onClick={this.selectGroup}
            selectedKeys={[`${currGroup._id}`]}
          >
            {
              this.state.groupList.map((group) => (
                <Menu.Item key={`${group._id}`} className="group-item">
                  <Icon type="folder-open" />{group.group_name}
                </Menu.Item>
              ))
            }
          </Menu>

        <Modal
          title="添加分组"
          visible={this.state.addGroupModalVisible}
          onOk={this.addGroup}
          onCancel={this.hideModal}
          className="add-group-modal"
        >
          <Row gutter={6} className="modal-input">
            <Col span="5">
分组名：
</Col>
            <Col span="15">
              <Input placeholder="请输入分组名称" onChange={this.inputNewGroupName}></Input>
            </Col>
          </Row>
          <Row gutter={6} className="modal-input">
            <Col span="5">
简介：
</Col>
            <Col span="15">
              <TextArea rows = {3} placeholder="请输入分组描述" onChange={this.inputNewGroupDesc}></TextArea>
            </Col>
          </Row>
        </Modal>
        <Modal
          title="编辑分组"
          visible={this.state.editGroupModalVisible}
          onOk={this.editGroup}
          onCancel={() => this.hideModal(TYPE_EDIT)}
          className="add-group-modal"
        >
          <Row gutter={6} className="modal-input">
            <Col span="5">
分组名：
</Col>
            <Col span="15">
              <Input placeholder="请输入分组名称" value={this.state.currGroupName} onChange={(e) => this.inputNewGroupName(e, TYPE_EDIT)}></Input>
            </Col>
          </Row>
          <Row gutter={6} className="modal-input">
            <Col span="5">
简介：
</Col>
            <Col span="15">
              <Input placeholder="请输入分组描述" value={this.state.currGroupDesc} onChange={(e) => this.inputNewGroupDesc(e, TYPE_EDIT)}></Input>
            </Col>
          </Row>
        </Modal>

    )
  }
}
<|endoftext|>


<commit_before>(function(EventEmitter, window) {

    var MAX = 32768;
    var FIRST = [0];
    var LAST = [MAX];

    /**
     * @param @optional {Object} atoms
     */
    function Logoot(atoms) {
        EventEmitter.apply(this);

        // this.atoms contains the contents that compose your linear data. this.atoms
        // maps an atom id to an atom. An atom can be any data type.
        this.atoms = atoms || {};

        // this.ids is an ordered index of atom ids. If has pseudo-atoms FIRST and
        // LAST to denote the begining and ending boundaries of our linear data.
        var ids = getIdentifiers(this.atoms);
        ids.unshift(FIRST);
        ids.push(LAST);

        this.ids = ids;

        // this.clock is a counter incremented on each insert operation
        this.clock = 0;
    }

    Logoot.prototype = Object.create(EventEmitter.prototype);

    /**
     * Inserts an `atom` with a given `id`, with the insertion being annotated as
     * being authored by `agent`
     *
     * @param {[Number, String]} id
     * @param {Object} atom
     * @param {String} agent
     * @param @optional {Number} insertAfter
     */
    Logoot.prototype.ins = function(id, atom, agent, insertAfter) {
        var ids = this.ids;
        if (typeof  insertAfter == "undefined") {
            insertAfter = indexOfGreatestLessThan(ids, id, compare);
        }
        ids.splice(insertAfter + 1, 0, id);
        this.atoms[hashId(id)] = atom;
        if (arguments.length < 4) {
            this.emit("ins", insertAfter, atom);
        }
        return ["ins", id, atom, agent];
    };

    /**
     * Deletes an atom with the given `id`, with the deletion being annotated as
     * being authored by `agent`. For efficiency's sake, we can optionally pass in
     * `indexOfId`, if it was calculated before the invocation, which allows us to
     * avoid looking up the location of id in our index, this.ids.
     *
     * @param {[Number, String]} id is the id of the atom we want to delete
     * @param {String} agent is the author of the deletion
     * @param @optional {Number} indexOfId is the index of id in this.ids
     */
    Logoot.prototype.del = function(id, agent, indexOfId) {
        var ids = this.ids;
        indexOfId || (indexOfId = indexOf(ids, id, compare));
        ids.splice(indexOfId, 1);
        delete this.atoms[hashId(id)];
        if (arguments.length < 3) {
            this.emit("del", indexOfId - 1);
        }
        return ["del", id, agent];
    };

    /**
     * Applies an operation that is typically received from another author.
     *
     * @param {Array} op can be either ['ins', id, line, agent] or ['del', id, agent]
     */
    Logoot.prototype.applyOp = function (op) {
        var args = op.slice(1);
        if (typeof args[0] == "string") {
            args[0] = deserializeId(args[0]);
        }
        this[op[0]].apply(this, args);
    };

    /**
     * Reduces over the sequence of atoms encapsulated by this Logoot. This is
     * handy when we want to convert our Logoot into a data structure more suitable
     * for our app (e.g., to String of character atoms or to an Array of atoms).
     *
     * @param {Function} fn(reducedValue, atom)
     * @param {init} is the initial value of reducedValue for fn
     * @return {Object} the reduced value
     */
    Logoot.prototype.reduce = function(fn, init) {
        var atoms = this.atoms;
        return this.ids.reduce(function(val, id) {
            if (id === FIRST || id === LAST) return val;
            return fn(val, atoms[hashId(id)]);
        }, init);
    };

    /**
     * Generates an id that is between from and to.
     *
     * @param {Array} from is an atom id currently stored in this.atoms
     * @param {Array} to is an atom id currently stored in this.atoms
     * @param {String} agent is the id of the author creating the id
     * @return {Array} id between from and to
     */
    Logoot.prototype.genId = function(from, to, agent, depth) {
        depth = depth || 1;

        var min = from[0] || 0;
        var max = to[0] || MAX * depth - 1;

        if (min < MAX * depth - 1) {
            return [randomIntBtwn(min, max), agent, this.clock++];
        }

        return [from[0] || 0, from[1] || agent, from[2] || 0]
            .concat(this.genId(from.slice(3), to.slice(3), agent, depth + 1));
    };

    /**
     * Generates a random integer between x and y, non-inclusive.
     *
     * @param {Number} x
     * @param {Number} y
     * @return {Number} random number
     */
    function randomIntBtwn(x, y) {
        return x + Math.ceil(Math.random() * (y - x));
    }

    /**
     * Returns the index of `x` inside of the array `xs`. Returns -1 if not found.
     *
     * @param {Array} xs is an array of objects
     * @param {Object} x is the member of xs that we are trying to locate
     * @param {Function} compare(a, b) returns -1 if a < b; 1 if a > b; 0 if a == b
     * @return {Number} index of x in xs. -1 if x is not in xs.
     */
    function indexOf(xs, x, compare) {
        for (var i = 0, l = xs.length; i < l; i++) {
            if (compare(xs[i], x) === 0) return i;
        }
        return -1;
    }

    /**
     * Returns the index of the largest element in `xs`, that is less than `x`,
     * according to a comparison function `compare`. Whether of not `x` is in `xs`,
     * the result will be the same, regardless.
     *
     * @param {Array} xs is an array of objects
     * @param {Object} x is the object to which members of `xs` are being compared
     * @param {Function} compare(a, b) returns -1 if a < b; 1 if a > b; 0 if a == b
     * @return {Number}
     */
    function indexOfGreatestLessThan(xs, x, compare) {
        var comp, i, l;
        for (i = 0, l = xs.length; i < l; i++) {
            comp = compare(xs[i], x);
            if (comp !== -1) {
                return i - 1;
            }
        }
        return i;
    }

    /**
     * Compares 2 ids of the form [Number, String]. `a` is less than `b`
     * if a[0] < b[0]. If a[0] == b[0], then `a` is less than `b` iff a[1] < b[1].
     * Returns -1 if a < b; returns 1 if a > b; returns 0 if a == b.
     *
     * @param {Array} a
     * @param {Array} b
     * @return {Number} -1 if a < b; 1 if a > b; 0 if a == b
     */
    function compare(a, b) {
        var depth = Math.max(a.length, b.length);
        var ai, bi;

        for (var i = 0; i < depth; i ++) {
            ai = a[i];
            bi = b[i];

            if (typeof ai == "undefined") return -1;
            if (typeof bi == "undefined") return 1;
            if (ai < bi) return -1;
            if (ai > bi) return 1;
        }

        return 0;
    }

    /**
     * Hashes `id`. Useful for hashing atom ids to store in a hash map.
     *
     * @param {Array} id
     * @return {String}
     */
    function hashId(id) {
        return id.join(".");
    }

    /**
     * Returns a list of composite identifiers from atoms.
     *
     * @param {Object} atoms
     * @return {Array}
     */
    function getIdentifiers(atoms) {
        return Object.keys(atoms).map(function(key) {
            return deserializeId(key);
        }).sort(compare);
    }

    /**
    * Returns a deserialized id value.
    *
    * @param {String} id - identifier.
    * @return {Array}
    */
    function deserializeId(id) {
        return id.split(".").map(function (val) {
            return parseInt(val);
        });
    }

    /**
     * Exports logoot.
     */
    window["Logoot"] = Logoot;

})(EventEmitter, window);<commit_msg>Logoot: added depth determination on the load<commit_after>(function(EventEmitter, window) {

    var MAX = 32;
    var FIRST = [0];
    var LAST = [undefined];

    // maximum depth of ids
    var maxDepth = 1;

    // counter incremented on each insert operation
    var clock = 0;

    // list of number generators on the specified range
    var numGenerators = [randomIntBtwn, boundaryHighIntBtwn, boundaryLowerIntBtwn];

    /**
     * @param @optional {Object} atoms
     */
    function Logoot(atoms) {
        EventEmitter.apply(this);

        // this.atoms contains the contents that compose your linear data. this.atoms
        // maps an atom id to an atom. An atom can be any data type.
        this.atoms = atoms || {};

        var ids = getIdentifiers(Object.keys(this.atoms));

        // determine maximum id depth
        maxDepth = getIdentifiersDepth(ids);

        // wrap ids in the tokens
        ids.unshift(FIRST);
        ids.push(LAST);

        // this.ids is an ordered index of atom ids. If has pseudo-atoms FIRST and
        // LAST to denote the begining and ending boundaries of our linear data.
        this.ids = ids;
    }

    Logoot.prototype = Object.create(EventEmitter.prototype);

    /**
     * Inserts an `atom` with a given `id`, with the insertion being annotated as
     * being authored by `agent`
     *
     * @param {[Number, String]} id
     * @param {Object} atom
     * @param {String} agent
     * @param @optional {Number} insertAfter
     */
    Logoot.prototype.ins = function(id, atom, agent, insertAfter) {
        var ids = this.ids;
        if (typeof insertAfter == "undefined") {
            insertAfter = indexOfGreatestLessThan(ids, id, compare);
        }
        ids.splice(insertAfter + 1, 0, id);
        this.atoms[hashId(id)] = atom;
        if (arguments.length < 4) {
            this.emit("ins", insertAfter, atom);
        }
        return ["ins", id, atom, agent];
    };

    /**
     * Deletes an atom with the given `id`, with the deletion being annotated as
     * being authored by `agent`. For efficiency's sake, we can optionally pass in
     * `indexOfId`, if it was calculated before the invocation, which allows us to
     * avoid looking up the location of id in our index, this.ids.
     *
     * @param {[Number, String]} id is the id of the atom we want to delete
     * @param {String} agent is the author of the deletion
     * @param @optional {Number} indexOfId is the index of id in this.ids
     */
    Logoot.prototype.del = function(id, agent, indexOfId) {
        var ids = this.ids;
        indexOfId || (indexOfId = indexOf(ids, id, compare));
        ids.splice(indexOfId, 1);
        delete this.atoms[hashId(id)];
        if (arguments.length < 3) {
            this.emit("del", indexOfId - 1);
        }
        return ["del", id, agent];
    };

    /**
     * Applies an operation that is typically received from another author.
     *
     * @param {Array} op can be either ['ins', id, line, agent] or ['del', id, agent]
     */
    Logoot.prototype.applyOp = function(op) {
        var args = op.slice(1);
        if (typeof args[0] == "string") {
            args[0] = deserializeId(args[0]);
        }
        this[op[0]].apply(this, args);
    };

    /**
     * Reduces over the sequence of atoms encapsulated by this Logoot. This is
     * handy when we want to convert our Logoot into a data structure more suitable
     * for our app (e.g., to String of character atoms or to an Array of atoms).
     *
     * @param {Function} fn(reducedValue, atom)
     * @param {init} is the initial value of reducedValue for fn
     * @return {Object} the reduced value
     */
    Logoot.prototype.reduce = function(fn, init) {
        var atoms = this.atoms;
        return this.ids.reduce(function(val, id) {
            if (id === FIRST || id === LAST) return val;
            return fn(val, atoms[hashId(id)]);
        }, init);
    };

    /**
     * Generates an id that is between from and to.
     *
     * @param {Array} from is an atom id currently stored in this.atoms
     * @param {Array} to is an atom id currently stored in this.atoms
     * @param {String} agent is the id of the author creating the id
     * @return {Array} id between from and to
     */
    Logoot.prototype.genId = function(from, to, agent, depth) {

        depth = depth || 1;

        // base depends on depth of identifiers
        maxDepth = Math.max(maxDepth, depth);
        var base = MAX * Math.pow(2, maxDepth);

        var min = from[0] || 0;
        var max = to[0] || base;

        if (min < base - 1) {
            var id = generateIntBtwn(min, max);
            return [id, agent, clock++];
        }

        return [from[0] || 0, from[1] || agent, from[2] || 0]
            .concat(this.genId(from.slice(3), to.slice(3), agent, depth + 1));
    };

    /**
     * Generates a new integer between min and max, non-inclusive.
     *
     * @param {Number} min
     * @param {Number} max
     * @return {Number} random number
     */
    function generateIntBtwn(min, max) {
        var generatorId = Math.floor(Math.random() * numGenerators.length);
        var generator = numGenerators[generatorId];

        return generator(min, max);
    }

    /**
     * Generates a random integer between min and max, non-inclusive.
     *
     * @param {Number} min
     * @param {Number} max
     * @return {Number} random number
     */
    function randomIntBtwn(min, max) {
        return Math.floor(Math.random() * (max - min - 1)) + min + 1;
    }

    /**
     * Generates a random integer between min and max, non-inclusive
     * with a lower boundary constraint.
     *
     * @param {Number} min
     * @param {Number} max
     * @return {Number} random number
     */
    function boundaryLowerIntBtwn(min, max) {
        return Math.floor(Math.random() * (max - min - 1)) + min + 1;
    }

    /**
     * Generates a random integer between min and max, non-inclusive
     * with a high boundary constraint.
     *
     * @param {Number} min
     * @param {Number} max
     * @return {Number} random number
     */
    function boundaryHighIntBtwn(min, max) {
        return Math.floor(Math.random() * (max - min - 1)) + min + 1;
    }

    /**
     * Returns the index of `x` inside of the array `xs`. Returns -1 if not found.
     *
     * @param {Array} xs is an array of objects
     * @param {Object} x is the member of xs that we are trying to locate
     * @param {Function} compare(a, b) returns -1 if a < b; 1 if a > b; 0 if a == b
     * @return {Number} index of x in xs. -1 if x is not in xs.
     */
    function indexOf(xs, x, comparer) {
        for (var i = 0, l = xs.length; i < l; i++) {
            if (comparer(xs[i], x) === 0) return i;
        }
        return -1;
    }

    /**
     * Returns the index of the largest element in `xs`, that is less than `x`,
     * according to a comparison function `compare`. Whether of not `x` is in `xs`,
     * the result will be the same, regardless.
     *
     * @param {Array} xs is an array of objects
     * @param {Object} x is the object to which members of `xs` are being compared
     * @param {Function} compare(a, b) returns -1 if a < b; 1 if a > b; 0 if a == b
     * @return {Number}
     */
    function indexOfGreatestLessThan(xs, x, comparer) {
        var comp, i, l;
        for (i = 0, l = xs.length; i < l; i++) {
            comp = comparer(xs[i], x);
            if (comp !== -1) {
                return i - 1;
            }
        }
        return i;
    }

    /**
     * Compares 2 ids of the form [Number, String]. `a` is less than `b`
     * if a[0] < b[0]. If a[0] == b[0], then `a` is less than `b` iff a[1] < b[1].
     * Returns -1 if a < b; returns 1 if a > b; returns 0 if a == b.
     *
     * @param {Array} a
     * @param {Array} b
     * @return {Number} -1 if a < b; 1 if a > b; 0 if a == b
     */
    function compare(a, b) {
        var depth = Math.max(a.length, b.length);
        var ai, bi;

        for (var i = 0; i < depth; i ++) {
            ai = a[i];
            bi = b[i];

            if (typeof ai == "undefined") return -1;
            if (typeof bi == "undefined") return 1;
            if (ai < bi) return -1;
            if (ai > bi) return 1;
        }

        return 0;
    }

    /**
     * Hashes `id`. Useful for hashing atom ids to store in a hash map.
     *
     * @param {Array} id
     * @return {String}
     */
    function hashId(id) {
        return id.join(".");
    }

    /**
     * Returns a list of composite identifiers from atoms.
     *
     * @param {Object} atoms
     * @return {Array}
     */
    function getIdentifiers(keys) {
        return keys.map(function(key) {
            return deserializeId(key);
        }).sort(compare);
    }

    /**
    * Returns a deserialized id value.
    *
    * @param {String} id - identifier.
    * @return {Array}
    */
    function deserializeId(id) {
        return id.split(".").map(function(val) {
            return parseInt(val);
        });
    }

    /**
    * Returns maximum depth of identfiers.
    *
    * @param {String} id - identifier.
    * @return {Array}
    */
    function getIdentifiersDepth(ids) {
        return ids.reduce(function(max, id) {
            return Math.max(max, id.length / 3);
        }, 1);
    }

    /**
     * Exports logoot.
     */
    window["Logoot"] = Logoot;

})(EventEmitter, window);<|endoftext|>


<commit_before>/**
 * The main class required to set up a Tree instance in the browser.
 *
 * @class Tree
 **/

define(function(require, exports, module) {
"use strict";

var oop = require("ace/lib/oop");
var Scrollable = require("./scrollable");
var dom = require("ace/lib/dom");
var escapeHTML = require("ace/lib/lang").escapeHTML;

var DataProvider = function(root) {
    this.rowHeight = 25;
    this.setRoot(root);
};

(function() {
    this.rowHeight = undefined;
    this.rowHeightInner = undefined;
    this.$indentSize = 10;

    oop.implement(this, Scrollable);

    this.$sortNodes = true;

    this.setRoot = function(root){
        if (Array.isArray(root))
            root = {items: root};

        this.root = root || {};

        if (this.root.$depth == undefined) {
            this.root.$depth = -1;
        }
        if (this.root.$depth < 0) {
            this.visibleItems = [];
            this.open(this.root);
            this.visibleItems.unshift();
        } else {
            this.visibleItems = [this.root];
        }
        this.$selectedNode = this.root;

        this._signal("setRoot");
        this._signal("change");
    };

    this.open =
    this.expand = function(node, deep, silent, justLoaded) {
        if (typeof deep != "number")
            deep = deep ? 100 : 0;
        if (!node)
            return;

        var items = this.visibleItems;
        if (this.isOpen(node) && (node !== this.root || items.length))
            return;
        var ch = this.getChildren(node);
        if (this.loadChildren && this.shouldLoadChildren(node, ch)) {
            var timer = setTimeout(function() {
                node.status = "loading";
                this._signal("change", node);
            }.bind(this), 100);
            this.loadChildren(node, function(err, ch) {
                clearTimeout(timer);
                this.collapse(node, null, true);
                node.status = "loaded";
                if (!err)
                    this.expand(node, null, false, true);
            }.bind(this));
            this.setOpen(node, true);
            return;
        }
        this.setOpen(node, true);
        var i = items.indexOf(node);
        if (!ch) {
            this._signal("change", node);
            return;
        }
        if (i === -1 && items.length || this.forceEmpty)
            return;
        ch = [i + 1, 0].concat(ch);
        items.splice.apply(items, ch);

        for (var j = 2; j < ch.length; j++) {
            var childNode = ch[j];
            if (this.isOpen(childNode)) {
                this.setOpen(childNode, false);
                this.open(childNode, deep - 1);
            } else if (deep > 0) {
                this.open(childNode, deep - 1);
            }
        }

        if (justLoaded)
            node.justLoaded = true;

        this.rows = items.length;
        silent || this._signal("expand", node);
    };

    this.close =
    this.collapse = function(node, deep, silent) {
        if (typeof deep != "number")
            deep = deep ? 1000 : 0;
        var items = this.visibleItems;
        var isRoot = node === this.root;
        if (isRoot) {
            this.setOpen(node, false);
            if (deep) {
                for (var i = 0; i < items.length; i++) {
                    var ch = items[i];
                    if (!ch.isRoot)
                    if (this.isOpen(ch) && ch.$depth - node.$depth < deep) {
                        this.setOpen(ch, false);
                        silent || this._signal("collapse", ch);
                    }
                }
            }
            items.length = 0;
            if (isRoot)
                this.open(this.root, 0, silent);
            return;
        }

        if (!node || !this.isOpen(node))
            return;
        var i = items.indexOf(node);
        if (i === -1)
            return;
        var thisDepth = node.$depth;
        var deletecount = 0;
        for (var t = i + 1; t < items.length; t++) {
            if (items[t].$depth > thisDepth)
                deletecount++;
            else
                break;
        }

        if (deep) {
            for (var j = 0; j < deletecount; j++) {
                var ch = items[j + i];
                if (this.isOpen(ch) && ch.$depth - node.$depth < deep) {
                    this.setOpen(ch, false);
                    silent || this._signal("collapse", ch);
                }
            }
        }
        items.splice(i + 1, deletecount);
        this.setOpen(node, false);
        silent || this._signal("collapse", node);

        if (isRoot)
            this.open(this.root, 0, silent);
    };

    this.toggleNode = function(node, deep, silent) {
        if (node && this.isOpen(node))
            this.close(node, deep, silent);
        else
            this.open(node, deep, silent);
    };

    this.sort = function(children, compare) {
        if (!compare) {
            compare = alphanumCompare;
        }
        return children.sort(function(a, b) {
            var aChildren = a.children || a.map;
            var bChildren = b.children || b.map;
            if (aChildren && !bChildren) return -1;
            if (!aChildren && bChildren) return 1;

            return compare(a.label || "", b.label || "");
        });
    };

    this.setFilter = function(fn) {
        this.$filterFn = fn;
        this.setRoot(this.root);
    };
    this.getChildren = function(node) {
        var children = node.children;
        if (!children) {
            if (node.status === "pending")
                return;
            if (node.map) {
                children = Object.keys(node.map).map(function(key) {
                    var ch = node.map[key];
                    ch.parent = node;
                    return ch;
                });
            } else if (node.items) {
                children = node.items;
            }
            if (children) {
                node.children = children;
            }
        }
        var ch = children && children[0] && children[0];
        if (ch) {
            var d = (node.$depth + 1) || 0;
            children.forEach(function(n) {
                 n.$depth = d;
                 n.parent = node;
            });
        }

        if (this.$filterFn) {
            children = children && children.filter(this.$filterFn);
        }

        if (this.$sortNodes && !node.$sorted) {
            children && this.sort(children);
        }
        return children;
    };
    this.loadChildren = null;
    this.shouldLoadChildren = function(node, ch) {
        return node.status === "pending";
    };

    this.hasChildren = function(node) {
        if (node.children)
            return node.children.length !== 0;
        return node.map || node.status === "pending"
            || node.items && node.items.length;
    };

    this.findNodeByPath = function() {

    };

    this.getSibling = function(node, dir) {
        if (!dir) dir = 1;
        var parent = node.parent;
        var ch = this.getChildren(parent);
        var pos = ch.indexOf(node);
        return ch[pos + dir];
    };

    this.getNodeAtIndex = function(i) {
        return this.visibleItems[i];
    };

    this.getIndexForNode = function(node) {
        return this.visibleItems.indexOf(node);
    };

    this.getMinIndex = function() {return 0};
    this.getMaxIndex = function() {return this.visibleItems.length - 1};

    this.setOpen = function(node, val) {
        return node.isOpen = val;
    };
    this.isOpen = function(node) {
        return node.isOpen;
    };
    this.isVisible = function(node) {
        return this.visibleItems.indexOf(node) !== -1;
    };
    this.isSelected = function(node) {
        return node.isSelected;
    };
    this.setSelected = function(node, val) {
        return node.isSelected = !!val;
    };
    this.isSelectable = function(node) {
        return !node || !(node.noSelect || node.$depth < 0);
    };

    this.isAncestor = function(node, child) {
        do {
            if (child == node)
                return true;
        } while (child = child.parent);
        return false;
    };

    this.setAttribute = function(node, name, value) {
        node[name] = value;
        this._signal("change", node);
    };

    this.getDataRange = function(rows, columns, callback) {
        var view = this.visibleItems.slice(rows.start, rows.start + rows.length);
        callback(null, view, false);
        return view;
    };

    this.getRange = function(top, bottom) {
        var start = Math.floor(top / this.rowHeight);
        var end = Math.ceil(bottom / this.rowHeight) + 1;
        var range = this.visibleItems.slice(start, end);
        range.count = start;
        range.size = this.rowHeight * range.count;
        return range;
    };
    this.getTotalHeight = function(top, bottom) {
        return this.rowHeight * this.visibleItems.length;
    };

    this.getNodePosition = function(node) {
        var i = this.visibleItems.indexOf(node);
        if (i == -1 && node && node.parent) {
            i = this.visibleItems.indexOf(node.parent);
        }
        var top = i * this.rowHeight;
        var height = this.rowHeight;
        return {top: top, height: height};
    };

    this.findItemAtOffset = function(offset, clip) {
        var index = Math.floor(offset / this.rowHeight);
        if (clip)
            index = Math.min(Math.max(0, index), this.visibleItems.length - 1);
        return this.visibleItems[index];
    };
    this.getIconHTML = function(node) {
        return "";
    };
    this.getClassName = function(node) {
        return (node.className || "") + (node.status == "loading" ? " loading" : "");
    };
    this.setClass = function(node, name, include) {
        node.className = node.className || "";
        dom.setCssClass(node, name, include);
        this._signal("changeClass");
    };
    this.redrawNode = null;
    this.getCaptionHTML = function(node) {
        return escapeHTML(node.label || node.name || (typeof node == "string" ? node : ""));
    };
    this.getContentHTML = null;
    this.getEmptyMessage = function() { return this.emptyMessage || "" };
    this.getText = function(node) {
        return node.label || node.name || "";
    };
    this.getRowIndent = function(node){
        return node.$depth;
    };
    this.hideAllNodes = function(){
        this.visibleItems = [];
        this.forceEmpty   = true;
        this.setRoot(this.root);
    };
    this.showAllNodes = function(){
        this.forceEmpty   = false;
        this.setRoot(this.root);
    };

}).call(DataProvider.prototype);

function alphanumCompare(a, b) {
    var caseOrder = 0;
    for (var x = 0, l = Math.min(a.length, b.length); x < l; x++) {
        var ch1 = a.charCodeAt(x);
        var ch2 = b.charCodeAt(x);

        if (ch1 < 58 && ch2 < 58 && ch1 > 47 && ch2 > 47) {
            var num1 = 0, num2 = 0;
            var n = x;
            do {
                num1 = 10 * num1 + (ch1 - 48);
                ch1 = a.charCodeAt(++n);
            } while(ch1 > 47 && ch1 < 58);
            n = x;
            do {
                num2 = 10 * num2 + (ch2 - 48);
                ch2 = b.charCodeAt(++n);
            } while(ch2 > 47 && ch2 < 58);

            if (num1 === num2)
                x = n - 1;
            else
                return num1 - num2;
        } else if (ch1 !== ch2) {
            var ch1L = a[x].toLowerCase();
            var ch2L = b[x].toLowerCase();
            if (ch1L < ch2L) return -1;
            if (ch1L > ch2L) return 1;
            if (!caseOrder) caseOrder = ch2 - ch1;
        }
    }
    return caseOrder || a.length - b.length;
}

DataProvider.alphanumCompare = alphanumCompare;
DataProvider.prototype.alphanumCompare = alphanumCompare;
DataProvider.variableHeightRowMixin = function() {
    var reset = function () {
        this.$cachedTotalHeight = 0;
    }.bind(this);
    this.on("collapse", reset);
    this.on("expand", reset);
    // this.rowCache
    this.getNodePosition = function(node) {
        var i = this.visibleItems.indexOf(node);
        if (i == -1 && node && node.parent) {
            i = this.visibleItems.indexOf(node.parent);
        }

        var items = this.visibleItems;
        var top = 0, height = 0;
        for (var index = 0; index < i; index++) {
            height = this.getItemHeight(items[index], index);
            top += height;
        }
        height = this.getItemHeight(items[i], i);
        return {top: top, height: height};
    };

    this.findIndexAtOffset = function(offset, clip) {
        var items = this.visibleItems;
        var top = 0, index = 0, l = items.length;
        while (index < l) {
            var height = this.getItemHeight(items[index].height, index);
            top += height;
            index++;
            if (top >= offset) {
                index--;
                top -= height;
                break;
            }
        }

        if (clip)
            index = Math.min(Math.max(0, index), items.length - 1);
        return index;
    };

    this.findItemAtOffset = function(offset, clip) {
        var index = this.findIndexAtOffset(offset, clip);
        return this.visibleItems[index];
    };

    this.getItemHeight = function(node, index) {
        return node.height || this.rowHeight;
    };

    this.getRange = function(top, bottom) {
        var items = this.visibleItems;
        var startH = 0, index = 0, l = items.length;
        while (index < l) {
            var height = this.getItemHeight(items[index].height, index);
            startH += height;
            index++;
            if (startH >= top) {
                index--;
                startH -= height;
                break;
            }
        }
        index = Math.min(Math.max(0, index), items.length - 1);

        var start = index;
        var end = this.findIndexAtOffset(bottom, true) + 1;
        var range = this.visibleItems.slice(start, end);
        range.count = start;
        range.size = startH;
        return range;
    };

    this.getTotalHeight = function () {
        if (!this.$cachedTotalHeight) {
            var items = this.visibleItems;
            var height = 0;
            for (var index = 0; index < items.length; index++) {
                height += this.getItemHeight(items[index], index);
            }
            this.$cachedTotalHeight = height;
        }
        return this.$cachedTotalHeight;
    };
};
module.exports = DataProvider;
});
<commit_msg>fix silent flag for tree expand<commit_after>/**
 * The main class required to set up a Tree instance in the browser.
 *
 * @class Tree
 **/

define(function(require, exports, module) {
"use strict";

var oop = require("ace/lib/oop");
var Scrollable = require("./scrollable");
var dom = require("ace/lib/dom");
var escapeHTML = require("ace/lib/lang").escapeHTML;

var DataProvider = function(root) {
    this.rowHeight = 25;
    this.setRoot(root);
};

(function() {
    this.rowHeight = undefined;
    this.rowHeightInner = undefined;
    this.$indentSize = 10;

    oop.implement(this, Scrollable);

    this.$sortNodes = true;

    this.setRoot = function(root){
        if (Array.isArray(root))
            root = {items: root};

        this.root = root || {};

        if (this.root.$depth == undefined) {
            this.root.$depth = -1;
        }
        if (this.root.$depth < 0) {
            this.visibleItems = [];
            this.open(this.root);
            this.visibleItems.unshift();
        } else {
            this.visibleItems = [this.root];
        }
        this.$selectedNode = this.root;

        this._signal("setRoot");
        this._signal("change");
    };

    this.open =
    this.expand = function(node, deep, silent, justLoaded) {
        if (typeof deep != "number")
            deep = deep ? 100 : 0;
        if (!node)
            return;

        var items = this.visibleItems;
        if (this.isOpen(node) && (node !== this.root || items.length))
            return;
        var ch = this.getChildren(node);
        if (this.loadChildren && this.shouldLoadChildren(node, ch)) {
            var timer = setTimeout(function() {
                node.status = "loading";
                this._signal("change", node);
            }.bind(this), 100);
            this.loadChildren(node, function(err, ch) {
                clearTimeout(timer);
                this.collapse(node, null, true);
                node.status = "loaded";
                if (!err)
                    this.expand(node, null, false, true);
            }.bind(this));
            this.setOpen(node, true);
            return;
        }
        this.setOpen(node, true);
        var i = items.indexOf(node);
        if (!ch) {
            this._signal("change", node);
            return;
        }
        if (i === -1 && items.length || this.forceEmpty)
            return;
        ch = [i + 1, 0].concat(ch);
        items.splice.apply(items, ch);

        for (var j = 2; j < ch.length; j++) {
            var childNode = ch[j];
            if (this.isOpen(childNode)) {
                this.setOpen(childNode, false);
                this.open(childNode, deep - 1, silent);
            } else if (deep > 0) {
                this.open(childNode, deep - 1, silent);
            }
        }

        if (justLoaded)
            node.justLoaded = true;

        this.rows = items.length;
        silent || this._signal("expand", node);
    };

    this.close =
    this.collapse = function(node, deep, silent) {
        if (typeof deep != "number")
            deep = deep ? 1000 : 0;
        var items = this.visibleItems;
        var isRoot = node === this.root;
        if (isRoot) {
            this.setOpen(node, false);
            if (deep) {
                for (var i = 0; i < items.length; i++) {
                    var ch = items[i];
                    if (!ch.isRoot)
                    if (this.isOpen(ch) && ch.$depth - node.$depth < deep) {
                        this.setOpen(ch, false);
                        silent || this._signal("collapse", ch);
                    }
                }
            }
            items.length = 0;
            if (isRoot)
                this.open(this.root, 0, silent);
            return;
        }

        if (!node || !this.isOpen(node))
            return;
        var i = items.indexOf(node);
        if (i === -1)
            return;
        var thisDepth = node.$depth;
        var deletecount = 0;
        for (var t = i + 1; t < items.length; t++) {
            if (items[t].$depth > thisDepth)
                deletecount++;
            else
                break;
        }

        if (deep) {
            for (var j = 0; j < deletecount; j++) {
                var ch = items[j + i];
                if (this.isOpen(ch) && ch.$depth - node.$depth < deep) {
                    this.setOpen(ch, false);
                    silent || this._signal("collapse", ch);
                }
            }
        }
        items.splice(i + 1, deletecount);
        this.setOpen(node, false);
        silent || this._signal("collapse", node);

        if (isRoot)
            this.open(this.root, 0, silent);
    };

    this.toggleNode = function(node, deep, silent) {
        if (node && this.isOpen(node))
            this.close(node, deep, silent);
        else
            this.open(node, deep, silent);
    };

    this.sort = function(children, compare) {
        if (!compare) {
            compare = alphanumCompare;
        }
        return children.sort(function(a, b) {
            var aChildren = a.children || a.map;
            var bChildren = b.children || b.map;
            if (aChildren && !bChildren) return -1;
            if (!aChildren && bChildren) return 1;

            return compare(a.label || "", b.label || "");
        });
    };

    this.setFilter = function(fn) {
        this.$filterFn = fn;
        this.setRoot(this.root);
    };
    this.getChildren = function(node) {
        var children = node.children;
        if (!children) {
            if (node.status === "pending")
                return;
            if (node.map) {
                children = Object.keys(node.map).map(function(key) {
                    var ch = node.map[key];
                    ch.parent = node;
                    return ch;
                });
            } else if (node.items) {
                children = node.items;
            }
            if (children) {
                node.children = children;
            }
        }
        var ch = children && children[0] && children[0];
        if (ch) {
            var d = (node.$depth + 1) || 0;
            children.forEach(function(n) {
                 n.$depth = d;
                 n.parent = node;
            });
        }

        if (this.$filterFn) {
            children = children && children.filter(this.$filterFn);
        }

        if (this.$sortNodes && !node.$sorted) {
            children && this.sort(children);
        }
        return children;
    };
    this.loadChildren = null;
    this.shouldLoadChildren = function(node, ch) {
        return node.status === "pending";
    };

    this.hasChildren = function(node) {
        if (node.children)
            return node.children.length !== 0;
        return node.map || node.status === "pending"
            || node.items && node.items.length;
    };

    this.findNodeByPath = function() {

    };

    this.getSibling = function(node, dir) {
        if (!dir) dir = 1;
        var parent = node.parent;
        var ch = this.getChildren(parent);
        var pos = ch.indexOf(node);
        return ch[pos + dir];
    };

    this.getNodeAtIndex = function(i) {
        return this.visibleItems[i];
    };

    this.getIndexForNode = function(node) {
        return this.visibleItems.indexOf(node);
    };

    this.getMinIndex = function() {return 0};
    this.getMaxIndex = function() {return this.visibleItems.length - 1};

    this.setOpen = function(node, val) {
        return node.isOpen = val;
    };
    this.isOpen = function(node) {
        return node.isOpen;
    };
    this.isVisible = function(node) {
        return this.visibleItems.indexOf(node) !== -1;
    };
    this.isSelected = function(node) {
        return node.isSelected;
    };
    this.setSelected = function(node, val) {
        return node.isSelected = !!val;
    };
    this.isSelectable = function(node) {
        return !node || !(node.noSelect || node.$depth < 0);
    };

    this.isAncestor = function(node, child) {
        do {
            if (child == node)
                return true;
        } while (child = child.parent);
        return false;
    };

    this.setAttribute = function(node, name, value) {
        node[name] = value;
        this._signal("change", node);
    };

    this.getDataRange = function(rows, columns, callback) {
        var view = this.visibleItems.slice(rows.start, rows.start + rows.length);
        callback(null, view, false);
        return view;
    };

    this.getRange = function(top, bottom) {
        var start = Math.floor(top / this.rowHeight);
        var end = Math.ceil(bottom / this.rowHeight) + 1;
        var range = this.visibleItems.slice(start, end);
        range.count = start;
        range.size = this.rowHeight * range.count;
        return range;
    };
    this.getTotalHeight = function(top, bottom) {
        return this.rowHeight * this.visibleItems.length;
    };

    this.getNodePosition = function(node) {
        var i = this.visibleItems.indexOf(node);
        if (i == -1 && node && node.parent) {
            i = this.visibleItems.indexOf(node.parent);
        }
        var top = i * this.rowHeight;
        var height = this.rowHeight;
        return {top: top, height: height};
    };

    this.findItemAtOffset = function(offset, clip) {
        var index = Math.floor(offset / this.rowHeight);
        if (clip)
            index = Math.min(Math.max(0, index), this.visibleItems.length - 1);
        return this.visibleItems[index];
    };
    this.getIconHTML = function(node) {
        return "";
    };
    this.getClassName = function(node) {
        return (node.className || "") + (node.status == "loading" ? " loading" : "");
    };
    this.setClass = function(node, name, include) {
        node.className = node.className || "";
        dom.setCssClass(node, name, include);
        this._signal("changeClass");
    };
    this.redrawNode = null;
    this.getCaptionHTML = function(node) {
        return escapeHTML(node.label || node.name || (typeof node == "string" ? node : ""));
    };
    this.getContentHTML = null;
    this.getEmptyMessage = function() { return this.emptyMessage || "" };
    this.getText = function(node) {
        return node.label || node.name || "";
    };
    this.getRowIndent = function(node){
        return node.$depth;
    };
    this.hideAllNodes = function(){
        this.visibleItems = [];
        this.forceEmpty   = true;
        this.setRoot(this.root);
    };
    this.showAllNodes = function(){
        this.forceEmpty   = false;
        this.setRoot(this.root);
    };

}).call(DataProvider.prototype);

function alphanumCompare(a, b) {
    var caseOrder = 0;
    for (var x = 0, l = Math.min(a.length, b.length); x < l; x++) {
        var ch1 = a.charCodeAt(x);
        var ch2 = b.charCodeAt(x);

        if (ch1 < 58 && ch2 < 58 && ch1 > 47 && ch2 > 47) {
            var num1 = 0, num2 = 0;
            var n = x;
            do {
                num1 = 10 * num1 + (ch1 - 48);
                ch1 = a.charCodeAt(++n);
            } while(ch1 > 47 && ch1 < 58);
            n = x;
            do {
                num2 = 10 * num2 + (ch2 - 48);
                ch2 = b.charCodeAt(++n);
            } while(ch2 > 47 && ch2 < 58);

            if (num1 === num2)
                x = n - 1;
            else
                return num1 - num2;
        } else if (ch1 !== ch2) {
            var ch1L = a[x].toLowerCase();
            var ch2L = b[x].toLowerCase();
            if (ch1L < ch2L) return -1;
            if (ch1L > ch2L) return 1;
            if (!caseOrder) caseOrder = ch2 - ch1;
        }
    }
    return caseOrder || a.length - b.length;
}

DataProvider.alphanumCompare = alphanumCompare;
DataProvider.prototype.alphanumCompare = alphanumCompare;
DataProvider.variableHeightRowMixin = function() {
    var reset = function () {
        this.$cachedTotalHeight = 0;
    }.bind(this);
    this.on("collapse", reset);
    this.on("expand", reset);
    // this.rowCache
    this.getNodePosition = function(node) {
        var i = this.visibleItems.indexOf(node);
        if (i == -1 && node && node.parent) {
            i = this.visibleItems.indexOf(node.parent);
        }

        var items = this.visibleItems;
        var top = 0, height = 0;
        for (var index = 0; index < i; index++) {
            height = this.getItemHeight(items[index], index);
            top += height;
        }
        height = this.getItemHeight(items[i], i);
        return {top: top, height: height};
    };

    this.findIndexAtOffset = function(offset, clip) {
        var items = this.visibleItems;
        var top = 0, index = 0, l = items.length;
        while (index < l) {
            var height = this.getItemHeight(items[index].height, index);
            top += height;
            index++;
            if (top >= offset) {
                index--;
                top -= height;
                break;
            }
        }

        if (clip)
            index = Math.min(Math.max(0, index), items.length - 1);
        return index;
    };

    this.findItemAtOffset = function(offset, clip) {
        var index = this.findIndexAtOffset(offset, clip);
        return this.visibleItems[index];
    };

    this.getItemHeight = function(node, index) {
        return node.height || this.rowHeight;
    };

    this.getRange = function(top, bottom) {
        var items = this.visibleItems;
        var startH = 0, index = 0, l = items.length;
        while (index < l) {
            var height = this.getItemHeight(items[index].height, index);
            startH += height;
            index++;
            if (startH >= top) {
                index--;
                startH -= height;
                break;
            }
        }
        index = Math.min(Math.max(0, index), items.length - 1);

        var start = index;
        var end = this.findIndexAtOffset(bottom, true) + 1;
        var range = this.visibleItems.slice(start, end);
        range.count = start;
        range.size = startH;
        return range;
    };

    this.getTotalHeight = function () {
        if (!this.$cachedTotalHeight) {
            var items = this.visibleItems;
            var height = 0;
            for (var index = 0; index < items.length; index++) {
                height += this.getItemHeight(items[index], index);
            }
            this.$cachedTotalHeight = height;
        }
        return this.$cachedTotalHeight;
    };
};
module.exports = DataProvider;
});
<|endoftext|>


<commit_before>var React = require('react'),
    Navigation = require('react-router').Navigation,
    AppStore = require('../../stores/AppStore'),
    EventDetailStore = require('../../stores/EventDetailStore'),
    EventDetailActions = require('../../actions/EventDetailActions'),
    ObservationList = require('../ObservationList/ObservationList'),
    ObservationCreate = require('../ObservationList/ObservationCreate'),
    AttendeesList = require('../AttendeesList/AttendeesList'),
    AttendeeStore = require('../../stores/AttendeeStore');

var intervalId;
var prevEvent;

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

  getInitialState: function() {
    prevEvent = EventDetailStore.getCurrentEvent();
    return {
      mode: AppStore.getCurrentMode(),
      isAttendee: EventDetailStore.isAttendee(),
      event: EventDetailStore.getCurrentEvent()
    };
  },

  // Adds event listeners when events are created or edited
  componentDidMount: function() {
    EventDetailStore.addEventListener('edit', this._onEdit);
    EventDetailStore.addEventListener('updateCurrentEvent', this._onEventSet);
    EventDetailStore.addEventListener('attend', this._onAttend);
    AttendeeStore.addEventListener('acquiredSheep', this._onAcquireSheep);
    if (this.props.params.eventId) {
      EventDetailActions.get(this.props.params.eventId);
      intervalId = setInterval(function(){EventDetailActions.get(this.props.params.eventId)}.bind(this), 2000);
    }
  },

  // Removes event listener when events are deleted or edited
  componentWillUnmount: function() {
    EventDetailStore.removeEventListener('edit', this._onEdit);
    EventDetailStore.removeEventListener('updateCurrentEvent', this._onEventSet);
    EventDetailStore.removeEventListener('attend', this._onAttend);
    AttendeeStore.removeEventListener('acquiredSheep', this._onAcquireSheep);
    clearInterval(intervalId);
  },

  render: function() {

    return (

            Currently {this.state.isAttendee ? 'Attending' : 'Observing'} Event

            { (this.state.mode === 'sheep') ?
              <button className="btn btn-default edit-buttons" onClick={this._attend}>
                {this.state.isAttendee ? 'Leave' : 'Attend'} Event
              </button> : null }

{this.state.event.name}

              {this.state.mode === 'shepherd' ? <button className="btn btn-default edit-buttons" onClick={this._editName}>Edit Name</button> : null}

                {this.state.event.description}

              {this.state.mode === 'shepherd' ? <button className="btn btn-default edit-buttons" onClick={this._editDescription}>Edit Description</button> : null}

                {this.state.event.location}

              { this.state.mode === 'shepherd' ? <button className="btn btn-default edit-buttons" onClick={this._editLocation}>Edit Location</button> : null }

              {/* Use Moment.js to format start and end times to the following format: Mon, Jun 1, 2015 4:30 PM */}

{moment(this.state.event.start).format('lll')} -
                {moment(this.state.event.end).format('lll')}

              { this.state.mode === 'shepherd' ? <button className="btn btn-default edit-buttons" onClick={this._endEvent}>End Event</button> : null }

                Action: {this.state.event.action}

              { this.state.mode === 'shepherd' ? <button className="btn btn-default edit-buttons" onClick={this._editAction}>Edit Action</button> : null }

          <AttendeesList eventId={this.state.event.id || this.props.params.eventId}/>

        { /* Add the observation create if and only if sheep is attending event */ }
        { (this.state.mode === 'sheep' && this.state.isAttendee) ? <ObservationCreate eventId={this.state.event.id}/> : null }
        <ObservationList eventId={this.state.event.id || this.props.params.eventId}/>

    );
  },

  _onEventSet: function() {
    var storeCurrentEvent = EventDetailStore.getCurrentEvent();
    if (prevEvent.action !== storeCurrentEvent.action){
      prevEvent = storeCurrentEvent;
      if (this.state.mode === 'sheep'){
        alert(storeCurrentEvent.action);
      }
    }
    this.setState({event: storeCurrentEvent});
  },

  // Created a prompt to change the event name
  _editName: function(){
    var obj = this.state.event;
    var name = prompt('What should the Event be called?').trim();
    if (this.isResponseValid(name)){
      obj.name = name;
      EventDetailActions.edit(obj);
    } else {
      alert('Input cannot be an empty string');
    }
  },

  // Created a prompt to change the event location
  _editLocation: function(){
    var obj = this.state.event;
    var location = prompt('Where is the location?').trim();
    if (this.isResponseValid(location)){
      obj.location = location;
      EventDetailActions.edit(obj);
    } else {
      alert('Input cannot be an empty string');
    }
  },

  _editDescription: function(){
    var obj = this.state.event;
    var description = prompt('What is the new description?').trim();
    if (this.isResponseValid(description)){
      obj.description = description;
      EventDetailActions.edit(obj);
    } else {
      alert('Input cannot be an empty string');
    }
  },

  // Created a prompt to change the event action
  _editAction: function(){
    var obj = this.state.event;
    var action = prompt('What is the action?').trim();
    if (this.isResponseValid(action)){
      obj.action = action;
      EventDetailActions.edit(obj);
    } else {
      alert('Input cannot be an empty string');
    }
  },

  // Toggles whether a sheep is participating or not in an event
  _attend: function () {
    EventDetailActions.attend(this.state.event, AppStore.getCurrentUser(), !this.state.isAttendee);
  },

  _onAttend: function() {
    // console.log(EventDetailStore.isAttendee());
    this.setState({isAttendee: EventDetailStore.isAttendee()});
  },

  // Updates the current event properties on the page
  _onEdit: function(){
    this.setState({event: EventDetailStore.getCurrentEvent()});

    // If the event's end time is updated and is earlier than now,
    // redirect to the events list.
    // this.state.event.end will be a string of the date in ISO format
    if (new Date() > new Date(this.state.event.end)) {
      this.transitionTo('events');
    }
  },

  _onAcquireSheep: function() {
    var participatingSheep = AttendeeStore.getAllSheep();
    var currentUserId = AppStore.getCurrentUser().id;
    var currentUserAttendee = false;
    for (var i = 0; i < participatingSheep.length; i++) {
      if (participatingSheep[i]['id'] === currentUserId) {
        currentUserAttendee = true;
        break;
      }
    }
    this.setState({isAttendee: currentUserAttendee});
  },

  isResponseValid: function(input){
    return input.trim() !== '';
  },

  // Set the event end time to now so that it is no longer considered an active event
  _endEvent: function() {
    var obj = this.state.event;
    obj.end = new Date().toISOString();
    EventDetailActions.edit(obj);
  }

});

module.exports = EventDetail;
<commit_msg>Fixed EventDetail to conform to new layout<commit_after>var React = require('react'),
    Navigation = require('react-router').Navigation,
    AppStore = require('../../stores/AppStore'),
    EventDetailStore = require('../../stores/EventDetailStore'),
    EventDetailActions = require('../../actions/EventDetailActions'),
    ObservationList = require('../ObservationList/ObservationList'),
    ObservationCreate = require('../ObservationList/ObservationCreate'),
    AttendeesList = require('../AttendeesList/AttendeesList'),
    AttendeeStore = require('../../stores/AttendeeStore');

var intervalId;
var prevEvent;

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

  getInitialState: function() {
    prevEvent = EventDetailStore.getCurrentEvent();
    return {
      mode: AppStore.getCurrentMode(),
      isAttendee: EventDetailStore.isAttendee(),
      event: EventDetailStore.getCurrentEvent()
    };
  },

  // Adds event listeners when events are created or edited
  componentDidMount: function() {
    EventDetailStore.addEventListener('edit', this._onEdit);
    EventDetailStore.addEventListener('updateCurrentEvent', this._onEventSet);
    EventDetailStore.addEventListener('attend', this._onAttend);
    AttendeeStore.addEventListener('acquiredSheep', this._onAcquireSheep);
    if (this.props.params.eventId) {
      EventDetailActions.get(this.props.params.eventId);
      intervalId = setInterval(function(){EventDetailActions.get(this.props.params.eventId)}.bind(this), 2000);
    }
  },

  // Removes event listener when events are deleted or edited
  componentWillUnmount: function() {
    EventDetailStore.removeEventListener('edit', this._onEdit);
    EventDetailStore.removeEventListener('updateCurrentEvent', this._onEventSet);
    EventDetailStore.removeEventListener('attend', this._onAttend);
    AttendeeStore.removeEventListener('acquiredSheep', this._onAcquireSheep);
    clearInterval(intervalId);
  },

  render: function() {
    return (

            Currently {this.state.isAttendee ? 'Attending' : 'Observing'} Event

            { (this.state.mode === 'sheep') ?
              <button className="btn btn-default edit-buttons" onClick={this._attend}>
                {this.state.isAttendee ? 'Leave' : 'Attend'} Event
              </button> : null }

{this.state.event.name}

              {this.state.mode === 'shepherd' ? <button className="btn btn-default edit-buttons" onClick={this._editName}>Edit Name</button> : null}

                {this.state.event.description}

              {this.state.mode === 'shepherd' ? <button className="btn btn-default edit-buttons" onClick={this._editDescription}>Edit Description</button> : null}

                {this.state.event.location}

              { this.state.mode === 'shepherd' ? <button className="btn btn-default edit-buttons" onClick={this._editLocation}>Edit Location</button> : null }

              {/* Use Moment.js to format start and end times to the following format: Mon, Jun 1, 2015 4:30 PM */}

{moment(this.state.event.start).format('lll')} -
                {moment(this.state.event.end).format('lll')}

              { this.state.mode === 'shepherd' ? <button className="btn btn-default edit-buttons" onClick={this._endEvent}>End Event</button> : null }

                Action: {this.state.event.action}

              { this.state.mode === 'shepherd' ? <button className="btn btn-default edit-buttons" onClick={this._editAction}>Edit Action</button> : null }

          <AttendeesList eventId={this.state.event.id || this.props.params.eventId}/>

        { /* Add the observation create if and only if sheep is attending event */ }
        { (this.state.mode === 'sheep' && this.state.isAttendee) ? <ObservationCreate eventId={this.state.event.id}/> : null }
        <ObservationList eventId={this.state.event.id || this.props.params.eventId}/>

    );
  },

  _onEventSet: function() {
    var storeCurrentEvent = EventDetailStore.getCurrentEvent();
    if (prevEvent.action !== storeCurrentEvent.action){
      prevEvent = storeCurrentEvent;
      if (this.state.mode === 'sheep'){
        alert(storeCurrentEvent.action);
      }
    }
    this.setState({event: storeCurrentEvent});
  },

  // Created a prompt to change the event name
  _editName: function(){
    var obj = this.state.event;
    var name = prompt('What should the Event be called?').trim();
    if (this.isResponseValid(name)){
      obj.name = name;
      EventDetailActions.edit(obj);
    } else {
      alert('Input cannot be an empty string');
    }
  },

  // Created a prompt to change the event location
  _editLocation: function(){
    var obj = this.state.event;
    var location = prompt('Where is the location?').trim();
    if (this.isResponseValid(location)){
      obj.location = location;
      EventDetailActions.edit(obj);
    } else {
      alert('Input cannot be an empty string');
    }
  },

  _editDescription: function(){
    var obj = this.state.event;
    var description = prompt('What is the new description?').trim();
    if (this.isResponseValid(description)){
      obj.description = description;
      EventDetailActions.edit(obj);
    } else {
      alert('Input cannot be an empty string');
    }
  },

  // Created a prompt to change the event action
  _editAction: function(){
    var obj = this.state.event;
    var action = prompt('What is the action?').trim();
    if (this.isResponseValid(action)){
      obj.action = action;
      EventDetailActions.edit(obj);
    } else {
      alert('Input cannot be an empty string');
    }
  },

  // Toggles whether a sheep is participating or not in an event
  _attend: function () {
    EventDetailActions.attend(this.state.event, AppStore.getCurrentUser(), !this.state.isAttendee);
  },

  _onAttend: function() {
    // console.log(EventDetailStore.isAttendee());
    this.setState({isAttendee: EventDetailStore.isAttendee()});
  },

  // Updates the current event properties on the page
  _onEdit: function(){
    this.setState({event: EventDetailStore.getCurrentEvent()});

    // If the event's end time is updated and is earlier than now,
    // redirect to the events list.
    // this.state.event.end will be a string of the date in ISO format
    if (new Date() > new Date(this.state.event.end)) {
      this.transitionTo('events');
    }
  },

  _onAcquireSheep: function() {
    var participatingSheep = AttendeeStore.getAllSheep();
    var currentUserId = AppStore.getCurrentUser().id;
    var currentUserAttendee = false;
    for (var i = 0; i < participatingSheep.length; i++) {
      if (participatingSheep[i]['id'] === currentUserId) {
        currentUserAttendee = true;
        break;
      }
    }
    this.setState({isAttendee: currentUserAttendee});
  },

  isResponseValid: function(input){
    return input.trim() !== '';
  },

  // Set the event end time to now so that it is no longer considered an active event
  _endEvent: function() {
    var obj = this.state.event;
    obj.end = new Date().toISOString();
    EventDetailActions.edit(obj);
  }

});

module.exports = EventDetail;
<|endoftext|>