<commit_before>import _ from 'lodash';
import React, {Component, PropTypes} from 'react';
import { routeActions } from '../../actions/routes'
import { connect } from 'react-redux';
import {viewCompanySelector} from '../../selectors/companies';
import {authable} from '../../components/authmanager';
import {companiesActions} from '../../actions/companies';
import {personsActions} from '../../actions/persons';
import {missionsActions} from '../../actions/missions';
import {Header, HeaderLeft, HeaderRight, GoBack, Title, AvatarView, TextLabel, Labels, MarkdownText} from '../../components/widgets';
import {Edit as EditPerson, Delete as DeletePerson, Preview as PersonPreview} from '../../components/person/widgets';
import TagsField from '../../containers/tags';
import Notes from '../../containers/notes';
import {Edit as EditMission, Preview as MissionPreview, Closed as ClosedMission} from '../../components/mission/widgets';
import {Edit, Preferred, Delete} from '../../components/company/widgets';
import {Content} from '../../components/layout';
import tagsForm from '../../forms/tags';
import sitemap from '../../routes';

class CompanyView extends Component {
  state = {};

  componentWillMount() {
    const {dispatch, company} = this.props;
    if(!company) dispatch(routeActions.replace(sitemap.company.list));

    this.tagsField = tagsForm({tags: company.get('tags')}).field('tags');
    this.unsubscribeTagsField = this.tagsField.onValue( state => {
      if(state.hasBeenModified) dispatch(companiesActions.updateTags(company.toJS(), state.value));
    });

    dispatch(personsActions.load());
    dispatch(missionsActions.load());
  }

  componentWillUnmount(){
    if(this.unsubscribeTagsField) this.unsubscribeTagsField();
  }

  goBack = () => {
    this.props.dispatch(routeActions.goBack());
  }

  // handleClickTag = (tag) => {
  //   this.props.dispatch(routeActions.push(sitemap.company.list, {filter: `#${tag} `}));
  // }

  render(){
    const {company, persons, workers, missions, isLoading} = this.props;
    if( !company || !persons) return false;
    return (
      <Content>

          <HeaderLeft>
            <GoBack goBack={this.goBack} isLoading={isLoading}/>
            <AvatarView style={{paddingRight: '10px'}} obj={company}/>
            <Title title={company.get('name')}/>
            <Preferred active={true} company={company}/>
          </HeaderLeft>

          <HeaderRight>
            <AddMission company={company}/>
            <AddPerson company={company}/>
            <Edit company={company}/>
            <Delete workers={workers} company={company} postAction={this.goBack}/>
          </HeaderRight>

        <Card
          company={company}
          missions={missions}
          tagsField={this.tagsField}
          persons={persons}
          workers={workers}
        />
      </Content>
    )
  }
}

CompanyView.propTypes = {
  company: PropTypes.object,
  persons: PropTypes.object,
  workers: PropTypes.object,
  missions: PropTypes.object,
  dispatch: PropTypes.func.isRequired,
}

const Card = ({company, persons, workers, missions, tagsField}) =>  {
  const styles={
    container:{
      marginTop: '3rem',
    },
  }

  const editTags = () => {
    return (

        <TagsField field={tagsField}/>

    )
  }

  return (

        <TextLabel label="Type" value={company.get('type')}/>

        <TextLabel url={company.get('website')} label="website" value={company.get('website')}/>

        <TextLabel label="Street" value={company.getIn(['address', 'street'])}/>

        <TextLabel label="Zip Code" value={company.getIn(['address', 'zipcode'])}/>

        <TextLabel label="City" value={company.getIn(['address', 'city'])}/>

        <TextLabel label="Country" value={company.getIn(['address', 'country'])}/>

        <Persons
          label="Contacts"
          persons={workers}
          company={company}/>

        <Missions
          label="Missions"
          persons={persons}
          company={company}
          missions={missions}/>

        {editTags()}

      <Notes entity={company}/>

  )
}

Card.PropTypes = {
  company: PropTypes.object.isRequired,
  persons: PropTypes.object.isRequired,
  workers: PropTypes.object.isRequired,
  missions: PropTypes.object.isRequired,
  tagsField: PropTypes.object.isRequired,
}

const Missions = ({label, missions, company, persons}) => {
  if(!missions || !missions.size) return
;
  const styles={
    container:{
      //marginBottom: '50px',
      marginLeft: 'auto',
      marginRight: 'auto',
    },
    item:{
      height: '80px',
    }
  };

  const data = missions.sort( (a,b) => b.get('startDate') > a.get('startDate') ).map(mission =>{
      const workers = persons.filter(person => mission.get('workerIds').indexOf(person.get('_id')) !== -1);
      return (

          <MissionPreview
            mission={mission}
            manager={persons.get(mission.get('managerId'))}
            workers={workers}
            company={company}>
            <EditMission mission={mission}/>
            <ClosedMission mission={mission}/>
          </MissionPreview>

        )
      }).toSetSeq();

  return (
    <fieldset className="form-group">
       {label}

        {data}

    </fieldset>
  )
}

Missions.propTypes = {
  label: PropTypes.string.isRequired,
  company: PropTypes.object.isRequired,
  persons: PropTypes.object.isRequired,
  missions: PropTypes.object.isRequired,
}

const Persons = ({label, company, persons}) => {
 const styles={
    container:{
      //marginBottom: '50px',
      marginLeft: 'auto',
      marginRight: 'auto',
    },
    item:{
      height: '80px',
    }
  };

    const data = _.chain(persons.toArray())
    .sortBy(person => person.get('name'))
    .map( person => {
      return (

          <PersonPreview person={person}>
            <LeaveCompany company={company} person={person}/>
            <EditPerson person={person}/>
            <DeletePerson person={person}/>
          </PersonPreview>

        )
      }).value()

  if(!data.length) return
;

  return (
    <fieldset className="form-group">
       {label}

        {data}

    </fieldset>
  )
}

Persons.propTypes = {
  label: PropTypes.string.isRequired,
  company: PropTypes.object.isRequired,
  persons: PropTypes.object.isRequired,
}

export const LeaveCompany = authable(({company, person}, {authManager, dispatch}) => {
  const handleChange = (e) => {
    e.preventDefault();
    const answer = confirm(`Can you confirm you want to fire "${person.get('name')}"`);
    if(answer){
      dispatch(companiesActions.leave(company, person));
    }
  }

  if(authManager.company.isAuthorized('leave')){
    return (

    )
  }else{
    return
  }
})

LeaveCompany.propTypes = {
  company: PropTypes.object.isRequired,
  person: PropTypes.object.isRequired,
}

export const AddPerson =authable(({company}, {authManager, dispatch}) => {
  const handleChange = (e) => {
    e.preventDefault();
    dispatch(routeActions.push(sitemap.person.new, {companyId: company.get('_id')}));
  }

  if(authManager.isAuthorized(sitemap.person.new)){
    return (

    )
  }else{
    return
  }
})

AddPerson.propTypes = {
  company: PropTypes.object.isRequired,
}

export const AddMission =authable(({company}, {authManager, dispatch}) => {
  const handleChange = (e) => {
    e.preventDefault();
    dispatch(routeActions.push(sitemap.mission.new, {clientId: company.get('_id')}));
  }

  if(authManager.isAuthorized(sitemap.mission.new)){
    return (

    )
  }else{
    return
  }
})

AddMission.propTypes = {
  company: PropTypes.object.isRequired,
}

export default connect(viewCompanySelector)(CompanyView);
<commit_msg>add FadeIn to company/view page.<commit_after>import _ from 'lodash';
import React, {Component, PropTypes} from 'react';
import { routeActions } from '../../actions/routes'
import { connect } from 'react-redux';
import {viewCompanySelector} from '../../selectors/companies';
import {authable} from '../../components/authmanager';
import {companiesActions} from '../../actions/companies';
import {personsActions} from '../../actions/persons';
import {missionsActions} from '../../actions/missions';
import {Header, HeaderLeft, HeaderRight, GoBack, Title, AvatarView, TextLabel, Labels, MarkdownText, FadeIn} from '../../components/widgets';
import {Edit as EditPerson, Delete as DeletePerson, Preview as PersonPreview} from '../../components/person/widgets';
import TagsField from '../../containers/tags';
import Notes from '../../containers/notes';
import {Edit as EditMission, Preview as MissionPreview, Closed as ClosedMission} from '../../components/mission/widgets';
import {Edit, Preferred, Delete} from '../../components/company/widgets';
import {Content} from '../../components/layout';
import tagsForm from '../../forms/tags';
import sitemap from '../../routes';

class CompanyView extends Component {
  state = {};

  componentWillMount() {
    const {dispatch, company} = this.props;
    if(!company) dispatch(routeActions.replace(sitemap.company.list));

    this.tagsField = tagsForm({tags: company.get('tags')}).field('tags');
    this.unsubscribeTagsField = this.tagsField.onValue( state => {
      if(state.hasBeenModified) dispatch(companiesActions.updateTags(company.toJS(), state.value));
    });

    dispatch(personsActions.load());
    dispatch(missionsActions.load());
  }

  componentWillUnmount(){
    if(this.unsubscribeTagsField) this.unsubscribeTagsField();
  }

  goBack = () => {
    this.props.dispatch(routeActions.goBack());
  }

  // handleClickTag = (tag) => {
  //   this.props.dispatch(routeActions.push(sitemap.company.list, {filter: `#${tag} `}));
  // }

  render(){
    const {company, persons, workers, missions, isLoading} = this.props;
    if( !company || !persons) return false;
    return (
      <Content>

          <HeaderLeft>
            <GoBack goBack={this.goBack} isLoading={isLoading}/>
            <AvatarView style={{paddingRight: '10px'}} obj={company}/>
            <Title title={company.get('name')}/>
            <Preferred active={true} company={company}/>
          </HeaderLeft>

          <HeaderRight>
            <AddMission company={company}/>
            <AddPerson company={company}/>
            <Edit company={company}/>
            <Delete workers={workers} company={company} postAction={this.goBack}/>
          </HeaderRight>

        <FadeIn>
          <Card
            company={company}
            missions={missions}
            tagsField={this.tagsField}
            persons={persons}
            workers={workers}
          />
        </FadeIn>
      </Content>
    )
  }
}

CompanyView.propTypes = {
  company: PropTypes.object,
  persons: PropTypes.object,
  workers: PropTypes.object,
  missions: PropTypes.object,
  dispatch: PropTypes.func.isRequired,
}

const Card = ({company, persons, workers, missions, tagsField}) =>  {
  const styles={
    container:{
      marginTop: '3rem',
    },
  }

  const editTags = () => {
    return (

        <TagsField field={tagsField}/>

    )
  }

  return (

        <TextLabel label="Type" value={company.get('type')}/>

        <TextLabel url={company.get('website')} label="website" value={company.get('website')}/>

        <TextLabel label="Street" value={company.getIn(['address', 'street'])}/>

        <TextLabel label="Zip Code" value={company.getIn(['address', 'zipcode'])}/>

        <TextLabel label="City" value={company.getIn(['address', 'city'])}/>

        <TextLabel label="Country" value={company.getIn(['address', 'country'])}/>

        <Persons
          label="Contacts"
          persons={workers}
          company={company}/>

        <Missions
          label="Missions"
          persons={persons}
          company={company}
          missions={missions}/>

        {editTags()}

      <Notes entity={company}/>

  )
}

Card.PropTypes = {
  company: PropTypes.object.isRequired,
  persons: PropTypes.object.isRequired,
  workers: PropTypes.object.isRequired,
  missions: PropTypes.object.isRequired,
  tagsField: PropTypes.object.isRequired,
}

const Missions = ({label, missions, company, persons}) => {
  if(!missions || !missions.size) return
;
  const styles={
    container:{
      //marginBottom: '50px',
      marginLeft: 'auto',
      marginRight: 'auto',
    },
    item:{
      height: '80px',
    }
  };

  const data = missions.sort( (a,b) => b.get('startDate') > a.get('startDate') ).map(mission =>{
      const workers = persons.filter(person => mission.get('workerIds').indexOf(person.get('_id')) !== -1);
      return (

          <MissionPreview
            mission={mission}
            manager={persons.get(mission.get('managerId'))}
            workers={workers}
            company={company}>
            <EditMission mission={mission}/>
            <ClosedMission mission={mission}/>
          </MissionPreview>

        )
      }).toSetSeq();

  return (
    <fieldset className="form-group">
       {label}

        {data}

    </fieldset>
  )
}

Missions.propTypes = {
  label: PropTypes.string.isRequired,
  company: PropTypes.object.isRequired,
  persons: PropTypes.object.isRequired,
  missions: PropTypes.object.isRequired,
}

const Persons = ({label, company, persons}) => {
 const styles={
    container:{
      //marginBottom: '50px',
      marginLeft: 'auto',
      marginRight: 'auto',
    },
    item:{
      height: '80px',
    }
  };

    const data = _.chain(persons.toArray())
    .sortBy(person => person.get('name'))
    .map( person => {
      return (

          <PersonPreview person={person}>
            <LeaveCompany company={company} person={person}/>
            <EditPerson person={person}/>
            <DeletePerson person={person}/>
          </PersonPreview>

        )
      }).value()

  if(!data.length) return
;

  return (
    <fieldset className="form-group">
       {label}

        {data}

    </fieldset>
  )
}

Persons.propTypes = {
  label: PropTypes.string.isRequired,
  company: PropTypes.object.isRequired,
  persons: PropTypes.object.isRequired,
}

export const LeaveCompany = authable(({company, person}, {authManager, dispatch}) => {
  const handleChange = (e) => {
    e.preventDefault();
    const answer = confirm(`Can you confirm you want to fire "${person.get('name')}"`);
    if(answer){
      dispatch(companiesActions.leave(company, person));
    }
  }

  if(authManager.company.isAuthorized('leave')){
    return (

    )
  }else{
    return
  }
})

LeaveCompany.propTypes = {
  company: PropTypes.object.isRequired,
  person: PropTypes.object.isRequired,
}

export const AddPerson =authable(({company}, {authManager, dispatch}) => {
  const handleChange = (e) => {
    e.preventDefault();
    dispatch(routeActions.push(sitemap.person.new, {companyId: company.get('_id')}));
  }

  if(authManager.isAuthorized(sitemap.person.new)){
    return (

    )
  }else{
    return
  }
})

AddPerson.propTypes = {
  company: PropTypes.object.isRequired,
}

export const AddMission =authable(({company}, {authManager, dispatch}) => {
  const handleChange = (e) => {
    e.preventDefault();
    dispatch(routeActions.push(sitemap.mission.new, {clientId: company.get('_id')}));
  }

  if(authManager.isAuthorized(sitemap.mission.new)){
    return (

    )
  }else{
    return
  }
})

AddMission.propTypes = {
  company: PropTypes.object.isRequired,
}

export default connect(viewCompanySelector)(CompanyView);
<|endoftext|>


<commit_before>import _ from 'lodash';
import moment from 'moment';
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux'
import DateTimePicker from 'react-widgets/lib/DateTimePicker';
import {Content} from '../../components/layout';
import {Header, HeaderLeft, HeaderRight, GoBack, Title, AvatarView, TextLabel, Labels, MarkdownText} from '../../components/widgets';
import {Edit as EditPerson, Preview as PersonPreview} from '../../components/person/widgets';
import {Edit as EditCompany, Preview as CompanyPreview} from '../../components/company/widgets';
import {Edit, Delete, OpenClose} from '../../components/mission/widgets';
import {missionsActions} from '../../actions/missions';
import {personsActions} from '../../actions/persons';
import {companiesActions} from '../../actions/companies';
import {replace, goBack} from '../../actions/routes'
import sitemap from '../../routes';
import Notes from '../../containers/notes';
import {viewMissionSelector} from '../../selectors/missions'

class ViewMission extends Component {

  goBack = () => {
    this.props.dispatch(goBack())
  }

  componentWillMount() {
    const {dispatch, mission} = this.props
    if(!mission) { dispatch(replace(sitemap.mission.list)) ; return }

    dispatch(missionsActions.load({ids: [mission.get('_id')]}))
    dispatch(companiesActions.load({ids: [mission.get('clientId')]}))
    dispatch(personsActions.load({
      ids: [mission.get('managerId'), ...mission.get('workerIds').toArray()]
    }))
  }

  render(){
    const {mission, client, manager, workers, isLoading} = this.props
    if (!mission) return false

    return (
      <Content>

          <HeaderLeft>
            <GoBack goBack={this.goBack} isLoading={isLoading}/>
            <AvatarView obj={client}/>
            <Title title={mission.get('name')}/>
          </HeaderLeft>
          <HeaderRight>
            <Edit mission={mission}/>
            <OpenClose mission={mission}/>
            <Delete mission={mission} postAction={this.goBack}/>
          </HeaderRight>

        <Card
          mission={mission}
          client={client}
          manager={manager}
          workers={workers}  />
      </Content>
    )
  }
}

const Card = ({mission, client, manager, workers}) =>  {

  const styles={
    container:{
      marginTop: '3rem',
    },
  }

  return (

        <Date label="Start Date" date={mission.get("startDate")}/>

        <Date label="End Date" date={mission.get("endDate")}/>

        <Client label="Client" workers={workers} client={client}/>

        <Manager label="Manager" manager={manager}/>

        <Workers
          label="Workers"
          workers={workers}
          mission={mission}/>

        <Notes entity={mission}/>

  )
}

const Date = ({label, date}) => {
  return (
    <fieldset className="form-group">
       {label}
      <DateTimePicker
        defaultValue={date}
        time={false}
        readOnly={true}
        />
    </fieldset>
  )
}

const Client = ({label, client, workers}) => {
 const styles={
    container:{
      //marginBottom: '50px',
      marginLeft: 'auto',
      marginRight: 'auto',
    },
    item:{
      height: '80px',
    }
  };

  if(!client)return
;
  return (
    <fieldset className="form-group">
       {label}

          <CompanyPreview workers={workers} company={client}>
            <EditCompany company={client}/>
          </CompanyPreview>

    </fieldset>
  )
}

const Manager = ({label, manager}) => {
 const styles={
    container:{
      //marginBottom: '50px',
      marginLeft: 'auto',
      marginRight: 'auto',
    },
    item:{
      height: '80px',
    }
  };

  if(!manager)return
;
  return (
    <fieldset className="form-group">
       {label}

          <PersonPreview person={manager}>
            <EditPerson person={manager}/>
          </PersonPreview>

    </fieldset>
  )

}

const Workers = ({label, workers, mission}) => {
 const styles={
    container:{
      //marginBottom: '50px',
      marginLeft: 'auto',
      marginRight: 'auto',
    },
    item:{
      height: '80px',
    }
  };

  const className = workers.size > 1 ? "col-md-6 tm list-item" : "col-md-12 tm list-item";
  const data = _.chain(workers.toArray())
    .sortBy( person => person.get('name') )
    .map( person => {
      return (

          <PersonPreview person={person}>
            <EditPerson person={person}/>
          </PersonPreview>

        )
      })
    .value();

  if(!data.length) return
;

  return (
    <fieldset className="form-group">
       {label}

        {data}

    </fieldset>
  )
}

export default connect(viewMissionSelector)(ViewMission)
<commit_msg>add propTypes in person/view containers.<commit_after>import _ from 'lodash';
import moment from 'moment';
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux'
import DateTimePicker from 'react-widgets/lib/DateTimePicker';
import {Content} from '../../components/layout';
import {Header, HeaderLeft, HeaderRight, GoBack, Title, AvatarView, TextLabel, Labels, MarkdownText} from '../../components/widgets';
import {Edit as EditPerson, Preview as PersonPreview} from '../../components/person/widgets';
import {Edit as EditCompany, Preview as CompanyPreview} from '../../components/company/widgets';
import {Edit, Delete, OpenClose} from '../../components/mission/widgets';
import {missionsActions} from '../../actions/missions';
import {personsActions} from '../../actions/persons';
import {companiesActions} from '../../actions/companies';
import {replace, goBack} from '../../actions/routes'
import sitemap from '../../routes';
import Notes from '../../containers/notes';
import {viewMissionSelector} from '../../selectors/missions'

class ViewMission extends Component {

  goBack = () => {
    this.props.dispatch(goBack())
  }

  componentWillMount() {
    const {dispatch, mission} = this.props
    if(!mission) { dispatch(replace(sitemap.mission.list)) ; return }

    dispatch(missionsActions.load({ids: [mission.get('_id')]}))
    dispatch(companiesActions.load({ids: [mission.get('clientId')]}))
    dispatch(personsActions.load({
      ids: [mission.get('managerId'), ...mission.get('workerIds').toArray()]
    }))
  }

  render(){
    const {mission, client, manager, workers, isLoading} = this.props
    if (!mission) return false

    return (
      <Content>

          <HeaderLeft>
            <GoBack goBack={this.goBack} isLoading={isLoading}/>
            <AvatarView obj={client}/>
            <Title title={mission.get('name')}/>
          </HeaderLeft>
          <HeaderRight>
            <Edit mission={mission}/>
            <OpenClose mission={mission}/>
            <Delete mission={mission} postAction={this.goBack}/>
          </HeaderRight>

        <Card
          mission={mission}
          client={client}
          manager={manager}
          workers={workers}  />
      </Content>
    )
  }
}

ViewMission.propTypes = {
  mission:     PropTypes.object,
  client:      PropTypes.object,
  manager:     PropTypes.object,
  workers:     PropTypes.object,
  isLoading:   PropTypes.bool,
  dispatch:    PropTypes.func.isRequired

}

const Card = ({mission, client, manager, workers}) =>  {

  const styles={
    container:{
      marginTop: '3rem',
    },
  }

  return (

        <Date label="Start Date" date={mission.get("startDate")}/>

        <Date label="End Date" date={mission.get("endDate")}/>

        <Client label="Client" workers={workers} client={client}/>

        <Manager label="Manager" manager={manager}/>

        <Workers
          label="Workers"
          workers={workers}
          mission={mission}/>

        <Notes entity={mission}/>

  )
}

Card.propTypes = {
  mission:     PropTypes.object.isRequired,
  client:      PropTypes.object.isRequired,
  manager:     PropTypes.object.isRequired,
  workers:     PropTypes.object.isRequired,
}

const Date = ({label, date}) => {
  return (
    <fieldset className="form-group">
       {label}
      <DateTimePicker
        defaultValue={date}
        time={false}
        readOnly={true}
        />
    </fieldset>
  )
}

Date.propTypes = {
  label:    PropTypes.string.isRequired,
  date:     PropTypes.object
}

const Client = ({label, client, workers}) => {
 const styles={
    container:{
      //marginBottom: '50px',
      marginLeft: 'auto',
      marginRight: 'auto',
    },
    item:{
      height: '80px',
    }
  };

  if(!client)return
;
  return (
    <fieldset className="form-group">
       {label}

          <CompanyPreview workers={workers} company={client}>
            <EditCompany company={client}/>
          </CompanyPreview>

    </fieldset>
  )
}

Client.propTypes = {
  label:    PropTypes.string.isRequired,
  client:   PropTypes.object.isRequired,
  workers:  PropTypes.object.isRequired
}

const Manager = ({label, manager}) => {
 const styles={
    container:{
      //marginBottom: '50px',
      marginLeft: 'auto',
      marginRight: 'auto',
    },
    item:{
      height: '80px',
    }
  };

  if(!manager)return
;
  return (
    <fieldset className="form-group">
       {label}

          <PersonPreview person={manager}>
            <EditPerson person={manager}/>
          </PersonPreview>

    </fieldset>
  )

}

Manager.propTypes = {
  label:    PropTypes.string.isRequired,
  manager:  PropTypes.object.isRequired
}

const Workers = ({label, workers, mission}) => {
 const styles={
    container:{
      //marginBottom: '50px',
      marginLeft: 'auto',
      marginRight: 'auto',
    },
    item:{
      height: '80px',
    }
  };

  const className = workers.size > 1 ? "col-md-6 tm list-item" : "col-md-12 tm list-item";
  const data = _.chain(workers.toArray())
    .sortBy( person => person.get('name') )
    .map( person => {
      return (

          <PersonPreview person={person}>
            <EditPerson person={person}/>
          </PersonPreview>

        )
      })
    .value();

  if(!data.length) return
;

  return (
    <fieldset className="form-group">
       {label}

        {data}

    </fieldset>
  )
}

Worker.propTypes = {
  label:    PropTypes.string.isRequired,
  mission:  PropTypes.object.isRequired,
  workers:  PropTypes.object.isRequired
}

export default connect(viewMissionSelector)(ViewMission)
<|endoftext|>


<commit_before>(function($) {
// CMS.$ will be passed for $
$(document).ready(function () {

	CKEDITOR.plugins.add('cmsplugins', {

		// Register the icons. They must match command names.
		icons: 'cmsplugins',

		// The plugin initialization logic goes inside this method.
		init: function(editor) {
			var that = this;

			this.options = CMS.CKEditor.options.settings;
			this.editor = editor;

			// don't do anything if there are no plugins defined
			if(this.options === undefined || this.options.plugins === undefined) return false;

			this.setupDialog();

			// add the button
			this.editor.ui.add('cmsplugins', CKEDITOR.UI_PANELBUTTON, {
				'toolbar': 'cms,0',
				'label': this.options.lang.toolbar,
				'title': this.options.lang.toolbar,
				'className' : 'cke_panelbutton__cmsplugins',
				'modes': { wysiwyg:1 },
				'editorFocus': 1,

				'panel': {
					'css': [CKEDITOR.skin.getPath('editor')].concat(that.editor.config.contentsCss),
					'attributes': { role: 'cmsplugins', 'aria-label': this.options.lang.aria }
				},

				// this is called when creating the dropdown list
				'onBlock': function (panel, block) {
					block.element.setHtml(that.editor.plugins.cmsplugins.setupDropdown());

					var anchors = $(block.element.$).find('.cke_panel_listItem a');
						anchors.bind('click', function (e) {
							e.preventDefault();

							that.addPlugin($(this), panel);
						});
				}
			});

			// handle edit event via context menu
			if(this.editor.contextMenu) {
				this.setupContextMenu();
				this.editor.addCommand('cmspluginsEdit', {
					exec: function () {
						var selection = that.editor.getSelection();
						var element = selection.getSelectedElement() || selection.getCommonAncestor().getAscendant('a', true);
						that.editPlugin(element);
					}
				});
			}

			// handle edit event on double click
			// if event is a jQuery event (touchend), than we mutate
			// event a bit so we make the payload similar to what ckeditor.event produces
			var handleEdit = function(event) {
				if (event.type === 'touchend') {
					var element = event.currentTarget;
					event.data = event.data ||  {};
				} else {
					var selection = that.editor.getSelection();
					var element = selection.getSelectedElement() || selection.getCommonAncestor().getAscendant('a', true);
				}
				if(element && element.getAttribute('id').indexOf('plugin_obj_') === 0) {
					event.data.dialog = '';
					that.editPlugin(element);
				}
			}
			this.editor.on('doubleclick', handleEdit);
			this.editor.on('instanceReady', function () {
				CMS.$('img', CMS.$('iframe.cke_wysiwyg_frame')[0].contentWindow.document.documentElement).on('touchend', handleEdit);
			});

			// setup CKEDITOR.htmlDataProcessor
			this.setupDataProcessor();
		},

		setupDialog: function () {
			var that = this;
			var definition = function () { return {
				'title': '',
				'minWidth': 600,
				'minHeight': 200,
				'contents': [{
					'elements': [{ type: 'html', html: '' }]
				}],
				'onOk': function () {
					var iframe = $(CKEDITOR.dialog.getCurrent().parts.contents.$).find('iframe').contents();
						iframe.find('form').submit();

					// catch the reload event and reattach
					var reload = CMS.API.Helpers.reloadBrowser;

					CMS.API.Helpers.reloadBrowser = function() {
						CKEDITOR.dialog.getCurrent().hide();

						that.insertPlugin(CMS.API.Helpers.dataBridge);

						CMS.API.Helpers.reloadBrowser = reload;
						return false;
					};
					return false;
				}
			}};

			// set default definition and open dialog
			CKEDITOR.dialog.add('cmspluginsDialog', definition);
		},

		setupDropdown: function () {
			var tpl = '
';

			// loop through the groups
			$.each(this.options.plugins, function (i, group) {
				// add template
				tpl += '
' + group.group + '
';
				tpl += '
';
				// loop through the plugins
				$.each(group.items, function (ii, item) {
					tpl += '
' + item.title + '
';
				});
				tpl += '
';
			});

			tpl += '
';

			return tpl;
		},

		setupContextMenu: function () {
			this.editor.addMenuGroup('cmspluginsGroup');
			this.editor.addMenuItem('cmspluginsItem', {
				label: this.options.lang.edit,
				icon: this.path + 'icons/cmsplugins.png',
				command: 'cmspluginsEdit',
				group: 'cmspluginsGroup'
			});

			this.editor.removeMenuItem('image');

			this.editor.contextMenu.addListener(function(element) {
				if (element.$.id.indexOf('plugin_obj_') === 0) {
					return { cmspluginsItem: CKEDITOR.TRISTATE_OFF };
				}
			});
		},

		editPlugin: function (element) {
			var id = element.getAttribute('id').replace('plugin_obj_', '');
			this.editor.openDialog('cmspluginsDialog');

			// now tweak in dynamic stuff
			var dialog = CKEDITOR.dialog.getCurrent();
			$(dialog.getElement().$).addClass('cms-ckeditor-dialog');
			$(dialog.parts.title.$).text(this.options.lang.edit);
			$(dialog.parts.contents.$).find('iframe').attr('src', '../' + id + '/?_popup=1&no_preview')
				.bind('load', function () {
					$(this).contents().find('.submit-row').hide().end()
						.find('#container').css('min-width', 0).css('padding', 0);
				});
		},

		addPlugin: function (item, panel) {
			var that = this;

			// hide the panel
			panel.hide();

			// lets figure out how to write something to the editor
			this.editor.focus();
			this.editor.fire('saveSnapshot');

			// gather data
			var data = {
				'placeholder_id': this.options.placeholder_id,
				'plugin_type': item.attr('rel'),
				'plugin_parent': this.options.plugin_id,
				'plugin_language':  this.options.plugin_language
			};

			// lets do some ajax
			$.ajax({
				'type': 'POST',
				'url': this.options.add_plugin_url,
				'data': data,
				'success': function (data) {
					// cancel if error is returned
					if(data === 'error') return false;

					// trigger dialog
					that.addPluginDialog(item, data);
				},
				'error': function (error) {
					alert('There was an error creating the plugin.');
				}
			});
		},

		addPluginDialog: function (item, data) {
			// open the dialog
			var selected_text = this.editor.getSelection().getSelectedText();
			this.editor.openDialog('cmspluginsDialog');

			// now tweak in dynamic stuff
			var dialog = CKEDITOR.dialog.getCurrent();
			$(dialog.getElement().$).addClass('cms-ckeditor-dialog');
			$(dialog.parts.title.$).text(this.options.lang.add);
			$(dialog.parts.contents.$).find('iframe').attr('src', data.url)
				.bind('load', function () {
					$(this).contents().find('.submit-row').hide().end()
					.find('#container').css('min-width', 0).css('padding', 0)
					.find('#id_name').val(selected_text);
				});
		},

		// on ajax receivement from server, build  or  tag dependig in the plugin type
		insertPlugin: function (data) {
			var element, attrs = { id: 'plugin_obj_' + data.plugin_id };
			if (data.plugin_type === this.options.lang.link) {
				element = new CKEDITOR.dom.element('a', this.editor.document);
				$.extend(attrs, {
					'href': '#',
					'data-cmsplugin_title': data.plugin_desc,
					'data-cmsplugin_alt': data.plugin_type,
					'data-cmsplugin_src': data.plugin_icon
				});
				element.setText(data.plugin_name);
			} else {
				element = new CKEDITOR.dom.element('img', this.editor.document);
				$.extend(attrs, {
					'title': data.plugin_desc,
					'alt': data.plugin_type,
					'src': data.plugin_icon
				});
			}
			element.setAttributes(attrs);
			this.editor.insertElement(element);
		},

		setupDataProcessor: function () {
			var link_name = this.options.lang.link;
			var link_pattern = new RegExp("^"+ link_name +"\\s-\\s(.+)$");

			this.editor.dataProcessor.dataFilter.addRules({
				elements: {
					// on load from server replace  tag by  in order to display a real link
					img: function(element) {
						var new_element, matches;
						if (element.attributes.id && element.attributes.alt
						  && element.attributes.alt === link_name
						  && element.attributes.id.indexOf('plugin_obj_') === 0) {
							matches = link_pattern.exec(element.attributes.title);
							new_element = new CKEDITOR.htmlParser.element('a', {
								'href': '#',
								'id': element.attributes.id,
								'data-cmsplugin_title': element.attributes.title,
								'data-cmsplugin_src': element.attributes.src,
								'data-cmsplugin_alt': element.attributes.alt
							});
							if (matches) {
								new_element.add(new CKEDITOR.htmlParser.text(matches[1]));
							}
							return new_element;
						}
					}
				}
			});

			this.editor.dataProcessor.htmlFilter.addRules({
				elements: {
					// on post to server replace  tag by  in order keep plugin format consistent
					a: function(element) {
						var new_element;
						if (element.attributes.id && element.attributes.id.indexOf('plugin_obj_') === 0) {
							new_element = new CKEDITOR.htmlParser.element('img', {
								id: element.attributes.id,
								src: element.attributes['data-cmsplugin_src'],
								title: element.attributes['data-cmsplugin_title'],
								alt: element.attributes['data-cmsplugin_alt']
							});
							return new_element;
						}
					}
				}
			});
		}

	});

});
})(CMS.$);
<commit_msg>use more restrictive selectors for touch edit handler<commit_after>(function($) {
// CMS.$ will be passed for $
$(document).ready(function () {

	CKEDITOR.plugins.add('cmsplugins', {

		// Register the icons. They must match command names.
		icons: 'cmsplugins',

		// The plugin initialization logic goes inside this method.
		init: function(editor) {
			var that = this;

			this.options = CMS.CKEditor.options.settings;
			this.editor = editor;

			// don't do anything if there are no plugins defined
			if(this.options === undefined || this.options.plugins === undefined) return false;

			this.setupDialog();

			// add the button
			this.editor.ui.add('cmsplugins', CKEDITOR.UI_PANELBUTTON, {
				'toolbar': 'cms,0',
				'label': this.options.lang.toolbar,
				'title': this.options.lang.toolbar,
				'className' : 'cke_panelbutton__cmsplugins',
				'modes': { wysiwyg:1 },
				'editorFocus': 1,

				'panel': {
					'css': [CKEDITOR.skin.getPath('editor')].concat(that.editor.config.contentsCss),
					'attributes': { role: 'cmsplugins', 'aria-label': this.options.lang.aria }
				},

				// this is called when creating the dropdown list
				'onBlock': function (panel, block) {
					block.element.setHtml(that.editor.plugins.cmsplugins.setupDropdown());

					var anchors = $(block.element.$).find('.cke_panel_listItem a');
						anchors.bind('click', function (e) {
							e.preventDefault();

							that.addPlugin($(this), panel);
						});
				}
			});

			// handle edit event via context menu
			if(this.editor.contextMenu) {
				this.setupContextMenu();
				this.editor.addCommand('cmspluginsEdit', {
					exec: function () {
						var selection = that.editor.getSelection();
						var element = selection.getSelectedElement() || selection.getCommonAncestor().getAscendant('a', true);
						that.editPlugin(element);
					}
				});
			}

			// handle edit event on double click
			// if event is a jQuery event (touchend), than we mutate
			// event a bit so we make the payload similar to what ckeditor.event produces
			var handleEdit = function(event) {
				if (event.type === 'touchend') {
					var element = event.currentTarget;
					event.data = event.data ||  {};
				} else {
					var selection = that.editor.getSelection();
					var element = selection.getSelectedElement() || selection.getCommonAncestor().getAscendant('a', true);
				}
				if(element && element.getAttribute('id').indexOf('plugin_obj_') === 0) {
					event.data.dialog = '';
					that.editPlugin(element);
				}
			}
			this.editor.on('doubleclick', handleEdit);
			this.editor.on('instanceReady', function () {
				CMS.$('img[id*="plugin_obj_"]', CMS.$('iframe.cke_wysiwyg_frame')[0].contentWindow.document.documentElement).on('touchend', handleEdit);
			});

			// setup CKEDITOR.htmlDataProcessor
			this.setupDataProcessor();
		},

		setupDialog: function () {
			var that = this;
			var definition = function () { return {
				'title': '',
				'minWidth': 600,
				'minHeight': 200,
				'contents': [{
					'elements': [{ type: 'html', html: '' }]
				}],
				'onOk': function () {
					var iframe = $(CKEDITOR.dialog.getCurrent().parts.contents.$).find('iframe').contents();
						iframe.find('form').submit();

					// catch the reload event and reattach
					var reload = CMS.API.Helpers.reloadBrowser;

					CMS.API.Helpers.reloadBrowser = function() {
						CKEDITOR.dialog.getCurrent().hide();

						that.insertPlugin(CMS.API.Helpers.dataBridge);

						CMS.API.Helpers.reloadBrowser = reload;
						return false;
					};
					return false;
				}
			}};

			// set default definition and open dialog
			CKEDITOR.dialog.add('cmspluginsDialog', definition);
		},

		setupDropdown: function () {
			var tpl = '
';

			// loop through the groups
			$.each(this.options.plugins, function (i, group) {
				// add template
				tpl += '
' + group.group + '
';
				tpl += '
';
				// loop through the plugins
				$.each(group.items, function (ii, item) {
					tpl += '
' + item.title + '
';
				});
				tpl += '
';
			});

			tpl += '
';

			return tpl;
		},

		setupContextMenu: function () {
			this.editor.addMenuGroup('cmspluginsGroup');
			this.editor.addMenuItem('cmspluginsItem', {
				label: this.options.lang.edit,
				icon: this.path + 'icons/cmsplugins.png',
				command: 'cmspluginsEdit',
				group: 'cmspluginsGroup'
			});

			this.editor.removeMenuItem('image');

			this.editor.contextMenu.addListener(function(element) {
				if (element.$.id.indexOf('plugin_obj_') === 0) {
					return { cmspluginsItem: CKEDITOR.TRISTATE_OFF };
				}
			});
		},

		editPlugin: function (element) {
			var id = element.getAttribute('id').replace('plugin_obj_', '');
			this.editor.openDialog('cmspluginsDialog');

			// now tweak in dynamic stuff
			var dialog = CKEDITOR.dialog.getCurrent();
			$(dialog.getElement().$).addClass('cms-ckeditor-dialog');
			$(dialog.parts.title.$).text(this.options.lang.edit);
			$(dialog.parts.contents.$).find('iframe').attr('src', '../' + id + '/?_popup=1&no_preview')
				.bind('load', function () {
					$(this).contents().find('.submit-row').hide().end()
						.find('#container').css('min-width', 0).css('padding', 0);
				});
		},

		addPlugin: function (item, panel) {
			var that = this;

			// hide the panel
			panel.hide();

			// lets figure out how to write something to the editor
			this.editor.focus();
			this.editor.fire('saveSnapshot');

			// gather data
			var data = {
				'placeholder_id': this.options.placeholder_id,
				'plugin_type': item.attr('rel'),
				'plugin_parent': this.options.plugin_id,
				'plugin_language':  this.options.plugin_language
			};

			// lets do some ajax
			$.ajax({
				'type': 'POST',
				'url': this.options.add_plugin_url,
				'data': data,
				'success': function (data) {
					// cancel if error is returned
					if(data === 'error') return false;

					// trigger dialog
					that.addPluginDialog(item, data);
				},
				'error': function (error) {
					alert('There was an error creating the plugin.');
				}
			});
		},

		addPluginDialog: function (item, data) {
			// open the dialog
			var selected_text = this.editor.getSelection().getSelectedText();
			this.editor.openDialog('cmspluginsDialog');

			// now tweak in dynamic stuff
			var dialog = CKEDITOR.dialog.getCurrent();
			$(dialog.getElement().$).addClass('cms-ckeditor-dialog');
			$(dialog.parts.title.$).text(this.options.lang.add);
			$(dialog.parts.contents.$).find('iframe').attr('src', data.url)
				.bind('load', function () {
					$(this).contents().find('.submit-row').hide().end()
					.find('#container').css('min-width', 0).css('padding', 0)
					.find('#id_name').val(selected_text);
				});
		},

		// on ajax receivement from server, build  or  tag dependig in the plugin type
		insertPlugin: function (data) {
			var element, attrs = { id: 'plugin_obj_' + data.plugin_id };
			if (data.plugin_type === this.options.lang.link) {
				element = new CKEDITOR.dom.element('a', this.editor.document);
				$.extend(attrs, {
					'href': '#',
					'data-cmsplugin_title': data.plugin_desc,
					'data-cmsplugin_alt': data.plugin_type,
					'data-cmsplugin_src': data.plugin_icon
				});
				element.setText(data.plugin_name);
			} else {
				element = new CKEDITOR.dom.element('img', this.editor.document);
				$.extend(attrs, {
					'title': data.plugin_desc,
					'alt': data.plugin_type,
					'src': data.plugin_icon
				});
			}
			element.setAttributes(attrs);
			this.editor.insertElement(element);
		},

		setupDataProcessor: function () {
			var link_name = this.options.lang.link;
			var link_pattern = new RegExp("^"+ link_name +"\\s-\\s(.+)$");

			this.editor.dataProcessor.dataFilter.addRules({
				elements: {
					// on load from server replace  tag by  in order to display a real link
					img: function(element) {
						var new_element, matches;
						if (element.attributes.id && element.attributes.alt
						  && element.attributes.alt === link_name
						  && element.attributes.id.indexOf('plugin_obj_') === 0) {
							matches = link_pattern.exec(element.attributes.title);
							new_element = new CKEDITOR.htmlParser.element('a', {
								'href': '#',
								'id': element.attributes.id,
								'data-cmsplugin_title': element.attributes.title,
								'data-cmsplugin_src': element.attributes.src,
								'data-cmsplugin_alt': element.attributes.alt
							});
							if (matches) {
								new_element.add(new CKEDITOR.htmlParser.text(matches[1]));
							}
							return new_element;
						}
					}
				}
			});

			this.editor.dataProcessor.htmlFilter.addRules({
				elements: {
					// on post to server replace  tag by  in order keep plugin format consistent
					a: function(element) {
						var new_element;
						if (element.attributes.id && element.attributes.id.indexOf('plugin_obj_') === 0) {
							new_element = new CKEDITOR.htmlParser.element('img', {
								id: element.attributes.id,
								src: element.attributes['data-cmsplugin_src'],
								title: element.attributes['data-cmsplugin_title'],
								alt: element.attributes['data-cmsplugin_alt']
							});
							return new_element;
						}
					}
				}
			});
		}

	});

});
})(CMS.$);
<|endoftext|>


<commit_before>/**
 * Creates a new pretty test runner.
 * @param {String} product the name of the "product" being tested
 * @class Runs Crucible tests, displaying the results graphically in the browser
 * window.
 * @augments Crucible.Runner
 */
Crucible.PrettyRunner = function PrettyRunner(product) {
	Crucible.Runner.call(this);
	if (product)
		this.product = product;

	for (var name in this._eventListeners) {
		this[name].add(this._eventListeners[name], this);
	}

	this._test_messages = [];

	this.draw();
};

Crucible.PrettyRunner.prototype = new Crucible.Runner();

Crucible.augment(Crucible.PrettyRunner.prototype,
	/** @lends Crucible.PrettyRunner.prototype */
{
	/**
	 * PrettyRunner status.
	 * @type String
	 */
	status: null,

	/**
	 * The product that the PrettyRunner is testing.
	 */
	product: 'the product',

	doneAdding: function done_adding_tasks_to_pretty_runner() {
		var msg;
		this._done_adding = true;

		if (!this.root) {
			Crucible.defer(Crucible.bind(this.doneAdding, this));
			return;
		}

		function start() {
			this.tallies = {pass: 0, fail: 0, error: 0};
			this.run();
		}

		msg = this.addMessage('prompt', "Crucible is ready to test " +
			this.product + ".", {'Start': Crucible.bind(start, this)});
	},

	displayMessage: function pr_display_message(message, buttons) {
		this.addMessage('prompt', message, buttons);
	},

	_getMessage: function _get_message_for_test(test, remove) {
		var i, len, entry, message;
		for (i = 0, len = this._test_messages.length; i < len; ++i) {
			entry = this._test_messages[i];
			if (entry && entry.test == test) {
				message = entry.message;
				if (remove)
					this._test_messages.splice(i, 1);
				return message;
			}
		}

		return null;
	},

	_setMessage: function _set_message_for_test(test, message) {
		var old = this._getMessage(test, true);
		if (old && old.remove)
			old.remove();
		this._test_messages.push({test: test, message: message});
		return message;
	},

	_eventListeners: {
		testStarted: function pr_test_started(test) {
			var msg = this.addMessage('running', 'Testing “' + test.name +
				'”…');
			this._setMessage(test, msg);
		},

		testPassed: function pr_test_succeeded(test) {
			var message = this._getMessage(test);
			message.setType('pass');
			message.setMessage(test.name);
			this.tallies.pass++;
		},

		testFailed: function pr_test_failed(test, failure) {
			var message = this._getMessage(test);
			message.setType('fail');
			message.setMessage(test.name + ': ' + failure.description);
			if (this.status == 'ok')
				this.setStatus('failure');
			this.tallies.fail++;
		},

		testError: function pr_test_error(test, error) {
			var message = this._getMessage(test);
			message.setType('error');
			message.setMessage('Test “' + test.name + '” ' +
				'had an error: ' + error.error.toString());
			this.setStatus('error');
			this.tallies.error++;
		},

		completed: function pr_run_completed() {
			var doc = this.body.ownerDocument;
			var frag = doc.createDocumentFragment();
			var message = doc.createElement('P');
			var tally_table = doc.createElement('TABLE');
			var tallies = this.tallies;
			var total_tests = tallies.pass + tallies.fail + tallies.error;
			tally_table.id = "pr_tally";

			function round(number) {
				try {
					return String(number).match(/\d+(\.\d)?/)[0];
				} catch (e) {
					return '0';
				}
			}

			function make_row(title, count) {
				var row = doc.createElement('TR');
				var head = doc.createElement('TH');
				var number;
				var percent;
				head.appendChild(doc.createTextNode(title));
				row.appendChild(head);

				number = doc.createElement('TD');
				number.innerHTML = count + ' tests';
				row.appendChild(number);

				percent = doc.createElement('TD');
				percent.innerHTML =  '(' + round(100 * (count / total_tests)) +
					'%)';
				row.appendChild(percent);
				tally_table.appendChild(row);
			}

			message.innerHTML = "Crucible has finished testing "
				+ this.product + "."
			frag.appendChild(message);

			make_row('Passed:', tallies.pass);
			make_row('Failed:', tallies.fail);
			make_row('Errors:', tallies.error);
			frag.appendChild(tally_table);

			this.addMessage('done', frag);
		}
	},

	/**
	 * @type Boolean
	 * @private
	 */
	_drawn: false,

	/**
	 * @type Boolean
	 * @private
	 */
	_done_adding: false,

	/**
	 * @type Object[]
	 * @private
	 */
	_test_messages: null,

	/**
	 * @type Object
	 * @private
	 */
	tallies: null,

	/**
	 * @type String
	 * @private
	 */
	base: null,

	/**
	 * The root runner window.
	 * @type HTMLDivElement
	 * @private
	 */
	root: null,

	/**
	 * The runner window title bar.
	 * @type HTMLDivElement
	 * @private
	 */
	titlebar: null,

	/**
	 * The status icon in the title bar.
	 * @type HTMLImageElement
	 * @private
	 */
	status_icon: null,

	/**
	 * The runner window main body.
	 * @type HTMLDivElement
	 * @private
	 */
	body: null,

	/**
	 * The results table.
	 * @type HTMLTableElement
	 * @private
	 */
	results: null,

	addMessage: function add_message_to_table(type, message, buttons) {
		var doc, row, icon_cell, icon, message_cell, button_cell;
		var mo;
		var runner = this;
		if (!this.results)
			this.createResultTable();

		doc = this.results.ownerDocument;
		row = this.results.insertRow(-1);

		icon_cell = row.insertCell(-1);
		icon_cell.className = 'pr_result_icon';
		icon = document.createElement('IMG');
		icon_cell.appendChild(icon);

		message_cell = row.insertCell(-1);
		message_cell.className = 'pr_result_body';

		mo = {
			setType: function set_pr_message_type(type) {
				var params;
				if (!(params = Crucible.PrettyRunner._type_params[type]))
					throw new Error('Unknown message type "' + type + '".');
				row.className = params.row_class;
				icon.src = runner.base + 'assets/icons/' + params.icon;
			},

			setMessage: function set_pr_message_text(message) {
				if (typeof(message) == 'string') {
					message_cell.innerHTML = message;
				} else {
					while (message_cell.firstChild)
						message_cell.removeChild(message_cell.firstChild);
					message_cell.appendChild(message);
				}
			},

			setButtons: function set_pr_message_buttons(buttons) {
				var button;
				function click_listener(button) {
					return function (ev) {
						if (typeof(button.pr_action) == 'function')
							button.pr_action.call(null);
						else
							button.pr_action.run(runner);
						mo.remove();
						ev.preventDefault();
					};
				}

				message_cell.colSpan = (buttons) ? 1 : 2;

				if (!buttons && button_cell) {
					button_cell.parentNode.removeChild(button_cell);
					button_cell = null;
				} else if (buttons) {
					if (button_cell) {
						while (button_cell.firstChild)
							button_cell.removeChild(button_cell.firstChild);
					} else {
						button_cell = row.insertCell(-1);
						button_cell.className = 'pr_result_buttons';
					}

					for (var label in buttons) {
						button = doc.createElement('A');
						button.href = '#';
						button.innerHTML = label;
						button.pr_action = buttons[label];
						Crucible.observeEvent(button, 'click',
							click_listener(button));
						button_cell.appendChild(button);
					}
				}
			},

			remove: function remove_pr_message() {
				runner.results.deleteRow(row.rowIndex);
			}
		};

		mo.setType(type);
		mo.setMessage(message);
		mo.setButtons(buttons);

		row.scrollIntoView();
		return mo;
	},

	/**
	 * Sets the status icon.
	 * @private
	 * @return {void}
	 */
	setStatus: function set_pretty_runner_status_icon(status) {
		var base = this.base + 'assets/icons/';
		switch (status) {
			case 'ok':
				this.status_icon.src = base + 'ok.png';
				this.status_icon.alt = 'OK';
				this.status_icon.title = 'No errors.';
				break;
			case 'failure':
				this.status_icon.src = base + 'error.png';
				this.status_icon.alt = 'Failure(s)';
				this.status_icon.title = 'One or more tests have failed.';
				break;
			case 'error':
				this.status_icon.src = base + 'exclamation.png';
				this.status_icon.alt = 'Error(s)';
				this.status_icon.title = 'One or more tests have encountered ' +
					'errors.';
				break;
			default:
				throw new Error('Unknown runner status code "' + status + '".');
		}
		this.status = status;
	},

	/**
	 * Creates the runner interface.
	 * @private
	 * @return {void}
	 */
	draw: function add_pretty_runner_stylesheet() {
		var base;

		if (this._drawn)
			return;

		if (!document.body) {
			Crucible.defer(this.draw, this);
			return;
		}

		this._drawn = true;

		base = this.base = Crucible.determineBase();
		Crucible.addStyleSheet(base + 'assets/css/pretty_runner.css');

		//this.appendUI();
		Crucible.defer(this.appendUI, this);
	},

	/**
	 * Creates the runner UI elements and appends them to the document.
	 * @private
	 * @return {void}
	 */
	appendUI: function append_pretty_runner_ui() {
		if (this.root && this.titlebar && this.status_icon && this.body)
			return; // guard

		this.root = document.createElement('DIV');
		this.root.id = "pretty_runner";

		this.titlebar = document.createElement('DIV');
		this.titlebar.id = "pretty_runner_title";
		this.titlebar.title = 'Click to open.';

		this.status_icon = document.createElement('IMG');
		this.status_icon.className = 'pr_status';
		this.setStatus('ok');
		this.titlebar.appendChild(this.status_icon)
		this.titlebar.appendChild(document.createTextNode("\nCrucible"));
		this.root.appendChild(this.titlebar);

		Crucible.observeEvent(this.titlebar, 'click', Crucible.bind(function() {
			var row;

			this.body.className = (!this.body.className)
				? 'pr_active'
				: '';
			this.titlebar.title = (this.body.className == 'pr_active')
				? 'Click to close.'
				: 'Click to open.';
			if (this.body.className == 'pr_active' && this.results) {
				row = this.results.rows[this.results.rows.length-1];
				Crucible.defer(function() {
					row.scrollIntoView();
				});
			}
		}, this));

		this.body = document.createElement('DIV');
		this.body.id = 'pretty_runner_body';
		this.root.appendChild(this.body);

		document.body.appendChild(this.root);
	},

	/**
	 * Creates the results table.
	 * @private
	 * @return {void}
	 */
	createResultTable: function create_pretty_runner_result_table() {
		this.results = this.root.ownerDocument.createElement('TABLE');
		this.results.id = "pretty_runner_results";
		this.results.cellSpacing = 0;
		this.body.appendChild(this.results);
	}
});

Crucible.augment(Crucible.PrettyRunner, {
	_window_loaded: false,
	_pending_runners: [],

	_window_load: function _pretty_runner_onload() {
		var pending, i;

		Crucible.PrettyRunner._window_loaded = true;

		pending = Crucible.PrettyRunner._pending_runners;
		Crucible.PrettyRunner._pending_runners = [];
		for (i = 0; i < pending.count; i++) {
			pending[i].draw();
		}
	},

	_type_params: {
		'prompt': {
			row_class: 'pr_message',
			icon: 'bullet_go.png'
		},
		'running': {
			row_class: 'pr_running',
			icon: 'bullet_yellow.png'
		},
		'pass': {
			row_class: 'pr_passed',
			icon: 'tick.png'
		},
		'fail': {
			row_class: 'pr_failed',
			icon: 'cross.png'
		},
		'error': {
			row_class: 'pr_error',
			icon: 'exclamation.png'
		},
		'done': {
			row_class: 'pr_done',
			icon: 'flag_blue.png'
		}
	}
});

Crucible.observeEvent(window, 'load', Crucible.PrettyRunner._window_load);
<commit_msg>Fixed the weirdness resulting from HTMLTableElement#removeRow on IE.<commit_after>/**
 * Creates a new pretty test runner.
 * @param {String} product the name of the "product" being tested
 * @class Runs Crucible tests, displaying the results graphically in the browser
 * window.
 * @augments Crucible.Runner
 */
Crucible.PrettyRunner = function PrettyRunner(product) {
	Crucible.Runner.call(this);
	if (product)
		this.product = product;

	for (var name in this._eventListeners) {
		this[name].add(this._eventListeners[name], this);
	}

	this._test_messages = [];

	this.draw();
};

Crucible.PrettyRunner.prototype = new Crucible.Runner();

Crucible.augment(Crucible.PrettyRunner.prototype,
	/** @lends Crucible.PrettyRunner.prototype */
{
	/**
	 * PrettyRunner status.
	 * @type String
	 */
	status: null,

	/**
	 * The product that the PrettyRunner is testing.
	 */
	product: 'the product',

	doneAdding: function done_adding_tasks_to_pretty_runner() {
		var msg;
		this._done_adding = true;

		if (!this.root) {
			Crucible.defer(Crucible.bind(this.doneAdding, this));
			return;
		}

		function start() {
			this.tallies = {pass: 0, fail: 0, error: 0};
			this.run();
		}

		msg = this.addMessage('prompt', "Crucible is ready to test " +
			this.product + ".", {'Start': Crucible.bind(start, this)});
	},

	displayMessage: function pr_display_message(message, buttons) {
		this.addMessage('prompt', message, buttons);
	},

	_getMessage: function _get_message_for_test(test, remove) {
		var i, len, entry, message;
		for (i = 0, len = this._test_messages.length; i < len; ++i) {
			entry = this._test_messages[i];
			if (entry && entry.test == test) {
				message = entry.message;
				if (remove)
					this._test_messages.splice(i, 1);
				return message;
			}
		}

		return null;
	},

	_setMessage: function _set_message_for_test(test, message) {
		var old = this._getMessage(test, true);
		if (old && old.remove)
			old.remove();
		this._test_messages.push({test: test, message: message});
		return message;
	},

	_eventListeners: {
		testStarted: function pr_test_started(test) {
			var msg = this.addMessage('running', 'Testing “' + test.name +
				'”…');
			this._setMessage(test, msg);
		},

		testPassed: function pr_test_succeeded(test) {
			var message = this._getMessage(test);
			message.setType('pass');
			message.setMessage(test.name);
			this.tallies.pass++;
		},

		testFailed: function pr_test_failed(test, failure) {
			var message = this._getMessage(test);
			message.setType('fail');
			message.setMessage(test.name + ': ' + failure.description);
			if (this.status == 'ok')
				this.setStatus('failure');
			this.tallies.fail++;
		},

		testError: function pr_test_error(test, error) {
			var message = this._getMessage(test);
			message.setType('error');
			message.setMessage('Test “' + test.name + '” ' +
				'had an error: ' + error.error.toString());
			this.setStatus('error');
			this.tallies.error++;
		},

		completed: function pr_run_completed() {
			var doc = this.body.ownerDocument;
			var frag = doc.createDocumentFragment();
			var message = doc.createElement('P');
			var tally_table = doc.createElement('TABLE');
			var tallies = this.tallies;
			var total_tests = tallies.pass + tallies.fail + tallies.error;
			tally_table.id = "pr_tally";

			function round(number) {
				try {
					return String(number).match(/\d+(\.\d)?/)[0];
				} catch (e) {
					return '0';
				}
			}

			function make_row(title, count) {
				var row = doc.createElement('TR');
				var head = doc.createElement('TH');
				var number;
				var percent;
				head.appendChild(doc.createTextNode(title));
				row.appendChild(head);

				number = doc.createElement('TD');
				number.innerHTML = count + ' tests';
				row.appendChild(number);

				percent = doc.createElement('TD');
				percent.innerHTML =  '(' + round(100 * (count / total_tests)) +
					'%)';
				row.appendChild(percent);
				tally_table.appendChild(row);
			}

			message.innerHTML = "Crucible has finished testing "
				+ this.product + "."
			frag.appendChild(message);

			make_row('Passed:', tallies.pass);
			make_row('Failed:', tallies.fail);
			make_row('Errors:', tallies.error);
			frag.appendChild(tally_table);

			this.addMessage('done', frag);
		}
	},

	/**
	 * @type Boolean
	 * @private
	 */
	_drawn: false,

	/**
	 * @type Boolean
	 * @private
	 */
	_done_adding: false,

	/**
	 * @type Object[]
	 * @private
	 */
	_test_messages: null,

	/**
	 * @type Object
	 * @private
	 */
	tallies: null,

	/**
	 * @type String
	 * @private
	 */
	base: null,

	/**
	 * The root runner window.
	 * @type HTMLDivElement
	 * @private
	 */
	root: null,

	/**
	 * The runner window title bar.
	 * @type HTMLDivElement
	 * @private
	 */
	titlebar: null,

	/**
	 * The status icon in the title bar.
	 * @type HTMLImageElement
	 * @private
	 */
	status_icon: null,

	/**
	 * The runner window main body.
	 * @type HTMLDivElement
	 * @private
	 */
	body: null,

	/**
	 * The results table.
	 * @type HTMLTableElement
	 * @private
	 */
	results: null,

	addMessage: function add_message_to_table(type, message, buttons) {
		var doc, row, icon_cell, icon, message_cell, button_cell;
		var mo;
		var runner = this;
		if (!this.results)
			this.createResultTable();

		doc = this.results.ownerDocument;
		row = this.results.insertRow(-1);

		icon_cell = row.insertCell(-1);
		icon_cell.className = 'pr_result_icon';
		icon = document.createElement('IMG');
		icon_cell.appendChild(icon);

		message_cell = row.insertCell(-1);
		message_cell.className = 'pr_result_body';

		mo = {
			setType: function set_pr_message_type(type) {
				var params;
				if (!(params = Crucible.PrettyRunner._type_params[type]))
					throw new Error('Unknown message type "' + type + '".');
				row.className = params.row_class;
				icon.src = runner.base + 'assets/icons/' + params.icon;
			},

			setMessage: function set_pr_message_text(message) {
				if (typeof(message) == 'string') {
					message_cell.innerHTML = message;
				} else {
					while (message_cell.firstChild)
						message_cell.removeChild(message_cell.firstChild);
					message_cell.appendChild(message);
				}
			},

			setButtons: function set_pr_message_buttons(buttons) {
				var button;
				function click_listener(button) {
					return function (ev) {
						if (typeof(button.pr_action) == 'function')
							button.pr_action.call(null);
						else
							button.pr_action.run(runner);
						mo.remove();
						ev.preventDefault();
					};
				}

				message_cell.colSpan = (buttons) ? 1 : 2;

				if (!buttons && button_cell) {
					button_cell.parentNode.removeChild(button_cell);
					button_cell = null;
				} else if (buttons) {
					if (button_cell) {
						while (button_cell.firstChild)
							button_cell.removeChild(button_cell.firstChild);
					} else {
						button_cell = row.insertCell(-1);
						button_cell.className = 'pr_result_buttons';
					}

					for (var label in buttons) {
						button = doc.createElement('A');
						button.href = '#';
						button.innerHTML = label;
						button.pr_action = buttons[label];
						Crucible.observeEvent(button, 'click',
							click_listener(button));
						button_cell.appendChild(button);
					}
				}
			},

			remove: function remove_pr_message() {
				row.parentNode.removeChild(row);
				row = null;
			}
		};

		mo.setType(type);
		mo.setMessage(message);
		mo.setButtons(buttons);

		row.scrollIntoView();
		return mo;
	},

	/**
	 * Sets the status icon.
	 * @private
	 * @return {void}
	 */
	setStatus: function set_pretty_runner_status_icon(status) {
		var base = this.base + 'assets/icons/';
		switch (status) {
			case 'ok':
				this.status_icon.src = base + 'ok.png';
				this.status_icon.alt = 'OK';
				this.status_icon.title = 'No errors.';
				break;
			case 'failure':
				this.status_icon.src = base + 'error.png';
				this.status_icon.alt = 'Failure(s)';
				this.status_icon.title = 'One or more tests have failed.';
				break;
			case 'error':
				this.status_icon.src = base + 'exclamation.png';
				this.status_icon.alt = 'Error(s)';
				this.status_icon.title = 'One or more tests have encountered ' +
					'errors.';
				break;
			default:
				throw new Error('Unknown runner status code "' + status + '".');
		}
		this.status = status;
	},

	/**
	 * Creates the runner interface.
	 * @private
	 * @return {void}
	 */
	draw: function add_pretty_runner_stylesheet() {
		var base;

		if (this._drawn)
			return;

		if (!document.body) {
			Crucible.defer(this.draw, this);
			return;
		}

		this._drawn = true;

		base = this.base = Crucible.determineBase();
		Crucible.addStyleSheet(base + 'assets/css/pretty_runner.css');

		//this.appendUI();
		Crucible.defer(this.appendUI, this);
	},

	/**
	 * Creates the runner UI elements and appends them to the document.
	 * @private
	 * @return {void}
	 */
	appendUI: function append_pretty_runner_ui() {
		if (this.root && this.titlebar && this.status_icon && this.body)
			return; // guard

		this.root = document.createElement('DIV');
		this.root.id = "pretty_runner";

		this.titlebar = document.createElement('DIV');
		this.titlebar.id = "pretty_runner_title";
		this.titlebar.title = 'Click to open.';

		this.status_icon = document.createElement('IMG');
		this.status_icon.className = 'pr_status';
		this.setStatus('ok');
		this.titlebar.appendChild(this.status_icon)
		this.titlebar.appendChild(document.createTextNode("\nCrucible"));
		this.root.appendChild(this.titlebar);

		Crucible.observeEvent(this.titlebar, 'click', Crucible.bind(function() {
			var row;

			this.body.className = (!this.body.className)
				? 'pr_active'
				: '';
			this.titlebar.title = (this.body.className == 'pr_active')
				? 'Click to close.'
				: 'Click to open.';
			if (this.body.className == 'pr_active' && this.results) {
				row = this.results.rows[this.results.rows.length-1];
				Crucible.defer(function() {
					row.scrollIntoView();
				});
			}
		}, this));

		this.body = document.createElement('DIV');
		this.body.id = 'pretty_runner_body';
		this.root.appendChild(this.body);

		document.body.appendChild(this.root);
	},

	/**
	 * Creates the results table.
	 * @private
	 * @return {void}
	 */
	createResultTable: function create_pretty_runner_result_table() {
		this.results = this.root.ownerDocument.createElement('TABLE');
		this.results.id = "pretty_runner_results";
		this.results.cellSpacing = 0;
		this.body.appendChild(this.results);
	}
});

Crucible.augment(Crucible.PrettyRunner, {
	_window_loaded: false,
	_pending_runners: [],

	_window_load: function _pretty_runner_onload() {
		var pending, i;

		Crucible.PrettyRunner._window_loaded = true;

		pending = Crucible.PrettyRunner._pending_runners;
		Crucible.PrettyRunner._pending_runners = [];
		for (i = 0; i < pending.count; i++) {
			pending[i].draw();
		}
	},

	_type_params: {
		'prompt': {
			row_class: 'pr_message',
			icon: 'bullet_go.png'
		},
		'running': {
			row_class: 'pr_running',
			icon: 'bullet_yellow.png'
		},
		'pass': {
			row_class: 'pr_passed',
			icon: 'tick.png'
		},
		'fail': {
			row_class: 'pr_failed',
			icon: 'cross.png'
		},
		'error': {
			row_class: 'pr_error',
			icon: 'exclamation.png'
		},
		'done': {
			row_class: 'pr_done',
			icon: 'flag_blue.png'
		}
	}
});

Crucible.observeEvent(window, 'load', Crucible.PrettyRunner._window_load);
<|endoftext|>


<commit_before>// @flow

import React, { Component } from 'react';
import { View } from 'react-native';

import { overlayFrame as styles } from './styles';

/**
 * The type of the React {@code Component} props of {@code OverlayFrame}.
 */
type Props = {

    /**
     * The children components to be displayed into the overlay frame.
     */
    children?: React$Node,
};

/**
 * Implements a React component to act as the frame for overlays.
 */
export default class OverlayFrame extends Component<Props> {
    /**
     * Implements React's {@link Component#render()}.
     *
     * @inheritdoc
     * @returns {React$Element}
     */
    render() {
        return (
            <View style = { styles.container }>
                { this.props.children }
            </View>
        );
    }
}
<commit_msg>[RN] Use a SafeAreaView for OverlayFrame<commit_after>// @flow

import React, { Component, type Node } from 'react';
import { SafeAreaView } from 'react-native';

import { overlayFrame as styles } from './styles';

/**
 * The type of the React {@code Component} props of {@code OverlayFrame}.
 */
type Props = {

    /**
     * The children components to be displayed into the overlay frame.
     */
    children: Node,
};

/**
 * Implements a React component to act as the frame for overlays.
 */
export default class OverlayFrame extends Component<Props> {
    /**
     * Implements React's {@link Component#render()}.
     *
     * @inheritdoc
     * @returns {ReactElement}
     */
    render() {
        return (
            <SafeAreaView style = { styles.container }>
                { this.props.children }
            </SafeAreaView>
        );
    }
}
<|endoftext|>


<commit_before><commit_msg>Fix redundant code and comment<commit_after><|endoftext|>


<commit_before>import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
  fetchAssignedBooks,
  deleteAssignment,
  markBookAsRead,
  updateAssignmentProgress
} from '../actions/crudActions';
import { browserHistory } from 'react-router';
import IconButton from 'material-ui/lib/icon-button';
import Popover from 'material-ui/lib/popover/popover';
import RaisedButton from 'material-ui/lib/raised-button';

const mapStateToProps = (state) => {
  return {
    books: state.bookReducers.assignedBooks
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    onFetchAssignedBooks: () => {
      dispatch(fetchAssignedBooks());
    },

    onDeleteAssignedBook: (id) => {
      dispatch(deleteAssignment(id));
    },

    onUpdateAssignmentProgress: (id, numPages) => {
      dispatch(updateAssignmentProgress(id, numPages));
    },

    onMarkBookAsRead: (id) => {
      dispatch(markBookAsRead(id));
    }
  };
};

class BookComponent extends Component {
  componentDidMount() {
    this.props.onFetchAssignedBooks();
  }

  getProperties(item) {
    const res = {};
    res.id = item.id;
    res.title = item.book.title;
    res.author = item.book.author;

    let pagesProgression = `0/${item.book.num_pages}`;
    let percentProgression = '0%';
    let progressId = null;
    let isRead = false;

    if (item.progress !== null) {
      pagesProgression = `${item.progress.num_pages_read}/${item.book.num_pages}`;
      percentProgression = `${Math.round(
        +item.progress.num_pages_read / item.book.num_pages * 100)}%`;
      progressId = item.progress.id;
      isRead = item.progress.is_read;
    }

    res.pagesProgression = pagesProgression;
    res.percentProgression = percentProgression;
    res.progressId = progressId;
    res.isRead = isRead;

    return res;
  }

  render() {
    return (

          Your books

              Title
              Author
              Pages Read
              Progress

              {this.props.books.map((item) => {
                return (
                  <AssignmentItem
                    properties={this.getProperties(item)}
                    onDeleteAssignedBook={this.props.onDeleteAssignedBook}
                    onUpdateAssignmentProgress={this.props.onUpdateAssignmentProgress}
                    onMarkBookAsRead={this.props.onMarkBookAsRead}
                  />
                );
              })}

    );
  }
}

class AssignmentItem extends Component {
  constructor(props) {
    super(props);
    this.state = {
      inEditMode: false
    };
  }

  render() {
    const component = (!this.state.inEditMode) ?
      <ReadOnlyAssignmentItem
        properties={this.props.properties}
        onDeleteAssignedBook={this.props.onDeleteAssignedBook}
        onClickEdit={() => this.setState({ inEditMode: true })}
        onClickMarkAsRead={this.props.onMarkBookAsRead}
      />
    :
      <EditableAssignmentItem
        properties={this.props.properties}
        onDeleteAssignedBook={this.props.onDeleteAssignedBook}
        onClickGoBack={() => this.setState({ inEditMode: false })}
        onClickSave={(id, numPages) => {
          this.setState({ inEditMode: false });
          this.props.onUpdateAssignmentProgress(id, numPages);
        }}
      />;

    return component;
  }
}

const ReadOnlyAssignmentItem = ({ properties, onDeleteAssignedBook,
    onClickEdit, onClickMarkAsRead }) => {
  const { id, title, author, pagesProgression, percentProgression, isRead } = properties;
  let markAsReadOption = (percentProgression === '100%' && !isRead) ?
        <MarkAsReadOption clickHandler={() => onClickMarkAsRead(id)} /> : null;

  let uploadResponseOption = (isRead) ?
        <UploadResponseButton assignmentId={id}/> : null;

  return (

      {title}
      {author}
      {pagesProgression}
      {percentProgression}

          <IconButton
            iconClassName='material-icons'
            onClick={onClickEdit}
          >
            create
          </IconButton>
          <IconButton
            iconClassName='material-icons'
            iconStyle={{ color: '#d50000' }}
            onClick={() => onDeleteAssignedBook(id)}
          >
            delete_forever
          </IconButton>
          {markAsReadOption}
          {uploadResponseOption}

  );
};

class UploadResponseButton extends Component {
  constructor(props) {
    super(props);
    this.state = {
      showingOptions: false,
      anchorEl: null
    };
  }

  render() {
    return (

        <IconButton
          iconClassName='material-icons'
          iconStyle={{ color: '#00bfe8' }}
          onClick={(e) => this.setState({ showingOptions: true, anchorEl: e.currentTarget })}
        >
        cloud_upload
      </IconButton>
        <Popover
          open={this.state.showingOptions}
          anchorEl={this.state.anchorEl}
          anchorOrigin={{ horizontal: 'right', vertical: 'center' }}
          targetOrigin={{ horizontal: 'left', vertical: 'center' }}
          onRequestClose={() => this.setState({ showingOptions: false })}
        >

            <button
              className='popover-response-option'
              onClick={() => {
                browserHistory.push(`/app/responses/write/${this.props.assignmentId}`);
              }}
            >
              Write A Response
            </button>
            <button
              className='popover-response-option'
              onClick={() => browserHistory.push(`/app/responses/video/${this.props.assignmentId}`)}
            >
              Upload A Video
            </button>
            <button
              className='popover-response-option'
              onClick={() => browserHistory.push(`/app/responses/image/${this.props.assignmentId}`)}
            >
              Upload An Image
            </button>

        </Popover>

    );
  }
}

const MarkAsReadOption = ({ clickHandler }) => (
  <IconButton
    onClick={clickHandler}
    iconStyle={{ color: '#20bb51' }}
    iconClassName='material-icons'
  >
    done
  </IconButton>
);

class EditableAssignmentItem extends Component {
  constructor(props) {
    super(props);
    const { pagesProgression } = this.props.properties;
    const [_numPagesRead, _maxNumPages] = pagesProgression.split('/');
    this.state = {
      numPagesRead: parseInt(_numPagesRead, 10),
      maxNumPages: parseInt(_maxNumPages, 10)
    };
  }

  onChange(e) {
    const val = parseInt(e.target.value, 10);
    if (!isNaN(val) && val <= this.state.maxNumPages) {
      this.setState({ numPagesRead: val });
    }
  }

  render() {
    return (

        {this.props.properties.title}
        {this.props.properties.author}

          <input
            className='my-books-item-num-pages-input'
            defaultValue={this.state.numPagesRead}
            onChange={(e) => this.onChange(e)}
          >
          </input>

          {this.props.properties.percentProgression}

            <IconButton
              iconClassName='material-icons'
              onClick={() => { this.props.onClickGoBack(); }}
            >
              backspace
            </IconButton>
            <IconButton
              iconClassName='material-icons'
              onClick={() =>
                this.props.onClickSave(this.props.properties.progressId, this.state.numPagesRead)
              }
            >
              save
            </IconButton>
            <IconButton
              iconStyle={{ color: '#d50000' }}
              iconClassName='material-icons'
              onClick={() => this.props.onDeleteAssignedBook(this.props.properties.id)}
            >
              delete_forever
            </IconButton>

    );
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(BookComponent);
<commit_msg>remove unused import<commit_after>import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
  fetchAssignedBooks,
  deleteAssignment,
  markBookAsRead,
  updateAssignmentProgress
} from '../actions/crudActions';
import { browserHistory } from 'react-router';
import IconButton from 'material-ui/lib/icon-button';
import Popover from 'material-ui/lib/popover/popover';

const mapStateToProps = (state) => {
  return {
    books: state.bookReducers.assignedBooks
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    onFetchAssignedBooks: () => {
      dispatch(fetchAssignedBooks());
    },

    onDeleteAssignedBook: (id) => {
      dispatch(deleteAssignment(id));
    },

    onUpdateAssignmentProgress: (id, numPages) => {
      dispatch(updateAssignmentProgress(id, numPages));
    },

    onMarkBookAsRead: (id) => {
      dispatch(markBookAsRead(id));
    }
  };
};

class BookComponent extends Component {
  componentDidMount() {
    this.props.onFetchAssignedBooks();
  }

  getProperties(item) {
    const res = {};
    res.id = item.id;
    res.title = item.book.title;
    res.author = item.book.author;

    let pagesProgression = `0/${item.book.num_pages}`;
    let percentProgression = '0%';
    let progressId = null;
    let isRead = false;

    if (item.progress !== null) {
      pagesProgression = `${item.progress.num_pages_read}/${item.book.num_pages}`;
      percentProgression = `${Math.round(
        +item.progress.num_pages_read / item.book.num_pages * 100)}%`;
      progressId = item.progress.id;
      isRead = item.progress.is_read;
    }

    res.pagesProgression = pagesProgression;
    res.percentProgression = percentProgression;
    res.progressId = progressId;
    res.isRead = isRead;

    return res;
  }

  render() {
    return (

          Your books

              Title
              Author
              Pages Read
              Progress

              {this.props.books.map((item) => {
                return (
                  <AssignmentItem
                    properties={this.getProperties(item)}
                    onDeleteAssignedBook={this.props.onDeleteAssignedBook}
                    onUpdateAssignmentProgress={this.props.onUpdateAssignmentProgress}
                    onMarkBookAsRead={this.props.onMarkBookAsRead}
                  />
                );
              })}

    );
  }
}

class AssignmentItem extends Component {
  constructor(props) {
    super(props);
    this.state = {
      inEditMode: false
    };
  }

  render() {
    const component = (!this.state.inEditMode) ?
      <ReadOnlyAssignmentItem
        properties={this.props.properties}
        onDeleteAssignedBook={this.props.onDeleteAssignedBook}
        onClickEdit={() => this.setState({ inEditMode: true })}
        onClickMarkAsRead={this.props.onMarkBookAsRead}
      />
    :
      <EditableAssignmentItem
        properties={this.props.properties}
        onDeleteAssignedBook={this.props.onDeleteAssignedBook}
        onClickGoBack={() => this.setState({ inEditMode: false })}
        onClickSave={(id, numPages) => {
          this.setState({ inEditMode: false });
          this.props.onUpdateAssignmentProgress(id, numPages);
        }}
      />;

    return component;
  }
}

const ReadOnlyAssignmentItem = ({ properties, onDeleteAssignedBook,
    onClickEdit, onClickMarkAsRead }) => {
  const { id, title, author, pagesProgression, percentProgression, isRead } = properties;
  let markAsReadOption = (percentProgression === '100%' && !isRead) ?
        <MarkAsReadOption clickHandler={() => onClickMarkAsRead(id)} /> : null;

  let uploadResponseOption = (isRead) ?
        <UploadResponseButton assignmentId={id}/> : null;

  return (

      {title}
      {author}
      {pagesProgression}
      {percentProgression}

          <IconButton
            iconClassName='material-icons'
            onClick={onClickEdit}
          >
            create
          </IconButton>
          <IconButton
            iconClassName='material-icons'
            iconStyle={{ color: '#d50000' }}
            onClick={() => onDeleteAssignedBook(id)}
          >
            delete_forever
          </IconButton>
          {markAsReadOption}
          {uploadResponseOption}

  );
};

class UploadResponseButton extends Component {
  constructor(props) {
    super(props);
    this.state = {
      showingOptions: false,
      anchorEl: null
    };
  }

  render() {
    return (

        <IconButton
          iconClassName='material-icons'
          iconStyle={{ color: '#00bfe8' }}
          onClick={(e) => this.setState({ showingOptions: true, anchorEl: e.currentTarget })}
        >
        cloud_upload
      </IconButton>
        <Popover
          open={this.state.showingOptions}
          anchorEl={this.state.anchorEl}
          anchorOrigin={{ horizontal: 'right', vertical: 'center' }}
          targetOrigin={{ horizontal: 'left', vertical: 'center' }}
          onRequestClose={() => this.setState({ showingOptions: false })}
        >

            <button
              className='popover-response-option'
              onClick={() => {
                browserHistory.push(`/app/responses/write/${this.props.assignmentId}`);
              }}
            >
              Write A Response
            </button>
            <button
              className='popover-response-option'
              onClick={() => browserHistory.push(`/app/responses/video/${this.props.assignmentId}`)}
            >
              Upload A Video
            </button>
            <button
              className='popover-response-option'
              onClick={() => browserHistory.push(`/app/responses/image/${this.props.assignmentId}`)}
            >
              Upload An Image
            </button>

        </Popover>

    );
  }
}

const MarkAsReadOption = ({ clickHandler }) => (
  <IconButton
    onClick={clickHandler}
    iconStyle={{ color: '#20bb51' }}
    iconClassName='material-icons'
  >
    done
  </IconButton>
);

class EditableAssignmentItem extends Component {
  constructor(props) {
    super(props);
    const { pagesProgression } = this.props.properties;
    const [_numPagesRead, _maxNumPages] = pagesProgression.split('/');
    this.state = {
      numPagesRead: parseInt(_numPagesRead, 10),
      maxNumPages: parseInt(_maxNumPages, 10)
    };
  }

  onChange(e) {
    const val = parseInt(e.target.value, 10);
    if (!isNaN(val) && val <= this.state.maxNumPages) {
      this.setState({ numPagesRead: val });
    }
  }

  render() {
    return (

        {this.props.properties.title}
        {this.props.properties.author}

          <input
            className='my-books-item-num-pages-input'
            defaultValue={this.state.numPagesRead}
            onChange={(e) => this.onChange(e)}
          >
          </input>

          {this.props.properties.percentProgression}

            <IconButton
              iconClassName='material-icons'
              onClick={() => { this.props.onClickGoBack(); }}
            >
              backspace
            </IconButton>
            <IconButton
              iconClassName='material-icons'
              onClick={() =>
                this.props.onClickSave(this.props.properties.progressId, this.state.numPagesRead)
              }
            >
              save
            </IconButton>
            <IconButton
              iconStyle={{ color: '#d50000' }}
              iconClassName='material-icons'
              onClick={() => this.props.onDeleteAssignedBook(this.props.properties.id)}
            >
              delete_forever
            </IconButton>

    );
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(BookComponent);
<|endoftext|>


<commit_before>class Influxdb {
    constructor(host, sendPointsOnClose, sendOnInsert) {
        if (typeof sendPointsOnClose == 'undefined') this.sendPointsOnClose = false;
        if (typeof sendOnInsert == 'undefined') this.sendOnInsert = false;
        this.sendPointsOnClose = sendPointsOnClose;
        this.sendOnInsert = sendOnInsert;
        this.host = host;
        this.points = [];
        this.beaconSent = false;
        if (this.sendPointsOnClose) this.registerUnloadEvent();
        return this;
    }

    registerUnloadEvent() {
        var _self = this;
        //need both events to work in chrome http://stackoverflow.com/a/20322988/1368683
        window.addEventListener('unload', _self.sendBeacon, false);
        window.onbeforeunload = function (e) {
            _self.sendBeacon();
        };
    }

    sendBeacon() {
        //need this polyfill https://github.com/miguelmota/Navigator.sendBeacon
        if (this.points.length == 0) return;
        if (this.beaconSent) return;
        this.beaconSent = true;
        var data = this.implodePoints();
        navigator.sendBeacon(this.host, data);
    }

    point(key, fields, tags) {
        return this._addPoint(new Influxdb_Point(key, fields, tags));
    }

    _addPoint(point) {
        if (point.isValid) {
            this.points.push(point);
            if (this.sendOnInsert) this.send();
        }
        return this;
    }

    implodePoints() {
        if (this.points.length == 0) return '';
        var index, data='', _data = '';
        for (index = 0; index < this.points.length; ++index) {
            _data = this.points[index].getLine();
            if (index > 0 && data) data = data + '\n';
            data = data + this.points[index].getLine();
        }
        return data;
    }

    send() {
        if (this.points.length == 0) return false;
        var data = this.implodePoints();
        if (data) {
            var request = new XMLHttpRequest();
            request.open('POST', this.host, true);
            request.setRequestHeader('Content-Type', 'text/plain; charset=UTF-8');
            request.send(data);
        }

        this.points = [];
    }

}

class Influxdb_Point {

    constructor(key, fields, tags, time) {
        this.key = key;
        if (typeof fields !== 'undefined' && !this.isEmpty(fields)) this.fields = fields;
        if (typeof tags !== 'undefined' && !this.isEmpty(fields)) this.tags = tags;
        if (typeof time !== 'undefined') this.time = time;
        if (!this.key || !this.fields) {
            this.isValid = false;
        } else {
            this.isValid = true;
        }

    }

    getLine() {
        if (!this.key && !this.fields) return;
        this.line = this.key;
        if (this.tags) {
            this.line = this.line + ',' + this.objToString(this.tags)
        }
        if (this.fields) {
            this.line = this.line + ' ' + this.objToString(this.fields);
        }
        if (this.time) {
            this.line = this.line + ' ' + this.time;
        }
        return this.line;
    }

    sortObjectByKey(obj) {
        if (!Array.prototype.forEach) return obj;
        if (!Array.prototype.sort) return obj;
        if (!Object.keys) return obj;
        var ordered = {};
        Object.keys(obj).sort().forEach(function (key) {
            ordered[key] = obj[key];
        });
        return ordered;
    }

    objToString(obj) {
        var i = 0;
        var str = '';
        var key, value;
        //Tags should be sorted by key before being sent for best performance https://docs.influxdata.com/influxdb/v0.13/write_protocols/line/#key
        obj = this.sortObjectByKey(obj);
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                //todo: right now not supporting space in tags where it should be support with back slash
                key = key.replace(/\s+/g, '');
                value = obj[key].toString();
                value = value.replace(/\s+/g, '');
                i++;
                if (i > 1) str = str + ',';
                str += key + '=' + value;
            }
        }
        return str;
    }

    isEmpty(obj) {
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop))
                return false;
        }
        if (!window.JSON) return true;
        return true && JSON.stringify(obj) === JSON.stringify({});
    }
}
<commit_msg>custom time support<commit_after>class Influxdb {
    constructor(host, sendPointsOnClose) {
        this.sendPointsOnClose = sendPointsOnClose;
        this.host = host;
        this.points = [];
        this.beaconSent = false;
        if (this.sendPointsOnClose) this.registerUnloadEvent();
        return this;
    }

    registerUnloadEvent() {
        var _self = this;
        //need both events to work in chrome http://stackoverflow.com/a/20322988/1368683
        window.addEventListener('unload', _self.sendBeacon, false);
        window.onbeforeunload = function (e) {
            _self.sendBeacon();
        };
    }

    sendBeacon() {
        //need this polyfill https://github.com/miguelmota/Navigator.sendBeacon
        if (this.points.length == 0) return;
        if (this.beaconSent) return;
        this.beaconSent = true;
        var data = this.implodePoints();
        navigator.sendBeacon(this.host, data);
    }

    point(key, fields, tags, time) {
        return this._addPoint(new Influxdb_Point(key, fields, tags, time));
    }

    _addPoint(point) {
        if (point.isValid()) {
            this.points.push(point);
        }
        return this;
    }

    implodePoints() {
        if (this.points.length == 0) return '';
        var index, data = '', _data = '';
        for (index = 0; index < this.points.length; ++index) {
            if (!this.points[index].isValid()){
                this.points.slice(index,1);
                continue;
            }
            _data = this.points[index].getLine();
            if (index > 0 && data) data = data + '\n';
            data = data + this.points[index].getLine();
        }
        return data;
    }

    send() {
        if (this.points.length == 0) return false;
        var data = this.implodePoints();
        if (data) {
            var request = new XMLHttpRequest();
            request.open('POST', this.host, true);
            request.setRequestHeader('Content-Type', 'text/plain; charset=UTF-8');
            request.send(data);
        }

        this.points = [];
    }

}

class Influxdb_Point {

    constructor(key, fields, tags, time) {
        this.key = key;
        if (typeof fields !== 'undefined' && !this.isEmpty(fields)) this.fields = fields;
        if (typeof tags !== 'undefined' && !this.isEmpty(fields)) this.tags = tags;
        if (typeof time !== 'undefined') this.time = time;

    }

    isValid(){
        if (!this.key || !this.fields) return false;
        return true;
    }

    getLine() {
        if (!this.key && !this.fields) return;
        this.line = this.key;
        if (this.tags) {
            this.line = this.line + ',' + this.objToString(this.tags)
        }
        if (this.fields) {
            this.line = this.line + ' ' + this.objToString(this.fields);
        }
        if (this.time) {
            this.line = this.line + ' ' + this.time;
        }
        return this.line;
    }

    sortObjectByKey(obj) {
        if (!Array.prototype.forEach) return obj;
        if (!Array.prototype.sort) return obj;
        if (!Object.keys) return obj;
        var ordered = {};
        Object.keys(obj).sort().forEach(function (key) {
            ordered[key] = obj[key];
        });
        return ordered;
    }

    objToString(obj) {
        var i = 0;
        var str = '';
        var key, value;
        //Tags should be sorted by key before being sent for best performance https://docs.influxdata.com/influxdb/v0.13/write_protocols/line/#key
        obj = this.sortObjectByKey(obj);
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                //todo: right now not supporting space in tags where it should be support with back slash
                key = key.replace(/\s+/g, '');
                value = obj[key].toString();
                value = value.replace(/\s+/g, '');
                i++;
                if (i > 1) str = str + ',';
                str += key + '=' + value;
            }
        }
        return str;
    }

    isEmpty(obj) {
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop))
                return false;
        }
        if (!window.JSON) return true;
        return true && JSON.stringify(obj) === JSON.stringify({});
    }
}
<|endoftext|>


<commit_before>const DoublyLinkedList = require('./DoublyLinkedList');

class Queue {
  constructor() {
    this._list = new DoublyLinkedList();
  }

  /**
   * Get length of Queue
   * @return {Number} Size of Queue
   */
  get size() {
    return this._list.length;
  }

  /**
   * Check if Queue is empty or not
   * @return {Boolean} `true` if empty else `false`
   */
  isEmpty() {
    return this._list.isEmpty();
  }

  /**
   * Pushes node in Queue
   * @param  {*}   data Data or value to be pushed
   * @return {None}
   */
  push(data) {
    this._list.push(data);
  }

}

module.exports = Queue;
<commit_msg>Queue: Add pop method<commit_after>const DoublyLinkedList = require('./DoublyLinkedList');

class Queue {
  constructor() {
    this._list = new DoublyLinkedList();
  }

  /**
   * Get length of Queue
   * @return {Number} Size of Queue
   */
  get size() {
    return this._list.length;
  }

  /**
   * Check if Queue is empty or not
   * @return {Boolean} `true` if empty else `false`
   */
  isEmpty() {
    return this._list.isEmpty();
  }

  /**
   * Pushes node in Queue
   * @param  {*}   data Data or value to be pushed
   * @return {None}
   */
  push(data) {
    this._list.push(data);
  }

  /**
   * Pop node from Queue
   * @return {*} Value of node which was popped
   */
  pop() {
    const front = this._list.tail;
    this._list.deleteNode(front);

    return front.value;
  }

}

module.exports = Queue;
<|endoftext|>