<commit_before>import React from 'react';
import Relay from 'react-relay';
import shallowCompare from 'react-addons-shallow-compare';

import AnchoredPopover from '../shared/Popover/anchored';
import Media from '../shared/Media';
import Spinner from '../shared/Spinner';
import UserAvatar from '../shared/UserAvatar';

import FlashesStore from '../../stores/FlashesStore';

import EmailCreateMutation from '../../mutations/EmailCreate';
import EmailResendVerificationMutation from '../../mutations/EmailResendVerification';
import NoticeDismissMutation from '../../mutations/NoticeDismiss';

class AvatarWithEmailPrompt extends React.Component {
  static propTypes = {
    build: React.PropTypes.shape({
      createdBy: React.PropTypes.shape({
        email: React.PropTypes.string
      })
    }).isRequired,
    viewer: React.PropTypes.shape({
      emails: React.PropTypes.shape({
        edges: React.PropTypes.arrayOf(
          React.PropTypes.shape({
            node: React.PropTypes.shape({
              id: React.PropTypes.string,
              address: React.PropTypes.string,
              verified: React.PropTypes.bool
            })
          })
        )
      }).isRequired,
      notice: React.PropTypes.shape({
        dismissedAt: React.PropTypes.string
      })
    }).isRequired,
    relay: React.PropTypes.object
  };

  state = {
    isAddingEmail: false,
    isDismissing: false,
    isSendingVerification: false,
    hasSentSomething: false,
    hasBeenDismissed: false
  };

  getUserEmail(email) {
    const userEmails = this.props.viewer.emails.edges;

    const userEmail = userEmails.find(
      ({ node: { address: userEmail } }) => (
        userEmail.toLowerCase() === email.toLowerCase()
      )
    );

    if (userEmail) {
      return userEmail.node;
    }
  }

  isCurrentUsersValidatedEmail(email) {
    const userEmail = this.getUserEmail(email);
    return userEmail && userEmail.verified;
  }

  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  componentWillReceiveProps(nextProps) {
    const { createdBy } = nextProps.build;

    if (createdBy && createdBy.email && !this.isCurrentUsersValidatedEmail(createdBy.email)) {
      this.props.relay.setVariables({
        isTryingToPrompt: true,
        emailForPrompt: createdBy.email.toLowerCase()
      });
    }
  }

  handleMutationFailure = (transaction) => {
    this.setState({ isAddingEmail: false, isDismissing: false, isSendingVerification: false });

    FlashesStore.flash(FlashesStore.ERROR, transaction.getError());
  };

  handleDismissClick = () => {
    this.setState({ isDismissing: true });

    const mutation = new NoticeDismissMutation({ viewer: this.props.viewer, notice: this.props.viewer.notice });

    Relay.Store.commitUpdate(mutation, { onSuccess: this.handleDismissedSucess, onFailure: this.handleMutationFailure });
  };

  handleAddEmailClick = () => {
    this.setState({ isAddingEmail: true });

    const mutation = new EmailCreateMutation({ address: this.props.build.createdBy.email, viewer: this.props.viewer });

    Relay.Store.commitUpdate(mutation, { onSuccess: this.handleEmailAddedSuccess, onFailure: this.handleMutationFailure });
  };

  handleResendVerificationClick = () => {
    this.setState({ isSendingVerification: true });

    const mutation = new EmailResendVerificationMutation({ email: this.getUserEmail(this.props.build.createdBy.email) });

    Relay.Store.commitUpdate(mutation, { onSuccess: this.handleVerificationResentSuccess, onFailure: this.handleMutationFailure });
  };

  handleDismissedSucess = () => {
    this.setState({ isDismissing: false });
  };

  handleEmailAddedSuccess = () => {
    this.setState({ isAddingEmail: false, hasSentSomething: true });
  };

  handleVerificationResentSuccess = () => {
    this.setState({ isSendingVerification: false, hasSentSomething: true });
  };

  handleLocalDismissClick = () => {
    this.setState({ hasBeenDismissed: true });
  }

  getMessages(loading) {
    const {
      build: {
        createdBy: {
          email
        }
      },
      relay: {
        variables: {
          isTryingToPrompt
        }
      },
      viewer: {
        notice
      }
    } = this.props;
    const { hasBeenDismissed, hasSentSomething } = this.state;

    // There won't be an email address if this build was created by a
    // registered user or if this build just has no owner (perhaps it was
    // created by Buildkite)
    if (!email) {
      return {};
    }

    // If we haven't decided to send a query for this yet, don't show anything!
    if (!isTryingToPrompt) {
      return {};
    }

    // If the user has seen the notice and has been dismissed
    if (notice && notice.dismissedAt) {
      return {};
    }

    // If the user has dismissed this notice instance
    if (hasBeenDismissed) {
      return {};
    }

    let message;
    let buttons;

    const userEmail = this.getUserEmail(email);

    if (userEmail) {
      if (userEmail.verified) {
        return {};
      }

      message = (

          Almost done! Click the verification link we’ve sent to {email} to finish adding this email to your account.

      );
      buttons = [
        <button
          key="dismiss-verification"
          className="btn btn-outline border-gray flex-auto mx1"
          onClick={this.handleLocalDismissClick}
          disabled={loading}
        >
          Dismiss
        </button>
      ];

      if (!hasSentSomething) {
        message = 'Verify this email to finish adding it to your account and take ownership of this build.'
        buttons.unshift(
          <button
            key="resend-verification"
            className="btn btn-primary flex-auto mx1"
            onClick={this.handleResendVerificationClick}
            disabled={loading}
          >
            Resend Verification Email
          </button>
        );
      }
    } else {
      // Otherwise, we've got an unknown (to Buildkite) email address on our hands!
      message = 'Own this email address? If so, add it to your account to take ownership of this build.';
      buttons = [
        <button
          key="add-email"
          className="btn btn-primary flex-auto mx1"
          onClick={this.handleAddEmailClick}
          disabled={loading}
        >
          Add This Email Address
        </button>,
        <button
          key="dismiss-email"
          className="btn btn-outline border-gray flex-auto mx1"
          onClick={this.handleDismissClick}
          disabled={loading}
        >
          Dismiss
        </button>
      ];
    }

    return { message, buttons, loading };
  }

  isLoading = () => (
    this.state.isAddingEmail || this.state.isDismissing || this.state.isSendingVerification
  );

  render() {
    const { build } = this.props;
    const loading = this.isLoading();

    const avatar = (
      <UserAvatar
        user={build.createdBy}
        style={{ width: 32, height: 32 }}
      />
    );

    const { message, buttons } = this.getMessages(loading);
    let buttonContent;

    if (buttons) {
      buttonContent = (
        <Media className="mx4 mt2 my3" style={{ marginTop: -5 }}>
          <Media.Image className="mr2 center" style={{ width: 32 }}>
            {loading && <Spinner />}
          </Media.Image>
          <Media.Description
            className="flex flex-auto"
            style={{
              marginLeft: -5,
              marginRight: -5
            }}
          >
            {buttons}
          </Media.Description>
        </Media>
      );
    }

    if (message) {
      return (
        <AnchoredPopover
          alwaysShow={true}
          width={400}
        >
          {avatar}
          <Media align="top" className="mx4 my3">
            <Media.Image className="mr2 center">
              <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1">
                <g transform="translate(1, 1)" fill="none" strokeWidth="2" stroke="#7EAF25">
                  <ellipse cx="15" cy="15" rx="15" ry="15" />
                  <rect x="7" y="9" width="16" height="12" rx="2" />
                  <polyline points="7 9 15 15 23 9.00146484" />
                </g>
              </svg>
            </Media.Image>
            <Media.Description>
              {message}
            </Media.Description>
          </Media>
          {buttonContent}
        </AnchoredPopover>
      );
    }

    return avatar;
  }
}

export default Relay.createContainer(AvatarWithEmailPrompt, {
  initialVariables: {
    emailForPrompt: null,
    isTryingToPrompt: false
  },

  fragments: {
    build: () => Relay.QL`
      fragment on Build {
        createdBy {
          ...on UnregisteredUser {
            email
            name
            avatar {
              url
            }
          }
          ...on User {
            name
            avatar {
              url
            }
          }
        }
      }
    `,
    viewer: () => Relay.QL`
      fragment on Viewer {
        ${EmailCreateMutation.getFragment('viewer')}
        emails(first: 50) {
          edges {
            node {
              id
              address
              verified
            }
          }
        }
        notice(namespace: NOTICE_NAMESPACE_EMAIL_SUGGESTION, scope: $emailForPrompt) @include(if: $isTryingToPrompt) {
          ${NoticeDismissMutation.getFragment('notice')}
          dismissedAt
        }
      }
    `
  }
});
<commit_msg>Tiny alignment change<commit_after>import React from 'react';
import Relay from 'react-relay';
import shallowCompare from 'react-addons-shallow-compare';

import AnchoredPopover from '../shared/Popover/anchored';
import Media from '../shared/Media';
import Spinner from '../shared/Spinner';
import UserAvatar from '../shared/UserAvatar';

import FlashesStore from '../../stores/FlashesStore';

import EmailCreateMutation from '../../mutations/EmailCreate';
import EmailResendVerificationMutation from '../../mutations/EmailResendVerification';
import NoticeDismissMutation from '../../mutations/NoticeDismiss';

class AvatarWithEmailPrompt extends React.Component {
  static propTypes = {
    build: React.PropTypes.shape({
      createdBy: React.PropTypes.shape({
        email: React.PropTypes.string
      })
    }).isRequired,
    viewer: React.PropTypes.shape({
      emails: React.PropTypes.shape({
        edges: React.PropTypes.arrayOf(
          React.PropTypes.shape({
            node: React.PropTypes.shape({
              id: React.PropTypes.string,
              address: React.PropTypes.string,
              verified: React.PropTypes.bool
            })
          })
        )
      }).isRequired,
      notice: React.PropTypes.shape({
        dismissedAt: React.PropTypes.string
      })
    }).isRequired,
    relay: React.PropTypes.object
  };

  state = {
    isAddingEmail: false,
    isDismissing: false,
    isSendingVerification: false,
    hasSentSomething: false,
    hasBeenDismissed: false
  };

  getUserEmail(email) {
    const userEmails = this.props.viewer.emails.edges;

    const userEmail = userEmails.find(
      ({ node: { address: userEmail } }) => (
        userEmail.toLowerCase() === email.toLowerCase()
      )
    );

    if (userEmail) {
      return userEmail.node;
    }
  }

  isCurrentUsersValidatedEmail(email) {
    const userEmail = this.getUserEmail(email);
    return userEmail && userEmail.verified;
  }

  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  componentWillReceiveProps(nextProps) {
    const { createdBy } = nextProps.build;

    if (createdBy && createdBy.email && !this.isCurrentUsersValidatedEmail(createdBy.email)) {
      this.props.relay.setVariables({
        isTryingToPrompt: true,
        emailForPrompt: createdBy.email.toLowerCase()
      });
    }
  }

  handleMutationFailure = (transaction) => {
    this.setState({ isAddingEmail: false, isDismissing: false, isSendingVerification: false });

    FlashesStore.flash(FlashesStore.ERROR, transaction.getError());
  };

  handleDismissClick = () => {
    this.setState({ isDismissing: true });

    const mutation = new NoticeDismissMutation({ viewer: this.props.viewer, notice: this.props.viewer.notice });

    Relay.Store.commitUpdate(mutation, { onSuccess: this.handleDismissedSucess, onFailure: this.handleMutationFailure });
  };

  handleAddEmailClick = () => {
    this.setState({ isAddingEmail: true });

    const mutation = new EmailCreateMutation({ address: this.props.build.createdBy.email, viewer: this.props.viewer });

    Relay.Store.commitUpdate(mutation, { onSuccess: this.handleEmailAddedSuccess, onFailure: this.handleMutationFailure });
  };

  handleResendVerificationClick = () => {
    this.setState({ isSendingVerification: true });

    const mutation = new EmailResendVerificationMutation({ email: this.getUserEmail(this.props.build.createdBy.email) });

    Relay.Store.commitUpdate(mutation, { onSuccess: this.handleVerificationResentSuccess, onFailure: this.handleMutationFailure });
  };

  handleDismissedSucess = () => {
    this.setState({ isDismissing: false });
  };

  handleEmailAddedSuccess = () => {
    this.setState({ isAddingEmail: false, hasSentSomething: true });
  };

  handleVerificationResentSuccess = () => {
    this.setState({ isSendingVerification: false, hasSentSomething: true });
  };

  handleLocalDismissClick = () => {
    this.setState({ hasBeenDismissed: true });
  }

  getMessages(loading) {
    const {
      build: {
        createdBy: {
          email
        }
      },
      relay: {
        variables: {
          isTryingToPrompt
        }
      },
      viewer: {
        notice
      }
    } = this.props;
    const { hasBeenDismissed, hasSentSomething } = this.state;

    // There won't be an email address if this build was created by a
    // registered user or if this build just has no owner (perhaps it was
    // created by Buildkite)
    if (!email) {
      return {};
    }

    // If we haven't decided to send a query for this yet, don't show anything!
    if (!isTryingToPrompt) {
      return {};
    }

    // If the user has seen the notice and has been dismissed
    if (notice && notice.dismissedAt) {
      return {};
    }

    // If the user has dismissed this notice instance
    if (hasBeenDismissed) {
      return {};
    }

    let message;
    let buttons;

    const userEmail = this.getUserEmail(email);

    if (userEmail) {
      if (userEmail.verified) {
        return {};
      }

      message = (

          Almost done! Click the verification link we’ve sent to {email} to finish adding this email to your account.

      );
      buttons = [
        <button
          key="dismiss-verification"
          className="btn btn-outline border-gray flex-auto mx1"
          onClick={this.handleLocalDismissClick}
          disabled={loading}
        >
          Dismiss
        </button>
      ];

      if (!hasSentSomething) {
        message = 'Verify this email to finish adding it to your account and take ownership of this build.'
        buttons.unshift(
          <button
            key="resend-verification"
            className="btn btn-primary flex-auto mx1"
            onClick={this.handleResendVerificationClick}
            disabled={loading}
          >
            Resend Verification Email
          </button>
        );
      }
    } else {
      // Otherwise, we've got an unknown (to Buildkite) email address on our hands!
      message = 'Own this email address? If so, add it to your account to take ownership of this build.';
      buttons = [
        <button
          key="add-email"
          className="btn btn-primary flex-auto mx1"
          onClick={this.handleAddEmailClick}
          disabled={loading}
        >
          Add This Email Address
        </button>,
        <button
          key="dismiss-email"
          className="btn btn-outline border-gray flex-auto mx1"
          onClick={this.handleDismissClick}
          disabled={loading}
        >
          Dismiss
        </button>
      ];
    }

    return { message, buttons, loading };
  }

  isLoading = () => (
    this.state.isAddingEmail || this.state.isDismissing || this.state.isSendingVerification
  );

  render() {
    const { build } = this.props;
    const loading = this.isLoading();

    const avatar = (
      <UserAvatar
        user={build.createdBy}
        style={{ width: 32, height: 32 }}
      />
    );

    const { message, buttons } = this.getMessages(loading);
    let buttonContent;

    if (buttons) {
      buttonContent = (
        <Media className="mx4 mt2 my3" style={{ marginTop: -5 }}>
          <Media.Image className="mr2 center" style={{ width: 32 }}>
            {loading && <Spinner />}
          </Media.Image>
          <Media.Description
            className="flex flex-auto"
            style={{
              marginLeft: -5,
              marginRight: -5
            }}
          >
            {buttons}
          </Media.Description>
        </Media>
      );
    }

    if (message) {
      return (
        <AnchoredPopover
          alwaysShow={true}
          width={400}
        >
          {avatar}
          <Media align="top" className="mx4 my3">
            <Media.Image className="mr2 center" style={{ marginTop: 2 }}>
              <svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1">
                <g transform="translate(1, 1)" fill="none" strokeWidth="2" stroke="#7EAF25">
                  <ellipse cx="15" cy="15" rx="15" ry="15" />
                  <rect x="7" y="9" width="16" height="12" rx="2" />
                  <polyline points="7 9 15 15 23 9.00146484" />
                </g>
              </svg>
            </Media.Image>
            <Media.Description>
              {message}
            </Media.Description>
          </Media>
          {buttonContent}
        </AnchoredPopover>
      );
    }

    return avatar;
  }
}

export default Relay.createContainer(AvatarWithEmailPrompt, {
  initialVariables: {
    emailForPrompt: null,
    isTryingToPrompt: false
  },

  fragments: {
    build: () => Relay.QL`
      fragment on Build {
        createdBy {
          ...on UnregisteredUser {
            email
            name
            avatar {
              url
            }
          }
          ...on User {
            name
            avatar {
              url
            }
          }
        }
      }
    `,
    viewer: () => Relay.QL`
      fragment on Viewer {
        ${EmailCreateMutation.getFragment('viewer')}
        emails(first: 50) {
          edges {
            node {
              id
              address
              verified
            }
          }
        }
        notice(namespace: NOTICE_NAMESPACE_EMAIL_SUGGESTION, scope: $emailForPrompt) @include(if: $isTryingToPrompt) {
          ${NoticeDismissMutation.getFragment('notice')}
          dismissedAt
        }
      }
    `
  }
});
<|endoftext|>


<commit_before>import Ember from 'ember';
import DS from 'ember-data';

const {
    inject,
    computed,
    run,
    observer,
    Logger,
    Component
} = Ember;

const MAX_MESSAGE_LENGTH = 300;

export default Component.extend({
    classNames: ['conversation-pane'],

    ajax: inject.service(),
    application: inject.service(),
    auth: inject.service(),
    intl: inject.service(),
    store: inject.service(),

    conversation: computed.alias('store.activeConversation'),

    target: computed.alias('conversation.conversationTarget'),

    MAX_MESSAGE_LENGTH,

    didInsertElement() {
        run.scheduleOnce('afterRender', this, this.focus);

        return this._super(...arguments);
    },

    messageLengthError: computed('text', function () {
        return this.get('text.length') > MAX_MESSAGE_LENGTH;
    }),

    disableCallButton: computed('target.skypePhoneNumbers.isFulfilled', 'hasPurecloudStation.isFulfilled', function () {
        let skypeNumbers = this.get('target.skypePhoneNumbers');
        let purecloudStation = this.get('hasPurecloudStation');

        return skypeNumbers && purecloudStation ?
            !skypeNumbers.get('content.0') || !purecloudStation.get('content') :
            true;
    }),

    hasPurecloudStation: computed('auth.purecloudAccessToken', function () {
        let platformClient = window.require('platformClient');
        const environment = this.get('application.environment') || 'inindca.com';
        platformClient.ApiClient.instance.setEnvironment(environment);
        platformClient.ApiClient.instance.authentications['PureCloud Auth'].accessToken = this.get('auth.purecloudAccessToken');
        let apiInstance = new platformClient.UsersApi();

        let promise = apiInstance.getUsersMe().then( (user) => {
           return apiInstance.getUserStation(user.id).then( (res) => {
                return !!res.associatedStation;
            });
        });

        return DS.PromiseObject.create({ promise });
    }),

    conversationChanged: observer('conversation', function () {
        run.scheduleOnce('afterRender', this, this.focus);
    }),

    focus() {
        if (this.element) {
            let textarea = this.element.querySelector('textarea');
            if (textarea) {
                textarea.focus();
            }
        }
    },

    actions: {
        keyup({ target }) {
            this.set('text', target.value);
        },

        keydown(event) {
            const {key, keyCode, shiftKey, target} = event;

            if ((key === 'Escape' || keyCode === 27) && target.value === '') {
                this.get('conversation').clearUnreadState();
            }

            if ((key === 'Enter' || keyCode === 13) && !shiftKey && keyCode !== 229) {
                event.preventDefault();

                if (target.value.length > MAX_MESSAGE_LENGTH) {
                    return;
                }

                let messageText = target.value;
                this.get('conversation').sendMessage(messageText);

                target.value = '';
            }
        },

        makeCall() {
            let platformClient = window.require('platformClient');
            this.get('target.skypePhoneNumbers').then(numbers => {
                if (!numbers || !numbers.length) {
                    // error?
                    return;
                }

                const workSkypeNumber = numbers.find(number => number && number.type() === 'Work');
                let number = numbers[0];
                if (workSkypeNumber) {
                    number = workSkypeNumber;
                }
                if (number && number.displayString) {
                    let displayString = number.displayString();
                    if (displayString.indexOf('+') === -1) {
                        displayString = `+${displayString}`;
                    }
                    displayString = displayString.replace(/[\s-]+/g, '')
                    number = `tel:${displayString}`;

                    const environment = this.get('application.environment') || 'inindca.com';
                    platformClient.ApiClient.instance.setEnvironment(environment);
                    platformClient.ApiClient.instance.authentications['PureCloud Auth'].accessToken = this.get('auth.purecloudAccessToken');
                    let apiInstance = new platformClient.ConversationsApi();
                    let body = {
                        phoneNumber: number,
                    };

                    apiInstance.postConversationsCalls(body).then((data) => {
                        Logger.log('Call Made', data);
                    }).catch((err) => {
                        let title = this.get('intl').t('errors.title');
                        let text = this.get('intl').t('errors.calls.general');
                        let options = {
                            type: 'error',
                            timeout: 30,
                            showCloseButton: true
                        }

                        Logger.error('CALL ERROR', err);
                        this.get('application.clientApp').alerting.showToastPopup(title, text, options);
                    });
                } else {
                    // no phone number, error
                }
            });
        }
    }
})
<commit_msg>consts are great<commit_after>import Ember from 'ember';
import DS from 'ember-data';

const {
    inject,
    computed,
    run,
    observer,
    Logger,
    Component
} = Ember;

const MAX_MESSAGE_LENGTH = 300;

const ENTER_KEY = 13;
const ESCAPE_KEY = 27;
const SPECIAL_ENTER = 229;

export default Component.extend({
    classNames: ['conversation-pane'],

    ajax: inject.service(),
    application: inject.service(),
    auth: inject.service(),
    intl: inject.service(),
    store: inject.service(),

    conversation: computed.alias('store.activeConversation'),

    target: computed.alias('conversation.conversationTarget'),

    MAX_MESSAGE_LENGTH,

    didInsertElement() {
        run.scheduleOnce('afterRender', this, this.focus);

        return this._super(...arguments);
    },

    messageLengthError: computed('text', function () {
        return this.get('text.length') > MAX_MESSAGE_LENGTH;
    }),

    disableCallButton: computed('target.skypePhoneNumbers.isFulfilled', 'hasPurecloudStation.isFulfilled', function () {
        let skypeNumbers = this.get('target.skypePhoneNumbers');
        let purecloudStation = this.get('hasPurecloudStation');

        return skypeNumbers && purecloudStation ?
            !skypeNumbers.get('content.0') || !purecloudStation.get('content') :
            true;
    }),

    hasPurecloudStation: computed('auth.purecloudAccessToken', function () {
        let platformClient = window.require('platformClient');
        const environment = this.get('application.environment') || 'inindca.com';
        platformClient.ApiClient.instance.setEnvironment(environment);
        platformClient.ApiClient.instance.authentications['PureCloud Auth'].accessToken = this.get('auth.purecloudAccessToken');
        let apiInstance = new platformClient.UsersApi();

        let promise = apiInstance.getUsersMe().then( (user) => {
           return apiInstance.getUserStation(user.id).then( (res) => {
                return !!res.associatedStation;
            });
        });

        return DS.PromiseObject.create({ promise });
    }),

    conversationChanged: observer('conversation', function () {
        run.scheduleOnce('afterRender', this, this.focus);
    }),

    focus() {
        if (this.element) {
            let textarea = this.element.querySelector('textarea');
            if (textarea) {
                textarea.focus();
            }
        }
    },

    actions: {
        keyup({ target }) {
            this.set('text', target.value);
        },

        keydown(event) {
            const {key, keyCode, shiftKey, target} = event;

            if ((key === 'Escape' || keyCode === ESCAPE_KEY) && target.value === '') {
                this.get('conversation').clearUnreadState();
            }

            if ((key === 'Enter' || keyCode === ENTER_KEY) && !shiftKey && keyCode !== SPECIAL_ENTER) {
                event.preventDefault();

                if (target.value.length > MAX_MESSAGE_LENGTH) {
                    return;
                }

                let messageText = target.value;
                this.get('conversation').sendMessage(messageText);

                target.value = '';
            }
        },

        makeCall() {
            let platformClient = window.require('platformClient');
            this.get('target.skypePhoneNumbers').then(numbers => {
                if (!numbers || !numbers.length) {
                    // error?
                    return;
                }

                const workSkypeNumber = numbers.find(number => number && number.type() === 'Work');
                let number = numbers[0];
                if (workSkypeNumber) {
                    number = workSkypeNumber;
                }
                if (number && number.displayString) {
                    let displayString = number.displayString();
                    if (displayString.indexOf('+') === -1) {
                        displayString = `+${displayString}`;
                    }
                    displayString = displayString.replace(/[\s-]+/g, '')
                    number = `tel:${displayString}`;

                    const environment = this.get('application.environment') || 'inindca.com';
                    platformClient.ApiClient.instance.setEnvironment(environment);
                    platformClient.ApiClient.instance.authentications['PureCloud Auth'].accessToken = this.get('auth.purecloudAccessToken');
                    let apiInstance = new platformClient.ConversationsApi();
                    let body = {
                        phoneNumber: number,
                    };

                    apiInstance.postConversationsCalls(body).then((data) => {
                        Logger.log('Call Made', data);
                    }).catch((err) => {
                        let title = this.get('intl').t('errors.title');
                        let text = this.get('intl').t('errors.calls.general');
                        let options = {
                            type: 'error',
                            timeout: 30,
                            showCloseButton: true
                        }

                        Logger.error('CALL ERROR', err);
                        this.get('application.clientApp').alerting.showToastPopup(title, text, options);
                    });
                } else {
                    // no phone number, error
                }
            });
        }
    }
})
<|endoftext|>


<commit_before>/**
 * TagSpaces - universal file and folder organizer
 * Copyright (C) 2017-present TagSpaces UG (haftungsbeschraenkt)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License (version 3) as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * @flow
 */

import React from 'react';
import Button from '@material-ui/core/Button';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import FileIcon from '@material-ui/icons/InsertDriveFileOutlined';
import Typography from '@material-ui/core/Typography';
import GenericDialog, { onEnterKeyHandler } from './GenericDialog';
import { type Tag } from '../../reducers/taglibrary';
import TagsSelect from '../TagsSelect';
import i18n from '../../services/i18n';
import { extractFileName } from '../../utils/paths';

type Props = {
  open: boolean,
  selectedEntries?: Array<Object>,
  onClose: () => void,
  addTags: (paths: Array<string>, tags: Array<Tag>) => void,
  removeTags: (paths: Array<string>, tags: Array<Tag>) => void,
  removeAllTags: (paths: Array<string>) => void
};

type State = {
  disableConfirmButton?: boolean,
  open?: boolean,
  newlyAddedTags?: Array<Tag>,
  isConfirmDialogOpened?: boolean
};

class AddRemoveTagsDialog extends React.Component<Props, State> {
  state = {
    disableConfirmButton: true,
    newlyAddedTags: [],
    open: false,
    isConfirmDialogOpened: false
  };

  handleChange = (name, value) => {
    this.setState({
      newlyAddedTags: value
    });
  };

  /* onAddTag = (tag) => {
    const { newlyAddedTags } = this.state;
    newlyAddedTags.push(tag);
    this.setState({ newlyAddedTags });
  };

  onRemoveTag = (tag) => {
    const { newlyAddedTags } = this.state;
    const modifiedTags = newlyAddedTags.filter(addedTag => addedTag.title !== tag.title);
    this.setState({ newlyAddedTags: modifiedTags });
  }; */

  onClose = () => {
    this.setState({ newlyAddedTags: [] });
    this.props.onClose();
  };

  addTags = () => {
    if (this.props.selectedEntries && this.props.selectedEntries.length > 0) {
      const paths = [];
      this.props.selectedEntries.map((entry) => {
        paths.push(entry.path);
        return true;
      });
      this.props.addTags(paths, this.state.newlyAddedTags);
    }
    this.onClose();
  }

  render() {
    const {
      open,
      selectedEntries = [],
      removeTags,
      removeAllTags,
      onClose
    } = this.props;
    const { newlyAddedTags = [] } = this.state;

    return (
      <GenericDialog
        open={open}
        onClose={onClose}
        onEnterKey={(event) => onEnterKeyHandler(event, this.addTags)}
        renderTitle={() => (
          <DialogTitle>{i18n.t('core:tagOperationTitle')}</DialogTitle>
        )}
        renderContent={() => (
          <DialogContent style={{ minHeight: 330 }}>
            <TagsSelect tags={newlyAddedTags} handleChange={this.handleChange} />
            <List dense style={{ width: 550 }}>
              {selectedEntries.length > 0 && selectedEntries.map((entry) => (
                <ListItem title={entry.path}>
                  <ListItemIcon>
                    <FileIcon />
                  </ListItemIcon>
                  <Typography variant="inherit" noWrap>{extractFileName(entry.path || '')}</Typography>
                </ListItem>
              ))}
            </List>
          </DialogContent>
        )}
        renderActions={() => (
          <DialogActions>
            <Button
              data-tid="cancel"
              onClick={this.onClose}
              color="primary"
            >
              {i18n.t('core:cancel')}
            </Button>
            <Button
              data-tid="cleanTagsMultipleEntries"
              disabled={selectedEntries.length < 1}
              color="primary"
              onClick={() => {
                if (selectedEntries && selectedEntries.length > 0) {
                  const paths = [];
                  selectedEntries.map((entry) => {
                    paths.push(entry.path);
                    return true;
                  });
                  removeAllTags(paths);
                }
                this.onClose();
              }}
            >
              {i18n.t('core:tagOperationCleanTags')}
            </Button>
            <Button
              data-tid="removeTagsMultipleEntries"
              disabled={newlyAddedTags.length === 0 || selectedEntries.length < 1}
              color="primary"
              onClick={() => {
                if (selectedEntries && selectedEntries.length > 0) {
                  const paths = [];
                  selectedEntries.map((entry) => {
                    paths.push(entry.path);
                    return true;
                  });
                  removeTags(paths, newlyAddedTags);
                }
                this.onClose();
              }}
            >
              {i18n.t('core:tagOperationRemoveTag')}
            </Button>
            <Button
              data-tid="addTagsMultipleEntries"
              disabled={newlyAddedTags.length < 1 || selectedEntries.length < 1}
              color="primary"
              onClick={this.addTags}
            >
              {i18n.t('core:tagOperationAddTag')}
            </Button>
          </DialogActions>
        )}
      />
    );
  }
}

export default AddRemoveTagsDialog;
<commit_msg>Fxing #1009, tagging issue<commit_after>/**
 * TagSpaces - universal file and folder organizer
 * Copyright (C) 2017-present TagSpaces UG (haftungsbeschraenkt)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License (version 3) as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * @flow
 */

import React from 'react';
import Button from '@material-ui/core/Button';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import FileIcon from '@material-ui/icons/InsertDriveFileOutlined';
import Typography from '@material-ui/core/Typography';
import GenericDialog, { onEnterKeyHandler } from './GenericDialog';
import { type Tag } from '../../reducers/taglibrary';
import TagsSelect from '../TagsSelect';
import i18n from '../../services/i18n';
import { extractFileName } from '../../utils/paths';

type Props = {
  open: boolean,
  selectedEntries: Array<Object>,
  onClose: () => void,
  addTags: (paths: Array<string>, tags: Array<Tag>) => void,
  removeTags: (paths: Array<string>, tags: Array<Tag>) => void,
  removeAllTags: (paths: Array<string>) => void
};

type State = {
  disableConfirmButton: boolean,
  open: boolean,
  newlyAddedTags: Array<Tag>,
  isConfirmDialogOpened: boolean
};

class AddRemoveTagsDialog extends React.Component<Props, State> {
  state = {
    disableConfirmButton: true,
    newlyAddedTags: [],
    open: false,
    isConfirmDialogOpened: false
  };

  handleChange = (name, value) => {
    this.setState({
      newlyAddedTags: value
    });
  };

  /* onAddTag = (tag) => {
    const { newlyAddedTags } = this.state;
    newlyAddedTags.push(tag);
    this.setState({ newlyAddedTags });
  };

  onRemoveTag = (tag) => {
    const { newlyAddedTags } = this.state;
    const modifiedTags = newlyAddedTags.filter(addedTag => addedTag.title !== tag.title);
    this.setState({ newlyAddedTags: modifiedTags });
  }; */

  onClose = () => {
    this.setState({ newlyAddedTags: [] });
    this.props.onClose();
  };

  addTags = () => {
    if (this.props.selectedEntries && this.props.selectedEntries.length > 0) {
      const paths = [];
      this.props.selectedEntries.map((entry) => {
        paths.push(entry.path);
        return true;
      });
      this.props.addTags(paths, this.state.newlyAddedTags);
    }
    this.onClose();
  }

  render() {
    const {
      open,
      selectedEntries = [],
      removeTags,
      removeAllTags,
      onClose
    } = this.props;
    const { newlyAddedTags = [] } = this.state;

    return (
      <GenericDialog
        open={open}
        onClose={onClose}
        onEnterKey={(event) => onEnterKeyHandler(event, this.addTags)}
        renderTitle={() => (
          <DialogTitle>{i18n.t('core:tagOperationTitle')}</DialogTitle>
        )}
        renderContent={() => (
          <DialogContent style={{ minHeight: 330 }}>
            <TagsSelect tags={newlyAddedTags} handleChange={this.handleChange} />
            <List dense style={{ width: 550 }}>
              {selectedEntries.length > 0 && selectedEntries.map((entry) => (
                <ListItem title={entry.path}>
                  <ListItemIcon>
                    <FileIcon />
                  </ListItemIcon>
                  <Typography variant="inherit" noWrap>{extractFileName(entry.path || '')}</Typography>
                </ListItem>
              ))}
            </List>
          </DialogContent>
        )}
        renderActions={() => (
          <DialogActions>
            <Button
              data-tid="cancel"
              onClick={this.onClose}
              color="primary"
            >
              {i18n.t('core:cancel')}
            </Button>
            <Button
              data-tid="cleanTagsMultipleEntries"
              disabled={selectedEntries.length < 1}
              color="primary"
              onClick={() => {
                if (selectedEntries && selectedEntries.length > 0) {
                  const paths = [];
                  selectedEntries.map((entry) => {
                    paths.push(entry.path);
                    return true;
                  });
                  removeAllTags(paths);
                }
                this.onClose();
              }}
            >
              {i18n.t('core:tagOperationCleanTags')}
            </Button>
            <Button
              data-tid="removeTagsMultipleEntries"
              disabled={!newlyAddedTags || newlyAddedTags.length < 1 || selectedEntries.length < 1}
              color="primary"
              onClick={() => {
                if (selectedEntries && selectedEntries.length > 0) {
                  const paths = [];
                  selectedEntries.map((entry) => {
                    paths.push(entry.path);
                    return true;
                  });
                  removeTags(paths, newlyAddedTags);
                }
                this.onClose();
              }}
            >
              {i18n.t('core:tagOperationRemoveTag')}
            </Button>
            <Button
              data-tid="addTagsMultipleEntries"
              disabled={!newlyAddedTags || newlyAddedTags.length < 1 || selectedEntries.length < 1}
              color="primary"
              onClick={this.addTags}
            >
              {i18n.t('core:tagOperationAddTag')}
            </Button>
          </DialogActions>
        )}
      />
    );
  }
}

export default AddRemoveTagsDialog;
<|endoftext|>


<commit_before>import Ember from 'ember';

const { computed } = Ember;

const INTERCOM_APP_ID = 'htxi7bqv';

export default Ember.Component.extend({
  attributeBindings: ['href', 'id'],
  href: `mailto:${INTERCOM_APP_ID}@incoming.intercom.io`,
  id: 'custom-intercom-link',
  tagName: 'a'
});
<commit_msg>Pass linting<commit_after>import Ember from 'ember';

const INTERCOM_APP_ID = 'htxi7bqv';

export default Ember.Component.extend({
  attributeBindings: ['href', 'id'],
  href: `mailto:${INTERCOM_APP_ID}@incoming.intercom.io`,
  id: 'custom-intercom-link',
  tagName: 'a'
});
<|endoftext|>


<commit_before>import { windowBounds } from '../../utilities/'

export class Gridlines extends HTMLElement {

  constructor() {
    super()
    this.$shadow = this.attachShadow({mode: 'closed'})
  }

  connectedCallback() {}
  disconnectedCallback() {}

  set position(boundingRect) {
    this.$shadow.innerHTML  = this.render(boundingRect)
  }

  set update({ width, height, top, left }) {
    const { winHeight, winWidth } = windowBounds()

    this.$shadow.host.style.display = 'block'
    const svg = this.$shadow.children[1]

    svg.setAttribute('viewBox', `0 0 ${winWidth} ${winHeight}`)
    svg.style.top = window.scrollY + 'px'
    svg.children[0].setAttribute('width', width + 'px')
    svg.children[0].setAttribute('height', height + 'px')
    svg.children[0].setAttribute('x', left)
    svg.children[0].setAttribute('y', top)
    svg.children[1].setAttribute('x1', left)
    svg.children[1].setAttribute('x2', left)
    svg.children[2].setAttribute('x1', left + width)
    svg.children[2].setAttribute('x2', left + width)
    svg.children[3].setAttribute('y1', top)
    svg.children[3].setAttribute('y2', top)
    svg.children[3].setAttribute('x2', winWidth)
    svg.children[4].setAttribute('y1', top + height)
    svg.children[4].setAttribute('y2', top + height)
    svg.children[4].setAttribute('x2', winWidth)
  }

  render({ x, y, width, height, top, left }) {
    const { winHeight, winWidth } = windowBounds()

    return `
      ${this.styles()}
      <svg
        width="100%" height="100%"
        viewBox="0 0 ${winWidth} ${winHeight}"
        version="1.1" xmlns="http://www.w3.org/2000/svg"
      >
        <rect
          fill="none"
          width="${width}" height="${height}"
          x="${x}" y="${y}"
          style="display:none;"
        ></rect>
        <line x1="${x}" y1="0" x2="${x}" y2="${winHeight}"></line>
        <line x1="${x + width}" y1="0" x2="${x + width}" y2="${winHeight}"></line>
        <line x1="0" y1="${y}" x2="${winWidth}" y2="${y}"></line>
        <line x1="0" y1="${y + height}" x2="${winWidth}" y2="${y + height}"></line>
      </svg>
    `
  }

  styles() {
    return `
      <style>
        :host {
          --color: rebeccapurple;
        }

        :host > svg {
          position:absolute;
          top:${window.scrollY}px;
          left:0;
          overflow:visible;
          pointer-events:none;
          z-index:2147483642;
        }

        :host rect, :host line {
          stroke: var(--color);
        }

        :host line {
          stroke-dasharray: 2;
          stroke-dasharray-offset: 3;
        }
      </style>
    `
  }
}

customElements.define('visbug-gridlines', Gridlines)
<commit_msg>fixes update method for gridlines positions<commit_after>import { windowBounds } from '../../utilities/'

export class Gridlines extends HTMLElement {

  constructor() {
    super()
    this.$shadow = this.attachShadow({mode: 'closed'})
  }

  connectedCallback() {}
  disconnectedCallback() {}

  set position(boundingRect) {
    this.$shadow.innerHTML  = this.render(boundingRect)
  }

  set update({ width, height, top, left }) {
    const { winWidth, winHeight } = windowBounds()

    this.$shadow.host.style.display = 'block'
    const svg = this.$shadow.children[1]

    svg.setAttribute('viewBox', `0 0 ${winWidth} ${winHeight}`)
    svg.children[0].setAttribute('width', width + 'px')
    svg.children[0].setAttribute('height', height + 'px')
    svg.children[0].setAttribute('x', left)
    svg.children[0].setAttribute('y', top)
    svg.children[1].setAttribute('x1', left)
    svg.children[1].setAttribute('x2', left)
    svg.children[2].setAttribute('x1', left + width)
    svg.children[2].setAttribute('x2', left + width)
    svg.children[3].setAttribute('y1', top + window.scrollY)
    svg.children[3].setAttribute('y2', top + window.scrollY)
    svg.children[3].setAttribute('x2', winWidth)
    svg.children[4].setAttribute('y1', top + window.scrollY + height)
    svg.children[4].setAttribute('y2', top + window.scrollY + height)
    svg.children[4].setAttribute('x2', winWidth)
  }

  render({ x, y, width, height, top, left }) {
    const { winWidth, winHeight } = windowBounds()
    const { offsetHeight } = document.body
    const calced_y = y + window.scrollY

    return `
      ${this.styles()}
      <svg
        width="100%" height="100%"
        viewBox="0 0 ${winWidth} ${winHeight}"
        version="1.1" xmlns="http://www.w3.org/2000/svg"
      >
        <rect
          fill="none"
          width="${width}" height="${height}"
          x="${x}" y="${y}"
          style="display:none;"
        ></rect>
        <line x1="${x}" y1="0" x2="${x}" y2="${offsetHeight}"></line>
        <line x1="${x + width}" y1="0" x2="${x + width}" y2="${offsetHeight}"></line>
        <line x1="0" y1="${calced_y}" x2="${winWidth}" y2="${calced_y}"></line>
        <line x1="0" y1="${calced_y + height}" x2="${winWidth}" y2="${calced_y + height}"></line>
      </svg>
    `
  }

  styles() {
    return `
      <style>
        :host {
          --color: rebeccapurple;
        }

        :host > svg {
          position:absolute;
          top:0;
          left:0;
          overflow:visible;
          pointer-events:none;
          z-index:2147483642;
        }

        :host rect, :host line {
          stroke: var(--color);
        }

        :host line {
          stroke-dasharray: 2;
          stroke-dasharray-offset: 3;
        }
      </style>
    `
  }
}

customElements.define('visbug-gridlines', Gridlines)
<|endoftext|>


<commit_before>import BorderHex from "./border-hex"
import Graph from "./graph"
import NumberDots from "./number-dots"
import ResourceHex from "./resource-hex"

// helpers not associated with class data

const toResource = x => x.resource
const hasPort = x => x.port !== null
const toDots = x => x.number === null ? 0 : NumberDots[x.number]
function sumObject(x) {
    let ret = 0
    for (const p in x) {
        ret += x[p]
    }
    return ret
}
function weightedRandom(x, sum) {
    const cutoffs = {}
    let prevWeight = 0
    for (const p in x) {
        cutoffs[p] = x[p] / sum + prevWeight
        prevWeight = cutoffs[p]
    }
    const rand = Math.random()
    for (const p in cutoffs) {
        if (rand <= cutoffs[p]) {
            return p
        }
    }
    throw new Error("no")
}

// the class

class Board {

    // setup

    constructor({
        resourceAmts,
        numberAmts,
        portAmts,
        numNonPort,
        edges,
        offsets,
        maxDotsPerIntersection,
        maxDotsPerResource
    }) {
        this.resourceAmts = resourceAmts
        this.numberAmts = numberAmts
        this.portAmts = portAmts
        this.offsets = offsets
        this.maxDotsPerIntersection = maxDotsPerIntersection
        this.maxDotsPerResource = maxDotsPerResource

        this.numResourceHex = sumObject(resourceAmts)
        this.numBorderHex = sumObject(portAmts) + numNonPort
        this.size = this.numResourceHex + this.numBorderHex
        this.array = []
        this.graph = new Graph()

        for (let i = 0; i < this.numResourceHex; i++) {
            const t = new ResourceHex(null, null)
            this.array.push(t)
            this.graph.add(t)
        }
        for (let i = this.numResourceHex; i < this.size; i++) {
            const t = new BorderHex(null)
            this.array.push(t)
            this.graph.add(t)
        }
        for (let i = 0; i < this.size; i++) {
            this.graph
                .find(this.array[i])
                .edges
                .push(...edges[i].map(j => this.array[j]))
        }
    }

    // draw

    draw(x, y, r) {
        const xOff = r / 7
        const yOff = xOff * 1.732050808 // 3 / sqrt(3)
        const hexR = yOff / 1.5 - 1
        for (let i = 0; i < this.size; i++) {
            const off = this.offsets[i]
            this.array[i].draw(x + off[0] * xOff, y + off[1] * yOff, hexR)
        }
    }

    // setting resources

    nearby(x) {
        const p = Object.getPrototypeOf(x)
        return this.graph
            .find(x)
            .edges
            .filter(y => Object.getPrototypeOf(y) === p)
    }

    setResource(x, remaining) {
        const nearbyRs = this.nearby(x).map(toResource)
        const okRs = {}
        let sum = 0
        for (const r in remaining) {
            if (nearbyRs.indexOf(r) === -1) {
                sum += okRs[r] = remaining[r]
            }
        }
        if (sum === 0) {
            return false
        }
        const r = weightedRandom(okRs, sum)
        x.resource = r
        remaining[r]--
        return true
    }

    setResources() {
        const remaining = Object.assign({}, this.resourceAmts)
        for (let i = 0; i < this.numResourceHex; i++) {
            if (!this.setResource(this.array[i], remaining)) {
                return false
            }
        }
        return true
    }

    resetResources() {
        for (let i = 0; i < this.numResourceHex; i++) {
            this.array[i].resource = null
        }
    }

    // setting numbers

    maxDots(x, resourceDots) {
        const ns = this.nearby(x)
        let intersectionMax = 0
        for (let i = 0; i < ns.length; i++) {
            for (let j = i + 1; j < ns.length; j++) {
                if (this.nearby(ns[i]).indexOf(ns[j]) === -1) {
                    continue
                }
                const get = toDots(ns[i]) + toDots(ns[j])
                if (get > intersectionMax) {
                    intersectionMax = get
                }
            }
        }
        return Math.min(
            this.maxDotsPerIntersection - intersectionMax,
            this.maxDotsPerResource - resourceDots[x.resource]
        )
    }

    setNumber(x, remaining, resourceDots) {
        if (x.resource === "desert") {
            return true
        }
        const okNs = {}
        let sum = 0
        const md = this.maxDots(x, resourceDots)
        for (const n in remaining) {
            if (NumberDots[n] <= md) {
                sum += okNs[n] = remaining[n]
            }
        }
        if (sum === 0) {
            return false
        }
        const n = weightedRandom(okNs, sum)
        x.number = Number(n)
        remaining[n]--
        resourceDots[x.resource] += NumberDots[n]
        return true
    }

    setNumbers() {
        const remaining = Object.assign({}, this.numberAmts)
        const resourceDots = {
            brick: 0,
            wood: 0,
            wheat: 0,
            sheep: 0,
            ore: 0
        }
        for (let i = 0; i < this.numResourceHex; i++) {
            if (!this.setNumber(this.array[i], remaining, resourceDots)) {
                return false
            }
        }
        return true
    }

    resetNumbers() {
        for (let i = 0; i < this.numResourceHex; i++) {
            this.array[i].number = null
        }
    }

    // setting ports

    setPort(x, remaining) {
        if (this.nearby(x).some(hasPort)) {
            return true
        }
        const p = weightedRandom(remaining, sumObject(remaining))
        x.port = p
        remaining[p]--
        return true
    }

    setPorts() {
        const remaining = Object.assign({}, this.portAmts)
        for (let i = this.numResourceHex; i < this.size; i++) {
            if (!this.setPort(this.array[i], remaining)) {
                return false
            }
        }
        return true
    }

    resetPorts() {
        for (let i = this.numResourceHex; i < this.size; i++) {
            this.array[i].port = null
        }
    }

    // generating

    generate() {
        // gross, but guarenteed to work
        do {
            this.resetResources()
        } while (!this.setResources())
        do {
            this.resetNumbers()
        } while (!this.setNumbers())
        do {
            this.resetPorts()
        } while (!this.setPorts())
    }
}

export default Board
<commit_msg>use sameType<commit_after>import BorderHex from "./border-hex"
import Graph from "./graph"
import NumberDots from "./number-dots"
import ResourceHex from "./resource-hex"

// helpers not associated with class data

const toResource = x => x.resource
const hasPort = x => x.port !== null
const toDots = x => x.number === null ? 0 : NumberDots[x.number]
function sumObject(x) {
    let ret = 0
    for (const p in x) {
        ret += x[p]
    }
    return ret
}
function weightedRandom(x, sum) {
    const cutoffs = {}
    let prevWeight = 0
    for (const p in x) {
        cutoffs[p] = x[p] / sum + prevWeight
        prevWeight = cutoffs[p]
    }
    const rand = Math.random()
    for (const p in cutoffs) {
        if (rand <= cutoffs[p]) {
            return p
        }
    }
    throw new Error("no")
}
const sameType = x => y =>
    Object.getPrototypeOf(x) === Object.getPrototypeOf(y)

// the class

class Board {

    // setup

    constructor({
        resourceAmts,
        numberAmts,
        portAmts,
        numNonPort,
        edges,
        offsets,
        maxDotsPerIntersection,
        maxDotsPerResource
    }) {
        this.resourceAmts = resourceAmts
        this.numberAmts = numberAmts
        this.portAmts = portAmts
        this.offsets = offsets
        this.maxDotsPerIntersection = maxDotsPerIntersection
        this.maxDotsPerResource = maxDotsPerResource

        this.numResourceHex = sumObject(resourceAmts)
        this.numBorderHex = sumObject(portAmts) + numNonPort
        this.size = this.numResourceHex + this.numBorderHex
        this.array = []
        this.graph = new Graph()

        for (let i = 0; i < this.numResourceHex; i++) {
            const t = new ResourceHex(null, null)
            this.array.push(t)
            this.graph.add(t)
        }
        for (let i = this.numResourceHex; i < this.size; i++) {
            const t = new BorderHex(null)
            this.array.push(t)
            this.graph.add(t)
        }
        for (let i = 0; i < this.size; i++) {
            this.graph
                .find(this.array[i])
                .edges
                .push(...edges[i].map(j => this.array[j]))
        }
    }

    // draw

    draw(x, y, r) {
        const xOff = r / 7
        const yOff = xOff * 1.732050808 // 3 / sqrt(3)
        const hexR = yOff / 1.5 - 1
        for (let i = 0; i < this.size; i++) {
            const off = this.offsets[i]
            this.array[i].draw(x + off[0] * xOff, y + off[1] * yOff, hexR)
        }
    }

    // setting resources

    nearby(x) {
        return this.graph
            .find(x)
            .edges
            .filter(sameType(x))
    }

    setResource(x, remaining) {
        const nearbyRs = this.nearby(x).map(toResource)
        const okRs = {}
        let sum = 0
        for (const r in remaining) {
            if (nearbyRs.indexOf(r) === -1) {
                sum += okRs[r] = remaining[r]
            }
        }
        if (sum === 0) {
            return false
        }
        const r = weightedRandom(okRs, sum)
        x.resource = r
        remaining[r]--
        return true
    }

    setResources() {
        const remaining = Object.assign({}, this.resourceAmts)
        for (let i = 0; i < this.numResourceHex; i++) {
            if (!this.setResource(this.array[i], remaining)) {
                return false
            }
        }
        return true
    }

    resetResources() {
        for (let i = 0; i < this.numResourceHex; i++) {
            this.array[i].resource = null
        }
    }

    // setting numbers

    maxDots(x, resourceDots) {
        const ns = this.nearby(x)
        let intersectionMax = 0
        for (let i = 0; i < ns.length; i++) {
            for (let j = i + 1; j < ns.length; j++) {
                if (this.nearby(ns[i]).indexOf(ns[j]) === -1) {
                    continue
                }
                const get = toDots(ns[i]) + toDots(ns[j])
                if (get > intersectionMax) {
                    intersectionMax = get
                }
            }
        }
        return Math.min(
            this.maxDotsPerIntersection - intersectionMax,
            this.maxDotsPerResource - resourceDots[x.resource]
        )
    }

    setNumber(x, remaining, resourceDots) {
        if (x.resource === "desert") {
            return true
        }
        const okNs = {}
        let sum = 0
        const md = this.maxDots(x, resourceDots)
        for (const n in remaining) {
            if (NumberDots[n] <= md) {
                sum += okNs[n] = remaining[n]
            }
        }
        if (sum === 0) {
            return false
        }
        const n = weightedRandom(okNs, sum)
        x.number = Number(n)
        remaining[n]--
        resourceDots[x.resource] += NumberDots[n]
        return true
    }

    setNumbers() {
        const remaining = Object.assign({}, this.numberAmts)
        const resourceDots = {
            brick: 0,
            wood: 0,
            wheat: 0,
            sheep: 0,
            ore: 0
        }
        for (let i = 0; i < this.numResourceHex; i++) {
            if (!this.setNumber(this.array[i], remaining, resourceDots)) {
                return false
            }
        }
        return true
    }

    resetNumbers() {
        for (let i = 0; i < this.numResourceHex; i++) {
            this.array[i].number = null
        }
    }

    // setting ports

    setPort(x, remaining) {
        if (this.nearby(x).some(hasPort)) {
            return true
        }
        const p = weightedRandom(remaining, sumObject(remaining))
        x.port = p
        remaining[p]--
        return true
    }

    setPorts() {
        const remaining = Object.assign({}, this.portAmts)
        for (let i = this.numResourceHex; i < this.size; i++) {
            if (!this.setPort(this.array[i], remaining)) {
                return false
            }
        }
        return true
    }

    resetPorts() {
        for (let i = this.numResourceHex; i < this.size; i++) {
            this.array[i].port = null
        }
    }

    // generating

    generate() {
        // gross, but guarenteed to work
        do {
            this.resetResources()
        } while (!this.setResources())
        do {
            this.resetNumbers()
        } while (!this.setNumbers())
        do {
            this.resetPorts()
        } while (!this.setPorts())
    }
}

export default Board
<|endoftext|>


<commit_before>!function(n){"use strict";function t(n,e){var r;G.object(n),G.object(e);for(r in e)if(e.hasOwnProperty(r)){if(n.hasOwnProperty(r)===!1||typeof n[r]!=typeof e[r])return!1;if(u(n[r])&&t(n[r],e[r])===!1)return!1}return!0}function e(n,t){return n&&d(t)&&n instanceof t?!0:!1}function r(n){return u(n)&&0===Object.keys(n).length}function u(n){return i(n)&&"[object Object]"===Object.prototype.toString.call(n)}function i(n){return!o(n)&&!a(n)}function o(n){return void 0===n}function a(n){return null===n}function c(n,t){return G.number(t),i(n)&&n.length===t}function f(n){return Array.isArray(n)}function l(n){return"[object Date]"===Object.prototype.toString.call(n)}function d(n){return"function"==typeof n}function b(n){return s(n)&&/^(https?:)?\/\/([\w-\.~:@]+)(\/[\w-\.~\/\?#\[\]&\(\)\*\+,;=%]*)?$/.test(n)}function s(n){return v(n)&&""!==n}function v(n){return"string"==typeof n}function y(n){return g(n)&&!p(n)}function p(n){return j(n)&&n%2===0}function g(n){return j(n)&&n%1===0}function m(n){return j(n)&&n>0}function I(n){return j(n)&&0>n}function j(n){return"number"==typeof n&&isNaN(n)===!1&&n!==Number.POSITIVE_INFINITY&&n!==Number.NEGATIVE_INFINITY}function h(n){return n===!1||n===!0}function O(n,t){return G.array(n),d(t)?n.map(function(n){return t(n)}):(G.array(t),G.length(n,t.length),n.map(function(n,e){return t[e](n)}))}function w(n,t){var e,r={};return G.object(n),G.object(t),e=Object.keys(t),G.length(Object.keys(n),e.length),e.forEach(function(e){var i=t[e];d(i)?r[e]=i(n[e]):u(i)&&(r[e]=w(n[e],i))}),r}function N(n){return f(n)?k(n,!1):(G.object(n),E(n,!1))}function k(n,t){var e;for(e=0;e<n.length;e+=1)if(n[e]===t)return t;return!t}function E(n,t){var e,r;for(e in n)if(n.hasOwnProperty(e)){if(r=n[e],u(r)&&E(r,t)===t)return t;if(r===t)return t}return!t}function S(n){return f(n)?k(n,!0):(G.object(n),E(n,!0))}function P(n,t){return Object.keys(t).forEach(function(e){n[e]=t[e]}),n}function T(n,t){return function(){var e;if(n.apply(null,arguments)===!1)throw e=arguments[arguments.length-1],new Error(s(e)?e:t)}}function A(n){return function(){return i(arguments[0])?n.apply(null,arguments):!0}}function U(n){return function(){return!n.apply(null,arguments)}}function x(n){return F(n,_)}function F(n,t){var e={};return Object.keys(t).forEach(function(r){e[r]=n(t[r],Y[r])}),e}function V(t){"function"==typeof define&&define.amd?define(function(){return t}):"undefined"!=typeof module&&null!==module&&module.exports?module.exports=t:n.check=t}var Y,_,D,G,L,R;Y={like:"Invalid type",instance:"Invalid type",emptyObject:"Invalid object",object:"Invalid object",assigned:"Invalid value",undefined:"Invalid value","null":"Invalid value",length:"Invalid length",array:"Invalid array",date:"Invalid date",fn:"Invalid function",webUrl:"Invalid URL",unemptyString:"Invalid string",string:"Invalid string",odd:"Invalid number",even:"Invalid number",positive:"Invalid number",negative:"Invalid number",integer:"Invalid number",number:"Invalid number","boolean":"Invalid boolean"},_={like:t,instance:e,emptyObject:r,object:u,assigned:i,undefined:o,"null":a,length:c,array:f,date:l,"function":d,webUrl:b,unemptyString:s,string:v,odd:y,even:p,positive:m,negative:I,integer:g,number:j,"boolean":h},D={apply:O,map:w,all:N,any:S},D=P(D,_),G=x(T),L=x(A),R=x(U),G.maybe=F(T,L),G.not=F(T,R),V(P(D,{assert:G,maybe:L,not:R}))}(this);<commit_msg>Update minified script.<commit_after>!function(n){"use strict";function t(n,e){var r;G.object(n),G.object(e);for(r in e)if(e.hasOwnProperty(r)){if(n.hasOwnProperty(r)===!1||typeof n[r]!=typeof e[r])return!1;if(u(n[r])&&t(n[r],e[r])===!1)return!1}return!0}function e(n,t){return n&&d(t)&&n instanceof t?!0:!1}function r(n){return u(n)&&0===Object.keys(n).length}function u(n){return i(n)&&"[object Object]"===Object.prototype.toString.call(n)}function i(n){return!o(n)&&!a(n)}function o(n){return void 0===n}function a(n){return null===n}function c(n,t){return G.not.undefined(t),i(n)&&n.length===t}function f(n){return Array.isArray(n)}function l(n){return"[object Date]"===Object.prototype.toString.call(n)}function d(n){return"function"==typeof n}function b(n){return s(n)&&/^(https?:)?\/\/([\w-\.~:@]+)(\/[\w-\.~\/\?#\[\]&\(\)\*\+,;=%]*)?$/.test(n)}function s(n){return v(n)&&""!==n}function v(n){return"string"==typeof n}function y(n){return g(n)&&!p(n)}function p(n){return j(n)&&n%2===0}function g(n){return j(n)&&n%1===0}function m(n){return j(n)&&n>0}function I(n){return j(n)&&0>n}function j(n){return"number"==typeof n&&isNaN(n)===!1&&n!==Number.POSITIVE_INFINITY&&n!==Number.NEGATIVE_INFINITY}function h(n){return n===!1||n===!0}function O(n,t){return G.array(n),d(t)?n.map(function(n){return t(n)}):(G.array(t),G.length(n,t.length),n.map(function(n,e){return t[e](n)}))}function w(n,t){var e,r={};return G.object(n),G.object(t),e=Object.keys(t),G.length(Object.keys(n),e.length),e.forEach(function(e){var i=t[e];d(i)?r[e]=i(n[e]):u(i)&&(r[e]=w(n[e],i))}),r}function N(n){return f(n)?k(n,!1):(G.object(n),E(n,!1))}function k(n,t){var e;for(e=0;e<n.length;e+=1)if(n[e]===t)return t;return!t}function E(n,t){var e,r;for(e in n)if(n.hasOwnProperty(e)){if(r=n[e],u(r)&&E(r,t)===t)return t;if(r===t)return t}return!t}function S(n){return f(n)?k(n,!0):(G.object(n),E(n,!0))}function P(n,t){return Object.keys(t).forEach(function(e){n[e]=t[e]}),n}function T(n,t){return function(){var e;if(n.apply(null,arguments)===!1)throw e=arguments[arguments.length-1],new Error(s(e)?e:t)}}function A(n){return function(){return i(arguments[0])?n.apply(null,arguments):!0}}function U(n){return function(){return!n.apply(null,arguments)}}function x(n){return F(n,_)}function F(n,t){var e={};return Object.keys(t).forEach(function(r){e[r]=n(t[r],Y[r])}),e}function V(t){"function"==typeof define&&define.amd?define(function(){return t}):"undefined"!=typeof module&&null!==module&&module.exports?module.exports=t:n.check=t}var Y,_,D,G,L,R;Y={like:"Invalid type",instance:"Invalid type",emptyObject:"Invalid object",object:"Invalid object",assigned:"Invalid value",undefined:"Invalid value","null":"Invalid value",length:"Invalid length",array:"Invalid array",date:"Invalid date",fn:"Invalid function",webUrl:"Invalid URL",unemptyString:"Invalid string",string:"Invalid string",odd:"Invalid number",even:"Invalid number",positive:"Invalid number",negative:"Invalid number",integer:"Invalid number",number:"Invalid number","boolean":"Invalid boolean"},_={like:t,instance:e,emptyObject:r,object:u,assigned:i,undefined:o,"null":a,length:c,array:f,date:l,"function":d,webUrl:b,unemptyString:s,string:v,odd:y,even:p,positive:m,negative:I,integer:g,number:j,"boolean":h},D={apply:O,map:w,all:N,any:S},D=P(D,_),G=x(T),L=x(A),R=x(U),G.maybe=F(T,L),G.not=F(T,R),V(P(D,{assert:G,maybe:L,not:R}))}(this);<|endoftext|>


<commit_before>'use strict';

var nconf = require('nconf');
var path = require('path');
var paths = require('./paths');
var UglifyJS = require("uglify-js");
var CleanCSS = require('clean-css');
var _ = require('lodash');

nconf.argv().env().file({
    file: path.join(__dirname, '../src/config.json')
});

var defaultConfig = {
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM',
        'socket.io-client': 'io',
        'moment': 'moment',
        'moment/locale/zh-cn': 'moment.locale',
    },
    resolve: {
        modules: ["node_modules"],
        moduleExtensions: ['.js', '.json', '.jsx'],
        alias: {
            'react-native': 'react-native-web',
        }
    },
    copyWebpackPlugin: [
        {
            from: paths.appSrc + '/client/static/css/common.css',
            to: paths.appBuild + '/static/css/common.css',
            transform: function (content, absoluteFrom) {
                return cleanCSS(content);
            }
        },
        {
            from: paths.appSrc + '/client/static/css/customer.css',
            to: paths.appBuild + '/static/css/customer.css',
        },
        {
            from: paths.appSrc + '/client/views/customer/storage.html',
            to: paths.appBuild + '/storage.html'
        },
        {
            from: paths.storageJS,
            to: paths.appBuild + '/storage.js',
            transform: function (content, absoluteFrom) {
                var code = (content + '').replace(/'..\/..'\+/g, '');
                return minify(code);
            }
        },
        {
            from: paths.customerJS,
            to: paths.appBuild + '/static/js/uuchat.js',
            transform: function (content, absoluteFrom) {
                var code = (content + '').replace(/'..\/..'\+/g, '');
                return minify(code);
            }
        },
        {
            from: paths.customerLoaderJS,
            to: paths.appBuild + '/static/js/loader.js',
            transform: function (content, absoluteFrom) {
                var code = (content + '');
                return minify(code);
            }
        },
        {
            from: paths.customerHtml,
            to: paths.appBuild + '/customer.html',
            transform: function (content, absoluteFrom) {
                var result = content + '';
                if (nconf.get('app:ssl')) {
                    result = result.replace(/https:\/\/(uuchat.io)/g, "https://$1");
                }
                var result = result.replace(/uuchat.io/g,
                    nconf.get('app:address') + ':' + nconf.get('app:port'));
                return result;
            }
        },
        {
            from: paths.appContent + '/html/home.html',
            to: paths.appBuild + '/index.html'
        },
        {
            from: paths.appContent + '/html/static/css',
            to: paths.appBuild + '/static/css',
            transform: function (content, absoluteFrom) {
                return cleanCSS(content);
            }
        },
        {
            from: paths.appContent + '/html/static/img',
            to: paths.appBuild + '/static/img'
        },
        {
            from: paths.appWebview,
            to: paths.appBuild + '/app'
        },
        {
            from: paths.appWebview + '/webview.js',
            to: paths.appBuild + '/app/webview.js',
            force: true,
            transform: function (content, absoluteFrom) {
                var code = (content + ''),
                    domain = nconf.get('app:domain');

                if (_.isEmpty(domain)) {
                    code = code.replace(/https:\/\/(uuchat.io)/g, 'http://'+nconf.get('app:address')+':'+nconf.get('app:port'));
                } else {
                    code = code.replace(/https:\/\/(uuchat.io)/g, domain);
                }

                return minify(code);
            }
        },
        {
            from: paths.appWebview+'/index.html',
            to: paths.appBuild + '/app/index.html',
            force: true,
            transform: function (content, absoluteFrom) {
                var code = (content + '');

                if (process.env.NODE_ENV === 'production') {
                    code = code.replace(/app\//g, '');
                }

                return code;
            }
        }
    ],
    node: false
};

function cleanCSS(content) {
    if (process.env.NODE_ENV === 'development') {
        return content + '';
    } else {
        var result = new CleanCSS({}).minify((content + ''));
        return result.styles;
    }
}

function minify(code) {
    if (process.env.NODE_ENV === 'development') {
        return code;
    } else {
        var result = UglifyJS.minify(code, {fromString: true});
        if (result.error) {
            result = UglifyJS.minify(code);
        }
        return result.code;
    }
}

module.exports = defaultConfig;

<commit_msg>webview build<commit_after>'use strict';

var nconf = require('nconf');
var path = require('path');
var paths = require('./paths');
var UglifyJS = require("uglify-js");
var CleanCSS = require('clean-css');
var _ = require('lodash');

nconf.argv().env().file({
    file: path.join(__dirname, '../src/config.json')
});

var defaultConfig = {
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM',
        'socket.io-client': 'io',
        'moment': 'moment',
        'moment/locale/zh-cn': 'moment.locale',
    },
    resolve: {
        modules: ["node_modules"],
        moduleExtensions: ['.js', '.json', '.jsx'],
        alias: {
            'react-native': 'react-native-web',
        }
    },
    copyWebpackPlugin: [
        {
            from: paths.appSrc + '/client/static/css/common.css',
            to: paths.appBuild + '/static/css/common.css',
            transform: function (content, absoluteFrom) {
                return cleanCSS(content);
            }
        },
        {
            from: paths.appSrc + '/client/static/css/customer.css',
            to: paths.appBuild + '/static/css/customer.css',
        },
        {
            from: paths.appSrc + '/client/views/customer/storage.html',
            to: paths.appBuild + '/storage.html'
        },
        {
            from: paths.storageJS,
            to: paths.appBuild + '/storage.js',
            transform: function (content, absoluteFrom) {
                var code = (content + '').replace(/'..\/..'\+/g, '');
                return minify(code);
            }
        },
        {
            from: paths.customerJS,
            to: paths.appBuild + '/static/js/uuchat.js',
            transform: function (content, absoluteFrom) {
                var code = (content + '').replace(/'..\/..'\+/g, '');
                return minify(code);
            }
        },
        {
            from: paths.customerLoaderJS,
            to: paths.appBuild + '/static/js/loader.js',
            transform: function (content, absoluteFrom) {
                var code = (content + '');
                return minify(code);
            }
        },
        {
            from: paths.customerHtml,
            to: paths.appBuild + '/customer.html',
            transform: function (content, absoluteFrom) {
                var result = content + '';
                if (nconf.get('app:ssl')) {
                    result = result.replace(/https:\/\/(uuchat.io)/g, "https://$1");
                }
                var result = result.replace(/uuchat.io/g,
                    nconf.get('app:address') + ':' + nconf.get('app:port'));
                return result;
            }
        },
        {
            from: paths.appContent + '/html/home.html',
            to: paths.appBuild + '/index.html'
        },
        {
            from: paths.appContent + '/html/static/css',
            to: paths.appBuild + '/static/css',
            transform: function (content, absoluteFrom) {
                return cleanCSS(content);
            }
        },
        {
            from: paths.appContent + '/html/static/img',
            to: paths.appBuild + '/static/img'
        },
        {
            from: paths.appWebview,
            to: paths.appBuild + '/app'
        },
        {
            from: paths.appWebview + '/webview.js',
            to: paths.appBuild + '/app/webview.js',
            force: true,
            transform: function (content, absoluteFrom) {
                var code = (content + ''),
                    domain = nconf.get('app:domain');

                if (_.isEmpty(domain)) {
                    code = code.replace(/https:\/\/(uuchat.io)/g, 'http://'+nconf.get('app:address')+':'+nconf.get('app:port'));
                } else {
                    code = code.replace(/https:\/\/(uuchat.io)/g, domain);
                }

                return minify(code);
            }
        }
    ],
    node: false
};

function cleanCSS(content) {
    if (process.env.NODE_ENV === 'development') {
        return content + '';
    } else {
        var result = new CleanCSS({}).minify((content + ''));
        return result.styles;
    }
}

function minify(code) {
    if (process.env.NODE_ENV === 'development') {
        return code;
    } else {
        var result = UglifyJS.minify(code, {fromString: true});
        if (result.error) {
            result = UglifyJS.minify(code);
        }
        return result.code;
    }
}

module.exports = defaultConfig;

<|endoftext|>


<commit_before>/**
 * This list is used to skip abilities for the purposes of *hit
 * counting*. These abilities produce many hits but generally aren't
 * worth mitigating, skewing the results of tracking.
 */

const SHARED = [
  // ULDIR
  266948, // Vectis, Plague Bomb (intermission soak)
  // ANTORUS
  // Garothi Worldbreaker
  247159, // Luring Destruction, intermission pulse
  // Felhounds of Sargeras
  // Antoran High Command
  // Eonar (SKIP)
  // Portal Keeper
  // Imonar
  248424, // Gathering Power (Imonar bridge phase)
  // Kin'Garoth
  246646, // Flames of the Forge (Kin'garoth intermission pulsing aoe)
  // Varimathras
  // Coven of Shivarra
  258018, // Sense of Dread, pulsing aoe from inactive shivarra
  // Aggramar
  244912, // Blazing Eruption, the pulse of Embers on Aggramar
  254329, // Unleashed Flame, Embers on Aggramar
  245632, // Unchecked Flame, Flames on Aggramar (there are two that can't be stack, can't bof both)
  // Argus
  256396, // Reorigination pulse, aoe hit from orbs. generally don't get bof on these
];

export const BOF = SHARED.concat([
  // ULDIR
  265178, // Vectis, Evolving Affliction
  265143, // Vectis, Omega Vector (tank *shouldnt* get, but w/e)
  274358, // Zul, Rupturing Blood (dot interaction with bof is unknown; assuming that it won't change individual ticks for now)
  263334, // G'huun, Putrid Blood
  // ANTORUS
  // Kin'Garoth
  246779, // Empowered bombs (Kin'garoth)
]);

export const ISB = SHARED.concat([
]);
<commit_msg>updated mitigation blacklist for ISB hit tracking<commit_after>/**
 * This list is used to skip abilities for the purposes of *hit
 * counting*. These abilities produce many hits but generally aren't
 * worth mitigating, skewing the results of tracking.
 */

const SHARED = [
  // CRUCIBLE OF STORMS
  282743, // Cabal, Storm of Annihilation (Crown effect)
  295479, // Uu'nat, Touch of the End
  // BOD
  288806, // Mekkatorque, Gigavolt Blast
  286646, // Mekkatorque, Gigavolt Charge
  289648, // Mekkatorque (Spark Bot), Spark Shield -- RJW causes a bunch of events for this. not much you can do to avoid it
  288939, // Mekkatorque, Gigavolt Radiation
  284831, // Rastakhan, Scorching Detonation -- DoT
  285195, // Rastakhan, Deadly Withering
  285000, // Stormwall, Kelp-Wrapped
  287993, // Jaina, Chilling Touch
  // ULDIR
  266948, // Vectis, Plague Bomb (intermission soak)
  // ANTORUS
  // Garothi Worldbreaker
  247159, // Luring Destruction, intermission pulse
  // Felhounds of Sargeras
  // Antoran High Command
  // Eonar (SKIP)
  // Portal Keeper
  // Imonar
  248424, // Gathering Power (Imonar bridge phase)
  // Kin'Garoth
  246646, // Flames of the Forge (Kin'garoth intermission pulsing aoe)
  // Varimathras
  // Coven of Shivarra
  258018, // Sense of Dread, pulsing aoe from inactive shivarra
  // Aggramar
  244912, // Blazing Eruption, the pulse of Embers on Aggramar
  254329, // Unleashed Flame, Embers on Aggramar
  245632, // Unchecked Flame, Flames on Aggramar (there are two that can't be stack, can't bof both)
  // Argus
  256396, // Reorigination pulse, aoe hit from orbs. generally don't get bof on these
];

export const BOF = SHARED.concat([
  // ULDIR
  265178, // Vectis, Evolving Affliction
  265143, // Vectis, Omega Vector (tank *shouldnt* get, but w/e)
  274358, // Zul, Rupturing Blood (dot interaction with bof is unknown; assuming that it won't change individual ticks for now)
  263334, // G'huun, Putrid Blood
  // ANTORUS
  // Kin'Garoth
  246779, // Empowered bombs (Kin'garoth)
]);

export const ISB = SHARED.concat([
]);
<|endoftext|>


<commit_before>define(['angular', 'lodash',  'directives/checkbox'], function(angular, _ ) {'use strict';
    var Dropbox;

    return ['$scope', '$http', 'documents','$rootScope', 'allowBack','$window', function ($scope, $http, documents,$rootScope, allowBack,$window) {

    if(!$scope.viewOnly)
      require(['dropbox-dropins'],function(val){
        Dropbox = val;
      })
		var publicComputer    = true; // TODO
		var signedInToDropbox = false;

		$scope.close            = close;
		$scope.documents        = documents;
		$scope.allowBack        = allowBack;
		$scope.languages        = _(documents).map('files').flatten().map('language').uniq().sortBy().value();
		$scope.formats          = _(documents).map('files').flatten().map('type'  ).uniq().sortBy().value();
		$scope.selectedLanguages= {};
		$scope.selectedFormats  = {};
        $scope.canDropbox       = canDropbox;
        $scope.sendToDropbox    = sendToDropbox;
    $scope.download        = download

		if($scope.languages.length==1) $scope.selectedLanguages[$scope.languages[0]] = true;
		if($scope.formats  .length==1) $scope.selectedFormats[$scope.formats[0]] = true;

		$scope.$watch('selectedFormats', initDownloadLink, true);
		$scope.$watch('selectedLanguages', initDownloadLink, true);

    if($scope.viewOnly) {
      $window.addEventListener('message', closeDialogRemote)
      $scope.$on('$destroy', function(){
        $window.removeEventListener('message', closeDialogRemote)
      });
    }

		if(canDropbox && publicComputer)
			signoutDropbox();

		//==============================================
		//
		//
		//==============================================
		function initDownloadLink()	{

			delete $scope.success;
			delete $scope.downloadLink;

			var urls = selectedLinks();

			if(urls.length) {

				$http.post('/api/v2014/printsmart-downloads', urls).then(function(res){
					$scope.downloadLink = '/api/v2014/printsmart-downloads/'+res.data._id;
				}).catch(function(err){
					console.error(err);
				});
			}
		}

    function download(event){

      if($scope.viewOnly ) {
        $scope.success='download'
        event.preventDefault()
        event.stopPropagation()
        $window.parent.postMessage({type:'saveFiles',data:selectedLinks(true)},'*');
      } else if(!$scope.downloadLink){
        event.preventDefault()
        event.stopPropagation()
      }

    }
        //==============================================
        //
        //
        //==============================================
        function logDownloadHit(urls) {

            if(urls && urls.length) {
                $http.post('/api/v2014/printsmart-downloads/hit', urls);
            }
        }

		//==============================================
		//
		//
		//==============================================
		function selectedLinks(allProps) {

            return _(documents).map(function(doc){
                if(allProps) {
                  return _(doc.files||[])
                      .filter(function(f){ return $scope.selectedLanguages[f.language] && $scope.selectedFormats[f.type]; })
                      .value()
                }
                return _(doc.files||[])
                    .filter(function(f){ return $scope.selectedLanguages[f.language] && $scope.selectedFormats[f.type]; })
                    .pluck('url')
                    .value();
            }).flatten()
              .compact()
              .uniq()
              .value();
		}

		//==============================================
		//
		//
		//==============================================
		function canDropbox() {

			return !$scope.viewOnly && Dropbox && Dropbox.isBrowserSupported();
		}

		//==============================================
		//
		//
		//==============================================
		function sendToDropbox() {

			$scope.success = null;

            var urls  = selectedLinks();
			var files = _.map(urls, function(url) { return { url : url }; });

			if(!files.length)
				return;

			signedInToDropbox = true;

			var started = false;

			Dropbox.save({
			    files : files,
			    progress : function () {

					if(started)
						return;

					started = true;

					$scope.$applyAsync(function() {
						$scope.success = 'dropbox';
                        logDownloadHit(urls);
					});
				},
			    error : function (errorMessage) {

					console.error('DROPBOX ERROR', errorMessage);

					$scope.$applyAsync(function() {
						$scope.error = {
							error   : 'DROPBOX',
							message : errorMessage
						};
					});
				}
			});
		}

		//==============================================
		//
		//
		//==============================================
		function signoutDropbox() {

			angular.element('body').append('');
		}

    //==============================================
		//
		//
		//==============================================
    function closeDialogRemote(event) {

      var msg = event.data

      if(!msg || msg !== '{"type":"closeDialogRemote"}' ) return //other events

      msg = JSON.parse(msg)

      $scope.closeThisDialog($scope.success && 'clear');
		}

		//==============================================
		//
		//
		//==============================================
		function close(target) {

			if(signedInToDropbox && publicComputer)
				signoutDropbox();

            $scope.closeThisDialog(target);
		}
	}];
});
<commit_msg>suggestions<commit_after>define(['angular', 'lodash',  'directives/checkbox'], function(angular, _ ) {'use strict';
    var Dropbox;

    return ['$scope', '$http', 'documents','$rootScope', 'allowBack','$window', function ($scope, $http, documents,$rootScope, allowBack,$window) {

    if(!$scope.viewOnly)
      require(['dropbox-dropins'],function(val){
        Dropbox = val;
      })
		var publicComputer    = true; // TODO
		var signedInToDropbox = false;

		$scope.close            = close;
		$scope.documents        = documents;
		$scope.allowBack        = allowBack;
		$scope.languages        = _(documents).map('files').flatten().map('language').uniq().sortBy().value();
		$scope.formats          = _(documents).map('files').flatten().map('type'  ).uniq().sortBy().value();
		$scope.selectedLanguages= {};
		$scope.selectedFormats  = {};
        $scope.canDropbox       = canDropbox;
        $scope.sendToDropbox    = sendToDropbox;
    $scope.download        = download

		if($scope.languages.length==1) $scope.selectedLanguages[$scope.languages[0]] = true;
		if($scope.formats  .length==1) $scope.selectedFormats[$scope.formats[0]] = true;

		$scope.$watch('selectedFormats', initDownloadLink, true);
		$scope.$watch('selectedLanguages', initDownloadLink, true);

    if($scope.viewOnly) {
      $window.addEventListener('message', closeDialogRemote)
      $scope.$on('$destroy', function(){
        $window.removeEventListener('message', closeDialogRemote)
      });
    }

		if(canDropbox && publicComputer)
			signoutDropbox();

		//==============================================
		//
		//
		//==============================================
		function initDownloadLink()	{

			delete $scope.success;
			delete $scope.downloadLink;

			var urls = selectedLinks();

			if(urls.length) {

				$http.post('/api/v2014/printsmart-downloads', urls).then(function(res){
					$scope.downloadLink = '/api/v2014/printsmart-downloads/'+res.data._id;
				}).catch(function(err){
					console.error(err);
				});
			}
		}

    function download(event){

      if($scope.viewOnly ) {
        $scope.success='download'
        event.preventDefault()
        event.stopPropagation()
        $window.parent.postMessage({type:'saveFiles',data:selectedLinks(true)},'*');
      } else if(!$scope.downloadLink){
        event.preventDefault()
        event.stopPropagation()
      }

    }
        //==============================================
        //
        //
        //==============================================
        function logDownloadHit(urls) {

            if(urls && urls.length) {
                $http.post('/api/v2014/printsmart-downloads/hit', urls);
            }
        }

		//==============================================
		//
		//
		//==============================================
		function selectedLinks(allProps) {

            return _(documents).map(function(doc){
                if(allProps) {
                  return _(doc.files||[])
                      .filter(function(f){ return $scope.selectedLanguages[f.language] && $scope.selectedFormats[f.type]; })
                      .value()
                }
                return _(doc.files||[])
                    .filter(function(f){ return $scope.selectedLanguages[f.language] && $scope.selectedFormats[f.type]; })
                    .pluck('url')
                    .value();
            }).flatten()
              .compact()
              .uniq()
              .value();
		}

		//==============================================
		//
		//
		//==============================================
		function canDropbox() {

			return !$scope.viewOnly && Dropbox && Dropbox.isBrowserSupported();
		}

		//==============================================
		//
		//
		//==============================================
		function sendToDropbox() {

			$scope.success = null;

            var urls  = selectedLinks();
			var files = _.map(urls, function(url) { return { url : url }; });

			if(!files.length)
				return;

			signedInToDropbox = true;

			var started = false;

			Dropbox.save({
			    files : files,
			    progress : function () {

					if(started)
						return;

					started = true;

					$scope.$applyAsync(function() {
						$scope.success = 'dropbox';
                        logDownloadHit(urls);
					});
				},
			    error : function (errorMessage) {

					console.error('DROPBOX ERROR', errorMessage);

					$scope.$applyAsync(function() {
						$scope.error = {
							error   : 'DROPBOX',
							message : errorMessage
						};
					});
				}
			});
		}

		//==============================================
		//
		//
		//==============================================
		function signoutDropbox() {

			angular.element('body').append('');
		}

    //==============================================
		//
		//
		//==============================================
    function closeDialogRemote(event) {

      var msg = event.data

      if(!msg || typeof msg !== 'string' || msg.indexOf('{')!==0  ) return //other events

      if(msg.type === 'closeDialogRemote')
      msg = JSON.parse(msg)

      $scope.closeThisDialog($scope.success && 'clear');
		}

		//==============================================
		//
		//
		//==============================================
		function close(target) {

			if(signedInToDropbox && publicComputer)
				signoutDropbox();

            $scope.closeThisDialog(target);
		}
	}];
});
<|endoftext|>