<commit_before>var express = require('express');
var router = express.Router();

function determineWeekday() {
  var today = new Date();
  var weekday_integer = today.getDay(); // 0 for Sunday, 1 for Monday, etc
  var hour = today.getHours(); // 0 for midnight, 23:59 for 11:59pm

  // is it late night? food trucks might still be out from previous day
  if (hour >= 0 && hour <= 4) {
    // is it sunday? rewinding one day needs to be 6, not -1
    if (weekday_integer === 0) {
      weekday_integer = 6;
    } else {
      weekday_integer -= 1;
    }
  }

  var weekdayMap = {
    0: "sunday",
    1: "monday",
    2: "tuesday",
    3: "wednesday",
    4: "thursday",
    5: "friday",
    6: "saturday"
  };
  var weekday = weekdayMap[weekday_integer];
  return weekday;
}

var FoodTruck = require('../models/foodtruck');
var Brewery = require('../models/brewery');
// var Distillery = require('../models/distillery');

/* GET api foodtrucks. */
router.get('/foodtrucks', function(req, res, next) {

  var weekday = determineWeekday();

  var query = {};
  query["schedule." + weekday + ".address"] = {$exists: true};

  FoodTruck.find(query).exec(function(err, foodtrucks) {
      if (err) {
        res.send(err);
      }
      res.json(foodtrucks);
    });
});

/* GET api breweries. */
router.get('/breweries', function(req, res, next) {
  Brewery.find(function(err, brewries) {
      if (err) {
        res.send(err);
      }
      res.json(brewries);
    });
});

/* GET api distilleries. */
router.get('/distilleries', function(req, res, next) {
  Distillery.find(function(err, distilleries) {
      if (err) {
        res.send(err);
      }
      res.json(distilleries);
    });
});

module.exports = router;
<commit_msg>Included distillery model.<commit_after>var express = require('express');
var router = express.Router();

function determineWeekday() {
  var today = new Date();
  var weekday_integer = today.getDay(); // 0 for Sunday, 1 for Monday, etc
  var hour = today.getHours(); // 0 for midnight, 23:59 for 11:59pm

  // is it late night? food trucks might still be out from previous day
  if (hour >= 0 && hour <= 4) {
    // is it sunday? rewinding one day needs to be 6, not -1
    if (weekday_integer === 0) {
      weekday_integer = 6;
    } else {
      weekday_integer -= 1;
    }
  }

  var weekdayMap = {
    0: "sunday",
    1: "monday",
    2: "tuesday",
    3: "wednesday",
    4: "thursday",
    5: "friday",
    6: "saturday"
  };
  var weekday = weekdayMap[weekday_integer];
  return weekday;
}

var FoodTruck = require('../models/foodtruck');
var Brewery = require('../models/brewery');
var Distillery = require('../models/distillery');

/* GET api foodtrucks. */
router.get('/foodtrucks', function(req, res, next) {

  var weekday = determineWeekday();

  var query = {};
  query["schedule." + weekday + ".address"] = {$exists: true};

  FoodTruck.find(query).exec(function(err, foodtrucks) {
      if (err) {
        res.send(err);
      }
      res.json(foodtrucks);
    });
});

/* GET api breweries. */
router.get('/breweries', function(req, res, next) {
  Brewery.find(function(err, brewries) {
      if (err) {
        res.send(err);
      }
      res.json(brewries);
    });
});

/* GET api distilleries. */
router.get('/distilleries', function(req, res, next) {
  Distillery.find(function(err, distilleries) {
      if (err) {
        res.send(err);
      }
      res.json(distilleries);
    });
});

module.exports = router;
<|endoftext|>


<commit_before>// Copyright 2016-2022, University of Colorado Boulder

/**
 * Spinner for numbers.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Andrea Lin (PhET Interactive Simulations)
 */

import InstanceRegistry from '../../phet-core/js/documentation/InstanceRegistry.js';
import merge from '../../phet-core/js/merge.js';
// @ts-ignore FIX DEPENDENCY ON SCENERY-PHET
import NumberDisplay from '../../scenery-phet/js/NumberDisplay.js';
import { Node, SceneryConstants } from '../../scenery/js/imports.js';
import Tandem from '../../tandem/js/Tandem.js';
import AccessibleNumberSpinner from './accessibility/AccessibleNumberSpinner.js';
import ArrowButton from './buttons/ArrowButton.js';
import sun from './sun.js';

// possible values for options.arrowsPosition
const ARROWS_POSITION_VALUES = [
  'leftRight', // arrow buttons on left and right of value
  'topBottom', // arrow buttons on top and bottom of value
  'bothRight', // both arrow buttons to the right of the value
  'bothBottom' // both arrow buttons below the value
];

class NumberSpinner extends AccessibleNumberSpinner( Node, 0 ) {

  /**
   * @param {Property.<number>} numberProperty value, must be an integer
   * @param {Property.<Range>} rangeProperty - Dynamic range of values, min and max must be integers
   * @param {Object} [options]
   * @mixes AccessibleNumberSpinner
   */
  constructor( numberProperty, rangeProperty, options ) {

    assert && assert( rangeProperty.value.contains( numberProperty.get() ),
      `value ${numberProperty.get()} is out of range ${rangeProperty.value.toString()}` );

    options = merge( {

      // {string} where to place the arrow buttons, see ARROWS_POSITION_VALUES
      arrowsPosition: 'bothRight',

      // {number|null} By default, arrows are scaled to fit dimensions of value background. This is an additional scale factor.
      arrowsScale: null,
      arrowButtonFill: 'white',
      arrowButtonStroke: 'black',
      arrowButtonLineWidth: 1,

      // {function(number):number|null} function called when the increment button is pressed.
      // Parameter is the current value, returns the new value. Defaults to adding options.deltaValue.
      incrementFunction: null,

      // {function(number):number|null} function called when the decrement button is pressed.
      // Parameter is the current value, returns the new value. Defaults to subtracting options.deltaValue.
      decrementFunction: null,

      // may be ignored if incrementFunction and decrementFunction are provided
      deltaValue: 1,

      // {number} - opt into Node's disabled opacity when enabled:false
      disabledOpacity: SceneryConstants.DISABLED_OPACITY,
      xSpacing: 5,
      ySpacing: 3,

      // NumberDisplay options
      numberDisplayOptions: {
        cornerRadius: 5,
        backgroundStroke: 'black'
      },

      // AccessibleNumberSpinner options
      valueProperty: numberProperty,
      enabledRangeProperty: rangeProperty,

      // arrow button pointer areas
      touchAreaXDilation: 0,
      touchAreaYDilation: 0,
      mouseAreaXDilation: 0,
      mouseAreaYDilation: 0,

      // PhET-iO
      tandem: Tandem.REQUIRED,
      phetioEnabledPropertyInstrumented: true // opt into default PhET-iO instrumented enabledProperty
    }, options );

    // validate options
    assert && assert( _.includes( ARROWS_POSITION_VALUES, options.arrowsPosition ), `invalid arrowsPosition: ${options.arrowsPosition}` );
    assert && assert( options.numberDisplayOptions.tandem === undefined, 'NumberSpinner sets tandem for NumberDisplay' );

    // Defaults for incrementFunction and decrementFunction
    if ( !options.incrementFunction ) {
      options.incrementFunction = value => value + options.deltaValue;
    }
    if ( !options.decrementFunction ) {
      options.decrementFunction = value => value - options.deltaValue;
    }

    const numberDisplay = new NumberDisplay( numberProperty, rangeProperty.value, merge( {},
      options.numberDisplayOptions, {
        tandem: options.tandem.createTandem( 'numberDisplay' )
      } ) );

    // buttons
    const arrowButtonOptions = {
      baseColor: options.arrowButtonFill,
      stroke: options.arrowButtonStroke,
      lineWidth: options.arrowButtonLineWidth,
      focusable: false,

      // as requested in https://github.com/phetsims/sun/issues/575
      enabledPropertyOptions: {
        phetioReadOnly: true,
        phetioFeatured: false
      }
    };

    // increment button
    const incrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'up' : 'right';
    const incrementButton = new ArrowButton( incrementDirection,
      () => incrementButton.enabled && numberProperty.set( options.incrementFunction( numberProperty.get() ) ),
      merge( {
        tandem: options.tandem.createTandem( 'incrementButton' )
      }, arrowButtonOptions ) );

    // decrement button
    const decrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'down' : 'left';
    const decrementButton = new ArrowButton( decrementDirection,
      () => decrementButton.enabled && numberProperty.set( options.decrementFunction( numberProperty.get() ) ),
      merge( {
        tandem: options.tandem.createTandem( 'decrementButton' )
      }, arrowButtonOptions ) );

    // arrow button scaling
    let arrowsScale;
    if ( !arrowsScale ) {
      if ( options.arrowsPosition === 'leftRight' ) {
        arrowsScale = numberDisplay.height / incrementButton.height;
      }
      else if ( options.arrowsPosition === 'topBottom' ) {
        arrowsScale = numberDisplay.width / incrementButton.width;
      }
      else if ( options.arrowsPosition === 'bothRight' ) {
        arrowsScale = ( ( numberDisplay.height / 2 ) - ( options.ySpacing / 2 ) ) / incrementButton.height;
      }
      else { // 'bothBottom'
        arrowsScale = ( ( numberDisplay.width / 2 ) - ( options.xSpacing / 2 ) ) / incrementButton.width;
      }
    }
    if ( options.arrowsScale ) {
      arrowsScale = arrowsScale * options.arrowsScale;
    }
    incrementButton.setScaleMagnitude( arrowsScale );
    decrementButton.setScaleMagnitude( arrowsScale );

    // Because range may change via rangeProperty, the size of numberDisplay may change, in which case layout will need
    // to be revised. See https://github.com/phetsims/sun/issues/709
    const updateLayout = () => {
      if ( options.arrowsPosition === 'leftRight' ) {
        incrementButton.left = numberDisplay.right + options.xSpacing;
        decrementButton.right = numberDisplay.left - options.xSpacing;
        incrementButton.centerY = decrementButton.centerY = numberDisplay.centerY;
      }
      else if ( options.arrowsPosition === 'topBottom' ) {
        incrementButton.centerX = decrementButton.centerX = numberDisplay.centerX;
        incrementButton.bottom = numberDisplay.top - options.ySpacing;
        decrementButton.top = numberDisplay.bottom + options.ySpacing;
      }
      else if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.left = decrementButton.left = numberDisplay.right + options.xSpacing;
        incrementButton.bottom = numberDisplay.centerY - ( options.ySpacing / 2 );
        decrementButton.top = numberDisplay.centerY + ( options.ySpacing / 2 );
      }
      else { // 'bothBottom'
        incrementButton.left = numberDisplay.centerX + ( options.xSpacing / 2 );
        decrementButton.right = numberDisplay.centerX - ( options.xSpacing / 2 );
        incrementButton.top = decrementButton.top = numberDisplay.bottom + options.ySpacing;
      }
    };
    numberDisplay.boundsProperty.link( () => updateLayout() );

    // touch areas
    if ( options.touchAreaXDilation || options.touchAreaYDilation ) {

      incrementButton.touchArea = incrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );
      decrementButton.touchArea = decrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedY( -options.touchAreaYDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedY( options.touchAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedX( options.touchAreaXDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedX( -options.touchAreaXDilation );
      }
    }

    // mouse areas
    if ( options.mouseAreaXDilation || options.mouseAreaYDilation ) {

      incrementButton.mouseArea = incrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );
      decrementButton.mouseArea = decrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedY( -options.mouseAreaYDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedY( options.mouseAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedX( options.mouseAreaXDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedX( -options.mouseAreaXDilation );
      }
    }

    assert && assert( !options.children, 'decoration not supported' );
    options.children = [ numberDisplay, incrementButton, decrementButton ];

    // pdom - NumberSpinner uses AccessibleValueHandler for accessibility, but it was decided that keyboardStep
    // and shiftKeyboardStep should have the same behavior as the NumberSpinner ArrowButtons AND the ArrowButtons
    // should look depressed when interacting with those keys. To accomplish this we actually press the ArrowButtons
    // in response to input with those keys. keyboardStep and shiftKeyboardStep are set to zero so the value isn't
    // modified again by AccessibleValueHandler.
    assert && assert( options.keyboardStep === undefined, 'NumberSpinner sets keyboardStep, it will be the same as deltaValue' );
    assert && assert( options.shiftKeyboardStep === undefined, 'NumberSpinner sets shiftKeyboardStep, it will be the same as deltaValue' );
    assert && assert( options.pageKeyboardStep === undefined, 'NumberSpinner sets pageKeyboardStep, it should not be used with NumberSpinner' );
    options.keyboardStep = 0;
    options.shiftKeyboardStep = 0;
    options.pageKeyboardStep = 0;

    const boundsRequiredOptionKeys = _.pick( options, Node.REQUIRES_BOUNDS_OPTION_KEYS );
    options = _.omit( options, Node.REQUIRES_BOUNDS_OPTION_KEYS );

    super( options );

    // enable/disable arrow buttons
    const updateEnabled = () => {
      incrementButton.enabled = ( options.incrementFunction( numberProperty.value ) <= rangeProperty.value.max );
      decrementButton.enabled = ( options.decrementFunction( numberProperty.value ) >= rangeProperty.value.min );
    };

    // synchronize with number value
    const numberPropertyObserver = value => {
      assert && assert( rangeProperty.value.contains( value ), `value out of range: ${value}` );
      updateEnabled();
    };
    numberProperty.link( numberPropertyObserver ); // must be unlinked in dispose

    // Dynamic range changes, see https://github.com/phetsims/scenery-phet/issues/305
    const rangeObserver = range => {
      // If our value is outside our new range, adjust it to be within the range.
      numberProperty.value = range.constrainValue( numberProperty.value );

      // Range changes may change whether the buttons are enabled
      updateEnabled();
    };
    rangeProperty.link( rangeObserver );

    // pdom - click arrow buttons on press of arrow keys so that the Property value changes by deltaValue
    // and the buttons look depressed
    const increasedListener = function( isDown ) { isDown && incrementButton.pdomClick(); };
    const decreasedListener = function( isDown ) { isDown && decrementButton.pdomClick(); };
    this.incrementDownEmitter.addListener( increasedListener );
    this.decrementDownEmitter.addListener( decreasedListener );

    // @private
    this.disposeNumberSpinner = () => {

      // dispose of subcomponents
      numberDisplay.dispose();
      incrementButton.dispose();
      decrementButton.dispose();

      // dispose a11y features
      this.incrementDownEmitter.removeListener( increasedListener );
      this.decrementDownEmitter.removeListener( decreasedListener );

      numberProperty.unlink( numberPropertyObserver );
      rangeProperty.unlink( rangeObserver );
    };

    this.mutate( boundsRequiredOptionKeys );

    // Create a link to associated Property, so it's easier to find in Studio. Must be after instrumentation
    this.addLinkedElement( numberProperty, {
      tandem: options.tandem.createTandem( 'property' )
    } );

    // support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'NumberSpinner', this );
  }

  // @public Ensures that this node is eligible for GC.
  dispose() {
    this.disposeNumberSpinner();
    super.dispose();
  }
}

sun.register( 'NumberSpinner', NumberSpinner );
export default NumberSpinner;<commit_msg>remove @ts-nocheck<commit_after>// Copyright 2016-2022, University of Colorado Boulder

// @ts-nocheck
/**
 * Spinner for numbers.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Andrea Lin (PhET Interactive Simulations)
 */

import InstanceRegistry from '../../phet-core/js/documentation/InstanceRegistry.js';
import merge from '../../phet-core/js/merge.js';
// @ts-ignore FIX DEPENDENCY ON SCENERY-PHET
import NumberDisplay from '../../scenery-phet/js/NumberDisplay.js';
import { Node, SceneryConstants } from '../../scenery/js/imports.js';
import Tandem from '../../tandem/js/Tandem.js';
import AccessibleNumberSpinner from './accessibility/AccessibleNumberSpinner.js';
import ArrowButton from './buttons/ArrowButton.js';
import sun from './sun.js';

// possible values for options.arrowsPosition
const ARROWS_POSITION_VALUES = [
  'leftRight', // arrow buttons on left and right of value
  'topBottom', // arrow buttons on top and bottom of value
  'bothRight', // both arrow buttons to the right of the value
  'bothBottom' // both arrow buttons below the value
];

class NumberSpinner extends AccessibleNumberSpinner( Node, 0 ) {

  /**
   * @param {Property.<number>} numberProperty value, must be an integer
   * @param {Property.<Range>} rangeProperty - Dynamic range of values, min and max must be integers
   * @param {Object} [options]
   * @mixes AccessibleNumberSpinner
   */
  constructor( numberProperty, rangeProperty, options ) {

    assert && assert( rangeProperty.value.contains( numberProperty.get() ),
      `value ${numberProperty.get()} is out of range ${rangeProperty.value.toString()}` );

    options = merge( {

      // {string} where to place the arrow buttons, see ARROWS_POSITION_VALUES
      arrowsPosition: 'bothRight',

      // {number|null} By default, arrows are scaled to fit dimensions of value background. This is an additional scale factor.
      arrowsScale: null,
      arrowButtonFill: 'white',
      arrowButtonStroke: 'black',
      arrowButtonLineWidth: 1,

      // {function(number):number|null} function called when the increment button is pressed.
      // Parameter is the current value, returns the new value. Defaults to adding options.deltaValue.
      incrementFunction: null,

      // {function(number):number|null} function called when the decrement button is pressed.
      // Parameter is the current value, returns the new value. Defaults to subtracting options.deltaValue.
      decrementFunction: null,

      // may be ignored if incrementFunction and decrementFunction are provided
      deltaValue: 1,

      // {number} - opt into Node's disabled opacity when enabled:false
      disabledOpacity: SceneryConstants.DISABLED_OPACITY,
      xSpacing: 5,
      ySpacing: 3,

      // NumberDisplay options
      numberDisplayOptions: {
        cornerRadius: 5,
        backgroundStroke: 'black'
      },

      // AccessibleNumberSpinner options
      valueProperty: numberProperty,
      enabledRangeProperty: rangeProperty,

      // arrow button pointer areas
      touchAreaXDilation: 0,
      touchAreaYDilation: 0,
      mouseAreaXDilation: 0,
      mouseAreaYDilation: 0,

      // PhET-iO
      tandem: Tandem.REQUIRED,
      phetioEnabledPropertyInstrumented: true // opt into default PhET-iO instrumented enabledProperty
    }, options );

    // validate options
    assert && assert( _.includes( ARROWS_POSITION_VALUES, options.arrowsPosition ), `invalid arrowsPosition: ${options.arrowsPosition}` );
    assert && assert( options.numberDisplayOptions.tandem === undefined, 'NumberSpinner sets tandem for NumberDisplay' );

    // Defaults for incrementFunction and decrementFunction
    if ( !options.incrementFunction ) {
      options.incrementFunction = value => value + options.deltaValue;
    }
    if ( !options.decrementFunction ) {
      options.decrementFunction = value => value - options.deltaValue;
    }

    const numberDisplay = new NumberDisplay( numberProperty, rangeProperty.value, merge( {},
      options.numberDisplayOptions, {
        tandem: options.tandem.createTandem( 'numberDisplay' )
      } ) );

    // buttons
    const arrowButtonOptions = {
      baseColor: options.arrowButtonFill,
      stroke: options.arrowButtonStroke,
      lineWidth: options.arrowButtonLineWidth,
      focusable: false,

      // as requested in https://github.com/phetsims/sun/issues/575
      enabledPropertyOptions: {
        phetioReadOnly: true,
        phetioFeatured: false
      }
    };

    // increment button
    const incrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'up' : 'right';
    const incrementButton = new ArrowButton( incrementDirection,
      () => incrementButton.enabled && numberProperty.set( options.incrementFunction( numberProperty.get() ) ),
      merge( {
        tandem: options.tandem.createTandem( 'incrementButton' )
      }, arrowButtonOptions ) );

    // decrement button
    const decrementDirection = ( options.arrowsPosition === 'topBottom' || options.arrowsPosition === 'bothRight' ) ? 'down' : 'left';
    const decrementButton = new ArrowButton( decrementDirection,
      () => decrementButton.enabled && numberProperty.set( options.decrementFunction( numberProperty.get() ) ),
      merge( {
        tandem: options.tandem.createTandem( 'decrementButton' )
      }, arrowButtonOptions ) );

    // arrow button scaling
    let arrowsScale;
    if ( !arrowsScale ) {
      if ( options.arrowsPosition === 'leftRight' ) {
        arrowsScale = numberDisplay.height / incrementButton.height;
      }
      else if ( options.arrowsPosition === 'topBottom' ) {
        arrowsScale = numberDisplay.width / incrementButton.width;
      }
      else if ( options.arrowsPosition === 'bothRight' ) {
        arrowsScale = ( ( numberDisplay.height / 2 ) - ( options.ySpacing / 2 ) ) / incrementButton.height;
      }
      else { // 'bothBottom'
        arrowsScale = ( ( numberDisplay.width / 2 ) - ( options.xSpacing / 2 ) ) / incrementButton.width;
      }
    }
    if ( options.arrowsScale ) {
      arrowsScale = arrowsScale * options.arrowsScale;
    }
    incrementButton.setScaleMagnitude( arrowsScale );
    decrementButton.setScaleMagnitude( arrowsScale );

    // Because range may change via rangeProperty, the size of numberDisplay may change, in which case layout will need
    // to be revised. See https://github.com/phetsims/sun/issues/709
    const updateLayout = () => {
      if ( options.arrowsPosition === 'leftRight' ) {
        incrementButton.left = numberDisplay.right + options.xSpacing;
        decrementButton.right = numberDisplay.left - options.xSpacing;
        incrementButton.centerY = decrementButton.centerY = numberDisplay.centerY;
      }
      else if ( options.arrowsPosition === 'topBottom' ) {
        incrementButton.centerX = decrementButton.centerX = numberDisplay.centerX;
        incrementButton.bottom = numberDisplay.top - options.ySpacing;
        decrementButton.top = numberDisplay.bottom + options.ySpacing;
      }
      else if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.left = decrementButton.left = numberDisplay.right + options.xSpacing;
        incrementButton.bottom = numberDisplay.centerY - ( options.ySpacing / 2 );
        decrementButton.top = numberDisplay.centerY + ( options.ySpacing / 2 );
      }
      else { // 'bothBottom'
        incrementButton.left = numberDisplay.centerX + ( options.xSpacing / 2 );
        decrementButton.right = numberDisplay.centerX - ( options.xSpacing / 2 );
        incrementButton.top = decrementButton.top = numberDisplay.bottom + options.ySpacing;
      }
    };
    numberDisplay.boundsProperty.link( () => updateLayout() );

    // touch areas
    if ( options.touchAreaXDilation || options.touchAreaYDilation ) {

      incrementButton.touchArea = incrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );
      decrementButton.touchArea = decrementButton.localBounds.dilatedXY( options.touchAreaXDilation, options.touchAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedY( -options.touchAreaYDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedY( options.touchAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.touchArea = incrementButton.touchArea.shiftedX( options.touchAreaXDilation );
        decrementButton.touchArea = decrementButton.touchArea.shiftedX( -options.touchAreaXDilation );
      }
    }

    // mouse areas
    if ( options.mouseAreaXDilation || options.mouseAreaYDilation ) {

      incrementButton.mouseArea = incrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );
      decrementButton.mouseArea = decrementButton.localBounds.dilatedXY( options.mouseAreaXDilation, options.mouseAreaYDilation );

      // shift touch areas for these options, to prevent overlap
      if ( options.arrowsPosition === 'bothRight' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedY( -options.mouseAreaYDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedY( options.mouseAreaYDilation );
      }
      else if ( options.arrowsPosition === 'bothBottom' ) {
        incrementButton.mouseArea = incrementButton.mouseArea.shiftedX( options.mouseAreaXDilation );
        decrementButton.mouseArea = decrementButton.mouseArea.shiftedX( -options.mouseAreaXDilation );
      }
    }

    assert && assert( !options.children, 'decoration not supported' );
    options.children = [ numberDisplay, incrementButton, decrementButton ];

    // pdom - NumberSpinner uses AccessibleValueHandler for accessibility, but it was decided that keyboardStep
    // and shiftKeyboardStep should have the same behavior as the NumberSpinner ArrowButtons AND the ArrowButtons
    // should look depressed when interacting with those keys. To accomplish this we actually press the ArrowButtons
    // in response to input with those keys. keyboardStep and shiftKeyboardStep are set to zero so the value isn't
    // modified again by AccessibleValueHandler.
    assert && assert( options.keyboardStep === undefined, 'NumberSpinner sets keyboardStep, it will be the same as deltaValue' );
    assert && assert( options.shiftKeyboardStep === undefined, 'NumberSpinner sets shiftKeyboardStep, it will be the same as deltaValue' );
    assert && assert( options.pageKeyboardStep === undefined, 'NumberSpinner sets pageKeyboardStep, it should not be used with NumberSpinner' );
    options.keyboardStep = 0;
    options.shiftKeyboardStep = 0;
    options.pageKeyboardStep = 0;

    const boundsRequiredOptionKeys = _.pick( options, Node.REQUIRES_BOUNDS_OPTION_KEYS );
    options = _.omit( options, Node.REQUIRES_BOUNDS_OPTION_KEYS );

    super( options );

    // enable/disable arrow buttons
    const updateEnabled = () => {
      incrementButton.enabled = ( options.incrementFunction( numberProperty.value ) <= rangeProperty.value.max );
      decrementButton.enabled = ( options.decrementFunction( numberProperty.value ) >= rangeProperty.value.min );
    };

    // synchronize with number value
    const numberPropertyObserver = value => {
      assert && assert( rangeProperty.value.contains( value ), `value out of range: ${value}` );
      updateEnabled();
    };
    numberProperty.link( numberPropertyObserver ); // must be unlinked in dispose

    // Dynamic range changes, see https://github.com/phetsims/scenery-phet/issues/305
    const rangeObserver = range => {
      // If our value is outside our new range, adjust it to be within the range.
      numberProperty.value = range.constrainValue( numberProperty.value );

      // Range changes may change whether the buttons are enabled
      updateEnabled();
    };
    rangeProperty.link( rangeObserver );

    // pdom - click arrow buttons on press of arrow keys so that the Property value changes by deltaValue
    // and the buttons look depressed
    const increasedListener = function( isDown ) { isDown && incrementButton.pdomClick(); };
    const decreasedListener = function( isDown ) { isDown && decrementButton.pdomClick(); };
    this.incrementDownEmitter.addListener( increasedListener );
    this.decrementDownEmitter.addListener( decreasedListener );

    // @private
    this.disposeNumberSpinner = () => {

      // dispose of subcomponents
      numberDisplay.dispose();
      incrementButton.dispose();
      decrementButton.dispose();

      // dispose a11y features
      this.incrementDownEmitter.removeListener( increasedListener );
      this.decrementDownEmitter.removeListener( decreasedListener );

      numberProperty.unlink( numberPropertyObserver );
      rangeProperty.unlink( rangeObserver );
    };

    this.mutate( boundsRequiredOptionKeys );

    // Create a link to associated Property, so it's easier to find in Studio. Must be after instrumentation
    this.addLinkedElement( numberProperty, {
      tandem: options.tandem.createTandem( 'property' )
    } );

    // support for binder documentation, stripped out in builds and only runs when ?binder is specified
    assert && phet.chipper.queryParameters.binder && InstanceRegistry.registerDataURL( 'sun', 'NumberSpinner', this );
  }

  // @public Ensures that this node is eligible for GC.
  dispose() {
    this.disposeNumberSpinner();
    super.dispose();
  }
}

sun.register( 'NumberSpinner', NumberSpinner );
export default NumberSpinner;<|endoftext|>


<commit_before>// ***********************************************************
// REQUIRED CONSTANTS, DO NOT MODIFY
// ***********************************************************
var ASSIGNMENT_MARKING = 'Assignment Marking';
var COURSE_MATERIAL_PREPARATION = 'Course Material Preparation';
var TUTORIAL = 'Tutorial';
var POST_URL = '/~tssclaim/tutor/teach_claim.php';
var END_REDIRECT_URL = '/~tssclaim/tutor/teach_claim.php?page=list';

// ***********************************************************
// READ THE FOLLOWING BEFORE STARTING
// ***********************************************************
// 1. **IMPORTANT STEP** Change the properties in the config object in the next section.

// 2. Login to the portal at: https://mysoc.nus.edu.sg/~tssclaim/. Fill in your bank account information if you haven't.

// 3. Access the page titled 'Student Claim Submission' (https://mysoc.nus.edu.sg/~tssclaim/tutor/teach_claim.php?page=1) and click on
//    the 'Claim' button under your module. You should see the interface for you to enter details of the teaching claim activity.

// 4. Open the JS console (Ctrl/Cmd + Shift/Option + J), paste all the code in this file in the JS console and press enter. You should
// 	  see the message 'Claim object successfully created. Run c.makeAllClaims() to start.'.

// 5. Run the function c.makeAllClaims() . Wait until the alert 'All claims made!' is shown, then press 'OK'.

// 6. You will be brought back to the previous page. Click on the button 'Claim' again and verify that you have 80 hours in total.

// To delete all claims on the page, run the function c.deleteAllClaims()

// ***********************************************************
// CONFIGURE THE RELEVANT PROPERTIES IN THE CONFIG OBJECT
// ***********************************************************

var config = {
	// Format: YYYY/MM/DD
	// Note: Month is from 0-11, Date is from 1-31
	// This should be the semester's week 1. For AY13/14 Sem 1, it's Monday, Aug 12
	first_day_of_sem: new Date(2013,07,12),
	// the id you use to log in to the portal
	student_id: 'a0073063',
	// module you are claiming hours for
	module: 'CS1010S',
	// in case you want to customize the duties field for each activity
	duties: {
		'Assignment Marking': 'Graded students\' assignments',
		'Course Material Preparation': 'Prepared course materials',
		'Tutorial': 'Conducted tutorial'
	},

	// the following function should return a list of claim objects that you want to make
	activities_list_fn: function() {
		var claims_list = [];
		// Course prep: 20 hours
		for (var week = 1; week <= 2; week++) {
			claims_list.push({
				activity_type: COURSE_MATERIAL_PREPARATION,
				week: week,
				day: "MONDAY",
				start_time: '1200',
				end_time: '1700'
			});
			claims_list.push({
				activity_type: COURSE_MATERIAL_PREPARATION,
				week: week,
				day: "TUESDAY",
				start_time: '1200',
				end_time: '1700'
			});
		}

		// Weekly stuff (Tutorials and Assignments Marking: 20 + 40 = 60 hours)
		for (var week = 3; week <= 13; week++) {
			if (week === 7) { // there was no tutorial in week 7
				continue;
			}
			claims_list.push({
				activity_type: TUTORIAL,
				week: week,
				day: "MONDAY",
				start_time: '1200',
				end_time: '1700'
			});
			claims_list.push({
				activity_type: TUTORIAL,
				week: week,
				day: "TUESDAY",
				start_time: '1400',
				end_time: '1500'
			});
			claims_list.push({
				activity_type: ASSIGNMENT_MARKING,
				week: week,
				day: "SATURDAY",
				start_time: '1200',
				end_time: '1600'
			});
		};

		return claims_list;
	}
}

// ***********************************************************
// DO NOT CHANGE THE BOTTOM UNLESS YOU KNOW WHAT YOU ARE DOING
// ***********************************************************

var ACTIVITY_DICT = {};
ACTIVITY_DICT[ASSIGNMENT_MARKING] = '003';
ACTIVITY_DICT[COURSE_MATERIAL_PREPARATION] = '006';
ACTIVITY_DICT[TUTORIAL] = 'T';

var DAY_DICT = { 'MONDAY': 0, 'TUESDAY': 1, 'WEDNESDAY': 2, 'THURSDAY': 3, 'FRIDAY': 4, 'SATURDAY': 5, 'SUNDAY': 6 };

function Claim(config) {
	this.student_id = config.student_id.toLowerCase();
	this.module = config.module;
	this.remarks = config.duties;
	this.first_day_of_sem = config.first_day_of_sem;
	this.error = false;
	var that = this;
	function createActivity(activity_type, week, day, start_time, end_time) {
		// obj has the properties:
		var day_upper = day.toUpperCase();
		try {
			if (ACTIVITY_DICT[activity_type] == undefined || typeof activity_type != "string") {
				throw "Activity error: " + activity_type + ". Activity type not supported.";
			}
			if (typeof week != "number" || week <= 0) {
				throw "Week error: " + week + ". Week value has to be a positive number.";
			}
			if (DAY_DICT[day_upper] == undefined || typeof day_upper != "string") {
				throw "Day error: " + day + ". Day value has to be a valid day string.";
			}
			function checkTime(time) {
				var start_time_hour = time.slice(0,2);
				var start_time_min = time.slice(2);
				if (typeof time != "string" ||
					time.length != 4 ||
					!(parseInt(start_time_hour) >= 0 && parseInt(start_time_hour) <= 23) ||
					!(start_time_min == "00" || start_time_min == "30")) {
					throw "Time error: " + time + ". Time has to be string in 24-hr format at half-hour intervals.";
				}
			}
			checkTime(start_time);
			checkTime(end_time);
			var start_time_hour = parseInt(start_time.slice(0,2));
			var end_time_hour = parseInt(end_time.slice(0,2));
			if (start_time_hour > end_time_hour || start_time === end_time) {
				throw "Time error: end_time: " + end_time + " must be after start_time: " + start_time + ".";
			} else if (end_time_hour - start_time_hour > 8) {
				throw "Time error: " + start_time + " - " + end_time + ". Activity cannot be more than 8 hours.";
			}
		} catch (err) {
			error = true;
			console.log(err);
		}
		return function() {
			that.makeClaim(activity_type, week, day, start_time, end_time);
		};
	}
	var activities = config.activities_list_fn();
	this.activities_list = [];
	for (var i = 0; i < activities.length; i++) {
		var a = activities[i];
		this.activities_list.push(createActivity(a.activity_type, a.week, a.day, a.start_time, a.end_time));
	}

	this.ajax_index = 0; // index to keep track of the current ajax call
	console.log('Claim object successfully created. Run c.makeAllClaims() to start.');
}

Claim.prototype.makeClaim = function(activity_type, week, day, start_time, end_time) {
	var day_num = DAY_DICT[day];
	var number_of_days = (week < 7 ? week - 1 : week)*7 + day_num;
	var activity_date = new Date();
	activity_date.setTime(this.first_day_of_sem.getTime() + (number_of_days * 24 * 60 * 60 * 1000));
	var claim_date_array = activity_date.toDateString().split(' ');
	var claim_date_str = [claim_date_array[2], claim_date_array[1], claim_date_array[3].slice(2)].join('-');

	var post_data = {
		mod_c: this.module,
		action: 'ADD',
		std_id: this.student_id,
		activity_c: ACTIVITY_DICT[activity_type],
		remarks: this.remarks[activity_type],
		claim_date: claim_date_str,
		start_time_hr: start_time.slice(0,2),
		start_time_min: start_time.slice(2),
		end_time_hr: end_time.slice(0,2),
		end_time_min: end_time.slice(2),
		submit: 'ADD + Save as Draft'
	}

	var that = this;
	console.log('Successfully added ' + activity_type + ' for ' + claim_date_str);
		that.ajax_index += 1;
		if (that.ajax_index < that.activities_list.length) {
			that.activities_list[that.ajax_index]();
		} else {
			alert('All claims made! Press OK to continue.');
			// redirect to previous page because a refresh of the page would trigger the last ajax call
			window.location.href = window.location.protocol +'//'+ window.location.host + END_REDIRECT_URL;
		}};

Claim.prototype.deleteAllClaims = function() {
	var that = this;
	function deleteClaim(claim_id) {
		$.post(POST_URL, {
			mod_c: that.module,
			claim_id: claim_id,
			action: 'DELETE',
			std_id: that.student_id,
			submit: 'DELETE + Save as Draft'
		}, function(data) {
			console.log('Claim ' + claim_id + ' deleted');
			count += 1;
			if (count === $existing_claims.length) {
				alert('All claims deleted! Press OK to continue.');
				window.location.href = window.location.protocol +'//'+ window.location.host + END_REDIRECT_URL;
			}
		});
	}
	var count = 0;
	var $existing_claims = $('#claim-info-div table [name="claim_id"]');
	$existing_claims.each(function() {
		deleteClaim(this.value);
	});
}

Claim.prototype.makeAllClaims = function() {
	if (!this.error) {
		this.activities_list[this.ajax_index]();
	}
}

var c = new Claim(config);
c.makeAllClaims();<commit_msg>Modified CS1010S script<commit_after>// ***********************************************************
// REQUIRED CONSTANTS, DO NOT MODIFY
// ***********************************************************
var ASSIGNMENT_MARKING = 'Assignment Marking';
var COURSE_MATERIAL_PREPARATION = 'Course Material Preparation';
var TUTORIAL = 'Tutorial';
var POST_URL = '/~tssclaim/tutor/teach_claim.php';
var END_REDIRECT_URL = '/~tssclaim/tutor/teach_claim.php?page=list';

// ***********************************************************
// READ THE FOLLOWING BEFORE STARTING
// ***********************************************************
// 1. **IMPORTANT STEP** Change the properties in the config object in the next section.

// 2. Login to the portal at: https://mysoc.nus.edu.sg/~tssclaim/. Fill in your bank account information if you haven't.

// 3. Access the page titled 'Student Claim Submission' (https://mysoc.nus.edu.sg/~tssclaim/tutor/teach_claim.php?page=1) and click on
//    the 'Claim' button under your module. You should see the interface for you to enter details of the teaching claim activity.

// 4. Open the JS console (Ctrl/Cmd + Shift/Option + J), paste all the code in this file in the JS console and press enter. You should
// 	  see the message 'Claim object successfully created. Run c.makeAllClaims() to start.'.

// 5. Run the function c.makeAllClaims() . Wait until the alert 'All claims made!' is shown, then press 'OK'.

// 6. You will be brought back to the previous page. Click on the button 'Claim' again and verify that you have 80 hours in total.

// To delete all claims on the page, run the function c.deleteAllClaims()

// ***********************************************************
// CONFIGURE THE RELEVANT PROPERTIES IN THE CONFIG OBJECT
// ***********************************************************

var config = {
	// Format: YYYY/MM/DD
	// Note: Month is from 0-11, Date is from 1-31
	// This should be the semester's week 1. For AY13/14 Sem 1, it's Monday, Aug 12
	first_day_of_sem: new Date(2014,0,13),
	// the id you use to log in to the portal
	student_id: 'a0073063',
	// module you are claiming hours for
	module: 'CS1010S',
	// in case you want to customize the duties field for each activity
	duties: {
		'Assignment Marking': 'Graded students\' assignments',
		'Course Material Preparation': 'Prepared course materials',
		'Tutorial': 'Conducted tutorial'
	},

	// the following function should return a list of claim objects that you want to make
	activities_list_fn: function() {
		var claims_list = [];
		// Course prep: 20 hours
		for (var week = 1; week <= 2; week++) {
			claims_list.push({
				activity_type: COURSE_MATERIAL_PREPARATION,
				week: week,
				day: "MONDAY",
				start_time: '1200',
				end_time: '1700'
			});
			claims_list.push({
				activity_type: COURSE_MATERIAL_PREPARATION,
				week: week,
				day: "TUESDAY",
				start_time: '1200',
				end_time: '1700'
			});
		}

		// Weekly stuff (Tutorials and Assignments Marking: 20 + 40 = 60 hours)
		for (var week = 3; week <= 13; week++) {
			if (week === 7) { // there was no tutorial in week 7
				continue;
			}
			claims_list.push({
				activity_type: TUTORIAL,
				week: week,
				day: "MONDAY",
				start_time: '1200',
				end_time: '1700'
			});
			claims_list.push({
				activity_type: TUTORIAL,
				week: week,
				day: "TUESDAY",
				start_time: '1400',
				end_time: '1500'
			});
			claims_list.push({
				activity_type: ASSIGNMENT_MARKING,
				week: week,
				day: "SATURDAY",
				start_time: '1200',
				end_time: '1600'
			});
		};

		return claims_list;
	}
}

// ***********************************************************
// DO NOT CHANGE THE BOTTOM UNLESS YOU KNOW WHAT YOU ARE DOING
// ***********************************************************

var ACTIVITY_DICT = {};
ACTIVITY_DICT[ASSIGNMENT_MARKING] = '003';
ACTIVITY_DICT[COURSE_MATERIAL_PREPARATION] = '006';
ACTIVITY_DICT[TUTORIAL] = 'T';

var DAY_DICT = { 'MONDAY': 0, 'TUESDAY': 1, 'WEDNESDAY': 2, 'THURSDAY': 3, 'FRIDAY': 4, 'SATURDAY': 5, 'SUNDAY': 6 };

function Claim(config) {
	this.student_id = config.student_id.toLowerCase();
	this.module = config.module;
	this.remarks = config.duties;
	this.first_day_of_sem = config.first_day_of_sem;
	this.error = false;
	var that = this;
	function createActivity(activity_type, week, day, start_time, end_time) {
		// obj has the properties:
		var day_upper = day.toUpperCase();
		try {
			if (ACTIVITY_DICT[activity_type] == undefined || typeof activity_type != "string") {
				throw "Activity error: " + activity_type + ". Activity type not supported.";
			}
			if (typeof week != "number" || week <= 0) {
				throw "Week error: " + week + ". Week value has to be a positive number.";
			}
			if (DAY_DICT[day_upper] == undefined || typeof day_upper != "string") {
				throw "Day error: " + day + ". Day value has to be a valid day string.";
			}
			function checkTime(time) {
				var start_time_hour = time.slice(0,2);
				var start_time_min = time.slice(2);
				if (typeof time != "string" ||
					time.length != 4 ||
					!(parseInt(start_time_hour) >= 0 && parseInt(start_time_hour) <= 23) ||
					!(start_time_min == "00" || start_time_min == "30")) {
					throw "Time error: " + time + ". Time has to be string in 24-hr format at half-hour intervals.";
				}
			}
			checkTime(start_time);
			checkTime(end_time);
			var start_time_hour = parseInt(start_time.slice(0,2));
			var end_time_hour = parseInt(end_time.slice(0,2));
			if (start_time_hour > end_time_hour || start_time === end_time) {
				throw "Time error: end_time: " + end_time + " must be after start_time: " + start_time + ".";
			} else if (end_time_hour - start_time_hour > 8) {
				throw "Time error: " + start_time + " - " + end_time + ". Activity cannot be more than 8 hours.";
			}
		} catch (err) {
			error = true;
			console.log(err);
		}
		return function() {
			that.makeClaim(activity_type, week, day, start_time, end_time);
		};
	}
	var activities = config.activities_list_fn();
	this.activities_list = [];
	for (var i = 0; i < activities.length; i++) {
		var a = activities[i];
		this.activities_list.push(createActivity(a.activity_type, a.week, a.day, a.start_time, a.end_time));
	}

	this.ajax_index = 0; // index to keep track of the current ajax call
	console.log('Claim object successfully created. Run c.makeAllClaims() to start.');
}

Claim.prototype.makeClaim = function(activity_type, week, day, start_time, end_time) {
	var day_num = DAY_DICT[day];
	var number_of_days = (week < 7 ? week - 1 : week)*7 + day_num;
	var activity_date = new Date();
	activity_date.setTime(this.first_day_of_sem.getTime() + (number_of_days * 24 * 60 * 60 * 1000));
	var claim_date_array = activity_date.toDateString().split(' ');
	var claim_date_str = [claim_date_array[2], claim_date_array[1], claim_date_array[3].slice(2)].join('-');

	var post_data = {
		mod_c: this.module,
		action: 'ADD',
		std_id: this.student_id,
		activity_c: ACTIVITY_DICT[activity_type],
		remarks: this.remarks[activity_type],
		claim_date: claim_date_str,
		start_time_hr: start_time.slice(0,2),
		start_time_min: start_time.slice(2),
		end_time_hr: end_time.slice(0,2),
		end_time_min: end_time.slice(2),
		submit: 'ADD + Save as Draft'
	}

	var that = this;
	console.log('Successfully added ' + activity_type + ' for ' + claim_date_str);
		that.ajax_index += 1;
		if (that.ajax_index < that.activities_list.length) {
			that.activities_list[that.ajax_index]();
		} else {
			alert('All claims made! Press OK to continue.');
			// redirect to previous page because a refresh of the page would trigger the last ajax call
			window.location.href = window.location.protocol +'//'+ window.location.host + END_REDIRECT_URL;
		}};

Claim.prototype.deleteAllClaims = function() {
	var that = this;
	function deleteClaim(claim_id) {
		$.post(POST_URL, {
			mod_c: that.module,
			claim_id: claim_id,
			action: 'DELETE',
			std_id: that.student_id,
			submit: 'DELETE + Save as Draft'
		}, function(data) {
			console.log('Claim ' + claim_id + ' deleted');
			count += 1;
			if (count === $existing_claims.length) {
				alert('All claims deleted! Press OK to continue.');
				window.location.href = window.location.protocol +'//'+ window.location.host + END_REDIRECT_URL;
			}
		});
	}
	var count = 0;
	var $existing_claims = $('#claim-info-div table [name="claim_id"]');
	$existing_claims.each(function() {
		deleteClaim(this.value);
	});
}

Claim.prototype.makeAllClaims = function() {
	if (!this.error) {
		this.activities_list[this.ajax_index]();
	}
}

var c = new Claim(config);
c.makeAllClaims();<|endoftext|>


<commit_before>import FontFaceObserver from 'fontfaceobserver';
import Fabric from 'fabric';
import autoBind from 'auto-bind';
import Styles from './Styles';
import Worker from './dataTransformations/googleLocationToGeoJson.worker.js';
import PosterStylingPojo from './PosterStylingPojo';

/**
 *  Confgiure the styling of the poster
 */
export default class PosterStyling {
    constructor(map) {
        this.styles = new Styles();
        this.map = map.map;
        this.scale = window.devicePixelRatio;

        // get inputs
        this.cityInput = document.querySelector('#city');
        this.countryInput = document.querySelector('#country');
        this.locationInput = document.querySelector('#location');
        this.locationDataInput = document.querySelector('#data-file');

        this.html = document.querySelector('html');

        // get control elements
        this.ornamentalColorPicker = document.querySelector('.ornamental-color-picker');
        this.gradientColorPicker = document.querySelector('.gradient-color-picker');
        this.ornamentalPosition = document.querySelector('.ornamental-position');
        this.ornamentalSize = document.querySelector('.ornamental-size');
        this.exportBtn = document.querySelector('.overlay-export');

        // bind this to this context
        this.posterStylingPojo = new PosterStylingPojo(2400, 1800, window.devicePixelRatio);
        autoBind(this);

        // add event listeners
        this.pojoProxy = this.handler();
        this.createStyleSelector();
        this.addEventListeners();
        this.createOverlayCanvas();
        this.bindInputEvents();
        this.bindFontEvents();
        PosterStyling.bindToggles();
        // this.bindColorEvents();
    }

    addEventListeners() {
        this.ornamentalColorPicker.addEventListener('change', this.setOrnamentalColor, false);
        this.gradientColorPicker.addEventListener('change', this.setGradientColor, false);
        this.ornamentalPosition.addEventListener('input', this.setOrnamentalPosition, false);
        this.ornamentalSize.addEventListener('input', this.setOrnamentalSize, false);
        this.locationDataInput.addEventListener('change', this.setLocationData, false);
    }

    static addListenerMulti(el, s, fn) {
        // console.log(s);
        const events = s.split(',');
        console.log(events);
        events.forEach(e => el.addEventListener(e, fn, false));
    }

    bindInputEvents() {
        const elements = document.querySelectorAll('input[data-k-canvas-node]');
        elements.forEach((el) => {
            const events = el.dataset.kEvents === undefined ? 'change' : el.dataset.kEvents;
            PosterStyling.addListenerMulti(el, events, this.updateCanvasElement);
        });
    }

    bindFontEvents() {
        const elements = document.querySelectorAll('select[data-k-attr="fontFamily"]');
        elements.forEach((el) => {
            el.onchange = this.updateCanvasElement;
        });
    }

    static bindToggles() {
        const elements = document.querySelectorAll('.text-toggle');
        elements.forEach((el) => {
            el.addEventListener('click', (e) => {
                document.querySelector(`.${e.target.dataset.toggle}`).classList.toggle('text-style-collapsed');
            });
        });
    }

    async updateCanvasElement(e) {
        const target = e.target.dataset.kCanvasNode;
        const elementAttr = e.target.dataset.kAttr;
        const value = e.target.value;
        if (value === 'undefined' || value === '') return;
        console.group('Canvas Attributes');
        console.log(target);
        console.log(elementAttr);
        console.log(value);
        console.groupEnd();
        if (elementAttr === 'fontFamily') {
            const myFont = new FontFaceObserver(e.target.value);
            await myFont.load();
        }
        this.pojoProxy[target][elementAttr] = e.target.value;
    }

    async setLocationData(e) {
        const file = e.target.files[0];
        // console.log(file);
        const reader = new FileReader();
        reader.readAsText(file, 'UTF-8');
        const worker = new Worker();
        reader.onload = async (e) => {
            worker.postMessage(JSON.parse(e.target.result));
            worker.onmessage = this.showHeatMap;
            e = {};
        };
    }

    showHeatMap(e) {
        console.log('Rendering Map');
        // Add a geojson point source.
        // Heatmap layers also work with a vector tile source.
        let i = 0;
        e.data.forEach((subDataSet) => {
            this.map.addSource(`layer${i}`, {
                type: 'geojson',
                buffer: 64,
                data: subDataSet,
                tolerance: 0.999,
            });

            this.map.addLayer(
                {
                    id: `layer${i}`,
                    type: 'heatmap',
                    source: `layer${i}`,
                    maxzoom: 15,
                    paint: {
                        // Increase the heatmap weight based on frequency and property magnitude
                        'heatmap-radius': 25,
                        // Increase the heatmap color weight weight by zoom level
                        // heatmap-ntensity is a multiplier on top of heatmap-weight
                        'heatmap-intensity': {
                            stops: [[0, 1], [9, 3]],
                        },
                        // Color ramp for heatmap.  Domain is 0 (low) to 1 (high).
                        // Begin color ramp at 0-stop with a 0-transparancy color
                        // to create a blur-like effect.
                        'heatmap-color': [
                            'interpolate',
                            ['linear'],
                            ['heatmap-density'],
                            0,
                            'rgba(33,102,172,0)',
                            0.2,
                            'rgb(103,169,207)',
                            0.4,
                            'rgb(209,229,240)',
                            0.5,
                            'rgb(216, 253, 199)',
                            0.9,
                            'rgb(239,138,98)',
                            1,
                            'rgb(178,24,43)',
                        ],
                        // Transition from heatmap to circle layer by zoom level
                        'heatmap-opacity': {
                            default: 1,
                            stops: [[7, 0.65], [9, 0.55], [15, 0.5]],
                        },
                    },
                },
                'waterway-label',
            );

            i += 1;
        });
    }

    static formatCoord(lat, long) {
        return ` ${lat.toFixed(4)}° ${lat < 0 ? 'S' : 'N'} / ${Math.abs(long.toFixed(4))}° ${long < 0 ? 'W' : 'E'}`;
    }

    setTextColor(e) {
        this.setColor(e.target.value, 'text');
    }

    setGradientColor(e) {
        this.setColor(e.target.value, 'gradient');
    }

    setOrnamentalColor(e) {
        this.setColor(e.target.value, 'ornament');
    }

    setFont(e) {
        const font = e.target.value;
        Object.keys(this.pojoProxy).forEach((key) => {
            if (this.pojoProxy[key].type === 'text') {
                this.pojoProxy[key].fontFamily = font;
            }
        });
    }

    setColor(value, filter) {
        if (filter === 'gradient') {
            this.setGradientColorValue(value);
            return true;
        }
        Object.keys(this.pojoProxy).forEach((key) => {
            if (this.pojoProxy[key].type === filter) {
                this.pojoProxy[key].fill = value;
            }
        });
        return false;
    }

    setGradientColorValue(value) {
        this.pojoProxy.gradientFill.colorStops['0'] = `${value.substr(1)}00`;
        this.pojoProxy.gradientFill.colorStops['0.26'] = value.substr(1);
        this.pojoProxy.gradientFill.colorStops['1'] = value.substr(1);
        this.gradient.setGradient('fill', this.pojoProxy.gradientFill);
    }

    setOrnamentalPosition(e) {
        const value = e.target.value;
        this.pojoProxy.leftOrnament.left = parseInt(value);
        this.pojoProxy.rightOrnament.left = this.pojoProxy.canvasW - value;
        this.leftOrnament.set(this.pojoProxy.leftOrnament);
        this.rightOrnament.set(this.pojoProxy.rightOrnament);
    }

    setOrnamentalSize(e) {
        const value = e.target.value;
        this.pojoProxy.leftOrnament.width = parseInt(value);
        this.pojoProxy.rightOrnament.width = parseInt(value);
        this.leftOrnament.set(this.pojoProxy.leftOrnament);
        this.rightOrnament.set(this.pojoProxy.rightOrnament);
    }

    /**
     * Pass the event to set the style by name
     * @param {event} style - the event that contains the style event
     */
    setStyle(style) {
        const newStyle = this.styles.getStyleByName(style.target.value)[0];
        this.map.setStyle(newStyle.url);
        this.setOverlayStyle(newStyle.overlay);
    }

    setOverlayStyle(style) {
        console.log(style);
        this.setColor(style.textColor, 'text');
        this.setColor(style.ornamentalColor, 'ornament');
        this.setColor(style.gradientColor, 'gradient');
    }

    /**
     * Create the Style selector
     */
    createStyleSelector() {
        const styles = this.styles.getStyles();
        this.styleSelector = document.querySelector('.style-select');
        const option = `${styles.map(style => `<option value='${style.name}'>${style.name}</option>`)}`;
        this.styleSelector.innerHTML = option;
        this.styleSelector.onchange = this.setStyle;
    }

    createOverlayCanvas() {
        const mapCanvas = document.querySelector('canvas.mapboxgl-canvas');
        const overlayCanavas = document.createElement('canvas');
        overlayCanavas.id = 'overlay-canvas';
        overlayCanavas.width = 1800;
        overlayCanavas.height = 2400;
        mapCanvas.after(overlayCanavas);
        const ctx = overlayCanavas.getContext('2d');
        overlayCanavas.width = 1800 * window.devicePixelRatio;
        overlayCanavas.height = 2400 * window.devicePixelRatio;
        ctx.scale(window.devicePixelRatio, window.devicePixelRatio);

        this.oCanvas = new fabric.Canvas('overlay-canvas');
        this.gradient = new fabric.Rect(this.pojoProxy.gradient);
        this.gradient.setGradient('fill', this.pojoProxy.gradientFill);
        this.leftOrnament = new fabric.Rect(this.pojoProxy.leftOrnament);
        this.rightOrnament = new fabric.Rect(this.pojoProxy.rightOrnament);

        this.oCanvas.add(this.gradient);
        this.oCanvas.add(this.leftOrnament);
        this.oCanvas.add(this.rightOrnament);

        this.city = new fabric.Textbox('BERLIN', this.pojoProxy.city);
        this.country = new fabric.Textbox('GERMANY', this.pojoProxy.country);
        this.location = new fabric.Textbox('52.5200° N / 13.4050° E', this.pojoProxy.location);

        PosterStyling.loadFont(this.city, this.oCanvas);
        PosterStyling.loadFont(this.country, this.oCanvas);
        PosterStyling.loadFont(this.location, this.oCanvas);
    }

    static loadFont(textbox, canvas, font = 'Montserrat') {
        const myFont = new FontFaceObserver(font);
        myFont
            .load()
            .then(() => {
                canvas.add(textbox);
                textbox.centerH();
                textbox.set('fontFamily', font);
                canvas.requestRenderAll();
            })
            .catch((e) => {
                console.log(e);
            });
    }

    /**
     * This needs some TLC
     */
    handler() {
        const target = {};
        const that = this;
        const handler = {
            get(target, key) {
                if (typeof target[key] === 'object' && target[key] !== null) {
                    return new Proxy(target[key], handler);
                }
                return target[key];
            },
            set(target, key, value) {
                console.log(`key ${key}`);

                switch (key) {
                case 'text':
                    target[key] = value.toUpperCase();
                    break;
                case 'fontSize':
                case 'width':
                    target[key] = parseInt(value) * Math.round(that.scale);
                    break;
                case 'height':
                    target[key] = value * that.scale;
                    break;
                default:
                    target[key] = value;
                    break;
                }
                that.updateOverlay();
                console.log(that.pojoProxy);
                return true;
            },
        };
        return new Proxy(this.posterStylingPojo, handler);
    }
    updateOverlay() {
        Object.keys(this.pojoProxy).forEach((key) => {
            if (key in this) {
                this[key].set(this.pojoProxy[key]);
            }
        });
        this.gradient.setGradient('fill', this.pojoProxy.gradientFill);
        this.oCanvas.requestRenderAll();
    }
}
<commit_msg>Clean up eslint<commit_after>import FontFaceObserver from 'fontfaceobserver';
import { fabric } from 'fabric';
import autoBind from 'auto-bind';
import Styles from './Styles';
import Worker from './dataTransformations/googleLocationToGeoJson.worker.js';
import PosterStylingPojo from './PosterStylingPojo';

/**
 *  Confgiure the styling of the poster
 */
export default class PosterStyling {
    constructor(map) {
        this.styles = new Styles();
        this.map = map.map;
        this.scale = window.devicePixelRatio;

        // get inputs
        this.cityInput = document.querySelector('#city');
        this.countryInput = document.querySelector('#country');
        this.locationInput = document.querySelector('#location');
        this.locationDataInput = document.querySelector('#data-file');

        this.html = document.querySelector('html');

        // get control elements
        this.ornamentalColorPicker = document.querySelector('.ornamental-color-picker');
        this.gradientColorPicker = document.querySelector('.gradient-color-picker');
        this.ornamentalPosition = document.querySelector('.ornamental-position');
        this.ornamentalSize = document.querySelector('.ornamental-size');
        this.exportBtn = document.querySelector('.overlay-export');

        // bind this to this context
        this.posterStylingPojo = new PosterStylingPojo(2400, 1800, window.devicePixelRatio);
        autoBind(this);

        // add event listeners
        this.pojoProxy = this.handler();
        this.createStyleSelector();
        this.addEventListeners();
        this.createOverlayCanvas();
        this.bindInputEvents();
        this.bindFontEvents();
        PosterStyling.bindToggles();
        // this.bindColorEvents();
    }

    addEventListeners() {
        this.ornamentalColorPicker.addEventListener('change', this.setOrnamentalColor, false);
        this.gradientColorPicker.addEventListener('change', this.setGradientColor, false);
        this.ornamentalPosition.addEventListener('input', this.setOrnamentalPosition, false);
        this.ornamentalSize.addEventListener('input', this.setOrnamentalSize, false);
        this.locationDataInput.addEventListener('change', this.setLocationData, false);
    }

    static addListenerMulti(el, s, fn) {
        // console.log(s);
        const events = s.split(',');
        console.log(events);
        events.forEach(e => el.addEventListener(e, fn, false));
    }

    bindInputEvents() {
        const elements = document.querySelectorAll('input[data-k-canvas-node]');
        elements.forEach((el) => {
            const events = el.dataset.kEvents === undefined ? 'change' : el.dataset.kEvents;
            PosterStyling.addListenerMulti(el, events, this.updateCanvasElement);
        });
    }

    bindFontEvents() {
        const elements = document.querySelectorAll('select[data-k-attr="fontFamily"]');
        elements.forEach((el) => {
            el.onchange = this.updateCanvasElement;
        });
    }

    static bindToggles() {
        const elements = document.querySelectorAll('.text-toggle');
        elements.forEach((el) => {
            el.addEventListener('click', (e) => {
                document.querySelector(`.${e.target.dataset.toggle}`).classList.toggle('text-style-collapsed');
            });
        });
    }

    async updateCanvasElement(e) {
        const target = e.target.dataset.kCanvasNode;
        const elementAttr = e.target.dataset.kAttr;
        const { value } = e.target;
        if (value === 'undefined' || value === '') return;
        console.group('Canvas Attributes');
        console.log(target);
        console.log(elementAttr);
        console.log(value);
        console.groupEnd();
        if (elementAttr === 'fontFamily') {
            const myFont = new FontFaceObserver(e.target.value);
            await myFont.load();
        }
        this.pojoProxy[target][elementAttr] = e.target.value;
    }

    async setLocationData(e) {
        const file = e.target.files[0];
        // console.log(file);
        const reader = new FileReader();
        reader.readAsText(file, 'UTF-8');
        const worker = new Worker();
        reader.onload = async (e) => {
            worker.postMessage(JSON.parse(e.target.result));
            worker.onmessage = this.showHeatMap;
            e = {};
        };
    }

    showHeatMap(e) {
        console.log('Rendering Map');
        // Add a geojson point source.
        // Heatmap layers also work with a vector tile source.
        let i = 0;
        e.data.forEach((subDataSet) => {
            this.map.addSource(`layer${i}`, {
                type: 'geojson',
                buffer: 64,
                data: subDataSet,
                tolerance: 0.999,
            });

            this.map.addLayer(
                {
                    id: `layer${i}`,
                    type: 'heatmap',
                    source: `layer${i}`,
                    maxzoom: 15,
                    paint: {
                        // Increase the heatmap weight based on frequency and property magnitude
                        'heatmap-radius': 25,
                        // Increase the heatmap color weight weight by zoom level
                        // heatmap-ntensity is a multiplier on top of heatmap-weight
                        'heatmap-intensity': {
                            stops: [[0, 1], [9, 3]],
                        },
                        // Color ramp for heatmap.  Domain is 0 (low) to 1 (high).
                        // Begin color ramp at 0-stop with a 0-transparancy color
                        // to create a blur-like effect.
                        'heatmap-color': [
                            'interpolate',
                            ['linear'],
                            ['heatmap-density'],
                            0,
                            'rgba(33,102,172,0)',
                            0.2,
                            'rgb(103,169,207)',
                            0.4,
                            'rgb(209,229,240)',
                            0.5,
                            'rgb(216, 253, 199)',
                            0.9,
                            'rgb(239,138,98)',
                            1,
                            'rgb(178,24,43)',
                        ],
                        // Transition from heatmap to circle layer by zoom level
                        'heatmap-opacity': {
                            default: 1,
                            stops: [[7, 0.65], [9, 0.55], [15, 0.5]],
                        },
                    },
                },
                'waterway-label',
            );

            i += 1;
        });
    }

    static formatCoord(lat, long) {
        return ` ${lat.toFixed(4)}° ${lat < 0 ? 'S' : 'N'} / ${Math.abs(long.toFixed(4))}° ${long < 0 ? 'W' : 'E'}`;
    }

    setTextColor(e) {
        this.setColor(e.target.value, 'text');
    }

    setGradientColor(e) {
        this.setColor(e.target.value, 'gradient');
    }

    setOrnamentalColor(e) {
        this.setColor(e.target.value, 'ornament');
    }

    setFont(e) {
        const { value } = e.target;
        Object.keys(this.pojoProxy).forEach((key) => {
            if (this.pojoProxy[key].type === 'text') {
                this.pojoProxy[key].fontFamily = value;
            }
        });
    }

    setColor(value, filter) {
        if (filter === 'gradient') {
            this.setGradientColorValue(value);
            return true;
        }
        Object.keys(this.pojoProxy).forEach((key) => {
            if (this.pojoProxy[key].type === filter) {
                this.pojoProxy[key].fill = value;
            }
        });
        return false;
    }

    setGradientColorValue(value) {
        this.pojoProxy.gradientFill.colorStops['0'] = `${value.substr(1)}00`;
        this.pojoProxy.gradientFill.colorStops['0.26'] = value.substr(1);
        this.pojoProxy.gradientFill.colorStops['1'] = value.substr(1);
        this.gradient.setGradient('fill', this.pojoProxy.gradientFill);
    }

    setOrnamentalPosition(e) {
        const { value } = e.target;
        console.log(value);
        this.pojoProxy.leftOrnament.left = parseInt(value, 10);
        console.log(this.pojoProxy.leftOrnament.left);
        this.pojoProxy.rightOrnament.left = this.pojoProxy.canvasW - value;
        this.leftOrnament.set(this.pojoProxy.leftOrnament);
        this.rightOrnament.set(this.pojoProxy.rightOrnament);
    }

    setOrnamentalSize(e) {
        const { value } = e.target;
        this.pojoProxy.leftOrnament.width = parseInt(value, 10);
        this.pojoProxy.rightOrnament.width = parseInt(value, 10);
        this.leftOrnament.set(this.pojoProxy.leftOrnament);
        this.rightOrnament.set(this.pojoProxy.rightOrnament);
    }

    /**
     * Pass the event to set the style by name
     * @param {event} style - the event that contains the style event
     */
    setStyle(style) {
        const newStyle = this.styles.getStyleByName(style.target.value)[0];
        this.map.setStyle(newStyle.url);
        this.setOverlayStyle(newStyle.overlay);
    }

    setOverlayStyle(style) {
        console.log(style);
        this.setColor(style.textColor, 'text');
        this.setColor(style.ornamentalColor, 'ornament');
        this.setColor(style.gradientColor, 'gradient');
    }

    /**
     * Create the Style selector
     */
    createStyleSelector() {
        const styles = this.styles.getStyles();
        this.styleSelector = document.querySelector('.style-select');
        const option = `${styles.map(style => `<option value='${style.name}'>${style.name}</option>`)}`;
        this.styleSelector.innerHTML = option;
        this.styleSelector.onchange = this.setStyle;
    }

    createOverlayCanvas() {
        const mapCanvas = document.querySelector('canvas.mapboxgl-canvas');
        const overlayCanavas = document.createElement('canvas');
        overlayCanavas.id = 'overlay-canvas';
        overlayCanavas.width = 1800;
        overlayCanavas.height = 2400;
        mapCanvas.after(overlayCanavas);
        const ctx = overlayCanavas.getContext('2d');
        overlayCanavas.width = 1800 * window.devicePixelRatio;
        overlayCanavas.height = 2400 * window.devicePixelRatio;
        ctx.scale(window.devicePixelRatio, window.devicePixelRatio);

        this.oCanvas = new fabric.Canvas('overlay-canvas');
        this.gradient = new fabric.Rect(this.pojoProxy.gradient);
        this.gradient.setGradient('fill', this.pojoProxy.gradientFill);
        this.leftOrnament = new fabric.Rect(this.pojoProxy.leftOrnament);
        this.rightOrnament = new fabric.Rect(this.pojoProxy.rightOrnament);

        this.oCanvas.add(this.gradient);
        this.oCanvas.add(this.leftOrnament);
        this.oCanvas.add(this.rightOrnament);

        this.city = new fabric.Textbox('BERLIN', this.pojoProxy.city);
        this.country = new fabric.Textbox('GERMANY', this.pojoProxy.country);
        this.location = new fabric.Textbox('52.5200° N / 13.4050° E', this.pojoProxy.location);

        PosterStyling.loadFont(this.city, this.oCanvas);
        PosterStyling.loadFont(this.country, this.oCanvas);
        PosterStyling.loadFont(this.location, this.oCanvas);
    }

    static loadFont(textbox, canvas, font = 'Montserrat') {
        const myFont = new FontFaceObserver(font);
        myFont
            .load()
            .then(() => {
                canvas.add(textbox);
                textbox.centerH();
                textbox.set('fontFamily', font);
                canvas.requestRenderAll();
            })
            .catch((e) => {
                console.log(e);
            });
    }
    /*eslint-disable */
    /**
     * This needs some TLC
     */
    handler() {
        const target = {};
        const that = this;
        const handler = {
            get(target, key) {
                if (typeof target[key] === 'object' && target[key] !== null) {
                    return new Proxy(target[key], handler);
                }
                return target[key];
            },
            set(target, key, value) {
                console.log(`key ${key} value ${value}`);

                switch (key) {
                case 'text':
                    target[key] = value.toUpperCase();
                    break;
                case 'fontSize':
                case 'width':
                    target[key] = parseInt(value) * Math.round(that.scale);
                    break;
                case 'height':
                    target[key] = value * that.scale;
                    break;
                default:
                    target[key] = value;
                    break;
                }
                that.updateOverlay();
                console.log(that.pojoProxy);
                return true;
            },
        };
        return new Proxy(this.posterStylingPojo, handler);
    }

    updateOverlay() {
        Object.keys(this.pojoProxy).forEach((key) => {
            if (key in this) {
                this[key].set(this.pojoProxy[key]);
            }
        });
        this.gradient.setGradient('fill', this.pojoProxy.gradientFill);
        this.oCanvas.requestRenderAll();
    }
}
<|endoftext|>


<commit_before>/**
 * routes/api.js - Defines /api/search and /api/browse routes for API requests
 *
 * Authors: Ian McGaunn; Dave Zimmelman
 * Modified: 09 Mar 15
 */

var express = require('express');
var request = require('request');
var xml2js = require('xml2js');
var router = express.Router();
var parser = new xml2js.Parser({explicitArray: false});
var parseString = parser.parseString;

var clientKey = require('../lib/clientkey').Key;
var Cache = require('../lib/badcache');
var Podcasts = require('../lib/podcasts');

/**
 * Routes for api/view URLs
 */
router.get('/view/splash', function (req, res, next) {
    var casts;
    if (req.session.user) {
        res.render('splash', {
            user: req.session.user,
            genres: Podcasts.Genres});
    }
    else {
        res.render('splash-guest', {
            genres: Podcasts.Genres});
    }
});
router.get('/view/podcast/:id', function (req, res, next) {
    var id = req.params.id;
    // HTTP request information on podcast ID via Apple API
    Podcasts.getPodcast(id, function (error, msg) {
        res.render('error', { message: msg, error: error});
    }, function (podcast) {
        request(podcast.feedUrl, function (error, response, body) {
            // Data returned in XML format, and must be parsed.
            parseString(body, function (err, obj) {
                console.log(req.session.user);
                // Render client 'podcast' page/view, passing necessary data.
                res.render('podcast', {id: id,
                                       user: req.session.user,
                                       title: podcast.title,
                                       podcast: podcast,
                                       safetitle: id + '/' + podcast.title_uri,
                                       feed: obj.rss.channel });
            });
        });
    });
});
router.get('/castcat/:gid', function (req, res, next) {
    if (req.session.user) {
        Cache.aggregate_cat(req.params.gid,
                            req.session.user.subscriptions,
                            function (err, msg) {
            console.log(msg);
            console.log(err);
            res.render(msg);
        }, function (data) {
            res.json({podcasts: data[0], favorites: data[1]});
        });
    } else {
        Cache.aggregate_cat(req.params.gid, false, function (err, msg) {
            console.log(msg);
            console.log(err);
            res.render(msg);
        }, function (data) {
            res.json({podcasts: data[0]});
        });
    }
});

/**
 * Route for api/clientkey URL
 */
router.post('/clientkey', function (req, res, next) {
    if (!req.body.key) {
        res.json({status: 400,
                  message: "Bad Request"});
    }
    else if (req.body.key !== 'fish') {
        res.json({status: 401,
                  message: "Unauthorized"});
    }
    else {
        res.json({status: 200,
                  message: clientKey});
    }
});

/**
 * Route for api/search URL
 */
router.get('/search', function (req, res, next) {
    var api = 'https://itunes.apple.com/search?entity=podcast&term=';
    // Name is ambiguous, and might confuse things when we implement local searching by station/artist/etc.
    // For now, it's best to use `term` to avoid confusion.
    var queryURL = api + req.query.term;

    console.log('url: ' + queryURL);

    // Send requested search data received from Apple API back to caller.
    request(queryURL, function (error, response, body) {
        // Parse JSON results
        var results = JSON.parse(body).results;
        if (results) {
            // Cache results
            Podcasts.cacheSearchResults(results, function (err, msg) {
                console.log(msg);
                console.log(err);
                res.json({"error": msg});
            }, function (pcasts) {
                Cache.search[req.query.term] = pcasts;
                res.json(pcasts);
            });
        } else {
            res.json({});
        }
    });
});

/**
 * Route for api/quicksearch URL
 */
router.get('/quicksearch', function (req, res, next) {
    Podcasts.searchByTerm(req.query.term, function (data) {
        res.json(data);
    });
});

/**
 * Route for api/browse URL
 */
router.get('/browse', function (req, res, next) {
    var api = 'https://itunes.apple.com/us/rss/toppodcasts';
    var limit = (req.query.limit && req.query.limit >= 0 && req.query.limit <= 200)
        ? '/limit=' + req.query.limit
        : '/limit=50';
    var explicit = (req.query.safe)
        ? '/explicit=false'
        : '/explicit=true';
    var genre = (req.query.genre && req.query.genre !== 0)
        ? '/genre=' + req.query.genre
        : '';
    var queryURL = api + limit + genre + explicit + '/json';

    console.log('url: ' + queryURL);

    // Send requested browse data received from Apple API back to caller.
    request(queryURL, function (error, response, body) {
        // Update cache with request
        Cache.browse[req.query.genre] = JSON.parse(body);
        res.send(body);
    });
});

// Expose routes
module.exports = router;
<commit_msg>[Debug] More<commit_after>/**
 * routes/api.js - Defines /api/search and /api/browse routes for API requests
 *
 * Authors: Ian McGaunn; Dave Zimmelman
 * Modified: 09 Mar 15
 */

var express = require('express');
var request = require('request');
var xml2js = require('xml2js');
var router = express.Router();
var parser = new xml2js.Parser({explicitArray: false});
var parseString = parser.parseString;

var clientKey = require('../lib/clientkey').Key;
var Cache = require('../lib/badcache');
var Podcasts = require('../lib/podcasts');

/**
 * Routes for api/view URLs
 */
router.get('/view/splash', function (req, res, next) {
    var casts;
    if (req.session.user) {
        res.render('splash', {
            user: req.session.user,
            genres: Podcasts.Genres});
    }
    else {
        res.render('splash-guest', {
            genres: Podcasts.Genres});
    }
});
router.get('/view/podcast/:id', function (req, res, next) {
    var id = req.params.id;
    // HTTP request information on podcast ID via Apple API
    Podcasts.getPodcast(id, function (error, msg) {
        res.render('error', { message: msg, error: error});
    }, function (podcast) {
        request(podcast.feedUrl, function (error, response, body) {
            // Data returned in XML format, and must be parsed.
            parseString(body, function (err, obj) {
                console.log(req.session.user.isFavorited);
                // Render client 'podcast' page/view, passing necessary data.
                res.render('podcast', {id: id,
                                       user: req.session.user,
                                       title: podcast.title,
                                       podcast: podcast,
                                       safetitle: id + '/' + podcast.title_uri,
                                       feed: obj.rss.channel });
            });
        });
    });
});
router.get('/castcat/:gid', function (req, res, next) {
    if (req.session.user) {
        Cache.aggregate_cat(req.params.gid,
                            req.session.user.subscriptions,
                            function (err, msg) {
            console.log(msg);
            console.log(err);
            res.render(msg);
        }, function (data) {
            res.json({podcasts: data[0], favorites: data[1]});
        });
    } else {
        Cache.aggregate_cat(req.params.gid, false, function (err, msg) {
            console.log(msg);
            console.log(err);
            res.render(msg);
        }, function (data) {
            res.json({podcasts: data[0]});
        });
    }
});

/**
 * Route for api/clientkey URL
 */
router.post('/clientkey', function (req, res, next) {
    if (!req.body.key) {
        res.json({status: 400,
                  message: "Bad Request"});
    }
    else if (req.body.key !== 'fish') {
        res.json({status: 401,
                  message: "Unauthorized"});
    }
    else {
        res.json({status: 200,
                  message: clientKey});
    }
});

/**
 * Route for api/search URL
 */
router.get('/search', function (req, res, next) {
    var api = 'https://itunes.apple.com/search?entity=podcast&term=';
    // Name is ambiguous, and might confuse things when we implement local searching by station/artist/etc.
    // For now, it's best to use `term` to avoid confusion.
    var queryURL = api + req.query.term;

    console.log('url: ' + queryURL);

    // Send requested search data received from Apple API back to caller.
    request(queryURL, function (error, response, body) {
        // Parse JSON results
        var results = JSON.parse(body).results;
        if (results) {
            // Cache results
            Podcasts.cacheSearchResults(results, function (err, msg) {
                console.log(msg);
                console.log(err);
                res.json({"error": msg});
            }, function (pcasts) {
                Cache.search[req.query.term] = pcasts;
                res.json(pcasts);
            });
        } else {
            res.json({});
        }
    });
});

/**
 * Route for api/quicksearch URL
 */
router.get('/quicksearch', function (req, res, next) {
    Podcasts.searchByTerm(req.query.term, function (data) {
        res.json(data);
    });
});

/**
 * Route for api/browse URL
 */
router.get('/browse', function (req, res, next) {
    var api = 'https://itunes.apple.com/us/rss/toppodcasts';
    var limit = (req.query.limit && req.query.limit >= 0 && req.query.limit <= 200)
        ? '/limit=' + req.query.limit
        : '/limit=50';
    var explicit = (req.query.safe)
        ? '/explicit=false'
        : '/explicit=true';
    var genre = (req.query.genre && req.query.genre !== 0)
        ? '/genre=' + req.query.genre
        : '';
    var queryURL = api + limit + genre + explicit + '/json';

    console.log('url: ' + queryURL);

    // Send requested browse data received from Apple API back to caller.
    request(queryURL, function (error, response, body) {
        // Update cache with request
        Cache.browse[req.query.genre] = JSON.parse(body);
        res.send(body);
    });
});

// Expose routes
module.exports = router;
<|endoftext|>