<commit_before>// @flow

import startsWith from 'lodash/startsWith'
import series from 'p-series'
import {status, json} from '@gob/lib'
import debug from 'debug'
import {Notification} from './lib-dispatch'
import needsUpdate from './needs-update'
import updateDatabase from './update-database'
import removeDuplicateAreas from './remove-duplicate-areas'
import type {InfoFileTypeEnum, InfoFileRef, InfoIndexFile} from './types'
const log = debug('worker:load-data:load-files')

const fetchJson = (...args): Promise<mixed> => {
	return fetch(...args)
		.then(status)
		.then(json)
}

type Args = {|
	baseUrl: string,
	notification: Notification,
	oldestYear: number,
	type: InfoFileTypeEnum,
|}

export default function loadFiles(url: string, baseUrl: string) {
	log(url)

	return fetchJson(url)
		.then(data => proceedWithUpdate(baseUrl, ((data: any): InfoIndexFile)))
		.catch(err => handleErrors(err, url))
}

export async function proceedWithUpdate(baseUrl: string, data: InfoIndexFile) {
	const type: InfoFileTypeEnum = data.type
	const notification = new Notification(type)
	const oldestYear = new Date().getFullYear() - 5
	const args = {type, notification, baseUrl, oldestYear}

	const files = await getFilesToLoad(args, data)
	const filtered = await filterFiles(args, files)
	await slurpIntoDatabase(args, filtered)

	await deduplicateAreas(args)
	await finishUp(args)
}

export function getFilesToLoad({type, oldestYear}: Args, data: InfoIndexFile) {
	let files = data.files

	if (type === 'courses') {
		files = files.filter(f => filterForRecentCourses(f, oldestYear))
	}

	return files
}

export async function filterFiles(
	{type}: Args,
	files: InfoFileRef[],
): Promise<Array<InfoFileRef>> {
	// For each file, see if it needs loading. We then update each promise
	// with either the path or `null`.
	const promises = files.map(async (file: InfoFileRef) => {
		if (await needsUpdate(type, file.path, file.hash)) {
			return file
		}
		return null
	})

	// Finally, we filter the items
	// $FlowFixMe
	return (await Promise.all(promises)).filter(Boolean)
}

export function slurpIntoDatabase(
	{type, baseUrl, notification}: Args,
	files: Array<InfoFileRef>,
) {
	// Exit early if nothing needs to happen
	if (files.length === 0) {
		log(`[${type}] no files need loading`)
		return
	}

	log(`[${type}] these files need loading:`, ...files)

	// Fire off the progress bar
	notification.start(files.length)

	// Load them into the database
	const runUpdate = file => () =>
		updateDatabase(type, baseUrl, notification, file)
	return series(files.map(runUpdate))
}

export function deduplicateAreas({type}: Args) {
	// Clean up the database a bit
	if (type === 'areas') {
		return removeDuplicateAreas()
	}
}

export function finishUp({type, notification}: Args) {
	// Remove the progress bar after 1.5 seconds
	notification.remove()
}

function handleErrors(err: Error, url: string) {
	if (startsWith(err.message, 'Failed to fetch')) {
		log(`Failed to fetch ${url}`)
		return
	}
	throw err
}

export function filterForRecentCourses(file: InfoFileRef, oldestYear: number) {
	// Only download the json courses
	const isJson = file.type === 'json'

	// Only get the last four years of data
	const isRecent = file.year && file.year >= oldestYear

	return isJson && isRecent
}
<commit_msg>remove unused arg from load-files<commit_after>// @flow

import startsWith from 'lodash/startsWith'
import series from 'p-series'
import {status, json} from '@gob/lib'
import debug from 'debug'
import {Notification} from './lib-dispatch'
import needsUpdate from './needs-update'
import updateDatabase from './update-database'
import removeDuplicateAreas from './remove-duplicate-areas'
import type {InfoFileTypeEnum, InfoFileRef, InfoIndexFile} from './types'
const log = debug('worker:load-data:load-files')

const fetchJson = (...args): Promise<mixed> => {
	return fetch(...args)
		.then(status)
		.then(json)
}

type Args = {|
	baseUrl: string,
	notification: Notification,
	oldestYear: number,
	type: InfoFileTypeEnum,
|}

export default function loadFiles(url: string, baseUrl: string) {
	log(url)

	return fetchJson(url)
		.then(data => proceedWithUpdate(baseUrl, ((data: any): InfoIndexFile)))
		.catch(err => handleErrors(err, url))
}

export async function proceedWithUpdate(baseUrl: string, data: InfoIndexFile) {
	const type: InfoFileTypeEnum = data.type
	const notification = new Notification(type)
	const oldestYear = new Date().getFullYear() - 5
	const args = {type, notification, baseUrl, oldestYear}

	const files = await getFilesToLoad(args, data)
	const filtered = await filterFiles(args, files)
	await slurpIntoDatabase(args, filtered)

	await deduplicateAreas(args)
	await finishUp(args)
}

export function getFilesToLoad({type, oldestYear}: Args, data: InfoIndexFile) {
	let files = data.files

	if (type === 'courses') {
		files = files.filter(f => filterForRecentCourses(f, oldestYear))
	}

	return files
}

export async function filterFiles(
	{type}: Args,
	files: InfoFileRef[],
): Promise<Array<InfoFileRef>> {
	// For each file, see if it needs loading. We then update each promise
	// with either the path or `null`.
	const promises = files.map(async (file: InfoFileRef) => {
		if (await needsUpdate(type, file.path, file.hash)) {
			return file
		}
		return null
	})

	// Finally, we filter the items
	// $FlowFixMe
	return (await Promise.all(promises)).filter(Boolean)
}

export function slurpIntoDatabase(
	{type, baseUrl, notification}: Args,
	files: Array<InfoFileRef>,
) {
	// Exit early if nothing needs to happen
	if (files.length === 0) {
		log(`[${type}] no files need loading`)
		return
	}

	log(`[${type}] these files need loading:`, ...files)

	// Fire off the progress bar
	notification.start(files.length)

	// Load them into the database
	const runUpdate = file => () =>
		updateDatabase(type, baseUrl, notification, file)
	return series(files.map(runUpdate))
}

export function deduplicateAreas({type}: Args) {
	// Clean up the database a bit
	if (type === 'areas') {
		return removeDuplicateAreas()
	}
}

export function finishUp({notification}: Args) {
	// Remove the progress bar after 1.5 seconds
	notification.remove()
}

function handleErrors(err: Error, url: string) {
	if (startsWith(err.message, 'Failed to fetch')) {
		log(`Failed to fetch ${url}`)
		return
	}
	throw err
}

export function filterForRecentCourses(file: InfoFileRef, oldestYear: number) {
	// Only download the json courses
	const isJson = file.type === 'json'

	// Only get the last four years of data
	const isRecent = file.year && file.year >= oldestYear

	return isJson && isRecent
}
<|endoftext|>


<commit_before>import * as types from 'constants/action_types'
import lbry from 'lbry'
import lbryio from 'lbryio'
import lbryuri from 'lbryuri'
import rewards from 'rewards'
import {
  selectBalance,
} from 'selectors/wallet'
import {
  selectFileInfoForUri,
  selectUrisDownloading,
} from 'selectors/file_info'
import {
  selectResolvingUris
} from 'selectors/content'
import {
  selectCostInfoForUri,
} from 'selectors/cost_info'
import {
  selectClaimsByUri,
} from 'selectors/claims'
import {
  doOpenModal,
} from 'actions/app'

export function doResolveUri(uri) {
  return function(dispatch, getState) {

    uri = lbryuri.normalize(uri)

    const state = getState()
    const alreadyResolving = selectResolvingUris(state).indexOf(uri) !== -1

    if (!alreadyResolving) {
      dispatch({
        type: types.RESOLVE_URI_STARTED,
        data: { uri }
      })

      lbry.resolve({ uri }).then((resolutionInfo) => {
        const {
          claim,
          certificate,
        } = resolutionInfo ? resolutionInfo : { claim : null, certificate: null }

        dispatch({
          type: types.RESOLVE_URI_COMPLETED,
          data: {
            uri,
            claim,
            certificate,
          }
        })
      })
    }
  }
}

export function doCancelResolveUri(uri) {
  return function(dispatch, getState) {
    lbry.cancelResolve({ uri })
    dispatch({
      type: types.RESOLVE_URI_CANCELED,
      data: { uri }
    })
  }
}

export function doFetchFeaturedUris() {
  return function(dispatch, getState) {
    const state = getState()

    dispatch({
      type: types.FETCH_FEATURED_CONTENT_STARTED,
    })

    const success = ({ Categories, Uris }) => {

      let featuredUris = {}

      Categories.forEach((category) => {
        if (Uris[category] && Uris[category].length) {
          featuredUris[category] = Uris[category]
        }
      })

      dispatch({
        type: types.FETCH_FEATURED_CONTENT_COMPLETED,
        data: {
          categories: Categories,
          uris: featuredUris,
        }
      })
    }

    const failure = () => {
      dispatch({
        type: types.FETCH_FEATURED_CONTENT_COMPLETED,
        data: {
          categories: [],
          uris: {}
        }
      })
    }

    lbryio.call('discover', 'list', { version: "early-access" } )
      .then(success, failure)
  }
}

export function doUpdateLoadStatus(uri, outpoint) {
  return function(dispatch, getState) {
    const state = getState()

    lbry.file_list({
      outpoint: outpoint,
      full_status: true,
    }).then(([fileInfo]) => {
      if(!fileInfo || fileInfo.written_bytes == 0) {
        // download hasn't started yet
        setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
      } else if (fileInfo.completed) {
        // TODO this isn't going to get called if they reload the client before
        // the download finished
        rewards.claimEligiblePurchaseRewards()
        dispatch({
          type: types.DOWNLOADING_COMPLETED,
          data: {
            uri,
            outpoint,
            fileInfo,
          }
        })
      } else {
        // ready to play
        const {
          total_bytes,
          written_bytes,
        } = fileInfo
        const progress = (written_bytes / total_bytes) * 100

        dispatch({
          type: types.DOWNLOADING_PROGRESSED,
          data: {
            uri,
            outpoint,
            fileInfo,
            progress,
          }
        })
        setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
      }
    })
  }
}

export function doDownloadFile(uri, streamInfo) {
  return function(dispatch, getState) {
    const state = getState()

    lbry.file_list({ outpoint: streamInfo.outpoint, full_status: true }).then(([fileInfo]) => {
      dispatch({
        type: types.DOWNLOADING_STARTED,
        data: {
          uri,
          outpoint: streamInfo.outpoint,
          fileInfo,
        }
      })
    })

    lbryio.call('file', 'view', {
      uri: uri,
      outpoint: streamInfo.outpoint,
      claimId: streamInfo.claim_id,
    }).catch(() => {})
    dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint))
  }
}

export function doLoadVideo(uri) {
  return function(dispatch, getState) {
    const state = getState()

    dispatch({
      type: types.LOADING_VIDEO_STARTED,
      data: {
        uri
      }
    })

    lbry.get({ uri }).then(streamInfo => {
      const timeout = streamInfo === null ||
        typeof streamInfo !== 'object' ||
        streamInfo.error == 'Timeout'

      if(timeout) {
        dispatch({
          type: types.LOADING_VIDEO_FAILED,
          data: { uri }
        })
        dispatch(doOpenModal('timedOut'))
      } else {
        dispatch(doDownloadFile(uri, streamInfo))
      }
    })
  }
}

export function doPurchaseUri(uri) {
  return function(dispatch, getState) {
    const state = getState()
    const balance = selectBalance(state)
    const fileInfo = selectFileInfoForUri(state, { uri })
    const costInfo = selectCostInfoForUri(state, { uri })
    const downloadingByUri = selectUrisDownloading(state)
    const alreadyDownloading = !!downloadingByUri[uri]
    const { cost } = costInfo

    // BUG if you delete a file from the file system system you're going to be
    // asked to pay for it again. We need to check if the file is in the blobs
    // here and then dispatch doLoadVideo() which will reconstruct it again from
    // the blobs. Or perhaps there's another way to see if a file was already
    // purchased?

    // we already fully downloaded the file. If completed is true but
    // writtenBytes is false then we downloaded it before but deleted it again,
    // which means it needs to be reconstructed from the blobs by dispatching
    // doLoadVideo.
    if (fileInfo && fileInfo.completed && !!fileInfo.writtenBytes) {
      return Promise.resolve()
    }

    // we are already downloading the file
    if (alreadyDownloading) {
      return Promise.resolve()
    }

    // the file is free or we have partially downloaded it
    if (cost <= 0.01 || (fileInfo && fileInfo.download_directory)) {
      dispatch(doLoadVideo(uri))
      return Promise.resolve()
    }

    if (cost > balance) {
      dispatch(doOpenModal('notEnoughCredits'))
    } else {
      dispatch(doOpenModal('affirmPurchase'))
    }

    return Promise.resolve()
  }
}

export function doFetchClaimsByChannel(uri) {
  return function(dispatch, getState) {
    dispatch({
      type: types.FETCH_CHANNEL_CLAIMS_STARTED,
      data: { uri }
    })

    lbry.resolve({ uri }).then((resolutionInfo) => {
      const {
        claims_in_channel,
      } = resolutionInfo ? resolutionInfo : { claims_in_channel: [] }

      dispatch({
        type: types.FETCH_CHANNEL_CLAIMS_COMPLETED,
        data: {
          uri,
          claims: claims_in_channel
        }
      })
    })
  }
}

export function doClaimListMine() {
  return function(dispatch, getState) {
    dispatch({
      type: types.CLAIM_LIST_MINE_STARTED
    })

    lbry.claim_list_mine().then((claims) => {
      dispatch({
        type: types.CLAIM_LIST_MINE_COMPLETED,
        data: {
          claims
        }
      })
    })
  }
}<commit_msg>move up time of rewad claim for downloads<commit_after>import * as types from 'constants/action_types'
import lbry from 'lbry'
import lbryio from 'lbryio'
import lbryuri from 'lbryuri'
import rewards from 'rewards'
import {
  selectBalance,
} from 'selectors/wallet'
import {
  selectFileInfoForUri,
  selectUrisDownloading,
} from 'selectors/file_info'
import {
  selectResolvingUris
} from 'selectors/content'
import {
  selectCostInfoForUri,
} from 'selectors/cost_info'
import {
  selectClaimsByUri,
} from 'selectors/claims'
import {
  doOpenModal,
} from 'actions/app'

export function doResolveUri(uri) {
  return function(dispatch, getState) {

    uri = lbryuri.normalize(uri)

    const state = getState()
    const alreadyResolving = selectResolvingUris(state).indexOf(uri) !== -1

    if (!alreadyResolving) {
      dispatch({
        type: types.RESOLVE_URI_STARTED,
        data: { uri }
      })

      lbry.resolve({ uri }).then((resolutionInfo) => {
        const {
          claim,
          certificate,
        } = resolutionInfo ? resolutionInfo : { claim : null, certificate: null }

        dispatch({
          type: types.RESOLVE_URI_COMPLETED,
          data: {
            uri,
            claim,
            certificate,
          }
        })
      })
    }
  }
}

export function doCancelResolveUri(uri) {
  return function(dispatch, getState) {
    lbry.cancelResolve({ uri })
    dispatch({
      type: types.RESOLVE_URI_CANCELED,
      data: { uri }
    })
  }
}

export function doFetchFeaturedUris() {
  return function(dispatch, getState) {
    const state = getState()

    dispatch({
      type: types.FETCH_FEATURED_CONTENT_STARTED,
    })

    const success = ({ Categories, Uris }) => {

      let featuredUris = {}

      Categories.forEach((category) => {
        if (Uris[category] && Uris[category].length) {
          featuredUris[category] = Uris[category]
        }
      })

      dispatch({
        type: types.FETCH_FEATURED_CONTENT_COMPLETED,
        data: {
          categories: Categories,
          uris: featuredUris,
        }
      })
    }

    const failure = () => {
      dispatch({
        type: types.FETCH_FEATURED_CONTENT_COMPLETED,
        data: {
          categories: [],
          uris: {}
        }
      })
    }

    lbryio.call('discover', 'list', { version: "early-access" } )
      .then(success, failure)
  }
}

export function doUpdateLoadStatus(uri, outpoint) {
  return function(dispatch, getState) {
    const state = getState()

    lbry.file_list({
      outpoint: outpoint,
      full_status: true,
    }).then(([fileInfo]) => {
      if(!fileInfo || fileInfo.written_bytes == 0) {
        // download hasn't started yet
        setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
      } else if (fileInfo.completed) {
        // TODO this isn't going to get called if they reload the client before
        // the download finished
        dispatch({
          type: types.DOWNLOADING_COMPLETED,
          data: {
            uri,
            outpoint,
            fileInfo,
          }
        })
      } else {
        // ready to play
        const {
          total_bytes,
          written_bytes,
        } = fileInfo
        const progress = (written_bytes / total_bytes) * 100

        dispatch({
          type: types.DOWNLOADING_PROGRESSED,
          data: {
            uri,
            outpoint,
            fileInfo,
            progress,
          }
        })
        setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
      }
    })
  }
}

export function doDownloadFile(uri, streamInfo) {
  return function(dispatch, getState) {
    const state = getState()

    lbry.file_list({ outpoint: streamInfo.outpoint, full_status: true }).then(([fileInfo]) => {
      dispatch({
        type: types.DOWNLOADING_STARTED,
        data: {
          uri,
          outpoint: streamInfo.outpoint,
          fileInfo,
        }
      })
    })

    lbryio.call('file', 'view', {
      uri: uri,
      outpoint: streamInfo.outpoint,
      claimId: streamInfo.claim_id,
    }).catch(() => {})

    rewards.claimEligiblePurchaseRewards()

    dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint))
  }
}

export function doLoadVideo(uri) {
  return function(dispatch, getState) {
    const state = getState()

    dispatch({
      type: types.LOADING_VIDEO_STARTED,
      data: {
        uri
      }
    })

    lbry.get({ uri }).then(streamInfo => {
      const timeout = streamInfo === null ||
        typeof streamInfo !== 'object' ||
        streamInfo.error == 'Timeout'

      if(timeout) {
        dispatch({
          type: types.LOADING_VIDEO_FAILED,
          data: { uri }
        })
        dispatch(doOpenModal('timedOut'))
      } else {
        dispatch(doDownloadFile(uri, streamInfo))
      }
    })
  }
}

export function doPurchaseUri(uri) {
  return function(dispatch, getState) {
    const state = getState()
    const balance = selectBalance(state)
    const fileInfo = selectFileInfoForUri(state, { uri })
    const costInfo = selectCostInfoForUri(state, { uri })
    const downloadingByUri = selectUrisDownloading(state)
    const alreadyDownloading = !!downloadingByUri[uri]
    const { cost } = costInfo

    // BUG if you delete a file from the file system system you're going to be
    // asked to pay for it again. We need to check if the file is in the blobs
    // here and then dispatch doLoadVideo() which will reconstruct it again from
    // the blobs. Or perhaps there's another way to see if a file was already
    // purchased?

    // we already fully downloaded the file. If completed is true but
    // writtenBytes is false then we downloaded it before but deleted it again,
    // which means it needs to be reconstructed from the blobs by dispatching
    // doLoadVideo.
    if (fileInfo && fileInfo.completed && !!fileInfo.writtenBytes) {
      return Promise.resolve()
    }

    // we are already downloading the file
    if (alreadyDownloading) {
      return Promise.resolve()
    }

    // the file is free or we have partially downloaded it
    if (cost <= 0.01 || (fileInfo && fileInfo.download_directory)) {
      dispatch(doLoadVideo(uri))
      return Promise.resolve()
    }

    if (cost > balance) {
      dispatch(doOpenModal('notEnoughCredits'))
    } else {
      dispatch(doOpenModal('affirmPurchase'))
    }

    return Promise.resolve()
  }
}

export function doFetchClaimsByChannel(uri) {
  return function(dispatch, getState) {
    dispatch({
      type: types.FETCH_CHANNEL_CLAIMS_STARTED,
      data: { uri }
    })

    lbry.resolve({ uri }).then((resolutionInfo) => {
      const {
        claims_in_channel,
      } = resolutionInfo ? resolutionInfo : { claims_in_channel: [] }

      dispatch({
        type: types.FETCH_CHANNEL_CLAIMS_COMPLETED,
        data: {
          uri,
          claims: claims_in_channel
        }
      })
    })
  }
}

export function doClaimListMine() {
  return function(dispatch, getState) {
    dispatch({
      type: types.CLAIM_LIST_MINE_STARTED
    })

    lbry.claim_list_mine().then((claims) => {
      dispatch({
        type: types.CLAIM_LIST_MINE_COMPLETED,
        data: {
          claims
        }
      })
    })
  }
}<|endoftext|>


<commit_before>'use strict';

var P = require('bluebird');

/**
 * The (abstract) base class for Controllers.
 * Each controller is a state machine. A state might instantiate a different controller (which also has a state).
 * There is usually a single Controller that everything begins with (the FrontController).
 * If a route is assigned, it simply defines the hierarchy of states it must assign, for
 * example ['page', 'contact', 'map'] would cause the FrontController to enter the state 'page', this state
 * creates a PageController which is assigned the state 'contact', and so on.
 * @param {ClientContext} context
 * @constructor
 */
function Controller(context)
{
        if (this.constructor === Controller)
        {
                throw Error('Controller is abstract');
        }

        Object.defineProperty(this, 'context' , { value: context });

        this.currentState = null;
        this.__child__ = null;
        this.__parent__ = null;
}

module.exports = Controller;
Object.defineProperty(Controller.prototype, 'isController', { value: true });

Object.defineProperty(Controller.prototype, 'child', {
        get: function()
        {
                return this.__child__;
        },
        set: function(value)
        {
                if (value && value.isController !== true)
                {
                        throw Error('value should be a Controller or null');
                }

                if (this.__child__)
                {
                        this.__child__.__parent__ = null;
                }

                this.__child__ = value;

                if (this.__child__)
                {
                        this.__child__.__parent__ = this;
                }
        }
});

Object.defineProperty(Controller.prototype, 'parent', {
        get: function()
        {
                return this.__parent__;
        },
        set: function(value)
        {
                if (value && value.isController !== true)
                {
                        throw Error('value should be a Controller or null');
                }

                if (this.__parent__)
                {
                        this.__parent__.__child__ = null;
                }

                this.__parent__ = value;

                if (this.__parent__)
                {
                        this.__parent__.__child__ = this;
                }
        }
});

/**
 * @example Controller.stateMethodName('enter', 'foo') // 'enterFoo'
 * @example Controller.stateMethodName('enter', {abc: 5, stateName: 'enterFoo', ...} // 'enterFoo'
 * @param {string} prefix
 * @param {string} state
 * @returns {string}
 */
Controller.stateMethodName = function(prefix, state)
{
        if (typeof state === 'object')
        {
                state = state.stateName;
        }

        state = state.toString();

        return prefix + state.charAt(0).toUpperCase() + state.slice(1);
};

/**
 * Are two states equal?.
 * If both states are a string, case sensitive string matching is used.
 * If stateA is an object, return stateA.isStateEqual(stateB)
 * @param {ControllerState} stateA
 * @param {ControllerState} stateB
 * @returns {boolean}
 */
Controller.statesEqual = function(stateA, stateB)
{
        if (stateA &&
            stateB &&
            typeof stateA === 'object')
        {
                if (!stateA.isStateEqual)
                {
                        throw Error('If a state is an object, it must implement a isStateEqual(other) method');
                }

                return stateA.isStateEqual(stateB);
        }

        return stateA === stateB;
};

/**
 * Assign a chain of states to this controller and its children.
 * Each controller has 0 or 1 child controllers. When a controller enters a state, it might create a child controller
 * for that state (by setting the child attribute on its controller).
 * That child controller is also assigned a state by this method (the next one in the chain).
 *
 * @example
 * myController.state('pages', 'contact', 'foo'); // myController is set to the state 'pages',
 *                                                // its child controller is set to 'contact', and so on.
 * @param {?string...} state The state chain (vararg or an array), or null to leave the state without setting a new one.
 * @return {!Promise}
 */
Controller.prototype.state = function(state)
{
        var args;

        var childLeave = function()
        {
                if (this.child)
                {
                        return this.child.state([null]);
                }
        }.bind(this);

        var myLeave = function()
        {
                if (this.currentState)
                {
                        try
                        {
                                return this.leave();
                        }
                        finally
                        {
                                this.currentState = null;
                                this.child = null;
                        }
                }
        }.bind(this);

        var myEnter = function()
        {
                if (state)
                {
                        this.currentState = state;
                        return this.enter(state);
                }
        }.bind(this);

        var childEnter = function()
        {
                if (args.length > 1)
                {
                        if (!this.child)
                        {
                                throw Error('Attempting to set child state "'+args[1]+'", but no child controller has been set by the state "' + state + '".');
                        }

                        return this.child.state(Array.prototype.slice.call(args, 1));
                }
                else
                {
                        if (this.child)
                        {
                                throw Error('Attempting to set state "' + state + '", but the state for the child controller is missing');
                        }
                }
        }.bind(this);

        if (arguments.length === 1)
        {
                args =  Array.isArray(state) ? state : [state];
                state = args[0];
        }
        else
        {
                args = arguments;
        }

        if (Controller.statesEqual(this.currentState, state))
        {
                return P.resolve().then(childEnter);
        }
        else
        {
                return P.resolve().then(childLeave).then(myLeave).then(myEnter).then(childEnter);
        }
};

/**
 * This method is invoked by .state(...) when the state for this controller should change (leave() is called first).
 * The default implementation translates the state to a method invocation.
 * e.g. "foo" -> this.enterFoo()
 * Override this method if you want to do something else (like pulling the states
 * out of a database);
 * @param {string|object} state string or an object that describes your state. toString() is called on the object to
 *        determine the method name to use. {abc: 5, toString: function(){ return 'foo';}, ...} -> this.enterFoo()
 * @throws {Error} If the state method does not exist.
 * @return {?Promise}
 * @protected Use .state() to set the state
 */
Controller.prototype.enter = function(state)
{
        var method;
        this.currentState = state;

        method = Controller.stateMethodName('enter', state);
        if (typeof this[method] !== 'function')
        {
                throw Error('State method ' + method + ' does not exist');
        }

        return this[method](state);
};

/**
 * This method is invoked by .state(...) when the current state is being left.
 * The default implementation translates the state to a method invocation.
 * e.g. "foo" -> this.leaveFoo();
 * Unlike enter(), this method does not throw if this method does not exist.
 * @protected Use .state(null) to leave the state
 * @return {?Promise}
 */
Controller.prototype.leave = function()
{
        var method;

        method = Controller.stateMethodName('leave', this.currentState);

        if (typeof this[method] === 'function')
        {
                return this[method]();
        }
};

/**
 * Find the top most Controller by iterating over .parent and return it
 * @returns {Controller}
 */
Controller.prototype.getRootController = function()
{
        var cont = this;
        while (cont.parent)
        {
                cont = cont.parent;
        }

        return cont;
};

/**
 * Return the states of this controller and all its children (in that order)
 * @returns {Array}
 */
Controller.prototype.getChildrenStateList = function()
{
        var states = [];
        var cont = this;

        while (cont)
        {
                states.push(cont.currentState);
                cont = cont.child;
        }

        return states;
};

/**
 * Return the states of all the parents of this controller and the controller itself (in that order)
 * @returns {Array}
 */
Controller.prototype.getParentsStateList = function()
{
        var states = [];
        var cont = this;

        while (cont)
        {
                states.unshift(cont.currentState);
                cont = cont.parent;
        }

        return states;
};
<commit_msg>Some jsdoc improvements<commit_after>'use strict';

var P = require('bluebird');

/**
 * @typedef {(string|Object)} ControllerState
 */
/**
 * @typedef {ControllerState[]} ControllerStateList
 */

/**
 * The (abstract) base class for Controllers.
 * Each controller is a state machine. A state might instantiate a different controller (which also has a state).
 * There is usually a single Controller that everything begins with (the FrontController).
 * If a route is assigned, it simply defines the hierarchy of states it must assign, for
 * example ['page', 'contact', 'map'] would cause the FrontController to enter the state 'page', this state
 * creates a PageController which is assigned the state 'contact', and so on.
 * @param {ClientContext} context
 * @constructor
 */
function Controller(context)
{
        if (this.constructor === Controller)
        {
                throw Error('Controller is abstract');
        }

        Object.defineProperty(this, 'context' , { value: context });

        this.currentState = null;
        this.__child__ = null;
        this.__parent__ = null;
}

module.exports = Controller;
Object.defineProperty(Controller.prototype, 'isController', { value: true });

/** The current child controller. Setting this attribute also updates the proper .parent attributes.
 * @member {Controller} child
 * @memberOf Controller
 * @instance
 */
Object.defineProperty(Controller.prototype, 'child', {
        get: function()
        {
                return this.__child__;
        },
        set: function(value)
        {
                if (value && value.isController !== true)
                {
                        throw Error('value should be a Controller or null');
                }

                if (this.__child__)
                {
                        this.__child__.__parent__ = null;
                }

                this.__child__ = value;

                if (this.__child__)
                {
                        this.__child__.__parent__ = this;
                }
        }
});

/** The current parent controller. Setting this attribute also updates the proper .child attributes.
 * @member {Controller} parent
 * @memberOf Controller
 * @instance
 */
Object.defineProperty(Controller.prototype, 'parent', {
        get: function()
        {
                return this.__parent__;
        },
        set: function(value)
        {
                if (value && value.isController !== true)
                {
                        throw Error('value should be a Controller or null');
                }

                if (this.__parent__)
                {
                        this.__parent__.__child__ = null;
                }

                this.__parent__ = value;

                if (this.__parent__)
                {
                        this.__parent__.__child__ = this;
                }
        }
});

/**
 * @example Controller.stateMethodName('enter', 'foo') // 'enterFoo'
 * @example Controller.stateMethodName('enter', {abc: 5, stateName: 'enterFoo', ...} // 'enterFoo'
 * @param {string} prefix
 * @param {ControllerState} state
 * @returns {string}
 */
Controller.stateMethodName = function(prefix, state)
{
        if (typeof state === 'object')
        {
                state = state.stateName;
        }

        state = state.toString();

        return prefix + state.charAt(0).toUpperCase() + state.slice(1);
};

/**
 * Are two states equal?.
 * If both states are a string, case sensitive string matching is used.
 * If stateA is an object, return stateA.isStateEqual(stateB)
 * @param {ControllerState} stateA
 * @param {ControllerState} stateB
 * @returns {boolean}
 */
Controller.statesEqual = function(stateA, stateB)
{
        if (stateA &&
            stateB &&
            typeof stateA === 'object')
        {
                if (!stateA.isStateEqual)
                {
                        throw Error('If a state is an object, it must implement a isStateEqual(other) method');
                }

                return stateA.isStateEqual(stateB);
        }

        return stateA === stateB;
};

/**
 * Assign a chain of states to this controller and its children.
 * Each controller has 0 or 1 child controllers. When a controller enters a state, it might create a child controller
 * for that state (by setting the child attribute on its controller).
 * That child controller is also assigned a state by this method (the next one in the chain).
 *
 * @example
 * myController.state('pages', 'contact', 'foo'); // myController is set to the state 'pages',
 *                                                // its child controller is set to 'contact', and so on.
 * @param {?ControllerStateList} state The state chain (vararg or an array), or null to leave the state without setting a new one.
 * @return {!Promise}
 */
Controller.prototype.state = function(state)
{
        var args;

        var childLeave = function()
        {
                if (this.child)
                {
                        return this.child.state([null]);
                }
        }.bind(this);

        var myLeave = function()
        {
                if (this.currentState)
                {
                        try
                        {
                                return this.leave();
                        }
                        finally
                        {
                                this.currentState = null;
                                this.child = null;
                        }
                }
        }.bind(this);

        var myEnter = function()
        {
                if (state)
                {
                        this.currentState = state;
                        return this.enter(state);
                }
        }.bind(this);

        var childEnter = function()
        {
                if (args.length > 1)
                {
                        if (!this.child)
                        {
                                throw Error('Attempting to set child state "'+args[1]+'", but no child controller has been set by the state "' + state + '".');
                        }

                        return this.child.state(Array.prototype.slice.call(args, 1));
                }
                else
                {
                        if (this.child)
                        {
                                throw Error('Attempting to set state "' + state + '", but the state for the child controller is missing');
                        }
                }
        }.bind(this);

        if (arguments.length === 1)
        {
                args =  Array.isArray(state) ? state : [state];
                state = args[0];
        }
        else
        {
                args = arguments;
        }

        if (Controller.statesEqual(this.currentState, state))
        {
                return P.resolve().then(childEnter);
        }
        else
        {
                return P.resolve().then(childLeave).then(myLeave).then(myEnter).then(childEnter);
        }
};

/**
 * This method is invoked by .state(...) when the state for this controller should change (leave() is called first).
 * The default implementation translates the state to a method invocation.
 * e.g. "foo" -> this.enterFoo()
 * Override this method if you want to do something else (like pulling the states
 * out of a database);
 * @param {ControllerState} state string or an object that describes your state. toString() is called on the object to
 *        determine the method name to use. {abc: 5, toString: function(){ return 'foo';}, ...} -> this.enterFoo()
 * @throws {Error} If the state method does not exist.
 * @return {?Promise}
 * @protected Use .state() to set the state
 */
Controller.prototype.enter = function(state)
{
        var method;
        this.currentState = state;

        method = Controller.stateMethodName('enter', state);
        if (typeof this[method] !== 'function')
        {
                throw Error('State method ' + method + ' does not exist');
        }

        return this[method](state);
};

/**
 * This method is invoked by .state(...) when the current state is being left.
 * The default implementation translates the state to a method invocation.
 * e.g. "foo" -> this.leaveFoo();
 * Unlike enter(), this method does not throw if this method does not exist.
 * @protected Use .state(null) to leave the state
 * @return {?Promise}
 */
Controller.prototype.leave = function()
{
        var method;

        method = Controller.stateMethodName('leave', this.currentState);

        if (typeof this[method] === 'function')
        {
                return this[method]();
        }
};

/**
 * Find the top most Controller by iterating over .parent and return it
 * @returns {Controller}
 */
Controller.prototype.getRootController = function()
{
        var cont = this;
        while (cont.parent)
        {
                cont = cont.parent;
        }

        return cont;
};

/**
 * Return the states of this controller and all its children (in that order).
 * If this controller has not been assigned a state yet, [null] is returned.
 * @returns {ControllerStateList}
 */
Controller.prototype.getChildrenStateList = function()
{
        var states = [];
        var cont = this;

        while (cont)
        {
                states.push(cont.currentState);
                cont = cont.child;
        }

        return states;
};

/**
 * Return the states of all the parents of this controller and the controller itself (in that order)
 * @returns {ControllerStateList}
 */
Controller.prototype.getParentsStateList = function()
{
        var states = [];
        var cont = this;

        while (cont)
        {
                states.unshift(cont.currentState);
                cont = cont.parent;
        }

        return states;
};
<|endoftext|>


<commit_before>exports.onRouteUpdate = ({ location }) => {
  window.analytics && window.analytics.page();
  if (
    typeof window !== 'undefined' &&
    (!location.action || location.action === 'push')
  ) {
    setTimeout(() => {
      window.scrollTo(0, 0)
    })
  }
}
<commit_msg>Removed legacy scrollTo code (#220)<commit_after>exports.onRouteUpdate = ({ location }) => {
  window.analytics && window.analytics.page()
}
<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var path = require('path');
var spawn = require('child_process').spawn;
var yeoman = require('yeoman-generator');
var chalk = require('chalk');

var AppGenerator = module.exports = function Appgenerator(args, options, config) {
    yeoman.generators.Base.apply(this, arguments);

    // setup the test-framework property, Gruntfile template will need this
    this.testFramework = options['test-framework'] || 'mocha';
    this.coffee = options.coffee;

    // for hooks to resolve on mocha by default
    options['test-framework'] = this.testFramework;

    // resolved to mocha by default (could be switched to jasmine for instance)
    this.hookFor('test-framework', {
        as: 'app',
        options: {
            options: {
                'skip-install': options['skip-install-message'],
                'skip-message': options['skip-install']
            }
        }
    });

    this.options = options;

    this.pkg = JSON.parse(this.readFileAsString(path.join(__dirname, '../package.json')));
};

util.inherits(AppGenerator, yeoman.generators.Base);

AppGenerator.prototype.askFor = function askFor() {
    var cb = this.async();

    // welcome message
    if (!this.options['skip-welcome-message']) {
        //console.log(this.yeoman);
        console.log(chalk.magenta('Moin Moin'));
    }

    var prompts = [{
        type: 'checkbox',
        name: 'features',
        message: 'What more would you like?',
        choices: [{
            name: 'Sass with Compass',
            value: 'includeCompass',
            checked: true
        }, {
            name: 'Bootstrap',
            value: 'includeBootstrap',
            checked: true
        }, {
            name: 'Modernizr',
            value: 'includeModernizr',
            checked: true
        }, {
            name: 'Backbone',
            value: 'includeBackbone',
            checked: true
        }, {
            name: 'Dploy',
            value: 'includeDploy',
            checked: false
        }]
    }];

    this.prompt(prompts, function(answers) {
        var features = answers.features;

        function hasFeature(feat) {
            if (features) {
                return features.indexOf(feat) !== -1;
            }
            return true;
        }

        // manually deal with the response, get back and store the results.
        // we change a bit this way of doing to automatically do this in the self.prompt() method.
        this.includeCompass = hasFeature('includeCompass');
        this.includeBootstrap = hasFeature('includeBootstrap');
        this.includeModernizr = hasFeature('includeModernizr');
        this.includeBackbone = hasFeature('includeBackbone');
        this.includeDploy = hasFeature('includeDploy');

        cb();
    }.bind(this));
};

AppGenerator.prototype.gruntfile = function gruntfile() {
    this.template('Gruntfile.js');
};

AppGenerator.prototype.packageJSON = function packageJSON() {
    this.template('_package.json', 'package.json');
};

AppGenerator.prototype.git = function git() {
    this.copy('gitignore', '.gitignore');
    this.copy('gitattributes', '.gitattributes');
};

AppGenerator.prototype.bower = function bower() {
    this.copy('bowerrc', '.bowerrc');
    this.copy('_bower.json', 'bower.json');
};

AppGenerator.prototype.jshint = function jshint() {
    this.copy('jshintrc', '.jshintrc');
};

AppGenerator.prototype.editorConfig = function editorConfig() {
    this.copy('editorconfig', '.editorconfig');
};

AppGenerator.prototype.h5bp = function h5bp() {
    // this.copy('favicon.ico', 'app/favicon.ico');
    // this.copy('404.html', 'app/404.html');
    // this.copy('robots.txt', 'app/robots.txt');
    // this.copy('htaccess', 'app/.htaccess');
};

AppGenerator.prototype.mainStylesheet = function mainStylesheet() {
    // var css = 'main.' + (this.includeCompass ? 's' : '') + 'css';
    // this.copy(css, 'app/styles/' + css);
};

AppGenerator.prototype.writeIndex = function writeIndex() {

    // this.indexFile = this.readFileAsString(path.join(this.sourceRoot(), 'index.html'));
    // this.indexFile = this.engine(this.indexFile, this);

    // this.mainJSFile = this.readFileAsString(path.join(this.sourceRoot(), 'app/assets/js/main.js'));
    // this.mainJSFile = this.engine(this.mainJSFile, this);

};

AppGenerator.prototype.app = function app() {
    this.mkdir('app');
    this.directory('app', 'app');
    // this.write('app/index.html', this.indexFile);
    // this.write('app/assets/js/main.js', this.mainJSFile);
};

AppGenerator.prototype.install = function() {
    if (this.options['skip-install']) {
        return;
    }

    var done = this.async();
    this.installDependencies({
        skipMessage: this.options['skip-install-message'],
        skipInstall: this.options['skip-install'],
        callback: done
    });
};
<commit_msg>adds setup option for jquery version<commit_after>'use strict';
var util = require('util');
var path = require('path');
var spawn = require('child_process').spawn;
var yeoman = require('yeoman-generator');
var chalk = require('chalk');

var AppGenerator = module.exports = function Appgenerator(args, options, config) {
    yeoman.generators.Base.apply(this, arguments);

    // setup the test-framework property, Gruntfile template will need this
    this.testFramework = options['test-framework'] || 'mocha';
    this.coffee = options.coffee;

    // for hooks to resolve on mocha by default
    options['test-framework'] = this.testFramework;

    // resolved to mocha by default (could be switched to jasmine for instance)
    this.hookFor('test-framework', {
        as: 'app',
        options: {
            options: {
                'skip-install': options['skip-install-message'],
                'skip-message': options['skip-install']
            }
        }
    });

    this.options = options;

    this.pkg = JSON.parse(this.readFileAsString(path.join(__dirname, '../package.json')));
};

util.inherits(AppGenerator, yeoman.generators.Base);

AppGenerator.prototype.askFor = function askFor() {
    var cb = this.async();

    // welcome message
    if (!this.options['skip-welcome-message']) {
        //console.log(this.yeoman);
        console.log(chalk.magenta('Moin Moin'));
    }

    var prompts = [{
        type: 'checkbox',
        name: 'features',
        message: 'What more would you like?',
        choices: [{
            name: 'jQuery with IE<=8 support',
            value: 'jQuery1',
            checked: true
        }, {
            name: 'Sass with Compass',
            value: 'includeCompass',
            checked: true
        }, {
            name: 'Bootstrap',
            value: 'includeBootstrap',
            checked: true
        }, {
            name: 'Modernizr',
            value: 'includeModernizr',
            checked: true
        }, {
            name: 'Backbone',
            value: 'includeBackbone',
            checked: true
        }, {
            name: 'Dploy',
            value: 'includeDploy',
            checked: false
        }]
    }];

    this.prompt(prompts, function(answers) {
        var features = answers.features;

        function hasFeature(feat) {
            if (features) {
                return features.indexOf(feat) !== -1;
            }
            return true;
        }

        // manually deal with the response, get back and store the results.
        // we change a bit this way of doing to automatically do this in the self.prompt() method.
        this.jQuery1 = hasFeature('jQuery1');
        this.includeCompass = hasFeature('includeCompass');
        this.includeBootstrap = hasFeature('includeBootstrap');
        this.includeModernizr = hasFeature('includeModernizr');
        this.includeBackbone = hasFeature('includeBackbone');
        this.includeDploy = hasFeature('includeDploy');

        cb();
    }.bind(this));
};

AppGenerator.prototype.gruntfile = function gruntfile() {
    this.template('Gruntfile.js');
};

AppGenerator.prototype.packageJSON = function packageJSON() {
    this.template('_package.json', 'package.json');
};

AppGenerator.prototype.git = function git() {
    this.copy('gitignore', '.gitignore');
    this.copy('gitattributes', '.gitattributes');
};

AppGenerator.prototype.bower = function bower() {
    this.copy('bowerrc', '.bowerrc');
    this.copy('_bower.json', 'bower.json');
};

AppGenerator.prototype.jshint = function jshint() {
    this.copy('jshintrc', '.jshintrc');
};

AppGenerator.prototype.editorConfig = function editorConfig() {
    this.copy('editorconfig', '.editorconfig');
};

AppGenerator.prototype.h5bp = function h5bp() {
    // this.copy('favicon.ico', 'app/favicon.ico');
    // this.copy('404.html', 'app/404.html');
    // this.copy('robots.txt', 'app/robots.txt');
    // this.copy('htaccess', 'app/.htaccess');
};

AppGenerator.prototype.mainStylesheet = function mainStylesheet() {
    // var css = 'main.' + (this.includeCompass ? 's' : '') + 'css';
    // this.copy(css, 'app/styles/' + css);
};

AppGenerator.prototype.writeIndex = function writeIndex() {

    // this.indexFile = this.readFileAsString(path.join(this.sourceRoot(), 'index.html'));
    // this.indexFile = this.engine(this.indexFile, this);

    // this.mainJSFile = this.readFileAsString(path.join(this.sourceRoot(), 'app/assets/js/main.js'));
    // this.mainJSFile = this.engine(this.mainJSFile, this);

};

AppGenerator.prototype.app = function app() {
    this.mkdir('app');
    this.directory('app', 'app');
    // this.write('app/index.html', this.indexFile);
    // this.write('app/assets/js/main.js', this.mainJSFile);
};

AppGenerator.prototype.install = function() {
    if (this.options['skip-install']) {
        return;
    }

    var done = this.async();
    this.installDependencies({
        skipMessage: this.options['skip-install-message'],
        skipInstall: this.options['skip-install'],
        callback: done
    });
};
<|endoftext|>


<commit_before>var compliments = {
	complimentLocation: '.compliment',
	currentCompliment: '',
	complimentList: {
		'morning': config.compliments.morning,
		'afternoon': config.compliments.afternoon,
		'evening': config.compliments.evening
	},
	updateInterval: config.compliments.interval || 30000,
	fadeInterval: config.compliments.fadeInterval || 4000,
	intervalId: null
};

compliments.updateCompliment = function () {

	var _list = [];

	var hour = moment().hour();

	if (hour >= 3 && hour < 12) {
		// Morning compliments
		_list = compliments.complimentList['morning'];
	} else if (hour >= 12 && hour < 17) {
		// Afternoon compliments
		_list = compliments.complimentList['afternoon'];
	} else if (hour >= 17 || hour < 3) {
		// Evening compliments
		_list = compliments.complimentList['evening'];
	} else {
		// Edge case in case something weird happens
		// This will select a compliment from all times of day
		Object.keys(compliments.complimentList).forEach(function (_curr) {

			_list = _list.concat(compliments.complimentList[_curr]);

		});
	}

	// Search for the location of the current compliment in the list
	var _spliceIndex = _list.indexOf(compliments.currentCompliment);

	// If it exists, remove it so we don't see it again
	if (_spliceIndex !== -1) {
		_list = _list.slice(_spliceIndex, 1);
	}

	// Randomly select a location
	var _location = Math.floor(Math.random() * _list.length);
	compliments.currentCompliment = _list[_location];

	$('.compliment').updateWithText(compliments.currentCompliment, compliments.fadeInterval);

}

compliments.init = function () {

	this.updateCompliment();

	this.intervalId = setInterval(function () {
		this.updateCompliment();
	}.bind(this), this.updateInterval)

}<commit_msg>add comments to compliments.js<commit_after>var compliments = {
	complimentLocation: '.compliment',
	currentCompliment: '',
	complimentList: {
		'morning': config.compliments.morning,
		'afternoon': config.compliments.afternoon,
		'evening': config.compliments.evening
	},
	updateInterval: config.compliments.interval || 30000,
	fadeInterval: config.compliments.fadeInterval || 4000,
	intervalId: null
};

/**
 * Changes the compliment visible on the screen
 */
compliments.updateCompliment = function () {

	var _list = [];

	var hour = moment().hour();

	if (hour >= 3 && hour < 12) {
		// Morning compliments
		_list = compliments.complimentList['morning'];
	} else if (hour >= 12 && hour < 17) {
		// Afternoon compliments
		_list = compliments.complimentList['afternoon'];
	} else if (hour >= 17 || hour < 3) {
		// Evening compliments
		_list = compliments.complimentList['evening'];
	} else {
		// Edge case in case something weird happens
		// This will select a compliment from all times of day
		Object.keys(compliments.complimentList).forEach(function (_curr) {

			_list = _list.concat(compliments.complimentList[_curr]);

		});
	}

	// Search for the location of the current compliment in the list
	var _spliceIndex = _list.indexOf(compliments.currentCompliment);

	// If it exists, remove it so we don't see it again
	if (_spliceIndex !== -1) {
		_list = _list.slice(_spliceIndex, 1);
	}

	// Randomly select a location
	var _location = Math.floor(Math.random() * _list.length);
	compliments.currentCompliment = _list[_location];

	$('.compliment').updateWithText(compliments.currentCompliment, compliments.fadeInterval);

}

compliments.init = function () {

	this.updateCompliment();

	this.intervalId = setInterval(function () {
		this.updateCompliment();
	}.bind(this), this.updateInterval)

}<|endoftext|>


<commit_before>module.exports = magnetURIDecode
module.exports.decode = magnetURIDecode
module.exports.encode = magnetURIEncode

var base32 = require('thirty-two')
var extend = require('xtend')
var uniq = require('uniq')

/**
 * Parse a magnet URI and return an object of keys/values
 *
 * @param  {string} uri
 * @return {Object} parsed uri
 */
function magnetURIDecode (uri) {
  var result = {}
  var data = uri.split('magnet:?')[1]

  var params = (data && data.length >= 0)
    ? data.split('&')
    : []

  params.forEach(function (param) {
    var keyval = param.split('=')

    // This keyval is invalid, skip it
    if (keyval.length !== 2) return

    var key = keyval[0]
    var val = keyval[1]

    // Clean up torrent name
    if (key === 'dn') val = decodeURIComponent(val).replace(/\+/g, ' ')

    // Address tracker (tr), exact source (xs), and acceptable source (as) are encoded
    // URIs, so decode them
    if (key === 'tr' || key === 'xs' || key === 'as' || key === 'ws') {
      val = decodeURIComponent(val)
    }

    // Return keywords as an array
    if (key === 'kt') val = decodeURIComponent(val).split('+')

    // If there are repeated parameters, return an array of values
    if (result[key]) {
      if (Array.isArray(result[key])) {
        result[key].push(val)
      } else {
        var old = result[key]
        result[key] = [old, val]
      }
    } else {
      result[key] = val
    }
  })

  // Convenience properties for parity with `parse-torrent-file` module
  var m
  if (result.xt) {
    var xts = Array.isArray(result.xt) ? result.xt : [ result.xt ]
    xts.forEach(function (xt) {
      if ((m = xt.match(/^urn:btih:(.{40})/))) {
        result.infoHash = m[1].toLowerCase()
      } else if ((m = xt.match(/^urn:btih:(.{32})/))) {
        var decodedStr = base32.decode(m[1])
        result.infoHash = new Buffer(decodedStr, 'binary').toString('hex')
      }
    })
  }
  if (result.infoHash) result.infoHashBuffer = new Buffer(result.infoHash, 'hex')

  if (result.dn) result.name = result.dn
  if (result.kt) result.keywords = result.kt

  if (typeof result.tr === 'string') result.announce = [ result.tr ]
  else if (Array.isArray(result.tr)) result.announce = result.tr
  else result.announce = []

  uniq(result.announce)

  result.urlList = []
  if (typeof result.as === 'string' || Array.isArray(result.as)) {
    result.urlList = result.urlList.concat(result.as)
  }
  if (typeof result.ws === 'string' || Array.isArray(result.ws)) {
    result.urlList = result.urlList.concat(result.ws)
  }

  return result
}

function magnetURIEncode (obj) {
  obj = extend(obj) // clone obj, so we can mutate it

  // support using convenience names, in addition to spec names
  // (example: `infoHash` for `xt`, `name` for `dn`)
  if (obj.infoHashBuffer) obj.xt = 'urn:btih:' + obj.infoHashBuffer.toString('hex')
  if (obj.infoHash) obj.xt = 'urn:btih:' + obj.infoHash
  if (obj.name) obj.dn = obj.name
  if (obj.keywords) obj.kt = obj.keywords
  if (obj.announce) obj.tr = obj.announce
  if (obj.urlList) {
    obj.ws = obj.urlList
    delete obj.as
  }

  var result = 'magnet:?'
  Object.keys(obj)
    .filter(function (key) {
      return key.length === 2
    })
    .forEach(function (key, i) {
      var values = Array.isArray(obj[key]) ? obj[key] : [ obj[key] ]
      values.forEach(function (val, j) {
        if ((i > 0 || j > 0) && (key !== 'kt' || j === 0)) result += '&'

        if (key === 'dn') val = encodeURIComponent(val).replace(/%20/g, '+')
        if (key === 'tr' || key === 'xs' || key === 'as' || key === 'ws') {
          val = encodeURIComponent(val)
        }
        if (key === 'kt') val = encodeURIComponent(val)

        if (key === 'kt' && j > 0) result += '+' + val
        else result += key + '=' + val
      })
    })

  return result
}
<commit_msg>deduplicate url list<commit_after>module.exports = magnetURIDecode
module.exports.decode = magnetURIDecode
module.exports.encode = magnetURIEncode

var base32 = require('thirty-two')
var extend = require('xtend')
var uniq = require('uniq')

/**
 * Parse a magnet URI and return an object of keys/values
 *
 * @param  {string} uri
 * @return {Object} parsed uri
 */
function magnetURIDecode (uri) {
  var result = {}
  var data = uri.split('magnet:?')[1]

  var params = (data && data.length >= 0)
    ? data.split('&')
    : []

  params.forEach(function (param) {
    var keyval = param.split('=')

    // This keyval is invalid, skip it
    if (keyval.length !== 2) return

    var key = keyval[0]
    var val = keyval[1]

    // Clean up torrent name
    if (key === 'dn') val = decodeURIComponent(val).replace(/\+/g, ' ')

    // Address tracker (tr), exact source (xs), and acceptable source (as) are encoded
    // URIs, so decode them
    if (key === 'tr' || key === 'xs' || key === 'as' || key === 'ws') {
      val = decodeURIComponent(val)
    }

    // Return keywords as an array
    if (key === 'kt') val = decodeURIComponent(val).split('+')

    // If there are repeated parameters, return an array of values
    if (result[key]) {
      if (Array.isArray(result[key])) {
        result[key].push(val)
      } else {
        var old = result[key]
        result[key] = [old, val]
      }
    } else {
      result[key] = val
    }
  })

  // Convenience properties for parity with `parse-torrent-file` module
  var m
  if (result.xt) {
    var xts = Array.isArray(result.xt) ? result.xt : [ result.xt ]
    xts.forEach(function (xt) {
      if ((m = xt.match(/^urn:btih:(.{40})/))) {
        result.infoHash = m[1].toLowerCase()
      } else if ((m = xt.match(/^urn:btih:(.{32})/))) {
        var decodedStr = base32.decode(m[1])
        result.infoHash = new Buffer(decodedStr, 'binary').toString('hex')
      }
    })
  }
  if (result.infoHash) result.infoHashBuffer = new Buffer(result.infoHash, 'hex')

  if (result.dn) result.name = result.dn
  if (result.kt) result.keywords = result.kt

  if (typeof result.tr === 'string') result.announce = [ result.tr ]
  else if (Array.isArray(result.tr)) result.announce = result.tr
  else result.announce = []

  result.urlList = []
  if (typeof result.as === 'string' || Array.isArray(result.as)) {
    result.urlList = result.urlList.concat(result.as)
  }
  if (typeof result.ws === 'string' || Array.isArray(result.ws)) {
    result.urlList = result.urlList.concat(result.ws)
  }

  uniq(result.announce)
  uniq(result.urlList)

  return result
}

function magnetURIEncode (obj) {
  obj = extend(obj) // clone obj, so we can mutate it

  // support using convenience names, in addition to spec names
  // (example: `infoHash` for `xt`, `name` for `dn`)
  if (obj.infoHashBuffer) obj.xt = 'urn:btih:' + obj.infoHashBuffer.toString('hex')
  if (obj.infoHash) obj.xt = 'urn:btih:' + obj.infoHash
  if (obj.name) obj.dn = obj.name
  if (obj.keywords) obj.kt = obj.keywords
  if (obj.announce) obj.tr = obj.announce
  if (obj.urlList) {
    obj.ws = obj.urlList
    delete obj.as
  }

  var result = 'magnet:?'
  Object.keys(obj)
    .filter(function (key) {
      return key.length === 2
    })
    .forEach(function (key, i) {
      var values = Array.isArray(obj[key]) ? obj[key] : [ obj[key] ]
      values.forEach(function (val, j) {
        if ((i > 0 || j > 0) && (key !== 'kt' || j === 0)) result += '&'

        if (key === 'dn') val = encodeURIComponent(val).replace(/%20/g, '+')
        if (key === 'tr' || key === 'xs' || key === 'as' || key === 'ws') {
          val = encodeURIComponent(val)
        }
        if (key === 'kt') val = encodeURIComponent(val)

        if (key === 'kt' && j > 0) result += '+' + val
        else result += key + '=' + val
      })
    })

  return result
}
<|endoftext|>


<commit_before>define(['Modernizr'], function( Modernizr ) {
  // IE8 compat mode aka Fake IE7
  // by Erich Ocean

  // In this case, IE8 will be acting as IE7. You may choose to remove features in this case.

  // related:
  // james.padolsey.com/javascript/detect-ie-in-js-using-conditional-comments/

  Modernizr.addTest('ie8compat', (!window.addEventListener && document.documentMode && document.documentMode === 7));
});
<commit_msg>Fixed an `undefined` detect result for `ie8compat` in old IE<commit_after>define(['Modernizr'], function( Modernizr ) {
  // IE8 compat mode aka Fake IE7
  // by Erich Ocean

  // In this case, IE8 will be acting as IE7. You may choose to remove features in this case.

  // related:
  // james.padolsey.com/javascript/detect-ie-in-js-using-conditional-comments/

  Modernizr.addTest('ie8compat', (!window.addEventListener && !!document.documentMode && document.documentMode === 7));
});
<|endoftext|>


<commit_before>/**
 * The frontend application.
 */
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, hashHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import injectTapEventPlugin from 'react-tap-event-plugin';
import routes from './routes';
import configureStore from './store/configureStore';
import { loadInternalDataset } from './actions/datasets';
import { selectSound } from './actions/sounds';
import { mapXYtoParam } from './actions/mapping';

import './app.global.css';
import { join } from 'path';

const production = process.env.NODE_ENV === 'production';
// how ironic, the path to config is different
// const config = require(production ? './config/index' : '../config/index');
// console.log('dirname', __dirname);

const store = configureStore();
const history = syncHistoryWithStore(hashHistory, store);

// connect two-way calling of actions
// the other half is in main.js
const ipcRenderer = require('electron').ipcRenderer;
import handleActionOnRenderer from './ipc/handleActionOnRenderer';
ipcRenderer.on('dispatch-action', (sender, action) => {
  handleActionOnRenderer(store.dispatch, sender, action);
});

// listen to redux store changes and call actions on main thread
// to create sounds
import connectSoundApp from './sound/connectSoundApp';
import callActionOnMain from './ipc/callActionOnMain';
connectSoundApp(store, callActionOnMain);

// Needed for onTouchTap and material-ui
// Can go away when react 1.0 release
// Check this repo:
// https://github.com/zilverline/react-tap-event-plugin
injectTapEventPlugin();

// add right click inspect element context menu
if (!production) {
  require('debug-menu').install();
}

render(
  <Provider store={store}>
    <Router history={history} routes={routes} />
  </Provider>,
  document.getElementById('root')
);

// load an initial dataset and sound
const appRoot = production ? __dirname : join(__dirname, '../app');
const iris = join(appRoot, 'vendor/datasets', 'iris.csv');
store.dispatch(loadInternalDataset(iris));

store.dispatch(selectSound('grainFM'));
store.dispatch(mapXYtoParam('x', 'modfreq'));
store.dispatch(mapXYtoParam('y', 'carfreq'));
<commit_msg>fix: minifier cannot do dead code removal with a const variable, needs process.env.NODE_ENV === 'production'<commit_after>/* eslint global-require: 0 */
/**
 * The frontend application.
 */
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, hashHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import injectTapEventPlugin from 'react-tap-event-plugin';
import routes from './routes';
import configureStore from './store/configureStore';
import { loadInternalDataset } from './actions/datasets';
import { selectSound } from './actions/sounds';
import { mapXYtoParam } from './actions/mapping';

import './app.global.css';
import { join } from 'path';

// how ironic, the path to config is different
// const config = require(production ? './config/index' : '../config/index');
// console.log('dirname', __dirname);

const store = configureStore();
const history = syncHistoryWithStore(hashHistory, store);

// connect two-way calling of actions
// the other half is in main.js
const ipcRenderer = require('electron').ipcRenderer;
import handleActionOnRenderer from './ipc/handleActionOnRenderer';
ipcRenderer.on('dispatch-action', (sender, action) => {
  handleActionOnRenderer(store.dispatch, sender, action);
});

// listen to redux store changes and call actions on main thread
// to create sounds
import connectSoundApp from './sound/connectSoundApp';
import callActionOnMain from './ipc/callActionOnMain';
connectSoundApp(store, callActionOnMain);

// Needed for onTouchTap and material-ui
// Can go away when react 1.0 release
// Check this repo:
// https://github.com/zilverline/react-tap-event-plugin
injectTapEventPlugin();

// add right click inspect element context menu
if (process.env.NODE_ENV !== 'production') {
  require('debug-menu').install();
}

render(
  <Provider store={store}>
    <Router history={history} routes={routes} />
  </Provider>,
  document.getElementById('root')
);

// load an initial dataset and sound
const appRoot = process.env.NODE_ENV === 'production' ? __dirname : join(__dirname, '../app');
const iris = join(appRoot, 'vendor/datasets', 'iris.csv');
store.dispatch(loadInternalDataset(iris));

store.dispatch(selectSound('grainFM'));
store.dispatch(mapXYtoParam('x', 'modfreq'));
store.dispatch(mapXYtoParam('y', 'carfreq'));
<|endoftext|>


<commit_before>/*Copyright 2012 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
#limitations under the License.*/

/**
 * @fileoverview Description of this file.
 * @author ctiller@google.com (Craig Tiller)
 */

var http = require('http')
var assert = require('assert')
var fs = require('fs')

var host = null;
var port = null;
var SERVERID = null;
var DEBUG = true;
var INDENT_JSON = DEBUG ? 4: null;

exports.DEBUG = DEBUG
exports.INDENT_JSON = INDENT_JSON

var pairing_key = JSON.parse(fs.readFileSync(__dirname + '/../shared/pairing-key.json')).key
exports.pairing_key = pairing_key

// exponential backoff knobs
var check_connection_delay_s = .1;
var check_connection_delay_s_max = 300;
var check_connection_delay_s_ok = 30;

function get(path, cb) {
  var caller = get.caller.name;
  var options = {
    host: host,
    port: port,
    path: path
  };
  var result = "";
  var donecb = false;
  function callback(arg) {
    if (!donecb) cb(arg);
    donecb = true;
  }
  url = 'http://' + options.host + ':' + options.port + (options.path[0] == '/' ? '' : '/') + options.path;
  // console.log(caller + ': GET ' + url);
  http.get(options, function(res) {
    if (res.statusCode != 200) {
      console.log(caller + ': GET ' + url + ' -> HTTP status code ' + res.statusCode);
      callback(null);
      return;
    }
    res.on('data', function(chunk) {
      result += chunk;
    });
    res.on('end', function() {
      console.log(caller + ': GET', url, '->', result);
      callback(result);
    });
    res.on('close', function() {
      callback(null);
    });
  }).on('error', function(e) {
    console.log(caller + ': GET ' + url + ' -> FAILED due to ' + e);
    callback(null);
  })
}

function post(path, contents, cb) {
  var caller = post.caller = call ? post.caller.caller.name : post.caller.name;
  var options = {
    host: host,
    port: port,
    path: path,
    method: 'POST',
    headers: {
      'Content-Length': contents.length
    }
  };
  var result = "";
  var donecb = false;
  function callback(arg) {
    if (!donecb) cb(arg);
    donecb = true;
  }
  url = 'http://' + options.host + ':' + options.port + (options.path[0] == '/' ? '' : '/') + options.path;
  // console.log(caller + ': POST ' + url);
  var req = http.request(options, function(res) {
    if (res.statusCode != 200) {
      console.log(caller + ': POST ' + url + ' -> HTTP status code ' + res.statusCode + ':\n' + res);
      callback(null);
      return;
    }
    res.on('data', function(chunk) {
      result += chunk;
    });
    res.on('end', function() {
      console.log(caller + ':', contents, '->', url, '->', result);
      callback(result);
    });
    res.on('close', function() {
      callback(null);
    });
  });
  req.on('error', function(e) {
    console.log(caller + ': POST ' + url + ' -> FAILED due to ' + e);
    callback(null);
  });
  req.write(contents);
  req.end();
}

function call(path, args, callback) {
  post(path, JSON.stringify(args, undefined, INDENT_JSON), function(r) {
    var result = null;
    try {
      if (r) {
        result = JSON.parse(r);
      } else {
        result = {
          success: false,
          exception: 'POST FAILED'
        };
      }
    } catch (e) {
      result = {
        success: false,
        exception: e
      };
    }
    assert.ok(result);
    callback(result);
  });
}

function checkConnectionSoon(controller_port, delay_s) {
  check_connection_delay_s = delay_s;
  console.log('Will check connection again in ' + delay_s + ' seconds');
  setTimeout(function() { checkConnection(controller_port); }, delay_s * 1000);
}

function checkConnection(controller_port) {
  var info = {
    controller_port: controller_port,
    serverid: SERVERID,
    pairing_key: pairing_key,
  };
  uri = '/register-controller';
  call(uri, info, function(r) {
    if (!r || !r.success || r.backend != 'matcher') {
      //console.log(checkConnection.name + ': FAILED -> ', r);
      // use exponential backoff + fuzz factor
      var delay_s = Math.min(check_connection_delay_s_max, check_connection_delay_s * 2 + Math.ceil(Math.random() * 5));
      checkConnectionSoon(controller_port, delay_s);
      return;
    }
    //console.log(checkConnection.name + ':', info, '->', uri, '->', r);
    checkConnectionSoon(controller_port, check_connection_delay_s_ok);
  });
}

function updateGameState(controller_port, game_state) {
  var info = {
    controller_port: controller_port,
    serverid: SERVERID,
    game_state: game_state,
  };
  uri = '/update-game-state';
  call(uri, info, function(r) {
    if (!r || !r.success || r.backend != 'matcher') {
      console.log('UNEXPECTED RESPONSE', r);
    }
  });
}
exports.updateGameState = updateGameState;

function reportGameOver(instance_id, stats) {
  var info = {
    name: instance_id,
    serverid: SERVERID,
    counts: stats.counts,
    logs: stats.logs,
  };
  uri = '/game-over';
  call(uri, info, function(r) {
    if (!r || !r.success || r.backend != 'matcher') {
      console.log('UNEXPECTED RESPONSE', r);
    }
  });
}
exports.reportGameOver = reportGameOver

function connect(h, p, controller_port, serverid) {
  assert.ok(!host);
  assert.ok(!port);
  assert.ok(h);
  assert.ok(p);
  host = h;
  port = p;
  SERVERID = serverid
  checkConnection(controller_port);
}
exports.connect = connect;

<commit_msg>additional logging<commit_after>/*Copyright 2012 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
#limitations under the License.*/

/**
 * @fileoverview Description of this file.
 * @author ctiller@google.com (Craig Tiller)
 */

var http = require('http')
var assert = require('assert')
var fs = require('fs')

var host = null;
var port = null;
var SERVERID = null;
var DEBUG = true;
var INDENT_JSON = DEBUG ? 4: null;

exports.DEBUG = DEBUG
exports.INDENT_JSON = INDENT_JSON

var pairing_key = JSON.parse(fs.readFileSync(__dirname + '/../shared/pairing-key.json')).key
exports.pairing_key = pairing_key

// exponential backoff knobs
var check_connection_delay_s = .1;
var check_connection_delay_s_max = 300;
var check_connection_delay_s_ok = 30;

function get(path, cb) {
  var caller = get.caller.name;
  var options = {
    host: host,
    port: port,
    path: path
  };
  var result = "";
  var donecb = false;
  function callback(arg) {
    if (!donecb) cb(arg);
    donecb = true;
  }
  url = 'http://' + options.host + ':' + options.port + (options.path[0] == '/' ? '' : '/') + options.path;
  // console.log(caller + ': GET ' + url);
  http.get(options, function(res) {
    if (res.statusCode != 200) {
      console.log(caller + ': GET ' + url + ' -> HTTP status code ' + res.statusCode);
      callback(null);
      return;
    }
    res.on('data', function(chunk) {
      result += chunk;
    });
    res.on('end', function() {
      console.log(caller + ': GET', url, '->', result);
      callback(result);
    });
    res.on('close', function() {
      callback(null);
    });
  }).on('error', function(e) {
    console.log(caller + ': GET ' + url + ' -> FAILED due to ' + e);
    callback(null);
  })
}

function post(path, contents, cb) {
  var caller = post.caller = call ? post.caller.caller.name : post.caller.name;
  var options = {
    host: host,
    port: port,
    path: path,
    method: 'POST',
    headers: {
      'Content-Length': contents.length
    }
  };
  var result = "";
  var donecb = false;
  function callback(arg) {
    if (!donecb) cb(arg);
    donecb = true;
  }
  url = 'http://' + options.host + ':' + options.port + (options.path[0] == '/' ? '' : '/') + options.path;
  // console.log(caller + ': POST ' + url);
  var req = http.request(options, function(res) {
    if (res.statusCode != 200) {
      console.log(caller + ': POST ' + url + ' -> HTTP status code ' + res.statusCode + ':\n' + res);
      callback(null);
      return;
    }
    res.on('data', function(chunk) {
      result += chunk;
    });
    res.on('end', function() {
      console.log(caller + ':', contents, '->', url, '->', result);
      callback(result);
    });
    res.on('close', function() {
      callback(null);
    });
  });
  req.on('error', function(e) {
    console.log(caller + ': POST ' + url + ' -> FAILED due to ' + e);
    callback(null);
  });
  req.write(contents);
  req.end();
}

function call(path, args, callback) {
  post(path, JSON.stringify(args, undefined, INDENT_JSON), function(r) {
    var result = null;
    try {
      if (r) {
        result = JSON.parse(r);
      } else {
        result = {
          success: false,
          exception: 'POST FAILED'
        };
      }
    } catch (e) {
      result = {
        success: false,
        exception: e
      };
    }
    assert.ok(result);
    callback(result);
  });
}

function checkConnectionSoon(controller_port, delay_s) {
  check_connection_delay_s = delay_s;
  console.log('Will check connection again in', delay_s, 'seconds');
  setTimeout(function() { checkConnection(controller_port); }, delay_s * 1000);
}

function checkConnection(controller_port) {
  console.log(checkConnection.name + '()');
  var info = {
    controller_port: controller_port,
    serverid: SERVERID,
    pairing_key: pairing_key,
  };
  uri = '/register-controller';
  call(uri, info, function(r) {
    if (!r || !r.success || r.backend != 'matcher') {
      console.log(checkConnection.name, 'FAILED', r);
      // use exponential backoff + fuzz factor
      var delay_s = Math.min(check_connection_delay_s_max, check_connection_delay_s * 2 + Math.ceil(Math.random() * 5));
      checkConnectionSoon(controller_port, delay_s);
      return;
    }
    console.log(checkConnection.name, 'SUCCESS', info, '->', uri, '->', r);
    checkConnectionSoon(controller_port, check_connection_delay_s_ok);
  });
}

function updateGameState(controller_port, game_state) {
  var info = {
    controller_port: controller_port,
    serverid: SERVERID,
    game_state: game_state,
  };
  uri = '/update-game-state';
  call(uri, info, function(r) {
    if (!r || !r.success || r.backend != 'matcher') {
      console.log('UNEXPECTED RESPONSE', r);
    }
  });
}
exports.updateGameState = updateGameState;

function reportGameOver(instance_id, stats) {
  var info = {
    name: instance_id,
    serverid: SERVERID,
    counts: stats.counts,
    logs: stats.logs,
  };
  uri = '/game-over';
  call(uri, info, function(r) {
    if (!r || !r.success || r.backend != 'matcher') {
      console.log('UNEXPECTED RESPONSE', r);
    }
  });
}
exports.reportGameOver = reportGameOver

function connect(h, p, controller_port, serverid) {
  assert.ok(!host);
  assert.ok(!port);
  assert.ok(h);
  assert.ok(p);
  host = h;
  port = p;
  SERVERID = serverid
  checkConnection(controller_port);
}
exports.connect = connect;

<|endoftext|>


<commit_before>import React, {PureComponent} from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import LogoMenu from './logo-menu';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { List, ListItem } from 'material-ui/List';
import { getURLParam } from '../utils';

import '../../css/app.less';

// Required by Material-UI library.
injectTapEventPlugin();

darkBaseTheme.palette.textColor = '#ccc';

export default class LandingPage extends PureComponent {
  constructor(props) {
    super(props);
    let particleMenuMode = getURLParam("particleMenu") ? true : false;
    this.state = {
      particleMenuMode
    }
    this.showParticleMenu = this.showParticleMenu.bind(this);
  }
  showParticleMenu() {
    var pageUrl = '?' + "particleMenu=true";
    window.history.pushState('', '', pageUrl);
    this.setState({ particleMenuMode: true });
  }
  render() {
    const { particleMenuMode } = this.state;
    return (
      <MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>

        {!particleMenuMode &&

Thermoscope and Particle Modeler Examples

          <LogoMenu scale="logo-menu" navPath="./index.html" />

settings

        }
        {particleMenuMode &&

Particle Modeler Examples

          <LogoMenu scale="logo-menu small" />

              <List>

Empty Particle Modeler

              <ListItem primaryText="Particle Modeler" href="./particle-modeler/" />
              <ListItem primaryText="Particle Modeler (Authoring)" href="./particle-modeler/#authoring=true" />

Particle Modeler Examples

              <ListItem primaryText="One Particle Example" href= "./particle-modeler/#targetTemperature=140&gravitationalField=0.000005&timeStep=0.6&viscosity=0.8&showFreezeButton=true&elements=1&element1Sigma=0.392&element1Epsilon=-0.5&element1Mass=10&element2Sigma=0.255&element2Epsilon=0&element2Mass=19.9&pair11Epsilon=-0.5&pair11Sigma=0.392&pair12Forces=true&pair12Epsilon=-0.25&pair12Sigma=0.378&pair22Forces=true&pair22Epsilon=-0.065&pair22Sigma=0.157" key="6" />
              <ListItem primaryText="One Particle Example (Authoring)" href= "./particle-modeler/#authoring=true&targetTemperature=140&gravitationalField=0.000005&timeStep=0.6&viscosity=0.8&showFreezeButton=true&elements=1&element1Sigma=0.392&element1Epsilon=-0.5&element1Mass=10&element2Sigma=0.255&element2Epsilon=0&element2Mass=19.9&pair11Epsilon=-0.5&pair11Sigma=0.392&pair12Forces=true&pair12Epsilon=-0.25&pair12Sigma=0.378&pair22Forces=true&pair22Epsilon=-0.065&pair22Sigma=0.157" key="7" />

            </List>

          }

iOS 20171024.1

    </MuiThemeProvider>
    );
  }
}

<commit_msg>update version string on Master<commit_after>import React, {PureComponent} from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import LogoMenu from './logo-menu';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { List, ListItem } from 'material-ui/List';
import { getURLParam } from '../utils';

import '../../css/app.less';

// Required by Material-UI library.
injectTapEventPlugin();

darkBaseTheme.palette.textColor = '#ccc';

export default class LandingPage extends PureComponent {
  constructor(props) {
    super(props);
    let particleMenuMode = getURLParam("particleMenu") ? true : false;
    this.state = {
      particleMenuMode
    }
    this.showParticleMenu = this.showParticleMenu.bind(this);
  }
  showParticleMenu() {
    var pageUrl = '?' + "particleMenu=true";
    window.history.pushState('', '', pageUrl);
    this.setState({ particleMenuMode: true });
  }
  render() {
    const { particleMenuMode } = this.state;
    return (
      <MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>

        {!particleMenuMode &&

Thermoscope and Particle Modeler Examples

          <LogoMenu scale="logo-menu" navPath="./index.html" />

settings

        }
        {particleMenuMode &&

Particle Modeler Examples

          <LogoMenu scale="logo-menu small" />

              <List>

Empty Particle Modeler

              <ListItem primaryText="Particle Modeler" href="./particle-modeler/" />
              <ListItem primaryText="Particle Modeler (Authoring)" href="./particle-modeler/#authoring=true" />

Particle Modeler Examples

              <ListItem primaryText="One Particle Example" href= "./particle-modeler/#targetTemperature=140&gravitationalField=0.000005&timeStep=0.6&viscosity=0.8&showFreezeButton=true&elements=1&element1Sigma=0.392&element1Epsilon=-0.5&element1Mass=10&element2Sigma=0.255&element2Epsilon=0&element2Mass=19.9&pair11Epsilon=-0.5&pair11Sigma=0.392&pair12Forces=true&pair12Epsilon=-0.25&pair12Sigma=0.378&pair22Forces=true&pair22Epsilon=-0.065&pair22Sigma=0.157" key="6" />
              <ListItem primaryText="One Particle Example (Authoring)" href= "./particle-modeler/#authoring=true&targetTemperature=140&gravitationalField=0.000005&timeStep=0.6&viscosity=0.8&showFreezeButton=true&elements=1&element1Sigma=0.392&element1Epsilon=-0.5&element1Mass=10&element2Sigma=0.255&element2Epsilon=0&element2Mass=19.9&pair11Epsilon=-0.5&pair11Sigma=0.392&pair12Forces=true&pair12Epsilon=-0.25&pair12Sigma=0.378&pair22Forces=true&pair22Epsilon=-0.065&pair22Sigma=0.157" key="7" />

            </List>

          }

Master 20171024.1

    </MuiThemeProvider>
    );
  }
}

<|endoftext|>