<commit_before>const express = require('express'),
    path = require('path'),
    fs = require('fs-extra'),
    sequelize = require('../db/connect'),
    models = require('../db/models')(sequelize)
    config = require('../config'),
    multer = require('multer')
    upload = multer({dest: path.join(__dirname, '../documents/tmp')}),
    helpers = require('../helpers');

module.exports = (function () {
    'use strict';

    let pdfs = express.Router();

    pdfs.post('/upload', upload.single('document'), (req, res) => {
        let { courseName, fileName, userId, username, courseId } = req.body;
        let oldPath = req.file.path;
        let newPath = path.join(__dirname, `../documents/${courseName}/`);

        models.Document.create({
            name: fileName,
            userId: userId,
            courseId: courseId
        })
        .then(doc => {
            fs.ensureDir(path.join(__dirname, '../documents'), function(err) {
                fs.ensureDir(path.join(__dirname, '../documents/tmp'), function(err) {
                    newPath += `${username}${doc.id}.pdf`
                    res.json(doc);
                    return helpers.moveFile(oldPath, newPath)
                })
            });
        });
    });

    pdfs.get('/:courseId', (req, res) => {
        let { courseId } = req.params;
        models.Document.findAll({
            where: {courseId: courseId},
            include: [
                { model: models.User, as: 'user', attributes: ['id', 'name', 'username']},
            ],
            attributes: [ 'id', 'name', 'createdAt',
                [sequelize.literal('(SELECT COUNT("documentId") from comments where "comments"."documentId" = 1)'), 'commentsCount']
            ]
        })
        .then(docs => {
            res.json(docs);
        });
    })

    pdfs.get('/profile/:userId', (req, res) => {
        let userId = req.params.userId;

        models.Document.findAll({
            where: {userId: userId},
            include: [{
                model: models.User, as: 'user',
                attributes: [
                    'id',
                    'name',
                    'username'
                ]
            }],
            attributes: [ 'id', 'name', 'createdAt',
                [sequelize.literal('(SELECT COUNT("documentId") from comments where "comments"."documentId" = 1)'), 'commentsCount']
            ]
        })
        .then(docs => {
            res.json(docs);
        });

    });

    pdfs.get('/download/:username/:courseName/:id', (req, res) => {
        let {username, courseName, id} = req.params;
        var doc = path.join(__dirname , `../documents/${courseName}/${username}${id}.pdf`);
        res.download(doc, `${courseName}-${username}.pdf`);
    });

    return pdfs;

})();<commit_msg>fixed the document api route to include comment count used a raw sql query to add comment count for documents<commit_after>const express = require('express'),
    path = require('path'),
    fs = require('fs-extra'),
    sequelize = require('../db/connect'),
    models = require('../db/models')(sequelize)
    config = require('../config'),
    multer = require('multer')
    upload = multer({dest: path.join(__dirname, '../documents/tmp')}),
    helpers = require('../helpers');

module.exports = (function () {
    'use strict';

    let pdfs = express.Router();

    pdfs.post('/upload', upload.single('document'), (req, res) => {
        let { courseName, fileName, userId, username, courseId } = req.body;
        let oldPath = req.file.path;
        let newPath = path.join(__dirname, `../documents/${courseName}/`);

        models.Document.create({
            name: fileName,
            userId: userId,
            courseId: courseId
        })
        .then(doc => {
            fs.ensureDir(path.join(__dirname, '../documents'), function(err) {
                fs.ensureDir(path.join(__dirname, '../documents/tmp'), function(err) {
                    newPath += `${username}${doc.id}.pdf`
                    res.json(doc);
                    return helpers.moveFile(oldPath, newPath)
                })
            });
        });
    });

    pdfs.get('/:courseId', (req, res) => {
        let { courseId } = req.params;
        models.Document.findAll({
            where: {courseId: courseId},
            include: [
                { model: models.User, as: 'user', attributes: ['id', 'name', 'username']},
            ],
            attributes: [ 'id', 'name', 'createdAt',
                [sequelize.literal(`(SELECT COUNT("documentId") from comments where "comments"."documentId" = Document.id)`), 'commentsCount']
            ]
        })
        .then(docs => {
            res.json(docs);
        });
    })

    pdfs.get('/profile/:userId', (req, res) => {
        let userId = req.params.userId;

        models.Document.findAll({
            where: {userId: userId},
            include: [{
                model: models.User, as: 'user',
                attributes: [
                    'id',
                    'name',
                    'username'
                ]
            }],
            attributes: [ 'id', 'name', 'createdAt',
                [sequelize.literal(`(SELECT COUNT("documentId") from comments where "comments"."documentId" = Document.id)`), 'commentsCount']
            ]
        })
        .then(docs => {
            res.json(docs);
        });

    });

    pdfs.get('/download/:username/:courseName/:id', (req, res) => {
        let {username, courseName, id} = req.params;
        var doc = path.join(__dirname , `../documents/${courseName}/${username}${id}.pdf`);
        res.download(doc, `${courseName}-${username}.pdf`);
    });

    return pdfs;

})();<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { List, Map } from 'immutable';
import initFetch from 'iso-fetch-stream';

import { isSchema, getIdField } from './lib/schema';
import store from './store';
import logger, { logError, logWarn } from './lib/log';
import getQueryString from './lib/query_string';

/* basic state tree design for api.js
* {
*   $api: {
*     [url]: {
*       [resource]: {
*         models: {
*           [id]: {
*             [key]: any,
*             _fetched?: boolean
*           }
*         },
*         requests: {
*           [url]: {
*             pending?: boolean,
*             error?: Object,
*             data?: List | Map
*           }
*         }
*       }
*     }
*   }
* }
*/

// TODO: use some more specific method from store's undo implementation
const isUndo = () => store.getState( '_reset' );

function createActions({ url, ...props }) {

  const fetchOptions = props.credentials ? { credentials: props.credentials } : {};
  if ( props.auth || props.authorization ) fetchOptions.headers = { Authorization: props.auth || props.authorization };
  const fetch = initFetch( fetchOptions );

  return Object.keys( props ).reduce(( memo, key ) => {

    if ( isSchema( props[ key ] ) ) {

      const schema       = props[ key ].schema || props[ key ];
      const getData      = props[ key ].getData || ( data => data );
      const idField      = getIdField( schema );
      const resourceType = schema.title;
      const resourcePath = `/${resourceType}`;

      const routes = Object.assign({
        create: () => resourcePath,
        list:   () => resourcePath,
        search: () => resourcePath,
        get:    id => `${resourcePath}/${id}`,
        update: id => `${resourcePath}/${id}`,
        delete: id => `${resourcePath}/${id}`
      }, props[ key ].routes || {} );

      const getState      = key => store.getState([ '$api', url, resourceType ].concat( key || [] ).map( item => String( item ) ) );
      const setState      = ( val, key ) => store.setState([ '$api', url, resourceType ].concat( key || [] ).map( item => String( item ) ), val );
      const setStateQuiet = ( val, key ) => store.setStateQuiet([ '$api', url, resourceType ].concat( key || [] ), val );

      const getRequests = path => getState([ 'requests' ].concat( path || [] ) );
      const getRequestsData = path => getRequests([ path, 'data' ]);
      const setRequests = ( data, path ) => setState( data, [ 'requests' ].concat( path || [] ) );
      const setRequestsData = ( path, data ) => setState( data, [ 'requests', path, 'data' ] );

      const getCollection = path => {
        const collection = getRequestsData( path );
        if ( collection ) {
          const models = getModels();
          return collection.reduce(( memo, id ) => {
            const model = models.get( id );
            if ( model ) return memo.push( model );
            return memo;
          }, List());
        } else {
          return null;
        }
      };

      const setCollection = ( data = List(), path = '/' ) => {
        const state = getState();
        const nextState = state.withMutations( map => {
          const dict = data.reduce(( memo, item ) => ({ ...memo, [item[idField]]: item }), {});
          map.set( 'models', getModels().mergeDeep( dict ) );
          map.setIn([ 'requests', path, 'data' ], List( Object.keys( dict ) ) );
        });
        setState( nextState );
      };

      const clearCollection = ( path = '/' ) => setRequestsData( path, null );
      const clearAll = () => {
        setRequests( Map() );
        setModels( Map() );
      };

      const getModels  = () => getState( 'models' ) || Map();
      const setModels  = data => setState( data, 'models' );
      const getModel   = id => getState([ 'models', id ]);
      const setModel   = ( id, data ) => setState( data, [ 'models', id ] );
      const getPending = path => getState([ 'requests', path, 'pending' ]);
      const setPending = ( path, data ) => setStateQuiet( data, [ 'requests', path, 'pending' ]);
      const getError   = path => getState([ 'requests', path, 'error' ]);
      const setError   = ( path, error, options = {} ) => {
        const method = options.quiet ? setStateQuiet : setState;
        method( error, [ 'requests', path, 'error' ] );
      };

      const setSearchResults = path => data => setCollection( data, path );
      const getSearchResults = path => getCollection( path );

      const removeFromCollections = ( map, id ) =>
        [ ...map.get( 'requests' ).entries() ]
          .reduce(( undo, [path, request] ) => {
            const collection = request.get( 'data' );
            if ( List.isList( collection ) ) {
              const modelIdx = collection.findIndex( modelId => modelId === id );
              if ( ~modelIdx ) {
                const nextCollection = collection.delete( modelIdx );
                map.setIn([ 'requests', path, 'data' ], nextCollection );
                undo.push(() => setCollection( nextCollection.insert( modelIdx, id ), path ));
              }
            }
            return undo;
          }, []);

      const deleteModel = id => {
        const undo = [];
        const state = getState();
        setState( state.withMutations( map => {
          const model = getModel( id );
          if ( model ) {
            map.set( 'models', getModels().delete( id ) );
            undo.push(() => setModels( getModels().set( id, model ) ));
            undo.push( ...removeFromCollections( map, id ) );
          }
        }));
        return undo;
      };

      const updateModel = ( id, vals ) => {
        const undo = [];
        const model = getModel( id );
        if ( model ) {
          setModel( id, model.mergeDeep( vals ) );
          undo.push(() => setModels( getModels().set( id, model ) ) );
        }
        return undo;
      };

      const shouldFetch = path => !isUndo() && !getPending( path ) && !getError( path );

      const api = ['get', 'post', 'put', 'delete' ].reduce(( memo, method ) => ({
        ...memo,
        [method]: ( path, ...args ) => {
          setPending( path, true );
          return fetch[ method ]( `${url}${path}`, ...args )
            .then(
              response => {
                const data = getData( response );
                setPending( path, false );
                setError( path, null, { quiet: true } );
                return data;
              },
              err => {
                setPending( path, false );
                setError( path, err );
                return Promise.reject( err );
              }
            );
        }
      }), {});

      /**
       * api calls
       */
      const fetchAll = path => {
        const route = path || routes.list();
        if ( shouldFetch( route ) ) {
          api.get( route ).then( data => setCollection( data, route ) );
        }
      };

      const fetchOne = id => {
        const route = routes.get( id );
        if ( shouldFetch( route ) ) {
          api.get( route ).then( data => setModel( data[ idField ], { ...data, _fetched: true }));
        }
      };

      const createOne = data => {
        const route = routes.create( data );
        return api.post( route, data ).then( data => {
          fetchAll();
          return data;
        });
      };

      const updateOne = ( id, data ) => {
        const route = routes.update( id, data );
        return api.put( route, data );
      };

      const deleteOne = id => {
        const route = routes.delete( id );
        return api.delete( route );
      };

      const search = route => {
        if ( shouldFetch( route ) ) {
          return api.get( route ).then( data => {
            setSearchResults( route, data );
            return data;
          });
        }
        // TODO: store promise as pending value so it can be used on repeat
        // calls
        return Promise.resolve( 'pending' );
      };

      /**/

      const $clear = id => () => setModel( id, null );
      const $reset = id => () => fetchOne( id );

      const optimisticDelete = id => {
        const undoDelete = deleteModel( id );
        if ( undoDelete.length ) {
          return deleteOne( id ).catch( err => {
            logError( `Failed to delete ${id}`, err );
            undoDelete.forEach( undo => undo() );
            return Promise.reject( err );
          });
        } else {
          return Promise.reject( new Error( 404 ) );
        }
      };

      const $delete = id => ( options = {} ) =>
        options.optimistic === false || !getModel( id )
          ? deleteOne( id ).then(() => deleteModel( id ) )
          : optimisticDelete( id );

      const optimisticUpdate = ( id, vals, options = {} ) => {
        const placeholder = typeof options.optimistic === 'function'
          ? options.optimistic({ id, ...vals })
          : vals;
        const undoUpdate = updateModel( id, placeholder );
        if ( undoUpdate.length ) {
          return updateOne( id, vals ).catch( err => {
            logError( `Failed to update ${id} with vals`, vals, err );
            undoUpdate.forEach( undo => undo() );
            return Promise.reject( err );
          });
        } else {
          return Promise.reject( new Error( 404 ) );
        }
      };

      const $update = id => ( vals, options = {} ) =>
        options.optimistic === false || !getModel( id )
          ? updateOne( id, vals ).then( data => updateModel( id, data ) )
          : optimisticUpdate( id, vals, options );

      const getPlaceholder = ( path = null, dataType = List ) => {
        const placeholder = dataType();
        placeholder.$isPending = true;
        placeholder.$error = path ? getError( path ) : null;
        return placeholder;
      };

      const addRestMethods = model => {
        const id = model.get( idField );
        model.$delete = $delete( id );
        model.$update = $update( id );
        model.$clear  = $clear( id );
        model.$reset  = $reset( id );
        return model;
      };

      const $list = params => {
        const path = routes.list() + ( params ? `?${getQueryString( params )}` : '' );
        const collection = getCollection( path );
        if ( !collection ) {
          fetchAll( path );
          return getPlaceholder( path );
        } else {
          return collection.map( addRestMethods );
        }
      };

      const main = params => $list( params );

      main.$list = $list;

      main.$get = id => {
        const model = getModel( id );
        if ( !model || !model.get( '_fetched' ) ) {
          const path = routes.get( id );
          fetchOne( id );
          const placeholder = getPlaceholder( path, Map );
          placeholder.$delete = $delete( id );
          placeholder.$update = $update( id );
          return placeholder;
        } else {
          return addRestMethods( model );
        }
      };

      // remove the cache for the resource collection
      main.$clear = () => clearCollection();
      main.$clearAll = clearAll;
      main.$reset = fetchAll;

      const optimisticCreate = ( data, options = {} ) => {
        if ( typeof options.optimistic === 'function' ) {
          const nextState = getState().withMutations( map => options.optimistic( map, data ) );
          if ( nextState ) setState( nextState );
          return createOne( data );
        } else {
          logWarn( `Optimistic creates must receive a function that updates the state with the optimistic data. The create will proceed pessimistically.`);
          return createOne( data );
        }
      };

      main.$create = ( data, options = {} ) =>
        !options.optimistic
          ? createOne( data )
          : optimisticCreate( data, options );

      main.$search = ({ route, ...args }) => {
        const queryString = getQueryString( args );
        const fullRoute = ( route || routes.search() ) + `?${queryString}`;
        return search( fullRoute );
      };

      main.$search.results = ({ route, ...args }) => {
        const queryString = getQueryString( args );
        const fullRoute = ( route || routes.search() ) + `?${queryString}`;
        const resultsCached = getSearchResults( fullRoute );
        if ( resultsCached ) {
          return addRestMethods( resultsCached );
        } else {
          const placeholder = List();
          placeholder.$isPending = !!getPending( fullRoute );
          placeholder.$error = getError( fullRoute );
          return placeholder;
        }
      };

      // hang standard api methods off of .api so devs can construct
      // non-standard paths
      main.api = [ 'delete', '$get', 'get', 'post', 'put', 'stream' ].reduce(( memo, method ) => ({
        ...memo,
        [method]: ( path, ...args ) => {
          const route = resourcePath + path;
          if ( method === '$get' ) {
            const cache = getRequestsData( route );
            if ( cache ) {
              return List.isList( cache )
                ? getCollection( route ).map( addRestMethods )
                : cache;
            } else {
              if ( shouldFetch( route ) ) {
                api.get( route, ...args ).then( data => {
                  if ( Array.isArray( data ) ) {
                    setCollection( data, route );
                  } else {
                    setRequestsData( route, data );
                  }
                  return data;
                });
              }
              return getPlaceholder( route );
            }
          } else {
            return api[ method ]( route, ...args );
          }
        }
      }), {});

      main._schema       = schema;
      main._resourceType = resourceType;
      main.getState      = getState;

      memo[ key ] = main;
      if ( typeof window !== 'undefined' ) {
        window.jetset = window.jetset || {};
        window.jetset[ key ] = main;
      }
    }
    return memo;
  }, {} );
}

export default class Api extends React.Component {

  static propTypes = {
    url: PropTypes.string.isRequired,
    // see https://github.com/github/fetch#sending-cookies for reference
    credentials: PropTypes.oneOf( [ 'same-origin', 'include' ] ),
    token: PropTypes.string
  }

  subscriptions = []

  constructor( props ) {
    super( props );
    this.api = createActions( props );
    this.state = { cache: null };
  }

  componentWillMount() {
    this.subscriptions = Object.keys( this.api ).map( key => {
      const resource = this.api[ key ]._resourceType;
      return store.subscribeTo([ '$api', this.props.url, resource ], state => {
        logger(`\uD83C\uDF00 <Api> is re-rendering based on state changes on branch: %c${this.props.url + ' ‣ ' + resource}`, 'color: #5B4532' );
        this.setState({ cache: state });
      });
    });
  }

  componentWillUnmount() {
    this.subscriptions.forEach( subscription => store.unsubscribe( subscription ) );
  }

  render() {
    const { children, ...props } = this.props;
    return children && typeof children.type === 'function'
      ? React.cloneElement( children, { ...props, ...this.api } )
      : children;
  }
}
<commit_msg>make sure params are passed in to routes.list() and routes.search()<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { List, Map } from 'immutable';
import initFetch from 'iso-fetch-stream';

import { isSchema, getIdField } from './lib/schema';
import store from './store';
import logger, { logError, logWarn } from './lib/log';
import getQueryString from './lib/query_string';

/* basic state tree design for api.js
* {
*   $api: {
*     [url]: {
*       [resource]: {
*         models: {
*           [id]: {
*             [key]: any,
*             _fetched?: boolean
*           }
*         },
*         requests: {
*           [url]: {
*             pending?: boolean,
*             error?: Object,
*             data?: List | Map
*           }
*         }
*       }
*     }
*   }
* }
*/

// TODO: use some more specific method from store's undo implementation
const isUndo = () => store.getState( '_reset' );

function createActions({ url, ...props }) {

  const fetchOptions = props.credentials ? { credentials: props.credentials } : {};
  if ( props.auth || props.authorization ) fetchOptions.headers = { Authorization: props.auth || props.authorization };
  const fetch = initFetch( fetchOptions );

  return Object.keys( props ).reduce(( memo, key ) => {

    if ( isSchema( props[ key ] ) ) {

      const schema       = props[ key ].schema || props[ key ];
      const getData      = props[ key ].getData || ( data => data );
      const idField      = getIdField( schema );
      const resourceType = schema.title;
      const resourcePath = `/${resourceType}`;

      const routes = Object.assign({
        create: () => resourcePath,
        list:   () => resourcePath,
        search: () => resourcePath,
        get:    id => `${resourcePath}/${id}`,
        update: id => `${resourcePath}/${id}`,
        delete: id => `${resourcePath}/${id}`
      }, props[ key ].routes || {} );

      const getState      = key => store.getState([ '$api', url, resourceType ].concat( key || [] ).map( item => String( item ) ) );
      const setState      = ( val, key ) => store.setState([ '$api', url, resourceType ].concat( key || [] ).map( item => String( item ) ), val );
      const setStateQuiet = ( val, key ) => store.setStateQuiet([ '$api', url, resourceType ].concat( key || [] ), val );

      const getRequests = path => getState([ 'requests' ].concat( path || [] ) );
      const getRequestsData = path => getRequests([ path, 'data' ]);
      const setRequests = ( data, path ) => setState( data, [ 'requests' ].concat( path || [] ) );
      const setRequestsData = ( path, data ) => setState( data, [ 'requests', path, 'data' ] );

      const getCollection = path => {
        const collection = getRequestsData( path );
        if ( collection ) {
          const models = getModels();
          return collection.reduce(( memo, id ) => {
            const model = models.get( id );
            if ( model ) return memo.push( model );
            return memo;
          }, List());
        } else {
          return null;
        }
      };

      const setCollection = ( data = List(), path = '/' ) => {
        const state = getState();
        const nextState = state.withMutations( map => {
          const dict = data.reduce(( memo, item ) => ({ ...memo, [item[idField]]: item }), {});
          map.set( 'models', getModels().mergeDeep( dict ) );
          map.setIn([ 'requests', path, 'data' ], List( Object.keys( dict ) ) );
        });
        setState( nextState );
      };

      const clearCollection = ( path = '/' ) => setRequestsData( path, null );
      const clearAll = () => {
        setRequests( Map() );
        setModels( Map() );
      };

      const getModels  = () => getState( 'models' ) || Map();
      const setModels  = data => setState( data, 'models' );
      const getModel   = id => getState([ 'models', id ]);
      const setModel   = ( id, data ) => setState( data, [ 'models', id ] );
      const getPending = path => getState([ 'requests', path, 'pending' ]);
      const setPending = ( path, data ) => setStateQuiet( data, [ 'requests', path, 'pending' ]);
      const getError   = path => getState([ 'requests', path, 'error' ]);
      const setError   = ( path, error, options = {} ) => {
        const method = options.quiet ? setStateQuiet : setState;
        method( error, [ 'requests', path, 'error' ] );
      };

      const setSearchResults = path => data => setCollection( data, path );
      const getSearchResults = path => getCollection( path );

      const removeFromCollections = ( map, id ) =>
        [ ...map.get( 'requests' ).entries() ]
          .reduce(( undo, [path, request] ) => {
            const collection = request.get( 'data' );
            if ( List.isList( collection ) ) {
              const modelIdx = collection.findIndex( modelId => modelId === id );
              if ( ~modelIdx ) {
                const nextCollection = collection.delete( modelIdx );
                map.setIn([ 'requests', path, 'data' ], nextCollection );
                undo.push(() => setCollection( nextCollection.insert( modelIdx, id ), path ));
              }
            }
            return undo;
          }, []);

      const deleteModel = id => {
        const undo = [];
        const state = getState();
        setState( state.withMutations( map => {
          const model = getModel( id );
          if ( model ) {
            map.set( 'models', getModels().delete( id ) );
            undo.push(() => setModels( getModels().set( id, model ) ));
            undo.push( ...removeFromCollections( map, id ) );
          }
        }));
        return undo;
      };

      const updateModel = ( id, vals ) => {
        const undo = [];
        const model = getModel( id );
        if ( model ) {
          setModel( id, model.mergeDeep( vals ) );
          undo.push(() => setModels( getModels().set( id, model ) ) );
        }
        return undo;
      };

      const shouldFetch = path => !isUndo() && !getPending( path ) && !getError( path );

      const api = ['get', 'post', 'put', 'delete' ].reduce(( memo, method ) => ({
        ...memo,
        [method]: ( path, ...args ) => {
          setPending( path, true );
          return fetch[ method ]( `${url}${path}`, ...args )
            .then(
              response => {
                const data = getData( response );
                setPending( path, false );
                setError( path, null, { quiet: true } );
                return data;
              },
              err => {
                setPending( path, false );
                setError( path, err );
                return Promise.reject( err );
              }
            );
        }
      }), {});

      /**
       * api calls
       */
      const fetchAll = path => {
        const route = path || routes.list();
        if ( shouldFetch( route ) ) {
          api.get( route ).then( data => setCollection( data, route ) );
        }
      };

      const fetchOne = id => {
        const route = routes.get( id );
        if ( shouldFetch( route ) ) {
          api.get( route ).then( data => setModel( data[ idField ], { ...data, _fetched: true }));
        }
      };

      const createOne = data => {
        const route = routes.create( data );
        return api.post( route, data ).then( data => {
          fetchAll();
          return data;
        });
      };

      const updateOne = ( id, data ) => {
        const route = routes.update( id, data );
        return api.put( route, data );
      };

      const deleteOne = id => {
        const route = routes.delete( id );
        return api.delete( route );
      };

      const search = route => {
        if ( shouldFetch( route ) ) {
          return api.get( route ).then( data => {
            setSearchResults( route, data );
            return data;
          });
        }
        // TODO: store promise as pending value so it can be used on repeat
        // calls
        return Promise.resolve( 'pending' );
      };

      /**/

      const $clear = id => () => setModel( id, null );
      const $reset = id => () => fetchOne( id );

      const optimisticDelete = id => {
        const undoDelete = deleteModel( id );
        if ( undoDelete.length ) {
          return deleteOne( id ).catch( err => {
            logError( `Failed to delete ${id}`, err );
            undoDelete.forEach( undo => undo() );
            return Promise.reject( err );
          });
        } else {
          return Promise.reject( new Error( 404 ) );
        }
      };

      const $delete = id => ( options = {} ) =>
        options.optimistic === false || !getModel( id )
          ? deleteOne( id ).then(() => deleteModel( id ) )
          : optimisticDelete( id );

      const optimisticUpdate = ( id, vals, options = {} ) => {
        const placeholder = typeof options.optimistic === 'function'
          ? options.optimistic({ id, ...vals })
          : vals;
        const undoUpdate = updateModel( id, placeholder );
        if ( undoUpdate.length ) {
          return updateOne( id, vals ).catch( err => {
            logError( `Failed to update ${id} with vals`, vals, err );
            undoUpdate.forEach( undo => undo() );
            return Promise.reject( err );
          });
        } else {
          return Promise.reject( new Error( 404 ) );
        }
      };

      const $update = id => ( vals, options = {} ) =>
        options.optimistic === false || !getModel( id )
          ? updateOne( id, vals ).then( data => updateModel( id, data ) )
          : optimisticUpdate( id, vals, options );

      const getPlaceholder = ( path = null, dataType = List ) => {
        const placeholder = dataType();
        placeholder.$isPending = true;
        placeholder.$error = path ? getError( path ) : null;
        return placeholder;
      };

      const addRestMethods = model => {
        const id = model.get( idField );
        model.$delete = $delete( id );
        model.$update = $update( id );
        model.$clear  = $clear( id );
        model.$reset  = $reset( id );
        return model;
      };

      const $list = params => {
        const path = routes.list( params ) + ( params ? `?${getQueryString( params )}` : '' );
        const collection = getCollection( path );
        if ( !collection ) {
          fetchAll( path );
          return getPlaceholder( path );
        } else {
          return collection.map( addRestMethods );
        }
      };

      const main = params => $list( params );

      main.$list = $list;

      main.$get = id => {
        const model = getModel( id );
        if ( !model || !model.get( '_fetched' ) ) {
          const path = routes.get( id );
          fetchOne( id );
          const placeholder = getPlaceholder( path, Map );
          placeholder.$delete = $delete( id );
          placeholder.$update = $update( id );
          return placeholder;
        } else {
          return addRestMethods( model );
        }
      };

      // remove the cache for the resource collection
      main.$clear = () => clearCollection();
      main.$clearAll = clearAll;
      main.$reset = fetchAll;

      const optimisticCreate = ( data, options = {} ) => {
        if ( typeof options.optimistic === 'function' ) {
          const nextState = getState().withMutations( map => options.optimistic( map, data ) );
          if ( nextState ) setState( nextState );
          return createOne( data );
        } else {
          logWarn( `Optimistic creates must receive a function that updates the state with the optimistic data. The create will proceed pessimistically.`);
          return createOne( data );
        }
      };

      main.$create = ( data, options = {} ) =>
        !options.optimistic
          ? createOne( data )
          : optimisticCreate( data, options );

      main.$search = ({ route, ...args }) => {
        const queryString = getQueryString( args );
        const fullRoute = ( route || routes.search( args ) ) + `?${queryString}`;
        return search( fullRoute );
      };

      main.$search.results = ({ route, ...args }) => {
        const queryString = getQueryString( args );
        const fullRoute = ( route || routes.search( args ) ) + `?${queryString}`;
        const resultsCached = getSearchResults( fullRoute );
        if ( resultsCached ) {
          return addRestMethods( resultsCached );
        } else {
          const placeholder = List();
          placeholder.$isPending = !!getPending( fullRoute );
          placeholder.$error = getError( fullRoute );
          return placeholder;
        }
      };

      // hang standard api methods off of .api so devs can construct
      // non-standard paths
      main.api = [ 'delete', '$get', 'get', 'post', 'put', 'stream' ].reduce(( memo, method ) => ({
        ...memo,
        [method]: ( path, ...args ) => {
          const route = resourcePath + path;
          if ( method === '$get' ) {
            const cache = getRequestsData( route );
            if ( cache ) {
              return List.isList( cache )
                ? getCollection( route ).map( addRestMethods )
                : cache;
            } else {
              if ( shouldFetch( route ) ) {
                api.get( route, ...args ).then( data => {
                  if ( Array.isArray( data ) ) {
                    setCollection( data, route );
                  } else {
                    setRequestsData( route, data );
                  }
                  return data;
                });
              }
              return getPlaceholder( route );
            }
          } else {
            return api[ method ]( route, ...args );
          }
        }
      }), {});

      main._schema       = schema;
      main._resourceType = resourceType;
      main.getState      = getState;

      memo[ key ] = main;
      if ( typeof window !== 'undefined' ) {
        window.jetset = window.jetset || {};
        window.jetset[ key ] = main;
      }
    }
    return memo;
  }, {} );
}

export default class Api extends React.Component {

  static propTypes = {
    url: PropTypes.string.isRequired,
    // see https://github.com/github/fetch#sending-cookies for reference
    credentials: PropTypes.oneOf( [ 'same-origin', 'include' ] ),
    token: PropTypes.string
  }

  subscriptions = []

  constructor( props ) {
    super( props );
    this.api = createActions( props );
    this.state = { cache: null };
  }

  componentWillMount() {
    this.subscriptions = Object.keys( this.api ).map( key => {
      const resource = this.api[ key ]._resourceType;
      return store.subscribeTo([ '$api', this.props.url, resource ], state => {
        logger(`\uD83C\uDF00 <Api> is re-rendering based on state changes on branch: %c${this.props.url + ' ‣ ' + resource}`, 'color: #5B4532' );
        this.setState({ cache: state });
      });
    });
  }

  componentWillUnmount() {
    this.subscriptions.forEach( subscription => store.unsubscribe( subscription ) );
  }

  render() {
    const { children, ...props } = this.props;
    return children && typeof children.type === 'function'
      ? React.cloneElement( children, { ...props, ...this.api } )
      : children;
  }
}
<|endoftext|>


<commit_before>Template.navbar.events({
	'click #logout-link': function(e) {
		e.preventDefault();
		Meteor.logout(function() {
			Meteor.Router.homePath();
			$('html,body').scrollTop(0);
		});
	}
});<commit_msg>Added flash message on log out<commit_after>Template.navbar.events({
	'click #logout-link': function(e) {
		e.preventDefault();
		Meteor.logout(function() {
			Meteor.Router.homePath();
			FlashMessages.sendSuccess("Successfully Logged Out!", { autoHide: true, hideDelay: 5000})
			$('html,body').scrollTop(0);
		});
	}
});<|endoftext|>


<commit_before>exports.commands = {
	l: 'lick',
        lick: function (target, room, user) {
		if (!target) return;
		target = this.splitTarget(target);
		var targetUser = this.targetUser;
		if (!targetUser || !targetUser.connected) {
			return this.sendReply("User " + this.targetUsername + " not found.");
		}
		if (!this.can('lick', targetUser, room)) return false;
		targetUser.popup("You have been " + msg);
	}
};
<commit_msg>Update lick.js<commit_after>exports.commands = {
	l: 'lick',
        lick: function (target, room, user) {
		if (!target) return;
		target = this.splitTarget(target);
		var targetUser = this.targetUser;
		if (!targetUser || !targetUser.connected) {
			return this.sendReply("User " + this.targetUsername + " not found.");
		}
		if (!this.can('lick', targetUser, room)) return false;
		this.addModCommand("" + targetUser.name + " was " + msg);
		targetUser.popup("You have been " + msg);
	}
};
<|endoftext|>


<commit_before>'use strict';
// generated on 2016-10-03 using generator-ratchet 0.1.1

var gulp = require('gulp');

// load plugins
var $ = require('gulp-load-plugins')();

gulp.task('styles', function () {
    return gulp.src('app/styles/main.scss')
        .pipe($.rubySass({
            style: 'expanded',
            precision: 10
        }))
        .pipe($.autoprefixer('last 1 version'))
        .pipe(gulp.dest('.tmp/styles'))
        .pipe($.size());
});

gulp.task('jshint', function () {
    return gulp.src('app/scripts/**/*.js')
        .pipe($.jshint())
        .pipe($.jshint.reporter('jshint-stylish'))
        .pipe($.jshint.reporter('fail'))
        .pipe($.size());
});

gulp.task('html', ['styles'], function () {
    var jsFilter = $.filter('**/*.js');
    var cssFilter = $.filter('**/*.css');

    return gulp.src('app/*.html')
        .pipe($.useref.assets({searchPath: '{.tmp,app}'}))
        .pipe(jsFilter)
        .pipe($.uglify())
        .pipe(jsFilter.restore())
        .pipe(cssFilter)
        .pipe($.csso())
        .pipe(cssFilter.restore())
        .pipe($.useref.restore())
        .pipe($.useref())
        .pipe(gulp.dest('dist'))
        .pipe($.size());
});

gulp.task('images', function () {
    return gulp.src('app/images/**/*')
        .pipe($.cache($.imagemin({
            optimizationLevel: 3,
            progressive: true,
            interlaced: true
        })))
        .pipe(gulp.dest('dist/images'))
        .pipe($.size());
});

gulp.task('fonts', function () {
    var streamqueue = require('streamqueue');
    return streamqueue({objectMode: true},
        $.bowerFiles(),
        gulp.src('app/fonts/**/*')
    )
        .pipe($.filter('**/*.{eot,svg,ttf,woff}'))
        .pipe($.flatten())
        .pipe(gulp.dest('dist/fonts'))
        .pipe($.size());
});

gulp.task('extras', function () {
    return gulp.src(['app/*.*', '!app/*.html'], { dot: true })
        .pipe(gulp.dest('dist'));
});

gulp.task('clean', function () {
    return gulp.src(['.tmp', 'dist'], { read: false }).pipe($.clean());
});

gulp.task('build', ['jshint', 'html', 'images', 'fonts', 'extras']);

gulp.task('default', ['clean'], function () {
    gulp.start('build');
});

gulp.task('connect', function () {
    var connect = require('connect');
    var app = connect()
        .use(require('connect-livereload')({ port: 35729 }))
        .use(connect.static('app'))
        .use(connect.static('.tmp'))
        // paths to bower_components should be relative to the current file
        // e.g. in app/index.html you should use ../bower_components
        .use('/bower_components', connect.static('bower_components'))
        .use(connect.directory('app'));

    require('http').createServer(app)
        .listen(9000)
        .on('listening', function () {
            console.log('Started connect web server on http://localhost:9000');
        });
});

gulp.task('serve', ['connect', 'styles'], function () {
    require('opn')('http://localhost:9000');
});

// inject bower components
gulp.task('wiredep', function () {
    var wiredep = require('wiredep').stream;

    gulp.src('app/styles/*.scss')
        .pipe(wiredep({
            directory: 'bower_components'
        }))
        .pipe(gulp.dest('app/styles'));

    gulp.src('app/*.html')
        .pipe(wiredep({
            directory: 'bower_components'
        }))
        .pipe(gulp.dest('app'));
});

gulp.task('watch', ['connect', 'serve'], function () {
    var server = $.livereload();

    // watch for changes
    gulp.watch([
        'app/*.html',
        '.tmp/styles/**/*.css',
        'app/scripts/**/*.js',
        'app/images/**/*'
    ]).on('change', function (file) {
        server.changed(file.path);
    });

    gulp.watch('app/styles/**/*.scss', ['styles']);
    gulp.watch('app/images/**/*', ['images']);
    gulp.watch('bower.json', ['wiredep']);
});<commit_msg>auto compile coffee<commit_after>'use strict';
// generated on 2016-10-03 using generator-ratchet 0.1.1

var gulp = require('gulp');

// load plugins
var $ = require('gulp-load-plugins')();

gulp.task('styles', function () {
    return gulp.src('app/styles/main.scss')
        .pipe($.rubySass({
            style: 'expanded',
            precision: 10
        }))
        .pipe($.autoprefixer('last 1 version'))
        .pipe(gulp.dest('.tmp/styles'))
        .pipe($.size());
});

gulp.task('scripts', () => {
  return gulp.src('app/scripts/**/*.coffee')
    .pipe($.coffee())
    .pipe(gulp.dest('.tmp/scripts'));
});

gulp.task('jshint', function () {
    return gulp.src('app/scripts/**/*.js')
        .pipe($.jshint())
        .pipe($.jshint.reporter('jshint-stylish'))
        .pipe($.jshint.reporter('fail'))
        .pipe($.size());
});

gulp.task('html', ['styles', 'scripts'], function () {
    var jsFilter = $.filter('**/*.js');
    var cssFilter = $.filter('**/*.css');

    return gulp.src('app/*.html')
        .pipe($.useref.assets({searchPath: '{.tmp,app}'}))
        .pipe(jsFilter)
        .pipe($.uglify())
        .pipe(jsFilter.restore())
        .pipe(cssFilter)
        .pipe($.csso())
        .pipe(cssFilter.restore())
        .pipe($.useref.restore())
        .pipe($.useref())
        .pipe(gulp.dest('dist'))
        .pipe($.size());
});

gulp.task('images', function () {
    return gulp.src('app/images/**/*')
        .pipe($.cache($.imagemin({
            optimizationLevel: 3,
            progressive: true,
            interlaced: true
        })))
        .pipe(gulp.dest('dist/images'))
        .pipe($.size());
});

gulp.task('fonts', function () {
    var streamqueue = require('streamqueue');
    return streamqueue({objectMode: true},
        $.bowerFiles(),
        gulp.src('app/fonts/**/*')
    )
        .pipe($.filter('**/*.{eot,svg,ttf,woff}'))
        .pipe($.flatten())
        .pipe(gulp.dest('dist/fonts'))
        .pipe($.size());
});

gulp.task('extras', function () {
    return gulp.src(['app/*.*', '!app/*.html'], { dot: true })
        .pipe(gulp.dest('dist'));
});

gulp.task('clean', function () {
    return gulp.src(['.tmp', 'dist'], { read: false }).pipe($.clean());
});

gulp.task('build', ['jshint', 'html', 'images', 'fonts', 'extras']);

gulp.task('default', ['clean'], function () {
    gulp.start('build');
});

gulp.task('connect', function () {
    var connect = require('connect');
    var app = connect()
        .use(require('connect-livereload')({ port: 35729 }))
        .use(connect.static('app'))
        .use(connect.static('.tmp'))
        // paths to bower_components should be relative to the current file
        // e.g. in app/index.html you should use ../bower_components
        .use('/bower_components', connect.static('bower_components'))
        .use(connect.directory('app'));

    require('http').createServer(app)
        .listen(9000)
        .on('listening', function () {
            console.log('Started connect web server on http://localhost:9000');
        });
});

gulp.task('serve', ['connect', 'styles', 'scripts'], function () {
    require('opn')('http://localhost:9000');
});

// inject bower components
gulp.task('wiredep', function () {
    var wiredep = require('wiredep').stream;

    gulp.src('app/styles/*.scss')
        .pipe(wiredep({
            directory: 'bower_components'
        }))
        .pipe(gulp.dest('app/styles'));

    gulp.src('app/*.html')
        .pipe(wiredep({
            directory: 'bower_components'
        }))
        .pipe(gulp.dest('app'));
});

gulp.task('watch', ['connect', 'serve'], function () {
    var server = $.livereload();

    // watch for changes
    gulp.watch([
        'app/*.html',
        '.tmp/styles/**/*.css',
        '.tmp/scripts/**/*.js',
        'app/scripts/**/*.js',
        'app/images/**/*'
    ]).on('change', function (file) {
        server.changed(file.path);
    });

    gulp.watch('app/styles/**/*.scss', ['styles']);
    gulp.watch('app/scripts/**/*.coffee', ['scripts']);
    gulp.watch('app/images/**/*', ['images']);
    gulp.watch('bower.json', ['wiredep']);
});<|endoftext|>


<commit_before>/**
 * @author Serge Babayan
 * @module views/ThrottleView
 * @requires models/Commands
 * @requires models/TelemetryData
 * @requires util/Template
 * @requires util/Logger
 * @requires util/Validator
 * @requires electron
 * @listens models/TelemetryData~TelemetryData:data_received
 * @copyright Waterloo Aerial Robotics Group 2016
 * @licence https://raw.githubusercontent.com/UWARG/WARG-Ground-Station/master/LICENSE
 * @description View that displays the aircraft's throttle, flap, and attitude rate values
 */

var remote = require('electron').remote;
var Logger = remote.require('./app/util/Logger');
var TelemetryData = remote.require('./app/models/TelemetryData');
var Validator = require('../util/Validator');
var Commands = remote.require('./app/models/Commands');
var Template = require('../util/Template');

module.exports = function (Marionette) {

  return Marionette.ItemView.extend({
    template: Template('ThrottleView'),
    className: 'throttleView',

    ui: {
      throttle: '.throttle-value',
      yaw: '.yaw-value',
      yaw_setpoint: '.yaw-setpoint-value',
      flap: '.flap-value',
      roll_rate_rad: '.roll-rate-value-rad',
      pitch_rate_rad: '.pitch-rate-value-rad',
      yaw_rate_rad: '.yaw-rate-value-rad',
      roll_rate_deg: '.roll-rate-value-deg',
      pitch_rate_deg: '.pitch-rate-value-deg',
      yaw_rate_deg: '.yaw-rate-value-deg',
      throttle_input: '.throttle-form input',
      flap_input: '.flap-form input'
    },
    events: {
      'submit .flap-form': 'sendSetFlapCommand',
      'submit .throttle-form': 'sendSetThrottleCommand'
    },
    initialize: function () {
      this.on_telemetry_callback = null;
    },
    onRender: function () {
      this.on_telemetry_callback = this.onTelemetryCallback.bind(this);
      TelemetryData.addListener('data_received', this.on_telemetry_callback);
    },
    onTelemetryCallback: function (data) {
      this.setThrottle(data.throttle_setpoint);
      this.setYaw(data.yaw);
      //this.setYawSetpoint(data.int_yaw_setpoint);
      this.setFlap(data.flap_setpoint);

      this.setYawRate(data.yaw_rate);
      this.setRollRate(data.roll_rate);
      this.setPitchRate(data.pitch_rate);
    },
    onBeforeDestroy: function () {
      TelemetryData.removeListener('data_received', this.on_telemetry_callback);
    },
    setThrottle: function (num) {
      if (Validator.isValidThrottle(num)) {
        var num = Number(num);
        this.ui.throttle.text(Number((num + 1024) * 100 / 2048).toFixed(1));//-1024 represents 0%
      } else {
        Logger.warn('Invalid throttle setpoint value received! Throttle:' + num);
        this.ui.throttle.text('Invalid');
      }
    },
    setYaw: function (num) {
      if (Validator.isValidYaw(num)) {
        this.ui.yaw.text(Number(num).toFixed(1));
      } else {
        Logger.warn('Invalid yaw value received! Yaw:' + num);
        this.ui.yaw.text('Invalid');
      }
    },
    // setYawSetpoint: function(num){
    //   if(Validator.isValidYaw(num)){
    //     this.ui.yaw_setpoint.text(Number(num).toFixed(1));
    //   }else{
    //     Logger.warn('Invalid yaw setpoint value received! Yaw Setpoint: '+num);
    //     this.ui.yaw_setpoint.text('Invalid');
    //   }
    // },
    setPitchRate: function (num) {
      if (Validator.isValidNumber(num)) {
        var rad = Number(num);
        var degrees = Number(num) * 180 / Math.PI;
        this.ui.pitch_rate_rad.text(rad.toFixed(1));
        this.ui.pitch_rate_deg.text(degrees.toFixed(1));
      } else {
        Logger.warn('Invalid pitch rate value received!  Pitch Rate: ' + num);
        this.ui.pitch_rate_rad.text('Invalid');
        this.ui.pitch_rate_deg.text('Invalid');
      }
    },
    setYawRate: function (num) {
      if (Validator.isValidNumber(num)) {
        var rad = Number(num);
        var degrees = Number(num) * 180 / Math.PI;
        this.ui.yaw_rate_rad.text(rad.toFixed(1));
        this.ui.yaw_rate_deg.text(degrees.toFixed(1));
      } else {
        Logger.warn('Invalid yaw rate value received!  Yaw Rate: ' + num);
        this.ui.yaw_rate_rad.text('Invalid');
        this.ui.yaw_rate_deg.text('Invalid');
      }
    },
    setRollRate: function (num) {
      if (Validator.isValidNumber(num)) {
        var rad = Number(num);
        var degrees = Number(num) * 180 / Math.PI;
        this.ui.roll_rate_rad.text(rad.toFixed(1));
        this.ui.roll_rate_deg.text(degrees.toFixed(1));
      } else {
        Logger.warn('Invalid roll rate value received!  Roll Rate: ' + num);
        this.ui.roll_rate_rad.text('Invalid');
        this.ui.roll_rate_deg.text('Invalid');
      }
    },
    setFlap: function (num) {
      if (Validator.isValidFlap(num)) {
        var num = Number(num);
        this.ui.flap.text(Number((num + 1024) * 100 / 2048).toFixed(1));//-1024 represents 0%
      } else {
        Logger.warn('Invalid flap setpoint value received! Flap Setpoint: ' + num);
        this.ui.flap.text('Invalid');
      }
    },
    sendSetThrottleCommand: function (e) {
      e.preventDefault();
      Commands.sendThrottle(this.ui.throttle_input.val());
      this.ui.throttle_input.val('');
    },
    sendSetFlapCommand: function (e) {
      e.preventDefault();
      Commands.sendFlap(this.ui.flap_input.val());
      this.ui.flap_input.val('');
    }
  });
};<commit_msg>Throttle view using new telemetry data format<commit_after>/**
 * @author Serge Babayan
 * @module views/ThrottleView
 * @requires models/Commands
 * @requires models/TelemetryData
 * @requires util/Template
 * @requires util/Logger
 * @requires util/Validator
 * @requires electron
 * @listens models/TelemetryData~TelemetryData:data_received
 * @copyright Waterloo Aerial Robotics Group 2016
 * @licence https://raw.githubusercontent.com/UWARG/WARG-Ground-Station/master/LICENSE
 * @description View that displays the aircraft's throttle, flap, and attitude rate values
 */

var remote = require('electron').remote;
var Logger = remote.require('./app/util/Logger');
var TelemetryData = remote.require('./app/models/TelemetryData');
var Validator = require('../util/Validator');
var Commands = remote.require('./app/models/Commands');
var Template = require('../util/Template');

module.exports = function (Marionette) {

  return Marionette.ItemView.extend({
    template: Template('ThrottleView'),
    className: 'throttleView',

    ui: {
      throttle: '.throttle-value',
      yaw: '.yaw-value',
      yaw_setpoint: '.yaw-setpoint-value',
      flap: '.flap-value',
      roll_rate_rad: '.roll-rate-value-rad',
      pitch_rate_rad: '.pitch-rate-value-rad',
      yaw_rate_rad: '.yaw-rate-value-rad',
      roll_rate_deg: '.roll-rate-value-deg',
      pitch_rate_deg: '.pitch-rate-value-deg',
      yaw_rate_deg: '.yaw-rate-value-deg',
      throttle_input: '.throttle-form input',
      flap_input: '.flap-form input'
    },
    events: {
      'submit .flap-form': 'sendSetFlapCommand',
      'submit .throttle-form': 'sendSetThrottleCommand'
    },
    initialize: function () {
      this.aircraft_position_callback = this.aircraftPositionCallback.bind(this);
      TelemetryData.addListener('aircraft_position', this.aircraft_position_callback);
    },

    aircraftPositionCallback: function(data){
      this.setThrottle(data.throttle_setpoint);
      this.setFlap(data.flap_setpoint);
    },

    onBeforeDestroy: function(){
      TelemetryData.removeListener('aircraft_position', this.aircraft_position_callback);
    },

    setThrottle: function (throttle) {
      if (throttle !== null) {
        this.ui.throttle.text(((throttle + 1024) * 100 / 2048).toFixed(1));//-1024 represents 0%
      }
    },

    setFlap: function (flap) {
      if (flap !== null) {
        this.ui.flap.text((flap + 1024) * 100 / 2048).toFixed(1);//-1024 represents 0%
      }
    },

    sendSetThrottleCommand: function (e) {
      e.preventDefault();
      Commands.sendThrottle(this.ui.throttle_input.val());
      this.ui.throttle_input.val('');
    },
    sendSetFlapCommand: function (e) {
      e.preventDefault();
      Commands.sendFlap(this.ui.flap_input.val());
      this.ui.flap_input.val('');
    }
  });
};<|endoftext|>


<commit_before>(function () {
  'use strict';

  angular.module('janusHangouts')
    .factory('Feed', ['$timeout', 'DataChannelService', feedFactory]);

  function feedFactory($timeout, DataChannelService) {
    return function(attrs) {
      attrs = attrs || {};
      var that = this;

      this.id = attrs.id || 0;
      this.display = attrs.display || null;
      this.pluginHandle = attrs.pluginHandle || null;
      this.stream = attrs.stream || null;
      this.isPublisher = attrs.isPublisher || false;
      this.isDataOpen = attrs.isDataOpen || false;
      this.isLocalScreen = attrs.isLocalScreen || false;

      this.audioEnabled = true;
      this.videoEnabled = true;
      this.speaking = false;

      this.setEnabledTrack = function(type, enabled) {
        var that = this;
        if (this.isPublisher) {
          // We need to use $timeout function just to let AngularJS know
          // about changes in the feed.
          $timeout(function() {
            var track = getTrack(type);
            track.enabled = enabled;
            that[type + "Enabled"] = enabled;
            DataChannelService.sendStatus(that);
          });
        } else {
          if (type === "audio" && enabled === false) {
            DataChannelService.sendMuteRequest(this);
          }
        }
      };

      this.setSpeaking = function(speaking) {
        var that = this;
        // We need to use $timeout function just to let AngularJS know
        // about changes in the feed.
        $timeout(function() {
          that.speaking = speaking;
          DataChannelService.sendStatus(that);
        });
      }

      this.getStatus = function() {
        return {
          audioEnabled: that.audioEnabled,
          videoEnabled: that.videoEnabled,
          speaking:     that.speaking,
          display:      that.display
        };
      }

      // Update local representation of the feed (used to process
      // information sent by the remote peer)
      this.setStatus = function(attrs) {
        // We need to use $timeout function just to let AngularJS know
        // about changes in the feed.
        var that = this;
        $timeout(function() {
          _.assign(that, attrs);
        });
      };

      this.hasAudio = function() {
        var that = this;
        return (getTrack('audio') !== null);
      }

      this.hasVideo = function() {
        var that = this;
        return (getTrack('video') !== null);
      }

      function getTrack(type) {
        if(that.stream === null || that.stream === undefined) {
          return null;
        }
        var func = "get" + _.capitalize(type) + "Tracks";
        if(that.stream[func]() === null || that.stream[func]() === undefined) {
          return null;
        }
        var track = that.stream[func]()[0];
        if(track === undefined) {
          return null;
        }
        return track;
      }
    };
  }
})();
<commit_msg>Sync feed.speaking and feed.audioEnabled<commit_after>(function () {
  'use strict';

  angular.module('janusHangouts')
    .factory('Feed', ['$timeout', 'DataChannelService', feedFactory]);

  function feedFactory($timeout, DataChannelService) {
    return function(attrs) {
      attrs = attrs || {};
      var that = this;

      this.id = attrs.id || 0;
      this.display = attrs.display || null;
      this.pluginHandle = attrs.pluginHandle || null;
      this.stream = attrs.stream || null;
      this.isPublisher = attrs.isPublisher || false;
      this.isDataOpen = attrs.isDataOpen || false;
      this.isLocalScreen = attrs.isLocalScreen || false;

      this.audioEnabled = true;
      this.videoEnabled = true;
      this.speaking = false;

      this.setEnabledTrack = function(type, enabled) {
        var that = this;
        if (this.isPublisher) {
          // We need to use $timeout function just to let AngularJS know
          // about changes in the feed.
          $timeout(function() {
            var track = getTrack(type);
            track.enabled = enabled;
            that[type + "Enabled"] = enabled;
            if (type === "audio" && enabled === false) {
              that.speaking = false;
            }
            DataChannelService.sendStatus(that);
          });
        } else {
          if (type === "audio" && enabled === false) {
            DataChannelService.sendMuteRequest(this);
          }
        }
      };

      this.setSpeaking = function(speaking) {
        var that = this;
        // We need to use $timeout function just to let AngularJS know
        // about changes in the feed.
        $timeout(function() {
          if (that.audioEnabled === false) {
            speaking = false;
          }
          if (that.speaking !== speaking) {
            that.speaking = speaking;
            DataChannelService.sendStatus(that);
          }
        });
      }

      this.getStatus = function() {
        return {
          audioEnabled: that.audioEnabled,
          videoEnabled: that.videoEnabled,
          speaking:     that.speaking,
          display:      that.display
        };
      }

      // Update local representation of the feed (used to process
      // information sent by the remote peer)
      this.setStatus = function(attrs) {
        // We need to use $timeout function just to let AngularJS know
        // about changes in the feed.
        var that = this;
        $timeout(function() {
          _.assign(that, attrs);
        });
      };

      this.hasAudio = function() {
        var that = this;
        return (getTrack('audio') !== null);
      }

      this.hasVideo = function() {
        var that = this;
        return (getTrack('video') !== null);
      }

      function getTrack(type) {
        if(that.stream === null || that.stream === undefined) {
          return null;
        }
        var func = "get" + _.capitalize(type) + "Tracks";
        if(that.stream[func]() === null || that.stream[func]() === undefined) {
          return null;
        }
        var track = that.stream[func]()[0];
        if(track === undefined) {
          return null;
        }
        return track;
      }
    };
  }
})();
<|endoftext|>


<commit_before>var ssr = require("../lib/");
var helpers = require("./helpers");
var assert = require("assert");
var path = require("path");
var Writable = require("stream").Writable;
var through = require("through2");
var noop = Function.prototype;
var chromeUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36";

describe("Incremental rendering", function(){
	this.timeout(10000);

	before(function(){
		this.oldXHR = global.XMLHttpRequest;
		global.XMLHttpRequest = helpers.mockXHR(
			'[ { "a": "a" }, { "b": "b" } ]');

		this.render = ssr({
			config: "file:" + path.join(__dirname, "tests", "package.json!npm"),
			main: "async/index.stache!done-autorender"
		}, {
			strategy: "incremental"
		});
	});

	after(function(){
		global.XMLHttpRequest = this.oldXHR;
	});

	it("Sends the correct rendering instructions", function(done){
		var request = {
			url: "/",
			headers: {
				"user-agent": chromeUA
			}
		};

		var response = through(noop);
		response.writeHead = noop;
		response.push = function(){
			var count = 0;
			return new Writable({
				write(chunk, enc, next) {
					var json = chunk.toString();
					var instrs = JSON.parse(json);

					if(++count === 1) {
						var instr = instrs[4];
						var route = instr.route;
						assert.equal(route, "0.2.5");
					} else {
						// TODO test this one maybe
						done();
					}
					next();
				}
			});
		};

		this.render(request).pipe(response);
	});
});
<commit_msg>Fix the incremental render test<commit_after>var ssr = require("../lib/");
var helpers = require("./helpers");
var assert = require("assert");
var path = require("path");
var Writable = require("stream").Writable;
var through = require("through2");
var noop = Function.prototype;
var chromeUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36";

describe("Incremental rendering", function(){
	this.timeout(10000);

	before(function(){
		this.oldXHR = global.XMLHttpRequest;
		global.XMLHttpRequest = helpers.mockXHR(
			'[ { "a": "a" }, { "b": "b" } ]');

		this.render = ssr({
			config: "file:" + path.join(__dirname, "tests", "package.json!npm"),
			main: "async/index.stache!done-autorender"
		}, {
			strategy: "incremental"
		});
	});

	after(function(){
		global.XMLHttpRequest = this.oldXHR;
	});

	it("Sends the correct rendering instructions", function(done){
		var request = {
			url: "/",
			headers: {
				"user-agent": chromeUA
			}
		};

		var response = through(noop);
		response.writeHead = noop;
		response.push = function(){
			var count = 0;
			return new Writable({
				write(chunk, enc, next) {
					var json = chunk.toString();
					var instrs = JSON.parse(json);

					if(++count === 1) {
						var instr = instrs[0];
						var route = instr.route;
						assert.equal(route, "0.2.5");
					} else {
						// TODO test this one maybe
						done();
					}
					next();
				}
			});
		};

		this.render(request).pipe(response);
	});
});
<|endoftext|>


<commit_before>var fs = require('fs');
var version = "0.0.0";

fs.readFile("package.json", "utf-8", readPackageJson);
function readPackageJson(err, data) {
	if (err) {
		console.log("index: Could not read package.json");
		return;
	}
	var packageJson = JSON.parse(data);
	version = packageJson.version;
}

exports.index = function renderIndex(req, res){
	var params = {
		host: req.get('Host'),
		version: version
	};
	res.render('index', params);
};<commit_msg>Index: Use relative path to read package.json<commit_after>var fs = require('fs');
var path = require('path');
var version = "0.0.0";

var packagePath = path.join(__dirname, "..", "package.json");
fs.readFile(packagePath, "utf-8", readPackageJson);

function readPackageJson(err, data) {
	if (err) {
		console.log("index: Could not read package.json at: " + packagePath);
		return;
	}
	var packageJson = JSON.parse(data);
	version = packageJson.version;
}

exports.index = function renderIndex(req, res){
	var params = {
		host: req.get('Host'),
		version: version
	};
	res.render('index', params);
};<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var extend = require('util')._extend;
var util = require('../util');
var url = require('url');
var mime = require('mime');
var querystring = require('querystring');
var PassThrough = require('stream').PassThrough;
var protoMgr = require('../rules/protocols');
var CRLF_RE = /\r\n|\r|\n/g;
var RAW_FILE_RE = /rawfile/;
var HEADERS_SEP_RE = /(\r?\n(?:\r\n|\r|\n)|\r\r\n?)/;
var MAX_HEADERS_SIZE = 256 * 1024;
var TPL_RE = /(?:dust|tpl|jsonp):$/;

function isTplProtocol(protocol) {
	return TPL_RE.test(protocol);
}

function isRawFileProtocol(protocol) {
	return RAW_FILE_RE.test(protocol);
}

function readFiles(files, callback) {
	var file = files.shift();
	var execCallback = function(err, stat) {
		 if(!err && stat && stat.isFile()) {
			 callback(null, file);
		 } else if (files.length) {
			 readFiles(files, callback);
		 }  else {
			 callback(err || new Error('Not found file ' + file), file);
		 }
	};

	!file || typeof file != 'string' ? execCallback() : fs.stat(file, execCallback);
}

function parseRes(str) {
	if (!str) {
		return {
			statusCode: 200,
			headers: {}
		};
	}
	var headers = str.split(CRLF_RE);
	var statusLine = headers.shift().trim().split(/\s+/g);
	return {
		statusCode: statusLine[1] || 200,
		headers: util.parseHeaders(headers)
	};
}

function getRawResByValue(body) {
	var headers;
	if (HEADERS_SEP_RE.test(body)) {
		var crlfStr = RegExp.$1;
		var index = body.indexOf(crlfStr);
		headers = body.substring(0, index);
		body = body.substring(index + crlfStr.length);
	}
	var reader = parseRes(headers);
	reader.body = body;
	return reader;
}

function getRawResByPath(protocol, path, callback) {
	var reader = fs.createReadStream(path);
	if (isRawFileProtocol(protocol)) {
		var buffer;
		var response = function(err, crlf) {
			reader.removeAllListeners('data');
			reader.removeAllListeners('error');
			reader.removeAllListeners('end');
			var stream = reader;
			reader = new PassThrough();
			if (err) {
				reader.statusCode = 500;
				reader.push(err.stack);
				reader.push(null);
			} else {
				if (crlf) {
					crlf = new Buffer(crlf);
					var index = util.indexOfList(buffer, crlf);
					if (index != -1) {
						extend(reader, parseRes(buffer.slice(0, index) + ''));
						buffer = buffer.slice(index + crlf.length);
					}
				}
				buffer && reader.push(buffer);
				stream.on('error', function(err) {
					reader.emit('error', err);
				});
				stream.pipe(reader);
			}
			callback(reader);
		};

		reader.on('data', function(data) {
			buffer = buffer ? Buffer.concat([buffer, data]) : data;
			if (HEADERS_SEP_RE.test(buffer + '')) {
				response(null, RegExp.$1);
			} else if (buffer.length > MAX_HEADERS_SIZE) {
				response();
			}
		});
		reader.on('error', response);
		reader.on('end', response);
	} else {
		callback(reader);
	}
}

module.exports = function(req, res, next) {
	var options = req.options;
	var config = this.config;
	var protocol = options && options.protocol;
	if (!protoMgr.isFileProxy(protocol)) {
		return next();
	}
	var defaultType = mime.lookup(req.fullUrl.replace(/[?#].*$/, ''), 'text/html');
	var rule = req.rules.rule;
	if (rule.value) {
		var body = util.removeProtocol(rule.value, true);
		var reader = isRawFileProtocol(protocol) ? getRawResByValue(body) : {
				statusCode: 200,
				body: body,
				headers: {
				    	'content-type': (rule.key ? mime.lookup(rule.key, defaultType) : defaultType) + '; charset=utf-8'
				    }
		};

	    if (isTplProtocol(protocol)){
	    	reader.realUrl = rule.matcher;
	    	render(reader);
	    } else {
	    	reader = util.wrapResponse(reader);
	    	reader.realUrl = rule.matcher;
	    	res.response(reader);
	    }
		return;
	}

	readFiles(util.getRuleFiles(rule), function(err, path) {
		if (err) {
	    	if (/^x/.test(protocol)) {
				  extend(options, url.parse(/^xs/.test(protocol) ? req.fullUrl.replace(/^http:/, 'https:') : req.fullUrl));
				  next();
			  } else {
				  var notFound = util.wrapResponse({
						statusCode: 404,
						body: 'Not found file ' + path + '',
						headers: {
						    	'content-type': 'text/html; charset=utf-8'
						    }
				  });
				  notFound.realUrl = path;
				  res.response(notFound);
			  }
	    	return;
	    }

		var headers = {
	    		'server': config.name,
		    	'content-type': mime.lookup(path, defaultType) + '; charset=utf-8'
		    };

	    if (isTplProtocol(protocol)) {
	    	var reader = {
	    			statusCode: 200,
	    			realUrl: path,
	    			headers: headers
	    	};
	    	fs.readFile(path, {encoding: 'utf8'}, function(err, data) {
	    		if (err) {
	    			return util.emitError(req, err);
	    		}
	    		reader.body = data;
	    		render(reader);
	    	});
	    } else {
	    	getRawResByPath(protocol, path, function(reader) {
	    		reader.realUrl = path;
	    		reader.statusCode = reader.statusCode || 200;
	    		reader.headers = reader.headers || headers;
	    		res.response(reader);
	    	});
	    }
	});

	function render(reader) {
		if (reader.body) {
			var data = querystring.parse(util.getQueryString(req.fullUrl));
			if (!util.isEmptyObject(data)) {
				reader.body = reader.body.replace(/\{([\w\-$]+)\}/g, function(all, matched) {
					return matched in data ? data[matched] : all;
				});
			}
		}
		var realUrl = reader.realUrl;
		reader = util.wrapResponse(reader);
		reader.realUrl = realUrl;
		res.response(reader);
	}
};<commit_msg>refactor: fix eslint<commit_after>var fs = require('fs');
var extend = require('util')._extend;
var util = require('../util');
var url = require('url');
var mime = require('mime');
var querystring = require('querystring');
var PassThrough = require('stream').PassThrough;
var protoMgr = require('../rules/protocols');
var CRLF_RE = /\r\n|\r|\n/g;
var RAW_FILE_RE = /rawfile/;
var HEADERS_SEP_RE = /(\r?\n(?:\r\n|\r|\n)|\r\r\n?)/;
var MAX_HEADERS_SIZE = 256 * 1024;
var TPL_RE = /(?:dust|tpl|jsonp):$/;

function isTplProtocol(protocol) {
	return TPL_RE.test(protocol);
}

function isRawFileProtocol(protocol) {
	return RAW_FILE_RE.test(protocol);
}

function readFiles(files, callback) {
	var file = files.shift();
	var execCallback = function(err, stat) {
		 if(!err && stat && stat.isFile()) {
			 callback(null, file);
		 } else if (files.length) {
			 readFiles(files, callback);
		 }  else {
			 callback(err || new Error('Not found file ' + file), file);
		 }
	};

	!file || typeof file != 'string' ? execCallback() : fs.stat(file, execCallback);
}

function parseRes(str) {
	if (!str) {
		return {
			statusCode: 200,
			headers: {}
		};
	}
	var headers = str.split(CRLF_RE);
	var statusLine = headers.shift().trim().split(/\s+/g);
	return {
		statusCode: statusLine[1] || 200,
		headers: util.parseHeaders(headers)
	};
}

function getRawResByValue(body) {
	var headers;
	if (HEADERS_SEP_RE.test(body)) {
		var crlfStr = RegExp.$1;
		var index = body.indexOf(crlfStr);
		headers = body.substring(0, index);
		body = body.substring(index + crlfStr.length);
	}
	var reader = parseRes(headers);
	reader.body = body;
	return reader;
}

function getRawResByPath(protocol, path, callback) {
	var reader = fs.createReadStream(path);
	if (isRawFileProtocol(protocol)) {
		var buffer;
		var response = function(err, crlf) {
			reader.removeAllListeners('data');
			reader.removeAllListeners('error');
			reader.removeAllListeners('end');
			var stream = reader;
			reader = new PassThrough();
			if (err) {
				reader.statusCode = 500;
				reader.push(err.stack);
				reader.push(null);
			} else {
				if (crlf) {
					crlf = new Buffer(crlf);
					var index = util.indexOfList(buffer, crlf);
					if (index != -1) {
						extend(reader, parseRes(buffer.slice(0, index) + ''));
						buffer = buffer.slice(index + crlf.length);
					}
				}
				buffer && reader.push(buffer);
				stream.on('error', function(err) {
					reader.emit('error', err);
				});
				stream.pipe(reader);
			}
			callback(reader);
		};

		reader.on('data', function(data) {
			buffer = buffer ? Buffer.concat([buffer, data]) : data;
			if (HEADERS_SEP_RE.test(buffer + '')) {
				response(null, RegExp.$1);
			} else if (buffer.length > MAX_HEADERS_SIZE) {
				response();
			}
		});
		reader.on('error', response);
		reader.on('end', response);
	} else {
		callback(reader);
	}
}

module.exports = function(req, res, next) {
	var options = req.options;
	var config = this.config;
	var protocol = options && options.protocol;
	if (!protoMgr.isFileProxy(protocol)) {
		return next();
	}
	var defaultType = mime.lookup(req.fullUrl.replace(/[?#].*$/, ''), 'text/html');
	var rule = req.rules.rule;
	if (rule.value) {
		var body = util.removeProtocol(rule.value, true);
		var reader = isRawFileProtocol(protocol) ? getRawResByValue(body) : {
				statusCode: 200,
				body: body,
				headers: {
				    	'content-type': (rule.key ? mime.lookup(rule.key, defaultType) : defaultType) + '; charset=utf-8'
				    }
		};

	    if (isTplProtocol(protocol)){
	    	reader.realUrl = rule.matcher;
	    	render(reader);
	    } else {
	    	reader = util.wrapResponse(reader);
	    	reader.realUrl = rule.matcher;
	    	res.response(reader);
	    }
		return;
	}

	readFiles(util.getRuleFiles(rule), function(err, path) {
		if (err) {
	    	if (/^x/.test(protocol)) {
				  extend(options, url.parse(/^xs/.test(protocol) ? req.fullUrl.replace(/^http:/, 'https:') : req.fullUrl));
				  next();
			  } else {
				  var notFound = util.wrapResponse({
						statusCode: 404,
						body: 'Not found file ' + path + '',
						headers: {
						    	'content-type': 'text/html; charset=utf-8'
						    }
				  });
				  notFound.realUrl = path;
				  res.response(notFound);
			  }
	    	return;
	    }

		var headers = {
	    		'server': config.name,
		    	'content-type': mime.lookup(path, defaultType) + '; charset=utf-8'
		    };

	    if (isTplProtocol(protocol)) {
	    	var reader = {
	    			statusCode: 200,
	    			realUrl: path,
	    			headers: headers
	    	};
	    	fs.readFile(path, {encoding: 'utf8'}, function(err, data) {
	    		if (err) {
	    			return util.emitError(req, err);
	    		}
	    		reader.body = data;
	    		render(reader);
	    	});
	    } else {
	    	getRawResByPath(protocol, path, function(reader) {
	    		reader.realUrl = path;
	    		reader.statusCode = reader.statusCode || 200;
	    		reader.headers = reader.headers || headers;
	    		res.response(reader);
	    	});
	    }
	});

	function render(reader) {
		if (reader.body) {
			var data = querystring.parse(util.getQueryString(req.fullUrl));
			if (!util.isEmptyObject(data)) {
				reader.body = reader.body.replace(/\{([\w\-$]+)\}/g, function(all, matched) {
					return matched in data ? data[matched] : all;
				});
			}
		}
		var realUrl = reader.realUrl;
		reader = util.wrapResponse(reader);
		reader.realUrl = realUrl;
		res.response(reader);
	}
};<|endoftext|>


<commit_before>import React, { Component } from 'react';
import Switch from 'react-toggle-switch';
import Issues from './components/issues';
import config from './config';
import './App.css';

class App extends Component {
  constructor() {
    super();

    const placeholderLabels =
    this.state = {
      totalCount: '',
      sortDesc: true,
      sortType: 'updated',
      issues: [],
      labelValues: 'help wanted, bug'
    };

    this.toggleSortType = this.toggleSortType.bind(this);
    this.toggleSortOrder = this.toggleSortOrder.bind(this);
    this.initiateAPICall = this.initiateAPICall.bind(this);
    this.handleLabelChange = this.handleLabelChange.bind(this);

    this.initiateAPICall(this.state.sortDesc, this.state.sortType, this.state.labelValues);
  }

  initiateAPICall(sortDesc, sortType, labelValues) {
    let myHeaders = new Headers({
      Authorization: 'token ' + config.apiToken
    });

    let myInit = {
      method: 'GET',
      headers: myHeaders,
      mode: 'cors'
    };

    let labelQuery = this.state.labelValues;
    if (labelValues) {
      let labels = labelValues.split(',');

      if (labels.length > 0) {
        labelQuery = '';
      }
      for (let label of labels) {
        labelQuery += 'label:"' + label.trim() + '"+';
      }

      //Remove last +
      labelQuery = labelQuery.slice(0, -1);
    }

    let resultsPerPage = 25;
    let sortOrder = sortDesc ? 'desc' : 'asc';
    let myRequest = new Request('https://api.github.com/search/issues?q=' + labelQuery + '&per_page='+ resultsPerPage +'&type=issue&state=open&page=1&sort=' + sortType + '&order=' + sortOrder);

    let self = this;
    fetch(myRequest, myInit).then(function (response) {
      return response.json();
    }).then(function (data) {
      //console.log(data);
      self.setState({totalCount: data.total_count, issues: data.items, sortDesc: sortDesc, sortType: sortType, labelValues: labelValues});
    });
  }

  render() {
    return (

Open Source Help Wanted

Find issues you can work on in Github. Be a contributor!

Displaying {this.state.issues.length} of {this.state.totalCount} issues.

                Github label names
                <input className="input-element labelSearch" type="text" placeholder="help wanted, bug" onKeyPress={this.handleLabelChange}/>

              Sorting Options

                  sort by created time
                  <Switch on={this.state.sortType === 'updated'} onClick={this.toggleSortType}/>
                  sorty by updated time

                  Oldest first
                  <Switch on={this.state.sortDesc} onClick={this.toggleSortOrder}/>
                  Newest first

            <Issues data={this.state.issues}/>

Fork me on Github!

    );

  }

  toggleSortType() {
    let sortType = 'updated';
    if (this.state.sortType === 'updated') {
      sortType = 'created';
    }
    this.initiateAPICall(this.state.sortDesc, sortType, this.state.labelValues);
  }

  toggleSortOrder() {
    this.initiateAPICall(!this.state.sortDesc, this.state.sortType,  this.state.labelValues);
  }

  handleLabelChange(event) {
    if (event.key === 'Enter') {
      console.log('tagetvalue: ' +  event.target.value);
      this.initiateAPICall(this.state.sortDesc, this.state.sortType, (event.target.value ? event.target.value : ' '));
    }
  }

}

export default App;
<commit_msg>Reform file slightly.<commit_after>import React, {Component} from 'react';
import Switch from 'react-toggle-switch';
import Issues from './components/issues';
import config from './config';
import './App.css';

class App extends Component {
  constructor() {
    super();

    const placeholderLabels =
      this.state = {
        totalCount: '',
        sortDesc: true,
        sortType: 'updated',
        issues: [],
        labelValues: 'help wanted, bug'
      };

    this.toggleSortType = this.toggleSortType.bind(this);
    this.toggleSortOrder = this.toggleSortOrder.bind(this);
    this.initiateAPICall = this.initiateAPICall.bind(this);
    this.handleLabelChange = this.handleLabelChange.bind(this);

    this.initiateAPICall(this.state.sortDesc, this.state.sortType, this.state.labelValues);
  }

  initiateAPICall(sortDesc, sortType, labelValues) {
    let myHeaders = new Headers({
      Authorization: 'token ' + config.apiToken
    });

    let myInit = {
      method: 'GET',
      headers: myHeaders,
      mode: 'cors'
    };

    let labelQuery = this.state.labelValues;
    if (labelValues) {
      let labels = labelValues.split(',');

      if (labels.length > 0) {
        labelQuery = '';
      }
      for (let label of labels) {
        labelQuery += 'label:"' + label.trim() + '"+';
      }

      //Remove last +
      labelQuery = labelQuery.slice(0, -1);
    }

    let resultsPerPage = 25;
    let sortOrder = sortDesc ? 'desc' : 'asc';
    let myRequest = new Request('https://api.github.com/search/issues?q=' + labelQuery + '&per_page=' + resultsPerPage + '&type=issue&state=open&page=1&sort=' + sortType + '&order=' + sortOrder);

    let self = this;
    fetch(myRequest, myInit).then(function (response) {
      return response.json();
    }).then(function (data) {
      //console.log(data);
      self.setState({
        totalCount: data.total_count,
        issues: data.items,
        sortDesc: sortDesc,
        sortType: sortType,
        labelValues: labelValues
      });
    });
  }

  render() {
    return (

Open Source Help Wanted

Find issues you can work on in Github. Be a contributor!

Displaying {this.state.issues.length} of {this.state.totalCount} issues.

                Github label names
                <input className="input-element labelSearch" type="text" placeholder="help wanted, bug"
                       onKeyPress={this.handleLabelChange}/>

              Sorting Options

                  sort by created time
                  <Switch on={this.state.sortType === 'updated'} onClick={this.toggleSortType}/>
                  sorty by updated time

                  Oldest first
                  <Switch on={this.state.sortDesc} onClick={this.toggleSortOrder}/>
                  Newest first

            <Issues data={this.state.issues}/>

Fork me on Github!

    );

  }

  toggleSortType() {
    let sortType = 'updated';
    if (this.state.sortType === 'updated') {
      sortType = 'created';
    }
    this.initiateAPICall(this.state.sortDesc, sortType, this.state.labelValues);
  }

  toggleSortOrder() {
    this.initiateAPICall(!this.state.sortDesc, this.state.sortType, this.state.labelValues);
  }

  handleLabelChange(event) {
    if (event.key === 'Enter') {
      console.log('tagetvalue: ' + event.target.value);
      this.initiateAPICall(this.state.sortDesc, this.state.sortType, (event.target.value ? event.target.value : ' '));
    }
  }

}

export default App;
<|endoftext|>


<commit_before>(function() {
  'use strict';

  angular.module('dimApp')
    .factory('dimFarmingService', FarmingService);

  FarmingService.$inject = ['$rootScope', '$q', 'dimItemService', 'dimStoreService', '$interval', 'dimCategory', 'toaster', 'dimBucketService'];

  /**
   * A service for "farming" items by moving them continuously off a character,
   * so that they don't go to the Postmaster.
   */
  function FarmingService($rootScope, $q, dimItemService, dimStoreService, $interval, dimCategory, toaster, dimBucketService) {
    var intervalId;
    var cancelReloadListener;
    var glimmerHashes = [
      269776572, // -house-banners
      3632619276, // -silken-codex
      2904517731, // -axiomatic-beads
      1932910919 // -network-keys
    ];
    return {
      active: false,
      store: null,
      itemsMoved: 0,
      movingItems: false,
      makingRoom: false,
      // Move all items on the selected character to the vault.
      moveItemsToVault: function(items, incrementCounter) {
        var self = this;
        var nospace = "No space left!";
        return dimBucketService
          .then(function(buckets) {
            return _.reduce(items, function(promise, item) {
              return promise
                .then(function() {
                  var vault = dimStoreService.getVault();
                  var vaultSpaceLeft = vault.spaceLeftForItem(item);
                  if (vaultSpaceLeft <= 1) {
                    // If we're down to one space, try putting it on other characters
                    var otherStores = _.select(dimStoreService.getStores(), function(store) {
                      return !store.isVault && store.id !== self.store.id;
                    });

                    var otherStoresWithSpace = _.select(otherStores, function(store) {
                      return store.spaceLeftForItem(item) > 0;
                    });
                    if (otherStoresWithSpace.length) {
                      return dimItemService.moveTo(item, otherStoresWithSpace[0], false, item.amount, items);
                    } else if (vaultSpaceLeft === 0) {
                      // If there's no room on other characters to move out of the vault,
                      // give up entirely.
                      if (!_.any(otherStores, function(store) {
                        return _.any(dimCategory[item.bucket.sort], function(category) {
                          return store.spaceLeftForItem({ type: category, location: buckets.byType[category], bucket: buckets.byType[category] }) > 0;
                        });
                      })) {
                        return $q.reject(new Error(nospace));
                      }
                    }
                  }
                  return dimItemService.moveTo(item, vault, false, item.amount, items);
                })
                .then(function() {
                  if (incrementCounter) {
                    self.itemsMoved++;
                  }
                })
                .catch(function(e) {
                  // No need to whine about being out of space
                  if (e.message !== nospace) {
                    toaster.pop('error', item.name, e.message);
                  }
                });
            }, $q.resolve());
          });
      },
      farmItems: function() {
        var self = this;
        var store = dimStoreService.getStore(self.store.id);
        var toMove = _.select(store.items, function(i) {
          return !i.location.inPostmaster && (
            (i.tier === 'Uncommon' && i.type === 'ClassItem') || // TODO: remove this once bug is fixed in game.
            i.isEngram() ||
            glimmerHashes.includes(i.hash));
        });

        if (toMove.length === 0) {
          return $q.resolve();
        }

        self.movingItems = true;
        return self.moveItemsToVault(toMove, true)
          .finally(function() {
            self.movingItems = false;
          });
      },
      // Ensure that there's one open space in each category that could
      // hold an engram, so they don't go to the postmaster.
      makeRoomForItems: function() {
        var self = this;

        var store = dimStoreService.getStore(self.store.id);
        // TODO: this'll be easier with buckets
        // These types can have engrams
        var engramTypes = ['Primary',
                          'Special',
                          'Heavy',
                          'Helmet',
                          'Gauntlets',
                          'Chest',
                          'Leg',
                          'ClassItem',
                          'Ghost',
                          'Consumables',
                          'Materials'];

        var applicableItems = _.select(store.items, function(i) {
          return !i.equipped &&
            !i.location.inPostmaster &&
            _.contains(engramTypes, i.type);
        });
        var itemsByType = _.groupBy(applicableItems, 'type');

        // If any category is full, we'll move one aside
        var itemsToMove = [];
        _.each(itemsByType, function(items) {
          // subtract 1 from capacity because we excluded the equipped item
          // if we are at capacity, but there are any Uncommon items in the mix. don't move anything.
          if (items.length > 0 && items.length >= (store.capacityForItem(items[0]) - 1) && !_.pluck(items, 'tier').includes('Uncommon')) {
            // We'll move the lowest-value item to the vault.
            itemsToMove.push(_.min(_.select(items, { notransfer: false }), function(i) {
              var value = {
                Common: 0,
                Uncommon: 1,
                Rare: 2,
                Legendary: 3,
                Exotic: 4
              }[i.tier];
              // And low-stat
              if (i.primStat) {
                value += i.primStat.value / 1000.0;
              }
              return value;
            }));
          }
        });

        if (itemsToMove.length === 0) {
          return $q.resolve();
        }

        self.makingRoom = true;
        return self.moveItemsToVault(itemsToMove, false)
          .finally(function() {
            self.makingRoom = false;
          });
      },
      start: function(store) {
        var self = this;
        function farm() {
          var consolidateHashes = [
            417308266, // three of coins
            211861343, // heavy ammo synth
            937555249, // motes of light
            1738186005, // motes of light
//            1542293174, // armor materials
//            1898539128, // weapon parts
          ];

          self.consolidate = consolidateHashes.map(function(hash) {
            var ret = angular.copy(dimItemService.getItem({
              hash: hash
            }));
            ret.amount = 0;
            dimStoreService.getStores().forEach(function(s) {
              ret.amount += s.amountOfItem(ret);
            });
            return ret;
          });

          self.farmItems().then(function() {
            self.makeRoomForItems();
          });
        }

        if (!this.active) {
          this.active = true;
          this.store = store;
          this.itemsMoved = 0;
          this.movingItems = false;
          this.makingRoom = false;

          // Whenever the store is reloaded, run the farming algo
          // That way folks can reload manually too
          cancelReloadListener = $rootScope.$on('dim-stores-updated', function() {
            // prevent some recursion...
            if (self.active && !self.movingItems && !self.makingRoom) {
              farm();
            }
          });
          intervalId = $interval(function() {
            // just start reloading stores more often
            dimStoreService.reloadStores();
          }, 60000);
          farm();
        }
      },
      stop: function() {
        if (intervalId) {
          $interval.cancel(intervalId);
        }
        if (cancelReloadListener) {
          cancelReloadListener();
        }
        this.active = false;
      }
    };
  }
})();
<commit_msg>fix for farming mode<commit_after>(function() {
  'use strict';

  angular.module('dimApp')
    .factory('dimFarmingService', FarmingService);

  FarmingService.$inject = ['$rootScope', '$q', 'dimItemService', 'dimStoreService', '$interval', 'dimCategory', 'toaster', 'dimBucketService'];

  /**
   * A service for "farming" items by moving them continuously off a character,
   * so that they don't go to the Postmaster.
   */
  function FarmingService($rootScope, $q, dimItemService, dimStoreService, $interval, dimCategory, toaster, dimBucketService) {
    var intervalId;
    var cancelReloadListener;
    var glimmerHashes = [
      269776572, // -house-banners
      3632619276, // -silken-codex
      2904517731, // -axiomatic-beads
      1932910919 // -network-keys
    ];
    return {
      active: false,
      store: null,
      itemsMoved: 0,
      movingItems: false,
      makingRoom: false,
      // Move all items on the selected character to the vault.
      moveItemsToVault: function(items, incrementCounter) {
        var self = this;
        var nospace = "No space left!";
        return dimBucketService
          .then(function(buckets) {
            return _.reduce(items, function(promise, item) {
              return promise
                .then(function() {
                  var vault = dimStoreService.getVault();
                  var vaultSpaceLeft = vault.spaceLeftForItem(item);
                  if (vaultSpaceLeft <= 1) {
                    // If we're down to one space, try putting it on other characters
                    var otherStores = _.select(dimStoreService.getStores(), function(store) {
                      return !store.isVault && store.id !== self.store.id;
                    });

                    var otherStoresWithSpace = _.select(otherStores, function(store) {
                      return store.spaceLeftForItem(item) > 0;
                    });
                    if (otherStoresWithSpace.length) {
                      return dimItemService.moveTo(item, otherStoresWithSpace[0], false, item.amount, items);
                    } else if (vaultSpaceLeft === 0) {
                      // If there's no room on other characters to move out of the vault,
                      // give up entirely.
                      if (!_.any(otherStores, function(store) {
                        return _.any(dimCategory[item.bucket.sort], function(category) {
                          return store.spaceLeftForItem({ type: category, location: buckets.byType[category], bucket: buckets.byType[category] }) > 0;
                        });
                      })) {
                        return $q.reject(new Error(nospace));
                      }
                    }
                  }
                  return dimItemService.moveTo(item, vault, false, item.amount, items);
                })
                .then(function() {
                  if (incrementCounter) {
                    self.itemsMoved++;
                  }
                })
                .catch(function(e) {
                  // No need to whine about being out of space
                  if (e.message !== nospace) {
                    toaster.pop('error', item.name, e.message);
                  }
                });
            }, $q.resolve());
          });
      },
      farmItems: function() {
        var self = this;
        var store = dimStoreService.getStore(self.store.id);
        var toMove = _.select(store.items, function(i) {
          return !i.location.inPostmaster && (
            (i.tier === 'Uncommon' && i.type === 'ClassItem') || // TODO: remove this once bug is fixed in game.
            i.isEngram() ||
            glimmerHashes.includes(i.hash));
        });

        if (toMove.length === 0) {
          return $q.resolve();
        }

        self.movingItems = true;
        return self.moveItemsToVault(toMove, true)
          .finally(function() {
            self.movingItems = false;
          });
      },
      // Ensure that there's one open space in each category that could
      // hold an engram, so they don't go to the postmaster.
      makeRoomForItems: function() {
        var self = this;

        var store = dimStoreService.getStore(self.store.id);
        // TODO: this'll be easier with buckets
        // These types can have engrams
        var engramTypes = ['Primary',
                          'Special',
                          'Heavy',
                          'Helmet',
                          'Gauntlets',
                          'Chest',
                          'Leg',
                          'ClassItem',
                          'Ghost',
                          'Consumables',
                          'Materials'];

        var applicableItems = _.select(store.items, function(i) {
          return !i.equipped &&
            !i.location.inPostmaster &&
            _.contains(engramTypes, i.type);
        });
        var itemsByType = _.groupBy(applicableItems, 'type');

        // If any category is full, we'll move one aside
        var itemsToMove = [];
        _.each(itemsByType, function(items) {
          // subtract 1 from capacity because we excluded the equipped item
          // if we are at capacity, but there are any Uncommon items in the mix. don't move anything.
          if (items.length > 0 && items.length >= (store.capacityForItem(items[0]) - 1) && !_.pluck(items, 'tier').includes('Uncommon')) {
            // We'll move the lowest-value item to the vault.
            itemsToMove.push(_.min(_.select(items, { notransfer: false }), function(i) {
              var value = {
                Common: 0,
                Uncommon: 1,
                Rare: 2,
                Legendary: 3,
                Exotic: 4
              }[i.tier];
              // And low-stat
              if (i.primStat) {
                value += i.primStat.value / 1000.0;
              }
              return value;
            }));
          }
        });

        if (itemsToMove.length === 0) {
          return $q.resolve();
        }

        self.makingRoom = true;
        return self.moveItemsToVault(itemsToMove, false)
          .finally(function() {
            self.makingRoom = false;
          });
      },
      start: function(store) {
        var self = this;
        function farm() {
          var consolidateHashes = [
            417308266, // three of coins
            211861343, // heavy ammo synth
            937555249, // motes of light
            1738186005 // motes of light
//            1542293174, // armor materials
//            1898539128, // weapon parts
          ];

          self.consolidate = consolidateHashes.map(function(hash) {
            var ret = angular.copy(dimItemService.getItem({
              hash: hash
            }));
            if (ret !== null) {
              ret.amount = 0;
              dimStoreService.getStores().forEach(function(s) {
                ret.amount += s.amountOfItem(ret);
              });
            }
            return ret;
          });

          self.farmItems().then(function() {
            self.makeRoomForItems();
          });
        }

        if (!this.active) {
          this.active = true;
          this.store = store;
          this.itemsMoved = 0;
          this.movingItems = false;
          this.makingRoom = false;

          // Whenever the store is reloaded, run the farming algo
          // That way folks can reload manually too
          cancelReloadListener = $rootScope.$on('dim-stores-updated', function() {
            // prevent some recursion...
            if (self.active && !self.movingItems && !self.makingRoom) {
              farm();
            }
          });
          intervalId = $interval(function() {
            // just start reloading stores more often
            dimStoreService.reloadStores();
          }, 60000);
          farm();
        }
      },
      stop: function() {
        if (intervalId) {
          $interval.cancel(intervalId);
        }
        if (cancelReloadListener) {
          cancelReloadListener();
        }
        this.active = false;
      }
    };
  }
})();
<|endoftext|>