<commit_before>import React from 'react'
import _ from 'lodash'
import API from '../../api'
import {objectToQueryString} from '../../utils/query'

export default class Typeahead extends React.Component {

  static propTypes = {
    code:            React.PropTypes.string,
    disabled:        React.PropTypes.bool,
    placeholder:     React.PropTypes.string,
    defaultValue:    React.PropTypes.string,
    endpoint:        React.PropTypes.string,
    query:           React.PropTypes.string,
    extraQueries:    React.PropTypes.object,
    resultField:     React.PropTypes.string,
    client:          React.PropTypes.function,
    requestThrottle: React.PropTypes.number,
    itemComponent:   React.PropTypes.element,
    listComponent:   React.PropTypes.element
  }

  static defaultProps = {
    onChange:        _.noop,
    onChooseResult:  _.noop,
    query:           'q',
    extraQueries:    {},
    resultField:     'results',
    requestThrottle: 500,
    listComponent:   TypeaheadResultList
  }

  constructor(props) {
    super(props)
    this.state = {
      results:             [],
      searchValue:         '',
      isLoadingResults:    false,
      focused:             false,
      resultCursor:        -1,
      queryCounter:        0,
      errorLoadingResults: false,
      showResults:         false
    }

    this.inputChangeHandler = _.throttle(this.onInputChange.bind(this), this.props.requestThrottle, { leading: false })

    this.api = new API(props.client)
  }

  render() {
    const { results, searchValue, isLoadingResults, showResults } = this.state
    const { placeholder, itemComponent, onChooseResult } = this.props

    let classes = ['typeahead']
    classes.push((true) ? 'top' : 'bottom')
    if (showResults && results.length > 0) {
      classes.push('active')
    }

    const ListComponent = this.props.listComponent || TypeaheadResultList

    return (

          <input type="text"
                 ref="input"
                 value={searchValue}
                 {...this.attachInputCallbacks()}
                 placeholder={placeholder} />

        {(() => {
          if (isLoadingResults && _.isEmpty(results)) {
            return <TypeaheadResultLoader />
          }
          else if (showResults && !_.isEmpty(results)) {
            return <ListComponent results={results} onChooseResult={onChooseResult}
                                  itemComponent={itemComponent} />
          }
          else if (showResults && searchValue.length >= 1) {
            return <TypeaheadEmptyResult />
          }
          else {
            // Show nothing
          }
        })()}

    )
  }

  componentDidMount() {
    const component    = this
    this.closeListener = e => {
      if (e.target !== component.refs.input) {
        component.hideResults()
      }
    }
    document.addEventListener('click', this.closeListener)
  }

  componentWillUnmount() {
    document.removeEventListener('click', this.closeListener)
  }

  attachInputCallbacks() {
    return {
      onFocus:  () => {
        this.setState({ focused: true, showResults: true })
      },
      onBlur:   () => {
        // this.setState({ focused: false })
      },
      onChange: (event) => {
        this.setState({ searchValue: event.target.value, isLoadingResults: true })
        this.inputChangeHandler()
      }
    }
  }

  onInputChange() {
    const { searchValue, queryCounter } = this.state
    const { endpoint, query, extraQueries, resultField } = this.props

    if (_.isEmpty(searchValue)) {
      this.setState({
        queryCounter:        this.state.queryCounter + 1,
        isLoadingResults:    false,
        errorLoadingResults: false,
        showResults:         false,
        results:             []
      })
      return
    }

    const requestQueryString = objectToQueryString({ ...extraQueries, [query]: searchValue })
    const queryId            = queryCounter + 1

    this.setState({ queryCounter: queryId, errorLoadingResults: false })

    this.api.loadJSON(endpoint + requestQueryString)
      .then(response => {
        // Tracking the queryId ensures only the latest result is processed, in case multiple
        // requests arrive out of order.
        if (this.state.queryCounter == queryId) {
          const newResults = resultField ? response[resultField] : response
          this.setState({ results: newResults || [], isLoadingResults: false, showResults: true })
        }
      })
      .catch(error => this.setState({ errorLoadingResults: true, showResults: true }))
  }

  clear() {
    this.setState({
      queryCounter:        this.state.queryCounter + 1,
      isLoadingResults:    false,
      errorLoadingResults: false,
      results:             [],
      searchValue:         '',
      showResults:         false
    })
  }

  hideResults() {
    this.setState({
      showResults: false
    })
  }

}

export const TypeaheadResultLoader = props => {
  return (

Loading

  )
}

export const TypeaheadEmptyResult = props => {
  return (

No Results

  )
}

export const TypeaheadDefaultResult = ({ onClick = _.noop, result }) => {
  return (

{result.title || result.name || (result.first_name
        ? `${result.first_name} ${result.last_name}`
        : null) || result.label || result.id}

{result.description || result.details}

  )
}

export class TypeaheadResultList extends React.Component {

  static propTypes = {
    results:        React.PropTypes.arrayOf(React.PropTypes.object),
    itemComponent:  React.PropTypes.element,
    onChooseResult: React.PropTypes.func
  }

  static defaultProps = {
    results:        [],
    itemComponent:  TypeaheadDefaultResult,
    onChooseResult: _.noop
  }

  render() {
    const { results, itemComponent, onChooseResult } = this.props
    const clickHandler = (r, e) => {
      e.preventDefault()
      e.stopPropagation()
      onChooseResult(r)
    }
    return (

        {_.map(results,
          result => React.createElement(itemComponent, { result, onClick: _.partial(clickHandler, result) }))}

    )
  }

}
<commit_msg>Add the clearOnBlur prop to Typeaheads Boolean. When set, the Typeahead clears itself when it loses focus.<commit_after>import React from 'react'
import _ from 'lodash'
import API from '../../api'
import {objectToQueryString} from '../../utils/query'

export default class Typeahead extends React.Component {

  static propTypes = {
    code:            React.PropTypes.string,
    disabled:        React.PropTypes.bool,
    placeholder:     React.PropTypes.string,
    defaultValue:    React.PropTypes.string,
    endpoint:        React.PropTypes.string,
    query:           React.PropTypes.string,
    extraQueries:    React.PropTypes.object,
    resultField:     React.PropTypes.string,
    client:          React.PropTypes.function,
    requestThrottle: React.PropTypes.number,
    itemComponent:   React.PropTypes.element,
    listComponent:   React.PropTypes.element,
    clearOnBlur:     React.PropTypes.bool
  }

  static defaultProps = {
    onChange:        _.noop,
    onChooseResult:  _.noop,
    query:           'q',
    extraQueries:    {},
    resultField:     'results',
    requestThrottle: 500,
    listComponent:   TypeaheadResultList,
    clearOnBlur:     false
  }

  constructor(props) {
    super(props)
    this.state = {
      results:             [],
      searchValue:         '',
      isLoadingResults:    false,
      focused:             false,
      resultCursor:        -1,
      queryCounter:        0,
      errorLoadingResults: false,
      showResults:         false
    }

    this.inputChangeHandler = _.throttle(this.onInputChange.bind(this), this.props.requestThrottle, { leading: false })

    this.api = new API(props.client)
  }

  render() {
    const { results, searchValue, isLoadingResults, showResults } = this.state
    const { placeholder, itemComponent, onChooseResult } = this.props

    let classes = ['typeahead']
    classes.push((true) ? 'top' : 'bottom')
    if (showResults && results.length > 0) {
      classes.push('active')
    }

    const ListComponent = this.props.listComponent || TypeaheadResultList

    return (

          <input type="text"
                 ref="input"
                 value={searchValue}
                 {...this.attachInputCallbacks()}
                 placeholder={placeholder} />

        {(() => {
          if (isLoadingResults && _.isEmpty(results)) {
            return <TypeaheadResultLoader />
          }
          else if (showResults && !_.isEmpty(results)) {
            return <ListComponent results={results} onChooseResult={onChooseResult}
                                  itemComponent={itemComponent} />
          }
          else if (showResults && searchValue.length >= 1) {
            return <TypeaheadEmptyResult />
          }
          else {
            // Show nothing
          }
        })()}

    )
  }

  componentDidMount() {
    const component    = this
    this.closeListener = e => {
      if (e.target !== component.refs.input) {
        if(this.props.clearOnBlur) {
          component.clear()
        }
        else {
          component.hideResults()
        }
      }
    }
    document.addEventListener('click', this.closeListener)
  }

  componentWillUnmount() {
    document.removeEventListener('click', this.closeListener)
  }

  attachInputCallbacks() {
    return {
      onFocus:  () => {
        this.setState({ focused: true, showResults: true })
      },
      onBlur:   () => {
        // this.setState({ focused: false })
      },
      onChange: (event) => {
        this.setState({ searchValue: event.target.value, isLoadingResults: true })
        this.inputChangeHandler()
      }
    }
  }

  onInputChange() {
    const { searchValue, queryCounter } = this.state
    const { endpoint, query, extraQueries, resultField } = this.props

    if (_.isEmpty(searchValue)) {
      this.setState({
        queryCounter:        this.state.queryCounter + 1,
        isLoadingResults:    false,
        errorLoadingResults: false,
        showResults:         false,
        results:             []
      })
      return
    }

    const requestQueryString = objectToQueryString({ ...extraQueries, [query]: searchValue })
    const queryId            = queryCounter + 1

    this.setState({ queryCounter: queryId, errorLoadingResults: false })

    this.api.loadJSON(endpoint + requestQueryString)
      .then(response => {
        // Tracking the queryId ensures only the latest result is processed, in case multiple
        // requests arrive out of order.
        if (this.state.queryCounter == queryId) {
          const newResults = resultField ? response[resultField] : response
          this.setState({ results: newResults || [], isLoadingResults: false, showResults: true })
        }
      })
      .catch(error => this.setState({ errorLoadingResults: true, showResults: true }))
  }

  clear() {
    this.setState({
      queryCounter:        this.state.queryCounter + 1,
      isLoadingResults:    false,
      errorLoadingResults: false,
      results:             [],
      searchValue:         '',
      showResults:         false
    })
  }

  hideResults() {
    this.setState({
      showResults: false
    })
  }

}

export const TypeaheadResultLoader = props => {
  return (

Loading

  )
}

export const TypeaheadEmptyResult = props => {
  return (

No Results

  )
}

export const TypeaheadDefaultResult = ({ onClick = _.noop, result }) => {
  return (

{result.title || result.name || (result.first_name
        ? `${result.first_name} ${result.last_name}`
        : null) || result.label || result.id}

{result.description || result.details}

  )
}

export class TypeaheadResultList extends React.Component {

  static propTypes = {
    results:        React.PropTypes.arrayOf(React.PropTypes.object),
    itemComponent:  React.PropTypes.element,
    onChooseResult: React.PropTypes.func
  }

  static defaultProps = {
    results:        [],
    itemComponent:  TypeaheadDefaultResult,
    onChooseResult: _.noop
  }

  render() {
    const { results, itemComponent, onChooseResult } = this.props
    const clickHandler = (r, e) => {
      e.preventDefault()
      e.stopPropagation()
      onChooseResult(r)
    }
    return (

        {_.map(results,
          result => React.createElement(itemComponent, { result, onClick: _.partial(clickHandler, result) }))}

    )
  }

}
<|endoftext|>


<commit_before>// string fields
module.exports = function(manager) {
  return function(property) {
    // type validator
    var checkType = property.checkType;
    property.checkType = function(value) {
      return typeof value === 'string' && checkType(value);
    };

    // contents validator
    if (property.hasOwnProperty('validate')) {
      var checkContents = property.checkContents;
      if (property.validate instanceof Array) {
        property.checkContents = function(value) {
          if (property.validate.length == 1) {
            if (value.length > property.validate[0]) {
              throw new Error('"' + property.name + '" size exceeds ' + property.validate[0] + ' limit');
            }
          } else {
            if (value.length > property.validate[1]) {
              throw new Error('"' + property.name + '" size exceeds ' + property.validate[1] + ' limit');
            }
            if (value.length < property.validate[0]) {
              throw new Error('"' + property.name + '" must contain at least ' + property.validate[0] + ' chars');
            }
          }
          return checkContents(value);
        };
      } else if (property.validate instanceof RegExp) {
        property.checkContents = function(value) {
          if (!property.validate.test(value)) {
            throw new Error('Bad format for "' + property.name + '"');
          }
          return checkContents(value);
        };
      } else if (property.validate instanceof Function) {
        property.checkContents = function(value) {
          if (!property.validate.apply(property, [value])) {
            throw new Error('Bad format for "' + property.name + '"');
          }
          return checkContents(value);
        };
      } else {
        throw new Error('Bad validator format for "' + property.name + '"');
      }
    }
  };
};<commit_msg>add helpers for max length and regexp<commit_after>// string fields
module.exports = function(manager) {
  return function(property) {
    // type validator
    var checkType = property.checkType;
    property.checkType = function(value) {
      return typeof value === 'string' && checkType(value);
    };

    // contents validator
    if (property.hasOwnProperty('validate')) {
      var checkContents = property.checkContents;
      var valType = typeof(property.validate);
      if (valType === 'number') {
        property.validate = [0, property.validate];
      } else if (valType === 'string') {
        property.validate = new RegExp(property.validate);
      }
      if (property.validate instanceof Array) {
        property.checkContents = function(value) {
          if (property.validate.length == 1) {
            if (value.length > property.validate[0]) {
              throw new Error('"' + property.name + '" size exceeds ' + property.validate[0] + ' limit');
            }
          } else {
            if (value.length > property.validate[1]) {
              throw new Error('"' + property.name + '" size exceeds ' + property.validate[1] + ' limit');
            }
            if (value.length < property.validate[0]) {
              throw new Error('"' + property.name + '" must contain at least ' + property.validate[0] + ' chars');
            }
          }
          return checkContents(value);
        };
      } else if (property.validate instanceof RegExp) {
        property.checkContents = function(value) {
          if (!property.validate.test(value)) {
            throw new Error('Bad format for "' + property.name + '", does not match with ' + property.validate);
          }
          return checkContents(value);
        };
      } else if (property.validate instanceof Function) {
        property.checkContents = function(value) {
          if (!property.validate.apply(property, [value])) {
            throw new Error('Bad format for "' + property.name + '"');
          }
          return checkContents(value);
        };
      } else {
        throw new Error('Bad validator format for "' + property.name + '"');
      }
    }
  };
};<|endoftext|>


<commit_before>import React from 'react';
import { IndexRedirect, IndexRoute, Route } from 'react-router';
import Loom from 'loom-data';
import AuthService from '../../utils/AuthService';
import Container from './Container';
import { DataModel } from './Schemas/DataModel';
import Login from './Login/Login';
import HomeComponent from '../../containers/home/HomeComponent';
import { Settings } from './Settings/Settings';
import { Visualize } from './Visualizations/Visualize';
import { Link } from './Link/Link';
import CatalogComponent from '../../containers/catalog/CatalogComponent';
import EntitySetDataSearch from '../../containers/entitysetsearch/EntitySetDataSearch';
import AdvancedDataSearch from '../../containers/entitysetsearch/AdvancedDataSearch';
import EntitySetDetailComponent from '../../containers/entitysetdetail/EntitySetDetailComponent';
import DatasourcesComponent from '../../containers/datasets/DatasetsComponent';
import PageConsts from '../../utils/Consts/PageConsts';
import EnvConsts from '../../utils/Consts/EnvConsts';
import { ADMIN } from '../../utils/Consts/UserRoleConsts';
import { configure as edmApiConfigure } from '../../containers/Api';
import { getDisplayName } from '../../containers/principals/PrincipalUtils';

import OrganizationsContainerComponent from '../../containers/organizations/components/OrganizationsContainerComponent';
import OrganizationDetailsComponent from '../../containers/organizations/components/OrganizationDetailsComponent';
import OrganizationsListComponent from '../../containers/organizations/components/OrganizationsListComponent';

// injected by Webpack.DefinePlugin
declare var __AUTH0_CLIENT_ID__;
declare var __AUTH0_DOMAIN__;
declare var __DEV__;

const auth = new AuthService(__AUTH0_CLIENT_ID__, __AUTH0_DOMAIN__, __DEV__);

// onEnter callback to validate authentication in private routes
const requireAuth = (nextState, replace) => {
  if (!auth.loggedIn()) {
    replace({ pathname: `/${PageConsts.LOGIN}` });
  }
  else {
    const authToken = auth.getToken();
    const host = window.location.host;
    const hostName = (host.startsWith('www.')) ? host.substring('www.'.length) : host;
    const baseUrl = (__DEV__) ? EnvConsts.LOCAL : `https://api.${hostName}`;
    Loom.configure({ baseUrl, authToken });
    // TODO: Remove once loom-data-js upgrades
    edmApiConfigure(baseUrl, authToken);
  }
};

const requireAdmin = (nextState, replace) => {
  requireAuth(nextState, replace);
  if (!auth.getProfile().roles.includes(ADMIN)) {
    replace({ pathname: `/${PageConsts.HOME}` });
  }
};

const isAdmin = () => {
  return (auth.loggedIn() && auth.getProfile().hasOwnProperty('roles') && auth.getProfile().roles.includes(ADMIN));
};

const getName = () => {
  if (auth.loggedIn()) {
    return getDisplayName(auth.getProfile());
  } else {
    return null;
  }
};

const getProfileStatus = () => {
  return {
    isAdmin: isAdmin(),
    name: getName()
  };
};

export const makeMainRoutes = () => {
  return (
    <Route path={'/'} component={Container} auth={auth} profileFn={getProfileStatus}>
      <IndexRedirect to={`/${PageConsts.HOME}`} />
      <Route path={PageConsts.HOME} component={HomeComponent} onEnter={requireAuth} />
      <Route path={PageConsts.CATALOG} component={CatalogComponent} onEnter={requireAuth} />
      <Route path={`${PageConsts.SEARCH}/:entitySetId`} component={EntitySetDataSearch} onEnter={requireAuth} />
      <Route path={`${PageConsts.ADVANCED_SEARCH}/:entitySetId`} component={AdvancedDataSearch} onEnter={requireAuth} />
      <Route path={'entitysets/:id'} component={EntitySetDetailComponent} onEnter={requireAuth} />
      <Route path={PageConsts.DATA_MODEL} component={DataModel} onEnter={requireAuth} />
      <Route path={PageConsts.SETTINGS} component={Settings} onEnter={requireAdmin} />
      <Route path={PageConsts.VISUALIZE} component={Visualize} onEnter={requireAuth} />
      <Route path={PageConsts.DATASOURCES} component={DatasourcesComponent} onEnter={requireAuth} />
      <Route path={'orgs'} component={OrganizationsContainerComponent} onEnter={requireAuth}>
        <IndexRoute component={OrganizationsListComponent} />
        <Route path=":orgId" component={OrganizationDetailsComponent} onEnter={requireAuth} />
      </Route>
      <Route path={PageConsts.LOGIN} component={Login} />
      <Route path={'access_token=:token'} component={Login} /> {/* to prevent router errors*/}
      <Route path={PageConsts.LINK} component={Link} onEnter={requireAuth} />
    </Route>
  );
};

export default makeMainRoutes;
<commit_msg>Handle wildcard endpoints in router<commit_after>import React from 'react';
import { IndexRedirect, IndexRoute, Route } from 'react-router';
import Loom from 'loom-data';
import AuthService from '../../utils/AuthService';
import Container from './Container';
import { DataModel } from './Schemas/DataModel';
import Login from './Login/Login';
import HomeComponent from '../../containers/home/HomeComponent';
import { Settings } from './Settings/Settings';
import { Visualize } from './Visualizations/Visualize';
import { Link } from './Link/Link';
import CatalogComponent from '../../containers/catalog/CatalogComponent';
import EntitySetDataSearch from '../../containers/entitysetsearch/EntitySetDataSearch';
import AdvancedDataSearch from '../../containers/entitysetsearch/AdvancedDataSearch';
import EntitySetDetailComponent from '../../containers/entitysetdetail/EntitySetDetailComponent';
import DatasourcesComponent from '../../containers/datasets/DatasetsComponent';
import PageConsts from '../../utils/Consts/PageConsts';
import EnvConsts from '../../utils/Consts/EnvConsts';
import { ADMIN } from '../../utils/Consts/UserRoleConsts';
import { configure as edmApiConfigure } from '../../containers/Api';
import { getDisplayName } from '../../containers/principals/PrincipalUtils';

import OrganizationsContainerComponent from '../../containers/organizations/components/OrganizationsContainerComponent';
import OrganizationDetailsComponent from '../../containers/organizations/components/OrganizationDetailsComponent';
import OrganizationsListComponent from '../../containers/organizations/components/OrganizationsListComponent';

// injected by Webpack.DefinePlugin
declare var __AUTH0_CLIENT_ID__;
declare var __AUTH0_DOMAIN__;
declare var __DEV__;

const auth = new AuthService(__AUTH0_CLIENT_ID__, __AUTH0_DOMAIN__, __DEV__);

// onEnter callback to validate authentication in private routes
const requireAuth = (nextState, replace) => {
  if (!auth.loggedIn()) {
    replace({ pathname: `/${PageConsts.LOGIN}` });
  }
  else {
    const authToken = auth.getToken();
    const host = window.location.host;
    const hostName = (host.startsWith('www.')) ? host.substring('www.'.length) : host;
    const baseUrl = (__DEV__) ? EnvConsts.LOCAL : `https://api.${hostName}`;
    Loom.configure({ baseUrl, authToken });
    // TODO: Remove once loom-data-js upgrades
    edmApiConfigure(baseUrl, authToken);
  }
};

const requireAdmin = (nextState, replace) => {
  requireAuth(nextState, replace);
  if (!auth.getProfile().roles.includes(ADMIN)) {
    replace({ pathname: `/${PageConsts.HOME}` });
  }
};

const isAdmin = () => {
  return (auth.loggedIn() && auth.getProfile().hasOwnProperty('roles') && auth.getProfile().roles.includes(ADMIN));
};

const getName = () => {
  if (auth.loggedIn()) {
    return getDisplayName(auth.getProfile());
  } else {
    return null;
  }
};

const getProfileStatus = () => {
  return {
    isAdmin: isAdmin(),
    name: getName()
  };
};

export const makeMainRoutes = () => {
  return (
    <Route path={'/'} component={Container} auth={auth} profileFn={getProfileStatus}>
      <IndexRedirect to={`/${PageConsts.HOME}`} />
      <Route path={PageConsts.HOME} component={HomeComponent} onEnter={requireAuth} />
      <Route path={PageConsts.CATALOG} component={CatalogComponent} onEnter={requireAuth} />
      <Route path={`${PageConsts.SEARCH}/:entitySetId`} component={EntitySetDataSearch} onEnter={requireAuth} />
      <Route path={`${PageConsts.ADVANCED_SEARCH}/:entitySetId`} component={AdvancedDataSearch} onEnter={requireAuth} />
      <Route path={'entitysets/:id'} component={EntitySetDetailComponent} onEnter={requireAuth} />
      <Route path={PageConsts.DATA_MODEL} component={DataModel} onEnter={requireAuth} />
      <Route path={PageConsts.SETTINGS} component={Settings} onEnter={requireAdmin} />
      <Route path={PageConsts.VISUALIZE} component={Visualize} onEnter={requireAuth} />
      <Route path={PageConsts.DATASOURCES} component={DatasourcesComponent} onEnter={requireAuth} />
      <Route path={'orgs'} component={OrganizationsContainerComponent} onEnter={requireAuth}>
        <IndexRoute component={OrganizationsListComponent} />
        <Route path=":orgId" component={OrganizationDetailsComponent} onEnter={requireAuth} />
      </Route>
      <Route path={PageConsts.LOGIN} component={Login} />
      <Route path={'access_token=:token'} component={Login} /> {/* to prevent router errors*/}
      <Route path={PageConsts.LINK} component={Link} onEnter={requireAuth} />
      <Route path='*' component={HomeComponent} onEnter={requireAuth} />
    </Route>
  );
};

export default makeMainRoutes;
<|endoftext|>


<commit_before>import React from 'react';
import { Route, IndexRedirect } from 'react-router';
import Loom from 'loom-data';
import AuthService from '../../utils/AuthService';
import { Container } from './Container.js';
import { Catalog } from './Catalog/Catalog';
import { Login } from './Login/Login';
import { Home } from './Home/Home';
import { Settings } from './Settings/Settings';
import Consts from '../../utils/AppConsts';

declare var __LOCAL__;

const auth = new AuthService(Consts.AUTH0_CLIENT_ID, Consts.AUTH0_DOMAIN);

// onEnter callback to validate authentication in private routes
const requireAuth = (nextState, replace) => {
  if (!auth.loggedIn()) {
    replace({ pathname: `/${Consts.LOGIN}` });
  }
  else {
    const authToken = auth.getToken();
    let baseUrl = window.location.origin;
    if (__LOCAL__) {
      baseUrl = Consts.LOCAL;
    }
    Loom.configure({ baseUrl, authToken });
  }
};

export const makeMainRoutes = () => {
  return (
    <Route path={'/'} component={Container} auth={auth}>
      <IndexRedirect to={`/${Consts.HOME}`} />
      <Route path={Consts.HOME} component={Home} onEnter={requireAuth} />
      <Route path={Consts.CATALOG} component={Catalog} onEnter={requireAuth} />
      <Route path={Consts.LOGIN} component={Login} />
      <Route path={'access_token=:token'} component={Login} /> {/* to prevent router errors*/}
      <Route path={Consts.SETTINGS} component={Settings} />
    </Route>
  );
};

export default makeMainRoutes;
<commit_msg>prepend api<commit_after>import React from 'react';
import { Route, IndexRedirect } from 'react-router';
import Loom from 'loom-data';
import AuthService from '../../utils/AuthService';
import { Container } from './Container.js';
import { Catalog } from './Catalog/Catalog';
import { Login } from './Login/Login';
import { Home } from './Home/Home';
import { Settings } from './Settings/Settings';
import Consts from '../../utils/AppConsts';

declare var __LOCAL__;

const auth = new AuthService(Consts.AUTH0_CLIENT_ID, Consts.AUTH0_DOMAIN);

const getBaseUrl = () => {
  const host = window.location.host;
  const domainRegex = /(?:[^\.]+\.)*([^\.]+\.[^\.]+)(?:\:\d+)?/;
  return `https://api.${host.match(domainRegex)[1]}`;
};

// onEnter callback to validate authentication in private routes
const requireAuth = (nextState, replace) => {
  if (!auth.loggedIn()) {
    replace({ pathname: `/${Consts.LOGIN}` });
  }
  else {
    const authToken = auth.getToken();
    const baseUrl = (__LOCAL__) ? Consts.LOCAL : getBaseUrl();
    Loom.configure({ baseUrl, authToken });
  }
};

export const makeMainRoutes = () => {
  return (
    <Route path={'/'} component={Container} auth={auth}>
      <IndexRedirect to={`/${Consts.HOME}`} />
      <Route path={Consts.HOME} component={Home} onEnter={requireAuth} />
      <Route path={Consts.CATALOG} component={Catalog} onEnter={requireAuth} />
      <Route path={Consts.LOGIN} component={Login} />
      <Route path={'access_token=:token'} component={Login} /> {/* to prevent router errors*/}
      <Route path={Consts.SETTINGS} component={Settings} />
    </Route>
  );
};

export default makeMainRoutes;
<|endoftext|>


<commit_before>import {
    ADD_PRODUCT,
    REMOVE_PRODUCT,

} from './../mutation-types'

export default {
    state: {
        products: [],
        total: 0,
        count: 0
    },
    mutations: {
        [ADD_PRODUCT] (state, product) {
            let index = state.products.findIndex(p => p.id === product.id)

            if (index >= 0) {
                state.products = state.products.map(p => {
                    if (p.id === product.id) {
                        p.quantity = p.quantity + 1
                    }

                    return p
                })
            } else {
                product.quantity = 1
                state.products.push(product)
            }

            state.total += product.price
            state.count += 1
        },

        [REMOVE_PRODUCT] (state, id) {
            state.products = state.products.filter(p => p.id !== id)
            state.total = state.products.reduce((a, b) => a + (b.price * b.quantity), 0)
            state.count = state.products.length
        }
    },
    actions: {
        addProduct ({ commit }, product) {
            commit(ADD_PRODUCT, product)
        },
        removeProduct ({ commit }, id) {
            commit(REMOVE_PRODUCT, id)
        }
    }
}
<commit_msg>Fix quantity not updating properly<commit_after>import {
    ADD_PRODUCT,
    REMOVE_PRODUCT,

} from './../mutation-types'

import Vue from 'vue'

export default {
    state: {
        products: [],
        total: 0,
        count: 0
    },
    mutations: {
        [ADD_PRODUCT] (state, product) {
            let productExist = state.products.find(p => p.id === product.id)

            if (productExist) {
                state.products = state.products.map(p => {
                    if (p.id === product.id) {
                        p.quantity += 1
                    }

                    return p
                })
            } else {
                Vue.set(product, 'quantity', 1)
                state.products.push(product)
            }

            state.total += product.price
            state.count += 1
        },

        [REMOVE_PRODUCT] (state, id) {
            state.products = state.products.filter(p => p.id !== id)
            state.total = state.products.reduce((a, b) => a + (b.price * b.quantity), 0)
            state.count = state.products.length
        }
    },
    actions: {
        addProduct ({ commit }, product) {
            commit(ADD_PRODUCT, product)
        },
        removeProduct ({ commit }, id) {
            commit(REMOVE_PRODUCT, id)
        }
    }
}
<|endoftext|>


<commit_before>import ExtractTextPlugin from 'extract-text-webpack-plugin';
import fs from 'fs';
import path from 'path';
import webpack from 'webpack';
import XhrEvalChunkPlugin from 'xhr-eval-chunk-webpack-plugin';

const babelLoader = require.resolve('babel-loader');
const combineLoader = require.resolve('combine-loader');
const cssLoader = require.resolve('css-loader');
const frontMatterLoader = require.resolve('front-matter-loader');
const htmlLoader = require.resolve('html-loader');
const jsonLoader = require.resolve('json-loader');
const markdownItLoader = require.resolve('markdown-it-loader');
const urlLoader = require.resolve('url-loader');

const layoutLoader = require.resolve('./loaders/layout-loader');

export default function configure({
  entry,
  hmr = false,
  name,
  outputPath,
  target,
  xhrEvalChunks = false,
}) {
  const entryDir = fs.statSync(path.dirname(entry)).isDirectory()
    ? entry
    : path.dirname(entry);

  return {
    name,
    entry: [
      ...(hmr ? [`${require.resolve('webpack-hot-middleware/client')}?reload=true`] : []),
      require.resolve('../entry'),
    ],
    output: {
      path: outputPath,
      publicPath: '/assets/',
      chunkFilename: '[name].bundle.js',
      filename: '[name].bundle.js',
      pathinfo: true,
      libraryTarget: 'umd',
    },
    target,
    devtool: 'cheap-module-eval-source-map',
    module: {
      loaders: [
        {
          test: /\.css$/,
          exclude: /\.module\.css$/,
          loader: ExtractTextPlugin.extract({ loader: `${cssLoader}?sourceMap` }),
        },
        {
          test: /\.module\.css$/,
          loader: ExtractTextPlugin.extract({ loader: `${cssLoader}?sourceMap&modules` }),
        },
        {
          test: /\.jsx?$/,
          include: entryDir,
          exclude: /node_modules/,
          loader: babelLoader,
        },
        {
          test: /\.json$/,
          loader: jsonLoader,
        },
        {
          test: /\.md$/,
          include: entryDir,
          loader: `${combineLoader}?${JSON.stringify({
            layout: [layoutLoader, jsonLoader, `${frontMatterLoader}?onlyAttributes`],
            meta: [jsonLoader, `${frontMatterLoader}?onlyAttributes`],
            html: [htmlLoader, markdownItLoader, `${frontMatterLoader}?onlyBody`],
          })}`,
        },
        {
          test: /\.(gif|jpg|jpeg|png|svg|eot|woff|woff2|ttf)(\?v=.+)?$/,
          loader: `${urlLoader}?limit=10000`,
        },
      ],
    },
    resolve: {
      alias: {
        nucleate: path.resolve(__dirname, '../..'),
      },
      modules: [entryDir, 'node_modules'],
      extensions: ['', '.js', '.jsx', '.json'],
    },
    resolveLoader: {
      modules: path.resolve(__dirname, './loaders'),
    },
    plugins: [
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
        __SITE_ENTRY__: JSON.stringify(entry),
      }),
      new ExtractTextPlugin({ filename: '[name].bundle.css', allChunks: true }),
      ...(hmr ? [
        new webpack.HotModuleReplacementPlugin(),
      ] : []),
      ...(xhrEvalChunks ? [
        new XhrEvalChunkPlugin(),
      ] : []),
    ],
    'markdown-it': {
      preset: 'commonmark',
    },
  };
}
<commit_msg>Disable module transform in babel-loader<commit_after>import ExtractTextPlugin from 'extract-text-webpack-plugin';
import fs from 'fs';
import path from 'path';
import webpack from 'webpack';
import XhrEvalChunkPlugin from 'xhr-eval-chunk-webpack-plugin';

const babelLoader = require.resolve('babel-loader');
const combineLoader = require.resolve('combine-loader');
const cssLoader = require.resolve('css-loader');
const frontMatterLoader = require.resolve('front-matter-loader');
const htmlLoader = require.resolve('html-loader');
const jsonLoader = require.resolve('json-loader');
const markdownItLoader = require.resolve('markdown-it-loader');
const urlLoader = require.resolve('url-loader');

const layoutLoader = require.resolve('./loaders/layout-loader');

export default function configure({
  entry,
  hmr = false,
  name,
  outputPath,
  target,
  xhrEvalChunks = false,
}) {
  const entryDir = fs.statSync(path.dirname(entry)).isDirectory()
    ? entry
    : path.dirname(entry);

  return {
    name,
    entry: [
      ...(hmr ? [`${require.resolve('webpack-hot-middleware/client')}?reload=true`] : []),
      require.resolve('../entry'),
    ],
    output: {
      path: outputPath,
      publicPath: '/assets/',
      chunkFilename: '[name].bundle.js',
      filename: '[name].bundle.js',
      pathinfo: true,
      libraryTarget: 'umd',
    },
    target,
    devtool: 'cheap-module-eval-source-map',
    module: {
      loaders: [
        {
          test: /\.css$/,
          exclude: /\.module\.css$/,
          loader: ExtractTextPlugin.extract({ loader: `${cssLoader}?sourceMap` }),
        },
        {
          test: /\.module\.css$/,
          loader: ExtractTextPlugin.extract({ loader: `${cssLoader}?sourceMap&modules` }),
        },
        {
          test: /\.jsx?$/,
          include: entryDir,
          exclude: /node_modules/,
          loader: babelLoader,
          query: {
            presets: [
              ['es2015', { modules: false }],
              'stage-1',
              'react',
            ],
          },
        },
        {
          test: /\.json$/,
          loader: jsonLoader,
        },
        {
          test: /\.md$/,
          include: entryDir,
          loader: `${combineLoader}?${JSON.stringify({
            layout: [layoutLoader, jsonLoader, `${frontMatterLoader}?onlyAttributes`],
            meta: [jsonLoader, `${frontMatterLoader}?onlyAttributes`],
            html: [htmlLoader, markdownItLoader, `${frontMatterLoader}?onlyBody`],
          })}`,
        },
        {
          test: /\.(gif|jpg|jpeg|png|svg|eot|woff|woff2|ttf)(\?v=.+)?$/,
          loader: `${urlLoader}?limit=10000`,
        },
      ],
    },
    resolve: {
      alias: {
        nucleate: path.resolve(__dirname, '../..'),
      },
      modules: [entryDir, 'node_modules'],
      extensions: ['', '.js', '.jsx', '.json'],
    },
    resolveLoader: {
      modules: path.resolve(__dirname, './loaders'),
    },
    plugins: [
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
        __SITE_ENTRY__: JSON.stringify(entry),
      }),
      new ExtractTextPlugin({ filename: '[name].bundle.css', allChunks: true }),
      ...(hmr ? [
        new webpack.HotModuleReplacementPlugin(),
      ] : []),
      ...(xhrEvalChunks ? [
        new XhrEvalChunkPlugin(),
      ] : []),
    ],
    'markdown-it': {
      preset: 'commonmark',
    },
  };
}
<|endoftext|>


<commit_before>// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Desk Page', {
	refresh: function(frm) {
		frm.get_field("is_standard").toggle(frappe.boot.developer_mode);
		frm.get_field("extends_another_page").toggle(frappe.boot.developer_mode);
		if (!frappe.boot.developer_mode) {
			frm.set_read_only();
			frm.fields
				.filter(field => field.has_input)
				.forEach(field => {
					frm.set_df_property(field.df.fieldname, "read_only", "1")
				})
			frm.disable_save();
		}
	}
});
<commit_msg>fix (linting): added semicolon<commit_after>// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Desk Page', {
	refresh: function(frm) {
		frm.get_field("is_standard").toggle(frappe.boot.developer_mode);
		frm.get_field("extends_another_page").toggle(frappe.boot.developer_mode);
		if (!frappe.boot.developer_mode) {
			frm.set_read_only();
			frm.fields
				.filter(field => field.has_input)
				.forEach(field => {
					frm.set_df_property(field.df.fieldname, "read_only", "1");
				});
			frm.disable_save();
		}
	}
});
<|endoftext|>


<commit_before>// Initialization.

// class_names is the unparsed text pulled in from GOLD about each class (including its dept., num., and name).
var class_names = [];
// meeting_times is the unparsed text about all meeting times.
var meeting_times = [];
// class_times is the parsed values for the meeting times of the all the classes. It is an array of arrays, with a top-level element for each class.
var class_times = [];
// instructors is an array for the professors of each class.
var instructors = [];
// finals is an array for the final exam times of each class.
var finals = [];
// Counter.
var count = 0;

// Get all the tags of class "clcellprimaryalt". Class names seem to always be of this class.
var classes = document.getElementsByClassName("clcellprimaryalt");

// Loop through all those tags.
for (var i = 0; i < classes.length; i++) {
	// We only want divs, those are the class name tags. Ignore everything else.
	if (classes[i].tagName.toLowerCase() == "div") {
		// Get the meeting times for each class. They seem to be in an element with id "pageContent_CourseList_MeetingTimesList_X"
		//	where X is the index of the course in GOLD table, indexed from 0 (i.e. 0, 1, 2, 3, 4, etc).
		var meeting_time = document.getElementById("pageContent_CourseList_MeetingTimesList_" + count);

		// Add that to the meeting_times array, and add the class name to the class_names array.
		meeting_times[count] = meeting_time;

		// Get the instructor from the page.
		var instructor = document.getElementById("pageContent_CourseList_InstructorList_" + count);

		// Check f we found instructors.
		if (instructor) {
			// If we did, get all the instructors from the table. The elements they're in may be clcellprimary or clcellprimaryalt, so handle both.
			var currentinstructors = instructor.getElementsByClassName("clcellprimary");
			var currentinstructorsstr = [];
			if (currentinstructors.length == 0){
				currentinstructors = instructor.getElementsByClassName("clcellprimaryalt");
			}

			// Go through all the instructors, and get their string representation, trimmed.
			for (var j = 0; j < currentinstructors.length; j++) {
						currentinstructorsstr[j] = currentinstructors[j].textContent.trim();
			}

			// Add the instructor array for this course, trimmed, to the main instructors array.
			instructors[count] = currentinstructorsstr;
		}

		class_names[count++] = classes[i].textContent;
	}
}

var finalTable = document.getElementById("pageContent_FinalsGrid");
if (finalTable) {
	var numFinals = 0;
	var rows = finalTable.getElementsByTagName("table");
	for (var i=0; i < rows.length; i++) {
		var cells = rows[i].getElementsByClassName("clcellprimary");
		if (cells.length > 1) {
			finals[numFinals++] = cells[1].textContent;
		}
	}
}

// We're only interested in continuing if we found classes in the page. Otherwise don't waste the browser's time.
if (class_names.length > 0) {

	// Loop through the array of meeting times. Each element of it is a tag that contains all the meeting times for a class
	//	somewhere in it's child tree. This can be thought of as looping through the classes.
	for (var i=0; i < meeting_times.length; i++) {
		// The times are either of class "clcellprimary" or "clcellprimaryalt", depending on which row in the table the course is. GOLD
		//	alternates colours for the table rows, and that's why it could be either. First check if it's primary, and if not, then get
		//	primaryalt.
		var cells = meeting_times[i].getElementsByClassName("clcellprimary");
		if (cells.length == 0){
			cells = meeting_times[i].getElementsByClassName("clcellprimaryalt");
		}

		// Make a new array element in class_times. This array contains all the meeting times for one class.
		class_times[i] = [];

		// Loop through all the cells that were found in the current meeting_times tag hierarchy.
		for (var j=0; j < cells.length; j++) {
			// Check for a link (<a> tag) in the cell. If there is one, just get the content of that link. This is for the meeting locations, which
			//	are linked to a popup. Otherwise just get the content of the tag (for everything but the meeting locations). Add them to the new
			//	element in class_times.
			if (cells[j].getElementsByTagName("A").length != 0) {
				class_times[i][j] = cells[j].getElementsByTagName("A")[0].textContent.trim();
			} else {
				class_times[i][j] = cells[j].textContent.trim();
			}
		}
	}

	// Initialize the message data, that will be passed to the popover.
	var pass_data = [];

	// Set the first element of the pass data to the name of the quarter we're analyzing. It's the text of the selected option of the page's select tag.
	pass_data[0] = document.getElementsByTagName("SELECT")[0].options[document.getElementsByTagName("SELECT")[0].selectedIndex].textContent.trimLeft().trimRight();

	// Loop through all the classes.
	for (var i = 0; i < class_names.length; i++) {
		// Split based on the first number, which is part of the course number.
		var num = /\d/;
		var firstNum = class_names[i].search(num);
		var department = class_names[i].substr(0,firstNum);
		var split1 = class_names[i].substr(firstNum);
		// Split the second part by hyphen surrounded by spaces, which should give the number and name separately.
		var split2 = split1.split(" - ");
		var number = split2[0];

		// Get the class name.
		var className = split2[1];
		// Another hyphen with spaces was in the title so extra splitting occurred
		if (split2.length > 2) {
			for(var j = 2; j < split2.length; j++) {
				// Append the parts that shouldn't have been split off
				className += " - "+split2[j];
			}
		}

		// Get final for this class, and if there is one, trim it.
		var finalT = finals[i];
		if (finalT) {
			finalT = finalT.trim();
		}

		// Create a new array with the department, number, name, an array of meeting times, and the instructors. Add that as a new element of the passed data.
		var final_split = {'department': department.trim(),'number': number.trim(),'name': className.trim(),'meetingTimes': class_times[i],'instructor': instructors[i],'final': finalT};
		// Offset the index at one, because the first element is the quarter.
		pass_data[i+1] = final_split;
	}

	// If classes were found, then pass them to the popover as a message with name "classData".
	if (pass_data.length > 0) {
		safari.self.tab.dispatchMessage("classData",pass_data);
	}
}

<commit_msg>Fixed some comparisons to be better<commit_after>// Initialization.

// class_names is the unparsed text pulled in from GOLD about each class (including its dept., num., and name).
var class_names = [];
// meeting_times is the unparsed text about all meeting times.
var meeting_times = [];
// class_times is the parsed values for the meeting times of the all the classes. It is an array of arrays, with a top-level element for each class.
var class_times = [];
// instructors is an array for the professors of each class.
var instructors = [];
// finals is an array for the final exam times of each class.
var finals = [];
// Counter.
var count = 0;

// Get all the tags of class "clcellprimaryalt". Class names seem to always be of this class.
var classes = document.getElementsByClassName("clcellprimaryalt");

// Loop through all those tags.
for (var i = 0; i < classes.length; i++) {
	// We only want divs, those are the class name tags. Ignore everything else.
	if (classes[i].tagName.toLowerCase() === "div") {
		// Get the meeting times for each class. They seem to be in an element with id "pageContent_CourseList_MeetingTimesList_X"
		//	where X is the index of the course in GOLD table, indexed from 0 (i.e. 0, 1, 2, 3, 4, etc).
		var meeting_time = document.getElementById("pageContent_CourseList_MeetingTimesList_" + count);

		// Add that to the meeting_times array, and add the class name to the class_names array.
		meeting_times[count] = meeting_time;

		// Get the instructor from the page.
		var instructor = document.getElementById("pageContent_CourseList_InstructorList_" + count);

		// Check f we found instructors.
		if (instructor) {
			// If we did, get all the instructors from the table. The elements they're in may be clcellprimary or clcellprimaryalt, so handle both.
			var currentinstructors = instructor.getElementsByClassName("clcellprimary");
			var currentinstructorsstr = [];
			if (currentinstructors.length < 1){
				currentinstructors = instructor.getElementsByClassName("clcellprimaryalt");
			}

			// Go through all the instructors, and get their string representation, trimmed.
			for (var j = 0; j < currentinstructors.length; j++) {
						currentinstructorsstr[j] = currentinstructors[j].textContent.trim();
			}

			// Add the instructor array for this course, trimmed, to the main instructors array.
			instructors[count] = currentinstructorsstr;
		}

		class_names[count++] = classes[i].textContent;
	}
}

var finalTable = document.getElementById("pageContent_FinalsGrid");
if (finalTable) {
	var numFinals = 0;
	var rows = finalTable.getElementsByTagName("table");
	for (var i=0; i < rows.length; i++) {
		var cells = rows[i].getElementsByClassName("clcellprimary");
		if (cells.length > 1) {
			finals[numFinals++] = cells[1].textContent;
		}
	}
}

// We're only interested in continuing if we found classes in the page. Otherwise don't waste the browser's time.
if (class_names.length > 0) {

	// Loop through the array of meeting times. Each element of it is a tag that contains all the meeting times for a class
	//	somewhere in it's child tree. This can be thought of as looping through the classes.
	for (var i=0; i < meeting_times.length; i++) {
		// The times are either of class "clcellprimary" or "clcellprimaryalt", depending on which row in the table the course is. GOLD
		//	alternates colours for the table rows, and that's why it could be either. First check if it's primary, and if not, then get
		//	primaryalt.
		var cells = meeting_times[i].getElementsByClassName("clcellprimary");
		if (cells.length < 1){
			cells = meeting_times[i].getElementsByClassName("clcellprimaryalt");
		}

		// Make a new array element in class_times. This array contains all the meeting times for one class.
		class_times[i] = [];

		// Loop through all the cells that were found in the current meeting_times tag hierarchy.
		for (var j=0; j < cells.length; j++) {
			// Check for a link (<a> tag) in the cell. If there is one, just get the content of that link. This is for the meeting locations, which
			//	are linked to a popup. Otherwise just get the content of the tag (for everything but the meeting locations). Add them to the new
			//	element in class_times.
			if (cells[j].getElementsByTagName("A").length > 0) {
				class_times[i][j] = cells[j].getElementsByTagName("A")[0].textContent.trim();
			} else {
				class_times[i][j] = cells[j].textContent.trim();
			}
		}
	}

	// Initialize the message data, that will be passed to the popover.
	var pass_data = [];

	// Set the first element of the pass data to the name of the quarter we're analyzing. It's the text of the selected option of the page's select tag.
	pass_data[0] = document.getElementsByTagName("SELECT")[0].options[document.getElementsByTagName("SELECT")[0].selectedIndex].textContent.trimLeft().trimRight();

	// Loop through all the classes.
	for (var i = 0; i < class_names.length; i++) {
		// Split based on the first number, which is part of the course number.
		var num = /\d/;
		var firstNum = class_names[i].search(num);
		var department = class_names[i].substr(0,firstNum);
		var split1 = class_names[i].substr(firstNum);
		// Split the second part by hyphen surrounded by spaces, which should give the number and name separately.
		var split2 = split1.split(" - ");
		var number = split2[0];

		// Get the class name.
		var className = split2[1];
		// Another hyphen with spaces was in the title so extra splitting occurred
		if (split2.length > 2) {
			for(var j = 2; j < split2.length; j++) {
				// Append the parts that shouldn't have been split off
				className += " - "+split2[j];
			}
		}

		// Get final for this class, and if there is one, trim it.
		var finalT = finals[i];
		if (finalT) {
			finalT = finalT.trim();
		}

		// Create a new array with the department, number, name, an array of meeting times, and the instructors. Add that as a new element of the passed data.
		var final_split = {'department': department.trim(),'number': number.trim(),'name': className.trim(),'meetingTimes': class_times[i],'instructor': instructors[i],'final': finalT};
		// Offset the index at one, because the first element is the quarter.
		pass_data[i+1] = final_split;
	}

	// If classes were found, then pass them to the popover as a message with name "classData".
	if (pass_data.length > 0) {
		safari.self.tab.dispatchMessage("classData",pass_data);
	}
}

<|endoftext|>


<commit_before>define([
    'urlutils',
    '../../components/vue/dist/vue.min'
], function (urlutils, Vue) {
    'use strict';

    /* Create application_list ViewModel
    (will next be wrapped in a main ViewModel which will contain the
    applicationListView and the applicationView) */
    var ApplicationListView = Vue.extend({
        el: '#applist',

        props: {
            model: {
                type: Object,
                required: true
            }
        },

        data: function() {
            return {
                loading: true,
                model: { app_list: [], selected_index: null },
                selected_app_callback: function() {}, // Temporary
                stop_application_callback: function() {} // Temporary
            };
        },

        methods: {
            get_icon_src: function(app_data) {
                var icon_data = app_data.image.icon_128;
                return (
                    icon_data ?
                    'data:image/png;base64,' + icon_data :
                    urlutils.path_join(
                        this.base_url, 'static', 'images', 'generic_appicon_128.png'
                    )
                );
            },
            get_app_name: function(app_data) {
                return (
                    app_data.image.ui_name ?
                    app_data.image.ui_name :
                    app_data.image.name
                );
            },
            stop_application: function(index) {
                this.stop_application_callback(index);
            }
        },

        watch: {
            'model.selected_index': function() { this.selected_app_callback(); }
        }
    });

    return {
        ApplicationListView : ApplicationListView
    };
});
<commit_msg>Clear unused code<commit_after>define([
    'urlutils',
    '../../components/vue/dist/vue.min'
], function (urlutils, Vue) {
    'use strict';

    /* Create application_list ViewModel
    (will next be wrapped in a main ViewModel which will contain the
    applicationListView and the applicationView) */
    var ApplicationListView = Vue.extend({
        el: '#applist',

        data: function() {
            return {
                loading: true,
                model: { app_list: [], selected_index: null },
                selected_app_callback: function() {}, // Temporary
                stop_application_callback: function() {} // Temporary
            };
        },

        methods: {
            get_icon_src: function(app_data) {
                var icon_data = app_data.image.icon_128;
                return (
                    icon_data ?
                    'data:image/png;base64,' + icon_data :
                    urlutils.path_join(
                        this.base_url, 'static', 'images', 'generic_appicon_128.png'
                    )
                );
            },
            get_app_name: function(app_data) {
                return (
                    app_data.image.ui_name ?
                    app_data.image.ui_name :
                    app_data.image.name
                );
            },
            stop_application: function(index) {
                this.stop_application_callback(index);
            }
        },

        watch: {
            'model.selected_index': function() { this.selected_app_callback(); }
        }
    });

    return {
        ApplicationListView : ApplicationListView
    };
});
<|endoftext|>


<commit_before>'use strict';

angular.module('treasuremapApp')
.controller('UserCtrl', function ($scope, $stateParams, Auth, User) {
    $scope.currentUser = Auth.getCurrentUser();
    $scope.isFriend = Auth.isFriend;
    $scope.user = User.get({ id: $stateParams.id });
    $scope.locations = User.locations({ id: $stateParams.id });

    $scope.addFriend = function (user) {
      Auth.addFriend(user._id).then( function() {
        $scope.currentUser.friends.push(user._id);
        console.log('Friend added');
      });
    };

    $scope.removeFriend = function (user) {
      Auth.removeFriend(user._id).then( function() {
        $scope.currentUser.friends.splice($scope.currentUser.friends.indexOf(user._id), 1);
        console.log('Friend removed');
      });
    };
  });
<commit_msg>add friend button updating when friend added<commit_after>'use strict';

angular.module('treasuremapApp')
.controller('UserCtrl', function ($scope, $stateParams, Auth, User) {
    $scope.currentUser = Auth.getCurrentUser();
    $scope.isFriend = Auth.isFriend;
    $scope.user = User.get({ id: $stateParams.id });
    $scope.locations = User.locations({ id: $stateParams.id });

    $scope.addFriend = function (user) {
      Auth.addFriend(user._id).then( function() {
        $scope.currentUser.friends.push(user);
        console.log('Friend added');
      });
    };

    $scope.removeFriend = function (user) {
      Auth.removeFriend(user._id).then( function() {
        $scope.currentUser.friends.splice($scope.currentUser.friends.indexOf(user._id), 1);
        console.log('Friend removed');
      });
    };
  });
<|endoftext|>


<commit_before>const fs = require('fs')
const path = require('path')
const tus = require('tus-js-client')
const uuid = require('uuid')
const createTailReadStream = require('@uppy/fs-tail-stream')
const emitter = require('./emitter')
const request = require('request')
const serializeError = require('serialize-error')
const { jsonStringify, hasMatch } = require('./helpers/utils')
const logger = require('./logger')
const validator = require('validator')
const headerSanitize = require('./header-blacklist')

const PROTOCOLS = Object.freeze({
  multipart: 'multipart',
  s3Multipart: 's3-multipart',
  tus: 'tus'
})

class Uploader {
  /**
   * Uploads file to destination based on the supplied protocol (tus, s3-multipart, multipart)
   * For tus uploads, the deferredLength option is enabled, because file size value can be unreliable
   * for some providers (Instagram particularly)
   *
   * @typedef {object} UploaderOptions
   * @property {string} endpoint
   * @property {string=} uploadUrl
   * @property {string} protocol
   * @property {number} size
   * @property {string=} fieldname
   * @property {string} pathPrefix
   * @property {any=} s3
   * @property {any} metadata
   * @property {any} uppyOptions
   * @property {any=} storage
   * @property {any=} headers
   *
   * @param {UploaderOptions} options
   */
  constructor (options) {
    if (!this.validateOptions(options)) {
      logger.debug(this._errRespMessage, 'uploader.validator.fail')
      return
    }

    this.options = options
    this.token = uuid.v4()
    this.path = `${this.options.pathPrefix}/${Uploader.FILE_NAME_PREFIX}-${this.token}`
    this.options.metadata = this.options.metadata || {}
    this.uploadFileName = this.options.metadata.name || path.basename(this.path)
    this.streamsEnded = false
    this.duplexStream = null
    // @TODO disabling parallel uploads and downloads for now
    // if (this.options.protocol === PROTOCOLS.tus) {
    //   this.duplexStream = new stream.PassThrough()
    //     .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.duplex.error'))
    // }
    this.writeStream = fs.createWriteStream(this.path, { mode: 0o666 }) // no executable files
      .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.write.error'))
    /** @type {number} */
    this.emittedProgress = 0
    this.storage = options.storage
    this._paused = false

    if (this.options.protocol === PROTOCOLS.tus) {
      emitter().on(`pause:${this.token}`, () => {
        this._paused = true
        if (this.tus) {
          this.tus.abort()
        }
      })

      emitter().on(`resume:${this.token}`, () => {
        this._paused = false
        if (this.tus) {
          this.tus.start()
        }
      })
    }
  }

  /**
   * the number of bytes written into the streams
   */
  get bytesWritten () {
    return this.writeStream.bytesWritten
  }

  /**
   * Validate the options passed down to the uplaoder
   *
   * @param {UploaderOptions} options
   * @returns {boolean}
   */
  validateOptions (options) {
    // s3 uploads don't require upload destination
    // validation, because the destination is determined
    // by the server's s3 config
    if (options.protocol === PROTOCOLS.s3Multipart) {
      return true
    }

    if (!options.endpoint && !options.uploadUrl) {
      this._errRespMessage = 'No destination specified'
      return false
    }

    const validatorOpts = { require_protocol: true, require_tld: !options.uppyOptions.debug }
    return [options.endpoint, options.uploadUrl].every((url) => {
      if (url && !validator.isURL(url, validatorOpts)) {
        this._errRespMessage = 'Invalid destination url'
        return false
      }

      const allowedUrls = options.uppyOptions.uploadUrls
      if (allowedUrls && url && !hasMatch(url, allowedUrls)) {
        this._errRespMessage = 'upload destination does not match any allowed destinations'
        return false
      }

      return true
    })
  }

  hasError () {
    return this._errRespMessage != null
  }

  /**
   * returns a substring of the token
   */
  get shortToken () {
    return this.token.substring(0, 8)
  }

  /**
   *
   * @param {function} callback
   */
  onSocketReady (callback) {
    emitter().once(`connection:${this.token}`, () => callback())
    logger.debug(`${this.shortToken} waiting for connection`, 'uploader.socket.wait')
  }

  cleanUp () {
    fs.unlink(this.path, (err) => {
      if (err) {
        logger.error(`cleanup failed for: ${this.path} err: ${err}`, 'uploader.cleanup.error')
      }
    })
    emitter().removeAllListeners(`pause:${this.token}`)
    emitter().removeAllListeners(`resume:${this.token}`)
  }

  /**
   *
   * @param {Buffer | Buffer[]} chunk
   */
  handleChunk (chunk) {
    // @todo a default protocol should not be set. We should ensure that the user specifies her protocol.
    const protocol = this.options.protocol || PROTOCOLS.multipart

    // The download has completed; close the file and start an upload if necessary.
    if (chunk === null) {
      this.writeStream.on('finish', () => {
        this.streamsEnded = true
        if (this.options.endpoint && protocol === PROTOCOLS.multipart) {
          this.uploadMultipart()
        }

        if (protocol === PROTOCOLS.tus && !this.tus) {
          return this.uploadTus()
        }
      })

      return this.endStreams()
    }

    this.writeStream.write(chunk, () => {
      logger.debug(`${this.shortToken} ${this.bytesWritten} bytes`, 'uploader.download.progress')
      if (protocol === PROTOCOLS.multipart || protocol === PROTOCOLS.tus) {
        return this.emitIllusiveProgress()
      }

      if (protocol === PROTOCOLS.s3Multipart && !this.s3Upload) {
        return this.uploadS3()
      }
      // @TODO disabling parallel uploads and downloads for now
      // if (!this.options.endpoint) return

      // if (protocol === PROTOCOLS.tus && !this.tus) {
      //   return this.uploadTus()
      // }
    })
  }

  /**
   * @param {Buffer | Buffer[]} chunk
   * @param {function} cb
   */
  writeToStreams (chunk, cb) {
    const done = []
    const doneLength = this.duplexStream ? 2 : 1
    const onDone = () => {
      done.push(true)
      if (done.length >= doneLength) {
        cb()
      }
    }

    this.writeStream.write(chunk, onDone)
    if (this.duplexStream) {
      this.duplexStream.write(chunk, onDone)
    }
  }

  endStreams () {
    this.writeStream.end()
    if (this.duplexStream) {
      this.duplexStream.end()
    }
  }

  getResponse () {
    if (this._errRespMessage) {
      return { body: this._errRespMessage, status: 400 }
    }
    return { body: { token: this.token }, status: 200 }
  }

  /**
   * @typedef {{action: string, payload: object}} State
   * @param {State} state
   */
  saveState (state) {
    if (!this.storage) return
    this.storage.set(`${Uploader.STORAGE_PREFIX}:${this.token}`, jsonStringify(state))
  }

  /**
   * This method emits upload progress but also creates an "upload progress" illusion
   * for the waiting period while only download is happening. Hence, it combines both
   * download and upload into an upload progress.
   * @see emitProgress
   * @param {number=} bytesUploaded the bytes actually Uploaded so far
   */
  emitIllusiveProgress (bytesUploaded) {
    if (this._paused) {
      return
    }

    let bytesTotal = this.streamsEnded ? this.bytesWritten : this.options.size
    if (!this.streamsEnded) {
      bytesTotal = Math.max(bytesTotal, this.bytesWritten)
    }
    bytesUploaded = bytesUploaded || 0
    // for a 10MB file, 10MB of download will account for 5MB upload progress
    // and 10MB of actual upload will account for the other 5MB upload progress.
    const illusiveBytesUploaded = (this.bytesWritten / 2) + (bytesUploaded / 2)

    logger.debug(
      `${this.shortToken} ${bytesUploaded} ${illusiveBytesUploaded} ${bytesTotal}`,
      'uploader.illusive.progress'
    )
    this.emitProgress(illusiveBytesUploaded, bytesTotal)
  }

  /**
   *
   * @param {number} bytesUploaded
   * @param {number | null} bytesTotal
   */
  emitProgress (bytesUploaded, bytesTotal) {
    bytesTotal = bytesTotal || this.options.size
    if (this.tus && this.tus.options.uploadLengthDeferred && this.streamsEnded) {
      bytesTotal = this.bytesWritten
    }
    const percentage = (bytesUploaded / bytesTotal * 100)
    const formatPercentage = percentage.toFixed(2)
    logger.debug(
      `${this.shortToken} ${bytesUploaded} ${bytesTotal} ${formatPercentage}%`,
      'uploader.upload.progress'
    )

    const dataToEmit = {
      action: 'progress',
      payload: { progress: formatPercentage, bytesUploaded, bytesTotal }
    }
    this.saveState(dataToEmit)

    // avoid flooding the client with progress events.
    const roundedPercentage = Math.floor(percentage)
    if (this.emittedProgress !== roundedPercentage) {
      this.emittedProgress = roundedPercentage
      emitter().emit(this.token, dataToEmit)
    }
  }

  /**
   *
   * @param {string} url
   * @param {object} extraData
   */
  emitSuccess (url, extraData = {}) {
    const emitData = {
      action: 'success',
      payload: Object.assign(extraData, { complete: true, url })
    }
    this.saveState(emitData)
    emitter().emit(this.token, emitData)
  }

  /**
   *
   * @param {Error} err
   * @param {object=} extraData
   */
  emitError (err, extraData = {}) {
    const dataToEmit = {
      action: 'error',
      // TODO: consider removing the stack property
      payload: Object.assign(extraData, { error: serializeError(err) })
    }
    this.saveState(dataToEmit)
    emitter().emit(this.token, dataToEmit)
  }

  /**
   * start the tus upload
   */
  uploadTus () {
    const file = fs.createReadStream(this.path)
    const uploader = this

    // @ts-ignore
    this.tus = new tus.Upload(file, {
      endpoint: this.options.endpoint,
      uploadUrl: this.options.uploadUrl,
      // @ts-ignore
      uploadLengthDeferred: false,
      resume: true,
      retryDelays: [0, 1000, 3000, 5000],
      uploadSize: this.bytesWritten,
      metadata: Object.assign(
        {
          // file name and type as specified by tus protocol
          filename: this.uploadFileName,
          filetype: this.options.metadata.type
        }, this.options.metadata
      ),
      /**
       *
       * @param {Error} error
       */
      onError (error) {
        logger.error(error, 'uploader.tus.error')
        uploader.emitError(error)
      },
      /**
       *
       * @param {number} bytesUploaded
       * @param {number} bytesTotal
       */
      onProgress (bytesUploaded, bytesTotal) {
        uploader.emitIllusiveProgress(bytesUploaded)
      },
      onSuccess () {
        uploader.emitSuccess(uploader.tus.url)
        uploader.cleanUp()
      }
    })

    if (!this._paused) {
      this.tus.start()
    }
  }

  uploadMultipart () {
    const file = fs.createReadStream(this.path)

    // upload progress
    let bytesUploaded = 0
    file.on('data', (data) => {
      bytesUploaded += data.length
      this.emitIllusiveProgress(bytesUploaded)
    })

    const formData = Object.assign(
      {},
      this.options.metadata,
      {
        [this.options.fieldname]: {
          value: file,
          options: {
            filename: this.uploadFileName,
            contentType: this.options.metadata.type
          }
        }
      }
    )
    const headers = headerSanitize(this.options.headers)
    request.post({ url: this.options.endpoint, headers, formData, encoding: null }, (error, response, body) => {
      if (error) {
        logger.error(error, 'upload.multipart.error')
        this.emitError(error)
        return
      }
      const headers = response.headers
      // remove browser forbidden headers
      delete headers['set-cookie']
      delete headers['set-cookie2']

      const respObj = {
        responseText: body.toString(),
        status: response.statusCode,
        statusText: response.statusMessage,
        headers
      }

      if (response.statusCode >= 400) {
        logger.error(`upload failed with status: ${response.statusCode}`, 'upload.multipart.error')
        this.emitError(new Error(response.statusMessage), respObj)
      } else if (bytesUploaded !== this.bytesWritten && bytesUploaded !== this.options.size) {
        const errMsg = `uploaded only ${bytesUploaded} of ${this.bytesWritten} with status: ${response.statusCode}`
        logger.error(errMsg, 'upload.multipart.mismatch.error')
        this.emitError(new Error(errMsg))
      } else {
        this.emitSuccess(null, { response: respObj })
      }

      this.cleanUp()
    })
  }

  /**
   * Upload the file to S3 while it is still being downloaded.
   */
  uploadS3 () {
    const file = createTailReadStream(this.path, {
      tail: true
    })

    this.writeStream.on('finish', () => {
      file.close()
    })

    return this._uploadS3(file)
  }

  /**
   * Upload a stream to S3.
   */
  _uploadS3 (stream) {
    if (!this.options.s3) {
      this.emitError(new Error('The S3 client is not configured on this companion instance.'))
      return
    }

    const filename = this.options.metadata.filename || path.basename(this.path)
    const { client, options } = this.options.s3

    const upload = client.upload({
      Bucket: options.bucket,
      Key: options.getKey(null, filename),
      ACL: options.acl,
      ContentType: this.options.metadata.type,
      Body: stream
    })

    this.s3Upload = upload

    upload.on('httpUploadProgress', ({ loaded, total }) => {
      this.emitProgress(loaded, total)
    })

    upload.send((error, data) => {
      this.s3Upload = null
      if (error) {
        this.emitError(error)
      } else {
        this.emitSuccess(null, {
          response: {
            responseText: JSON.stringify(data),
            headers: {
              'content-type': 'application/json'
            }
          }
        })
      }
      this.cleanUp()
    })
  }
}

Uploader.FILE_NAME_PREFIX = 'uppy-file'
Uploader.STORAGE_PREFIX = 'companion'

module.exports = Uploader
<commit_msg>companion: more comments<commit_after>const fs = require('fs')
const path = require('path')
const tus = require('tus-js-client')
const uuid = require('uuid')
const createTailReadStream = require('@uppy/fs-tail-stream')
const emitter = require('./emitter')
const request = require('request')
const serializeError = require('serialize-error')
const { jsonStringify, hasMatch } = require('./helpers/utils')
const logger = require('./logger')
const validator = require('validator')
const headerSanitize = require('./header-blacklist')

const PROTOCOLS = Object.freeze({
  multipart: 'multipart',
  s3Multipart: 's3-multipart',
  tus: 'tus'
})

class Uploader {
  /**
   * Uploads file to destination based on the supplied protocol (tus, s3-multipart, multipart)
   * For tus uploads, the deferredLength option is enabled, because file size value can be unreliable
   * for some providers (Instagram particularly)
   *
   * @typedef {object} UploaderOptions
   * @property {string} endpoint
   * @property {string=} uploadUrl
   * @property {string} protocol
   * @property {number} size
   * @property {string=} fieldname
   * @property {string} pathPrefix
   * @property {any=} s3
   * @property {any} metadata
   * @property {any} uppyOptions
   * @property {any=} storage
   * @property {any=} headers
   *
   * @param {UploaderOptions} options
   */
  constructor (options) {
    if (!this.validateOptions(options)) {
      logger.debug(this._errRespMessage, 'uploader.validator.fail')
      return
    }

    this.options = options
    this.token = uuid.v4()
    this.path = `${this.options.pathPrefix}/${Uploader.FILE_NAME_PREFIX}-${this.token}`
    this.options.metadata = this.options.metadata || {}
    this.uploadFileName = this.options.metadata.name || path.basename(this.path)
    this.streamsEnded = false
    this.duplexStream = null
    // @TODO disabling parallel uploads and downloads for now
    // if (this.options.protocol === PROTOCOLS.tus) {
    //   this.duplexStream = new stream.PassThrough()
    //     .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.duplex.error'))
    // }
    this.writeStream = fs.createWriteStream(this.path, { mode: 0o666 }) // no executable files
      .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.write.error'))
    /** @type {number} */
    this.emittedProgress = 0
    this.storage = options.storage
    this._paused = false

    if (this.options.protocol === PROTOCOLS.tus) {
      emitter().on(`pause:${this.token}`, () => {
        this._paused = true
        if (this.tus) {
          this.tus.abort()
        }
      })

      emitter().on(`resume:${this.token}`, () => {
        this._paused = false
        if (this.tus) {
          this.tus.start()
        }
      })
    }
  }

  /**
   * the number of bytes written into the streams
   */
  get bytesWritten () {
    return this.writeStream.bytesWritten
  }

  /**
   * Validate the options passed down to the uplaoder
   *
   * @param {UploaderOptions} options
   * @returns {boolean}
   */
  validateOptions (options) {
    // s3 uploads don't require upload destination
    // validation, because the destination is determined
    // by the server's s3 config
    if (options.protocol === PROTOCOLS.s3Multipart) {
      return true
    }

    if (!options.endpoint && !options.uploadUrl) {
      this._errRespMessage = 'No destination specified'
      return false
    }

    const validatorOpts = { require_protocol: true, require_tld: !options.uppyOptions.debug }
    return [options.endpoint, options.uploadUrl].every((url) => {
      if (url && !validator.isURL(url, validatorOpts)) {
        this._errRespMessage = 'Invalid destination url'
        return false
      }

      const allowedUrls = options.uppyOptions.uploadUrls
      if (allowedUrls && url && !hasMatch(url, allowedUrls)) {
        this._errRespMessage = 'upload destination does not match any allowed destinations'
        return false
      }

      return true
    })
  }

  hasError () {
    return this._errRespMessage != null
  }

  /**
   * returns a substring of the token
   */
  get shortToken () {
    return this.token.substring(0, 8)
  }

  /**
   *
   * @param {function} callback
   */
  onSocketReady (callback) {
    emitter().once(`connection:${this.token}`, () => callback())
    logger.debug(`${this.shortToken} waiting for connection`, 'uploader.socket.wait')
  }

  cleanUp () {
    fs.unlink(this.path, (err) => {
      if (err) {
        logger.error(`cleanup failed for: ${this.path} err: ${err}`, 'uploader.cleanup.error')
      }
    })
    emitter().removeAllListeners(`pause:${this.token}`)
    emitter().removeAllListeners(`resume:${this.token}`)
  }

  /**
   *
   * @param {Buffer | Buffer[]} chunk
   */
  handleChunk (chunk) {
    // @todo a default protocol should not be set. We should ensure that the user specifies her protocol.
    const protocol = this.options.protocol || PROTOCOLS.multipart

    // The download has completed; close the file and start an upload if necessary.
    if (chunk === null) {
      this.writeStream.on('finish', () => {
        this.streamsEnded = true
        if (this.options.endpoint && protocol === PROTOCOLS.multipart) {
          this.uploadMultipart()
        }

        if (protocol === PROTOCOLS.tus && !this.tus) {
          return this.uploadTus()
        }
      })

      return this.endStreams()
    }

    this.writeStream.write(chunk, () => {
      logger.debug(`${this.shortToken} ${this.bytesWritten} bytes`, 'uploader.download.progress')
      if (protocol === PROTOCOLS.multipart || protocol === PROTOCOLS.tus) {
        return this.emitIllusiveProgress()
      }

      if (protocol === PROTOCOLS.s3Multipart && !this.s3Upload) {
        return this.uploadS3()
      }
      // @TODO disabling parallel uploads and downloads for now
      // if (!this.options.endpoint) return

      // if (protocol === PROTOCOLS.tus && !this.tus) {
      //   return this.uploadTus()
      // }
    })
  }

  /**
   * @param {Buffer | Buffer[]} chunk
   * @param {function} cb
   */
  writeToStreams (chunk, cb) {
    const done = []
    const doneLength = this.duplexStream ? 2 : 1
    const onDone = () => {
      done.push(true)
      if (done.length >= doneLength) {
        cb()
      }
    }

    this.writeStream.write(chunk, onDone)
    if (this.duplexStream) {
      this.duplexStream.write(chunk, onDone)
    }
  }

  endStreams () {
    this.writeStream.end()
    if (this.duplexStream) {
      this.duplexStream.end()
    }
  }

  getResponse () {
    if (this._errRespMessage) {
      return { body: this._errRespMessage, status: 400 }
    }
    return { body: { token: this.token }, status: 200 }
  }

  /**
   * @typedef {{action: string, payload: object}} State
   * @param {State} state
   */
  saveState (state) {
    if (!this.storage) return
    this.storage.set(`${Uploader.STORAGE_PREFIX}:${this.token}`, jsonStringify(state))
  }

  /**
   * This method emits upload progress but also creates an "upload progress" illusion
   * for the waiting period while only download is happening. Hence, it combines both
   * download and upload into an upload progress.
   * @see emitProgress
   * @param {number=} bytesUploaded the bytes actually Uploaded so far
   */
  emitIllusiveProgress (bytesUploaded) {
    if (this._paused) {
      return
    }

    let bytesTotal = this.streamsEnded ? this.bytesWritten : this.options.size
    if (!this.streamsEnded) {
      bytesTotal = Math.max(bytesTotal, this.bytesWritten)
    }
    bytesUploaded = bytesUploaded || 0
    // for a 10MB file, 10MB of download will account for 5MB upload progress
    // and 10MB of actual upload will account for the other 5MB upload progress.
    const illusiveBytesUploaded = (this.bytesWritten / 2) + (bytesUploaded / 2)

    logger.debug(
      `${this.shortToken} ${bytesUploaded} ${illusiveBytesUploaded} ${bytesTotal}`,
      'uploader.illusive.progress'
    )
    this.emitProgress(illusiveBytesUploaded, bytesTotal)
  }

  /**
   *
   * @param {number} bytesUploaded
   * @param {number | null} bytesTotal
   */
  emitProgress (bytesUploaded, bytesTotal) {
    bytesTotal = bytesTotal || this.options.size
    if (this.tus && this.tus.options.uploadLengthDeferred && this.streamsEnded) {
      bytesTotal = this.bytesWritten
    }
    const percentage = (bytesUploaded / bytesTotal * 100)
    const formatPercentage = percentage.toFixed(2)
    logger.debug(
      `${this.shortToken} ${bytesUploaded} ${bytesTotal} ${formatPercentage}%`,
      'uploader.upload.progress'
    )

    const dataToEmit = {
      action: 'progress',
      payload: { progress: formatPercentage, bytesUploaded, bytesTotal }
    }
    this.saveState(dataToEmit)

    // avoid flooding the client with progress events.
    const roundedPercentage = Math.floor(percentage)
    if (this.emittedProgress !== roundedPercentage) {
      this.emittedProgress = roundedPercentage
      emitter().emit(this.token, dataToEmit)
    }
  }

  /**
   *
   * @param {string} url
   * @param {object} extraData
   */
  emitSuccess (url, extraData = {}) {
    const emitData = {
      action: 'success',
      payload: Object.assign(extraData, { complete: true, url })
    }
    this.saveState(emitData)
    emitter().emit(this.token, emitData)
  }

  /**
   *
   * @param {Error} err
   * @param {object=} extraData
   */
  emitError (err, extraData = {}) {
    const dataToEmit = {
      action: 'error',
      // TODO: consider removing the stack property
      payload: Object.assign(extraData, { error: serializeError(err) })
    }
    this.saveState(dataToEmit)
    emitter().emit(this.token, dataToEmit)
  }

  /**
   * start the tus upload
   */
  uploadTus () {
    const file = fs.createReadStream(this.path)
    const uploader = this

    // @ts-ignore
    this.tus = new tus.Upload(file, {
      endpoint: this.options.endpoint,
      uploadUrl: this.options.uploadUrl,
      // @ts-ignore
      uploadLengthDeferred: false,
      resume: true,
      retryDelays: [0, 1000, 3000, 5000],
      uploadSize: this.bytesWritten,
      metadata: Object.assign(
        {
          // file name and type as required by the tusd tus server
          // https://github.com/tus/tusd/blob/5b376141903c1fd64480c06dde3dfe61d191e53d/unrouted_handler.go#L614-L646
          filename: this.uploadFileName,
          filetype: this.options.metadata.type
        }, this.options.metadata
      ),
      /**
       *
       * @param {Error} error
       */
      onError (error) {
        logger.error(error, 'uploader.tus.error')
        uploader.emitError(error)
      },
      /**
       *
       * @param {number} bytesUploaded
       * @param {number} bytesTotal
       */
      onProgress (bytesUploaded, bytesTotal) {
        uploader.emitIllusiveProgress(bytesUploaded)
      },
      onSuccess () {
        uploader.emitSuccess(uploader.tus.url)
        uploader.cleanUp()
      }
    })

    if (!this._paused) {
      this.tus.start()
    }
  }

  uploadMultipart () {
    const file = fs.createReadStream(this.path)

    // upload progress
    let bytesUploaded = 0
    file.on('data', (data) => {
      bytesUploaded += data.length
      this.emitIllusiveProgress(bytesUploaded)
    })

    const formData = Object.assign(
      {},
      this.options.metadata,
      {
        [this.options.fieldname]: {
          value: file,
          options: {
            filename: this.uploadFileName,
            contentType: this.options.metadata.type
          }
        }
      }
    )
    const headers = headerSanitize(this.options.headers)
    request.post({ url: this.options.endpoint, headers, formData, encoding: null }, (error, response, body) => {
      if (error) {
        logger.error(error, 'upload.multipart.error')
        this.emitError(error)
        return
      }
      const headers = response.headers
      // remove browser forbidden headers
      delete headers['set-cookie']
      delete headers['set-cookie2']

      const respObj = {
        responseText: body.toString(),
        status: response.statusCode,
        statusText: response.statusMessage,
        headers
      }

      if (response.statusCode >= 400) {
        logger.error(`upload failed with status: ${response.statusCode}`, 'upload.multipart.error')
        this.emitError(new Error(response.statusMessage), respObj)
      } else if (bytesUploaded !== this.bytesWritten && bytesUploaded !== this.options.size) {
        const errMsg = `uploaded only ${bytesUploaded} of ${this.bytesWritten} with status: ${response.statusCode}`
        logger.error(errMsg, 'upload.multipart.mismatch.error')
        this.emitError(new Error(errMsg))
      } else {
        this.emitSuccess(null, { response: respObj })
      }

      this.cleanUp()
    })
  }

  /**
   * Upload the file to S3 while it is still being downloaded.
   */
  uploadS3 () {
    const file = createTailReadStream(this.path, {
      tail: true
    })

    this.writeStream.on('finish', () => {
      file.close()
    })

    return this._uploadS3(file)
  }

  /**
   * Upload a stream to S3.
   */
  _uploadS3 (stream) {
    if (!this.options.s3) {
      this.emitError(new Error('The S3 client is not configured on this companion instance.'))
      return
    }

    const filename = this.options.metadata.filename || path.basename(this.path)
    const { client, options } = this.options.s3

    const upload = client.upload({
      Bucket: options.bucket,
      Key: options.getKey(null, filename),
      ACL: options.acl,
      ContentType: this.options.metadata.type,
      Body: stream
    })

    this.s3Upload = upload

    upload.on('httpUploadProgress', ({ loaded, total }) => {
      this.emitProgress(loaded, total)
    })

    upload.send((error, data) => {
      this.s3Upload = null
      if (error) {
        this.emitError(error)
      } else {
        this.emitSuccess(null, {
          response: {
            responseText: JSON.stringify(data),
            headers: {
              'content-type': 'application/json'
            }
          }
        })
      }
      this.cleanUp()
    })
  }
}

Uploader.FILE_NAME_PREFIX = 'uppy-file'
Uploader.STORAGE_PREFIX = 'companion'

module.exports = Uploader
<|endoftext|>


<commit_before>import _ from 'lodash';
import Vue from 'vue';
import { Definition } from 'classes/helpers';
import api from 'plugins/http/api';
import { undoStackInstance } from 'plugins/undo-redo';
import { eventBus } from 'plugins/eventbus';

const state = {
	currentBlockIndex: null,
	currentRegion: 'main',
	blockMeta: {
		sizes: [],
		offsets: [],
		moved: null
	},
	pageData: {
		blocks: {
			main: []
		}
	},
	pageName: '',
	pageScale: .4,
	loaded: false
};

const mutations = {

	setPage(state, page) {
		if(!page.blocks) {
			page.blocks = {
				main: []
			};
		}

		state.pageData = page;
	},

	setLoaded(state, loaded = true) {
		state.loaded = loaded;
	},

	setBlock(state, { index } = { index: null }) {
		state.currentBlockIndex = index;
	},

	reorderBlocks(state, { from, to, value }) {
		state.pageData.blocks[state.currentRegion].splice(from, 1);
		state.pageData.blocks[state.currentRegion].splice(to, 0, value);
	},

	updateBlockPositionsOrder(state, { type, from, to, value }) {
		state.blockMeta[type].splice(from, 1);
		state.blockMeta[type].splice(to, 0, value);
	},

	updateBlockPositions(state, { type, index, value }) {
		state.blockMeta[type].splice(index, 1, value);
	},

	updateFieldValue(state, { index, name, value }) {
		let
			idx = index !== void 0 ? index : state.currentBlockIndex,
			fields = state.pageData.blocks[state.currentRegion][idx].fields;

		// if field exists just update it
		if(_.has(fields, name)) {
			_.set(fields, name, value);
		}
		// otherwise update all fields to maintain reactivity
		else {
			const clone = _.clone(fields);
			_.set(clone, name, value);
			fields = clone;
		}

		// TODO: use state for this
		Vue.nextTick(() => eventBus.$emit('block:updateOverlay'));
	},

	changePage(state, name) {
		state.pageName = name;
	},

	addBlock(state, { region, index, block }) {
		if(region === void 0) {
			region = state.currentRegion;
		}

		if(index === void 0) {
			index = state.pageData.blocks[state.currentRegion].length;
		}

		Definition.fillBlockFields(block);

		state.pageData.blocks[state.currentRegion].splice(index, 1, block || {});
	},

	deleteBlock(state, { index } = { index: null }) {
		if(index === null) {
			index = state.currentBlockIndex;
		}

		state.pageData.blocks[state.currentRegion].splice(index, 1);

		// TODO: use state for this
		Vue.nextTick(() => eventBus.$emit('block:updateOverlay'));
	}
};

const actions = {

	fetchPage({ state, commit }, id) {

		// TODO: refactor into smaller methods
		api
			.get(`page/${id}?include=blocks`)
			.then(response => {
				const page = response.data.data;

				api
					.get(`layout/${page.layout_name}/definition?include=region_definitions.block_definitions`)
					.then(({ data: region }) => {

						region.data.region_definitions.forEach((region) => {
							region.block_definitions.forEach(definition => {
								Definition.set(definition);
							});
						});

						commit('setBlockDefinitions', Definition.definitions, { root: true });

						let blocks;

						if(page.blocks) {
							blocks = page.blocks;
							delete page.blocks;
						}
						else {
							blocks = {};
						}

						commit('setPage', _.cloneDeep(page));

						Object.keys(blocks).forEach(region => {
							blocks[region].forEach((block, index) => {
								commit('addBlock', { region, index, block })
							});
						});

						commit('setLoaded');

						undoStackInstance.init(state.pageData);
					});

			});
	}
};

const getters = {

	getFieldValue: (state) => (index, name) => {
		const block = state.pageData.blocks[state.currentRegion][index];

		if(!block) {
			return null;
		}

		return _.get(block.fields, name, null);
	},

	getCurrentFieldValue: (state, getters) => (name) => {
		return getters.getFieldValue(state.currentBlockIndex, name);
	},

	getCurrentBlock: (state) => () => {
		return state.pageData.blocks[state.currentRegion][state.currentBlockIndex];
	}
};

export default {
	state,
	mutations,
	actions,
	getters
};
<commit_msg>Pass current block index to "updateOverlay" event.<commit_after>import _ from 'lodash';
import Vue from 'vue';
import { Definition } from 'classes/helpers';
import api from 'plugins/http/api';
import { undoStackInstance } from 'plugins/undo-redo';
import { eventBus } from 'plugins/eventbus';

const state = {
	currentBlockIndex: null,
	currentRegion: 'main',
	blockMeta: {
		sizes: [],
		offsets: [],
		moved: null
	},
	pageData: {
		blocks: {
			main: []
		}
	},
	pageName: '',
	pageScale: .4,
	loaded: false
};

const mutations = {

	setPage(state, page) {
		if(!page.blocks) {
			page.blocks = {
				main: []
			};
		}

		state.pageData = page;
	},

	setLoaded(state, loaded = true) {
		state.loaded = loaded;
	},

	setBlock(state, { index } = { index: null }) {
		state.currentBlockIndex = index;
	},

	reorderBlocks(state, { from, to, value }) {
		state.pageData.blocks[state.currentRegion].splice(from, 1);
		state.pageData.blocks[state.currentRegion].splice(to, 0, value);
	},

	updateBlockPositionsOrder(state, { type, from, to, value }) {
		state.blockMeta[type].splice(from, 1);
		state.blockMeta[type].splice(to, 0, value);
	},

	updateBlockPositions(state, { type, index, value }) {
		state.blockMeta[type].splice(index, 1, value);
	},

	updateFieldValue(state, { index, name, value }) {
		let
			idx = index !== void 0 ? index : state.currentBlockIndex,
			fields = state.pageData.blocks[state.currentRegion][idx].fields;

		// if field exists just update it
		if(_.has(fields, name)) {
			_.set(fields, name, value);
		}
		// otherwise update all fields to maintain reactivity
		else {
			const clone = _.clone(fields);
			_.set(clone, name, value);
			fields = clone;
		}

		// TODO: use state for this
		Vue.nextTick(() => eventBus.$emit('block:updateOverlay'));
	},

	changePage(state, name) {
		state.pageName = name;
	},

	addBlock(state, { region, index, block }) {
		if(region === void 0) {
			region = state.currentRegion;
		}

		if(index === void 0) {
			index = state.pageData.blocks[state.currentRegion].length;
		}

		Definition.fillBlockFields(block);

		state.pageData.blocks[state.currentRegion].splice(index, 1, block || {});
	},

	deleteBlock(state, { index } = { index: null }) {
		if(index === null) {
			index = state.currentBlockIndex;
		}

		state.pageData.blocks[state.currentRegion].splice(index, 1);

		// TODO: use state for this
		Vue.nextTick(() => eventBus.$emit('block:updateOverlay', index));
	}
};

const actions = {

	fetchPage({ state, commit }, id) {

		// TODO: refactor into smaller methods
		api
			.get(`page/${id}?include=blocks`)
			.then(response => {
				const page = response.data.data;

				api
					.get(`layout/${page.layout_name}/definition?include=region_definitions.block_definitions`)
					.then(({ data: region }) => {

						region.data.region_definitions.forEach((region) => {
							region.block_definitions.forEach(definition => {
								Definition.set(definition);
							});
						});

						commit('setBlockDefinitions', Definition.definitions, { root: true });

						let blocks;

						if(page.blocks) {
							blocks = page.blocks;
							delete page.blocks;
						}
						else {
							blocks = {};
						}

						commit('setPage', _.cloneDeep(page));

						Object.keys(blocks).forEach(region => {
							blocks[region].forEach((block, index) => {
								commit('addBlock', { region, index, block })
							});
						});

						commit('setLoaded');

						undoStackInstance.init(state.pageData);
					});

			});
	}
};

const getters = {

	getFieldValue: (state) => (index, name) => {
		const block = state.pageData.blocks[state.currentRegion][index];

		if(!block) {
			return null;
		}

		return _.get(block.fields, name, null);
	},

	getCurrentFieldValue: (state, getters) => (name) => {
		return getters.getFieldValue(state.currentBlockIndex, name);
	},

	getCurrentBlock: (state) => () => {
		return state.pageData.blocks[state.currentRegion][state.currentBlockIndex];
	}
};

export default {
	state,
	mutations,
	actions,
	getters
};
<|endoftext|>


<commit_before>function remove_element(element){

  element.parentNode.removeChild(element);
}

function remove_nth_element_with_class(class_selector, index){
  elements = document.getElementsByClassName(class_selector);
  if(elements.length > 0){
    remove_element(elements[index]);
    return true;
  }
  return false;
}

function remove_first_element_by_selector(selector){
  e = document.querySelector(selector);
  if(e){
    remove_element(e);
    return true;
  }
  return false;
}

/*
 * function hideCookiePolicyBySelectors
 * @selectors: is an array of selectors, first element that match the selector
 * is removed from the DOM
 * */
function hideCookiePolicyBySelectors(selectors){
  var selectors_length = selectors.length;
  for(var i=0; i<selectors_length; i++){
    if(remove_first_element_by_selector(selectors[i])){
      return;
    }
  }
}
hideCookiePolicyBySelectors([
'#cookie-bar', '#eu-cookie-policy', '#cccwr', '#cookieChoiceInfo',
'#iubenda-cs-banner', '#eu-cookie', '#banner_cookie',
'#iubenda-cs-banner',
'[data-testid="cookie-policy-banner"]',
'.pea_cook_wrapper', '.cc-cookies', 'eu-cookie-notice']);
<commit_msg>another class<commit_after>function remove_element(element){

  element.parentNode.removeChild(element);
}

function remove_nth_element_with_class(class_selector, index){
  elements = document.getElementsByClassName(class_selector);
  if(elements.length > 0){
    remove_element(elements[index]);
    return true;
  }
  return false;
}

function remove_first_element_by_selector(selector){
  e = document.querySelector(selector);
  if(e){
    remove_element(e);
    return true;
  }
  return false;
}

/*
 * function hideCookiePolicyBySelectors
 * @selectors: is an array of selectors, first element that match the selector
 * is removed from the DOM
 * */
function hideCookiePolicyBySelectors(selectors){
  var selectors_length = selectors.length;
  for(var i=0; i<selectors_length; i++){
    if(remove_first_element_by_selector(selectors[i])){
      return;
    }
  }
}
hideCookiePolicyBySelectors([
'#cookie-bar', '#eu-cookie-policy', '#cccwr', '#cookieChoiceInfo',
'#iubenda-cs-banner', '#eu-cookie', '#banner_cookie',
'#iubenda-cs-banner',
'[data-testid="cookie-policy-banner"]',
'.pea_cook_wrapper', '.cc-cookies', 'eu-cookie-notice',
'.b-cookie-hint']);
<|endoftext|>