<commit_before>angular.module('ts.sheets').provider('$media', function(){

  var SHEET_ID = "$$sheetId";

  //TODO:  support automatic interpolation of values between transitions?
  //       and/or support manual interpolation with custom function passing
  var OBSERVER_CONF = {
    attributes: true,
    childList: true
  };

  function _mutationCallback(records, observer){
    angular.forEach(records, function(record){
      //TODO:  this could probably be optimized by
      //       digging into record.type and breaking out
      //       behavior for record.type === "attributes" vs "childList"
      //       but for now, just reapply to entire parent element's subtree
      //TODO:  double-check if record.target (in case of record.type === "attributes") is the parent node or specific child node
      //       If it's the child node, we'll still need to update the parent tree instead of the child, which might require some
      //       passing of the element ID by closure when creating the mutuationCallback
      console.log('mutation observed', record);
      if(record.type === "attributes") console.warn("ensure that record.target is the parent node, not a child node", record)

      _updateElement(record.target);
    });
  }

  var _windowSizeDirty = true;

  function _windowResizeCallback(){
    _windowSizeDirty = true;
    _updateEverything();
  }

  var _cachedResolvedMediaQueries = [];

  //determines all registered media queries that are true, caches results
  function _resolveMediaQueries(){
    if(!_windowSizeDirty) return _cachedResolvedMediaQueries;

    var matched = [];
    angular.forEach(_registeredMediaQueries, function(mediaQuery){
      if(mediaQuery.conditionFn()) matched.push(mediaQuery);
    });

    if(!matched.length) throw new Error('$media assumes at least one media query will be matched (by default this is \'xs\').')

    //sort media queries descendingly by priority
    matched.sort(function(a, b){return b.priority - a.priority});

    _windowSizeDirty = false;
    _cachedResolvedMediaQueries = matched;
    return _cachedResolvedMediaQueries;
  }

  //reevaluate every element (may be expensive)
  function _updateEverything(){
    var elementIds = Object.keys(_elements);
    angular.forEach(elementIds, function(id){
      _updateElement(_elements[id]);
    });
  }

  //naively and greedily test depth by probing first subtree
  function _getObjectDepth(obj, acc){
    var keys = !Array.isArray(obj) && Object.keys(obj);
    if(keys && keys.length) return _getObjectDepth(obj[keys[0]], ++acc);
    return acc;
  }

  //update element and its subtree, applying all relevant sheets
  function _updateElement(element){
    var id = element.data(SHEET_ID);
    var sheets = _elementSheets[id];

    var matchedQueries = _resolveMediaQueries();

    angular.forEach(sheets, function(sheet){

      //support having media queries or omitting them
      //probe depth:  if depth === 3, we have media queries;
      //              if depth === 2, we don't.

      var depth = _getObjectDepth(sheet, 0);
      var matchedLayout;
      if(depth === 2){
        matchedLayout = sheet;
      }else if(depth === 3){
        var found = false;
        //loop through the matched queries by descending priority, finding the first query
        //that exists in obj
        angular.forEach(matchedQueries, function(query){
          if(found) return;
          if(sheet[query.name]){
            found = true;
            matchedLayout = sheet[query.name];

          }
        });
      }else{
        throw new Error('Malformed Sheet.  Object depth of 2 or 3 expected.  Actual depth was ' + depth);
      }

      //nothing to see here
      if(!matchedLayout) return;

      var selectors = Object.keys(matchedLayout);

      angular.forEach(selectors, function(selector){
        var fields = Object.keys(matchedLayout[selector]);
        var elements = element[0].querySelectorAll(selector);

        angular.forEach(elements, function(childElement){
          angular.forEach(fields, function(field){
            var payload = matchedLayout[selector][field];
            _fieldHandlers[field](childElement, payload);
          });
        });

      })

    });
  }

  //cross-browser addEvent from http://stackoverflow.com/questions/641857/javascript-window-resize-event
  var _addEvent = function(elem, type, eventHandle) {
    if (elem == null || typeof(elem) == 'undefined') return;
    if ( elem.addEventListener ) {
      elem.addEventListener( type, eventHandle, false );
    } else if ( elem.attachEvent ) {
      elem.attachEvent( "on" + type, eventHandle );
    } else {
      elem["on"+type]=eventHandle;
    }
  };

  var _registeredMediaQueries = [],
      _sheets = {},
      _elementSheets = {},
      _elements = {},
      _fieldHandlers = {},
      _observers = {};

  var _registerMediaQuery = this.$registerMediaQuery = function(name, conditionFn, priority){
      _registeredMediaQueries[priority] = {name: name, conditionFn: conditionFn, priority: priority};
  };
  var _registerFieldHandler = this.$registerFieldHandler = function(fieldName, handlerFn){
      //TODO:  support passing an array of supported elements
      //       or even a function to determine if a given element is supported
      _fieldHandlers[fieldName] = handlerFn;
  };

  var $media = {

    //declare a sheet in a controller/presenter
    $sheet: function(name, spec){
      if(_sheets[name] !== undefined) console.warn('Sheet name \''+name+'\' is already defined.  The latest declaration will overwrite previous declarations.')
      _sheets[name] = spec;
    },

    //apply a sheet by name, called at least by ts-sheet directive
    $applySheet: function(element, name){
      element.data(SHEET_ID, element.data(SHEET_ID) || Math.random()); //TODO:  better uniqueid?
      var id = element.data(SHEET_ID);
      var spec = _sheets[name];
      if(spec === undefined) throw new Error('Sheet \'' + name + '\' is undefined.  Sheets must be defined using $media.$sheet before they can be applied with ts-sheet.');

      _elements[id] = element;

      _elementSheets[id] = _elementSheets[id] || [];
      _elementSheets[id].push(spec);

      var observer = _observers[id] = _observers[id] || new MutationObserver(_mutationCallback);
      observer.observe(element[0], OBSERVER_CONF);

      //kick off initial action
      _updateElement(element);
    },

    //used for clean-up in ts-sheet directive
    //detaches all sheets from an element and stops an element from being updated
    //does NOT automatically undo whatever logic is declared in payload functions
    $clearSheets: function(element, name) {
      //TODO: test that this works
      var id = element.data(SHEET_ID);

      delete _sheets[name];
      delete _elementSheets[id];
      delete _elements[id]
      _observers[id].disconnect();
    },

    //test whether the mediaQuery of the given name is currently fulfilled
    $query: function(mediaQuery){
      return _registeredMediaQueries[mediaQuery]();
    }
  };

  //expose $media service
  this.$get = function($window){
    //note that this will trigger several times per actual resize in some browsers
    _addEvent($window, "resize", _windowResizeCallback);

    var DEFAULT_MEDIA_QUERIES = [
      {
        name: 'xs',
        conditionFn: function(){
          return true;
        },
        priority: 0
      },
      {
        name: 'sm',
        conditionFn: function(){
          return $window.innerWidth >= 768;
        },
        priority: 1
      },
      {
        name: 'md',
        conditionFn: function(){
          return $window.innerWidth >= 992;
        },
        priority: 2
      },
      {
        name: 'lg',
        conditionFn: function(){
          return $window.innerWidth >= 1200;
        },
        priority: 3
      }
    ];

    //TODO:  support different field names by element type
    //       (e.g. different behavior for 'color' for a div vs a span)
    var DEFAULT_FIELD_HANDLERS = [];

    angular.forEach(DEFAULT_MEDIA_QUERIES, function(mediaQuery){
      _registerMediaQuery(mediaQuery.name, mediaQuery.conditionFn, mediaQuery.priority);
    });

    angular.forEach(DEFAULT_FIELD_HANDLERS, function(fieldHandler){
      _registerFieldHandler(fieldHandler.field, fieldHandler.handlerFn);
    });

    return $media;
  }

});<commit_msg>fix query function by storing media queries both by priority and by name<commit_after>angular.module('ts.sheets').provider('$media', function(){

  var SHEET_ID = "$$sheetId";

  //TODO:  support automatic interpolation of values between transitions?
  //       and/or support manual interpolation with custom function passing
  var OBSERVER_CONF = {
    attributes: true,
    childList: true
  };

  function _mutationCallback(records, observer){
    angular.forEach(records, function(record){
      //TODO:  this could probably be optimized by
      //       digging into record.type and breaking out
      //       behavior for record.type === "attributes" vs "childList"
      //       but for now, just reapply to entire parent element's subtree
      //TODO:  double-check if record.target (in case of record.type === "attributes") is the parent node or specific child node
      //       If it's the child node, we'll still need to update the parent tree instead of the child, which might require some
      //       passing of the element ID by closure when creating the mutuationCallback
      console.log('mutation observed', record);
      if(record.type === "attributes") console.warn("ensure that record.target is the parent node, not a child node", record)

      _updateElement(record.target);
    });
  }

  var _windowSizeDirty = true;

  function _windowResizeCallback(){
    _windowSizeDirty = true;
    _updateEverything();
  }

  var _cachedResolvedMediaQueries = [];

  //determines all registered media queries that are true, caches results
  function _resolveMediaQueries(){
    if(!_windowSizeDirty) return _cachedResolvedMediaQueries;

    var matched = [];
    angular.forEach(_registeredMediaQueries, function(mediaQuery){
      if(mediaQuery.conditionFn()) matched.push(mediaQuery);
    });

    if(!matched.length) throw new Error('$media assumes at least one media query will be matched (by default this is \'xs\').')

    //sort media queries descendingly by priority
    matched.sort(function(a, b){return b.priority - a.priority});

    _windowSizeDirty = false;
    _cachedResolvedMediaQueries = matched;
    return _cachedResolvedMediaQueries;
  }

  //reevaluate every element (may be expensive)
  function _updateEverything(){
    var elementIds = Object.keys(_elements);
    angular.forEach(elementIds, function(id){
      _updateElement(_elements[id]);
    });
  }

  //naively and greedily test depth by probing first subtree
  function _getObjectDepth(obj, acc){
    var keys = !Array.isArray(obj) && Object.keys(obj);
    if(keys && keys.length) return _getObjectDepth(obj[keys[0]], ++acc);
    return acc;
  }

  //update element and its subtree, applying all relevant sheets
  function _updateElement(element){
    var id = element.data(SHEET_ID);
    var sheets = _elementSheets[id];

    var matchedQueries = _resolveMediaQueries();

    angular.forEach(sheets, function(sheet){

      //support having media queries or omitting them
      //probe depth:  if depth === 3, we have media queries;
      //              if depth === 2, we don't.

      var depth = _getObjectDepth(sheet, 0);
      var matchedLayout;
      if(depth === 2){
        matchedLayout = sheet;
      }else if(depth === 3){
        var found = false;
        //loop through the matched queries by descending priority, finding the first query
        //that exists in obj
        angular.forEach(matchedQueries, function(query){
          if(found) return;
          if(sheet[query.name]){
            found = true;
            matchedLayout = sheet[query.name];

          }
        });
      }else{
        throw new Error('Malformed Sheet.  Object depth of 2 or 3 expected.  Actual depth was ' + depth);
      }

      //nothing to see here
      if(!matchedLayout) return;

      var selectors = Object.keys(matchedLayout);

      angular.forEach(selectors, function(selector){
        var fields = Object.keys(matchedLayout[selector]);
        var elements = element[0].querySelectorAll(selector);

        angular.forEach(elements, function(childElement){
          angular.forEach(fields, function(field){
            var payload = matchedLayout[selector][field];
            _fieldHandlers[field](childElement, payload);
          });
        });

      })

    });
  }

  //cross-browser addEvent from http://stackoverflow.com/questions/641857/javascript-window-resize-event
  var _addEvent = function(elem, type, eventHandle) {
    if (elem == null || typeof(elem) == 'undefined') return;
    if ( elem.addEventListener ) {
      elem.addEventListener( type, eventHandle, false );
    } else if ( elem.attachEvent ) {
      elem.attachEvent( "on" + type, eventHandle );
    } else {
      elem["on"+type]=eventHandle;
    }
  };

  var _registeredMediaQueries = [],
      _registeredMediaQueriesByName = {},
      _sheets = {},
      _elementSheets = {},
      _elements = {},
      _fieldHandlers = {},
      _observers = {};

  var _registerMediaQuery = this.$registerMediaQuery = function(name, conditionFn, priority){
      _registeredMediaQueries[priority] = {name: name, conditionFn: conditionFn, priority: priority};
      _registeredMediaQueriesByName[name] = {name: name, conditionFn: conditionFn, priority: priority};
  };
  var _registerFieldHandler = this.$registerFieldHandler = function(fieldName, handlerFn){
      //TODO:  support passing an array of supported elements
      //       or even a function to determine if a given element is supported
      _fieldHandlers[fieldName] = handlerFn;
  };

  var $media = {

    //declare a sheet in a controller/presenter
    $sheet: function(name, spec){
      if(_sheets[name] !== undefined) console.warn('Sheet name \''+name+'\' is already defined.  The latest declaration will overwrite previous declarations.')
      _sheets[name] = spec;
    },

    //apply a sheet by name, called at least by ts-sheet directive
    $applySheet: function(element, name){
      element.data(SHEET_ID, element.data(SHEET_ID) || Math.random()); //TODO:  better uniqueid?
      var id = element.data(SHEET_ID);
      var spec = _sheets[name];
      if(spec === undefined) throw new Error('Sheet \'' + name + '\' is undefined.  Sheets must be defined using $media.$sheet before they can be applied with ts-sheet.');

      _elements[id] = element;

      _elementSheets[id] = _elementSheets[id] || [];
      _elementSheets[id].push(spec);

      var observer = _observers[id] = _observers[id] || new MutationObserver(_mutationCallback);
      observer.observe(element[0], OBSERVER_CONF);

      //kick off initial action
      _updateElement(element);
    },

    //used for clean-up in ts-sheet directive
    //detaches all sheets from an element and stops an element from being updated
    //does NOT automatically undo whatever logic is declared in payload functions
    $clearSheets: function(element, name) {
      //TODO: test that this works
      var id = element.data(SHEET_ID);

      delete _sheets[name];
      delete _elementSheets[id];
      delete _elements[id]
      _observers[id].disconnect();
    },

    //test whether the mediaQuery of the given name is currently fulfilled
    $query: function(mediaQueryName){
      return _registeredMediaQueriesByName[mediaQueryName]();
    }
  };

  //expose $media service
  this.$get = function($window){
    //note that this will trigger several times per actual resize in some browsers
    _addEvent($window, "resize", _windowResizeCallback);

    var DEFAULT_MEDIA_QUERIES = [
      {
        name: 'xs',
        conditionFn: function(){
          return true;
        },
        priority: 0
      },
      {
        name: 'sm',
        conditionFn: function(){
          return $window.innerWidth >= 768;
        },
        priority: 1
      },
      {
        name: 'md',
        conditionFn: function(){
          return $window.innerWidth >= 992;
        },
        priority: 2
      },
      {
        name: 'lg',
        conditionFn: function(){
          return $window.innerWidth >= 1200;
        },
        priority: 3
      }
    ];

    //TODO:  support different field names by element type
    //       (e.g. different behavior for 'color' for a div vs a span)
    var DEFAULT_FIELD_HANDLERS = [];

    angular.forEach(DEFAULT_MEDIA_QUERIES, function(mediaQuery){
      _registerMediaQuery(mediaQuery.name, mediaQuery.conditionFn, mediaQuery.priority);
    });

    angular.forEach(DEFAULT_FIELD_HANDLERS, function(fieldHandler){
      _registerFieldHandler(fieldHandler.field, fieldHandler.handlerFn);
    });

    return $media;
  }

});<|endoftext|>


<commit_before>/**
 * @fileoverview Provides a factory to create a popup in the page.
 * The factory returns a ngeo.Popup object.
 *
 * Example:
 *
 * var popup = ngeoCreatePopup();
 * popup.setTitle("A title");
 * popup.setContent("Some content");
 * popup.show();
 *
 */

goog.provide('ngeo.CreatePopup');
goog.provide('ngeo.Popup');

goog.require('ngeo');
goog.require('ngeo.popupDirective');

/**
 * @typedef {function():!ngeo.Popup}
 */
ngeo.CreatePopup;

/**
 * @constructor
 * @param {angular.$compile} $compile The compile provider.
 * @param {angular.Scope} $rootScope The rootScope provider.
 */
ngeo.Popup = function($compile, $rootScope) {

  /**
   * The scope the compiled element is link to.
   * @type {angular.Scope}
   * @private
   */
  this.scope_ = $rootScope.$new(true);

  /**
   * The element.
   * @type {angular.JQLite}
   * @private
   */
  this.element_ = angular.element('

');

  // Add the element and its content to the document
  angular.element(document.body).append(this.element_);
  $compile(this.element_)(this.scope_);
};

/**
 * Display the popup.
 */
ngeo.Popup.prototype.show = function() {
  this.scope_['open'] = true;
};

/**
 * Destroy the popup.
 */
ngeo.Popup.prototype.destroy = function() {
  this.scope_.$destroy();
  this.element_.remove();
};

/**
 * Set the popup's title.
 * @param {string} title The title.
 */
ngeo.Popup.prototype.setTitle = function(title) {
  this.scope_['title'] = title;
};

/**
 * Set the popup's content.
 * Note: the type of the `content` param is `*` instead of `string`, this
 * is because the content may be trusted using `$sce.trustAsHtml`.
 * @param {*} content The content.
 */
ngeo.Popup.prototype.setContent = function(content) {
  this.scope_['content'] = content;
};

/**
 * @param {angular.$compile} $compile Angular compile service.
 * @param {angular.Scope} $rootScope Angular rootScope service.
 * @return {ngeo.CreatePopup} The function to create a popup.
 * @ngInject
 */
ngeo.createPopupServiceFactory = function($compile, $rootScope) {
  return (
      /**
       * @return {!ngeo.Popup} The popup instance.
       */
      function() {
        return new ngeo.Popup($compile, $rootScope);
      });
};
ngeoModule.factory('ngeoCreatePopup', ngeo.createPopupServiceFactory);
<commit_msg>Compile & link popup element before appending it to document<commit_after>/**
 * @fileoverview Provides a factory to create a popup in the page.
 * The factory returns a ngeo.Popup object.
 *
 * Example:
 *
 * var popup = ngeoCreatePopup();
 * popup.setTitle("A title");
 * popup.setContent("Some content");
 * popup.show();
 *
 */

goog.provide('ngeo.CreatePopup');
goog.provide('ngeo.Popup');

goog.require('ngeo');
goog.require('ngeo.popupDirective');

/**
 * @typedef {function():!ngeo.Popup}
 */
ngeo.CreatePopup;

/**
 * @constructor
 * @param {angular.$compile} $compile The compile provider.
 * @param {angular.Scope} $rootScope The rootScope provider.
 */
ngeo.Popup = function($compile, $rootScope) {

  /**
   * The scope the compiled element is link to.
   * @type {angular.Scope}
   * @private
   */
  this.scope_ = $rootScope.$new(true);

  /**
   * The element.
   * @type {angular.JQLite}
   * @private
   */
  this.element_ = angular.element('

');

  // Compile the element, link it to the scope and add it to the document.
  $compile(this.element_)(this.scope_);
  angular.element(document.body).append(this.element_);
};

/**
 * Display the popup.
 */
ngeo.Popup.prototype.show = function() {
  this.scope_['open'] = true;
};

/**
 * Destroy the popup.
 */
ngeo.Popup.prototype.destroy = function() {
  this.scope_.$destroy();
  this.element_.remove();
};

/**
 * Set the popup's title.
 * @param {string} title The title.
 */
ngeo.Popup.prototype.setTitle = function(title) {
  this.scope_['title'] = title;
};

/**
 * Set the popup's content.
 * Note: the type of the `content` param is `*` instead of `string`, this
 * is because the content may be trusted using `$sce.trustAsHtml`.
 * @param {*} content The content.
 */
ngeo.Popup.prototype.setContent = function(content) {
  this.scope_['content'] = content;
};

/**
 * @param {angular.$compile} $compile Angular compile service.
 * @param {angular.Scope} $rootScope Angular rootScope service.
 * @return {ngeo.CreatePopup} The function to create a popup.
 * @ngInject
 */
ngeo.createPopupServiceFactory = function($compile, $rootScope) {
  return (
      /**
       * @return {!ngeo.Popup} The popup instance.
       */
      function() {
        return new ngeo.Popup($compile, $rootScope);
      });
};
ngeoModule.factory('ngeoCreatePopup', ngeo.createPopupServiceFactory);
<|endoftext|>


<commit_before>'use strict'
let dotenv = require('dotenv').config(),
    zlib = require('zlib'),
    request = require('request-json'),
    slack_client = request.createClient('https://hooks.slack.com/services/')

exports.handler = function(event, context, callback) {
  console.log('raw data: ', event.awslogs.data)
  let payload = new Buffer(event.awslogs.data, 'base64'),
      events = '',
      message = '',
      slack_fields = [],
      who = 'Unknown',
      // ignore_users = ['gordon', 'serverless'],
      slack_notify = false

  zlib.gunzip(payload, (e, result) => {
    if (e) {
        callback(e)
    } else {
        result = JSON.parse(result.toString('utf8'))

        for (let e of result.logEvents) {
          message = JSON.parse(e.message.toString('utf8'))
          who = message.userIdentity.userName || who

          // Don't want to include events that we don't care about:
          if (
            (message.eventName.startsWith('Delete') || message.eventName.startsWith('Create') || message.eventName.startsWith('Update') || message.eventName.startsWith('Put'))
          ) {
            slack_notify = true
            slack_fields.push(
              {"title": "Who", "value": who, "short": true},
              {"title": "Event Name", "value": message.eventName, "short": true},
              {"title": "Event Time", "value": message.eventTime, "short": true},
              {"title": "Action Via", "value": message.eventType, "short": true},
              {"title": "AWS Region", "value": message.awsRegion, "short": true},
              {"title": "Source IP", "value": message.sourceIPAddress, "short": true}
            )
          }
        }

        let slack_payload = {
          "attachments": [
            {
              "fallback": "New AWS event",
              "color": "good",
              // "pretext": "New AWS CloudTrail event",
              "author_name": who,
              "author_link": "",
              "author_icon": "",
              "title": "New AWS CloudTrail event",
              "title_link": "https://console.aws.amazon.com/cloudtrail/home?region=us-east-1#/events",
              // "text": events,
              "fields": slack_fields,
              // "image_url": "http://my-website.com/path/to/image.jpg",
              // "thumb_url": "http://example.com/path/to/thumb.png",
              // "footer": "Footer text",
              // "footer_icon": "https://platform.slack-edge.com/img/default_application_icon.png",
            }
          ]
        }

        if (slack_notify) {
          slack_client.post(process.env.SLACK_TOKEN, slack_payload, function(error, res, body) {
            console.log(error)
            callback(error)
          })
        }

        callback(null, `Successfully processed ${result.logEvents.length} log events.\n\n Decoded payload: ${JSON.stringify(result)}`)
    }
  })
}
<commit_msg>supporting more events<commit_after>'use strict'
let dotenv = require('dotenv').config(),
    zlib = require('zlib'),
    request = require('request-json'),
    slack_client = request.createClient('https://hooks.slack.com/services/')

exports.handler = function(event, context, callback) {
  console.log('raw data: ', event.awslogs.data)
  let payload = new Buffer(event.awslogs.data, 'base64'),
      events = '',
      message = '',
      slack_fields = [],
      who = 'Unknown',
      // ignore_users = ['gordon', 'serverless'],
      slack_notify = false

  zlib.gunzip(payload, (e, result) => {
    if (e) {
        callback(e)
    } else {
        result = JSON.parse(result.toString('utf8'))

        for (let e of result.logEvents) {
          message = JSON.parse(e.message.toString('utf8'))
          who = message.userIdentity.userName || who

          // Don't want to include events that we don't care about:
          if (message.eventName.startsWith('Delete')
            || message.eventName.startsWith('Create')
            || message.eventName.startsWith('Update')
            || message.eventName.startsWith('Put')
            || message.eventName.startsWith('Authorize')
            || message.eventName.startsWith('Modify')
            || message.eventName.startsWith('Allocate')
            || message.eventName.startsWith('Run')
          ) {
            slack_notify = true
            slack_fields.push(
              {"title": "Who", "value": who, "short": true},
              {"title": "Event Name", "value": message.eventName, "short": true},
              {"title": "Event Time", "value": message.eventTime, "short": true},
              {"title": "Action Via", "value": message.eventType, "short": true},
              {"title": "AWS Region", "value": message.awsRegion, "short": true},
              {"title": "Source IP", "value": message.sourceIPAddress, "short": true}
            )
          }
        }

        let slack_payload = {
          "attachments": [
            {
              "fallback": "New AWS event",
              "color": "good",
              // "pretext": "New AWS CloudTrail event",
              "author_name": who,
              "author_link": "",
              "author_icon": "",
              "title": "New AWS CloudTrail event",
              "title_link": "https://console.aws.amazon.com/cloudtrail/home?region=us-east-1#/events",
              // "text": events,
              "fields": slack_fields,
              // "image_url": "http://my-website.com/path/to/image.jpg",
              // "thumb_url": "http://example.com/path/to/thumb.png",
              // "footer": "Footer text",
              // "footer_icon": "https://platform.slack-edge.com/img/default_application_icon.png",
            }
          ]
        }

        if (slack_notify) {
          slack_client.post(process.env.SLACK_TOKEN, slack_payload, function(error, res, body) {
            console.log(error)
            callback(error)
          })
        }

        callback(null, `Successfully processed ${result.logEvents.length} log events.\n\n Decoded payload: ${JSON.stringify(result)}`)
    }
  })
}
<|endoftext|>


<commit_before>import log from '../utils/log';
import DataSource, {NetworkTileSource} from './data_source';
import Tile from '../tile';
import Geo from '../geo';
import Utils from '../utils/utils';

// TODO: support high-density `@2x`-style filenames for raster tiles and geo-referenced images

export class RasterTileSource extends NetworkTileSource {

    constructor (source, sources) {
        super(source, sources);

        if (this.rasters.indexOf(this.name) === -1) {
            this.rasters.unshift(this.name); // add this raster as the first
        }
        this.filtering = source.filtering; // optional texture filtering (nearest, linear, mipmap)

        // save texture objects by tile key, so URL remains stable if tile is built multiple times,
        // e.g. avoid re-loading the same tile texture under a different subdomain when using tile hosts
        this.textures = {};
    }

    async load (tile) {
        tile.source_data = {};
        tile.source_data.layers = {};
        tile.pad_scale = this.pad_scale;
        tile.rasters = [...this.rasters]; // copy list of rasters to load for tile

        // Generate a single quad that fills the entire tile
        let scale = Geo.tile_scale;
        tile.source_data.layers = {
            _default: {
                type: 'FeatureCollection',
                features: [{
                    geometry: {
                        type: 'Polygon',
                        coordinates: [[
                            [0, 0], [scale, 0],
                            [scale, -scale], [0, -scale], [0, 0]
                        ]]
                    },
                    properties: {}
                }]
            }
        };

        tile.default_winding = 'CW';
        return tile;
    }

    // Return texture info for a raster tile
    async tileTexture (tile) {
        let coords = Tile.coordinateWithMaxZoom(tile.coords, this.max_zoom);
        let key = coords.key;
        if (!this.textures[key]) {
            let url = this.formatURL(this.url, { coords });
            this.textures[key] = {
                name: url,
                url,
                filtering: this.filtering,
                coords
            };
        }
        return this.textures[key];
    }

}

// Data source for loading standalone, geo-referenced raster images
// The `bounds` parameter is used to position the raster image on the map
// Currently, only axis-aligned, rectangular North-up images are supported
// TODO: add support for arbitrarily rotated images and quadrangle control points
export class RasterSource extends RasterTileSource {

    constructor (source, sources) {
        super(source, sources);

        this.load_image = {}; // resolves to image, cached for life of data source

        // alpha factor applied at time raster images are loaded and tiled (*not* at shader render-time)
        this.alpha = (source.alpha != null) ? Math.max(Math.min(source.alpha, 1), 0) : null;

        // non-full-alpha pixels should be discarded (for rendering rasters w/opaque blend)
        this.mask_alpha = true;

        // Optionally composite multiple images into one raster layer
        if (Array.isArray(source.composite)) {
            // TODO: calculate enclosing bounding box to speed tile intersection checks
            this.images = source.composite.map(s => {
                return {
                    url: s.url,
                    bounds: this.parseBounds(s),
                    alpha: (s.alpha != null) ? Math.max(Math.min(s.alpha, 1), 0) : null
                };
            });
        }
        // Single image raster layer
        else {
            this.images = [{
                url: this.url,
                bounds: this.bounds,
                alpha: this.alpha
            }];
        }
    }

    // Render the sub-rectangle of the source raster image for the given tile, to a texture.
    // Clipping the source image to individual raster tiles naturally partitions images
    // (which may be large or only have a small portion in current view), and maintains
    // consistency with the raster tile pipeline allowing for sampling within the fragment shader,
    // and clipping the raster against vector source data.
    async tileTexture (tile, { blend }) {
        const coords = Tile.coordinateWithMaxZoom(tile.coords, this.max_zoom);
        const use_alpha = (blend !== 'opaque'); // ignore source alpha multiplier with opaque blending
        const name = `raster-${this.name}-${coords.key}-${use_alpha ? 'alpha' : 'opaque'}`; // unique texture name

        // Display density, with extra 2x for better intra-zoom scaling, because raster tiles
        // can be scaled up to 100% before next zoom level is loaded
        const dpr = Utils.device_pixel_ratio * 2;

        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = this.tile_size * dpr; // adjusted for display density
        canvas.height = this.tile_size * dpr;

        // Applies nearest neighbor filtering to the canvas image rendering when data source requests it
        // NB: does not work on IE11 (image will be blurry when scaled)
        ctx.imageSmoothingEnabled = (this.filtering !== 'nearest');

        // Draw one or more images
        const images = this.images.filter(r => this.checkBounds(tile.coords, r.bounds));
        await Promise.all(images.map(i => {
            // TODO: log warning if alpha specified but will be ignored (in opaque mode)?
            const alpha = (use_alpha ? (i.alpha != null ? i.alpha : this.alpha) : 1);
            return this.drawImage(i.url, i.bounds, alpha, tile, dpr, ctx);
        }));

        // texture config
        return {
            name,
            element: canvas,
            filtering: this.filtering,
            coords
        };
    }

    // Draw a single image to the tile canvas based on on its bounds
    async drawImage (url, bounds, alpha, tile, dpr, ctx) {
        // Get source raster image
        this.load_image[url] = this.load_image[url] || this.loadImage(url);
        const image = await this.load_image[url];

        // Meters per pixel for this zoom, adjusted for display density and source tile size (e.g. 512px tiles)
        const mpp = Geo.metersPerPixel(tile.coords.z) / dpr / (this.tile_size / Geo.tile_size);

        // Raster origin relative to tile origin (get delta in meters, then convert to pixels)
        const dx = (bounds.meters.min[0] - tile.min.x) / mpp;
        const dy = -(bounds.meters.min[1] - tile.min.y) / mpp;

        // Raster size in pixels for current zoom
        const sx = (bounds.meters.max[0] - bounds.meters.min[0]) / mpp;
        const sy = -(bounds.meters.max[1] - bounds.meters.min[1]) / mpp;

        // Draw the raster, clipped to the current tile
        // NB: this is drawing the *whole* raster, no matter how large, and relying on the native Canvas clipping.
        // May want to benchmark with a pre-clipped draw area, though the native implementation is likely fast,
        // and has to apply its own clipping check anyway.
        ctx.globalAlpha = (alpha != null) ? alpha : 1;
        ctx.drawImage(image, dx, dy, sx, sy);
    }

    // Load source raster image
    loadImage (url) {
        return new Promise(resolve => {
            let image = new Image();
            image.onload = () => resolve(image);
            image.onerror = e => {
                // Warn and resolve on error
                log('warn', `Raster source '${this.name}': failed to load url: '${url}'`, e);
                resolve(null);
            };

            // Safari has a bug loading data-URL images with CORS enabled, so it must be disabled in that case
            // https://bugs.webkit.org/show_bug.cgi?id=123978
            if (!(Utils.isSafari() && url.slice(0, 5) === 'data:')) {
                image.crossOrigin = 'anonymous';
            }

            image.src = url;
        });
    }

    // Checks if tile interects any rasters in this source
    includesTile (coords, style_zoom) {
        // Checks zoom range and dependent rasters
        if (!DataSource.prototype.includesTile.call(this, coords, style_zoom)) {
            return false;
        }

        return this.images.some(r => this.checkBounds(coords, r.bounds)); // check if any images intersect
    }

    validate (source) {
        const is_composite = Array.isArray(source.composite);

        let url_msg = 'Raster data source must provide a string `url` parameter, or an array of `composite` raster ';
        url_msg += 'image objects that each have a `url` parameter';

        let bounds_msg = 'Raster data source must provide a `bounds` parameter, or an array of `composite` raster ';
        bounds_msg += 'image objects that each have a `bounds` parameter';

        let mutex_msg = 'Raster data source must have *either* a single image specified as `url` and `bounds `';
        mutex_msg += 'parameters, or an array of `composite` raster image objects, each with `url` and `bounds`.';

        if (is_composite) {
            if (source.composite.some(s => typeof s.url !== 'string')) {
                throw Error(url_msg);
            }

            if (source.composite.some(s => !(Array.isArray(s.bounds) && s.bounds.length === 4))) {
                throw Error(bounds_msg);
            }

            if (source.url != null || source.bounds != null) {
                throw Error(mutex_msg);
            }
        }
        else {
            if (typeof source.url !== 'string') {
                throw Error(url_msg);
            }

            if (!(Array.isArray(source.bounds) && source.bounds.length === 4)) {
                throw Error(bounds_msg);
            }
        }
    }

}

// Check for URL tile pattern, if not found, treat as geo-referenced raster layer
DataSource.register('Raster', source => {
    return RasterTileSource.urlHasTilePattern(source.url) ? RasterTileSource : RasterSource;
});
<commit_msg>un-tiled rasters: use hash of URL for shorter keys<commit_after>import log from '../utils/log';
import DataSource, {NetworkTileSource} from './data_source';
import Tile from '../tile';
import Geo from '../geo';
import Utils from '../utils/utils';
import hashString from '../utils/hash';

// TODO: support high-density `@2x`-style filenames for raster tiles and geo-referenced images

export class RasterTileSource extends NetworkTileSource {

    constructor (source, sources) {
        super(source, sources);

        if (this.rasters.indexOf(this.name) === -1) {
            this.rasters.unshift(this.name); // add this raster as the first
        }
        this.filtering = source.filtering; // optional texture filtering (nearest, linear, mipmap)

        // save texture objects by tile key, so URL remains stable if tile is built multiple times,
        // e.g. avoid re-loading the same tile texture under a different subdomain when using tile hosts
        this.textures = {};
    }

    async load (tile) {
        tile.source_data = {};
        tile.source_data.layers = {};
        tile.pad_scale = this.pad_scale;
        tile.rasters = [...this.rasters]; // copy list of rasters to load for tile

        // Generate a single quad that fills the entire tile
        let scale = Geo.tile_scale;
        tile.source_data.layers = {
            _default: {
                type: 'FeatureCollection',
                features: [{
                    geometry: {
                        type: 'Polygon',
                        coordinates: [[
                            [0, 0], [scale, 0],
                            [scale, -scale], [0, -scale], [0, 0]
                        ]]
                    },
                    properties: {}
                }]
            }
        };

        tile.default_winding = 'CW';
        return tile;
    }

    // Return texture info for a raster tile
    async tileTexture (tile) {
        let coords = Tile.coordinateWithMaxZoom(tile.coords, this.max_zoom);
        let key = coords.key;
        if (!this.textures[key]) {
            let url = this.formatURL(this.url, { coords });
            this.textures[key] = {
                name: url,
                url,
                filtering: this.filtering,
                coords
            };
        }
        return this.textures[key];
    }

}

// Data source for loading standalone, geo-referenced raster images
// The `bounds` parameter is used to position the raster image on the map
// Currently, only axis-aligned, rectangular North-up images are supported
// TODO: add support for arbitrarily rotated images and quadrangle control points
export class RasterSource extends RasterTileSource {

    constructor (source, sources) {
        super(source, sources);

        this.load_image = {}; // resolves to image, cached for life of data source

        // alpha factor applied at time raster images are loaded and tiled (*not* at shader render-time)
        this.alpha = (source.alpha != null) ? Math.max(Math.min(source.alpha, 1), 0) : null;

        // non-full-alpha pixels should be discarded (for rendering rasters w/opaque blend)
        this.mask_alpha = true;

        // Optionally composite multiple images into one raster layer
        if (Array.isArray(source.composite)) {
            // TODO: calculate enclosing bounding box to speed tile intersection checks
            this.images = source.composite.map(s => {
                return {
                    url: s.url,
                    bounds: this.parseBounds(s),
                    alpha: (s.alpha != null) ? Math.max(Math.min(s.alpha, 1), 0) : null
                };
            });
        }
        // Single image raster layer
        else {
            this.images = [{
                url: this.url,
                bounds: this.bounds,
                alpha: this.alpha
            }];
        }
    }

    // Render the sub-rectangle of the source raster image for the given tile, to a texture.
    // Clipping the source image to individual raster tiles naturally partitions images
    // (which may be large or only have a small portion in current view), and maintains
    // consistency with the raster tile pipeline allowing for sampling within the fragment shader,
    // and clipping the raster against vector source data.
    async tileTexture (tile, { blend }) {
        const coords = Tile.coordinateWithMaxZoom(tile.coords, this.max_zoom);
        const use_alpha = (blend !== 'opaque'); // ignore source alpha multiplier with opaque blending
        const name = `raster-${this.name}-${coords.key}-${use_alpha ? 'alpha' : 'opaque'}`; // unique texture name

        // Display density, with extra 2x for better intra-zoom scaling, because raster tiles
        // can be scaled up to 100% before next zoom level is loaded
        const dpr = Utils.device_pixel_ratio * 2;

        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = this.tile_size * dpr; // adjusted for display density
        canvas.height = this.tile_size * dpr;

        // Applies nearest neighbor filtering to the canvas image rendering when data source requests it
        // NB: does not work on IE11 (image will be blurry when scaled)
        ctx.imageSmoothingEnabled = (this.filtering !== 'nearest');

        // Draw one or more images
        const images = this.images.filter(r => this.checkBounds(tile.coords, r.bounds));
        await Promise.all(images.map(i => {
            // TODO: log warning if alpha specified but will be ignored (in opaque mode)?
            const alpha = (use_alpha ? (i.alpha != null ? i.alpha : this.alpha) : 1);
            return this.drawImage(i.url, i.bounds, alpha, tile, dpr, ctx);
        }));

        // texture config
        return {
            name,
            element: canvas,
            filtering: this.filtering,
            coords
        };
    }

    // Draw a single image to the tile canvas based on on its bounds
    async drawImage (url, bounds, alpha, tile, dpr, ctx) {
        // Get source raster image
        const key = hashString(url); // use hash of URL for shorter keys
        this.load_image[key] = this.load_image[key] || this.loadImage(url);
        const image = await this.load_image[key];

        // Meters per pixel for this zoom, adjusted for display density and source tile size (e.g. 512px tiles)
        const mpp = Geo.metersPerPixel(tile.coords.z) / dpr / (this.tile_size / Geo.tile_size);

        // Raster origin relative to tile origin (get delta in meters, then convert to pixels)
        const dx = (bounds.meters.min[0] - tile.min.x) / mpp;
        const dy = -(bounds.meters.min[1] - tile.min.y) / mpp;

        // Raster size in pixels for current zoom
        const sx = (bounds.meters.max[0] - bounds.meters.min[0]) / mpp;
        const sy = -(bounds.meters.max[1] - bounds.meters.min[1]) / mpp;

        // Draw the raster, clipped to the current tile
        // NB: this is drawing the *whole* raster, no matter how large, and relying on the native Canvas clipping.
        // May want to benchmark with a pre-clipped draw area, though the native implementation is likely fast,
        // and has to apply its own clipping check anyway.
        ctx.globalAlpha = (alpha != null) ? alpha : 1;
        ctx.drawImage(image, dx, dy, sx, sy);
    }

    // Load source raster image
    loadImage (url) {
        return new Promise(resolve => {
            let image = new Image();
            image.onload = () => resolve(image);
            image.onerror = e => {
                // Warn and resolve on error
                log('warn', `Raster source '${this.name}': failed to load url: '${url}'`, e);
                resolve(null);
            };

            // Safari has a bug loading data-URL images with CORS enabled, so it must be disabled in that case
            // https://bugs.webkit.org/show_bug.cgi?id=123978
            if (!(Utils.isSafari() && url.slice(0, 5) === 'data:')) {
                image.crossOrigin = 'anonymous';
            }

            image.src = url;
        });
    }

    // Checks if tile interects any rasters in this source
    includesTile (coords, style_zoom) {
        // Checks zoom range and dependent rasters
        if (!DataSource.prototype.includesTile.call(this, coords, style_zoom)) {
            return false;
        }

        return this.images.some(r => this.checkBounds(coords, r.bounds)); // check if any images intersect
    }

    validate (source) {
        const is_composite = Array.isArray(source.composite);

        let url_msg = 'Raster data source must provide a string `url` parameter, or an array of `composite` raster ';
        url_msg += 'image objects that each have a `url` parameter';

        let bounds_msg = 'Raster data source must provide a `bounds` parameter, or an array of `composite` raster ';
        bounds_msg += 'image objects that each have a `bounds` parameter';

        let mutex_msg = 'Raster data source must have *either* a single image specified as `url` and `bounds `';
        mutex_msg += 'parameters, or an array of `composite` raster image objects, each with `url` and `bounds`.';

        if (is_composite) {
            if (source.composite.some(s => typeof s.url !== 'string')) {
                throw Error(url_msg);
            }

            if (source.composite.some(s => !(Array.isArray(s.bounds) && s.bounds.length === 4))) {
                throw Error(bounds_msg);
            }

            if (source.url != null || source.bounds != null) {
                throw Error(mutex_msg);
            }
        }
        else {
            if (typeof source.url !== 'string') {
                throw Error(url_msg);
            }

            if (!(Array.isArray(source.bounds) && source.bounds.length === 4)) {
                throw Error(bounds_msg);
            }
        }
    }

}

// Check for URL tile pattern, if not found, treat as geo-referenced raster layer
DataSource.register('Raster', source => {
    return RasterTileSource.urlHasTilePattern(source.url) ? RasterTileSource : RasterSource;
});
<|endoftext|>


<commit_before>/* global ButtonList, w2, h2, Phaser, musicList */
var credits = function (game) {
  this.title = null
  this.buttons = null
  this.containerScrollBar = null
  this.scrollMask = null
  this.items = null
  this.disabledCount = 0
  this.autoScroll = false
  this.scrollStep = 0
}

credits.prototype = {
  init: function (autoScroll) {
    if (autoScroll) this.autoScroll = autoScroll
  },

  create: function () {
    setScreenFixed(baseH * 1.5, baseH * 1.1, this.game)

    var title = this.autoScroll ? 'curvatron' : 'credits'
    this.title = this.game.add.text(w2, 100, title, {
      font: '150px dosis',
      fill: '#ffffff',
      align: 'center'
    })
    this.title.anchor.setTo(0.5, 0.5)

    this.containerX = 1.7 * w2
    this.containerY = this.autoScroll ? 0 : 200
    this.listY = 300

    this.buttons = new ButtonList(this, this.game)
    this.buttons.distance = 80

    if (!this.autoScroll) {
      var backButton = this.buttons.add('back_button', 'back', this.backPressed)
      backButton.h = 70
    } else {
      this.buttons.textColor = 'black'
      this.game.time.events.add(Phaser.Timer.SECOND * 3, function () {
        this.scrollStep = 0.3
      }, this)
    }

    this.getCreditsText()
    this.game.input.mouse.mouseWheelCallback = this.mouseWheel.bind(this)

    if (this.autoScroll) {
      var overlay = this.game.add.sprite(0, 0, 'overlay')
      overlay.width = this.game.world.width
      overlay.height = this.game.world.height
      overlay.alpha = 1
      var fadeIn = this.game.add.tween(overlay)
      fadeIn.to({alpha:0}, 3000, Phaser.Easing.Linear.None)
      fadeIn.start()
    }
  },

  getCreditsText: function () {
    var creds = []

    this.addDiv('GAME DEVELOPMENT AND DESIGN')
    this.addCred('Ricardo Lopes', 'https://twitter.com/Raicuparta', creds)
    this.addCred('Tiago Silva', 'https://twitter.com/JeronimoPie', creds)

    this.addDiv('DEVELOPMENT ADVISING AND WISDOM PROVIDING')
    this.addCred('Francisco Dias', 'https://twitter.com/xicombd', creds)

    this.addDiv('SOUND EFFECTS')
    this.addCred('StumpyStrust: snake movement', 'http://opengameart.org/content/ui-sounds', creds)
    this.addCred('David Lin: everything else', 'http://google.com', creds)

    this.addDiv('MUSIC')
    for (var i = 0; i < musicList.length; i++) {
      var title = musicList[i].title + " : " + musicList[i].author
      if (!title) title = 'level ' + (i + 1);
      (function (i) {
        var button = this.buttons.add(null, title, function () {
          var greenworks = require('./greenworks')
          greenworks.activateGameOverlayToWebPage(musicList[i].site) // link for the musicians pages
        })
        button.w = 550
        button.h = 70
        button.textOnly = true
      }.bind(this))(i)
    }

    this.addDiv('DANK ANDROID TRAILER')
    this.addCred('Pedro Mota', 'https://www.youtube.com/user/pnamota/videos', creds)

    this.addDiv('BETA TESTING')
    this.addCred('B-Man99', 'http://chainreactionmusic.com/', creds)
    this.addCred('LPChip', 'http://www.lpchip.nl/', creds)
    this.addCred('Beatriz Santos', null, creds)
    this.addCred('Francisco Dias', null, creds)
    this.addCred('Pedro Mota', null, creds)
    this.addCred('sergiocornaga', 'http://sergiocornaga.tumblr.com/', creds)
    this.addCred('/r/gamedev', 'https://www.reddit.com/r/gamedev/comments/41ejwm/wanna_help_me_test_my_level_editor_steam_keys_to/', creds)
    this.addCred('Stabyourself forum', 'http://forum.stabyourself.net/viewtopic.php?f=11&t=4886', creds)

    this.addDiv('SNAKES USED FOR PHYSICS TESTS')
    this.addCred('Solid', null, creds)
    this.addCred('Nagini', null, creds)
    this.addCred('Nokia 3310', null, creds)
    this.addCred('Ekans', null, creds)

    this.addDiv('No snakes were harmed in the development of this game')

    for (var i = 0; i < creds.length; i++) {
      creds[i].w = 550
      creds[i].h = 70
      creds[i].textOnly = true
    }

    this.createButtons()
  },

  addDiv: function (name) {
    var div = this.buttons.add(null, name)
    div.w = 700
    div.h = 70
    div.disable()
    this.disabledCount++
  },

  addCred: function (name, url, creds) {
    creds.push(this.buttons.add(null, name, function () {
      if (url) {
        var greenworks = require('./greenworks')
        greenworks.activateGameOverlayToWebPage(url)
      }
    }))
  },

  createButtons: function () {
    this.buttons.create()
    this.buttons.setScrolling(true)
    if (!this.autoScroll) this.buttons.select(1)

    var barHeight = 2 * h2 - this.containerY + 100
    var draggyHeight = Math.min(barHeight * (7 / (musicList.length + 1)), barHeight)

    this.containerScrollBar = this.game.add.sprite(this.containerX, this.containerY - 100, 'scroll_button')
    this.containerScrollBar.scale.set(50, draggyHeight)
    this.containerScrollBar.anchor.set(1, 0)
    if (this.autoScroll) this.containerScrollBar.alpha = 0.5
    if (draggyHeight === barHeight) this.containerScrollBar.visible = false

    this.scrollMask = this.game.add.graphics(0, 0)
    this.scrollMask.beginFill(0xffffff)
    this.scrollMask.drawRect(w2 * 0.5, this.containerY, w2 * 2, h2 * 2)
    this.scrollMask.endFill()

    this.buttons.setMask(this.scrollMask)

    this.containerScrollBar.inputEnabled = true
    this.containerScrollBar.input.useHandCursor = true
    this.containerScrollBar.input.enableDrag(false, true)
    this.containerScrollBar.input.allowHorizontalDrag = false
    this.containerScrollBar.input.boundsRect = new Phaser.Rectangle(
      0,
      this.containerY - 100,
      2 * w2,
      barHeight)
  },

  update: function () {
    if (this.containerScrollBar) {
      var length = this.buttons.length() + 1 + this.disabledCount
      this.buttons.setY(this.listY + (this.containerY - 100 - this.containerScrollBar.y) / ((2 * h2 - this.containerY + 100) / ((length) * this.buttons.distance)))
      this.buttons.update()
      this.containerScrollBar.input.checkBoundsRect()
      if (this.autoScroll) {
        this.containerScrollBar.y += this.scrollStep
        this.title.y = this.buttons.y - 150
      }

    }
  },

  backPressed: function () {
    this.game.state.start('Menu')
  },

  mouseWheel: function (event) {
    var min = 200, max = 900
    var c = this.containerScrollBar
    c.y -= this.game.input.mouse.wheelDelta * 10
    this.containerScrollBar.input.checkBoundsRect()
  },

  up: function () {
    this.buttons.selectUp()
    if (this.buttons.length() > 6 && !this.autoScroll) {
      if (this.buttons.selection < this.buttons.length() - 1) {
        if (this.containerScrollBar.y > this.containerY - 100) {
          this.buttons.setY(this.buttons.y + this.buttons.distance)
          var length = this.buttons.length() + 1 + this.disabledCount
          this.containerScrollBar.y = -(this.buttons.y - 300) * ((2 * h2 - this.containerY + 100) / ((length) * this.buttons.distance)) + this.containerY - 100
        }
      } else {
        this.buttons.setY(this.buttons.y + this.buttons.distance * this.buttons.length)
        this.containerScrollBar.y = h2 * 2 - this.containerScrollBar.height
      }
    }
  },

  down: function () {
    if (this.buttons.length() > 6 && !this.autoScroll) {
      if (this.buttons.selection < this.buttons.length() - 1  + this.disabledCount) {
        if (this.containerScrollBar.y < h2 * 2 - this.containerScrollBar.height) {
          this.buttons.setY(this.buttons.y - this.buttons.distance)
          var length = this.buttons.length() + 1 + this.disabledCount
          this.containerScrollBar.y = -(this.buttons.y - 300) * ((2 * h2 - this.containerY + 100) / ((length) * this.buttons.distance)) + this.containerY - 100
        }
      } else {
        this.buttons.setY(this.buttons.y - this.buttons.distance * this.buttons.length)
        this.containerScrollBar.y = this.containerY - 100
      }
    }
    this.buttons.selectDown()
  },

  selectPress: function () {
    this.buttons.selectPress()
  },

  selectRelease: function () {
    this.buttons.selectRelease()
  }

}
<commit_msg>credits scroll velocity = level scroll velocity<commit_after>/* global ButtonList, w2, h2, Phaser, musicList */
var credits = function (game) {
  this.title = null
  this.buttons = null
  this.containerScrollBar = null
  this.scrollMask = null
  this.items = null
  this.disabledCount = 0
  this.autoScroll = false
  this.scrollStep = 0
}

credits.prototype = {
  init: function (autoScroll) {
    if (autoScroll) this.autoScroll = autoScroll
  },

  create: function () {
    setScreenFixed(baseH * 1.5, baseH * 1.1, this.game)

    var title = this.autoScroll ? 'curvatron' : 'credits'
    this.title = this.game.add.text(w2, 100, title, {
      font: '150px dosis',
      fill: '#ffffff',
      align: 'center'
    })
    this.title.anchor.setTo(0.5, 0.5)

    this.containerX = 1.7 * w2
    this.containerY = this.autoScroll ? 0 : 200
    this.listY = 300

    this.buttons = new ButtonList(this, this.game)
    this.buttons.distance = 80

    if (!this.autoScroll) {
      var backButton = this.buttons.add('back_button', 'back', this.backPressed)
      backButton.h = 70
    } else {
      this.buttons.textColor = 'black'
      this.game.time.events.add(Phaser.Timer.SECOND * 3, function () {
        this.scrollStep = 0.3
      }, this)
    }

    this.getCreditsText()
    this.game.input.mouse.mouseWheelCallback = this.mouseWheel.bind(this)

    if (this.autoScroll) {
      var overlay = this.game.add.sprite(0, 0, 'overlay')
      overlay.width = this.game.world.width
      overlay.height = this.game.world.height
      overlay.alpha = 1
      var fadeIn = this.game.add.tween(overlay)
      fadeIn.to({alpha:0}, 3000, Phaser.Easing.Linear.None)
      fadeIn.start()
    }
  },

  getCreditsText: function () {
    var creds = []

    this.addDiv('GAME DEVELOPMENT AND DESIGN')
    this.addCred('Ricardo Lopes', 'https://twitter.com/Raicuparta', creds)
    this.addCred('Tiago Silva', 'https://twitter.com/JeronimoPie', creds)

    this.addDiv('DEVELOPMENT ADVISING AND WISDOM PROVIDING')
    this.addCred('Francisco Dias', 'https://twitter.com/xicombd', creds)

    this.addDiv('SOUND EFFECTS')
    this.addCred('StumpyStrust: snake movement', 'http://opengameart.org/content/ui-sounds', creds)
    this.addCred('David Lin: everything else', 'http://google.com', creds)

    this.addDiv('MUSIC')
    for (var i = 0; i < musicList.length; i++) {
      var title = musicList[i].title + " : " + musicList[i].author
      if (!title) title = 'level ' + (i + 1);
      (function (i) {
        var button = this.buttons.add(null, title, function () {
          var greenworks = require('./greenworks')
          greenworks.activateGameOverlayToWebPage(musicList[i].site) // link for the musicians pages
        })
        button.w = 550
        button.h = 70
        button.textOnly = true
      }.bind(this))(i)
    }

    this.addDiv('DANK ANDROID TRAILER')
    this.addCred('Pedro Mota', 'https://www.youtube.com/user/pnamota/videos', creds)

    this.addDiv('BETA TESTING')
    this.addCred('B-Man99', 'http://chainreactionmusic.com/', creds)
    this.addCred('LPChip', 'http://www.lpchip.nl/', creds)
    this.addCred('Beatriz Santos', null, creds)
    this.addCred('Francisco Dias', null, creds)
    this.addCred('Pedro Mota', null, creds)
    this.addCred('sergiocornaga', 'http://sergiocornaga.tumblr.com/', creds)
    this.addCred('/r/gamedev', 'https://www.reddit.com/r/gamedev/comments/41ejwm/wanna_help_me_test_my_level_editor_steam_keys_to/', creds)
    this.addCred('Stabyourself forum', 'http://forum.stabyourself.net/viewtopic.php?f=11&t=4886', creds)

    this.addDiv('SNAKES USED FOR PHYSICS TESTS')
    this.addCred('Solid', null, creds)
    this.addCred('Nagini', null, creds)
    this.addCred('Nokia 3310', null, creds)
    this.addCred('Ekans', null, creds)

    this.addDiv('No snakes were harmed in the development of this game')

    for (var i = 0; i < creds.length; i++) {
      creds[i].w = 550
      creds[i].h = 70
      creds[i].textOnly = true
    }

    this.createButtons()
  },

  addDiv: function (name) {
    var div = this.buttons.add(null, name)
    div.w = 700
    div.h = 70
    div.disable()
    this.disabledCount++
  },

  addCred: function (name, url, creds) {
    creds.push(this.buttons.add(null, name, function () {
      if (url) {
        var greenworks = require('./greenworks')
        greenworks.activateGameOverlayToWebPage(url)
      }
    }))
  },

  createButtons: function () {
    this.buttons.create()
    this.buttons.setScrolling(true)
    if (!this.autoScroll) this.buttons.select(1)

    var barHeight = 2 * h2 - this.containerY + 100
    var draggyHeight = Math.min(barHeight * (7 / (musicList.length + 1)), barHeight)

    this.containerScrollBar = this.game.add.sprite(this.containerX, this.containerY - 100, 'scroll_button')
    this.containerScrollBar.scale.set(50, draggyHeight)
    this.containerScrollBar.anchor.set(1, 0)
    if (this.autoScroll) this.containerScrollBar.alpha = 0.5
    if (draggyHeight === barHeight) this.containerScrollBar.visible = false

    this.scrollMask = this.game.add.graphics(0, 0)
    this.scrollMask.beginFill(0xffffff)
    this.scrollMask.drawRect(w2 * 0.5, this.containerY, w2 * 2, h2 * 2)
    this.scrollMask.endFill()

    this.buttons.setMask(this.scrollMask)

    this.containerScrollBar.inputEnabled = true
    this.containerScrollBar.input.useHandCursor = true
    this.containerScrollBar.input.enableDrag(false, true)
    this.containerScrollBar.input.allowHorizontalDrag = false
    this.containerScrollBar.input.boundsRect = new Phaser.Rectangle(
      0,
      this.containerY - 100,
      2 * w2,
      barHeight)
  },

  update: function () {
    if (this.containerScrollBar) {
      var length = this.buttons.length() + 1 + this.disabledCount
      this.buttons.setY(this.listY + (this.containerY - 100 - this.containerScrollBar.y) / ((2 * h2 - this.containerY + 100) / ((length) * this.buttons.distance)))
      this.buttons.update()
      this.containerScrollBar.input.checkBoundsRect()
      if (this.autoScroll) {
        this.containerScrollBar.y += this.scrollStep
        this.title.y = this.buttons.y - 150
      }

    }
  },

  backPressed: function () {
    this.game.state.start('Menu')
  },

  mouseWheel: function (event) {
    var min = 200, max = 900
    var c = this.containerScrollBar
    c.y -= this.game.input.mouse.wheelDelta * 20
    this.containerScrollBar.input.checkBoundsRect()
  },

  up: function () {
    this.buttons.selectUp()
    if (this.buttons.length() > 6 && !this.autoScroll) {
      if (this.buttons.selection < this.buttons.length() - 1) {
        if (this.containerScrollBar.y > this.containerY - 100) {
          this.buttons.setY(this.buttons.y + this.buttons.distance)
          var length = this.buttons.length() + 1 + this.disabledCount
          this.containerScrollBar.y = -(this.buttons.y - 300) * ((2 * h2 - this.containerY + 100) / ((length) * this.buttons.distance)) + this.containerY - 100
        }
      } else {
        this.buttons.setY(this.buttons.y + this.buttons.distance * this.buttons.length)
        this.containerScrollBar.y = h2 * 2 - this.containerScrollBar.height
      }
    }
  },

  down: function () {
    if (this.buttons.length() > 6 && !this.autoScroll) {
      if (this.buttons.selection < this.buttons.length() - 1  + this.disabledCount) {
        if (this.containerScrollBar.y < h2 * 2 - this.containerScrollBar.height) {
          this.buttons.setY(this.buttons.y - this.buttons.distance)
          var length = this.buttons.length() + 1 + this.disabledCount
          this.containerScrollBar.y = -(this.buttons.y - 300) * ((2 * h2 - this.containerY + 100) / ((length) * this.buttons.distance)) + this.containerY - 100
        }
      } else {
        this.buttons.setY(this.buttons.y - this.buttons.distance * this.buttons.length)
        this.containerScrollBar.y = this.containerY - 100
      }
    }
    this.buttons.selectDown()
  },

  selectPress: function () {
    this.buttons.selectPress()
  },

  selectRelease: function () {
    this.buttons.selectRelease()
  }

}
<|endoftext|>


<commit_before>class Preload extends Phaser.State {

	preload() {
		this.game.load.image('mountains-back', 'assets/mountains-back.png');
		this.game.load.image('hills-mid1', 'assets/hills-mid1.png');
		this.game.load.image('fence-mid2', 'assets/fence-mid2.png');
		this.game.load.image('ground-front', 'assets/ground-front.png');
	}

	create() {
		this.game.state.start("Main");
	}

}

export default Preload;
<commit_msg>refactor<commit_after>class Preload extends Phaser.State {

	preload() {
		this.game.load.image('mountains-back', 'assets/mountains-back.png');
		this.game.load.image('hills-mid1', 'assets/hills-mid1.png');
		this.game.load.image('fence-mid2', 'assets/fence-mid2.png');
		this.game.load.image('ground-front', 'assets/ground-front.png');
		this.game.load.image('rock', 'assets/rock.gif');

	}

	create() {
		this.game.state.start("Main");
	}

}

export default Preload;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View,
  Image,
  Dimensions,
  ScrollView,
  ListView,
  FlatList,
  RecyclerViewBackedScrollView,
  InteractionManager,
  Platform,
  PanResponder,
  Linking,
  Navigator,
  Animated,
} from 'react-native';
import { connect } from 'react-redux';
import HtmlView from 'react-native-htmlview';
import Icon from 'react-native-vector-icons/FontAwesome';
import * as Animatable from 'react-native-animatable';
import Share, { ShareSheet, Button } from 'react-native-share';
import { denormalize } from 'normalizr';
import Loader from '../components/Loader';
import PXTouchable from '../components/PXTouchable';
import FollowButtonContainer from './FollowButtonContainer';
import PXImage from '../components/PXImage';
import PXCacheImageTouchable from '../components/PXCacheImageTouchable';
import PXThumbnail from '../components/PXThumbnail';
import PXThumbnailTouchable from '../components/PXThumbnailTouchable';
import Tags from '../components/Tags';
// import DetailTabBar from '../components/DetailTabBar';
import RelatedIllusts from './RelatedIllusts';
import IllustComments from './IllustComments';
import Schemas from '../common/constants/schemas';

const windowWidth = Dimensions.get('window').width; //full width
const windowHeight = Dimensions.get('window').height; //full height

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    // backgroundColor: '#F5FCFF',
  },
  infoContainer: {
    margin: 10,
    // backgroundColor: 'transparent',
    // position: 'absolute',
    // overflow: 'hidden',
    // bottom: 200,
    // left: 0
  },
  // commentContainer: {
  //   margin: 10
  // },
  sectionHeader: {
    margin: 10,
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  sectionTitle: {
    fontWeight: 'bold',
  },
  viewMoreContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  chevronIcon: {
    marginLeft: 5,
  },
  header: {
    // paddingTop: 0,
    // top: 15,
    // right: 0,
    // left: 100,
    // position: 'absolute',
    ...Platform.select({
      ios: {
        top: 15
      },
    }),
    // right: 0,
    //left: 100,
    // position: 'absolute',
    alignItems: 'center'
  },
  headerThumnailNameContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    marginHorizontal: 16
  },
  thumnailNameContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  profileContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center'
  },
  nameContainer: {
    flexDirection: 'column',
    marginLeft: 10
  },
  imageContainer: {
    width: Dimensions.get('window').width,
    //height: Dimensions.get('window').height - 150
  },
  captionContainer: {
    marginVertical: 10
  },
  imagePageNumberContainer: {
    top: 10,
    right: 10,
    position: 'absolute',
    justifyContent: 'center',
    backgroundColor: 'grey',
    borderRadius: 10,
    paddingHorizontal: 8,
    // height: 32,
  },
  imagePageNumber: {
    color: '#fff',
    padding: 2
  },
});

class Detail extends Component {
  static navigationOptions = {
    header: ({ state, setParams, navigate, goBack }, defaultHeader) => {
      const { item, openBottomSheet, shareOptions } = state.params;
      return {
        ...defaultHeader,
        title: (
          <PXTouchable
            style={styles.headerThumnailNameContainer}
            onPress={() => navigate('UserDetail', { userId: item.user.id })}
          >
            <PXThumbnail uri={item.user.profile_image_urls.medium} />
            <View style={styles.nameContainer}>
              <Text>{item.user.name}</Text>
              <Text>{item.user.account}</Text>
            </View>
          </PXTouchable>
        ),
        right: (
          <View style={{flexDirection: "row"}}>
            <PXTouchable onPress={() => Share.open(shareOptions).catch((err) => { err && console.log(err); })}>
              <Icon
                name="share-alt"
                size={20}
                style={{paddingVertical: 10, paddingHorizontal: 10}}
              />
            </PXTouchable>
            <PXTouchable onPress={openBottomSheet}>
              <Icon
                name="ellipsis-v"
                style={{paddingVertical: 10, paddingHorizontal: 20}}
                size={20}
              />
            </PXTouchable>
          </View>
        )
      }
    }
  }

  constructor(props) {
    // const { item } = props;
    super(props);
    const { item } = props;
    const images = item.page_count > 1 ? item.meta_pages.map(page => {
      return page.image_urls.original;
    }) : [item.meta_single_page.original_image_url];
    this.state = {
      mounting: true,
      isInitState: true,
      images
    };
  }

  componentDidMount() {
    const { item, navigation, screenProps: { openBottomSheet } } = this.props;
    const { images } = this.state;
    const shareOptions = {
      message: `${item.title} | ${item.user.name} #pixivrn`, //todo
      url: `http://www.pixiv.net/member_illust.php?illust_id=${item.id}&mode=medium`,
    };
    navigation.setParams({
      item,
      openBottomSheet: () => openBottomSheet(images),
      shareOptions,
    });
    InteractionManager.runAfterInteractions(() => {
      console.log('done mouting')
      if (this.detailView) {
        this.setState({ mounting: false });
      }
    });
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }

  renderRow = ({ item, index }) => {
    return (
      <PXCacheImageTouchable
        key={item.image_urls.large}
        uri={item.image_urls.large}
        initWidth={windowWidth}
        initHeight={200}
        style={{
          backgroundColor: '#E9EBEE',
          borderBottomWidth: StyleSheet.hairlineWidth,
          borderBottomColor: 'red',
        }}
        imageStyle={{
          resizeMode: "contain",
        }}
        onPress={() => this.handleOnPressImage(index)}
      />
    )
  }

  renderFooter = () => {
    const { item, navigation, screenProps } = this.props;
    return (
      <View>
        <View style={styles.infoContainer}>
          <View style={styles.profileContainer}>
            <PXTouchable
              style={styles.thumnailNameContainer}
              onPress={() => this.handleOnPressAvatar(item.user.id)}
            >
              <PXThumbnail uri={item.user.profile_image_urls.medium} />
              <View style={styles.nameContainer}>
                <Text>{item.user.name}</Text>
                <Text>{item.user.account}</Text>
              </View>
            </PXTouchable>
            <FollowButtonContainer user={item.user} screenProps={screenProps} />
          </View>
          <View style={styles.captionContainer}>
            <HtmlView
              value={item.caption}
              onLinkPress={this.handleOnLinkPress}
            />
          </View>
          {
            <Tags tags={item.tags} onPressTag={this.handleOnPressTag} />
          }
        </View>
        <View>
          <View style={styles.sectionHeader}>
            <Text style={styles.sectionTitle}>Comments</Text>
            <PXTouchable onPress={this.handleOnPressViewMoreComments}>
              <View style={styles.viewMoreContainer}>
                <Text>View More</Text>
                <Icon name="chevron-right" style={styles.chevronIcon} />
              </View>
            </PXTouchable>
          </View>
          <IllustComments illustId={item.id} isFeatureInDetailPage={true} maxItems={6} navigation={navigation} />
        </View>
        {
          <View>
            <View style={styles.sectionHeader}>
              <Text style={styles.sectionTitle}>Related Works</Text>
              <PXTouchable onPress={this.handleOnPressViewMoreRelatedIllust}>
                <View style={styles.viewMoreContainer}>
                  <Text>View More</Text>
                  <Icon name="chevron-right" style={styles.chevronIcon} />
                </View>
              </PXTouchable>
            </View>
            <RelatedIllusts illustId={item.id} isFeatureInDetailPage={true} maxItems={6} navigation={navigation} />
          </View>
        }
      </View>
    )
  }

  handleOnPressTag = (tag) => {
    const { navigate } = this.props.navigation;
    navigate('SearchResult', { word: tag });
    // Actions.searchResult({ word: tag });
  }

  handleOnPressAvatar = (userId) => {
    const { navigate } = this.props.navigation;
    navigate('UserDetail', { userId });
  }

  handleOnViewableItemsChanged = ({ viewableItems, changed }) => {
    console.log('viewableItems ', viewableItems)
    console.log('changed ', changed)
    // not trigger on android
    // https://github.com/facebook/react-native/issues/5688
    // const { item } = this.props;
    const { item } = this.props;
    if (item.meta_pages && item.meta_pages.length && viewableItems && viewableItems.length) {
      //console.log('visible row ', visibleRowNumbers)
      //Actions.refresh({ title: `${visibleRowNumbers[0] + 1} / ${item.meta_pages.length}`});
      this.setState({
        imagePageNumber: `${viewableItems[0].index + 1} / ${item.meta_pages.length}`
      });
      if (viewableItems.length === 2) {
        // console.log('visible row ', visibleRowNumbers[0])
        // Actions.refresh({ title: `${visibleRowNumbers[0] + 1} of ${item.meta_pages.length}`});
          // visible row is visibleRowNumbers[0]
      }
      if (viewableItems.length === 3) {
          // visible row is visibleRowNumbers[1]
      }
    }
    else {
      const { imagePageNumber } = this.state;
      if (imagePageNumber) {
        this.setState({
          imagePageNumber: null
        });
      }
    }
  }

  handleOnScroll = () => {
    const { isInitState, isScrolling } = this.state;
    if (isInitState) {
      this.setState({
        isInitState: false
      });
    }
    this.setState({
      isScrolling: true
    });
    clearTimeout(this.timer);
    this.timer = setTimeout(() => this.setState({ isScrolling: false }), 500)
  }

  // handleOnEndReached = () => {
  //   console.log('end reached')
  //   // this.setState({
  //   //   endReached: true
  //   // });
  // }

  handleOnLinkPress = (url) => {
    console.log('clicked link: ', url)
    Linking.canOpenURL(url).then(supported => {
      if (!supported) {
        console.log('Can\'t handle url: ' + url);
      } else {
        return Linking.openURL(url);
      }
    }).catch(err => {
      console.error('Error on link press ', err)
    });
  }

  handleOnPressImage = (index) => {
    const { navigate } = this.props.navigation;
    const { images } = this.state;
    navigate('ImagesViewer', {
      images: images,
      viewerIndex: index
    });
  }

  handleOnPressViewMoreComments = () => {
    const { item, navigation: { navigate } } = this.props;
    navigate('IllustComments', {
      illustId: item.id,
      navigation: this.props.navigation
    });
  }

  handleOnPressViewMoreRelatedIllust = () => {
    const { item, navigation: { navigate } } = this.props;
    navigate('RelatedIllusts', {
      illustId: item.id,
      navigation: this.props.navigation
    });
  }

  render() {
    const { item, navigation } = this.props;
    const { isShowBottomSheet } = navigation.state.params;
    const { mounting, imagePageNumber, isScrolling, isInitState, images } = this.state;
    return (
      <View style={styles.container} ref={(ref) => this.detailView = ref }>
        {
          mounting ?
          <Loader />
          :
          (item.page_count > 1) ?
          <View>
            <View style={{flex: 1}}>
              <FlatList
                data={item.meta_pages}
                keyExtractor={(item, index) => item.image_urls.large}
                renderItem={this.renderRow}
                debug={false}
                disableVirtualization={false}
                FooterComponent={this.renderFooter}
                onScroll={this.handleOnScroll}
                onViewableItemsChanged={this.handleOnViewableItemsChanged}
              />
            </View>
            {
              (isInitState || isScrolling) && imagePageNumber &&
              <View style={styles.imagePageNumberContainer}>
                <Text style={styles.imagePageNumber}>{imagePageNumber}</Text>
              </View>
            }
          </View>
          :
          <ScrollView>
            <PXCacheImageTouchable
              uri={item.image_urls.large}
              initWidth={item.width > windowWidth ? windowWidth : item.width}
              initHeight={windowWidth * item.height / item.width}
              style={{
                backgroundColor: '#E9EBEE',
              }}
              imageStyle={{
                resizeMode: "contain",
              }}
              onPress={() => this.handleOnPressImage(0)}
            />
            {this.renderFooter()}
          </ScrollView>
        }
      </View>
    );
  }
}

export default connect((state, props) => {
  const { entities } = state;
  const item = denormalize(props.navigation.state.params.item.id, Schemas.ILLUST, entities);
  return { item }
})(Detail);<commit_msg>fixed missing detail info and paging indicator<commit_after>import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View,
  Image,
  Dimensions,
  ScrollView,
  ListView,
  FlatList,
  RecyclerViewBackedScrollView,
  InteractionManager,
  Platform,
  PanResponder,
  Linking,
  Navigator,
  Animated,
} from 'react-native';
import { connect } from 'react-redux';
import HtmlView from 'react-native-htmlview';
import Icon from 'react-native-vector-icons/FontAwesome';
import * as Animatable from 'react-native-animatable';
import Share, { ShareSheet, Button } from 'react-native-share';
import { denormalize } from 'normalizr';
import Loader from '../components/Loader';
import PXTouchable from '../components/PXTouchable';
import FollowButtonContainer from './FollowButtonContainer';
import PXImage from '../components/PXImage';
import PXCacheImageTouchable from '../components/PXCacheImageTouchable';
import PXThumbnail from '../components/PXThumbnail';
import PXThumbnailTouchable from '../components/PXThumbnailTouchable';
import Tags from '../components/Tags';
// import DetailTabBar from '../components/DetailTabBar';
import RelatedIllusts from './RelatedIllusts';
import IllustComments from './IllustComments';
import Schemas from '../common/constants/schemas';

const windowWidth = Dimensions.get('window').width; //full width
const windowHeight = Dimensions.get('window').height; //full height

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    // backgroundColor: '#F5FCFF',
  },
  infoContainer: {
    margin: 10,
    // backgroundColor: 'transparent',
    // position: 'absolute',
    // overflow: 'hidden',
    // bottom: 200,
    // left: 0
  },
  // commentContainer: {
  //   margin: 10
  // },
  sectionHeader: {
    margin: 10,
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  sectionTitle: {
    fontWeight: 'bold',
  },
  viewMoreContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  chevronIcon: {
    marginLeft: 5,
  },
  header: {
    // paddingTop: 0,
    // top: 15,
    // right: 0,
    // left: 100,
    // position: 'absolute',
    ...Platform.select({
      ios: {
        top: 15
      },
    }),
    // right: 0,
    //left: 100,
    // position: 'absolute',
    alignItems: 'center'
  },
  headerThumnailNameContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    marginHorizontal: 16
  },
  thumnailNameContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  profileContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center'
  },
  nameContainer: {
    flexDirection: 'column',
    marginLeft: 10
  },
  imageContainer: {
    width: Dimensions.get('window').width,
    //height: Dimensions.get('window').height - 150
  },
  captionContainer: {
    marginVertical: 10
  },
  imagePageNumberContainer: {
    top: 10,
    right: 10,
    position: 'absolute',
    justifyContent: 'center',
    backgroundColor: 'grey',
    borderRadius: 10,
    paddingHorizontal: 8,
    // height: 32,
  },
  imagePageNumber: {
    color: '#fff',
    padding: 2
  },
});

class Detail extends Component {
  static navigationOptions = {
    header: ({ state, setParams, navigate, goBack }, defaultHeader) => {
      const { item, openBottomSheet, shareOptions } = state.params;
      return {
        ...defaultHeader,
        title: (
          <PXTouchable
            style={styles.headerThumnailNameContainer}
            onPress={() => navigate('UserDetail', { userId: item.user.id })}
          >
            <PXThumbnail uri={item.user.profile_image_urls.medium} />
            <View style={styles.nameContainer}>
              <Text>{item.user.name}</Text>
              <Text>{item.user.account}</Text>
            </View>
          </PXTouchable>
        ),
        right: (
          <View style={{flexDirection: "row"}}>
            <PXTouchable onPress={() => Share.open(shareOptions).catch((err) => { err && console.log(err); })}>
              <Icon
                name="share-alt"
                size={20}
                style={{paddingVertical: 10, paddingHorizontal: 10}}
              />
            </PXTouchable>
            <PXTouchable onPress={openBottomSheet}>
              <Icon
                name="ellipsis-v"
                style={{paddingVertical: 10, paddingHorizontal: 20}}
                size={20}
              />
            </PXTouchable>
          </View>
        )
      }
    }
  }

  constructor(props) {
    // const { item } = props;
    super(props);
    const { item } = props;
    const images = item.page_count > 1 ? item.meta_pages.map(page => {
      return page.image_urls.original;
    }) : [item.meta_single_page.original_image_url];
    this.state = {
      mounting: true,
      isInitState: true,
      images
    };
  }

  componentDidMount() {
    const { item, navigation, screenProps: { openBottomSheet } } = this.props;
    const { images } = this.state;
    const shareOptions = {
      message: `${item.title} | ${item.user.name} #pixivrn`, //todo
      url: `http://www.pixiv.net/member_illust.php?illust_id=${item.id}&mode=medium`,
    };
    navigation.setParams({
      item,
      openBottomSheet: () => openBottomSheet(images),
      shareOptions,
    });
    InteractionManager.runAfterInteractions(() => {
      console.log('done mouting')
      if (this.detailView) {
        this.setState({ mounting: false });
      }
    });
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }

  renderRow = ({ item, index }) => {
    return (
      <PXCacheImageTouchable
        key={item.image_urls.large}
        uri={item.image_urls.large}
        initWidth={windowWidth}
        initHeight={200}
        style={{
          backgroundColor: '#E9EBEE',
          borderBottomWidth: StyleSheet.hairlineWidth,
          borderBottomColor: 'red',
        }}
        imageStyle={{
          resizeMode: "contain",
        }}
        onPress={() => this.handleOnPressImage(index)}
      />
    )
  }

  renderFooter = () => {
    const { item, navigation, screenProps } = this.props;
    return (
      <View>
        <View style={styles.infoContainer}>
          <View style={styles.profileContainer}>
            <PXTouchable
              style={styles.thumnailNameContainer}
              onPress={() => this.handleOnPressAvatar(item.user.id)}
            >
              <PXThumbnail uri={item.user.profile_image_urls.medium} />
              <View style={styles.nameContainer}>
                <Text>{item.user.name}</Text>
                <Text>{item.user.account}</Text>
              </View>
            </PXTouchable>
            <FollowButtonContainer user={item.user} screenProps={screenProps} />
          </View>
          <View style={styles.captionContainer}>
            <HtmlView
              value={item.caption}
              onLinkPress={this.handleOnLinkPress}
            />
          </View>
          {
            <Tags tags={item.tags} onPressTag={this.handleOnPressTag} />
          }
        </View>
        <View>
          <View style={styles.sectionHeader}>
            <Text style={styles.sectionTitle}>Comments</Text>
            <PXTouchable onPress={this.handleOnPressViewMoreComments}>
              <View style={styles.viewMoreContainer}>
                <Text>View More</Text>
                <Icon name="chevron-right" style={styles.chevronIcon} />
              </View>
            </PXTouchable>
          </View>
          <IllustComments illustId={item.id} isFeatureInDetailPage={true} maxItems={6} navigation={navigation} />
        </View>
        {
          <View>
            <View style={styles.sectionHeader}>
              <Text style={styles.sectionTitle}>Related Works</Text>
              <PXTouchable onPress={this.handleOnPressViewMoreRelatedIllust}>
                <View style={styles.viewMoreContainer}>
                  <Text>View More</Text>
                  <Icon name="chevron-right" style={styles.chevronIcon} />
                </View>
              </PXTouchable>
            </View>
            <RelatedIllusts illustId={item.id} isFeatureInDetailPage={true} maxItems={6} navigation={navigation} />
          </View>
        }
      </View>
    )
  }

  handleOnPressTag = (tag) => {
    const { navigate } = this.props.navigation;
    navigate('SearchResult', { word: tag });
    // Actions.searchResult({ word: tag });
  }

  handleOnPressAvatar = (userId) => {
    const { navigate } = this.props.navigation;
    navigate('UserDetail', { userId });
  }

  handleOnViewableItemsChanged = ({ viewableItems, changed }) => {
    // console.log('viewableItems ', viewableItems)
    // console.log('changed ', changed)
    // not trigger on android
    // https://github.com/facebook/react-native/issues/5688
    // const { item } = this.props;
    const { item } = this.props;
    if (item.meta_pages && item.meta_pages.length && viewableItems && viewableItems.length) {
      //console.log('visible row ', visibleRowNumbers)
      //Actions.refresh({ title: `${visibleRowNumbers[0] + 1} / ${item.meta_pages.length}`});
      this.setState({
        imagePageNumber: `${viewableItems[0].index + 1} / ${item.meta_pages.length}`
      });
      if (viewableItems.length === 2) {
        // console.log('visible row ', visibleRowNumbers[0])
        // Actions.refresh({ title: `${visibleRowNumbers[0] + 1} of ${item.meta_pages.length}`});
          // visible row is visibleRowNumbers[0]
      }
      if (viewableItems.length === 3) {
          // visible row is visibleRowNumbers[1]
      }
    }
    else {
      const { imagePageNumber } = this.state;
      if (imagePageNumber) {
        this.setState({
          imagePageNumber: null
        });
      }
    }
  }

  handleOnScroll = () => {
    const { isInitState, isScrolling } = this.state;
    if (isInitState) {
      this.setState({
        isInitState: false
      });
    }
    if (!isScrolling) {
      this.setState({
        isScrolling: true
      });
    }
    clearTimeout(this.timer);
    this.timer = setTimeout(() => {
      this.setState({ isScrolling: false });
    }, 2000);
  }

  // handleOnEndReached = () => {
  //   console.log('end reached')
  //   // this.setState({
  //   //   endReached: true
  //   // });
  // }

  handleOnLinkPress = (url) => {
    console.log('clicked link: ', url)
    Linking.canOpenURL(url).then(supported => {
      if (!supported) {
        console.log('Can\'t handle url: ' + url);
      } else {
        return Linking.openURL(url);
      }
    }).catch(err => {
      console.error('Error on link press ', err)
    });
  }

  handleOnPressImage = (index) => {
    const { navigate } = this.props.navigation;
    const { images } = this.state;
    navigate('ImagesViewer', {
      images: images,
      viewerIndex: index
    });
  }

  handleOnPressViewMoreComments = () => {
    const { item, navigation: { navigate } } = this.props;
    navigate('IllustComments', {
      illustId: item.id,
      navigation: this.props.navigation
    });
  }

  handleOnPressViewMoreRelatedIllust = () => {
    const { item, navigation: { navigate } } = this.props;
    navigate('RelatedIllusts', {
      illustId: item.id,
      navigation: this.props.navigation
    });
  }

  render() {
    const { item, navigation } = this.props;
    const { isShowBottomSheet } = navigation.state.params;
    const { mounting, imagePageNumber, isScrolling, isInitState, images } = this.state;
    return (
      <View style={styles.container} ref={(ref) => this.detailView = ref }>
        {
          mounting ?
          <Loader />
          :
          (item.page_count > 1) ?
          <View>
            <View style={{flex: 1}}>
              <FlatList
                data={item.meta_pages}
                keyExtractor={(item, index) => item.image_urls.large}
                renderItem={this.renderRow}
                debug={false}
                disableVirtualization={false}
                ListFooterComponent={this.renderFooter}
                onScroll={this.handleOnScroll}
                onViewableItemsChanged={this.handleOnViewableItemsChanged}
              />
            </View>
            {
              (isInitState || isScrolling) && imagePageNumber &&
              <View style={styles.imagePageNumberContainer}>
                <Text style={styles.imagePageNumber}>{imagePageNumber}</Text>
              </View>
            }
          </View>
          :
          <ScrollView>
            <PXCacheImageTouchable
              uri={item.image_urls.large}
              initWidth={item.width > windowWidth ? windowWidth : item.width}
              initHeight={windowWidth * item.height / item.width}
              style={{
                backgroundColor: '#E9EBEE',
              }}
              imageStyle={{
                resizeMode: "contain",
              }}
              onPress={() => this.handleOnPressImage(0)}
            />
            {this.renderFooter()}
          </ScrollView>
        }
      </View>
    );
  }
}

export default connect((state, props) => {
  const { entities } = state;
  const item = denormalize(props.navigation.state.params.item.id, Schemas.ILLUST, entities);
  return { item }
})(Detail);<|endoftext|>


<commit_before>'use strict';

// TODO: add easings to all of current methods.

// TODO: Pass group options to every sounds considering every group of sound.

// Play instances (free/all/one by id/src) with @options.
// Usages:
// .play() - play all free sounds with group options.
// .play(id) - play only one instance (if exists) from group by id or src with group options.
// .play(id, options) - play only one instance (if exists) from group by id or src with custom @options extended by group options.
// .play(options, true) - play all (of group) sounds with @options extened by gropu options; @options can contain .id or .src for play only defined sound.
// .play(options) - play all free sounds with @options extended by group options; @options can contain .id or .src for play only defined sound; @options can contain .all property for select playing sounds from all group sounds (not only free sounds).
SoundJSDirectorGroupProto.play = function playGroup () {

  var arg0 = arguments[0], id, options, all = false;

  switch (arguments.length) {

    default:
    case 2: {
      var arg1 = arguments[1];
      if ('object' === typeof arg1) {
        id = arg0;
        options = arg1;
        all = false;
      }
      else if ('object' !== typeof arg0) {
        throw 'SoundJSDirector.Group.play() given bad arguments';
      }
      else {
        options = arg0;
        all = arg1;
      }
    } break;

    case 1: {
      switch (typeof arg0) {
        case 'string': id = arg0; break;
        case 'object': options = arg0; id = options.id || options.src; break;
        case 'boolean': all = arg0; break;
        default: throw 'SoundJSDirector.Group.play() given bad arguments';
      }
    } break;

    case 0: break;
  }

  var _options = SoundJSDirector.extend(
    SoundJSDirector.parseOptions(options),
    this.options
  );

  if (id) {
    this.playSound(id, _options);
  }

  else if (!_options.collapsed) {
    this._playSounds(_options, all);
  }

  else {
    this._playCollapsed(_options, all);
  }

  return this;

};

// Play sound by @id (id/src of sound instance) with @options.
SoundJSDirectorGroupProto.playSound = function playGroupSound (id, options) {

  var sounds = this.sounds,
      sound = sounds[id];

  if (!sound && isNaN(parseInt(id))) {
    this.eachSound(function (_sound) {
      if (id === _sound.id || id === _sound.src) {
        sound = _sound;
        return SoundJSDirector.OUT_EACH;
      }
    });
  }

  if (sound) {
    this._playSoundWithOptions(sound, options);
  }

  return this;
};

// Play @sound instance with @options.
SoundJSDirectorGroupProto._playSoundWithOptions =
function playSoundWithOptions (sound, options) {
  var _options = {};
  SoundJSDirector.each(options, function (value, name) {
    switch (name) {
      default: _options[name] = value; break;
      case 'pan': _options[name] = this.getSoundPan(sound, value); break;
      case 'volume': _options[name] = this.getSoundVolume(sound, value); break;
    }
  }, this);
  sound.play(_options);
  return this;
};

// Play free/all sounds in group.
SoundJSDirectorGroupProto._playSounds = function playSounds (options, all) {
  SoundJSDirector.each(
    all ? this.sounds : this._wait,
    function (sound) {
      this._playSoundWithOptions(sound, options);
    },
    this
  );
  return this;
};

// Play first of free/all sounds.
SoundJSDirectorGroupProto._playCollapsed = function playCollapsed (options, all) {
  var sound = SoundJSDirector.randomItem(all ? this.sounds : this._wait);
  if (sound) {
    this._playSoundWithOptions(sound, options);
  }
  return this;
};

// Pause all playing sounds.
SoundJSDirectorGroupProto.pause = function pauseGroup () {
  this.eachPlayingSound(function (sound) {
    sound.pause();
  });
  return this;
};

// Resume all paused group sounds.
SoundJSDirectorGroupProto.resume = function resumeGroup () {
  this.eachPlayingSound(function (sound) {
    sound.resume();
  });
  return this;
};

// Stop all playing group sounds.
SoundJSDirectorGroupProto.stop = function stopGroup () {
  this.eachPlayingSound(function (sound) {
    sound.stop();
  });
  return this;
};

<commit_msg>Removed useless todo from src/controls/playback.js<commit_after>'use strict';

// Play instances (free/all/one by id/src) with @options.
// Usages:
// .play() - play all free sounds with group options.
// .play(id) - play only one instance (if exists) from group by id or src with group options.
// .play(id, options) - play only one instance (if exists) from group by id or src with custom @options extended by group options.
// .play(options, true) - play all (of group) sounds with @options extened by gropu options; @options can contain .id or .src for play only defined sound.
// .play(options) - play all free sounds with @options extended by group options; @options can contain .id or .src for play only defined sound; @options can contain .all property for select playing sounds from all group sounds (not only free sounds).
SoundJSDirectorGroupProto.play = function playGroup () {

  var arg0 = arguments[0], id, options, all = false;

  switch (arguments.length) {

    default:
    case 2: {
      var arg1 = arguments[1];
      if ('object' === typeof arg1) {
        id = arg0;
        options = arg1;
        all = false;
      }
      else if ('object' !== typeof arg0) {
        throw 'SoundJSDirector.Group.play() given bad arguments';
      }
      else {
        options = arg0;
        all = arg1;
      }
    } break;

    case 1: {
      switch (typeof arg0) {
        case 'string': id = arg0; break;
        case 'object': options = arg0; id = options.id || options.src; break;
        case 'boolean': all = arg0; break;
        default: throw 'SoundJSDirector.Group.play() given bad arguments';
      }
    } break;

    case 0: break;
  }

  var _options = SoundJSDirector.extend(
    SoundJSDirector.parseOptions(options),
    this.options
  );

  if (id) {
    this.playSound(id, _options);
  }

  else if (!_options.collapsed) {
    this._playSounds(_options, all);
  }

  else {
    this._playCollapsed(_options, all);
  }

  return this;

};

// Play sound by @id (id/src of sound instance) with @options.
SoundJSDirectorGroupProto.playSound = function playGroupSound (id, options) {

  var sounds = this.sounds,
      sound = sounds[id];

  if (!sound && isNaN(parseInt(id))) {
    this.eachSound(function (_sound) {
      if (id === _sound.id || id === _sound.src) {
        sound = _sound;
        return SoundJSDirector.OUT_EACH;
      }
    });
  }

  if (sound) {
    this._playSoundWithOptions(sound, options);
  }

  return this;
};

// Play @sound instance with @options.
SoundJSDirectorGroupProto._playSoundWithOptions =
function playSoundWithOptions (sound, options) {
  var _options = {};
  SoundJSDirector.each(options, function (value, name) {
    switch (name) {
      default: _options[name] = value; break;
      case 'pan': _options[name] = this.getSoundPan(sound, value); break;
      case 'volume': _options[name] = this.getSoundVolume(sound, value); break;
    }
  }, this);
  sound.play(_options);
  return this;
};

// Play free/all sounds in group.
SoundJSDirectorGroupProto._playSounds = function playSounds (options, all) {
  SoundJSDirector.each(
    all ? this.sounds : this._wait,
    function (sound) {
      this._playSoundWithOptions(sound, options);
    },
    this
  );
  return this;
};

// Play first of free/all sounds.
SoundJSDirectorGroupProto._playCollapsed = function playCollapsed (options, all) {
  var sound = SoundJSDirector.randomItem(all ? this.sounds : this._wait);
  if (sound) {
    this._playSoundWithOptions(sound, options);
  }
  return this;
};

// Pause all playing sounds.
SoundJSDirectorGroupProto.pause = function pauseGroup () {
  this.eachPlayingSound(function (sound) {
    sound.pause();
  });
  return this;
};

// Resume all paused group sounds.
SoundJSDirectorGroupProto.resume = function resumeGroup () {
  this.eachPlayingSound(function (sound) {
    sound.resume();
  });
  return this;
};

// Stop all playing group sounds.
SoundJSDirectorGroupProto.stop = function stopGroup () {
  this.eachPlayingSound(function (sound) {
    sound.stop();
  });
  return this;
};

<|endoftext|>