<commit_before>/**
 * A simple HN React Native client
 * https://github.com/nvladimiroff/HNReactNative
 */
'use strict';

var React = require('react-native');
var {
  AppRegistry,
  ListView,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} = React;

// Lets us open URLs. Facebook hasn't open sourced their official Android
// version of this yet. TODO: Remove when they do.
var WebIntent = require('react-native-webintent');

/**
 * API Docs: https://github.com/HackerNews/API
 * They suggest using Firebase's library, but I wasn't able to get it to work
 * on React Native (despite their claim of supporting it).
 */
var apiurl = "https://hacker-news.firebaseio.com/v0/";

var HNReactNative = React.createClass({
  /**
   * Our initial state consists of a list of the 30 top stories,
   * a ListView DataSource that contains a json object for each story
   * and a boolean to know if we've loaded everything or not yet.
   */
  getInitialState: function() {
    return {
      topStories: [],
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  },

  componentDidMount: function() {
    // First, we load the list of top stories.
    fetch(apiurl + "topstories.json")
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          topStories: responseData.slice(0,30),
        }); })
      .then(() => {
        // Once we have the list, we can load all individual JSON data for each
        // story. To do this, we create a list of Promises for each story
        // and then flatten them into a single one with Promise.all.
        Promise.all(this.state.topStories.map(this.fetchStory))
          .then((stories) => {
            // stories is our final list of JSON data that we will use for
            // the data source of each row.
            this.setState({
              dataSource: this.state.dataSource.cloneWithRows(stories),
              loaded: true,
            });
          }).done();
      }).done();
    },

  /**
   * Get the JSON of a single story. Returns a promise
   */
  fetchStory: function(story) {
    return fetch(apiurl + "item/" + story + ".json")
      .then((response) => response.json())
  },

  /**
   * The main render method that React will use to display the app.
   */
  render: function() {
    if (!this.state.loaded) {
      return (
        <LoadingView />
      );
    } else {
      return (
        <MainView
          dataSource={this.state.dataSource}
        />
      );
    }
  },
});

/**
 * The main view that the user sees: the header and a list of all the stories.
 */
var MainView = React.createClass({
  render: function() {
    return (
      <View style={styles.container}>
        <View style={styles.header}>
          <Text style={styles.headerText}>
            Hacker News
          </Text>
        </View>
        <ListView
            dataSource={this.props.dataSource}
            renderRow={this.renderStory}
            style={styles.listView}
          />
      </View>
    );
  },

  renderStory: function(storyJson) {
    return (
      <Row
        onButtonPress={() => WebIntent.open(storyJson.url)}
        story={storyJson}
      />
    );
  },
});

/**
 * Just display a 'Loading...' message while the data is downloaded.
 */
var LoadingView = React.createClass({
  render: function() {
    return (
      <View style={styles.loadingContainer}>
        <Text style={styles.loading}>
          Loading...
        </Text>
      </View>
    );
  },
});

/**
 * Assuming we have the JSON object for a story, render a single row.
 */
var Row = React.createClass({
  render: function() {
    return (
      // TouchableNativeFeedback looks nicer, but is currently Android only.
      <TouchableOpacity onPress={this.props.onButtonPress}>
        <View style={styles.rowContainer}>
          <Text style={styles.title}>
            {this.props.story.title}
          </Text>
          <Text style={styles.points}>
            {this.props.story.score} points
          </Text>
        </View>
      </TouchableOpacity>
    );
  },
});

var styles = StyleSheet.create({
  container: {
    flex: 1
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  loading: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  header: {
    backgroundColor: "#ff6600",
  },
  headerText: {
    fontSize: 18,
    fontWeight: '500',
    marginLeft: 20,
    marginTop: 7,
    marginBottom: 7,
    fontFamily: 'Verdana',
    color: '#000000',
  },
  rowContainer: {
    justifyContent: 'center',
    margin: 5,
    marginLeft: 10,
  },
  title: {
    fontFamily: 'Verdana',
    fontSize: 15,
    color: '#000000',
  },
  points: {
    fontSize: 11,
    color: "#828282",
  },
  listView: {
    paddingTop: 15,
    backgroundColor: '#F5FCFF',
  }
});

AppRegistry.registerComponent('HNReactNative', () => HNReactNative);
<commit_msg>Added a refresh button. Doesn't actually refresh yet.<commit_after>/**
 * A simple HN React Native client
 * https://github.com/nvladimiroff/HNReactNative
 */
'use strict';

var React = require('react-native');
var {
  AppRegistry,
  ListView,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} = React;

// Lets us open URLs. Facebook hasn't open sourced their official Android
// version of this yet. TODO: Remove when they do.
var WebIntent = require('react-native-webintent');

/**
 * API Docs: https://github.com/HackerNews/API
 * They suggest using Firebase's library, but I wasn't able to get it to work
 * on React Native (despite their claim of supporting it).
 */
var apiurl = "https://hacker-news.firebaseio.com/v0/";

var HNReactNative = React.createClass({
  /**
   * Our initial state consists of a list of the 30 top stories,
   * a ListView DataSource that contains a json object for each story
   * and a boolean to know if we've loaded everything or not yet.
   */
  getInitialState: function() {
    return {
      topStories: [],
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  },

  componentDidMount: function() {
    // First, we load the list of top stories.
    fetch(apiurl + "topstories.json")
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          topStories: responseData.slice(0,30),
        }); })
      .then(() => {
        // Once we have the list, we can load all individual JSON data for each
        // story. To do this, we create a list of Promises for each story
        // and then flatten them into a single one with Promise.all.
        Promise.all(this.state.topStories.map(this.fetchStory))
          .then((stories) => {
            // stories is our final list of JSON data that we will use for
            // the data source of each row.
            this.setState({
              dataSource: this.state.dataSource.cloneWithRows(stories),
              loaded: true,
            });
          }).done();
      }).done();
    },

  /**
   * Get the JSON of a single story. Returns a promise
   */
  fetchStory: function(story) {
    return fetch(apiurl + "item/" + story + ".json")
      .then((response) => response.json())
  },

  /**
   * The main render method that React will use to display the app.
   */
  render: function() {
    if (!this.state.loaded) {
      return (
        <LoadingView />
      );
    } else {
      return (
        <MainView
          dataSource={this.state.dataSource}
          refresh={() => { return; } }
        />
      );
    }
  },
});

/**
 * The main view that the user sees: the header and a list of all the stories.
 */
var MainView = React.createClass({
  render: function() {
    return (
      <View style={styles.container}>
        <View style={styles.header}>
          <Text style={styles.headerText}>
            Hacker News
          </Text>
          <TouchableOpacity onPress={this.props.refresh} style={styles.refresh}>
            <Text>
              Refresh
            </Text>
          </TouchableOpacity>
        </View>
        <ListView
            dataSource={this.props.dataSource}
            renderRow={this.renderStory}
            style={styles.listView}
          />
      </View>
    );
  },

  renderStory: function(storyJson) {
    return (
      <Row
        onButtonPress={() => WebIntent.open(storyJson.url)}
        story={storyJson}
      />
    );
  },
});

/**
 * Just display a 'Loading...' message while the data is downloaded.
 */
var LoadingView = React.createClass({
  render: function() {
    return (
      <View style={styles.loadingContainer}>
        <Text style={styles.loading}>
          Loading...
        </Text>
      </View>
    );
  },
});

/**
 * Assuming we have the JSON object for a story, render a single row.
 */
var Row = React.createClass({
  render: function() {
    return (
      // TouchableNativeFeedback looks nicer, but is currently Android only.
      <TouchableOpacity onPress={this.props.onButtonPress}>
        <View style={styles.rowContainer}>
          <Text style={styles.title}>
            {this.props.story.title}
          </Text>
          <Text style={styles.points}>
            {this.props.story.score} points
          </Text>
        </View>
      </TouchableOpacity>
    );
  },
});

var styles = StyleSheet.create({
  container: {
    flex: 1
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  loading: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    backgroundColor: "#ff6600",
  },
  headerText: {
    fontSize: 18,
    fontWeight: '500',
    marginLeft: 20,
    marginTop: 7,
    marginBottom: 7,
    fontFamily: 'Verdana',
    color: '#000000',
  },
  rowContainer: {
    justifyContent: 'center',
    margin: 5,
    marginLeft: 10,
  },
  title: {
    fontFamily: 'Verdana',
    fontSize: 15,
    color: '#000000',
  },
  points: {
    fontSize: 11,
    color: "#828282",
  },
  listView: {
    paddingTop: 15,
    backgroundColor: '#F5FCFF',
  },
  refresh: {
    alignSelf: 'center',
    marginRight: 10,
  }
});

AppRegistry.registerComponent('HNReactNative', () => HNReactNative);
<|endoftext|>


<commit_before>'use strict';

import React, { PropTypes } from 'react';
import ReactNative, { requireNativeComponent, View, StyleSheet } from 'react-native';

var {
    addons: { PureRenderMixin },
    NativeModules: { UIManager, CrosswalkWebViewManager: { JSNavigationScheme } }
} = ReactNative;

var resolveAssetSource = require('resolveAssetSource')

var WEBVIEW_REF = 'crosswalkWebView';

var CrosswalkWebView = React.createClass({
    mixins:    [PureRenderMixin],
    statics:   { JSNavigationScheme },
    propTypes: {
        localhost:               PropTypes.bool.isRequired,
        onNavigationStateChange: PropTypes.func,
        url:                     PropTypes.string,
        injectedJavaScript:      PropTypes.string,
        source:                  PropTypes.oneOfType([
          PropTypes.shape({
           uri: PropTypes.string, // uri to load in webview
          }),
          PropTypes.shape({
           html: PropTypes.string, // static html to load in webview
          }),
           PropTypes.number, // used internally by packager
        ]),
        ...View.propTypes
    },
    getDefaultProps () {
        return {
            localhost: false
        };
    },
    render () {
      var source = this.props.source || {};
      if (this.props.url) {
        source.uri = this.props.url;
      }
      return (
          <NativeCrosswalkWebView
            { ...this.props }
            ref={ WEBVIEW_REF }
            source={resolveAssetSource(source)}
            onNavigationStateChange={ this.onNavigationStateChange } />
      );
    },
    getWebViewHandle () {
        return React.findNodeHandle(this.refs[WEBVIEW_REF]);
    },
    onNavigationStateChange (event) {
        var { onNavigationStateChange } = this.props;
        if (onNavigationStateChange) {
            onNavigationStateChange(event.nativeEvent);
        }
    },
    goBack () {
        UIManager.dispatchViewManagerCommand(
            this.getWebViewHandle(),
            UIManager.NativeCrosswalkWebView.Commands.goBack,
            null
        );
    },
    goForward () {
        UIManager.dispatchViewManagerCommand(
            this.getWebViewHandle(),
            UIManager.NativeCrosswalkWebView.Commands.goForward,
            null
        );
    },
    reload () {
        UIManager.dispatchViewManagerCommand(
            this.getWebViewHandle(),
            UIManager.NativeCrosswalkWebView.Commands.reload,
            null
        );
    }
});

var NativeCrosswalkWebView = requireNativeComponent('CrosswalkWebView', CrosswalkWebView);

export default CrosswalkWebView;
<commit_msg>Change resolveAssetSource require to use correct path<commit_after>'use strict';

import React, { PropTypes } from 'react';
import ReactNative, { requireNativeComponent, View, StyleSheet } from 'react-native';

var {
    addons: { PureRenderMixin },
    NativeModules: { UIManager, CrosswalkWebViewManager: { JSNavigationScheme } }
} = ReactNative;

var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource')

var WEBVIEW_REF = 'crosswalkWebView';

var CrosswalkWebView = React.createClass({
    mixins:    [PureRenderMixin],
    statics:   { JSNavigationScheme },
    propTypes: {
        localhost:               PropTypes.bool.isRequired,
        onNavigationStateChange: PropTypes.func,
        url:                     PropTypes.string,
        injectedJavaScript:      PropTypes.string,
        source:                  PropTypes.oneOfType([
          PropTypes.shape({
           uri: PropTypes.string, // uri to load in webview
          }),
          PropTypes.shape({
           html: PropTypes.string, // static html to load in webview
          }),
           PropTypes.number, // used internally by packager
        ]),
        ...View.propTypes
    },
    getDefaultProps () {
        return {
            localhost: false
        };
    },
    render () {
      var source = this.props.source || {};
      if (this.props.url) {
        source.uri = this.props.url;
      }
      return (
          <NativeCrosswalkWebView
            { ...this.props }
            ref={ WEBVIEW_REF }
            source={resolveAssetSource(source)}
            onNavigationStateChange={ this.onNavigationStateChange } />
      );
    },
    getWebViewHandle () {
        return React.findNodeHandle(this.refs[WEBVIEW_REF]);
    },
    onNavigationStateChange (event) {
        var { onNavigationStateChange } = this.props;
        if (onNavigationStateChange) {
            onNavigationStateChange(event.nativeEvent);
        }
    },
    goBack () {
        UIManager.dispatchViewManagerCommand(
            this.getWebViewHandle(),
            UIManager.NativeCrosswalkWebView.Commands.goBack,
            null
        );
    },
    goForward () {
        UIManager.dispatchViewManagerCommand(
            this.getWebViewHandle(),
            UIManager.NativeCrosswalkWebView.Commands.goForward,
            null
        );
    },
    reload () {
        UIManager.dispatchViewManagerCommand(
            this.getWebViewHandle(),
            UIManager.NativeCrosswalkWebView.Commands.reload,
            null
        );
    }
});

var NativeCrosswalkWebView = requireNativeComponent('CrosswalkWebView', CrosswalkWebView);

export default CrosswalkWebView;
<|endoftext|>


<commit_before>// Copyright 2012, 2013 Patrick Wang <kk1fff@patrickz.net>
//
// 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.

var exec    = require('child_process').exec,
    limit   = 2,
    running = 0;

var cmdQueue = [];

function runQueue() {
  var queuedItem;
  if (running < limit) {
    while (!queuedItem && cmdQueue.length > 0) {
      queuedItem = cmdQueue.shift();
    }
    if (queuedItem) {
      running++;
      exec(queuedItem.cmd, function() {
        var args = Array.prototype.slice.call(arguments);
        // Callback, then run next task.
        queuedItem.cb.apply(this, args);
        running--;
        setTimeout(runQueue(), 0);
      });
    }
  }
};

exports.exec = function execEnqueue(cmd, callback) {
  cmdQueue.push({
    cmd: cmd,
    cb: callback
  });
  setTimeout(runQueue, 0);
};
<commit_msg>Fix execqueue<commit_after>// Copyright 2012, 2013 Patrick Wang <kk1fff@patrickz.net>
//
// 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.

var exec    = require('child_process').exec,
    limit   = 2,
    running = 0;

var cmdQueue = [];

function runQueue() {
  var queuedItem;
  if (running < limit) {
    while (!queuedItem && cmdQueue.length > 0) {
      queuedItem = cmdQueue.shift();
    }
    if (queuedItem) {
      running++;
      exec(queuedItem.cmd, function() {
        var args = Array.prototype.slice.call(arguments);
        // Callback, then run next task.
        queuedItem.cb.apply(this, args);
        running--;
        setTimeout(runQueue, 0);
      });
    }
  }
};

exports.exec = function execEnqueue(cmd, callback) {
  cmdQueue.push({
    cmd: cmd,
    cb: callback
  });
  setTimeout(runQueue, 0);
};
<|endoftext|>


<commit_before>var async = require('async');
var defaults = require('lodash.defaults');
var glob = require('glob');
var path = require('path');

var routes = require('../server/routes.json');
var serviceId = process.env.FASTLY_SERVICE_ID
var s3Bucket = process.env.S3_BUCKET_NAME;

var fastly = require('./lib/fastly-extended')(process.env.FASTLY_API_KEY, serviceId);

const PASS_REQUEST_CONDITION_NAME = 'Pass';
const NOT_PASS_REQUEST_CONDITION_NAME = '!(Pass)'
const PASS_CACHE_CONDITION_NAME = 'Cache Pass';
const BUCKET_NAME_HEADER_NAME = 'Bucket name';

var extraAppRoutes = [
    // Homepage with querystring.
    // TODO: Should this be added for every route?
    '^/\\?',
    // Version output by build
    '/version\.txt$',
    // View html
    '^/[^\/]*\.html'
];

/*
 * Given the relative path to the static directory, return an array of
 * patterns matching the files and directories there.
 */
var getStaticPaths = function (pathToStatic) {
    var staticPaths = glob.sync(path.resolve(__dirname, pathToStatic));
    return staticPaths.map( function (pathName) {
        // Reduce absolute path to relative paths like '/js'
        var base = path.dirname(path.resolve(__dirname, pathToStatic));
        return '^' + pathName.replace(base, '');
    });
}

/*
 * Given a list of express routes, return a list of patterns to match
 * the express route and a static view file associated with the route
 */
var getViewPaths = function (routes) {
    return routes.map(function (route) {
        return route.pattern;
    });
}

/*
 * Given a list of patterns for paths, OR all of them together into one
 * string suitable for a Fastly condition
 */
var pathsToCondition = function (paths) {
    return paths.reduce(function(conditionString, pattern) {
        var patternCondition = 'req.url ~ "' + pattern + '"';
        return conditionString + (conditionString ? ' || ' : '') + patternCondition;
    }, '');
}

/*
 * Combine static paths, routes, and any additional paths to a single
 * fastly condition to match req.url
 */
var getAppRouteCondition = function (pathToStatic, routes, additionalPaths) {
    var staticPaths = getStaticPaths(pathToStatic);
    var viewPaths = getViewPaths(routes);
    var allPaths = [].concat(staticPaths, viewPaths, additionalPaths);
    return pathsToCondition(allPaths);
}

var getConditionNameForView = function (view) {
    return 'routes/' + view;
};

var getHeaderNameForView = function (view) {
    return 'rewrites/' + view;
}

var notPassCondition = {
    name: NOT_PASS_REQUEST_CONDITION_NAME,
    statement: getAppRouteCondition('../static/*', routes, extraAppRoutes),
    type: 'REQUEST',
    priority: 10
};
var passCondition = {
    name: PASS_REQUEST_CONDITION_NAME,
    statement: fastly.negateConditionStatement(notPassCondition.statement),
    type: 'REQUEST',
    priority: 10
};
var passCacheCondition = defaults({name: PASS_CACHE_CONDITION_NAME, type: 'CACHE'}, passCondition);
var bucketNameHeader = {
    name: BUCKET_NAME_HEADER_NAME,
    action: 'set',
    ignore_if_set: 0,
    type: 'REQUEST',
    dst: 'http.host',
    src: '"' + s3Bucket + '"',
    request_condition: NOT_PASS_REQUEST_CONDITION_NAME,
    priority: 1
};

async.waterfall([
    fastly.getLatestVersion.bind(fastly),
    function (version, next) {
        if (version.active) return next('Latest version is active. Will not modify.');
        if (version.locked) return next('Latest version is locked. Cannot modify.');
        async.parallel([
            function (cb) {
                async.series([
                    async.apply(fastly.setCondition.bind(fastly), version.number, notPassCondition),
                    async.apply(fastly.setFastlyHeader.bind(fastly), version.number, bucketNameHeader)
                ], cb)
            },
            function (cb) {
                async.series([
                    async.apply(fastly.setCondition.bind(fastly), version.number, passCondition),
                    function (cb) {
                        async.parallel([
                            async.apply(
                                fastly.request.bind(fastly), 'PUT',
                                fastly.getFastlyAPIPrefix(serviceId, version.number) + '/request_settings/Pass',
                                {request_condition: PASS_REQUEST_CONDITION_NAME}
                             ),
                            async.apply(
                                fastly.request.bind(fastly), 'PUT',
                                fastly.getFastlyAPIPrefix(serviceId, version.number) + '/backend/femto',
                                {request_condition: PASS_REQUEST_CONDITION_NAME}
                            )
                        ], cb)
                    }
                ], cb)
            },
            function (cb) {
                async.series([
                    async.apply(fastly.setCondition.bind(fastly), version.number, passCacheCondition),
                    async.apply(
                        fastly.request.bind(fastly), 'PUT',
                        fastly.getFastlyAPIPrefix(serviceId, version.number) + '/cache_settings/Pass',
                        {request_condition: PASS_CACHE_CONDITION_NAME}
                    ),
                ], cb)
            },
            function (cb) {
                async.forEachOf(routes, function (route, id, cb) {
                    var condition = {
                        name: getConditionNameForView(route.view),
                        statement: 'req.url ~ "' + route.pattern + '"',
                        type: 'REQUEST',
                        priority: 10
                    };
                    var header = {
                        name: getHeaderNameForView(route.view),
                        action: 'set',
                        ignore_if_set: 0,
                        type: 'request',
                        dst: 'url',
                        src: '"/' + route.view + '.html"',
                        request_condition: getConditionNameForView(route.view),
                        priority: id
                    };
                    async.series([
                        async.apply(fastly.setCondition.bind(fastly), version.number, condition),
                        async.apply(fastly.setFastlyHeader.bind(fastly), version.number, header)
                    ], cb);
                }, cb)
            }
        ], next);
    }], function (err, responses) {
        if (err) {
            console.log(err);
            process.exit(1);
        }
    }
);<commit_msg>Fix cache_settings request<commit_after>var async = require('async');
var defaults = require('lodash.defaults');
var glob = require('glob');
var path = require('path');

var routes = require('../server/routes.json');
var serviceId = process.env.FASTLY_SERVICE_ID
var s3Bucket = process.env.S3_BUCKET_NAME;

var fastly = require('./lib/fastly-extended')(process.env.FASTLY_API_KEY, serviceId);

const PASS_REQUEST_CONDITION_NAME = 'Pass';
const NOT_PASS_REQUEST_CONDITION_NAME = '!(Pass)'
const PASS_CACHE_CONDITION_NAME = 'Cache Pass';
const BUCKET_NAME_HEADER_NAME = 'Bucket name';

var extraAppRoutes = [
    // Homepage with querystring.
    // TODO: Should this be added for every route?
    '^/\\?',
    // Version output by build
    '/version\.txt$',
    // View html
    '^/[^\/]*\.html'
];

/*
 * Given the relative path to the static directory, return an array of
 * patterns matching the files and directories there.
 */
var getStaticPaths = function (pathToStatic) {
    var staticPaths = glob.sync(path.resolve(__dirname, pathToStatic));
    return staticPaths.map( function (pathName) {
        // Reduce absolute path to relative paths like '/js'
        var base = path.dirname(path.resolve(__dirname, pathToStatic));
        return '^' + pathName.replace(base, '');
    });
}

/*
 * Given a list of express routes, return a list of patterns to match
 * the express route and a static view file associated with the route
 */
var getViewPaths = function (routes) {
    return routes.map(function (route) {
        return route.pattern;
    });
}

/*
 * Given a list of patterns for paths, OR all of them together into one
 * string suitable for a Fastly condition
 */
var pathsToCondition = function (paths) {
    return paths.reduce(function(conditionString, pattern) {
        var patternCondition = 'req.url ~ "' + pattern + '"';
        return conditionString + (conditionString ? ' || ' : '') + patternCondition;
    }, '');
}

/*
 * Combine static paths, routes, and any additional paths to a single
 * fastly condition to match req.url
 */
var getAppRouteCondition = function (pathToStatic, routes, additionalPaths) {
    var staticPaths = getStaticPaths(pathToStatic);
    var viewPaths = getViewPaths(routes);
    var allPaths = [].concat(staticPaths, viewPaths, additionalPaths);
    return pathsToCondition(allPaths);
}

var getConditionNameForView = function (view) {
    return 'routes/' + view;
};

var getHeaderNameForView = function (view) {
    return 'rewrites/' + view;
}

var notPassCondition = {
    name: NOT_PASS_REQUEST_CONDITION_NAME,
    statement: getAppRouteCondition('../static/*', routes, extraAppRoutes),
    type: 'REQUEST',
    priority: 10
};
var passCondition = {
    name: PASS_REQUEST_CONDITION_NAME,
    statement: fastly.negateConditionStatement(notPassCondition.statement),
    type: 'REQUEST',
    priority: 10
};
var passCacheCondition = defaults({name: PASS_CACHE_CONDITION_NAME, type: 'CACHE'}, passCondition);
var bucketNameHeader = {
    name: BUCKET_NAME_HEADER_NAME,
    action: 'set',
    ignore_if_set: 0,
    type: 'REQUEST',
    dst: 'http.host',
    src: '"' + s3Bucket + '"',
    request_condition: NOT_PASS_REQUEST_CONDITION_NAME,
    priority: 1
};

async.waterfall([
    fastly.getLatestVersion.bind(fastly),
    function (version, next) {
        if (version.active) return next('Latest version is active. Will not modify.');
        if (version.locked) return next('Latest version is locked. Cannot modify.');
        async.parallel([
            function (cb) {
                async.series([
                    async.apply(fastly.setCondition.bind(fastly), version.number, notPassCondition),
                    async.apply(fastly.setFastlyHeader.bind(fastly), version.number, bucketNameHeader)
                ], cb)
            },
            function (cb) {
                async.series([
                    async.apply(fastly.setCondition.bind(fastly), version.number, passCondition),
                    function (cb) {
                        async.parallel([
                            async.apply(
                                fastly.request.bind(fastly), 'PUT',
                                fastly.getFastlyAPIPrefix(serviceId, version.number) + '/request_settings/Pass',
                                {request_condition: PASS_REQUEST_CONDITION_NAME}
                             ),
                            async.apply(
                                fastly.request.bind(fastly), 'PUT',
                                fastly.getFastlyAPIPrefix(serviceId, version.number) + '/backend/femto',
                                {request_condition: PASS_REQUEST_CONDITION_NAME}
                            )
                        ], cb)
                    }
                ], cb)
            },
            function (cb) {
                async.series([
                    async.apply(fastly.setCondition.bind(fastly), version.number, passCacheCondition),
                    async.apply(
                        fastly.request.bind(fastly), 'PUT',
                        fastly.getFastlyAPIPrefix(serviceId, version.number) + '/cache_settings/Pass',
                        {cache_condition: PASS_CACHE_CONDITION_NAME}
                    ),
                ], cb)
            },
            function (cb) {
                async.forEachOf(routes, function (route, id, cb) {
                    var condition = {
                        name: getConditionNameForView(route.view),
                        statement: 'req.url ~ "' + route.pattern + '"',
                        type: 'REQUEST',
                        priority: 10
                    };
                    var header = {
                        name: getHeaderNameForView(route.view),
                        action: 'set',
                        ignore_if_set: 0,
                        type: 'request',
                        dst: 'url',
                        src: '"/' + route.view + '.html"',
                        request_condition: getConditionNameForView(route.view),
                        priority: id
                    };
                    async.series([
                        async.apply(fastly.setCondition.bind(fastly), version.number, condition),
                        async.apply(fastly.setFastlyHeader.bind(fastly), version.number, header)
                    ], cb);
                }, cb)
            }
        ], next);
    }], function (err, responses) {
        if (err) {
            console.log(err);
            process.exit(1);
        }
    }
);<|endoftext|>


<commit_before>(function(){

"use strict";

var WSController = function(endpoint){
	this.endpoint = endpoint;
	this.initWebSocket();
	this.stats = {
		'live': [],
		'lifetime': {},
		'global': [0, 0],
		'project': {},
		'currentScanRate': 0
	};
	this.reconnectTimer = null;
	this.scanRateBucket = [];
	this.prevScanBucketIndex = 0;
	this.updateInterval = 500;

	for (var i = 0; i < 60; i++) {
		this.scanRateBucket.push(0);
	}
};

WSController.prototype.initWebSocket = function () {
	var scheme = window.location.protocol == "https:" ? "wss://" : "ws://";
	this.ws = new WebSocket("ws://" + window.location.host + this.endpoint);
	this.ws.onmessage = this._onmessage.bind(this);
	this.ws.onopen = this.onConnect.bind(this);
	this.ws.onerror = this.onDisconnect.bind(this);
	this.ws.onclose = this.onDisconnect.bind(this);
}

WSController.prototype._onmessage = function(evt){
	var message = JSON.parse(evt.data);

	if(message.live){
		this.stats.live = message.live;
	}

	if(message.lifetime){
		this.stats.lifetime = message.lifetime;
	}

	if(message.global){
		this.stats.global = message.global;
	}

	if(message.project){
		this.stats.project = message.project;
	}

	if(message.live_new){
		this.stats.live.unshift(message.live_new);

		var lifetime = this.stats.lifetime[message.live_new.username];
		if(lifetime === undefined){
			lifetime = [0, 0];
			this.stats.lifetime[message.live_new.username] = lifetime;
		}
		lifetime[0] += message.live_new.found;
		lifetime[1] += message.live_new.scanned;

		this.stats.global[0] += message.live_new.found;
		this.stats.global[1] += message.live_new.scanned;

		var project = this.stats.project[message.live_new.project];
		if(lifetime === undefined){
			project = [0, 0];
			this.stats.project[message.live_new.project] = project;
		}
		project[0] += message.live_new.found;
		project[1] += message.live_new.scanned;

		var dateNow = new Date();
		var bucketIndex = dateNow.getUTCSeconds();

		if (this.prevScanBucketIndex != bucketIndex) {
			this.prevScanBucketIndex = bucketIndex;
			this.scanRateBucket[bucketIndex] = message.live_new.scanned;
		} else {
			this.scanRateBucket[bucketIndex] += message.live_new.scanned;
		}

		var sum = 0;
		for (var i = 0; i < 60; i++) {
			sum += this.scanRateBucket[i];
		}
		this.stats.currentScanRate = sum / 60.0;
	}

	this.onMessage(message);
};

WSController.prototype.onMessage = function(message){
};

WSController.prototype.onConnect = function () {
	var wsDiv = document.getElementById('websocket_message');
	wsDiv.style.display = 'none';
};

WSController.prototype.onDisconnect = function () {
	var wsDiv = document.getElementById('websocket_message');
	wsDiv.style.display = 'inherit';

	if (this.reconnectTimer) {
		clearTimeout(this.reconnectTimer);
	}
	this.reconnectTimer = setTimeout(this.initWebSocket.bind(this), 60000);
};

var app = angular.module("stats", []);
var lastUpdate = new Date(0);

app.constant("endpoint", "/api/live_stats");
app.constant("max_display", 30);

app.service("ws", ["endpoint", function(endpoint){
	return new WSController(endpoint);
}]);

app.filter("toArray", function(){
	return function(obj) {
		var result = [];
		angular.forEach(obj, function(val, key) {
			result.push([key, val]);
		});
		return result;
	};
});

app.controller("StatsController", ["$scope", "$filter", "ws", "max_display", function($scope, $filter, ws, max_display){
	$scope.stats = ws.stats;
	$scope.totalLimit = 30;
	$scope.recentLimit = 30;
	$scope.getScanned = function(item){
		return item[1][1];
	};
	ws.onMessage = function(){
		ws.stats.live.splice($scope.recentLimit - 1, ws.stats.live.length);
		// somehow computing this in page cause infinite digest cycles
		$scope.lifetime = $filter("toArray")(ws.stats.lifetime);

		var dateNow = new Date();

		if (dateNow - lastUpdate > ws.updateInterval) {
			$scope.$apply();
			lastUpdate = dateNow;
			var afterDate = new Date();
			var applyDifference = afterDate - dateNow;

			if (applyDifference > 50) {
				ws.updateInterval = 1000 + Math.min(10000, applyDifference * 2);
			} else {
				ws.updateInterval = 500;
			}
		}
	};
}]);

document.getElementById('websocket_message').innerHTML = 'Sorry, live stat updates is currently unavailable.';

})();<commit_msg>fixup! tracker web: Connect websocket to wss if https.<commit_after>(function(){

"use strict";

var WSController = function(endpoint){
	this.endpoint = endpoint;
	this.initWebSocket();
	this.stats = {
		'live': [],
		'lifetime': {},
		'global': [0, 0],
		'project': {},
		'currentScanRate': 0
	};
	this.reconnectTimer = null;
	this.scanRateBucket = [];
	this.prevScanBucketIndex = 0;
	this.updateInterval = 500;

	for (var i = 0; i < 60; i++) {
		this.scanRateBucket.push(0);
	}
};

WSController.prototype.initWebSocket = function () {
	var scheme = window.location.protocol == "https:" ? "wss://" : "ws://";
	this.ws = new WebSocket("" + scheme + window.location.host + this.endpoint);
	this.ws.onmessage = this._onmessage.bind(this);
	this.ws.onopen = this.onConnect.bind(this);
	this.ws.onerror = this.onDisconnect.bind(this);
	this.ws.onclose = this.onDisconnect.bind(this);
}

WSController.prototype._onmessage = function(evt){
	var message = JSON.parse(evt.data);

	if(message.live){
		this.stats.live = message.live;
	}

	if(message.lifetime){
		this.stats.lifetime = message.lifetime;
	}

	if(message.global){
		this.stats.global = message.global;
	}

	if(message.project){
		this.stats.project = message.project;
	}

	if(message.live_new){
		this.stats.live.unshift(message.live_new);

		var lifetime = this.stats.lifetime[message.live_new.username];
		if(lifetime === undefined){
			lifetime = [0, 0];
			this.stats.lifetime[message.live_new.username] = lifetime;
		}
		lifetime[0] += message.live_new.found;
		lifetime[1] += message.live_new.scanned;

		this.stats.global[0] += message.live_new.found;
		this.stats.global[1] += message.live_new.scanned;

		var project = this.stats.project[message.live_new.project];
		if(lifetime === undefined){
			project = [0, 0];
			this.stats.project[message.live_new.project] = project;
		}
		project[0] += message.live_new.found;
		project[1] += message.live_new.scanned;

		var dateNow = new Date();
		var bucketIndex = dateNow.getUTCSeconds();

		if (this.prevScanBucketIndex != bucketIndex) {
			this.prevScanBucketIndex = bucketIndex;
			this.scanRateBucket[bucketIndex] = message.live_new.scanned;
		} else {
			this.scanRateBucket[bucketIndex] += message.live_new.scanned;
		}

		var sum = 0;
		for (var i = 0; i < 60; i++) {
			sum += this.scanRateBucket[i];
		}
		this.stats.currentScanRate = sum / 60.0;
	}

	this.onMessage(message);
};

WSController.prototype.onMessage = function(message){
};

WSController.prototype.onConnect = function () {
	var wsDiv = document.getElementById('websocket_message');
	wsDiv.style.display = 'none';
};

WSController.prototype.onDisconnect = function () {
	var wsDiv = document.getElementById('websocket_message');
	wsDiv.style.display = 'inherit';

	if (this.reconnectTimer) {
		clearTimeout(this.reconnectTimer);
	}
	this.reconnectTimer = setTimeout(this.initWebSocket.bind(this), 60000);
};

var app = angular.module("stats", []);
var lastUpdate = new Date(0);

app.constant("endpoint", "/api/live_stats");
app.constant("max_display", 30);

app.service("ws", ["endpoint", function(endpoint){
	return new WSController(endpoint);
}]);

app.filter("toArray", function(){
	return function(obj) {
		var result = [];
		angular.forEach(obj, function(val, key) {
			result.push([key, val]);
		});
		return result;
	};
});

app.controller("StatsController", ["$scope", "$filter", "ws", "max_display", function($scope, $filter, ws, max_display){
	$scope.stats = ws.stats;
	$scope.totalLimit = 30;
	$scope.recentLimit = 30;
	$scope.getScanned = function(item){
		return item[1][1];
	};
	ws.onMessage = function(){
		ws.stats.live.splice($scope.recentLimit - 1, ws.stats.live.length);
		// somehow computing this in page cause infinite digest cycles
		$scope.lifetime = $filter("toArray")(ws.stats.lifetime);

		var dateNow = new Date();

		if (dateNow - lastUpdate > ws.updateInterval) {
			$scope.$apply();
			lastUpdate = dateNow;
			var afterDate = new Date();
			var applyDifference = afterDate - dateNow;

			if (applyDifference > 50) {
				ws.updateInterval = 1000 + Math.min(10000, applyDifference * 2);
			} else {
				ws.updateInterval = 500;
			}
		}
	};
}]);

document.getElementById('websocket_message').innerHTML = 'Sorry, live stat updates is currently unavailable.';

})();<|endoftext|>


<commit_before>(function() {

var _requestCache = {};

/**
 * defines the container for an overlay.
 * It places the overlay
 */
var Overlay = {

  _types: {},

  // register a type to be created
  register: function(type, creatorFn) {
    Overlay._types[type] = creatorFn;
  },

  // create a type given the data
  // raise an exception if the type does not exist
  create: function(type, vis, data) {
    var t = Overlay._types[type];
    if(!t) {
      cdb.log.error("Overlay: " + type + " does not exist");
    }
    var widget = t(data, vis);
    widget.type = type;
    return widget;
  }
};

cdb.vis.Overlay = Overlay;

// layer factory
var Layers = {

  _types: {},

  register: function(type, creatorFn) {
    this._types[type] = creatorFn;
  },

  create: function(type, vis, data) {
    if(!type) {
      cdb.log.error("creating a layer without type");
      return null;
    }
    var t = this._types[type.toLowerCase()];

    var c = {};
    c.type = type;
    _.extend(c, data, data.options);
    return new t(vis, c);
  }

};

cdb.vis.Layers = Layers;

var Loader = cdb.vis.Loader = {

  queue: [],
  current: undefined,
  _script: null,
  head: null,

  get: function(url, callback) {
    if(!Loader._script) {
      Loader.current = callback;
      var script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = url + (~url.indexOf('?') ? '&' : '?') + 'callback=vizjson';
      script.async = true;
      Loader._script = script;
      if(!Loader.head) {
        Loader.head = document.getElementsByTagName('head')[0];
      }
      Loader.head.appendChild(script);
    } else {
      Loader.queue.push([url, callback]);
    }
  }

};

window.vizjson = function(data) {
  Loader.current && Loader.current(data);
  // remove script
  Loader.head.removeChild(Loader._script);
  Loader._script = null;
  // next element
  var a = Loader.queue.shift();
  if(a) {
    Loader.get(a[0], a[1]);
  }
};

/**
 * visulization creation
 */
var Vis = cdb.core.View.extend({

  initialize: function() {
    _.bindAll(this, 'loadingTiles', 'loadTiles');

    this.https = false;
    this.overlays = [];

    if(this.options.mapView) {
      this.mapView = this.options.mapView;
      this.map = this.mapView.map;
    }
  },

  load: function(data, options) {
    var self = this;
    if(typeof(data) === 'string') {
      var url = data;
      cdb.vis.Loader.get(url, function(data) {
        if(data) {
          self.load(data, options);
        } else {
          self.trigger('error', 'error fetching viz.json file');
        }
      });
      return this;
    }

    // configure the vis in http or https
    if(window && window.location.protocol && window.location.protocol === 'https:') {
      this.https = true;
    }

    if(data.https) {
      this.https = data.https;
    }

    if(options) {
      this._applyOptions(data, options);
      this.cartodb_logo = options.cartodb_logo;
    }

    // map
    data.maxZoom || (data.maxZoom = 20);
    data.minZoom || (data.minZoom = 0);

    var mapConfig = {
      title: data.title,
      description: data.description,
      maxZoom: data.maxZoom,
      minZoom: data.minZoom,
      provider: data.map_provider
    };

    // if the boundaries are defined, we add them to the map
    if(data.bounding_box_sw && data.bounding_box_ne) {
      mapConfig.bounding_box_sw = data.bounding_box_sw;
      mapConfig.bounding_box_ne = data.bounding_box_ne;
    }
    if(data.bounds) {
      mapConfig.view_bounds_sw = data.bounds[0];
      mapConfig.view_bounds_ne = data.bounds[1];
    } else {
      var center = data.center;
      if (typeof(center) === "string") {
        center = $.parseJSON(center);
      }
      mapConfig.center = center || [0, 0];
      mapConfig.zoom = data.zoom == undefined ? 4: data.zoom;
    }

    var map = new cdb.geo.Map(mapConfig);
    this.map = map;
    this.updated_at = data.updated_at || new Date().getTime();

    var div = $('<div>').css({
      position: 'relative',
      width: '100%',
      height: '100%'
    });
    this.container = div;

    // Another div to prevent leaflet grabs the div
    var div_hack = $('<div>')
      .addClass("map-wrapper")
      .css({
        position: "absolute",
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        width: '100%'
      });

    div.append(div_hack);
    this.$el.append(div);

    // Create the map
    var mapView = new cdb.geo.MapView.create(div_hack, map);
    this.mapView = mapView;

    // Add layers
    for(var i in data.layers) {
      var layerData = data.layers[i];
      this.loadLayer(layerData);
    }

    // Create the overlays
    for (var i in data.overlays) {
      this.addOverlay(data.overlays[i]);
    }

    _.defer(function() {
      self.trigger('done', self, self.getLayers());
    })

    return this;
  },

  addOverlay: function(overlay) {
    overlay.map = this.map;
    var v = Overlay.create(overlay.type, this, overlay);

    if (v) {
      // Save tiles loader view for later
      if (overlay.type == "loader") {
        this.loader = v;
      }

      this.addView(v);
      this.container.append(v.el);
      this.overlays.push(v);

      v.bind('clean', function() {
        for(var i in this.overlays) {
          var o = this.overlays[i];
          if(v.cid === o.cid) {
            this.overlays.splice(i, 1)
            return;
          }
        }
      }, this);

      // Set map position correctly taking into account
      // header height
      if (overlay.type == "header") {
        this.setMapPosition();
      }
    }
    return v;
  },

  // change vizjson based on options
  _applyOptions: function(vizjson, opt) {
    opt = opt || {};
    opt = _.defaults(opt, {
      search: false,
      title: false,
      description: false,
      tiles_loader: true,
      zoomControl: true,
      loaderControl: true,
      searchControl: false
    });
    vizjson.overlays = vizjson.overlays || [];
    vizjson.layers = vizjson.layers || [];

    function search_overlay(name) {
      if(!vizjson.overlays) return null;
      for(var i = 0; i < vizjson.overlays.length; ++i) {
        if(vizjson.overlays[i].type === name) {
          return vizjson.overlays[i];
        }
      }
    }

    function remove_overlay(name) {
      if(!vizjson.overlays) return;
      for(var i = 0; i < vizjson.overlays.length; ++i) {
        if(vizjson.overlays[i].type === name) {
          vizjson.overlays.splice(i, 1);
          return;
        }
      }
    }

    if(opt.https) {
      this.https = true;
    }

    // remove search if the vizualization does not contain it
    if (opt.search || opt.searchControl) {
      vizjson.overlays.push({
         type: "search"
      });
    }

    if(opt.title  || opt.description || opt.shareable) {
      vizjson.overlays.unshift({
        type: "header",
        shareable: opt.shareable ? true: false,
        url: vizjson.url
      });
    }

    if(!opt.title) {
      vizjson.title = null;
    }

    if(!opt.description) {
      vizjson.description = null;
    }

    if(!opt.tiles_loader) {
      remove_overlay('loader');
    }

    if(!opt.zoomControl) {
      remove_overlay('zoom');
    }

    if(!opt.loaderControl) {
      remove_overlay('loader');
    }

    // if bounds are present zoom and center will not taken into account
    if(opt.zoom !== undefined) {
      vizjson.zoom = parseFloat(opt.zoom);
      vizjson.bounds = null;
    }

    if(opt.center_lat !== undefined) {
      vizjson.center = [parseFloat(opt.center_lat), parseFloat(opt.center_lon)];
      vizjson.bounds = null;
    }

    if(opt.center !== undefined) {
      vizjson.center = opt.center;
      vizjson.bounds = null;
    }

    if(opt.sw_lat !== undefined) {
      vizjson.bounds = [
        [parseFloat(opt.sw_lat), parseFloat(opt.sw_lon)],
        [parseFloat(opt.ne_lat), parseFloat(opt.ne_lon)],
      ];
    }

    if(vizjson.layers.length > 1) {
      if(opt.sql) {
        vizjson.layers[1].options.query = opt.sql;
      }
      if(opt.style) {
        vizjson.layers[1].options.tile_style = opt.style;
      }

      vizjson.layers[1].options.no_cdn = opt.no_cdn;
    }

  },

  // Set map top position taking into account header height
  setMapPosition: function() {
    var header_h = this.$el.find(".header:not(.cartodb-popup)").outerHeight();

    this.$el
      .find("div.map-wrapper")
      .css("top", header_h);

    this.mapView.invalidateSize();
  },

  createLayer: function(layerData, opts) {
    var layerModel = Layers.create(layerData.type || layerData.kind, this, layerData);
    return this.mapView.createLayer(layerModel);
  },

  addInfowindow: function(layerView) {
    var model = layerView.model;
    var eventType = layerView.model.get('eventType') || 'featureClick';
    var infowindow = Overlay.create('infowindow', this, model.get('infowindow'), true);
    var mapView = this.mapView;
    mapView.addInfowindow(infowindow);

    var infowindowFields = layerView.model.get('infowindow');
    // HACK: REMOVE
    var port = model.get('sql_port');
    var domain = model.get('sql_domain') + (port ? ':' + port: '')
    var protocol = model.get('sql_protocol');
    var version = 'v1';
    if(domain.indexOf('cartodb.com') !== -1) {
      protocol = 'http';
      domain = "cartodb.com";
      version = 'v2';
    }

    var sql = new cartodb.SQL({
      user: model.get('user_name'),
      protocol: protocol,
      host: domain,
      version: version
    });

    // if the layer has no infowindow just pass the interaction
    // data to the infowindow
    layerView.bind(eventType, function(e, latlng, pos, data) {
        var cartodb_id = data.cartodb_id
        var fields = infowindowFields.fields;

        // Send request
        sql.execute("select {{fields}} from {{table_name}} where cartodb_id = {{cartodb_id }}", {
          fields: _.pluck(fields, 'name').join(','),
          cartodb_id: cartodb_id,
          table_name: model.get('table_name')
        })
        .done(function(interact_data) {
          if(interact_data.rows.length == 0 ) return;
          interact_data = interact_data.rows[0];
          if(infowindowFields) {
            var render_fields = [];
            var fields = infowindowFields.fields;
            for(var j = 0; j < fields.length; ++j) {
              var f = fields[j];
              if(interact_data[f.name] != undefined) {
                render_fields.push({
                  title: f.title ? f.name: null,
                  value: interact_data[f.name],
                  index: j ? j:null // mustache does not recognize 0 as false :(
                });
              }
            }
            // manage when there is no data to render
            if(render_fields.length === 0) {
              render_fields.push({
                title: null,
                value: 'No data available',
                index: j ? j:null, // mustache does not recognize 0 as false :(
                type: 'empty'
              });
            }
            content = render_fields;
          }

          infowindow.model.set({
            content:  {
              fields: content,
              data: interact_data
            }
          })
          infowindow.adjustPan();
        });

        // Show infowindow with loading state
        infowindow
          .setLatLng(latlng)
          .setLoading()
          .showInfowindow();
    });

    layerView.bind('featureOver', function(e, latlon, pxPos, data) {
      mapView.setCursor('pointer');
    });
    layerView.bind('featureOut', function() {
      mapView.setCursor('auto');
    });

    layerView.infowindow = infowindow.model;
  },

  loadLayer: function(layerData, opts) {
    var map = this.map;
    var mapView = this.mapView;
    layerData.type = layerData.kind;
    var layer_cid = map.addLayer(Layers.create(layerData.type || layerData.kind, this, layerData), opts);

    var layerView = mapView.getLayerByCid(layer_cid);

    // add the associated overlays
    if(layerData.infowindow &&
      layerData.infowindow.fields &&
      layerData.infowindow.fields.length > 0) {
      this.addInfowindow(layerView);
    }

    if (layerView) {
      layerView.bind('loading', this.loadingTiles);
      layerView.bind('load',    this.loadTiles);
    }

    return layerView;

  },

  loadingTiles: function() {
    if(this.loader) {
      this.loader.show()
    }
  },

  loadTiles: function() {
    if(this.loader) {
      this.loader.hide();
    }
  },

  error: function(fn) {
    return this.bind('error', fn);
  },

  done: function(fn) {
    return this.bind('done', fn);
  },

  // public methods
  //

  // get the native map used behind the scenes
  getNativeMap: function() {
    return this.mapView.getNativeMap();
  },

  // returns an array of layers
  getLayers: function() {
    var self = this;
    return this.map.layers.map(function(layer) {
      return self.mapView.getLayerByCid(layer.cid);
    });
  },

  getOverlays: function() {
    return this.overlays;
  },

  getOverlay: function(type) {
    return _(this.overlays).find(function(v) {
      return v.type == type;
    });
  }

});

cdb.vis.Vis = Vis;

})();
<commit_msg>fixed infowindow option in createVis fixed #31<commit_after>(function() {

var _requestCache = {};

/**
 * defines the container for an overlay.
 * It places the overlay
 */
var Overlay = {

  _types: {},

  // register a type to be created
  register: function(type, creatorFn) {
    Overlay._types[type] = creatorFn;
  },

  // create a type given the data
  // raise an exception if the type does not exist
  create: function(type, vis, data) {
    var t = Overlay._types[type];
    if(!t) {
      cdb.log.error("Overlay: " + type + " does not exist");
    }
    var widget = t(data, vis);
    widget.type = type;
    return widget;
  }
};

cdb.vis.Overlay = Overlay;

// layer factory
var Layers = {

  _types: {},

  register: function(type, creatorFn) {
    this._types[type] = creatorFn;
  },

  create: function(type, vis, data) {
    if(!type) {
      cdb.log.error("creating a layer without type");
      return null;
    }
    var t = this._types[type.toLowerCase()];

    var c = {};
    c.type = type;
    _.extend(c, data, data.options);
    return new t(vis, c);
  }

};

cdb.vis.Layers = Layers;

var Loader = cdb.vis.Loader = {

  queue: [],
  current: undefined,
  _script: null,
  head: null,

  get: function(url, callback) {
    if(!Loader._script) {
      Loader.current = callback;
      var script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = url + (~url.indexOf('?') ? '&' : '?') + 'callback=vizjson';
      script.async = true;
      Loader._script = script;
      if(!Loader.head) {
        Loader.head = document.getElementsByTagName('head')[0];
      }
      Loader.head.appendChild(script);
    } else {
      Loader.queue.push([url, callback]);
    }
  }

};

window.vizjson = function(data) {
  Loader.current && Loader.current(data);
  // remove script
  Loader.head.removeChild(Loader._script);
  Loader._script = null;
  // next element
  var a = Loader.queue.shift();
  if(a) {
    Loader.get(a[0], a[1]);
  }
};

/**
 * visulization creation
 */
var Vis = cdb.core.View.extend({

  initialize: function() {
    _.bindAll(this, 'loadingTiles', 'loadTiles');

    this.https = false;
    this.overlays = [];

    if(this.options.mapView) {
      this.mapView = this.options.mapView;
      this.map = this.mapView.map;
    }
  },

  load: function(data, options) {
    var self = this;
    if(typeof(data) === 'string') {
      var url = data;
      cdb.vis.Loader.get(url, function(data) {
        if(data) {
          self.load(data, options);
        } else {
          self.trigger('error', 'error fetching viz.json file');
        }
      });
      return this;
    }

    // configure the vis in http or https
    if(window && window.location.protocol && window.location.protocol === 'https:') {
      this.https = true;
    }

    if(data.https) {
      this.https = data.https;
    }

    if(options) {
      this._applyOptions(data, options);
      this.cartodb_logo = options.cartodb_logo;
    }

    // map
    data.maxZoom || (data.maxZoom = 20);
    data.minZoom || (data.minZoom = 0);

    var mapConfig = {
      title: data.title,
      description: data.description,
      maxZoom: data.maxZoom,
      minZoom: data.minZoom,
      provider: data.map_provider
    };

    // if the boundaries are defined, we add them to the map
    if(data.bounding_box_sw && data.bounding_box_ne) {
      mapConfig.bounding_box_sw = data.bounding_box_sw;
      mapConfig.bounding_box_ne = data.bounding_box_ne;
    }
    if(data.bounds) {
      mapConfig.view_bounds_sw = data.bounds[0];
      mapConfig.view_bounds_ne = data.bounds[1];
    } else {
      var center = data.center;
      if (typeof(center) === "string") {
        center = $.parseJSON(center);
      }
      mapConfig.center = center || [0, 0];
      mapConfig.zoom = data.zoom == undefined ? 4: data.zoom;
    }

    var map = new cdb.geo.Map(mapConfig);
    this.map = map;
    this.updated_at = data.updated_at || new Date().getTime();

    var div = $('<div>').css({
      position: 'relative',
      width: '100%',
      height: '100%'
    });
    this.container = div;

    // Another div to prevent leaflet grabs the div
    var div_hack = $('<div>')
      .addClass("map-wrapper")
      .css({
        position: "absolute",
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        width: '100%'
      });

    div.append(div_hack);
    this.$el.append(div);

    // Create the map
    var mapView = new cdb.geo.MapView.create(div_hack, map);
    this.mapView = mapView;

    // Add layers
    for(var i in data.layers) {
      var layerData = data.layers[i];
      this.loadLayer(layerData);
    }

    // Create the overlays
    for (var i in data.overlays) {
      this.addOverlay(data.overlays[i]);
    }

    _.defer(function() {
      self.trigger('done', self, self.getLayers());
    })

    return this;
  },

  addOverlay: function(overlay) {
    overlay.map = this.map;
    var v = Overlay.create(overlay.type, this, overlay);

    if (v) {
      // Save tiles loader view for later
      if (overlay.type == "loader") {
        this.loader = v;
      }

      this.addView(v);
      this.container.append(v.el);
      this.overlays.push(v);

      v.bind('clean', function() {
        for(var i in this.overlays) {
          var o = this.overlays[i];
          if(v.cid === o.cid) {
            this.overlays.splice(i, 1)
            return;
          }
        }
      }, this);

      // Set map position correctly taking into account
      // header height
      if (overlay.type == "header") {
        this.setMapPosition();
      }
    }
    return v;
  },

  // change vizjson based on options
  _applyOptions: function(vizjson, opt) {
    opt = opt || {};
    opt = _.defaults(opt, {
      search: false,
      title: false,
      description: false,
      tiles_loader: true,
      zoomControl: true,
      loaderControl: true,
      searchControl: false,
      infowindow: true
    });
    vizjson.overlays = vizjson.overlays || [];
    vizjson.layers = vizjson.layers || [];

    function search_overlay(name) {
      if(!vizjson.overlays) return null;
      for(var i = 0; i < vizjson.overlays.length; ++i) {
        if(vizjson.overlays[i].type === name) {
          return vizjson.overlays[i];
        }
      }
    }

    function remove_overlay(name) {
      if(!vizjson.overlays) return;
      for(var i = 0; i < vizjson.overlays.length; ++i) {
        if(vizjson.overlays[i].type === name) {
          vizjson.overlays.splice(i, 1);
          return;
        }
      }
    }

    this.infowindow = opt.infowindow;

    if(opt.https) {
      this.https = true;
    }

    // remove search if the vizualization does not contain it
    if (opt.search || opt.searchControl) {
      vizjson.overlays.push({
         type: "search"
      });
    }

    if(opt.title  || opt.description || opt.shareable) {
      vizjson.overlays.unshift({
        type: "header",
        shareable: opt.shareable ? true: false,
        url: vizjson.url
      });
    }

    if(!opt.title) {
      vizjson.title = null;
    }

    if(!opt.description) {
      vizjson.description = null;
    }

    if(!opt.tiles_loader) {
      remove_overlay('loader');
    }

    if(!opt.zoomControl) {
      remove_overlay('zoom');
    }

    if(!opt.loaderControl) {
      remove_overlay('loader');
    }

    // if bounds are present zoom and center will not taken into account
    if(opt.zoom !== undefined) {
      vizjson.zoom = parseFloat(opt.zoom);
      vizjson.bounds = null;
    }

    if(opt.center_lat !== undefined) {
      vizjson.center = [parseFloat(opt.center_lat), parseFloat(opt.center_lon)];
      vizjson.bounds = null;
    }

    if(opt.center !== undefined) {
      vizjson.center = opt.center;
      vizjson.bounds = null;
    }

    if(opt.sw_lat !== undefined) {
      vizjson.bounds = [
        [parseFloat(opt.sw_lat), parseFloat(opt.sw_lon)],
        [parseFloat(opt.ne_lat), parseFloat(opt.ne_lon)],
      ];
    }

    if(vizjson.layers.length > 1) {
      if(opt.sql) {
        vizjson.layers[1].options.query = opt.sql;
      }
      if(opt.style) {
        vizjson.layers[1].options.tile_style = opt.style;
      }

      vizjson.layers[1].options.no_cdn = opt.no_cdn;
    }

  },

  // Set map top position taking into account header height
  setMapPosition: function() {
    var header_h = this.$el.find(".header:not(.cartodb-popup)").outerHeight();

    this.$el
      .find("div.map-wrapper")
      .css("top", header_h);

    this.mapView.invalidateSize();
  },

  createLayer: function(layerData, opts) {
    var layerModel = Layers.create(layerData.type || layerData.kind, this, layerData);
    return this.mapView.createLayer(layerModel);
  },

  addInfowindow: function(layerView) {
    var model = layerView.model;
    var eventType = layerView.model.get('eventType') || 'featureClick';
    var infowindow = Overlay.create('infowindow', this, model.get('infowindow'), true);
    var mapView = this.mapView;
    mapView.addInfowindow(infowindow);

    var infowindowFields = layerView.model.get('infowindow');
    // HACK: REMOVE
    var port = model.get('sql_port');
    var domain = model.get('sql_domain') + (port ? ':' + port: '')
    var protocol = model.get('sql_protocol');
    var version = 'v1';
    if(domain.indexOf('cartodb.com') !== -1) {
      protocol = 'http';
      domain = "cartodb.com";
      version = 'v2';
    }

    var sql = new cartodb.SQL({
      user: model.get('user_name'),
      protocol: protocol,
      host: domain,
      version: version
    });

    // if the layer has no infowindow just pass the interaction
    // data to the infowindow
    layerView.bind(eventType, function(e, latlng, pos, data) {
        var cartodb_id = data.cartodb_id
        var fields = infowindowFields.fields;

        // Send request
        sql.execute("select {{fields}} from {{table_name}} where cartodb_id = {{cartodb_id }}", {
          fields: _.pluck(fields, 'name').join(','),
          cartodb_id: cartodb_id,
          table_name: model.get('table_name')
        })
        .done(function(interact_data) {
          if(interact_data.rows.length == 0 ) return;
          interact_data = interact_data.rows[0];
          if(infowindowFields) {
            var render_fields = [];
            var fields = infowindowFields.fields;
            for(var j = 0; j < fields.length; ++j) {
              var f = fields[j];
              if(interact_data[f.name] != undefined) {
                render_fields.push({
                  title: f.title ? f.name: null,
                  value: interact_data[f.name],
                  index: j ? j:null // mustache does not recognize 0 as false :(
                });
              }
            }
            // manage when there is no data to render
            if(render_fields.length === 0) {
              render_fields.push({
                title: null,
                value: 'No data available',
                index: j ? j:null, // mustache does not recognize 0 as false :(
                type: 'empty'
              });
            }
            content = render_fields;
          }

          infowindow.model.set({
            content:  {
              fields: content,
              data: interact_data
            }
          })
          infowindow.adjustPan();
        });

        // Show infowindow with loading state
        infowindow
          .setLatLng(latlng)
          .setLoading()
          .showInfowindow();
    });

    layerView.bind('featureOver', function(e, latlon, pxPos, data) {
      mapView.setCursor('pointer');
    });
    layerView.bind('featureOut', function() {
      mapView.setCursor('auto');
    });

    layerView.infowindow = infowindow.model;
  },

  loadLayer: function(layerData, opts) {
    var map = this.map;
    var mapView = this.mapView;
    layerData.type = layerData.kind;
    var layer_cid = map.addLayer(Layers.create(layerData.type || layerData.kind, this, layerData), opts);

    var layerView = mapView.getLayerByCid(layer_cid);

    // add the associated overlays
    if(layerData.infowindow &&
      layerData.infowindow.fields &&
      layerData.infowindow.fields.length > 0 &&
      this.infowindow) {
      this.addInfowindow(layerView);
    }

    if (layerView) {
      layerView.bind('loading', this.loadingTiles);
      layerView.bind('load',    this.loadTiles);
    }

    return layerView;

  },

  loadingTiles: function() {
    if(this.loader) {
      this.loader.show()
    }
  },

  loadTiles: function() {
    if(this.loader) {
      this.loader.hide();
    }
  },

  error: function(fn) {
    return this.bind('error', fn);
  },

  done: function(fn) {
    return this.bind('done', fn);
  },

  // public methods
  //

  // get the native map used behind the scenes
  getNativeMap: function() {
    return this.mapView.getNativeMap();
  },

  // returns an array of layers
  getLayers: function() {
    var self = this;
    return this.map.layers.map(function(layer) {
      return self.mapView.getLayerByCid(layer.cid);
    });
  },

  getOverlays: function() {
    return this.overlays;
  },

  getOverlay: function(type) {
    return _(this.overlays).find(function(v) {
      return v.type == type;
    });
  }

});

cdb.vis.Vis = Vis;

})();
<|endoftext|>


<commit_before>/*global console, clearTimeout: true, self:true, setTimeout:true */
"use strict";

var self = require("sdk/self");
var pageMod = require("sdk/page-mod");
var childProcess = require("sdk/system/child_process");
var { emit } = require("sdk/event/core");
var system = require("sdk/system");
var url = require("sdk/url");
var { setTimeout, clearTimeout } = require("sdk/timers");

function execBin(event, domain, challenge, callbackid, worker, timeout) {
  console.info("EB1", event, domain, challenge);
  var exe = system.platform + "_" + system.architecture + "-" + system.compiler + "/u2f" +
    (system.platform == "winnt" ? ".exe" : "");
  var path = url.toFilename(self.data.url("../bin/" + exe));
  path = "/home/prefiks/src/my/mozext/u2f4fox/ext/bin/linux_x86_64-gcc3/u2f";
  console.info("EB2", path);
  var cmd = childProcess.spawn(path, [], {});
  console.info("EB3", cmd);
  var response = {value: "", responded: false};

  var timer = setTimeout(function () {
    cmd.kill();
  }, timeout);

  cmd.stdout.on("data", function(data) {
    console.info("EBD", data);
    response.value += data;
    if (response.value[0] == "i") {
      console.info("insert device");
      worker.port.emit("insert");
      response.value = response.value.substr(1);
    }
    var r = response.value.match(/^(.)(....)/);
    var len = r && parseInt(r[2], 16);
    if (r && response.value.length >= len + 5) {
      worker.port.emit(event, callbackid, JSON.parse(response.value.substr(5, len)));
      response.value = response.value.substr(5 + len);
      response.responded = true;
    }
  });
  cmd.on("exit", function(code, signal) {
    console.info("exit", code, signal);
    clearTimeout(timer);
    if (cmd.killed)
      return;

    if (code == null || code < 0)
      worker.port.emit(event, callbackid, {errorCode: 1, errorMessage: "Couldn't spawn binary"});
    else if (!response.responded)
      worker.port.emit(event, callbackid, {errorCode: 1, errorMessage: "No response from binary: " + response.value});
  });
  var stdin = (event == "signResponse" ? "s" : "r") +
    [domain, challenge].map(v => (0x10000 + v.length).toString(16).substr(1)).join("") +
    domain + challenge;

  emit(cmd.stdin, "data", stdin);
  emit(cmd.stdin, "end");
}

pageMod.PageMod({ // eslint-disable-line new-cap
  include: "*",
  contentScriptWhen: "start",
  attachTo: ["top", "frame"],
  contentScriptFile: "./content-script.js",
  onAttach: function(worker) {

    worker.port.on("register", function(requests, callbackid, domain, timeout) {
      var req = Array.isArray(requests) ? requests[0] : requests;
      var reqS = JSON.stringify(req);
      execBin("registerResponse", domain, reqS, callbackid, worker, timeout);
    });
    worker.port.on("sign", function(signRequests, callbackid, domain, timeout) {
      var req = Array.isArray(signRequests) ? signRequests[0] : signRequests;
      var reqS = JSON.stringify(req);
      execBin("signResponse", domain, reqS, callbackid, worker, timeout);
    });
  }
});
<commit_msg>Revert "Add travis config file"<commit_after>/*global console, clearTimeout: true, self:true, setTimeout:true */
"use strict";

var self = require("sdk/self");
var pageMod = require("sdk/page-mod");
var childProcess = require("sdk/system/child_process");
var { emit } = require("sdk/event/core");
var system = require("sdk/system");
var url = require("sdk/url");
var { setTimeout, clearTimeout } = require("sdk/timers");

function execBin(event, domain, challenge, callbackid, worker, timeout) {
  console.info("EB1", event, domain, challenge);
  var exe = system.platform + "_" + system.architecture + "-" + system.compiler + "/u2f" +
    (system.platform == "winnt" ? ".exe" : "");
  var path = url.toFilename(self.data.url("../bin/" + exe));
  console.info("EB2", path);
  var cmd = childProcess.spawn(path, [], {});
  console.info("EB3", cmd);
  var response = {value: "", responded: false};

  var timer = setTimeout(function () {
    cmd.kill();
  }, timeout);

  cmd.stdout.on("data", function(data) {
    console.info("EBD", data);
    response.value += data;
    if (response.value[0] == "i") {
      console.info("insert device");
      worker.port.emit("insert");
      response.value = response.value.substr(1);
    }
    var r = response.value.match(/^(.)(....)/);
    var len = r && parseInt(r[2], 16);
    if (r && response.value.length >= len + 5) {
      worker.port.emit(event, callbackid, JSON.parse(response.value.substr(5, len)));
      response.value = response.value.substr(5 + len);
      response.responded = true;
    }
  });
  cmd.on("exit", function(code, signal) {
    console.info("exit", code, signal);
    clearTimeout(timer);
    if (cmd.killed)
      return;

    if (code == null || code < 0)
      worker.port.emit(event, callbackid, {errorCode: 1, errorMessage: "Couldn't spawn binary"});
    else if (!response.responded)
      worker.port.emit(event, callbackid, {errorCode: 1, errorMessage: "No response from binary: " + response.value});
  });
  var stdin = (event == "signResponse" ? "s" : "r") +
    [domain, challenge].map(v => (0x10000 + v.length).toString(16).substr(1)).join("") +
    domain + challenge;

  emit(cmd.stdin, "data", stdin);
  emit(cmd.stdin, "end");
}

pageMod.PageMod({ // eslint-disable-line new-cap
  include: "*",
  contentScriptWhen: "start",
  attachTo: ["top", "frame"],
  contentScriptFile: "./content-script.js",
  onAttach: function(worker) {

    worker.port.on("register", function(requests, callbackid, domain, timeout) {
      var req = Array.isArray(requests) ? requests[0] : requests;
      var reqS = JSON.stringify(req);
      execBin("registerResponse", domain, reqS, callbackid, worker, timeout);
    });
    worker.port.on("sign", function(signRequests, callbackid, domain, timeout) {
      var req = Array.isArray(signRequests) ? signRequests[0] : signRequests;
      var reqS = JSON.stringify(req);
      execBin("signResponse", domain, reqS, callbackid, worker, timeout);
    });
  }
});
<|endoftext|>


<commit_before>
var
  // #57 - see why ipad needs to double click
  isiDevice = navigator.userAgent.match(/ip(hone|od|ad)/i),
  html5_audio,
  isMobile = true,
  listenEvent = isiDevice ? 'touchend' : 'click',
  ev = EvDa({start: '', name: '', station: '', ampm: '', day: []}),
  fullName = {
    sun: 'Sundays', mon: 'Mondays', tue: 'Tuesdays', wed: 'Wednesdays',
    thu: 'Thursdays', fri: 'Fridays', sat: 'Saturdays'
  },
  tpl = {},
  time_re = /^\s*(1[0-2]|[1-9])(:[0-5][0-9])?\s*([ap]m)?\s*$/i;

// remote API
// first we have a generic promise harness
function stub() {
  var res = {};

  // Take the things to be stubbed out
  _.each(_.toArray(arguments), function(what) {

    // Assign each one to the map as a function
    res[what] = function(cb) {

      // If the arguments passed in is another function
      // then we put this in our callback list.
      if(_.isFunction(cb)) {
        res[what].cb.push(cb);

        // and return our primary object
        // for chaining
        return res;

      } else {
        // If the arguments passed in is not a function,
        // then this is data.  We go through the list
        // that we've constructed (if any) and call each
        // callback with the argument passed in - returning
        // the results as a list, if needed.
        return _.map(res[what].cb, function(cb_ix) {
          cb_ix(cb);
        });
      }
    }

    res[what].cb = [];

  });

  return res;
}

// Then we have a queue-able api call system
function do_remote(ep, what, verb, amd) {
  remote.lock = true;
  if(_.isArray(ep)) {
    ep = ep.join('/');
  }

  $[verb || 'post']('/api/' + ep.split('/').pop(), what, function(res) {
    if(res.result) {
      amd.then(res.data);
    } else {
      amd.fail(res);
    }
  }, 'json')
    .fail(amd.fail)
    .always(function(){
      remote.ix++;
      console.log('r:' + ep + (what === undefined ? "" : (":" + JSON.stringify(what))));
      remote.lock = false;
      amd.always();

      if(remote.queue.length) {
        var opts = remote.queue.shift();
        do_remote.apply(this, opts);
      }
    })
}

function remote_queue(ep, what, verb) {
  var amd = stub('then', 'fail', 'always');

  if(remote.lock) {
    remote.queue.push([ep, what, verb, amd]);
  } else {
    do_remote(ep, what, verb, amd);
  }

  return amd;
}

function remote(ep, what, verb) {
  var amd = stub('then', 'fail', 'always');

  do_remote(ep, what, verb, amd);

  return amd;
}

remote.queue = [];
remote.lock = false;
remote.ix = 0;

// local storage
function ls(key, value) {
  if (arguments.length == 1) {
    return localStorage[key] || false;
  } else {
    localStorage[key] = value;
  }
  return value;
}

function easy_bind(list, instance) {
  if(!instance) {
    instance = ev;
  }

  _.each(list, function(what) {

    var node = document.querySelector('#' + what);

    if(!node) {
      node = document.querySelector('input[name="' + what + '"]');
      if(!node) {
        throw new Error("Can't find anything matching " + what);
      }
    }

    if(node.nodeName == 'INPUT') {

      $(node).on('blur focus change keyup', function() {
        instance(what, this.value, {node: this});
      });

      instance(what, function(val){
        if(val !== undefined) {
          node.value = val;
        }
      });

    } else {
      $("a,button", node).on(listenEvent, function(){
        // This tricks stupid iDevices into not fucking around and screwing with the user.
        // (Requiring a user to tap twice to select anything.  WTF apple...)
        var mthis = this;

        setTimeout(function(){
          instance(what, mthis.getAttribute('data') || mthis.innerHTML);
        }, 0);
      });

      instance(what, function(val) {
        $("a", node).removeClass('selected');
        if(val) {
          $("a", node).filter(function(){return this.innerHTML == val}).addClass("selected");
          $("a[data='" + val + "']", node).addClass("selected");
        }
      });
    }

    instance.fire(what);
  });
}

function time_markers() {
  var right_now = new Date();

  return {
    current_hour: {
      human_time: 'the current hour',
      start_time: +date_diff(right_now, {minutes: 0}) / 1000,
      end_time: +date_diff(right_now, {minutes: 0, hours: "+1"}) / 1000
    },

    last_half_hour: {
      human_time: 'the previous half hour',
      start_time: +date_diff(right_now, {minutes: "- 30 - ts.getMinutes() % 30"}) / 1000,
      end_time: +date_diff(right_now, {minutes: "- ts.getMinutes() % 30"}) / 1000
    },

    current_half_hour: {
      human_time: 'the current half hour',
      start_time: +date_diff(right_now, {minutes: " - (ts.getMinutes() % 30)"}) / 1000,
      end_time: +date_diff(right_now, {minutes: " + 30 - (ts.getMinutes() % 30)"}) / 1000
    },

    plus_minus_twenty: {
      human_time: '-20min - +20min',
      start_time: +date_diff(right_now, {minutes: " - 20"}) / 1000,
      end_time: +date_diff(right_now, {minutes: " + 20"}) / 1000
    }
  };
}

function easy_sync(list) {
  _.each(list, function(what) {
    if(ls(what)) {
      ev(what, ls(what));
    }
    ev.after(what, function(value) {
      ls(what, value);
    });
  });
  return ev('');
}

function station_select() {
  $("#station-preselect").slideUp();
  $("#station").slideDown();
}

function htmldo(what) {
  // hyperlinking
  what = what.replace(/[a-z]+:\/\/[^\s^<]+/g, '$&');

  // paragrapherizing
  what = what.replace(/\n/g, '

');

  // finalerizing
  return '
' + what + '
';
}

//
// This is a python inspired way of doing things.
// change_map has datetime.timedelta syntax and operates
//
//  as an override if it's an integer
//  as an eval if it's a string (such as +1 or -1)
//
// Currently all we care about are
// hours and minutes.
//
// seconds and milliseconds are zeroed for us.
//
// It can be empty of course.
//
function date_diff(ts, change_map) {

  change_map = change_map || {};

  if( !('hours' in change_map) ) {
    change_map['hours'] = ts.getHours();
  } else if (change_map.hours.length) {
    // oh noes! The spirit of Douglas Crockford has now cursed my family!
    eval("change_map['hours'] = ts.getHours() " + change_map['hours']);
  }

  if( !('minutes' in change_map) ) {
    change_map['minutes'] = ts.getMinutes();
  } else if (change_map.minutes.length) {
    eval("change_map['minutes'] = ts.getMinutes() " + change_map['minutes']);
  }
  console.log(change_map);

  return new Date(
    ts.getFullYear(),
    ts.getMonth(),
    ts.getDate(),
    change_map.hours,
    change_map.minutes,
    0,
    0
  );
}

function do_random() {
  set_player(random_url());
}

function set_fallback(url, count) {
  $("#flash-widget").show();

  $f("flash-widget", "http://releases.flowplayer.org/swf/flowplayer-3.2.18.swf", {
    onError: function() {
      // this means that both the html5 and flash player failed ... so we
      // just move on to a new track.
      set_player(random_url());
    },
    clip: {
      url: url,
      provider: 'audio',
      live: true,
      autoPlay: count
    },
    plugins: {
      controls: {
        height: 30,
        fullscreen: false,
        autoHide: false
      },
      audio: {
        url: "flowplayer.audio-3.2.11.swf",
      }
    }
  });
}

function set_player(url) {
  if(!html5_audio) {
    html5_audio = document.getElementById('radio-control');
  }

  var local = self.audio_count ? self.audio_count : 0;

  $("#url").html(url);

  $("#flash-widget").hide();
  html5_audio.addEventListener('error', function() {
    $("#html5-widget").hide();
    set_fallback(url, local);
  });

  html5_audio.addEventListener('loadstart', function(){
    $("#html5-widget").show();
  });

  html5_audio.src = url;

  if(self.hasOwnProperty('audio_count')) {
    // Don't auto-play if it's the first
    if (audio_count > 0) {
      html5_audio.play();
    }

    audio_count ++;
  }

  return url;
}

function random_url(){
  var
    station = random.station(),
    day = random.day(),
    what_time = random.time(),
    duration = random.duration();

  //if(random.num() == 0) {
    return 'http://indycast.net/' + station + '/live/' + what_time;
  //} else {
  //  return 'http://indycast.net/' + [station, day, random.time(), duration].join('/');
  //}
}

$(function() {
  $("#template-list > script").each(function(){
    var name = this.id.split('-').pop();
    tpl[name] = _.template(this.innerHTML);
  });
});

// #30: Da Goog!
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-28399789-2', 'auto');
ga('send', 'pageview');

<commit_msg>fixes #153 - flash error on mobile<commit_after>
var
  // #57 - see why ipad needs to double click
  isiDevice = navigator.userAgent.match(/ip(hone|od|ad)/i),
  html5_audio,
  isMobile = true,
  listenEvent = isiDevice ? 'touchend' : 'click',
  ev = EvDa({start: '', name: '', station: '', ampm: '', day: []}),
  fullName = {
    sun: 'Sundays', mon: 'Mondays', tue: 'Tuesdays', wed: 'Wednesdays',
    thu: 'Thursdays', fri: 'Fridays', sat: 'Saturdays'
  },
  tpl = {},
  time_re = /^\s*(1[0-2]|[1-9])(:[0-5][0-9])?\s*([ap]m)?\s*$/i;

// remote API
// first we have a generic promise harness
function stub() {
  var res = {};

  // Take the things to be stubbed out
  _.each(_.toArray(arguments), function(what) {

    // Assign each one to the map as a function
    res[what] = function(cb) {

      // If the arguments passed in is another function
      // then we put this in our callback list.
      if(_.isFunction(cb)) {
        res[what].cb.push(cb);

        // and return our primary object
        // for chaining
        return res;

      } else {
        // If the arguments passed in is not a function,
        // then this is data.  We go through the list
        // that we've constructed (if any) and call each
        // callback with the argument passed in - returning
        // the results as a list, if needed.
        return _.map(res[what].cb, function(cb_ix) {
          cb_ix(cb);
        });
      }
    }

    res[what].cb = [];

  });

  return res;
}

// Then we have a queue-able api call system
function do_remote(ep, what, verb, amd) {
  remote.lock = true;
  if(_.isArray(ep)) {
    ep = ep.join('/');
  }

  $[verb || 'post']('/api/' + ep.split('/').pop(), what, function(res) {
    if(res.result) {
      amd.then(res.data);
    } else {
      amd.fail(res);
    }
  }, 'json')
    .fail(amd.fail)
    .always(function(){
      remote.ix++;
      console.log('r:' + ep + (what === undefined ? "" : (":" + JSON.stringify(what))));
      remote.lock = false;
      amd.always();

      if(remote.queue.length) {
        var opts = remote.queue.shift();
        do_remote.apply(this, opts);
      }
    })
}

function remote_queue(ep, what, verb) {
  var amd = stub('then', 'fail', 'always');

  if(remote.lock) {
    remote.queue.push([ep, what, verb, amd]);
  } else {
    do_remote(ep, what, verb, amd);
  }

  return amd;
}

function remote(ep, what, verb) {
  var amd = stub('then', 'fail', 'always');

  do_remote(ep, what, verb, amd);

  return amd;
}

remote.queue = [];
remote.lock = false;
remote.ix = 0;

// local storage
function ls(key, value) {
  if (arguments.length == 1) {
    return localStorage[key] || false;
  } else {
    localStorage[key] = value;
  }
  return value;
}

function easy_bind(list, instance) {
  if(!instance) {
    instance = ev;
  }

  _.each(list, function(what) {

    var node = document.querySelector('#' + what);

    if(!node) {
      node = document.querySelector('input[name="' + what + '"]');
      if(!node) {
        throw new Error("Can't find anything matching " + what);
      }
    }

    if(node.nodeName == 'INPUT') {

      $(node).on('blur focus change keyup', function() {
        instance(what, this.value, {node: this});
      });

      instance(what, function(val){
        if(val !== undefined) {
          node.value = val;
        }
      });

    } else {
      $("a,button", node).on(listenEvent, function(){
        // This tricks stupid iDevices into not fucking around and screwing with the user.
        // (Requiring a user to tap twice to select anything.  WTF apple...)
        var mthis = this;

        setTimeout(function(){
          instance(what, mthis.getAttribute('data') || mthis.innerHTML);
        }, 0);
      });

      instance(what, function(val) {
        $("a", node).removeClass('selected');
        if(val) {
          $("a", node).filter(function(){return this.innerHTML == val}).addClass("selected");
          $("a[data='" + val + "']", node).addClass("selected");
        }
      });
    }

    instance.fire(what);
  });
}

function time_markers() {
  var right_now = new Date();

  return {
    current_hour: {
      human_time: 'the current hour',
      start_time: +date_diff(right_now, {minutes: 0}) / 1000,
      end_time: +date_diff(right_now, {minutes: 0, hours: "+1"}) / 1000
    },

    last_half_hour: {
      human_time: 'the previous half hour',
      start_time: +date_diff(right_now, {minutes: "- 30 - ts.getMinutes() % 30"}) / 1000,
      end_time: +date_diff(right_now, {minutes: "- ts.getMinutes() % 30"}) / 1000
    },

    current_half_hour: {
      human_time: 'the current half hour',
      start_time: +date_diff(right_now, {minutes: " - (ts.getMinutes() % 30)"}) / 1000,
      end_time: +date_diff(right_now, {minutes: " + 30 - (ts.getMinutes() % 30)"}) / 1000
    },

    plus_minus_twenty: {
      human_time: '-20min - +20min',
      start_time: +date_diff(right_now, {minutes: " - 20"}) / 1000,
      end_time: +date_diff(right_now, {minutes: " + 20"}) / 1000
    }
  };
}

function easy_sync(list) {
  _.each(list, function(what) {
    if(ls(what)) {
      ev(what, ls(what));
    }
    ev.after(what, function(value) {
      ls(what, value);
    });
  });
  return ev('');
}

function station_select() {
  $("#station-preselect").slideUp();
  $("#station").slideDown();
}

function htmldo(what) {
  // hyperlinking
  what = what.replace(/[a-z]+:\/\/[^\s^<]+/g, '$&');

  // paragrapherizing
  what = what.replace(/\n/g, '

');

  // finalerizing
  return '
' + what + '
';
}

//
// This is a python inspired way of doing things.
// change_map has datetime.timedelta syntax and operates
//
//  as an override if it's an integer
//  as an eval if it's a string (such as +1 or -1)
//
// Currently all we care about are
// hours and minutes.
//
// seconds and milliseconds are zeroed for us.
//
// It can be empty of course.
//
function date_diff(ts, change_map) {

  change_map = change_map || {};

  if( !('hours' in change_map) ) {
    change_map['hours'] = ts.getHours();
  } else if (change_map.hours.length) {
    // oh noes! The spirit of Douglas Crockford has now cursed my family!
    eval("change_map['hours'] = ts.getHours() " + change_map['hours']);
  }

  if( !('minutes' in change_map) ) {
    change_map['minutes'] = ts.getMinutes();
  } else if (change_map.minutes.length) {
    eval("change_map['minutes'] = ts.getMinutes() " + change_map['minutes']);
  }
  console.log(change_map);

  return new Date(
    ts.getFullYear(),
    ts.getMonth(),
    ts.getDate(),
    change_map.hours,
    change_map.minutes,
    0,
    0
  );
}

function do_random() {
  set_player(random_url());
}

function set_fallback(url, count) {
  $("#flash-widget").show();

  $f("flash-widget", "http://releases.flowplayer.org/swf/flowplayer-3.2.18.swf", {
    onError: function() {
      // this means that both the html5 and flash player failed ... so we
      // just move on to a new track.
      set_player(random_url());
    },
    clip: {
      url: url,
      provider: 'audio',
      live: true,
      autoPlay: count
    },
    plugins: {
      controls: {
        height: 30,
        fullscreen: false,
        autoHide: false
      },
      audio: {
        url: "flowplayer.audio-3.2.11.swf",
      }
    }
  });
}

function set_player(url) {
  if(!html5_audio) {
    html5_audio = document.getElementById('radio-control');
  }

  var local = self.audio_count ? self.audio_count : 0;

  $("#url").html(url);

  $("#flash-widget").hide();
  html5_audio.addEventListener('error', function() {
    // Mobile doesn't have flash these days
    if(!isMobile) {
      $("#html5-widget").hide();
      set_fallback(url, local);
    }
  });

  html5_audio.addEventListener('loadstart', function(){
    $("#html5-widget").show();
  });

  html5_audio.src = url;

  if(self.hasOwnProperty('audio_count')) {
    // Don't auto-play if it's the first
    if (audio_count > 0) {
      html5_audio.play();
    }

    audio_count ++;
  }

  return url;
}

function random_url(){
  var
    station = random.station(),
    day = random.day(),
    what_time = random.time(),
    duration = random.duration();

  //if(random.num() == 0) {
    return 'http://indycast.net/' + station + '/live/' + what_time;
  //} else {
  //  return 'http://indycast.net/' + [station, day, random.time(), duration].join('/');
  //}
}

$(function() {
  $("#template-list > script").each(function(){
    var name = this.id.split('-').pop();
    tpl[name] = _.template(this.innerHTML);
  });
});

// #30: Da Goog!
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-28399789-2', 'auto');
ga('send', 'pageview');

<|endoftext|>