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

const {
  Cc, Ci
} = require('chrome');
const {
  Class
} = require('sdk/core/heritage');
const CP_NS = require('sdk/core/namespace').ns();
const {
  ensure
} = require('sdk/system/unload');
const {
  validateOptions
} = require('sdk/deprecated/api-utils');
const {
  id: ADDON_ID
} = require('sdk/self');
const xpcom = require('sdk/platform/xpcom');

const CM = Cc["@mozilla.org/categorymanager;1"]
  .getService(Ci.nsICategoryManager);

const ACCEPT = exports.ACCEPT = Ci.nsIContentPolicy.ACCEPT;
const REJECT = exports.REJECT = Ci.nsIContentPolicy.REJECT_REQUEST;

const accept = function () ACCEPT;

let ContentPolicy_ID = 0;

const RULES = {
  description: {
    map: function (v) {
      return v ? v : '';
    },
    is: ['string']
  },
  contract: {
    map: function (v) {
      if (v === undefined) {
        v = '@erikvold.com/content-policy.' + ADDON_ID + ';' + ContentPolicy_ID++;
      }
      return v;
    },
    is: ['string']
  },
  entry: {
    is: ['string', 'undefined']
  },
  shouldLoad: {
    is: ['function', 'undefined']
  },
  shouldProcess: {
    is: ['function', 'undefined']
  },
};

function getType(aType) {
  switch (aType) {
  case Ci.nsIContentPolicy.TYPE_SCRIPT:
    return 'script';
  case Ci.nsIContentPolicy.TYPE_IMAGE:
    return 'image';
  case Ci.nsIContentPolicy.TYPE_STYLESHEET:
    return 'stylesheet';
  case Ci.nsIContentPolicy.TYPE_OBJECT:
    return 'object';
  case Ci.nsIContentPolicy.TYPE_DOCUMENT:
    return 'document';
  case Ci.nsIContentPolicy.TYPE_SUBDOCUMENT:
    return 'subdocument';
  case Ci.nsIContentPolicy.TYPE_REFRESH:
    return 'refresh';
  case Ci.nsIContentPolicy.TYPE_XBL:
    return 'xbl';
  case Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST:
    return 'xhr';
  case Ci.nsIContentPolicy.TYPE_PING:
    return 'ping';
    // TODO: support more types
  }
  return 'other';
}
const getTypeMemod = memoize(getType, 12, 1);

function makeDetails(aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess) {
  return {
    type: getTypeMemod(aContentType),
    location: aContentLocation,
    origin: aRequestOrigin.spec,
    context: null, // TODO: support this in a safe way somehow..
    mimeTypeGuess: String(aMimeTypeGuess)
  };
}

let ContentPolicy = exports.ContentPolicy = Class({
  initialize: function (options) {
    const self = this;
    options = CP_NS(self).options = validateOptions(options, RULES);
    CP_NS(self).shouldLoad = options.shouldLoad || accept;
    CP_NS(self).shouldProcess = options.shouldProcess || accept;

    let factory = CP_NS(this).factory = xpcom.Factory({
      Component: getProvider(self),
      description: options.description,
      contract: options.contract
    });

    let entry = options.entry || options.contract;
    CM.addCategoryEntry('content-policy', entry, factory.contract, false, true);
    ensure(this, 'destroy');
  },
  destroy: function () {
    // already destroyed?
    if (!CP_NS(this).options)
      return;

    let options = CP_NS(this).options;
    CP_NS(this).options = null;
    CP_NS(this).shouldLoad = accept;
    CP_NS(this).shouldProcess = accept;

    CM.deleteCategoryEntry('content-policy', options.entry || options.contract, false);
  }
});

function getProvider(self) {
  return Class({
    extends: xpcom.Unknown,
    interfaces: ['nsIContentPolicy'],
    shouldLoad: function (aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aExtra) {
      let load = CP_NS(self).shouldLoad(makeDetails.apply(null, arguments));
      return (load == REJECT || (!load && load !== undefined)) ? REJECT : ACCEPT;
    },
    shouldProcess: function (aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aExtra) {
      let load = CP_NS(self).shouldProcess(makeDetails.apply(null, arguments));
      return (load == REJECT || (!load && load !== undefined)) ? REJECT : ACCEPT;
    }
  });
}

function memoize(func) {
  let cache = Object.create(null);
  return function (a) {
    let key = a.toString();
    if (key in cache)
      return cache[key];
    return cache[key] = func.call(null, a);
  };
}
<commit_msg>Improve logic to avoid returning an assignment<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global require, exports */
'use strict';

const {
  Cc, Ci
} = require('chrome');
const {
  Class
} = require('sdk/core/heritage');
const CP_NS = require('sdk/core/namespace').ns();
const {
  ensure
} = require('sdk/system/unload');
const {
  validateOptions
} = require('sdk/deprecated/api-utils');
const {
  id: ADDON_ID
} = require('sdk/self');
const xpcom = require('sdk/platform/xpcom');

const CM = Cc["@mozilla.org/categorymanager;1"]
  .getService(Ci.nsICategoryManager);

const ACCEPT = exports.ACCEPT = Ci.nsIContentPolicy.ACCEPT;
const REJECT = exports.REJECT = Ci.nsIContentPolicy.REJECT_REQUEST;

const accept = function () ACCEPT;

let ContentPolicy_ID = 0;

const RULES = {
  description: {
    map: function (v) {
      return v ? v : '';
    },
    is: ['string']
  },
  contract: {
    map: function (v) {
      if (v === undefined) {
        v = '@erikvold.com/content-policy.' + ADDON_ID + ';' + ContentPolicy_ID++;
      }
      return v;
    },
    is: ['string']
  },
  entry: {
    is: ['string', 'undefined']
  },
  shouldLoad: {
    is: ['function', 'undefined']
  },
  shouldProcess: {
    is: ['function', 'undefined']
  },
};

function getType(aType) {
  switch (aType) {
  case Ci.nsIContentPolicy.TYPE_SCRIPT:
    return 'script';
  case Ci.nsIContentPolicy.TYPE_IMAGE:
    return 'image';
  case Ci.nsIContentPolicy.TYPE_STYLESHEET:
    return 'stylesheet';
  case Ci.nsIContentPolicy.TYPE_OBJECT:
    return 'object';
  case Ci.nsIContentPolicy.TYPE_DOCUMENT:
    return 'document';
  case Ci.nsIContentPolicy.TYPE_SUBDOCUMENT:
    return 'subdocument';
  case Ci.nsIContentPolicy.TYPE_REFRESH:
    return 'refresh';
  case Ci.nsIContentPolicy.TYPE_XBL:
    return 'xbl';
  case Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST:
    return 'xhr';
  case Ci.nsIContentPolicy.TYPE_PING:
    return 'ping';
    // TODO: support more types
  }
  return 'other';
}
const getTypeMemod = memoize(getType, 12, 1);

function makeDetails(aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess) {
  return {
    type: getTypeMemod(aContentType),
    location: aContentLocation,
    origin: aRequestOrigin.spec,
    context: null, // TODO: support this in a safe way somehow..
    mimeTypeGuess: String(aMimeTypeGuess)
  };
}

let ContentPolicy = exports.ContentPolicy = Class({
  initialize: function (options) {
    const self = this;
    options = CP_NS(self).options = validateOptions(options, RULES);
    CP_NS(self).shouldLoad = options.shouldLoad || accept;
    CP_NS(self).shouldProcess = options.shouldProcess || accept;

    let factory = CP_NS(this).factory = xpcom.Factory({
      Component: getProvider(self),
      description: options.description,
      contract: options.contract
    });

    let entry = options.entry || options.contract;
    CM.addCategoryEntry('content-policy', entry, factory.contract, false, true);
    ensure(this, 'destroy');
  },
  destroy: function () {
    // already destroyed?
    if (!CP_NS(this).options)
      return;

    let options = CP_NS(this).options;
    CP_NS(this).options = null;
    CP_NS(this).shouldLoad = accept;
    CP_NS(this).shouldProcess = accept;

    CM.deleteCategoryEntry('content-policy', options.entry || options.contract, false);
  }
});

function getProvider(self) {
  return Class({
    extends: xpcom.Unknown,
    interfaces: ['nsIContentPolicy'],
    shouldLoad: function (aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aExtra) {
      let load = CP_NS(self).shouldLoad(makeDetails.apply(null, arguments));
      return (load == REJECT || (!load && load !== undefined)) ? REJECT : ACCEPT;
    },
    shouldProcess: function (aContentType, aContentLocation, aRequestOrigin, aContext, aMimeTypeGuess, aExtra) {
      let load = CP_NS(self).shouldProcess(makeDetails.apply(null, arguments));
      return (load == REJECT || (!load && load !== undefined)) ? REJECT : ACCEPT;
    }
  });
}

function memoize(func) {
  let cache = Object.create(null);
  return function (a) {
    let key = a.toString();
    if (!(key in cache)) {
      cache[key] = func.call(null, a);
    }
    return cache[key];
  };
}
<|endoftext|>


<commit_before>import types from '../constants'
import simpleStorage from 'simplestorage.js'

const { LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE, LOGOUT_REQUEST, LOGOUT_SUCCESS } = types

const authenticated = () => {
    return simpleStorage.hasKey('token')
}

const initialState = {
    isFetching: false,
    isAuthenticated: authenticated()
}

const authReducer = (state = initialState, action) => {
    switch (action.type) {
    case LOGIN_REQUEST:
        return {
            ...state,
            isFetching: true,
            isAuthenticated: false,
            provider: action.provider
        }
    case LOGIN_SUCCESS:
        return {
            ...state,
            isFetching: false,
            isAuthenticated: true,
            user: action.user
        }
    case LOGIN_FAILURE:
        return {
            ...state,
            isFetching: false,
            isAuthenticated: false,
            error: action.error
        }
    case LOGOUT_REQUEST:
        return {
            ...state,
            isFetching: true,
            isAuthenticated: true
        }
    case LOGOUT_SUCCESS:
        return {
            ...state,
            isFetching: false,
            isAuthenticated: false
        }
    default:
        return state
    }
}
export default authReducer
<commit_msg>reset provider and user fields after user logout<commit_after>import types from '../constants'
import simpleStorage from 'simplestorage.js'

const { LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE, LOGOUT_REQUEST, LOGOUT_SUCCESS } = types

const authenticated = () => {
    return simpleStorage.hasKey('token')
}

const initialState = {
    isFetching: false,
    isAuthenticated: authenticated()
}

const authReducer = (state = initialState, action) => {
    switch (action.type) {
    case LOGIN_REQUEST:
        return {
            ...state,
            isFetching: true,
            isAuthenticated: false,
            provider: action.provider
        }
    case LOGIN_SUCCESS:
        return {
            ...state,
            isFetching: false,
            isAuthenticated: true,
            user: action.user
        }
    case LOGIN_FAILURE:
        return {
            ...state,
            isFetching: false,
            isAuthenticated: false,
            error: action.error
        }
    case LOGOUT_REQUEST:
        return {
            ...state,
            isFetching: true,
            isAuthenticated: true
        }
    case LOGOUT_SUCCESS:
        return {
            ...state,
            isFetching: false,
            isAuthenticated: false,
            provider: null,
            user: null
        }
    default:
        return state
    }
}
export default authReducer
<|endoftext|>


<commit_before>// @flow
import { dsctypes } from "constants/dsctypes"

const {
  LOGIN_REQUEST,
  LOGIN_SUCCESS,
  LOGIN_FAILURE,
  LOGOUT_REQUEST,
  LOGOUT_SUCCESS,
  FETCH_USER_REQUEST,
  FETCH_USER_SUCCESS,
  FETCH_USER_FAILURE,
  FETCH_ROLE_REQUEST,
  FETCH_ROLE_SUCCESS,
  FETCH_ROLE_FAILURE,
  FETCH_NON_AUTH_ROLE_REQUEST,
  FETCH_NON_AUTH_ROLE_SUCCESS,
  FETCH_NON_AUTH_ROLE_FAILURE,
  FETCH_PERMISSION_REQUEST,
  FETCH_PERMISSION_SUCCESS,
  FETCH_PERMISSION_FAILURE
} = dsctypes

const authReducer = (state: Object = {}, action: Object) => {
  switch (action.type) {
    case LOGIN_REQUEST:
      return {
        ...state,
        isFetching: true,
        isAuthenticated: false,
        provider: action.payload.provider
      }
    case LOGIN_SUCCESS:
      return {
        ...state,
        isFetching: false,
        isAuthenticated: action.payload.token ? true : false,
        token: action.payload.token,
        user: action.payload.user
      }
    case LOGIN_FAILURE:
      return {
        ...state,
        isFetching: false,
        isAuthenticated: false,
        error: action.payload.error,
        provider: null
      }
    case LOGOUT_REQUEST:
      return {
        ...state,
        isFetching: true
      }
    case LOGOUT_SUCCESS:
      return {
        ...state,
        isFetching: false,
        isAuthenticated: false,
        provider: null,
        user: null,
        token: null
      }
    case FETCH_USER_REQUEST:
      return {
        ...state,
        isFetching: true
      }
    case FETCH_USER_SUCCESS:
      return {
        ...state,
        isFetching: false,
        fetchedUserData: action.payload.json
      }
    case FETCH_USER_FAILURE:
      return {
        ...state,
        isFetching: false,
        error: action.payload.error
      }
    case FETCH_ROLE_REQUEST:
      return {
        ...state,
        isFetching: true
      }
    case FETCH_ROLE_SUCCESS:
      return {
        ...state,
        isFetching: false,
        user: {
          ...state.user,
          // merge roles into one array, regardless if they are one or many
          roles: [].concat(action.payload.roles.data)
        }
      }
    case FETCH_ROLE_FAILURE:
      return {
        ...state,
        isFetching: false,
        error: action.payload.error
      }
    case FETCH_NON_AUTH_ROLE_REQUEST:
      return {
        ...state,
        isFetching: true
      }
    case FETCH_NON_AUTH_ROLE_SUCCESS:
      return {
        ...state,
        isFetching: false,
        fetchedUserData: {
          ...state.fetchedUserData,
          // merge roles into one array, regardless if they are one or many
          roles: [].concat(action.payload.json.data)
        }
      }
    case FETCH_NON_AUTH_ROLE_FAILURE:
      return {
        ...state,
        isFetching: false,
        error: action.payload.error
      }
    case FETCH_PERMISSION_REQUEST:
      return {
        ...state,
        isFetching: true
      }
    case FETCH_PERMISSION_SUCCESS:
      return {
        ...state,
        isFetching: false,
        user: {
          ...state.user,
          // merge permissions into one array, regardless if they are one or many
          permissions: [].concat(action.payload.permissions.data)
        }
      }
    case FETCH_PERMISSION_FAILURE:
      return {
        ...state,
        isFetching: false,
        error: action.payload.error
      }
    default:
      return state
  }
}
export default authReducer
<commit_msg>fix payload reference in auth reducer<commit_after>// @flow
import { dsctypes } from "constants/dsctypes"

const {
  LOGIN_REQUEST,
  LOGIN_SUCCESS,
  LOGIN_FAILURE,
  LOGOUT_REQUEST,
  LOGOUT_SUCCESS,
  FETCH_USER_REQUEST,
  FETCH_USER_SUCCESS,
  FETCH_USER_FAILURE,
  FETCH_ROLE_REQUEST,
  FETCH_ROLE_SUCCESS,
  FETCH_ROLE_FAILURE,
  FETCH_NON_AUTH_ROLE_REQUEST,
  FETCH_NON_AUTH_ROLE_SUCCESS,
  FETCH_NON_AUTH_ROLE_FAILURE,
  FETCH_PERMISSION_REQUEST,
  FETCH_PERMISSION_SUCCESS,
  FETCH_PERMISSION_FAILURE
} = dsctypes

const authReducer = (state: Object = {}, action: Object) => {
  switch (action.type) {
    case LOGIN_REQUEST:
      return {
        ...state,
        isFetching: true,
        isAuthenticated: false,
        provider: action.payload.provider
      }
    case LOGIN_SUCCESS:
      return {
        ...state,
        isFetching: false,
        isAuthenticated: action.payload.token ? true : false,
        token: action.payload.token,
        user: action.payload.user
      }
    case LOGIN_FAILURE:
      return {
        ...state,
        isFetching: false,
        isAuthenticated: false,
        error: action.payload.error,
        provider: null
      }
    case LOGOUT_REQUEST:
      return {
        ...state,
        isFetching: true
      }
    case LOGOUT_SUCCESS:
      return {
        ...state,
        isFetching: false,
        isAuthenticated: false,
        provider: null,
        user: null,
        token: null
      }
    case FETCH_USER_REQUEST:
      return {
        ...state,
        isFetching: true
      }
    case FETCH_USER_SUCCESS:
      return {
        ...state,
        isFetching: false,
        fetchedUserData: action.payload.json
      }
    case FETCH_USER_FAILURE:
      return {
        ...state,
        isFetching: false,
        error: action.payload.error
      }
    case FETCH_ROLE_REQUEST:
      return {
        ...state,
        isFetching: true
      }
    case FETCH_ROLE_SUCCESS:
      return {
        ...state,
        isFetching: false,
        user: {
          ...state.user,
          // merge roles into one array, regardless if they are one or many
          roles: [].concat(action.payload.roles.data)
        }
      }
    case FETCH_ROLE_FAILURE:
      return {
        ...state,
        isFetching: false,
        error: action.payload.error
      }
    case FETCH_NON_AUTH_ROLE_REQUEST:
      return {
        ...state,
        isFetching: true
      }
    case FETCH_NON_AUTH_ROLE_SUCCESS:
      return {
        ...state,
        isFetching: false,
        fetchedUserData: {
          ...state.fetchedUserData,
          // merge roles into one array, regardless if they are one or many
          roles: [].concat(action.payload.json.data)
        }
      }
    case FETCH_NON_AUTH_ROLE_FAILURE:
      return {
        ...state,
        isFetching: false,
        error: action.payload.error
      }
    case FETCH_PERMISSION_REQUEST:
      return {
        ...state,
        isFetching: true
      }
    case FETCH_PERMISSION_SUCCESS:
      return {
        ...state,
        isFetching: false,
        user: {
          ...state.user,
          // merge permissions into one array, regardless if they are one or many
          permissions: [].concat(action.payload.json.data)
        }
      }
    case FETCH_PERMISSION_FAILURE:
      return {
        ...state,
        isFetching: false,
        error: action.payload.error
      }
    default:
      return state
  }
}
export default authReducer
<|endoftext|>


<commit_before>import * as ACTION_TYPES from '../constants/action_types'
import * as MAPPING_TYPES from '../constants/mapping_types'
import uniq from 'lodash.uniq'
import { RELATIONSHIP_PRIORITY } from '../constants/relationship_types'

// adding methods and accessing them from this object
// allows the unit tests to stub methods in this module
const methods = {}
let hasLoadedFirstStream = false

function mergeModel(state, type, params) {
  if (params.id) {
    const newType = { ...state[type] }
    newType[params.id] = { ...newType[params.id], ...params }
    state[type] = newType
  }
}
methods.mergeModel = (state, type, params) => {
  return mergeModel(state, type, params)
}

function addModels(state, type, data) {
  // add state['modelType']
  if (!state[type]) { state[type] = {} }
  const ids = []
  if (data[type] && data[type].length) {
    // add arrays of models to state['modelType']['id']
    data[type].map((model) => {
      methods.mergeModel(state, type, model)
      ids.push(model.id)
    })
  } else if (data[type] && typeof data[type] === 'object') {
    // add single model objects to state['modelType']['id']
    const model = data[type]
    methods.mergeModel(state, type, model)
    ids.push(model.id)
  }
  return ids
}
methods.addModels = (state, type, data) => {
  return addModels(state, type, data)
}

function updateRelationship(newState, action) {
  const { userId, priority } = action.payload
  const { mappingType } = action.meta
  let followersCount = parseInt(newState[mappingType][userId].followersCount, 10)
  switch (priority) {
  case RELATIONSHIP_PRIORITY.FRIEND:
  case RELATIONSHIP_PRIORITY.NOISE:
    followersCount += 1
    break
  default:
    followersCount -= 1
    break
  }
  // TODO: update the current user's followingCount +1 (this might happen in the profile reducer)
  // TODO: if the priority changes to MUTE or BLOCK we should remove this user from the store
  methods.mergeModel(newState, mappingType, { id: userId, relationshipPriority: priority, followersCount })
  return newState
}
methods.updateRelationship = (newState, action) => {
  return updateRelationship(newState, action)
}

function updatePostLoves(state, newState, action) {
  const { method, model } = action.payload
  let delta = 0
  let loved = false
  switch (action.type) {
  case ACTION_TYPES.POST.LOVE_REQUEST:
    if (method === 'DELETE') {
      delta = -1
      loved = false
    } else {
      delta = 1
      loved = true
    }
    break
  case ACTION_TYPES.POST.LOVE_FAILURE:
    if (method === 'POST') {
      delta = -1
      loved = false
    } else {
      delta = 1
      loved = true
    }
    break
  default:
    return state
  }
  methods.mergeModel(newState, MAPPING_TYPES.POSTS, { id: model.id, lovesCount: Number(model.lovesCount) + delta, loved: loved })
  return newState
}
methods.updatePostLoves = (state, newState, action) => {
  return updatePostLoves(state, newState, action)
}

function addNewIdsToResult(state, newState, router) {
  if (!newState.pages) { newState.pages = {} }
  const result = newState.pages[router.location.pathname]
  if (!result || !result.newIds) { return state }
  result.ids = result.newIds.concat(result.ids)
  delete result.newIds
  return newState
}
methods.addNewIdsToResult = (state, newState, router) => {
  return addNewIdsToResult(state, newState, router)
}

function setLayoutMode(action, state, newState, router) {
  if (!newState.pages) { newState.pages = {} }
  const result = newState.pages[router.location.pathname]
  if (!result || (result && result.mode === action.payload.mode)) { return state }
  result.mode = action.payload.mode
  return newState
}
methods.setLayoutMode = (action, state, newState, router) => {
  return setLayoutMode(action, state, newState, router)
}

// parses the 'linked' node of the JSON
// api responses into the json store
function parseLinked(linked, newState) {
  if (!linked) { return }
  for (const linkedType in linked) {
    if ({}.hasOwnProperty.call(linked, linkedType)) {
      methods.addModels(newState, linkedType, linked)
    }
  }
}
methods.parseLinked = (linked, newState) => {
  return parseLinked(linked, newState)
}

// parse main part of request into the state and
// pull out the ids as this is the main payload
function getResult(response, newState, action) {
  const { mappingType, resultFilter } = action.meta
  const ids = methods.addModels(newState, mappingType, response)
  // set the result to the resultFilter if it exists
  const result = (typeof resultFilter === 'function') ? resultFilter(response[mappingType]) : { type: mappingType, ids: ids }
  result.pagination = action.payload.pagination
  return result
}
methods.getResult = (response, newState, action) => {
  return getResult(response, newState, action)
}

// TODO: need to test the existingResult conditional logic!!!!
function updateResult(response, newState, action, router) {
  if (!newState.pages) { newState.pages = {} }
  const result = methods.getResult(response, newState, action)
  const { resultKey } = action.meta
  // the action payload pathname comes from before the fetch so that
  // we can be sure that the result is being assigned to the proper page
  const pathname = action.payload && action.payload.pathname ? action.payload.pathname : router.location.pathname
  const resultPath = resultKey ? `${pathname}_${resultKey}` : pathname
  const existingResult = newState.pages[resultPath]
  if (existingResult && action.type === ACTION_TYPES.LOAD_NEXT_CONTENT_SUCCESS) {
    existingResult.pagination = result.pagination
    if (existingResult.next) {
      existingResult.next.ids = uniq(existingResult.next.ids.concat(result.ids))
    } else {
      existingResult.next = result
    }
  } else if (existingResult && existingResult.ids[0] !== result.ids[0]) {
    const existingIndex = result.ids.indexOf(existingResult.ids[0])
    // only do this for top level streams, nested ones like lovers/reposters
    // should just update with the new results
    if (hasLoadedFirstStream && !resultKey && existingIndex > 0) {
      existingResult.newIds = result.ids.slice(0, existingIndex)
    } else {
      // this condition should only happen if there was an existingResult
      // and the new result is more than one page away from the existingResult
      // resetting the result would clear out any of the 'old' pages in next
      // this could break down if the results were random and needed a seed
      // for pagination at which point we would want to check against the seed
      // and reset the result if the previous seed didn't match the new one
      // to avoid duplicate results
      newState.pages[resultPath] = { ...existingResult, ...result }
    }
  } else if (existingResult) {
    if (resultKey) {
      // this keeps the pagination correct on a refresh for the lovers/reposters
      // should probably be resetting the results here, but the more button breaks
      newState.pages[resultPath] = { ...result, ...existingResult }
    } else {
      newState.pages[resultPath] = { ...existingResult, ...result }
    }
  } else {
    newState.pages[resultPath] = result
  }
}
methods.updateResult = (response, newState, action, router) => {
  return updateResult(response, newState, action, router)
}

export default function json(state = {}, action = { type: '' }, router) {
  const newState = { ...state }
  if (action.type === ACTION_TYPES.RELATIONSHIPS.UPDATE) {
    return methods.updateRelationship(newState, action)
  } else if (action.type === ACTION_TYPES.POST.LOVE_REQUEST || action.type === ACTION_TYPES.POST.LOVE_SUCCESS || action.type === ACTION_TYPES.POST.LOVE_FAILURE) {
    return methods.updatePostLoves(state, newState, action)
  } else if (action.type === ACTION_TYPES.ADD_NEW_IDS_TO_RESULT) {
    return methods.addNewIdsToResult(state, newState, router)
  } else if (action.type === ACTION_TYPES.SET_LAYOUT_MODE) {
    return methods.setLayoutMode(action, state, newState, router)
  }
  // whitelist actions
  switch (action.type) {
  case ACTION_TYPES.LOAD_NEXT_CONTENT_SUCCESS:
  case ACTION_TYPES.LOAD_STREAM_SUCCESS:
    break
  default:
    return state
  }
  const { response } = action.payload
  if (!response) { return state }
  // parse the linked part of the response into the state
  methods.parseLinked(response.linked, newState)
  // parse main part of response into the state
  // and update the paging information
  methods.updateResult(response, newState, action, router)
  hasLoadedFirstStream = true
  return newState
}

export { json, methods }

<commit_msg>Fixes an issue with page results on a refresh.<commit_after>import * as ACTION_TYPES from '../constants/action_types'
import * as MAPPING_TYPES from '../constants/mapping_types'
import uniq from 'lodash.uniq'
import { RELATIONSHIP_PRIORITY } from '../constants/relationship_types'

// adding methods and accessing them from this object
// allows the unit tests to stub methods in this module
const methods = {}
let hasLoadedFirstStream = false

function mergeModel(state, type, params) {
  if (params.id) {
    const newType = { ...state[type] }
    newType[params.id] = { ...newType[params.id], ...params }
    state[type] = newType
  }
}
methods.mergeModel = (state, type, params) => {
  return mergeModel(state, type, params)
}

function addModels(state, type, data) {
  // add state['modelType']
  if (!state[type]) { state[type] = {} }
  const ids = []
  if (data[type] && data[type].length) {
    // add arrays of models to state['modelType']['id']
    data[type].map((model) => {
      methods.mergeModel(state, type, model)
      ids.push(model.id)
    })
  } else if (data[type] && typeof data[type] === 'object') {
    // add single model objects to state['modelType']['id']
    const model = data[type]
    methods.mergeModel(state, type, model)
    ids.push(model.id)
  }
  return ids
}
methods.addModels = (state, type, data) => {
  return addModels(state, type, data)
}

function updateRelationship(newState, action) {
  const { userId, priority } = action.payload
  const { mappingType } = action.meta
  let followersCount = parseInt(newState[mappingType][userId].followersCount, 10)
  switch (priority) {
  case RELATIONSHIP_PRIORITY.FRIEND:
  case RELATIONSHIP_PRIORITY.NOISE:
    followersCount += 1
    break
  default:
    followersCount -= 1
    break
  }
  // TODO: update the current user's followingCount +1 (this might happen in the profile reducer)
  // TODO: if the priority changes to MUTE or BLOCK we should remove this user from the store
  methods.mergeModel(newState, mappingType, { id: userId, relationshipPriority: priority, followersCount })
  return newState
}
methods.updateRelationship = (newState, action) => {
  return updateRelationship(newState, action)
}

function updatePostLoves(state, newState, action) {
  const { method, model } = action.payload
  let delta = 0
  let loved = false
  switch (action.type) {
  case ACTION_TYPES.POST.LOVE_REQUEST:
    if (method === 'DELETE') {
      delta = -1
      loved = false
    } else {
      delta = 1
      loved = true
    }
    break
  case ACTION_TYPES.POST.LOVE_FAILURE:
    if (method === 'POST') {
      delta = -1
      loved = false
    } else {
      delta = 1
      loved = true
    }
    break
  default:
    return state
  }
  methods.mergeModel(newState, MAPPING_TYPES.POSTS, { id: model.id, lovesCount: Number(model.lovesCount) + delta, loved: loved })
  return newState
}
methods.updatePostLoves = (state, newState, action) => {
  return updatePostLoves(state, newState, action)
}

function addNewIdsToResult(state, newState, router) {
  if (!newState.pages) { newState.pages = {} }
  const result = newState.pages[router.location.pathname]
  if (!result || !result.newIds) { return state }
  result.ids = result.newIds.concat(result.ids)
  delete result.newIds
  return newState
}
methods.addNewIdsToResult = (state, newState, router) => {
  return addNewIdsToResult(state, newState, router)
}

function setLayoutMode(action, state, newState, router) {
  if (!newState.pages) { newState.pages = {} }
  const result = newState.pages[router.location.pathname]
  if (!result || (result && result.mode === action.payload.mode)) { return state }
  result.mode = action.payload.mode
  return newState
}
methods.setLayoutMode = (action, state, newState, router) => {
  return setLayoutMode(action, state, newState, router)
}

// parses the 'linked' node of the JSON
// api responses into the json store
function parseLinked(linked, newState) {
  if (!linked) { return }
  for (const linkedType in linked) {
    if ({}.hasOwnProperty.call(linked, linkedType)) {
      methods.addModels(newState, linkedType, linked)
    }
  }
}
methods.parseLinked = (linked, newState) => {
  return parseLinked(linked, newState)
}

// parse main part of request into the state and
// pull out the ids as this is the main payload
function getResult(response, newState, action) {
  const { mappingType, resultFilter } = action.meta
  const ids = methods.addModels(newState, mappingType, response)
  // set the result to the resultFilter if it exists
  const result = (typeof resultFilter === 'function') ? resultFilter(response[mappingType]) : { type: mappingType, ids: ids }
  result.pagination = action.payload.pagination
  return result
}
methods.getResult = (response, newState, action) => {
  return getResult(response, newState, action)
}

// TODO: need to test the existingResult conditional logic!!!!
function updateResult(response, newState, action, router) {
  if (!newState.pages) { newState.pages = {} }
  const result = methods.getResult(response, newState, action)
  const { resultKey } = action.meta
  // the action payload pathname comes from before the fetch so that
  // we can be sure that the result is being assigned to the proper page
  const pathname = action.payload && action.payload.pathname ? action.payload.pathname : router.location.pathname
  const resultPath = resultKey ? `${pathname}_${resultKey}` : pathname
  const existingResult = newState.pages[resultPath]
  if (existingResult && action.type === ACTION_TYPES.LOAD_NEXT_CONTENT_SUCCESS) {
    existingResult.pagination = result.pagination
    if (existingResult.next) {
      existingResult.next.ids = uniq(existingResult.next.ids.concat(result.ids))
    } else {
      existingResult.next = result
    }
  } else if (existingResult && existingResult.ids[0] !== result.ids[0]) {
    const existingIndex = result.ids.indexOf(existingResult.ids[0])
    // only do this for top level streams, nested ones like lovers/reposters
    // should just update with the new results
    if (hasLoadedFirstStream && !resultKey && existingIndex > 0) {
      existingResult.newIds = result.ids.slice(0, existingIndex)
    } else {
      // this condition should only happen if there was an existingResult
      // and the new result is more than one page away from the existingResult
      // resetting the result would clear out any of the 'old' pages in next
      // this could break down if the results were random and needed a seed
      // for pagination at which point we would want to check against the seed
      // and reset the result if the previous seed didn't match the new one
      // to avoid duplicate results
      newState.pages[resultPath] = { ...existingResult, ...result }
    }
  } else if (existingResult) {
    // this keeps the pagination correct on a refresh
    // should probably be resetting the results here, but the more button breaks
    newState.pages[resultPath] = { ...result, ...existingResult }
  } else {
    newState.pages[resultPath] = result
  }
}
methods.updateResult = (response, newState, action, router) => {
  return updateResult(response, newState, action, router)
}

export default function json(state = {}, action = { type: '' }, router) {
  const newState = { ...state }
  if (action.type === ACTION_TYPES.RELATIONSHIPS.UPDATE) {
    return methods.updateRelationship(newState, action)
  } else if (action.type === ACTION_TYPES.POST.LOVE_REQUEST || action.type === ACTION_TYPES.POST.LOVE_SUCCESS || action.type === ACTION_TYPES.POST.LOVE_FAILURE) {
    return methods.updatePostLoves(state, newState, action)
  } else if (action.type === ACTION_TYPES.ADD_NEW_IDS_TO_RESULT) {
    return methods.addNewIdsToResult(state, newState, router)
  } else if (action.type === ACTION_TYPES.SET_LAYOUT_MODE) {
    return methods.setLayoutMode(action, state, newState, router)
  }
  // whitelist actions
  switch (action.type) {
  case ACTION_TYPES.LOAD_NEXT_CONTENT_SUCCESS:
  case ACTION_TYPES.LOAD_STREAM_SUCCESS:
    break
  default:
    return state
  }
  const { response } = action.payload
  if (!response) { return state }
  // parse the linked part of the response into the state
  methods.parseLinked(response.linked, newState)
  // parse main part of response into the state
  // and update the paging information
  methods.updateResult(response, newState, action, router)
  hasLoadedFirstStream = true
  return newState
}

export { json, methods }

<|endoftext|>


<commit_before>import {
  appendPayloadByPath, composeReducers, keyedCounter, mergeList
} from './util'
import {
  FETCH_LEFT_NAV_TAGS, FETCH_LIVE_STATUS, FETCH_TAG, FETCH_TAGS,
  FETCH_TAG_SUMMARY, FOLLOW_TAG_PENDING, REMOVE_TAG
} from '../actions'
import { filter, get, isEmpty, merge, omitBy, toPairs } from 'lodash'
import qs from 'querystring'

const matchesRemovedTag = (id, slug) => {
  const key = qs.parse(id)
  return key.subject === 'community' && key.id === slug
}

const filterOutRemovedTag = (state, { type, error, meta }) => {
  if (type !== REMOVE_TAG || error) return state
  const updated = toPairs(state).reduce((acc, [id, tags]) => {
    if (matchesRemovedTag(id, meta.slug)) {
      acc[id] = filter(tags, t => t.id !== meta.id)
    }
    return acc
  }, {})
  return {...state, ...updated}
}

const decrementForRemovedTag = (state, { type, error, meta }) => {
  if (type !== REMOVE_TAG || error) return state
  const updated = toPairs(state).reduce((acc, [id, count]) => {
    if (matchesRemovedTag(id, meta.slug)) {
      acc[id] = count - 1
    }
    return acc
  }, {})
  return {...state, ...updated}
}

export const tagsByQuery = composeReducers(
  appendPayloadByPath(FETCH_TAGS, 'meta.cache.id', 'items', t => t.id),
  filterOutRemovedTag
)

export const totalTagsByQuery = composeReducers(
  keyedCounter(FETCH_TAGS, 'total'),
  decrementForRemovedTag
)

export const tagsByCommunity = (state = {}, action) => {
  const mergeLeftNavTags = (state, leftNavTags, id) => {
    if (isEmpty(leftNavTags)) return state
    let labeledTags = leftNavTags.followed.map(f => merge(f, {followed: true}))
    .concat(leftNavTags.created.map(c => merge(c, {created: true})))
    return {
      ...state,
      [id]: mergeList(state[id] || {}, labeledTags, 'name')
    }
  }

  // meta.id here is whatever params.id is in CommunityProfile, i.e. a slug
  let { type, payload, meta, error } = action
  if (error) return state

  let oldCommunityTags, oldTag

  switch (type) {
    case FETCH_TAG:
      oldCommunityTags = state[meta.id] || {}
      return {
        ...state,
        [meta.id]: {
          ...oldCommunityTags,
          [meta.tagName]: payload
        }
      }
    case FETCH_LEFT_NAV_TAGS:
      return mergeLeftNavTags(state, payload, meta.id)
    case FETCH_LIVE_STATUS:
      return mergeLeftNavTags(state, payload.left_nav_tags, get(meta, 'slug'))
    case FOLLOW_TAG_PENDING:
      oldCommunityTags = state[meta.id] || {}
      oldTag = oldCommunityTags[meta.tagName]
      return {
        ...state,
        [meta.id]: {
          ...oldCommunityTags,
          [meta.tagName]: {...oldTag, followed: !oldTag.followed}
        }
      }
    case FETCH_TAG_SUMMARY:
      oldCommunityTags = state[meta.id] || {}
      oldTag = oldCommunityTags[meta.tagName]
      return {
        ...state,
        [meta.id]: {
          ...oldCommunityTags,
          [meta.tagName]: {...oldTag, ...payload}
        }
      }
    case REMOVE_TAG:
      return {
        ...state,
        [meta.slug]: omitBy(state[meta.slug], t => t.name === meta.name)
      }
  }
  return state
}
<commit_msg>expect left nav tag content from API to be only followed tags<commit_after>import {
  appendPayloadByPath, composeReducers, keyedCounter, mergeList
} from './util'
import {
  FETCH_LEFT_NAV_TAGS, FETCH_LIVE_STATUS, FETCH_TAG, FETCH_TAGS,
  FETCH_TAG_SUMMARY, FOLLOW_TAG_PENDING, REMOVE_TAG
} from '../actions'
import { filter, get, isEmpty, merge, omitBy, toPairs } from 'lodash'
import qs from 'querystring'

const matchesRemovedTag = (id, slug) => {
  const key = qs.parse(id)
  return key.subject === 'community' && key.id === slug
}

const filterOutRemovedTag = (state, { type, error, meta }) => {
  if (type !== REMOVE_TAG || error) return state
  const updated = toPairs(state).reduce((acc, [id, tags]) => {
    if (matchesRemovedTag(id, meta.slug)) {
      acc[id] = filter(tags, t => t.id !== meta.id)
    }
    return acc
  }, {})
  return {...state, ...updated}
}

const decrementForRemovedTag = (state, { type, error, meta }) => {
  if (type !== REMOVE_TAG || error) return state
  const updated = toPairs(state).reduce((acc, [id, count]) => {
    if (matchesRemovedTag(id, meta.slug)) {
      acc[id] = count - 1
    }
    return acc
  }, {})
  return {...state, ...updated}
}

export const tagsByQuery = composeReducers(
  appendPayloadByPath(FETCH_TAGS, 'meta.cache.id', 'items', t => t.id),
  filterOutRemovedTag
)

export const totalTagsByQuery = composeReducers(
  keyedCounter(FETCH_TAGS, 'total'),
  decrementForRemovedTag
)

export const tagsByCommunity = (state = {}, action) => {
  const mergeLeftNavTags = (state, tags, id) => {
    if (isEmpty(tags)) return state
    const labeledTags = tags.map(f => ({...f, followed: true}))
    return {
      ...state,
      [id]: mergeList(state[id] || {}, labeledTags, 'name')
    }
  }

  // meta.id here is whatever params.id is in CommunityProfile, i.e. a slug
  let { type, payload, meta, error } = action
  if (error) return state

  let oldCommunityTags, oldTag

  switch (type) {
    case FETCH_TAG:
      oldCommunityTags = state[meta.id] || {}
      return {
        ...state,
        [meta.id]: {
          ...oldCommunityTags,
          [meta.tagName]: payload
        }
      }
    case FETCH_LEFT_NAV_TAGS:
      return mergeLeftNavTags(state, payload, meta.id)
    case FETCH_LIVE_STATUS:
      return mergeLeftNavTags(state, payload.left_nav_tags, get(meta, 'slug'))
    case FOLLOW_TAG_PENDING:
      oldCommunityTags = state[meta.id] || {}
      oldTag = oldCommunityTags[meta.tagName]
      return {
        ...state,
        [meta.id]: {
          ...oldCommunityTags,
          [meta.tagName]: {...oldTag, followed: !oldTag.followed}
        }
      }
    case FETCH_TAG_SUMMARY:
      oldCommunityTags = state[meta.id] || {}
      oldTag = oldCommunityTags[meta.tagName]
      return {
        ...state,
        [meta.id]: {
          ...oldCommunityTags,
          [meta.tagName]: {...oldTag, ...payload}
        }
      }
    case REMOVE_TAG:
      return {
        ...state,
        [meta.slug]: omitBy(state[meta.slug], t => t.name === meta.name)
      }
  }
  return state
}
<|endoftext|>


<commit_before>addModule('twitter', function(module, moduleID) {
	module.moduleName = 'Twitter';
	module.category = 'Site Modules';

	module.init = function() {
		$("#page-container").delegate(".js-actionable-tweet", "mouseover", function() {
			var replyAction,
				changeTipButton,
				bgImgURL = chrome.extension.getURL('images/changetip_twitter_icon.png'),
				link,
				username,
				button,
				buttonImg;

			if (!$(this).data('changetip-button-added')) {
				replyAction = $(this).find("div.ProfileTweet-action--reply");
				if (replyAction) {
					changeTipButton = replyAction.clone();

					username = replyAction.closest('.content').find('.username').text();
					console.log(username);

					button = changeTipButton.find(".js-actionReply");
					button.empty();
					buttonImg = $('');
					button.append(buttonImg);

					button.addClass("js-actionChangeTip")
							.removeAttr("data-modal")
							.attr("title", "ChangeTip");

					button.find(".Icon").removeClass("Icon--reply");
					button.find(".u-isHiddenVisually").html("ChangeTip");

					button.attr('data-username', username);
					button.on("click", module.delayedTipViaReply);

					link = changeTipButton.find(".js-actionCount");
					link.removeClass("ProfileTweet-actionCount--isZero");
					link.attr("title", "ChangeTip");
					link.find("span").html("Tip");
					link.on("click", module.delayedTipViaReply);

					replyAction.after(changeTipButton);
				}
			}
			$(this).data('changetip-button-added', true);
		});
	};

	// the string used to mention changetip on this medium
	module.ctMentionString = '@changetip';

	/**
	 * given a tip amount, return a message string to be appended
	 * to the comment reply box using ChangeTip.applyTipTemplate
	 *
	 * @return {string} message to be placed into comment box
	 */
	module.getTipMessage = function(tipAmount) {
		return ChangeTip.applyTipTemplate(tipAmount) + ' ' + module.ctMentionString;
	};

	/**
	 * handle form submission from the ChangTip dialog - this function
	 * should call module.getTipMessage, and place that message in to
	 * the appropriate place
	 */
	module.submit = function(tipAmount) {
		var tipString = module.getTipMessage(tipAmount),
			text;

		// focus the textarea immediately, because twitter is going to do some DOM swapping that
		// needs to happen before we attempt to manipulate the text.
		module.textarea.focus();

		// get the existing textcontent (filter out the HTML, as twitter will re-assemble it),
		// and append the tipString.
		text = module.textarea.textContent;

		// we add an extra space to the end to avoid the auto complete popups after the @mention
		text = text + tipString + ' ';

		module.textarea.innerHTML = text;
	};

	/**
	 * If the onCancel function exists, the core changeTip dialog will
	 * call it when the dialog is cancelled
	 */
	module.onCancel = function() {
		module.textarea.focus();
	};

	// we need the tip box to exist, so we'll wait for it...
	module.delayedTipViaReply = function(e) {
		var textarea = $(e.target).closest('.original-tweet-container').find('div.tweet-box.rich-editor');
		if (!textarea || !textarea.length) {
			setTimeout(function() {
				module.delayedTipViaReply(e);
			}, 100);
		} else {
			module.tipViaReply(e, textarea[0]);
		}
	};

	module.tipViaReply = function(e, textarea) {
		var tipButton = e.target,
			username;

		if (tipButton.tagName === 'IMG') {
			tipButton = tipButton.parentNode;
		}
		username = tipButton.getAttribute('data-username');

		if (!textarea) {
			throw "ChangeTip Exception: comment textarea not found";
		}
		module.textarea = textarea;
		ChangeTip.openDialog({
			x: $(module.textarea).offset().left,
			y: $(module.textarea).offset().top,
			textarea: textarea,
			amount: ChangeTip.getSetting('defaultAmount'),
			username: username
		});
	};

});<commit_msg>remove stray console.log<commit_after>addModule('twitter', function(module, moduleID) {
	module.moduleName = 'Twitter';
	module.category = 'Site Modules';

	module.init = function() {
		$("#page-container").delegate(".js-actionable-tweet", "mouseover", function() {
			var replyAction,
				changeTipButton,
				bgImgURL = chrome.extension.getURL('images/changetip_twitter_icon.png'),
				link,
				username,
				button,
				buttonImg;

			if (!$(this).data('changetip-button-added')) {
				replyAction = $(this).find("div.ProfileTweet-action--reply");
				if (replyAction) {
					changeTipButton = replyAction.clone();

					username = replyAction.closest('.content').find('.username').text();

					button = changeTipButton.find(".js-actionReply");
					button.empty();
					buttonImg = $('');
					button.append(buttonImg);

					button.addClass("js-actionChangeTip")
							.removeAttr("data-modal")
							.attr("title", "ChangeTip");

					button.find(".Icon").removeClass("Icon--reply");
					button.find(".u-isHiddenVisually").html("ChangeTip");

					button.attr('data-username', username);
					button.on("click", module.delayedTipViaReply);

					link = changeTipButton.find(".js-actionCount");
					link.removeClass("ProfileTweet-actionCount--isZero");
					link.attr("title", "ChangeTip");
					link.find("span").html("Tip");
					link.on("click", module.delayedTipViaReply);

					replyAction.after(changeTipButton);
				}
			}
			$(this).data('changetip-button-added', true);
		});
	};

	// the string used to mention changetip on this medium
	module.ctMentionString = '@changetip';

	/**
	 * given a tip amount, return a message string to be appended
	 * to the comment reply box using ChangeTip.applyTipTemplate
	 *
	 * @return {string} message to be placed into comment box
	 */
	module.getTipMessage = function(tipAmount) {
		return ChangeTip.applyTipTemplate(tipAmount) + ' ' + module.ctMentionString;
	};

	/**
	 * handle form submission from the ChangTip dialog - this function
	 * should call module.getTipMessage, and place that message in to
	 * the appropriate place
	 */
	module.submit = function(tipAmount) {
		var tipString = module.getTipMessage(tipAmount),
			text;

		// focus the textarea immediately, because twitter is going to do some DOM swapping that
		// needs to happen before we attempt to manipulate the text.
		module.textarea.focus();

		// get the existing textcontent (filter out the HTML, as twitter will re-assemble it),
		// and append the tipString.
		text = module.textarea.textContent;

		// we add an extra space to the end to avoid the auto complete popups after the @mention
		text = text + tipString + ' ';

		module.textarea.innerHTML = text;
	};

	/**
	 * If the onCancel function exists, the core changeTip dialog will
	 * call it when the dialog is cancelled
	 */
	module.onCancel = function() {
		module.textarea.focus();
	};

	// we need the tip box to exist, so we'll wait for it...
	module.delayedTipViaReply = function(e) {
		var textarea = $(e.target).closest('.original-tweet-container').find('div.tweet-box.rich-editor');
		if (!textarea || !textarea.length) {
			setTimeout(function() {
				module.delayedTipViaReply(e);
			}, 100);
		} else {
			module.tipViaReply(e, textarea[0]);
		}
	};

	module.tipViaReply = function(e, textarea) {
		var tipButton = e.target,
			username;

		if (tipButton.tagName === 'IMG') {
			tipButton = tipButton.parentNode;
		}
		username = tipButton.getAttribute('data-username');

		if (!textarea) {
			throw "ChangeTip Exception: comment textarea not found";
		}
		module.textarea = textarea;
		ChangeTip.openDialog({
			x: $(module.textarea).offset().left,
			y: $(module.textarea).offset().top,
			textarea: textarea,
			amount: ChangeTip.getSetting('defaultAmount'),
			username: username
		});
	};

});<|endoftext|>


<commit_before>import { GET_USER, CREATE_USER, EDIT_USER} from '../actions/types';
import _ from 'lodash';

export default function user (state = {}, action) {
  switch (action.type) {
    case GET_USER:
      return state.user = _.assign({id: action.id}, state.user, action.payload);
    case EDIT_USER:
      return state.user = _.assign({id: action.id}, state.user, action.payload);
    default:
      return state;
  }
}<commit_msg>Add a whitespace<commit_after>import { GET_USER, CREATE_USER, EDIT_USER } from '../actions/types';
import _ from 'lodash';

export default function user (state = {}, action) {
  switch (action.type) {
    case GET_USER:
      return state.user = _.assign({id: action.id}, state.user, action.payload);
    case EDIT_USER:
      return state.user = _.assign({id: action.id}, state.user, action.payload);
    default:
      return state;
  }
}<|endoftext|>


<commit_before>var spawn = require('child_process').spawn;

module.exports = function (command, options) {
  var file, args;
  if (process.platform === 'win32') {
    file = 'cmd.exe';
    args = ['/s', '/c', command];
  }
  else {
    file = '/bin/sh';
    args = ['-c', command];
  }
  return spawn(file, args, options);
};
<commit_msg>verbatim args<commit_after>var util = require('util');
var spawn = require('child_process').spawn;

module.exports = function (command, options) {
  var file, args;
  if (process.platform === 'win32') {
    file = 'cmd.exe';
    args = ['/s', '/c', '"' + command + '"'];
    options = util._extend({}, options);
    options.windowsVerbatimArguments = true;
  }
  else {
    file = '/bin/sh';
    args = ['-c', command];
  }
  return spawn(file, args, options);
};
<|endoftext|>


<commit_before>var events = require("events");
var fs = require('fs');
var http = require('http');
var https = require('https');
var util = require('util');

var Promise = require('promise');
var Speaker = require('speaker');
var lame = require('lame');
var mpg123Util = require('node-mpg123-util');

/**
 * @param {string} urlOrPath
 * @constructor
 */
StupidPlayer = function(urlOrPath) {
	this._decoder = null;
	this._speaker = null;
	this._request = null;

	this._src = urlOrPath;

	this._onDecoderFormatted = this._onDecoderFormatted.bind(this);
	this._onDecoderClosed = this._onDecoderClosed.bind(this);
	this._onError = this._onError.bind(this);

	this.play(urlOrPath);
};
util.inherits(StupidPlayer, events.EventEmitter);

/**
 * @param {string} uri
 * @return {Promise.<Format>}
 */
StupidPlayer.prototype.play = function(uri) {
	if (this._isStream()) {
		return this.playStream(uri);
	} else {
		return this.playFile(uri);
	}
};

/**
 * @param {string} url
 * @return {Promise.<Format>}
 */
StupidPlayer.prototype.playStream = function(url) {
	return new Promise(function(resolve, reject) {
		var request = url.indexOf('https') === 0 ? https : http;

		this._request = request.get(url, function(res, req) {
			this._makeDecoder(res)
				.then(function(format) {
					resolve(format);
				});
		}.bind(this));

		this._request.once('error', this._onError);
	}.bind(this));
};

/**
 * @param {string} path
 * @return {Promise.<Format>}
 */
StupidPlayer.prototype.playFile = function(path) {
	return new Promise(function(resolve, reject) {
		var readStream = fs.createReadStream(path);

		readStream.once('error', this._onError);

		return this
			._makeDecoder(readStream)
			.then(function(format) {
				resolve(format);
			});
	}.bind(this));
};

/**
 *
 */
StupidPlayer.prototype.stop = function() {
	if (this._state !== this.state.STOP) {
		this._state = this.state.STOP;
		this._emit(this.EVENT_STOP);
		this.deinit();
	}
};

/**
 * @param {number} value 0..1
 * @return {Promise.<number>}
 */
StupidPlayer.prototype.setVolume = function(value) {
	return new Promise(function(resolve, reject) {
		var resolver = function() {
			resolve(value);
			this._emit(this.EVENT_VOLUME_CHANGE, value);
		}.bind(this);

		if (this._decoder) {
			mpg123Util.setVolume(this._decoder.mh, value);
			setTimeout(resolver, this.VOLUME_CHANGE_TIMEOUT);
		} else {
			reject(null);
		}
	}.bind(this));
};

/**
 * @return {?number} 0..1
 */
StupidPlayer.prototype.getVolume = function() {
	if (this._decoder) {
		return mpg123Util.getVolume(this._decoder.mh);
	} else {
		return null;
	}
};

/**
 *
 */
StupidPlayer.prototype.deinit = function() {
	this._closeConnection();
	if (this._speaker) {
		this._decoder.removeListener('error', this._onError);

		this._decoder.unpipe();
		this._speaker.end();

		this._speaker = null;
		this._decoder = null;
	}
};

/**
 *
 * @protected
 */
StupidPlayer.prototype._closeConnection = function() {
	if (this._request) {
		this._request.removeListener('error', this._onError);
		this._request.destroy();
		this._request = null;
	}
};

/**
 *
 * @return {boolean}
 * @protected
 */
StupidPlayer.prototype._isStream = function() {
	var src = this._src;
	return src.indexOf('http') === 0 || src.indexOf('https') === 0;
};

/**
 *
 * @param res
 * @return {Promise.<Format>}
 * @protected
 */
StupidPlayer.prototype._makeDecoder = function(res) {
	return new Promise(function(resolve, reject) {
		if (!this._isStream() || this._checkConnect(res, function() {})) {// todo need simple method
			this._decoder = new lame.Decoder();
			res.pipe(this._decoder);

			this._decoder.once('format', function(format) {
				this._onDecoderFormatted(format);
				resolve(format);
			}.bind(this));

			this._decoder.on('error', this._onError);

		} else {
			this._state = this.state.STOP;
			this._emit(this.EVENT_STOP);
		}
	}.bind(this));
};

/**
 *
 * @param format
 * @protected
 */
StupidPlayer.prototype._play = function(format) {
	if (this._state !== this.state.STOP) {
		this._speaker = new Speaker(format);

		this._emit(this.EVENT_START, this._isStream());
		this._state = this.state.PLAY;

		this._pipedDecoder = this._decoder.pipe(this._speaker);
		this._pipedDecoder.once('close', this._onDecoderClosed);
	}
};

/**
 *
 * @param res
 * @param callback
 * @return {boolean}
 * @protected
 */
StupidPlayer.prototype._checkConnect = function(res, callback) {
	var isOk = res.statusCode === 200;
	var isAudio = (res.headers['content-type'].indexOf('audio/mpeg') !== -1);
	if (!isOk) {
		callback(new Error('resource invalid'));
		return false;
	}
	if (!isAudio) {
		callback(new Error('resource type is unsupported'));
		return false;
	}
	return true;
};

/**
 * @param {string} event
 * @param {*?} opt_data
 * @protected
 */
StupidPlayer.prototype._emit = function(event, opt_data) {
//	console.log('stupid-player emit', event);
	this.emit(event, opt_data);
};

/**
 *
 * @param format
 * @protected
 */
StupidPlayer.prototype._onDecoderFormatted = function(format) {
	this._play(format);
};

/**
 *
 * @protected
 */
StupidPlayer.prototype._onDecoderClosed = function() {
	this.stop();
};

/**
 *
 * @param {string} error
 * @protected
 */
StupidPlayer.prototype._onError = function(error) {
	if (this._request) {
		this._request = null;
	} else {
		return;
	}
	this._emit(this.EVENT_ERROR, error);
};

/**
 *
 */
StupidPlayer.prototype._decoder;

/**
 *
 */
StupidPlayer.prototype._speaker;

/**
 *
 */
StupidPlayer.prototype._pipedDecoder;

/**
 * @type {string}
 */
StupidPlayer.prototype._src;

/**
 * @type {ClientRequest}
 */
StupidPlayer.prototype._request;

/**
 * @const {number}
 */
StupidPlayer.prototype.VOLUME_CHANGE_TIMEOUT = 300;

/**
 * @const {string}
 */
StupidPlayer.prototype.EVENT_START = 'start';

/**
 * @const {string}
 */
StupidPlayer.prototype.EVENT_STOP = 'stop';

/**
 * Fired with: {number} volume
 * @const {string}
 */
StupidPlayer.prototype.EVENT_VOLUME_CHANGE = 'volume-change';

/**
 * Fired with: string
 * @const {string}
 */
StupidPlayer.prototype.EVENT_ERROR = 'error';

/**
 * @enum {string}
 */
StupidPlayer.prototype.state = {
	PLAY: 'play',
	STOP: 'stop',
	PAUSE: 'pause'
};

module.exports = StupidPlayer;
<commit_msg>Rename urlOrPath to uri in constructor<commit_after>var events = require("events");
var fs = require('fs');
var http = require('http');
var https = require('https');
var util = require('util');

var Promise = require('promise');
var Speaker = require('speaker');
var lame = require('lame');
var mpg123Util = require('node-mpg123-util');

/**
 * @param {string} uri
 * @constructor
 */
StupidPlayer = function(uri) {
	this._decoder = null;
	this._speaker = null;
	this._request = null;

	this._src = uri;

	this._onDecoderFormatted = this._onDecoderFormatted.bind(this);
	this._onDecoderClosed = this._onDecoderClosed.bind(this);
	this._onError = this._onError.bind(this);

	this.play(uri);
};
util.inherits(StupidPlayer, events.EventEmitter);

/**
 * @param {string} uri
 * @return {Promise.<Format>}
 */
StupidPlayer.prototype.play = function(uri) {
	if (this._isStream()) {
		return this.playStream(uri);
	} else {
		return this.playFile(uri);
	}
};

/**
 * @param {string} url
 * @return {Promise.<Format>}
 */
StupidPlayer.prototype.playStream = function(url) {
	return new Promise(function(resolve, reject) {
		var request = url.indexOf('https') === 0 ? https : http;

		this._request = request.get(url, function(res, req) {
			this._makeDecoder(res)
				.then(function(format) {
					resolve(format);
				});
		}.bind(this));

		this._request.once('error', this._onError);
	}.bind(this));
};

/**
 * @param {string} path
 * @return {Promise.<Format>}
 */
StupidPlayer.prototype.playFile = function(path) {
	return new Promise(function(resolve, reject) {
		var readStream = fs.createReadStream(path);

		readStream.once('error', this._onError);

		return this
			._makeDecoder(readStream)
			.then(function(format) {
				resolve(format);
			});
	}.bind(this));
};

/**
 *
 */
StupidPlayer.prototype.stop = function() {
	if (this._state !== this.state.STOP) {
		this._state = this.state.STOP;
		this._emit(this.EVENT_STOP);
		this.deinit();
	}
};

/**
 * @param {number} value 0..1
 * @return {Promise.<number>}
 */
StupidPlayer.prototype.setVolume = function(value) {
	return new Promise(function(resolve, reject) {
		var resolver = function() {
			resolve(value);
			this._emit(this.EVENT_VOLUME_CHANGE, value);
		}.bind(this);

		if (this._decoder) {
			mpg123Util.setVolume(this._decoder.mh, value);
			setTimeout(resolver, this.VOLUME_CHANGE_TIMEOUT);
		} else {
			reject(null);
		}
	}.bind(this));
};

/**
 * @return {?number} 0..1
 */
StupidPlayer.prototype.getVolume = function() {
	if (this._decoder) {
		return mpg123Util.getVolume(this._decoder.mh);
	} else {
		return null;
	}
};

/**
 *
 */
StupidPlayer.prototype.deinit = function() {
	this._closeConnection();
	if (this._speaker) {
		this._decoder.removeListener('error', this._onError);

		this._decoder.unpipe();
		this._speaker.end();

		this._speaker = null;
		this._decoder = null;
	}
};

/**
 *
 * @protected
 */
StupidPlayer.prototype._closeConnection = function() {
	if (this._request) {
		this._request.removeListener('error', this._onError);
		this._request.destroy();
		this._request = null;
	}
};

/**
 *
 * @return {boolean}
 * @protected
 */
StupidPlayer.prototype._isStream = function() {
	var src = this._src;
	return src.indexOf('http') === 0 || src.indexOf('https') === 0;
};

/**
 *
 * @param res
 * @return {Promise.<Format>}
 * @protected
 */
StupidPlayer.prototype._makeDecoder = function(res) {
	return new Promise(function(resolve, reject) {
		if (!this._isStream() || this._checkConnect(res, function() {})) {// todo need simple method
			this._decoder = new lame.Decoder();
			res.pipe(this._decoder);

			this._decoder.once('format', function(format) {
				this._onDecoderFormatted(format);
				resolve(format);
			}.bind(this));

			this._decoder.on('error', this._onError);

		} else {
			this._state = this.state.STOP;
			this._emit(this.EVENT_STOP);
		}
	}.bind(this));
};

/**
 *
 * @param format
 * @protected
 */
StupidPlayer.prototype._play = function(format) {
	if (this._state !== this.state.STOP) {
		this._speaker = new Speaker(format);

		this._emit(this.EVENT_START, this._isStream());
		this._state = this.state.PLAY;

		this._pipedDecoder = this._decoder.pipe(this._speaker);
		this._pipedDecoder.once('close', this._onDecoderClosed);
	}
};

/**
 *
 * @param res
 * @param callback
 * @return {boolean}
 * @protected
 */
StupidPlayer.prototype._checkConnect = function(res, callback) {
	var isOk = res.statusCode === 200;
	var isAudio = (res.headers['content-type'].indexOf('audio/mpeg') !== -1);
	if (!isOk) {
		callback(new Error('resource invalid'));
		return false;
	}
	if (!isAudio) {
		callback(new Error('resource type is unsupported'));
		return false;
	}
	return true;
};

/**
 * @param {string} event
 * @param {*?} opt_data
 * @protected
 */
StupidPlayer.prototype._emit = function(event, opt_data) {
//	console.log('stupid-player emit', event);
	this.emit(event, opt_data);
};

/**
 *
 * @param format
 * @protected
 */
StupidPlayer.prototype._onDecoderFormatted = function(format) {
	this._play(format);
};

/**
 *
 * @protected
 */
StupidPlayer.prototype._onDecoderClosed = function() {
	this.stop();
};

/**
 *
 * @param {string} error
 * @protected
 */
StupidPlayer.prototype._onError = function(error) {
	if (this._request) {
		this._request = null;
	} else {
		return;
	}
	this._emit(this.EVENT_ERROR, error);
};

/**
 *
 */
StupidPlayer.prototype._decoder;

/**
 *
 */
StupidPlayer.prototype._speaker;

/**
 *
 */
StupidPlayer.prototype._pipedDecoder;

/**
 * @type {string}
 */
StupidPlayer.prototype._src;

/**
 * @type {ClientRequest}
 */
StupidPlayer.prototype._request;

/**
 * @const {number}
 */
StupidPlayer.prototype.VOLUME_CHANGE_TIMEOUT = 300;

/**
 * @const {string}
 */
StupidPlayer.prototype.EVENT_START = 'start';

/**
 * @const {string}
 */
StupidPlayer.prototype.EVENT_STOP = 'stop';

/**
 * Fired with: {number} volume
 * @const {string}
 */
StupidPlayer.prototype.EVENT_VOLUME_CHANGE = 'volume-change';

/**
 * Fired with: string
 * @const {string}
 */
StupidPlayer.prototype.EVENT_ERROR = 'error';

/**
 * @enum {string}
 */
StupidPlayer.prototype.state = {
	PLAY: 'play',
	STOP: 'stop',
	PAUSE: 'pause'
};

module.exports = StupidPlayer;
<|endoftext|>


<commit_before>var _ = require('underscore');
var $ = jQuery = require('jquery-browserify');
var Backbone = require('../lib/backbone');
var Handlebars = require('handlebars');

var requestFormTemplate = Handlebars.compile([

        '
',
            '<select id="fromPlace" style="width: 100%" placeholder="Start Address"></select>',
        '
',
        '
',
            '<select id="toPlace" style="width: 100%"  placeholder="End Address"></select>',
        '
',
        '
',
            '<select id="arriveBy" class="span3" placeholder="Arrive"><option value="true">Arrive</option><option value="false" selected>Depart</option></select>',

            '
',
                '<input id="time" type="text" class="input-small">',
                '',
            '
',

            '
',
                '<input id="date" class="input-small" size="16" type="text">',
            '
',

            '<form class="form-horizontal">',
                '
',
                    'Travel by: ',
                    '
',
                        '<select id="mode" class="span12" placeholder="Arrive"><option value="TRANSIT,WALK">Transit</option><option value="WALK">Walk only</option><option value="BICYCLE">Bike only</option><option value="TRANSIT,BICYCLE">Transit & Bike</option></select>',
                    '
',
                '
',

                '
',
                    'Find: ',
                    '
',
                        '<select id="optimize" class="span12" placeholder="Arrive"><option value="QUICK">Quickest trip</option><option value="TRANSFERS">Fewest transfers</option><option>Custom trip</option></select>',
                    '
',
                '
',

                '
',
                    '
',
                    '
',
                '
',

                '
',
                    'Maximum walk: ',
                    '
',
                        '<select id="maxWalkDistance" class="span12" placeholder="Arrive"><option value="402">1/4 mile</option><option value="804">1/2 mile</option><option value="1223" selected>3/4 mile</option><option value="1609">1 mile</option><option value="3218">2 miles</option><option value="4828">3 miles</option></select>',
                    '
',
                '
',
            '</form>',
        '
'
].join('\n'));

var geocodeItem = Handlebars.compile([
    '
',
        '{{address}}',
        '{{city}}, {{state}}',
    '
'
].join('\n'));

var OtpRequestFormView = Backbone.View.extend({

    events: {
        "change #fromPlace"     : "changeForm",
        "change #toPlace"       : "changeForm",
        "change #arriveBy"        : "changeForm",
        "change #time"          : "changeForm",
        "change #date"          : "changeForm",
        "change #mode"          : "changeForm",
        "change #optimize"          : "changeForm",
        "change #maxWalkDistance" : "changeForm"
    },

    initialize : function() {

        var view = this;

        this.updatingForm = false;

        this.model.on("change", function(data) {

            var reverseLookup = false;

            if(_.has(data.changed, 'fromPlace') && data.attributes.fromPlace &&  view.selectFrom) {

                var select = view.selectFrom[0].selectize;

                if(reverseLookup) {
                    $.ajax({
                        url: 'http://localhost:9000/r/' + encodeURIComponent(data.attributes.fromPlace),
                        type: 'GET',
                        error: function() {
                            view.updatingForm = true;

                            select.clearOptions();
                            select.addOption({address: "Marker location", latlon: data.attributes.fromPlace, lat: '', lon: '', city: '', state: ''});
                            select.setValue(data.attributes.fromPlace);

                            view.updatingForm = false;
                        },
                        success: function(res) {
                                view.updatingForm = true;
                                res.latlon = res.lat + ',' + res.lon;
                                select.clearOptions();
                                select.addOption(res);
                                select.setValue(res.latlon);
                                view.updatingForm = false;
                        }
                    });
                }
                else {

                    // this isn't great but appears to be neccesary as selectize triggers a change event even when programatically updated
                    view.updatingForm = true;

                    select.clearOptions();
                    select.addOption({address: "Marker location", latlon: data.attributes.fromPlace, lat: '', lon: '', city: '', state: ''});
                    select.setValue(data.attributes.fromPlace);

                    view.updatingForm = false;

                }
            }

            if(_.has(data.changed, 'toPlace') && data.attributes.toPlace && view.selectTo) {

                var select = view.selectTo[0].selectize;

                if(reverseLookup) {
                    $.ajax({
                        url: 'http://localhost:9000/r/' + encodeURIComponent(data.attributes.toPlace),
                        type: 'GET',
                        error: function() {
                            view.updatingForm = true;

                            select.clearOptions();
                            select.addOption({address: "Marker location", latlon: data.attributes.toPlace, lat: '', lon: '', city: '', state: ''});
                            select.setValue(data.attributes.toPlace);

                            view.updatingForm = false;
                        },
                        success: function(res) {
                                view.updatingForm = true;
                                res.latlon = res.lat + ',' + res.lon;
                                select.clearOptions();
                                select.addOption(res);
                                select.setValue(res.latlon);
                                view.updatingForm = false;
                        }
                    });
                }
                else {

                    view.updatingForm = true;

                    select.clearOptions();
                    select.addOption({address: "Marker location", latlon: data.attributes.toPlace, lat: '', lon: '', city: '', state: ''});
                    select.setValue(data.attributes.toPlace);

                    view.updatingForm = false;
                }
            }
        });
    },

    render : function() {

        var view = this;

        var html = requestFormTemplate();

        this.$el.html(html);

        view.updatingForm = true;

        $('#date').datepicker();
        $('#date').datepicker('setValue', new Date());

        $('#time').timepicker();

        this.bikeTriangle = new OTP.bike_views.OtpBikeTrianglePanel({
            model: this.model,
            el: $('#bikeTriangle')
        });

        var loadCallback = function(query, callback, select) {
            if (!query.length) return callback();
            $.ajax({
                url: 'http://localhost:9000/q/' + encodeURIComponent(query) + '/' + encodeURIComponent('Salem, OR'),
                type: 'GET',

                error: function() {
                    callback();
                },
                success: function(res) {
                        select.clearOptions();
                        var data = new Array();
                        for(var item in res.slice(0, 10)) {
                            res[item].latlon = res[item].lat + ',' + res[item].lon;
                            data.push(res[item]);
                        }

                        callback(data);
                }
            });
        }

        this.selectFrom = $('#fromPlace').selectize({
            valueField: 'latlon',
            labelField: 'address',
            searchField: 'address',
            loadThrottle: 1000,
            create: false,
            render: { option: function(item, escape) {
                    return geocodeItem(item);
                }
            },
            load: function(query, callback) {
                loadCallback(query, callback, view.selectFrom[0].selectize);
            }
        });

        this.selectTo = $('#toPlace').selectize({
            valueField: 'latlon',
            labelField: 'address',
            searchField: 'address',
            loadThrottle: 1000,
            create: false,
            render: { option: function(item, escape) {
                    return geocodeItem(item);
                }
            },
            load: function(query, callback) {
                loadCallback(query, callback, view.selectTo[0].selectize);
            }
        });

        view.updatingForm = false;

        this.changeForm();
    },

    changeForm: function(evt) {

        // skip duplicate change events caused by selectize form inputs
        if(this.updatingForm)
            return;

        var data = {
            fromPlace: $('#fromPlace').val(),
            toPlace: $('#toPlace').val(),
            date: $('#date').val(),
            time: $('#time').val(),
            arriveBy: $('#arriveBy').val(),
            maxWalkDistance: $('#maxWalkDistance').val(),
            //triangleSafetyFactor: $('#').val(),
            //triangleSlopeFactor: $('#').val(),
            //triangleTimeFactor: $('#').val(),
            optimize: $('#optimize').val(),
            mode: $('#mode').val()
        };

        this.model.set(data);

    }
});

module.exports.OtpRequestFormView = OtpRequestFormView;
<commit_msg>only show mode-relevant input controls<commit_after>var _ = require('underscore');
var $ = jQuery = require('jquery-browserify');
var Backbone = require('../lib/backbone');
var Handlebars = require('handlebars');

var requestFormTemplate = Handlebars.compile([

        '
',
            '<select id="fromPlace" class="apiParam" style="width: 100%" placeholder="Start Address"></select>',
        '
',
        '
',
            '<select id="toPlace" class="apiParam" style="width: 100%"  placeholder="End Address"></select>',
        '
',
        '
',
            '<select id="arriveBy" class="apiParam span3" placeholder="Arrive"><option value="true">Arrive</option><option value="false" selected>Depart</option></select>',

            '
',
                '<input id="time" type="text" class="apiParam input-small">',
                '',
            '
',

            '
',
                '<input id="date" class="apiParam input-small" size="16" type="text">',
            '
',

            '<form class="form-horizontal">',
                '
',
                    'Travel by: ',
                    '
',
                        '<select id="mode" class="apiParam span12" placeholder="Arrive"><option value="TRANSIT,WALK">Transit</option><option value="WALK">Walk only</option><option value="BICYCLE">Bike only</option><option value="TRANSIT,BICYCLE">Transit & Bike</option></select>',
                    '
',
                '
',

                '
',
                    'Find: ',
                    '
',
                        '<select id="optimize" class="span12" placeholder="Arrive"><option value="QUICK">Quickest trip</option><option value="TRANSFERS">Fewest transfers</option><option>Custom trip</option></select>',
                    '
',
                '
',

                '
',
                    '
',
                    '
',
                '
',

                '
',
                    'Maximum walk: ',
                    '
',
                        '<select id="maxWalkDistance" class="apiParam span12" placeholder="Arrive"><option value="402">1/4 mile</option><option value="804">1/2 mile</option><option value="1223" selected>3/4 mile</option><option value="1609">1 mile</option><option value="3218">2 miles</option><option value="4828">3 miles</option></select>',
                    '
',
                '
',

                '
',
                    'Maximum bike: ',
                    '
',
                        '<select id="maxBikeDistance" class="apiParam span12" placeholder="Arrive"><option value="804">1/2 mile</option><option value="1609">1 mile</option><option value="4026">2.5 miles</option><option value="8047">5 miles</option><option value="16093">10 miles</option></select>',
                    '
',
                '
',

            '</form>',
        '
'
].join('\n'));

var geocodeItem = Handlebars.compile([
    '
',
        '{{address}}',
        '{{city}}, {{state}}',
    '
'
].join('\n'));

var OtpRequestFormView = Backbone.View.extend({

    events: {
        "change .apiParam"      : "changeForm",
        "change #mode"          : "updateModeControls"
    },

    initialize : function() {

        var view = this;

        this.updatingForm = false;

        this.model.on("change", function(data) {

            var reverseLookup = false;

            if(_.has(data.changed, 'fromPlace') && data.attributes.fromPlace &&  view.selectFrom) {

                var select = view.selectFrom[0].selectize;

                if(reverseLookup) {
                    $.ajax({
                        url: 'http://localhost:9000/r/' + encodeURIComponent(data.attributes.fromPlace),
                        type: 'GET',
                        error: function() {
                            view.updatingForm = true;

                            select.clearOptions();
                            select.addOption({address: "Marker location", latlon: data.attributes.fromPlace, lat: '', lon: '', city: '', state: ''});
                            select.setValue(data.attributes.fromPlace);

                            view.updatingForm = false;
                        },
                        success: function(res) {
                                view.updatingForm = true;
                                res.latlon = res.lat + ',' + res.lon;
                                select.clearOptions();
                                select.addOption(res);
                                select.setValue(res.latlon);
                                view.updatingForm = false;
                        }
                    });
                }
                else {

                    // this isn't great but appears to be neccesary as selectize triggers a change event even when programatically updated
                    view.updatingForm = true;

                    select.clearOptions();
                    select.addOption({address: "Marker location", latlon: data.attributes.fromPlace, lat: '', lon: '', city: '', state: ''});
                    select.setValue(data.attributes.fromPlace);

                    view.updatingForm = false;

                }
            }

            if(_.has(data.changed, 'toPlace') && data.attributes.toPlace && view.selectTo) {

                var select = view.selectTo[0].selectize;

                if(reverseLookup) {
                    $.ajax({
                        url: 'http://localhost:9000/r/' + encodeURIComponent(data.attributes.toPlace),
                        type: 'GET',
                        error: function() {
                            view.updatingForm = true;

                            select.clearOptions();
                            select.addOption({address: "Marker location", latlon: data.attributes.toPlace, lat: '', lon: '', city: '', state: ''});
                            select.setValue(data.attributes.toPlace);

                            view.updatingForm = false;
                        },
                        success: function(res) {
                                view.updatingForm = true;
                                res.latlon = res.lat + ',' + res.lon;
                                select.clearOptions();
                                select.addOption(res);
                                select.setValue(res.latlon);
                                view.updatingForm = false;
                        }
                    });
                }
                else {

                    view.updatingForm = true;

                    select.clearOptions();
                    select.addOption({address: "Marker location", latlon: data.attributes.toPlace, lat: '', lon: '', city: '', state: ''});
                    select.setValue(data.attributes.toPlace);

                    view.updatingForm = false;
                }
            }
        });
    },

    render : function() {

        var view = this;

        var html = requestFormTemplate();

        this.$el.html(html);

        view.updatingForm = true;

        $('#date').datepicker();
        $('#date').datepicker('setValue', new Date());

        $('#time').timepicker();

        this.bikeTriangle = new OTP.bike_views.OtpBikeTrianglePanel({
            model: this.model,
            el: $('#bikeTriangle')
        });

        var loadCallback = function(query, callback, select) {
            if (!query.length) return callback();
            $.ajax({
                url: 'http://localhost:9000/q/' + encodeURIComponent(query) + '/' + encodeURIComponent('Salem, OR'),
                type: 'GET',

                error: function() {
                    callback();
                },
                success: function(res) {
                        select.clearOptions();
                        var data = new Array();
                        for(var item in res.slice(0, 10)) {
                            res[item].latlon = res[item].lat + ',' + res[item].lon;
                            data.push(res[item]);
                        }

                        callback(data);
                }
            });
        }

        this.selectFrom = $('#fromPlace').selectize({
            valueField: 'latlon',
            labelField: 'address',
            searchField: 'address',
            loadThrottle: 1000,
            create: false,
            render: { option: function(item, escape) {
                    return geocodeItem(item);
                }
            },
            load: function(query, callback) {
                loadCallback(query, callback, view.selectFrom[0].selectize);
            }
        });

        this.selectTo = $('#toPlace').selectize({
            valueField: 'latlon',
            labelField: 'address',
            searchField: 'address',
            loadThrottle: 1000,
            create: false,
            render: { option: function(item, escape) {
                    return geocodeItem(item);
                }
            },
            load: function(query, callback) {
                loadCallback(query, callback, view.selectTo[0].selectize);
            }
        });

        view.updatingForm = false;

        this.changeForm();

        this.updateModeControls();
    },

    changeForm: function(evt) {

        // skip duplicate change events caused by selectize form inputs
        if(this.updatingForm)
            return;

        var maxDistance = $('#mode').val().indexOf("WALK") != -1 ?
            $('#maxWalkDistance').val() : $('#maxBikeDistance').val()

        var data = {
            fromPlace: $('#fromPlace').val(),
            toPlace: $('#toPlace').val(),
            date: $('#date').val(),
            time: $('#time').val(),
            arriveBy: $('#arriveBy').val(),
            maxWalkDistance: maxDistance,
            //triangleSafetyFactor: $('#').val(),
            //triangleSlopeFactor: $('#').val(),
            //triangleTimeFactor: $('#').val(),
            optimize: $('#optimize').val(),
            mode: $('#mode').val()
        };

        this.model.set(data);

    },

    updateModeControls : function() {
        var mode = $('#mode').val();

        if(mode.indexOf("WALK") != -1 && mode.indexOf("TRANSIT") != -1) this.$el.find(".maxWalkControl").show();
        else this.$el.find(".maxWalkControl").hide();

        if(mode.indexOf("BICYCLE") != -1 && mode.indexOf("TRANSIT") != -1) this.$el.find(".maxBikeControl").show();
        else this.$el.find(".maxBikeControl").hide();

        if(mode.indexOf("TRANSIT") != -1) this.$el.find(".optimizeControl").show();
        else this.$el.find(".optimizeControl").hide();

        if(mode.indexOf("BICYCLE") != -1) this.$el.find(".bikeTriangleControl").show();
        else this.$el.find(".bikeTriangleControl").hide();
    }

});

module.exports.OtpRequestFormView = OtpRequestFormView;
<|endoftext|>


<commit_before>var _ = require('underscore');
var async = require('async');
var syncUtil = require('./util');
var DatasetClient = require('./DatasetClient');
var util = require('util');

var interceptors, ackQueue, pendingQueue, syncStorage, syncConfig;

var datasetClientsUpdateQueue;
/**
 * Add the given items to the given queue. For each item, also add the given extraParams.
 * @param {Array} items the items to add to the queue
 * @param {Object} extraParams extra data that should be added to each item
 * @param {MongodbQueue} targetQueue the queue to push the messages to
 * @param {Function} cb the callback function
 */
function addToQueue(items, extraParams, targetQueue, cb) {
  if (!items || items.length === 0) {
    return cb();
  }
  var itemsToPush = _.map(items, function(item){
    return _.extend({}, item, extraParams);
  });
  syncUtil.doLog(syncUtil.SYNC_LOGGER, "debug", "adding " + itemsToPush.length + " items to queue " + targetQueue.getName());
  targetQueue.addMany(itemsToPush, cb);
}

/**
 * Reformat the give processedUpdates array to an object format that is expected by the sync client
 * @param {Array} processedUpdates the array of updates to process
 * @returns {Object} an object that contains the updates with different types
 */
function formatUpdates(processedUpdates) {
  var updates = {
    hashes: {}
  };
  _.each(processedUpdates, function(update){
    var type = update.type;
    var hash = update.hash;
    updates.hashes[hash] = update;
    updates[type] = updates[type] || {};
    updates[type][hash] = update;
  });
  return updates;
}

/**
 * Remove all the records in `updatesInRequest` from `updatesInDb` if they exist
 * @param {Array} updatesInDb
 * @param {Array} updatesInRequest
 * @returns
 */
function removeUpdatesInRequest(updatesInDb, updatesInRequest) {
  var updatesNotInRequest = _.filter(updatesInDb, function(dbUpdate){
    var foundInRequest = _.findWhere(updatesInRequest, {hash: dbUpdate.hash});
    return !foundInRequest;
  });
  return updatesNotInRequest;
}

function processSyncAPI(datasetId, params, readDatasetClient, cb) {
  var queryParams = params.query_params || {};
  var metaData = params.meta_data || {};
  var cuid = syncUtil.getCuid(params);
  var datasetClient = new DatasetClient(datasetId, {queryParams: queryParams, metaData: metaData});
  var datasetClientFields = {id: datasetClient.getId(), datasetId: datasetId,  queryParams: queryParams, metaData: metaData, lastAccessed: Date.now()};
  async.parallel({
    pushDatasetClient: function(callback) {
      //after some loading testing, we noticed that upsertDatasetClient is an atomic operation and requires a lock. If there are  a lot operations on the same document (collection?) those operations will queued up in mongo, which blocks the request.
      //To avoid that, we push the updates into a queue and call upsertDatasetClient with a reasonable concurrency value to void the locking issue.
      datasetClientsUpdateQueue.push(datasetClientFields);
      return callback();
    },
    addAcks: function(callback) {
      syncUtil.doLog(datasetId, 'debug', 'adding acks to queue. size = ' + (params.acknowledgements && params.acknowledgements.length || 0));
      addToQueue(params.acknowledgements, {datasetId: datasetId, cuid: cuid}, ackQueue, callback);
    },
    addPendings: function(callback) {
      syncUtil.doLog(datasetId, 'debug', 'adding pendings to queue. size = ' + (params.pending && params.pending.length || 0));
      addToQueue(params.pending, {datasetId: datasetId, cuid: cuid, meta_data: metaData}, pendingQueue, callback);
    },
    processedUpdates: function(callback) {
      syncUtil.doLog(datasetId, 'debug', 'list updates for client cuid = ' + cuid);
      syncStorage.listUpdates(datasetId, {cuid: cuid}, callback);
    }
  }, function(err, results){
    if (err) {
      syncUtil.doLog(datasetId, 'error', 'sync request error = ' + util.inspect(err), params);
      return cb(err);
    } else {
      syncUtil.doLog(datasetId, 'debug', 'syn API results ' + util.inspect(results));
      var globalHash = readDatasetClient? readDatasetClient.globalHash: undefined;
      //the acknowledgements in the current request will be put on the queue and take some time to process, so don't return them if they are still in the db
      var remainingUpdates = removeUpdatesInRequest(results.processedUpdates, params.acknowledgements);
      var response = {hash: globalHash, updates: formatUpdates(remainingUpdates)};
      interceptors.responseInterceptor(datasetId, queryParams, function(err){
        if (err) {
          syncUtil.doLog(datasetId, 'debug', 'sync response interceptor returns error = ' + util.inspect(err), params);
          return cb(err);
        }
        syncUtil.doLog(datasetId, 'debug', 'sync API response ' + util.inspect(response));
        return cb(null, response);
      });
    }
  });
}

/**
 * Process the sync request. It will
 * - validate the request body via the requestInterceptor
 * - check if the dataset client is stopped for sync
 * - create or update the dataset client
 * - process acknowledgements, push each of them to the ackQueue
 * - process pending changes, push each of them to the pendingQueue
 * - list any processed updates for the given client
 * @param {String} datasetId the id of the dataset
 * @param {Object} params the request body, it normally contain those fields:
 * @param {Object} params.query_params the query parameter for the dataset from the client
 * @param {Object} params.meta_data the meta data for the dataset from the client
 * @param {Object} params._fh an object added by the client sdk. it could have the `cuid` of the client
 * @param {Array} params.pending the pending changes array
 * @param {Array} params.acknowledgements the acknowledgements from the client
 * @param {Function} cb the callback function
 */
function sync(datasetId, params, cb) {
  syncUtil.doLog(datasetId, 'debug', 'process sync request for dataset ' + datasetId);
  var queryParams = params.query_params || {};
  var metaData = params.meta_data || {};
  var datasetClient = new DatasetClient(datasetId, {queryParams: queryParams, metaData: metaData});
  syncUtil.doLog(datasetId, 'debug', 'processing sync API request :: query_params = ' + util.inspect(queryParams) + ' :: meta_data = ' + util.inspect(metaData));
  async.series({
    requestInterceptor: async.apply(interceptors.requestInterceptor, datasetId, params),
    readDatasetClient: function checkDatasetclientStopped(callback) {
      syncStorage.readDatasetClient(datasetClient.getId(), function(err, datasetClientJson){
        if (err) {
          return callback(err);
        }
        if (datasetClientJson && datasetClientJson.stopped === true) {
          return callback(new Error('sync stopped for dataset ' + datasetId));
        } else {
          return callback(null, datasetClientJson);
        }
      });
    }
  }, function(err, results){
    if (err) {
      syncUtil.doLog(datasetId, 'debug', 'sync request returns error = ' + util.inspect(err), params);
      return cb(err);
    }
    return processSyncAPI(datasetId, params, results.readDatasetClient, cb);
  });
}

module.exports = function(interceptorsImpl, ackQueueImpl, pendingQueueImpl, syncStorageImpl, syncConfigInst){
  interceptors = interceptorsImpl;
  ackQueue = ackQueueImpl;
  pendingQueue = pendingQueueImpl;
  syncStorage = syncStorageImpl;
  syncConfig = syncConfigInst;
  datasetClientsUpdateQueue = async.queue(function(datasetClientJson, cb){
    syncStorage.upsertDatasetClient(datasetClientJson.id, datasetClientJson, cb);
  }, syncConfig.datasetClientUpdateConcurrency || 10);
  return sync;
}
<commit_msg>add comment<commit_after>var _ = require('underscore');
var async = require('async');
var syncUtil = require('./util');
var DatasetClient = require('./DatasetClient');
var util = require('util');

var interceptors, ackQueue, pendingQueue, syncStorage, syncConfig;

var datasetClientsUpdateQueue;
/**
 * Add the given items to the given queue. For each item, also add the given extraParams.
 * @param {Array} items the items to add to the queue
 * @param {Object} extraParams extra data that should be added to each item
 * @param {MongodbQueue} targetQueue the queue to push the messages to
 * @param {Function} cb the callback function
 */
function addToQueue(items, extraParams, targetQueue, cb) {
  if (!items || items.length === 0) {
    return cb();
  }
  var itemsToPush = _.map(items, function(item){
    return _.extend({}, item, extraParams);
  });
  syncUtil.doLog(syncUtil.SYNC_LOGGER, "debug", "adding " + itemsToPush.length + " items to queue " + targetQueue.getName());
  targetQueue.addMany(itemsToPush, cb);
}

/**
 * Reformat the give processedUpdates array to an object format that is expected by the sync client
 * @param {Array} processedUpdates the array of updates to process
 * @returns {Object} an object that contains the updates with different types
 */
function formatUpdates(processedUpdates) {
  var updates = {
    hashes: {}
  };
  _.each(processedUpdates, function(update){
    var type = update.type;
    var hash = update.hash;
    updates.hashes[hash] = update;
    updates[type] = updates[type] || {};
    updates[type][hash] = update;
  });
  return updates;
}

/**
 * Remove all the records in `updatesInRequest` from `updatesInDb` if they exist
 * @param {Array} updatesInDb
 * @param {Array} updatesInRequest
 * @returns
 */
function removeUpdatesInRequest(updatesInDb, updatesInRequest) {
  var updatesNotInRequest = _.filter(updatesInDb, function(dbUpdate){
    var foundInRequest = _.findWhere(updatesInRequest, {hash: dbUpdate.hash});
    return !foundInRequest;
  });
  return updatesNotInRequest;
}

function processSyncAPI(datasetId, params, readDatasetClient, cb) {
  var queryParams = params.query_params || {};
  var metaData = params.meta_data || {};
  var cuid = syncUtil.getCuid(params);
  var datasetClient = new DatasetClient(datasetId, {queryParams: queryParams, metaData: metaData});
  var datasetClientFields = {id: datasetClient.getId(), datasetId: datasetId,  queryParams: queryParams, metaData: metaData, lastAccessed: Date.now()};
  async.parallel({
    pushDatasetClient: function(callback) {
      //after some loading testing, we noticed that upsertDatasetClient is an atomic operation and requires a lock. If there are  a lot operations on the same document (collection?) those operations will queued up in mongo, which blocks the request.
      //To avoid that, we push the updates into a queue and call upsertDatasetClient with a reasonable concurrency value to void the locking issue.
      datasetClientsUpdateQueue.push(datasetClientFields);
      return callback();
    },
    addAcks: function(callback) {
      syncUtil.doLog(datasetId, 'debug', 'adding acks to queue. size = ' + (params.acknowledgements && params.acknowledgements.length || 0));
      addToQueue(params.acknowledgements, {datasetId: datasetId, cuid: cuid}, ackQueue, callback);
    },
    addPendings: function(callback) {
      syncUtil.doLog(datasetId, 'debug', 'adding pendings to queue. size = ' + (params.pending && params.pending.length || 0));
      addToQueue(params.pending, {datasetId: datasetId, cuid: cuid, meta_data: metaData}, pendingQueue, callback);
    },
    processedUpdates: function(callback) {
      syncUtil.doLog(datasetId, 'debug', 'list updates for client cuid = ' + cuid);
      syncStorage.listUpdates(datasetId, {cuid: cuid}, callback);
    }
  }, function(err, results){
    if (err) {
      syncUtil.doLog(datasetId, 'error', 'sync request error = ' + util.inspect(err), params);
      return cb(err);
    } else {
      syncUtil.doLog(datasetId, 'debug', 'syn API results ' + util.inspect(results));
      var globalHash = readDatasetClient? readDatasetClient.globalHash: undefined;
      //the acknowledgements in the current request will be put on the queue and take some time to process, so don't return them if they are still in the db
      var remainingUpdates = removeUpdatesInRequest(results.processedUpdates, params.acknowledgements);
      var response = {hash: globalHash, updates: formatUpdates(remainingUpdates)};
      interceptors.responseInterceptor(datasetId, queryParams, function(err){
        if (err) {
          syncUtil.doLog(datasetId, 'debug', 'sync response interceptor returns error = ' + util.inspect(err), params);
          return cb(err);
        }
        syncUtil.doLog(datasetId, 'debug', 'sync API response ' + util.inspect(response));
        return cb(null, response);
      });
    }
  });
}

/**
 * Process the sync request. It will
 * - validate the request body via the requestInterceptor
 * - check if the dataset client is stopped for sync
 * - create or update the dataset client
 * - process acknowledgements, push each of them to the ackQueue
 * - process pending changes, push each of them to the pendingQueue
 * - list any processed updates for the given client
 * @param {String} datasetId the id of the dataset
 * @param {Object} params the request body, it normally contain those fields:
 * @param {Object} params.query_params the query parameter for the dataset from the client
 * @param {Object} params.meta_data the meta data for the dataset from the client
 * @param {Object} params._fh an object added by the client sdk. it could have the `cuid` of the client
 * @param {Array} params.pending the pending changes array
 * @param {Array} params.acknowledgements the acknowledgements from the client
 * @param {Function} cb the callback function
 */
function sync(datasetId, params, cb) {
  syncUtil.doLog(datasetId, 'debug', 'process sync request for dataset ' + datasetId);
  var queryParams = params.query_params || {};
  var metaData = params.meta_data || {};
  var datasetClient = new DatasetClient(datasetId, {queryParams: queryParams, metaData: metaData});
  syncUtil.doLog(datasetId, 'debug', 'processing sync API request :: query_params = ' + util.inspect(queryParams) + ' :: meta_data = ' + util.inspect(metaData));
  async.series({
    requestInterceptor: async.apply(interceptors.requestInterceptor, datasetId, params),
    readDatasetClient: function checkDatasetclientStopped(callback) {
      syncStorage.readDatasetClient(datasetClient.getId(), function(err, datasetClientJson){
        if (err) {
          return callback(err);
        }
        if (datasetClientJson && datasetClientJson.stopped === true) {
          return callback(new Error('sync stopped for dataset ' + datasetId));
        } else {
          return callback(null, datasetClientJson);
        }
      });
    }
  }, function(err, results){
    if (err) {
      syncUtil.doLog(datasetId, 'debug', 'sync request returns error = ' + util.inspect(err), params);
      return cb(err);
    }
    return processSyncAPI(datasetId, params, results.readDatasetClient, cb);
  });
}

module.exports = function(interceptorsImpl, ackQueueImpl, pendingQueueImpl, syncStorageImpl, syncConfigInst){
  interceptors = interceptorsImpl;
  ackQueue = ackQueueImpl;
  pendingQueue = pendingQueueImpl;
  syncStorage = syncStorageImpl;
  syncConfig = syncConfigInst;
  //we just use an in-memory queue here to update the dataset clients, the main purpose of the update here is:
  //1. make sure a new dataset client will be created if it's never created before. 2. update the `lastAccessed` field of an existing dataset client to determine when a dataset client should be set to "inactive".
  //So even if the app crashes, and some of those records are lost, it means some of the datasetclients may not be synced.
  //But as soon as the clients makes another request, it will be synced again. If the clients doesn't sent the request, then the dataset client should not be synced anyway.
  datasetClientsUpdateQueue = async.queue(function(datasetClientJson, cb){
    syncStorage.upsertDatasetClient(datasetClientJson.id, datasetClientJson, cb);
  }, syncConfig.datasetClientUpdateConcurrency || 10);
  return sync;
}
<|endoftext|>