<commit_before>var keystone = require('keystone');
var async = require('async');
var striptags = require('striptags');
var Entities = require('html-entities').AllHtmlEntities;
var iconv  = require('iconv-lite');
var request  = require('request');

var Bill = keystone.list('Bill');

module.exports = function(legislature, force, done)
{
    console.log('importing bills for legislature', legislature);

    var numErrors = 0;
    var numSkipped = 0;

    fetchBillNumbers(
        legislature,
        function(numbers)
        {
            console.log(numbers.length + ' bills to import');

            var numImportedBills = 0;

            var ops = numbers.map(function(number)
            {
                return function(callback)
                {
                    console.log(
                        '(' + ++numImportedBills + '/' + numbers.length
                        + ') importing bill ' + number
                    );
                    Bill.model.findOne({legislature : legislature, number : number})
                        .exec(function(err, bill)
                        {
                            if (err)
                                return callback(err);

                            if (!bill || force == 'true')
                            {
                                fetchBillMarkdown(
                                    legislature,
                                    number,
                                    function(text)
                                    {
                                        getRegistrationDate(
                                            legislature,
                                            number,
                                            text,
                                            function(registrationDate)
                                            {
                                                if (!bill)
                                                {
                                                    bill = Bill.model({
                                                        legislature: legislature,
                                                        number: number,
                                                        text: text,
                                                        registrationDate: registrationDate,
                                                        importDate: Date.now()
                                                    });
                                                }
                                                else
                                                {
                                                    bill.text = text;
                                                    bill.registrationDate = registrationDate;
                                                    bill.importDate = Date.now();
                                                }

                                                bill.save(function(err)
                                                {
                                                    console.log('\tok: imported');
                                                    return callback(err);
                                                });

                                            },
                                            function(err)
                                            {
                                                numErrors++;
                                                console.log('\terror: could not parse recording date');

                                                return callback();
                                            }
                                        );
                                    },
                                    function(body)
                                    {
                                        if (body.indexOf('pas encore édité') >= 0)
                                        {
                                            numSkipped++;
                                            console.log('\tskip: not yet available');
                                        }
                                        else
                                        {
                                            numErrors++;
                                            console.log('\terror: could not parse text');
                                        }

                                        return callback();
                                    }
                                );
                            }
                            else
                            {
                                numSkipped++;
                                console.log('\tskip: already imported');

                                return callback();
                            }
                        });
                };
            });

            async.waterfall(ops, function(error)
            {
                if (error)
                    console.log(error);

                console.log(
                    'total: ' + numbers.length
                    + ', success: ' + (numbers.length - numErrors - numSkipped)
                    + ', skipped: ' + numSkipped
                    + ', error: ' + numErrors
                );

                done();
            });
        }
    );
}

function fetchBillNumbers(legislature, callback)
{
    request(
        {
            uri: "http://www.assemblee-nationale.fr/"
                + legislature
                + "/documents/index-projets.asp"
        },
        function(error, response, body)
        {
            var projectAnchors = null;
            var re = new RegExp(
                '<a href="/' + legislature + '/projets/pl(\\\d+).asp"',
                'g'
            );

            var numbers = [];
            while ((projectAnchors = re.exec(body)) !== null)
                numbers.push(projectAnchors[1]);

            callback(numbers);
        }
    );
}

function fetchBillMarkdown(legislature, id, successCallback, errorCallback)
{
    request(
        {
            uri: "http://www.assemblee-nationale.fr/"
                + legislature
                + "/projets/pl"
                + id
                + ".asp",
            encoding: null
        },
        function(error, response, body)
        {
            var text = iconv.decode(new Buffer(body), "ISO-8859-1");
            var begin = text.indexOf(
                '',
                text.indexOf('
')
            );
            var end = text.indexOf('
');
            if (begin < 0 || end < 0 || end < begin)
                return errorCallback && errorCallback(text);

            text = text.substring(begin, end);

            // strip all HTML tags
            text = striptags(text);

            // remove horizontal rules made of undercores
            text = text.replace(/^\*\*_+\*\*$/gm, '');

            // replace em dash with an actual dash
            text = text.replace(/^–/gm, '-');

            // decode HTML entities
            text = new Entities().decode(text);
            // remove Windows nl
            text = text.replace(/\r/gm, '');
            // trim whitespaces
            text = text.trim();

            successCallback(text);
        });
}

function guessRecordingYear(legislature, number, day, month, successCallback, errorCallback)
{
    console.log('\twarning: missing registration year, guessing based on the next bill')
    Bill.model.findOne({legislature:legislature, number: {$gt:number}})
        .sort('number')
        .select('-text')
        .exec(function(err, nextBill)
        {
            if (err)
                return errorCallback(err);

            if (month > nextBill.registrationDate.getMonth() + 1)
                return successCallback(new Date(
                    (nextBill.registrationDate.getFullYear() - 1)
                    + '-' + month
                    + '-' + day
                ));
            else
                return successCallback(new Date(
                    nextBill.registrationDate.getFullYear()
                    + '-' + month
                    + '-' + day
                ));
        });
}

function getRegistrationDate(legislature, number, text, successCallback, errorCallback)
{
    var months = [
        'janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',
        'août', 'septembre', 'octobre', 'novembre', 'décembre'
    ];
    var re = /^\s*enregistrée? à la présidence\sde l['’]assemblée nationale\s+le (\d+|1er)\s+(.+)\s+(\d{4})\s*\.?$/im;
    var match = re.exec(text);

    if (!match)
    {
        // Sometimes, the year is not specified. So we will have to fetch the day & month
        // and guess the year based on those values + the values of the "next" bill.
        var re = /^\s*enregistrée? à la présidence\sde l['’]assemblée nationale\s+le (\d+|1er)\s+(.+)\.$/im;
        var match = re.exec(text);

        if (!match)
            return errorCallback(null);

        var day = match[1] == '1er' ? '1' : match[1];
        var month = months.indexOf(match[2]) + 1;

        return guessRecordingYear(legislature, number, day, month, successCallback, errorCallback);
    }

    var day = match[1] == '1er' ? '1' : match[1];
    var month = months.indexOf(match[2]) + 1;
    var year = match[3];

    return successCallback(new Date(year + '-' + month + '-' + day));
}
<commit_msg>FIx the import-bill.js script to match with the latest changes on the official website of the Assemblée Nationale.<commit_after>var keystone = require('keystone');
var async = require('async');
var striptags = require('striptags');
var Entities = require('html-entities').AllHtmlEntities;
var iconv  = require('iconv-lite');
var request  = require('request');

var Bill = keystone.list('Bill');

module.exports = function(legislature, force, bill, done)
{
    console.log('fetching bills for legislature', legislature);

    var numErrors = 0;
    var numSkipped = 0;
    var importSpecificBill = !!bill;
    var billNumberToImport = importSpecificBill ? parseInt(bill) : 0;

    fetchBillURIs(
        legislature,
        function(uris)
        {
            console.log(uris.length + ' bills fetched');

            var numImportedBills = 0;

            var ops = uris.map(function(uri)
            {
                return function(callback)
                {
                    var billNumber = getBillNumber(uri);

                    console.log(
                        Math.ceil(numImportedBills / uris.length * 100) + '%'
                        + '\t(' + ++numImportedBills + '/' + uris.length
                        + ') importing bill ' + billNumber
                    );
                    Bill.model.findOne({legislature : legislature, number : billNumber})
                        .exec(function(err, bill)
                        {
                            if (err)
                                return callback(err);

                            if (!bill || force == 'true' || (importSpecificBill && billNumber == billNumberToImport))
                            {
                                fetchBillMarkdown(
                                    legislature,
                                    uri,
                                    function(text)
                                    {
                                        getRegistrationDate(
                                            legislature,
                                            billNumber,
                                            text,
                                            function(registrationDate)
                                            {
                                                if (!bill)
                                                {
                                                    bill = Bill.model({
                                                        legislature: legislature,
                                                        number: billNumber,
                                                        text: text,
                                                        registrationDate: registrationDate,
                                                        importDate: Date.now()
                                                    });
                                                }
                                                else
                                                {
                                                    bill.text = text;
                                                    bill.registrationDate = registrationDate;
                                                    bill.importDate = Date.now();
                                                }

                                                bill.save(function(err)
                                                {
                                                    console.log('\tok: imported');
                                                    return callback(err);
                                                });

                                            },
                                            function(err)
                                            {
                                                numErrors++;
                                                console.log('\terror: could not parse recording date');

                                                return callback();
                                            }
                                        );
                                    },
                                    function(body)
                                    {
                                        if (body.indexOf('pas encore édité') >= 0)
                                        {
                                            numSkipped++;
                                            console.log('\tskip: not yet available');
                                        }
                                        else
                                        {
                                            numErrors++;
                                            console.log('\terror: could not parse text');
                                        }

                                        return callback();
                                    }
                                );
                            }
                            else
                            {
                                numSkipped++;
                                console.log('\tskip: already imported');

                                return callback();
                            }
                        });
                };
            });

            async.waterfall(ops, function(error)
            {
                if (error)
                    console.log(error);

                console.log(
                    'total: ' + uris.length
                    + ', success: ' + (uris.length - numErrors - numSkipped)
                    + ', skipped: ' + numSkipped
                    + ', error: ' + numErrors
                );

                done();
            });
        }
    );
}

function getBillNumber(uri)
{
    var re = /(pion|pl)(\d+)/g;
    var match = re.exec(uri);

    return parseInt(match[2]);
}

function fetchBillURIs(legislature, callback)
{
    request(
        {
            uri: "http://www2.assemblee-nationale.fr/documents/liste/(type)/depots/(archives)/index-depots/(limit)/999999999999/(legis)/"
                + legislature
        },
        function(error, response, body)
        {
            var projectAnchors = null;
            var re = new RegExp(
                '<a href="(/documents/notice/' + legislature + '/(propositions|projets)/(pion|pl)\\\d+/\\\(index\\\)/depots)"',
                'g'
            );

            var uris = [];
            while ((projectAnchors = re.exec(body)) !== null)
                uris.push('http://www2.assemblee-nationale.fr' + projectAnchors[1]);

            callback(uris);
        }
    );
}

function fetchBillMarkdown(legislature, uri, successCallback, errorCallback)
{
    request(
        {
            uri: uri
        },
        function(error, response, body)
        {
            var text = body;
            var begin = text.indexOf(
                '',
                text.indexOf('<div class="interieur-contenu-principal"')
            );
            var end = text.indexOf('
');

            if (begin < 0 || end < 0 || end < begin)
                return errorCallback && errorCallback(text);

            text = text.substring(begin, end);

            // strip all HTML tags
            text = striptags(text);

            // remove horizontal rules made of undercores
            text = text.replace(/^\*\*_+\*\*$/gm, '');

            // replace em dash with an actual dash
            text = text.replace(/^–/gm, '-');

            // decode HTML entities
            text = new Entities().decode(text);
            // remove Windows nl
            text = text.replace(/\r/gm, '');
            // trim whitespaces
            text = text.trim();

            successCallback(text);
        });
}

function guessRecordingYear(legislature, number, day, month, successCallback, errorCallback)
{
    console.log('\twarning: missing registration year, guessing based on the next bill')
    Bill.model.findOne({legislature:legislature, number: {$gt:number}})
        .sort('number')
        .select('-text')
        .exec(function(err, nextBill)
        {
            if (err)
                return errorCallback(err);

            if (month > nextBill.registrationDate.getMonth() + 1)
                return successCallback(new Date(
                    (nextBill.registrationDate.getFullYear() - 1)
                    + '-' + month
                    + '-' + day
                ));
            else
                return successCallback(new Date(
                    nextBill.registrationDate.getFullYear()
                    + '-' + month
                    + '-' + day
                ));
        });
}

function getRegistrationDate(legislature, number, text, successCallback, errorCallback)
{
    var months = [
        'janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',
        'août', 'septembre', 'octobre', 'novembre', 'décembre'
    ];
    var re = /^\s*enregistrée? à la présidence\sde l['’]assemblée nationale\s+le (\d+|1er)\s+(.+)\s+(\d{4})\s*\.?$/im;
    var match = re.exec(text);

    if (!match)
    {
        // Sometimes, the year is not specified. So we will have to fetch the day & month
        // and guess the year based on those values + the values of the "next" bill.
        var re = /^\s*enregistrée? à la présidence\sde l['’]assemblée nationale\s+le (\d+|1er)\s+(.+)\.$/im;
        var match = re.exec(text);

        if (!match)
            return errorCallback(null);

        var day = match[1] == '1er' ? '1' : match[1];
        var month = months.indexOf(match[2]) + 1;

        return guessRecordingYear(legislature, number, day, month, successCallback, errorCallback);
    }

    var day = match[1] == '1er' ? '1' : match[1];
    var month = months.indexOf(match[2]) + 1;
    var year = match[3];

    return successCallback(new Date(year + '-' + month + '-' + day));
}
<|endoftext|>


<commit_before>import Promise from 'bluebird';
import React from 'react';
import googleApiLoader from '../Lib/GoogleApiLoader';
import _ from 'lodash';
import SuggestionBox from './SuggestionBox';
import PreviewSound from './PreviewSound';
import AuthSlack from './AuthSlack';
import PlaySlackSound from './PlaySlackSound';
import Spinner from 'react-spinkit';
import {HotKeys} from 'react-hotkeys';
import swal from 'exports?swal!sweetalert2/src/sweetalert2.js';

var walkFilePages = function(request) {
  return new Promise((resolve) => {
    retrieveRecursiveFiles(request, [], resolve)
  });
};

var retrieveRecursiveFiles = function(request, arr, done) {
  return request.execute((resp) => {
    arr = arr.concat(resp.items);
    var nextPageToken = resp.nextPageToken;
    if (nextPageToken) {
      request = gapi.client.request({
        'path': '/drive/v2/files',
        'method': 'GET',
        'pageToken': nextPageToken,
        'params': {
          'q': 'trashed = false'
        }
      });
      retrieveRecursiveFiles(request, arr);
    } else {
      done(arr);
    }
  });
}

var retrieveAllFiles = function(folderId)  {
  var initialRequest = gapi.client.request({
    'path': '/drive/v2/files',
    'method': 'GET',
    'params': {
      'q': "trashed = false and '" + folderId + "' in parents"
    }
  });
  return walkFilePages(initialRequest);
}

function getSubFolders(folderId) {
  var initialRequest = gapi.client.request({
    'path': '/drive/v2/files',
    'method': 'GET',
    'params': {
      'q': "trashed = false and '" + folderId + "' in parents and mimeType='application/vnd.google-apps.folder'"
    }
  });
  return walkFilePages(initialRequest);
}

module.exports = React.createClass({

    maxCacheAge: 5 * 1000 * 60, // 5 min
    maxSlackAge: 24 * 60 * 60 * 1000, // 1 day

    getInitialState: function() {
      return {
        isAuthorized: false,
        selectedSound: {},
        folders: []
      };
    },

    componentDidMount: function () {
      var _this = this
      googleApiLoader.apiReady.then(() => {
        _this.setState({isAuthorized: true})
        return _this.fetchFiles()
      });

      _this.checkForSavedSlackToken()
    },

    queueFileRefresh() {
      setTimeout(this.fetchFiles, this.maxCacheAge)
    },

    fetchFiles() {
      var maxCacheMinutes = this.maxCacheAge / (1000 * 60)
      var oldestAllowedCache = (new Date()).setMinutes((new Date()).getMinutes() - maxCacheMinutes)
      var folderJson = localStorage.getItem('driveFileCache')
      var cacheAge = localStorage.getItem('driveFileCacheTime')
      var cacheIsStale = new Date(cacheAge) < oldestAllowedCache

      if (!cacheAge || !folderJson) {
        console.log('No valid cache, fetching new file listing')
        return this.fetchFilesFromGoogle()
      }

      console.log('Files loaded from localstorage!')
      this.setState({folders: JSON.parse(folderJson)})

      // Cache is old.. but we don't have any files yet
      // start google fetch, but use cache for now
      if (cacheIsStale && !this.state.folders.length) {
        console.log('Queuing Google fetch because the cache is stale...')
        this.fetchFilesFromGoogle()
      } else {
        this.queueFileRefresh()
      }
    },

    fetchFilesFromGoogle() {
      var _this = this
      var deletableFields = [
        'appDataContents',
        'copyable',
        'editable',
        'etag',
        'explicitlyTrashed',
        'iconLink',
        'kind',
        'labels',
        'lastModifyingUser',
        'lastViewedByMeDate',
        'markedViewedByMeDate',
        'mimeType',
        'modifiedByMeDate',
        'modifiedDate',
        'ownerNames',
        'owner',
        'owners',
        'version',
        'parents',
        'quotaBytesUsed',
        'shared',
        'spaces',
        'userPermission',
        'writersCanShare',
        'alternateLink',
        'fileExtension',
        'headRevisionId',
        'md5Checksum',
        'originalFilename',
        'selfLink',
        'webContentLink'
      ]

      function cleanFileList(list) {
        return list.map(function(file) {
          deletableFields.forEach(function(deletableField) {
            if (file[deletableField]) {
              delete file[deletableField]
            }
          })
          return file
        })
      }

      if (_this.state.loadingFromGoogle) {
        console.log('Alreading loading from Google... ignoring request')
        return
      }
      _this.setState({loadingFromGoogle: true});
      return gapi.client.drive.files.list({
        'q': "mimeType='application/vnd.google-apps.folder' and name='sounds'",
        'pageSize': 10,
        'fields': "nextPageToken, files(id, name)"
      }).execute((resp) => {
        var folders = cleanFileList(resp.files)

        var soundFolderId = folders[0].id

        var getSoundFolders = getSubFolders(soundFolderId).then((folders) => {
          return Promise.reduce(folders, function(soundFolders, folder) {
            return Promise.delay(100).then(() => {
              return retrieveAllFiles(folder.id).then(function (files) {

                folder.files = cleanFileList(files)
                soundFolders.push(folder)
                return soundFolders
              })
            });
          }, []);
        });

        getSoundFolders.then((soundFolders) => {
          console.log('Saving files to localstorage...')
          localStorage.setItem('driveFileCache', JSON.stringify(soundFolders))
          localStorage.setItem('driveFileCacheTime', new Date())
          _this.queueFileRefresh()
          _this.setState({
            folders: soundFolders,
            loadingFromGoogle: false
          });
        })
      });
    },

    checkForSavedSlackToken() {
      var maxCacheMinutes = this.maxSlackAge / (1000 * 60)
      var token = localStorage.getItem('slackAccessToken')
      var tokenSavedString = localStorage.getItem('slackAccessTokenTime')
      var oldestAllowedCache = (new Date()).setMinutes((new Date()).getMinutes() - maxCacheMinutes)
      var cacheIsStale = new Date(tokenSavedString) < oldestAllowedCache

      if (!token || !tokenSavedString) {
        return
      }

      if (cacheIsStale) {
        console.log('Local Slack token is older than a day, ignoring...')
        localStorage.removeItem('slackAccessToken')
        localStorage.removeItem('slackAccessTokenTime')
        return
      }

      console.log('Slack token loaded from localstorage!')
      this.setState({slackAccessToken: token})
    },

    signIn() {
      googleApiLoader.login();
    },

    onSuggestionSelected(e, data) {
      this.setState({ selectedSound: data.suggestion });
    },

    onAuthenticatedWithSlack(resp) {
      console.log('Saving Slack token to localstorage...');
      localStorage.setItem('slackAccessToken', resp.access_token)
      localStorage.setItem('slackAccessTokenTime', new Date())
      this.setState({slackAccessToken: resp.access_token})
    },

    playSound() {
      setTimeout(function() {
        let event = new window.CustomEvent('playSound');
        window.dispatchEvent(event);
      });
    },

    sendToSlack() {
      setTimeout(function() {
        let event = new window.CustomEvent('sendToSlack');
        window.dispatchEvent(event);
      });
    },

    stopSound() {
      setTimeout(function() {
        let event = new window.CustomEvent('stopSound');
        window.dispatchEvent(event);
      });
    },

    clickHelp() {
      swal({   title: "Sound Client Help!",   html: `

Login with Slack to play sounds

Search:

":" to get all sound files

"<folder>:" to get all sound files in a folder

Shortcuts:

command+enter to preview a sound

esc to stop a preview

shift+enter to play a sound on slack

      `});
    },

    render: function () {

      const handlers = {
        'playSound': this.playSound,
        'sendToSlack': this.sendToSlack,
        'stopSound': this.stopSound
      };

      var spinner;
      if (this.state.isAuthorized && !this.state.folders.length) {
        spinner = (

            <Spinner spinnerName='three-bounce' noFadeIn={true}/> Loading drive files...

        );
      }

      if (!this.state.isAuthorized) {
        return (

Sign in with your Google Drive account to search sound files

              <button className="btn btn-block btn-google-plus" onClick={this.signIn}>
                Sign in with Google
              </button>

        );
      }

      let slackAuth = (

          <AuthSlack onAuthenticated={this.onAuthenticatedWithSlack}/>

      );

      let helpBtn = (

            <button className="btn btn-default" onClick={this.clickHelp}>

            </button>
            <button className="btn btn-default btn-fetch-google" onClick={this.fetchFilesFromGoogle}>
              {this.state.loadingFromGoogle ?
                <Spinner spinnerName='cube-grid'/> :
                }
            </button>

      )

      let playSound;
      if (this.state.slackAccessToken) {
        playSound = (

            <PlaySlackSound
              accessToken={this.state.slackAccessToken}
              soundName={this.state.selectedSound.title}/>

);
        slackAuth = null;
      }

      return (

          {spinner}

            <HotKeys handlers={handlers}>
              <SuggestionBox
                folders={this.state.folders}
                onSuggestionSelected={this.onSuggestionSelected}
                onKeyDown={this.onSuggestionKeyDown}/>
            </HotKeys>
            <PreviewSound downloadUrl={this.state.selectedSound.downloadUrl}/>
            {playSound}
            {slackAuth}
            {helpBtn}

      )
    }
});
<commit_msg>Try to narrow down search<commit_after>import Promise from 'bluebird';
import React from 'react';
import googleApiLoader from '../Lib/GoogleApiLoader';
import _ from 'lodash';
import SuggestionBox from './SuggestionBox';
import PreviewSound from './PreviewSound';
import AuthSlack from './AuthSlack';
import PlaySlackSound from './PlaySlackSound';
import Spinner from 'react-spinkit';
import {HotKeys} from 'react-hotkeys';
import swal from 'exports?swal!sweetalert2/src/sweetalert2.js';

var walkFilePages = function(request) {
  return new Promise((resolve) => {
    retrieveRecursiveFiles(request, [], resolve)
  });
};

var retrieveRecursiveFiles = function(request, arr, done) {
  return request.execute((resp) => {
    arr = arr.concat(resp.items);
    var nextPageToken = resp.nextPageToken;
    if (nextPageToken) {
      request = gapi.client.request({
        'path': '/drive/v2/files',
        'method': 'GET',
        'pageToken': nextPageToken,
        'params': {
          'q': 'trashed = false'
        }
      });
      retrieveRecursiveFiles(request, arr);
    } else {
      done(arr);
    }
  });
}

var retrieveAllFiles = function(folderId)  {
  var initialRequest = gapi.client.request({
    'path': '/drive/v2/files',
    'method': 'GET',
    'params': {
      'q': "trashed = false and '" + folderId + "' in parents"
    }
  });
  return walkFilePages(initialRequest);
}

function getSubFolders(folderId) {
  var initialRequest = gapi.client.request({
    'path': '/drive/v2/files',
    'method': 'GET',
    'params': {
      'q': "trashed = false and '" + folderId + "' in parents and mimeType='application/vnd.google-apps.folder'"
    }
  });
  return walkFilePages(initialRequest);
}

module.exports = React.createClass({

    maxCacheAge: 5 * 1000 * 60, // 5 min
    maxSlackAge: 24 * 60 * 60 * 1000, // 1 day

    getInitialState: function() {
      return {
        isAuthorized: false,
        selectedSound: {},
        folders: []
      };
    },

    componentDidMount: function () {
      var _this = this
      googleApiLoader.apiReady.then(() => {
        _this.setState({isAuthorized: true})
        return _this.fetchFiles()
      });

      _this.checkForSavedSlackToken()
    },

    queueFileRefresh() {
      setTimeout(this.fetchFiles, this.maxCacheAge)
    },

    fetchFiles() {
      var maxCacheMinutes = this.maxCacheAge / (1000 * 60)
      var oldestAllowedCache = (new Date()).setMinutes((new Date()).getMinutes() - maxCacheMinutes)
      var folderJson = localStorage.getItem('driveFileCache')
      var cacheAge = localStorage.getItem('driveFileCacheTime')
      var cacheIsStale = new Date(cacheAge) < oldestAllowedCache

      if (!cacheAge || !folderJson) {
        console.log('No valid cache, fetching new file listing')
        return this.fetchFilesFromGoogle()
      }

      console.log('Files loaded from localstorage!')
      this.setState({folders: JSON.parse(folderJson)})

      // Cache is old.. but we don't have any files yet
      // start google fetch, but use cache for now
      if (cacheIsStale && !this.state.folders.length) {
        console.log('Queuing Google fetch because the cache is stale...')
        this.fetchFilesFromGoogle()
      } else {
        this.queueFileRefresh()
      }
    },

    fetchFilesFromGoogle() {
      var _this = this
      var deletableFields = [
        'appDataContents',
        'copyable',
        'editable',
        'etag',
        'explicitlyTrashed',
        'iconLink',
        'kind',
        'labels',
        'lastModifyingUser',
        'lastViewedByMeDate',
        'markedViewedByMeDate',
        'mimeType',
        'modifiedByMeDate',
        'modifiedDate',
        'ownerNames',
        'owner',
        'owners',
        'version',
        'parents',
        'quotaBytesUsed',
        'shared',
        'spaces',
        'userPermission',
        'writersCanShare',
        'alternateLink',
        'fileExtension',
        'headRevisionId',
        'md5Checksum',
        'originalFilename',
        'selfLink',
        'webContentLink'
      ]

      function cleanFileList(list) {
        return list.map(function(file) {
          deletableFields.forEach(function(deletableField) {
            if (file[deletableField]) {
              delete file[deletableField]
            }
          })
          return file
        })
      }

      if (_this.state.loadingFromGoogle) {
        console.log('Alreading loading from Google... ignoring request')
        return
      }
      _this.setState({loadingFromGoogle: true});
      return gapi.client.drive.files.list({
        'q': "mimeType='application/vnd.google-apps.folder' and name='sounds, -slack-sounds'",
        'pageSize': 10,
        'fields': "nextPageToken, files(id, name)"
      }).execute((resp) => {
        var folders = cleanFileList(resp.files)

        var soundFolderId = folders[0].id

        var getSoundFolders = getSubFolders(soundFolderId).then((folders) => {
          return Promise.reduce(folders, function(soundFolders, folder) {
            return Promise.delay(100).then(() => {
              return retrieveAllFiles(folder.id).then(function (files) {

                folder.files = cleanFileList(files)
                soundFolders.push(folder)
                return soundFolders
              })
            });
          }, []);
        });

        getSoundFolders.then((soundFolders) => {
          console.log('Saving files to localstorage...')
          localStorage.setItem('driveFileCache', JSON.stringify(soundFolders))
          localStorage.setItem('driveFileCacheTime', new Date())
          _this.queueFileRefresh()
          _this.setState({
            folders: soundFolders,
            loadingFromGoogle: false
          });
        })
      });
    },

    checkForSavedSlackToken() {
      var maxCacheMinutes = this.maxSlackAge / (1000 * 60)
      var token = localStorage.getItem('slackAccessToken')
      var tokenSavedString = localStorage.getItem('slackAccessTokenTime')
      var oldestAllowedCache = (new Date()).setMinutes((new Date()).getMinutes() - maxCacheMinutes)
      var cacheIsStale = new Date(tokenSavedString) < oldestAllowedCache

      if (!token || !tokenSavedString) {
        return
      }

      if (cacheIsStale) {
        console.log('Local Slack token is older than a day, ignoring...')
        localStorage.removeItem('slackAccessToken')
        localStorage.removeItem('slackAccessTokenTime')
        return
      }

      console.log('Slack token loaded from localstorage!')
      this.setState({slackAccessToken: token})
    },

    signIn() {
      googleApiLoader.login();
    },

    onSuggestionSelected(e, data) {
      this.setState({ selectedSound: data.suggestion });
    },

    onAuthenticatedWithSlack(resp) {
      console.log('Saving Slack token to localstorage...');
      localStorage.setItem('slackAccessToken', resp.access_token)
      localStorage.setItem('slackAccessTokenTime', new Date())
      this.setState({slackAccessToken: resp.access_token})
    },

    playSound() {
      setTimeout(function() {
        let event = new window.CustomEvent('playSound');
        window.dispatchEvent(event);
      });
    },

    sendToSlack() {
      setTimeout(function() {
        let event = new window.CustomEvent('sendToSlack');
        window.dispatchEvent(event);
      });
    },

    stopSound() {
      setTimeout(function() {
        let event = new window.CustomEvent('stopSound');
        window.dispatchEvent(event);
      });
    },

    clickHelp() {
      swal({   title: "Sound Client Help!",   html: `

Login with Slack to play sounds

Search:

":" to get all sound files

"<folder>:" to get all sound files in a folder

Shortcuts:

command+enter to preview a sound

esc to stop a preview

shift+enter to play a sound on slack

      `});
    },

    render: function () {

      const handlers = {
        'playSound': this.playSound,
        'sendToSlack': this.sendToSlack,
        'stopSound': this.stopSound
      };

      var spinner;
      if (this.state.isAuthorized && !this.state.folders.length) {
        spinner = (

            <Spinner spinnerName='three-bounce' noFadeIn={true}/> Loading drive files...

        );
      }

      if (!this.state.isAuthorized) {
        return (

Sign in with your Google Drive account to search sound files

              <button className="btn btn-block btn-google-plus" onClick={this.signIn}>
                Sign in with Google
              </button>

        );
      }

      let slackAuth = (

          <AuthSlack onAuthenticated={this.onAuthenticatedWithSlack}/>

      );

      let helpBtn = (

            <button className="btn btn-default" onClick={this.clickHelp}>

            </button>
            <button className="btn btn-default btn-fetch-google" onClick={this.fetchFilesFromGoogle}>
              {this.state.loadingFromGoogle ?
                <Spinner spinnerName='cube-grid'/> :
                }
            </button>

      )

      let playSound;
      if (this.state.slackAccessToken) {
        playSound = (

            <PlaySlackSound
              accessToken={this.state.slackAccessToken}
              soundName={this.state.selectedSound.title}/>

);
        slackAuth = null;
      }

      return (

          {spinner}

            <HotKeys handlers={handlers}>
              <SuggestionBox
                folders={this.state.folders}
                onSuggestionSelected={this.onSuggestionSelected}
                onKeyDown={this.onSuggestionKeyDown}/>
            </HotKeys>
            <PreviewSound downloadUrl={this.state.selectedSound.downloadUrl}/>
            {playSound}
            {slackAuth}
            {helpBtn}

      )
    }
});
<|endoftext|>


<commit_before>function loadFont(a,b,c,d){function e(){if(!window.FontFace)return!1;var a=new FontFace("t",'url("data:application/font-woff2,") format("woff2")',{}),b=a.load();try{b.then(null,function(){})}catch(c){}return"loading"===a.status}var f=navigator.userAgent,g=!window.addEventListener||f.match(/(Android (2|3|4.0|4.1|4.2|4.3))|(Opera (Mini|Mobi))/)&&!f.match(/Chrome/);if(!g){var h={};try{h=localStorage||{}}catch(i){}var j="font-"+a,k=j+"url",l=j+"css",m=h[k],n=h[l],o=document.createElement("style");if(o.rel="stylesheet",document.head.appendChild(o),!n||m!==b&&m!==c){var p=c&&e()?c:b,q=new XMLHttpRequest;q.open("GET",p),q.onload=function(){q.status>=200&&q.status<400&&(h[k]=p,h[l]=q.responseText,d||(o.textContent=q.responseText))},q.send()}else o.textContent=n}}<commit_msg>Update fontloader.js<commit_after>function loadFont(a,b,c){function d(){if(!window.FontFace)return!1;var a=new FontFace("t",'url("data:application/font-woff2,") format("woff2")',{});return a.load(),"loading"===a.status}var e=navigator.userAgent,f=!window.addEventListener||e.match(/(Android (2|3|4.0|4.1|4.2|4.3))|(Opera (Mini|Mobi))/)&&!e.match(/Chrome/);if(!f){var g={};try{g=localStorage||{}}catch(h){}var i="font-"+a,j=i+"url",k=i+"css",l=g[j],m=g[k],n=document.createElement("style");if(n.rel="stylesheet",document.head.appendChild(n),!m||l!==b&&l!==c){var o=c&&d()?c:b,p=new XMLHttpRequest;p.open("GET",o),p.onload=function(){p.status>=200&&p.status<400&&(g[j]=o,g[k]=n.textContent=p.responseText)},p.send()}else n.textContent=m}}
<|endoftext|>


<commit_before>import { default as React, Component } from 'react';
import { render } from 'react-dom';
import { GoogleMapLoader, GoogleMap, Marker, SearchBox } from "react-google-maps";
import InfoBox from 'react-google-maps/lib/addons/InfoBox';
import { default as MarkerClusterer } from "react-google-maps/lib/addons/MarkerClusterer";
import {queryObject, emitter} from '../middleware/ImmutableQuery.js';
import {manager} from '../middleware/ChannelManager.js';
import {AppbaseSearch} from '../sensors/AppbaseSearch';
import {SearchAsMove} from '../sensors/SearchAsMove';
import {MapStyles} from '../sensors/MapStyles';

var helper = require('../middleware/helper.js');
var Style = require('../helper/Style.js');

export class AppbaseMap extends Component {
  constructor(props) {
    super(props);
    this.state = {
      markers: [],
      selectedMarker: null,
      streamingStatus: 'Intializing..',
      center: this.props.defaultCenter,
      query: {},
      rawData: {}
    };
    this.previousSelectedSensor = {};
    this.handleSearch = this.handleSearch.bind(this);
    this.searchAsMoveChange = this.searchAsMoveChange.bind(this);
    this.mapStyleChange = this.mapStyleChange.bind(this);
  }
  componentDidMount() {
    this.createChannel();
    this.setGeoQueryInfo();
    let currentMapStyle = helper.getMapStyle(this.props.mapStyle);
    this.setState({
      currentMapStyle: currentMapStyle
    });
  }
  // Create a channel which passes the depends and receive results whenever depends changes
  createChannel() {
    // Set the depends - add self aggs query as well with depends
    let depends = this.props.depends ? this.props.depends : {};
    depends['geoQuery'] = { operation: "should" };
    // create a channel and listen the changes
    var channelObj = manager.create(depends);
    channelObj.emitter.addListener(channelObj.channelId, function(data) {
      this.setState({
        rawData: data
      }, this.getNewMarkers);
    }.bind(this));
  }
  // set the query type and input data
  setGeoQueryInfo() {
    var obj = {
        key: 'geoQuery',
        value: {
          queryType: 'geo_bounding_box',
          inputData: this.props.inputData
        }
    };
    helper.selectedSensor.setSensorInfo(obj);
  }
  getNewMarkers() {
    var self = this;
    this.searchQueryProgress = true;
    var data = this.state.rawData;
    self.searchQueryProgress = true;
    let newMarkersArray = [];
    var totalPosition = {lat: 0, lng: 0};
    var markersData = data.hits.hits.filter((hit, index) => {
      return hit._source.hasOwnProperty(self.props.inputData) && !(hit._source[self.props.inputData].lat === 0 && hit._source[self.props.inputData].lon === 0);
    });
    markersData = _.orderBy(markersData, [self.props.inputData.lat], ['desc']);
    newMarkersArray = markersData.map((hit, index) => {
      let field = hit._source[self.props.inputData];
      let position = {
        position: {
          lat: field.lat,
          lng: field.lon
        }
      }
      totalPosition.lat += field.lat;
      totalPosition.lng += field.lon;
      return (
        <Marker {...position} key={index} zIndex={1}
          onClick={() => self.props.markerOnClick(hit._source)}
          onDblclick={() => self.props.markerOnDblclick(hit._source)}
          onMouseover={() => self.props.markerOnMouseover(hit._source)}
          onMouseout={() => self.props.markerOnMouseout(hit._source)} />
      )
    });
    if(markersData.length) {
      var median = parseInt(markersData.length/2, 10);
      var selectedMarker = markersData[median];
      var defaultCenter = {
        lat: selectedMarker._source[self.props.inputData].lat,
        lng: selectedMarker._source[self.props.inputData].lon
      };
      console.log(defaultCenter.lat, defaultCenter.lng, newMarkersArray.length);
      self.setState({
        markers: newMarkersArray,
        center: defaultCenter
      }, function () {
        setTimeout(() => {
          this.searchQueryProgress = false;
        }, 500);
      });
    } else {
      self.setState({
        markers: newMarkersArray
      }, function () {
        setTimeout(() => {
          this.searchQueryProgress = false;
        }, 500);
      });
    }
  }
  startStreaming() {
    var self = this;
    let query = this.state.query;
    self.setState({
      streamingStatus: 'Listening...'
    });
    // Stop the previous instance of the streaming
    if (this.streamingInstance) {
      this.streamingInstance.stop()
    };
    this.streamingInstance = helper.appbaseRef.searchStream(query).on('data', function (stream) {
      let positionMarker = {
        position: {
          lat: stream._source[self.props.inputData].lat,
          lng: stream._source[self.props.inputData].lon
        }
      }
      // Using a different color marker for realtime markers
      let newMarker = <Marker {...positionMarker}
        key={Date.now() }
        icon="images/map.png"
        zIndex={100} />
      let newMarkersArray = self.state.markers;
      // If the marker is deleted, remove it from the map
      if (stream._deleted == true) {
        var deleteIndex = newMarkersArray.indexOf(newMarker);
        newMarkersArray.splice(deleteIndex, 1);
        self.props.markerOnDelete(positionMarker);
      }
      else {
        newMarkersArray.push(newMarker)
        self.props.markerOnIndex(positionMarker);
      }
      self.setState({
        markers: newMarkersArray,
        streamingStatus: 'Listening...'
      });
    }).on('error', function (error) {
      console.log(error)
    });
  }
  // Handle function which is fired when map is moved and reaches to idle position
  handleOnIdle() {
    if(this.searchAsMove && !this.searchQueryProgress) {
      var mapBounds = this.refs.map.getBounds();
      var north = mapBounds.getNorthEast().lat();
      var south = mapBounds.getSouthWest().lat();
      var east = mapBounds.getNorthEast().lng();
      var west = mapBounds.getSouthWest().lng();
      var boundingBoxCoordinates = {
        "top_left": [west, north],
        "bottom_right": [east, south]
      };
      this.setValue(boundingBoxCoordinates, this.searchAsMove);
    }
  }
  // set value
  setValue(value, isExecuteQuery=false) {
    var obj = {
        key: 'geoQuery',
        value: value
    };
    helper.selectedSensor.set(obj, isExecuteQuery);
  }
  // on change of selectiong
  searchAsMoveChange(value) {
    this.searchAsMove = value;
  }
  // mapStyle changes
  mapStyleChange(style) {
    this.setState({
      currentMapStyle: style
    });
  }
  // Handler function for bounds changed which udpates the map center
  handleBoundsChanged() {
    if(!this.searchQueryProgress) {
      // this.setState({
      //   center: this.refs.map.getCenter()
      // });
    } else {
      setTimeout(()=> {
        this.searchQueryProgress = false;
      }, 1000*1);
    }
  }
  // Handler function which is fired when an input is selected from autocomplete google places
  handlePlacesChanged() {
    const places = this.refs.searchBox.getPlaces();
    // this.setState({
    //   center: places[0].geometry.location
    // });
  }
  // Handler function which is fired when an input is selected from Appbase geo search field
  handleSearch(location) {
    // this.setState({
    //   center: new google.maps.LatLng(location.value.lat, location.value.lon)
    // });
  }
  render() {
    var markerComponent, searchComponent, searchAsMoveComponent, MapStylesComponent;
    let appbaseSearch;
    var searchComponentProps = {};
    var otherOptions;
    if (this.props.markerCluster) {
      markerComponent = <MarkerClusterer averageCenter enableRetinaIcons gridSize={ 60 } >
        {this.state.markers}
      </MarkerClusterer>;
    }
    else {
      markerComponent = this.state.markers;
    }
    // Auto center using markers data
    if(!this.searchAsMove && this.props.autoCenter) {
      searchComponentProps.center = this.state.center;
    } else {
      delete searchComponentProps.center;
    }
    // include searchasMove component
    if(this.props.searchAsMoveComponent) {
      searchAsMoveComponent = <SearchAsMove searchAsMoveChange={this.searchAsMoveChange} />;
    }
    // include mapStyle choose component
    if(this.props.MapStylesComponent) {
      MapStylesComponent = <MapStyles defaultSelected={this.props.mapStyle} mapStyleChange={this.mapStyleChange} />;
    }
    if (this.props.searchComponent === "appbase") {
      appbaseSearch = <AppbaseSearch
        inputData={this.props.searchField}
        sensorId="VenueSensor"
        searchRef="GeoVenue"
        depends={{
          'geoQuery': {
            "operation": "must",
            "doNotExecute": {true}
          }
        }} />
      searchComponentProps.onBoundsChanged = ::this.handleBoundsChanged;
    } else if (this.props.searchComponent === "google") {
      searchComponent = <SearchBox
        controlPosition={google.maps.ControlPosition.TOP_LEFT}
        onPlacesChanged={:: this.handlePlacesChanged}
        ref = "searchBox"
        placeholder = "Search location"
        style = { Style.inputStyle }
      />;
      searchComponentProps.onBoundsChanged = ::this.handleBoundsChanged;
  }
  return(

      {appbaseSearch}
      <GoogleMapLoader
        containerElement={

        }
        googleMapElement={<GoogleMap ref = "map"
          options = {{
            styles: this.state.currentMapStyle
          }}
          {...searchComponentProps}
          {...this.props}
          onIdle = {:: this.handleOnIdle}>
          {searchComponent}
          {markerComponent}

      </GoogleMap>}/>

 { this.state.streamingStatus }

        Powered by

      {searchAsMoveComponent}
      {MapStylesComponent}

    )
  }
}
AppbaseMap.propTypes = {
  inputData: React.PropTypes.string.isRequired,
  searchField: React.PropTypes.string,
  searchComponent: React.PropTypes.string,
  markerOnDelete: React.PropTypes.func,
  markerOnIndex: React.PropTypes.func,
  markerCluster: React.PropTypes.bool,
  historicalData: React.PropTypes.bool
};
AppbaseMap.defaultProps = {
  historicalData: true,
  markerCluster: true,
  searchComponent: "google",
  autoCenter: false,
  searchAsMoveComponent: false,
  MapStylesComponent: false,
  mapStyle: 'MapBox',
  markerOnClick: function() {},
  markerOnDblclick: function() {},
  markerOnMouseover: function() {},
  markerOnMouseout: function() {}
};<commit_msg>Remove search from appbase map<commit_after>import { default as React, Component } from 'react';
import { render } from 'react-dom';
import { GoogleMapLoader, GoogleMap, Marker, SearchBox } from "react-google-maps";
import InfoBox from 'react-google-maps/lib/addons/InfoBox';
import { default as MarkerClusterer } from "react-google-maps/lib/addons/MarkerClusterer";
import {queryObject, emitter} from '../middleware/ImmutableQuery.js';
import {manager} from '../middleware/ChannelManager.js';
import {AppbaseSearch} from '../sensors/AppbaseSearch';
import {SearchAsMove} from '../sensors/SearchAsMove';
import {MapStyles} from '../sensors/MapStyles';

var helper = require('../middleware/helper.js');
var Style = require('../helper/Style.js');

export class AppbaseMap extends Component {
  constructor(props) {
    super(props);
    this.state = {
      markers: [],
      selectedMarker: null,
      streamingStatus: 'Intializing..',
      center: this.props.defaultCenter,
      query: {},
      rawData: {}
    };
    this.previousSelectedSensor = {};
    this.handleSearch = this.handleSearch.bind(this);
    this.searchAsMoveChange = this.searchAsMoveChange.bind(this);
    this.mapStyleChange = this.mapStyleChange.bind(this);
  }
  componentDidMount() {
    this.createChannel();
    this.setGeoQueryInfo();
    let currentMapStyle = helper.getMapStyle(this.props.mapStyle);
    this.setState({
      currentMapStyle: currentMapStyle
    });
  }
  // Create a channel which passes the depends and receive results whenever depends changes
  createChannel() {
    // Set the depends - add self aggs query as well with depends
    let depends = this.props.depends ? this.props.depends : {};
    depends['geoQuery'] = { operation: "should" };
    // create a channel and listen the changes
    var channelObj = manager.create(depends);
    channelObj.emitter.addListener(channelObj.channelId, function(data) {
      this.setState({
        rawData: data
      }, this.getNewMarkers);
    }.bind(this));
  }
  // set the query type and input data
  setGeoQueryInfo() {
    var obj = {
        key: 'geoQuery',
        value: {
          queryType: 'geo_bounding_box',
          inputData: this.props.inputData
        }
    };
    helper.selectedSensor.setSensorInfo(obj);
  }
  getNewMarkers() {
    var self = this;
    this.searchQueryProgress = true;
    var data = this.state.rawData;
    self.searchQueryProgress = true;
    let newMarkersArray = [];
    var totalPosition = {lat: 0, lng: 0};
    var markersData = data.hits.hits.filter((hit, index) => {
      return hit._source.hasOwnProperty(self.props.inputData) && !(hit._source[self.props.inputData].lat === 0 && hit._source[self.props.inputData].lon === 0);
    });
    markersData = _.orderBy(markersData, [self.props.inputData.lat], ['desc']);
    newMarkersArray = markersData.map((hit, index) => {
      let field = hit._source[self.props.inputData];
      let position = {
        position: {
          lat: field.lat,
          lng: field.lon
        }
      }
      totalPosition.lat += field.lat;
      totalPosition.lng += field.lon;
      return (
        <Marker {...position} key={index} zIndex={1}
          onClick={() => self.props.markerOnClick(hit._source)}
          onDblclick={() => self.props.markerOnDblclick(hit._source)}
          onMouseover={() => self.props.markerOnMouseover(hit._source)}
          onMouseout={() => self.props.markerOnMouseout(hit._source)} />
      )
    });
    if(markersData.length) {
      var median = parseInt(markersData.length/2, 10);
      var selectedMarker = markersData[median];
      var defaultCenter = {
        lat: selectedMarker._source[self.props.inputData].lat,
        lng: selectedMarker._source[self.props.inputData].lon
      };
      console.log(defaultCenter.lat, defaultCenter.lng, newMarkersArray.length);
      self.setState({
        markers: newMarkersArray,
        center: defaultCenter
      }, function () {
        setTimeout(() => {
          this.searchQueryProgress = false;
        }, 500);
      });
    } else {
      self.setState({
        markers: newMarkersArray
      }, function () {
        setTimeout(() => {
          this.searchQueryProgress = false;
        }, 500);
      });
    }
  }
  startStreaming() {
    var self = this;
    let query = this.state.query;
    self.setState({
      streamingStatus: 'Listening...'
    });
    // Stop the previous instance of the streaming
    if (this.streamingInstance) {
      this.streamingInstance.stop()
    };
    this.streamingInstance = helper.appbaseRef.searchStream(query).on('data', function (stream) {
      let positionMarker = {
        position: {
          lat: stream._source[self.props.inputData].lat,
          lng: stream._source[self.props.inputData].lon
        }
      }
      // Using a different color marker for realtime markers
      let newMarker = <Marker {...positionMarker}
        key={Date.now() }
        icon="images/map.png"
        zIndex={100} />
      let newMarkersArray = self.state.markers;
      // If the marker is deleted, remove it from the map
      if (stream._deleted == true) {
        var deleteIndex = newMarkersArray.indexOf(newMarker);
        newMarkersArray.splice(deleteIndex, 1);
        self.props.markerOnDelete(positionMarker);
      }
      else {
        newMarkersArray.push(newMarker)
        self.props.markerOnIndex(positionMarker);
      }
      self.setState({
        markers: newMarkersArray,
        streamingStatus: 'Listening...'
      });
    }).on('error', function (error) {
      console.log(error)
    });
  }
  // Handle function which is fired when map is moved and reaches to idle position
  handleOnIdle() {
    if(this.searchAsMove && !this.searchQueryProgress) {
      var mapBounds = this.refs.map.getBounds();
      var north = mapBounds.getNorthEast().lat();
      var south = mapBounds.getSouthWest().lat();
      var east = mapBounds.getNorthEast().lng();
      var west = mapBounds.getSouthWest().lng();
      var boundingBoxCoordinates = {
        "top_left": [west, north],
        "bottom_right": [east, south]
      };
      this.setValue(boundingBoxCoordinates, this.searchAsMove);
    }
  }
  // set value
  setValue(value, isExecuteQuery=false) {
    var obj = {
        key: 'geoQuery',
        value: value
    };
    helper.selectedSensor.set(obj, isExecuteQuery);
  }
  // on change of selectiong
  searchAsMoveChange(value) {
    this.searchAsMove = value;
  }
  // mapStyle changes
  mapStyleChange(style) {
    this.setState({
      currentMapStyle: style
    });
  }
  // Handler function for bounds changed which udpates the map center
  handleBoundsChanged() {
    if(!this.searchQueryProgress) {
      // this.setState({
      //   center: this.refs.map.getCenter()
      // });
    } else {
      setTimeout(()=> {
        this.searchQueryProgress = false;
      }, 1000*1);
    }
  }
  // Handler function which is fired when an input is selected from autocomplete google places
  handlePlacesChanged() {
    const places = this.refs.searchBox.getPlaces();
    // this.setState({
    //   center: places[0].geometry.location
    // });
  }
  // Handler function which is fired when an input is selected from Appbase geo search field
  handleSearch(location) {
    // this.setState({
    //   center: new google.maps.LatLng(location.value.lat, location.value.lon)
    // });
  }
  render() {
    var markerComponent, searchComponent, searchAsMoveComponent, MapStylesComponent;
    let appbaseSearch;
    var searchComponentProps = {};
    var otherOptions;
    if (this.props.markerCluster) {
      markerComponent = <MarkerClusterer averageCenter enableRetinaIcons gridSize={ 60 } >
        {this.state.markers}
      </MarkerClusterer>;
    }
    else {
      markerComponent = this.state.markers;
    }
    // Auto center using markers data
    if(!this.searchAsMove && this.props.autoCenter) {
      searchComponentProps.center = this.state.center;
    } else {
      delete searchComponentProps.center;
    }
    // include searchasMove component
    if(this.props.searchAsMoveComponent) {
      searchAsMoveComponent = <SearchAsMove searchAsMoveChange={this.searchAsMoveChange} />;
    }
    // include mapStyle choose component
    if(this.props.MapStylesComponent) {
      MapStylesComponent = <MapStyles defaultSelected={this.props.mapStyle} mapStyleChange={this.mapStyleChange} />;
    }
  return(

      <GoogleMapLoader
        containerElement={

        }
        googleMapElement={<GoogleMap ref = "map"
          options = {{
            styles: this.state.currentMapStyle
          }}
          {...searchComponentProps}
          {...this.props}
          onIdle = {:: this.handleOnIdle}>
          {searchComponent}
          {markerComponent}

      </GoogleMap>}/>

 { this.state.streamingStatus }

        Powered by

      {searchAsMoveComponent}
      {MapStylesComponent}

    )
  }
}
AppbaseMap.propTypes = {
  inputData: React.PropTypes.string.isRequired,
  searchField: React.PropTypes.string,
  searchComponent: React.PropTypes.string,
  markerOnDelete: React.PropTypes.func,
  markerOnIndex: React.PropTypes.func,
  markerCluster: React.PropTypes.bool,
  historicalData: React.PropTypes.bool
};
AppbaseMap.defaultProps = {
  historicalData: true,
  markerCluster: true,
  searchComponent: "google",
  autoCenter: false,
  searchAsMoveComponent: false,
  MapStylesComponent: false,
  mapStyle: 'MapBox',
  markerOnClick: function() {},
  markerOnDblclick: function() {},
  markerOnMouseover: function() {},
  markerOnMouseout: function() {}
};<|endoftext|>


<commit_before>import AdapterFetch from 'ember-fetch/mixins/adapter-fetch';
import JSONAPIAdapter from 'ember-data/adapters/json-api';
import ENV from '../config/environment';

export default JSONAPIAdapter.extend(AdapterFetch, {
  host: ENV.host,

  ajaxOptions() {
    const options = this._super(...arguments);
    options.headers['content-type'] = 'application/vnd.api+json';
    return options;
  }
});
<commit_msg>Capitals for Content-Type<commit_after>import AdapterFetch from 'ember-fetch/mixins/adapter-fetch';
import JSONAPIAdapter from 'ember-data/adapters/json-api';
import ENV from '../config/environment';

export default JSONAPIAdapter.extend(AdapterFetch, {
  host: ENV.host,

  ajaxOptions() {
    const options = this._super(...arguments);
    options.headers['Content-Type'] = 'application/vnd.api+json';
    return options;
  }
});
<|endoftext|>


<commit_before>$(function () {
    jQuery('.btn-email-listing').each(function () {
        const btn = jQuery(this),
            url = btn.data('url'),
            type = btn.data('type'),
            id = btn.data('id'),
            cacheKey = 'emails.list.' + type + '.id' + id,
            modalEl = jQuery('#modalEmailListing'),
            refreshButtonEl = modalEl.find('.btn-refresh'),
            tbodyEl = modalEl ? modalEl.find('table tbody') : null;

        if (!url) {
            btn.toggleClass('disabled', true);
            return;
        }

        const updateButton = function (list) {
            const btnTitle = list.length === 1 ? ' 1 E-Mail ' : ' ' + list.length + ' E-Mails ';
            btn.find('.hidden-xs').html(btnTitle);
        };

        btn.toggleClass('loading-text', true);
        if (commonCache.has(cacheKey)) {
            updateButton(commonCache.get(cacheKey));
        } else {
            btn.toggleClass('disabled', true);
        }

        const renderEmails = function () {
                if (btn.hasClass('disabled') || !modalEl || !tbodyEl) {
                    return;
                }
                tbodyEl.html('');
                const emails = commonCache.get(cacheKey);
                let html = '';

                if (emails.length === 0) {
                    html += '
Keine passenden E-Mails gefunden.
';
                }

                jQuery.each(emails, function (key, email) {
                    let emailAddressesFrom = eHtml(email.from.join(', '));
                    let emailAddressesTo = eHtml(email.to.join(', '));

                    html += '
';
                    html += '';

                    if (email.organization_sender) {
                        html += 'Wir';
                    } else {
                        html += '' + emailAddressesFrom + '';
                    }
                    html += ' → ';
                    if (email.organization_receiver) {
                        html += 'Uns';
                    } else {
                        html += '' + emailAddressesTo + '';
                    }

                    html += '	';
                    html += '' + eHtml(email.date) + '	';
                    html += '';
                    if (email.attachment_count) {
                        html += ' ';
                    }
                    html += eHtml(email.subject);
                    html += '	';

                    html += '';
                    if (email.mailbox_symbol) {
                        html += ' ';
                    }
                    html += eHtml(email.mailbox);
                    html += '	';

                    html += '';
                    if (email.url_download_raw) {
                        html += '' +
                            '' +
                            ' ';
                    }
                    html += '	';

                    html += '
';
                });
                tbodyEl.html(html);
                tbodyEl.toggleClass('loading-text', false);
            },
            loadMails = function () {
                refreshButtonEl.toggleClass('disabled', true);
                jQuery.ajax({
                    type: 'GET',
                    url: url,
                    success: function (response) {
                        btn.toggleClass('disabled', false);
                        if (response.items) {
                            commonCache.set(cacheKey, response.items);
                            updateButton(response.items);
                            renderEmails();
                        }
                    },
                    complete: function () {
                        refreshButtonEl.toggleClass('disabled', false);
                        btn.toggleClass('disabled', false);
                        btn.toggleClass('loading-text', false);
                    }
                });
            };
        loadMails();

        refreshButtonEl.on('click', function () {
            if (refreshButtonEl.hasClass('disabled')) {
                return;
            }
            refreshButtonEl.toggleClass('disabled', true);
            tbodyEl.toggleClass('loading-text', true);
            loadMails();
        });

        btn.on('click', function () {
            renderEmails();
            modalEl.modal('show');
        });
    });
});
<commit_msg>Preventing errors<commit_after>$(function () {
    jQuery('.btn-email-listing').each(function () {
        const btn = jQuery(this),
            url = btn.data('url'),
            type = btn.data('type'),
            id = btn.data('id'),
            cacheKey = 'emails.list.' + type + '.id' + id,
            modalEl = jQuery('#modalEmailListing'),
            refreshButtonEl = modalEl.find('.btn-refresh'),
            tbodyEl = modalEl ? modalEl.find('table tbody') : null;

        if (!url) {
            btn.toggleClass('disabled', true);
            return;
        }

        const updateButton = function (list) {
            const btnTitle = list && list.length === 1 ? ' 1 E-Mail ' : ' ' + list.length + ' E-Mails ';
            btn.find('.hidden-xs').html(btnTitle);
        };

        btn.toggleClass('loading-text', true);
        if (commonCache.has(cacheKey)) {
            updateButton(commonCache.get(cacheKey));
        } else {
            btn.toggleClass('disabled', true);
        }

        const renderEmails = function () {
                if (btn.hasClass('disabled') || !modalEl || !tbodyEl) {
                    return;
                }
                tbodyEl.html('');
                const emails = commonCache.get(cacheKey);
                let html = '';

                if (emails.length === 0) {
                    html += '
Keine passenden E-Mails gefunden.
';
                }

                jQuery.each(emails, function (key, email) {
                    let emailAddressesFrom = eHtml(email.from.join(', '));
                    let emailAddressesTo = eHtml(email.to.join(', '));

                    html += '
';
                    html += '';

                    if (email.organization_sender) {
                        html += 'Wir';
                    } else {
                        html += '' + emailAddressesFrom + '';
                    }
                    html += ' → ';
                    if (email.organization_receiver) {
                        html += 'Uns';
                    } else {
                        html += '' + emailAddressesTo + '';
                    }

                    html += '	';
                    html += '' + eHtml(email.date) + '	';
                    html += '';
                    if (email.attachment_count) {
                        html += ' ';
                    }
                    html += eHtml(email.subject);
                    html += '	';

                    html += '';
                    if (email.mailbox_symbol) {
                        html += ' ';
                    }
                    html += eHtml(email.mailbox);
                    html += '	';

                    html += '';
                    if (email.url_download_raw) {
                        html += '' +
                            '' +
                            ' ';
                    }
                    html += '	';

                    html += '
';
                });
                tbodyEl.html(html);
                tbodyEl.toggleClass('loading-text', false);
            },
            loadMails = function () {
                refreshButtonEl.toggleClass('disabled', true);
                jQuery.ajax({
                    type: 'GET',
                    url: url,
                    success: function (response) {
                        btn.toggleClass('disabled', false);
                        if (response && response.items) {
                            commonCache.set(cacheKey, response.items);
                            updateButton(response.items);
                            renderEmails();
                        }
                    },
                    complete: function () {
                        refreshButtonEl.toggleClass('disabled', false);
                        btn.toggleClass('disabled', false);
                        btn.toggleClass('loading-text', false);
                    }
                });
            };
        loadMails();

        refreshButtonEl.on('click', function () {
            if (refreshButtonEl.hasClass('disabled')) {
                return;
            }
            refreshButtonEl.toggleClass('disabled', true);
            tbodyEl.toggleClass('loading-text', true);
            loadMails();
        });

        btn.on('click', function () {
            renderEmails();
            modalEl.modal('show');
        });
    });
});
<|endoftext|>


<commit_before>/**
 * Route map for the application
 */
Router.map(function() {
    this.route('test_report', {path:'/test_report'})
    this.route('root', {path:'/', waitOn:function(){
            return Meteor.subscribe('userData');
        },
        action:function(){
            if(this.ready()){
                if(!Hembu.userHasAddress)
                    Router.go('addressesCreate', {addressParam:'new'});
                else
                    Router.go('home', {addressParam: Hembu.getCurrentAddress().address});
            }
    }});
    this.route('home', {path:'/home/:addressParam/:boardParam?', layoutTemplate: 'layout',
        waitOn: function() {
            Hembu.setCurrentAddress(this.params.addressParam);
            return Meteor.subscribe('userData');
        },
        data:function(){
            if(this.ready()){
                var board = Boards.findOne({name:this.params.boardParam})
                var notices = this.params.boardParam === undefined ? Notices.find({},{sort:{postedAt:-1}}).fetch() : Notices.find({boardId:board._id},{sort:{postedAt:-1}}).fetch();
                var address = Hembu.getCurrentAddress();
                return {
                    address:address,
                    board: board,
                    notices:notices,
                    addressParam: address.address,
                    boardParam: board === undefined ? 'notice' : board.name
                };
            }
        },
        action: function(){
            this.render();
        }
    });
    this.route('notice', {path:'/home/:addressParam/:boardParam/:_id', layoutTemplate: 'blank',
        waitOn: function() {
            Hembu.setCurrentAddress(this.params.addressParam);
            return Meteor.subscribe('userData');
        },
        data:function(){
            if(this.ready()){
                var board = Boards.findOne({name:this.params.boardParam})
                var notice = Notices.findOne({_id:this.params._id})
                return {
                    address:Hembu.getCurrentAddress(),
                    board: board,
                    notice:notice
                };
            }
        },
        action: function(){
            this.render();
        }
    });
    this.route('facilitiesCreate', {path: '/facilities/:address/new/create', template:'facilitiesCreate', layoutTemplate: 'layout'});
	this.route('writeNotice', {path: '/home/:addressParam/:boardParam?/write', layoutTemplate: 'blank',
        waitOn: function() {
            Hembu.setCurrentAddress(this.params.addressParam);
            return Meteor.subscribe('userData');
        },
		action: function(){
            this.render();
        }
	});
    this.route('facilitiesList', {path: '/facilities/:address/', template:'facilitiesList', layoutTemplate: 'layout',
        data:function(){
            return {address: this.params.address, facilities : Facilities.find({address:this.params.address}).fetch()};
        },
        action: function(){
            this.render();
        }
    });
    this.route('facilitiesCalendar', {path: '/facilities/:address/:_id/calendar', template:'facilitiesCalendar', layoutTemplate: 'layout'});
    this.route('addressesCreate',{path:'/home/:address/create', template:'addressesCreate', layoutTemplate: 'layout'})
    this.route('login', {path: '/login', layoutTemplate: 'layout'});
    this.route('logout', {path: '/logout', action: function(){
        Meteor.logout(function(err){
            if(!err)
                Router.go('login');
        });
    }});
    this.route('signup', {path: '/signup', layoutTemplate: 'layout'});
});

/**
 * Functionality to check authenticated user and present unauthenticated
 * ones with a login dialog.
*/
Router.onBeforeAction(function(pause){

    if (!(Meteor.loggingIn() || Meteor.user())) {
          Router.go('login');
          pause();
    }
    else{
        if(!Meteor.user())
            pause();
    }
}, {except: ['login', 'signup']});

Router.onStop(function(){
    Router._previous = Router.current();
});
Router.previous = function(){
    return Router._previous;
};
Router.back = function(callback){
    var previous = Router.previous();
    if(previous){
        Router.go(previous.route.name, previous.params);
        if(callback)
            callback(null);
    }
    else{
        var err = new Error("No previous route available");
        if(callback)
            callback();
        else
            throw err;
    }
};<commit_msg>route fix<commit_after>/**
 * Route map for the application
 */
Router.map(function() {
    this.route('test_report', {path:'/test_report'})
    this.route('root', {path:'/', waitOn:function(){
            return Meteor.subscribe('userData');
        },
        action:function(){
            if(this.ready()){
                if(!Hembu.userHasAddress)
                    Router.go('addressesCreate', {addressParam:'new'});
                else
                    Router.go('home', {addressParam: Hembu.getCurrentAddress().address});
            }
    }});
    this.route('home', {path:'/home/:addressParam/:boardParam?', layoutTemplate: 'layout',
        waitOn: function() {
            Hembu.setCurrentAddress(this.params.addressParam);
            return Meteor.subscribe('userData');
        },
        data:function(){
            if(this.ready()){
                var board = Boards.findOne({name:this.params.boardParam})
                var notices = this.params.boardParam === undefined ? Notices.find({},{sort:{postedAt:-1}}).fetch() : Notices.find({boardId:board._id},{sort:{postedAt:-1}}).fetch();
                var address = Hembu.getCurrentAddress();
                return {
                    address:address,
                    board: board,
                    notices:notices,
                    addressParam: address.address,
                    boardParam: board === undefined ? 'notice' : board.name
                };
            }
        },
        action: function(){
            this.render();
        }
    });
    //write route has to be before view route due id param stuff!
    this.route('writeNotice', {path: '/home/:addressParam/:boardParam?/write', layoutTemplate: 'blank',
        waitOn: function() {
             Hembu.setCurrentAddress(this.params.addressParam);
             return Meteor.subscribe('userData');
         },
 		action: function(){
             this.render();
         }
 	});
    this.route('notice', {path:'/home/:addressParam/:boardParam/:_id', layoutTemplate: 'blank',
        waitOn: function() {
            Hembu.setCurrentAddress(this.params.addressParam);
            return Meteor.subscribe('userData');
        },
        data:function(){
            if(this.ready()){
                var board = Boards.findOne({name:this.params.boardParam})
                var notice = Notices.findOne({_id:this.params._id})
                return {
                    address:Hembu.getCurrentAddress(),
                    board: board,
                    notice:notice
                };
            }
        },
        action: function(){
            this.render();
        }
    });
    this.route('facilitiesCreate', {path: '/facilities/:address/new/create', template:'facilitiesCreate', layoutTemplate: 'layout'});
    this.route('facilitiesList', {path: '/facilities/:address/', template:'facilitiesList', layoutTemplate: 'layout',
        data:function(){
            return {address: this.params.address, facilities : Facilities.find({address:this.params.address}).fetch()};
        },
        action: function(){
            this.render();
        }
    });
    this.route('facilitiesCalendar', {path: '/facilities/:address/:_id/calendar', template:'facilitiesCalendar', layoutTemplate: 'layout'});
    this.route('addressesCreate',{path:'/home/:address/create', template:'addressesCreate', layoutTemplate: 'layout'})
    this.route('login', {path: '/login', layoutTemplate: 'layout'});
    this.route('logout', {path: '/logout', action: function(){
        Meteor.logout(function(err){
            if(!err)
                Router.go('login');
        });
    }});
    this.route('signup', {path: '/signup', layoutTemplate: 'layout'});
});

/**
 * Functionality to check authenticated user and present unauthenticated
 * ones with a login dialog.
*/
Router.onBeforeAction(function(pause){

    if (!(Meteor.loggingIn() || Meteor.user())) {
          Router.go('login');
          pause();
    }
    else{
        if(!Meteor.user())
            pause();
    }
}, {except: ['login', 'signup']});

Router.onStop(function(){
    Router._previous = Router.current();
});
Router.previous = function(){
    return Router._previous;
};
Router.back = function(callback){
    var previous = Router.previous();
    if(previous){
        Router.go(previous.route.name, previous.params);
        if(callback)
            callback(null);
    }
    else{
        var err = new Error("No previous route available");
        if(callback)
            callback();
        else
            throw err;
    }
};<|endoftext|>