<commit_before>import classNames from 'classnames';
import React from 'react';
import {Link} from 'react-router';
import {Table} from 'reactjs-components';

import CollapsingString from './CollapsingString';
import EventTypes from '../constants/EventTypes';
import ExpandingTable from './ExpandingTable';
import Icon from './Icon';
import MesosStateStore from '../stores/MesosStateStore';
import Pod from '../structs/Pod';
import PodInstanceList from '../structs/PodInstanceList';
import PodInstanceStatus from '../constants/PodInstanceStatus';
import PodTableHeaderLabels from '../constants/PodTableHeaderLabels';
import PodUtil from '../utils/PodUtil';
import TableUtil from '../utils/TableUtil';
import TimeAgo from './TimeAgo';
import Units from '../utils/Units';

const METHODS_TO_BIND = [
  'getColGroup',
  'handleItemCheck',
  'handleMesosStateChange',
  'renderColumnAddress',
  'renderColumnID',
  'renderColumnLogs',
  'renderColumnResource',
  'renderColumnStatus',
  'renderColumnUpdated',
  'renderColumnVersion'
];

class PodInstancesTable extends React.Component {
  constructor() {
    super(...arguments);

    this.state = {
      checkedItems: {}
    };

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillMount() {
    MesosStateStore.addChangeListener(
      EventTypes.MESOS_STATE_CHANGE,
      this.handleMesosStateChange
    );
  }

  componentWillUnmount() {
    MesosStateStore.removeChangeListener(
      EventTypes.MESOS_STATE_CHANGE,
      this.handleMesosStateChange
    );
  }

  handleMesosStateChange() {
    this.forceUpdate();
  }

  handleItemCheck(idsChecked) {
    let checkedItems = {};

    idsChecked.forEach(function (id) {
      checkedItems[id] = true;
    });
    this.setState({checkedItems});
  }

  getColGroup() {
    return (
      <colgroup>
        <col />
        <col className="hidden-mini" />
        <col className="hidden-mini" />
        <col className="hidden-mini" />
        <col className="hidden-mini" />
        <col className="hidden-mini" />
        <col />
        <col className="hidden-medium hidden-small hidden-mini" />
      </colgroup>
    );
  }

  getColumnHeading(prop, order, sortBy) {
    let caretClassNames = classNames(
      'caret',
      {
        [`caret--${order}`]: order != null,
        'caret--visible': prop === sortBy.prop
      }
    );

    return (

        {PodTableHeaderLabels[prop]}

    );
  }

  getColumnClassName(prop, sortBy, row) {
    let hiddenMiniCols = [
      'address',
      'status',
      'logs',
      'cpus',
      'mem',
      'version'
    ];

    return classNames(`column-${prop}`, {
      'highlight': prop === sortBy.prop,
      'clickable': row == null,
      'table-cell-task-dot': prop === 'status',
      'hidden-medium hidden-small': prop === 'version',
      'hidden-mini': hiddenMiniCols.includes(prop)
    });
  }

  getColumns() {
    return [
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'name',
        render: this.renderColumnID,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'address',
        render: this.renderColumnAddress,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'status',
        render: this.renderColumnStatus,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'logs',
        render: this.renderColumnLogs,
        sortable: false
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'cpus',
        render: this.renderColumnResource,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'mem',
        render: this.renderColumnResource,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'updated',
        render: this.renderColumnUpdated,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'version',
        render: this.renderColumnVersion,
        sortable: true
      }
    ];
  }

  getContainersWithResources(podSpec, containers, agentAddress) {
    let children = containers.map(function (container) {
      let containerResources = container.getResources();

      // TODO: Remove the following 4 lines when DCOS-10098 is addressed
      let containerSpec = podSpec.getContainerSpec(container.name);
      if (containerSpec) {
        containerResources = containerSpec.resources;
      }

      let addressComponents = container.endpoints.map(function (endpoint, i) {
        return (
          <a className="text-muted"
            href={`http://${agentAddress}:${endpoint.allocatedHostPort}`}
            key={i}
            target="_blank"
            title="Open in a new window">
            {endpoint.allocatedHostPort}

        );
      });

      return {
        id: container.getId(),
        name: container.getName(),
        status: container.getContainerStatus(),
        address: addressComponents,
        cpus: containerResources.cpus,
        mem: containerResources.mem,
        updated: container.getLastUpdated(),
        version: ''
      };
    });

    return children;
  }

  getTableDataFor(instances, filterText) {
    let podSpec = this.props.pod.getSpec();

    return instances.getItems().map((instance) => {
      let containers = instance.getContainers().filter(function (container) {
        return PodUtil.isContainerMatchingText(container, filterText);
      });
      let children = this.getContainersWithResources(
        podSpec, containers, instance.getAgentAddress()
      );
      let resourcesSum = instance.getResources();

      return {
        id: instance.getId(),
        name: instance.getName(),
        address: instance.getAgentAddress(),
        cpus: resourcesSum.cpus,
        mem: resourcesSum.mem,
        updated: instance.getLastUpdated(),
        status: instance.getInstanceStatus(),
        version: podSpec.getVersion(),
        children
      };
    });

  }

  getInstanceFilterStatus(instance) {
    let status = instance.getInstanceStatus();
    switch (status) {
      case PodInstanceStatus.STAGED:
        return 'staged';

      case PodInstanceStatus.HEALTHY:
      case PodInstanceStatus.UNHEALTHY:
      case PodInstanceStatus.RUNNING:
        return 'active';

      case PodInstanceStatus.KILLED:
        return 'completed';

      default:
        return '';
    }
  }

  renderWithClickHandler(rowOptions, content, className) {
    return (

        {content}

    );
  }

  renderColumnID(prop, row, rowOptions = {}) {
    if (!rowOptions.isParent) {
      return (

          <Link
            className="emphasize clickable text-overflow"
            to="services-task-details"
            params={{
              id: encodeURIComponent(this.props.pod.getId()),
              taskID: row.id
            }}
            title={row.name}>
            <CollapsingString string={row.name} />
          </Link>

      );
    }

    let classes = classNames('expanding-table-primary-cell is-expandable', {
      'is-expanded': rowOptions.isExpanded
    });

    return this.renderWithClickHandler(
      rowOptions,
      (<CollapsingString string={row.id} />),
      classes
    );
  }

  renderColumnLogs(prop, row, rowOptions = {}) {
    if (rowOptions.isParent) {
      // Because elements are just stacked we need a spacer
      return  ;
    }

    let {name, id} = row;

    return (
      <Link
        className="emphasize clickable text-overflow"
        to="services-task-details-logs"
        params={{
          id: encodeURIComponent(this.props.pod.getId()),
          taskID: id
        }}
        title={name}>
        <Icon
          color="grey"
          id="page"
          size="mini"
          family="mini" />
      </Link>
    );
  }

  renderColumnAddress(prop, row, rowOptions = {}) {
    let {address} = row;

    if (rowOptions.isParent) {
      let agent = MesosStateStore.getNodeFromHostname(address);
      if (!agent) {
        return this.renderWithClickHandler(rowOptions, (
          <CollapsingString string={address} />
        ));
      }

      return this.renderWithClickHandler(rowOptions, (
          <Link
            className="emphasize clickable text-overflow"
            to="node-tasks-tab"
            params={{nodeID: agent.id}}
            title={address}>
            <CollapsingString string={address} />
          </Link>
        ));
    }

    return this.renderWithClickHandler(rowOptions, address);
  }

  renderColumnStatus(prop, row, rowOptions = {}) {
    let {status} = row;
    return this.renderWithClickHandler(rowOptions, (

        <span className={`status-text ${status.textClassName}`}>
          {status.displayName}

    ));
  }

  renderColumnResource(prop, row, rowOptions = {}) {
    return this.renderWithClickHandler(rowOptions, (
      {Units.formatResource(prop, row[prop])}
    ));
  }

  renderColumnUpdated(prop, row, rowOptions = {}) {
    return this.renderWithClickHandler(rowOptions, (
      <TimeAgo time={row.updated} />
    ));
  }

  renderColumnVersion(prop, row, rowOptions = {}) {
    return this.renderWithClickHandler(rowOptions, (
      <CollapsingString string={row.version} />
    ));
  }

  render() {
    let {instances, pod, filterText} = this.props;
    let {checkedItems} = this.state;

    // If custom list of instances is not provided, use the default instances
    // from the pod
    if (instances == null) {
      instances = pod.getInstanceList();
    }

    return (
      <ExpandingTable
        allowMultipleSelect={false}
        className="pod-instances-table expanding-table table table-hover inverse table-borderless-outer table-borderless-inner-columns flush-bottom"
        childRowClassName="expanding-table-child"
        checkedItemsMap={checkedItems}
        columns={this.getColumns()}
        colGroup={this.getColGroup()}
        data={this.getTableDataFor(instances, filterText)}
        expandAll={!!filterText}
        getColGroup={this.getColGroup}
        itemHeight={TableUtil.getRowHeight()}
        onCheckboxChange={this.handleItemCheck}
        sortBy={{prop: 'name', order: 'asc'}}
        tableComponent={Table}
        uniqueProperty="id" />
    );
  }

}

PodInstancesTable.defaultProps = {
  filterText: '',
  instances: null,
  inverseStyle: false,
  pod: null
};

PodInstancesTable.propTypes = {
  filterText: React.PropTypes.string,
  instances: React.PropTypes.instanceOf(PodInstanceList),
  inverseStyle: React.PropTypes.bool,
  pod: React.PropTypes.instanceOf(Pod).isRequired
};

module.exports = PodInstancesTable;
<commit_msg>Use destructuring assignment<commit_after>import classNames from 'classnames';
import React from 'react';
import {Link} from 'react-router';
import {Table} from 'reactjs-components';

import CollapsingString from './CollapsingString';
import EventTypes from '../constants/EventTypes';
import ExpandingTable from './ExpandingTable';
import Icon from './Icon';
import MesosStateStore from '../stores/MesosStateStore';
import Pod from '../structs/Pod';
import PodInstanceList from '../structs/PodInstanceList';
import PodInstanceStatus from '../constants/PodInstanceStatus';
import PodTableHeaderLabels from '../constants/PodTableHeaderLabels';
import PodUtil from '../utils/PodUtil';
import TableUtil from '../utils/TableUtil';
import TimeAgo from './TimeAgo';
import Units from '../utils/Units';

const METHODS_TO_BIND = [
  'getColGroup',
  'handleItemCheck',
  'handleMesosStateChange',
  'renderColumnAddress',
  'renderColumnID',
  'renderColumnLogs',
  'renderColumnResource',
  'renderColumnStatus',
  'renderColumnUpdated',
  'renderColumnVersion'
];

class PodInstancesTable extends React.Component {
  constructor() {
    super(...arguments);

    this.state = {
      checkedItems: {}
    };

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillMount() {
    MesosStateStore.addChangeListener(
      EventTypes.MESOS_STATE_CHANGE,
      this.handleMesosStateChange
    );
  }

  componentWillUnmount() {
    MesosStateStore.removeChangeListener(
      EventTypes.MESOS_STATE_CHANGE,
      this.handleMesosStateChange
    );
  }

  handleMesosStateChange() {
    this.forceUpdate();
  }

  handleItemCheck(idsChecked) {
    let checkedItems = {};

    idsChecked.forEach(function (id) {
      checkedItems[id] = true;
    });
    this.setState({checkedItems});
  }

  getColGroup() {
    return (
      <colgroup>
        <col />
        <col className="hidden-mini" />
        <col className="hidden-mini" />
        <col className="hidden-mini" />
        <col className="hidden-mini" />
        <col className="hidden-mini" />
        <col />
        <col className="hidden-medium hidden-small hidden-mini" />
      </colgroup>
    );
  }

  getColumnHeading(prop, order, sortBy) {
    let caretClassNames = classNames(
      'caret',
      {
        [`caret--${order}`]: order != null,
        'caret--visible': prop === sortBy.prop
      }
    );

    return (

        {PodTableHeaderLabels[prop]}

    );
  }

  getColumnClassName(prop, sortBy, row) {
    let hiddenMiniCols = [
      'address',
      'status',
      'logs',
      'cpus',
      'mem',
      'version'
    ];

    return classNames(`column-${prop}`, {
      'highlight': prop === sortBy.prop,
      'clickable': row == null,
      'table-cell-task-dot': prop === 'status',
      'hidden-medium hidden-small': prop === 'version',
      'hidden-mini': hiddenMiniCols.includes(prop)
    });
  }

  getColumns() {
    return [
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'name',
        render: this.renderColumnID,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'address',
        render: this.renderColumnAddress,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'status',
        render: this.renderColumnStatus,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'logs',
        render: this.renderColumnLogs,
        sortable: false
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'cpus',
        render: this.renderColumnResource,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'mem',
        render: this.renderColumnResource,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'updated',
        render: this.renderColumnUpdated,
        sortable: true
      },
      {
        className: this.getColumnClassName,
        heading: this.getColumnHeading,
        prop: 'version',
        render: this.renderColumnVersion,
        sortable: true
      }
    ];
  }

  getContainersWithResources(podSpec, containers, agentAddress) {
    let children = containers.map(function (container) {
      let containerResources = container.getResources();

      // TODO: Remove the following 4 lines when DCOS-10098 is addressed
      let containerSpec = podSpec.getContainerSpec(container.name);
      if (containerSpec) {
        containerResources = containerSpec.resources;
      }

      let addressComponents = container.endpoints.map(function (endpoint, i) {
        return (
          <a className="text-muted"
            href={`http://${agentAddress}:${endpoint.allocatedHostPort}`}
            key={i}
            target="_blank"
            title="Open in a new window">
            {endpoint.allocatedHostPort}

        );
      });

      return {
        id: container.getId(),
        name: container.getName(),
        status: container.getContainerStatus(),
        address: addressComponents,
        cpus: containerResources.cpus,
        mem: containerResources.mem,
        updated: container.getLastUpdated(),
        version: ''
      };
    });

    return children;
  }

  getTableDataFor(instances, filterText) {
    let podSpec = this.props.pod.getSpec();

    return instances.getItems().map((instance) => {
      let containers = instance.getContainers().filter(function (container) {
        return PodUtil.isContainerMatchingText(container, filterText);
      });
      let children = this.getContainersWithResources(
        podSpec, containers, instance.getAgentAddress()
      );
      let {cpus, mem} = instance.getResources();

      return {
        id: instance.getId(),
        name: instance.getName(),
        address: instance.getAgentAddress(),
        cpus,
        mem,
        updated: instance.getLastUpdated(),
        status: instance.getInstanceStatus(),
        version: podSpec.getVersion(),
        children
      };
    });

  }

  getInstanceFilterStatus(instance) {
    let status = instance.getInstanceStatus();
    switch (status) {
      case PodInstanceStatus.STAGED:
        return 'staged';

      case PodInstanceStatus.HEALTHY:
      case PodInstanceStatus.UNHEALTHY:
      case PodInstanceStatus.RUNNING:
        return 'active';

      case PodInstanceStatus.KILLED:
        return 'completed';

      default:
        return '';
    }
  }

  renderWithClickHandler(rowOptions, content, className) {
    return (

        {content}

    );
  }

  renderColumnID(prop, row, rowOptions = {}) {
    if (!rowOptions.isParent) {
      return (

          <Link
            className="emphasize clickable text-overflow"
            to="services-task-details"
            params={{
              id: encodeURIComponent(this.props.pod.getId()),
              taskID: row.id
            }}
            title={row.name}>
            <CollapsingString string={row.name} />
          </Link>

      );
    }

    let classes = classNames('expanding-table-primary-cell is-expandable', {
      'is-expanded': rowOptions.isExpanded
    });

    return this.renderWithClickHandler(
      rowOptions,
      (<CollapsingString string={row.id} />),
      classes
    );
  }

  renderColumnLogs(prop, row, rowOptions = {}) {
    if (rowOptions.isParent) {
      // Because elements are just stacked we need a spacer
      return  ;
    }

    let {name, id} = row;

    return (
      <Link
        className="emphasize clickable text-overflow"
        to="services-task-details-logs"
        params={{
          id: encodeURIComponent(this.props.pod.getId()),
          taskID: id
        }}
        title={name}>
        <Icon
          color="grey"
          id="page"
          size="mini"
          family="mini" />
      </Link>
    );
  }

  renderColumnAddress(prop, row, rowOptions = {}) {
    let {address} = row;

    if (rowOptions.isParent) {
      let agent = MesosStateStore.getNodeFromHostname(address);
      if (!agent) {
        return this.renderWithClickHandler(rowOptions, (
          <CollapsingString string={address} />
        ));
      }

      return this.renderWithClickHandler(rowOptions, (
          <Link
            className="emphasize clickable text-overflow"
            to="node-tasks-tab"
            params={{nodeID: agent.id}}
            title={address}>
            <CollapsingString string={address} />
          </Link>
        ));
    }

    return this.renderWithClickHandler(rowOptions, address);
  }

  renderColumnStatus(prop, row, rowOptions = {}) {
    let {status} = row;
    return this.renderWithClickHandler(rowOptions, (

        <span className={`status-text ${status.textClassName}`}>
          {status.displayName}

    ));
  }

  renderColumnResource(prop, row, rowOptions = {}) {
    return this.renderWithClickHandler(rowOptions, (
      {Units.formatResource(prop, row[prop])}
    ));
  }

  renderColumnUpdated(prop, row, rowOptions = {}) {
    return this.renderWithClickHandler(rowOptions, (
      <TimeAgo time={row.updated} />
    ));
  }

  renderColumnVersion(prop, row, rowOptions = {}) {
    return this.renderWithClickHandler(rowOptions, (
      <CollapsingString string={row.version} />
    ));
  }

  render() {
    let {instances, pod, filterText} = this.props;
    let {checkedItems} = this.state;

    // If custom list of instances is not provided, use the default instances
    // from the pod
    if (instances == null) {
      instances = pod.getInstanceList();
    }

    return (
      <ExpandingTable
        allowMultipleSelect={false}
        className="pod-instances-table expanding-table table table-hover inverse table-borderless-outer table-borderless-inner-columns flush-bottom"
        childRowClassName="expanding-table-child"
        checkedItemsMap={checkedItems}
        columns={this.getColumns()}
        colGroup={this.getColGroup()}
        data={this.getTableDataFor(instances, filterText)}
        expandAll={!!filterText}
        getColGroup={this.getColGroup}
        itemHeight={TableUtil.getRowHeight()}
        onCheckboxChange={this.handleItemCheck}
        sortBy={{prop: 'name', order: 'asc'}}
        tableComponent={Table}
        uniqueProperty="id" />
    );
  }

}

PodInstancesTable.defaultProps = {
  filterText: '',
  instances: null,
  inverseStyle: false,
  pod: null
};

PodInstancesTable.propTypes = {
  filterText: React.PropTypes.string,
  instances: React.PropTypes.instanceOf(PodInstanceList),
  inverseStyle: React.PropTypes.bool,
  pod: React.PropTypes.instanceOf(Pod).isRequired
};

module.exports = PodInstancesTable;
<|endoftext|>


<commit_before>

METADATA_HOST = "http://www.ebi.ac.uk/eva/webservices/rest";
METADATA_VERSION = 'v1';

CELLBASE_HOST = "http://www.ebi.ac.uk/cellbase/webservices/rest";
CELLBASE_VERSION = "v3";

if(window.location.host.indexOf("wwwint.ebi.ac.uk") === -1){

    METADATA_HOST = "http://wwwint.ebi.ac.uk/eva/webservices/rest";
    METADATA_VERSION = 'v1';

    CELLBASE_HOST = "http://ws-beta.bioinfo.cipf.es/cellbase-staging/rest";
    CELLBASE_VERSION = "latest";
}

if(window.location.host.indexOf("wwwdev.ebi.ac.uk") === -1){

    METADATA_HOST = "http://wwwdev.ebi.ac.uk/eva/webservices/rest";
    METADATA_VERSION = 'v1';

    CELLBASE_HOST = "http://ws-beta.bioinfo.cipf.es/cellbase-staging/rest";
    CELLBASE_VERSION = "latest";
}

/**
 * Created by jag on 17/03/2014.
 */
//var evaApp = angular.module('evaApp', ['ui.bootstrap','ebiApp','highcharts-ng','ebiVar.Services.Metadata','homeWidgetModule','variantWidgetModule','checklist-model', 'geneWidgetModule', 'ui.router', 'duScroll']);
var evaApp = angular.module('evaApp', ['ui.bootstrap','ebiApp','homeWidgetModule','variantWidgetModule','geneWidgetModule', 'duScroll']);

<commit_msg>hostname check condition altered<commit_after>

METADATA_HOST = "http://www.ebi.ac.uk/eva/webservices/rest";
METADATA_VERSION = 'v1';

CELLBASE_HOST = "http://www.ebi.ac.uk/cellbase/webservices/rest";
CELLBASE_VERSION = "v3";

if(window.location.host.indexOf("wwwint.ebi.ac.uk") !== -1){

    METADATA_HOST = "http://wwwint.ebi.ac.uk/eva/webservices/rest";
    METADATA_VERSION = 'v1';

    CELLBASE_HOST = "http://ws-beta.bioinfo.cipf.es/cellbase-staging/rest";
    CELLBASE_VERSION = "latest";
}

if(window.location.host.indexOf("wwwdev.ebi.ac.uk") !== -1){

    METADATA_HOST = "http://wwwdev.ebi.ac.uk/eva/webservices/rest";
    METADATA_VERSION = 'v1';

    CELLBASE_HOST = "http://ws-beta.bioinfo.cipf.es/cellbase-staging/rest";
    CELLBASE_VERSION = "latest";
}

/**
 * Created by jag on 17/03/2014.
 */
//var evaApp = angular.module('evaApp', ['ui.bootstrap','ebiApp','highcharts-ng','ebiVar.Services.Metadata','homeWidgetModule','variantWidgetModule','checklist-model', 'geneWidgetModule', 'ui.router', 'duScroll']);
var evaApp = angular.module('evaApp', ['ui.bootstrap','ebiApp','homeWidgetModule','variantWidgetModule','geneWidgetModule', 'duScroll']);

<|endoftext|>


<commit_before>

$('document').ready(function () {

// Menu jquery

$('.hamburger').on('click', function() {
    $(this).toggleClass('open');     // hamburger changes to "x" as a result o f ".open" class styles being applyed to it.
    $('.full-menu').toggleClass('full-menu--open');   // full-screen menu appears
})

// Main-nav jquery

$('.local-link').on('click', function() {
    $('.full-menu').toggleClass('full-menu--open');   // when a menu link is clicked, the full-screen menu fades away so that the user can see the page again (thus the new section they're taken to).
    $('.hamburger').toggleClass('open');  // when a menu link is clicked, the "x" menu shape turns back into a hamburger shape.
})

// Refreshes page each time browser is re-sized

// window.addEventListener('resize', function () {
//     "use strict";
//     window.location.reload();
// });

});
<commit_msg>deleted file<commit_after><|endoftext|>


<commit_before>const cloudinary = require('cloudinary')
const config = require('config')
const User = require('../../models/user.js')
const Street = require('../../models/street.js')
const logger = require('../../../lib/logger.js')()
const { SAVE_THUMBNAIL_EVENTS } = require('../../../lib/util.js')

const ALLOW_ANON_STREET_THUMBNAILS = false

exports.post = async function (req, res) {
  let json

  try {
    json = await JSON.parse(req.body)
  } catch (error) {
    res.status(400).send('Could not parse body as JSON.')
    return
  }

  const { image, event, streetType } = json

  if (!image) {
    res.status(400).json({ status: 400, msg: 'Image data not specified.' })
    return
  }

  if (!req.params.street_id) {
    res.status(400).json({ status: 400, msg: 'Please provide street ID.' })
    return
  }

  const publicId = `${config.env}/street_thumbnails/${req.params.street_id}`
  logger.info({ event, street_type: streetType, public_id: publicId }, 'Uploading street thumbnail.')

  if (event !== SAVE_THUMBNAIL_EVENTS.INITIAL && event !== SAVE_THUMBNAIL_EVENTS.TEST) {
    res.status(501).json({ status: 501, msg: 'Only saving initial street rendered thumbnail.' })
    return
  }

  // 1) Check if street exists.
  let street

  try {
    street = await Street.findOne({ id: req.params.street_id })
  } catch (error) {
    logger.error(error)
    res.status(500).json({ status: 500, msg: 'Error finding street.' })
  }

  if (!street) {
    res.status(404).json({ status: 404, msg: 'Street not found.' })
    return
  }

  // 2) Check if street thumbnail exists.
  let resource

  try {
    resource = await cloudinary.v2.api.resource(publicId)
  } catch (error) {
    logger.error(error)
  }

  const handleUploadStreetThumbnail = async function (publicId) {
    if (!publicId) {
      res.status(400).json({ status: 400, msg: 'Please provide the public ID to be used.' })
      return
    }

    try {
      resource = await cloudinary.v2.uploader.upload(image, { public_id: publicId })
    } catch (error) {
      logger.error(error)
    }

    if (!resource) {
      res.status(500).json({ status: 500, msg: 'Error uploading street thumbnail.' })
      return
    }

    const thumbnail = {
      public_id: resource.public_id,
      width: resource.width,
      height: resource.height,
      format: resource.format,
      secure_url: resource.secure_url,
      created_at: resource.created_at
    }

    res.status(201).json(thumbnail)
  }

  const handleFindStreetWithCreator = async function (street) {
    if (!req.userId) {
      res.status(401).json({ status: 401, msg: 'Please provide a user ID.' })
      return
    }

    let user

    try {
      user = await User.findOne({ id: req.userId })
    } catch (error) {
      logger.error(error)
      res.status(500).json({ status: 500, msg: 'Error finding user.' })
      return
    }

    if (!user) {
      res.status(403).json({ status: 403, msg: 'User not found.' })
      return
    }

    if (street.creator_id.toString() !== user._id.toString()) {
      res.status(403).json({ status: 403, msg: 'User does not have the right permissions to upload street thumbnail.' })
      return
    }

    const publicId = `${config.env}/street_thumbnails/${street.id}`
    return publicId
  }

  const handleError = function (error) {
    logger.error(error)
    res.status(500).end()
  }

  // 3a) If street thumbnail does not exist, upload to Cloudinary no matter the currently signed in user.
  // 3b) If street was created by anonymous user, upload to Cloudinary.
  if (!resource || (!street.creator_id && ALLOW_ANON_STREET_THUMBNAILS)) {
    handleUploadStreetThumbnail(publicId)
      .catch(handleError)
  } else if (street.creator_id) {
    // 3c) If street thumbnail already exists and street was created by a user, check if signed in user = creator.
    handleFindStreetWithCreator(street)
      .then(handleUploadStreetThumbnail)
      .catch(handleError)
  } else {
    res.status(403).json({ status: 403, msg: 'User does not have the right permissions to upload street thumbnail.' })
  }
}

exports.delete = async function (req, res) {
  if (!req.params.street_id) {
    res.status(400).json({ status: 400, msg: 'Please provide street ID.' })
    return
  }

  // 1) Verify user is logged in.
  const userId = req.userId

  if (!userId) {
    res.status(401).json({ status: 401, msg: 'Please provide user ID.' })
    return
  }

  let user

  try {
    user = await User.findOne({ id: userId })
  } catch (error) {
    logger.error(error)
    res.status(500).json({ status: 500, msg: 'Error finding user.' })
  }

  if (!user) {
    res.status(404).json({ status: 404, msg: 'User not found.' })
    return
  }

  // Is requesting user logged in?
  if (user.login_tokens.indexOf(req.loginToken) === -1) {
    res.status(401).end()
    return
  }

  // 2) Check that street exists.
  // 3) Verify that street is owned by logged in user.
  let street

  try {
    street = await Street.findOne({ id: req.params.street_id })
  } catch (error) {
    logger.error(error)
    res.status(500).json({ status: 500, msg: 'Error finding street.' })
  }

  if (!street) {
    res.status(404).json({ status: 404, msg: 'Street not found.' })
    return
  } else if (street.creator_id.toString() !== user._id.toString()) {
    res.status(403).json({ status: 403, msg: 'Signed in user cannot delete street thumbnail.' })
    return
  }

  // 4) Delete street thumbnail from cloudinary.
  const publicId = `${config.env}/street_thumbnails/${req.params.street_id}`
  cloudinary.v2.uploader.destroy(publicId, function (error, result) {
    if (error) {
      logger.error(error)
      res.status(500).json({ status: 500, msg: 'Error deleting street thumbnail.' })
      return
    }

    res.status(204).end()
  })
}

exports.get = async function (req, res) {
  if (!req.params.street_id) {
    res.status(400).json({ status: 400, msg: 'Please provide a street id.' })
    return
  }

  let resource

  try {
    const publicId = `${config.env}/street_thumbnails/${req.params.street_id}`
    resource = await cloudinary.v2.api.resource(publicId)
  } catch (error) {
    logger.error(error)
    res.status(500).json({ status: 500, msg: 'Error finding street thumbnail.' })
    return
  }

  if (!resource) {
    res.status(404).json({ status: 404, msg: 'Could not find street thumbnail.' })
    return
  }

  res.status(200).json(resource)
}
<commit_msg>send error as json object instead of string<commit_after>const cloudinary = require('cloudinary')
const config = require('config')
const User = require('../../models/user.js')
const Street = require('../../models/street.js')
const logger = require('../../../lib/logger.js')()
const { SAVE_THUMBNAIL_EVENTS } = require('../../../lib/util.js')

const ALLOW_ANON_STREET_THUMBNAILS = false

exports.post = async function (req, res) {
  let json

  try {
    json = await JSON.parse(req.body)
  } catch (error) {
    res.status(400).json({ status: 400, msg: 'Could not parse body as JSON.' })
    return
  }

  const { image, event, streetType } = json

  if (!image) {
    res.status(400).json({ status: 400, msg: 'Image data not specified.' })
    return
  }

  if (!req.params.street_id) {
    res.status(400).json({ status: 400, msg: 'Please provide street ID.' })
    return
  }

  const publicId = `${config.env}/street_thumbnails/${req.params.street_id}`
  logger.info({ event, street_type: streetType, public_id: publicId }, 'Uploading street thumbnail.')

  if (event !== SAVE_THUMBNAIL_EVENTS.INITIAL && event !== SAVE_THUMBNAIL_EVENTS.TEST) {
    res.status(501).json({ status: 501, msg: 'Only saving initial street rendered thumbnail.' })
    return
  }

  // 1) Check if street exists.
  let street

  try {
    street = await Street.findOne({ id: req.params.street_id })
  } catch (error) {
    logger.error(error)
    res.status(500).json({ status: 500, msg: 'Error finding street.' })
  }

  if (!street) {
    res.status(404).json({ status: 404, msg: 'Street not found.' })
    return
  }

  // 2) Check if street thumbnail exists.
  let resource

  try {
    resource = await cloudinary.v2.api.resource(publicId)
  } catch (error) {
    logger.error(error)
  }

  const handleUploadStreetThumbnail = async function (publicId) {
    if (!publicId) {
      res.status(400).json({ status: 400, msg: 'Please provide the public ID to be used.' })
      return
    }

    try {
      resource = await cloudinary.v2.uploader.upload(image, { public_id: publicId })
    } catch (error) {
      logger.error(error)
    }

    if (!resource) {
      res.status(500).json({ status: 500, msg: 'Error uploading street thumbnail.' })
      return
    }

    const thumbnail = {
      public_id: resource.public_id,
      width: resource.width,
      height: resource.height,
      format: resource.format,
      secure_url: resource.secure_url,
      created_at: resource.created_at
    }

    res.status(201).json(thumbnail)
  }

  const handleFindStreetWithCreator = async function (street) {
    if (!req.userId) {
      res.status(401).json({ status: 401, msg: 'Please provide a user ID.' })
      return
    }

    let user

    try {
      user = await User.findOne({ id: req.userId })
    } catch (error) {
      logger.error(error)
      res.status(500).json({ status: 500, msg: 'Error finding user.' })
      return
    }

    if (!user) {
      res.status(403).json({ status: 403, msg: 'User not found.' })
      return
    }

    if (street.creator_id.toString() !== user._id.toString()) {
      res.status(403).json({ status: 403, msg: 'User does not have the right permissions to upload street thumbnail.' })
      return
    }

    const publicId = `${config.env}/street_thumbnails/${street.id}`
    return publicId
  }

  const handleError = function (error) {
    logger.error(error)
    res.status(500).end()
  }

  // 3a) If street thumbnail does not exist, upload to Cloudinary no matter the currently signed in user.
  // 3b) If street was created by anonymous user, upload to Cloudinary.
  if (!resource || (!street.creator_id && ALLOW_ANON_STREET_THUMBNAILS)) {
    handleUploadStreetThumbnail(publicId)
      .catch(handleError)
  } else if (street.creator_id) {
    // 3c) If street thumbnail already exists and street was created by a user, check if signed in user = creator.
    handleFindStreetWithCreator(street)
      .then(handleUploadStreetThumbnail)
      .catch(handleError)
  } else {
    res.status(403).json({ status: 403, msg: 'User does not have the right permissions to upload street thumbnail.' })
  }
}

exports.delete = async function (req, res) {
  if (!req.params.street_id) {
    res.status(400).json({ status: 400, msg: 'Please provide street ID.' })
    return
  }

  // 1) Verify user is logged in.
  const userId = req.userId

  if (!userId) {
    res.status(401).json({ status: 401, msg: 'Please provide user ID.' })
    return
  }

  let user

  try {
    user = await User.findOne({ id: userId })
  } catch (error) {
    logger.error(error)
    res.status(500).json({ status: 500, msg: 'Error finding user.' })
  }

  if (!user) {
    res.status(404).json({ status: 404, msg: 'User not found.' })
    return
  }

  // Is requesting user logged in?
  if (user.login_tokens.indexOf(req.loginToken) === -1) {
    res.status(401).end()
    return
  }

  // 2) Check that street exists.
  // 3) Verify that street is owned by logged in user.
  let street

  try {
    street = await Street.findOne({ id: req.params.street_id })
  } catch (error) {
    logger.error(error)
    res.status(500).json({ status: 500, msg: 'Error finding street.' })
  }

  if (!street) {
    res.status(404).json({ status: 404, msg: 'Street not found.' })
    return
  } else if (street.creator_id.toString() !== user._id.toString()) {
    res.status(403).json({ status: 403, msg: 'Signed in user cannot delete street thumbnail.' })
    return
  }

  // 4) Delete street thumbnail from cloudinary.
  const publicId = `${config.env}/street_thumbnails/${req.params.street_id}`
  cloudinary.v2.uploader.destroy(publicId, function (error, result) {
    if (error) {
      logger.error(error)
      res.status(500).json({ status: 500, msg: 'Error deleting street thumbnail.' })
      return
    }

    res.status(204).end()
  })
}

exports.get = async function (req, res) {
  if (!req.params.street_id) {
    res.status(400).json({ status: 400, msg: 'Please provide a street id.' })
    return
  }

  let resource

  try {
    const publicId = `${config.env}/street_thumbnails/${req.params.street_id}`
    resource = await cloudinary.v2.api.resource(publicId)
  } catch (error) {
    logger.error(error)
    res.status(500).json({ status: 500, msg: 'Error finding street thumbnail.' })
    return
  }

  if (!resource) {
    res.status(404).json({ status: 404, msg: 'Could not find street thumbnail.' })
    return
  }

  res.status(200).json(resource)
}
<|endoftext|>


<commit_before>/**
 * @preserve ChesterGL - Simple 2D WebGL demo/library
 *
 * Copyright (c) 2010-2011 Rolando Abarca
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/** @type {Object} */
var ChesterGL = {};

/**
 * creates a new block. Pass the rect if you want to set the frame at
 * creation time.
 *
 * @constructor
 * @param {Object.<string,number>|string=} rect
 * @param {number=} type
 * @param {ChesterGL.Block=} parent
 */
ChesterGL.Block = function (rect, type, parent) {};

/**
 * creates a new block group
 *
 * @constructor
 * @extends ChesterGL.Block
 * @param {string} texture An optional texture that will be shared with all children
 * @param {number=} noChildren The optional initial number of maxChidlren. Defaults to 10
 */
ChesterGL.BlockGroup = function (texture, noChildren) {};

/** @type {Object} */
ChesterGL.BlockFrames = {};

/**
 * @param {string} frameName
 * @return {Object}
 */
ChesterGL.BlockFrames.getFrame = function (frameName) {};

/**
 * @constructor
 * @extends ChesterGL.Block
 * @param {string} tmxFile
 */
ChesterGL.TMXBlock = function (tmxFile) {};

/**
 * @constructor
 * @param {ChesterGL.Block} block
 * @param {number=} totalTime
 */
ChesterGL.Action = function (block, totalTime) {};

/**
 * @constructor
 * @extends ChesterGL.Action
 * @param {ChesterGL.Block} block
 * @param {number} totalTime
 * @param {vec3} finalPosition
 */
ChesterGL.MoveAction = function (block, totalTime, finalPosition) {};
<commit_msg>small changes<commit_after>/**
 * @preserve ChesterGL - Simple 2D WebGL demo/library
 *
 * Copyright (c) 2010-2011 Rolando Abarca
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * @fileoverview Externs for ChesterGL
 *
 * @see https://github.com/funkaster/ChesterGL
 * @externs
 */

/** @type {Object} */
var ChesterGL = {};

/**
 * creates a new block. Pass the rect if you want to set the frame at
 * creation time.
 *
 * @constructor
 * @param {Object.<string,number>|string=} rect
 * @param {number=} type
 * @param {ChesterGL.Block=} parent
 */
ChesterGL.Block = function (rect, type, parent) {};

/**
 * creates a new block group
 *
 * @constructor
 * @extends ChesterGL.Block
 * @param {string} texture An optional texture that will be shared with all children
 * @param {number=} noChildren The optional initial number of maxChidlren. Defaults to 10
 */
ChesterGL.BlockGroup = function (texture, noChildren) {};

/** @type {Object} */
ChesterGL.BlockFrames = {};

/**
 * @param {string} frameName
 * @return {Object}
 */
ChesterGL.BlockFrames.getFrame = function (frameName) {};

/**
 * @constructor
 * @extends ChesterGL.Block
 * @param {string} tmxFile
 */
ChesterGL.TMXBlock = function (tmxFile) {};

/**
 * @constructor
 * @param {ChesterGL.Block} block
 * @param {number=} totalTime
 */
ChesterGL.Action = function (block, totalTime) {};

/**
 * @constructor
 * @extends ChesterGL.Action
 * @param {ChesterGL.Block} block
 * @param {number} totalTime
 * @param {vec3} finalPosition
 */
ChesterGL.MoveToAction = function (block, totalTime, finalPosition) {};
<|endoftext|>


<commit_before>(function () {
    var DashboardCtrl = function ($scope, $routeParams, $http, Dashboard) {
        $scope.dashboard = Dashboard.get({slug: $routeParams.dashboardSlug}, function(dashboard) {
            $scope.$parent.pageTitle = dashboard.name;
        });
    };

    var WidgetCtrl = function ($scope, $http, Query) {
        $scope.deleteWidget = function() {
            if (!confirm('Are you sure you want to remove "' + $scope.widget.query.name + '" from the dashboard?')) {
                return;
            }

            $http.delete('/api/widgets/' + $scope.widget.id).success(function() {
                $scope.dashboard.widgets = _.map($scope.dashboard.widgets, function(row) {
                    return _.filter(row, function(widget) {
                        return widget.id != $scope.widget.id;
                    })
                });
            });
        }

        $scope.query = new Query($scope.widget.query);
        $scope.queryResult = $scope.query.getQueryResult();

        $scope.updateTime = (new Date($scope.queryResult.getUpdatedAt())).toISOString();
        $scope.nextUpdateTime = moment(new Date(($scope.query.updated_at + $scope.query.ttl + $scope.query.runtime + 300) * 1000)).fromNow();

        $scope.updateTime = '';
    }

    var QueryFiddleCtrl = function ($scope, $window, $routeParams, $http, $location, growl, notifications, Query) {
        var leavingPageText = "You will lose your changes if you leave";
        var pristineQuery = null;

        $window.onbeforeunload = function(){
            if ($scope.queryChanged) {
                return leavingPageText;
            }
        }

        Mousetrap.bindGlobal("meta+s", function(e) {
            e.preventDefault();

            if (currentUser.canEdit($scope.query)) {
                $scope.saveQuery();
            }
        });

        $scope.$on('$locationChangeStart', function(event, next, current) {
            if (next.split("#")[0] == current.split("#")[0]) {
                return;
            }

            if($scope.queryChanged &&
                !confirm(leavingPageText + "\n\nAre you sure you want to leave this page?")) {
                event.preventDefault();
            }

            Mousetrap.unbind("meta+s");
        });

        $scope.$parent.pageTitle = "Query Fiddle";

        $scope.tabs = [{'key': 'table', 'name': 'Table'}, {'key': 'chart', 'name': 'Chart'},
                       {'key': 'pivot', 'name': 'Pivot Table'}, {'key': 'cohort', 'name': 'Cohort'}];

        $scope.lockButton = function (lock) {
            $scope.queryExecuting = lock;
        };

        $scope.formatQuery = function() {
            $scope.editorOptions.readOnly = 'nocursor';

            $http.post('/api/queries/format', {'query': $scope.query.query}).success(function(response) {
                $scope.query.query = response;
                $scope.editorOptions.readOnly = false;
            })
        }

        $scope.saveQuery = function (duplicate, oldId) {
            if (!oldId) {
                oldId = $scope.query.id;
            }
            delete $scope.query.latest_query_data;
            $scope.query.$save(function (q) {
                pristineQuery = q.query;
                $scope.queryChanged = false;

                if (duplicate) {
                    growl.addInfoMessage("Query duplicated.", {ttl: 2000});
                } else{
                    growl.addSuccessMessage("Query saved.", {ttl: 2000});
                }

                if (oldId != q.id) {
                    if (oldId == undefined) {
                        $location.path($location.path().replace('new', q.id)).replace();
                    } else {
                        // TODO: replace this with a safer method
                        $location.path($location.path().replace(oldId, q.id)).replace();
                    }
                }
            }, function(httpResponse) {
                growl.addErrorMessage("Query could not be saved");
            });
        };

        $scope.duplicateQuery = function () {
            var oldId = $scope.query.id;
            $scope.query.id = null;
            $scope.query.ttl = -1;

            $scope.saveQuery(true, oldId);
        };

        // Query Editor:
        $scope.editorOptions = {
            mode: 'text/x-sql',
            lineWrapping: true,
            lineNumbers: true,
            readOnly: false,
            matchBrackets: true,
            autoCloseBrackets: true
        };

        $scope.refreshOptions = [
            {value: -1, name: 'No Refresh'},
        ]

        _.each(_.range(1, 13), function(i) {
            $scope.refreshOptions.push({value: i*3600, name: 'Every ' + i + 'h'});
        })

        $scope.refreshOptions.push({value: 24*3600, name: 'Every 24h'});
        $scope.refreshOptions.push({value: 7*24*3600, name: 'Once a week'});

        $scope.$watch('queryResult && queryResult.getError()', function (newError, oldError) {
            if (newError == undefined) {
                return;
            }

            if (oldError == undefined && newError != undefined) {
                $scope.lockButton(false);
            }
        });

        $scope.$watch('queryResult && queryResult.getData()', function (data, oldData) {
            if (!data) {
                return;
            }

            if ($scope.queryResult.getId() == null) {
                $scope.dataUri = "";
            } else {
                $scope.dataUri = '/api/queries/' + $scope.query.id + '/results/' + $scope.queryResult.getId() + '.csv';
                $scope.dataFilename = $scope.query.name.replace(" ", "_") + moment($scope.queryResult.getUpdatedAt()).format("_YYYY_MM_DD") + ".csv";
            }
        });

        $scope.$watch("queryResult && queryResult.getStatus()", function (status) {
            if (!status) {
                return;
            }

            if (status == "done") {
                if ($scope.query.id && $scope.query.latest_query_data_id != $scope.queryResult.getId() &&
                    $scope.query.query_hash == $scope.queryResult.query_result.query_hash) {
                    Query.save({'id': $scope.query.id, 'latest_query_data_id': $scope.queryResult.getId()})
                }
                $scope.query.latest_query_data_id = $scope.queryResult.getId();

                notifications.showNotification("re:dash", $scope.query.name + " updated.");

                $scope.lockButton(false);
            }
        });

        if ($routeParams.queryId != undefined) {
            $scope.query = Query.get({id: $routeParams.queryId}, function(q) {
                pristineQuery = q.query;
                $scope.queryResult = $scope.query.getQueryResult();
            });
        } else {
            $scope.query = new Query({query: "", name: "New Query", ttl: -1, user: currentUser.name});
            $scope.lockButton(false);
        }

        $scope.$watch('query.name', function() {
            $scope.$parent.pageTitle = $scope.query.name;
        });
        $scope.$watch('query.query', function(q) {
            if (q) {
                $scope.queryChanged = (q != pristineQuery);
            }
        });

        $scope.executeQuery = function() {
            $scope.queryResult = $scope.query.getQueryResult(0);
            $scope.lockButton(true);
            $scope.cancelling = false;
        }

        $scope.cancelExecution = function() {
            $scope.cancelling = true;
            $scope.queryResult.cancelExecution();
        }

    }

    var QueriesCtrl = function($scope, $http, $location, $filter, Query) {
        $scope.$parent.pageTitle = "All Queries";
        $scope.gridConfig = {
            isPaginationEnabled: true,
            itemsByPage: 50,
            maxSize: 8,
            isGlobalSearchActivated: true
        }

        $scope.allQueries = [];
        $scope.queries = [];

        var dateFormatter = function (value) {
            if (!value) return "-";
            return value.format("DD/MM/YY HH:mm");
        }

        var filterQueries = function() {
            $scope.queries = _.filter($scope.allQueries, function(query) {
                if (!$scope.selectedTab) {
                    return false;
                }

                if ($scope.selectedTab.key == 'my') {
                    return query.user == currentUser.name && query.name != 'New Query';
                } else if ($scope.selectedTab.key == 'drafts') {
                    return query.user == currentUser.name && query.name == 'New Query';
                }

                return query.name != 'New Query';
            });
        }

        Query.query(function(queries) {
            $scope.allQueries = _.map(queries, function(query) {
                query.created_at = moment(query.created_at);
                query.last_retrieved_at = moment(query.last_retrieved_at);
                return query;
            });

            filterQueries();
        });

        $scope.gridColumns = [
            {
                "label": "Name",
                "map": "name",
                "cellTemplateUrl": "/views/queries_query_name_cell.html"
            },
            {
                'label': 'Created By',
                'map': 'user'
            },
            {
                'label': 'Created At',
                'map': 'created_at',
                'formatFunction': dateFormatter
            },
            {
                'label': 'Runtime (avg)',
                'map': 'avg_runtime',
                'formatFunction': function(value) {
                    return $filter('durationHumanize')(value);
                }
            },
            {
                'label': 'Runtime (min)',
                'map': 'min_runtime',
                'formatFunction': function(value) {
                    return $filter('durationHumanize')(value);
                }
            },
            {
                'label': 'Runtime (max)',
                'map': 'max_runtime',
                'formatFunction': function(value) {
                    return $filter('durationHumanize')(value);
                }
            },
            {
                'label': 'Last Executed At',
                'map': 'last_retrieved_at',
                'formatFunction': dateFormatter
            },
            {
                'label': 'Times Executed',
                'map': 'times_retrieved'
            },
            {
                'label': 'Update Schedule',
                'map': 'ttl',
                'formatFunction': function(value) {
                    return $filter('refreshRateHumanize')(value);
                }
            }
        ]
        $scope.tabs = [{"name": "My Queries", "key": "my"}, {"key": "all", "name": "All Queries"}, {"key": "drafts", "name": "Drafts"}];

        $scope.$watch('selectedTab', function(tab) {
            if (tab) {
                $scope.$parent.pageTitle =  tab.name;
            }

            filterQueries();
        })
    }

    var MainCtrl = function ($scope, Dashboard, notifications) {
        $scope.dashboards = [];
        $scope.reloadDashboards = function() {
            Dashboard.query(function (dashboards) {
                $scope.dashboards = _.sortBy(dashboards, "name");
                $scope.groupedDashboards = _.groupBy($scope.dashboards, function(d) {
                    parts = d.name.split(":");
                    if (parts.length == 1) {
                        return "Other";
                    }
                    return parts[0];
                });
                $scope.otherDashboards = $scope.groupedDashboards['Other'] || [];
                delete $scope.groupedDashboards['Other'];
            });
        }

        $scope.reloadDashboards();

        $scope.currentUser = currentUser;
        $scope.newDashboard = {
            'name': null,
            'layout': null
        }

        $(window).click(function () {
            notifications.getPermissions();
        });
    }

    var IndexCtrl = function($scope, Dashboard) {
        $scope.$parent.pageTitle = "Home";

        $scope.archiveDashboard = function(dashboard) {
            if (confirm('Are you sure you want to delete "' + dashboard.name + '" dashboard?')) {
                dashboard.$delete(function() {
                    $scope.$parent.reloadDashboards();
                });
            }
        }
    }

    angular.module('redash.controllers', [])
        .controller('DashboardCtrl', ['$scope', '$routeParams', '$http', 'Dashboard', DashboardCtrl])
        .controller('WidgetCtrl', ['$scope', '$http', 'Query', WidgetCtrl])
        .controller('QueriesCtrl', ['$scope', '$http', '$location', '$filter', 'Query', QueriesCtrl])
        .controller('QueryFiddleCtrl', ['$scope', '$window', '$routeParams', '$http', '$location', 'growl', 'notifications', 'Query', QueryFiddleCtrl])
        .controller('IndexCtrl', ['$scope', 'Dashboard', IndexCtrl])
        .controller('MainCtrl', ['$scope', 'Dashboard', 'notifications', MainCtrl]);
})();
<commit_msg>Only unbind save shortcut if we're actually leaving the page.<commit_after>(function () {
    var DashboardCtrl = function ($scope, $routeParams, $http, Dashboard) {
        $scope.dashboard = Dashboard.get({slug: $routeParams.dashboardSlug}, function(dashboard) {
            $scope.$parent.pageTitle = dashboard.name;
        });
    };

    var WidgetCtrl = function ($scope, $http, Query) {
        $scope.deleteWidget = function() {
            if (!confirm('Are you sure you want to remove "' + $scope.widget.query.name + '" from the dashboard?')) {
                return;
            }

            $http.delete('/api/widgets/' + $scope.widget.id).success(function() {
                $scope.dashboard.widgets = _.map($scope.dashboard.widgets, function(row) {
                    return _.filter(row, function(widget) {
                        return widget.id != $scope.widget.id;
                    })
                });
            });
        }

        $scope.query = new Query($scope.widget.query);
        $scope.queryResult = $scope.query.getQueryResult();

        $scope.updateTime = (new Date($scope.queryResult.getUpdatedAt())).toISOString();
        $scope.nextUpdateTime = moment(new Date(($scope.query.updated_at + $scope.query.ttl + $scope.query.runtime + 300) * 1000)).fromNow();

        $scope.updateTime = '';
    }

    var QueryFiddleCtrl = function ($scope, $window, $routeParams, $http, $location, growl, notifications, Query) {
        var leavingPageText = "You will lose your changes if you leave";
        var pristineQuery = null;

        $window.onbeforeunload = function(){
            if ($scope.queryChanged) {
                return leavingPageText;
            }
        }

        Mousetrap.bindGlobal("meta+s", function(e) {
            e.preventDefault();

            if (currentUser.canEdit($scope.query)) {
                $scope.saveQuery();
            }
        });

        $scope.$on('$locationChangeStart', function(event, next, current) {
            if (next.split("#")[0] == current.split("#")[0]) {
                return;
            }

            if($scope.queryChanged &&
                !confirm(leavingPageText + "\n\nAre you sure you want to leave this page?")) {
                event.preventDefault();
            } else {
                Mousetrap.unbind("meta+s");
            }
        });

        $scope.$parent.pageTitle = "Query Fiddle";

        $scope.tabs = [{'key': 'table', 'name': 'Table'}, {'key': 'chart', 'name': 'Chart'},
                       {'key': 'pivot', 'name': 'Pivot Table'}, {'key': 'cohort', 'name': 'Cohort'}];

        $scope.lockButton = function (lock) {
            $scope.queryExecuting = lock;
        };

        $scope.formatQuery = function() {
            $scope.editorOptions.readOnly = 'nocursor';

            $http.post('/api/queries/format', {'query': $scope.query.query}).success(function(response) {
                $scope.query.query = response;
                $scope.editorOptions.readOnly = false;
            })
        }

        $scope.saveQuery = function (duplicate, oldId) {
            if (!oldId) {
                oldId = $scope.query.id;
            }
            delete $scope.query.latest_query_data;
            $scope.query.$save(function (q) {
                pristineQuery = q.query;
                $scope.queryChanged = false;

                if (duplicate) {
                    growl.addInfoMessage("Query duplicated.", {ttl: 2000});
                } else{
                    growl.addSuccessMessage("Query saved.", {ttl: 2000});
                }

                if (oldId != q.id) {
                    if (oldId == undefined) {
                        $location.path($location.path().replace('new', q.id)).replace();
                    } else {
                        // TODO: replace this with a safer method
                        $location.path($location.path().replace(oldId, q.id)).replace();
                    }
                }
            }, function(httpResponse) {
                growl.addErrorMessage("Query could not be saved");
            });
        };

        $scope.duplicateQuery = function () {
            var oldId = $scope.query.id;
            $scope.query.id = null;
            $scope.query.ttl = -1;

            $scope.saveQuery(true, oldId);
        };

        // Query Editor:
        $scope.editorOptions = {
            mode: 'text/x-sql',
            lineWrapping: true,
            lineNumbers: true,
            readOnly: false,
            matchBrackets: true,
            autoCloseBrackets: true
        };

        $scope.refreshOptions = [
            {value: -1, name: 'No Refresh'},
        ]

        _.each(_.range(1, 13), function(i) {
            $scope.refreshOptions.push({value: i*3600, name: 'Every ' + i + 'h'});
        })

        $scope.refreshOptions.push({value: 24*3600, name: 'Every 24h'});
        $scope.refreshOptions.push({value: 7*24*3600, name: 'Once a week'});

        $scope.$watch('queryResult && queryResult.getError()', function (newError, oldError) {
            if (newError == undefined) {
                return;
            }

            if (oldError == undefined && newError != undefined) {
                $scope.lockButton(false);
            }
        });

        $scope.$watch('queryResult && queryResult.getData()', function (data, oldData) {
            if (!data) {
                return;
            }

            if ($scope.queryResult.getId() == null) {
                $scope.dataUri = "";
            } else {
                $scope.dataUri = '/api/queries/' + $scope.query.id + '/results/' + $scope.queryResult.getId() + '.csv';
                $scope.dataFilename = $scope.query.name.replace(" ", "_") + moment($scope.queryResult.getUpdatedAt()).format("_YYYY_MM_DD") + ".csv";
            }
        });

        $scope.$watch("queryResult && queryResult.getStatus()", function (status) {
            if (!status) {
                return;
            }

            if (status == "done") {
                if ($scope.query.id && $scope.query.latest_query_data_id != $scope.queryResult.getId() &&
                    $scope.query.query_hash == $scope.queryResult.query_result.query_hash) {
                    Query.save({'id': $scope.query.id, 'latest_query_data_id': $scope.queryResult.getId()})
                }
                $scope.query.latest_query_data_id = $scope.queryResult.getId();

                notifications.showNotification("re:dash", $scope.query.name + " updated.");

                $scope.lockButton(false);
            }
        });

        if ($routeParams.queryId != undefined) {
            $scope.query = Query.get({id: $routeParams.queryId}, function(q) {
                pristineQuery = q.query;
                $scope.queryResult = $scope.query.getQueryResult();
            });
        } else {
            $scope.query = new Query({query: "", name: "New Query", ttl: -1, user: currentUser.name});
            $scope.lockButton(false);
        }

        $scope.$watch('query.name', function() {
            $scope.$parent.pageTitle = $scope.query.name;
        });
        $scope.$watch('query.query', function(q) {
            if (q) {
                $scope.queryChanged = (q != pristineQuery);
            }
        });

        $scope.executeQuery = function() {
            $scope.queryResult = $scope.query.getQueryResult(0);
            $scope.lockButton(true);
            $scope.cancelling = false;
        }

        $scope.cancelExecution = function() {
            $scope.cancelling = true;
            $scope.queryResult.cancelExecution();
        }

    }

    var QueriesCtrl = function($scope, $http, $location, $filter, Query) {
        $scope.$parent.pageTitle = "All Queries";
        $scope.gridConfig = {
            isPaginationEnabled: true,
            itemsByPage: 50,
            maxSize: 8,
            isGlobalSearchActivated: true
        }

        $scope.allQueries = [];
        $scope.queries = [];

        var dateFormatter = function (value) {
            if (!value) return "-";
            return value.format("DD/MM/YY HH:mm");
        }

        var filterQueries = function() {
            $scope.queries = _.filter($scope.allQueries, function(query) {
                if (!$scope.selectedTab) {
                    return false;
                }

                if ($scope.selectedTab.key == 'my') {
                    return query.user == currentUser.name && query.name != 'New Query';
                } else if ($scope.selectedTab.key == 'drafts') {
                    return query.user == currentUser.name && query.name == 'New Query';
                }

                return query.name != 'New Query';
            });
        }

        Query.query(function(queries) {
            $scope.allQueries = _.map(queries, function(query) {
                query.created_at = moment(query.created_at);
                query.last_retrieved_at = moment(query.last_retrieved_at);
                return query;
            });

            filterQueries();
        });

        $scope.gridColumns = [
            {
                "label": "Name",
                "map": "name",
                "cellTemplateUrl": "/views/queries_query_name_cell.html"
            },
            {
                'label': 'Created By',
                'map': 'user'
            },
            {
                'label': 'Created At',
                'map': 'created_at',
                'formatFunction': dateFormatter
            },
            {
                'label': 'Runtime (avg)',
                'map': 'avg_runtime',
                'formatFunction': function(value) {
                    return $filter('durationHumanize')(value);
                }
            },
            {
                'label': 'Runtime (min)',
                'map': 'min_runtime',
                'formatFunction': function(value) {
                    return $filter('durationHumanize')(value);
                }
            },
            {
                'label': 'Runtime (max)',
                'map': 'max_runtime',
                'formatFunction': function(value) {
                    return $filter('durationHumanize')(value);
                }
            },
            {
                'label': 'Last Executed At',
                'map': 'last_retrieved_at',
                'formatFunction': dateFormatter
            },
            {
                'label': 'Times Executed',
                'map': 'times_retrieved'
            },
            {
                'label': 'Update Schedule',
                'map': 'ttl',
                'formatFunction': function(value) {
                    return $filter('refreshRateHumanize')(value);
                }
            }
        ]
        $scope.tabs = [{"name": "My Queries", "key": "my"}, {"key": "all", "name": "All Queries"}, {"key": "drafts", "name": "Drafts"}];

        $scope.$watch('selectedTab', function(tab) {
            if (tab) {
                $scope.$parent.pageTitle =  tab.name;
            }

            filterQueries();
        })
    }

    var MainCtrl = function ($scope, Dashboard, notifications) {
        $scope.dashboards = [];
        $scope.reloadDashboards = function() {
            Dashboard.query(function (dashboards) {
                $scope.dashboards = _.sortBy(dashboards, "name");
                $scope.groupedDashboards = _.groupBy($scope.dashboards, function(d) {
                    parts = d.name.split(":");
                    if (parts.length == 1) {
                        return "Other";
                    }
                    return parts[0];
                });
                $scope.otherDashboards = $scope.groupedDashboards['Other'] || [];
                delete $scope.groupedDashboards['Other'];
            });
        }

        $scope.reloadDashboards();

        $scope.currentUser = currentUser;
        $scope.newDashboard = {
            'name': null,
            'layout': null
        }

        $(window).click(function () {
            notifications.getPermissions();
        });
    }

    var IndexCtrl = function($scope, Dashboard) {
        $scope.$parent.pageTitle = "Home";

        $scope.archiveDashboard = function(dashboard) {
            if (confirm('Are you sure you want to delete "' + dashboard.name + '" dashboard?')) {
                dashboard.$delete(function() {
                    $scope.$parent.reloadDashboards();
                });
            }
        }
    }

    angular.module('redash.controllers', [])
        .controller('DashboardCtrl', ['$scope', '$routeParams', '$http', 'Dashboard', DashboardCtrl])
        .controller('WidgetCtrl', ['$scope', '$http', 'Query', WidgetCtrl])
        .controller('QueriesCtrl', ['$scope', '$http', '$location', '$filter', 'Query', QueriesCtrl])
        .controller('QueryFiddleCtrl', ['$scope', '$window', '$routeParams', '$http', '$location', 'growl', 'notifications', 'Query', QueryFiddleCtrl])
        .controller('IndexCtrl', ['$scope', 'Dashboard', IndexCtrl])
        .controller('MainCtrl', ['$scope', 'Dashboard', 'notifications', MainCtrl]);
})();
<|endoftext|>


<commit_before>import React from "react-native";
import AvatarController from "../controllers/avatar-controller";
import ChatBubble from "./chat-bubble";
import Embed from "./embed";
import Modal from "./modal";
import Icon from "./icon";
import Clipboard from "../../modules/clipboard";
import Linking from "../../modules/linking";
import textUtils from "../../lib/text-utils";
import timeUtils from "../../lib/time-utils";
import oembed from "../../lib/oembed";

const {
	StyleSheet,
	TouchableOpacity,
	View,
	Text,
	Image
} = React;

const styles = StyleSheet.create({
	container: {
		marginHorizontal: 8,
		marginVertical: 4
	},
	chat: {
		flex: 0,
		flexDirection: "column",
		alignItems: "flex-end"
	},
	received: {
		alignItems: "flex-start",
		marginLeft: 44
	},
	timestamp: {
		flexDirection: "row",
		marginTop: 4
	},
	timestampLeft: { marginLeft: 52 },
	timestampRight: { alignSelf: "flex-end" },
	timestampIcon: {
		color: "#000",
		opacity: 0.3,
		marginVertical: 2
	},
	timestampText: {
		color: "#000",
		fontSize: 12,
		marginHorizontal: 6,
		paddingHorizontal: 8,
		opacity: 0.3
	},
	avatar: {
		position: "absolute",
		top: 0,
		left: -36,
		height: 36,
		width: 36,
		borderRadius: 18,
		backgroundColor: "rgba(0, 0, 0, .16)",
		alignSelf: "flex-end"
	},
	embed: {
		width: 240,
		height: 135,
		marginVertical: 4
	},
	thumbnail: {
		marginVertical: 4
	},
	image: {
		flex: 1,
		resizeMode: "cover",
		borderRadius: 36
	},
	bubbleReceived: {
		marginRight: 8
	},
	bubbleSent: {
		marginLeft: 8
	}
});

export default class ChatItem extends React.Component {
	shouldComponentUpdate(nextProps) {
		if (this.props.text && nextProps.text && this.props.previousText && nextProps.previousText) {
			return (
					this.props.text.text !== nextProps.text.text ||
					this.props.text.from !== nextProps.text.from ||
					this.props.text.time !== nextProps.text.time ||
					this.props.previousText.from !== nextProps.previousText.from ||
					this.props.previousText.time !== nextProps.previousText.time
				);
		} else {
			return true;
		}
	}

	_showMenu() {
		const { text, textMetadata } = this.props;

		const menu = {};

		if (textMetadata && textMetadata.type === "image") {
			menu["Open image in browser"] = () => Linking.openURL(textMetadata.originalUrl);
			menu["Copy image link"] = () => Clipboard.setText(textMetadata.originalUrl);
		} else {
			menu["Copy text"] = () => Clipboard.setText(text.text);
			menu["Quote message"] = () => this.props.quoteMessage(text);
		}

		menu["Reply to @" + text.from] = () => this.props.replyToMessage(text);

		Modal.showActionSheetWithItems(menu);
	}

	render() {
		const { text, textMetadata, previousText, currentUser } = this.props;

		const received = text.from !== currentUser;

		const links = textUtils.getLinks(text.text);

		let cover;

		if (textMetadata && textMetadata.type === "image") {
			cover = (
				<Image
					style={[ styles.thumbnail, {
						height: parseInt(textMetadata.height, 10) || 160,
						width: parseInt(textMetadata.width, 10) || 160
					} ]}
					source={{ uri: textMetadata.thumbnailUrl }}
				/>
			);
		} else if (links.length) {
			const uri = links[0];
			const endpoint = oembed(uri);

			if (endpoint) {
				cover = (
					<Embed
						uri={uri}
						endpoint={endpoint}
						style={styles.embed}
					/>
				);
			}
		}

		let showAuthor = received,
			showTime = false;

		if (previousText) {
			if (received) {
				showAuthor = text.from !== previousText.from;
			}

			showTime = (text.time - previousText.time) > 300000;
		}

		return (
			<View {...this.props} style={[ styles.container, this.props.style ]}>
				<View style={[ styles.chat, received ? styles.received : null ]}>
					{received && showAuthor ?
						<View style={styles.avatar}>
							<AvatarController
								size={36}
								nick={text.from}
								style={styles.image}
							/>
						</View> :
						null
					}

					<TouchableOpacity onPress={this._showMenu.bind(this)}>
						<ChatBubble
							text={textMetadata && textMetadata.type === "image" ? { from: text.from } : text}
							type={received ? "left" : "right"}
							showAuthor={showAuthor}
							showArrow={received ? showAuthor : true}
							style={received ? styles.bubbleReceived : styles.bubbleSent}
						>
							{cover}
						</ChatBubble>
					</TouchableOpacity>
				</View>

				{showTime ?
					(<View style={[ styles.timestamp, received ? styles.timestampLeft : styles.timestampRight ]}>
					 <Icon
						name="access-time"
						style={styles.timestampIcon}
						size={12}
					 />
					 <Text style={styles.timestampText}>{timeUtils.long(text.time)}</Text>
					</View>) :
					null
				}
			</View>
		);
	}
}

ChatItem.propTypes = {
	text: React.PropTypes.shape({
		text: React.PropTypes.string.isRequired,
		from: React.PropTypes.string.isRequired,
		time: React.PropTypes.number.isRequired
	}).isRequired,
	textMetadata: React.PropTypes.object,
	previousText: React.PropTypes.shape({
		from: React.PropTypes.string.isRequired,
		time: React.PropTypes.number.isRequired
	}),
	currentUser: React.PropTypes.string.isRequired,
	quoteMessage: React.PropTypes.func.isRequired,
	replyToMessage: React.PropTypes.func.isRequired
};
<commit_msg>Don't show reply to for same user<commit_after>import React from "react-native";
import AvatarController from "../controllers/avatar-controller";
import ChatBubble from "./chat-bubble";
import Embed from "./embed";
import Modal from "./modal";
import Icon from "./icon";
import Clipboard from "../../modules/clipboard";
import Linking from "../../modules/linking";
import textUtils from "../../lib/text-utils";
import timeUtils from "../../lib/time-utils";
import oembed from "../../lib/oembed";

const {
	StyleSheet,
	TouchableOpacity,
	View,
	Text,
	Image
} = React;

const styles = StyleSheet.create({
	container: {
		marginHorizontal: 8,
		marginVertical: 4
	},
	chat: {
		flex: 0,
		flexDirection: "column",
		alignItems: "flex-end"
	},
	received: {
		alignItems: "flex-start",
		marginLeft: 44
	},
	timestamp: {
		flexDirection: "row",
		marginTop: 4
	},
	timestampLeft: { marginLeft: 52 },
	timestampRight: { alignSelf: "flex-end" },
	timestampIcon: {
		color: "#000",
		opacity: 0.3,
		marginVertical: 2
	},
	timestampText: {
		color: "#000",
		fontSize: 12,
		marginHorizontal: 6,
		paddingHorizontal: 8,
		opacity: 0.3
	},
	avatar: {
		position: "absolute",
		top: 0,
		left: -36,
		height: 36,
		width: 36,
		borderRadius: 18,
		backgroundColor: "rgba(0, 0, 0, .16)",
		alignSelf: "flex-end"
	},
	embed: {
		width: 240,
		height: 135,
		marginVertical: 4
	},
	thumbnail: {
		marginVertical: 4
	},
	image: {
		flex: 1,
		resizeMode: "cover",
		borderRadius: 36
	},
	bubbleReceived: {
		marginRight: 8
	},
	bubbleSent: {
		marginLeft: 8
	}
});

export default class ChatItem extends React.Component {
	shouldComponentUpdate(nextProps) {
		if (this.props.text && nextProps.text && this.props.previousText && nextProps.previousText) {
			return (
					this.props.text.text !== nextProps.text.text ||
					this.props.text.from !== nextProps.text.from ||
					this.props.text.time !== nextProps.text.time ||
					this.props.previousText.from !== nextProps.previousText.from ||
					this.props.previousText.time !== nextProps.previousText.time
				);
		} else {
			return true;
		}
	}

	_showMenu() {
		const { text, textMetadata, currentUser } = this.props;

		const menu = {};

		if (textMetadata && textMetadata.type === "image") {
			menu["Open image in browser"] = () => Linking.openURL(textMetadata.originalUrl);
			menu["Copy image link"] = () => Clipboard.setText(textMetadata.originalUrl);
		} else {
			menu["Copy text"] = () => Clipboard.setText(text.text);
			menu["Quote message"] = () => this.props.quoteMessage(text);
		}

		if (currentUser !== text.from) {
			menu["Reply to @" + text.from] = () => this.props.replyToMessage(text);
		}

		Modal.showActionSheetWithItems(menu);
	}

	render() {
		const { text, textMetadata, previousText, currentUser } = this.props;

		const received = text.from !== currentUser;

		const links = textUtils.getLinks(text.text);

		let cover;

		if (textMetadata && textMetadata.type === "image") {
			cover = (
				<Image
					style={[ styles.thumbnail, {
						height: parseInt(textMetadata.height, 10) || 160,
						width: parseInt(textMetadata.width, 10) || 160
					} ]}
					source={{ uri: textMetadata.thumbnailUrl }}
				/>
			);
		} else if (links.length) {
			const uri = links[0];
			const endpoint = oembed(uri);

			if (endpoint) {
				cover = (
					<Embed
						uri={uri}
						endpoint={endpoint}
						style={styles.embed}
					/>
				);
			}
		}

		let showAuthor = received,
			showTime = false;

		if (previousText) {
			if (received) {
				showAuthor = text.from !== previousText.from;
			}

			showTime = (text.time - previousText.time) > 300000;
		}

		return (
			<View {...this.props} style={[ styles.container, this.props.style ]}>
				<View style={[ styles.chat, received ? styles.received : null ]}>
					{received && showAuthor ?
						<View style={styles.avatar}>
							<AvatarController
								size={36}
								nick={text.from}
								style={styles.image}
							/>
						</View> :
						null
					}

					<TouchableOpacity onPress={this._showMenu.bind(this)}>
						<ChatBubble
							text={textMetadata && textMetadata.type === "image" ? { from: text.from } : text}
							type={received ? "left" : "right"}
							showAuthor={showAuthor}
							showArrow={received ? showAuthor : true}
							style={received ? styles.bubbleReceived : styles.bubbleSent}
						>
							{cover}
						</ChatBubble>
					</TouchableOpacity>
				</View>

				{showTime ?
					(<View style={[ styles.timestamp, received ? styles.timestampLeft : styles.timestampRight ]}>
					 <Icon
						name="access-time"
						style={styles.timestampIcon}
						size={12}
					 />
					 <Text style={styles.timestampText}>{timeUtils.long(text.time)}</Text>
					</View>) :
					null
				}
			</View>
		);
	}
}

ChatItem.propTypes = {
	text: React.PropTypes.shape({
		text: React.PropTypes.string.isRequired,
		from: React.PropTypes.string.isRequired,
		time: React.PropTypes.number.isRequired
	}).isRequired,
	textMetadata: React.PropTypes.object,
	previousText: React.PropTypes.shape({
		from: React.PropTypes.string.isRequired,
		time: React.PropTypes.number.isRequired
	}),
	currentUser: React.PropTypes.string.isRequired,
	quoteMessage: React.PropTypes.func.isRequired,
	replyToMessage: React.PropTypes.func.isRequired
};
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides control sap.ui.unified.MenuTextFieldItem.
sap.ui.define(['jquery.sap.global', 'sap/ui/core/ValueStateSupport', './MenuItemBase', './library'],
	function(jQuery, ValueStateSupport, MenuItemBase, library) {
	"use strict";

	/**
	 * Constructor for a new MenuTextFieldItem element.
	 *
	 * @param {string} [sId] Id for the new control, generated automatically if no id is given
	 * @param {object} [mSettings] Initial settings for the new control
	 *
	 * @class
	 * Special menu item which contains a label and a text field. This menu item is e.g. helpful for filter implementations.
	 * The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 * @extends sap.ui.unified.MenuItemBase
	 *
	 * @author SAP SE
	 * @version ${version}
	 * @since 1.21.0
	 *
	 * @constructor
	 * @public
	 * @alias sap.ui.unified.MenuTextFieldItem
	 * @ui5-metamodel This control/element will also be described in the UI5 (legacy) design time meta model
	 */
	var MenuTextFieldItem = MenuItemBase.extend("sap.ui.unified.MenuTextFieldItem", /** @lends sap.ui.unified.MenuTextFieldItem.prototype */ { metadata : {

		library : "sap.ui.unified",
		properties : {

			/**
			 * Defines the label of the text field of the item.
			 */
			label : {type : "string", group : "Appearance", defaultValue : null},

			/**
			 * Defines the icon of the {@link sap.ui.core.IconPool sap.ui.core.IconPool} or an image which should be displayed on the item.
			 */
			icon : {type : "sap.ui.core.URI", group : "Appearance", defaultValue : null},

			/**
			 * Defines the value of the text field of the item.
			 */
			value : {type : "string", group : "Misc", defaultValue : null},

			/**
			 * Defines the value state of the text field of the item. This allows you to visualize e.g. warnings or errors.
			 */
			valueState : {type : "sap.ui.core.ValueState", group : "Appearance", defaultValue : sap.ui.core.ValueState.None}
		}
	}});

	(function() {

	MenuTextFieldItem.prototype.render = function(oRenderManager, oItem, oMenu, oInfo){
		var rm = oRenderManager,
			bIsEnabled = oMenu.checkEnabled(oItem),
			itemId = oItem.getId();

		var sClass = "sapUiMnuItm sapUiMnuTfItm";
		if (oInfo.iItemNo == 1) {
			sClass += " sapUiMnuItmFirst";
		} else if (oInfo.iItemNo == oInfo.iTotalItems) {
			sClass += " sapUiMnuItmLast";
		}
		if (!oMenu.checkEnabled(oItem)) {
			sClass += " sapUiMnuItmDsbl";
		}
		if (oItem.getStartsSection()) {
			sClass += " sapUiMnuItmSepBefore";
		}

		rm.write("

");

		// icon/check column
		rm.write("
");
		if (oItem.getIcon()) {
			rm.writeIcon(oItem.getIcon(), null, {title: null});
		}
		rm.write("
");

		// Text filed column
		rm.write("
");
		rm.write("");
		rm.writeEscaped(oItem.getLabel() || "");
		rm.write("");
		rm.write("

"); // Helper to strech the width if needed
		rm.write("
");
		rm.write("<input id=\"" + itemId + "-tf\" tabindex=\"-1\"");
		rm.writeAttributeEscaped("value", oItem.getValue() || "");
		rm.writeAttribute("class", bIsEnabled ? "sapUiMnuTfItemTf sapUiMnuTfItemTfEnbl" : "sapUiMnuTfItemTf sapUiMnuTfItemTfDsbl");
		if (!bIsEnabled) {
			rm.writeAttribute("disabled", "disabled");
		}
		if (oInfo.bAccessible) {
			rm.writeAccessibilityState(oItem, {
				role: "textbox",
				disabled: !bIsEnabled,
				multiline: false,
				autocomplete: "none",
				labelledby: {value: /*oMenu.getId() + "-label " + */itemId + "-lbl", append: true}
			});
		}
		rm.write("/>

");

		// Right border
		rm.write("

");

		rm.write("
");
	};

	MenuTextFieldItem.prototype.hover = function(bHovered, oMenu){
		this.$().toggleClass("sapUiMnuItmHov", bHovered);

		if (bHovered && oMenu.checkEnabled(this)) {
			oMenu.closeSubmenu(false, true);
			this.$("tf").focus();
		}
	};

	MenuTextFieldItem.prototype.onAfterRendering = function(){
		this._adaptSizes();
		this.setValueState(this.getValueState());
	};

	//************ Event Handling *************

	MenuTextFieldItem.prototype.onsapup = function(oEvent){
		this.getParent().focus();
		this.getParent().onsapprevious(oEvent);
	};

	MenuTextFieldItem.prototype.onsapdown = function(oEvent){
		this.getParent().focus();
		this.getParent().onsapnext(oEvent);
	};

	MenuTextFieldItem.prototype.onsaphome = function(oEvent){
		if (this._checkCursorPosForNav(false)) {
			this.getParent().focus();
			this.getParent().onsaphome(oEvent);
		}
	};

	MenuTextFieldItem.prototype.onsapend = function(oEvent){
		if (this._checkCursorPosForNav(true)) {
			this.getParent().focus();
			this.getParent().onsapend(oEvent);
		}
	};

	MenuTextFieldItem.prototype.onsappageup = function(oEvent){
		this.getParent().focus();
		this.getParent().onsappageup(oEvent);
	};

	MenuTextFieldItem.prototype.onsappagedown = function(oEvent){
		this.getParent().focus();
		this.getParent().onsappagedown(oEvent);
	};

	MenuTextFieldItem.prototype.onsapescape = function(oEvent){
		this.getParent().onsapescape(oEvent);
	};

	MenuTextFieldItem.prototype.onkeydown = function(oEvent){
		oEvent.stopPropagation(); //Avoid bubbling key events to the Menu -> Events are only selectively forwarded
	};

	MenuTextFieldItem.prototype.onclick = function(oEvent){
		this.getParent().closeSubmenu(false, true);
		if (!sap.ui.Device.system.desktop && this.getParent().checkEnabled(this)) {
			this.focus();
		}
		oEvent.stopPropagation();
	};

	MenuTextFieldItem.prototype.onsapenter = function(oEvent){
		var sValue = this.$("tf").val();
		this.setValue(sValue);
		this.getParent().selectItem(this);
		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	// ************ Overridden API functions *************

	/**
	 * The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 *
	 * @return {sap.ui.unified.Menu}
	 * @public
	 * @name sap.ui.unified.MenuTextFieldItem#getSubmenu
	 * @deprecated The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 * @function
	 */

	/**
	 * The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 *
	 * @return {sap.ui.unified.MenuTextFieldItem} this to allow method chaining
	 * @public
	 * @name sap.ui.unified.MenuTextFieldItem#destroySubmenu
	 * @deprecated The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 * @function
	 */

	/**
	 * The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 *
	 * @param {sap.ui.unified.Menu} oSubmenu
	 * @return {sap.ui.unified.MenuTextFieldItem} this to allow method chaining
	 * @public
	 * @deprecated The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 */
	MenuTextFieldItem.prototype.setSubmenu = function(oMenu){
		jQuery.sap.log.warning("The aggregation 'submenu' is not supported for this type of menu item.", "", "sap.ui.unified.MenuTextFieldItem");
		return this;
	};

	MenuTextFieldItem.prototype.setLabel = function(sLabel){
		this.setProperty("label", sLabel, true);
		this.$("lbl").text(sLabel);
		this._adaptSizes();
		return this;
	};

	MenuTextFieldItem.prototype.setValue = function(sValue){
		this.setProperty("value", sValue, true);
		this.$("tf").val(sValue);
		//this._adaptSizes();
		return this;
	};

	MenuTextFieldItem.prototype.setValueState = function(sValueState){
		this.setProperty("valueState", sValueState, true);
		var $tf = this.$("tf");
		$tf.toggleClass("sapUiMnuTfItemTfErr", sValueState == sap.ui.core.ValueState.Error);
		$tf.toggleClass("sapUiMnuTfItemTfWarn", sValueState == sap.ui.core.ValueState.Warning);
		var sTooltip = ValueStateSupport.enrichTooltip(this, this.getTooltip_AsString());
		this.$().attr("title", sTooltip ? sTooltip : "");
		return this;
	};

	//************ Private Helpers *************

	MenuTextFieldItem.prototype.getFocusDomRef = function () {
		var $FocusRef = this.$("tf");
		return $FocusRef.length ? $FocusRef.get(0) : null;
	};

	MenuTextFieldItem.prototype._adaptSizes = function(){
		var $tf = this.$("tf");
		var $lbl = this.$("lbl");
		var offsetLeft = $lbl.length ? $lbl.get(0).offsetLeft : 0;

		if (sap.ui.getCore().getConfiguration().getRTL()) {
			$tf.parent().css({"width": "auto", "right": (this.$().outerWidth(true) - offsetLeft + ($lbl.outerWidth(true) - $lbl.outerWidth())) + "px"});
		} else {
			$tf.parent().css({"width": "auto", "left": (offsetLeft + $lbl.outerWidth(true)) + "px"});
		}
	};

	MenuTextFieldItem.prototype._checkCursorPosForNav = function(bForward) {
		var bRtl = sap.ui.getCore().getConfiguration().getRTL();
		var bBack = bForward ? bRtl : !bRtl;
		var $input = this.$("tf");
		var iPos = $input.cursorPos();
		var iLen = $input.val().length;
		if (bRtl) {
			iPos = iLen - iPos;
		}
		if ((!bBack && iPos != iLen) || (bBack && iPos != 0)) {
			return false;
		}
		return true;
	};

	}());

	return MenuTextFieldItem;

}, /* bExport= */ true);
<commit_msg>[FIX] sap.ui.unified.MenuTextFieldItem: Process enter key on keyup like for other Menuitems instead of keydown<commit_after>/*!
 * ${copyright}
 */

// Provides control sap.ui.unified.MenuTextFieldItem.
sap.ui.define(['jquery.sap.global', 'sap/ui/core/ValueStateSupport', './MenuItemBase', './library'],
	function(jQuery, ValueStateSupport, MenuItemBase, library) {
	"use strict";

	/**
	 * Constructor for a new MenuTextFieldItem element.
	 *
	 * @param {string} [sId] Id for the new control, generated automatically if no id is given
	 * @param {object} [mSettings] Initial settings for the new control
	 *
	 * @class
	 * Special menu item which contains a label and a text field. This menu item is e.g. helpful for filter implementations.
	 * The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 * @extends sap.ui.unified.MenuItemBase
	 *
	 * @author SAP SE
	 * @version ${version}
	 * @since 1.21.0
	 *
	 * @constructor
	 * @public
	 * @alias sap.ui.unified.MenuTextFieldItem
	 * @ui5-metamodel This control/element will also be described in the UI5 (legacy) design time meta model
	 */
	var MenuTextFieldItem = MenuItemBase.extend("sap.ui.unified.MenuTextFieldItem", /** @lends sap.ui.unified.MenuTextFieldItem.prototype */ { metadata : {

		library : "sap.ui.unified",
		properties : {

			/**
			 * Defines the label of the text field of the item.
			 */
			label : {type : "string", group : "Appearance", defaultValue : null},

			/**
			 * Defines the icon of the {@link sap.ui.core.IconPool sap.ui.core.IconPool} or an image which should be displayed on the item.
			 */
			icon : {type : "sap.ui.core.URI", group : "Appearance", defaultValue : null},

			/**
			 * Defines the value of the text field of the item.
			 */
			value : {type : "string", group : "Misc", defaultValue : null},

			/**
			 * Defines the value state of the text field of the item. This allows you to visualize e.g. warnings or errors.
			 */
			valueState : {type : "sap.ui.core.ValueState", group : "Appearance", defaultValue : sap.ui.core.ValueState.None}
		}
	}});

	(function() {

	MenuTextFieldItem.prototype.render = function(oRenderManager, oItem, oMenu, oInfo){
		var rm = oRenderManager,
			bIsEnabled = oMenu.checkEnabled(oItem),
			itemId = oItem.getId();

		var sClass = "sapUiMnuItm sapUiMnuTfItm";
		if (oInfo.iItemNo == 1) {
			sClass += " sapUiMnuItmFirst";
		} else if (oInfo.iItemNo == oInfo.iTotalItems) {
			sClass += " sapUiMnuItmLast";
		}
		if (!oMenu.checkEnabled(oItem)) {
			sClass += " sapUiMnuItmDsbl";
		}
		if (oItem.getStartsSection()) {
			sClass += " sapUiMnuItmSepBefore";
		}

		rm.write("

");

		// icon/check column
		rm.write("
");
		if (oItem.getIcon()) {
			rm.writeIcon(oItem.getIcon(), null, {title: null});
		}
		rm.write("
");

		// Text filed column
		rm.write("
");
		rm.write("");
		rm.writeEscaped(oItem.getLabel() || "");
		rm.write("");
		rm.write("

"); // Helper to strech the width if needed
		rm.write("
");
		rm.write("<input id=\"" + itemId + "-tf\" tabindex=\"-1\"");
		rm.writeAttributeEscaped("value", oItem.getValue() || "");
		rm.writeAttribute("class", bIsEnabled ? "sapUiMnuTfItemTf sapUiMnuTfItemTfEnbl" : "sapUiMnuTfItemTf sapUiMnuTfItemTfDsbl");
		if (!bIsEnabled) {
			rm.writeAttribute("disabled", "disabled");
		}
		if (oInfo.bAccessible) {
			rm.writeAccessibilityState(oItem, {
				role: "textbox",
				disabled: !bIsEnabled,
				multiline: false,
				autocomplete: "none",
				labelledby: {value: /*oMenu.getId() + "-label " + */itemId + "-lbl", append: true}
			});
		}
		rm.write("/>

");

		// Right border
		rm.write("

");

		rm.write("
");
	};

	MenuTextFieldItem.prototype.hover = function(bHovered, oMenu){
		this.$().toggleClass("sapUiMnuItmHov", bHovered);

		if (bHovered && oMenu.checkEnabled(this)) {
			oMenu.closeSubmenu(false, true);
			this.$("tf").focus();
		}
	};

	MenuTextFieldItem.prototype.onAfterRendering = function(){
		this._adaptSizes();
		this.setValueState(this.getValueState());
	};

	//************ Event Handling *************

	MenuTextFieldItem.prototype.onsapup = function(oEvent){
		this.getParent().focus();
		this.getParent().onsapprevious(oEvent);
	};

	MenuTextFieldItem.prototype.onsapdown = function(oEvent){
		this.getParent().focus();
		this.getParent().onsapnext(oEvent);
	};

	MenuTextFieldItem.prototype.onsaphome = function(oEvent){
		if (this._checkCursorPosForNav(false)) {
			this.getParent().focus();
			this.getParent().onsaphome(oEvent);
		}
	};

	MenuTextFieldItem.prototype.onsapend = function(oEvent){
		if (this._checkCursorPosForNav(true)) {
			this.getParent().focus();
			this.getParent().onsapend(oEvent);
		}
	};

	MenuTextFieldItem.prototype.onsappageup = function(oEvent){
		this.getParent().focus();
		this.getParent().onsappageup(oEvent);
	};

	MenuTextFieldItem.prototype.onsappagedown = function(oEvent){
		this.getParent().focus();
		this.getParent().onsappagedown(oEvent);
	};

	MenuTextFieldItem.prototype.onsapescape = function(oEvent){
		this.getParent().onsapescape(oEvent);
	};

	MenuTextFieldItem.prototype.onkeydown = function(oEvent){
		oEvent.stopPropagation(); //Avoid bubbling key events to the Menu -> Events are only selectively forwarded
	};

	MenuTextFieldItem.prototype.onclick = function(oEvent){
		this.getParent().closeSubmenu(false, true);
		if (!sap.ui.Device.system.desktop && this.getParent().checkEnabled(this)) {
			this.focus();
		}
		oEvent.stopPropagation();
	};

	MenuTextFieldItem.prototype.onkeyup = function(oEvent){
		//like sapenter but on keyup -> see Menu.prototype.onkeyup
		if (!jQuery.sap.PseudoEvents.sapenter.fnCheck(oEvent)) {
			return;
		}
		var sValue = this.$("tf").val();
		this.setValue(sValue);
		this.getParent().selectItem(this);
		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	// ************ Overridden API functions *************

	/**
	 * The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 *
	 * @return {sap.ui.unified.Menu}
	 * @public
	 * @name sap.ui.unified.MenuTextFieldItem#getSubmenu
	 * @deprecated The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 * @function
	 */

	/**
	 * The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 *
	 * @return {sap.ui.unified.MenuTextFieldItem} this to allow method chaining
	 * @public
	 * @name sap.ui.unified.MenuTextFieldItem#destroySubmenu
	 * @deprecated The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 * @function
	 */

	/**
	 * The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 *
	 * @param {sap.ui.unified.Menu} oSubmenu
	 * @return {sap.ui.unified.MenuTextFieldItem} this to allow method chaining
	 * @public
	 * @deprecated The aggregation submenu (inherited from parent class) is not supported for this type of menu item.
	 */
	MenuTextFieldItem.prototype.setSubmenu = function(oMenu){
		jQuery.sap.log.warning("The aggregation 'submenu' is not supported for this type of menu item.", "", "sap.ui.unified.MenuTextFieldItem");
		return this;
	};

	MenuTextFieldItem.prototype.setLabel = function(sLabel){
		this.setProperty("label", sLabel, true);
		this.$("lbl").text(sLabel);
		this._adaptSizes();
		return this;
	};

	MenuTextFieldItem.prototype.setValue = function(sValue){
		this.setProperty("value", sValue, true);
		this.$("tf").val(sValue);
		//this._adaptSizes();
		return this;
	};

	MenuTextFieldItem.prototype.setValueState = function(sValueState){
		this.setProperty("valueState", sValueState, true);
		var $tf = this.$("tf");
		$tf.toggleClass("sapUiMnuTfItemTfErr", sValueState == sap.ui.core.ValueState.Error);
		$tf.toggleClass("sapUiMnuTfItemTfWarn", sValueState == sap.ui.core.ValueState.Warning);
		var sTooltip = ValueStateSupport.enrichTooltip(this, this.getTooltip_AsString());
		this.$().attr("title", sTooltip ? sTooltip : "");
		return this;
	};

	//************ Private Helpers *************

	MenuTextFieldItem.prototype.getFocusDomRef = function () {
		var $FocusRef = this.$("tf");
		return $FocusRef.length ? $FocusRef.get(0) : null;
	};

	MenuTextFieldItem.prototype._adaptSizes = function(){
		var $tf = this.$("tf");
		var $lbl = this.$("lbl");
		var offsetLeft = $lbl.length ? $lbl.get(0).offsetLeft : 0;

		if (sap.ui.getCore().getConfiguration().getRTL()) {
			$tf.parent().css({"width": "auto", "right": (this.$().outerWidth(true) - offsetLeft + ($lbl.outerWidth(true) - $lbl.outerWidth())) + "px"});
		} else {
			$tf.parent().css({"width": "auto", "left": (offsetLeft + $lbl.outerWidth(true)) + "px"});
		}
	};

	MenuTextFieldItem.prototype._checkCursorPosForNav = function(bForward) {
		var bRtl = sap.ui.getCore().getConfiguration().getRTL();
		var bBack = bForward ? bRtl : !bRtl;
		var $input = this.$("tf");
		var iPos = $input.cursorPos();
		var iLen = $input.val().length;
		if (bRtl) {
			iPos = iLen - iPos;
		}
		if ((!bBack && iPos != iLen) || (bBack && iPos != 0)) {
			return false;
		}
		return true;
	};

	}());

	return MenuTextFieldItem;

}, /* bExport= */ true);
<|endoftext|>