<commit_before>function logtray(options, DB) {

  'use strict';

// initiate
  let self = this;

  // Libraries
  const tracer  = require('../tools/tracer.js'),
        crel    = require('crel');

  self.icon     = { toggleTray: '<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3v-3h18v3z"/></svg>' };
  // Global variables
  self.elements = {
    controlbar: document.querySelector('#mde-controlbar'),
    reload: document.querySelector('#mde-reload'),
    toggleTray: {},
    tray: {},
    resizeTray: {},
    logs: {}
  };

  // Setup variables if not setup already
  DB.set('logtrayOpen', DB.get('logtrayOpen') || false);
  DB.set('logtrayHeight', DB.get('logtrayHeight') || window.innerHeight * 0.25);

  buildlogtrayButton(DB.get('logtrayOpen'));
  buildlogtray(DB.get('logtrayOpen'));

  // Bind toggle tray after all elements created in DOM
  self.elements.toggleTray.addEventListener('click', (e) => toggleLogTray(DB.get('logtrayOpen')), false);

  window.console.log = (message) => {
    // Gather message trace information
    const trace = tracer(new Error());

    displayLog({ message, fileName: trace.fileName, filePath: trace.filePath, lineNumber: trace.lineNumber });
  };

  if (options.displayErrors === true) {
    window.onerror = (message, filePath, lineNumber) => {
      const fileName = filePath.replace(/^.*[\\\/]/, '');
      displayLog({message, fileName, filePath, lineNumber, isError: true});
    }
  }

  function buildlogtrayButton(state) {
    state = state ? 'active': '';
    crel(self.elements.controlbar,
      self.elements.toggleTray = crel('button', { 'id': 'mde-toggle-logtray', 'class': 'mde ' + state } )
    );
    self.elements.toggleTray.innerHTML = self.icon.toggleTray;
  }

  function buildlogtray() {
    state = state() ? 'active': '';
    crel(document.body,
      self.elements.tray = crel('div',
         { 'id': 'mde-logtray', 'class': `mde ${state}`},
        self.elements.resizeTray = crel('button', {'id': 'mde-logtray-resize-bar', 'class': 'mde'}),
        self.elements.logs = crel('div', {'id': 'mde-logs'})
      )
    );
    self.elements.resizeTray.innerHTML = '• • •';

    setTrayHeight(DB.get('logtrayHeight'));

    window.addEventListener('resize', (e) => setTrayHeight(DB.get('logtrayHeight')), false);
    self.elements.resizeTray.addEventListener('touchstart', (e) => resizeLogTray(e), false);
    self.elements.resizeTray.addEventListener('mousedown', (e) => resizeLogTray(e), false);
  }

  // constants

  function state() {
    return DB.get('logtrayOpen');
  }

  // modify global

  function setTrayHeight(height) {
    // set min height to match resize button height
    const min = self.elements.controlbar.offsetHeight;

    // Set max height based on if reload button is displayed
    const max = window.innerHeight - (self.elements.controlbar.offsetHeight + 20);

    // Ensure height is within range, if not get closest value
    height = height > max ? max : height;
    height = height < min ? min : height;

    // Update logtray height and store value in DB
    self.elements.tray.style.height = height+'px';
    DB.set('logtrayHeight', height);
  }

  // actions

  function toggleLogTray(state) {
    DB.set('logtrayOpen', !state);
    self.elements.toggleTray.classList.toggle('active', !state);
    self.elements.tray.classList.toggle('active', !state);
  }

  // Resize logtray by dragging up and down on the bar
  function resizeLogTray(e) {
    // starting y coordinate on screen (from touch input or mouse)
    const startY = e.type === 'touchstart' ? e.changedTouches[0].clientY : e.clientY;
    // starting height of logtray
    const startH = DB.get('logtrayHeight');
    // check if log tray is scrolled to bottom,
    const startScrolledBottom = self.elements.logs.scrollHeight - self.elements.logs.scrollTop <= self.elements.logs.clientHeight + 1;
    // check if log tray is scrolled to top
    const startScrolledTop = self.elements.logs.scrollTop === 0;

    const onMove = (e) => {
      // Get current y position on screen
      const currentY = e.type === 'touchmove' ? e.changedTouches[0].clientY : e.clientY;
      // Calculate distance between starting and current Y
      const dragDistance = startY - currentY;

      // Update tray height
      setTrayHeight(startH + dragDistance);

      // If logtray is scrolled to the bottom, ensure it remains that way
      if (startScrolledBottom) {
        self.elements.logs.scrollTop = self.elements.logs.scrollHeight;
      }
    };

    function onEnd(e) {
      // remove listeners to prevent stacking and conflictions
      e.target.removeEventListener('touchmove', onMove, false);
      e.target.removeEventListener('touchend', onEnd, false);
      document.removeEventListener('mousemove', onMove, false);
      document.removeEventListener('mouseup', onEnd, false);
    }

    // Handle input dragging logtray resize bar
    e.target.addEventListener('touchmove', onMove, false);
    e.target.addEventListener('touchend', onEnd, false);
    document.addEventListener('mousemove', onMove, false);
    document.addEventListener('mouseup', onEnd, false);
  }
//
  function displayLog({message, filePath, fileName, lineNumber, type = 'log'}) {
    const lastLogElement = self.elements.logs.lastChild || false;

    // Get and handle var types
    if (type === 'error') {
      type = 'error';
    } else if (typeof message === "string") {
      type = 'string';
    } else if (typeof message === "number") {
      type = 'number';
    } else if (typeof message === "boolean") {
      type = 'boolean';
    } else if (typeof message === "object") {
      type = 'object';
    } else if (Array.isArray( message )) {
      type = 'array';
    } else if (message === null) {
      type = 'null';
    } else if (message == null) {
      type = 'undefined';
    }

    // manually set 'undefined' and 'null' messages which when left alone will display nothing at all
    if (type === 'null' || type === 'undefined') {
      message = type;
    // convert objects and arrays to string
    } else if (type === 'object' || type === 'array') {
      message = JSON.stringify(message, undefined, 2);
      // convert number and boolean values to string
    } else if (type === 'number' || type === 'boolean') {
      message = message.toString();
    }

    // Check whether to scroll tray to the bottom after displaying message
    // Scroll only if logtray is initially at the bottom
    const scrollToBottom = self.elements.logs.scrollHeight - self.elements.logs.scrollTop <= self.elements.logs.clientHeight + 1;

    // Check whether to increment last message amount or create a new message
    const lastLogMessage = typeof lastLogElement === 'object' ? lastLogElement.querySelector('.mde-log-message-full').innerHTML : null;
    const incrementLastMessage = self.elements.logs.children.length > 0 && lastLogMessage === message;

    // If this message matches the previous one, increment it.
    if (incrementLastMessage) {
      const lastLogAmount = lastLogElement.querySelector('.mde-log-amount').innerHTML || 1;
      lastLogElement.querySelector('.mde-log-amount').innerHTML = lastLogAmount + 1;
    // Otherwise create a new log message
    } else {
      const log = crel('div', { 'class': 'mde-log mde-log-type-' + type },
        crel('div', { 'class': 'mde-log-amount' }),
        crel('div', { 'class': 'mde-log-message-single' }, message),
        crel('a', { 'class': 'mde-log-trace', 'href': filePath, 'target': '_blank'}, fileName + ':' + lineNumber),
        crel('pre', { 'class': 'mde-log-message-full' })
      );

      log.querySelector('.mde-log-message-full').innerHTML = message;

      // Listen for toggling full message
      log.querySelector('.mde-log-message-single').addEventListener('click', (e) => {
        log.classList.toggle('mde-log-open');
      });

      // Append log to DOM
      crel(self.elements.logs, log);
    }
  }
}

module.exports = logtray;<commit_msg>export function via 'module.exports' directly<commit_after>module.exports = function logtray(options, DB) {

  'use strict';

  // initiate
  let self = this;

  // Libraries
  const tracer  = require('../tools/tracer.js'),
        crel    = require('crel');

  self.icon     = { toggleTray: '<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3v-3h18v3z"/></svg>' };
  // Global variables
  self.elements = {
    controlbar: document.querySelector('#mde-controlbar'),
    reload: document.querySelector('#mde-reload'),
    toggleTray: {},
    tray: {},
    resizeTray: {},
    logs: {}
  };

  // Setup variables if not setup already
  DB.set('logtrayOpen', DB.get('logtrayOpen') || false);
  DB.set('logtrayHeight', DB.get('logtrayHeight') || window.innerHeight * 0.25);

  buildlogtrayButton(DB.get('logtrayOpen'));
  buildlogtray(DB.get('logtrayOpen'));

  // Bind toggle tray after all elements created in DOM
  self.elements.toggleTray.addEventListener('click', (e) => toggleLogTray(DB.get('logtrayOpen')), false);

  window.console.log = (message) => {
    // Gather message trace information
    const trace = tracer(new Error());

    displayLog({ message, fileName: trace.fileName, filePath: trace.filePath, lineNumber: trace.lineNumber });
  };

  if (options.displayErrors === true) {
    window.onerror = (message, filePath, lineNumber) => {
      const fileName = filePath.replace(/^.*[\\\/]/, '');
      displayLog({message, fileName, filePath, lineNumber, isError: true});
    }
  }

  function buildlogtrayButton(state) {
    state = state ? 'active': '';
    crel(self.elements.controlbar,
      self.elements.toggleTray = crel('button', { 'id': 'mde-toggle-logtray', 'class': 'mde ' + state } )
    );
    self.elements.toggleTray.innerHTML = self.icon.toggleTray;
  }

  function buildlogtray() {
    state = state() ? 'active': '';
    crel(document.body,
      self.elements.tray = crel('div',
         { 'id': 'mde-logtray', 'class': `mde ${state}`},
        self.elements.resizeTray = crel('button', {'id': 'mde-logtray-resize-bar', 'class': 'mde'}),
        self.elements.logs = crel('div', {'id': 'mde-logs'})
      )
    );
    self.elements.resizeTray.innerHTML = '• • •';

    setTrayHeight(DB.get('logtrayHeight'));

    window.addEventListener('resize', (e) => setTrayHeight(DB.get('logtrayHeight')), false);
    self.elements.resizeTray.addEventListener('touchstart', (e) => resizeLogTray(e), false);
    self.elements.resizeTray.addEventListener('mousedown', (e) => resizeLogTray(e), false);
  }

  // constants

  function state() {
    return DB.get('logtrayOpen');
  }

  // modify global

  function setTrayHeight(height) {
    // set min height to match resize button height
    const min = self.elements.controlbar.offsetHeight;

    // Set max height based on if reload button is displayed
    const max = window.innerHeight - (self.elements.controlbar.offsetHeight + 20);

    // Ensure height is within range, if not get closest value
    height = height > max ? max : height;
    height = height < min ? min : height;

    // Update logtray height and store value in DB
    self.elements.tray.style.height = height+'px';
    DB.set('logtrayHeight', height);
  }

  // actions

  function toggleLogTray(state) {
    DB.set('logtrayOpen', !state);
    self.elements.toggleTray.classList.toggle('active', !state);
    self.elements.tray.classList.toggle('active', !state);
  }

  // Resize logtray by dragging up and down on the bar
  function resizeLogTray(e) {
    // starting y coordinate on screen (from touch input or mouse)
    const startY = e.type === 'touchstart' ? e.changedTouches[0].clientY : e.clientY;
    // starting height of logtray
    const startH = DB.get('logtrayHeight');
    // check if log tray is scrolled to bottom,
    const startScrolledBottom = self.elements.logs.scrollHeight - self.elements.logs.scrollTop <= self.elements.logs.clientHeight + 1;
    // check if log tray is scrolled to top
    const startScrolledTop = self.elements.logs.scrollTop === 0;

    const onMove = (e) => {
      // Get current y position on screen
      const currentY = e.type === 'touchmove' ? e.changedTouches[0].clientY : e.clientY;
      // Calculate distance between starting and current Y
      const dragDistance = startY - currentY;

      // Update tray height
      setTrayHeight(startH + dragDistance);

      // If logtray is scrolled to the bottom, ensure it remains that way
      if (startScrolledBottom) {
        self.elements.logs.scrollTop = self.elements.logs.scrollHeight;
      }
    };

    function onEnd(e) {
      // remove listeners to prevent stacking and conflictions
      e.target.removeEventListener('touchmove', onMove, false);
      e.target.removeEventListener('touchend', onEnd, false);
      document.removeEventListener('mousemove', onMove, false);
      document.removeEventListener('mouseup', onEnd, false);
    }

    // Handle input dragging logtray resize bar
    e.target.addEventListener('touchmove', onMove, false);
    e.target.addEventListener('touchend', onEnd, false);
    document.addEventListener('mousemove', onMove, false);
    document.addEventListener('mouseup', onEnd, false);
  }

  function displayLog({message, filePath, fileName, lineNumber, type = 'log'}) {
    const lastLogElement = self.elements.logs.lastChild || false;

    // Get and handle var types
    if (type === 'error') {
      type = 'error';
    } else if (typeof message === "string") {
      type = 'string';
    } else if (typeof message === "number") {
      type = 'number';
    } else if (typeof message === "boolean") {
      type = 'boolean';
    } else if (typeof message === "object") {
      type = 'object';
    } else if (Array.isArray( message )) {
      type = 'array';
    } else if (message === null) {
      type = 'null';
    } else if (message == null) {
      type = 'undefined';
    }

    // manually set 'undefined' and 'null' messages which when left alone will display nothing at all
    if (type === 'null' || type === 'undefined') {
      message = type;
    // convert objects and arrays to string
    } else if (type === 'object' || type === 'array') {
      message = JSON.stringify(message, undefined, 2);
      // convert number and boolean values to string
    } else if (type === 'number' || type === 'boolean') {
      message = message.toString();
    }

    // Check whether to scroll tray to the bottom after displaying message
    // Scroll only if logtray is initially at the bottom
    const scrollToBottom = self.elements.logs.scrollHeight - self.elements.logs.scrollTop <= self.elements.logs.clientHeight + 1;

    // Check whether to increment last message amount or create a new message
    const lastLogMessage = typeof lastLogElement === 'object' ? lastLogElement.querySelector('.mde-log-message-full').innerHTML : null;
    const incrementLastMessage = self.elements.logs.children.length > 0 && lastLogMessage === message;

    // If this message matches the previous one, increment it.
    if (incrementLastMessage) {
      const lastLogAmount = lastLogElement.querySelector('.mde-log-amount').innerHTML || 1;
      lastLogElement.querySelector('.mde-log-amount').innerHTML = lastLogAmount + 1;
    // Otherwise create a new log message
    } else {
      const log = crel('div', { 'class': 'mde-log mde-log-type-' + type },
        crel('div', { 'class': 'mde-log-amount' }),
        crel('div', { 'class': 'mde-log-message-single' }, message),
        crel('a', { 'class': 'mde-log-trace', 'href': filePath, 'target': '_blank'}, fileName + ':' + lineNumber),
        crel('pre', { 'class': 'mde-log-message-full' })
      );

      log.querySelector('.mde-log-message-full').innerHTML = message;

      // Listen for toggling full message
      log.querySelector('.mde-log-message-single').addEventListener('click', (e) => {
        log.classList.toggle('mde-log-open');
      });

      // Append log to DOM
      crel(self.elements.logs, log);
    }
  }
}<|endoftext|>


<commit_before>/* eslint-disable react/prop-types */
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
import { FormattedMessage, injectIntl } from 'react-intl'
import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
import React, { Component } from 'react'

import { ComponentContext } from '../../util/contexts'
import {
  findRoute,
  findRoutes,
  getVehiclePositionsForRoute
} from '../../actions/api'
import {
  getAgenciesFromRoutes,
  getModesForActiveAgencyFilter,
  getSortedFilteredRoutes
} from '../../util/state'
import { getFormattedMode } from '../../util/i18n'
import { getModeFromRoute } from '../../util/viewer'
import {
  setMainPanelContent,
  setRouteViewerFilter,
  setViewedRoute
} from '../../actions/ui'
import Icon from '../util/icon'

import { RouteName, RouteRow } from './RouteRow'
import RouteDetails from './route-details'

class RouteViewer extends Component {
  static propTypes = {
    agencies: PropTypes.array,
    filter: PropTypes.shape({
      agency: PropTypes.string,
      mode: PropTypes.string,
      search: PropTypes.string
    }),
    findRoute: findRoute.type,
    getVehiclePositionsForRoute: getVehiclePositionsForRoute.type,
    hideBackButton: PropTypes.bool,
    modes: PropTypes.array,
    routes: PropTypes.array,
    setViewedRoute: setViewedRoute.type,
    transitOperators: PropTypes.array,
    viewedRoute: PropTypes.shape({
      patternId: PropTypes.string,
      routeId: PropTypes.string
    }),
    // Routes have many more properties, but none are guaranteed
    viewedRouteObject: PropTypes.shape({ id: PropTypes.string })
  }

  state = {
    /** Used to track if all routes have been rendered */
    initialRender: true
  }

  static contextType = ComponentContext

  /**
   * If we're viewing a pattern's stops, route to
   * main route viewer, otherwise go back to main view
   */
  _backClicked = () =>
    this.props.viewedRoute === null
      ? this.props.setMainPanelContent(null)
      : this.props.setViewedRoute({
          ...this.props.viewedRoute,
          patternId: null
        })

  componentDidMount() {
    const { findRoutes, routes } = this.props
    if (!routes || routes.length <= 1) findRoutes()
  }

  /** Used to scroll to actively viewed route on load */
  componentDidUpdate() {
    const { routes } = this.props
    const { initialRender } = this.state

    // Wait until more than the one route is present.
    // This ensures that there is something to scroll past!
    if (initialRender && routes.length > 1) {
      // Using requestAnimationFrame() ensures that the scroll only happens once
      // paint is complete
      window.requestAnimationFrame(() => {
        // Setting initialRender to false ensures that routeRow will not initiate
        // any more scrolling
        this.setState({ initialRender: false })
      })
    }
  }

  /**
   * Handle filter dropdown change. Id of the filter is equivalent to the key in the
   * route object
   */
  onFilterChange = (event) => {
    const { eventPhase, target } = event
    // If the dropdown changes without user interaction, don't update!
    // see https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
    if (eventPhase !== Event.BUBBLING_PHASE) {
      return
    }
    const { id, value } = target
    // id will be either 'agency' or 'mode' based on the dropdown used
    this.props.setRouteViewerFilter({ [id]: value })
  }

  /**
   * Update search state when user updates search field
   */
  onSearchChange = (event) => {
    const { target } = event
    const { value } = target
    this.props.setRouteViewerFilter({ search: value })
  }

  render() {
    const {
      agencies,
      filter,
      findRoute,
      getVehiclePositionsForRoute,
      hideBackButton,
      intl,
      modes,
      routes: sortedRoutes,
      setViewedRoute,
      transitOperators,
      viewedRoute,
      viewedRouteObject
    } = this.props

    const { initialRender } = this.state
    const { ModeIcon } = this.context

    // If patternId is present, we're looking at a specific pattern's stops
    if (viewedRoute?.patternId && viewedRouteObject) {
      const { patternId } = viewedRoute
      const route = viewedRouteObject
      // Find operator based on agency_id (extracted from OTP route ID).
      const operator =
        coreUtils.route.getTransitOperatorFromOtpRoute(
          route,
          transitOperators
        ) || {}

      return (

          {/* Header Block */}

            {/* Always show back button, as we don't write a route anymore */}

              <Button bsSize="small" onClick={this._backClicked}>
                <Icon type="arrow-left" />
                <FormattedMessage id="common.forms.back" />
              </Button>

              {route && ModeIcon && (
                <ModeIcon
                  aria-label={getFormattedMode(
                    getModeFromRoute(route).toLowerCase(),
                    intl
                  )}
                  mode={getModeFromRoute(route)}
                  width={22}
                />
              )}
              <RouteName operator={operator} route={route} />

          <RouteDetails
            operator={operator}
            pattern={route?.patterns?.[patternId]}
            route={route}
          />

      )
    }
    const { search } = filter

    return (

        {/* Header Block */}

          {/* Back button */}
          {!hideBackButton && (

              <Button bsSize="small" onClick={this._backClicked}>
                <Icon type="arrow-left" />
                <FormattedMessage id="common.forms.back" />
              </Button>

          )}

          {/* Header Text */}

            <FormattedMessage id="components.RouteViewer.title" />

            <FormattedMessage id="components.RouteViewer.details" />

              <Icon type="filter" />
              <select
                aria-label={intl.formatMessage({
                  id: 'components.RouteViewer.agencyFilter'
                })}
                id="agency"
                onBlur={this.onFilterChange}
                onChange={this.onFilterChange}
                value={filter.agency || ''}
              >
                <option value="">
                  {intl.formatMessage({
                    id: 'components.RouteViewer.allAgencies'
                  })}
                </option>
                {agencies.map((agency) => (
                  <option key={agency} value={agency}>
                    {agency}
                  </option>
                ))}
              </select>
              <select
                aria-label={intl.formatMessage({
                  id: 'components.RouteViewer.modeFilter'
                })}
                id="mode"
                onBlur={this.onFilterChange}
                onChange={this.onFilterChange}
                value={filter.mode || ''}
              >
                <option value="">
                  {intl.formatMessage({
                    id: 'components.RouteViewer.allModes'
                  })}
                </option>
                {modes.map((mode) => (
                  <option key={mode} value={mode.toUpperCase()}>
                    {getFormattedMode(mode.toLowerCase(), intl)}
                  </option>
                ))}
              </select>

              <Icon type="search" />
              <input
                onChange={this.onSearchChange}
                placeholder={intl.formatMessage({
                  id: 'components.RouteViewer.findARoute'
                })}
                type="search"
                value={search}
              />

          {sortedRoutes.map((route) => {
            // Find operator based on agency_id (extracted from OTP route ID).
            const operator =
              coreUtils.route.getTransitOperatorFromOtpRoute(
                route,
                transitOperators
              ) || {}
            return (
              <RouteRow
                findRoute={findRoute}
                getVehiclePositionsForRoute={getVehiclePositionsForRoute}
                initialRender={initialRender}
                intl={intl}
                isActive={viewedRoute && viewedRoute.routeId === route.id}
                key={route.id}
                operator={operator}
                route={route}
                setViewedRoute={setViewedRoute}
              />
            )
          })}
          {/* check modes length to differentiate between loading and over-filtered */}
          {modes.length > 0 && sortedRoutes.length === 0 && (

              <FormattedMessage id="components.RouteViewer.noFilteredRoutesFound" />

          )}

    )
  }
}

// connect to redux store

const mapStateToProps = (state, ownProps) => {
  return {
    agencies: getAgenciesFromRoutes(state),
    filter: state.otp.ui.routeViewer.filter,
    modes: getModesForActiveAgencyFilter(state),
    routes: getSortedFilteredRoutes(state),
    transitOperators: state.otp.config.transitOperators,
    viewedRoute: state.otp.ui.viewedRoute,
    viewedRouteObject:
      state.otp.transitIndex.routes?.[state.otp.ui.viewedRoute?.routeId]
  }
}

const mapDispatchToProps = {
  findRoute,
  findRoutes,
  getVehiclePositionsForRoute,
  setMainPanelContent,
  setRouteViewerFilter,
  setViewedRoute
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(injectIntl(RouteViewer))
<commit_msg>refactor(route-viewer): remove fa icons<commit_after>/* eslint-disable react/prop-types */
import { ArrowLeft } from '@styled-icons/fa-solid/ArrowLeft'
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
import { Filter } from '@styled-icons/fa-solid/Filter'
import { FormattedMessage, injectIntl } from 'react-intl'
import { Search } from '@styled-icons/fa-solid/Search'
import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
import React, { Component } from 'react'

import { ComponentContext } from '../../util/contexts'
import {
  findRoute,
  findRoutes,
  getVehiclePositionsForRoute
} from '../../actions/api'
import {
  getAgenciesFromRoutes,
  getModesForActiveAgencyFilter,
  getSortedFilteredRoutes
} from '../../util/state'
import { getFormattedMode } from '../../util/i18n'
import { getModeFromRoute } from '../../util/viewer'

import { RouteName, RouteRow } from './RouteRow'
import {
  setMainPanelContent,
  setRouteViewerFilter,
  setViewedRoute
} from '../../actions/ui'
import StyledIconWrapper from '../util/styledIcon'

import RouteDetails from './route-details'

class RouteViewer extends Component {
  static propTypes = {
    agencies: PropTypes.array,
    filter: PropTypes.shape({
      agency: PropTypes.string,
      mode: PropTypes.string,
      search: PropTypes.string
    }),
    findRoute: findRoute.type,
    getVehiclePositionsForRoute: getVehiclePositionsForRoute.type,
    hideBackButton: PropTypes.bool,
    modes: PropTypes.array,
    routes: PropTypes.array,
    setViewedRoute: setViewedRoute.type,
    transitOperators: PropTypes.array,
    viewedRoute: PropTypes.shape({
      patternId: PropTypes.string,
      routeId: PropTypes.string
    }),
    // Routes have many more properties, but none are guaranteed
    viewedRouteObject: PropTypes.shape({ id: PropTypes.string })
  }

  state = {
    /** Used to track if all routes have been rendered */
    initialRender: true
  }

  static contextType = ComponentContext

  /**
   * If we're viewing a pattern's stops, route to
   * main route viewer, otherwise go back to main view
   */
  _backClicked = () =>
    this.props.viewedRoute === null
      ? this.props.setMainPanelContent(null)
      : this.props.setViewedRoute({
          ...this.props.viewedRoute,
          patternId: null
        })

  componentDidMount() {
    const { findRoutes, routes } = this.props
    if (!routes || routes.length <= 1) findRoutes()
  }

  /** Used to scroll to actively viewed route on load */
  componentDidUpdate() {
    const { routes } = this.props
    const { initialRender } = this.state

    // Wait until more than the one route is present.
    // This ensures that there is something to scroll past!
    if (initialRender && routes.length > 1) {
      // Using requestAnimationFrame() ensures that the scroll only happens once
      // paint is complete
      window.requestAnimationFrame(() => {
        // Setting initialRender to false ensures that routeRow will not initiate
        // any more scrolling
        this.setState({ initialRender: false })
      })
    }
  }

  /**
   * Handle filter dropdown change. Id of the filter is equivalent to the key in the
   * route object
   */
  onFilterChange = (event) => {
    const { eventPhase, target } = event
    // If the dropdown changes without user interaction, don't update!
    // see https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase
    if (eventPhase !== Event.BUBBLING_PHASE) {
      return
    }
    const { id, value } = target
    // id will be either 'agency' or 'mode' based on the dropdown used
    this.props.setRouteViewerFilter({ [id]: value })
  }

  /**
   * Update search state when user updates search field
   */
  onSearchChange = (event) => {
    const { target } = event
    const { value } = target
    this.props.setRouteViewerFilter({ search: value })
  }

  render() {
    const {
      agencies,
      filter,
      findRoute,
      getVehiclePositionsForRoute,
      hideBackButton,
      intl,
      modes,
      routes: sortedRoutes,
      setViewedRoute,
      transitOperators,
      viewedRoute,
      viewedRouteObject
    } = this.props

    const { initialRender } = this.state
    const { ModeIcon } = this.context

    // If patternId is present, we're looking at a specific pattern's stops
    if (viewedRoute?.patternId && viewedRouteObject) {
      const { patternId } = viewedRoute
      const route = viewedRouteObject
      // Find operator based on agency_id (extracted from OTP route ID).
      const operator =
        coreUtils.route.getTransitOperatorFromOtpRoute(
          route,
          transitOperators
        ) || {}

      return (

          {/* Header Block */}

            {/* Always show back button, as we don't write a route anymore */}

              <Button bsSize="small" onClick={this._backClicked}>

                <FormattedMessage id="common.forms.back" />
              </Button>

              {route && ModeIcon && (
                <ModeIcon
                  aria-label={getFormattedMode(
                    getModeFromRoute(route).toLowerCase(),
                    intl
                  )}
                  mode={getModeFromRoute(route)}
                  width={22}
                />
              )}
              <RouteName operator={operator} route={route} />

          <RouteDetails
            operator={operator}
            pattern={route?.patterns?.[patternId]}
            route={route}
          />

      )
    }
    const { search } = filter

    return (

        {/* Header Block */}

          {/* Back button */}
          {!hideBackButton && (

              <Button bsSize="small" onClick={this._backClicked}>

                <FormattedMessage id="common.forms.back" />
              </Button>

          )}

          {/* Header Text */}

            <FormattedMessage id="components.RouteViewer.title" />

            <FormattedMessage id="components.RouteViewer.details" />

              <select
                aria-label={intl.formatMessage({
                  id: 'components.RouteViewer.agencyFilter'
                })}
                id="agency"
                onBlur={this.onFilterChange}
                onChange={this.onFilterChange}
                value={filter.agency || ''}
              >
                <option value="">
                  {intl.formatMessage({
                    id: 'components.RouteViewer.allAgencies'
                  })}
                </option>
                {agencies.map((agency) => (
                  <option key={agency} value={agency}>
                    {agency}
                  </option>
                ))}
              </select>
              <select
                aria-label={intl.formatMessage({
                  id: 'components.RouteViewer.modeFilter'
                })}
                id="mode"
                onBlur={this.onFilterChange}
                onChange={this.onFilterChange}
                value={filter.mode || ''}
              >
                <option value="">
                  {intl.formatMessage({
                    id: 'components.RouteViewer.allModes'
                  })}
                </option>
                {modes.map((mode) => (
                  <option key={mode} value={mode.toUpperCase()}>
                    {getFormattedMode(mode.toLowerCase(), intl)}
                  </option>
                ))}
              </select>

              <input
                onChange={this.onSearchChange}
                placeholder={intl.formatMessage({
                  id: 'components.RouteViewer.findARoute'
                })}
                type="search"
                value={search}
              />

          {sortedRoutes.map((route) => {
            // Find operator based on agency_id (extracted from OTP route ID).
            const operator =
              coreUtils.route.getTransitOperatorFromOtpRoute(
                route,
                transitOperators
              ) || {}
            return (
              <RouteRow
                findRoute={findRoute}
                getVehiclePositionsForRoute={getVehiclePositionsForRoute}
                initialRender={initialRender}
                intl={intl}
                isActive={viewedRoute && viewedRoute.routeId === route.id}
                key={route.id}
                operator={operator}
                route={route}
                setViewedRoute={setViewedRoute}
              />
            )
          })}
          {/* check modes length to differentiate between loading and over-filtered */}
          {modes.length > 0 && sortedRoutes.length === 0 && (

              <FormattedMessage id="components.RouteViewer.noFilteredRoutesFound" />

          )}

    )
  }
}

// connect to redux store

const mapStateToProps = (state, ownProps) => {
  return {
    agencies: getAgenciesFromRoutes(state),
    filter: state.otp.ui.routeViewer.filter,
    modes: getModesForActiveAgencyFilter(state),
    routes: getSortedFilteredRoutes(state),
    transitOperators: state.otp.config.transitOperators,
    viewedRoute: state.otp.ui.viewedRoute,
    viewedRouteObject:
      state.otp.transitIndex.routes?.[state.otp.ui.viewedRoute?.routeId]
  }
}

const mapDispatchToProps = {
  findRoute,
  findRoutes,
  getVehiclePositionsForRoute,
  setMainPanelContent,
  setRouteViewerFilter,
  setViewedRoute
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(injectIntl(RouteViewer))
<|endoftext|>


<commit_before>Template.ratingSubmit.created = function () {
  Session.set('ratingSubmitErrors', {});
};

Template.ratingSubmit.helpers({

  errorMessage: function (field) {
    return Session.get('ratingSubmitErrors')[field];
  },

  errorClass: function (field) {
    return !!Session.get('ratingSubmitErrors')[field] ? 'has-error' : '';
  }

}); // Template.ratingSubmit.helpers

Template.ratingSubmit.events({

  'reset form': function (evt, template) {
    $('#score').rateit('value', null);
  },

  'submit form': function (evt, template) {
    evt.preventDefault();

    var errors = {};

    var score = $('#score').rateit('value');
    if (!score) {
      errors.body = "Please provide a score";
      return Session.set('ratingSubmitErrors', errors);
    }

    var rating = {
      score: score,
      tastingId: template.data._id
    };

    Meteor.call('ratingInsert', rating, function (err, ratingId) {
      if (err) {
        throwError(err.reason);
      }
    });
  }

}); // Template.ratingSubmit.events
<commit_msg>fixed problem with rendering rateit control at template render time.<commit_after>Template.ratingSubmit.created = function () {
  Session.set('ratingSubmitErrors', {});
};

Template.ratingSubmit.helpers({

  errorMessage: function (field) {
    return Session.get('ratingSubmitErrors')[field];
  },

  errorClass: function (field) {
    return !!Session.get('ratingSubmitErrors')[field] ? 'has-error' : '';
  }

}); // Template.ratingSubmit.helpers

Template.ratingSubmit.events({

  'reset form': function (evt, template) {
    $('#score').rateit('value', null);
  },

  'submit form': function (evt, template) {
    evt.preventDefault();

    var errors = {};

    var score = $('#score').rateit('value');
    if (!score) {
      errors.body = "Please provide a score";
      return Session.set('ratingSubmitErrors', errors);
    }

    var rating = {
      score: score,
      tastingId: template.data._id
    };

    Meteor.call('ratingInsert', rating, function (err, ratingId) {
      if (err) {
        throwError(err.reason);
      }
    });
  }

}); // Template.ratingSubmit.events

Template.ratingSubmit.rendered = function () {
  // at .created() time, it's too early to run rateit(), so run it
  // at .rendered() time.
  this.$('.rateit').rateit();
};
<|endoftext|>


<commit_before>'use strict';

require('dotenv').config();

const util = require('util');
const fs = require('fs');
const path = require('path');
const webPush = require('web-push');

webPush.setGCMAPIKey(process.env.GCM_API_KEY);

const NOTIFICATION_KEY = process.env.NOTIFICATIONS_KEY;

let storedPushCredentials = [];

/* TODO: SECURE DIAGNOSTIC ENDPOINT BEFORE HACKATHON */

exports.credentials = function(req, res) {
  res.json(storedPushCredentials);
}

exports.clearCredentials = function(req, res) {
  storedPushCredentials = [];
  res.json(storedPushCredentials);
}

/**
 * Publishes a notification
 */
exports.publish = function(req, res) {

    req.checkBody('apikey', 'API Key must not be empty').notEmpty();
    //req.checkBody('apikey', 'API Key is incorrect').notificationKeyValid(NOTIFICATION_KEY);
    req.checkBody('text', 'Payload must not be empty').notEmpty();

    const errors = req.validationErrors();
    if (errors) {
        res.status(400).send('There have been validation errors: ' + util.inspect(errors));
        return;
    }

  const pushPayload = {
    text: req.body.text || "Static server notification payload...",
    icon: req.body.icon || req.protocol + '://' + req.get('host') + '/apple-touch-icon.png'
  }

  storedPushCredentials.forEach(function(pushCredentials, i) {

    webPush.sendNotification(pushCredentials, JSON.stringify(pushPayload))
    .then(function() {
      console.log("Push notification sent successfully");
    }).catch(function(error) {
      console.log(error);
    });

  }); //end forEach

  res.sendStatus(201);
};

/**
 * Subscribes a client to notifications
 */
exports.subscribe = function(req, res) {
  const newPushCredentials = {
    endpoint: req.body.endpoint,
    keys: {
      p256dh: req.body.p256dh,
      auth: req.body.auth
    }
  };

   const found = storedPushCredentials.some(function (element) {
    return element.endpoint === newPushCredentials.endpoint;
  });

  if (!found) {
    console.log("Pushing");
    storedPushCredentials.push(newPushCredentials);
  }

  res.sendStatus(200);
};
<commit_msg>serve notification image over https<commit_after>'use strict';

require('dotenv').config();

const util = require('util');
const fs = require('fs');
const path = require('path');
const webPush = require('web-push');

webPush.setGCMAPIKey(process.env.GCM_API_KEY);

const NOTIFICATION_KEY = process.env.NOTIFICATIONS_KEY;

let storedPushCredentials = [];

/* TODO: SECURE DIAGNOSTIC ENDPOINT BEFORE HACKATHON */

exports.credentials = function(req, res) {
  res.json(storedPushCredentials);
}

exports.clearCredentials = function(req, res) {
  storedPushCredentials = [];
  res.json(storedPushCredentials);
}

/**
 * Publishes a notification
 */
exports.publish = function(req, res) {

    req.checkBody('apikey', 'API Key must not be empty').notEmpty();
    //req.checkBody('apikey', 'API Key is incorrect').notificationKeyValid(NOTIFICATION_KEY);
    req.checkBody('text', 'Payload must not be empty').notEmpty();

    const errors = req.validationErrors();
    if (errors) {
        res.status(400).send('There have been validation errors: ' + util.inspect(errors));
        return;
    }

  const pushPayload = {
    text: req.body.text || "Static server notification payload...",
    icon: req.body.icon || 'https://' + req.get('host') + '/apple-touch-icon.png'
  }

  storedPushCredentials.forEach(function(pushCredentials, i) {

    webPush.sendNotification(pushCredentials, JSON.stringify(pushPayload))
    .then(function() {
      console.log("Push notification sent successfully");
    }).catch(function(error) {
      console.log(error);
    });

  }); //end forEach

  res.sendStatus(201);
};

/**
 * Subscribes a client to notifications
 */
exports.subscribe = function(req, res) {
  const newPushCredentials = {
    endpoint: req.body.endpoint,
    keys: {
      p256dh: req.body.p256dh,
      auth: req.body.auth
    }
  };

   const found = storedPushCredentials.some(function (element) {
    return element.endpoint === newPushCredentials.endpoint;
  });

  if (!found) {
    console.log("Pushing");
    storedPushCredentials.push(newPushCredentials);
  }

  res.sendStatus(200);
};
<|endoftext|>


<commit_before>'use strict';

const mysql = require('mysql');

let del = function(clientEmail) {

    let connection = mysql.createConnection(this.config);

    connection.connect();

    let promise = new Promise((resolve, reject) => {

        if (typeof clientEmail !== 'string') {
            return reject({ statusCode: 400, message: 'Need be email to delete a client' });
        }

        let sql = 'UPDATE client SET `clientDeleted`=1 WHERE `email`=?;';

        connection.query(sql, clientEmail, (err, rows) => {
            if (err) {
                return reject(err);
            }

            connection.end();

            return resolve(rows);
        });
    });

    return promise;
};

module.exports = del;
<commit_msg>two params for delete to avoid wrong delete<commit_after>'use strict';

const mysql = require('mysql');

let del = function(id, clientEmail) {

    let connection = mysql.createConnection(this.config);

    connection.connect();

    let promise = new Promise((resolve, reject) => {

        if (typeof clientEmail !== 'string') {
            return reject({ statusCode: 400, message: 'Need be email to delete a client' });
        }

        let sql = 'UPDATE client SET `clientDeleted`=1 WHERE `email`=? AND id=?;';
        let query = mysql.format(sql, [clientEmail, id]);

        connection.query(query, (err, rows) => {
            if (err) {
                return reject(err);
            }

            connection.end();

            return resolve(rows);
        });
    });

    return promise;
};

module.exports = del;
<|endoftext|>


<commit_before>

define([], function() {
    'use strict';
    return {
        union: function(a,b) {
            return a.concat(b.filter(function (item) {
                return a.indexOf(item) < 0;
            }));
        },
	processModel: function(collection) {
	    var self = this;
	    self.checkObjects(collection.objects);
	    self.makeConvenienceMembers(collection.objects);
	},
        getObjectsByType: function(objects, type) {
            var self = this;
            var objPaths = Object.keys(objects);
            return objPaths.filter((objPath) => {
                var obj = objects[objPath];
                return obj.type == type;
            }).map((objPath) => {
                return objects[objPath];
            });
        },
	makeConvenienceMembers: function(objects) {
	    var self = this;
            var orderedTypes = [
                'Message',
                'Service',
                'Advertised Message',
                'Advertised Service',
                'Link',
                'Source Library',
                'System Library',
                'External Definitions',
                'Component',
                'Package',
            ];

            var typeToConvMap = {
                'Package': 'makePackageConvenience',
                'Component': 'makeComponentConvenience',
                'Message': 'makeMessageConvenience',
                'Service': 'makeServiceConvenience',
                'Advertised Message': 'makeAdvertisedMessageConvenience',
                'Advertised Service': 'makeAdvertisedServiceConvenience',
                'External Definitions': 'makeExternalDefinitionsConvenience',
                'Link': 'makeLinkConvenience',
                'Source Library': 'makeSourceLibraryConvenience',
                'System Library': 'makeSystemLibraryConvenience',
            };
            orderedTypes.map((type) => {
                self.getObjectsByType(objects, type).map((obj) => {
                    self[typeToConvMap[type]](obj, objects);
                });
            });
	},
        makePackageConvenience: function(obj, objects) {
            var self = this;
            obj.Packages = [];
            obj.CMAKE_COMMANDS = [];

            if (obj.Component_list) {
                obj.Component_list.map(function(o) {
                    obj.Packages = self.union(obj.Packages, o.Packages);
                    obj.CMAKE_COMMANDS = self.union(obj.CMAKE_COMMANDS, o.CMAKE_COMMANDS);
                });
            }

            obj.BuildDependencies = obj.Packages.filter((p) => { return p != obj.name; });
            obj.RunDependencies = obj.BuildDependencies;
            obj.PackageDependencies = obj.BuildDependencies;
        },
        makeComponentConvenience: function(obj, objects) {
            // make .Package convenience member for rendering code
            var parent = objects[obj.parentPath];
            obj.Package = parent.name;
            // make component have .Packages which are all the package
            // dependencies it has
            obj.Packages = [];
            // make component have .LinkLibraries which provide the
            // shared objects that need to be linked
            obj.LinkLibraries = [];
            obj.CMAKE_COMMANDS = [];
	    // make components have .Types which provide their
	    // messages/services
	    obj.Types = [];
            obj.Dependencies = [];

            // types
            function tFunc(t, key) {
                var type = t[key];
		if ( obj.Types.indexOf(type) == -1) {
                    // Add to types
		    obj.Types.push(type);
                    if ( obj.Packages.indexOf(type.Package) == -1 ) {
                        // add to packages
                        obj.Packages.push(type.Package);
                    }
                    if ( type.type.indexOf('External') == -1 &&
                         obj.Dependencies.indexOf(type.Package) == -1) {
                        obj.Dependencies.push(type.Package + '_generate_messages_cpp');
                    }
                }
            };
	    if (obj.Publisher_list) {
		obj.Publisher_list.map(function(pub) { tFunc(pub, 'Message'); });
	    }
	    if (obj.Subscriber_list) {
		obj.Subscriber_list.map(function(sub) { tFunc(sub, 'Message'); });
	    }
	    if (obj.Client_list) {
		obj.Client_list.map(function(cli) { tFunc(cli, 'Service'); });
	    }
	    if (obj.Server_list) {
		obj.Server_list.map(function(srv) { tFunc(srv, 'Service'); });
	    }
            // libraries
            if (obj.Libraries) {
                obj.Libraries.map(function(lib) {
                    if (lib.type == 'Source Library') {
                        if ( obj.Packages.indexOf(lib.name) == -1 ) {
                            // add to packages
                            obj.Packages.push(lib.name);
                        }
                    }
                    if ( lib.LIBRARIES ) {
                        if ( obj.LinkLibraries.indexOf(lib.LIBRARIES) == -1)
                            // add to dependencies
                            obj.LinkLibraries.push(lib.LIBRARIES);
                    }
                    if ( lib.CMAKE_COMMANDS ) {
                        if ( obj.CMAKE_COMMANDS.indexOf(lib.CMAKE_COMMANDS) == -1) {
                            obj.CMAKE_COMMANDS.push(lib.CMAKE_COMMANDS);
                        }
                    }
                });
            }
        },
        makeMessageConvenience: function(obj, objects) {
            var parent = objects[obj.parentPath];
            // make .Package convenience member for rendering code
            obj.Package = parent.name;
            // make .TypeName convenience member for rendering code
            obj.TypeName = obj.name;
            // make .AdvertisedName convenience member for rendering code
            obj.AdvertisedName = obj.Package + '/' + obj.name;
        },
        makeServiceConvenience: function(obj, objects) {
            var parent = objects[obj.parentPath];
            // make .Package convenience member for rendering code
            obj.Package = parent.name;
            // make .TypeName convenience member for rendering code
            obj.TypeName = obj.name;
            // make .AdvertisedName convenience member for rendering code
            obj.AdvertisedName = obj.Package + '/' + obj.name;
        },
        makeAdvertisedMessageConvenience: function(obj, objects) {
            var TopicType = objects[obj.pointers['Type']];
            // make .Package convenience member for rendering code
            obj.Package = TopicType.Package;
            // make .TypeName convenience member for rendering code
            obj.TypeName = TopicType.name;
            // make .AdvertisedName convenience member for rendering code
            obj.AdvertisedName = obj.name;
        },
        makeAdvertisedServiceConvenience: function(obj, objects) {
            var TopicType = objects[obj.pointers['Type']];
            // make .Package convenience member for rendering code
            obj.Package = TopicType.Package;
            // make .TypeName convenience member for rendering code
            obj.TypeName = TopicType.name;
            // make .AdvertisedName convenience member for rendering code
            obj.AdvertisedName = obj.name;
        },
        makeLinkConvenience: function(obj, objects) {
	    // copy the Link's address to the interface to which it's connected
	    var intf = obj.src;
	    intf.IP = obj.IP;
        },
        makeExternalDefinitionsConvenience: function(obj, objects) {
            // this object is a child of the Software object
            // and contains external message and service
            // definitions, should make them easier to access.
        },
        makeSourceLibraryConvenience: function(obj, objects) {
            if (obj.CompilesToSO) {
                obj.LIBRARY_NAME = "LIB_" + obj.name.toUpperCase();
                obj.CMAKE_COMMANDS = "set (" +
                    obj.LIBRARY_NAME +
                    " ${PROJECT_SOURCE_DIR}/../../devel/lib/lib"+
                    obj.name + ".so)";
                // update it so it can be put in directly to target_link_libraries
                obj.LIBRARIES = '${'+ obj.LIBRARY_NAME + '}';
            }
        },
        makeSystemLibraryConvenience: function(obj, objects) {
            obj.LIBRARIES = obj['Link Libraries'];
        },
        validName: function(name) {
	    var varDeclExp = new RegExp(/^[a-zA-Z_][a-zA-Z0-9_]*$/gi);
	    return varDeclExp.test(name);
        },
        checkName: function(o) {
            var self = this;
            if (!self.validName( o.name )) {
                throw new String(o.type + ' ' +
                                 o.name + ' has invalid name: '+o.name+
                                 '. Name must be valid c/c++ name, following regex pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/');
            }
        },
	checkObjects: function(objects) {
	    var self = this;

	    self.checkPointers(objects);

	    // check objects by type
	    var objPaths = Object.keys(objects);
	    objPaths.map(function(objPath) {
		var obj = objects[objPath];
		var parent = objects[ obj.parentPath ];
		if (obj.type == 'Host' && parent && parent.type == 'System') {
		    self.checkHost(obj);
		} else if (obj.type == 'Component') {
                    self.checkName(obj);
		} else if (obj.type == 'Package') {
                    self.checkName(obj);
		} else if (obj.type == 'Publisher') {
                    self.checkName(obj);
		} else if (obj.type == 'Subscriber') {
                    self.checkName(obj);
		} else if (obj.type == 'Client') {
                    self.checkName(obj);
		} else if (obj.type == 'Server') {
                    self.checkName(obj);
		} else if (obj.type == 'Timer') {
                    self.checkName(obj);
		} else if (obj.type == 'Message') {
                    self.checkName(obj);
		    self.checkMessage(obj);
		} else if (obj.type == 'Service') {
                    self.checkName(obj);
		    self.checkService(obj);
		} else if (obj.type == 'Node') {
		    self.checkNode(obj);
		} else if (obj.type == 'Container' && parent && parent.type == 'Deployment') {
		    self.checkContainer(obj);
		}
	    });
	},
	checkPointers: function(objects) {
	    // checks all objects' pointers to ensure no pointers are null and all pointers are to loaded objects
	    var objPaths = Object.keys(objects);
	    objPaths.map(function(objPath) {
		var obj = objects[objPath];
		for (var pointer in obj.pointers) {
		    var path = obj.pointers[pointer];
		    if (path) {
			var dst = objects[path];
			if (dst) {
			}
			else if (pointer != 'base') {
			    throw new String(obj.type + ' ' + obj.name + ' has pointer ' + pointer + ' to object outside of the project!');
			}
		    }
		    else {
			throw new String(obj.type + ' ' + obj.name + ' has null pointer ' + pointer + '!');
		    }
		}
	    });
	},
	checkHost: function(host) {
	    // checks host to ensure that it has at least one user and network interface
	    if (host.Interface_list === undefined) {
		throw new String("Error: " + host.name + ' has no defined network interfaces, make sure to add at least one in the model and connect it to a network!');
	    } else if (host.sets['Users'].length == 0) {
		throw new String("Error: " + host.name + " has no defined users, make sure to add one through the host's SetEditor visualizer!");
	    }
	},
	checkMessage: function(message) {
	    // checks message definition to ensure it has no ';'
	    if (message.Definition.indexOf(';') != -1) {
		throw new String("Error: " + message.name + " contains invalid symbol ';', ROS message definitions don't have ';' at the end of their lines!");
	    }
	},
	checkService: function(service) {
	    // checks message definition to ensure it has no ';' and that it has '---' to delineate input/output
	    if (service.Definition.indexOf(';') != -1) {
		throw new String("Error: " + service.name + " contains invalid symbol ';', ROS service definitions don't have ';' at the end of their lines!");
	    } else if (service.Definition.indexOf('---') == -1) {
		throw new String("Error: " + service.name + " does not contain '---', which must be on its own line separating the input (above) from the output (below)!");
	    }
	},
	checkNode: function(node) {
	    // checks node to ensure that there is at least one component
	    if (node.Component_list === undefined) {
		throw new String("Error: " + node.name + ' contains no component instances, make sure to add at least one in the model!');
	    }
	},
	checkContainer: function(container) {
	    // checks container to ensure that there is at least one node
	    if (container.Node_list === undefined) {
		throw new String("Error: "+container.name+' has no nodes, make sure to add at least one in the model!');
	    }
	},
    }
});
<commit_msg>fixes #211 properly render external message and external service into code now that we have advertised message/service as well<commit_after>

define([], function() {
    'use strict';
    return {
        union: function(a,b) {
            return a.concat(b.filter(function (item) {
                return a.indexOf(item) < 0;
            }));
        },
	processModel: function(collection) {
	    var self = this;
	    self.checkObjects(collection.objects);
	    self.makeConvenienceMembers(collection.objects);
	},
        getObjectsByType: function(objects, type) {
            var self = this;
            var objPaths = Object.keys(objects);
            return objPaths.filter((objPath) => {
                var obj = objects[objPath];
                return obj.type == type;
            }).map((objPath) => {
                return objects[objPath];
            });
        },
	makeConvenienceMembers: function(objects) {
	    var self = this;
            var orderedTypes = [
                'Message',
                'Service',
                'Advertised Message',
                'Advertised Service',
                'Link',
                'Source Library',
                'System Library',
                'External Definitions',
                'Component',
                'Package',
            ];

            var typeToConvMap = {
                'Package': 'makePackageConvenience',
                'Component': 'makeComponentConvenience',
                'Message': 'makeMessageConvenience',
                'Service': 'makeServiceConvenience',
                'External Message': 'makeExternalMessageConvenience',
                'External Service': 'makeExternalServiceConvenience',
                'Advertised Message': 'makeAdvertisedMessageConvenience',
                'Advertised Service': 'makeAdvertisedServiceConvenience',
                'External Definitions': 'makeExternalDefinitionsConvenience',
                'Link': 'makeLinkConvenience',
                'Source Library': 'makeSourceLibraryConvenience',
                'System Library': 'makeSystemLibraryConvenience',
            };
            orderedTypes.map((type) => {
                self.getObjectsByType(objects, type).map((obj) => {
                    self[typeToConvMap[type]](obj, objects);
                });
            });
	},
        makePackageConvenience: function(obj, objects) {
            var self = this;
            obj.Packages = [];
            obj.CMAKE_COMMANDS = [];

            if (obj.Component_list) {
                obj.Component_list.map(function(o) {
                    obj.Packages = self.union(obj.Packages, o.Packages);
                    obj.CMAKE_COMMANDS = self.union(obj.CMAKE_COMMANDS, o.CMAKE_COMMANDS);
                });
            }

            obj.BuildDependencies = obj.Packages.filter((p) => { return p != obj.name; });
            obj.RunDependencies = obj.BuildDependencies;
            obj.PackageDependencies = obj.BuildDependencies;
        },
        makeComponentConvenience: function(obj, objects) {
            // make .Package convenience member for rendering code
            var parent = objects[obj.parentPath];
            obj.Package = parent.name;
            // make component have .Packages which are all the package
            // dependencies it has
            obj.Packages = [];
            // make component have .LinkLibraries which provide the
            // shared objects that need to be linked
            obj.LinkLibraries = [];
            obj.CMAKE_COMMANDS = [];
	    // make components have .Types which provide their
	    // messages/services
	    obj.Types = [];
            obj.Dependencies = [];

            // types
            function tFunc(t, key) {
                var type = t[key];
		if ( obj.Types.indexOf(type) == -1) {
                    // Add to types
		    obj.Types.push(type);
                    if ( obj.Packages.indexOf(type.Package) == -1 ) {
                        // add to packages
                        obj.Packages.push(type.Package);
                    }
                    if ( type.type.indexOf('External') == -1 &&
                         obj.Dependencies.indexOf(type.Package) == -1) {
                        obj.Dependencies.push(type.Package + '_generate_messages_cpp');
                    }
                }
            };
	    if (obj.Publisher_list) {
		obj.Publisher_list.map(function(pub) { tFunc(pub, 'Message'); });
	    }
	    if (obj.Subscriber_list) {
		obj.Subscriber_list.map(function(sub) { tFunc(sub, 'Message'); });
	    }
	    if (obj.Client_list) {
		obj.Client_list.map(function(cli) { tFunc(cli, 'Service'); });
	    }
	    if (obj.Server_list) {
		obj.Server_list.map(function(srv) { tFunc(srv, 'Service'); });
	    }
            // libraries
            if (obj.Libraries) {
                obj.Libraries.map(function(lib) {
                    if (lib.type == 'Source Library') {
                        if ( obj.Packages.indexOf(lib.name) == -1 ) {
                            // add to packages
                            obj.Packages.push(lib.name);
                        }
                    }
                    if ( lib.LIBRARIES ) {
                        if ( obj.LinkLibraries.indexOf(lib.LIBRARIES) == -1)
                            // add to dependencies
                            obj.LinkLibraries.push(lib.LIBRARIES);
                    }
                    if ( lib.CMAKE_COMMANDS ) {
                        if ( obj.CMAKE_COMMANDS.indexOf(lib.CMAKE_COMMANDS) == -1) {
                            obj.CMAKE_COMMANDS.push(lib.CMAKE_COMMANDS);
                        }
                    }
                });
            }
        },
        makeMessageConvenience: function(obj, objects) {
            var parent = objects[obj.parentPath];
            // make .Package convenience member for rendering code
            obj.Package = parent.name;
            // make .TypeName convenience member for rendering code
            obj.TypeName = obj.name;
            // make .AdvertisedName convenience member for rendering code
            obj.AdvertisedName = obj.Package + '/' + obj.name;
        },
        makeServiceConvenience: function(obj, objects) {
            var parent = objects[obj.parentPath];
            // make .Package convenience member for rendering code
            obj.Package = parent.name;
            // make .TypeName convenience member for rendering code
            obj.TypeName = obj.name;
            // make .AdvertisedName convenience member for rendering code
            obj.AdvertisedName = obj.Package + '/' + obj.name;
        },
        makeExternalMessageConvenience: function(obj, objects) {
            // already will have .Package convenience member for rendering code from model
            // make .TypeName convenience member for rendering code
            obj.TypeName = obj.name;
            // make .AdvertisedName convenience member for rendering code
            obj.AdvertisedName = obj.Package + '/' + obj.name;
        },
        makeExternalServiceConvenience: function(obj, objects) {
            // already will have .Package convenience member for rendering code from model
            // make .TypeName convenience member for rendering code
            obj.TypeName = obj.name;
            // make .AdvertisedName convenience member for rendering code
            obj.AdvertisedName = obj.Package + '/' + obj.name;
        },
        makeAdvertisedMessageConvenience: function(obj, objects) {
            var TopicType = objects[obj.pointers['Type']];
            // make .Package convenience member for rendering code
            obj.Package = TopicType.Package;
            // make .TypeName convenience member for rendering code
            obj.TypeName = TopicType.name;
            // make .AdvertisedName convenience member for rendering code
            obj.AdvertisedName = obj.name;
        },
        makeAdvertisedServiceConvenience: function(obj, objects) {
            var TopicType = objects[obj.pointers['Type']];
            // make .Package convenience member for rendering code
            obj.Package = TopicType.Package;
            // make .TypeName convenience member for rendering code
            obj.TypeName = TopicType.name;
            // make .AdvertisedName convenience member for rendering code
            obj.AdvertisedName = obj.name;
        },
        makeLinkConvenience: function(obj, objects) {
	    // copy the Link's address to the interface to which it's connected
	    var intf = obj.src;
	    intf.IP = obj.IP;
        },
        makeExternalDefinitionsConvenience: function(obj, objects) {
            // this object is a child of the Software object
            // and contains external message and service
            // definitions, should make them easier to access.
        },
        makeSourceLibraryConvenience: function(obj, objects) {
            if (obj.CompilesToSO) {
                obj.LIBRARY_NAME = "LIB_" + obj.name.toUpperCase();
                obj.CMAKE_COMMANDS = "set (" +
                    obj.LIBRARY_NAME +
                    " ${PROJECT_SOURCE_DIR}/../../devel/lib/lib"+
                    obj.name + ".so)";
                // update it so it can be put in directly to target_link_libraries
                obj.LIBRARIES = '${'+ obj.LIBRARY_NAME + '}';
            }
        },
        makeSystemLibraryConvenience: function(obj, objects) {
            obj.LIBRARIES = obj['Link Libraries'];
        },
        validName: function(name) {
	    var varDeclExp = new RegExp(/^[a-zA-Z_][a-zA-Z0-9_]*$/gi);
	    return varDeclExp.test(name);
        },
        checkName: function(o) {
            var self = this;
            if (!self.validName( o.name )) {
                throw new String(o.type + ' ' +
                                 o.name + ' has invalid name: '+o.name+
                                 '. Name must be valid c/c++ name, following regex pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/');
            }
        },
	checkObjects: function(objects) {
	    var self = this;

	    self.checkPointers(objects);

	    // check objects by type
	    var objPaths = Object.keys(objects);
	    objPaths.map(function(objPath) {
		var obj = objects[objPath];
		var parent = objects[ obj.parentPath ];
		if (obj.type == 'Host' && parent && parent.type == 'System') {
		    self.checkHost(obj);
		} else if (obj.type == 'Component') {
                    self.checkName(obj);
		} else if (obj.type == 'Package') {
                    self.checkName(obj);
		} else if (obj.type == 'Publisher') {
                    self.checkName(obj);
		} else if (obj.type == 'Subscriber') {
                    self.checkName(obj);
		} else if (obj.type == 'Client') {
                    self.checkName(obj);
		} else if (obj.type == 'Server') {
                    self.checkName(obj);
		} else if (obj.type == 'Timer') {
                    self.checkName(obj);
		} else if (obj.type == 'Message') {
                    self.checkName(obj);
		    self.checkMessage(obj);
		} else if (obj.type == 'Service') {
                    self.checkName(obj);
		    self.checkService(obj);
		} else if (obj.type == 'Node') {
		    self.checkNode(obj);
		} else if (obj.type == 'Container' && parent && parent.type == 'Deployment') {
		    self.checkContainer(obj);
		}
	    });
	},
	checkPointers: function(objects) {
	    // checks all objects' pointers to ensure no pointers are null and all pointers are to loaded objects
	    var objPaths = Object.keys(objects);
	    objPaths.map(function(objPath) {
		var obj = objects[objPath];
		for (var pointer in obj.pointers) {
		    var path = obj.pointers[pointer];
		    if (path) {
			var dst = objects[path];
			if (dst) {
			}
			else if (pointer != 'base') {
			    throw new String(obj.type + ' ' + obj.name + ' has pointer ' + pointer + ' to object outside of the project!');
			}
		    }
		    else {
			throw new String(obj.type + ' ' + obj.name + ' has null pointer ' + pointer + '!');
		    }
		}
	    });
	},
	checkHost: function(host) {
	    // checks host to ensure that it has at least one user and network interface
	    if (host.Interface_list === undefined) {
		throw new String("Error: " + host.name + ' has no defined network interfaces, make sure to add at least one in the model and connect it to a network!');
	    } else if (host.sets['Users'].length == 0) {
		throw new String("Error: " + host.name + " has no defined users, make sure to add one through the host's SetEditor visualizer!");
	    }
	},
	checkMessage: function(message) {
	    // checks message definition to ensure it has no ';'
	    if (message.Definition.indexOf(';') != -1) {
		throw new String("Error: " + message.name + " contains invalid symbol ';', ROS message definitions don't have ';' at the end of their lines!");
	    }
	},
	checkService: function(service) {
	    // checks message definition to ensure it has no ';' and that it has '---' to delineate input/output
	    if (service.Definition.indexOf(';') != -1) {
		throw new String("Error: " + service.name + " contains invalid symbol ';', ROS service definitions don't have ';' at the end of their lines!");
	    } else if (service.Definition.indexOf('---') == -1) {
		throw new String("Error: " + service.name + " does not contain '---', which must be on its own line separating the input (above) from the output (below)!");
	    }
	},
	checkNode: function(node) {
	    // checks node to ensure that there is at least one component
	    if (node.Component_list === undefined) {
		throw new String("Error: " + node.name + ' contains no component instances, make sure to add at least one in the model!');
	    }
	},
	checkContainer: function(container) {
	    // checks container to ensure that there is at least one node
	    if (container.Node_list === undefined) {
		throw new String("Error: "+container.name+' has no nodes, make sure to add at least one in the model!');
	    }
	},
    }
});
<|endoftext|>


<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2012, Ajax.org B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

// [WIP]

define(function(require, exports, module) {
"use strict";
var TokenIterator = require("../token_iterator").TokenIterator;

function is(token, type) {
    return token.type.lastIndexOf(type + ".xml") > -1;
}

// do not indent after singleton tags or <html>
exports.singletonTags = ["area", "base", "br", "col", "command", "embed", "hr", "html", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];

// insert a line break after block level tags
exports.blockTags = ["article", "aside", "blockquote", "body", "div", "dl", "fieldset", "footer", "form", "head", "header", "html", "nav", "ol", "p", "script", "section", "style", "table", "tbody", "tfoot", "thead", "ul"];

exports.beautify = function(session) {
    var iterator = new TokenIterator(session, 0, 0);
    var token = iterator.getCurrentToken();
    var tabString = session.getTabString();
    var singletonTags = exports.singletonTags;
    var blockTags = exports.blockTags;
    var nextToken;
    var breakBefore = false;
    var spaceBefore = false;
    var spaceAfter = false;
    var code = "";
    var value = "";
    var tagName = "";
    var depth = 0;
    var lastDepth = 0;
    var lastIndent = 0;
    var indent = 0;
    var unindent = false;
    var roundDepth = 0;
    var onCaseLine = false;
    var row;
    var curRow = 0;
    var rowsToAdd = 0;
    var rowTokens = [];
    var abort = false;
    var i;
    var indentNextLine = false;
    var inTag = false;
    var inCSS = false;
    var inBlock = false;
    var inCase = false;

    var trimNext = function() {
        if (nextToken && nextToken.value && nextToken.type !== 'string.regexp')
            nextToken.value = nextToken.value.trim();
    };

    var trimLine = function() {
        code = code.replace(/ +$/, "");
    };

    while (token !== null) {
        curRow = iterator.getCurrentTokenRow();
        rowTokens = iterator.$rowTokens;
        nextToken = iterator.stepForward();

        if (typeof token !== "undefined") {
            value = token.value;
            unindent = false;

            // mode
            inCSS = (tagName === "style" || session.$modeId === "ace/mode/css");

            // in tag
            if (is(token, "tag-open"))
                inTag = true;

            if (is(token, "tag-close"))
                inTag = false;

            // are we in a block tag
            if (is(token, "tag-open") && nextToken)
                inBlock = (blockTags.indexOf(nextToken.value) !== -1);

            // comments
            if (is(token, "comment.start"))
                inBlock = true;
            else if (is(token, "comment.end"))
                inBlock = false;

            // html indentation
            if (is(token, "tag-open") && value === "</") {
                // line break before closing tag unless we just had one
                if (inBlock && !breakBefore && rowsToAdd < 1)
                    rowsToAdd++;

                if (inCSS)
                    rowsToAdd = 1;

                unindent = true;
                inBlock = false;
            }

            // line break before }
            if (!inTag && !rowsToAdd && token.type === "paren.rparen" && token.value.substr(0, 1) === "}") {
                rowsToAdd++;
            }

            // switch case
            onCaseLine = false;
            if (token.type === "keyword" && value.match(/^(case|default)$/)) {
                onCaseLine = true;
                inCase = true;
            } else if (token.type === "keyword" && value === "break")
                inCase = false;

            // add rows
            if (curRow !== row) {
                rowsToAdd = curRow;

                if (row)
                    rowsToAdd -= row;
            }

            if (rowsToAdd) {
                code = code.trimRight();
                for (; rowsToAdd > 0; rowsToAdd--)
                    code += "\n";

                breakBefore = true;

                // trim value if not in a comment or string
                if (!is(token, "comment") && !token.type.match(/^(comment|string)$/))
                   value = value.trimLeft();
            }

            if (value) {
                // whitespace
                if (token.type === "keyword" && value.match(/^(if|else|elseif|for|while|switch)$/)) {
                    spaceAfter = true;
                    trimNext();

                    // space before else, elseif
                    if (token.type === "keyword" && value.match(/^(else|elseif)$/)) {
                        if (code.match(/\}[\s]*$/)) {
                            code = code.trimRight();
                            breakBefore = false;
                            spaceBefore = true;
                        }
                    }
                // trim value after opening paren
                } else if (token.type === "paren.lparen") {
                    trimNext();

                    // whitespace after {
                    if (value.substr(-1) === "{") {
                        spaceAfter = true;
                        indentNextLine = false;

                        if(!inTag)
                            rowsToAdd = 1;
                    }

                    // ensure curly brace is preceeded by whitespace
                    if (value.substr(0, 1) === "{") {
                        spaceBefore = true;

                        // collapse square and curly brackets together
                        if (code.substr(-1) !== '[' && code.trimRight().substr(-1) === '[') {
                            spaceBefore = false;
                            code = code.trim();
                            breakBefore = false;
                        } else if (code.trimRight().substr(-1) === ')') {
                            code = code.trimRight();
                        } else {
                            trimLine();
                        }
                    }
                // remove space before closing paren
                } else if (token.type === "paren.rparen") {
                    // ensure curly brace is preceeded by whitespace
                    if (value.substr(0, 1) === "}") {
                        if (code.trimRight().substr(-1) === '{') {
                            breakBefore = false;
                            code = code.trimRight();
                        } else {
                            spaceBefore = true;

                            if (inCSS)
                                rowsToAdd+=2;
                        }
                    }

                    // collapse square and curly brackets together
                    if (value.substr(0, 1) === "]") {
                        if (code.substr(-1) !== '}' && code.trimRight().substr(-1) === '}') {
                            breakBefore = false;
                            spaceBefore = false;
                            indent++;
                            code = code.trimRight();
                        }
                    }

                    // collapse round brackets together
                    if (value.substr(0, 1) === ")") {
                        if (code.substr(-1) !== '(' && code.trimRight().substr(-1) === '(') {
                            breakBefore = false;
                            spaceBefore = false;
                            indent++;
                            code = code.trimRight();
                        }
                    }

                    trimLine();
                // add spaces around conditional operators
                } else if ((token.type === "keyword.operator" || token.type === "keyword") && value.match(/^(=|==|===|!=|!==|&&|\|\||and|or|xor|\+=|.=|>|>=|<|<=|=>)$/)) {
                    code = code.trimRight();
                    spaceBefore = true;
                    spaceAfter = true;
                    trimNext();
                // remove space before semicolon
                } else if (token.type === "punctuation.operator" && value === ';') {
                    code = code.trimRight();
                    spaceAfter = true;
                    trimNext();

                    if (inCSS)
                        rowsToAdd++;
                // space after colon or comma
                } else if (token.type === "punctuation.operator" && value.match(/^(:|,)$/)) {
                    code = code.trimRight();
                    spaceAfter = true;
                    trimNext();
                // ensure space before php closing tag
                } else if (token.type === "support.php_tag" && value === "?>" && !breakBefore) {
                    code = code.trimRight();
                    spaceBefore = true;
                // remove excess space before HTML attribute
                } else if (is(token, "attribute-name") && code.substr(-1).match(/^\s$/)) {
                    spaceBefore = true;
                // remove space around attribute equals
                } else if (is(token, "attribute-equals")) {
                    trimLine();
                    trimNext();
                // remove space before HTML closing tag
                } else if (is(token, "tag-close")) {
                    trimLine();
                    if(value === "/>")
                        spaceBefore = true;
                }

                // unindent after paren
                if (token.type === "paren.rparen")
                    unindent = true;

                // add indent to code unless multiline string or comment
                if (breakBefore && !(token.type.match(/^(comment)$/) && !value.substr(0, 1).match(/^[/#]$/)) && !(token.type.match(/^(string)$/) && !value.substr(0, 1).match(/^['"]$/))) {

                    indent = lastIndent;

                    if(depth > lastDepth)
                        indent++;
                    else if(depth < lastDepth && depth < lastIndent)
                        indent--;

                    lastDepth = depth;
                    lastIndent = indent;

                    if(unindent)
                        indent--;

                    if (inCase && !onCaseLine)
                        indent++;

                    if (indentNextLine && !roundDepth) {
                        indent++;
                        indentNextLine = false;
                    }

                    for (i = 0; i < indent; i++)
                        code += tabString;
                }

                // unindent after paren
                if (token.type === "paren.rparen")
                    depth -= value.length;

                // indent after paren
                if (token.type === "paren.lparen")
                    depth += value.length;

                // indent one line after if or else
                if (token.type === "paren.lparen")
                    roundDepth += (value.match(/\(/g) || []).length;

                if (token.type === "keyword" && value.match(/^(if|else|elseif|for|while)$/)) {
                    indentNextLine = true;
                    roundDepth = 0;
                } else if (!roundDepth && value.trim() && token.type !== "comment")
                    indentNextLine = false;

                if (token.type === "paren.rparen")
                    roundDepth -= (value.match(/\)/g) || []).length;

                // add to code
                if (spaceBefore && !breakBefore) {
                    trimLine();
                    if (code.substr(-1) !== "\n")
                        code += " ";
                }

                code += value;

                if (spaceAfter)
                    code += " ";

                breakBefore = false;
                spaceBefore = false;
                spaceAfter = false;

                // line break after block tag or doctype
                if ((is(token, "tag-close") && (inBlock || blockTags.indexOf(tagName) !== -1)) || (is(token, "doctype") && value === ">")) {
                    // undo linebreak if tag is immediately closed
                    if (inBlock && nextToken && nextToken.value === "</")
                        rowsToAdd = -1;
                    else
                        rowsToAdd = 1;
                }

                // html indentation
                if (is(token, "tag-open") && value === "</")
                    depth--;

                // indent after opening tag
                if (is(token, "tag-open") && value === "<" && singletonTags.indexOf(nextToken.value) === -1)
                    depth++;

                // remove indent if unknown singleton
                if (is(token, "tag-name"))
                    tagName = value;

                if (is(token, "tag-close") && value === "/>" && singletonTags.indexOf(tagName) === -1)
                    depth--;

                row = curRow;
            }
        }

        token = nextToken;
    }

    code = code.trim();
    session.doc.setValue(code);
};

exports.commands = [{
    name: "beautify",
    exec: function(editor) {
        exports.beautify(editor.session);
    },
    bindKey: "Ctrl-Shift-B"
}];

});
<commit_msg>fix excess whitespace before {<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2012, Ajax.org B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

// [WIP]

define(function(require, exports, module) {
"use strict";
var TokenIterator = require("../token_iterator").TokenIterator;

function is(token, type) {
    return token.type.lastIndexOf(type + ".xml") > -1;
}

// do not indent after singleton tags or <html>
exports.singletonTags = ["area", "base", "br", "col", "command", "embed", "hr", "html", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];

// insert a line break after block level tags
exports.blockTags = ["article", "aside", "blockquote", "body", "div", "dl", "fieldset", "footer", "form", "head", "header", "html", "nav", "ol", "p", "script", "section", "style", "table", "tbody", "tfoot", "thead", "ul"];

exports.beautify = function(session) {
    var iterator = new TokenIterator(session, 0, 0);
    var token = iterator.getCurrentToken();
    var tabString = session.getTabString();
    var singletonTags = exports.singletonTags;
    var blockTags = exports.blockTags;
    var nextToken;
    var breakBefore = false;
    var spaceBefore = false;
    var spaceAfter = false;
    var code = "";
    var value = "";
    var tagName = "";
    var depth = 0;
    var lastDepth = 0;
    var lastIndent = 0;
    var indent = 0;
    var unindent = false;
    var roundDepth = 0;
    var onCaseLine = false;
    var row;
    var curRow = 0;
    var rowsToAdd = 0;
    var rowTokens = [];
    var abort = false;
    var i;
    var indentNextLine = false;
    var inTag = false;
    var inCSS = false;
    var inBlock = false;
    var inCase = false;

    var trimNext = function() {
        if (nextToken && nextToken.value && nextToken.type !== 'string.regexp')
            nextToken.value = nextToken.value.trim();
    };

    var trimLine = function() {
        code = code.replace(/ +$/, "");
    };

    while (token !== null) {
        curRow = iterator.getCurrentTokenRow();
        rowTokens = iterator.$rowTokens;
        nextToken = iterator.stepForward();

        if (typeof token !== "undefined") {
            value = token.value;
            unindent = false;

            // mode
            inCSS = (tagName === "style" || session.$modeId === "ace/mode/css");

            // in tag
            if (is(token, "tag-open"))
                inTag = true;

            if (is(token, "tag-close"))
                inTag = false;

            // are we in a block tag
            if (is(token, "tag-open") && nextToken)
                inBlock = (blockTags.indexOf(nextToken.value) !== -1);

            // comments
            if (is(token, "comment.start"))
                inBlock = true;
            else if (is(token, "comment.end"))
                inBlock = false;

            // html indentation
            if (is(token, "tag-open") && value === "</") {
                // line break before closing tag unless we just had one
                if (inBlock && !breakBefore && rowsToAdd < 1)
                    rowsToAdd++;

                if (inCSS)
                    rowsToAdd = 1;

                unindent = true;
                inBlock = false;
            }

            // line break before }
            if (!inTag && !rowsToAdd && token.type === "paren.rparen" && token.value.substr(0, 1) === "}") {
                rowsToAdd++;
            }

            // switch case
            onCaseLine = false;
            if (token.type === "keyword" && value.match(/^(case|default)$/)) {
                onCaseLine = true;
                inCase = true;
            } else if (token.type === "keyword" && value === "break")
                inCase = false;

            // add rows
            if (curRow !== row) {
                rowsToAdd = curRow;

                if (row)
                    rowsToAdd -= row;
            }

            if (rowsToAdd) {
                code = code.trimRight();
                for (; rowsToAdd > 0; rowsToAdd--)
                    code += "\n";

                breakBefore = true;

                // trim value if not in a comment or string
                if (!is(token, "comment") && !token.type.match(/^(comment|string)$/))
                   value = value.trimLeft();
            }

            if (value) {
                // whitespace
                if (token.type === "keyword" && value.match(/^(if|else|elseif|for|while|switch)$/)) {
                    spaceAfter = true;
                    trimNext();

                    // space before else, elseif
                    if (token.type === "keyword" && value.match(/^(else|elseif)$/)) {
                        if (code.match(/\}[\s]*$/)) {
                            code = code.trimRight();
                            breakBefore = false;
                            spaceBefore = true;
                        }
                    }
                // trim value after opening paren
                } else if (token.type === "paren.lparen") {
                    trimNext();

                    // whitespace after {
                    if (value.substr(-1) === "{") {
                        spaceAfter = true;
                        indentNextLine = false;

                        if(!inTag)
                            rowsToAdd = 1;
                    }

                    // ensure curly brace is preceeded by whitespace
                    if (value.substr(0, 1) === "{") {
                        spaceBefore = true;

                        // collapse square and curly brackets together
                        if (code.substr(-1) !== '[' && code.trimRight().substr(-1) === '[') {
                            spaceBefore = false;
                            code = code.trim();
                            breakBefore = false;
                        } else if (code.trimRight().substr(-1) === ')') {
                            code = code.trimRight();
                            breakBefore = false;
                        } else {
                            trimLine();
                        }
                    }
                // remove space before closing paren
                } else if (token.type === "paren.rparen") {
                    // ensure curly brace is preceeded by whitespace
                    if (value.substr(0, 1) === "}") {
                        if (code.trimRight().substr(-1) === '{') {
                            breakBefore = false;
                            code = code.trimRight();
                        } else {
                            spaceBefore = true;

                            if (inCSS)
                                rowsToAdd+=2;
                        }
                    }

                    // collapse square and curly brackets together
                    if (value.substr(0, 1) === "]") {
                        if (code.substr(-1) !== '}' && code.trimRight().substr(-1) === '}') {
                            breakBefore = false;
                            spaceBefore = false;
                            indent++;
                            code = code.trimRight();
                        }
                    }

                    // collapse round brackets together
                    if (value.substr(0, 1) === ")") {
                        if (code.substr(-1) !== '(' && code.trimRight().substr(-1) === '(') {
                            breakBefore = false;
                            spaceBefore = false;
                            indent++;
                            code = code.trimRight();
                        }
                    }

                    trimLine();
                // add spaces around conditional operators
                } else if ((token.type === "keyword.operator" || token.type === "keyword") && value.match(/^(=|==|===|!=|!==|&&|\|\||and|or|xor|\+=|.=|>|>=|<|<=|=>)$/)) {
                    code = code.trimRight();
                    spaceBefore = true;
                    spaceAfter = true;
                    trimNext();
                // remove space before semicolon
                } else if (token.type === "punctuation.operator" && value === ';') {
                    code = code.trimRight();
                    spaceAfter = true;
                    trimNext();

                    if (inCSS)
                        rowsToAdd++;
                // space after colon or comma
                } else if (token.type === "punctuation.operator" && value.match(/^(:|,)$/)) {
                    code = code.trimRight();
                    spaceAfter = true;
                    trimNext();
                // ensure space before php closing tag
                } else if (token.type === "support.php_tag" && value === "?>" && !breakBefore) {
                    code = code.trimRight();
                    spaceBefore = true;
                // remove excess space before HTML attribute
                } else if (is(token, "attribute-name") && code.substr(-1).match(/^\s$/)) {
                    spaceBefore = true;
                // remove space around attribute equals
                } else if (is(token, "attribute-equals")) {
                    trimLine();
                    trimNext();
                // remove space before HTML closing tag
                } else if (is(token, "tag-close")) {
                    trimLine();
                    if(value === "/>")
                        spaceBefore = true;
                }

                // unindent after paren
                if (token.type === "paren.rparen")
                    unindent = true;

                // add indent to code unless multiline string or comment
                if (breakBefore && !(token.type.match(/^(comment)$/) && !value.substr(0, 1).match(/^[/#]$/)) && !(token.type.match(/^(string)$/) && !value.substr(0, 1).match(/^['"]$/))) {

                    indent = lastIndent;

                    if(depth > lastDepth)
                        indent++;
                    else if(depth < lastDepth && depth < lastIndent)
                        indent--;

                    lastDepth = depth;
                    lastIndent = indent;

                    if(unindent)
                        indent--;

                    if (inCase && !onCaseLine)
                        indent++;

                    if (indentNextLine && !roundDepth) {
                        indent++;
                        indentNextLine = false;
                    }

                    for (i = 0; i < indent; i++)
                        code += tabString;
                }

                // unindent after paren
                if (token.type === "paren.rparen")
                    depth -= value.length;

                // indent after paren
                if (token.type === "paren.lparen")
                    depth += value.length;

                // indent one line after if or else
                if (token.type === "paren.lparen")
                    roundDepth += (value.match(/\(/g) || []).length;

                if (token.type === "keyword" && value.match(/^(if|else|elseif|for|while)$/)) {
                    indentNextLine = true;
                    roundDepth = 0;
                } else if (!roundDepth && value.trim() && token.type !== "comment")
                    indentNextLine = false;

                if (token.type === "paren.rparen")
                    roundDepth -= (value.match(/\)/g) || []).length;

                // add to code
                if (spaceBefore && !breakBefore) {
                    trimLine();
                    if (code.substr(-1) !== "\n")
                        code += " ";
                }

                code += value;

                if (spaceAfter)
                    code += " ";

                breakBefore = false;
                spaceBefore = false;
                spaceAfter = false;

                // line break after block tag or doctype
                if ((is(token, "tag-close") && (inBlock || blockTags.indexOf(tagName) !== -1)) || (is(token, "doctype") && value === ">")) {
                    // undo linebreak if tag is immediately closed
                    if (inBlock && nextToken && nextToken.value === "</")
                        rowsToAdd = -1;
                    else
                        rowsToAdd = 1;
                }

                // html indentation
                if (is(token, "tag-open") && value === "</")
                    depth--;

                // indent after opening tag
                if (is(token, "tag-open") && value === "<" && singletonTags.indexOf(nextToken.value) === -1)
                    depth++;

                // remove indent if unknown singleton
                if (is(token, "tag-name"))
                    tagName = value;

                if (is(token, "tag-close") && value === "/>" && singletonTags.indexOf(tagName) === -1)
                    depth--;

                row = curRow;
            }
        }

        token = nextToken;
    }

    code = code.trim();
    session.doc.setValue(code);
};

exports.commands = [{
    name: "beautify",
    exec: function(editor) {
        exports.beautify(editor.session);
    },
    bindKey: "Ctrl-Shift-B"
}];

});
<|endoftext|>