<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Header from '../components/Header';
import Body from '../components/Body';
import Footer from '../components/Footer';
import CollectiveCover from '../components/CollectiveCover';
import ActionBar from '../components/ActionBar';
import Location from '../components/Location';
import HashLink from 'react-scrollchor';
import Tier from '../components/Tier';
import NotificationBar from '../components/NotificationBar';
import GetTicketForm from '../components/GetTicketForm';
import InterestedForm from '../components/InterestedForm';
import Sponsors from '../components/Sponsors';
import Responses from '../components/Responses';
import { filterCollection } from '../lib/utils';
import { addCreateResponseMutation } from '../graphql/mutations';
import Markdown from 'react-markdown';
import TicketsConfirmed from '../components/TicketsConfirmed';
import { FormattedMessage, FormattedDate, FormattedTime } from 'react-intl';
import { uniq } from 'underscore';

const defaultBackgroundImage = '/static/images/defaultBackgroundImage.png';

class Event extends React.Component {

  static propTypes = {
    event: PropTypes.object.isRequired,
    LoggedInUser: PropTypes.object
  }

  constructor(props) {
    super(props);
    this.event = this.props.event; // pre-loaded by SSR
    this.setInterested = this.setInterested.bind(this);
    this.updateResponse = this.updateResponse.bind(this);
    this.resetResponse = this.resetResponse.bind(this);
    this.handleGetTicketClick = this.handleGetTicketClick.bind(this);
    this.rsvp = this.rsvp.bind(this);
    this.closeModal = this.closeModal.bind(this);

    this.defaultActions = [
      {
        component: (<FormattedMessage id='actions.interested' defaultMessage='interested' />),
        // className: 'selected',
        // icon: 'star',
        onClick: this.setInterested
      },
      {
        className: 'whiteblue',
        component: <HashLink to="#tickets"><FormattedMessage id='actions.GetTicket' defaultMessage='get ticket' /></HashLink>
      }
    ];

    this.state = {
      view: 'default',
      showInterestedForm: false,
      response: {},
      api: { status: 'idle' },
      actions: this.defaultActions
    };

    // To test confirmation screen, uncomment the following:
    // this.state.modal = "TicketsConfirmed";
    // this.state.response = {
    //   user: { email: "etienne@gmail.com"},
    //   tier: this.event && this.event.tiers[0],
    //   quantity: 2
    // };

  }

  componentDidMount() {
    window.oc = { event: this.event }; // for easy debugging
  }

  /**
   * If user is logged in, we directly create a response
   * Otherwise, we show the form to enter an email address
   */
  async setInterested(user) {
    if (user || this.user) {
      this.setState({ showInterestedForm: false });
      const tokens = user.email.substr(0, user.email.indexOf('@')).split('.');
      user.firstName = tokens[0] || '';
      user.lastName = tokens[1] || '';
      const response = {
        status: 'INTERESTED',
        user
      };
      try {
        await this.createResponse(response);
        this.event.responses.push(response);
        const actions = this.state.actions;
        actions[0].className = 'selected';
        actions[0].icon = 'star';
        this.setState({ actions, showInterestedForm: false });
      } catch (e) {
        this.error(`An error occured 😳. We couldn't register you as interested. Please try again in a few.`);
      }
      return;
    } else {
      this.setState({ showInterestedForm: !this.state.showInterestedForm });
      return;
    }
  }

  async rsvp(response) {
    try {
      await this.createResponse(response);
      this.setState({ response, view: 'default', modal: 'TicketsConfirmed' });
      window.scrollTo(0,0);
    } catch (e) {
      this.error(`An error occured 😳. We couldn't register you. Please try again in a few.`);
    }
  }

  closeModal() {
    this.setState({ modal: null });
  }

  getDefaultActions(props) {
    const { LoggedInUser } = props || this.props;
    const editUrl = `/${this.event.collective.slug}/events/${this.event.slug}/edit`;
    if (LoggedInUser && LoggedInUser.canEditEvent) {
      return [...this.defaultActions, {
        className: 'whiteblue small',
        component: EDIT
      }]
    } else {
      return this.defaultActions;
    }
  }

  componentWillReceiveProps(props) {
    if (props) {
      this.setState({actions: this.getDefaultActions(props) });
    }
  }

  changeView(view) {
    let actions;
    switch (view) {
      case 'GetTicket':
        actions = [{
          label: (<FormattedMessage id='actions.GoBack' defaultMessage={`go back`} />),
          onClick: this.resetResponse
        }];
        break;
      default:
        actions = this.getDefaultActions();
        break;
    }
    this.setState({view, actions});
    window.scrollTo(0,0);
  }

  async createResponse(response) {
    response.tier = response.tier || {};
    const ResponseInputType = {
      collective: { slug: this.event.collective.slug },
      event: { slug: this.event.slug },
      tier: { id: response.tier.id },
      quantity: response.quantity,
      user: response.user,
      status: response.status
    };

    this.setState( { status: 'loading' });
    try {
      await this.props.createResponse(ResponseInputType);
      this.setState( { status: 'idle' });
    } catch (err) {
      console.error(">>> createResponse error: ", err);
      throw new Error(err.graphQLErrors[0].message);
    }
  }

  error(msg) {
    this.setState( {status: 'error', error: msg });
    setTimeout(() => {
      this.setState( { status: 'idle', error: null });
    }, 5000);
  }

  updateResponse(response) {
    this.setState({ response });
  }

  resetResponse() {
    this.setState({ response: {} });
    this.changeView('default');
  }

  handleGetTicketClick(response) {
    this.setState({ response, showInterestedForm: false });
    this.changeView('GetTicket');
  }

  render() {
    const responses = {};
    responses.sponsors = filterCollection(this.event.responses, { tier: { name: /sponsor/i }});
    responses.guests = filterCollection(uniq(this.event.responses, (r) => `${r.status}:${r.user.username}` ), { tier: { name: /sponsor/i }}, true);
    responses.going = filterCollection(responses.guests, {'status':'YES'});
    responses.interested = filterCollection(responses.guests, {'status':'INTERESTED'});

    const info = (
      <HashLink to="#location">
        <FormattedDate value={this.event.startsAt} weekday='short' day='numeric' month='long' />,
        <FormattedTime value={this.event.startsAt} timeZone={this.event.timezone} />  -
        {this.event.location.name}
      </HashLink>
    );

    const backgroundImage = this.event.backgroundImage || this.event.collective.backgroundImage || defaultBackgroundImage;

    return (

        <TicketsConfirmed
          show={this.state.modal === 'TicketsConfirmed'}
          onClose={this.closeModal}
          event={this.event}
          response={this.state.response} />

            <div className={`EventPage ${this.state.modal && 'showModal'}`}>

              <NotificationBar status={this.state.status} error={this.state.error} />

              {this.state.view === 'default' &&
                <CollectiveCover
                  collective={this.event.collective}
                  logo={this.event.collective.logo}
                  title={this.event.name}
                  backgroundImage={backgroundImage}
                  />
              }

              <ActionBar
                actions={this.state.actions}
                info={info}
                />

              {this.state.showInterestedForm &&
                <InterestedForm onSubmit={this.setInterested} />
              }

              {this.state.view == 'GetTicket' &&
                <GetTicketForm
                  onCancel={this.resetResponse}
                  onSubmit={this.rsvp}
                  quantity={this.state.response.quantity}
                  stripePublishableKey={this.event.collective.stripePublishableKey}
                  tier={this.state.response.tier || this.event.tiers[0]}
                  />
              }

              {this.state.view == 'default' &&

                      <Markdown source={this.event.description} />

                      {this.event.tiers.map((tier) =>
                        <Tier
                          key={tier.id}
                          className="tier"
                          tier={tier}
                          onChange={(response) => this.updateResponse(response)}
                          onClick={(response) => this.handleGetTicketClick(response)}
                          />
                      )}

                  <Location location={this.event.location} />

                  { responses.guests.length > 0 &&

                        <FormattedMessage id='event.responses.title.going' values={{n: responses.going.length}} defaultMessage={`{n} {n, plural, one {person going} other {people going}}`} />
                        { responses.interested.length > 0 &&

                             -
                            <FormattedMessage id='event.responses.title.interested' values={{n: responses.interested.length}} defaultMessage={`{n} interested`} />

                        }

                      <Responses responses={responses.guests} />

                  }
                  { responses.sponsors.length > 0 &&

                        <FormattedMessage id='event.sponsors.title' defaultMessage={`Sponsors`} />

                      <Sponsors sponsors={responses.sponsors.map(r => {
                        const user = Object.assign({}, r.user);
                        user.tier = r.tier;
                        user.createdAt = new Date(r.createdAt);
                        return user;
                      })} />

                  }

              }

    )
  }
}

export default addCreateResponseMutation(Event);
<commit_msg>added link to print nametags if canEditEvent<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Header from '../components/Header';
import Body from '../components/Body';
import Footer from '../components/Footer';
import CollectiveCover from '../components/CollectiveCover';
import ActionBar from '../components/ActionBar';
import Location from '../components/Location';
import HashLink from 'react-scrollchor';
import Tier from '../components/Tier';
import NotificationBar from '../components/NotificationBar';
import GetTicketForm from '../components/GetTicketForm';
import InterestedForm from '../components/InterestedForm';
import Sponsors from '../components/Sponsors';
import Responses from '../components/Responses';
import { filterCollection } from '../lib/utils';
import { addCreateResponseMutation } from '../graphql/mutations';
import Markdown from 'react-markdown';
import TicketsConfirmed from '../components/TicketsConfirmed';
import { FormattedMessage, FormattedDate, FormattedTime } from 'react-intl';
import { uniq } from 'underscore';

const defaultBackgroundImage = '/static/images/defaultBackgroundImage.png';

class Event extends React.Component {

  static propTypes = {
    event: PropTypes.object.isRequired,
    LoggedInUser: PropTypes.object
  }

  constructor(props) {
    super(props);
    this.event = this.props.event; // pre-loaded by SSR
    this.setInterested = this.setInterested.bind(this);
    this.updateResponse = this.updateResponse.bind(this);
    this.resetResponse = this.resetResponse.bind(this);
    this.handleGetTicketClick = this.handleGetTicketClick.bind(this);
    this.rsvp = this.rsvp.bind(this);
    this.closeModal = this.closeModal.bind(this);

    this.defaultActions = [
      {
        component: (<FormattedMessage id='actions.interested' defaultMessage='interested' />),
        // className: 'selected',
        // icon: 'star',
        onClick: this.setInterested
      },
      {
        className: 'whiteblue',
        component: <HashLink to="#tickets"><FormattedMessage id='actions.GetTicket' defaultMessage='get ticket' /></HashLink>
      }
    ];

    this.state = {
      view: 'default',
      showInterestedForm: false,
      response: {},
      api: { status: 'idle' },
      actions: this.defaultActions
    };

    // To test confirmation screen, uncomment the following:
    // this.state.modal = "TicketsConfirmed";
    // this.state.response = {
    //   user: { email: "etienne@gmail.com"},
    //   tier: this.event && this.event.tiers[0],
    //   quantity: 2
    // };

  }

  componentDidMount() {
    window.oc = { event: this.event }; // for easy debugging
  }

  /**
   * If user is logged in, we directly create a response
   * Otherwise, we show the form to enter an email address
   */
  async setInterested(user) {
    if (user || this.user) {
      this.setState({ showInterestedForm: false });
      const tokens = user.email.substr(0, user.email.indexOf('@')).split('.');
      user.firstName = tokens[0] || '';
      user.lastName = tokens[1] || '';
      const response = {
        status: 'INTERESTED',
        user
      };
      try {
        await this.createResponse(response);
        this.event.responses.push(response);
        const actions = this.state.actions;
        actions[0].className = 'selected';
        actions[0].icon = 'star';
        this.setState({ actions, showInterestedForm: false });
      } catch (e) {
        this.error(`An error occured 😳. We couldn't register you as interested. Please try again in a few.`);
      }
      return;
    } else {
      this.setState({ showInterestedForm: !this.state.showInterestedForm });
      return;
    }
  }

  async rsvp(response) {
    try {
      await this.createResponse(response);
      this.setState({ response, view: 'default', modal: 'TicketsConfirmed' });
      window.scrollTo(0,0);
    } catch (e) {
      this.error(`An error occured 😳. We couldn't register you. Please try again in a few.`);
    }
  }

  closeModal() {
    this.setState({ modal: null });
  }

  getDefaultActions(props) {
    const { LoggedInUser } = props || this.props;
    const editUrl = `/${this.event.collective.slug}/events/${this.event.slug}/edit`;
    if (LoggedInUser && LoggedInUser.canEditEvent) {
      return [...this.defaultActions, {
        className: 'whiteblue small',
        component: EDIT
      }]
    } else {
      return this.defaultActions;
    }
  }

  componentWillReceiveProps(props) {
    if (props) {
      this.setState({actions: this.getDefaultActions(props) });
    }
  }

  changeView(view) {
    let actions;
    switch (view) {
      case 'GetTicket':
        actions = [{
          label: (<FormattedMessage id='actions.GoBack' defaultMessage={`go back`} />),
          onClick: this.resetResponse
        }];
        break;
      default:
        actions = this.getDefaultActions();
        break;
    }
    this.setState({view, actions});
    window.scrollTo(0,0);
  }

  async createResponse(response) {
    response.tier = response.tier || {};
    const ResponseInputType = {
      collective: { slug: this.event.collective.slug },
      event: { slug: this.event.slug },
      tier: { id: response.tier.id },
      quantity: response.quantity,
      user: response.user,
      status: response.status
    };

    this.setState( { status: 'loading' });
    try {
      await this.props.createResponse(ResponseInputType);
      this.setState( { status: 'idle' });
    } catch (err) {
      console.error(">>> createResponse error: ", err);
      throw new Error(err.graphQLErrors[0].message);
    }
  }

  error(msg) {
    this.setState( {status: 'error', error: msg });
    setTimeout(() => {
      this.setState( { status: 'idle', error: null });
    }, 5000);
  }

  updateResponse(response) {
    this.setState({ response });
  }

  resetResponse() {
    this.setState({ response: {} });
    this.changeView('default');
  }

  handleGetTicketClick(response) {
    this.setState({ response, showInterestedForm: false });
    this.changeView('GetTicket');
  }

  render() {
    const { LoggedInUser } = this.props;
    const canEditEvent = LoggedInUser && LoggedInUser.canEditEvent;
    const responses = {};
    responses.sponsors = filterCollection(this.event.responses, { tier: { name: /sponsor/i }});
    responses.guests = filterCollection(uniq(this.event.responses, (r) => `${r.status}:${r.user.username}` ), { tier: { name: /sponsor/i }}, true);
    responses.going = filterCollection(responses.guests, {'status':'YES'});
    responses.interested = filterCollection(responses.guests, {'status':'INTERESTED'});

    const info = (
      <HashLink to="#location">
        <FormattedDate value={this.event.startsAt} weekday='short' day='numeric' month='long' />,
        <FormattedTime value={this.event.startsAt} timeZone={this.event.timezone} />  -
        {this.event.location.name}
      </HashLink>
    );

    const backgroundImage = this.event.backgroundImage || this.event.collective.backgroundImage || defaultBackgroundImage;

    return (

        <TicketsConfirmed
          show={this.state.modal === 'TicketsConfirmed'}
          onClose={this.closeModal}
          event={this.event}
          response={this.state.response} />

            <div className={`EventPage ${this.state.modal && 'showModal'}`}>

              <NotificationBar status={this.state.status} error={this.state.error} />

              {this.state.view === 'default' &&
                <CollectiveCover
                  collective={this.event.collective}
                  logo={this.event.collective.logo}
                  title={this.event.name}
                  backgroundImage={backgroundImage}
                  />
              }

              <ActionBar
                actions={this.state.actions}
                info={info}
                />

              {this.state.showInterestedForm &&
                <InterestedForm onSubmit={this.setInterested} />
              }

              {this.state.view == 'GetTicket' &&
                <GetTicketForm
                  onCancel={this.resetResponse}
                  onSubmit={this.rsvp}
                  quantity={this.state.response.quantity}
                  stripePublishableKey={this.event.collective.stripePublishableKey}
                  tier={this.state.response.tier || this.event.tiers[0]}
                  />
              }

              {this.state.view == 'default' &&

                      <Markdown source={this.event.description} />

                      {this.event.tiers.map((tier) =>
                        <Tier
                          key={tier.id}
                          className="tier"
                          tier={tier}
                          onChange={(response) => this.updateResponse(response)}
                          onClick={(response) => this.handleGetTicketClick(response)}
                          />
                      )}

                  <Location location={this.event.location} />

                  { responses.guests.length > 0 &&

                        <FormattedMessage id='event.responses.title.going' values={{n: responses.going.length}} defaultMessage={`{n} {n, plural, one {person going} other {people going}}`} />
                        { responses.interested.length > 0 &&

                             -
                            <FormattedMessage id='event.responses.title.interested' values={{n: responses.interested.length}} defaultMessage={`{n} interested`} />

                        }

                      { canEditEvent &&

                        <a href={`/${this.event.collective.slug}/events/${this.event.slug}/nametags.pdf`}>Print name tags

                      }
                      <Responses responses={responses.guests} />

                  }
                  { responses.sponsors.length > 0 &&

                        <FormattedMessage id='event.sponsors.title' defaultMessage={`Sponsors`} />

                      <Sponsors sponsors={responses.sponsors.map(r => {
                        const user = Object.assign({}, r.user);
                        user.tier = r.tier;
                        user.createdAt = new Date(r.createdAt);
                        return user;
                      })} />

                  }

              }

    )
  }
}

export default addCreateResponseMutation(Event);
<|endoftext|>


<commit_before>go.app = function() {
    var vumigo = require('vumigo_v02');
    var MenuState = vumigo.states.MenuState;
    var ChoiceState = vumigo.states.ChoiceState;
    var FreeText = vumigo.states.FreeText;
    var Choice = vumigo.states.Choice;
    var SeedJsboxUtils = require('seed-jsbox-utils');
    var IdentityStore = SeedJsboxUtils.IdentityStore;
    var JsonApi = vumigo.http.api.JsonApi;
    var App = vumigo.App;

    var GoNDOH = App.extend(function(self) {
        App.call(self, 'state_start');
        var utils = SeedJsboxUtils.utils;

        //variables for services
        var is;

        self.init = function() {
            // initialise services
            is = new IdentityStore(
                new JsonApi(self.im, {}),
                self.im.config.services.identity_store.token,
                self.im.config.services.identity_store.url
            );
        };

        self.states.add("state_start", function(name) {
            // Reset user answers when restarting the app
            self.im.user.answers = {};
            var msisdn = utils.normalize_msisdn(self.im.user.addr, '27');
            self.im.user.set_answer("operator_msisdn", msisdn);

            /*
                find identity & check subscription using CompanionApp

                will go to state_registered if found,
                else state_not_registered
            */
            return self.states.create('state_not_registered');

        });

        self.states.add('state_not_registered', function(name){
            return new FreeText(name, {
                question: ("Welcome to NurseConnect, where you can stay up to date with "+
                "maternal & child health. Reply '1' to start."),
                next: 'state_nurse_connect_options'
            });
          });

          // OPTIONS MENU
        self.states.add('state_nurse_connect_options', function(name) {
            return new MenuState(name, {
                question: ('Do you want to:'),
                choices: [
                    new Choice('state_weekly_messages', ('Sign up for weekly messages')),
                    new Choice('state_change_number', ('Change your no')),
                    new Choice('state_friend_register', ('Help a friend register')),
                ],
            });
        });

        self.states.add('state_weekly_messages', function(name) {
            self.im.user.set_answer("friend_registration", false);
            return new ChoiceState(name, {
                question: ("To register, your info needs to be collected, stored and used. " +
                           "You might also receive messages on public holidays. Do you agree?"),
                choices: [
                    new Choice('yes', ('Yes')),
                    new Choice('no', ('No')),
                ],
                next: function(choice) {
                    if (choice.value === 'yes'){
                        return 'state_enter_msisdn';
                    }
                    else if (choice.value === 'no'){
                        return 'state_no_registration';
                    }
                }
            });
        });

        self.states.add('state_friend_register', function(name) {
            self.im.user.set_answer("friend_registration", true);
            return new ChoiceState(name, {
                question: ("To register, your friend's info needs to be collected, stored and used. "+
                            "They may receive messages on public holidays. Do they agree?"),
                choices: [
                    new Choice('yes', ('Yes')),
                    new Choice('no', ('No')),
                ],
                next: function(choice) {
                    if (choice.value === 'yes'){
                        return 'state_enter_msisdn';
                    }
                    else if (choice.value === 'no'){
                        return 'state_no_registration';
                    }
                }
            });
        });

        self.states.add('state_no_registration', function(name) {
            return new MenuState(name, {
                question: ("If you/they don't agree to share info, we can't send NurseConnect messages. " +
                           "Reply '1' if you/they change your/their mind and would like to sign up."),
                choices: [
                    new Choice('state_nurse_connect_options', ('Main Menu')),
                ],
            });
        });

        self.states.add('state_enter_msisdn', function(name){
            var error = ("Sorry, the format of the mobile number is not correct. Please enter the mobile number again, e.g. 0726252020");
            var question = ("Please enter the number you would like to register, e.g. 0726252020:");
            return new FreeText(name, {
                question: question,
                check: function(content) {
                    if (!utils.is_valid_msisdn(content, 0, 10)) {
                        return error;
                    }
                },
                next: function(content) {
                    //add function to normalize msisdn
                    self.im.user.set_answer("registrant_msisdn", content);
                    //add function to add msisdn to identity, then
                    return self.states.create('state_check_optout');
                }
            });
        });

        self.states.add('state_check_optout', function(name) {
            //declare msisdn
            //if number has opted out previously, go to state_has_opted_out
            //else, go to state_confirm_faccode
            var registrant_msisdn = self.im.user.answers.registrant_msisdn;
            if (registrant_msisdn !== '0000000000' ) { //replace with statement checking if identity has opted out or not
                return self.states.create('state_faccode');
            } else {
                return self.states.create('state_has_opted_out');
            }
        });

        self.states.add('state_has_opted_out', function(name) {
            return new ChoiceState(name, {
                question: ("This number previously opted out of NurseConnect messages. Are <you/they> sure <you/they> want to sign up again?"),
                choices: [
                    new Choice('yes', ('Yes')),
                    new Choice('no', ('No'))
                ],
                next: function(choice) {
                    if (choice.value === 'yes') {
                        var optin_info = {
                            "request_source": self.im.config.name || "ussd_nurse",
                            "address_type": "msisdn"
                            //identity will be set here
                            //address will be set here
                        };
                        return is
                        .optin(optin_info)
                        .then(function() {
                            return 'state_faccode';
                        });
                    }else {
                        return self.states.create('state_no_subscription');
                    }
                }
            });
        });

        self.states.add('state_faccode', function(name) {
            var error = ("Sorry, we don't recognise that code. Please enter the 6- digit facility code again, e.g. 535970:");
            var question = ("Please enter <your/their> 6-digit facility code:");

            return new FreeText(name, {
                question: question,
                check: function(content) {
                    return self
                    // check if whatsapp user as well here
                    .then(function(facname) {
                        if (!facname) {
                            return error;
                        } else {
                            /*
                                set the facility name and code against the nurse identity here
                            */
                            return null; //null or undefined if check passes
                        }
                    });
                },
                next: 'state_facname',
            });
        });

    });

    return {
        GoNDOH: GoNDOH
    };
}();<commit_msg>added state_no_subscription<commit_after>go.app = function() {
    var vumigo = require('vumigo_v02');
    var MenuState = vumigo.states.MenuState;
    var ChoiceState = vumigo.states.ChoiceState;
    var FreeText = vumigo.states.FreeText;
    var Choice = vumigo.states.Choice;
    var SeedJsboxUtils = require('seed-jsbox-utils');
    var IdentityStore = SeedJsboxUtils.IdentityStore;
    var JsonApi = vumigo.http.api.JsonApi;
    var App = vumigo.App;

    var GoNDOH = App.extend(function(self) {
        App.call(self, 'state_start');
        var utils = SeedJsboxUtils.utils;

        //variables for services
        var is;

        self.init = function() {
            // initialise services
            is = new IdentityStore(
                new JsonApi(self.im, {}),
                self.im.config.services.identity_store.token,
                self.im.config.services.identity_store.url
            );
        };

        self.states.add("state_start", function(name) {
            // Reset user answers when restarting the app
            self.im.user.answers = {};
            var msisdn = utils.normalize_msisdn(self.im.user.addr, '27');
            self.im.user.set_answer("operator_msisdn", msisdn);

            /*
                find identity & check subscription using CompanionApp

                will go to state_registered if found,
                else state_not_registered
            */
            return self.states.create('state_not_registered');

        });

        self.states.add('state_not_registered', function(name){
            return new FreeText(name, {
                question: ("Welcome to NurseConnect, where you can stay up to date with "+
                "maternal & child health. Reply '1' to start."),
                next: 'state_nurse_connect_options'
            });
          });

          // OPTIONS MENU
        self.states.add('state_nurse_connect_options', function(name) {
            return new MenuState(name, {
                question: ('Do you want to:'),
                choices: [
                    new Choice('state_weekly_messages', ('Sign up for weekly messages')),
                    new Choice('state_change_number', ('Change your no')),
                    new Choice('state_friend_register', ('Help a friend register')),
                ],
            });
        });

        self.states.add('state_weekly_messages', function(name) {
            self.im.user.set_answer("friend_registration", false);
            return new ChoiceState(name, {
                question: ("To register, your info needs to be collected, stored and used. " +
                           "You might also receive messages on public holidays. Do you agree?"),
                choices: [
                    new Choice('yes', ('Yes')),
                    new Choice('no', ('No')),
                ],
                next: function(choice) {
                    if (choice.value === 'yes'){
                        return 'state_enter_msisdn';
                    }
                    else if (choice.value === 'no'){
                        return 'state_no_registration';
                    }
                }
            });
        });

        self.states.add('state_friend_register', function(name) {
            self.im.user.set_answer("friend_registration", true);
            return new ChoiceState(name, {
                question: ("To register, your friend's info needs to be collected, stored and used. "+
                            "They may receive messages on public holidays. Do they agree?"),
                choices: [
                    new Choice('yes', ('Yes')),
                    new Choice('no', ('No')),
                ],
                next: function(choice) {
                    if (choice.value === 'yes'){
                        return 'state_enter_msisdn';
                    }
                    else if (choice.value === 'no'){
                        return 'state_no_registration';
                    }
                }
            });
        });

        self.states.add('state_no_registration', function(name) {
            return new MenuState(name, {
                question: ("If you/they don't agree to share info, we can't send NurseConnect messages. " +
                           "Reply '1' if you/they change your/their mind and would like to sign up."),
                choices: [
                    new Choice('state_nurse_connect_options', ('Main Menu')),
                ],
            });
        });

        self.states.add('state_enter_msisdn', function(name){
            var error = ("Sorry, the format of the mobile number is not correct. Please enter the mobile number again, e.g. 0726252020");
            var question = ("Please enter the number you would like to register, e.g. 0726252020:");
            return new FreeText(name, {
                question: question,
                check: function(content) {
                    if (!utils.is_valid_msisdn(content, 0, 10)) {
                        return error;
                    }
                },
                next: function(content) {
                    //add function to normalize msisdn
                    self.im.user.set_answer("registrant_msisdn", content);
                    //add function to add msisdn to identity, then
                    return self.states.create('state_check_optout');
                }
            });
        });

        self.states.add('state_check_optout', function(name) {
            //declare msisdn
            //if number has opted out previously, go to state_has_opted_out
            //else, go to state_confirm_faccode
            var registrant_msisdn = self.im.user.answers.registrant_msisdn;
            if (registrant_msisdn !== '0820001004' ) { //replace with statement checking if identity has opted out or not
                return self.states.create('state_faccode');
            } else {
                return self.states.create('state_has_opted_out');
            }
        });

        self.states.add('state_has_opted_out', function(name) {
            return new ChoiceState(name, {
                question: ("This number previously opted out of NurseConnect messages. Are <you/they> sure <you/they> want to sign up again?"),
                choices: [
                    new Choice('yes', ('Yes')),
                    new Choice('state_no_subscription', ('No'))
                ],
                next: function(choice) {
                    if (choice.value === 'yes') {

                            //identity will be set here
                            //address will be set here
                            //address type will be set here
                            //config name will be set here

                        return 'state_faccode';
                    }
                    else{
                      return choice.value;
                    }
                }
            });
        });

        self.states.add('state_faccode', function(name) {
            var error = ("Sorry, we don't recognise that code. Please enter the 6- digit facility code again, e.g. 535970:");
            var question = ("Please enter <your/their> 6-digit facility code:");

            return new FreeText(name, {
                question: question,
                check: function(content) {
                    return self
                    // check if whatsapp user as well here
                    .then(function(facname) {
                        if (!facname) {
                            return error;
                        } else {
                            /*
                                set the facility name and code against the nurse identity here
                            */
                            return null; //null or undefined if check passes
                        }
                    });
                },
                next: 'state_facname',
            });
        });

        self.states.add('state_no_subscription', function(name) {
            return new MenuState(name, {
                question: ("You have chosen not to receive NurseConnect messages on this number."),
                choices: [
                    new Choice('state_nurse_connect_options', ('Main Menu')),
                ],
            });
        });

    });

    return {
        GoNDOH: GoNDOH
    };
}();
<|endoftext|>


<commit_before>var colorz = require('../colorz')

function engine () {
  var options

  function kindOf (value) {
  if (value === null) return 'null'
  if (value === undefined) return 'undefined'
  if (Array.isArray(value)) return 'array'

  return typeof value === 'object'
    ? Object.prototype.toString.call(value)
      .replace(/^\[object |\]$/g, '')
      .toLowerCase()
    : typeof value
}

  function isObject (obj) {
    return Object.prototype.toString.call(obj) === '[object Object]'
    // return typeof obj === 'object'
    // return obj.constructor.name === 'Object'
  }

  function filter (arr, fn) {
    var result = []

    var val
    var i = -1
    var j = -1
    var len = arr.length

    while (++i < len) {
      val = arr[i]
      if (fn(val, i, arr)) result[++j] = val
    }
    return result
  }

  function forEach (obj, fn) {
    var hasOwn = Object.prototype.hasOwnProperty
    var key

    for (key in obj) {
      if (hasOwn.call(obj, key)) {
        fn(obj[key], key, obj)
      }
    }
  }

  function repeat (str, n) {
    var result = ''

    while (n > 0) {
      if (n % 2) result += str
      n = Math.floor(n / 2)
      str += str
    }
    return result
  }

  function indent (str, char, num) {
    if (num === 0) return str
    char = num > 1
      ? repeat(char, num)
      : char

    var re = /^(?!\s*$)/mg
    return str.replace(re, char)
  }

  function getType (value) {
    var map = {
      'number'    : 'num',
      'string'    : 'str',
      'boolean'   : 'bool',
      'function'  : 'func',
      'null'      : 'null',
      'undefined' : 'undef',
      'object'    : 'obj',
      'array'     : 'arr',
      'regexp'    : 'regex',
      'date'      : 'date'
    }
    return map[kindOf(value)] || map['' + value]
  }

  function cleanObject (obj) {
    var lastKey = ''
    var key

    for (key in obj) {
      lastKey = key
      // checking the entire prototype of the object to correct placement of
      // commas. the last property before listing methods on an object
      // would not display a comma. Following commands show history of this transistion
      // if (obj.hasOwnProperty(key)) lastKey = key
      // (getType(obj[key]) === 'func') && delete obj[key] || (lastKey = key)
    }
    return lastKey
  }

  function cleanArray (arr) {
    if (options.display.xarr) return arr

    return filter(arr, function (item) {
      return ['func', 'date'].indexOf(getType(item)) === -1
    })
  }

  function generateLevel (level) {
    var levelStr = repeat(' ', options.level.spaces)
    var opts = options.level

    if (options.level.show && levelStr.length) {
      levelStr = levelStr.replace(' ', useColorProvider(opts.char, opts.color))
    }
    return repeat(levelStr, level)
  }

  function hasChild (obj) {
    var key
    for (key in obj) {
      if (Array.isArray(obj[key]) || isObject(obj[key])) return true
    }
  }

  function colorify (value, level) {
    var type

    if (value === '[Circular]') type = 'circ'
    else type = getType(value)

    var color = options.colors[type]

    return generateLevel(type === 'func' ? 0 : level)
      + (type === 'str' ? colorifySpec('"', 'quot') : '')
      + useColorProvider(formatOutputType(value, type, level), color)
      + (type === 'str' ? colorifySpec('"', 'quot') : '')
  }

  function colorifySpec (char, type, level) {
    return generateLevel(level) + useColorProvider('' + char, options.colors[type])
  }

  function useColorProvider (str, color) {
    if (!color) return str
    if (options.params.colored) {
      if (Array.isArray(color) && color.length > 1) {
        return useColorProvider(colorz[color[0]](str), color.slice(1))
      } else {
        return colorz[Array.isArray(color) ? color[0] : color](str)
      }
    }
    return str
  }

  function formatOutputType (value, type, level) {
    if (type === 'func') {
      if (options.display.func) {
        var str = value.toString().split(/\n/)
        var first = str[0] + '\n'
        var rest = str.slice(1)
        return first + indent(rest.join('\n'), ' ', generateLevel(level).length)
      } else {
        return '[Function]'
      }
    }

    if (type === 'date' && !options.display.date) return '[Date]'

    return '' + value
  }

  return {
    gen: function (json, level, isChild) {
      var colored = ''
      var result
      var key

      level = level || 0

      if (isObject(json)) {
        var lastKey = cleanObject(json)
        colored += colorifySpec('{', 'brack', isChild ? 0 : level) + '\n'
        level++

        for (key in json) {
          result = colorifySpec(key, 'attr', level)
            + colorifySpec(': ', 'punc')
            + this.gen(json[key], level, true)
            + (key !== lastKey ? colorifySpec(',', 'punc') : '')

          colored += result + '\n'
        }

        colored += colorifySpec('}', 'brack', --level)
      } else if (Array.isArray(json)) {
        json = cleanArray(json)

        if (hasChild(json)) {
          result = json.map(function (item) {
            return this.gen(item, level + 1)
          }.bind(this))

          colored += colorifySpec('[', 'brack', isChild ? 0 : level)
          colored += result.join(colorifySpec(', ', 'punc') + '\n')
          colored += colorifySpec(']', 'brack', level)
        } else {
          var coloredArray = colorifySpec('[', 'brack', isChild ? 0 : level)

          for (key in json) {
            coloredArray += colorify(json[key]) + (json.length - 1 > key ? colorifySpec(', ', 'punc') : '')
          }

          colored += coloredArray + colorifySpec(']', 'brack')
        }
      } else {
        return generateLevel(isChild ? 0 : level)
          + colorify(json, typeof json === 'function' ? level : '')
      }

      return colored
    },

    setOptions: function (opts) {
      options = opts
      return this
    }
  }
}

module.exports = engine()
<commit_msg>update util/jcolorz/engine — add circular function<commit_after>var colorz = require('../colorz')

function engine () {
  var options

  function kindOf (value) {
  if (value === null) return 'null'
  if (value === undefined) return 'undefined'
  if (Array.isArray(value)) return 'array'

  return typeof value === 'object'
    ? Object.prototype.toString.call(value)
      .replace(/^\[object |\]$/g, '')
      .toLowerCase()
    : typeof value
}

  function isObject (obj) {
    return Object.prototype.toString.call(obj) === '[object Object]'
    // return typeof obj === 'object'
    // return obj.constructor.name === 'Object'
  }

  function filter (arr, fn) {
    var result = []

    var val
    var i = -1
    var j = -1
    var len = arr.length

    while (++i < len) {
      val = arr[i]
      if (fn(val, i, arr)) result[++j] = val
    }
    return result
  }

  function forEach (obj, fn) {
    var hasOwn = Object.prototype.hasOwnProperty
    var key

    for (key in obj) {
      if (hasOwn.call(obj, key)) {
        fn(obj[key], key, obj)
      }
    }
  }

  function repeat (str, n) {
    var result = ''

    while (n > 0) {
      if (n % 2) result += str
      n = Math.floor(n / 2)
      str += str
    }
    return result
  }

  function indent (str, char, num) {
    if (num === 0) return str
    char = num > 1
      ? repeat(char, num)
      : char

    var re = /^(?!\s*$)/mg
    return str.replace(re, char)
  }

  function getType (value) {
    var map = {
      'number'    : 'num',
      'string'    : 'str',
      'boolean'   : 'bool',
      'function'  : 'func',
      'null'      : 'null',
      'undefined' : 'undef',
      'object'    : 'obj',
      'array'     : 'arr',
      'regexp'    : 'regex',
      'date'      : 'date'
    }
    return map[kindOf(value)] || map['' + value]
  }

  function cleanObject (obj) {
    var lastKey = ''
    var key

    for (key in obj) {
      lastKey = key
      // checking the entire prototype of the object to correct placement of
      // commas. the last property before listing methods on an object
      // would not display a comma. Following commands show history of this transistion
      // if (obj.hasOwnProperty(key)) lastKey = key
      // (getType(obj[key]) === 'func') && delete obj[key] || (lastKey = key)
    }
    return lastKey
  }

  function cleanArray (arr) {
    if (options.display.xarr) return arr

    return filter(arr, function (item) {
      return ['func', 'date'].indexOf(getType(item)) === -1
    })
  }

  function generateLevel (level) {
    var levelStr = repeat(' ', options.level.spaces)
    var opts = options.level

    if (options.level.show && levelStr.length) {
      levelStr = levelStr.replace(' ', useColorProvider(opts.char, opts.color))
    }
    return repeat(levelStr, level)
  }

  function hasChild (obj) {
    var key
    for (key in obj) {
      if (Array.isArray(obj[key]) || isObject(obj[key])) return true
    }
  }

  function colorify (value, level) {
    var type

    if (value === '[Circular]') type = 'circ'
    else type = getType(value)

    var color = options.colors[type]

    return generateLevel(type === 'func' ? 0 : level)
      + (type === 'str' ? colorifySpec('"', 'quot') : '')
      + useColorProvider(formatOutputType(value, type, level), color)
      + (type === 'str' ? colorifySpec('"', 'quot') : '')
  }

  function colorifySpec (char, type, level) {
    return generateLevel(level) + useColorProvider('' + char, options.colors[type])
  }

  function useColorProvider (str, color) {
    if (!color) return str
    if (options.params.colored) {
      if (Array.isArray(color) && color.length > 1) {
        return useColorProvider(colorz[color[0]](str), color.slice(1))
      } else {
        return colorz[Array.isArray(color) ? color[0] : color](str)
      }
    }
    return str
  }

  function formatOutputType (value, type, level) {
    if (type === 'func') {
      if (options.display.func) {
        var str = value.toString().split(/\n/)
        var first = str[0] + '\n'
        var rest = str.slice(1)
        return first + indent(rest.join('\n'), ' ', generateLevel(level).length)
      } else {
        return '[Function]'
      }
    }

    if (type === 'date' && !options.display.date) return '[Date]'

    return '' + value
  }

  function circular (obj) {
    function copy (from, seen) {
      var to = Array.isArray(from) ? [] : {}
      seen.push(from)

      forEach(from, function (val, key) {
        if (!val || !isObject(val))
          to[key] = val

        else if (seen.indexOf(val) === -1)
          to[key] = copy(val, seen.slice(0))

        else to[key] = '[Circular]'
      })

      return to
    }

    return copy(obj, [])
  }

  return {
    gen: function (json, level, isChild) {
      var colored = ''
      var result
      var key

      level = level || 0

      if (isObject(json)) {
        json = circular(json)
        var lastKey = cleanObject(json)
        colored += colorifySpec('{', 'brack', isChild ? 0 : level) + '\n'
        level++

        for (key in json) {
          result = colorifySpec(key, 'attr', level)
            + colorifySpec(': ', 'punc')
            + this.gen(json[key], level, true)
            + (key !== lastKey ? colorifySpec(',', 'punc') : '')

          colored += result + '\n'
        }

        colored += colorifySpec('}', 'brack', --level)
      } else if (Array.isArray(json)) {
        json = cleanArray(json)

        if (hasChild(json)) {
          result = json.map(function (item) {
            return this.gen(item, level + 1)
          }.bind(this))

          colored += colorifySpec('[', 'brack', isChild ? 0 : level)
          colored += result.join(colorifySpec(', ', 'punc') + '\n')
          colored += colorifySpec(']', 'brack', level)
        } else {
          var coloredArray = colorifySpec('[', 'brack', isChild ? 0 : level)

          for (key in json) {
            coloredArray += colorify(json[key]) + (json.length - 1 > key ? colorifySpec(', ', 'punc') : '')
          }

          colored += coloredArray + colorifySpec(']', 'brack')
        }
      } else {
        return generateLevel(isChild ? 0 : level)
          + colorify(json, typeof json === 'function' ? level : '')
      }

      return colored
    },

    setOptions: function (opts) {
      options = opts
      return this
    }
  }
}

module.exports = engine()
<|endoftext|>


<commit_before>import cheerio from 'cheerio';
import { combineResolvers } from 'graphql-resolvers';
import { contentUrl, profileUrl } from '../../../../shared/util/url_factory';
import { convertFromRaw } from 'draft-js';
import { convertToHTML } from 'draft-convert';
import { EditorHTMLPlugins } from 'hello-world-editor';
import { escapeRegExp } from '../../../../shared/util/regex';
import { isAdmin, isAuthor } from './authorization';
import { isRobotViewing } from '../../../util/crawler';
import nanoid from 'nanoid';
import Sequelize from 'sequelize';
import socialButterfly from '../../../social-butterfly';

const ATTRIBUTES_NAVIGATION = [
  'username',
  'section',
  'album',
  'name',
  'title',
  'thumb',
  'hidden',
  'template',
  'style',
  'code',
];

const Content = {
  Query: {
    allContent: combineResolvers(isAdmin, async (parent, args, { models }) => {
      return await models.Content.findAll();
    }),

    async fetchContent(parent, { username, name }, { currentUser, hostname, models, req }) {
      if (!username) {
        if (hostname) {
          const hostnameUserData = await models.User.findOne({ attributes: ['username'], where: { hostname } });
          if (hostnameUserData) {
            username = hostnameUserData.username;
            name = name || 'home';
          }
        }
        if (!username) {
          username = (await models.User.findOne({ attributes: ['username'], where: { id: 1 } })).username;
          name = name || 'main';
        }
      }

      let content = await models.Content.findOne({ where: { username, name } });

      if (!content) {
        name = 'home';
        // Could be that we don't have a 'main' page. Look for a 'home' page instead.
        content = await models.Content.findOne({ where: { username, name } });
      }

      // Inherit style, code, template from the album.
      if (content?.album && content.album !== 'main') {
        const albumContent = await models.Content.findOne({
          where: { username, section: content.section, album: 'main', name: content.album },
        });
        if (albumContent.style) {
          content.style = albumContent.style + content.style;
        }
        if (albumContent.code) {
          content.code = albumContent.code + content.code;
        }
      }

      // Inherit style, code, template from section.
      if (content && content?.section !== 'main') {
        const sectionContent = await models.Content.findOne({
          where: { username, section: 'main', name: content.section },
        });
        if (sectionContent.style) {
          content.style = sectionContent.style + content.style;
        }
        if (sectionContent.code) {
          content.code = sectionContent.code + content.code;
        }
        if (!content.template && content.album === 'main') {
          content.template = sectionContent.template;
        }
      }
      if (content && !content.template && content.album === 'main') {
        // This is when we're visiting a url of the form: /profile/section/album
        const parentContent = await models.Content.findOne({
          where: { username, section: 'main', name: content.section },
        });
        content.template = parentContent.template;
      }

      // Update count
      // lack of 'req' is a dumb check for !fetchContentHead :-/
      const isOwnerViewing = currentUser?.model?.username === username;
      if (req && content && !isOwnerViewing) {
        const attributes = isRobotViewing(req)
          ? { count_robot: content.count_robot + 1 }
          : { count: content.count + 1 };
        await models.Content.update(attributes, {
          where: {
            username,
            name: content.name,
          },
        });
      }

      return decorateWithRefreshFlag(content);
    },

    // XXX(mime): see HTMLHead.js :(
    async fetchContentHead(parent, { username, name }, { hostname, models }) {
      return await Content.Query.fetchContent(parent, { username, name }, { hostname, models });
    },

    async fetchContentNeighbors(parent, { username, section, album, name }, { currentUser, models }) {
      const ATTRIBUTES_NAVIGATION_WITH_VIEW = ATTRIBUTES_NAVIGATION.concat(['view']);

      if (!username) {
        username = (await models.User.findOne({ attributes: ['username'], where: { id: 1 } })).username;
      }
      name = name || 'main';

      const sectionContent = await models.Content.findOne({
        where: { username, section: 'main', name: section === 'main' ? name : section },
      });
      const order = [['order'], getSQLSortType(sectionContent.sort_type)];

      const isOwnerViewing = currentUser?.model?.username === username;

      const constraints = {
        redirect: false,
      };
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      const collectionConstraints = {
        username,
        section,
        album,
      };
      const collection = await models.Content.findAll({
        attributes: ATTRIBUTES_NAVIGATION_WITH_VIEW,
        where: Object.assign({}, constraints, collectionConstraints),
        order,
      });

      const contentIndex = collection.findIndex(i => i.name === name);

      const collectionItemConstraints = {
        username,
        section: !album ? 'main' : section,
        album: album ? 'main' : '',
        name: album ? album : section,
      };
      const collectionItem = await models.Content.findOne({
        attributes: ATTRIBUTES_NAVIGATION_WITH_VIEW,
        where: collectionItemConstraints,
      });

      return {
        first: decorateWithRefreshFlag(collection[collection.length - 1]),
        prev: decoratePrefetchImages(decorateWithRefreshFlag(collection[contentIndex + 1])),
        top: decorateWithRefreshFlag(collectionItem),
        next: decoratePrefetchImages(decorateWithRefreshFlag(collection[contentIndex - 1])),
        last: decorateWithRefreshFlag(collection[0]),
      };
    },

    async fetchCollection(parent, { username, section, album, name }, { currentUser, models }) {
      const isOwnerViewing = currentUser?.model?.username === username;

      const sectionContent = await models.Content.findOne({
        where: { username, section: !album ? 'main' : section, name },
      });
      const order = [['order'], getSQLSortType(sectionContent.sort_type)];

      const constraints = {
        redirect: false,
      };
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      // For links section, we grab the anchor url in the view.
      const attributes = section === 'links' ? ATTRIBUTES_NAVIGATION.concat('view') : ATTRIBUTES_NAVIGATION;

      let collection = [];
      // Check first to see if this is an album and grab items within it.
      if (section !== 'main') {
        const contentConstraints = { username, section, album: name };
        collection = await models.Content.findAll({
          attributes,
          where: Object.assign({}, constraints, contentConstraints),
          order,
        });
      }

      // If we couldn't find an album then, look for the section instead and find the albums within it.
      if (!collection.length) {
        const contentConstraints = { username, section: name, album: 'main' };
        collection = await models.Content.findAll({
          attributes,
          where: Object.assign({}, constraints, contentConstraints),
          order,
        });

        const albumConstraints = Object.assign({}, constraints, contentConstraints);
        for (const content of collection) {
          albumConstraints['album'] = content.name;
          const albumFirstContent = await models.Content.findOne({
            attributes: ['thumb'],
            where: Object.assign({}, constraints, albumConstraints),
            order,
          });
          if (albumFirstContent) {
            content.thumb = albumFirstContent.thumb;
          }
        }

        // Then, it combines with top-level items in the section (not in an album).
        const topLevelContentConstraints = { username, section: name, album: '' };
        const topLevelItems = await models.Content.findAll({
          attributes,
          where: Object.assign({}, constraints, topLevelContentConstraints),
          order,
        });
        collection = collection.concat(topLevelItems);
      }

      // This is if the top-level user page is an album. (I'm pretty sure at least :-/)
      if (!collection.length && section !== 'main') {
        const contentConstraints = { username, section };
        collection = await models.Content.findAll({
          attributes: ATTRIBUTES_NAVIGATION,
          where: Object.assign({}, constraints, contentConstraints),
          order,
        });
      }

      if (section === 'links') {
        collection.forEach(item => {
          const link = item.view.match(/https?:\/\/[^"']+/);
          const youTubeMatch = link?.[0].match(/youtube.com\/embed\/([^?<]+)/);
          item.externalLink = youTubeMatch ? `https://youtu.be/${youTubeMatch[1]}` : link?.[0];
        });
      }

      return decorateArrayWithRefreshFlag(collection);
    },

    async fetchCollectionPaginated(parent, { username, section, name, offset }, { currentUser, models }) {
      const isOwnerViewing = currentUser?.model?.username === username;
      const limit = 20;

      const constraints = {
        redirect: false,
      };
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      const notEqualToMain = { [Sequelize.Op.ne]: 'main' };
      const contentConstraints = {
        username,
        section: section !== 'main' ? section : name !== 'home' ? name : notEqualToMain,
        album: section !== 'main' ? name : notEqualToMain,
      };
      return await models.Content.findAll({
        where: Object.assign({}, constraints, contentConstraints),
        order: [['order'], ['createdAt', 'DESC']],
        limit,
        offset: offset * limit,
      });
    },

    async fetchCollectionLatest(parent, { username, section, name }, { currentUser, models }) {
      const isOwnerViewing = currentUser?.model?.username === username;

      const constraints = {
        redirect: false,
      };
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      const contentConstraints = { username, section: name };
      return await models.Content.findOne({
        where: Object.assign({}, constraints, contentConstraints),
        order: [['order'], ['createdAt', 'DESC']],
      });
    },

    async fetchSiteMap(parent, { username }, { currentUser, models }) {
      const isOwnerViewing = currentUser?.model?.username === username;
      const constraints = {};
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      const contentConstraints = {
        username,
        section: 'main',
        name: { [Sequelize.Op.notIn]: ['main', 'home', 'comments'] },
        redirect: 0,
      };

      const sections = await models.Content.findAll({
        attributes: ATTRIBUTES_NAVIGATION,
        where: Object.assign({}, constraints, contentConstraints),
        order: [['order'], ['createdAt']],
      });

      let siteMap = [];
      for (const section of sections) {
        const albumConstraints = {
          username,
          section: section.name,
          album: 'main',
        };

        const albums = await models.Content.findAll({
          attributes: ATTRIBUTES_NAVIGATION,
          where: Object.assign({}, constraints, albumConstraints),
          order: [['order'], ['createdAt']],
        });

        siteMap.push(section);
        if (albums.length) {
          siteMap = siteMap.concat(albums);
        }
      }

      return decorateArrayWithRefreshFlag(siteMap);
    },

    async searchContent(parent, { username, query }, { currentUser, models }) {
      const isOwnerViewing = currentUser?.model?.username === username;

      const constraints = {
        redirect: false,
        username,
      };
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      let collection = await models.Content.findAll({
        attributes: ATTRIBUTES_NAVIGATION.concat(['view']),
        where: [constraints, Sequelize.literal('match (title, view) against (:query)')],
        replacements: {
          query,
        },
      });

      for (const item of collection) {
        const HTML_REGEX = /<[^>]+>/g;
        item.preview = ellipsize(item.view.replace(HTML_REGEX, '').trim(), 130);
      }

      return decorateArrayWithRefreshFlag(collection);
    },
  },

  Mutation: {
    saveContent: combineResolvers(
      isAuthor,
      async (parent, { name, hidden, title, thumb, style, code, content }, { currentUser, models, req }) => {
        const username = currentUser.model.username;
        const view = toHTML(content, title);
        const thread = discoverThreadInHTML(view);

        await models.Content.update(
          {
            hidden,
            title,
            thumb,
            style,
            thread,
            code,
            content,
            view,
          },
          {
            where: {
              username,
              name,
            },
          }
        );

        const updatedContent = await models.Content.findOne({ where: { username, name } });

        if (!hidden) {
          // TODO(mime): hacky - how can we unify this (here and wherever we use syndicate())
          currentUser.model.url = profileUrl(currentUser.model.username, req);
          updatedContent.url = contentUrl(updatedContent, req);
          await socialButterfly().syndicate(req, currentUser.model, updatedContent);
        }

        return { username: currentUser.model.username, name, hidden, title, thumb, style, code, content };
      }
    ),

    postContent: combineResolvers(
      isAuthor,
      async (
        parent,
        { section, album, name, title, hidden, thumb, style, code, content },
        { currentUser, models, req }
      ) => {
        name = (name || 'untitled') + '-' + nanoid(10);
        name = name.replace(/[^A-Za-z0-9-]/, '-');

        const view = toHTML(content, title);
        const thread = discoverThreadInHTML(view);

        const createdContent = await models.Content.create({
          username: currentUser.model.username,
          section,
          album,
          name,
          title,
          thumb,
          thread,
          hidden,
          style,
          code,
          content,
          view,
        });

        if (!hidden) {
          // TODO(mime): hacky - how can we unify this (here and wherever we use syndicate())
          currentUser.model.url = profileUrl(currentUser.model.username, req);
          createdContent.url = contentUrl(createdContent, req);
          await socialButterfly().syndicate(req, currentUser.model, createdContent);
        }

        return {
          username: currentUser.model.username,
          section,
          album,
          name,
          title,
          hidden,
          thumb,
          style,
          code,
          content,
        };
      }
    ),

    deleteContent: combineResolvers(isAuthor, async (parent, { name }, { currentUser, models, req }) => {
      await models.Content.destroy({ where: { username: currentUser.model.username, name } });
      return true;
    }),
  },
};

export default Content;

export function toHTML(content, title) {
  title = title ? escapeRegExp(title) : '';
  const html = EditorHTMLPlugins(convertToHTML)(convertFromRaw(JSON.parse(content)));
  return html.replace(new RegExp(`^<p>${title}</p>`), '');
}

function discoverThreadInHTML(html) {
  const $ = cheerio.load(html);
  return $('a.u-in-reply-to')
    .first()
    .attr('href');
}

function getSQLSortType(sortType) {
  if (sortType === 'oldest') {
    return ['createdAt'];
  }
  if (sortType === 'alphabetical') {
    return ['title'];
  }
  return ['createdAt', 'DESC'];
}

function decorateArrayWithRefreshFlag(list) {
  for (const item of list) {
    decorateWithRefreshFlag(item);
  }

  return list;
}

function decorateWithRefreshFlag(item) {
  if (item) {
    item.forceRefresh = !!(item.style || item.code);
  }

  return item;
}

function decoratePrefetchImages(item) {
  if (item) {
    item.prefetchImages = (item.view.match(/src=['"][^'"]+['"]/g) || []).map(i => i.slice(5, -1));
  }

  return item;
}

function ellipsize(str, len) {
  if (str.length <= len) {
    return str;
  }

  return str.slice(0, len) + '…';
}
<commit_msg>embeds: fix up yt embeds regex<commit_after>import cheerio from 'cheerio';
import { combineResolvers } from 'graphql-resolvers';
import { contentUrl, profileUrl } from '../../../../shared/util/url_factory';
import { convertFromRaw } from 'draft-js';
import { convertToHTML } from 'draft-convert';
import { EditorHTMLPlugins } from 'hello-world-editor';
import { escapeRegExp } from '../../../../shared/util/regex';
import { isAdmin, isAuthor } from './authorization';
import { isRobotViewing } from '../../../util/crawler';
import nanoid from 'nanoid';
import Sequelize from 'sequelize';
import socialButterfly from '../../../social-butterfly';

const ATTRIBUTES_NAVIGATION = [
  'username',
  'section',
  'album',
  'name',
  'title',
  'thumb',
  'hidden',
  'template',
  'style',
  'code',
];

const Content = {
  Query: {
    allContent: combineResolvers(isAdmin, async (parent, args, { models }) => {
      return await models.Content.findAll();
    }),

    async fetchContent(parent, { username, name }, { currentUser, hostname, models, req }) {
      if (!username) {
        if (hostname) {
          const hostnameUserData = await models.User.findOne({ attributes: ['username'], where: { hostname } });
          if (hostnameUserData) {
            username = hostnameUserData.username;
            name = name || 'home';
          }
        }
        if (!username) {
          username = (await models.User.findOne({ attributes: ['username'], where: { id: 1 } })).username;
          name = name || 'main';
        }
      }

      let content = await models.Content.findOne({ where: { username, name } });

      if (!content) {
        name = 'home';
        // Could be that we don't have a 'main' page. Look for a 'home' page instead.
        content = await models.Content.findOne({ where: { username, name } });
      }

      // Inherit style, code, template from the album.
      if (content?.album && content.album !== 'main') {
        const albumContent = await models.Content.findOne({
          where: { username, section: content.section, album: 'main', name: content.album },
        });
        if (albumContent.style) {
          content.style = albumContent.style + content.style;
        }
        if (albumContent.code) {
          content.code = albumContent.code + content.code;
        }
      }

      // Inherit style, code, template from section.
      if (content && content?.section !== 'main') {
        const sectionContent = await models.Content.findOne({
          where: { username, section: 'main', name: content.section },
        });
        if (sectionContent.style) {
          content.style = sectionContent.style + content.style;
        }
        if (sectionContent.code) {
          content.code = sectionContent.code + content.code;
        }
        if (!content.template && content.album === 'main') {
          content.template = sectionContent.template;
        }
      }
      if (content && !content.template && content.album === 'main') {
        // This is when we're visiting a url of the form: /profile/section/album
        const parentContent = await models.Content.findOne({
          where: { username, section: 'main', name: content.section },
        });
        content.template = parentContent.template;
      }

      // Update count
      // lack of 'req' is a dumb check for !fetchContentHead :-/
      const isOwnerViewing = currentUser?.model?.username === username;
      if (req && content && !isOwnerViewing) {
        const attributes = isRobotViewing(req)
          ? { count_robot: content.count_robot + 1 }
          : { count: content.count + 1 };
        await models.Content.update(attributes, {
          where: {
            username,
            name: content.name,
          },
        });
      }

      return decorateWithRefreshFlag(content);
    },

    // XXX(mime): see HTMLHead.js :(
    async fetchContentHead(parent, { username, name }, { hostname, models }) {
      return await Content.Query.fetchContent(parent, { username, name }, { hostname, models });
    },

    async fetchContentNeighbors(parent, { username, section, album, name }, { currentUser, models }) {
      const ATTRIBUTES_NAVIGATION_WITH_VIEW = ATTRIBUTES_NAVIGATION.concat(['view']);

      if (!username) {
        username = (await models.User.findOne({ attributes: ['username'], where: { id: 1 } })).username;
      }
      name = name || 'main';

      const sectionContent = await models.Content.findOne({
        where: { username, section: 'main', name: section === 'main' ? name : section },
      });
      const order = [['order'], getSQLSortType(sectionContent.sort_type)];

      const isOwnerViewing = currentUser?.model?.username === username;

      const constraints = {
        redirect: false,
      };
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      const collectionConstraints = {
        username,
        section,
        album,
      };
      const collection = await models.Content.findAll({
        attributes: ATTRIBUTES_NAVIGATION_WITH_VIEW,
        where: Object.assign({}, constraints, collectionConstraints),
        order,
      });

      const contentIndex = collection.findIndex(i => i.name === name);

      const collectionItemConstraints = {
        username,
        section: !album ? 'main' : section,
        album: album ? 'main' : '',
        name: album ? album : section,
      };
      const collectionItem = await models.Content.findOne({
        attributes: ATTRIBUTES_NAVIGATION_WITH_VIEW,
        where: collectionItemConstraints,
      });

      return {
        first: decorateWithRefreshFlag(collection[collection.length - 1]),
        prev: decoratePrefetchImages(decorateWithRefreshFlag(collection[contentIndex + 1])),
        top: decorateWithRefreshFlag(collectionItem),
        next: decoratePrefetchImages(decorateWithRefreshFlag(collection[contentIndex - 1])),
        last: decorateWithRefreshFlag(collection[0]),
      };
    },

    async fetchCollection(parent, { username, section, album, name }, { currentUser, models }) {
      const isOwnerViewing = currentUser?.model?.username === username;

      const sectionContent = await models.Content.findOne({
        where: { username, section: !album ? 'main' : section, name },
      });
      const order = [['order'], getSQLSortType(sectionContent.sort_type)];

      const constraints = {
        redirect: false,
      };
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      // For links section, we grab the anchor url in the view.
      const attributes = section === 'links' ? ATTRIBUTES_NAVIGATION.concat('view') : ATTRIBUTES_NAVIGATION;

      let collection = [];
      // Check first to see if this is an album and grab items within it.
      if (section !== 'main') {
        const contentConstraints = { username, section, album: name };
        collection = await models.Content.findAll({
          attributes,
          where: Object.assign({}, constraints, contentConstraints),
          order,
        });
      }

      // If we couldn't find an album then, look for the section instead and find the albums within it.
      if (!collection.length) {
        const contentConstraints = { username, section: name, album: 'main' };
        collection = await models.Content.findAll({
          attributes,
          where: Object.assign({}, constraints, contentConstraints),
          order,
        });

        const albumConstraints = Object.assign({}, constraints, contentConstraints);
        for (const content of collection) {
          albumConstraints['album'] = content.name;
          const albumFirstContent = await models.Content.findOne({
            attributes: ['thumb'],
            where: Object.assign({}, constraints, albumConstraints),
            order,
          });
          if (albumFirstContent) {
            content.thumb = albumFirstContent.thumb;
          }
        }

        // Then, it combines with top-level items in the section (not in an album).
        const topLevelContentConstraints = { username, section: name, album: '' };
        const topLevelItems = await models.Content.findAll({
          attributes,
          where: Object.assign({}, constraints, topLevelContentConstraints),
          order,
        });
        collection = collection.concat(topLevelItems);
      }

      // This is if the top-level user page is an album. (I'm pretty sure at least :-/)
      if (!collection.length && section !== 'main') {
        const contentConstraints = { username, section };
        collection = await models.Content.findAll({
          attributes: ATTRIBUTES_NAVIGATION,
          where: Object.assign({}, constraints, contentConstraints),
          order,
        });
      }

      if (section === 'links') {
        collection.forEach(item => {
          const link = item.view.match(/https?:\/\/[^"']+/);
          const youTubeMatch = link?.[0].match(/youtube.com\/embed\/([^?<]+)/);
          const youTubeSeriesMatch = link?.[0].match(/youtube.com\/embed\/videoseries\?list=([^?<]+)/);
          item.externalLink = youTubeSeriesMatch
            ? `https://www.youtube.com/playlist?list=${youTubeSeriesMatch[1]}`
            : youTubeMatch
            ? `https://youtu.be/${youTubeMatch[1]}`
            : link?.[0];
        });
      }

      return decorateArrayWithRefreshFlag(collection);
    },

    async fetchCollectionPaginated(parent, { username, section, name, offset }, { currentUser, models }) {
      const isOwnerViewing = currentUser?.model?.username === username;
      const limit = 20;

      const constraints = {
        redirect: false,
      };
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      const notEqualToMain = { [Sequelize.Op.ne]: 'main' };
      const contentConstraints = {
        username,
        section: section !== 'main' ? section : name !== 'home' ? name : notEqualToMain,
        album: section !== 'main' ? name : notEqualToMain,
      };
      return await models.Content.findAll({
        where: Object.assign({}, constraints, contentConstraints),
        order: [['order'], ['createdAt', 'DESC']],
        limit,
        offset: offset * limit,
      });
    },

    async fetchCollectionLatest(parent, { username, section, name }, { currentUser, models }) {
      const isOwnerViewing = currentUser?.model?.username === username;

      const constraints = {
        redirect: false,
      };
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      const contentConstraints = { username, section: name };
      return await models.Content.findOne({
        where: Object.assign({}, constraints, contentConstraints),
        order: [['order'], ['createdAt', 'DESC']],
      });
    },

    async fetchSiteMap(parent, { username }, { currentUser, models }) {
      const isOwnerViewing = currentUser?.model?.username === username;
      const constraints = {};
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      const contentConstraints = {
        username,
        section: 'main',
        name: { [Sequelize.Op.notIn]: ['main', 'home', 'comments'] },
        redirect: 0,
      };

      const sections = await models.Content.findAll({
        attributes: ATTRIBUTES_NAVIGATION,
        where: Object.assign({}, constraints, contentConstraints),
        order: [['order'], ['createdAt']],
      });

      let siteMap = [];
      for (const section of sections) {
        const albumConstraints = {
          username,
          section: section.name,
          album: 'main',
        };

        const albums = await models.Content.findAll({
          attributes: ATTRIBUTES_NAVIGATION,
          where: Object.assign({}, constraints, albumConstraints),
          order: [['order'], ['createdAt']],
        });

        siteMap.push(section);
        if (albums.length) {
          siteMap = siteMap.concat(albums);
        }
      }

      return decorateArrayWithRefreshFlag(siteMap);
    },

    async searchContent(parent, { username, query }, { currentUser, models }) {
      const isOwnerViewing = currentUser?.model?.username === username;

      const constraints = {
        redirect: false,
        username,
      };
      if (!isOwnerViewing) {
        constraints['hidden'] = false;
      }

      let collection = await models.Content.findAll({
        attributes: ATTRIBUTES_NAVIGATION.concat(['view']),
        where: [constraints, Sequelize.literal('match (title, view) against (:query)')],
        replacements: {
          query,
        },
      });

      for (const item of collection) {
        const HTML_REGEX = /<[^>]+>/g;
        item.preview = ellipsize(item.view.replace(HTML_REGEX, '').trim(), 130);
      }

      return decorateArrayWithRefreshFlag(collection);
    },
  },

  Mutation: {
    saveContent: combineResolvers(
      isAuthor,
      async (parent, { name, hidden, title, thumb, style, code, content }, { currentUser, models, req }) => {
        const username = currentUser.model.username;
        const view = toHTML(content, title);
        const thread = discoverThreadInHTML(view);

        await models.Content.update(
          {
            hidden,
            title,
            thumb,
            style,
            thread,
            code,
            content,
            view,
          },
          {
            where: {
              username,
              name,
            },
          }
        );

        const updatedContent = await models.Content.findOne({ where: { username, name } });

        if (!hidden) {
          // TODO(mime): hacky - how can we unify this (here and wherever we use syndicate())
          currentUser.model.url = profileUrl(currentUser.model.username, req);
          updatedContent.url = contentUrl(updatedContent, req);
          await socialButterfly().syndicate(req, currentUser.model, updatedContent);
        }

        return { username: currentUser.model.username, name, hidden, title, thumb, style, code, content };
      }
    ),

    postContent: combineResolvers(
      isAuthor,
      async (
        parent,
        { section, album, name, title, hidden, thumb, style, code, content },
        { currentUser, models, req }
      ) => {
        name = (name || 'untitled') + '-' + nanoid(10);
        name = name.replace(/[^A-Za-z0-9-]/, '-');

        const view = toHTML(content, title);
        const thread = discoverThreadInHTML(view);

        const createdContent = await models.Content.create({
          username: currentUser.model.username,
          section,
          album,
          name,
          title,
          thumb,
          thread,
          hidden,
          style,
          code,
          content,
          view,
        });

        if (!hidden) {
          // TODO(mime): hacky - how can we unify this (here and wherever we use syndicate())
          currentUser.model.url = profileUrl(currentUser.model.username, req);
          createdContent.url = contentUrl(createdContent, req);
          await socialButterfly().syndicate(req, currentUser.model, createdContent);
        }

        return {
          username: currentUser.model.username,
          section,
          album,
          name,
          title,
          hidden,
          thumb,
          style,
          code,
          content,
        };
      }
    ),

    deleteContent: combineResolvers(isAuthor, async (parent, { name }, { currentUser, models, req }) => {
      await models.Content.destroy({ where: { username: currentUser.model.username, name } });
      return true;
    }),
  },
};

export default Content;

export function toHTML(content, title) {
  title = title ? escapeRegExp(title) : '';
  const html = EditorHTMLPlugins(convertToHTML)(convertFromRaw(JSON.parse(content)));
  return html.replace(new RegExp(`^<p>${title}</p>`), '');
}

function discoverThreadInHTML(html) {
  const $ = cheerio.load(html);
  return $('a.u-in-reply-to')
    .first()
    .attr('href');
}

function getSQLSortType(sortType) {
  if (sortType === 'oldest') {
    return ['createdAt'];
  }
  if (sortType === 'alphabetical') {
    return ['title'];
  }
  return ['createdAt', 'DESC'];
}

function decorateArrayWithRefreshFlag(list) {
  for (const item of list) {
    decorateWithRefreshFlag(item);
  }

  return list;
}

function decorateWithRefreshFlag(item) {
  if (item) {
    item.forceRefresh = !!(item.style || item.code);
  }

  return item;
}

function decoratePrefetchImages(item) {
  if (item) {
    item.prefetchImages = (item.view.match(/src=['"][^'"]+['"]/g) || []).map(i => i.slice(5, -1));
  }

  return item;
}

function ellipsize(str, len) {
  if (str.length <= len) {
    return str;
  }

  return str.slice(0, len) + '…';
}
<|endoftext|>


<commit_before>'use strict';

import React from 'react';
import Dialog from 'react-toolbox/lib/dialog';
import PureComponent from './PureComponent';
var {map, pick, mapObject, getIn, get} = require('./underscore_ext');
var {servers: {localHub}} = require('./defaultServers');
var platform = require('platform');
var {testStatus} = require('./xenaQuery');
var Rx = require('./rx');
import Link from 'react-toolbox/lib/link';
import styles from './LaunchHelper.module.css';

var osFiles = {
		osxJre: {
			pattern: "ucsc_xena_macos_[_0-9]*_with_jre.dmg",
			description: "OSX installer, bundled JRE",
			help: "Recommended for OSX 10.7 and above"
		},
		osxNoJre: {
			pattern: "ucsc_xena_macos_[_0-9]*.dmg",
			description: "OSX installer, no JRE",
			help: "Recommended for OSX 10.6"
		},
		win32: {
			pattern: "ucsc_xena_windows_[_0-9]*.exe",
			description: "Windows 32 bit installer, bundled JRE",
			help: "Recommended for all 32 bit versions of Windows"
		},
		win64: {
			pattern: "ucsc_xena_windows-x64_[_0-9]*.exe",
			description: "Windows 64 bit installer, bundled JRE",
			help: "Recommended for all 64 bit versions of Windows"
		},
		tar: {
			pattern: "ucsc_xena_[_0-9]*.tar.gz",
			description: "Tar archive, no updater or JRE",
			help: "Recommended for linux server deployments"
		}
	}, defaults = {
		'OS X': {32: 'osxNoJre', 64: 'osxJre'},
		'Windows': {32: 'win32', 64: 'win64'},
		'Linux': {32: 'tar', 64: 'tar'}
	};

function findMatch(pattern, list) {
	var matches = list.filter(s => s.match(pattern));
	return matches.length > 0 ? matches[0] : undefined;
}

var matchPaths = serverFiles =>
	mapObject(osFiles, ({pattern}, key) =>
			  findMatch(osFiles[key].pattern, serverFiles));

var parseInt10 = s => parseInt(s, 10);

function osxArch() {
	var version = getIn(platform, ['os', 'version']),
		parts = version && version.match(/(\d*)\.(\d*)/).slice(1).map(parseInt10); // [maj, min, patch]
	return (parts && parts[0] === 10 && parts[1] <= 6) ? 32 : 64;
}

function getOs() {
	var family = getIn(platform, ['os', 'family']);
	if (family) {
		if (family.indexOf('Windows') !== -1) {
			return 'Windows';
		}
		if (family.indexOf('OS X') !== -1) {
			return 'OS X';
		}
		if (family.indexOf('Linux') !== -1) {
			return 'Linux';
		}
	}
}

var downloadDir = 'https://genome-cancer.ucsc.edu/download/public/get-xena';
var i4jLogo = `${downloadDir}/install4j_small.png`;
var updatesPath = `${downloadDir}/updates.xml`;
var downloadPath = name => `${downloadDir}/${name}`;
var getFileName = e => downloadPath(e.getAttribute('fileName'));

class XenaDownload extends React.Component {
	state = {};

	componentDidMount() {
		this.sub = Rx.Observable.ajax({method: 'GET', responseType: 'xml', url: updatesPath, crossDomain: true}).subscribe(xml => {
			var files = matchPaths([...xml.response.getElementsByTagName('entry')]
								   .map(getFileName));

			this.setState({files}); //eslint-disable-line react/no-did-mount-set-state
		});
	}

	componentWillUnmount() {
		this.sub.unsubscribe();
	}

	onShowAdvanced = () => {
		this.props.onShowAdvanced(!this.props.advanced);
	}

	render() {
		var {files} = this.state,
			{advanced} = this.props,
			os = getOs(),
			arch = os === 'OS X' ? osxArch() : getIn(platform, ['os', 'architecture']),
			defaultTarget = getIn(defaults, [os, arch]),
			defaultInstall = get(files, defaultTarget);

		return (

				{defaultInstall ? <Link className={styles.downloadLink} href={defaultInstall} label='Install UCSC Xena hub'/> : null}
				{defaultInstall ? Show {advanced ? 'Basic' : 'Advanced'} : null}
				{defaultInstall ?
 : null}
				{(defaultInstall || advanced) ? null : 'Please download an installer'}
				{(files && (advanced || !defaultInstall)) ? map(pick(osFiles, (_, k) => files[k]), (info, key) =>
					 <Link className={styles.downloadLink} href={files[key]} title={info.help} label={info.description}/>) : null}

);
	}
}

var statusHelp = {
	launching: (

Attempting to start the Xena Hub on your computer...

),
	started: (

The Xena Hub is starting...

),
	failed: (

We were not able to start the Xena Hub for you. Please open the application from your Applications Folder,
				or download it if you have not previously installed it.

The Xena Hub needs to be running for you to import or visualize data from your own computer.

),
	failedStarting: (

The Xena Hub is taking a long time to start. If you have a lot of local data, this is expected.
			It should be ready soon. If it continues to be a problem, please contact us at genome-cancer@soe.ucsc.edu

),
	up: (

Your local Xena Hub is running.

To view your data, use the "Visualization" button

),
	lost: (

We have lost contact with your local Xena Hub.

To re-start it, you may reload this page.

)
};

var launch = () => {
	var i = document.getElementById('xenaLauncher');
	if (i) {
		document.body.removeChild(i);
	}
	i = document.createElement('iframe');
	i.id = 'xenaLauncher';
	document.body.appendChild(i);
	i.src = 'ucscxena://';
};

// up/down/launching/started/failed/failedStarting

var nextState = (state, status) =>
	status !== 'down' ? status :
	state === 'up' ? 'lost' :
	state;

var refresh = 2000;
var contactTimeout = 200;
var launchTimeout = 10 * 1000;
var bootTimeout = 2 * 60 * 1000;

var {of, interval} = Rx.Observable;

var wrap = (onStartup, Comp) => class extends PureComponent {
	static displayName = 'LaunchHelperWrapper';

	state = {advanced: false, show: false, status: 'down'};

	componentDidMount() {
		var shouldLaunch = !(this.props.localStatus === 'lost'),
			ping = interval(refresh).startWith(undefined)
				.switchMapTo(testStatus(localHub, contactTimeout).map(({status}) => status)).share(),
			firstDown = ping.first().filter(p => p === 'down'),
			pingStarted = ping.filter(p => p === 'started'),
			pingUp = ping.filter(p => p === 'up'),
			failed = of('failed'),
			failedStarting = of('failedStarting'),
			launching = of('launching'),

			boot = pingStarted.first().switchMapTo(failedStarting.delay(bootTimeout)),
			launch = firstDown.switchMap(() => shouldLaunch ? launching.concat(failed.delay(launchTimeout)) : failed),

			timeouts = launch.takeUntil(pingStarted).merge(boot).takeUntil(pingUp);

		this.sub = ping.merge(timeouts)
			.map(status => nextState(this.state.status, status))
			.distinctUntilChanged()
			.subscribe(this.updatePing);

		this.sub.add(firstDown.subscribe(this.showAndLaunch(shouldLaunch)));
	}

	componentWillUnmount() {
		if (this.timeoutID) {
			clearTimeout(this.timeoutID);
		}
		if (this.sub) {
			this.sub.unsubscribe();
		}
	}

	showAndLaunch = shouldLaunch => () => {
		this.setState({show: true});
		if (shouldLaunch) {
			launch();
		}
	}

	updatePing = nextStatus => {
		this.props.callback(['localStatus', nextStatus]);
		if (nextStatus === 'up' && this.state.status !== 'up') {
			onStartup(this.props);
			this.setState({show: false});
		}
		this.setState({status: nextStatus});
	}

	onShowAdvanced = advanced => {
		this.setState({advanced});
	}

	onHide = () => {
		this.setState({show: false});
	}

	onShow = () => {
		this.setState({show: true});
	}

	actions = [
		{label: 'Close', onClick: this.onHide}
	];

	render() {
		var {advanced, status, show} = this.state,
			statusBadge =
				status === 'up' ? (
					signal_wifi_4_bar) : (
					signal_wifi_off);

		return (
			<Comp {...this.props} badge={statusBadge}>
				<Dialog active={show} actions={this.actions}>
					Help

Starting Xena Hub

					{status === 'failed' ? <XenaDownload advanced={advanced} onShowAdvanced={this.onShowAdvanced}/> : null}
					{statusHelp[status]}
				</Dialog>
			</Comp>);
	}
};

var wrapLaunchHelper = (shouldMount, onStartup, Comp) => {
	var Wrapper = wrap(onStartup, Comp);
	return props => shouldMount(props) ? <Wrapper {...props}/> :
		<Comp {...props}/>;
};

export default wrapLaunchHelper;
<commit_msg>Display name for LaunchHelperMounter.<commit_after>'use strict';

import React from 'react';
import Dialog from 'react-toolbox/lib/dialog';
import PureComponent from './PureComponent';
var {map, pick, mapObject, getIn, get} = require('./underscore_ext');
var {servers: {localHub}} = require('./defaultServers');
var platform = require('platform');
var {testStatus} = require('./xenaQuery');
var Rx = require('./rx');
import Link from 'react-toolbox/lib/link';
import styles from './LaunchHelper.module.css';

var osFiles = {
		osxJre: {
			pattern: "ucsc_xena_macos_[_0-9]*_with_jre.dmg",
			description: "OSX installer, bundled JRE",
			help: "Recommended for OSX 10.7 and above"
		},
		osxNoJre: {
			pattern: "ucsc_xena_macos_[_0-9]*.dmg",
			description: "OSX installer, no JRE",
			help: "Recommended for OSX 10.6"
		},
		win32: {
			pattern: "ucsc_xena_windows_[_0-9]*.exe",
			description: "Windows 32 bit installer, bundled JRE",
			help: "Recommended for all 32 bit versions of Windows"
		},
		win64: {
			pattern: "ucsc_xena_windows-x64_[_0-9]*.exe",
			description: "Windows 64 bit installer, bundled JRE",
			help: "Recommended for all 64 bit versions of Windows"
		},
		tar: {
			pattern: "ucsc_xena_[_0-9]*.tar.gz",
			description: "Tar archive, no updater or JRE",
			help: "Recommended for linux server deployments"
		}
	}, defaults = {
		'OS X': {32: 'osxNoJre', 64: 'osxJre'},
		'Windows': {32: 'win32', 64: 'win64'},
		'Linux': {32: 'tar', 64: 'tar'}
	};

function findMatch(pattern, list) {
	var matches = list.filter(s => s.match(pattern));
	return matches.length > 0 ? matches[0] : undefined;
}

var matchPaths = serverFiles =>
	mapObject(osFiles, ({pattern}, key) =>
			  findMatch(osFiles[key].pattern, serverFiles));

var parseInt10 = s => parseInt(s, 10);

function osxArch() {
	var version = getIn(platform, ['os', 'version']),
		parts = version && version.match(/(\d*)\.(\d*)/).slice(1).map(parseInt10); // [maj, min, patch]
	return (parts && parts[0] === 10 && parts[1] <= 6) ? 32 : 64;
}

function getOs() {
	var family = getIn(platform, ['os', 'family']);
	if (family) {
		if (family.indexOf('Windows') !== -1) {
			return 'Windows';
		}
		if (family.indexOf('OS X') !== -1) {
			return 'OS X';
		}
		if (family.indexOf('Linux') !== -1) {
			return 'Linux';
		}
	}
}

var downloadDir = 'https://genome-cancer.ucsc.edu/download/public/get-xena';
var i4jLogo = `${downloadDir}/install4j_small.png`;
var updatesPath = `${downloadDir}/updates.xml`;
var downloadPath = name => `${downloadDir}/${name}`;
var getFileName = e => downloadPath(e.getAttribute('fileName'));

class XenaDownload extends React.Component {
	state = {};

	componentDidMount() {
		this.sub = Rx.Observable.ajax({method: 'GET', responseType: 'xml', url: updatesPath, crossDomain: true}).subscribe(xml => {
			var files = matchPaths([...xml.response.getElementsByTagName('entry')]
								   .map(getFileName));

			this.setState({files}); //eslint-disable-line react/no-did-mount-set-state
		});
	}

	componentWillUnmount() {
		this.sub.unsubscribe();
	}

	onShowAdvanced = () => {
		this.props.onShowAdvanced(!this.props.advanced);
	}

	render() {
		var {files} = this.state,
			{advanced} = this.props,
			os = getOs(),
			arch = os === 'OS X' ? osxArch() : getIn(platform, ['os', 'architecture']),
			defaultTarget = getIn(defaults, [os, arch]),
			defaultInstall = get(files, defaultTarget);

		return (

				{defaultInstall ? <Link className={styles.downloadLink} href={defaultInstall} label='Install UCSC Xena hub'/> : null}
				{defaultInstall ? Show {advanced ? 'Basic' : 'Advanced'} : null}
				{defaultInstall ?
 : null}
				{(defaultInstall || advanced) ? null : 'Please download an installer'}
				{(files && (advanced || !defaultInstall)) ? map(pick(osFiles, (_, k) => files[k]), (info, key) =>
					 <Link className={styles.downloadLink} href={files[key]} title={info.help} label={info.description}/>) : null}

);
	}
}

var statusHelp = {
	launching: (

Attempting to start the Xena Hub on your computer...

),
	started: (

The Xena Hub is starting...

),
	failed: (

We were not able to start the Xena Hub for you. Please open the application from your Applications Folder,
				or download it if you have not previously installed it.

The Xena Hub needs to be running for you to import or visualize data from your own computer.

),
	failedStarting: (

The Xena Hub is taking a long time to start. If you have a lot of local data, this is expected.
			It should be ready soon. If it continues to be a problem, please contact us at genome-cancer@soe.ucsc.edu

),
	up: (

Your local Xena Hub is running.

To view your data, use the "Visualization" button

),
	lost: (

We have lost contact with your local Xena Hub.

To re-start it, you may reload this page.

)
};

var launch = () => {
	var i = document.getElementById('xenaLauncher');
	if (i) {
		document.body.removeChild(i);
	}
	i = document.createElement('iframe');
	i.id = 'xenaLauncher';
	document.body.appendChild(i);
	i.src = 'ucscxena://';
};

// up/down/launching/started/failed/failedStarting

var nextState = (state, status) =>
	status !== 'down' ? status :
	state === 'up' ? 'lost' :
	state;

var refresh = 2000;
var contactTimeout = 200;
var launchTimeout = 10 * 1000;
var bootTimeout = 2 * 60 * 1000;

var {of, interval} = Rx.Observable;

var wrap = (onStartup, Comp) => class extends PureComponent {
	static displayName = 'LaunchHelperWrapper';

	state = {advanced: false, show: false, status: 'down'};

	componentDidMount() {
		var shouldLaunch = !(this.props.localStatus === 'lost'),
			ping = interval(refresh).startWith(undefined)
				.switchMapTo(testStatus(localHub, contactTimeout).map(({status}) => status)).share(),
			firstDown = ping.first().filter(p => p === 'down'),
			pingStarted = ping.filter(p => p === 'started'),
			pingUp = ping.filter(p => p === 'up'),
			failed = of('failed'),
			failedStarting = of('failedStarting'),
			launching = of('launching'),

			boot = pingStarted.first().switchMapTo(failedStarting.delay(bootTimeout)),
			launch = firstDown.switchMap(() => shouldLaunch ? launching.concat(failed.delay(launchTimeout)) : failed),

			timeouts = launch.takeUntil(pingStarted).merge(boot).takeUntil(pingUp);

		this.sub = ping.merge(timeouts)
			.map(status => nextState(this.state.status, status))
			.distinctUntilChanged()
			.subscribe(this.updatePing);

		this.sub.add(firstDown.subscribe(this.showAndLaunch(shouldLaunch)));
	}

	componentWillUnmount() {
		if (this.timeoutID) {
			clearTimeout(this.timeoutID);
		}
		if (this.sub) {
			this.sub.unsubscribe();
		}
	}

	showAndLaunch = shouldLaunch => () => {
		this.setState({show: true});
		if (shouldLaunch) {
			launch();
		}
	}

	updatePing = nextStatus => {
		this.props.callback(['localStatus', nextStatus]);
		if (nextStatus === 'up' && this.state.status !== 'up') {
			onStartup(this.props);
			this.setState({show: false});
		}
		this.setState({status: nextStatus});
	}

	onShowAdvanced = advanced => {
		this.setState({advanced});
	}

	onHide = () => {
		this.setState({show: false});
	}

	onShow = () => {
		this.setState({show: true});
	}

	actions = [
		{label: 'Close', onClick: this.onHide}
	];

	render() {
		var {advanced, status, show} = this.state,
			statusBadge =
				status === 'up' ? (
					signal_wifi_4_bar) : (
					signal_wifi_off);

		return (
			<Comp {...this.props} badge={statusBadge}>
				<Dialog active={show} actions={this.actions}>
					Help

Starting Xena Hub

					{status === 'failed' ? <XenaDownload advanced={advanced} onShowAdvanced={this.onShowAdvanced}/> : null}
					{statusHelp[status]}
				</Dialog>
			</Comp>);
	}
};

var wrapLaunchHelper = (shouldMount, onStartup, Comp) => {
	var Wrapper = wrap(onStartup, Comp),
		LaunchHelperMounter = props => shouldMount(props) ? <Wrapper {...props}/> : <Comp {...props}/>;
	return LaunchHelperMounter;
};

export default wrapLaunchHelper;
<|endoftext|>