<commit_before>import React, { Component } from 'react';

import {
  StyleSheet,
  Text,
  View,
  Image,
  TouchableOpacity,
  ListView,
  ScrollView
} from 'react-native';

let styles = StyleSheet.create({
  container: {
   flex: 1,
   paddingTop: 20,
   backgroundColor: '#ffffff'
  },
  hero: {
    flex: 3.25,
    flexDirection: 'column',
    backgroundColor: '#53D1E5'
  },
  history: {
    flex: 4,
    backgroundColor: '#43C6C6'
  },
  titleWrapper: {
    justifyContent: 'center',
    marginTop: 20,
    backgroundColor: 'white',
    flexDirection: 'row'
  },
  title: {
    textAlign: 'center',
    fontSize: 18,
    color: '#658D9F',
    padding: 15
  },
  profilePic:{
    width: 175,
    height: 175,
    alignSelf: 'center',
    backgroundColor: 'transparent'
  },
  buttonWrapper: {
    flex: .75,
    flexDirection: 'row',
  },
  leftButton: {
    flex: 0.25,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#5388e5',
    padding: 15,
    borderWidth: 1,
    borderColor: 'white'
  },
  rightButton: {
    flex: 0.25,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#53e5b0',
    padding: 15,
    borderWidth: 1,
    borderColor: 'white'
  },
  buttonText: {
    fontSize: 20,
    color: 'white'
  },
  contactCell: {
    flex:1,
    flexDirection: 'row',
    justifyContent: 'center',
    padding: 4,
    borderBottomWidth: .5,
    borderColor: 'lightgray'
  },
  classIcon: {
    padding: 0,
    width: 100,
    height: 100,
    backgroundColor: 'transparent'
    // marginRight: 20
  },
  rightContainer: {
    flex: 1,
    flexDirection: 'row',
    paddingLeft: 25,
    alignItems: 'center'
  },
  whiteText: {
    fontSize: 18,
    color: 'white'
  },
  lightText: {
    color: 'white'
  },
  descContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'space-between'
  },
  seperator: {
    height: 1,
    backgroundColor: 'white'
  },
})

var mockUser = [
  {
    name: 'Tommy C.',
    email: 'tommyc@gmail.com',
    profilePic: 'https://beautifulinnovation.files.wordpress.com/2015/06/circular-profile-pic.png?w=450&h=450&crop=1',
    classes: [
      {
        title: 'Ombre Watercolor Workshop',
        teacher: 'Jack J.',
        imgLink: 'http://ih1.redbubble.net/image.58824777.0188/flat,1000x1000,075,f.u2.jpg',
        lastContacted: 'June 11th, 2016'
      },
      {
        title: 'Brush Lettering',
        teacher: 'Bettie P.',
        imgLink: 'http://static1.squarespace.com/static/540fd39ae4b01475ae2448ee/t/5448713fe4b0d7fec27158cf/1414033731198/',
        lastContacted: 'June 1st, 2016'
      },
      {
        title: 'Print Making',
        teacher: 'Tanya B.',
        imgLink: 'https://upload.wikimedia.org/wikipedia/commons/7/7f/Hokusai-fuji7.png',
        lastContacted: 'May 28th, 2016'
      },
      {
        title: 'EL Wire Workshop',
        teacher: 'Matt F.',
        imgLink: 'http://overyonderlust.com/wp-content/uploads/2014/01/EL_Wire.jpg',
        lastContacted: 'May 15th, 2016'
      }
    ],
    reviews: [
      {
        title: 'Ombre Watercolor',
        teacher: 'Jack J.',
        knowlegeable: true,
        patient: true,
        friendly: true,
        technical_expertise: 4,
        passion: 5,
        course_difficulty: 3,
        summary: 'Jack is an awesome teacher, you can tell he really loves his work!'
      },
      {
        title: 'Print Making',
        teacher: 'Tanya B.',
        knowlegeable: true,
        patient: false,
        friendly: false,
        technical_expertise: 5,
        passion: 2,
        course_difficulty: 5,
        summary: 'I wouldnt recommend this class it is really hard & Tanya is not very friendly'
      },
      {
        title: 'EL Wire Workshop',
        teacher: 'Matt F.',
        knowlegeable: false,
        patient: true,
        friendly: true,
        technical_expertise: 2,
        passion: 5,
        course_difficulty: 1,
        summary: 'Matts enthusiasium for EL wire is great but I dont think I learned much in this class'
      }
    ]
  }
]

class User extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: '',
      email: '',
      profilePic: '',
      dataSource1: new ListView.DataSource({
        rowHasChanged: (r1, r2) => r1 !== r2,
      }),
      loaded: false,
    };
  }

  componentDidMount() {
      this.fetchData();
  }

  fetchData() {
    // return mocked data for now
    this.setState({
      name: mockUser[0].name,
      email: mockUser[0].email,
      profilePic: mockUser[0].profilePic,
      dataSource1: this.state.dataSource1.cloneWithRows(mockUser[0].classes),
      loaded: true,
    });
  }

  renderClasses(contact) {
    return (
      <ContactCell contact={contact}/>
    );
  }

  render(){
    return(
      <View style={styles.container}>
        <View style={styles.hero}>
        <View style={styles.titleWrapper}>
          <Text style={styles.title}>{this.state.name}</Text>
        </View>
          <Image
          style={styles.profilePic}
          source={{uri:'https://beautifulinnovation.files.wordpress.com/2015/06/circular-profile-pic.png?w=450&h=450&crop=1'}}
          />
        </View>
        <View style={styles.buttonWrapper}>
          <TouchableOpacity style={styles.leftButton}
          underlayColor="transparent"
          onPress={() => this._onClassesButton(this.props.id)}>
          <Text style={styles.buttonText}>
            Classes
          </Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.rightButton}
          underlayColor="transparent"
          onPress={() => this._onReviewsButton(this.props.id)}>
          <Text style={styles.buttonText}>
            Reviews
          </Text>
          </TouchableOpacity>
        </View>
        <View style={styles.history}>
        <ListView
        dataSource={this.state.dataSource1}
        renderRow={this.renderClasses}/>
        </View>
      </View>
    )
  }

  _onClassesButton() {

  }

  _onReviewsButton() {

  }
}

var ContactCell = React.createClass({
  render() {
    return(
      <View>
        <View style={styles.contactCell}>
        <Image
           style={styles.classIcon}
           source={{uri:this.props.contact.imgLink}} />
           <View style={styles.rightContainer}>
             <View>
               <Text style={styles.whiteText}>
                {this.props.contact.title}
               </Text>
               <Text style={styles.lightText}>
                 Instructor: {this.props.contact.teacher}
               </Text>
               <Text style={styles.lightText}>
                 Contacted: {this.props.contact.lastContacted}
               </Text>
             </View>
        </View>
      </View>
      <View style={styles.separator}/>
      </View>
    )
  }
})

export default User;
<commit_msg>add list population for reviews - not connected to buttons yet<commit_after>import React, { Component } from 'react';

import {
  StyleSheet,
  Text,
  View,
  Image,
  TouchableOpacity,
  ListView,
  ScrollView
} from 'react-native';

let styles = StyleSheet.create({
  container: {
   flex: 1,
   paddingTop: 20,
   backgroundColor: '#ffffff'
  },
  hero: {
    flex: 3.25,
    flexDirection: 'column',
    backgroundColor: '#53D1E5'
  },
  history: {
    flex: 4,
    backgroundColor: '#43C6C6'
  },
  titleWrapper: {
    justifyContent: 'center',
    marginTop: 20,
    backgroundColor: 'white',
    flexDirection: 'row'
  },
  title: {
    textAlign: 'center',
    fontSize: 18,
    color: '#658D9F',
    padding: 15
  },
  profilePic:{
    width: 175,
    height: 175,
    alignSelf: 'center',
    backgroundColor: 'transparent'
  },
  buttonWrapper: {
    flex: .75,
    flexDirection: 'row',
  },
  leftButton: {
    flex: 0.25,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#5388e5',
    padding: 15,
    borderWidth: 1,
    borderColor: 'white'
  },
  rightButton: {
    flex: 0.25,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#53e5b0',
    padding: 15,
    borderWidth: 1,
    borderColor: 'white'
  },
  buttonText: {
    fontSize: 20,
    color: 'white'
  },
  contactCell: {
    flex:1,
    flexDirection: 'row',
    justifyContent: 'center',
    padding: 4,
    borderBottomWidth: .5,
    borderColor: 'lightgray'
  },
  classIcon: {
    padding: 0,
    width: 100,
    height: 100,
    backgroundColor: 'transparent'
    // marginRight: 20
  },
  rightContainer: {
    flex: 1,
    flexDirection: 'row',
    paddingLeft: 25,
    alignItems: 'center'
  },
  whiteText: {
    fontSize: 18,
    color: 'white'
  },
  lightText: {
    color: 'white'
  },
  descContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'space-between'
  },
  seperator: {
    height: 1,
    backgroundColor: 'white'
  },
})

var mockUser = [
  {
    name: 'Tommy C.',
    email: 'tommyc@gmail.com',
    profilePic: 'https://beautifulinnovation.files.wordpress.com/2015/06/circular-profile-pic.png?w=450&h=450&crop=1',
    classes: [
      {
        title: 'Ombre Watercolor Workshop',
        teacher: 'Jack J.',
        imgLink: 'http://ih1.redbubble.net/image.58824777.0188/flat,1000x1000,075,f.u2.jpg',
        lastContacted: 'June 11th, 2016'
      },
      {
        title: 'Brush Lettering',
        teacher: 'Bettie P.',
        imgLink: 'http://static1.squarespace.com/static/540fd39ae4b01475ae2448ee/t/5448713fe4b0d7fec27158cf/1414033731198/',
        lastContacted: 'June 1st, 2016'
      },
      {
        title: 'Print Making',
        teacher: 'Tanya B.',
        imgLink: 'https://upload.wikimedia.org/wikipedia/commons/7/7f/Hokusai-fuji7.png',
        lastContacted: 'May 28th, 2016'
      },
      {
        title: 'EL Wire Workshop',
        teacher: 'Matt F.',
        imgLink: 'http://overyonderlust.com/wp-content/uploads/2014/01/EL_Wire.jpg',
        lastContacted: 'May 15th, 2016'
      }
    ],
    reviews: [
      {
        title: 'Ombre Watercolor',
        teacher: 'Jack J.',
        knowlegeable: true,
        patient: true,
        friendly: true,
        technical_expertise: 4,
        passion: 5,
        course_difficulty: 3,
        summary: 'Jack is an awesome teacher, you can tell he really loves his work!'
      },
      {
        title: 'Print Making',
        teacher: 'Tanya B.',
        knowlegeable: true,
        patient: false,
        friendly: false,
        technical_expertise: 5,
        passion: 2,
        course_difficulty: 5,
        summary: 'I wouldnt recommend this class it is really hard & Tanya is not very friendly'
      },
      {
        title: 'EL Wire Workshop',
        teacher: 'Matt F.',
        knowlegeable: false,
        patient: true,
        friendly: true,
        technical_expertise: 2,
        passion: 5,
        course_difficulty: 1,
        summary: 'Matts enthusiasium for EL wire is great but I dont think I learned much in this class'
      }
    ]
  }
]

class User extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: '',
      email: '',
      profilePic: '',
      dataSource1: new ListView.DataSource({
        rowHasChanged: (r1, r2) => r1 !== r2,
      }),
      dataSource2: new ListView.DataSource({
        rowHasChanged: (r1, r2) => r1 !== r2,
      }),
      loaded: false,
    };
  }

  componentDidMount() {
      this.fetchData();
  }

  fetchData() {
    // return mocked data for now
    this.setState({
      name: mockUser[0].name,
      email: mockUser[0].email,
      profilePic: mockUser[0].profilePic,
      dataSource1: this.state.dataSource1.cloneWithRows(mockUser[0].classes),
      dataSource2: this.state.dataSource2.cloneWithRows(mockUser[0].reviews),
      loaded: true,
    });
  }

  renderClasses(contact) {
    return (
      <ContactCell contact={contact}/>
    );
  }

  renderReviews(review) {
    return (
      <ReviewCell review={review}/>
    );
  }

  render(){
    return(
      <View style={styles.container}>
        <View style={styles.hero}>
        <View style={styles.titleWrapper}>
          <Text style={styles.title}>{this.state.name}</Text>
        </View>
          <Image
          style={styles.profilePic}
          source={{uri:'https://beautifulinnovation.files.wordpress.com/2015/06/circular-profile-pic.png?w=450&h=450&crop=1'}}
          />
        </View>
        <View style={styles.buttonWrapper}>
          <TouchableOpacity style={styles.leftButton}
          underlayColor="transparent"
          onPress={() => this._onClassesButton(this.props.id)}>
          <Text style={styles.buttonText}>
            Classes
          </Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.rightButton}
          underlayColor="transparent"
          onPress={() => this._onReviewsButton(this.props.id)}>
          <Text style={styles.buttonText}>
            Reviews
          </Text>
          </TouchableOpacity>
        </View>
        <View style={styles.history}>
        <ListView
        dataSource={this.state.dataSource1}
        renderRow={this.renderClasses}/>
        </View>
      </View>
    )
  }

  _onClassesButton() {

  }

  _onReviewsButton() {

  }
}

var ContactCell = React.createClass({
  render() {
    return(
      <View>
        <View style={styles.contactCell}>
        <Image
           style={styles.classIcon}
           source={{uri:this.props.contact.imgLink}} />
           <View style={styles.rightContainer}>
             <View>
               <Text style={styles.whiteText}>
                {this.props.contact.title}
               </Text>
               <Text style={styles.lightText}>
                 Instructor: {this.props.contact.teacher}
               </Text>
               <Text style={styles.lightText}>
                 Contacted:
               </Text>
             </View>
        </View>
      </View>
      <View style={styles.separator}/>
      </View>
    )
  }
})

var ReviewCell = React.createClass({
  render() {
    return(
      <View>
        <View style={styles.contactCell}>

           <View style={styles.rightContainer}>
             <View>
               <Text style={styles.whiteText}>
                {this.props.review.title}
               </Text>
               <Text style={styles.lightText}>
                 Instructor: {this.props.review.teacher}
               </Text>
               <Text style={styles.lightText}>
                 Summary: {this.props.review.summary}
               </Text>
             </View>
        </View>
      </View>
      <View style={styles.separator}/>
      </View>
    )
  }
})

export default User;
<|endoftext|>


<commit_before>import React from 'react';
import autoBind from 'react-autobind';
import alertify from 'alertifyjs';
import Select from 'react-select';
import ToggleSwitch from 'js/components/common/toggleSwitch';
import TextBox from 'js/components/common/textBox';
import {actions} from 'js/actions';
import {bem} from 'js/bem';
import {
  truncateFile,
  truncateString,
} from '../../utils';

const MAX_DISPLAYED_STRING_LENGTH = 30;

/*
 * Modal for connecting project data
 */
class ConnectProjects extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isInitialised: false,
      isLoading: false,
      // `data_sharing` is an empty object if never enabled before
      isShared: props.asset.data_sharing?.enabled || false,
      attachedParents: [],
      sharingEnabledAssets: null,
      newParent: null,
      newFilename: '',
      fieldsErrors: {},
    };

    autoBind(this);
  }

  /*
   * Setup
   */

  componentDidMount() {
    this.refreshAttachmentList();
    actions.dataShare.getSharingEnabledAssets();

    actions.dataShare.attachToParent.completed.listen(
      this.refreshAttachmentList
    );
    actions.dataShare.attachToParent.failed.listen(
      this.onAttachToParentFailed
    );
    actions.dataShare.detachParent.completed.listen(
      this.refreshAttachmentList
    );
    actions.dataShare.getSharingEnabledAssets.completed.listen(
      this.onGetSharingEnabledAssetsCompleted
    );
    actions.dataShare.getAttachedParents.completed.listen(
      this.onGetAttachedParentsCompleted
    );
    actions.dataShare.toggleDataSharing.completed.listen(
      this.onToggleDataSharingCompleted
    );
  }

  /*
   * `actions` Listeners
   */

  onAttachToParentFailed(response) {
    this.setState({
      isLoading: false,
      fieldsErrors: response.responseJSON || {},
    });
  }
  onGetAttachedParentsCompleted(response) {
    this.setState({
      isInitialised: true,
      isLoading: false,
      attachedParents: response,
    });
  }
  onGetSharingEnabledAssetsCompleted(response) {
    this.setState({sharingEnabledAssets: response});
  }
  onToggleDataSharingCompleted() {
    this.setState({isShared: !this.state.isShared});
  }
  refreshAttachmentList() {
    this.setState({
      newParent: null,
      newFilename: '',
    });
    actions.dataShare.getAttachedParents(this.props.asset.uid);
  }

  /*
   * UI Listeners
   */

  confirmAttachment(evt) {
    evt.preventDefault();

    let parentUrl = this.state.newParent?.url;
    let filename = this.state.newFilename;
    if (filename !== '' && parentUrl) {
      this.setState({
        isLoading: true,
        fieldsErrors: {},
      });

      var data = JSON.stringify({
        parent: parentUrl,
        fields: [],
        filename: filename,
      });
      actions.dataShare.attachToParent(this.props.asset.uid, data);
    } else {
      if (!parentUrl) {
        this.setState({
          fieldsErrors: Object.assign(
            this.state.fieldsErrors, {emptyParent: 'No project selected'}
          )
        });
      }
      if (filename === '') {
        this.setState({
          fieldsErrors: Object.assign(
            this.state.fieldsErrors, {emptyFilename: 'Field is empty'}
          )
        });
      }
    }
  }
  onParentChange(newVal) {
    this.setState({
      newParent: newVal,
      fieldsErrors: {},
    });
    this.generateAutoname(newVal);
  }
  onFilenameChange(newVal) {
    this.setState({
      newFilename: newVal,
      fieldsErrors: {},
    });
  }
  removeAttachment(newVal) {
    this.setState({isLoading: true})
    actions.dataShare.detachParent(newVal);
  }
  toggleSharingData() {
    var data = JSON.stringify({
      data_sharing: {
        enabled: !this.state.isShared
      }
    });

    if (!this.state.isShared) {
      let dialog = alertify.dialog('confirm');
      let opts = {
        title: `${t('Privacy Notice')}`,
        message: t('This will attach the full dataset from \"##ASSET_NAME##\" as a background XML file to this form. While not easily visible, it is technically possible for anyone entering data to your form to retrieve and view this dataset. Do not use this feature if \"##ASSET_NAME##\" includes sensitive data.').replaceAll('##ASSET_NAME##', this.props.asset.name),
        labels: {ok: t('Acknowledge and continue'), cancel: t('Cancel')},
        onok: (evt, value) => {
          actions.dataShare.toggleDataSharing(this.props.asset.uid, data);
          dialog.destroy();
        },
        oncancel: () => {
          dialog.destroy();
        }
      };
      dialog.set(opts).show();
    } else {
      actions.dataShare.toggleDataSharing(this.props.asset.uid, data);
    }
  }

  /*
   * Utilities
   */

  generateAutoname(newParent) {
    if (newParent) {
      let autoname = newParent.name;
      autoname = autoname.toLowerCase().substring(0, MAX_DISPLAYED_STRING_LENGTH).replace(/(\ |\.)/g, '_');
      this.setState({newFilename: autoname});
    }
  }
  generateFilteredAssetList() {
    let attachedParentUids = [];
    this.state.attachedParents.forEach((item) => {
      attachedParentUids.push(item.parentUid)
    });

    // Filter displayed asset list based on unattached projects
    return (
      this.state.sharingEnabledAssets.results.filter(
        item => !attachedParentUids.includes(item.uid)
      )
    );
  }

  /*
   * Rendering
   */

  renderLoading(message = t('loading…')) {
    return (
      <bem.Loading>
        <bem.Loading__inner>

          {message}
        </bem.Loading__inner>
      </bem.Loading>
    );
  }
  renderSwitchLabel() {
    if (this.state.isShared) {
      return (
        <ToggleSwitch
          onChange={this.toggleSharingData.bind(this)}
          label={t('Data sharing enabled')}
          checked={this.state.isShared}
        />
      );
    } else {
      return (
        <ToggleSwitch
          onChange={this.toggleSharingData.bind(this)}
          label={t('Data sharing disabled')}
          checked={this.state.isShared}
        />
      );
    }
  }
  renderSelect(sharingEnabledAssets) {
    const selectClassNames = ['kobo-select__wrapper'];
    if (this.state.fieldsErrors.emptyParent || this.state.fieldsErrors.parent) {
      selectClassNames.push('kobo-select__wrapper--error');
    }
    return(

        <Select
          placeholder={t('Select a different project to import data from')}
          options={sharingEnabledAssets}
          value={this.state.newParent}
          isLoading={(!this.state.isInitialised || this.state.isLoading || !sharingEnabledAssets)}
          getOptionLabel={option => option.name}
          getOptionValue={option => option.url}
          noOptionsMessage={() => {return t('No projects to connect')}}
          onChange={this.onParentChange}
          className='kobo-select'
          classNamePrefix='kobo-select'
        />

          {this.state.fieldsErrors.emptyParent || this.state.fieldsErrors.parent}

    );
  }

  render() {
    let sharingEnabledAssets = [];
    if (this.state.sharingEnabledAssets !== null) {
      sharingEnabledAssets = this.generateFilteredAssetList();
    }

    return (
      <bem.FormModal__form
        className='project-settings project-settings--upload-file connect-projects'
        onSubmit={this.confirmAttachment}
      >
        {/* Enable data sharing */}
        <bem.FormModal__item m='data-sharing'>

{t('Share data with other project forms')}

            {t('Enable data sharing to allow other forms to import and use dynamic data from this project. Learn more about dynamic data attachments')}
            {t(' ' + 'here')}

          {this.renderSwitchLabel()}
        </bem.FormModal__item>

        {/* Attach other projects data */}
        <bem.FormModal__item m='import-data'>

{t('Import other project data')}

            {t('Connect with other project(s) to import dynamic data from them into this project. Learn more about dynamic data attachments')}
            {t(' ' + 'here')}

          {/* Selecting project form*/}
          {sharingEnabledAssets &&

              {this.renderSelect(sharingEnabledAssets)}
              <TextBox
                placeholder={t('Give a unique name to the import')}
                value={this.state.newFilename}
                onChange={this.onFilenameChange}
                errors={this.state.fieldsErrors.emptyFilename ||
                        this.state.fieldsErrors.filename}
              />
              <bem.KoboButton m='blue'>
                {t('Import')}
              </bem.KoboButton>

          }

          {/* Display attached projects */}

            {t('Imported')}
            {(!this.state.isInitialised || this.state.isLoading) &&

                {this.renderLoading(t('Loading imported projects'))}

            }
            {!this.state.isLoading && this.state.attachedParents.length == 0 &&

                {t('No data imported')}

            }
            {!this.state.isLoading && this.state.attachedParents.length > 0 &&
                this.state.attachedParents.map((item, n) => {
                  return (

                          {truncateFile(item.filename, MAX_DISPLAYED_STRING_LENGTH)}

                          {truncateString(item.parentName, MAX_DISPLAYED_STRING_LENGTH)}

                       this.removeAttachment(item.attachmentUrl)}
                      />

                  );
                })
            }

        </bem.FormModal__item>

      </bem.FormModal__form>
    );
  }
}

export default ConnectProjects;
<commit_msg>PR Changes: call actions after listener is hooked<commit_after>import React from 'react';
import autoBind from 'react-autobind';
import alertify from 'alertifyjs';
import Select from 'react-select';
import ToggleSwitch from 'js/components/common/toggleSwitch';
import TextBox from 'js/components/common/textBox';
import {actions} from 'js/actions';
import {bem} from 'js/bem';
import {
  truncateFile,
  truncateString,
} from '../../utils';

const MAX_DISPLAYED_STRING_LENGTH = 30;

/*
 * Modal for connecting project data
 */
class ConnectProjects extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isInitialised: false,
      isLoading: false,
      // `data_sharing` is an empty object if never enabled before
      isShared: props.asset.data_sharing?.enabled || false,
      attachedParents: [],
      sharingEnabledAssets: null,
      newParent: null,
      newFilename: '',
      fieldsErrors: {},
    };

    autoBind(this);
  }

  /*
   * Setup
   */

  componentDidMount() {
    actions.dataShare.attachToParent.completed.listen(
      this.refreshAttachmentList
    );
    actions.dataShare.attachToParent.failed.listen(
      this.onAttachToParentFailed
    );
    actions.dataShare.detachParent.completed.listen(
      this.refreshAttachmentList
    );
    actions.dataShare.getSharingEnabledAssets.completed.listen(
      this.onGetSharingEnabledAssetsCompleted
    );
    actions.dataShare.getAttachedParents.completed.listen(
      this.onGetAttachedParentsCompleted
    );
    actions.dataShare.toggleDataSharing.completed.listen(
      this.onToggleDataSharingCompleted
    );

    this.refreshAttachmentList();
    actions.dataShare.getSharingEnabledAssets();
  }

  /*
   * `actions` Listeners
   */

  onAttachToParentFailed(response) {
    this.setState({
      isLoading: false,
      fieldsErrors: response.responseJSON || {},
    });
  }
  onGetAttachedParentsCompleted(response) {
    this.setState({
      isInitialised: true,
      isLoading: false,
      attachedParents: response,
    });
  }
  onGetSharingEnabledAssetsCompleted(response) {
    this.setState({sharingEnabledAssets: response});
  }
  onToggleDataSharingCompleted() {
    this.setState({isShared: !this.state.isShared});
  }
  refreshAttachmentList() {
    this.setState({
      newParent: null,
      newFilename: '',
    });
    actions.dataShare.getAttachedParents(this.props.asset.uid);
  }

  /*
   * UI Listeners
   */

  confirmAttachment(evt) {
    evt.preventDefault();

    let parentUrl = this.state.newParent?.url;
    let filename = this.state.newFilename;
    if (filename !== '' && parentUrl) {
      this.setState({
        isLoading: true,
        fieldsErrors: {},
      });

      var data = JSON.stringify({
        parent: parentUrl,
        fields: [],
        filename: filename,
      });
      actions.dataShare.attachToParent(this.props.asset.uid, data);
    } else {
      if (!parentUrl) {
        this.setState({
          fieldsErrors: Object.assign(
            this.state.fieldsErrors, {emptyParent: 'No project selected'}
          )
        });
      }
      if (filename === '') {
        this.setState({
          fieldsErrors: Object.assign(
            this.state.fieldsErrors, {emptyFilename: 'Field is empty'}
          )
        });
      }
    }
  }
  onParentChange(newVal) {
    this.setState({
      newParent: newVal,
      fieldsErrors: {},
    });
    this.generateAutoname(newVal);
  }
  onFilenameChange(newVal) {
    this.setState({
      newFilename: newVal,
      fieldsErrors: {},
    });
  }
  removeAttachment(newVal) {
    this.setState({isLoading: true})
    actions.dataShare.detachParent(newVal);
  }
  toggleSharingData() {
    var data = JSON.stringify({
      data_sharing: {
        enabled: !this.state.isShared
      }
    });

    if (!this.state.isShared) {
      let dialog = alertify.dialog('confirm');
      let opts = {
        title: `${t('Privacy Notice')}`,
        message: t('This will attach the full dataset from \"##ASSET_NAME##\" as a background XML file to this form. While not easily visible, it is technically possible for anyone entering data to your form to retrieve and view this dataset. Do not use this feature if \"##ASSET_NAME##\" includes sensitive data.').replaceAll('##ASSET_NAME##', this.props.asset.name),
        labels: {ok: t('Acknowledge and continue'), cancel: t('Cancel')},
        onok: (evt, value) => {
          actions.dataShare.toggleDataSharing(this.props.asset.uid, data);
          dialog.destroy();
        },
        oncancel: () => {
          dialog.destroy();
        }
      };
      dialog.set(opts).show();
    } else {
      actions.dataShare.toggleDataSharing(this.props.asset.uid, data);
    }
  }

  /*
   * Utilities
   */

  generateAutoname(newParent) {
    if (newParent) {
      let autoname = newParent.name;
      autoname = autoname.toLowerCase().substring(0, MAX_DISPLAYED_STRING_LENGTH).replace(/(\ |\.)/g, '_');
      this.setState({newFilename: autoname});
    }
  }
  generateFilteredAssetList() {
    let attachedParentUids = [];
    this.state.attachedParents.forEach((item) => {
      attachedParentUids.push(item.parentUid)
    });

    // Filter displayed asset list based on unattached projects
    return (
      this.state.sharingEnabledAssets.results.filter(
        item => !attachedParentUids.includes(item.uid)
      )
    );
  }

  /*
   * Rendering
   */

  renderLoading(message = t('loading…')) {
    return (
      <bem.Loading>
        <bem.Loading__inner>

          {message}
        </bem.Loading__inner>
      </bem.Loading>
    );
  }
  renderSwitchLabel() {
    if (this.state.isShared) {
      return (
        <ToggleSwitch
          onChange={this.toggleSharingData.bind(this)}
          label={t('Data sharing enabled')}
          checked={this.state.isShared}
        />
      );
    } else {
      return (
        <ToggleSwitch
          onChange={this.toggleSharingData.bind(this)}
          label={t('Data sharing disabled')}
          checked={this.state.isShared}
        />
      );
    }
  }
  renderSelect(sharingEnabledAssets) {
    const selectClassNames = ['kobo-select__wrapper'];
    if (this.state.fieldsErrors.emptyParent || this.state.fieldsErrors.parent) {
      selectClassNames.push('kobo-select__wrapper--error');
    }
    return(

        <Select
          placeholder={t('Select a different project to import data from')}
          options={sharingEnabledAssets}
          value={this.state.newParent}
          isLoading={(!this.state.isInitialised || this.state.isLoading || !sharingEnabledAssets)}
          getOptionLabel={option => option.name}
          getOptionValue={option => option.url}
          noOptionsMessage={() => {return t('No projects to connect')}}
          onChange={this.onParentChange}
          className='kobo-select'
          classNamePrefix='kobo-select'
        />

          {this.state.fieldsErrors.emptyParent || this.state.fieldsErrors.parent}

    );
  }

  render() {
    let sharingEnabledAssets = [];
    if (this.state.sharingEnabledAssets !== null) {
      sharingEnabledAssets = this.generateFilteredAssetList();
    }

    return (
      <bem.FormModal__form
        className='project-settings project-settings--upload-file connect-projects'
        onSubmit={this.confirmAttachment}
      >
        {/* Enable data sharing */}
        <bem.FormModal__item m='data-sharing'>

{t('Share data with other project forms')}

            {t('Enable data sharing to allow other forms to import and use dynamic data from this project. Learn more about dynamic data attachments')}
            {t(' ' + 'here')}

          {this.renderSwitchLabel()}
        </bem.FormModal__item>

        {/* Attach other projects data */}
        <bem.FormModal__item m='import-data'>

{t('Import other project data')}

            {t('Connect with other project(s) to import dynamic data from them into this project. Learn more about dynamic data attachments')}
            {t(' ' + 'here')}

          {/* Selecting project form*/}
          {sharingEnabledAssets &&

              {this.renderSelect(sharingEnabledAssets)}
              <TextBox
                placeholder={t('Give a unique name to the import')}
                value={this.state.newFilename}
                onChange={this.onFilenameChange}
                errors={this.state.fieldsErrors.emptyFilename ||
                        this.state.fieldsErrors.filename}
              />
              <bem.KoboButton m='blue'>
                {t('Import')}
              </bem.KoboButton>

          }

          {/* Display attached projects */}

            {t('Imported')}
            {(!this.state.isInitialised || this.state.isLoading) &&

                {this.renderLoading(t('Loading imported projects'))}

            }
            {!this.state.isLoading && this.state.attachedParents.length == 0 &&

                {t('No data imported')}

            }
            {!this.state.isLoading && this.state.attachedParents.length > 0 &&
                this.state.attachedParents.map((item, n) => {
                  return (

                          {truncateFile(item.filename, MAX_DISPLAYED_STRING_LENGTH)}

                          {truncateString(item.parentName, MAX_DISPLAYED_STRING_LENGTH)}

                       this.removeAttachment(item.attachmentUrl)}
                      />

                  );
                })
            }

        </bem.FormModal__item>

      </bem.FormModal__form>
    );
  }
}

export default ConnectProjects;
<|endoftext|>


<commit_before>var React = require('react');
var SurveyActions = require('../actions/SurveyActions');
var SurveyStore = require('../stores/SurveyStore');

var Controls = React.createClass({
    handleDownload() {
        var data = JSON.stringify({survey: SurveyStore.getSurveyData()}, null, 2);
        var url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(data);
        var link = this.refs.link.getDOMNode('link');
        link.href = url;
    },
    handleSave() {
        var title = prompt("Enter a title for the survey");
        if (title === null) {
            return;
        } else if (title.trim().length > 0) {
            SurveyActions.saveSurvey(title);
        } else {
            alert("Please enter a title");
        }
    },
    handleLoad() {
        SurveyActions.toggleLoadModal();
    },
    handleClear() {
        var choice = confirm("This will clear the survey and start a new one. " +
                             "Are you sure you want to proceed?");
        if (choice) {
            SurveyActions.clearSurvey();
        }
    },
    render() {
        return (

                  Survey Builder Area

Pallet

Survey Actions

                      <button type="button" className="btn btn-default" onClick={this.handleClear}>
                           New
                      </button>
                      <button type="button" className="btn btn-default" onClick={this.handleSave}>
                           Save
                      </button>
                      <button type="button" className="btn btn-default" onClick={this.handleLoad}>
                           Load
                      </button>
                        Download

        );
    }
});

module.exports = Controls;
<commit_msg>added control to run static anaylses; not currently working<commit_after>var React = require('react');
var SurveyActions = require('../actions/SurveyActions');
var SurveyStore = require('../stores/SurveyStore');

var Controls = React.createClass({
  handleDownload() {
    var data = JSON.stringify({survey: SurveyStore.getSurveyData()}, null, 2);
    var url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(data);
    var link = this.refs.link.getDOMNode('link');
    link.href = url;
  },
  handleSave() {
    var title = prompt("Enter a title for the survey");
    if (title === null) {
      return;
    } else if (title.trim().length > 0) {
      SurveyActions.saveSurvey(title);
    } else {
      alert("Please enter a title");
    }
  },
  handleLoad() {
    SurveyActions.toggleLoadModal();
  },
  handleClear() {
    var choice = confirm("This will clear the survey and start a new one. " +
        "Are you sure you want to proceed?");
    if (choice) {
      SurveyActions.clearSurvey();
    }
  },
  handleStaticAnalysis() {
    let xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = () => console.log(xhttp.responseText);
    xhttp.open("POST", "http://codemonkey.cs.umass.edu:1234/analyze", false);
    xhttp.setRequestHeader("Content-Type", "application/json");
    xhttp.onload = () => {
      if (xhttp.status >= 200 && xhttp.status < 400) {
        console.log(xhttp.responseText);
      } else {
        console.log('something went wrong');
      }
    };
    let data = JSON.stringify(SurveyStore.getSurveyData());
    console.log(data);
    xhttp.send(data);
  },
  render() {
    return (

            Survey Builder Area

Pallet

Survey Actions

              <button type="button" className="btn btn-default" onClick={this.handleClear}>
                 New
              </button>
              <button type="button" className="btn btn-default" onClick={this.handleSave}>
                 Save
              </button>
              <button type="button" className="btn btn-default" onClick={this.handleLoad}>
                 Load
              </button>

                 Download

              <button className="btn btn-default" onClick={this.handleStaticAnalysis}>Run Static Analysis</button>

    );
  }
});

module.exports = Controls;
<|endoftext|>


<commit_before>function QuestionLibraryDirective($restApi) {
    return {
        templateUrl: staticFilesUri + 'templates/QuestionLibrary.Directive.Template.html',
        scope: {
            clickHandler: '&',
            currentItem: '='
        },
        link: function (scope, element) {
            var sort_ul = element.find('ul');

            $restApi.create_question_api(scope).list();
            scope.handle_click = function (item) {
                scope.clickHandler({ item: item});
            };
            scope.hide_library_popup = function () {
                scope.$parent.displayQlib = false;
            };

            scope.set_item = function (item) {
                scope.currentItem = item;
            };

            sort_ul.sortable({
                items: "> li",
                connectWith: ".survey-editor__list"
            });
        }
    };
}<commit_msg>#975 - Questions being dragged from question library 'disappear' while being dragged<commit_after>function QuestionLibraryDirective($restApi) {
    return {
        templateUrl: staticFilesUri + 'templates/QuestionLibrary.Directive.Template.html',
        scope: {
            clickHandler: '&',
            currentItem: '='
        },
        link: function (scope, element) {
            var sort_ul = element.find('ul');

            $restApi.create_question_api(scope).list();
            scope.handle_click = function (item) {
                scope.clickHandler({ item: item});
            };
            scope.hide_library_popup = function () {
                scope.$parent.displayQlib = false;
            };

            scope.set_item = function (item) {
                scope.currentItem = item;
            };

            sort_ul.sortable({
                items: "> li",
                connectWith: ".survey-editor__list",
                helper: 'clone',
                start: function () {
                    sort_ul.find('li:hidden').show();
                }
            });
        }
    };
}<|endoftext|>


<commit_before>let Promise
try {
  Promise = require('bluebird')
} catch (err) {
  Promise = global.Promise
}

const { requireRecursive, Collection } = require('../util')

/** Resolver manager for resolving usages */
class Resolver extends Collection {
  /**
   * Creates a new Resolver instance
   * @arg {Client} client Client instance
   */
  constructor (client) {
    super()
    this._client = client
  }

  /**
   * Resolver function
   * @typedef {Object} ResolverObject
   * @prop {String} type Resolver type
   * @prop {Promise(Container)} resolve Promise that takes in {@link Container} as an argument and resolves a result
   */

  /**
   * Loads {@link ResolverObject|ResolverObjects} from a file path
   * @arg {String} path File path to load resolvers from
   * @returns {Promise<ResolverObject>}
   */
  loadResolvers (path) {
    const resolvers = requireRecursive(path)
    for (const name in resolvers) {
      const resolver = resolvers[name]
      if (!resolver.resolve || !resolver.type) continue
      this.set(resolver.type, resolver)
    }
    return resolvers
  }

  /**
   * Loads a command usage locally
   * @arg {CommandUsage} usage CommandUsage object to be loaded
   */
  load (data) {
    this.usage = this.verify(data)
  }

  /**
   * Verifies a {@link CommandUsage}
   * @arg {CommandUsage} usage CommandUsage object to be verified
   * @returns CommandUsage
   */
  verify (usage) {
    /**
     * An object documenting the requirements of a command argument
     * @namespace CommandUsage
     * @type {Object}
     */
    return (Array.isArray(usage) ? usage : [usage]).map(entry => {
      /**
       * The name of the argument
       * @type {String}
       * @memberof CommandUsage
       * @name name
       */
      if (!entry.name) {
        throw new Error('Argument specified in usage has no name')
      }
      /**
       * Single allowed argument type to be resolved
       * @type {String}
       * @memberof CommandUsage
       * @name type
       * @see {@link ResolverObject}
       */

      /**
       * Multiple allowed argument types to be resolved
       * @type {Array}
       * @memberof CommandUsage
       * @name types
       * @see {@link ResolverObject}
       */
      if (!entry.types) entry.types = [ entry.type || 'string' ]

      /**
       * The display name of the argument, to be used when displaying argument info
       * @type {?String}
       * @memberof CommandUsage
       * @name displayName
       */
      if (!entry.displayName) entry.displayName = entry.name
      return entry
    })
  }

  /**
   * Resolves a message
   * @arg {external:"Eris.Message"} message Eris message
   * @arg {String[]} args Array of strings, by default a message split by spaces, without command trigger and prefix
   * @arg {Object} data Additional data
   * @arg {String} data.prefix The client's prefix
   * @arg {String} data.command The command trigger
   * @arg {CommandUsage[]} [usage=this.usage] Array of CommandUsage objects
   * @returns {Promise<Object>}
   */
  resolve (message, rawArgs, data, rawUsage = this.usage) {
    let args = {}

    const usage = this.verify(rawUsage)
    if (!usage.length) return Promise.resolve(args)

    const argsCount = rawArgs.length
    const requiredArgs = usage.filter(arg => !arg.optional).length
    const optionalArgs = argsCount - requiredArgs

    if (argsCount < requiredArgs) {
      return Promise.reject({
        message: 'INSUFFICIENT_ARGS',
        requiredArgs: `**${requiredArgs}**`,
        argsCount: `**${argsCount}**.`
      })
    }

    let idx = 0
    let optArgs = 0
    let resolves = []
    let skip = false
    for (const arg of usage) {
      let rawArg = rawArgs[idx]
      if (arg.last) {
        rawArg = rawArgs.slice(idx).join(' ')
        skip = true
      } else {
        if (arg.optional) {
          if (optionalArgs > optArgs) {
            optArgs++
          } else {
            if (arg.default) args[arg.name] = arg.default
            continue
          }
        }
        if (typeof rawArg !== 'undefined') {
          if (rawArg.startsWith('"')) {
            const endQuote = rawArgs.findIndex((str, i) => str.endsWith('"') && i >= idx)
            if (endQuote > -1) {
              rawArg = rawArgs.slice(idx, endQuote + 1).join(' ').replace(/"/g, '')
              idx = endQuote
            } else {
              return Promise.reject({ message: 'NO_END_QUOTE' })
            }
          }
        }
        idx++
      }
      resolves.push(this._resolveArg(arg, rawArg, message, data).then(res => {
        args[arg.name] = res
        return res
      }))
      if (skip) break
    }
    return Promise.all(resolves).then(() => args)
  }

  _resolveArg (arg, rawArg, message, data) {
    const resolves = arg.types.map(type => {
      const resolver = this.get(type)
      if (!resolver) {
        return Promise.resolve({ err: 'Invalid resolver type' })
      }
      return resolver.resolve(rawArg, arg, message, this._client)
      .catch(err => Object.assign(arg, {
        arg: `**\`${arg.name || 'argument'}\`**`,
        err: err
      }))
    })
    return Promise.all(resolves).then(results => {
      const resolved = results.filter(v => !v.err)
      if (resolved.length) {
        const res = resolved.length === 1 ? resolved[0] : resolved.reduce((arr, c) => arr.concat(c), [])
        return res
      }
      let err = results[0].err
      if (err instanceof Error) {
        return Promise.reject({ message: 'PARSING_ERROR', err: err })
      }
      return Promise.reject({ message: err, arg: `**\`${arg.name || 'argument'}\`**` })
    })
  }

  /**
   * Gets a command usage
   * @arg {CommandUsage[]} [usage=this.usage] Array of CommandUsage objects
   * @arg {Object} data Additional data
   * @arg {String} data.prefix The client's prefix
   * @arg {String} data.command The command trigger
   * @returns {String}
   */
  getUsage (usage = this.usage, { prefix, command } = {}) {
    const argsUsage = usage.map(arg =>
      arg.last ? arg.displayName : arg.optional ? `[${arg.displayName}]` : `<${arg.displayName}>`
    ).join(' ')
    return `${prefix}${command}` + (usage.length ? ' ' + argsUsage : '')
  }
}

module.exports = Resolver
<commit_msg>Add {{usage}} when resolver rejects<commit_after>let Promise
try {
  Promise = require('bluebird')
} catch (err) {
  Promise = global.Promise
}

const { requireRecursive, Collection } = require('../util')

/** Resolver manager for resolving usages */
class Resolver extends Collection {
  /**
   * Creates a new Resolver instance
   * @arg {Client} client Client instance
   */
  constructor (client) {
    super()
    this._client = client
  }

  /**
   * Resolver function
   * @typedef {Object} ResolverObject
   * @prop {String} type Resolver type
   * @prop {Promise(Container)} resolve Promise that takes in {@link Container} as an argument and resolves a result
   */

  /**
   * Loads {@link ResolverObject|ResolverObjects} from a file path
   * @arg {String} path File path to load resolvers from
   * @returns {Promise<ResolverObject>}
   */
  loadResolvers (path) {
    const resolvers = requireRecursive(path)
    for (const name in resolvers) {
      const resolver = resolvers[name]
      if (!resolver.resolve || !resolver.type) continue
      this.set(resolver.type, resolver)
    }
    return resolvers
  }

  /**
   * Loads a command usage locally
   * @arg {CommandUsage} usage CommandUsage object to be loaded
   */
  load (data) {
    this.usage = this.verify(data)
  }

  /**
   * Verifies a {@link CommandUsage}
   * @arg {CommandUsage} usage CommandUsage object to be verified
   * @returns CommandUsage
   */
  verify (usage) {
    /**
     * An object documenting the requirements of a command argument
     * @namespace CommandUsage
     * @type {Object}
     */
    return (Array.isArray(usage) ? usage : [usage]).map(entry => {
      /**
       * The name of the argument
       * @type {String}
       * @memberof CommandUsage
       * @name name
       */
      if (!entry.name) {
        throw new Error('Argument specified in usage has no name')
      }
      /**
       * Single allowed argument type to be resolved
       * @type {String}
       * @memberof CommandUsage
       * @name type
       * @see {@link ResolverObject}
       */

      /**
       * Multiple allowed argument types to be resolved
       * @type {Array}
       * @memberof CommandUsage
       * @name types
       * @see {@link ResolverObject}
       */
      if (!entry.types) entry.types = [ entry.type || 'string' ]

      /**
       * The display name of the argument, to be used when displaying argument info
       * @type {?String}
       * @memberof CommandUsage
       * @name displayName
       */
      if (!entry.displayName) entry.displayName = entry.name
      return entry
    })
  }

  /**
   * Resolves a message
   * @arg {external:"Eris.Message"} message Eris message
   * @arg {String[]} args Array of strings, by default a message split by spaces, without command trigger and prefix
   * @arg {Object} data Additional data
   * @arg {String} data.prefix The client's prefix
   * @arg {String} data.command The command trigger
   * @arg {CommandUsage[]} [usage=this.usage] Array of CommandUsage objects
   * @returns {Promise<Object>}
   */
  resolve (message, rawArgs, data, rawUsage = this.usage) {
    let args = {}

    const usage = this.verify(rawUsage)
    if (!usage.length) return Promise.resolve(args)

    const argsCount = rawArgs.length
    const requiredArgs = usage.filter(arg => !arg.optional).length
    const optionalArgs = argsCount - requiredArgs

    if (argsCount < requiredArgs) {
      return Promise.reject({
        message: 'INSUFFICIENT_ARGS',
        requiredArgs: `**${requiredArgs}**`,
        argsCount: `**${argsCount}**.`,
        usage: this.getUsage(usage, data)
      })
    }

    let idx = 0
    let optArgs = 0
    let resolves = []
    let skip = false
    for (const arg of usage) {
      let rawArg = rawArgs[idx]
      if (arg.last) {
        rawArg = rawArgs.slice(idx).join(' ')
        skip = true
      } else {
        if (arg.optional) {
          if (optionalArgs > optArgs) {
            optArgs++
          } else {
            if (arg.default) args[arg.name] = arg.default
            continue
          }
        }
        if (typeof rawArg !== 'undefined') {
          if (rawArg.startsWith('"')) {
            const endQuote = rawArgs.findIndex((str, i) => str.endsWith('"') && i >= idx)
            if (endQuote > -1) {
              rawArg = rawArgs.slice(idx, endQuote + 1).join(' ').replace(/"/g, '')
              idx = endQuote
            } else {
              return Promise.reject({ message: 'NO_END_QUOTE' })
            }
          }
        }
        idx++
      }
      resolves.push(this._resolveArg(arg, rawArg, message, data, usage).then(res => {
        args[arg.name] = res
        return res
      }))
      if (skip) break
    }
    return Promise.all(resolves).then(() => args)
  }

  _resolveArg (arg, rawArg, message, data, usage) {
    const resolves = arg.types.map(type => {
      const resolver = this.get(type)
      if (!resolver) {
        return Promise.resolve({ err: 'Invalid resolver type' })
      }
      return resolver.resolve(rawArg, arg, message, this._client)
      .catch(err => Object.assign(arg, {
        arg: `**\`${arg.name || 'argument'}\`**`,
        err: err
      }))
    })
    return Promise.all(resolves).then(results => {
      const resolved = results.filter(v => !v.err)
      if (resolved.length) {
        const res = resolved.length === 1 ? resolved[0] : resolved.reduce((arr, c) => arr.concat(c), [])
        return res
      }
      let err = results[0].err
      if (err instanceof Error) {
        return Promise.reject({ message: 'PARSING_ERROR', err: err })
      }
      return Promise.reject({
        message: err,
        arg: `**\`${arg.name || 'argument'}\`**`,
        usage: this.getUsage(usage, data)
      })
    })
  }

  /**
   * Gets a command usage
   * @arg {CommandUsage[]} [usage=this.usage] Array of CommandUsage objects
   * @arg {Object} data Additional data
   * @arg {String} data.prefix The client's prefix
   * @arg {String} data.command The command trigger
   * @returns {String}
   */
  getUsage (usage = this.usage, { prefix, command } = {}) {
    const argsUsage = usage.map(arg =>
      arg.last ? arg.displayName : arg.optional ? `[${arg.displayName}]` : `<${arg.displayName}>`
    ).join(' ')
    return `${prefix}${command}` + (usage.length ? ' ' + argsUsage : '')
  }
}

module.exports = Resolver
<|endoftext|>


<commit_before>/** @jsx React.DOM */
define([
    'underscore', 'jquery', 'react', 'kendo',
    '../util/debug',
    '../ControlCommon'
], function (_, $, React, kendo, debug, controlCommon) {
    'use strict';

    void controlCommon;

    var QUERY_PARAM = 'nameOrEmail';
    var LIST_CLASS = 'userPicker';

    var UserPicker = React.createClass({

        fieldClass: 'formFieldAutocomplete',

        getDefaultProps: function () {
            return {
                value: undefined,
                onChange: function () {},
                id: undefined,
                placeholder: 'Select User...', // l10n requires thought, no strings down here
                template: '

#: fullName #

#: emailAddress #

',
                disabled: false,
                isValid: [true, ''],
                readonly: false,
                noControl: false
            };
        },

        componentWillMount: function () {
            debug.verify(this.props.dataSource);
        },

        componentDidMount: function (rootNode) {
            var $el = $(rootNode);
            debug.verify($el);

            var onChange = this.props.onChange,
                header = $('

'),
                autoComplete;

            $el.kendoAutoComplete({
                dataSource: this.props.dataSource,
                dataTextField: QUERY_PARAM,
                placeholder: this.props.placeholder,
                highlightFirst: true,
                suggest: false,
                template: this.props.template,
                close: function (e) {
                    var widget = e.sender;
                    // Don't close the picker on an empty search result because we want to tell the user about it.
                    if (widget._typing && widget.dataSource.total() === 0) {
                        e.preventDefault();
                    }
                },
                dataBound: function (e) {
                    var widget = e.sender,
                        dataSource = widget.dataSource;

                    if (dataSource.total() === 0) {
                        header.text('No matches');
                        widget.popup.open();
                    }
                    else if (dataSource.total() < 0) {
                        header.text('All Users (100+ matches)');
                    }
                    else {
                        header.text(kendo.format('All Users ({0:n0} matches)', dataSource.total()));
                    }
                },
                change: function (e) {
                    var widget = e.sender;

                    if (widget.dataSource.total() === 1) {
                        // Exact match - so raise the change event
                        onChange(widget.dataItem(0));
                    }
                },
                select: function (e) {
                    onChange(this.dataItem(e.item.index()));
                }
            }).on('blur', this.onBlur);

            autoComplete = $el.data('kendoAutoComplete');
            // Add a special class to the popup element so we can style the list items
            autoComplete.list.addClass(LIST_CLASS);

            if (this.props.value) {
                autoComplete.value(this.props.value.fullName);
            }
            if (this.props.disabled) {
                autoComplete.enable(false);
            }
            else if (this.props.readonly) {
                autoComplete.readonly(true);
            }
            // Add an element to contain some text above the list of users
            header.prependTo(autoComplete.list);
        },

        componentWillUnmount: function () {
            var $el = $(this.getDOMNode());

            $el.data('kendoAutoComplete').destroy();
        },

        componentDidUpdate: function (prevProps, prevState, rootNode) {
            var $el = $(rootNode),
                autoComplete = $el.data('kendoAutoComplete');

            autoComplete.value(this.props.value ? this.props.value.fullName : null);

            if (this.props.disabled) {
                autoComplete.enable(false);
            }
            else {
                autoComplete.readonly(this.props.readonly);
            }
        },

        onBlur: function (e) {
            var autoComplete = $(e.target).data('kendoAutoComplete');

            if (autoComplete) {
                // Always reset the text back to the current value, in case they typed stuff that didn't match anything
                autoComplete.value(this.props.value ? this.props.value.fullName : null);
            }
        },

        /*jshint ignore:start */
        render: function () {
            debug.verify(!this.props.noControl);
            return (<input type="text" id={this.props.id} className="k-textbox"/>);
        }
        /*jshint ignore:end */
    });

    return UserPicker;

});
<commit_msg>Change UserPicker to accept a dataTextField prop<commit_after>/** @jsx React.DOM */
define([
    'underscore', 'jquery', 'react', 'kendo',
    '../util/debug',
    '../ControlCommon'
], function (_, $, React, kendo, debug, controlCommon) {
    'use strict';

    void controlCommon;

    var LIST_CLASS = 'userPicker';

    var UserPicker = React.createClass({

        fieldClass: 'formFieldAutocomplete',

        getDefaultProps: function () {
            return {
                value: undefined,
                onChange: function () {},
                id: undefined,
                placeholder: 'Select User...', // l10n requires thought, no strings down here
                template: '

#: fullName #

#: emailAddress #

',
                dataTextField: 'nameOrEmail',
                disabled: false,
                isValid: [true, ''],
                readonly: false,
                noControl: false
            };
        },

        componentWillMount: function () {
            debug.verify(this.props.dataSource);
        },

        componentDidMount: function (rootNode) {
            var $el = $(rootNode);
            debug.verify($el);

            var onChange = this.props.onChange,
                header = $('

'),
                autoComplete;

            $el.kendoAutoComplete({
                dataSource: this.props.dataSource,
                dataTextField: this.props.dataTextField,
                placeholder: this.props.placeholder,
                highlightFirst: true,
                suggest: false,
                template: this.props.template,
                close: function (e) {
                    var widget = e.sender;
                    // Don't close the picker on an empty search result because we want to tell the user about it.
                    if (widget._typing && widget.dataSource.total() === 0) {
                        e.preventDefault();
                    }
                },
                dataBound: function (e) {
                    var widget = e.sender,
                        dataSource = widget.dataSource;

                    if (dataSource.total() === 0) {
                        header.text('No matches');
                        widget.popup.open();
                    }
                    else if (dataSource.total() < 0) {
                        header.text('All Users (100+ matches)');
                    }
                    else {
                        header.text(kendo.format('All Users ({0:n0} matches)', dataSource.total()));
                    }
                },
                change: function (e) {
                    var widget = e.sender;

                    if (widget.dataSource.total() === 1) {
                        // Exact match - so raise the change event
                        onChange(widget.dataItem(0));
                    }
                },
                select: function (e) {
                    onChange(this.dataItem(e.item.index()));
                }
            }).on('blur', this.onBlur);

            autoComplete = $el.data('kendoAutoComplete');
            // Add a special class to the popup element so we can style the list items
            autoComplete.list.addClass(LIST_CLASS);

            if (this.props.value) {
                autoComplete.value(this.props.value.fullName);
            }
            if (this.props.disabled) {
                autoComplete.enable(false);
            }
            else if (this.props.readonly) {
                autoComplete.readonly(true);
            }
            // Add an element to contain some text above the list of users
            header.prependTo(autoComplete.list);
        },

        componentWillUnmount: function () {
            var $el = $(this.getDOMNode());

            $el.data('kendoAutoComplete').destroy();
        },

        componentDidUpdate: function (prevProps, prevState, rootNode) {
            var $el = $(rootNode),
                autoComplete = $el.data('kendoAutoComplete');

            autoComplete.value(this.props.value ? this.props.value.fullName : null);

            if (this.props.disabled) {
                autoComplete.enable(false);
            }
            else {
                autoComplete.readonly(this.props.readonly);
            }
        },

        onBlur: function (e) {
            var autoComplete = $(e.target).data('kendoAutoComplete');

            if (autoComplete) {
                // Always reset the text back to the current value, in case they typed stuff that didn't match anything
                autoComplete.value(this.props.value ? this.props.value.fullName : null);
            }
        },

        /*jshint ignore:start */
        render: function () {
            debug.verify(!this.props.noControl);
            return (<input type="text" id={this.props.id} className="k-textbox"/>);
        }
        /*jshint ignore:end */
    });

    return UserPicker;

});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import styled from 'react-emotion';
import { Link } from 'react-router-dom';

import { ShieldIcon, InfoIcon, TrashcanIcon } from '@voussoir/icons';
import { colors } from '@voussoir/ui/src/theme';
import { Button } from '@voussoir/ui/src/primitives/buttons';
import { CheckboxPrimitive } from '@voussoir/ui/src/primitives/forms';
import { A11yText } from '@voussoir/ui/src/primitives/typography';
import DeleteItemModal from './DeleteItemModal';

// This import is loaded by the @voussoir/field-views-loader loader.
// It imports all the views required for a keystone app by looking at the adminMetaData
import FieldTypes from '../FIELD_TYPES';

// Styled Components
const Table = styled('table')({
  borderCollapse: 'collapse',
  borderSpacing: 0,
  tableLayout: 'fixed',
  width: '100%',
});
const HeaderCell = styled('td')({
  borderBottom: `2px solid ${colors.N10}`,
  color: '#999',
  display: 'table-cell',
  fontWeight: 'normal',
  paddingBottom: '8px',
  textAlign: 'left',
  verticalAlign: 'bottom',
});
const BodyCell = styled('td')(({ isSelected }) => ({
  backgroundColor: isSelected ? colors.B.L90 : null,
  boxShadow: isSelected
    ? `0 1px 0 ${colors.B.L75}, 0 -1px 0 ${colors.B.L75}`
    : `0 -1px 0 ${colors.N10}`,
  padding: '8px 0',
  position: 'relative',
  fontSize: 15,
}));
const ItemLink = styled(Link)`
  color: ${colors.text};

  /* Increase hittable area on item link */
  &::before {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    top: 0;
  }
`;

const NoResults = ({ children, ...props }) => (

    <InfoIcon css={{ height: 48, width: 48, marginBottom: '0.5em' }} />
    {children}

);

const SortDirectionArrow = styled.span(({ size = '0.2em', rotate = '0deg' }) => ({
  borderLeft: `${size} solid transparent`,
  borderRight: `${size} solid transparent`,
  borderTop: `${size} solid`,
  display: 'inline-block',
  height: 0,
  marginLeft: '0.33em',
  marginTop: '-0.125em',
  verticalAlign: 'middle',
  width: 0,
  transform: `rotate(${rotate})`,
}));

// Functional Components

type SortLinkProps = {
  handleSortChange: (Event, String) => void,
  active: boolean,
  sortAscending: boolean,
};

class SortLink extends React.Component<SortLinkProps> {
  constructor(props) {
    super(props);
    const { handleSortChange } = this.props;
    this.handleSortChange = handleSortChange;
  }

  onClick = event => {
    this.handleSortChange(event, this.props.label);
  };

  render() {
    const styles = {
      borderBottom: `2px solid ${colors.N10}`,
      display: 'table-cell',
      fontWeight: 'normal',
      paddingBottom: '8px',
      textAlign: 'left',
      verticalAlign: 'bottom',
      color: this.props.active ? '#000' : '#999',
    };

    return (

        {this.props.label}
        <SortDirectionArrow
          rotate={this.props.active && !this.props.sortAscending ? '180deg' : '0deg'}
        />

    );
  }
}

class ListDisplayRow extends Component {
  static defaultProps = {
    itemErrors: {},
  };

  state = {
    showDeleteModal: false,
  };
  componentDidMount() {
    this.mounted = true;
  }
  componentWillUnmount() {
    this.mounted = false;
  }
  showDeleteModal = () => {
    this.setState({ showDeleteModal: true });
  };
  closeDeleteModal = () => {
    this.setState({ showDeleteModal: false });
  };
  onDelete = result => {
    if (this.props.onDelete) this.props.onDelete(result);
    if (!this.mounted) return;
    this.setState({ showDeleteModal: false });
  };
  renderDeleteModal() {
    const { showDeleteModal } = this.state;
    const { item, list } = this.props;

    return (
      <DeleteItemModal
        isOpen={showDeleteModal}
        item={item}
        list={list}
        onClose={this.closeDeleteModal}
        onDelete={this.onDelete}
      />
    );
  }
  render() {
    const { list, link, item, itemErrors, fields } = this.props;

    return (

        <BodyCell>
          <Button
            appearance="warning"
            onClick={this.showDeleteModal}
            spacing="cozy"
            variant="subtle"
            style={{ height: 24 }}
          >
            <TrashcanIcon />
            <A11yText>Remove</A11yText>
          </Button>
          {this.renderDeleteModal()}
        </BodyCell>
        {fields.map((field, index) => {
          const { path } = field;

          const isLoading = !item.hasOwnProperty(path);
          if (isLoading) {
            // TODO: Better loading state?
            return <BodyCell key={path} />;
          }

          if (itemErrors[path] instanceof Error && itemErrors[path].name === 'AccessDeniedError') {
            return (
              <BodyCell key={path}>
                <ShieldIcon title={itemErrors[path].message} css={{ color: colors.N10 }} />
                <A11yText>{itemErrors[path].message}</A11yText>
              </BodyCell>
            );
          }

          let content;

          const Cell = FieldTypes[list.key][path].Cell;

          if (Cell) {
            const LinkComponent = ({ children, ...data }) => (
              <ItemLink to={link(data)}>{children}</ItemLink>
            );
            content = <Cell list={list} data={item[path]} field={field} Link={LinkComponent} />;
          } else {
            content = item[path];
          }

          return (
            <BodyCell key={path}>
              {!index ? (
                <ItemLink to={link({ path: list.path, id: item.id })}>{content}</ItemLink>
              ) : (
                content
              )}
            </BodyCell>
          );
        })}

    );
  }
}

function isKeyboardEvent(e) {
  return e.clientX === 0 && e.clientY === 0;
}

class ListManageRow extends Component {
  onMouseDown = e => {
    // bail when MouseClick on the actual input, which calls onClick twice
    if (e.target.nodeName === 'INPUT' && !isKeyboardEvent(e)) return;

    // trigger onClick with the current ID
    const { isSelected, item, onSelect, onSelectStart } = this.props;
    onSelectStart(!isSelected);
    onSelect([item.id]);
  };
  onMouseUp = () => {
    // make keyboard selection easier following a mouse select
    this.checkbox.focus();
  };
  onMouseEnter = () => {
    const { isSelected, item, selectOnEnter, onSelect } = this.props;
    if (
      (selectOnEnter === 'select' && !isSelected) ||
      (selectOnEnter === 'deselect' && isSelected)
    ) {
      onSelect([item.id]);
    }
  };
  onCheckboxChange = () => {
    const { item, onSelect } = this.props;
    onSelect([item.id]);
  };
  onCheckboxMouseDown = e => {
    e.stopPropagation();
  };
  getCheckbox = ref => {
    this.checkbox = ref;
  };
  render() {
    const { fields, isSelected, item } = this.props;

    return (

        <BodyCell isSelected={isSelected} key="checkbox">
          <CheckboxPrimitive
            checked={isSelected}
            innerRef={this.getCheckbox}
            value={item.id}
            onChange={this.onCheckboxChange}
            onMouseDown={this.onCheckboxMouseDown}
            css={{ marginLeft: 1 }}
            tabIndex="0"
          />
        </BodyCell>
        {fields.map(({ path }) => (
          <BodyCell isSelected={isSelected} key={path}>
            {item[path]}
          </BodyCell>
        ))}

    );
  }
}

export default class ListTable extends Component {
  state = {
    mouseOverSelectsRow: false,
    sortBy: null,
    sortAscending: true,
  };

  handleSelectAll = () => {
    const { items, onSelectAll, selectedItems } = this.props;
    const allSelected = items.length === selectedItems.length;
    const value = allSelected ? [] : items.map(i => i.id);
    onSelectAll(value);
  };

  handleSortChange = (event, field) => {
    // only change sort direction if sortBy is not being modified
    if (this.state.sortBy === field) {
      this.setState({
        sortAscending: !this.state.sortAscending,
      });
    } else {
      this.setState({
        sortAscending: true,
        sortBy: field,
      });
    }
  };

  onSelectStart = select => {
    const { isManaging } = this.props;
    if (!isManaging) return;
    this.setState({
      mouseOverSelectsRow: select ? 'select' : 'deselect',
    });
  };
  stopRowSelectOnEnter = () => {
    const { mouseOverSelectsRow } = this.state;
    if (mouseOverSelectsRow) {
      this.setState({ mouseOverSelectsRow: false });
    }
  };
  render() {
    const {
      adminPath,
      fields,
      isManaging,
      items,
      itemsErrors = [],
      list,
      noResultsMessage,
      onChange,
      onSelect,
      selectedItems,
    } = this.props;
    const { mouseOverSelectsRow } = this.state;

    return items.length ? (

        <colgroup>
          <col width="32" />
        </colgroup>

            <HeaderCell>

                <CheckboxPrimitive
                  checked={items.length === selectedItems.length}
                  onChange={this.handleSelectAll}
                  tabIndex="0"
                />

            </HeaderCell>
            {fields.map(field => (
              <SortLink
                key={field.path}
                label={field.label}
                handleSortChange={this.handleSortChange}
                active={this.state.sortBy == field.label}
                sortAscending={this.state.sortAscending}
              />
            ))}

          {items.map(
            (item, itemIndex) =>
              isManaging ? (
                <ListManageRow
                  fields={fields}
                  item={item}
                  key={item.id}
                  list={list}
                  isSelected={selectedItems.includes(item.id)}
                  selectOnEnter={mouseOverSelectsRow}
                  onSelect={onSelect}
                  onSelectStart={this.onSelectStart}
                />
              ) : (
                <ListDisplayRow
                  fields={fields}
                  item={item}
                  itemErrors={itemsErrors[itemIndex] || {}}
                  key={item.id}
                  link={({ path, id }) => `${adminPath}/${path}/${id}`}
                  list={list}
                  onDelete={onChange}
                />
              )
          )}

    ) : (
      <NoResults>{noResultsMessage}</NoResults>
    );
  }
}
<commit_msg>Minor cleanups<commit_after>import React, { Component } from 'react';
import styled from 'react-emotion';
import { Link } from 'react-router-dom';

import { ShieldIcon, InfoIcon, TrashcanIcon } from '@voussoir/icons';
import { colors } from '@voussoir/ui/src/theme';
import { Button } from '@voussoir/ui/src/primitives/buttons';
import { CheckboxPrimitive } from '@voussoir/ui/src/primitives/forms';
import { A11yText } from '@voussoir/ui/src/primitives/typography';
import DeleteItemModal from './DeleteItemModal';

// This import is loaded by the @voussoir/field-views-loader loader.
// It imports all the views required for a keystone app by looking at the adminMetaData
import FieldTypes from '../FIELD_TYPES';

// Styled Components
const Table = styled('table')({
  borderCollapse: 'collapse',
  borderSpacing: 0,
  tableLayout: 'fixed',
  width: '100%',
});
const HeaderCell = styled('td')({
  borderBottom: `2px solid ${colors.N10}`,
  color: '#999',
  display: 'table-cell',
  fontWeight: 'normal',
  paddingBottom: '8px',
  textAlign: 'left',
  verticalAlign: 'bottom',
});
const BodyCell = styled('td')(({ isSelected }) => ({
  backgroundColor: isSelected ? colors.B.L90 : null,
  boxShadow: isSelected
    ? `0 1px 0 ${colors.B.L75}, 0 -1px 0 ${colors.B.L75}`
    : `0 -1px 0 ${colors.N10}`,
  padding: '8px 0',
  position: 'relative',
  fontSize: 15,
}));
const ItemLink = styled(Link)`
  color: ${colors.text};

  /* Increase hittable area on item link */
  &::before {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    top: 0;
  }
`;

const NoResults = ({ children, ...props }) => (

    <InfoIcon css={{ height: 48, width: 48, marginBottom: '0.5em' }} />
    {children}

);

const SortDirectionArrow = styled.span(({ size = '0.2em', rotate = '0deg' }) => ({
  borderLeft: `${size} solid transparent`,
  borderRight: `${size} solid transparent`,
  borderTop: `${size} solid`,
  display: 'inline-block',
  height: 0,
  marginLeft: '0.33em',
  marginTop: '-0.125em',
  verticalAlign: 'middle',
  width: 0,
  transform: `rotate(${rotate})`,
}));

// Functional Components

type SortLinkProps = {
  handleSortChange: (Event, string) => void,
  active: boolean,
  sortAscending: boolean,
};

class SortLink extends React.Component<SortLinkProps> {
  onClick = event => {
    this.props.handleSortChange(event, this.props.label);
  };

  render() {
    const styles = {
      borderBottom: `2px solid ${colors.N10}`,
      display: 'table-cell',
      fontWeight: 'normal',
      paddingBottom: '8px',
      textAlign: 'left',
      verticalAlign: 'bottom',
      color: this.props.active ? '#000' : '#999',
    };

    return (

        {this.props.label}
        <SortDirectionArrow
          rotate={this.props.active && !this.props.sortAscending ? '180deg' : '0deg'}
        />

    );
  }
}

class ListDisplayRow extends Component {
  static defaultProps = {
    itemErrors: {},
  };

  state = {
    showDeleteModal: false,
  };
  componentDidMount() {
    this.mounted = true;
  }
  componentWillUnmount() {
    this.mounted = false;
  }
  showDeleteModal = () => {
    this.setState({ showDeleteModal: true });
  };
  closeDeleteModal = () => {
    this.setState({ showDeleteModal: false });
  };
  onDelete = result => {
    if (this.props.onDelete) this.props.onDelete(result);
    if (!this.mounted) return;
    this.setState({ showDeleteModal: false });
  };
  renderDeleteModal() {
    const { showDeleteModal } = this.state;
    const { item, list } = this.props;

    return (
      <DeleteItemModal
        isOpen={showDeleteModal}
        item={item}
        list={list}
        onClose={this.closeDeleteModal}
        onDelete={this.onDelete}
      />
    );
  }
  render() {
    const { list, link, item, itemErrors, fields } = this.props;

    return (

        <BodyCell>
          <Button
            appearance="warning"
            onClick={this.showDeleteModal}
            spacing="cozy"
            variant="subtle"
            style={{ height: 24 }}
          >
            <TrashcanIcon />
            <A11yText>Remove</A11yText>
          </Button>
          {this.renderDeleteModal()}
        </BodyCell>
        {fields.map((field, index) => {
          const { path } = field;

          const isLoading = !item.hasOwnProperty(path);
          if (isLoading) {
            // TODO: Better loading state?
            return <BodyCell key={path} />;
          }

          if (itemErrors[path] instanceof Error && itemErrors[path].name === 'AccessDeniedError') {
            return (
              <BodyCell key={path}>
                <ShieldIcon title={itemErrors[path].message} css={{ color: colors.N10 }} />
                <A11yText>{itemErrors[path].message}</A11yText>
              </BodyCell>
            );
          }

          let content;

          const Cell = FieldTypes[list.key][path].Cell;

          if (Cell) {
            const LinkComponent = ({ children, ...data }) => (
              <ItemLink to={link(data)}>{children}</ItemLink>
            );
            content = <Cell list={list} data={item[path]} field={field} Link={LinkComponent} />;
          } else {
            content = item[path];
          }

          return (
            <BodyCell key={path}>
              {!index ? (
                <ItemLink to={link({ path: list.path, id: item.id })}>{content}</ItemLink>
              ) : (
                content
              )}
            </BodyCell>
          );
        })}

    );
  }
}

function isKeyboardEvent(e) {
  return e.clientX === 0 && e.clientY === 0;
}

class ListManageRow extends Component {
  onMouseDown = e => {
    // bail when MouseClick on the actual input, which calls onClick twice
    if (e.target.nodeName === 'INPUT' && !isKeyboardEvent(e)) return;

    // trigger onClick with the current ID
    const { isSelected, item, onSelect, onSelectStart } = this.props;
    onSelectStart(!isSelected);
    onSelect([item.id]);
  };
  onMouseUp = () => {
    // make keyboard selection easier following a mouse select
    this.checkbox.focus();
  };
  onMouseEnter = () => {
    const { isSelected, item, selectOnEnter, onSelect } = this.props;
    if (
      (selectOnEnter === 'select' && !isSelected) ||
      (selectOnEnter === 'deselect' && isSelected)
    ) {
      onSelect([item.id]);
    }
  };
  onCheckboxChange = () => {
    const { item, onSelect } = this.props;
    onSelect([item.id]);
  };
  onCheckboxMouseDown = e => {
    e.stopPropagation();
  };
  getCheckbox = ref => {
    this.checkbox = ref;
  };
  render() {
    const { fields, isSelected, item } = this.props;

    return (

        <BodyCell isSelected={isSelected} key="checkbox">
          <CheckboxPrimitive
            checked={isSelected}
            innerRef={this.getCheckbox}
            value={item.id}
            onChange={this.onCheckboxChange}
            onMouseDown={this.onCheckboxMouseDown}
            css={{ marginLeft: 1 }}
            tabIndex="0"
          />
        </BodyCell>
        {fields.map(({ path }) => (
          <BodyCell isSelected={isSelected} key={path}>
            {item[path]}
          </BodyCell>
        ))}

    );
  }
}

export default class ListTable extends Component {
  state = {
    mouseOverSelectsRow: false,
    sortBy: null,
    sortAscending: true,
  };

  handleSelectAll = () => {
    const { items, onSelectAll, selectedItems } = this.props;
    const allSelected = items.length === selectedItems.length;
    const value = allSelected ? [] : items.map(i => i.id);
    onSelectAll(value);
  };

  handleSortChange = (event, field) => {
    // only change sort direction if sortBy is not being modified
    if (this.state.sortBy === field) {
      this.setState({
        sortAscending: !this.state.sortAscending,
      });
    } else {
      this.setState({
        sortAscending: true,
        sortBy: field,
      });
    }
  };

  onSelectStart = select => {
    const { isManaging } = this.props;
    if (!isManaging) return;
    this.setState({
      mouseOverSelectsRow: select ? 'select' : 'deselect',
    });
  };
  stopRowSelectOnEnter = () => {
    const { mouseOverSelectsRow } = this.state;
    if (mouseOverSelectsRow) {
      this.setState({ mouseOverSelectsRow: false });
    }
  };
  render() {
    const {
      adminPath,
      fields,
      isManaging,
      items,
      itemsErrors = [],
      list,
      noResultsMessage,
      onChange,
      onSelect,
      selectedItems,
    } = this.props;
    const { mouseOverSelectsRow } = this.state;

    return items.length ? (

        <colgroup>
          <col width="32" />
        </colgroup>

            <HeaderCell>

                <CheckboxPrimitive
                  checked={items.length === selectedItems.length}
                  onChange={this.handleSelectAll}
                  tabIndex="0"
                />

            </HeaderCell>
            {fields.map(field => (
              <SortLink
                key={field.path}
                label={field.label}
                handleSortChange={this.handleSortChange}
                active={this.state.sortBy == field.label}
                sortAscending={this.state.sortAscending}
              />
            ))}

          {items.map(
            (item, itemIndex) =>
              isManaging ? (
                <ListManageRow
                  fields={fields}
                  item={item}
                  key={item.id}
                  list={list}
                  isSelected={selectedItems.includes(item.id)}
                  selectOnEnter={mouseOverSelectsRow}
                  onSelect={onSelect}
                  onSelectStart={this.onSelectStart}
                />
              ) : (
                <ListDisplayRow
                  fields={fields}
                  item={item}
                  itemErrors={itemsErrors[itemIndex] || {}}
                  key={item.id}
                  link={({ path, id }) => `${adminPath}/${path}/${id}`}
                  list={list}
                  onDelete={onChange}
                />
              )
          )}

    ) : (
      <NoResults>{noResultsMessage}</NoResults>
    );
  }
}
<|endoftext|>


<commit_before>(function () {
    'use strict';

    angular.module('app.core')
        .factory('dataWrapper', dataWrapper);

    dataWrapper.$inject = ['moment'];

    function dataWrapper(moment) {
        var service = {
            wrapRuns: wrapRuns
        };

        function wrapRuns(runs) {
            if (!angular.isArray(runs)) return [];

            var envFullToString = function () {
                var result = '';
                var keys = Object.keys(this);
                for (var i = 0; i < keys.length; i++) {
                    result += keys[i] + ': ' + this[keys[i]] + '; ';
                }
                return result;
            };

            for (var i = 0; i < runs.length; i++) {
                runs[i].author.toString = function (type) {
                    if (this.lastName) {
                        if (type === 'short') return this.lastName + ' ' + this.firstName.slice(0, 1) + '.';
                        else return this.firstName + ' ' + this.lastName;
                    } else {
                        return this.fullName;
                    }
                };

                runs[i].date = {
                    value: runs[i].date,
                    toString: function () {
                        return moment(this.value).format('DD.MM.YY');
                    }
                };

                if (runs[i].envFull) {
                    Object.defineProperty(runs[i].envFull, 'toString', {
                        value: envFullToString,
                        enumerable: false
                    })
                }
            }

            return runs;
        }

        return service;
    }
})();
<commit_msg>Update data-wrapper.factory.js<commit_after>(function () {
    'use strict';

    angular.module('app.core')
        .factory('dataWrapper', dataWrapper);

    dataWrapper.$inject = ['moment'];

    function dataWrapper(moment) {
        var service = {
            wrapRuns: wrapRuns
        };

        function wrapRuns(runs) {
            if (!angular.isArray(runs)) return [];

            var envFullToString = function () {
                var result = '';
                var keys = Object.keys(this);
                for (var i = 0; i < keys.length; i++) {
                    result += keys[i] + ': ' + this[keys[i]] + '; ';
                }
                return result;
            };

            for (var i = 0; i < runs.length; i++) {
                runs[i].author.toString = function (type) {
                    if (this.lastName) {
                        if (type === 'short') return this.lastName + ' ' + this.firstName.slice(0, 1) + '.';
                        else return this.firstName + ' ' + this.lastName;
                    } else {
                        return this.fullName;
                    }
                };

                runs[i].date = {
                    value: runs[i].date,
                    toString: function () {
                        return moment(this.value).format('DD.MM.YY');
                    }
                };

                if (runs[i].envFull) {
                    Object.defineProperty(runs[i].envFull, 'toString', {
                        value: envFullToString,
                        enumerable: false
                    });
                }
            }

            return runs;
        }

        return service;
    }
})();
<|endoftext|>