<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { commitMutation, graphql } from 'react-relay/compat';
import { Store } from 'react-relay/classic';
import { makeStyles } from '@material-ui/core/styles';
import { browserHistory } from 'react-router';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import Radio from '@material-ui/core/Radio';
import RadioGroup from '@material-ui/core/RadioGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormControl from '@material-ui/core/FormControl';
import Box from '@material-ui/core/Box';
import ListIcon from '@material-ui/icons/List';
import { FormattedMessage } from 'react-intl';
import ConfirmProceedDialog from '../layout/ConfirmProceedDialog';
import { withSetFlashMessage } from '../FlashMessage';
import CheckChannels from '../../CheckChannels';
import { checkBlue } from '../../styles/js/shared';

const createMutation = graphql`
  mutation SaveListCreateSavedSearchMutation($input: CreateSavedSearchInput!) {
    createSavedSearch(input: $input) {
      saved_search {
        id
        dbid
      }
      team {
        saved_searches(first: 10000) {
          edges {
            node {
              id
              dbid
              title
              filters
            }
          }
        }
      }
    }
  }
`;

const updateMutation = graphql`
  mutation SaveListUpdateSavedSearchMutation($input: UpdateSavedSearchInput!) {
    updateSavedSearch(input: $input) {
      saved_search {
        filters
      }
    }
  }
`;

const updateSpecialPageMutation = graphql`
  mutation SaveListUpdateTeamMutation($input: UpdateTeamInput!) {
    updateTeam(input: $input) {
      team {
        get_tipline_inbox_filters
        get_suggested_matches_filters
        get_trends_filters
      }
    }
  }
`;

const useStyles = makeStyles(theme => ({
  saveListCreate: {
    whiteSpace: 'nowrap',
    marginRight: theme.spacing(1),
  },
  saveListButton: {
    color: checkBlue,
  },
  saveListCreateLabel: {
    marginRight: 0,
    flexGrow: 1,
  },
}));

const SaveList = ({
  team,
  project,
  projectGroup,
  savedSearch,
  query,
  setFlashMessage,
}) => {
  const currentPath = window.location.pathname.match(/^\/[^/]+\/(list|project|all-items|collection|tipline-inbox|suggested-matches|trends)(\/([0-9]+))?/);

  if (!currentPath) {
    return null;
  }

  const classes = useStyles();

  const objectType = currentPath[1];

  const [title, setTitle] = React.useState('');
  const [saving, setSaving] = React.useState(false);
  const [operation, setOperation] = React.useState(objectType === 'trends' ? 'UPDATE_SPECIAL_PAGE' : 'CREATE'); // or 'UPDATE'
  const [showNewDialog, setShowNewDialog] = React.useState(false);
  const [showExistingDialog, setShowExistingDialog] = React.useState(false);

  // Just show the button on: "All Items", collection and folder (create a list) or list (create a new list or update list)
  if (['all-items', 'project', 'list', 'collection', 'tipline-inbox', 'suggested-matches', 'trends'].indexOf(objectType) === -1) {
    return null;
  }

  // Don't even show the button if there is nothing to be saved
  if (!query || JSON.stringify(query) === '{}') {
    return null;
  }

  // Don't show the button if it's a list and nothing changed
  if (objectType === 'list' && savedSearch && JSON.stringify(query) === savedSearch.filters) {
    return null;
  }

  // Don't show the button if it's a tipline inbox or suggested matches page and nothing changed
  if (['tipline-inbox', 'suggested-matches', 'trends'].indexOf(objectType) !== -1) {
    let defaultQuery = {};
    let savedQuery = '{}';
    if (objectType === 'tipline-inbox') {
      defaultQuery = { read: ['0'], projects: ['-1'], verification_status: [team.verification_statuses.default] };
      savedQuery = team.get_tipline_inbox_filters;
    } else if (objectType === 'suggested-matches') {
      defaultQuery = { suggestions_count: { min: 1 } };
      savedQuery = team.get_suggested_matches_filters;
    } else if (objectType === 'trends') {
      defaultQuery = { trends: true, country: true };
      savedQuery = team.get_trends_filters;
    }
    // Don't show the button if it's a default list
    if (JSON.stringify(query) === JSON.stringify(defaultQuery)) {
      return null;
    }
    // Don't show the button if it's a saved search
    if (savedQuery && JSON.stringify(query) === JSON.stringify(savedQuery)) {
      return null;
    }
  }

  const handleClose = () => {
    setShowNewDialog(false);
    setShowExistingDialog(false);
  };

  const handleError = () => {
    setSaving(false);
    setFlashMessage((
      <FormattedMessage
        id="saveList.defaultErrorMessage"
        defaultMessage="Could not save list, please try again"
        description="Error message displayed when it's not possible to save a list"
      />
    ), 'error');
  };

  const handleSuccess = (response) => {
    setSaving(false);
    setFlashMessage((
      <FormattedMessage
        id="saveList.savedSuccessfully"
        defaultMessage="List saved successfully"
        description="Success message displayed when a list is saved"
      />
    ), 'success');
    setTitle('');
    handleClose();
    if (operation === 'CREATE') {
      browserHistory.push(`/${team.slug}/list/${response.createSavedSearch.saved_search.dbid}`);
    }
  };

  const handleSave = () => {
    setSaving(true);
    const input = {};
    let queryToBeSaved = {};
    // If it's a folder, add the project.id as a filter
    if (project) {
      queryToBeSaved.projects = [project.dbid];
    }
    // If it's a collection, add the projectGroup.id as a filter
    if (projectGroup) {
      queryToBeSaved.project_group_id = [projectGroup.dbid];
    }
    if (objectType === 'tipline-inbox' && operation !== 'UPDATE_SPECIAL_PAGE') {
      queryToBeSaved.channels = [CheckChannels.ANYTIPLINE];
    }
    queryToBeSaved = { ...queryToBeSaved, ...query };

    if (operation === 'UPDATE_SPECIAL_PAGE') {
      if (objectType === 'tipline-inbox') {
        input.tipline_inbox_filters = JSON.stringify(query);
      } else if (objectType === 'suggested-matches') {
        input.suggested_matches_filters = JSON.stringify(query);
      } else if (objectType === 'trends') {
        input.trends_filters = JSON.stringify(query);
      }
    } else {
      input.filters = JSON.stringify(queryToBeSaved);
    }

    let mutation = updateMutation;

    if (operation === 'CREATE') {
      input.team_id = team.dbid;
      input.title = title;
      mutation = createMutation;
    } else if (operation === 'UPDATE') {
      input.id = savedSearch.id;
      mutation = updateMutation;
    } else if (operation === 'UPDATE_SPECIAL_PAGE') {
      input.id = team.id;
      mutation = updateSpecialPageMutation;
    }

    commitMutation(Store, {
      mutation,
      variables: {
        input,
      },
      onCompleted: (response, error) => {
        if (error) {
          handleError();
        } else {
          handleSuccess(response);
        }
      },
      onError: () => {
        handleError();
      },
    });
  };

  const handleClick = () => {
    // From the "All Items" page, collection page and a folder page, we can just create a new list
    if (objectType === 'all-items' || objectType === 'project' || objectType === 'collection') {
      setShowNewDialog(true);
    // From a list page, we can either create a new one or update the one we're seeing
    } else if (objectType === 'list') {
      setOperation('UPDATE');
      setShowExistingDialog(true);
    } else if (['tipline-inbox', 'suggested-matches'].indexOf(objectType) !== -1) {
      setOperation('UPDATE_SPECIAL_PAGE');
      setShowExistingDialog(true);
    } else if (objectType === 'trends') {
      handleSave();
    }
  };

  return (
    <React.Fragment>

      {/* The "Save list" button */}
      <Button
        id="save-list__button"
        className={classes.saveListButton}
        startIcon={<ListIcon />}
        onClick={handleClick}
      >
        <FormattedMessage
          id="saveList.saveList"
          defaultMessage="Save list"
          description="'Save' here is in infinitive form - it's a button label, to save the current set of filters applied to a search result as a list"
        />
      </Button>

      {/* Create a new list */}
      <ConfirmProceedDialog
        open={showNewDialog}
        title={<FormattedMessage id="saveList.newList" defaultMessage="Save list" />}
        body={
          <Box>
            <TextField
              label={
                <FormattedMessage
                  id="saveList.title"
                  defaultMessage="Enter new list name"
                />
              }
              onChange={(e) => { setTitle(e.target.value); }}
              variant="outlined"
              margin="normal"
              className="new-list__title"
              fullWidth
            />
          </Box>
        }
        proceedDisabled={!title}
        proceedLabel={<FormattedMessage id="saveList.newList" defaultMessage="Save list" />}
        onProceed={handleSave}
        isSaving={saving}
        cancelLabel={<FormattedMessage id="saveList.cancel" defaultMessage="Cancel" />}
        onCancel={handleClose}
      />

      {/* Create a new list or update an existing list */}
      { savedSearch || ['tipline-inbox', 'suggested-matches'].indexOf(objectType) !== -1 ?
        <ConfirmProceedDialog
          open={showExistingDialog}
          title={<FormattedMessage id="saveList.newList" defaultMessage="Save list" />}
          body={
            <FormControl fullWidth>
              <RadioGroup value={operation} onChange={(e) => { setOperation(e.target.value); }}>
                { savedSearch ?
                  <FormControlLabel
                    value="UPDATE"
                    control={<Radio />}
                    label={<FormattedMessage id="saveList.update" defaultMessage='Save changes to the list "{listName}"' values={{ listName: savedSearch.title }} description="'Save' here is an infinitive verb" />}
                  /> :
                  <FormControlLabel
                    value="UPDATE_SPECIAL_PAGE"
                    control={<Radio />}
                    label={<FormattedMessage id="saveList.updateSpecialPage" defaultMessage="Save changes to the list" description="'Save' here is an infinitive verb" />}
                  />
                }
                <FormControlLabel
                  value="CREATE"
                  control={<Radio />}
                  className={classes.saveListCreateLabel}
                  classes={{ label: classes.saveListCreateLabel }}
                  label={
                    <Box display="flex" alignItems="center" width={1}>
                      <span className={classes.saveListCreate}>
                        <FormattedMessage id="saveList.create" defaultMessage="Create new list" description="'Create' here is an infinitive verb" />
                      </span>
                      <TextField
                        label={
                          <FormattedMessage
                            id="saveList.title"
                            defaultMessage="Enter new list name"
                          />
                        }
                        onChange={(e) => { setTitle(e.target.value); }}
                        variant="outlined"
                        margin="normal"
                        className="new-list__title"
                        disabled={operation === 'UPDATE'}
                        autoFocus={operation === 'CREATE'}
                        fullWidth
                      />
                    </Box>
                  }
                />
              </RadioGroup>
            </FormControl>
          }
          proceedDisabled={operation === 'CREATE' && !title}
          proceedLabel={<FormattedMessage id="saveList.newList" defaultMessage="Save list" />}
          onProceed={handleSave}
          isSaving={saving}
          cancelLabel={<FormattedMessage id="saveList.cancel" defaultMessage="Cancel" />}
          onCancel={handleClose}
        /> : null }
    </React.Fragment>
  );
};

SaveList.defaultProps = {
  project: null,
  projectGroup: null,
  savedSearch: null,
};

SaveList.propTypes = {
  team: PropTypes.shape({
    id: PropTypes.string.isRequired,
    dbid: PropTypes.number.isRequired,
    slug: PropTypes.string.isRequired,
    permissions: PropTypes.string.isRequired,
  }).isRequired,
  query: PropTypes.object.isRequired,
  project: PropTypes.shape({
    dbid: PropTypes.number.isRequired,
  }),
  projectGroup: PropTypes.shape({
    dbid: PropTypes.number.isRequired,
  }),
  savedSearch: PropTypes.shape({
    id: PropTypes.string.isRequired,
    title: PropTypes.string.isRequired,
    filters: PropTypes.string.isRequired,
  }),
};

export default withSetFlashMessage(SaveList);
<commit_msg>CHECK-1660: show save list when user change saved filters to default one (#1104)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { commitMutation, graphql } from 'react-relay/compat';
import { Store } from 'react-relay/classic';
import { makeStyles } from '@material-ui/core/styles';
import { browserHistory } from 'react-router';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import Radio from '@material-ui/core/Radio';
import RadioGroup from '@material-ui/core/RadioGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormControl from '@material-ui/core/FormControl';
import Box from '@material-ui/core/Box';
import ListIcon from '@material-ui/icons/List';
import { FormattedMessage } from 'react-intl';
import ConfirmProceedDialog from '../layout/ConfirmProceedDialog';
import { withSetFlashMessage } from '../FlashMessage';
import CheckChannels from '../../CheckChannels';
import { checkBlue } from '../../styles/js/shared';

const createMutation = graphql`
  mutation SaveListCreateSavedSearchMutation($input: CreateSavedSearchInput!) {
    createSavedSearch(input: $input) {
      saved_search {
        id
        dbid
      }
      team {
        saved_searches(first: 10000) {
          edges {
            node {
              id
              dbid
              title
              filters
            }
          }
        }
      }
    }
  }
`;

const updateMutation = graphql`
  mutation SaveListUpdateSavedSearchMutation($input: UpdateSavedSearchInput!) {
    updateSavedSearch(input: $input) {
      saved_search {
        filters
      }
    }
  }
`;

const updateSpecialPageMutation = graphql`
  mutation SaveListUpdateTeamMutation($input: UpdateTeamInput!) {
    updateTeam(input: $input) {
      team {
        get_tipline_inbox_filters
        get_suggested_matches_filters
        get_trends_filters
      }
    }
  }
`;

const useStyles = makeStyles(theme => ({
  saveListCreate: {
    whiteSpace: 'nowrap',
    marginRight: theme.spacing(1),
  },
  saveListButton: {
    color: checkBlue,
  },
  saveListCreateLabel: {
    marginRight: 0,
    flexGrow: 1,
  },
}));

const SaveList = ({
  team,
  project,
  projectGroup,
  savedSearch,
  query,
  setFlashMessage,
}) => {
  const currentPath = window.location.pathname.match(/^\/[^/]+\/(list|project|all-items|collection|tipline-inbox|suggested-matches|trends)(\/([0-9]+))?/);

  if (!currentPath) {
    return null;
  }

  const classes = useStyles();

  const objectType = currentPath[1];

  const [title, setTitle] = React.useState('');
  const [saving, setSaving] = React.useState(false);
  const [operation, setOperation] = React.useState(objectType === 'trends' ? 'UPDATE_SPECIAL_PAGE' : 'CREATE'); // or 'UPDATE'
  const [showNewDialog, setShowNewDialog] = React.useState(false);
  const [showExistingDialog, setShowExistingDialog] = React.useState(false);

  // Just show the button on: "All Items", collection and folder (create a list) or list (create a new list or update list)
  if (['all-items', 'project', 'list', 'collection', 'tipline-inbox', 'suggested-matches', 'trends'].indexOf(objectType) === -1) {
    return null;
  }

  // Don't even show the button if there is nothing to be saved
  if (!query || JSON.stringify(query) === '{}') {
    return null;
  }

  // Don't show the button if it's a list and nothing changed
  if (objectType === 'list' && savedSearch && JSON.stringify(query) === savedSearch.filters) {
    return null;
  }

  // Don't show the button if it's a tipline inbox or suggested matches page and nothing changed
  if (['tipline-inbox', 'suggested-matches', 'trends'].indexOf(objectType) !== -1) {
    let defaultQuery = {};
    let savedQuery = '{}';
    if (objectType === 'tipline-inbox') {
      defaultQuery = { read: ['0'], projects: ['-1'], verification_status: [team.verification_statuses.default] };
      savedQuery = team.get_tipline_inbox_filters;
    } else if (objectType === 'suggested-matches') {
      defaultQuery = { suggestions_count: { min: 1 } };
      savedQuery = team.get_suggested_matches_filters;
    } else if (objectType === 'trends') {
      defaultQuery = { trends: true, country: true };
      savedQuery = team.get_trends_filters;
    }
    // Don't show the button if it's a saved search or a default list
    if (savedQuery) {
      if (JSON.stringify(query) === JSON.stringify(savedQuery)) {
        return null;
      }
    } else if (JSON.stringify(query) === JSON.stringify(defaultQuery)) {
      return null;
    }
  }

  const handleClose = () => {
    setShowNewDialog(false);
    setShowExistingDialog(false);
  };

  const handleError = () => {
    setSaving(false);
    setFlashMessage((
      <FormattedMessage
        id="saveList.defaultErrorMessage"
        defaultMessage="Could not save list, please try again"
        description="Error message displayed when it's not possible to save a list"
      />
    ), 'error');
  };

  const handleSuccess = (response) => {
    setSaving(false);
    setFlashMessage((
      <FormattedMessage
        id="saveList.savedSuccessfully"
        defaultMessage="List saved successfully"
        description="Success message displayed when a list is saved"
      />
    ), 'success');
    setTitle('');
    handleClose();
    if (operation === 'CREATE') {
      browserHistory.push(`/${team.slug}/list/${response.createSavedSearch.saved_search.dbid}`);
    }
  };

  const handleSave = () => {
    setSaving(true);
    const input = {};
    let queryToBeSaved = {};
    // If it's a folder, add the project.id as a filter
    if (project) {
      queryToBeSaved.projects = [project.dbid];
    }
    // If it's a collection, add the projectGroup.id as a filter
    if (projectGroup) {
      queryToBeSaved.project_group_id = [projectGroup.dbid];
    }
    if (objectType === 'tipline-inbox' && operation !== 'UPDATE_SPECIAL_PAGE') {
      queryToBeSaved.channels = [CheckChannels.ANYTIPLINE];
    }
    queryToBeSaved = { ...queryToBeSaved, ...query };

    if (operation === 'UPDATE_SPECIAL_PAGE') {
      if (objectType === 'tipline-inbox') {
        input.tipline_inbox_filters = JSON.stringify(query);
      } else if (objectType === 'suggested-matches') {
        input.suggested_matches_filters = JSON.stringify(query);
      } else if (objectType === 'trends') {
        input.trends_filters = JSON.stringify(query);
      }
    } else {
      input.filters = JSON.stringify(queryToBeSaved);
    }

    let mutation = updateMutation;

    if (operation === 'CREATE') {
      input.team_id = team.dbid;
      input.title = title;
      mutation = createMutation;
    } else if (operation === 'UPDATE') {
      input.id = savedSearch.id;
      mutation = updateMutation;
    } else if (operation === 'UPDATE_SPECIAL_PAGE') {
      input.id = team.id;
      mutation = updateSpecialPageMutation;
    }

    commitMutation(Store, {
      mutation,
      variables: {
        input,
      },
      onCompleted: (response, error) => {
        if (error) {
          handleError();
        } else {
          handleSuccess(response);
        }
      },
      onError: () => {
        handleError();
      },
    });
  };

  const handleClick = () => {
    // From the "All Items" page, collection page and a folder page, we can just create a new list
    if (objectType === 'all-items' || objectType === 'project' || objectType === 'collection') {
      setShowNewDialog(true);
    // From a list page, we can either create a new one or update the one we're seeing
    } else if (objectType === 'list') {
      setOperation('UPDATE');
      setShowExistingDialog(true);
    } else if (['tipline-inbox', 'suggested-matches'].indexOf(objectType) !== -1) {
      setOperation('UPDATE_SPECIAL_PAGE');
      setShowExistingDialog(true);
    } else if (objectType === 'trends') {
      handleSave();
    }
  };

  return (
    <React.Fragment>

      {/* The "Save list" button */}
      <Button
        id="save-list__button"
        className={classes.saveListButton}
        startIcon={<ListIcon />}
        onClick={handleClick}
      >
        <FormattedMessage
          id="saveList.saveList"
          defaultMessage="Save list"
          description="'Save' here is in infinitive form - it's a button label, to save the current set of filters applied to a search result as a list"
        />
      </Button>

      {/* Create a new list */}
      <ConfirmProceedDialog
        open={showNewDialog}
        title={<FormattedMessage id="saveList.newList" defaultMessage="Save list" />}
        body={
          <Box>
            <TextField
              label={
                <FormattedMessage
                  id="saveList.title"
                  defaultMessage="Enter new list name"
                />
              }
              onChange={(e) => { setTitle(e.target.value); }}
              variant="outlined"
              margin="normal"
              className="new-list__title"
              fullWidth
            />
          </Box>
        }
        proceedDisabled={!title}
        proceedLabel={<FormattedMessage id="saveList.newList" defaultMessage="Save list" />}
        onProceed={handleSave}
        isSaving={saving}
        cancelLabel={<FormattedMessage id="saveList.cancel" defaultMessage="Cancel" />}
        onCancel={handleClose}
      />

      {/* Create a new list or update an existing list */}
      { savedSearch || ['tipline-inbox', 'suggested-matches'].indexOf(objectType) !== -1 ?
        <ConfirmProceedDialog
          open={showExistingDialog}
          title={<FormattedMessage id="saveList.newList" defaultMessage="Save list" />}
          body={
            <FormControl fullWidth>
              <RadioGroup value={operation} onChange={(e) => { setOperation(e.target.value); }}>
                { savedSearch ?
                  <FormControlLabel
                    value="UPDATE"
                    control={<Radio />}
                    label={<FormattedMessage id="saveList.update" defaultMessage='Save changes to the list "{listName}"' values={{ listName: savedSearch.title }} description="'Save' here is an infinitive verb" />}
                  /> :
                  <FormControlLabel
                    value="UPDATE_SPECIAL_PAGE"
                    control={<Radio />}
                    label={<FormattedMessage id="saveList.updateSpecialPage" defaultMessage="Save changes to the list" description="'Save' here is an infinitive verb" />}
                  />
                }
                <FormControlLabel
                  value="CREATE"
                  control={<Radio />}
                  className={classes.saveListCreateLabel}
                  classes={{ label: classes.saveListCreateLabel }}
                  label={
                    <Box display="flex" alignItems="center" width={1}>
                      <span className={classes.saveListCreate}>
                        <FormattedMessage id="saveList.create" defaultMessage="Create new list" description="'Create' here is an infinitive verb" />
                      </span>
                      <TextField
                        label={
                          <FormattedMessage
                            id="saveList.title"
                            defaultMessage="Enter new list name"
                          />
                        }
                        onChange={(e) => { setTitle(e.target.value); }}
                        variant="outlined"
                        margin="normal"
                        className="new-list__title"
                        disabled={operation === 'UPDATE'}
                        autoFocus={operation === 'CREATE'}
                        fullWidth
                      />
                    </Box>
                  }
                />
              </RadioGroup>
            </FormControl>
          }
          proceedDisabled={operation === 'CREATE' && !title}
          proceedLabel={<FormattedMessage id="saveList.newList" defaultMessage="Save list" />}
          onProceed={handleSave}
          isSaving={saving}
          cancelLabel={<FormattedMessage id="saveList.cancel" defaultMessage="Cancel" />}
          onCancel={handleClose}
        /> : null }
    </React.Fragment>
  );
};

SaveList.defaultProps = {
  project: null,
  projectGroup: null,
  savedSearch: null,
};

SaveList.propTypes = {
  team: PropTypes.shape({
    id: PropTypes.string.isRequired,
    dbid: PropTypes.number.isRequired,
    slug: PropTypes.string.isRequired,
    permissions: PropTypes.string.isRequired,
  }).isRequired,
  query: PropTypes.object.isRequired,
  project: PropTypes.shape({
    dbid: PropTypes.number.isRequired,
  }),
  projectGroup: PropTypes.shape({
    dbid: PropTypes.number.isRequired,
  }),
  savedSearch: PropTypes.shape({
    id: PropTypes.string.isRequired,
    title: PropTypes.string.isRequired,
    filters: PropTypes.string.isRequired,
  }),
};

export default withSetFlashMessage(SaveList);
<|endoftext|>


<commit_before>angular.module('myApp.main.conference', [
	'ui.bootstrap',
]).

config(['$routeProvider', 'securityAuthorizationProvider', function ( $routeProvider, securityAuthorizationProvider ){
	$routeProvider.when('/conference/:itemID', {
		title: 'Register',
		templateUrl: 'app/main/conference/conference.tpl.html',
		controller: 'RegisterConferenceCtrl',
		resolve: {
			conference: ['interface', '$route', function (interface, $route) {
				return interface.cart('getOption', $route.current.params);
			}],
			user: securityAuthorizationProvider.requireAuthenticatedUser
		}
	});
}]).

controller('RegisterConferenceCtrl', ['$scope', 'myPage', 'interface', 'conference', '$modal', 'theCart', 'appStrings', '$location', function ($scope, myPage, interface, conference, $modal, theCart, appStrings, $location) {
	$scope.con = conference;
	$scope.message = false;

	// Data, pre-processing
	var title = ($scope.con.item.template == 'conference') ? "Register" : "Options" ;
	myPage.setTitle(title, "for " + $scope.con.item.name);

	$scope.noFields = ($scope.con.fields.length === 0); // ensure item has quesitions
	if ($scope.noFields) return $location.path('/cart'); // no need to do any further processing if there are no options

	function processAttendees() {
		var attID = null;
		angular.forEach($scope.con.fields, function(value, key) { if (value.name == 'Attendees') attID = value.fieldID; });
		$scope.con.options.attID = attID;
		if (attID) $scope.con.options[attID] = $scope.con.options[attID] || []; // set empty attendee array

		if ($scope.con.item.oldData && $scope.con.item.oldData.hasOwnProperty(attID)) { // add old immutable attendees
			angular.forEach($scope.con.item.oldData[attID], function (person) {
				var found = false;
				angular.forEach($scope.con.options[attID], function (checkPerson) { // search for same user (re-edit)
					if (checkPerson.contactID == person.contactID) found = true;
				});
				if (!found) { // if user is not in list, add
					person.immutable = true;
					$scope.con.options[attID].unshift(person);
				}
			});
			$scope.message = appStrings.conference.immutable();
		}
		return attID;
	}
	$scope.attID = processAttendees();

	$scope.orig = angular.copy( $scope.con.options );

	// Attendee list controls (these will be disabled if $scope.attID is undefined)
	$scope.total = 0;
	$scope.computeCost = function(index, person) {
		var cost = 0, s = $scope.con.item.cost.settings;
		if (person.immutable) {
			$scope.total = 0;
			cost = 0;
		} else if ( index === 0 ) {
			cost = parseFloat( s.initial );
			$scope.total = 0;
		} else if ( index >= parseInt( s.after ) ) {
			cost = parseFloat( s.later );
		}
		$scope.total += cost;
		return cost;
	};
	$scope.clr = function() {
		$scope.con.options[ $scope.attID ] = [];
		$scope.total = 0;
		processAttendees();
	};
	$scope.rem = function(index, $event) {
		$event.preventDefault();
		var o = $scope.con.options[ $scope.attID ];
		o.splice(index,1);
		if (!o.length) $scope.total = 0;
	};
	$scope.add = function() { $scope.edit(); };
	$scope.edit = function(contact) {
		var o = $scope.con.options[ $scope.attID ],
		modalInstance = $modal.open({
			templateUrl: 'app/main/conference/modal-contact.tpl.html',
			controller: 'ContactModalCtrl',
			resolve: {
				contact: function() { return angular.copy( contact ); },
				prep: ['interface', function(interface) { return interface.user('prepAtten'); }],
				opt: function() { return angular.copy( o ); }
			}
		});
		modalInstance.result.then(function (modContact) {
			if (!o) o = $scope.con.options[ $scope.attID ] = []; // in case the settings object is empty
			var index = o.indexOf( contact );
			if (index === -1) {
				o.push( modContact );
			} else {
				o[index] = modContact;
			}
		});
	};
	$scope.equal = function(x,y) { return angular.equals(x,y);};
	$scope.reset = function() { $scope.con.options = angular.copy( $scope.orig ); };

	// Overall Controlls
	$scope.save = function() {
		if ($scope.attID && $scope.con.options[ $scope.attID ].length === 0) {
			$scope.message = appStrings.conference.attendee();
			return;
		}
		interface.cart('setOption', $scope.con).then(function() {
			theCart.setDirty();
			$modal.open({
				templateUrl: 'registerConfNextActionTPL.html',
				controller: ['$scope', '$modalInstance', '$location', function ($scope, $modalInstance, $location) {
					$scope.cart = function () {
						$location.path('/cart');
						$modalInstance.dismiss('cancel');
					};
				}]
			});
			$scope.orig = angular.copy( $scope.con.options );
		}, function() {
			$scope.message = appStrings.conference.error();
		});
	};

	// Generic fields helper
	$scope.temp = {};
	$scope.helpers = {
		otherSelect: {
			pre: function(arr) {
				arr = angular.copy( arr );
				arr.push('Other');
				return arr;
			},
			change: function(elem) {
				$scope.helpers.otherSelect.isOther = (elem == 'Other');
			},
			isOther: false
		},
		otherCheckbox: {
			toggle: function(item, fieldID) {
				var arr = [];
				if ($scope.con.options[fieldID]) arr = $scope.con.options[fieldID].split(', ');
				var idx = arr.indexOf(item);
				if (idx > -1) {
					arr.splice(idx, 1);
				} else {
					arr.push(item);
				}
				$scope.con.options[fieldID] = arr.join(', ');
			},
			isSelected: function(item, fieldID) {
				return ($scope.con.options[fieldID] || '').indexOf(item) > -1;
			},
			other: function(fieldID) {
				var arr = [];
				if ($scope.con.options[fieldID]) arr = $scope.con.options[fieldID].split(', ');
				for (var i=0; i<arr.length; i++)
					if (arr[i].substring(0, 7) == 'Other: ')
						arr[i] = 'Other: ' + $scope.temp[fieldID];
				$scope.con.options[fieldID] = arr.join(', ');
			},
			oToggle: function(fieldID) {
				$scope.temp[fieldID] = $scope.temp[fieldID] || '';
				$scope.helpers.otherCheckbox.toggle('Other: ' + $scope.temp[fieldID], fieldID);
			}
		}
	};
}]).

directive('uaImageUpload', [function() {
	return {
		restrict: 'A',
		scope: {
			'uaImageUpload': '='
		},
		templateUrl: 'app/main/conference/image.tpl.html',
		link: function($scope, elem, attrs) {
			$scope.state = ($scope.uaImageUpload) ? 2 : 0;
			$scope.image = $scope.uaImageUpload || false;

			$scope.$watch('uaImageUpload', function (val) {
				if (!val) {
					$scope.state = 0;
					$scope.image = false;
					// TODO: delete image
				}
			});

			var reader = new FileReader();
			reader.onload = function (e) {
				$scope.image = e.target.result;
				$scope.$apply();
			};

			elem.on('change', function() {
				reader.readAsDataURL(elem[0].children[0].children[0].files[0]);
				$scope.setFiles(elem[0].children[0].children[0]);
			});

			$scope.setFiles = function(element) {
				$scope.files = [];
				for (var i = 0; i < element.files.length; i++) $scope.files.push(element.files[i]);
				$scope.progressVisible = false;
				$scope.state = 1;
				$scope.$apply();
			};
			$scope.uploadFile = function() {
				var fd = new FormData();
				for (var i in $scope.files) fd.append("uploadedFile", $scope.files[i]);
					var xhr = new XMLHttpRequest();
				xhr.upload.addEventListener("progress", uploadProgress, false);
				xhr.addEventListener("load", uploadComplete, false);
				xhr.addEventListener("error", uploadFailed, false);
				xhr.addEventListener("abort", uploadCanceled, false);
				xhr.open("POST", "/uploader.php");
				$scope.progressVisible = true;
				xhr.send(fd);
				$scope.progress = 1;
			};
			function uploadProgress(evt) {
				if (evt.lengthComputable) {
					$scope.progress = Math.round(evt.loaded * 100 / evt.total);
				} else {
					$scope.progress = 'unable to compute';
				}
				$scope.$apply();
			}
			function uploadComplete(evt) {
				$scope.image = evt.target.responseText.substring(1);
				$scope.uaImageUpload = $scope.image;
				$scope.progress = 100;
				$scope.files = [];
				$scope.progressVisible = false;
				$scope.state = 2;
				$scope.$apply();
			}
			function uploadFailed(evt) {
				alert("There was an error attempting to upload the file.");
			}
			function uploadCanceled(evt) {
				$scope.progressVisible = false;
				$scope.$apply();
				alert("The upload has been canceled by the user or the browser dropped the connection.");
			}
			$scope.resetImage = function() {
				$scope.state = 0;
				$scope.uaImageUpload = undefined;
				// TODO: delete file on server
			};
		}
	};
}]).

directive('uaMagicFormatter', ['$filter', function ($filter) {
	var formatters = {
		currency: function(val) {
			var decimal = val.split('.')[1];
			decimal = (decimal === undefined) ? "" : ("." + decimal.substr(0,2)); // decimal formatting
			val = parseInt(val.replace(/[^\d\.]/g,'')); // convert to number
			val = $filter('currency')(val, '$'); // format directly
			val = val.substring(0, val.length-3); // remove decimals
			return val + decimal;
		},
		numeric: function(val) {
			val = parseFloat(val.replace(/[^\d\.]/g, ''));
			return isNaN(val) ? '' : val ;
		}
	};
	return {
		restrict: 'A',
		require: '?ngModel',
		link: function(scope, element, attrs, ctrl) {
			ctrl.$parsers.unshift(function(val) {
				if ( formatters.hasOwnProperty( attrs.uaMagicFormatter ) ) val = formatters[ attrs.uaMagicFormatter ]( val );
				return element.val( val ).val();
			});
		}
	};
}]).

controller('ContactModalCtrl', ['$scope', '$modalInstance', 'contact', 'prep', 'interface', '$modal', 'opt', '$filter', 'appStrings', function ($scope, $modalInstance, contact, prep, interface, $modal, opt, $filter, appStrings) {
	var blankAddr = {addressID:null, addr2:null};
	var oldUserAddr = ( contact && contact.addr.addressID != prep.add.addressID ) ? contact.addr : blankAddr ; // null address handler
	$scope.contact = contact || {addr:blankAddr}; // null contact handler

	// filter list on `opt` (remove already added people)
	$scope.firmEmploy = $filter('filter')(prep.emp, function(item) { // filter here not on template
		var found = false; // http://stackoverflow.com/a/14844516
		angular.forEach(opt, function (obj){ if (!found && obj.contactID == item.contactID ) found = true; });
		return !found;
	});

	// view changing variables
	$scope.addNew = (contact !== undefined) || $scope.firmEmploy.length === 0; // !blank passed contact or no employees left
	$scope.canChange = ($scope.contact.contactID === undefined) && $scope.firmEmploy.length !== 0;

	// for managing same changes
	var tmpAddrID = contact ? contact.addr.addressID : -2 ; // wierd case checking
	$scope.sameAddr = ($scope.contact.addr.addressID == prep.add.addressID); // check if same address
	$scope.$watch('sameAddr', function(value) {
		if (value) {
			if (tmpAddrID != prep.add.addressID) oldUserAddr = $scope.contact.addr; // ensure not firm
			$scope.contact.addr = prep.add;
		} else {
			$scope.contact.addr = oldUserAddr;
		}
	});

	$scope.toggle = function () { $scope.addNew = !$scope.addNew; }; // changes view
	$scope.cancel = function () { $modalInstance.dismiss('cancel'); }; // closes the dialog
	$scope.edit = function (user) {
		$scope.contact = user;
		$scope.addNew = true;
		tmpAddrID = user.addr.addressID;
		$scope.sameAddr = (tmpAddrID == prep.add.addressID); // check if same address
		oldUserAddr = $scope.sameAddr ? blankAddr : user.addr ; // clear or assign last
	};
	$scope.choose = function (user, $event) { // chooses a specific user
		$event.preventDefault();
		$modalInstance.close(user);
	};
	$scope.ok = function () {
		if ($scope.contact.addr.addressID === null) {
			$scope.message = appStrings.contact.address();
			return;
		}
		var query = ($scope.contact.contactID === undefined) ? 'add' : 'edit'; // change add or edit based on existence of contactID
		interface.user(query + 'Contact', $scope.contact).then(function(res) {
			$scope.contact.contactID = JSON.parse(res);
			$modalInstance.close( $scope.contact );
		}, function (err) {
			$scope.message = (err == 'dup') ? appStrings.contact.duplicate() : appStrings.contact.error();
		});
	};
	$scope.setAddr = function () { // open modal here with address form
		var modalInstance = $modal.open({ // modal insterts into db and returns full object
			templateUrl: 'common/modal/address/modal-address.tpl.html',
			controller: 'ModalAddressCtrl',
			resolve: { address: function() { return angular.copy( $scope.contact.addr ); } }
		});
		modalInstance.result.then(function(address) { $scope.contact.addr = address; });
	};
}]);<commit_msg>0.3.01: fixing conference error<commit_after>angular.module('myApp.main.conference', [
	'ui.bootstrap',
]).

config(['$routeProvider', 'securityAuthorizationProvider', function ( $routeProvider, securityAuthorizationProvider ){
	$routeProvider.when('/conference/:itemID', {
		title: 'Register',
		templateUrl: 'app/main/conference/conference.tpl.html',
		controller: 'RegisterConferenceCtrl',
		resolve: {
			conference: ['interface', '$route', function (interface, $route) {
				return interface.cart('getOption', $route.current.params);
			}],
			user: securityAuthorizationProvider.requireAuthenticatedUser
		}
	});
}]).

controller('RegisterConferenceCtrl', ['$scope', 'myPage', 'interface', 'conference', '$modal', 'theCart', 'appStrings', '$location', function ($scope, myPage, interface, conference, $modal, theCart, appStrings, $location) {
	$scope.con = conference;
	$scope.message = false;

	// Data, pre-processing
	var title = ($scope.con.item.template == 'conference') ? "Register" : "Options" ;
	myPage.setTitle(title, "for " + $scope.con.item.name);

	$scope.noFields = ($scope.con.fields.length === 0); // ensure item has quesitions
	if ($scope.noFields) return $location.path('/cart'); // no need to do any further processing if there are no options

	function processAttendees() {
		var attID = null;
		angular.forEach($scope.con.fields, function(value, key) { if (value.name == 'Attendees') attID = value.fieldID; });
		$scope.con.options.attID = attID;
		if (attID) $scope.con.options[attID] = $scope.con.options[attID] || []; // set empty attendee array

		if ($scope.con.item.oldData && $scope.con.item.oldData.hasOwnProperty(attID)) { // add old immutable attendees
			angular.forEach($scope.con.item.oldData[attID], function (person) {
				var found = false;
				angular.forEach($scope.con.options[attID], function (checkPerson) { // search for same user (re-edit)
					if (checkPerson.contactID == person.contactID) found = true;
				});
				if (!found) { // if user is not in list, add
					person.immutable = true;
					$scope.con.options[attID].unshift(person);
				}
			});
			$scope.message = appStrings.conference.immutable();
		}
		return attID;
	}
	$scope.attID = processAttendees();

	$scope.orig = angular.copy( $scope.con.options );

	// Attendee list controls (these will be disabled if $scope.attID is undefined)
	$scope.total = 0;
	$scope.computeCost = function(index, person) {
		var cost = 0, s = $scope.con.item.cost.settings;
		if (person.immutable) {
			$scope.total = 0;
			cost = 0;
		} else if ( index === 0 ) {
			cost = parseFloat( s.initial );
			$scope.total = 0;
		} else if ( index >= parseInt( s.after ) ) {
			cost = parseFloat( s.later );
		}
		$scope.total += cost;
		return cost;
	};
	$scope.clr = function() {
		$scope.con.options[ $scope.attID ] = [];
		$scope.total = 0;
		processAttendees();
	};
	$scope.rem = function(index, $event) {
		$event.preventDefault();
		var o = $scope.con.options[ $scope.attID ];
		o.splice(index,1);
		if (!o.length) $scope.total = 0;
	};
	$scope.add = function() { $scope.edit(); };
	$scope.edit = function(contact) {
		var o = $scope.con.options[ $scope.attID ],
		modalInstance = $modal.open({
			templateUrl: 'app/main/conference/modal-contact.tpl.html',
			controller: 'ContactModalCtrl',
			resolve: {
				contact: function() { return angular.copy( contact ); },
				prep: ['interface', function(interface) { return interface.user('prepAtten'); }],
				opt: function() { return angular.copy( o ); }
			}
		});
		modalInstance.result.then(function (modContact) {
			if (!o) o = $scope.con.options[ $scope.attID ] = []; // in case the settings object is empty
			var index = o.indexOf( contact );
			if (index === -1) {
				o.push( modContact );
			} else {
				o[index] = modContact;
			}
		});
	};
	$scope.equal = function(x,y) { return angular.equals(x,y);};
	$scope.reset = function() { $scope.con.options = angular.copy( $scope.orig ); };

	// Overall Controlls
	$scope.save = function() {
		if ($scope.attID && $scope.con.options[ $scope.attID ].length === 0) {
			$scope.message = appStrings.conference.attendee();
			return;
		}
		interface.cart('setOption', $scope.con).then(function() {
			theCart.setDirty();
			$modal.open({
				templateUrl: 'registerConfNextActionTPL.html',
				controller: ['$scope', '$modalInstance', '$location', function ($scope, $modalInstance, $location) {
					$scope.cart = function () {
						$location.path('/cart');
						$modalInstance.dismiss('cancel');
					};
				}]
			});
			$scope.orig = angular.copy( $scope.con.options );
		}, function() {
			$scope.message = appStrings.conference.error();
		});
	};

	// Generic fields helper
	$scope.temp = {};
	$scope.helpers = {
		otherSelect: {
			pre: function(arr) {
				arr = angular.copy( arr );
				arr.push('Other');
				return arr;
			},
			change: function(elem) {
				$scope.helpers.otherSelect.isOther = (elem == 'Other');
			},
			isOther: false
		},
		otherCheckbox: {
			toggle: function(item, fieldID) {
				var arr = [];
				if ($scope.con.options[fieldID]) arr = $scope.con.options[fieldID].split(', ');
				var idx = arr.indexOf(item);
				if (idx > -1) {
					arr.splice(idx, 1);
				} else {
					arr.push(item);
				}
				$scope.con.options[fieldID] = arr.join(', ');
			},
			isSelected: function(item, fieldID) {
				return ($scope.con.options[fieldID] || '').indexOf(item) > -1;
			},
			other: function(fieldID) {
				var arr = [];
				if ($scope.con.options[fieldID]) arr = $scope.con.options[fieldID].split(', ');
				for (var i=0; i<arr.length; i++)
					if (arr[i].substring(0, 7) == 'Other: ')
						arr[i] = 'Other: ' + $scope.temp[fieldID];
				$scope.con.options[fieldID] = arr.join(', ');
			},
			oToggle: function(fieldID) {
				$scope.temp[fieldID] = $scope.temp[fieldID] || '';
				$scope.helpers.otherCheckbox.toggle('Other: ' + $scope.temp[fieldID], fieldID);
			}
		}
	};
}]).

directive('uaImageUpload', [function() {
	return {
		restrict: 'A',
		scope: {
			'uaImageUpload': '='
		},
		templateUrl: 'app/main/conference/image.tpl.html',
		link: function($scope, elem, attrs) {
			$scope.state = ($scope.uaImageUpload) ? 2 : 0;
			$scope.image = $scope.uaImageUpload || false;

			$scope.$watch('uaImageUpload', function (val) {
				if (!val) {
					$scope.state = 0;
					$scope.image = false;
					// TODO: delete image
				}
			});

			var reader = new FileReader();
			reader.onload = function (e) {
				$scope.image = e.target.result;
				$scope.$apply();
			};

			elem.on('change', function() {
				reader.readAsDataURL(elem[0].children[0].children[0].files[0]);
				$scope.setFiles(elem[0].children[0].children[0]);
			});

			$scope.setFiles = function(element) {
				$scope.files = [];
				for (var i = 0; i < element.files.length; i++) $scope.files.push(element.files[i]);
				$scope.progressVisible = false;
				$scope.state = 1;
				$scope.$apply();
			};
			$scope.uploadFile = function() {
				var fd = new FormData();
				for (var i in $scope.files) fd.append("uploadedFile", $scope.files[i]);
					var xhr = new XMLHttpRequest();
				xhr.upload.addEventListener("progress", uploadProgress, false);
				xhr.addEventListener("load", uploadComplete, false);
				xhr.addEventListener("error", uploadFailed, false);
				xhr.addEventListener("abort", uploadCanceled, false);
				xhr.open("POST", "/uploader.php");
				$scope.progressVisible = true;
				xhr.send(fd);
				$scope.progress = 1;
			};
			function uploadProgress(evt) {
				if (evt.lengthComputable) {
					$scope.progress = Math.round(evt.loaded * 100 / evt.total);
				} else {
					$scope.progress = 'unable to compute';
				}
				$scope.$apply();
			}
			function uploadComplete(evt) {
				$scope.image = evt.target.responseText.substring(1);
				$scope.uaImageUpload = $scope.image;
				$scope.progress = 100;
				$scope.files = [];
				$scope.progressVisible = false;
				$scope.state = 2;
				$scope.$apply();
			}
			function uploadFailed(evt) {
				alert("There was an error attempting to upload the file.");
			}
			function uploadCanceled(evt) {
				$scope.progressVisible = false;
				$scope.$apply();
				alert("The upload has been canceled by the user or the browser dropped the connection.");
			}
			$scope.resetImage = function() {
				$scope.state = 0;
				$scope.uaImageUpload = undefined;
				// TODO: delete file on server
			};
		}
	};
}]).

directive('uaMagicFormatter', ['$filter', function ($filter) {
	var formatters = {
		currency: function(val) {
			var decimal = val.split('.')[1];
			decimal = (decimal === undefined) ? "" : ("." + decimal.substr(0,2)); // decimal formatting
			val = parseInt(val.replace(/[^\d\.]/g,'')); // convert to number
			val = $filter('currency')(val, '$'); // format directly
			val = val.substring(0, val.length-3); // remove decimals
			return val + decimal;
		},
		numeric: function(val) {
			val = parseFloat(val.replace(/[^\d\.]/g, ''));
			return isNaN(val) ? '' : val ;
		}
	};
	return {
		restrict: 'A',
		require: '?ngModel',
		link: function(scope, element, attrs, ctrl) {
			ctrl.$parsers.unshift(function(val) {
				if ( formatters.hasOwnProperty( attrs.uaMagicFormatter ) ) val = formatters[ attrs.uaMagicFormatter ]( val );
				return element.val( val ).val();
			});
		}
	};
}]).

controller('ContactModalCtrl', ['$scope', '$modalInstance', 'contact', 'prep', 'interface', '$modal', 'opt', '$filter', 'appStrings', function ($scope, $modalInstance, contact, prep, interface, $modal, opt, $filter, appStrings) {
	var blankAddr = {addressID:null, addr2:null};
	var oldUserAddr = ( contact && contact.addr.addressID != prep.add.addressID ) ? contact.addr : blankAddr ; // null address handler
	$scope.contact = contact || {addr:blankAddr,preName:'',phone:''}; // null contact handler

	// filter list on `opt` (remove already added people)
	$scope.firmEmploy = $filter('filter')(prep.emp, function(item) { // filter here not on template
		var found = false; // http://stackoverflow.com/a/14844516
		angular.forEach(opt, function (obj){ if (!found && obj.contactID == item.contactID ) found = true; });
		return !found;
	});

	// view changing variables
	$scope.addNew = (contact !== undefined) || $scope.firmEmploy.length === 0; // !blank passed contact or no employees left
	$scope.canChange = ($scope.contact.contactID === undefined) && $scope.firmEmploy.length !== 0;

	// for managing same changes
	var tmpAddrID = contact ? contact.addr.addressID : -2 ; // wierd case checking
	$scope.sameAddr = ($scope.contact.addr.addressID == prep.add.addressID); // check if same address
	$scope.$watch('sameAddr', function(value) {
		if (value) {
			if (tmpAddrID != prep.add.addressID) oldUserAddr = $scope.contact.addr; // ensure not firm
			$scope.contact.addr = prep.add;
		} else {
			$scope.contact.addr = oldUserAddr;
		}
	});

	$scope.toggle = function () { $scope.addNew = !$scope.addNew; }; // changes view
	$scope.cancel = function () { $modalInstance.dismiss('cancel'); }; // closes the dialog
	$scope.edit = function (user) {
		$scope.contact = user;
		$scope.addNew = true;
		tmpAddrID = user.addr.addressID;
		$scope.sameAddr = (tmpAddrID == prep.add.addressID); // check if same address
		oldUserAddr = $scope.sameAddr ? blankAddr : user.addr ; // clear or assign last
	};
	$scope.choose = function (user, $event) { // chooses a specific user
		$event.preventDefault();
		$modalInstance.close(user);
	};
	$scope.ok = function () {
		if ($scope.contact.addr.addressID === null) {
			$scope.message = appStrings.contact.address();
			return;
		}
		var query = ($scope.contact.contactID === undefined) ? 'add' : 'edit'; // change add or edit based on existence of contactID
		interface.user(query + 'Contact', $scope.contact).then(function(res) {
			$scope.contact.contactID = JSON.parse(res);
			$modalInstance.close( $scope.contact );
		}, function (err) {
			$scope.message = (err == 'dup') ? appStrings.contact.duplicate() : appStrings.contact.error();
		});
	};
	$scope.setAddr = function () { // open modal here with address form
		var modalInstance = $modal.open({ // modal insterts into db and returns full object
			templateUrl: 'common/modal/address/modal-address.tpl.html',
			controller: 'ModalAddressCtrl',
			resolve: { address: function() { return angular.copy( $scope.contact.addr ); } }
		});
		modalInstance.result.then(function(address) { $scope.contact.addr = address; });
	};
}]);<|endoftext|>


<commit_before>
import Permission from '../classes/Permission'
import { User, db } from '../db'
import NicknameQuery from '../query/NicknameQuery'
import { CustomPresenter} from '../classes/Presenters'
import { NotFoundAPIError, ConflictAPIError, ForbiddenAPIError } from '../classes/APIError'

import NickServ from '../Anope/NickServ'
import HostServ from '../Anope/HostServ'
import API, {
  authenticated,
  GET,
  POST,
  PUT,
  DELETE,
  parameters,
} from '../classes/API'
import { websocket } from '../classes/WebSocket'

export default class Nicknames extends API {
  @GET('/nicknames/info/:nickname')
  @websocket('nicknames', 'info')
  @authenticated
  @parameters('nickname')
  async info (ctx) {
    let info = await NickServ.info(ctx.params.nickname)
    if (!info) {
      throw new NotFoundAPIError({ parameter: 'nickname' })
    }

    return Nicknames.presenter.render(info)
  }

  @POST('/nicknames')
  @websocket('nicknames', 'register')
  @authenticated
  async register (ctx) {
    if (Permission.isAdmin(ctx.state.user)) {
      throw new ForbiddenAPIError({})
    }

    ctx.data.nickname = ctx.data.nickname.toLowerCase()

    let { nicknames } = ctx.state.user.data.attributes
    if (nicknames.includes(ctx.data.nickname)) {
      throw new ConflictAPIError({ pointer: '/data/attributes/nickname' })
    }

    if (nicknames.length > 0) {
      await NickServ.group(ctx.data.nickname, nicknames[0], ctx.data.password)
    } else {
      await NickServ.register(ctx.data.nickname, ctx.data.password, ctx.state.user.data.attributes.email)
      await NickServ.confirm(ctx.data.nickname)
    }

    let updatedNicknames = await NickServ.list(ctx.data.nickname)

    await User.update({ nicknames: db.cast(updatedNicknames, 'citext[]') }, {
      where: { id: ctx.state.user.data.id }
    })

    await HostServ.update(ctx.state.user)
    return true
  }

  @PUT('/nicknames')
  @websocket('nicknames', 'connect')
  @authenticated
  async connect (ctx) {
    ctx.data.nickname = ctx.data.nickname.toLowerCase()
    let { nicknames } = ctx.state.user.data.attributes
    if (nicknames.includes(ctx.data.nickname)) {
      throw new ConflictAPIError({ pointer: '/data/attributes/nickname' })
    }

    await NickServ.identify(ctx.data.nickname, ctx.data.password)
    if (nicknames.length > 0) {
      await NickServ.group(ctx.data.nickname, nicknames[0], ctx.data.password)
    }

    let updatedNicknames = await NickServ.list(ctx.data.nickname)

    await User.update({ nicknames: db.cast(updatedNicknames, 'citext[]') }, {
      where: { id: ctx.state.user.data.id }
    })

    await HostServ.update(ctx.state.user)
    return true
  }

  @GET('/nicknames/:nickname')
  @websocket('nicknames', 'search')
  @authenticated
  @parameters('nickname')
  async search (ctx) {
    let result = await User.scope('public').findAndCountAll(new NicknameQuery(ctx.params, ctx).toSequelize)
    return Nicknames.presenter.render(result)
  }

  @DELETE('/nicknames/:nickname')
  @websocket('nicknames', 'delete')
  @authenticated
  @parameters('nickname')
  async delete (ctx) {
    ctx.params.nickname = ctx.params.nickname.toLowerCase()
    if (ctx.state.user.data.attributes.nicknames.includes(ctx.params.nickname) ||
      Permission.require(['nickname.delete'], ctx.state.user, ctx.state.scope)) {
      await NickServ.drop(ctx.params.nickname)

      let nicknames = await NickServ.list(ctx.state.user.data.attributes.nicknames[0])

      await User.update({
        nicknames: db.cast(nicknames, 'citext[]')
      }, {
        where: {
          id: ctx.state.user.data.id
        }
      })

      return true
    }
    return null
  }

  static get presenter () {
    class NicknamesPresenter extends CustomPresenter {
      id (instance) {
        return instance.nickname
      }
    }
    NicknamesPresenter.prototype.type = 'nicknames'
    return NicknamesPresenter
  }
}<commit_msg>Get rid of horrible citext casting on nickname updates<commit_after>
import Permission from '../classes/Permission'
import { User, db } from '../db'
import NicknameQuery from '../query/NicknameQuery'
import { CustomPresenter} from '../classes/Presenters'
import { NotFoundAPIError, ConflictAPIError, ForbiddenAPIError } from '../classes/APIError'

import NickServ from '../Anope/NickServ'
import HostServ from '../Anope/HostServ'
import API, {
  authenticated,
  GET,
  POST,
  PUT,
  DELETE,
  parameters,
} from '../classes/API'
import { websocket } from '../classes/WebSocket'

export default class Nicknames extends API {
  @GET('/nicknames/info/:nickname')
  @websocket('nicknames', 'info')
  @authenticated
  @parameters('nickname')
  async info (ctx) {
    let info = await NickServ.info(ctx.params.nickname)
    if (!info) {
      throw new NotFoundAPIError({ parameter: 'nickname' })
    }

    return Nicknames.presenter.render(info)
  }

  @POST('/nicknames')
  @websocket('nicknames', 'register')
  @authenticated
  async register (ctx) {
    if (Permission.isAdmin(ctx.state.user)) {
      throw new ForbiddenAPIError({})
    }

    ctx.data.nickname = ctx.data.nickname.toLowerCase()

    let { nicknames } = ctx.state.user.data.attributes
    if (nicknames.includes(ctx.data.nickname)) {
      throw new ConflictAPIError({ pointer: '/data/attributes/nickname' })
    }

    if (nicknames.length > 0) {
      await NickServ.group(ctx.data.nickname, nicknames[0], ctx.data.password)
    } else {
      await NickServ.register(ctx.data.nickname, ctx.data.password, ctx.state.user.data.attributes.email)
      await NickServ.confirm(ctx.data.nickname)
    }

    let updatedNicknames = await NickServ.list(ctx.data.nickname)

    await User.update({ updatedNicknames }, {
      where: { id: ctx.state.user.data.id }
    })

    await HostServ.update(ctx.state.user)
    return true
  }

  @PUT('/nicknames')
  @websocket('nicknames', 'connect')
  @authenticated
  async connect (ctx) {
    ctx.data.nickname = ctx.data.nickname.toLowerCase()
    let { nicknames } = ctx.state.user.data.attributes
    if (nicknames.includes(ctx.data.nickname)) {
      throw new ConflictAPIError({ pointer: '/data/attributes/nickname' })
    }

    await NickServ.identify(ctx.data.nickname, ctx.data.password)
    if (nicknames.length > 0) {
      await NickServ.group(ctx.data.nickname, nicknames[0], ctx.data.password)
    }

    let updatedNicknames = await NickServ.list(ctx.data.nickname)

    await User.update({ nicknames: updatedNicknames }, {
      where: { id: ctx.state.user.data.id }
    })

    await HostServ.update(ctx.state.user)
    return true
  }

  @GET('/nicknames/:nickname')
  @websocket('nicknames', 'search')
  @authenticated
  @parameters('nickname')
  async search (ctx) {
    let result = await User.scope('public').findAndCountAll(new NicknameQuery(ctx.params, ctx).toSequelize)
    return Nicknames.presenter.render(result)
  }

  @DELETE('/nicknames/:nickname')
  @websocket('nicknames', 'delete')
  @authenticated
  @parameters('nickname')
  async delete (ctx) {
    ctx.params.nickname = ctx.params.nickname.toLowerCase()
    if (ctx.state.user.data.attributes.nicknames.includes(ctx.params.nickname) ||
      Permission.require(['nickname.delete'], ctx.state.user, ctx.state.scope)) {
      await NickServ.drop(ctx.params.nickname)

      let nicknames = await NickServ.list(ctx.state.user.data.attributes.nicknames[0])

      await User.update({
        nicknames: db.cast(nicknames, 'citext[]')
      }, {
        where: {
          id: ctx.state.user.data.id
        }
      })

      return true
    }
    return null
  }

  static get presenter () {
    class NicknamesPresenter extends CustomPresenter {
      id (instance) {
        return instance.nickname
      }
    }
    NicknamesPresenter.prototype.type = 'nicknames'
    return NicknamesPresenter
  }
}<|endoftext|>


<commit_before>pc.extend(pc, function () {
    function getKey(layer, blendType, isCommand, materialId) {
        // Key definition:
        // Bit
        // 31      : sign bit (leave)
        // 28 - 30 : layer
        // 26 - 27 : translucency type (opaque: 3, normal, additive, subtractive)
        // 25      : Command bit (1: this key is for a command, 0: it's a mesh instance)
        // 0 - 24  : Material ID (if oqaque) or 0 (if transparent - will be depth)
        return ((layer & 0x7) << 28) |
               ((blendType & 0x3) << 26) |
               ((isCommand ? 1 : 0) << 25) |
               ((materialId & 0x1ffffff) << 0);
    }

    /**
     * @name pc.Mesh
     * @class A graphical primitive. The mesh is defined by a {@link pc.VertexBuffer} and an optional
     * {@link pc.IndexBuffer}. It also contains a primitive definition which controls the type of the
     * primitive and the portion of the vertex or index buffer to use.
     * @property {pc.VertexBuffer} vertexBuffer The vertex buffer holding the vertex data of the mesh.
     * @property {pc.IndexBuffer[]} indexBuffer An array of index buffers. For unindexed meshes, this array can
     * be empty. The first index buffer in the array is used by {@link pc.MeshInstance}s with a renderStyle
     * property set to pc.RENDERSTYLE_SOLID. The second index buffer in the array is used if renderStyle is
     * set to pc.RENDERSTYLE_WIREFRAME.
     * @property {Object[]} primitive Array of primitive objects defining how vertex (and index) data in the
     * mesh should be interpreted by the graphics device. For details on the primitive object, see
     * {@link pc.GraphicsDevice#draw}. The primitive is ordered based on render style like the indexBuffer property.
     * @property {pc.BoundingBox} aabb The axis-aligned bounding box for the object space vertices of this mesh.
     */
    var Mesh = function () {
        this.vertexBuffer = null;
        this.indexBuffer = [ null ];
        this.primitive = [{
            type: 0,
            base: 0,
            count: 0
        }];
        this.skin = null;

        // AABB for object space mesh vertices
        this.aabb = new pc.BoundingBox();

        // Array of object space AABBs of vertices affected by each bone
        this.boneAabb = null;
    };

    var InstancingData = function (numObjects, dynamic, instanceSize) {
        instanceSize = instanceSize || 16;
        this.buffer = new Float32Array(numObjects * instanceSize);
        this.count = numObjects;
        this.usage = dynamic? pc.BUFFER_DYNAMIC : pc.BUFFER_STATIC;
        this._buffer = null;
    };

    InstancingData.prototype = {
        update: function () {
            if (this._buffer) {
                this._buffer.setData(this.buffer);
            }
        }
    };

    /**
     * @name pc.MeshInstance
     * @class An instance of a {@link pc.Mesh}. A single mesh can be referenced by many
     * mesh instances that can have different transforms and materials.
     * @param {pc.GraphNode} node The graph node defining the transform for this instance.
     * @param {pc.Mesh} mesh The graphics mesh being instanced.
     * @param {pc.Material} material The material used to render this instance.
     * @example
     * // Create a mesh instance pointing to a 1x1x1 'cube' mesh
     * var mesh = pc.createBox(graphicsDevice);
     * var material = new pc.PhongMaterial();
     * var node = new pc.GraphNode();
     * var meshInstance = new pc.MeshInstance(node, mesh, material);
     * @property {pc.BoundingBox} aabb The world space axis-aligned bounding box for this
     * mesh instance.
     * @property {Boolean} castShadow Controls whether the mesh instances casts shadows.
     * Defaults to false.
     * @property {Number} layer The layer used by this pc.MeshInstance. Can be:
     *

     *
pc.LAYER_WORLD

     *
pc.LAYER_FX

     *
pc.LAYER_GIZMO

     *
pc.LAYER_HUD

     *

     * Defaults to pc.LAYER_WORLD.
     * @property {pc.Material} material The material used by this pc.MeshInstance.
     * @property {Number} renderStyle The render style of the mesh instance. Can be:
     *

     *
pc.RENDERSTYLE_SOLID

     *
pc.RENDERSTYLE_WIREFRAME

     *
pc.RENDERSTYLE_POINTS

     *

     * Defaults to pc.RENDERSTYLE_SOLID.
     */
    var MeshInstance = function MeshInstance(node, mesh, material) {
        this.node = node;           // The node that defines the transform of the mesh instance
        this.mesh = mesh;           // The mesh that this instance renders
        this.material = material;   // The material with which to render this instance

        this._shader = null;
        this._shaderDefs = 256; // 1 byte toggles, 3 bytes light mask; Default value is no toggles and mask = 1
        this._shaderDefs |= mesh.vertexBuffer.format.hasUv0? pc.SHADERDEF_UV0 : 0;
        this._shaderDefs |= mesh.vertexBuffer.format.hasUv1? pc.SHADERDEF_UV1 : 0;
        this._shaderDefs |= mesh.vertexBuffer.format.hasColor? pc.SHADERDEF_VCOLOR : 0;

        // Render options
        this.layer = pc.LAYER_WORLD;
        this.renderStyle = pc.RENDERSTYLE_SOLID;
        this.castShadow = false;
        this._receiveShadow = true;
        this.drawToDepth = true;
        this.cull = true;
        this.pick = true;

        // 64-bit integer key that defines render order of this mesh instance
        this.key = 0;
        this.updateKey();

        this._skinInstance = null;

        // World space AABB
        this.aabb = new pc.BoundingBox();
        this.normalMatrix = new pc.Mat3();

        this._boneAabb = null;

        this.parameters = {};
    };

    Object.defineProperty(MeshInstance.prototype, 'aabb', {
        get: function () {
            if (this.skinInstance) {
                var numBones = this.mesh.skin.boneNames.length;
                var i;
                // Initialize local bone AABBs if needed
                if (!this.mesh.boneAabb) {
                    this.mesh.boneAabb = [];
                    var elems = this.mesh.vertexBuffer.format.elements;
                    var numVerts = this.mesh.vertexBuffer.numVertices;
                    var vertSize = this.mesh.vertexBuffer.format.size;
                    var data = new DataView(this.mesh.vertexBuffer.storage);
                    var boneVerts;
                    var index;
                    var offsetP, offsetI;
                    var j, k;
                    for(i=0; i<elems.length; i++) {
                        if (elems[i].name===pc.SEMANTIC_POSITION) {
                            offsetP = elems[i].offset;
                        } else if (elems[i].name===pc.SEMANTIC_BLENDINDICES) {
                            offsetI = elems[i].offset;
                        }
                    }
                    for(i=0; i<numBones; i++) {
                        boneVerts = [];
                        for(j=0; j<numVerts; j++) {
                            for(k=0; k<4; k++) {
                                index = data.getUint8(j * vertSize + offsetI + k, true);
                                if (index===i) {
                                    // Vertex j is affected by bone i
                                    boneVerts.push( data.getFloat32(j * vertSize + offsetP, true) );
                                    boneVerts.push( data.getFloat32(j * vertSize + offsetP + 4, true) );
                                    boneVerts.push( data.getFloat32(j * vertSize + offsetP + 8, true) );
                                }
                            }
                        }
                        this.mesh.boneAabb.push(new pc.BoundingBox());
                        this.mesh.boneAabb[i].compute(boneVerts);
                    }
                }
                // Initialize per-instance AABBs if needed
                if (!this._boneAabb) {
                    this._boneAabb = [];
                    for(i=0; i<this.mesh.boneAabb.length; i++) {
                        this._boneAabb[i] = new pc.BoundingBox();
                    }
                }
                // Update per-instance bone AABBs
                for(i=0; i<this.mesh.boneAabb.length; i++) {
                    this._boneAabb[i].setFromTransformedAabb(this.mesh.boneAabb[i], this.skinInstance.matrices[i]);
                }
                // Update full instance AABB
                this._aabb.center.copy(this._boneAabb[0].center);
                this._aabb.halfExtents.copy(this._boneAabb[0].halfExtents);
                for(i=0; i<this.mesh.boneAabb.length; i++) {
                    this._aabb.add(this._boneAabb[i]);
                }
            } else {
                this._aabb.setFromTransformedAabb(this.mesh.aabb, this.node.worldTransform);
            }
            return this._aabb;
        },
        set: function (aabb) {
            this._aabb = aabb;
        }
    });

    Object.defineProperty(MeshInstance.prototype, 'material', {
        get: function () {
            return this._material;
        },
        set: function (material) {
            this._shader = null;
            // Remove the material's reference to this mesh instance
            if (this._material) {
                var meshInstances = this._material.meshInstances;
                var index = meshInstances.indexOf(this);
                if (index !== -1) {
                    meshInstances.splice(index, 1);
                }
            }

            this._material = material;

            // Record that the material is referenced by this mesh instance
            this._material.meshInstances.push(this);

            this.updateKey();
        }
    });

    Object.defineProperty(MeshInstance.prototype, 'layer', {
        get: function () {
            return this._layer;
        },
        set: function (layer) {
            this._layer = layer;
            this.updateKey();
        }
    });

    Object.defineProperty(MeshInstance.prototype, 'receiveShadow', {
        get: function () {
            return this._receiveShadow;
        },
        set: function (val) {
            this._receiveShadow = val;
            this._shaderDefs = val? (this._shaderDefs & ~pc.SHADERDEF_NOSHADOW) : (this._shaderDefs | pc.SHADERDEF_NOSHADOW);
            this._shader = null;
        }
    });

    Object.defineProperty(MeshInstance.prototype, 'skinInstance', {
        get: function () {
            return this._skinInstance;
        },
        set: function (val) {
            this._skinInstance = val;
            this._shaderDefs = val? (this._shaderDefs | pc.SHADERDEF_SKIN) : (this._shaderDefs & ~pc.SHADERDEF_SKIN);
            this._shader = null;
        }
    });

    /**
     * @name pc.MeshInstance#mask
     * @type Number
     * @description Mask controlling which {@link pc.LightComponent}s light this mesh instance.
     * To ignore all dynamic lights, set mask to 0. Defaults to 1.
     */
    Object.defineProperty(MeshInstance.prototype, 'mask', {
        get: function () {
            return this._shaderDefs >> 8;
        },
        set: function (val) {
            var toggles = this._shaderDefs & 0x000000FF;
            this._shaderDefs = toggles | (val << 8);
            this._shader = null;
        }
    });

    pc.extend(MeshInstance.prototype, {
        syncAabb: function () {
            // Deprecated
        },

        updateKey: function () {
            var material = this.material;
            this.key = getKey(this.layer, material.blendType, false, material.id);
        },

        setParameter : pc.Material.prototype.setParameter,
        setParameters : pc.Material.prototype.setParameters,
        deleteParameter : pc.Material.prototype.deleteParameter,
        getParameter : pc.Material.prototype.getParameter,
        getParameters : pc.Material.prototype.getParameters,
        clearParameters : pc.Material.prototype.clearParameters
    });

    var Command = function (layer, blendType, command) {
        this.key = getKey(layer, blendType, true, 0);
        this.command = command;
    };

    return {
        Command: Command,
        Mesh: Mesh,
        MeshInstance: MeshInstance,
        InstancingData: InstancingData,
        _getDrawcallSortKey: getKey
    };
}());
<commit_msg>[DOCS] Mesh API tweaks.<commit_after>pc.extend(pc, function () {
    function getKey(layer, blendType, isCommand, materialId) {
        // Key definition:
        // Bit
        // 31      : sign bit (leave)
        // 28 - 30 : layer
        // 26 - 27 : translucency type (opaque: 3, normal, additive, subtractive)
        // 25      : Command bit (1: this key is for a command, 0: it's a mesh instance)
        // 0 - 24  : Material ID (if oqaque) or 0 (if transparent - will be depth)
        return ((layer & 0x7) << 28) |
               ((blendType & 0x3) << 26) |
               ((isCommand ? 1 : 0) << 25) |
               ((materialId & 0x1ffffff) << 0);
    }

    /**
     * @name pc.Mesh
     * @class A graphical primitive. The mesh is defined by a {@link pc.VertexBuffer} and an optional
     * {@link pc.IndexBuffer}. It also contains a primitive definition which controls the type of the
     * primitive and the portion of the vertex or index buffer to use.
     * @description Create a new mesh.
     * @property {pc.VertexBuffer} vertexBuffer The vertex buffer holding the vertex data of the mesh.
     * @property {pc.IndexBuffer[]} indexBuffer An array of index buffers. For unindexed meshes, this array can
     * be empty. The first index buffer in the array is used by {@link pc.MeshInstance}s with a renderStyle
     * property set to pc.RENDERSTYLE_SOLID. The second index buffer in the array is used if renderStyle is
     * set to pc.RENDERSTYLE_WIREFRAME.
     * @property {Object[]} primitive Array of primitive objects defining how vertex (and index) data in the
     * mesh should be interpreted by the graphics device. For details on the primitive object, see
     * {@link pc.GraphicsDevice#draw}. The primitive is ordered based on render style like the indexBuffer property.
     * @property {pc.BoundingBox} aabb The axis-aligned bounding box for the object space vertices of this mesh.
     */
    var Mesh = function () {
        this.vertexBuffer = null;
        this.indexBuffer = [ null ];
        this.primitive = [{
            type: 0,
            base: 0,
            count: 0
        }];
        this.skin = null;

        // AABB for object space mesh vertices
        this.aabb = new pc.BoundingBox();

        // Array of object space AABBs of vertices affected by each bone
        this.boneAabb = null;
    };

    var InstancingData = function (numObjects, dynamic, instanceSize) {
        instanceSize = instanceSize || 16;
        this.buffer = new Float32Array(numObjects * instanceSize);
        this.count = numObjects;
        this.usage = dynamic? pc.BUFFER_DYNAMIC : pc.BUFFER_STATIC;
        this._buffer = null;
    };

    InstancingData.prototype = {
        update: function () {
            if (this._buffer) {
                this._buffer.setData(this.buffer);
            }
        }
    };

    /**
     * @name pc.MeshInstance
     * @class An instance of a {@link pc.Mesh}. A single mesh can be referenced by many
     * mesh instances that can have different transforms and materials.
     * @description Create a new mesh instance.
     * @param {pc.GraphNode} node The graph node defining the transform for this instance.
     * @param {pc.Mesh} mesh The graphics mesh being instanced.
     * @param {pc.Material} material The material used to render this instance.
     * @example
     * // Create a mesh instance pointing to a 1x1x1 'cube' mesh
     * var mesh = pc.createBox(graphicsDevice);
     * var material = new pc.PhongMaterial();
     * var node = new pc.GraphNode();
     * var meshInstance = new pc.MeshInstance(node, mesh, material);
     * @property {pc.BoundingBox} aabb The world space axis-aligned bounding box for this
     * mesh instance.
     * @property {Boolean} castShadow Controls whether the mesh instances casts shadows.
     * Defaults to false.
     * @property {Number} layer The layer used by this mesh instance. Can be:
     *

     *
pc.LAYER_WORLD

     *
pc.LAYER_FX

     *
pc.LAYER_GIZMO

     *
pc.LAYER_HUD

     *

     * Defaults to pc.LAYER_WORLD.
     * @property {pc.Material} material The material used by this mesh instance.
     * @property {Number} renderStyle The render style of the mesh instance. Can be:
     *

     *
pc.RENDERSTYLE_SOLID

     *
pc.RENDERSTYLE_WIREFRAME

     *
pc.RENDERSTYLE_POINTS

     *

     * Defaults to pc.RENDERSTYLE_SOLID.
     */
    var MeshInstance = function MeshInstance(node, mesh, material) {
        this.node = node;           // The node that defines the transform of the mesh instance
        this.mesh = mesh;           // The mesh that this instance renders
        this.material = material;   // The material with which to render this instance

        this._shader = null;
        this._shaderDefs = 256; // 1 byte toggles, 3 bytes light mask; Default value is no toggles and mask = 1
        this._shaderDefs |= mesh.vertexBuffer.format.hasUv0? pc.SHADERDEF_UV0 : 0;
        this._shaderDefs |= mesh.vertexBuffer.format.hasUv1? pc.SHADERDEF_UV1 : 0;
        this._shaderDefs |= mesh.vertexBuffer.format.hasColor? pc.SHADERDEF_VCOLOR : 0;

        // Render options
        this.layer = pc.LAYER_WORLD;
        this.renderStyle = pc.RENDERSTYLE_SOLID;
        this.castShadow = false;
        this._receiveShadow = true;
        this.drawToDepth = true;
        this.cull = true;
        this.pick = true;

        // 64-bit integer key that defines render order of this mesh instance
        this.key = 0;
        this.updateKey();

        this._skinInstance = null;

        // World space AABB
        this.aabb = new pc.BoundingBox();
        this.normalMatrix = new pc.Mat3();

        this._boneAabb = null;

        this.parameters = {};
    };

    Object.defineProperty(MeshInstance.prototype, 'aabb', {
        get: function () {
            if (this.skinInstance) {
                var numBones = this.mesh.skin.boneNames.length;
                var i;
                // Initialize local bone AABBs if needed
                if (!this.mesh.boneAabb) {
                    this.mesh.boneAabb = [];
                    var elems = this.mesh.vertexBuffer.format.elements;
                    var numVerts = this.mesh.vertexBuffer.numVertices;
                    var vertSize = this.mesh.vertexBuffer.format.size;
                    var data = new DataView(this.mesh.vertexBuffer.storage);
                    var boneVerts;
                    var index;
                    var offsetP, offsetI;
                    var j, k;
                    for(i=0; i<elems.length; i++) {
                        if (elems[i].name===pc.SEMANTIC_POSITION) {
                            offsetP = elems[i].offset;
                        } else if (elems[i].name===pc.SEMANTIC_BLENDINDICES) {
                            offsetI = elems[i].offset;
                        }
                    }
                    for(i=0; i<numBones; i++) {
                        boneVerts = [];
                        for(j=0; j<numVerts; j++) {
                            for(k=0; k<4; k++) {
                                index = data.getUint8(j * vertSize + offsetI + k, true);
                                if (index===i) {
                                    // Vertex j is affected by bone i
                                    boneVerts.push( data.getFloat32(j * vertSize + offsetP, true) );
                                    boneVerts.push( data.getFloat32(j * vertSize + offsetP + 4, true) );
                                    boneVerts.push( data.getFloat32(j * vertSize + offsetP + 8, true) );
                                }
                            }
                        }
                        this.mesh.boneAabb.push(new pc.BoundingBox());
                        this.mesh.boneAabb[i].compute(boneVerts);
                    }
                }
                // Initialize per-instance AABBs if needed
                if (!this._boneAabb) {
                    this._boneAabb = [];
                    for(i=0; i<this.mesh.boneAabb.length; i++) {
                        this._boneAabb[i] = new pc.BoundingBox();
                    }
                }
                // Update per-instance bone AABBs
                for(i=0; i<this.mesh.boneAabb.length; i++) {
                    this._boneAabb[i].setFromTransformedAabb(this.mesh.boneAabb[i], this.skinInstance.matrices[i]);
                }
                // Update full instance AABB
                this._aabb.center.copy(this._boneAabb[0].center);
                this._aabb.halfExtents.copy(this._boneAabb[0].halfExtents);
                for(i=0; i<this.mesh.boneAabb.length; i++) {
                    this._aabb.add(this._boneAabb[i]);
                }
            } else {
                this._aabb.setFromTransformedAabb(this.mesh.aabb, this.node.worldTransform);
            }
            return this._aabb;
        },
        set: function (aabb) {
            this._aabb = aabb;
        }
    });

    Object.defineProperty(MeshInstance.prototype, 'material', {
        get: function () {
            return this._material;
        },
        set: function (material) {
            this._shader = null;
            // Remove the material's reference to this mesh instance
            if (this._material) {
                var meshInstances = this._material.meshInstances;
                var index = meshInstances.indexOf(this);
                if (index !== -1) {
                    meshInstances.splice(index, 1);
                }
            }

            this._material = material;

            // Record that the material is referenced by this mesh instance
            this._material.meshInstances.push(this);

            this.updateKey();
        }
    });

    Object.defineProperty(MeshInstance.prototype, 'layer', {
        get: function () {
            return this._layer;
        },
        set: function (layer) {
            this._layer = layer;
            this.updateKey();
        }
    });

    Object.defineProperty(MeshInstance.prototype, 'receiveShadow', {
        get: function () {
            return this._receiveShadow;
        },
        set: function (val) {
            this._receiveShadow = val;
            this._shaderDefs = val? (this._shaderDefs & ~pc.SHADERDEF_NOSHADOW) : (this._shaderDefs | pc.SHADERDEF_NOSHADOW);
            this._shader = null;
        }
    });

    Object.defineProperty(MeshInstance.prototype, 'skinInstance', {
        get: function () {
            return this._skinInstance;
        },
        set: function (val) {
            this._skinInstance = val;
            this._shaderDefs = val? (this._shaderDefs | pc.SHADERDEF_SKIN) : (this._shaderDefs & ~pc.SHADERDEF_SKIN);
            this._shader = null;
        }
    });

    /**
     * @name pc.MeshInstance#mask
     * @type Number
     * @description Mask controlling which {@link pc.LightComponent}s light this mesh instance.
     * To ignore all dynamic lights, set mask to 0. Defaults to 1.
     */
    Object.defineProperty(MeshInstance.prototype, 'mask', {
        get: function () {
            return this._shaderDefs >> 8;
        },
        set: function (val) {
            var toggles = this._shaderDefs & 0x000000FF;
            this._shaderDefs = toggles | (val << 8);
            this._shader = null;
        }
    });

    pc.extend(MeshInstance.prototype, {
        syncAabb: function () {
            // Deprecated
        },

        updateKey: function () {
            var material = this.material;
            this.key = getKey(this.layer, material.blendType, false, material.id);
        },

        setParameter : pc.Material.prototype.setParameter,
        setParameters : pc.Material.prototype.setParameters,
        deleteParameter : pc.Material.prototype.deleteParameter,
        getParameter : pc.Material.prototype.getParameter,
        getParameters : pc.Material.prototype.getParameters,
        clearParameters : pc.Material.prototype.clearParameters
    });

    var Command = function (layer, blendType, command) {
        this.key = getKey(layer, blendType, true, 0);
        this.command = command;
    };

    return {
        Command: Command,
        Mesh: Mesh,
        MeshInstance: MeshInstance,
        InstancingData: InstancingData,
        _getDrawcallSortKey: getKey
    };
}());
<|endoftext|>