<commit_before>/**
 * @ngdoc function
 * @name errorDialogClosed
 * @description Called after error dialog is closed.
 * @param {object} root scope
 * @param {object} current scope
 * @param {object} reference to log service
 */
function errorDialogClosed($rootScope, $scope, log) {
    log.debug("Error dialog closed.");
}

/**
 * @ngdoc function
 * @name showErrorDialog
 * @description Shows up the error dialog with the given error details
 * @param {object} root scope
 * @param {object} current scope
 * @param {object} reference to loading indicator
 * @param {object} reference to log service
 * @param {object} title to use
 * @param {object} error code to use
 * @param {object} message to display
 */
function showErrorDialog($rootScope, $scope, $ionicLoading, log, errorTitle, errorCode, errorDetails) {
    $ionicLoading.show();
    navigator.notification.alert(errorCode + "\n" + errorDetails, errorDialogClosed($rootScope, $scope, log), "Error - " + errorTitle);
    $ionicLoading.hide();
}

/**
 * @ngdoc function
 * @name openExternalLink
 * @param {string} url to open
 * @description open external url in browser app on mobile device, otherwise in a new tab on desktop device
 */
function openExternalLink(pUrl) {
        if (isMobileDevice()) {
            window.open(pUrl, '_system', 'location=yes');
        } else {
            window.open(pUrl, '_blank');
        }
    }
    /**
     * @ngdoc function
     * @name handleOpenURL
     * @description redirect app url handler to app
     */
function handleOpenURL(url) {
    setTimeout(function() {
        if (url.toLowerCase().search('flynnapp') > -1) {
            var type = url.substring(url.indexOf('//') + 2, url.indexOf('?')),
                params = url.substring(url.indexOf('?') + 1).split("&");
            // create JSON object
            var args = {};
            for (var i = 0, len = params.length; i < len; i++) {
                var paramData = params[i].split("=");
                args[paramData[0]] = paramData[1];
            }
            // get Angular scope from the known DOM element
            e = document.body;
            scope = angular.element(e).scope();
            // broadcast URL data
            switch (type) {
                case 'config':
                    scope.$root.$broadcast('settings.handleURL', args);
                    break;
            }
        }
    }, 0);
}

/**
 * @ngdoc function
 * @name isMobileDevice
 * @description returns true if running on a mobile device, otherwise false
 */
function isMobileDevice() {
    if (window.cordova) {
        return true;
    } else {
        return false; //this is the browser
    }
}

function configureLogging(loggerProvider, config) {
    if(config){
        loggerProvider.dbName('flynnDB_logs');
        loggerProvider.outputOnly(!config.dbLogging);
        loggerProvider.debugLogging(config.debug);
        if (config.debug) {
            // enable couchDB debug
            PouchDB.debug.enable('*');
        } else {
            PouchDB.debug.disable();
        }
        loggerProvider.traceLogging(config.trace);
    }
}

/**
 * @ngdoc function
 * @name onDeviceReady
 * @description Called after device is ready to use
 */
function onDeviceReady() {
    var $http = angular.injector(['ng']).get('$http'),
        $rootScope = angular.injector(['ng']).get('$rootScope');
    settingsLS = localStorage['ls.flynn_app.settings'];
    $http.get('config.json').success(function(data, status, headers, config) {
        var appConf;
        if (settingsLS) {
            var settings = JSON.parse(settingsLS);
            if (settings.appConfig) {
                appConf = settings.appConfig;
                // overwrite if needed
                if (data.overwrite) {
                    for (var configEntry in data.config) {
                        appConf[configEntry] = data.config[configEntry];
                    }
                    appConf.update = true;
                }
            } else {

                // without settings use template from config.json
                appConf = data.config;
            }
        } else {
            // without settings use template from config.json
            appConf = data.config;
        }
        app.constant('APP_CONFIG', appConf);
        app.constant('APP_INFO', data.info);
        if (config.dev === true) {
            navigator.notification.alert('Running in dev mode!', null, 'Info');
        }
        // bootstrap app:
        angular.bootstrap(document, ['flynnBookScannerApp']);

    }).error(function(data, status, headers, config) {
        console.error('Did not get valid config.json file.');
        navigator.notification.alert('Server did not show valid response.', null, 'Server Error');
    });
}

// on dev fire up event directly
if (isMobileDevice()) {
    document.addEventListener("deviceready", onDeviceReady, false);
} else {
    onDeviceReady();
}

/**
 * @ngdoc object
 * @name app declaration
 * @description  declares flynn app
 * @module flynnBookScannerApp
 */
var app = angular.module('flynnBookScannerApp', [
    'ui.bootstrap',
    'LocalStorageModule',
    'ngCookies',
    'ngResource',
    'ngSanitize',
    'ngRoute',
    'ngTouch',
    'ionic',
    'dbLog',
    'webWorkerPool'
]);

/**
 * @ngdoc filter
 * @name reverse
 * @module flynnBookScannerApp
 * @description Reverse list in order
 */
app.filter('reverse', function() {
    return function(items) {
        if (items) {
            return items.slice().reverse();
        } else {
            return;
        }
    };
});

/**
 * @ngdoc filter
 * @name bookFilter
 * @module flynnBookScannerApp
 * @description book filter
 */
app.filter('bookFilter', [function() {
    /**
     * Search in list for text
     */
    function searchList(pList, pSearchText) {
        for (var i = 0, len = pList.length; i < len; i++) {
            var entry = pList[i].toUpperCase();
            if (entry.indexOf(pSearchText) > -1) {
                return true;
            } else {
                return false;
            }
        }
    }
    return function(pBooks, pSearchText) {
        if (pSearchText) {
            var found = false,
                filtered,
                searchText = pSearchText.toUpperCase();
            for (var i = 0, len = pBooks.length; i < len; i++) {
                var book = pBooks[i],
                    found = false;
                if (book.value.volumeInfo.title.toUpperCase() === searchText) { // matches whole word
                    found = true;
                } else {
                    if (book.value.volumeInfo.title.toUpperCase().indexOf(searchText) > -1) {
                        found = true;
                    } else {
                        if (book.value.volumeInfo.subtitle && book.value.volumeInfo.subtitle.toUpperCase().indexOf(searchText) > -1) {
                            found = true;
                        } else {
                            if (book.value.volumeInfo.description && book.value.volumeInfo.description.toUpperCase().indexOf(searchText) > -1) {
                                found = true;
                            } else {
                                if (book.value.volumeInfo.publishedDate && book.value.volumeInfo.publishedDate.toString().toUpperCase().indexOf(searchText) > -1) {
                                    found = true;
                                } else {
                                    if (book.value.volumeInfo.authors && searchList(book.value.volumeInfo.authors, searchText)) {
                                        found = true;
                                    }
                                }
                            }
                        }
                    }
                }
                if (found) {
                    if (!filtered) {
                        filtered = [];
                    }
                    filtered.push(book);
                }
            };
            return filtered;
        } else {
            return pBooks;
        }
    };
}]);

app.filter('unsafe', function($sce) {
    return function(val) {
        return $sce.trustAsHtml(val);
    };
});

/**
 * @ngdoc function
 * @name constant
 * @module flynnBookScannerApp
 * @description  Set loading text
 */
app.constant('$ionicLoadingConfig', {
    template: '<ion-spinner></ion-spinner> <br> Loading '
});

/**
 * @ngdoc function
 * @name run
 * @module flynnBookScannerApp
 * @description  init ionic
 */
app.run(function($ionicPlatform) {
    $ionicPlatform.ready(function() {
        if (window.cordova && window.cordova.plugins.Keyboard) {
            cordova.plugins.Keyboard.hideKeyboardAccessoryBar(false);
        }
        if (window.StatusBar) {
            // org.apache.cordova.statusbar required
            StatusBar.styleDefault();
        }
    });
})

/**
 * @ngdoc function
 * @name config
 * @module flynnBookScannerApp
 * @description configure app
 */
app.config(function($urlRouterProvider, $provide, $compileProvider, $httpProvider, $stateProvider, $ionicConfigProvider, webWorkerPoolProvider, loggerProvider, APP_CONFIG) {
    // fix wp8 cordova errors
    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|ghttps?|ms-appx|x-wmapp0):/);
    // limit webworker threads
    webWorkerPoolProvider.workerUrl('scripts/webworker.renderImage.js');
    webWorkerPoolProvider.capacity(4);
    // places them at the bottom for all OS
    $ionicConfigProvider.tabs.position("bottom");
    // makes them all look the same across all OS
    $ionicConfigProvider.tabs.style("standard");
    // configure caching
    $ionicConfigProvider.views.maxCache(5);
    $ionicConfigProvider.templates.maxPrefetch(3);
    // configure logging
    configureLogging(loggerProvider, APP_CONFIG);
    $provide.decorator('$log', function($delegate) {
        return loggerProvider.$get($delegate);
    });
    // configure routes and states
    $stateProvider
        .state('app', {
            url: "/app",
            templateUrl: 'views/navbarView.html',
            controller: 'AppController'
        })
        .state('app.settings', {
            url: "/settings",
            views: {
                'menuContent': {
                    templateUrl: 'views/settingsView.html',
                    controller: 'SettingsController'
                }
            }
        })
        .state('app.book_add', {
            url: "/book/add",
            views: {
                'menuContent': {
                    templateUrl: 'views/addBookView.html',
                    controller: 'BookController'
                }
            }
        })
        .state('app.books', {
            url: "/books",
            views: {
                'menuContent': {
                    templateUrl: 'views/booksView.html',
                    controller: 'BooksController'
                }
            }
        })
        .state('app.book_show', {
            url: "/book/:bookId",
            views: {
                'menuContent': {
                    templateUrl: 'views/bookView.html',
                    controller: 'BookDetailsController'
                }
            }
        })
        .state('app.book_edit', {
            url: "/book/edit/:bookId",
            views: {
                'menuContent': {
                    templateUrl: 'views/editBookView.html',
                    controller: 'BookEditController'
                }
            }
        })
        .state('app.about', {
            url: "/about",
            views: {
                'menuContent': {
                    templateUrl: 'views/aboutView.html',
                    controller: 'AboutController'
                }
            }
        })
        .state('app.dev', {
            url: "/dev",
            views: {
                'menuContent': {
                    templateUrl: 'views/devView.html',
                    controller: 'DevController'
                }
            }
        });
    // if none of the above states are matched, use this as the fallback
    $urlRouterProvider.otherwise('/app');
    // configure http provider for cross-domain
    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common['X-Requested-With'];
});<commit_msg>Fix for search error<commit_after>/**
 * @ngdoc function
 * @name errorDialogClosed
 * @description Called after error dialog is closed.
 * @param {object} root scope
 * @param {object} current scope
 * @param {object} reference to log service
 */
function errorDialogClosed($rootScope, $scope, log) {
    log.debug("Error dialog closed.");
}

/**
 * @ngdoc function
 * @name showErrorDialog
 * @description Shows up the error dialog with the given error details
 * @param {object} root scope
 * @param {object} current scope
 * @param {object} reference to loading indicator
 * @param {object} reference to log service
 * @param {object} title to use
 * @param {object} error code to use
 * @param {object} message to display
 */
function showErrorDialog($rootScope, $scope, $ionicLoading, log, errorTitle, errorCode, errorDetails) {
    $ionicLoading.show();
    navigator.notification.alert(errorCode + "\n" + errorDetails, errorDialogClosed($rootScope, $scope, log), "Error - " + errorTitle);
    $ionicLoading.hide();
}

/**
 * @ngdoc function
 * @name openExternalLink
 * @param {string} url to open
 * @description open external url in browser app on mobile device, otherwise in a new tab on desktop device
 */
function openExternalLink(pUrl) {
        if (isMobileDevice()) {
            window.open(pUrl, '_system', 'location=yes');
        } else {
            window.open(pUrl, '_blank');
        }
    }
    /**
     * @ngdoc function
     * @name handleOpenURL
     * @description redirect app url handler to app
     */
function handleOpenURL(url) {
    setTimeout(function() {
        if (url.toLowerCase().search('flynnapp') > -1) {
            var type = url.substring(url.indexOf('//') + 2, url.indexOf('?')),
                params = url.substring(url.indexOf('?') + 1).split("&");
            // create JSON object
            var args = {};
            for (var i = 0, len = params.length; i < len; i++) {
                var paramData = params[i].split("=");
                args[paramData[0]] = paramData[1];
            }
            // get Angular scope from the known DOM element
            e = document.body;
            scope = angular.element(e).scope();
            // broadcast URL data
            switch (type) {
                case 'config':
                    scope.$root.$broadcast('settings.handleURL', args);
                    break;
            }
        }
    }, 0);
}

/**
 * @ngdoc function
 * @name isMobileDevice
 * @description returns true if running on a mobile device, otherwise false
 */
function isMobileDevice() {
    if (window.cordova) {
        return true;
    } else {
        return false; //this is the browser
    }
}

function configureLogging(loggerProvider, config) {
    if(config){
        loggerProvider.dbName('flynnDB_logs');
        loggerProvider.outputOnly(!config.dbLogging);
        loggerProvider.debugLogging(config.debug);
        if (config.debug) {
            // enable couchDB debug
            PouchDB.debug.enable('*');
        } else {
            PouchDB.debug.disable();
        }
        loggerProvider.traceLogging(config.trace);
    }
}

/**
 * @ngdoc function
 * @name onDeviceReady
 * @description Called after device is ready to use
 */
function onDeviceReady() {
    var $http = angular.injector(['ng']).get('$http'),
        $rootScope = angular.injector(['ng']).get('$rootScope');
    settingsLS = localStorage['ls.flynn_app.settings'];
    $http.get('config.json').success(function(data, status, headers, config) {
        var appConf;
        if (settingsLS) {
            var settings = JSON.parse(settingsLS);
            if (settings.appConfig) {
                appConf = settings.appConfig;
                // overwrite if needed
                if (data.overwrite) {
                    for (var configEntry in data.config) {
                        appConf[configEntry] = data.config[configEntry];
                    }
                    appConf.update = true;
                }
            } else {

                // without settings use template from config.json
                appConf = data.config;
            }
        } else {
            // without settings use template from config.json
            appConf = data.config;
        }
        app.constant('APP_CONFIG', appConf);
        app.constant('APP_INFO', data.info);
        if (config.dev === true) {
            navigator.notification.alert('Running in dev mode!', null, 'Info');
        }
        // bootstrap app:
        angular.bootstrap(document, ['flynnBookScannerApp']);

    }).error(function(data, status, headers, config) {
        console.error('Did not get valid config.json file.');
        navigator.notification.alert('Server did not show valid response.', null, 'Server Error');
    });
}

// on dev fire up event directly
if (isMobileDevice()) {
    document.addEventListener("deviceready", onDeviceReady, false);
} else {
    onDeviceReady();
}

/**
 * @ngdoc object
 * @name app declaration
 * @description  declares flynn app
 * @module flynnBookScannerApp
 */
var app = angular.module('flynnBookScannerApp', [
    'ui.bootstrap',
    'LocalStorageModule',
    'ngCookies',
    'ngResource',
    'ngSanitize',
    'ngRoute',
    'ngTouch',
    'ionic',
    'dbLog',
    'webWorkerPool'
]);

/**
 * @ngdoc filter
 * @name reverse
 * @module flynnBookScannerApp
 * @description Reverse list in order
 */
app.filter('reverse', function() {
    return function(items) {
        if (items) {
            return items.slice().reverse();
        } else {
            return;
        }
    };
});

/**
 * @ngdoc filter
 * @name bookFilter
 * @module flynnBookScannerApp
 * @description book filter
 */
app.filter('bookFilter', [function() {
    /**
     * Search in list for text
     */
    function searchList(pList, pSearchText) {
        for (var i = 0, len = pList.length; i < len; i++) {
            var entry = pList[i].toUpperCase();
            if (entry.indexOf(pSearchText) > -1) {
                return true;
            }
        }
    }
    return function(pBooks, pSearchText) {
        if (pSearchText) {
            var found = false,
                filtered,
                searchText = pSearchText.toUpperCase();
            for (var i = 0, len = pBooks.length; i < len; i++) {
                var book = pBooks[i],
                    found = false;
                if (book.value.volumeInfo.title.toUpperCase() === searchText) { // matches whole word
                    found = true;
                } else {
                    if (book.value.volumeInfo.title.toUpperCase().indexOf(searchText) > -1) {
                        found = true;
                    } else {
                        if (book.value.volumeInfo.subtitle && book.value.volumeInfo.subtitle.toUpperCase().indexOf(searchText) > -1) {
                            found = true;
                        } else {
                            if (book.value.volumeInfo.description && book.value.volumeInfo.description.toUpperCase().indexOf(searchText) > -1) {
                                found = true;
                            } else {
                                if (book.value.volumeInfo.publishedDate && book.value.volumeInfo.publishedDate.toString().toUpperCase().indexOf(searchText) > -1) {
                                    found = true;
                                } else {
                                    if (book.value.volumeInfo.authors && searchList(book.value.volumeInfo.authors, searchText)) {
                                        found = true;
                                    }
                                }
                            }
                        }
                    }
                }
                if (found) {
                    if (!filtered) {
                        filtered = [];
                    }
                    filtered.push(book);
                }
            };
            return filtered;
        } else {
            return pBooks;
        }
    };
}]);

app.filter('unsafe', function($sce) {
    return function(val) {
        return $sce.trustAsHtml(val);
    };
});

/**
 * @ngdoc function
 * @name constant
 * @module flynnBookScannerApp
 * @description  Set loading text
 */
app.constant('$ionicLoadingConfig', {
    template: '<ion-spinner></ion-spinner> <br> Loading '
});

/**
 * @ngdoc function
 * @name run
 * @module flynnBookScannerApp
 * @description  init ionic
 */
app.run(function($ionicPlatform) {
    $ionicPlatform.ready(function() {
        if (window.cordova && window.cordova.plugins.Keyboard) {
            cordova.plugins.Keyboard.hideKeyboardAccessoryBar(false);
        }
        if (window.StatusBar) {
            // org.apache.cordova.statusbar required
            StatusBar.styleDefault();
        }
    });
})

/**
 * @ngdoc function
 * @name config
 * @module flynnBookScannerApp
 * @description configure app
 */
app.config(function($urlRouterProvider, $provide, $compileProvider, $httpProvider, $stateProvider, $ionicConfigProvider, webWorkerPoolProvider, loggerProvider, APP_CONFIG) {
    // fix wp8 cordova errors
    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|ghttps?|ms-appx|x-wmapp0):/);
    // limit webworker threads
    webWorkerPoolProvider.workerUrl('scripts/webworker.renderImage.js');
    webWorkerPoolProvider.capacity(4);
    // places them at the bottom for all OS
    $ionicConfigProvider.tabs.position("bottom");
    // makes them all look the same across all OS
    $ionicConfigProvider.tabs.style("standard");
    // configure caching
    $ionicConfigProvider.views.maxCache(5);
    $ionicConfigProvider.templates.maxPrefetch(3);
    // configure logging
    configureLogging(loggerProvider, APP_CONFIG);
    $provide.decorator('$log', function($delegate) {
        return loggerProvider.$get($delegate);
    });
    // configure routes and states
    $stateProvider
        .state('app', {
            url: "/app",
            templateUrl: 'views/navbarView.html',
            controller: 'AppController'
        })
        .state('app.settings', {
            url: "/settings",
            views: {
                'menuContent': {
                    templateUrl: 'views/settingsView.html',
                    controller: 'SettingsController'
                }
            }
        })
        .state('app.book_add', {
            url: "/book/add",
            views: {
                'menuContent': {
                    templateUrl: 'views/addBookView.html',
                    controller: 'BookController'
                }
            }
        })
        .state('app.books', {
            url: "/books",
            views: {
                'menuContent': {
                    templateUrl: 'views/booksView.html',
                    controller: 'BooksController'
                }
            }
        })
        .state('app.book_show', {
            url: "/book/:bookId",
            views: {
                'menuContent': {
                    templateUrl: 'views/bookView.html',
                    controller: 'BookDetailsController'
                }
            }
        })
        .state('app.book_edit', {
            url: "/book/edit/:bookId",
            views: {
                'menuContent': {
                    templateUrl: 'views/editBookView.html',
                    controller: 'BookEditController'
                }
            }
        })
        .state('app.about', {
            url: "/about",
            views: {
                'menuContent': {
                    templateUrl: 'views/aboutView.html',
                    controller: 'AboutController'
                }
            }
        })
        .state('app.dev', {
            url: "/dev",
            views: {
                'menuContent': {
                    templateUrl: 'views/devView.html',
                    controller: 'DevController'
                }
            }
        });
    // if none of the above states are matched, use this as the fallback
    $urlRouterProvider.otherwise('/app');
    // configure http provider for cross-domain
    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common['X-Requested-With'];
});<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {browserHistory} from 'react-router';
import {findDOMNode} from 'react-dom';
import EventDispatcher from '../dispatcher/EventDispatcher';
import Dataset from '../models/Dataset';
import {omit, isEqual, isEmpty, isFunction, isPlainObject, isString, isArray, debounce} from 'lodash';
import DataHandler from '../utils/DataHandler';
import Registry from '../utils/Registry';
import {qFromParams, getOwnQueryParams, getFID, objToQueryString} from '../utils/utils';

export default class BaseComponent extends Component {

  constructor(props) {
    super(props);
    this.state = {
      data: [],
      dataset: null,
      queryObj: Object.assign({from: 0}, this.props.queryObj), // dataset query
      isFeching: false,
    };
  }

  componentWillMount() {
    // Register to all the actions
    EventDispatcher.register(this.onAction.bind(this));
    let q = '';

    if (this.props.location) {
      q = this.props.location.query;
    }

    let ownParams = getOwnQueryParams(q, this.props.cid, this.props.multi) || {};
    if (this.props.type == 'Autocomplete') console.log('own', ownParams);
    this.setState({ownParams: ownParams});
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this._resizeHandler);
  }

  componentWillReceiveProps() {
    this.applyOwnFilters();
  }

  /**
   * Allowable types:
   *   backend - uses an existing data backend (CSV, CartoDB, etc)
   *   global - uses a dataHandler function to extract data from globalData
   *   data - component is supplied data via props.data which it will use directly
   */
  getFetchType() {
    let type = 'global';
    if (this.props.fetchData && this.props.fetchData.type) {
      type = 'backend';
    } else if (this.props.data) {
      type = 'data';
    }
    return type;
  }

  addResizeListener() {
    this._resizeHandler = (e) => {
      let componentWidth = findDOMNode(this).getBoundingClientRect().width;
      this.setState({ componentWidth : componentWidth});
      this.onResize(e);
    }
    window.addEventListener('resize', this._resizeHandler);
  }

  componentDidMount(){
    // resize magic
    let componentWidth = findDOMNode(this).getBoundingClientRect().width;
    this.setState({ componentWidth : componentWidth});
    this.addResizeListener();
    this.fetchData();
    this.onResize();
  }

  // if global data
  componentDidUpdate(nextProps, nextState) {
    let globalDataEqual = _.isEqual(nextProps.globalData, this.props.globalData);
    // if globalData has been updated, we should run fetchData again
    if (!globalDataEqual) {
      this.fetchData();
    }
  }

  /**
   * NOTE:
   * fetchData and datahandling generally should ultimately be
   * handled OUTSIDE OF THE REACT-DASHBOARD APPLICATION
   * All components should be able to accept a know data format
   * and render from state.data
   *
   *  The fetchData logic here represents an interim model layer
   *  that will eventually be a redux store or even another app
   */
  fetchData() {
    let type = this.getFetchType();
    switch (type) {
      case 'backend':
        this.fetchBackend();
      case 'global':
        this.applyDataHandlers();
      case 'data':
        this.applyDataHandlers(this.props.data);
    }
  }

  /**
   * Use backends to fetch data and query the result
   */
  fetchBackend() {
    let dataset = new Dataset(omit(this.props.fetchData, 'type'));
    let queryObj = this.state.queryObj;
    this.setState({isFeching: true, dataset: dataset});
    dataset.fetch().then(() => {
      this.state.dataset.query(queryObj).then(queryRes => {
        this.applyDataHandlers(queryRes);
      }).catch(e => {
      });
    });
  }

  getFilters() {
		let filters;
  	if (Array.isArray(this.props.filters)) {
      filters = this.props.filters.map(filter => {
         filter.onChange = this.onFilter.bind(this, filter);
         return React.createElement(Registry.get('Filter'), filter);
      });
	  }
    return filters;
  }

  // onFilter sets new state
  // this triggers applyOwnFilters -> handleFiter
  // @@TODO fid should be array index
  onFilter(filter, e) {
    console.log('onFilter', filter, e);
    if (this.props.multi) return this.onFilterMulti(filter, e);

    let fid = 'fid'+filter.cid;
    let own = this.state.ownParams || {};
    let newQFragment = {};

    // asFilter components use state.data as their stored filter value
    if (this.props.asFilter) {
      newQFragment[this.props.cid] = e.value;
    } else {
      newQFragment[this.props.cid] = 'fid' + filter.cid + '__' + e.value;
    }

    const newQ = Object.assign(this.props.location.query, newQFragment);
    let newQueryString = decodeURIComponent(objToQueryString(newQ)).replace(/\[\]/g, '');
    browserHistory.push('/?' + newQueryString);

    // Update state with new filter values
    let z = {};

    z[fid] = e.value;
    let newState = Object.assign(own, z);
    this.setState({ownParams: newState});
  }

  // the big deal here is that multi fiter values are arrays
  // note that we overwrite data and ownParams with every update
  // @@TODO - Address multi type for component filters
  // @@TODO this shares code with onFilter - refactor?
  onFilterMulti(filter, e) {
    console.log('OFM', filter, e);
    let newQFragment = {};
    let z = [];
    let newQ, newQueryString, newState;

    e.forEach(_e => {
      if (this.props.asFilter) {
        if (newQFragment[this.props.cid]) {
          newQFragment[this.props.cid].push(_e.value)
        } else {
          newQFragment[this.props.cid] = [_e.value]
        }
      } else {
        return console.error('"Multi" type is not currently supported for component filters, only for components with "asFilter" prop, and really only for Autocomplete component ');
       // newQFragment[this.props.cid] = 'fid' + filter.cid + '__' + e.value;
      }
      z.push(_e.value);
    });
    console.log('Z',z);
    newQ = Object.assign(this.props.location.query, newQFragment);

    if (isEmpty(newQFragment)) delete newQ[this.props.cid];

    newQueryString = decodeURIComponent(objToQueryString(newQ)).replace(/\[\]/g, '');
    browserHistory.push('/?' + newQueryString);
    console.log('New Data', z);
    this.setState({ownParams: z, data: z});
  }

  // add datahandlers to stack
  handleFilter(filter, e) {
    let handlers = Object.assign([], filter.dataHandlers);
    this.setState({filterHandlers: handlers, filterEvent: e});
    setTimeout(() => {this.fetchData()},10); // @@TODO - this is obv. wrong but we need state
  }

  /**
   * @@TODO fid should be filter index
   * + parse fids from ownParams
   * + call onFilter with the appropriate data
   **/
  applyOwnFilters() {
    const ownParams = this.state.ownParams;
    let ownFilters = [];
    if (ownParams) {
      // multi filters
      if (isArray(ownParams)) {
        console.log('Apply - is array')
        this.handleFilter(this, {value: ownParams});
      } else {
       for (var p in ownParams) {
         let fid = getFID(p);
         let z = {};
         z.value = ownParams[p];
         if (fid && this.props.filters) {
           const filter = this.props.filters[fid];
           this.handleFilter(filter, z);
         } else if (this.props.asFilter) {
           this.handleFilter(this, z);
         }
       }
     }
    }
  }

  applyDataHandlers(data = [], handlers) {
    let _handlers = handlers || this.state.filterHandlers || this.props.dataHandlers;
    let _data = data.hits || data;
    let _total = data.total || data.length;
    _data = DataHandler.handle.call(this, _handlers, _data, this.getGlobalData(), this.state.filterEvent);
    // @@TODO this is a cheat for autocomplete to get the value
    if (isEmpty(_data) && this.state.filterEvent) _data = this.state.filterEvent.value;
    this.setState({data: _data, total: _total, isFeching: false});
  }

  emit(payload) {
    EventDispatcher.dispatch(payload);
  }

  getGlobalData() {
    return this.props.globalData || [];
  }

  /**
   * Abstract
   */

  onResize() {
    /* IMPLEMENT */
  }

  onAction() {
    /* IMPLEMENT */
  }

}
<commit_msg>Build query string on top of current path<commit_after>import React, { Component } from 'react';
import {browserHistory} from 'react-router';
import {findDOMNode} from 'react-dom';
import EventDispatcher from '../dispatcher/EventDispatcher';
import Dataset from '../models/Dataset';
import {omit, isEqual, isEmpty, isFunction, isPlainObject, isString, isArray, debounce} from 'lodash';
import DataHandler from '../utils/DataHandler';
import Registry from '../utils/Registry';
import {qFromParams, getOwnQueryParams, getFID, objToQueryString} from '../utils/paramRouting';

export default class BaseComponent extends Component {

  constructor(props) {
    super(props);
    this.state = {
      data: [],
      dataset: null,
      queryObj: Object.assign({from: 0}, this.props.queryObj), // dataset query
      isFeching: false,
    };
  }

  componentWillMount() {
    // Register to all the actions
    EventDispatcher.register(this.onAction.bind(this));
    let q = '';

    if (this.props.location) {
      q = this.props.location.query;
    }

    let ownParams = getOwnQueryParams(q, this.props.cid, this.props.multi) || {};
    if (this.props.type == 'Autocomplete') console.log('own', ownParams);
    this.setState({ownParams: ownParams});
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this._resizeHandler);
  }

  componentWillReceiveProps() {
    this.applyOwnFilters();
  }

  /**
   * Allowable types:
   *   backend - uses an existing data backend (CSV, CartoDB, etc)
   *   global - uses a dataHandler function to extract data from globalData
   *   data - component is supplied data via props.data which it will use directly
   */
  getFetchType() {
    let type = 'global';
    if (this.props.fetchData && this.props.fetchData.type) {
      type = 'backend';
    } else if (this.props.data) {
      type = 'data';
    }
    return type;
  }

  addResizeListener() {
    this._resizeHandler = (e) => {
      let componentWidth = findDOMNode(this).getBoundingClientRect().width;
      this.setState({ componentWidth : componentWidth});
      this.onResize(e);
    }
    window.addEventListener('resize', this._resizeHandler);
  }

  componentDidMount(){
    // resize magic
    let componentWidth = findDOMNode(this).getBoundingClientRect().width;
    this.setState({ componentWidth : componentWidth});
    this.addResizeListener();
    this.fetchData();
    this.onResize();
  }

  // if global data
  componentDidUpdate(nextProps, nextState) {
    let globalDataEqual = _.isEqual(nextProps.globalData, this.props.globalData);
    // if globalData has been updated, we should run fetchData again
    if (!globalDataEqual) {
      this.fetchData();
    }
  }

  /**
   * NOTE:
   * fetchData and datahandling generally should ultimately be
   * handled OUTSIDE OF THE REACT-DASHBOARD APPLICATION
   * All components should be able to accept a know data format
   * and render from state.data
   *
   *  The fetchData logic here represents an interim model layer
   *  that will eventually be a redux store or even another app
   */
  fetchData() {
    let type = this.getFetchType();
    switch (type) {
      case 'backend':
        this.fetchBackend();
      case 'global':
        this.applyDataHandlers();
      case 'data':
        this.applyDataHandlers(this.props.data);
    }
  }

  /**
   * Use backends to fetch data and query the result
   */
  fetchBackend() {
    let dataset = new Dataset(omit(this.props.fetchData, 'type'));
    let queryObj = this.state.queryObj;
    this.setState({isFeching: true, dataset: dataset});
    dataset.fetch().then(() => {
      this.state.dataset.query(queryObj).then(queryRes => {
        this.applyDataHandlers(queryRes);
      }).catch(e => {
      });
    });
  }

  getFilters() {
		let filters;
  	if (Array.isArray(this.props.filters)) {
      filters = this.props.filters.map(filter => {
         filter.onChange = this.onFilter.bind(this, filter);
         return React.createElement(Registry.get('Filter'), filter);
      });
	  }
    return filters;
  }

  // onFilter sets new state
  // this triggers applyOwnFilters -> handleFiter
  // @@TODO fid should be array index
  onFilter(filter, e) {
    console.log('onFilter', filter, e);
    if (this.props.multi) return this.onFilterMulti(filter, e);

    let fid = 'fid'+filter.cid;
    let own = this.state.ownParams || {};
    let newQFragment = {};

    // asFilter components use state.data as their stored filter value
    if (this.props.asFilter) {
      newQFragment[this.props.cid] = e.value;
    } else {
      newQFragment[this.props.cid] = 'fid' + filter.cid + '__' + e.value;
    }

    const newQ = Object.assign(this.props.location.query, newQFragment);
    let newQueryString = decodeURIComponent(objToQueryString(newQ)).replace(/\[\]/g, '');
    browserHistory.push(this.props.location.pathname + '?' + newQueryString);

    // Update state with new filter values
    let z = {};

    z[fid] = e.value;
    let newState = Object.assign(own, z);
    this.setState({ownParams: newState});
  }

  // the big deal here is that multi fiter values are arrays
  // note that we overwrite data and ownParams with every update
  // @@TODO - Address multi type for component filters
  // @@TODO this shares code with onFilter - refactor?
  onFilterMulti(filter, e) {
    console.log('OFM', filter, e);
    let newQFragment = {};
    let z = [];
    let newQ, newQueryString, newState;

    e.forEach(_e => {
      if (this.props.asFilter) {
        if (newQFragment[this.props.cid]) {
          newQFragment[this.props.cid].push(_e.value)
        } else {
          newQFragment[this.props.cid] = [_e.value]
        }
      } else {
        return console.error('"Multi" type is not currently supported for component filters, only for components with "asFilter" prop, and really only for Autocomplete component ');
       // newQFragment[this.props.cid] = 'fid' + filter.cid + '__' + e.value;
      }
      z.push(_e.value);
    });
    console.log('Z',z);
    newQ = Object.assign(this.props.location.query, newQFragment);

    if (isEmpty(newQFragment)) delete newQ[this.props.cid];

    newQueryString = decodeURIComponent(objToQueryString(newQ)).replace(/\[\]/g, '');
    browserHistory.push('/?' + newQueryString);
    console.log('New Data', z);
    this.setState({ownParams: z, data: z});
  }

  // add datahandlers to stack
  handleFilter(filter, e) {
    let handlers = Object.assign([], filter.dataHandlers);
    this.setState({filterHandlers: handlers, filterEvent: e});
    setTimeout(() => {this.fetchData()},10); // @@TODO - this is obv. wrong but we need state
  }

  /**
   * @@TODO fid should be filter index
   * + parse fids from ownParams
   * + call onFilter with the appropriate data
   **/
  applyOwnFilters() {
    const ownParams = this.state.ownParams;
    let ownFilters = [];
    if (ownParams) {
      // multi filters
      if (isArray(ownParams)) {
        console.log('Apply - is array')
        this.handleFilter(this, {value: ownParams});
      } else {
       for (var p in ownParams) {
         let fid = getFID(p);
         let z = {};
         z.value = ownParams[p];
         if (fid && this.props.filters) {
           const filter = this.props.filters[fid];
           this.handleFilter(filter, z);
         } else if (this.props.asFilter) {
           this.handleFilter(this, z);
         }
       }
     }
    }
  }

  applyDataHandlers(data = [], handlers) {
    let _handlers = handlers || this.state.filterHandlers || this.props.dataHandlers;
    let _data = data.hits || data;
    let _total = data.total || data.length;
    _data = DataHandler.handle.call(this, _handlers, _data, this.getGlobalData(), this.state.filterEvent);
    // @@TODO this is a cheat for autocomplete to get the value
    if (isEmpty(_data) && this.state.filterEvent) _data = this.state.filterEvent.value;
    this.setState({data: _data, total: _total, isFeching: false});
  }

  emit(payload) {
    EventDispatcher.dispatch(payload);
  }

  getGlobalData() {
    return this.props.globalData || [];
  }

  /**
   * Abstract
   */

  onResize() {
    /* IMPLEMENT */
  }

  onAction() {
    /* IMPLEMENT */
  }

}
<|endoftext|>


<commit_before>import Vue from 'vue';
import Vuex from 'vuex';
import createLogger from 'vuex/dist/logger';
import createPersistedState from 'vuex-persistedstate';

import * as actions from './actions';
import * as getters from './getters';
import { state, mutations } from './mutations';

Vue.use(Vuex);

const debug = process.env.NODE_ENV !== 'production';

const plugins = debug ? [createLogger()] : [];

export default new Vuex.Store({
  state,
  actions,
  getters,
  mutations,
  plugins: [
    createPersistedState({ storage: window.sessionStorage }),
    ...plugins
  ],
  strict: debug
});
<commit_msg>update sotre index.js<commit_after>import Vue from 'vue';
import Vuex from 'vuex';
import createLogger from 'vuex/dist/logger';
import createPersistedState from 'vuex-persistedstate';

import actions from './actions';
import getters from './getters';
import { state, mutations } from './mutations';

Vue.use(Vuex);

const debug = process.env.NODE_ENV !== 'production';

const plugins = debug ? [createLogger()] : [];

export default new Vuex.Store({
  state,
  actions,
  getters,
  mutations,
  plugins: [
    createPersistedState({ storage: window.sessionStorage }),
    ...plugins
  ],
  strict: debug
});
<|endoftext|>


<commit_before>import CSVExport from './CSVExport';
import flatten from 'flat';
import Form from './Item/Form';
import List from './List';
import MultiSearch from './MultiSearch';
import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';
import Table from '../Tickets/Table';

class Budgets extends Component {
	constructor() {
		super();

    const inputData = {
      items: [
        {
          id: 1,
          summary: "Klevu discovery & calls",
          phase: "dev/Klevu",
          feature: "Klevu",
          budgetHours: {
            column: "Discovery",
            value: 6,
          },
          descriptions: {
            workplan: [
              "Time for communication with Klevu.",
            ],
            budget: [],
            assumptions: [
              "Accounts for one onboarding call."
            ],
            exclusions: [],
          },
          tags: "klevu",
        },
        {
          id: 2,
          summary: "Install Klevu extension",
          phase: "dev/Klevu",
          feature: "Klevu",
          budgetHours: {
            column: 'Dev',
            value: 4,
          },
          descriptions: {
            workplan: [
              "Install Klevu extension using composer.",
            ],
            assumptions: [
              "Install extension once using code provided by Klevu."
            ],
          },
          tags: "klevu",
        },
        {
          id: 3,
          summary: "Configure Klevu flyout",
          phase: "dev/Klevu",
          feature: "Klevu",
          budgetHours: {
            column: 'Dev',
            value: 4,
          },
          descriptions: {
            workplan: [
              "Use Klevu control panel to choose between autocomplete and faceted.",
            ],
            assumptions: [
              "Use one of out of box options provided by Klevu (autocomplete or faceted) without customization.",
            ],
          },
          tags: "klevu",
        },
        {
          id: 10,
          summary: "Development meetings",
          phase: "dev",
          feature: "Build",
          budgetHours: {
            column: "Development",
            value: 20,
          },
          descriptions: {
            workplan: [],
            budget: [],
            assumptions: [],
            exclusions: [],
          },
          tags: "build",
        },
      ],
    };

    const defaultUserColumns = {
      summary: true,
      phase: true,
      feature: true,
      'budgetHours.column': true,
      'budgetHours.value': true,
      tags: true,
    };

    this.state = {
      items: inputData.items.map((item) => (
        item = {
          ...item,
          isVisible: true,
        })),
      filter: {
        field: 'summary',
        value: '',
      },
      query: {
        summary:'',
        phase:'',
        feature:'',
        'budgetHours.column': '',
        'budgetHours.value': '',
        tags: '',
      },
      userColumns: defaultUserColumns,
    };

    this.onAdd    = this.onAdd.bind(this);
    this.onDelete = this.onDelete.bind(this);
    this.onEdit   = this.onEdit.bind(this);
    this.onFilter = this.onFilter.bind(this);
    this.onFormSubmit = this.onFormSubmit.bind(this);
    this.renderList = this.renderList.bind(this);
    this.search   = this.search.bind(this);
    this.toggleColumn = this.toggleColumn.bind(this);
  }

  betterIsVisible(item, query){
    let result = true;

    for (const property in query) {
      if(!Array.isArray(query[property])){

        item[property] = (item[property] + '').toLowerCase();
        if(item[property].indexOf((query[property]).toLowerCase()) === -1){
          result = false;
        }
      } else {

        for (const value in query[property]) {
          if(item[property].indexOf(value) === -1){
            result = false;
          }
        }
      }
    }

    return result;
  }

  isVisible(item, field = this.state.filter.field, value = this.state.filter.value) {
    let flatItem = flatten(item);
    const itemValue = (flatItem[field] + '').toLowerCase();
    const filterValue = (value + '').toLowerCase();

    return itemValue.includes(filterValue);
  }

  onFilter({ field = this.state.filter.field, value = this.state.filter.value }) {
    this.setState({
      items: this.state.items.map((item) => ({
          ...item,
          isVisible: this.isVisible(item, field, value),
        })
      ),
      filter: {
        field,
        value,
      },
    });
  }

  onFormSubmit(item) {
    this.onAdd(item);
  }

  onAdd(item) {
    item = {
      ...item,
      isVisible: this.isVisible(item),
    }

    this.setState({
      items: [
        ...this.state.items,
        item,
      ],
    });
  }

  onCustomFilter(property){
    if(property === 'tags') {
      return (<MultiSearch
                items={this.state.items}
                query={this.state.query}
                onFilter={this.search}
              />);
    }
  }

  onDelete(itemId) {
    this.setState({
      items: this.state.items.filter(item => item.id !== itemId),
    });
  }

  onEdit(updatedItem) {
    updatedItem = {
      ...updatedItem,
      isVisible: this.isVisible(updatedItem),
    }

    this.setState({
      items: this.state.items.map(item => updatedItem.id === item.id ? updatedItem : item),
    })
  }

  renderList() {
     return (
       <List
         items={this.state.items}
         filter={this.state.filter}
         fields={this.props.fields}
         onFilter={this.onFilter}
         onEdit={this.onEdit}
         onDelete={this.onDelete}
       />
     );
   }

   search(query) {
     this.setState({
       items: this.state.items.map((item) => ({
         ...item,
         isVisible: this.betterIsVisible(item, query),
       })),
       query: query,
     });
   }

   toggleColumn(payload){
     const isVisible = this.state.userColumns[payload.columnName];
     this.setState({
       userColumns: {
         ...this.state.userColumns,
         [payload.columnName]: !isVisible,
       }
     });
   }

  render() {
    const columns = this.props.fields.map((field) => {
      const column = {
        property: field.name,
        header: {
          label: field.label,
        },
        filterType: field.filterType,
      };

      if (field.filterType === 'custom') {
        column.customFilter = () => {
          return this.onCustomFilter(field.name);
        }
      }

      return column;
    });

    let userColumns = columns.map((field) => field = field.property);
    userColumns = userColumns.filter((column) => this.state.userColumns[column]);

    return (

Budget Tool

            <Form
              onSubmit={this.onFormSubmit}
              fields={this.props.fields}
            />

View Selection

                <Link to={this.props.match.url + '/list'}>List</Link>

                <Link to={this.props.match.url + '/table'}>Table</Link>

            <Route
              exact path={this.props.match.url}
              render={this.renderList}
            />
            <Route
              path={this.props.match.url + '/list'}
              render={this.renderList}
            />
            <Route
              path={this.props.match.url + '/table'}
              render={() => (

                  <CSVExport items={this.state.items} />

              )}
            />

    );
  }
}

Budgets.defaultProps = {
  fields: [
    {
      filterType: 'textfield',
      name: 'summary',
      label: 'Summary',
      type: 'text',
      required: true,
    },
    {
      filterType: 'textfield',
      name: 'phase',
      label: 'Phase',
      type: 'text',
      required: true,
    },
    {
      filterType: 'textfield',
      name: 'feature',
      label: 'Feature',
      type: 'text',
    },
    {
      filterType: 'textfield',
      name: 'budgetHours.column',
      label: 'Team',
      type: 'text',
    },
    {
      filterType: 'textfield',
      name: 'budgetHours.value',
      label: 'Hours',
      type: 'number',
    },
    {
      filterType: 'none',
      name: 'descriptions.workplan',
      label: 'Workplan description',
      type: 'text',
      required: true,
    },
    {
      filterType: 'none',
      name: 'descriptions.budget',
      label: 'Budget description',
      type: 'text',
    },
    {
      filterType: 'none',
      name: 'descriptions.assumptions',
      label: 'Assumptions',
      type: 'text',
    },
    {
      filterType: 'none',
      name: 'descriptions.exclusions',
      label: 'Exclusions',
      type: 'text',
    },
    {
      filterType: 'custom',
      name: 'tags',
      label: 'Tags',
      type: 'text',
      required: true,
    },
  ]
};

export default Budgets;
<commit_msg>Table displays items stored in Redux Store Add Item adds an item to the Redux Store<commit_after>import * as BudgetsActions from '../../actions/budgets';
import CSVExport from './CSVExport';
import flatten from 'flat';
import Form from './Item/Form';
import List from './List';
import MultiSearch from './MultiSearch';
import React, { Component } from 'react';
import Table from '../Tickets/Table';
import { connect } from 'react-redux';
import { Route, Link } from 'react-router-dom';

class Budgets extends Component {
	constructor() {
		super();

    const inputData = {
      items: [
        {
          id: 1,
          summary: "Klevu discovery & calls",
          phase: "dev/Klevu",
          feature: "Klevu",
          budgetHours: {
            column: "Discovery",
            value: 6,
          },
          descriptions: {
            workplan: [
              "Time for communication with Klevu.",
            ],
            budget: [],
            assumptions: [
              "Accounts for one onboarding call."
            ],
            exclusions: [],
          },
          tags: "klevu",
        },
        {
          id: 2,
          summary: "Install Klevu extension",
          phase: "dev/Klevu",
          feature: "Klevu",
          budgetHours: {
            column: 'Dev',
            value: 4,
          },
          descriptions: {
            workplan: [
              "Install Klevu extension using composer.",
            ],
            assumptions: [
              "Install extension once using code provided by Klevu."
            ],
          },
          tags: "klevu",
        },
        {
          id: 3,
          summary: "Configure Klevu flyout",
          phase: "dev/Klevu",
          feature: "Klevu",
          budgetHours: {
            column: 'Dev',
            value: 4,
          },
          descriptions: {
            workplan: [
              "Use Klevu control panel to choose between autocomplete and faceted.",
            ],
            assumptions: [
              "Use one of out of box options provided by Klevu (autocomplete or faceted) without customization.",
            ],
          },
          tags: "klevu",
        },
        {
          id: 10,
          summary: "Development meetings",
          phase: "dev",
          feature: "Build",
          budgetHours: {
            column: "Development",
            value: 20,
          },
          descriptions: {
            workplan: [],
            budget: [],
            assumptions: [],
            exclusions: [],
          },
          tags: "build",
        },
      ],
    };

    const defaultUserColumns = {
      summary: true,
      phase: true,
      feature: true,
      'budgetHours.column': true,
      'budgetHours.value': true,
      tags: true,
    };

    this.state = {
      items: inputData.items.map((item) => (
        item = {
          ...item,
          isVisible: true,
        })),
      filter: {
        field: 'summary',
        value: '',
      },
      query: {
        summary:'',
        phase:'',
        feature:'',
        'budgetHours.column': '',
        'budgetHours.value': '',
        tags: '',
      },
      userColumns: defaultUserColumns,
    };

    this.onAdd    = this.onAdd.bind(this);
    this.onDelete = this.onDelete.bind(this);
    this.onEdit   = this.onEdit.bind(this);
    this.onFilter = this.onFilter.bind(this);
    this.onFormSubmit = this.onFormSubmit.bind(this);
    this.renderList = this.renderList.bind(this);
    this.search   = this.search.bind(this);
    this.toggleColumn = this.toggleColumn.bind(this);
  }

  betterIsVisible(item, query){
    let result = true;

    for (const property in query) {
      if(!Array.isArray(query[property])){

        item[property] = (item[property] + '').toLowerCase();
        if(item[property].indexOf((query[property]).toLowerCase()) === -1){
          result = false;
        }
      } else {

        for (const value in query[property]) {
          if(item[property].indexOf(value) === -1){
            result = false;
          }
        }
      }
    }

    return result;
  }

  isVisible(item, field = this.state.filter.field, value = this.state.filter.value) {
    let flatItem = flatten(item);
    const itemValue = (flatItem[field] + '').toLowerCase();
    const filterValue = (value + '').toLowerCase();

    return itemValue.includes(filterValue);
  }

  onFilter({ field = this.state.filter.field, value = this.state.filter.value }) {
    this.setState({
      items: this.state.items.map((item) => ({
          ...item,
          isVisible: this.isVisible(item, field, value),
        })
      ),
      filter: {
        field,
        value,
      },
    });
  }

  onFormSubmit(item) {
    this.onAdd(item);
  }

  onAdd(item) {
    item = {
      ...item,
      isVisible: this.isVisible(item),
    }

    this.setState({
      items: [
        ...this.state.items,
        item,
      ],
    });

    this.props.dispatch(BudgetsActions.addItem(item));
  }

  onCustomFilter(property){
    if(property === 'tags') {
      return (<MultiSearch
                items={this.state.items}
                query={this.state.query}
                onFilter={this.search}
              />);
    }
  }

  onDelete(itemId) {
    this.setState({
      items: this.state.items.filter(item => item.id !== itemId),
    });
  }

  onEdit(updatedItem) {
    updatedItem = {
      ...updatedItem,
      isVisible: this.isVisible(updatedItem),
    }

    this.setState({
      items: this.state.items.map(item => updatedItem.id === item.id ? updatedItem : item),
    })
  }

  renderList() {
     return (
       <List
         items={this.props.items}
         filter={this.state.filter}
         fields={this.props.fields}
         onFilter={this.onFilter}
         onEdit={this.onEdit}
         onDelete={this.onDelete}
       />
     );
   }

   search(query) {
     this.setState({
       items: this.state.items.map((item) => ({
         ...item,
         isVisible: this.betterIsVisible(item, query),
       })),
       query: query,
     });
   }

   toggleColumn(payload){
     const isVisible = this.state.userColumns[payload.columnName];
     this.setState({
       userColumns: {
         ...this.state.userColumns,
         [payload.columnName]: !isVisible,
       }
     });
   }

  render() {
    const columns = this.props.fields.map((field) => {
      const column = {
        property: field.name,
        header: {
          label: field.label,
        },
        filterType: field.filterType,
      };

      if (field.filterType === 'custom') {
        column.customFilter = () => {
          return this.onCustomFilter(field.name);
        }
      }

      return column;
    });

    let userColumns = columns.map((field) => field = field.property);
    userColumns = userColumns.filter((column) => this.state.userColumns[column]);

    return (

Budget Tool

            <Form
              onSubmit={this.onFormSubmit}
              fields={this.props.fields}
            />

View Selection

                <Link to={this.props.match.url + '/list'}>List</Link>

                <Link to={this.props.match.url + '/table'}>Table</Link>

            <Route
              exact path={this.props.match.url}
              render={this.renderList}
            />
            <Route
              path={this.props.match.url + '/list'}
              render={this.renderList}
            />
            <Route
              path={this.props.match.url + '/table'}
              render={() => (

                  <CSVExport items={this.state.items} />

              )}
            />

    );
  }
}

const mapStateToProps = state => ({
  items: state.budgets.items,
  fields: state.budgets.fields,
})

export default connect(mapStateToProps)(Budgets);
<|endoftext|>


<commit_before>import React, { Component } from 'react';

export default class NoWeb3 extends Component {
  render () {
    return (

Wallet not found, or access to Ethereum account not granted

                If a Wallet extension is installed on your web browser, please verify that the access to Ethereum account has been granted and is available for the corresponding domain.
                . Check Token Wizard GitHub for the instruction.

    );
  }
}
<commit_msg>Remove double dots<commit_after>import React, { Component } from 'react';

export default class NoWeb3 extends Component {
  render () {
    return (

Wallet not found, or access to Ethereum account not granted

                If a Wallet extension is installed on your web browser, please verify that the access to Ethereum account has been granted and is available for the corresponding domain.
                Check Token Wizard GitHub for the instruction.

    );
  }
}
<|endoftext|>


<commit_before>/**
 * Elements that can (naturally) receive keyboard focus.
 */
const FocusElements = [
  'a[href]',
  'area[href]',
  'input:not([disabled])',
  'select:not([disabled])',
  'textarea:not([disabled])',
  'button:not([disabled])',
  'iframe',
  'object',
  'embed',
  '*[tabindex]',
  '*[contenteditable]'
].join(', ');

module.exports = FocusElements;
<commit_msg>FocusElements now returns an array of elements<commit_after>/**
 * Elements that can (naturally) receive keyboard focus.
 */
const DOM = require('DOM');
const FocusElements = function (context) {
  let selector = [
    'a[href]',
    'area[href]',
    'input',
    'select',
    'textarea',
    'button',
    'iframe',
    'object',
    'embed',
    '*[tabindex]',
    '*[contenteditable]'
  ].join(', ');
  let elements = DOM
    .scry(selector, context)
    .filter((element) => !element.disabled);
  return elements;
};

module.exports = FocusElements;
<|endoftext|>


<commit_before>import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Link } from 'react-router';

import { closeMessage } from '../../store/uiReducer';

import Alert from '../Alert/Alert';
import Loader from '../Loader';
import './Header.scss';

export class Header extends React.Component {
  constructor (props) {
    super(props);
    this.state = { showAlert: false };
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.message) {
      this.setState({ showAlert: nextProps.message.active });
    }
  }

  render () {
    const { isFetching, message, closeMessage } = this.props;
    let alertMessage = null;
    if (this.state.showAlert === true) {
      alertMessage = (
        <Alert
          message={message.text}
          style={(message.success ? 'alert-success' : 'alert-danger')}
          close={closeMessage}
        />
      );
      setTimeout(closeMessage, 5000);
    }

    return (

          <Link to='/' className='brand-title navbar-brand'>Tiedonohjausjärjestelmä Alpha v0.1.4</Link>

        {isFetching &&
        <Loader />
        }
        {message.text !== '' && alertMessage}

    );
  }
};

Header.propTypes = {
  closeMessage: React.PropTypes.func,
  isFetching: React.PropTypes.bool,
  message: React.PropTypes.object
};

const mapStateToProps = (state) => {
  return {
    message: state.ui.message,
    isFetching: state.ui.isFetching || state.navigation.isFetching || state.selectedTOS.isFetching
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    closeMessage: bindActionCreators(closeMessage, dispatch)
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Header);
<commit_msg>Change evaluation to target state<commit_after>import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Link } from 'react-router';

import { closeMessage } from '../../store/uiReducer';

import Alert from '../Alert/Alert';
import Loader from '../Loader';
import './Header.scss';

export class Header extends React.Component {
  constructor (props) {
    super(props);
    this.state = { showAlert: false };
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.message) {
      this.setState({ showAlert: nextProps.message.active });
    }
  }

  render () {
    const { isFetching, message, closeMessage } = this.props;
    let alertMessage = null;
    if (this.state.showAlert === true) {
      alertMessage = (
        <Alert
          message={message.text}
          style={(message.success ? 'alert-success' : 'alert-danger')}
          close={closeMessage}
        />
      );
      setTimeout(closeMessage, 5000);
    }

    return (

          <Link to='/' className='brand-title navbar-brand'>Tiedonohjausjärjestelmä Alpha v0.1.4</Link>

        {isFetching &&
        <Loader />
        }
        {this.state.showAlert && alertMessage}

    );
  }
};

Header.propTypes = {
  closeMessage: React.PropTypes.func,
  isFetching: React.PropTypes.bool,
  message: React.PropTypes.object
};

const mapStateToProps = (state) => {
  return {
    message: state.ui.message,
    isFetching: state.ui.isFetching || state.navigation.isFetching || state.selectedTOS.isFetching
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    closeMessage: bindActionCreators(closeMessage, dispatch)
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Header);
<|endoftext|>


<commit_before>import React from 'react'
import { api } from '../../data'
import { Field, Submit } from '../components/Forms'
import { withFormik } from 'formik'
import yup from 'yup'
import fetch from 'unfetch'
import { withRouter } from 'react-static'

const statusMessage = status => (
  status
    ? {
      success: 'Logging in!',
      error: 'Something went wrong'
    }[status]
    : 'Login'
)
const InnerForm = ({
  values,
  errors,
  touched,
  handleChange,
  handleBlur,
  handleSubmit,
  isSubmitting,
  status
}) => (
  <form onSubmit={handleSubmit}>
    <Field
      label="We just sent a login code to that address. Once you get it, please check your inbox."
      name="login_code"
      p="Paste your login code"
      value={values.loginCode}
      onChange={handleChange}
      onBlur={handleBlur}
      error={touched.loginCode && errors.loginCode}
    />
    <Submit
      disabled={isSubmitting}
      onClick={handleSubmit}
      value={statusMessage(status)}
      mt="6px"
    />
  </form>
)

const LoginCodeForm = withFormik({
  mapPropsToValues: ({ params }) => ({ ...params }),
  validationSchema: yup.object().shape({
    login_code: yup
      .string()
      .required('required')
  }),
  handleSubmit: (data, { props, setSubmitting, setErrors, setStatus }) => {
    fetch(`${api}/v1/applicants/${props.id}/exchange_login_code`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    })
      .then(res => {
        if (res.ok) {
          return res.json()
        } else {
          throw res.statusText
        }
      })
      .then(json => {
        window.localStorage.setItem('authToken', json.auth_token)
        setStatus('success')
        props.history.push('/apply')
      })
      .catch(e => {
        console.error(e)
        setSubmitting(false)
        setStatus('error')
      })
  },
  displayName: 'LoginCodeForm'
})(InnerForm)

export default withRouter(LoginCodeForm)
<commit_msg>Simplify login code message<commit_after>import React from 'react'
import { api } from '../../data'
import { Field, Submit } from '../components/Forms'
import { withFormik } from 'formik'
import yup from 'yup'
import fetch from 'unfetch'
import { withRouter } from 'react-static'

const statusMessage = status => (
  status
    ? {
      success: 'Logging in!',
      error: 'Something went wrong'
    }[status]
    : 'Login'
)
const InnerForm = ({
  values,
  errors,
  touched,
  handleChange,
  handleBlur,
  handleSubmit,
  isSubmitting,
  status
}) => (
  <form onSubmit={handleSubmit}>
    <Field
      label="We just sent a login code to that address. Please check your inbox."
      name="login_code"
      p="Paste your login code"
      value={values.loginCode}
      onChange={handleChange}
      onBlur={handleBlur}
      error={touched.loginCode && errors.loginCode}
    />
    <Submit
      disabled={isSubmitting}
      onClick={handleSubmit}
      value={statusMessage(status)}
      mt="6px"
    />
  </form>
)

const LoginCodeForm = withFormik({
  mapPropsToValues: ({ params }) => ({ ...params }),
  validationSchema: yup.object().shape({
    login_code: yup
      .string()
      .required('required')
  }),
  handleSubmit: (data, { props, setSubmitting, setErrors, setStatus }) => {
    fetch(`${api}/v1/applicants/${props.id}/exchange_login_code`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    })
      .then(res => {
        if (res.ok) {
          return res.json()
        } else {
          throw res.statusText
        }
      })
      .then(json => {
        window.localStorage.setItem('authToken', json.auth_token)
        setStatus('success')
        props.history.push('/apply')
      })
      .catch(e => {
        console.error(e)
        setSubmitting(false)
        setStatus('error')
      })
  },
  displayName: 'LoginCodeForm'
})(InnerForm)

export default withRouter(LoginCodeForm)
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Cell, Card, CardTitle, CardActions, Icon } from 'react-mdl';

import '../css/PhotoGridItem.css';

class PhotoGridItem extends Component {

  render() {

    const { imageData, handleSaveToDropbox } = this.props;
    const smallimageUrl = `${imageData.urls.small}`;
    const downloadUrl = `${imageData.links.download}`;
    const dimension = `${imageData.width} x ${imageData.height}`;
    const imageId = `${imageData.id}`;
    const backgroundStyle = {
      background: `url(${smallimageUrl}) center center no-repeat`
    };

    return (
      <Cell col={3}>
        <Card className="photo-grid__item" shadow={0} style={backgroundStyle}>
          <CardTitle expand />
          <CardActions style={{height: '52px', padding: '16px', background: 'rgba(0,0,0,0.2)', display: 'flex', boxSizing: 'border-box', alignItems: 'center', color: '#fff'}}>

              {dimension}

                <Icon name="file_download" />

                <Icon
                  name="cloud_download"
                  className="save-to-dropbox"
                  onClick={() => handleSaveToDropbox(imageId, downloadUrl) }
                />

          </CardActions>
        </Card>

      </Cell>
    );
  }
}

PhotoGridItem.propTypes = {
  imageData: PropTypes.object.isRequired,
  handleSaveToDropbox: PropTypes.func.isRequired
}

export default PhotoGridItem;
<commit_msg>Add fallback background color for photos<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Cell, Card, CardTitle, CardActions, Icon } from 'react-mdl';

import '../css/PhotoGridItem.css';

class PhotoGridItem extends Component {

  render() {

    const { imageData, handleSaveToDropbox } = this.props;
    const smallimageUrl = `${imageData.urls.small}`;
    const downloadUrl = `${imageData.links.download}`;
    const dimension = `${imageData.width} x ${imageData.height}`;
    const imageId = `${imageData.id}`;
    const backgroundStyle = {
      background: `url(${smallimageUrl}) #efefef center center no-repeat`
    };

    return (
      <Cell col={3}>
        <Card className="photo-grid__item" shadow={0} style={backgroundStyle}>
          <CardTitle expand />
          <CardActions style={{height: '52px', padding: '16px', background: 'rgba(0,0,0,0.2)', display: 'flex', boxSizing: 'border-box', alignItems: 'center', color: '#fff', justifyContent: 'space-between'}}>

              {dimension}

                <Icon name="file_download" />

                <Icon
                  name="cloud_download"
                  className="save-to-dropbox"
                  onClick={() => handleSaveToDropbox(imageId, downloadUrl) }
                />

          </CardActions>
        </Card>

      </Cell>
    );
  }
}

PhotoGridItem.propTypes = {
  imageData: PropTypes.object.isRequired,
  handleSaveToDropbox: PropTypes.func.isRequired
}

export default PhotoGridItem;
<|endoftext|>


<commit_before>const {
  Mongoose,
  Types: { ObjectId },
} = require('mongoose');
const inflection = require('inflection');
const { escapeRegExp } = require('@keystonejs/utils');
const {
  BaseKeystoneAdapter,
  BaseListAdapter,
  BaseFieldAdapter,
} = require('@keystonejs/core/adapters');

const debugMongoose = () => !!process.env.DEBUG_MONGOOSE;

function getMongoURI({ dbName, name }) {
  return (
    process.env.MONGO_URI ||
    process.env.MONGO_URL ||
    process.env.MONGODB_URI ||
    process.env.MONGODB_URL ||
    `mongodb://localhost/${dbName || inflection.dasherize(name).toLowerCase()}`
  );
}

function getIdQueryConditions(args) {
  const conditions = [];
  if (!args) {
    return conditions;
  }
  // id is how it looks in the schema
  if ('id' in args) {
    // _id is how it looks in the MongoDB
    conditions.push({ _id: { $eq: ObjectId(args.id) } });
  }
  // id is how it looks in the schema
  if ('id_not' in args) {
    // _id is how it looks in the MongoDB
    conditions.push({ _id: { $ne: ObjectId(args.id_not) } });
  }
  if ('id_in' in args) {
    conditions.push({ _id: { $in: args.id_in.map(id => ObjectId(id)) } });
  }
  if ('id_not_in' in args) {
    conditions.push({
      _id: { $not: { $in: args.id_not_in.map(id => ObjectId(id)) } },
    });
  }
  return conditions;
}

class MongooseAdapter extends BaseKeystoneAdapter {
  constructor() {
    super(...arguments);

    this.name = this.name || 'mongoose';

    this.mongoose = new Mongoose();
    if (debugMongoose()) {
      this.mongoose.set('debug', true);
    }
    this.listAdapterClass =
      this.listAdapterClass || this.defaultListAdapterClass;
  }

  connect(to, config) {
    const { name, dbName, ...adapterConnectOptions } = config;
    const uri = to || getMongoURI({ name, dbName });
    this.mongoose.connect(
      uri,
      { ...adapterConnectOptions }
    );
    const db = this.mongoose.connection;
    db.on('error', console.error.bind(console, 'Mongoose connection error'));
    db.once('open', () => console.log('Connection success'));
  }

  close() {
    return new Promise((resolve, reject) => {
      this.mongoose.connection.close(true, (error) => {
        if (error) {
          return reject(error);
        }
        resolve(this.mongoose.disconnect());
      });
    });
  }

  dropDatabase() {
    // This will completely drop the backing database. Use wisely.
    return this.mongoose.connection.dropDatabase();
  }
}

class MongooseListAdapter extends BaseListAdapter {
  constructor(key, parentAdapter, config) {
    super(...arguments);

    const { mongoose } = parentAdapter;
    const { configureMongooseSchema, mongooseSchemaOptions } = config;

    this.mongoose = mongoose;
    this.configureMongooseSchema = configureMongooseSchema;
    this.schema = new mongoose.Schema({}, mongooseSchemaOptions);

    // Need to call prepareModel() once all fields have registered.
    this.model = null;
  }

  prepareFieldAdapter(fieldAdapter) {
    fieldAdapter.addToMongooseSchema(this.schema, this.mongoose);
  }

  prepareModel() {
    if (this.configureMongooseSchema) {
      this.configureMongooseSchema(this.schema, { mongoose: this.mongoose });
    }
    this.model = this.mongoose.model(this.key, this.schema);
  }

  create(data) {
    return this.model.create(data);
  }

  delete(id) {
    return this.model.findByIdAndRemove(id);
  }

  update(id, update, options) {
    return this.model.findByIdAndUpdate(id, update, options);
  }

  findAll() {
    return this.model.find();
  }

  findById(id) {
    return this.model.findById(id);
  }

  find(condition) {
    return this.model.find(condition);
  }

  findOne(condition) {
    return this.model.findOne(condition);
  }

  itemsQueryConditions(args, depthGuard = 0) {
    if (!args) {
      return [];
    }

    // TODO: can depthGuard be an instance variable, to track the recursion
    // depth instead of passing it through to the individual fields and back
    // again?
    if (depthGuard > 1) {
      throw new Error(
        'Nesting where args deeper than 1 level is not currently supported'
      );
    }

    return this.fieldAdapters.reduce(
      (conds, fieldAdapater) => {
        const fieldConditions = fieldAdapater.getQueryConditions(
          args,
          this,
          depthGuard + 1
        );

        if (fieldConditions && !Array.isArray(fieldConditions)) {
          console.warn(
            `${this.key}.${fieldAdapater.path} (${
              fieldAdapater.fieldName
            }) returned a non-array for .getQueryConditions(). This is probably a mistake. Ignoring.`
          );
          return conds;
        }

        // Nothing to do
        if (!fieldConditions || !fieldConditions.length) {
          return conds;
        }

        return [
          ...conds,
          ...fieldConditions.map(condition => {
            if (condition && condition.$isComplexStage) {
              return condition;
            }
            return { [fieldAdapater.path]: condition };
          }),
        ];
      },
      // Special case for `_id` where it presents as `id` in the graphQL schema,
      // and isn't a field type
      getIdQueryConditions(args)
    );
  }
  itemsQuery(args, { meta = false } = {}) {
    const conditions = this.itemsQueryConditions(args.where);

    const pipeline = [];
    const postAggregateMutation = [];

    // TODO: Order isn't important. Might as well put all the simple `$match`s
    // first, and complex ones last.
    // TODO: Change this to a `for...of` loop
    let iterator = conditions[Symbol.iterator]();
    let itr = iterator.next();
    while (!itr.done) {
      // Gather up all the simple matches
      let simpleMatches = [];
      while (!itr.done && !itr.value.$isComplexStage) {
        simpleMatches.push(itr.value);
        itr = iterator.next();
      }

      if (simpleMatches.length) {
        pipeline.push({
          $match: {
            $and: simpleMatches,
          },
        });
      }

      // Push all the complex stages onto the pipeline as-is
      while (!itr.done && itr.value.$isComplexStage) {
        pipeline.push(...itr.value.pipeline);
        if (itr.value.mutator) {
          postAggregateMutation.push(itr.value.mutator);
        }
        itr = iterator.next();
      }
    }

    if (args.search) {
      // TODO: Implement configurable search fields for lists
      pipeline.push({
        $match: {
          name: new RegExp(`${escapeRegExp(args.search)}`, 'i'),
        },
      });
    }

    if (args.orderBy) {
      const [orderField, orderDirection] = args.orderBy.split('_');

      pipeline.push({
        $sort: {
          [orderField]: orderDirection === 'ASC' ? 1 : -1,
        },
      });
    }

    if (args.skip < Infinity && args.skip > 0) {
      pipeline.push({
        $skip: args.skip,
      });
    }

    if (args.first < Infinity && args.first > 0) {
      pipeline.push({
        $limit: args.first,
      });
    }

    if (meta) {
      pipeline.push({
        $count: 'count',
      });
    }

    if (!pipeline.length) {
      return this.findAll();
    }

    // Map _id => id
    // Normally, mongoose would do this for us, but because we're breaking out
    // and going straight Mongo, gotta do it ourselves.
    pipeline.push({
      $addFields: {
        id: '$_id',
      },
    });
    return this.model
      .aggregate(pipeline)
      .exec()
      .then(data => {
        if (meta) {
          return data[0];
        }

        return (
          data
            .map((item, index, list) =>
              // Iterate over all the mutations
              postAggregateMutation.reduce(
                // And pass through the result to the following mutator
                (mutatedItem, mutation) => mutation(mutatedItem, index, list),
                // Starting at the original item
                item
              )
            )
            // If anything gets removed, we clear it out here
            .filter(Boolean)
        );
      });
  }
  itemsQueryMeta(args) {
    return this.itemsQuery(args, { meta: true });
  }
}

class MongooseFieldAdapter extends BaseFieldAdapter {
  addToMongooseSchema() {
    throw new Error(
      `Field type [${this.fieldName}] does not implement addToMongooseSchema()`
    );
  }

  getQueryConditions() {
    return [];
  }
}

MongooseAdapter.defaultListAdapterClass = MongooseListAdapter;

module.exports = {
  MongooseAdapter,
  MongooseListAdapter,
  MongooseFieldAdapter,
};
<commit_msg>Prettier<commit_after>const {
  Mongoose,
  Types: { ObjectId },
} = require('mongoose');
const inflection = require('inflection');
const { escapeRegExp } = require('@keystonejs/utils');
const {
  BaseKeystoneAdapter,
  BaseListAdapter,
  BaseFieldAdapter,
} = require('@keystonejs/core/adapters');

const debugMongoose = () => !!process.env.DEBUG_MONGOOSE;

function getMongoURI({ dbName, name }) {
  return (
    process.env.MONGO_URI ||
    process.env.MONGO_URL ||
    process.env.MONGODB_URI ||
    process.env.MONGODB_URL ||
    `mongodb://localhost/${dbName || inflection.dasherize(name).toLowerCase()}`
  );
}

function getIdQueryConditions(args) {
  const conditions = [];
  if (!args) {
    return conditions;
  }
  // id is how it looks in the schema
  if ('id' in args) {
    // _id is how it looks in the MongoDB
    conditions.push({ _id: { $eq: ObjectId(args.id) } });
  }
  // id is how it looks in the schema
  if ('id_not' in args) {
    // _id is how it looks in the MongoDB
    conditions.push({ _id: { $ne: ObjectId(args.id_not) } });
  }
  if ('id_in' in args) {
    conditions.push({ _id: { $in: args.id_in.map(id => ObjectId(id)) } });
  }
  if ('id_not_in' in args) {
    conditions.push({
      _id: { $not: { $in: args.id_not_in.map(id => ObjectId(id)) } },
    });
  }
  return conditions;
}

class MongooseAdapter extends BaseKeystoneAdapter {
  constructor() {
    super(...arguments);

    this.name = this.name || 'mongoose';

    this.mongoose = new Mongoose();
    if (debugMongoose()) {
      this.mongoose.set('debug', true);
    }
    this.listAdapterClass =
      this.listAdapterClass || this.defaultListAdapterClass;
  }

  connect(to, config) {
    const { name, dbName, ...adapterConnectOptions } = config;
    const uri = to || getMongoURI({ name, dbName });
    this.mongoose.connect(
      uri,
      { ...adapterConnectOptions }
    );
    const db = this.mongoose.connection;
    db.on('error', console.error.bind(console, 'Mongoose connection error'));
    db.once('open', () => console.log('Connection success'));
  }

  close() {
    return new Promise((resolve, reject) => {
      this.mongoose.connection.close(true, error => {
        if (error) {
          return reject(error);
        }
        resolve(this.mongoose.disconnect());
      });
    });
  }

  dropDatabase() {
    // This will completely drop the backing database. Use wisely.
    return this.mongoose.connection.dropDatabase();
  }
}

class MongooseListAdapter extends BaseListAdapter {
  constructor(key, parentAdapter, config) {
    super(...arguments);

    const { mongoose } = parentAdapter;
    const { configureMongooseSchema, mongooseSchemaOptions } = config;

    this.mongoose = mongoose;
    this.configureMongooseSchema = configureMongooseSchema;
    this.schema = new mongoose.Schema({}, mongooseSchemaOptions);

    // Need to call prepareModel() once all fields have registered.
    this.model = null;
  }

  prepareFieldAdapter(fieldAdapter) {
    fieldAdapter.addToMongooseSchema(this.schema, this.mongoose);
  }

  prepareModel() {
    if (this.configureMongooseSchema) {
      this.configureMongooseSchema(this.schema, { mongoose: this.mongoose });
    }
    this.model = this.mongoose.model(this.key, this.schema);
  }

  create(data) {
    return this.model.create(data);
  }

  delete(id) {
    return this.model.findByIdAndRemove(id);
  }

  update(id, update, options) {
    return this.model.findByIdAndUpdate(id, update, options);
  }

  findAll() {
    return this.model.find();
  }

  findById(id) {
    return this.model.findById(id);
  }

  find(condition) {
    return this.model.find(condition);
  }

  findOne(condition) {
    return this.model.findOne(condition);
  }

  itemsQueryConditions(args, depthGuard = 0) {
    if (!args) {
      return [];
    }

    // TODO: can depthGuard be an instance variable, to track the recursion
    // depth instead of passing it through to the individual fields and back
    // again?
    if (depthGuard > 1) {
      throw new Error(
        'Nesting where args deeper than 1 level is not currently supported'
      );
    }

    return this.fieldAdapters.reduce(
      (conds, fieldAdapater) => {
        const fieldConditions = fieldAdapater.getQueryConditions(
          args,
          this,
          depthGuard + 1
        );

        if (fieldConditions && !Array.isArray(fieldConditions)) {
          console.warn(
            `${this.key}.${fieldAdapater.path} (${
              fieldAdapater.fieldName
            }) returned a non-array for .getQueryConditions(). This is probably a mistake. Ignoring.`
          );
          return conds;
        }

        // Nothing to do
        if (!fieldConditions || !fieldConditions.length) {
          return conds;
        }

        return [
          ...conds,
          ...fieldConditions.map(condition => {
            if (condition && condition.$isComplexStage) {
              return condition;
            }
            return { [fieldAdapater.path]: condition };
          }),
        ];
      },
      // Special case for `_id` where it presents as `id` in the graphQL schema,
      // and isn't a field type
      getIdQueryConditions(args)
    );
  }
  itemsQuery(args, { meta = false } = {}) {
    const conditions = this.itemsQueryConditions(args.where);

    const pipeline = [];
    const postAggregateMutation = [];

    // TODO: Order isn't important. Might as well put all the simple `$match`s
    // first, and complex ones last.
    // TODO: Change this to a `for...of` loop
    let iterator = conditions[Symbol.iterator]();
    let itr = iterator.next();
    while (!itr.done) {
      // Gather up all the simple matches
      let simpleMatches = [];
      while (!itr.done && !itr.value.$isComplexStage) {
        simpleMatches.push(itr.value);
        itr = iterator.next();
      }

      if (simpleMatches.length) {
        pipeline.push({
          $match: {
            $and: simpleMatches,
          },
        });
      }

      // Push all the complex stages onto the pipeline as-is
      while (!itr.done && itr.value.$isComplexStage) {
        pipeline.push(...itr.value.pipeline);
        if (itr.value.mutator) {
          postAggregateMutation.push(itr.value.mutator);
        }
        itr = iterator.next();
      }
    }

    if (args.search) {
      // TODO: Implement configurable search fields for lists
      pipeline.push({
        $match: {
          name: new RegExp(`${escapeRegExp(args.search)}`, 'i'),
        },
      });
    }

    if (args.orderBy) {
      const [orderField, orderDirection] = args.orderBy.split('_');

      pipeline.push({
        $sort: {
          [orderField]: orderDirection === 'ASC' ? 1 : -1,
        },
      });
    }

    if (args.skip < Infinity && args.skip > 0) {
      pipeline.push({
        $skip: args.skip,
      });
    }

    if (args.first < Infinity && args.first > 0) {
      pipeline.push({
        $limit: args.first,
      });
    }

    if (meta) {
      pipeline.push({
        $count: 'count',
      });
    }

    if (!pipeline.length) {
      return this.findAll();
    }

    // Map _id => id
    // Normally, mongoose would do this for us, but because we're breaking out
    // and going straight Mongo, gotta do it ourselves.
    pipeline.push({
      $addFields: {
        id: '$_id',
      },
    });
    return this.model
      .aggregate(pipeline)
      .exec()
      .then(data => {
        if (meta) {
          return data[0];
        }

        return (
          data
            .map((item, index, list) =>
              // Iterate over all the mutations
              postAggregateMutation.reduce(
                // And pass through the result to the following mutator
                (mutatedItem, mutation) => mutation(mutatedItem, index, list),
                // Starting at the original item
                item
              )
            )
            // If anything gets removed, we clear it out here
            .filter(Boolean)
        );
      });
  }
  itemsQueryMeta(args) {
    return this.itemsQuery(args, { meta: true });
  }
}

class MongooseFieldAdapter extends BaseFieldAdapter {
  addToMongooseSchema() {
    throw new Error(
      `Field type [${this.fieldName}] does not implement addToMongooseSchema()`
    );
  }

  getQueryConditions() {
    return [];
  }
}

MongooseAdapter.defaultListAdapterClass = MongooseListAdapter;

module.exports = {
  MongooseAdapter,
  MongooseListAdapter,
  MongooseFieldAdapter,
};
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';

import './Photos.scss';

export default class Photos extends Component {
  static propTypes = {
    place: PropTypes.object.isRequired
  }

  constructor(props) {
    super(props);
    this.msnry = null;
  }

  componentDidMount() {
    this.initMasonry();

    if (typeof instgrm === 'undefined') {
      return;
    }
    setTimeout(() => {
      instgrm.Embeds.process();

      if (this.msnry !== null) {
        this.msnry.layout();
      }
    }, 200);
  }

  /* eslint-disable */
  onImagesLoad(instance) {
    // console.log('all images are loaded', instance);
    this.msnry = new Masonry('.photos__list', {
      itemSelector: '.photos__item'
    });
    this.msnry.layout();
  }

  initMasonry() {
    const photos = document.querySelectorAll('.photos__list');
    const imgLoad = imagesLoaded(photos);
    imgLoad.on('always', this.onImagesLoad);
  }
  /* eslint-enable */

  imgs(place) {
    const { photos, media, slug, title } = place.properties;

    if (photos === undefined || photos < 1) {
      return [];
    }

    let mediaEmbeds = null;
    if (media !== undefined) {
      mediaEmbeds = media.map((content, idx) =>
        <li
          className="photos__item photos__item--media"
          dangerouslySetInnerHTML={{ __html: content }}
          key={`media-${idx}`}
        />
      );
    }

    return (

        {mediaEmbeds}
        {Array(photos).fill().map((i, idx) =>
          <li className="photos__item" key={`img-${idx}`}>
            <a className="photos__link" href={`/images/${slug}/${(idx + 1)}.jpg`} target="_blank">
              <img className="photos__img" src={`/images/${slug}/${(idx + 1)}.jpg`} alt={title} />

        )}

    );
  }

  render() {
    const { place } = this.props;

    return (

        {this.imgs(place)}

    );
  }
}
<commit_msg>Tweaking the photos<commit_after>import React, { Component, PropTypes } from 'react';

import './Photos.scss';

export default class Photos extends Component {
  static propTypes = {
    place: PropTypes.object.isRequired
  }

  constructor(props) {
    super(props);
    this.msnry = null;
  }

  componentDidMount() {
    this.initMasonry();

    if (typeof instgrm === 'undefined') {
      return;
    }
    setTimeout(() => {
      instgrm.Embeds.process();

      if (this.msnry !== null) {
        this.msnry.layout();
      }
    }, 200);
  }

  /* eslint-disable */
  onImagesLoad(instance) {
    // console.log('all images are loaded', instance);
    const photos = document.querySelector('.photos__list');
    if (photos === null) {
      return;
    }

    this.msnry = new Masonry(photos, {
      itemSelector: '.photos__item'
    });
    this.msnry.layout();
  }

  initMasonry() {
    const photos = document.querySelectorAll('.photos__list');
    const imgLoad = imagesLoaded(photos);
    imgLoad.on('always', this.onImagesLoad);
  }
  /* eslint-enable */

  imgs(place) {
    const { photos, media, slug, title } = place.properties;

    if (photos === undefined || photos < 1) {
      return [];
    }

    let mediaEmbeds = null;
    if (media !== undefined) {
      mediaEmbeds = media.map((content, idx) =>
        <li
          className="photos__item photos__item--media"
          dangerouslySetInnerHTML={{ __html: content }}
          key={`media-${idx}`}
        />
      );
    }

    return (

        {mediaEmbeds}
        {Array(photos).fill().map((i, idx) =>
          <li className="photos__item" key={`img-${idx}`}>
            <a className="photos__link" href={`/images/${slug}/${(idx + 1)}.jpg`} target="_blank">
              <img className="photos__img" src={`/images/${slug}/${(idx + 1)}.jpg`} alt={title} />

        )}

    );
  }

  render() {
    const { place } = this.props;

    return (

        {this.imgs(place)}

    );
  }
}
<|endoftext|>


<commit_before>import React from 'react'
import { throttle } from 'lodash'
import { VelocityTransitionGroup } from 'velocity-react'
import { position, viewportTop } from '../util/scrolling'
import cx from 'classnames'
const { func } = React.PropTypes

export default class RefreshButton extends React.Component {
  static propTypes = {
    refresh: func
  }
  constructor (props) {
    super(props)
    this.state = {isStatic: true}
  }
  handleScrollEvents = throttle(event => {
    event.preventDefault()
    if (this.state.isStatic) {
      if (viewportTop() > this.startingY) {
        this.setState({isStatic: false})
      }
    } else {
      if (viewportTop() < this.startingY) {
        this.setState({isStatic: true})
      }
    }
  }, 50)

  componentDidMount () {
    this.startingY = position(this.refs['placeholder']).y
    this.setState({isStatic: viewportTop() < this.startingY})
    window.addEventListener('scroll', this.handleScrollEvents)
  }

  componentWillUnmount () {
    window.removeEventListener('scroll', this.handleScrollEvents)
  }

  render () {
    let { refresh } = this.props
    let { isStatic } = this.state
    return
      <VelocityTransitionGroup enter={{animation: 'slideDown'}} leave={{animation: 'slideUp'}}>
        {refresh &&

          New Posts are available. Refresh

}
      </VelocityTransitionGroup>

  }
}
<commit_msg>make whole RefreshButton clickable<commit_after>import React from 'react'
import { throttle } from 'lodash'
import { VelocityTransitionGroup } from 'velocity-react'
import { position, viewportTop } from '../util/scrolling'
import cx from 'classnames'
const { func } = React.PropTypes

export default class RefreshButton extends React.Component {
  static propTypes = {
    refresh: func
  }
  constructor (props) {
    super(props)
    this.state = {isStatic: true}
  }
  handleScrollEvents = throttle(event => {
    event.preventDefault()
    if (this.state.isStatic) {
      if (viewportTop() > this.startingY) {
        this.setState({isStatic: false})
      }
    } else {
      if (viewportTop() < this.startingY) {
        this.setState({isStatic: true})
      }
    }
  }, 50)

  componentDidMount () {
    this.startingY = position(this.refs['placeholder']).y
    this.setState({isStatic: viewportTop() < this.startingY})
    window.addEventListener('scroll', this.handleScrollEvents)
  }

  componentWillUnmount () {
    window.removeEventListener('scroll', this.handleScrollEvents)
  }

  render () {
    let { refresh } = this.props
    let { isStatic } = this.state
    return
      <VelocityTransitionGroup enter={{animation: 'slideDown'}} leave={{animation: 'slideUp'}}>
        {refresh &&

          New Posts are available. Refresh

}
      </VelocityTransitionGroup>

  }
}
<|endoftext|>