<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import React, { PureComponent, Fragment } from 'react';
import { ContextMenu, MenuItem } from 'react-contextmenu';
import explicitConnect from '../../utils/connect';
import { selectedThreadSelectors } from '../../selectors/per-thread';
import { funcHasRecursiveCall } from '../../profile-logic/transforms';
import { getFunctionName } from '../../profile-logic/function-info';
import copy from 'copy-to-clipboard';
import {
  addTransformToStack,
  expandAllCallNodeDescendants,
  setCallNodeContextMenuVisibility,
} from '../../actions/profile-view';
import {
  getSelectedTab,
  getSelectedThreadIndex,
  getImplementationFilter,
  getInvertCallstack,
} from '../../selectors/url-state';
import {
  convertToTransformType,
  assertExhaustiveCheck,
} from '../../utils/flow';

import type { TransformType } from '../../types/transforms';
import type { ImplementationFilter } from '../../types/actions';
import type { TabSlug } from '../../app-logic/tabs-handling';
import type {
  IndexIntoCallNodeTable,
  CallNodeInfo,
  CallNodePath,
} from '../../types/profile-derived';
import type { Thread, ThreadIndex } from '../../types/profile';
import type {
  ExplicitConnectOptions,
  ConnectedProps,
} from '../../utils/connect';

type OwnProps = {|
  forceOpenForTests?: boolean,
|};

type StateProps = {|
  +thread: Thread,
  +threadIndex: ThreadIndex,
  +callNodeInfo: CallNodeInfo,
  +implementation: ImplementationFilter,
  +inverted: boolean,
  +selectedCallNodePath: CallNodePath,
  +selectedCallNodeIndex: IndexIntoCallNodeTable | null,
  +selectedTab: TabSlug,
|};

type DispatchProps = {|
  +addTransformToStack: typeof addTransformToStack,
  +expandAllCallNodeDescendants: typeof expandAllCallNodeDescendants,
  +setCallNodeContextMenuVisibility: typeof setCallNodeContextMenuVisibility,
|};

type Props = ConnectedProps<OwnProps, StateProps, DispatchProps>;

type State = {|
  isShown: boolean,
|};

require('./CallNodeContextMenu.css');

class CallNodeContextMenu extends PureComponent<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      isShown: Boolean(this.props.forceOpenForTests),
    };
  }

  _showMenu = () => {
    this.props.setCallNodeContextMenuVisibility(true);
    this.setState({ isShown: true });
  };

  _hideMenu = () => {
    this.props.setCallNodeContextMenuVisibility(false);
    this.setState({ isShown: false });
  };

  _getFunctionName(): string {
    const {
      selectedCallNodeIndex,
      thread: { stringTable, funcTable },
      callNodeInfo: { callNodeTable },
    } = this.props;

    if (selectedCallNodeIndex === null) {
      throw new Error(
        "The context menu assumes there is a selected call node and there wasn't one."
      );
    }

    const funcIndex = callNodeTable.func[selectedCallNodeIndex];
    const isJS = funcTable.isJS[funcIndex];
    const stringIndex = funcTable.name[funcIndex];
    const functionCall = stringTable.getString(stringIndex);
    const name = isJS ? functionCall : getFunctionName(functionCall);
    return name;
  }

  lookupFunctionOnSearchfox(): void {
    const name = this._getFunctionName();
    window.open(
      `https://searchfox.org/mozilla-central/search?q=${encodeURIComponent(
        name
      )}`,
      '_blank'
    );
  }

  copyFunctionName(): void {
    copy(this._getFunctionName());
  }

  copyUrl(): void {
    const {
      selectedCallNodeIndex,
      thread: { stringTable, funcTable },
      callNodeInfo: { callNodeTable },
    } = this.props;

    if (selectedCallNodeIndex === null) {
      throw new Error(
        "The context menu assumes there is a selected call node and there wasn't one."
      );
    }

    const funcIndex = callNodeTable.func[selectedCallNodeIndex];
    const stringIndex = funcTable.fileName[funcIndex];
    if (stringIndex !== null) {
      const fileName = stringTable.getString(stringIndex);
      copy(fileName);
    }
  }

  copyStack(): void {
    const {
      selectedCallNodeIndex,
      thread: { stringTable, funcTable },
      callNodeInfo: { callNodeTable },
    } = this.props;

    if (selectedCallNodeIndex === null) {
      throw new Error(
        "The context menu assumes there is a selected call node and there wasn't one."
      );
    }

    let stack = '';
    let callNodeIndex = selectedCallNodeIndex;

    do {
      const funcIndex = callNodeTable.func[callNodeIndex];
      const stringIndex = funcTable.name[funcIndex];
      stack += stringTable.getString(stringIndex) + '\n';
      callNodeIndex = callNodeTable.prefix[callNodeIndex];
    } while (callNodeIndex !== -1);

    copy(stack);
  }

  _handleClick = (event: SyntheticEvent<>, data: { type: string }): void => {
    const { type } = data;

    const transformType = convertToTransformType(type);
    if (transformType) {
      this.addTransformToStack(transformType);
      return;
    }

    switch (type) {
      case 'searchfox':
        this.lookupFunctionOnSearchfox();
        break;
      case 'copy-function-name':
        this.copyFunctionName();
        break;
      case 'copy-url':
        this.copyUrl();
        break;
      case 'copy-stack':
        this.copyStack();
        break;
      case 'expand-all':
        this.expandAll();
        break;
      default:
        throw new Error(`Unknown type ${type}`);
    }
  };

  addTransformToStack(type: TransformType): void {
    const {
      addTransformToStack,
      threadIndex,
      implementation,
      selectedCallNodePath,
      inverted,
      thread,
    } = this.props;
    const selectedFunc = selectedCallNodePath[selectedCallNodePath.length - 1];

    switch (type) {
      case 'focus-subtree':
        addTransformToStack(threadIndex, {
          type: 'focus-subtree',
          callNodePath: selectedCallNodePath,
          implementation,
          inverted,
        });
        break;
      case 'focus-function':
        addTransformToStack(threadIndex, {
          type: 'focus-function',
          funcIndex: selectedFunc,
        });
        break;
      case 'merge-call-node':
        addTransformToStack(threadIndex, {
          type: 'merge-call-node',
          callNodePath: selectedCallNodePath,
          implementation,
        });
        break;
      case 'merge-function':
        addTransformToStack(threadIndex, {
          type: 'merge-function',
          funcIndex: selectedFunc,
        });
        break;
      case 'drop-function':
        addTransformToStack(threadIndex, {
          type: 'drop-function',
          funcIndex: selectedFunc,
        });
        break;
      case 'collapse-resource': {
        const { funcTable } = thread;
        const resourceIndex = funcTable.resource[selectedFunc];
        // A new collapsed func will be inserted into the table at the end. Deduce
        // the index here.
        const collapsedFuncIndex = funcTable.length;
        addTransformToStack(threadIndex, {
          type: 'collapse-resource',
          resourceIndex,
          collapsedFuncIndex,
          implementation,
        });
        break;
      }
      case 'collapse-direct-recursion': {
        addTransformToStack(threadIndex, {
          type: 'collapse-direct-recursion',
          funcIndex: selectedFunc,
          implementation,
        });
        break;
      }
      case 'collapse-function-subtree': {
        addTransformToStack(threadIndex, {
          type: 'collapse-function-subtree',
          funcIndex: selectedFunc,
        });
        break;
      }
      default:
        assertExhaustiveCheck(type);
    }
  }

  expandAll(): void {
    const {
      expandAllCallNodeDescendants,
      threadIndex,
      selectedCallNodeIndex,
      callNodeInfo,
    } = this.props;
    if (selectedCallNodeIndex === null) {
      throw new Error(
        "The context menu assumes there is a selected call node and there wasn't one."
      );
    }

    expandAllCallNodeDescendants(
      threadIndex,
      selectedCallNodeIndex,
      callNodeInfo
    );
  }

  getNameForSelectedResource(): string | null {
    const {
      selectedCallNodePath,
      thread: { funcTable, stringTable, resourceTable, libs },
    } = this.props;

    const funcIndex = selectedCallNodePath[selectedCallNodePath.length - 1];
    if (funcIndex === undefined) {
      return null;
    }
    const isJS = funcTable.isJS[funcIndex];

    if (isJS) {
      const fileNameIndex = funcTable.fileName[funcIndex];
      return fileNameIndex === null
        ? null
        : stringTable.getString(fileNameIndex);
    }
    const resourceIndex = funcTable.resource[funcIndex];
    if (resourceIndex === -1) {
      return null;
    }
    const libIndex = resourceTable.lib[resourceIndex];
    if (libIndex === undefined || libIndex === null || libIndex === -1) {
      return null;
    }
    return libs[libIndex].name;
  }

  /**
   * Determine if this CallNode represent a recursive function call.
   */
  isRecursiveCall(): boolean {
    const { selectedCallNodePath, thread, implementation } = this.props;
    const funcIndex = selectedCallNodePath[selectedCallNodePath.length - 1];
    if (funcIndex === undefined) {
      return false;
    }
    // Do the easy thing first, see if this function was called by itself.
    if (selectedCallNodePath[selectedCallNodePath.length - 2] === funcIndex) {
      return true;
    }

    // Do a full check of the stackTable for recursion.
    return funcHasRecursiveCall(thread, implementation, funcIndex);
  }

  componentWillReceiveProps(nextProps: Props) {
    if (nextProps.selectedCallNodeIndex === null && this.state.isShown) {
      // If the menu was visible while selectedCallNodeIndex was
      // changed to null, the onHide callback will not execute when
      // null is returned below. Call _menuHidden() here to be ensure
      // the visibility state is updated.
      this._hideMenu();
    }
  }

  renderContextMenuContents() {
    const {
      selectedCallNodeIndex,
      inverted,
      thread: { funcTable },
      callNodeInfo: { callNodeTable },
      selectedTab,
    } = this.props;

    if (selectedCallNodeIndex === null) {
      console.error(
        "The context menu assumes there is a selected call node and there wasn't one."
      );
      return
;
    }

    const funcIndex = callNodeTable.func[selectedCallNodeIndex];
    const isJS = funcTable.isJS[funcIndex];
    // This could be the C++ library, or the JS filename.
    const nameForResource = this.getNameForSelectedResource();
    const showExpandAll = selectedTab === 'calltree';

    return (
      <Fragment>
        {inverted ? null : (
          <MenuItem
            onClick={this._handleClick}
            data={{ type: 'merge-call-node' }}
          >

            Merge node into calling function
          </MenuItem>
        )}
        <MenuItem onClick={this._handleClick} data={{ type: 'merge-function' }}>

          Merge function into caller across the entire tree
        </MenuItem>
        <MenuItem onClick={this._handleClick} data={{ type: 'focus-subtree' }}>

          Focus on subtree
        </MenuItem>
        <MenuItem onClick={this._handleClick} data={{ type: 'focus-function' }}>

          {inverted
            ? 'Focus on calls made by this function'
            : 'Focus on function'}
        </MenuItem>
        <MenuItem
          onClick={this._handleClick}
          data={{ type: 'collapse-function-subtree' }}
        >

          {'Collapse function’s subtree across the entire tree'}
        </MenuItem>
        {nameForResource ? (
          <MenuItem
            onClick={this._handleClick}
            data={{ type: 'collapse-resource' }}
          >

            Collapse functions in{' '}
            {nameForResource}
          </MenuItem>
        ) : null}
        {this.isRecursiveCall() ? (
          <MenuItem
            onClick={this._handleClick}
            data={{ type: 'collapse-direct-recursion' }}
          >

            Collapse direct recursion
          </MenuItem>
        ) : null}
        <MenuItem onClick={this._handleClick} data={{ type: 'drop-function' }}>

          Drop samples with this function
        </MenuItem>

        {showExpandAll ? (
          <Fragment>
            <MenuItem onClick={this._handleClick} data={{ type: 'expand-all' }}>
              Expand all
            </MenuItem>

          </Fragment>
        ) : null}
        <MenuItem onClick={this._handleClick} data={{ type: 'searchfox' }}>
          Look up the function name on Searchfox
        </MenuItem>
        <MenuItem
          onClick={this._handleClick}
          data={{ type: 'copy-function-name' }}
        >
          Copy function name
        </MenuItem>
        {isJS ? (
          <MenuItem onClick={this._handleClick} data={{ type: 'copy-url' }}>
            Copy script URL
          </MenuItem>
        ) : null}
        <MenuItem onClick={this._handleClick} data={{ type: 'copy-stack' }}>
          Copy stack
        </MenuItem>
      </Fragment>
    );
  }

  render() {
    const { selectedCallNodeIndex } = this.props;

    if (selectedCallNodeIndex === null) {
      return null;
    }

    return (
      <ContextMenu
        id="CallNodeContextMenu"
        onShow={this._showMenu}
        onHide={this._hideMenu}
      >
        {this.state.isShown ? (
          this.renderContextMenuContents()
        ) : (
          // ContextMenu expects at least 1 child.

        )}
      </ContextMenu>
    );
  }
}

const options: ExplicitConnectOptions<OwnProps, StateProps, DispatchProps> = {
  mapStateToProps: state => ({
    thread: selectedThreadSelectors.getFilteredThread(state),
    threadIndex: getSelectedThreadIndex(state),
    callNodeInfo: selectedThreadSelectors.getCallNodeInfo(state),
    implementation: getImplementationFilter(state),
    inverted: getInvertCallstack(state),
    selectedCallNodePath: selectedThreadSelectors.getSelectedCallNodePath(
      state
    ),
    selectedCallNodeIndex: selectedThreadSelectors.getSelectedCallNodeIndex(
      state
    ),
    selectedTab: getSelectedTab(state),
  }),
  mapDispatchToProps: {
    addTransformToStack,
    expandAllCallNodeDescendants,
    setCallNodeContextMenuVisibility,
  },
  component: CallNodeContextMenu,
};
export default explicitConnect(options);
<commit_msg>Prevent focusing of the context menu after mousedown event<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import React, { PureComponent, Fragment } from 'react';
import { ContextMenu, MenuItem } from 'react-contextmenu';
import explicitConnect from '../../utils/connect';
import { selectedThreadSelectors } from '../../selectors/per-thread';
import { funcHasRecursiveCall } from '../../profile-logic/transforms';
import { getFunctionName } from '../../profile-logic/function-info';
import copy from 'copy-to-clipboard';
import {
  addTransformToStack,
  expandAllCallNodeDescendants,
  setCallNodeContextMenuVisibility,
} from '../../actions/profile-view';
import {
  getSelectedTab,
  getSelectedThreadIndex,
  getImplementationFilter,
  getInvertCallstack,
} from '../../selectors/url-state';
import {
  convertToTransformType,
  assertExhaustiveCheck,
} from '../../utils/flow';

import type { TransformType } from '../../types/transforms';
import type { ImplementationFilter } from '../../types/actions';
import type { TabSlug } from '../../app-logic/tabs-handling';
import type {
  IndexIntoCallNodeTable,
  CallNodeInfo,
  CallNodePath,
} from '../../types/profile-derived';
import type { Thread, ThreadIndex } from '../../types/profile';
import type {
  ExplicitConnectOptions,
  ConnectedProps,
} from '../../utils/connect';

type OwnProps = {|
  forceOpenForTests?: boolean,
|};

type StateProps = {|
  +thread: Thread,
  +threadIndex: ThreadIndex,
  +callNodeInfo: CallNodeInfo,
  +implementation: ImplementationFilter,
  +inverted: boolean,
  +selectedCallNodePath: CallNodePath,
  +selectedCallNodeIndex: IndexIntoCallNodeTable | null,
  +selectedTab: TabSlug,
|};

type DispatchProps = {|
  +addTransformToStack: typeof addTransformToStack,
  +expandAllCallNodeDescendants: typeof expandAllCallNodeDescendants,
  +setCallNodeContextMenuVisibility: typeof setCallNodeContextMenuVisibility,
|};

type Props = ConnectedProps<OwnProps, StateProps, DispatchProps>;

type State = {|
  isShown: boolean,
|};

require('./CallNodeContextMenu.css');

class CallNodeContextMenu extends PureComponent<Props, State> {
  _contextMenu: { menu: HTMLElement } | null = null;
  _takeContextMenuRef = (contextMenu: ContextMenu | null) => {
    this._contextMenu = contextMenu;
  };

  constructor(props: Props) {
    super(props);
    this.state = {
      isShown: Boolean(this.props.forceOpenForTests),
    };
  }

  _showMenu = () => {
    this.props.setCallNodeContextMenuVisibility(true);
    this.setState({ isShown: true });
  };

  _hideMenu = () => {
    this.props.setCallNodeContextMenuVisibility(false);
    this.setState({ isShown: false });
  };

  _getFunctionName(): string {
    const {
      selectedCallNodeIndex,
      thread: { stringTable, funcTable },
      callNodeInfo: { callNodeTable },
    } = this.props;

    if (selectedCallNodeIndex === null) {
      throw new Error(
        "The context menu assumes there is a selected call node and there wasn't one."
      );
    }

    const funcIndex = callNodeTable.func[selectedCallNodeIndex];
    const isJS = funcTable.isJS[funcIndex];
    const stringIndex = funcTable.name[funcIndex];
    const functionCall = stringTable.getString(stringIndex);
    const name = isJS ? functionCall : getFunctionName(functionCall);
    return name;
  }

  lookupFunctionOnSearchfox(): void {
    const name = this._getFunctionName();
    window.open(
      `https://searchfox.org/mozilla-central/search?q=${encodeURIComponent(
        name
      )}`,
      '_blank'
    );
  }

  copyFunctionName(): void {
    copy(this._getFunctionName());
  }

  copyUrl(): void {
    const {
      selectedCallNodeIndex,
      thread: { stringTable, funcTable },
      callNodeInfo: { callNodeTable },
    } = this.props;

    if (selectedCallNodeIndex === null) {
      throw new Error(
        "The context menu assumes there is a selected call node and there wasn't one."
      );
    }

    const funcIndex = callNodeTable.func[selectedCallNodeIndex];
    const stringIndex = funcTable.fileName[funcIndex];
    if (stringIndex !== null) {
      const fileName = stringTable.getString(stringIndex);
      copy(fileName);
    }
  }

  copyStack(): void {
    const {
      selectedCallNodeIndex,
      thread: { stringTable, funcTable },
      callNodeInfo: { callNodeTable },
    } = this.props;

    if (selectedCallNodeIndex === null) {
      throw new Error(
        "The context menu assumes there is a selected call node and there wasn't one."
      );
    }

    let stack = '';
    let callNodeIndex = selectedCallNodeIndex;

    do {
      const funcIndex = callNodeTable.func[callNodeIndex];
      const stringIndex = funcTable.name[funcIndex];
      stack += stringTable.getString(stringIndex) + '\n';
      callNodeIndex = callNodeTable.prefix[callNodeIndex];
    } while (callNodeIndex !== -1);

    copy(stack);
  }

  _handleClick = (event: SyntheticEvent<>, data: { type: string }): void => {
    const { type } = data;

    const transformType = convertToTransformType(type);
    if (transformType) {
      this.addTransformToStack(transformType);
      return;
    }

    switch (type) {
      case 'searchfox':
        this.lookupFunctionOnSearchfox();
        break;
      case 'copy-function-name':
        this.copyFunctionName();
        break;
      case 'copy-url':
        this.copyUrl();
        break;
      case 'copy-stack':
        this.copyStack();
        break;
      case 'expand-all':
        this.expandAll();
        break;
      default:
        throw new Error(`Unknown type ${type}`);
    }
  };

  addTransformToStack(type: TransformType): void {
    const {
      addTransformToStack,
      threadIndex,
      implementation,
      selectedCallNodePath,
      inverted,
      thread,
    } = this.props;
    const selectedFunc = selectedCallNodePath[selectedCallNodePath.length - 1];

    switch (type) {
      case 'focus-subtree':
        addTransformToStack(threadIndex, {
          type: 'focus-subtree',
          callNodePath: selectedCallNodePath,
          implementation,
          inverted,
        });
        break;
      case 'focus-function':
        addTransformToStack(threadIndex, {
          type: 'focus-function',
          funcIndex: selectedFunc,
        });
        break;
      case 'merge-call-node':
        addTransformToStack(threadIndex, {
          type: 'merge-call-node',
          callNodePath: selectedCallNodePath,
          implementation,
        });
        break;
      case 'merge-function':
        addTransformToStack(threadIndex, {
          type: 'merge-function',
          funcIndex: selectedFunc,
        });
        break;
      case 'drop-function':
        addTransformToStack(threadIndex, {
          type: 'drop-function',
          funcIndex: selectedFunc,
        });
        break;
      case 'collapse-resource': {
        const { funcTable } = thread;
        const resourceIndex = funcTable.resource[selectedFunc];
        // A new collapsed func will be inserted into the table at the end. Deduce
        // the index here.
        const collapsedFuncIndex = funcTable.length;
        addTransformToStack(threadIndex, {
          type: 'collapse-resource',
          resourceIndex,
          collapsedFuncIndex,
          implementation,
        });
        break;
      }
      case 'collapse-direct-recursion': {
        addTransformToStack(threadIndex, {
          type: 'collapse-direct-recursion',
          funcIndex: selectedFunc,
          implementation,
        });
        break;
      }
      case 'collapse-function-subtree': {
        addTransformToStack(threadIndex, {
          type: 'collapse-function-subtree',
          funcIndex: selectedFunc,
        });
        break;
      }
      default:
        assertExhaustiveCheck(type);
    }
  }

  expandAll(): void {
    const {
      expandAllCallNodeDescendants,
      threadIndex,
      selectedCallNodeIndex,
      callNodeInfo,
    } = this.props;
    if (selectedCallNodeIndex === null) {
      throw new Error(
        "The context menu assumes there is a selected call node and there wasn't one."
      );
    }

    expandAllCallNodeDescendants(
      threadIndex,
      selectedCallNodeIndex,
      callNodeInfo
    );
  }

  getNameForSelectedResource(): string | null {
    const {
      selectedCallNodePath,
      thread: { funcTable, stringTable, resourceTable, libs },
    } = this.props;

    const funcIndex = selectedCallNodePath[selectedCallNodePath.length - 1];
    if (funcIndex === undefined) {
      return null;
    }
    const isJS = funcTable.isJS[funcIndex];

    if (isJS) {
      const fileNameIndex = funcTable.fileName[funcIndex];
      return fileNameIndex === null
        ? null
        : stringTable.getString(fileNameIndex);
    }
    const resourceIndex = funcTable.resource[funcIndex];
    if (resourceIndex === -1) {
      return null;
    }
    const libIndex = resourceTable.lib[resourceIndex];
    if (libIndex === undefined || libIndex === null || libIndex === -1) {
      return null;
    }
    return libs[libIndex].name;
  }

  /**
   * Determine if this CallNode represent a recursive function call.
   */
  isRecursiveCall(): boolean {
    const { selectedCallNodePath, thread, implementation } = this.props;
    const funcIndex = selectedCallNodePath[selectedCallNodePath.length - 1];
    if (funcIndex === undefined) {
      return false;
    }
    // Do the easy thing first, see if this function was called by itself.
    if (selectedCallNodePath[selectedCallNodePath.length - 2] === funcIndex) {
      return true;
    }

    // Do a full check of the stackTable for recursion.
    return funcHasRecursiveCall(thread, implementation, funcIndex);
  }

  componentWillReceiveProps(nextProps: Props) {
    if (nextProps.selectedCallNodeIndex === null && this.state.isShown) {
      // If the menu was visible while selectedCallNodeIndex was
      // changed to null, the onHide callback will not execute when
      // null is returned below. Call _menuHidden() here to be ensure
      // the visibility state is updated.
      this._hideMenu();
    }
  }

  _mouseDownHandler(event: MouseEvent): void {
    event.preventDefault();
  }

  componentDidUpdate() {
    if (this.state.isShown && this._contextMenu && this._contextMenu.menu) {
      this._contextMenu.menu.addEventListener(
        'mousedown',
        this._mouseDownHandler
      );
    }
  }

  renderContextMenuContents() {
    const {
      selectedCallNodeIndex,
      inverted,
      thread: { funcTable },
      callNodeInfo: { callNodeTable },
      selectedTab,
    } = this.props;

    if (selectedCallNodeIndex === null) {
      console.error(
        "The context menu assumes there is a selected call node and there wasn't one."
      );
      return
;
    }

    const funcIndex = callNodeTable.func[selectedCallNodeIndex];
    const isJS = funcTable.isJS[funcIndex];
    // This could be the C++ library, or the JS filename.
    const nameForResource = this.getNameForSelectedResource();
    const showExpandAll = selectedTab === 'calltree';

    return (
      <Fragment>
        {inverted ? null : (
          <MenuItem
            onClick={this._handleClick}
            data={{ type: 'merge-call-node' }}
          >

            Merge node into calling function
          </MenuItem>
        )}
        <MenuItem onClick={this._handleClick} data={{ type: 'merge-function' }}>

          Merge function into caller across the entire tree
        </MenuItem>
        <MenuItem onClick={this._handleClick} data={{ type: 'focus-subtree' }}>

          Focus on subtree
        </MenuItem>
        <MenuItem onClick={this._handleClick} data={{ type: 'focus-function' }}>

          {inverted
            ? 'Focus on calls made by this function'
            : 'Focus on function'}
        </MenuItem>
        <MenuItem
          onClick={this._handleClick}
          data={{ type: 'collapse-function-subtree' }}
        >

          {'Collapse function’s subtree across the entire tree'}
        </MenuItem>
        {nameForResource ? (
          <MenuItem
            onClick={this._handleClick}
            data={{ type: 'collapse-resource' }}
          >

            Collapse functions in{' '}
            {nameForResource}
          </MenuItem>
        ) : null}
        {this.isRecursiveCall() ? (
          <MenuItem
            onClick={this._handleClick}
            data={{ type: 'collapse-direct-recursion' }}
          >

            Collapse direct recursion
          </MenuItem>
        ) : null}
        <MenuItem onClick={this._handleClick} data={{ type: 'drop-function' }}>

          Drop samples with this function
        </MenuItem>

        {showExpandAll ? (
          <Fragment>
            <MenuItem onClick={this._handleClick} data={{ type: 'expand-all' }}>
              Expand all
            </MenuItem>

          </Fragment>
        ) : null}
        <MenuItem onClick={this._handleClick} data={{ type: 'searchfox' }}>
          Look up the function name on Searchfox
        </MenuItem>
        <MenuItem
          onClick={this._handleClick}
          data={{ type: 'copy-function-name' }}
        >
          Copy function name
        </MenuItem>
        {isJS ? (
          <MenuItem onClick={this._handleClick} data={{ type: 'copy-url' }}>
            Copy script URL
          </MenuItem>
        ) : null}
        <MenuItem onClick={this._handleClick} data={{ type: 'copy-stack' }}>
          Copy stack
        </MenuItem>
      </Fragment>
    );
  }

  render() {
    const { selectedCallNodeIndex } = this.props;

    if (selectedCallNodeIndex === null) {
      return null;
    }

    return (
      <ContextMenu
        id="CallNodeContextMenu"
        ref={this._takeContextMenuRef}
        onShow={this._showMenu}
        onHide={this._hideMenu}
      >
        {this.state.isShown ? (
          this.renderContextMenuContents()
        ) : (
          // ContextMenu expects at least 1 child.

        )}
      </ContextMenu>
    );
  }
}

const options: ExplicitConnectOptions<OwnProps, StateProps, DispatchProps> = {
  mapStateToProps: state => ({
    thread: selectedThreadSelectors.getFilteredThread(state),
    threadIndex: getSelectedThreadIndex(state),
    callNodeInfo: selectedThreadSelectors.getCallNodeInfo(state),
    implementation: getImplementationFilter(state),
    inverted: getInvertCallstack(state),
    selectedCallNodePath: selectedThreadSelectors.getSelectedCallNodePath(
      state
    ),
    selectedCallNodeIndex: selectedThreadSelectors.getSelectedCallNodeIndex(
      state
    ),
    selectedTab: getSelectedTab(state),
  }),
  mapDispatchToProps: {
    addTransformToStack,
    expandAllCallNodeDescendants,
    setCallNodeContextMenuVisibility,
  },
  component: CallNodeContextMenu,
};
export default explicitConnect(options);
<|endoftext|>


<commit_before>/*
Copyright 2017 Vector Creations Ltd.
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import Matrix from 'matrix-js-sdk';
const InteractiveAuth = Matrix.InteractiveAuth;

import React from 'react';
import PropTypes from 'prop-types';

import {getEntryComponentForLoginType} from '../views/auth/InteractiveAuthEntryComponents';

export default React.createClass({
    displayName: 'InteractiveAuth',

    propTypes: {
        // matrix client to use for UI auth requests
        matrixClient: PropTypes.object.isRequired,

        // response from initial request. If not supplied, will do a request on
        // mount.
        authData: PropTypes.shape({
            flows: PropTypes.array,
            params: PropTypes.object,
            session: PropTypes.string,
        }),

        // callback
        makeRequest: PropTypes.func.isRequired,

        // callback called when the auth process has finished,
        // successfully or unsuccessfully.
        // @param {bool} status True if the operation requiring
        //     auth was completed sucessfully, false if canceled.
        // @param {object} result The result of the authenticated call
        //     if successful, otherwise the error object
        // @param {object} extra Additional information about the UI Auth
        //     process:
        //      * emailSid {string} If email auth was performed, the sid of
        //            the auth session.
        //      * clientSecret {string} The client secret used in auth
        //            sessions with the ID server.
        onAuthFinished: PropTypes.func.isRequired,

        // Inputs provided by the user to the auth process
        // and used by various stages. As passed to js-sdk
        // interactive-auth
        inputs: PropTypes.object,

        // As js-sdk interactive-auth
        requestEmailToken: PropTypes.func,
        sessionId: PropTypes.string,
        clientSecret: PropTypes.string,
        emailSid: PropTypes.string,

        // If true, poll to see if the auth flow has been completed
        // out-of-band
        poll: PropTypes.bool,

        // If true, components will be told that the 'Continue' button
        // is managed by some other party and should not be managed by
        // the component itself.
        continueIsManaged: PropTypes.bool,
    },

    getInitialState: function() {
        return {
            authStage: null,
            busy: false,
            errorText: null,
            stageErrorText: null,
            submitButtonEnabled: false,
        };
    },

    componentWillMount: function() {
        this._unmounted = false;
        this._authLogic = new InteractiveAuth({
            authData: this.props.authData,
            doRequest: this._requestCallback,
            inputs: this.props.inputs,
            stateUpdated: this._authStateUpdated,
            matrixClient: this.props.matrixClient,
            sessionId: this.props.sessionId,
            clientSecret: this.props.clientSecret,
            emailSid: this.props.emailSid,
            requestEmailToken: this._requestEmailToken,
        });

        this._authLogic.attemptAuth().then((result) => {
            const extra = {
                emailSid: this._authLogic.getEmailSid(),
                clientSecret: this._authLogic.getClientSecret(),
            };
            this.props.onAuthFinished(true, result, extra);
        }).catch((error) => {
            this.props.onAuthFinished(false, error);
            console.error("Error during user-interactive auth:", error);
            if (this._unmounted) {
                return;
            }

            const msg = error.message || error.toString();
            this.setState({
                errorText: msg,
            });
        }).done();

        this._intervalId = null;
        if (this.props.poll) {
            this._intervalId = setInterval(() => {
                this._authLogic.poll();
            }, 2000);
        }
    },

    componentWillUnmount: function() {
        this._unmounted = true;

        if (this._intervalId !== null) {
            clearInterval(this._intervalId);
        }
    },

    _requestEmailToken: async function(...args) {
        this.setState({
            busy: true,
        });
        try {
            return await this.props.requestEmailToken(...args);
        } finally {
            this.setState({
                busy: false,
            });
        }
    },

    tryContinue: function() {
        if (this.refs.stageComponent && this.refs.stageComponent.tryContinue) {
            this.refs.stageComponent.tryContinue();
        }
    },

    _authStateUpdated: function(stageType, stageState) {
        const oldStage = this.state.authStage;
        this.setState({
            authStage: stageType,
            stageState: stageState,
            errorText: stageState.error,
        }, () => {
            if (oldStage != stageType) this._setFocus();
        });
    },

    _requestCallback: function(auth, background) {
        const makeRequestPromise = this.props.makeRequest(auth);

        // if it's a background request, just do it: we don't want
        // it to affect the state of our UI.
        if (background) return makeRequestPromise;

        // otherwise, manage the state of the spinner and error messages
        this.setState({
            busy: true,
            errorText: null,
            stageErrorText: null,
        });
        return makeRequestPromise.finally(() => {
            if (this._unmounted) {
                return;
            }
            this.setState({
                busy: false,
            });
        });
    },

    _setFocus: function() {
        if (this.refs.stageComponent && this.refs.stageComponent.focus) {
            this.refs.stageComponent.focus();
        }
    },

    _submitAuthDict: function(authData) {
        this._authLogic.submitAuthDict(authData);
    },

    _renderCurrentStage: function() {
        const stage = this.state.authStage;
        if (!stage) return null;

        const StageComponent = getEntryComponentForLoginType(stage);
        return (
            <StageComponent ref="stageComponent"
                loginType={stage}
                matrixClient={this.props.matrixClient}
                authSessionId={this._authLogic.getSessionId()}
                clientSecret={this._authLogic.getClientSecret()}
                stageParams={this._authLogic.getStageParams(stage)}
                submitAuthDict={this._submitAuthDict}
                errorText={this.state.stageErrorText}
                busy={this.state.busy}
                inputs={this.props.inputs}
                stageState={this.state.stageState}
                fail={this._onAuthStageFailed}
                setEmailSid={this._setEmailSid}
                showContinue={!this.props.continueIsManaged}
            />
        );
    },

    _onAuthStageFailed: function(e) {
        this.props.onAuthFinished(false, e);
    },
    _setEmailSid: function(sid) {
        this._authLogic.setEmailSid(sid);
    },

    render: function() {
        let error = null;
        if (this.state.errorText) {
            error = (

                    { this.state.errorText }

            );
        }

        return (

                    { this._renderCurrentStage() }
                    { error }

        );
    },
});
<commit_msg>Use setBusy interface of js-sdk interactive auth<commit_after>/*
Copyright 2017 Vector Creations Ltd.
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import Matrix from 'matrix-js-sdk';
const InteractiveAuth = Matrix.InteractiveAuth;

import React from 'react';
import PropTypes from 'prop-types';

import {getEntryComponentForLoginType} from '../views/auth/InteractiveAuthEntryComponents';

import sdk from '../../index';

export default React.createClass({
    displayName: 'InteractiveAuth',

    propTypes: {
        // matrix client to use for UI auth requests
        matrixClient: PropTypes.object.isRequired,

        // response from initial request. If not supplied, will do a request on
        // mount.
        authData: PropTypes.shape({
            flows: PropTypes.array,
            params: PropTypes.object,
            session: PropTypes.string,
        }),

        // callback
        makeRequest: PropTypes.func.isRequired,

        // callback called when the auth process has finished,
        // successfully or unsuccessfully.
        // @param {bool} status True if the operation requiring
        //     auth was completed sucessfully, false if canceled.
        // @param {object} result The result of the authenticated call
        //     if successful, otherwise the error object
        // @param {object} extra Additional information about the UI Auth
        //     process:
        //      * emailSid {string} If email auth was performed, the sid of
        //            the auth session.
        //      * clientSecret {string} The client secret used in auth
        //            sessions with the ID server.
        onAuthFinished: PropTypes.func.isRequired,

        // Inputs provided by the user to the auth process
        // and used by various stages. As passed to js-sdk
        // interactive-auth
        inputs: PropTypes.object,

        // As js-sdk interactive-auth
        requestEmailToken: PropTypes.func,
        sessionId: PropTypes.string,
        clientSecret: PropTypes.string,
        emailSid: PropTypes.string,

        // If true, poll to see if the auth flow has been completed
        // out-of-band
        poll: PropTypes.bool,

        // If true, components will be told that the 'Continue' button
        // is managed by some other party and should not be managed by
        // the component itself.
        continueIsManaged: PropTypes.bool,
    },

    getInitialState: function() {
        return {
            authStage: null,
            busy: false,
            errorText: null,
            stageErrorText: null,
            submitButtonEnabled: false,
        };
    },

    componentWillMount: function() {
        this._unmounted = false;
        this._authLogic = new InteractiveAuth({
            authData: this.props.authData,
            doRequest: this.props.makeRequest,
            setBusy: this._setBusy,
            inputs: this.props.inputs,
            stateUpdated: this._authStateUpdated,
            matrixClient: this.props.matrixClient,
            sessionId: this.props.sessionId,
            clientSecret: this.props.clientSecret,
            emailSid: this.props.emailSid,
            requestEmailToken: this._requestEmailToken,
        });

        this._authLogic.attemptAuth().then((result) => {
            const extra = {
                emailSid: this._authLogic.getEmailSid(),
                clientSecret: this._authLogic.getClientSecret(),
            };
            this.props.onAuthFinished(true, result, extra);
        }).catch((error) => {
            this.props.onAuthFinished(false, error);
            console.error("Error during user-interactive auth:", error);
            if (this._unmounted) {
                return;
            }

            const msg = error.message || error.toString();
            this.setState({
                errorText: msg,
            });
        }).done();

        this._intervalId = null;
        if (this.props.poll) {
            this._intervalId = setInterval(() => {
                this._authLogic.poll();
            }, 2000);
        }
    },

    componentWillUnmount: function() {
        this._unmounted = true;

        if (this._intervalId !== null) {
            clearInterval(this._intervalId);
        }
    },

    _requestEmailToken: async function(...args) {
        this.setState({
            busy: true,
        });
        try {
            return await this.props.requestEmailToken(...args);
        } finally {
            this.setState({
                busy: false,
            });
        }
    },

    tryContinue: function() {
        if (this.refs.stageComponent && this.refs.stageComponent.tryContinue) {
            this.refs.stageComponent.tryContinue();
        }
    },

    _authStateUpdated: function(stageType, stageState) {
        const oldStage = this.state.authStage;
        this.setState({
            authStage: stageType,
            stageState: stageState,
            errorText: stageState.error,
        }, () => {
            if (oldStage != stageType) this._setFocus();
        });
    },

    _setBusy: function(busy) {
        // if we've started doing stuff, reset the error messages
        if (busy) {
            this.setState({
                busy: true,
                errorText: null,
                stageErrorText: null,
            });
        } else {
            this.setState({
                busy: false,
            });
        }
    },

    _setFocus: function() {
        if (this.refs.stageComponent && this.refs.stageComponent.focus) {
            this.refs.stageComponent.focus();
        }
    },

    _submitAuthDict: function(authData) {
        this._authLogic.submitAuthDict(authData);
    },

    _renderCurrentStage: function() {
        const stage = this.state.authStage;
        if (!stage) {
            if (this.state.busy) {
                const Loader = sdk.getComponent("elements.Spinner");
                return <Loader />;
            } else {
                return null;
            }
        }

        const StageComponent = getEntryComponentForLoginType(stage);
        return (
            <StageComponent ref="stageComponent"
                loginType={stage}
                matrixClient={this.props.matrixClient}
                authSessionId={this._authLogic.getSessionId()}
                clientSecret={this._authLogic.getClientSecret()}
                stageParams={this._authLogic.getStageParams(stage)}
                submitAuthDict={this._submitAuthDict}
                errorText={this.state.stageErrorText}
                busy={this.state.busy}
                inputs={this.props.inputs}
                stageState={this.state.stageState}
                fail={this._onAuthStageFailed}
                setEmailSid={this._setEmailSid}
                showContinue={!this.props.continueIsManaged}
            />
        );
    },

    _onAuthStageFailed: function(e) {
        this.props.onAuthFinished(false, e);
    },
    _setEmailSid: function(sid) {
        this._authLogic.setEmailSid(sid);
    },

    render: function() {
        let error = null;
        if (this.state.errorText) {
            error = (

                    { this.state.errorText }

            );
        }

        return (

                    { this._renderCurrentStage() }
                    { error }

        );
    },
});
<|endoftext|>


<commit_before>//---------------------------------------------------
//
// STYLED WIDGETS THAT CAN BE USED WITH THREEJS PLUGIN
//
//---------------------------------------------------

//---------------------------------------------------
// GRID LAYOUTED Group
//---------------------------------------------------
//
// PARAMETERS:  width = width in world coordinates
//              height = height in world coordinates
//              color = hexadecimal
//

WIDGET3D.GridWindow = function(parameters){

  var that = this;

  WIDGET3D.Group.call( this );

  var parameters = parameters || {};

  this.width = parameters.width !== undefined ? parameters.width : 1000;
  this.height = parameters.height !== undefined ? parameters.height : 1000;
  this.density = parameters.density !== undefined ? parameters.density : 6;
  this.depth = (this.width/this.density);

  this.maxChildren = this.density * this.density;

  this.color = parameters.color !== undefined ? parameters.color : 0x6B6B6B;
  this.lineWidth = parameters.lineWidth !== undefined ? parameters.lineWidth : 2;
  this.opacity = parameters.opacity !== undefined ? parameters.opacity : 0.5;

  this.material = new THREE.MeshBasicMaterial({
    color: this.color,
    opacity: this.opacity,
    wireframe: true,
    side : THREE.DoubleSide,
    wireframeLinewidth : this.lineWidth
  });

  var geometry = new THREE.CubeGeometry( this.width, this.height, this.depth, this.density, this.density, 1 );
  var mesh =  new THREE.Mesh(geometry, this.material);
  this.grid = new WIDGET3D.Basic();
  this.grid.setObject3D(mesh);

  var hideGrid = parameters.hideGrid !== undefined ? parameters.hideGrid : false;
  if(hideGrid){
    this.grid.hide();
  }
  this.add(this.grid);

  this.icons = new Array();
  this.gridIndexes = new Array();

  //default mouse controls in use
  this.defaultControls = parameters.defaultControls !== undefined ? parameters.defaultControls : false;

  if(this.defaultControls){

    var button = parameters.mouseButton !== undefined ? parameters.mouseButton : 0;
    var shift = parameters.shiftKey !== undefined ? parameters.shiftKey : false;

    var control = new WIDGET3D.RollControl(this, {mouseButton : button, shiftKey : shift});
  }

  this.calculateGrid();
};

WIDGET3D.GridWindow.prototype = WIDGET3D.Group.prototype.inheritance();

WIDGET3D.GridWindow.prototype.addSlots = function(newDensity){
  this.density = newDensity;
  this.maxChildren = newDensity * newDensity;
  this.depth = (this.width/this.density);

  var grid = new THREE.CubeGeometry( this.width, this.height, this.depth, this.density, this.density, 1 );
  var gridMesh =  new THREE.Mesh(grid, this.material);
  this.grid.setObject3D(gridMesh);

  //calculating the new indexes
  this.calculateGrid();

  var tmpChilds = this.icons;
  this.icons = new Array();

  for(var i = 0; i < tmpChilds.length; ++i){

    var icon = tmpChilds[i];
    this.icons.push(icon);

    icon.setNewSize();

    var pos = this.gridIndexes[i];
    icon.setPosition(pos.x, pos.y, pos.z);

  }

  return this;
};

//Adds children to the group
WIDGET3D.GridWindow.prototype.add = function(child){

  if(child != this){
    if(child.parent){
      //removing event listeners from former parent
      if(child.parent != WIDGET3D.getApplication()){
        child.parent.removeRelatedEventListeners(child);
      }
      child.parent.removeFromObjects(child);
      child.parent.removeFromIcons(child);

      for(var i = 0; i < child.parent.icons.length; ++i){
        var icon = child.parent.icons[i];
        var pos = child.parent.gridIndexes[i];
        icon.setPosition(pos.x, pos.y, pos.z);
      }
    }
    child.parent = this;
    this.children.push(child);
    this.object3D.add(child.object3D);

    if(child != this.grid){
      this.icons.push(child);
      child.setNewSize();
      if(this.icons.length > this.maxChildren){
        console.log("Grid is full! Creating bigger one");
        this.addSlots(Math.ceil(this.density * 1.5));
      }
      else{
        pos = this.gridIndexes[this.icons.length-1];
        child.setPosition(pos.x, pos.y, pos.z);
      }
    }

    return this;
  }
  else{
    console.log("You can't add object to it self!");
    return false;
  }

};

WIDGET3D.GridWindow.prototype.removeFromIcons = function(child){
  for(var k = 0; k < this.icons.length; ++k){
    if(this.icons[k] === child){
      var removedObj = this.icons.splice(k, 1);
      return removedObj[0];
    }
  }
  return false;
};

WIDGET3D.GridWindow.prototype.calculateGrid = function(){
  this.gridIndexes = new Array();
  var pos = this.getPosition();

  var left = -this.width/2.0 + pos.x/this.width;
  var top =  this.height/2.0 + pos.y/this.height;

  var stepX = this.width/this.density;
  var stepY = this.height/this.density;

  var slotCenterX = stepX/2;
  var slotCenterY = stepY/2;

  var lastX = left + slotCenterX;
  var lastY = top - slotCenterY;

  for(var i = 0; i < this.maxChildren; ++i){
    if(i == 0){
      var x = lastX;
      var y = lastY;
    }
    else if((i%this.density) == 0){
      //changing a row
      var x = left + slotCenterX;
      var y = lastY - stepY;
    }
    else{
      //going onwords normally
      var x = lastX + stepY;
      var y = lastY;
    }

    lastX = x;
    lastY = y;
    this.gridIndexes.push({x: x, y: y, z: pos.z/this.height});
  }
};

//---------------------------------------------------
// ICONS FOR GRIDWINDOW
//---------------------------------------------------
WIDGET3D.GridIcon = function(parameters){

  WIDGET3D.Basic.call( this );

  var parameters = parameters || {};

  var parent = parameters.parent;
  if(parent == undefined){
    console.log("GridIcon needs to have grid window as parent!");
    console.log("Parent has to be given in parameters.");
    return false;
  }

  this.color = parameters.color !== undefined ? parameters.color : 0xFFFFFF;
  this.url = parameters.url !== undefined ? parameters.url : false;
  this.img = parameters.img !== undefined ? parameters.img : false;
  //object can store metadata in a format that user like
  this.metadata = parameters.metadata !== undefined ? parameters.metadata : false;

  this.width = parent.width/(parent.density + 3.3);
  this.height = parent.height/(parent.density + 3.3);

  this.depth = parameters.depth !== undefined ? parameters.depth : this.height;

  var geometry = new THREE.CubeGeometry(this.width, this.height, this.depth);

  this.texture = false;

  if(this.img){
    this.texture = new THREE.Texture(this.img);
    this.texture.needsUpdate = true;
  }
  else if(this.url){
    this.texture = THREE.ImageUtils.loadTexture(this.url);
  }

  this.material = new THREE.MeshBasicMaterial({map : this.texture, color: this.color});

  var mesh = new THREE.Mesh( geometry, this.material);

  this.setObject3D(mesh);
  parent.add(this);
};

WIDGET3D.GridIcon.prototype = WIDGET3D.Basic.prototype.inheritance();

WIDGET3D.GridIcon.prototype.setNewSize = function(){
  this.width = this.parent.width/(this.parent.density + 3.3);
  this.height = this.parent.height/(this.parent.density + 3.3);

  var geometry = new THREE.CubeGeometry(this.width, this.height, this.depth);
  var mesh = new THREE.Mesh( geometry, this.material);
  this.setObject3D(mesh);
};
<commit_msg>Fixes to removing and adding back children<commit_after>//---------------------------------------------------
//
// STYLED WIDGETS THAT CAN BE USED WITH THREEJS PLUGIN
//
//---------------------------------------------------

//---------------------------------------------------
// GRID LAYOUTED Group
//---------------------------------------------------
//
// PARAMETERS:  width = width in world coordinates
//              height = height in world coordinates
//              color = hexadecimal
//

WIDGET3D.GridWindow = function(parameters){

  var that = this;

  WIDGET3D.Group.call( this );

  var parameters = parameters || {};

  this.width = parameters.width !== undefined ? parameters.width : 1000;
  this.height = parameters.height !== undefined ? parameters.height : 1000;
  this.density = parameters.density !== undefined ? parameters.density : 6;
  this.depth = (this.width/this.density);

  this.maxChildren = this.density * this.density;

  this.color = parameters.color !== undefined ? parameters.color : 0x6B6B6B;
  this.lineWidth = parameters.lineWidth !== undefined ? parameters.lineWidth : 2;
  this.opacity = parameters.opacity !== undefined ? parameters.opacity : 0.5;

  this.material = new THREE.MeshBasicMaterial({
    color: this.color,
    opacity: this.opacity,
    wireframe: true,
    side : THREE.DoubleSide,
    wireframeLinewidth : this.lineWidth
  });

  var geometry = new THREE.CubeGeometry( this.width, this.height, this.depth, this.density, this.density, 1 );
  var mesh =  new THREE.Mesh(geometry, this.material);
  this.grid = new WIDGET3D.Basic();
  this.grid.setObject3D(mesh);

  var hideGrid = parameters.hideGrid !== undefined ? parameters.hideGrid : false;
  if(hideGrid){
    this.grid.hide();
  }
  this.add(this.grid);

  this.icons = new Array();
  this.gridIndexes = new Array();

  //default mouse controls in use
  this.defaultControls = parameters.defaultControls !== undefined ? parameters.defaultControls : false;

  if(this.defaultControls){

    var button = parameters.mouseButton !== undefined ? parameters.mouseButton : 0;
    var shift = parameters.shiftKey !== undefined ? parameters.shiftKey : false;

    var control = new WIDGET3D.RollControl(this, {mouseButton : button, shiftKey : shift});
  }

  this.calculateGrid();
};

WIDGET3D.GridWindow.prototype = WIDGET3D.Group.prototype.inheritance();

WIDGET3D.GridWindow.prototype.addSlots = function(newDensity){
  this.density = newDensity;
  this.maxChildren = newDensity * newDensity;
  this.depth = (this.width/this.density);

  var grid = new THREE.CubeGeometry( this.width, this.height, this.depth, this.density, this.density, 1 );
  var gridMesh =  new THREE.Mesh(grid, this.material);
  this.grid.setObject3D(gridMesh);

  //calculating the new indexes
  this.calculateGrid();

  var tmpChilds = this.icons;
  this.icons = new Array();

  for(var i = 0; i < tmpChilds.length; ++i){

    var icon = tmpChilds[i];
    this.icons.push(icon);

    icon.setNewSize();

    var pos = this.gridIndexes[i];
    icon.setPosition(pos.x, pos.y, pos.z);

  }

  return this;
};

//Adds children to the group
WIDGET3D.GridWindow.prototype.add = function(child){

  if(child != this){
    if(child.parent){
      //removing event listeners from former parent
      if(child.parent != WIDGET3D.getApplication()){
        child.parent.removeRelatedEventListeners(child);
      }
      child.parent.removeFromObjects(child);
    }
    child.parent = this;
    this.children.push(child);
    this.object3D.add(child.object3D);

    if(child != this.grid){
      this.icons.push(child);
      child.setNewSize();
      if(this.icons.length > this.maxChildren){
        console.log("Grid is full! Creating bigger one");
        this.addSlots(Math.ceil(this.density * 1.5));
      }
      else{
        pos = this.gridIndexes[this.icons.length-1];
        child.setPosition(pos.x, pos.y, pos.z);
      }
    }

    return this;
  }
  else{
    console.log("You can't add object to it self!");
    return false;
  }

};

WIDGET3D.GridWindow.prototype.removeFromIcons = function(child){
  for(var k = 0; k < this.icons.length; ++k){
    if(this.icons[k] === child){
      var removedObj = this.icons.splice(k, 1);
      return removedObj[0];
    }
  }
  return false;
};

//removes object in place 'index' from object list
WIDGET3D.GridWindow.prototype.removeFromObjects = function(child){
  WIDGET3D.Group.prototype.removeFromObjects.call(this, child);
  this.removeFromIcons(child);

  for(var i = 0; i < this.icons.length; ++i){
    var icon = this.icons[i];
    var pos = this.gridIndexes[i];
    icon.setPosition(pos.x, pos.y, pos.z);
  }

};

WIDGET3D.GridWindow.prototype.calculateGrid = function(){
  this.gridIndexes = new Array();
  var pos = this.getPosition();

  var left = -this.width/2.0 + pos.x/this.width;
  var top =  this.height/2.0 + pos.y/this.height;

  var stepX = this.width/this.density;
  var stepY = this.height/this.density;

  var slotCenterX = stepX/2;
  var slotCenterY = stepY/2;

  var lastX = left + slotCenterX;
  var lastY = top - slotCenterY;

  for(var i = 0; i < this.maxChildren; ++i){
    if(i == 0){
      var x = lastX;
      var y = lastY;
    }
    else if((i%this.density) == 0){
      //changing a row
      var x = left + slotCenterX;
      var y = lastY - stepY;
    }
    else{
      //going onwords normally
      var x = lastX + stepY;
      var y = lastY;
    }

    lastX = x;
    lastY = y;
    this.gridIndexes.push({x: x, y: y, z: pos.z/this.height});
  }
};

//---------------------------------------------------
// ICONS FOR GRIDWINDOW
//---------------------------------------------------
WIDGET3D.GridIcon = function(parameters){

  WIDGET3D.Widget.call( this );

  var parameters = parameters || {};

  var parent = parameters.parent;
  if(parent == undefined){
    console.log("GridIcon needs to have grid window as parent!");
    console.log("Parent has to be given in parameters.");
    return false;
  }

  this.color = parameters.color !== undefined ? parameters.color : 0xFFFFFF;
  this.url = parameters.url !== undefined ? parameters.url : false;
  this.img = parameters.img !== undefined ? parameters.img : false;
  //object can store metadata in a format that user like
  this.metadata = parameters.metadata !== undefined ? parameters.metadata : false;

  this.width = parent.width/(parent.density + 3.3);
  this.height = parent.height/(parent.density + 3.3);

  this.depth = parameters.depth !== undefined ? parameters.depth : this.height;

  var geometry = new THREE.CubeGeometry(this.width, this.height, this.depth);

  this.texture = false;

  if(this.img){
    this.texture = new THREE.Texture(this.img);
    this.texture.needsUpdate = true;
  }
  else if(this.url){
    this.texture = THREE.ImageUtils.loadTexture(this.url);
  }

  this.material = new THREE.MeshBasicMaterial({map : this.texture, color: this.color});

  var mesh = new THREE.Mesh( geometry, this.material);

  this.setObject3D(mesh);
  parent.add(this);
};

WIDGET3D.GridIcon.prototype = WIDGET3D.Widget.prototype.inheritance();

WIDGET3D.GridIcon.prototype.setNewSize = function(){
  this.width = this.parent.width/(this.parent.density + 3.3);
  this.height = this.parent.height/(this.parent.density + 3.3);

  var geometry = new THREE.CubeGeometry(this.width, this.height, this.depth);
  var mesh = new THREE.Mesh( geometry, this.material);
  this.setObject3D(mesh);
};
<|endoftext|>


<commit_before>
trace1 = {
    x: capacityTable.headings,
    y: capacityTable.rows[0].values,
    line: {width: 3.5},
    marker: {
        line: {width: -0.5},
        size: 9
    },
    name: 'Capacity %',
};
trace2 = {
    x: capacityTable.headings,
    y: capacityTable.rows[1].values,
    line: {width: 3.5},
    marker: {
        line: {width: -0.5},
        size: 9
    },
    name: 'Reduction Hours',
};
data = [trace1, trace2];
layout = {
    hovermode: 'closest',
    legend: {
        x: 0.436873550022,
        y: -0.208008047869,
        borderwidth: 0,
        font: {size: 12},
        orientation: 'h'
    },
    margin: {
        r: 0,
        t: 40,
        b: 60,
        l: 50
    },
    shapes: [
    {
        fillcolor: 'rgb(109, 206, 250)',
        line: {
            color: 'rgba(68, 68, 68, 100)',
            dash: 'dot',
            width: 1
        },
        opacity: 0.3,
        type: 'rectangle',
        x0: 0,
        x1: 1,
        xref: 'paper',
        y0: 90,
        y1: 110,
        yref: 'y'
    }
    ],
    showlegend: true,
    xaxis: {
        autorange: true,
        fixedrange: true,
        mirror: false,
        nticks: 0,
        showgrid: false,
        showline: true,
        side: 'bottom',
        ticks: 'outside',
        title: '2016',
        titlefont: {size: 15},
        rangeselector: {buttons: [
        {
          count: 1,
          label: '1 Month',
          step: 'month',
          stepmode: 'backward'
        },
        {
          count: 6,
          label: '6 Months',
          step: 'month',
          stepmode: 'backward'
        },
        {
          count: 12,
          label: '12 Months',
          step: 'month',
          stepmode: 'backward'
        },
        {
          count: 18,
          label: '18 Months',
          step: 'month',
          stepmode: 'backward'
        },
        {step: 'all'}
      ]},
        type: 'date',
        zeroline: false,
        zerolinewidth: 11
    },
    yaxis: {
        autorange: false,
        fixedrange: true,
        range: [0, 120],
        showline: true,
        ticks: 'outside',
        title: 'Capacity %',
        type: 'linear'
    }
};
Plotly.plot('plotly-div-line', {
    data: data,
    layout: layout
});<commit_msg>Removing all button<commit_after>
trace1 = {
    x: capacityTable.headings,
    y: capacityTable.rows[0].values,
    line: {width: 3.5},
    marker: {
        line: {width: -0.5},
        size: 9
    },
    name: 'Capacity %',
};
trace2 = {
    x: capacityTable.headings,
    y: capacityTable.rows[1].values,
    line: {width: 3.5},
    marker: {
        line: {width: -0.5},
        size: 9
    },
    name: 'Reduction Hours',
};
data = [trace1, trace2];
layout = {
    hovermode: 'closest',
    legend: {
        x: 0.436873550022,
        y: -0.208008047869,
        borderwidth: 0,
        font: {size: 12},
        orientation: 'h'
    },
    margin: {
        r: 0,
        t: 40,
        b: 60,
        l: 50
    },
    shapes: [
    {
        fillcolor: 'rgb(109, 206, 250)',
        line: {
            color: 'rgba(68, 68, 68, 100)',
            dash: 'dot',
            width: 1
        },
        opacity: 0.3,
        type: 'rectangle',
        x0: 0,
        x1: 1,
        xref: 'paper',
        y0: 90,
        y1: 110,
        yref: 'y'
    }
    ],
    showlegend: true,
    xaxis: {
        autorange: true,
        fixedrange: true,
        mirror: false,
        nticks: 0,
        showgrid: false,
        showline: true,
        side: 'bottom',
        ticks: 'outside',
        title: '2016',
        titlefont: {size: 15},
        rangeselector: {buttons: [
        {
          count: 1,
          label: '1 Month',
          step: 'month',
          stepmode: 'backward'
        },
        {
          count: 6,
          label: '6 Months',
          step: 'month',
          stepmode: 'backward'
        },
        {
          count: 12,
          label: '12 Months',
          step: 'month',
          stepmode: 'backward'
        },
        {
          count: 18,
          label: '18 Months',
          step: 'month',
          stepmode: 'backward'
        }
      ]},
        type: 'date',
        zeroline: false,
        zerolinewidth: 11
    },
    yaxis: {
        autorange: false,
        fixedrange: true,
        range: [0, 120],
        showline: true,
        ticks: 'outside',
        title: 'Capacity %',
        type: 'linear'
    }
};
Plotly.plot('plotly-div-line', {
    data: data,
    layout: layout
});<|endoftext|>


<commit_before>I18n.translations || (I18n.translations = {});
I18n.translations["en"] = {"js":{"fail":{"mark_feed_as_read":"\u003cstrong\u003eFeed\u003c/strong\u003e couldn't be marked as read","navigate":"\u003cstrong\u003eRequested Page\u003c/strong\u003e couldn't be loaded","subscribe":"\u003cstrong\u003eFeed\u003c/strong\u003e couldn't be subscribed","toggle_read":"\u003cstrong\u003eItem\u003c/strong\u003e couldn't be changed","upload":"\u003cstrong\u003eOPML\u003c/strong\u003e couldn't be uploaded"},"feed":{"updated":"\u003cstrong\u003eNews entries\u003c/strong\u003e updated"}}};
<commit_msg>Update translations<commit_after>var I18n = I18n || {};
I18n.translations = {"en":{"js":{"fail":{"navigate":"\u003cstrong\u003eRequested Page\u003c/strong\u003e couldn't be loaded","mark_feed_as_read":"\u003cstrong\u003eFeed\u003c/strong\u003e couldn't be marked as read","toggle_read":"\u003cstrong\u003eItem\u003c/strong\u003e couldn't be changed","subscribe":"\u003cstrong\u003eFeed\u003c/strong\u003e couldn't be subscribed","upload":"\u003cstrong\u003eOPML\u003c/strong\u003e couldn't be uploaded"},"feed":{"updated":"\u003cstrong\u003eNews entries\u003c/strong\u003e updated"}}}};<|endoftext|>


<commit_before>const reqr = global.GENTLY ? GENTLY.hijack(require) : require
const request = reqr('request')
const crypto = reqr('crypto')
const _ = reqr('underscore')
const fs = reqr('fs')
const retry = reqr('retry')
const PaginationStream = reqr('./PaginationStream')

let unknownErrMsg = 'Unknown error. Please report this at '
unknownErrMsg += 'https://github.com/transloadit/node-sdk/issues/new?title=Unknown%20error'

class TransloaditClient {
  constructor (opts = {}) {
    if (opts.useSsl == null) {
      opts.useSsl = true
    }

    if (opts.authKey == null) {
      throw new Error('Please provide an authKey')
    }

    if (opts.authSecret == null) {
      throw new Error('Please provide an authSecret')
    }

    this._authKey = opts.authKey
    this._authSecret = opts.authSecret
    this._service = opts.service || 'api2.transloadit.com'
    this._region = opts.region || 'us-east-1'
    this._protocol = opts.useSsl ? 'https://' : 'http://'
    this._streams = {}

    this._lastUsedAssemblyUrl = ''
  }

  addStream (name, stream) {
    stream.pause()
    this._streams[name] = stream
  }

  addFile (name, path) {
    const stream = fs.createReadStream(path)
    stream.on('error', err => {
      // handle the error event to avoid the error being thrown
      console.error(err)
    })
    this.addStream(name, stream)
  }

  getLastUsedAssemblyUrl () {
    return this._lastUsedAssemblyUrl
  }

  createAssembly (opts, cb) {
    const defaultOpts = {
      params: {},
      fields: {},
      waitForCompletion: false
    }
    opts = _.extend(defaultOpts, opts)

    let stream
    const callback = cb
    let called = false
    cb = (err, result) => {
      if (!called) {
        called = true
        callback(err, result)
      }
    }

    this._lastUsedAssemblyUrl = `${this._serviceUrl()}/assemblies`

    const requestOpts = {
      url    : this._lastUsedAssemblyUrl,
      method : 'post',
      timeout: 24 * 60 * 60 * 1000, // 1 day
      params : opts.params,
      fields : opts.fields,
    }

    const streams = (() => {
      const result = []
      for (let label in this._streams) {
        stream = this._streams[label]
        result.push(stream)
      }
      return result
    })()

    const sendRequest = () => {
      this._remoteJson(requestOpts, (err, result = {}) => {
        // reset streams so they do not get used again in subsequent requests
        this._streams = {}

        if (!err && result.error != null) {
          err = new Error(result.error)
        }

        if (err) {
          return cb(err)
        }

        if (!opts.waitForCompletion) {
          return cb(null, result)
        }

        this.awaitAssemblyCompletion(result.assembly_id, cb)
      })
    }

    let ncompleted = 0
    const streamErrCb = err => {
      if (err != null) {
        cb(err)
      }

      if (++ncompleted === streams.length) {
        sendRequest()
      }
    }

    for (stream of Array.from(streams)) {
      stream.on('error', cb)

      if (stream.path == null) {
        streamErrCb(null)
        continue
      }

      fs.access(stream.path, fs.F_OK | fs.R_OK, err => {
        if (err != null) {
          return streamErrCb(err)
        }

        streamErrCb(null)
      })
    }

    // make sure sendRequest gets called when there are no @_streams
    if (streams.length === 0) {
      sendRequest()
    }
  }

  awaitAssemblyCompletion (assemblyId, cb) {
    this.getAssembly(assemblyId, (err, result) => {
      if (!err && result.error != null) {
        err = new Error(result.error)
      }

      if (err) {
        return cb(err)
      }

      if (result.ok === 'ASSEMBLY_COMPLETED') {
        return cb(null, result)
      }

      if (result.ok === 'ASSEMBLY_UPLOADING' || result.ok === 'ASSEMBLY_EXECUTING') {
        return setTimeout(() => {
          this.awaitAssemblyCompletion(assemblyId, cb)
        }, 1 * 1000)
      }

      return cb(new Error(unknownErrMsg))
    })
  }

  deleteAssembly (assemblyId, cb) {
    this.getAssembly(assemblyId, (err, { assembly_url } = {}) => {
      if (err != null) {
        return cb(err)
      }

      const opts = {
        url    : assembly_url,
        timeout: 5000,
        method : 'del',
        params : {},
      }

      this._remoteJson(opts, cb)
    })
  }

  replayAssembly ({ assembly_id: assemblyId, notify_url: notifyUrl }, cb) {
    const requestOpts = {
      url   : this._serviceUrl() + `/assemblies/${assemblyId}/replay`,
      method: 'post',
    }

    if (notifyUrl != null) {
      requestOpts.params = { notifyUrl }
    }

    this._remoteJson(requestOpts, cb)
  }

  replayAssemblyNotification ({ assembly_id: assemblyId, notify_url: notifyUrl }, cb) {
    const requestOpts = {
      url   : this._serviceUrl() + `/assembly_notifications/${assemblyId}/replay`,
      method: 'post',
    }

    if (notifyUrl != null) {
      requestOpts.params = { notifyUrl }
    }

    this._remoteJson(requestOpts, cb)
  }

  listAssemblyNotifications (params, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/assembly_notifications`,
      method: 'get',
      params: params || {},
    }

    this._remoteJson(requestOpts, cb)
  }

  streamAssemblyNotifications (params) {
    return new PaginationStream((pageno, cb) => {
      this.listAssemblyNotifications(_.extend({}, params, { page: pageno }), cb)
    })
  }

  listAssemblies (params, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/assemblies`,
      method: 'get',
      params: params || {},
    }

    this._remoteJson(requestOpts, cb)
  }

  streamAssemblies (params) {
    return new PaginationStream((pageno, cb) => {
      this.listAssemblies(_.extend({}, params, { page: pageno }), cb)
    })
  }

  getAssembly (assemblyId, cb) {
    const opts = { url: this._serviceUrl() + `/assemblies/${assemblyId}` }

    const retryOpts = {
      retries   : 5,
      factor    : 3.28,
      minTimeout: 1 * 1000,
      maxTimeout: 8 * 1000,
    }

    const operation = retry.operation(retryOpts)
    operation.attempt(attempt => {
      this._remoteJson(opts, (err, result) => {
        if (err != null) {
          if (operation.retry(err)) {
            return
          }

          return cb(operation.mainError())
        }

        if (result.assembly_url == null || result.assembly_ssl_url == null) {
          if (operation.retry(new Error('got incomplete assembly status response'))) {
            return
          }

          return cb(operation.mainError())
        }

        cb(null, result)
      })
    })
  }

  createTemplate (params, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/templates`,
      method: 'post',
      params: params || {},
    }

    this._remoteJson(requestOpts, (err, result) => {
      let left
      if (err) {
        return cb(err)
      }

      if (result && result.ok) {
        return cb(null, result)
      }

      err = new Error((left = result.error != null ? result.error : result.message) != null ? left : unknownErrMsg)
      cb(err)
    })
  }

  editTemplate (templateId, params, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/templates/${templateId}`,
      method: 'put',
      params: params || {},
    }

    this._remoteJson(requestOpts, (err, result) => {
      let left
      if (err) {
        return cb(err)
      }

      if (result && result.ok) {
        return cb(null, result)
      }

      err = new Error((left = result.error != null ? result.error : result.message) != null ? left : unknownErrMsg)
      cb(err)
    })
  }

  deleteTemplate (templateId, cb) {
    const requestOpts = {
      url   : this._serviceUrl() + `/templates/${templateId}`,
      method: 'del',
      params: {},
    }

    this._remoteJson(requestOpts, cb)
  }

  getTemplate (templateId, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/templates/${templateId}`,
      method: 'get',
      params: {},
    }

    this._remoteJson(requestOpts, cb)
  }

  listTemplates (params, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/templates`,
      method: 'get',
      params: params || {},
    }

    this._remoteJson(requestOpts, cb)
  }

  streamTemplates (params) {
    return new PaginationStream((pageno, cb) => {
      this.listTemplates(_.extend({}, params, { page: pageno }), cb)
    })
  }

  getBill (month, cb) {
    const requestOpts = {
      url   : this._serviceUrl() + `/bill/${month}`,
      method: 'get',
      params: {},
    }

    this._remoteJson(requestOpts, cb)
  }

  calcSignature (params) {
    const jsonParams = this._prepareParams(params)
    const signature = this._calcSignature(jsonParams)

    return { signature, params: jsonParams }
  }

  _calcSignature (toSign) {
    return crypto
      .createHmac('sha1', this._authSecret)
      .update(Buffer.from(toSign, 'utf-8'))
      .digest('hex')
  }

  // Sets the multipart/form-data for POST, PUT and DELETE requests, including
  // the streams, the signed params, and any additional fields.
  _appendForm (req, params, fields) {
    const sigData = this.calcSignature(params)
    const jsonParams = sigData.params
    const { signature } = sigData
    const form = req.form()

    form.append('params', jsonParams)

    if (fields == null) {
      fields = []
    }

    for (let key in fields) {
      let val = fields[key]
      if (_.isObject(fields[key]) || _.isArray(fields[key])) {
        val = JSON.stringify(fields[key])
      }

      form.append(key, val)
    }

    form.append('signature', signature)

    _.each(this._streams, (value, key) => form.append(key, value))
  }

  // Implements HTTP GET query params, handling the case where the url already
  // has params.
  _appendParamsToUrl (url, params) {
    const sigData = this.calcSignature(params)
    const { signature } = sigData
    let jsonParams = sigData.params

    if (url.indexOf('?') === -1) {
      url += `?signature=${signature}`
    } else {
      url += `&signature=${signature}`
    }

    jsonParams = encodeURIComponent(jsonParams)
    url += `&params=${jsonParams}`

    return url
  }

  // Responsible for including auth parameters in all requests
  _prepareParams (params) {
    if (params == null) {
      params = {}
    }
    if (params.auth == null) {
      params.auth = {}
    }
    if (params.auth.key == null) {
      params.auth.key = this._authKey
    }
    if (params.auth.expires == null) {
      params.auth.expires = this._getExpiresDate()
    }

    return JSON.stringify(params)
  }

  _getExpiresDate () {
    const expiresDate = new Date()
    expiresDate.setDate(expiresDate.getDate() + 1)
    return expiresDate.toISOString()
  }

  _serviceUrl () {
    return this._protocol + this._service
  }

  // Wrapper around __remoteJson which will retry in case of error
  _remoteJson (opts, cb) {
    const operation = retry.operation({
      retries   : 5,
      factor    : 3.28,
      minTimeout: 1 * 1000,
      maxTimeout: 8 * 1000,
    })

    operation.attempt(() => {
      this.__remoteJson(opts, (err, result) => {
        if (err != null) {
          if (err.error === 'RATE_LIMIT_REACHED') {
            console.warn(`Rate limit reached, retrying request in ${err.info.retryIn} seconds.`)
            // FIXME uses private internals of node-retry
            operation._timeouts.unshift(1000 * err.info.retryIn)
            return operation.retry(err)
          }

          if (err.code === 'ENOTFOUND') {
            console.warn(`The network connection is down, retrying request in 3 seconds.`)
            // FIXME uses private internals of node-retry
            operation._timeouts.unshift(3 * 1000)
            return operation.retry(err)
          }

          if (err.error === 'GET_ACCOUNT_UNKNOWN_AUTH_KEY') {
            console.warn(`Invalid auth key provided.`)
            return cb(err)
          }

          if (err.error !== undefined) {
            console.warn(err.error + ' - ' + err.message)
            return cb(err)
          }
        }

        if (operation.retry(err)) {
          return
        }

        let mainError = null
        if (err) {
          mainError = operation.mainError()
        }

        cb(mainError, result)
      })
    })
  }

  // Responsible for making API calls. Automatically sends streams with any POST,
  // PUT or DELETE requests. Automatically adds signature parameters to all
  // requests. Also automatically parses the JSON response.
  __remoteJson (opts, cb) {
    const timeout = opts.timeout || 5000
    let url = opts.url || null
    const method = opts.method || 'get'

    if (!url) {
      const err = new Error('No url provided!')
      return cb(err)
    }

    if (method === 'get' && opts.params != null) {
      url = this._appendParamsToUrl(url, opts.params)
    }

    const requestOpts = {
      uri: url,
      timeout,
    }

    if (opts.headers != null) {
      requestOpts.headers = opts.headers
    }

    const req = request[method](requestOpts, (err, { body, statusCode } = {}) => {
      if (err) {
        return cb(err)
      }

      // parse body
      let result = null
      try {
        result = JSON.parse(body)
      } catch (e) {
        const abbr = `${body}`.substr(0, 255)
        let msg = `Unable to parse JSON from '${requestOpts.uri}'. `
        msg += `Code: ${statusCode}. Body: ${abbr}. `
        return cb(new Error(msg))
      }
      if (statusCode !== 200 && statusCode !== 404 && statusCode >= 400 && statusCode <= 599) {
        return cb(_.extend(new Error(), result))
      }

      return cb(null, result)
    })

    if (method === 'post' || method === 'put' || method === 'del') {
      this._appendForm(req, opts.params, opts.fields)
    }
  }
}

module.exports = TransloaditClient
<commit_msg>fix: check path for only direct Readable instances (#50)<commit_after>const reqr = global.GENTLY ? GENTLY.hijack(require) : require
const request = reqr('request')
const crypto = reqr('crypto')
const _ = reqr('underscore')
const fs = reqr('fs')
const retry = reqr('retry')
const PaginationStream = reqr('./PaginationStream')
const Readable = reqr('stream').Readable

let unknownErrMsg = 'Unknown error. Please report this at '
unknownErrMsg += 'https://github.com/transloadit/node-sdk/issues/new?title=Unknown%20error'

class TransloaditClient {
  constructor (opts = {}) {
    if (opts.useSsl == null) {
      opts.useSsl = true
    }

    if (opts.authKey == null) {
      throw new Error('Please provide an authKey')
    }

    if (opts.authSecret == null) {
      throw new Error('Please provide an authSecret')
    }

    this._authKey = opts.authKey
    this._authSecret = opts.authSecret
    this._service = opts.service || 'api2.transloadit.com'
    this._region = opts.region || 'us-east-1'
    this._protocol = opts.useSsl ? 'https://' : 'http://'
    this._streams = {}

    this._lastUsedAssemblyUrl = ''
  }

  addStream (name, stream) {
    stream.pause()
    this._streams[name] = stream
  }

  addFile (name, path) {
    const stream = fs.createReadStream(path)
    stream.on('error', err => {
      // handle the error event to avoid the error being thrown
      console.error(err)
    })
    this.addStream(name, stream)
  }

  getLastUsedAssemblyUrl () {
    return this._lastUsedAssemblyUrl
  }

  createAssembly (opts, cb) {
    const defaultOpts = {
      params: {},
      fields: {},
      waitForCompletion: false
    }
    opts = _.extend(defaultOpts, opts)

    let stream
    const callback = cb
    let called = false
    cb = (err, result) => {
      if (!called) {
        called = true
        callback(err, result)
      }
    }

    this._lastUsedAssemblyUrl = `${this._serviceUrl()}/assemblies`

    const requestOpts = {
      url    : this._lastUsedAssemblyUrl,
      method : 'post',
      timeout: 24 * 60 * 60 * 1000, // 1 day
      params : opts.params,
      fields : opts.fields,
    }

    const streams = (() => {
      const result = []
      for (let label in this._streams) {
        stream = this._streams[label]
        result.push(stream)
      }
      return result
    })()

    const sendRequest = () => {
      this._remoteJson(requestOpts, (err, result = {}) => {
        // reset streams so they do not get used again in subsequent requests
        this._streams = {}

        if (!err && result.error != null) {
          err = new Error(result.error)
        }

        if (err) {
          return cb(err)
        }

        if (!opts.waitForCompletion) {
          return cb(null, result)
        }

        this.awaitAssemblyCompletion(result.assembly_id, cb)
      })
    }

    let ncompleted = 0
    const streamErrCb = err => {
      if (err != null) {
        cb(err)
      }

      if (++ncompleted === streams.length) {
        sendRequest()
      }
    }

    for (stream of Array.from(streams)) {
      stream.on('error', cb)

      // because an http response stream could also have a "path"
      // attribute but not referring to the local file system
      // see https://github.com/transloadit/node-sdk/pull/50#issue-261982855
      if (stream.path == null || !(stream instanceof Readable)) {
        streamErrCb(null)
        continue
      }

      fs.access(stream.path, fs.F_OK | fs.R_OK, err => {
        if (err != null) {
          return streamErrCb(err)
        }

        streamErrCb(null)
      })
    }

    // make sure sendRequest gets called when there are no @_streams
    if (streams.length === 0) {
      sendRequest()
    }
  }

  awaitAssemblyCompletion (assemblyId, cb) {
    this.getAssembly(assemblyId, (err, result) => {
      if (!err && result.error != null) {
        err = new Error(result.error)
      }

      if (err) {
        return cb(err)
      }

      if (result.ok === 'ASSEMBLY_COMPLETED') {
        return cb(null, result)
      }

      if (result.ok === 'ASSEMBLY_UPLOADING' || result.ok === 'ASSEMBLY_EXECUTING') {
        return setTimeout(() => {
          this.awaitAssemblyCompletion(assemblyId, cb)
        }, 1 * 1000)
      }

      return cb(new Error(unknownErrMsg))
    })
  }

  deleteAssembly (assemblyId, cb) {
    this.getAssembly(assemblyId, (err, { assembly_url } = {}) => {
      if (err != null) {
        return cb(err)
      }

      const opts = {
        url    : assembly_url,
        timeout: 5000,
        method : 'del',
        params : {},
      }

      this._remoteJson(opts, cb)
    })
  }

  replayAssembly ({ assembly_id: assemblyId, notify_url: notifyUrl }, cb) {
    const requestOpts = {
      url   : this._serviceUrl() + `/assemblies/${assemblyId}/replay`,
      method: 'post',
    }

    if (notifyUrl != null) {
      requestOpts.params = { notifyUrl }
    }

    this._remoteJson(requestOpts, cb)
  }

  replayAssemblyNotification ({ assembly_id: assemblyId, notify_url: notifyUrl }, cb) {
    const requestOpts = {
      url   : this._serviceUrl() + `/assembly_notifications/${assemblyId}/replay`,
      method: 'post',
    }

    if (notifyUrl != null) {
      requestOpts.params = { notifyUrl }
    }

    this._remoteJson(requestOpts, cb)
  }

  listAssemblyNotifications (params, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/assembly_notifications`,
      method: 'get',
      params: params || {},
    }

    this._remoteJson(requestOpts, cb)
  }

  streamAssemblyNotifications (params) {
    return new PaginationStream((pageno, cb) => {
      this.listAssemblyNotifications(_.extend({}, params, { page: pageno }), cb)
    })
  }

  listAssemblies (params, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/assemblies`,
      method: 'get',
      params: params || {},
    }

    this._remoteJson(requestOpts, cb)
  }

  streamAssemblies (params) {
    return new PaginationStream((pageno, cb) => {
      this.listAssemblies(_.extend({}, params, { page: pageno }), cb)
    })
  }

  getAssembly (assemblyId, cb) {
    const opts = { url: this._serviceUrl() + `/assemblies/${assemblyId}` }

    const retryOpts = {
      retries   : 5,
      factor    : 3.28,
      minTimeout: 1 * 1000,
      maxTimeout: 8 * 1000,
    }

    const operation = retry.operation(retryOpts)
    operation.attempt(attempt => {
      this._remoteJson(opts, (err, result) => {
        if (err != null) {
          if (operation.retry(err)) {
            return
          }

          return cb(operation.mainError())
        }

        if (result.assembly_url == null || result.assembly_ssl_url == null) {
          if (operation.retry(new Error('got incomplete assembly status response'))) {
            return
          }

          return cb(operation.mainError())
        }

        cb(null, result)
      })
    })
  }

  createTemplate (params, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/templates`,
      method: 'post',
      params: params || {},
    }

    this._remoteJson(requestOpts, (err, result) => {
      let left
      if (err) {
        return cb(err)
      }

      if (result && result.ok) {
        return cb(null, result)
      }

      err = new Error((left = result.error != null ? result.error : result.message) != null ? left : unknownErrMsg)
      cb(err)
    })
  }

  editTemplate (templateId, params, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/templates/${templateId}`,
      method: 'put',
      params: params || {},
    }

    this._remoteJson(requestOpts, (err, result) => {
      let left
      if (err) {
        return cb(err)
      }

      if (result && result.ok) {
        return cb(null, result)
      }

      err = new Error((left = result.error != null ? result.error : result.message) != null ? left : unknownErrMsg)
      cb(err)
    })
  }

  deleteTemplate (templateId, cb) {
    const requestOpts = {
      url   : this._serviceUrl() + `/templates/${templateId}`,
      method: 'del',
      params: {},
    }

    this._remoteJson(requestOpts, cb)
  }

  getTemplate (templateId, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/templates/${templateId}`,
      method: 'get',
      params: {},
    }

    this._remoteJson(requestOpts, cb)
  }

  listTemplates (params, cb) {
    const requestOpts = {
      url   : `${this._serviceUrl()}/templates`,
      method: 'get',
      params: params || {},
    }

    this._remoteJson(requestOpts, cb)
  }

  streamTemplates (params) {
    return new PaginationStream((pageno, cb) => {
      this.listTemplates(_.extend({}, params, { page: pageno }), cb)
    })
  }

  getBill (month, cb) {
    const requestOpts = {
      url   : this._serviceUrl() + `/bill/${month}`,
      method: 'get',
      params: {},
    }

    this._remoteJson(requestOpts, cb)
  }

  calcSignature (params) {
    const jsonParams = this._prepareParams(params)
    const signature = this._calcSignature(jsonParams)

    return { signature, params: jsonParams }
  }

  _calcSignature (toSign) {
    return crypto
      .createHmac('sha1', this._authSecret)
      .update(Buffer.from(toSign, 'utf-8'))
      .digest('hex')
  }

  // Sets the multipart/form-data for POST, PUT and DELETE requests, including
  // the streams, the signed params, and any additional fields.
  _appendForm (req, params, fields) {
    const sigData = this.calcSignature(params)
    const jsonParams = sigData.params
    const { signature } = sigData
    const form = req.form()

    form.append('params', jsonParams)

    if (fields == null) {
      fields = []
    }

    for (let key in fields) {
      let val = fields[key]
      if (_.isObject(fields[key]) || _.isArray(fields[key])) {
        val = JSON.stringify(fields[key])
      }

      form.append(key, val)
    }

    form.append('signature', signature)

    _.each(this._streams, (value, key) => form.append(key, value))
  }

  // Implements HTTP GET query params, handling the case where the url already
  // has params.
  _appendParamsToUrl (url, params) {
    const sigData = this.calcSignature(params)
    const { signature } = sigData
    let jsonParams = sigData.params

    if (url.indexOf('?') === -1) {
      url += `?signature=${signature}`
    } else {
      url += `&signature=${signature}`
    }

    jsonParams = encodeURIComponent(jsonParams)
    url += `&params=${jsonParams}`

    return url
  }

  // Responsible for including auth parameters in all requests
  _prepareParams (params) {
    if (params == null) {
      params = {}
    }
    if (params.auth == null) {
      params.auth = {}
    }
    if (params.auth.key == null) {
      params.auth.key = this._authKey
    }
    if (params.auth.expires == null) {
      params.auth.expires = this._getExpiresDate()
    }

    return JSON.stringify(params)
  }

  _getExpiresDate () {
    const expiresDate = new Date()
    expiresDate.setDate(expiresDate.getDate() + 1)
    return expiresDate.toISOString()
  }

  _serviceUrl () {
    return this._protocol + this._service
  }

  // Wrapper around __remoteJson which will retry in case of error
  _remoteJson (opts, cb) {
    const operation = retry.operation({
      retries   : 5,
      factor    : 3.28,
      minTimeout: 1 * 1000,
      maxTimeout: 8 * 1000,
    })

    operation.attempt(() => {
      this.__remoteJson(opts, (err, result) => {
        if (err != null) {
          if (err.error === 'RATE_LIMIT_REACHED') {
            console.warn(`Rate limit reached, retrying request in ${err.info.retryIn} seconds.`)
            // FIXME uses private internals of node-retry
            operation._timeouts.unshift(1000 * err.info.retryIn)
            return operation.retry(err)
          }

          if (err.code === 'ENOTFOUND') {
            console.warn(`The network connection is down, retrying request in 3 seconds.`)
            // FIXME uses private internals of node-retry
            operation._timeouts.unshift(3 * 1000)
            return operation.retry(err)
          }

          if (err.error === 'GET_ACCOUNT_UNKNOWN_AUTH_KEY') {
            console.warn(`Invalid auth key provided.`)
            return cb(err)
          }

          if (err.error !== undefined) {
            console.warn(err.error + ' - ' + err.message)
            return cb(err)
          }
        }

        if (operation.retry(err)) {
          return
        }

        let mainError = null
        if (err) {
          mainError = operation.mainError()
        }

        cb(mainError, result)
      })
    })
  }

  // Responsible for making API calls. Automatically sends streams with any POST,
  // PUT or DELETE requests. Automatically adds signature parameters to all
  // requests. Also automatically parses the JSON response.
  __remoteJson (opts, cb) {
    const timeout = opts.timeout || 5000
    let url = opts.url || null
    const method = opts.method || 'get'

    if (!url) {
      const err = new Error('No url provided!')
      return cb(err)
    }

    if (method === 'get' && opts.params != null) {
      url = this._appendParamsToUrl(url, opts.params)
    }

    const requestOpts = {
      uri: url,
      timeout,
    }

    if (opts.headers != null) {
      requestOpts.headers = opts.headers
    }

    const req = request[method](requestOpts, (err, { body, statusCode } = {}) => {
      if (err) {
        return cb(err)
      }

      // parse body
      let result = null
      try {
        result = JSON.parse(body)
      } catch (e) {
        const abbr = `${body}`.substr(0, 255)
        let msg = `Unable to parse JSON from '${requestOpts.uri}'. `
        msg += `Code: ${statusCode}. Body: ${abbr}. `
        return cb(new Error(msg))
      }
      if (statusCode !== 200 && statusCode !== 404 && statusCode >= 400 && statusCode <= 599) {
        return cb(_.extend(new Error(), result))
      }

      return cb(null, result)
    })

    if (method === 'post' || method === 'put' || method === 'del') {
      this._appendForm(req, opts.params, opts.fields)
    }
  }
}

module.exports = TransloaditClient
<|endoftext|>


<commit_before>var CourseCardContentPanel = {

    render: function (c_section, fetched_eval) {

        var eval_data = (fetched_eval? WSData.iasystem_data(): null);
        var index = c_section.index;

        // Determine if we have valid eval data for the section
        var has_valid_eval = (eval_data && eval_data.sections && eval_data.sections.length > 0 && eval_data.sections[index] && eval_data.sections[index].evaluation_data && eval_data.sections[index].evaluation_data.length > 0 ? true : false);
        c_section.has_eval = has_valid_eval;
        c_section.evals = (has_valid_eval ? eval_data.sections[index].evaluation_data : null);

        // Determine if there was an err when fetching the section eval data
        var eval_data_err = (fetched_eval && !has_valid_eval && eval_data && eval_data.sections[index].evaluation_data === null ? true :false);
        c_section.eval_data_err = eval_data_err;

        var source = $("#course_card_content_panel").html();
        var template = Handlebars.compile(source);
        var raw = template(c_section);
        $('#course_card_content' + index).html(raw);

        if (has_valid_eval) {
            CourseEvalPanel.render(c_section);
        }

        CourseSchePanel.render(c_section);

        CourseResourcePanel.render(c_section);
        if (c_section.instructors) {
            CourseInstructorPanel.render(c_section);
        }
    }
};
<commit_msg>add spacing<commit_after>var CourseCardContentPanel = {

    render: function (c_section, fetched_eval) {

        var eval_data = (fetched_eval? WSData.iasystem_data(): null);
        var index = c_section.index;

        // Determine if we have valid eval data for the section
        var has_valid_eval = (eval_data && eval_data.sections && eval_data.sections.length > 0 && eval_data.sections[index] && eval_data.sections[index].evaluation_data && eval_data.sections[index].evaluation_data.length > 0 ? true : false);
        c_section.has_eval = has_valid_eval;
        c_section.evals = (has_valid_eval ? eval_data.sections[index].evaluation_data : null);

        // Determine if there was an err when fetching the section eval data
        var eval_data_err = (fetched_eval && !has_valid_eval && eval_data && eval_data.sections[index].evaluation_data === null ? true :false);
        c_section.eval_data_err = eval_data_err;

        var source = $("#course_card_content_panel").html();
        var template = Handlebars.compile(source);
        var raw = template(c_section);
        $('#course_card_content' + index).html(raw);

        if (has_valid_eval) {
            CourseEvalPanel.render(c_section);
        }

        CourseSchePanel.render(c_section);

        CourseResourcePanel.render(c_section);

        if (c_section.instructors) {
            CourseInstructorPanel.render(c_section);
        }
    }
};
<|endoftext|>