<commit_before>(function(){

    'use strict';

    angular.module('nvd3', [])

        .directive('nvd3', ['utils', function(utils){
            return {
                restrict: 'AE',
                scope: {
                    data: '=',      //chart data, [required]
                    options: '=',   //chart options, according to nvd3 core api, [required]
                    api: '=?',      //directive global api, [optional]
                    events: '=?',   //global events that directive would subscribe to, [optional]
                    config: '=?'    //global directive configuration, [optional]
                },
                link: function(scope, element, attrs){
                    var defaultConfig = {
                        extended: false,
                        visible: true,
                        disabled: false,
                        autorefresh: true,
                        refreshDataOnly: false,
                        deepWatchData: true,
                        debounce: 10 // default 10ms, time silence to prevent refresh while multiple options changes at a time
                    };

                    //basic directive configuration
                    scope._config = angular.extend(defaultConfig, scope.config);

                    //directive global api
                    scope.api = {
                        // Fully refresh directive
                        refresh: function(){
                            scope.api.updateWithOptions(scope.options);
                        },

                        // Update chart layout (for example if container is resized)
                        update: function() {
                            scope.chart.update();
                        },

                        // Update chart with new options
                        updateWithOptions: function(options){
                            // Clearing
                            scope.api.clearElement();

                            // Exit if options are not yet bound
                            if (angular.isDefined(options) === false) return;

                            // Exit if chart is hidden
                            if (!scope._config.visible) return;

                            // Initialize chart with specific type
                            scope.chart = nv.models[options.chart.type]();

                            // Generate random chart ID
                            scope.chart.id = Math.random().toString(36).substr(2, 15);

                            angular.forEach(scope.chart, function(value, key){
                                if ([
                                    'options',
                                    '_options',
                                    '_inherited',
                                    '_d3options',
                                    'state',
                                    'id',
                                    'resizeHandler'
                                ].indexOf(key) >= 0);

                                else if (key === 'dispatch') {
                                    if (options.chart[key] === undefined || options.chart[key] === null) {
                                        if (scope._config.extended) options.chart[key] = {};
                                    }
                                    configureEvents(scope.chart[key], options.chart[key]);
                                }

                                else if ([
                                    'lines',
                                    'lines1',
                                    'lines2',
                                    'bars',
                                    'bars1',
                                    'bars2',
                                    'stack1',
                                    'stack2',
                                    'stacked',
                                    'multibar',
                                    'discretebar',
                                    'pie',
                                    'scatter',
                                    'bullet',
                                    'sparkline',
                                    'legend',
                                    'distX',
                                    'distY',
                                    'xAxis',
                                    'x2Axis',
                                    'yAxis',
                                    'yAxis1',
                                    'yAxis2',
                                    'y1Axis',
                                    'y2Axis',
                                    'y3Axis',
                                    'y4Axis',
                                    'interactiveLayer',
                                    'controls'
                                ].indexOf(key) >= 0){
                                    if (options.chart[key] === undefined || options.chart[key] === null) {
                                        if (scope._config.extended) options.chart[key] = {};
                                    }
                                    configure(scope.chart[key], options.chart[key], options.chart.type);
                                }

                                //TODO: need to fix bug in nvd3
                                else if ((key === 'xTickFormat' || key === 'yTickFormat') && options.chart.type === 'lineWithFocusChart');

                                else if (options.chart[key] === undefined || options.chart[key] === null){
                                    if (scope._config.extended) options.chart[key] = value();
                                }

                                else scope.chart[key](options.chart[key]);
                            });

                            // Update with data
                            scope.api.updateWithData(scope.data);

                            // Configure wrappers
                            if (options['title'] || scope._config.extended) configureWrapper('title');
                            if (options['subtitle'] || scope._config.extended) configureWrapper('subtitle');
                            if (options['caption'] || scope._config.extended) configureWrapper('caption');

                            // Configure styles
                            if (options['styles'] || scope._config.extended) configureStyles();

                            nv.addGraph(function() {
                                // Update the chart when window resizes
                                scope.chart.resizeHandler = nv.utils.windowResize(function() { scope.chart.update(); });
                                return scope.chart;
                            }, options.chart['callback']);
                        },

                        // Update chart with new data
                        updateWithData: function (data){
                            if (data) {
                                scope.options.chart['transitionDuration'] = +scope.options.chart['transitionDuration'] || 250;
                                // remove whole svg element with old data
                                d3.select(element[0]).select('svg').remove();

                                // Select the current element to add <svg> element and to render the chart in
                                d3.select(element[0]).append('svg')
                                    .attr('height', scope.options.chart.height)
                                    .attr('width', scope.options.chart.width  || '100%')
                                    .datum(data)
                                    .transition().duration(scope.options.chart['transitionDuration'])
                                    .call(scope.chart);
                            }
                        },

                        // Fully clear directive element
                        clearElement: function (){
                            element.find('.title').remove();
                            element.find('.subtitle').remove();
                            element.find('.caption').remove();
                            element.empty();
                            if (scope.chart) {
                                // clear window resize event handler
                                if (scope.chart.resizeHandler) scope.chart.resizeHandler.clear();

                                // remove chart from nv.graph list
                                for (var i = 0; i < nv.graphs.length; i++)
                                    if (nv.graphs[i].id === scope.chart.id) {
                                        nv.graphs.splice(i, 1);
                                    }
                            }
                            scope.chart = null;
                            nv.tooltip.cleanup();
                        },

                        // Get full directive scope
                        getScope: function(){ return scope; }
                    };

                    // Configure the chart model with the passed options
                    function configure(chart, options, chartType){
                        if (chart && options){
                            angular.forEach(chart, function(value, key){
                                if (key === 'dispatch') {
                                    if (options[key] === undefined || options[key] === null) {
                                        if (scope._config.extended) options[key] = {};
                                    }
                                    configureEvents(value, options[key]);
                                }
                                else if ([
                                    'scatter',
                                    'defined',
                                    'options',
                                    'axis',
                                    'rangeBand',
                                    'rangeBands',
                                    '_options',
                                    '_inherited',
                                    '_d3options',
                                    '_calls'
                                ].indexOf(key) < 0){
                                    if (options[key] === undefined || options[key] === null){
                                        if (scope._config.extended) options[key] = value();
                                    }
                                    else chart[key](options[key]);
                                }
                            });
                        }
                    }

                    // Subscribe to the chart events (contained in 'dispatch')
                    // and pass eventHandler functions in the 'options' parameter
                    function configureEvents(dispatch, options){
                        if (dispatch && options){
                            angular.forEach(dispatch, function(value, key){
                                if (options[key] === undefined || options[key] === null){
                                    if (scope._config.extended) options[key] = value.on;
                                }
                                else dispatch.on(key + '._', options[key]);
                            });
                        }
                    }

                    // Configure 'title', 'subtitle', 'caption'.
                    // nvd3 has no sufficient models for it yet.
                    function configureWrapper(name){
                        var _ = utils.deepExtend(defaultWrapper(name), scope.options[name] || {});

                        if (scope._config.extended) scope.options[name] = _;

                        var wrapElement = angular.element('

').html(_['html'] || '')
                            .addClass(name).addClass(_.class)
                            .removeAttr('style')
                            .css(_.css);

                        if (!_['html']) wrapElement.text(_.text);

                        if (_.enable) {
                            if (name === 'title') element.prepend(wrapElement);
                            else if (name === 'subtitle') element.find('.title').after(wrapElement);
                            else if (name === 'caption') element.append(wrapElement);
                        }
                    }

                    // Add some styles to the whole directive element
                    function configureStyles(){
                        var _ = utils.deepExtend(defaultStyles(), scope.options['styles'] || {});

                        if (scope._config.extended) scope.options['styles'] = _;

                        angular.forEach(_.classes, function(value, key){
                            value ? element.addClass(key) : element.removeClass(key);
                        });

                        element.removeAttr('style').css(_.css);
                    }

                    // Default values for 'title', 'subtitle', 'caption'
                    function defaultWrapper(_){
                        switch (_){
                            case 'title': return {
                                enable: false,
                                text: 'Write Your Title',
                                class: 'h4',
                                css: {
                                    width: scope.options.chart.width + 'px',
                                    textAlign: 'center'
                                }
                            };
                            case 'subtitle': return {
                                enable: false,
                                text: 'Write Your Subtitle',
                                css: {
                                    width: scope.options.chart.width + 'px',
                                    textAlign: 'center'
                                }
                            };
                            case 'caption': return {
                                enable: false,
                                text: 'Figure 1. Write Your Caption text.',
                                css: {
                                    width: scope.options.chart.width + 'px',
                                    textAlign: 'center'
                                }
                            };
                        }
                    }

                    // Default values for styles
                    function defaultStyles(){
                        return {
                            classes: {
                                'with-3d-shadow': true,
                                'with-transitions': true,
                                'gallery': false
                            },
                            css: {}
                        };
                    }

                    /* Event Handling */
                    // Watching on options changing
                    scope.$watch('options', utils.debounce(function(newOptions){
                        if (!scope._config.disabled && scope._config.autorefresh) scope.api.refresh();
                    }, scope._config.debounce, true), true);

                    // Watching on data changing
                    scope.$watch('data', function(newData, oldData){
                        if (newData !== oldData && scope.chart){
                            if (!scope._config.disabled && scope._config.autorefresh) {
                                scope._config.refreshDataOnly ? scope.chart.update() : scope.api.refresh(); // if wanted to refresh data only, use chart.update method, otherwise use full refresh.
                            }
                        }
                    }, scope._config.deepWatchData);

                    // Watching on config changing
                    scope.$watch('config', function(newConfig, oldConfig){
                        if (newConfig !== oldConfig){
                            scope._config = angular.extend(defaultConfig, newConfig);
                            scope.api.refresh();
                        }
                    }, true);

                    //subscribe on global events
                    angular.forEach(scope.events, function(eventHandler, event){
                        scope.$on(event, function(e){
                            return eventHandler(e, scope);
                        });
                    });

                    // remove completely when directive is destroyed
                    element.on('$destroy', function () {
                        scope.api.clearElement();
                    });
                }
            };
        }])

        .factory('utils', function(){
            return {
                debounce: function(func, wait, immediate) {
                    var timeout;
                    return function() {
                        var context = this, args = arguments;
                        var later = function() {
                            timeout = null;
                            if (!immediate) func.apply(context, args);
                        };
                        var callNow = immediate && !timeout;
                        clearTimeout(timeout);
                        timeout = setTimeout(later, wait);
                        if (callNow) func.apply(context, args);
                    };
                },
                deepExtend: function(dst){
                    var me = this;
                    angular.forEach(arguments, function(obj) {
                        if (obj !== dst) {
                            angular.forEach(obj, function(value, key) {
                                if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
                                    me.deepExtend(dst[key], value);
                                } else {
                                    dst[key] = value;
                                }
                            });
                        }
                    });
                    return dst;
                }
            };
        });
})();<commit_msg>rename utils service to something less common to avoid conflicts with other modules<commit_after>(function(){

    'use strict';

    angular.module('nvd3', [])

        .directive('nvd3', ['nvd3Utils', function(utils){
            return {
                restrict: 'AE',
                scope: {
                    data: '=',      //chart data, [required]
                    options: '=',   //chart options, according to nvd3 core api, [required]
                    api: '=?',      //directive global api, [optional]
                    events: '=?',   //global events that directive would subscribe to, [optional]
                    config: '=?'    //global directive configuration, [optional]
                },
                link: function(scope, element, attrs){
                    var defaultConfig = {
                        extended: false,
                        visible: true,
                        disabled: false,
                        autorefresh: true,
                        refreshDataOnly: false,
                        deepWatchData: true,
                        debounce: 10 // default 10ms, time silence to prevent refresh while multiple options changes at a time
                    };

                    //basic directive configuration
                    scope._config = angular.extend(defaultConfig, scope.config);

                    //directive global api
                    scope.api = {
                        // Fully refresh directive
                        refresh: function(){
                            scope.api.updateWithOptions(scope.options);
                        },

                        // Update chart layout (for example if container is resized)
                        update: function() {
                            scope.chart.update();
                        },

                        // Update chart with new options
                        updateWithOptions: function(options){
                            // Clearing
                            scope.api.clearElement();

                            // Exit if options are not yet bound
                            if (angular.isDefined(options) === false) return;

                            // Exit if chart is hidden
                            if (!scope._config.visible) return;

                            // Initialize chart with specific type
                            scope.chart = nv.models[options.chart.type]();

                            // Generate random chart ID
                            scope.chart.id = Math.random().toString(36).substr(2, 15);

                            angular.forEach(scope.chart, function(value, key){
                                if ([
                                    'options',
                                    '_options',
                                    '_inherited',
                                    '_d3options',
                                    'state',
                                    'id',
                                    'resizeHandler'
                                ].indexOf(key) >= 0);

                                else if (key === 'dispatch') {
                                    if (options.chart[key] === undefined || options.chart[key] === null) {
                                        if (scope._config.extended) options.chart[key] = {};
                                    }
                                    configureEvents(scope.chart[key], options.chart[key]);
                                }

                                else if ([
                                    'lines',
                                    'lines1',
                                    'lines2',
                                    'bars',
                                    'bars1',
                                    'bars2',
                                    'stack1',
                                    'stack2',
                                    'stacked',
                                    'multibar',
                                    'discretebar',
                                    'pie',
                                    'scatter',
                                    'bullet',
                                    'sparkline',
                                    'legend',
                                    'distX',
                                    'distY',
                                    'xAxis',
                                    'x2Axis',
                                    'yAxis',
                                    'yAxis1',
                                    'yAxis2',
                                    'y1Axis',
                                    'y2Axis',
                                    'y3Axis',
                                    'y4Axis',
                                    'interactiveLayer',
                                    'controls'
                                ].indexOf(key) >= 0){
                                    if (options.chart[key] === undefined || options.chart[key] === null) {
                                        if (scope._config.extended) options.chart[key] = {};
                                    }
                                    configure(scope.chart[key], options.chart[key], options.chart.type);
                                }

                                //TODO: need to fix bug in nvd3
                                else if ((key === 'xTickFormat' || key === 'yTickFormat') && options.chart.type === 'lineWithFocusChart');

                                else if (options.chart[key] === undefined || options.chart[key] === null){
                                    if (scope._config.extended) options.chart[key] = value();
                                }

                                else scope.chart[key](options.chart[key]);
                            });

                            // Update with data
                            scope.api.updateWithData(scope.data);

                            // Configure wrappers
                            if (options['title'] || scope._config.extended) configureWrapper('title');
                            if (options['subtitle'] || scope._config.extended) configureWrapper('subtitle');
                            if (options['caption'] || scope._config.extended) configureWrapper('caption');

                            // Configure styles
                            if (options['styles'] || scope._config.extended) configureStyles();

                            nv.addGraph(function() {
                                // Update the chart when window resizes
                                scope.chart.resizeHandler = nv.utils.windowResize(function() { scope.chart.update(); });
                                return scope.chart;
                            }, options.chart['callback']);
                        },

                        // Update chart with new data
                        updateWithData: function (data){
                            if (data) {
                                scope.options.chart['transitionDuration'] = +scope.options.chart['transitionDuration'] || 250;
                                // remove whole svg element with old data
                                d3.select(element[0]).select('svg').remove();

                                // Select the current element to add <svg> element and to render the chart in
                                d3.select(element[0]).append('svg')
                                    .attr('height', scope.options.chart.height)
                                    .attr('width', scope.options.chart.width  || '100%')
                                    .datum(data)
                                    .transition().duration(scope.options.chart['transitionDuration'])
                                    .call(scope.chart);
                            }
                        },

                        // Fully clear directive element
                        clearElement: function (){
                            element.find('.title').remove();
                            element.find('.subtitle').remove();
                            element.find('.caption').remove();
                            element.empty();
                            if (scope.chart) {
                                // clear window resize event handler
                                if (scope.chart.resizeHandler) scope.chart.resizeHandler.clear();

                                // remove chart from nv.graph list
                                for (var i = 0; i < nv.graphs.length; i++)
                                    if (nv.graphs[i].id === scope.chart.id) {
                                        nv.graphs.splice(i, 1);
                                    }
                            }
                            scope.chart = null;
                            nv.tooltip.cleanup();
                        },

                        // Get full directive scope
                        getScope: function(){ return scope; }
                    };

                    // Configure the chart model with the passed options
                    function configure(chart, options, chartType){
                        if (chart && options){
                            angular.forEach(chart, function(value, key){
                                if (key === 'dispatch') {
                                    if (options[key] === undefined || options[key] === null) {
                                        if (scope._config.extended) options[key] = {};
                                    }
                                    configureEvents(value, options[key]);
                                }
                                else if ([
                                    'scatter',
                                    'defined',
                                    'options',
                                    'axis',
                                    'rangeBand',
                                    'rangeBands',
                                    '_options',
                                    '_inherited',
                                    '_d3options',
                                    '_calls'
                                ].indexOf(key) < 0){
                                    if (options[key] === undefined || options[key] === null){
                                        if (scope._config.extended) options[key] = value();
                                    }
                                    else chart[key](options[key]);
                                }
                            });
                        }
                    }

                    // Subscribe to the chart events (contained in 'dispatch')
                    // and pass eventHandler functions in the 'options' parameter
                    function configureEvents(dispatch, options){
                        if (dispatch && options){
                            angular.forEach(dispatch, function(value, key){
                                if (options[key] === undefined || options[key] === null){
                                    if (scope._config.extended) options[key] = value.on;
                                }
                                else dispatch.on(key + '._', options[key]);
                            });
                        }
                    }

                    // Configure 'title', 'subtitle', 'caption'.
                    // nvd3 has no sufficient models for it yet.
                    function configureWrapper(name){
                        var _ = utils.deepExtend(defaultWrapper(name), scope.options[name] || {});

                        if (scope._config.extended) scope.options[name] = _;

                        var wrapElement = angular.element('

').html(_['html'] || '')
                            .addClass(name).addClass(_.class)
                            .removeAttr('style')
                            .css(_.css);

                        if (!_['html']) wrapElement.text(_.text);

                        if (_.enable) {
                            if (name === 'title') element.prepend(wrapElement);
                            else if (name === 'subtitle') element.find('.title').after(wrapElement);
                            else if (name === 'caption') element.append(wrapElement);
                        }
                    }

                    // Add some styles to the whole directive element
                    function configureStyles(){
                        var _ = utils.deepExtend(defaultStyles(), scope.options['styles'] || {});

                        if (scope._config.extended) scope.options['styles'] = _;

                        angular.forEach(_.classes, function(value, key){
                            value ? element.addClass(key) : element.removeClass(key);
                        });

                        element.removeAttr('style').css(_.css);
                    }

                    // Default values for 'title', 'subtitle', 'caption'
                    function defaultWrapper(_){
                        switch (_){
                            case 'title': return {
                                enable: false,
                                text: 'Write Your Title',
                                class: 'h4',
                                css: {
                                    width: scope.options.chart.width + 'px',
                                    textAlign: 'center'
                                }
                            };
                            case 'subtitle': return {
                                enable: false,
                                text: 'Write Your Subtitle',
                                css: {
                                    width: scope.options.chart.width + 'px',
                                    textAlign: 'center'
                                }
                            };
                            case 'caption': return {
                                enable: false,
                                text: 'Figure 1. Write Your Caption text.',
                                css: {
                                    width: scope.options.chart.width + 'px',
                                    textAlign: 'center'
                                }
                            };
                        }
                    }

                    // Default values for styles
                    function defaultStyles(){
                        return {
                            classes: {
                                'with-3d-shadow': true,
                                'with-transitions': true,
                                'gallery': false
                            },
                            css: {}
                        };
                    }

                    /* Event Handling */
                    // Watching on options changing
                    scope.$watch('options', utils.debounce(function(newOptions){
                        if (!scope._config.disabled && scope._config.autorefresh) scope.api.refresh();
                    }, scope._config.debounce, true), true);

                    // Watching on data changing
                    scope.$watch('data', function(newData, oldData){
                        if (newData !== oldData && scope.chart){
                            if (!scope._config.disabled && scope._config.autorefresh) {
                                scope._config.refreshDataOnly ? scope.chart.update() : scope.api.refresh(); // if wanted to refresh data only, use chart.update method, otherwise use full refresh.
                            }
                        }
                    }, scope._config.deepWatchData);

                    // Watching on config changing
                    scope.$watch('config', function(newConfig, oldConfig){
                        if (newConfig !== oldConfig){
                            scope._config = angular.extend(defaultConfig, newConfig);
                            scope.api.refresh();
                        }
                    }, true);

                    //subscribe on global events
                    angular.forEach(scope.events, function(eventHandler, event){
                        scope.$on(event, function(e){
                            return eventHandler(e, scope);
                        });
                    });

                    // remove completely when directive is destroyed
                    element.on('$destroy', function () {
                        scope.api.clearElement();
                    });
                }
            };
        }])

        .factory('nvd3Utils', function(){
            return {
                debounce: function(func, wait, immediate) {
                    var timeout;
                    return function() {
                        var context = this, args = arguments;
                        var later = function() {
                            timeout = null;
                            if (!immediate) func.apply(context, args);
                        };
                        var callNow = immediate && !timeout;
                        clearTimeout(timeout);
                        timeout = setTimeout(later, wait);
                        if (callNow) func.apply(context, args);
                    };
                },
                deepExtend: function(dst){
                    var me = this;
                    angular.forEach(arguments, function(obj) {
                        if (obj !== dst) {
                            angular.forEach(obj, function(value, key) {
                                if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
                                    me.deepExtend(dst[key], value);
                                } else {
                                    dst[key] = value;
                                }
                            });
                        }
                    });
                    return dst;
                }
            };
        });
})();<|endoftext|>


<commit_before><commit_msg>Remove email<commit_after><|endoftext|>


<commit_before>(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        //Allow using this built library as an AMD module
        //in another project. That other project will only
        //see this AMD call, not the internal modules in
        //the closure below.
        define([], factory);
    } else {
        //Browser globals case. Just assign the
        //result to a property on the global.
        root.Toolbox = factory();
    }
}(this, function () {
    //almond, and your modules will be inlined here
<commit_msg>commonjs support added<commit_after>(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        //Allow using this built library as an AMD module
        //in another project. That other project will only
        //see this AMD call, not the internal modules in
        //the closure below.
        define([], factory);
    }
	else if(typeof exports === 'object'){
		module.exports = factory() ;
	}
	else {
        //Browser globals case. Just assign the
        //result to a property on the global.
        root.Toolbox = factory();
    }
}(this, function () {
    //almond, and your modules will be inlined here
<|endoftext|>


<commit_before>// @flow

import * as React from 'react'
import {
	StyleSheet,
	ScrollView,
	View,
	Text,
	RefreshControl,
	Alert,
} from 'react-native'
import {TabBarIcon} from '@frogpond/navigation-tabs'
import {connect} from 'react-redux'
import {Cell, TableView, Section} from '@frogpond/tableview'
import {hasSeenAcknowledgement} from '../../redux/parts/settings'
import {logInViaCredentials} from '../../redux/parts/login'
import {type LoginStateEnum} from '../../redux/parts/login'
import {getBalances} from '../../lib/financials'
import {loadLoginCredentials} from '../../lib/login'
import {type ReduxState} from '../../redux'
import delay from 'delay'
import * as c from '@frogpond/colors'
import type {TopLevelViewPropsType} from '../types'

const DISCLAIMER = 'This data may be outdated or otherwise inaccurate.'
const LONG_DISCLAIMER =
	'This data may be inaccurate.\nBon Appétit is always right.\nThis app is unofficial.'

type ReactProps = TopLevelViewPropsType

type ReduxStateProps = {
	status: LoginStateEnum,
	alertSeen: boolean,
}

type ReduxDispatchProps = {
	logInViaCredentials: (string, string) => Promise<any>,
	hasSeenAcknowledgement: () => any,
}

type Props = ReactProps & ReduxStateProps & ReduxDispatchProps

type State = {
	loading: boolean,
	flex: ?string,
	ole: ?string,
	print: ?string,
	weeklyMeals: ?string,
	dailyMeals: ?string,
	mealPlan: ?string,
	message: ?string,
}

class BalancesView extends React.PureComponent<Props, State> {
	static navigationOptions = {
		tabBarLabel: 'Balances',
		tabBarIcon: TabBarIcon('card'),
	}

	state = {
		loading: false,
		flex: null,
		ole: null,
		print: null,
		weeklyMeals: null,
		dailyMeals: null,
		mealPlan: null,
		message: null,
	}

	componentDidMount() {
		// calling "refresh" here, to make clear to the user
		// that the data is being updated
		this.refresh()

		if (!this.props.alertSeen) {
			Alert.alert('', LONG_DISCLAIMER, [
				{text: 'I Disagree', onPress: this.goBack, style: 'cancel'},
				{text: 'Okay', onPress: this.props.hasSeenAcknowledgement},
			])
		}
	}

	refresh = async () => {
		let start = Date.now()
		this.setState(() => ({loading: true}))

		if (this.props.alertSeen) {
			await this.fetchData()
		}

		// wait 0.5 seconds – if we let it go at normal speed, it feels broken.
		let elapsed = Date.now() - start
		await delay(500 - elapsed)

		this.setState(() => ({loading: false}))
	}

	logIn = async () => {
		let {status} = this.props
		if (status === 'logged-in' || status === 'checking') {
			return
		}

		let {username = '', password = ''} = await loadLoginCredentials()
		if (username && password) {
			await this.props.logInViaCredentials(username, password)
		}
	}

	fetchData = async () => {
		// trigger the login so that the banner at the bottom hides itself
		await this.logIn()

		let balances = await getBalances()

		if (balances.error === true) {
			return
		}

		let {value} = balances

		let {flex, ole, print} = value
		let {weekly: weeklyMeals, daily: dailyMeals, plan: mealPlan} = value

		this.setState(() => ({
			flex,
			ole,
			print,
			weeklyMeals,
			dailyMeals,
			mealPlan,
		}))
	}

	openSettings = () => this.props.navigation.navigate('SettingsView')

	goBack = () => this.props.navigation.goBack(null)

	render() {
		let {
			flex,
			ole,
			print,
			dailyMeals,
			weeklyMeals,
			mealPlan,
			message,
			loading,
		} = this.state
		let {status} = this.props

		return (
			<ScrollView
				contentContainerStyle={styles.stage}
				refreshControl={
					<RefreshControl
						onRefresh={this.refresh}
						refreshing={this.state.loading}
					/>
				}
			>
				<TableView>

						<View style={styles.balancesRow}>
							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Flex"
								value={flex}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Ole"
								value={ole}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Copy/Print"
								style={styles.finalCell}
								value={print}
							/>
						</View>

						<View style={styles.balancesRow}>
							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Daily Meals Left"
								value={dailyMeals}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Weekly Meals Left"
								style={styles.finalCell}
								value={weeklyMeals}
							/>
						</View>
						{mealPlan && (
							<Cell cellStyle="Subtitle" detail={mealPlan} title="Meal Plan" />
						)}

				</TableView>

				{(status !== 'logged-in' || message) && (

						{status !== 'logged-in' ? (
							<Cell
								accessory="DisclosureIndicator"
								cellStyle="Basic"
								onPress={this.openSettings}
								title="Log in with St. Olaf"
							/>
						) : null}

						{message ? <Cell cellStyle="Basic" title={message} /> : null}

				)}
			</ScrollView>
		)
	}
}

function mapState(state: ReduxState): ReduxStateProps {
	return {
		alertSeen: state.settings ? state.settings.unofficiallyAcknowledged : false,
		status: state.login ? state.login.status : 'initializing',
	}
}

export default connect(
	mapState,
	{logInViaCredentials, hasSeenAcknowledgement},
)(BalancesView)

let cellMargin = 10
let cellSidePadding = 10
let cellEdgePadding = 10

let styles = StyleSheet.create({
	stage: {
		backgroundColor: c.sectionBgColor,
		paddingTop: 20,
		paddingBottom: 20,
	},

	common: {
		backgroundColor: c.white,
	},

	balances: {
		borderRightWidth: StyleSheet.hairlineWidth,
		borderRightColor: c.iosSeparator,
	},

	finalCell: {
		borderRightWidth: 0,
	},

	balancesRow: {
		flexDirection: 'row',
		marginTop: 0,
		marginBottom: -10,
	},

	rectangle: {
		height: 88,
		flex: 1,
		alignItems: 'center',
		paddingTop: cellSidePadding,
		paddingBottom: cellSidePadding,
		paddingRight: cellEdgePadding,
		paddingLeft: cellEdgePadding,
		marginBottom: cellMargin,
	},

	// Text styling
	financialText: {
		paddingTop: 8,
		color: c.iosDisabledText,
		textAlign: 'center',
		fontWeight: '200',
		fontSize: 23,
	},
	rectangleButtonText: {
		paddingTop: 15,
		color: c.black,
		textAlign: 'center',
		fontSize: 16,
	},
})

function getValueOrNa(value: ?string): string {
	if (value == null) {
		return 'N/A'
	}
	return value
}

function FormattedValueCell(props: {
	indeterminate: boolean,
	label: string,
	value: ?string,
	style?: any,
	formatter: (?string) => string,
}) {
	let {indeterminate, label, value, style, formatter} = props

	return (
		<View style={[styles.rectangle, styles.common, styles.balances, style]}>
			<Text
				autoAdjustsFontSize={true}
				selectable={true}
				style={styles.financialText}
			>
				{indeterminate ? '…' : formatter(value)}
			</Text>
			<Text autoAdjustsFontSize={true} style={styles.rectangleButtonText}>
				{label}
			</Text>
		</View>
	)
}
<commit_msg>Move alert check conditional to componentDidMount<commit_after>// @flow

import * as React from 'react'
import {
	StyleSheet,
	ScrollView,
	View,
	Text,
	RefreshControl,
	Alert,
} from 'react-native'
import {TabBarIcon} from '@frogpond/navigation-tabs'
import {connect} from 'react-redux'
import {Cell, TableView, Section} from '@frogpond/tableview'
import {hasSeenAcknowledgement} from '../../redux/parts/settings'
import {logInViaCredentials} from '../../redux/parts/login'
import {type LoginStateEnum} from '../../redux/parts/login'
import {getBalances} from '../../lib/financials'
import {loadLoginCredentials} from '../../lib/login'
import {type ReduxState} from '../../redux'
import delay from 'delay'
import * as c from '@frogpond/colors'
import type {TopLevelViewPropsType} from '../types'

const DISCLAIMER = 'This data may be outdated or otherwise inaccurate.'
const LONG_DISCLAIMER =
	'This data may be inaccurate.\nBon Appétit is always right.\nThis app is unofficial.'

type ReactProps = TopLevelViewPropsType

type ReduxStateProps = {
	status: LoginStateEnum,
	alertSeen: boolean,
}

type ReduxDispatchProps = {
	logInViaCredentials: (string, string) => Promise<any>,
	hasSeenAcknowledgement: () => any,
}

type Props = ReactProps & ReduxStateProps & ReduxDispatchProps

type State = {
	loading: boolean,
	flex: ?string,
	ole: ?string,
	print: ?string,
	weeklyMeals: ?string,
	dailyMeals: ?string,
	mealPlan: ?string,
	message: ?string,
}

class BalancesView extends React.PureComponent<Props, State> {
	static navigationOptions = {
		tabBarLabel: 'Balances',
		tabBarIcon: TabBarIcon('card'),
	}

	state = {
		loading: false,
		flex: null,
		ole: null,
		print: null,
		weeklyMeals: null,
		dailyMeals: null,
		mealPlan: null,
		message: null,
	}

	componentDidMount() {
		if (this.props.alertSeen) {
			// calling "refresh" here, to make clear to the user
			// that the data is being updated
			this.refresh()
		} else {
			Alert.alert('', LONG_DISCLAIMER, [
				{text: 'I Disagree', onPress: this.goBack, style: 'cancel'},
				{text: 'Okay', onPress: this.props.hasSeenAcknowledgement},
			])
		}
	}

	refresh = async () => {
		let start = Date.now()
		this.setState(() => ({loading: true}))

		await this.fetchData()

		// wait 0.5 seconds – if we let it go at normal speed, it feels broken.
		let elapsed = Date.now() - start
		await delay(500 - elapsed)

		this.setState(() => ({loading: false}))
	}

	logIn = async () => {
		let {status} = this.props
		if (status === 'logged-in' || status === 'checking') {
			return
		}

		let {username = '', password = ''} = await loadLoginCredentials()
		if (username && password) {
			await this.props.logInViaCredentials(username, password)
		}
	}

	fetchData = async () => {
		// trigger the login so that the banner at the bottom hides itself
		await this.logIn()

		let balances = await getBalances()

		if (balances.error === true) {
			return
		}

		let {value} = balances

		let {flex, ole, print} = value
		let {weekly: weeklyMeals, daily: dailyMeals, plan: mealPlan} = value

		this.setState(() => ({
			flex,
			ole,
			print,
			weeklyMeals,
			dailyMeals,
			mealPlan,
		}))
	}

	openSettings = () => this.props.navigation.navigate('SettingsView')

	goBack = () => this.props.navigation.goBack(null)

	render() {
		let {
			flex,
			ole,
			print,
			dailyMeals,
			weeklyMeals,
			mealPlan,
			message,
			loading,
		} = this.state
		let {status} = this.props

		return (
			<ScrollView
				contentContainerStyle={styles.stage}
				refreshControl={
					<RefreshControl
						onRefresh={this.refresh}
						refreshing={this.state.loading}
					/>
				}
			>
				<TableView>

						<View style={styles.balancesRow}>
							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Flex"
								value={flex}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Ole"
								value={ole}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Copy/Print"
								style={styles.finalCell}
								value={print}
							/>
						</View>

						<View style={styles.balancesRow}>
							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Daily Meals Left"
								value={dailyMeals}
							/>

							<FormattedValueCell
								formatter={getValueOrNa}
								indeterminate={loading}
								label="Weekly Meals Left"
								style={styles.finalCell}
								value={weeklyMeals}
							/>
						</View>
						{mealPlan && (
							<Cell cellStyle="Subtitle" detail={mealPlan} title="Meal Plan" />
						)}

				</TableView>

				{(status !== 'logged-in' || message) && (

						{status !== 'logged-in' ? (
							<Cell
								accessory="DisclosureIndicator"
								cellStyle="Basic"
								onPress={this.openSettings}
								title="Log in with St. Olaf"
							/>
						) : null}

						{message ? <Cell cellStyle="Basic" title={message} /> : null}

				)}
			</ScrollView>
		)
	}
}

function mapState(state: ReduxState): ReduxStateProps {
	return {
		alertSeen: state.settings ? state.settings.unofficiallyAcknowledged : false,
		status: state.login ? state.login.status : 'initializing',
	}
}

export default connect(
	mapState,
	{logInViaCredentials, hasSeenAcknowledgement},
)(BalancesView)

let cellMargin = 10
let cellSidePadding = 10
let cellEdgePadding = 10

let styles = StyleSheet.create({
	stage: {
		backgroundColor: c.sectionBgColor,
		paddingTop: 20,
		paddingBottom: 20,
	},

	common: {
		backgroundColor: c.white,
	},

	balances: {
		borderRightWidth: StyleSheet.hairlineWidth,
		borderRightColor: c.iosSeparator,
	},

	finalCell: {
		borderRightWidth: 0,
	},

	balancesRow: {
		flexDirection: 'row',
		marginTop: 0,
		marginBottom: -10,
	},

	rectangle: {
		height: 88,
		flex: 1,
		alignItems: 'center',
		paddingTop: cellSidePadding,
		paddingBottom: cellSidePadding,
		paddingRight: cellEdgePadding,
		paddingLeft: cellEdgePadding,
		marginBottom: cellMargin,
	},

	// Text styling
	financialText: {
		paddingTop: 8,
		color: c.iosDisabledText,
		textAlign: 'center',
		fontWeight: '200',
		fontSize: 23,
	},
	rectangleButtonText: {
		paddingTop: 15,
		color: c.black,
		textAlign: 'center',
		fontSize: 16,
	},
})

function getValueOrNa(value: ?string): string {
	if (value == null) {
		return 'N/A'
	}
	return value
}

function FormattedValueCell(props: {
	indeterminate: boolean,
	label: string,
	value: ?string,
	style?: any,
	formatter: (?string) => string,
}) {
	let {indeterminate, label, value, style, formatter} = props

	return (
		<View style={[styles.rectangle, styles.common, styles.balances, style]}>
			<Text
				autoAdjustsFontSize={true}
				selectable={true}
				style={styles.financialText}
			>
				{indeterminate ? '…' : formatter(value)}
			</Text>
			<Text autoAdjustsFontSize={true} style={styles.rectangleButtonText}>
				{label}
			</Text>
		</View>
	)
}
<|endoftext|>


<commit_before>'use strict';

var _                    = require('mano').i18n.bind('View: User')
  , loginDialog          = require('./components/login-dialog')
  , registerDialog       = require('./components/register-dialog')
  , modalContainer       = require('./components/modal-container')
  , requestAccountDialog = require('./components/request-account-dialog');

exports._parent = require('./base');

exports.menu = function () {
	modalContainer.append(loginDialog);
	modalContainer.append(registerDialog(this));

	insert(_if(this.user._isDemo,
		div(
			{ class: 'submitted-menu-demo-info-wrapper' },
			ul({ class: 'header-top-menu-demo' },
				li(a({ class: 'demo-public-out', href: '/logout/', rel: 'server' }, _("Out of demo mode"))),
				li(a({ class: 'demo-public-login', href: '#login' }, _("Log in")))),
			div({ class: 'submitted-menu-demo-info' },
				p(_("Did this demo convinced you?")),
				a({ href: '#register' }, _("Create account")))
		),
		ul(
			{ class: 'header-top-menu' },
			list(exports._menuItems, function (item) { return item.call(this); }.bind(this))
		)));
};

exports.main = function () {
	div({ class: 'submitted-menu' },
		div({ class: 'submitted-menu-bar content' },
			nav(ul({ class: 'submitted-menu-items', id: 'submitted-menu' },
				exports._submittedMenu.call(this))),
			_if(this.user._isDemo, div({ class: 'submitted-menu-demo' },
				a({ class: 'submitted-menu-demo-ribon' }, _("Demo"))))));

	insert(_if(this.user._isDemo,
		div({ class: 'submitted-menu-demo-msg' },
			div({ class: 'content' },
				h3(_("Demo version")),
				p(_("Introduction to demo version"))))));

	insert(_if(this.manager, function () {
		return this.manager._currentlyManagedUser.map(function (managedUser) {
			if (!managedUser) return;
			var isUserReallyManaged = eq(this.manager, managedUser._manager);

			requestAccountDialog(managedUser);

			return div({ class: 'manager-bar' },
				div({ class: 'content' },
					div({ class: 'manager-bar-info' },
						span(_("Client"), ": "),
						this.appName === 'user' ? a({ href: '/' }, managedUser._fullName)
						: exports._getMyAccountButton(this.manager, managedUser._fullName)),
					_if(isUserReallyManaged, div({ class: 'manager-bar-actions' },
						_if(not(managedUser._isActiveAccount),
							a({
								class: 'actions-create',
								href: '#request-create-account'
							}, span(_('Create account for this client')))),
						a({ href: '/managed-user-profile/' },
							span({ class: 'hint-optional hint-optional-left',
								'data-hint': _('edit user details') },
								i({ class: 'fa fa-cog' })))))));
		}.bind(this));
	}.bind(this)));

	div({ class: 'user-forms', id: 'sub-main' });
};

exports._submittedMenu = Function.prototype;

exports._getMyAccountButton = function (user, fullName) {
	return form({ method: 'post', action: '/change-business-process/' },
		input({ type: 'hidden',
			name: user.__id__ + '/currentBusinessProcess', value: null }),
		button({ type: 'submit' }, fullName));
};

exports._getManagerButton = function (user, roleTitle) {
	return form({ method: 'post', action: '/change-currently-managed-user/' },
		input({ type: 'hidden',
			name: user.__id__ + '/currentBusinessProcess', value: null }),
		input({ type: 'hidden',
			name: user.__id__ + '/currentlyManagedUser', value: null }),
		button({ type: 'submit' }, roleTitle));
};

exports._extraRoleLabel = function () {
	return _if(or(this.manager, eq(this.user._currentRoleResolved, 'manager')), li(
		span(
			{ class: 'manager-label' },
			_("Notary")
		)
	));
};

exports._userNameMenuItem = function () {
	function openMenu() {
		console.log("clicked");
		document.getElementById("dropDownMenu").classList.toggle("header-top-menu-opened");
		document.getElementById("dropDownMenu-angle").classList.toggle("fa-angle-down");
		document.getElementById("dropDownMenu-angle").classList.toggle("fa-angle-up");
	}
	window.onclick = function (event) {
		console.log(event);
		if (!event.target.classList.contains('header-top-dropdown-button')) {
			console.log("no btn clicked");
			document.getElementById("dropDownMenu").classList.remove("header-top-menu-opened");
			document.getElementById("dropDownMenu-angle").classList.add("fa-angle-down");
			document.getElementById("dropDownMenu-angle").classList.remove("fa-angle-up");
		}
	};
	return li({ id: "dropDownMenu", onclick: openMenu, class: "header-top-dropdown-container" },
		a(span({ class: 'header-top-user-name header-top-dropdown-button' },
			this.manager ? this.manager._fullName : this.user._fullName,
			i({ id: 'dropDownMenu-angle', class: 'fa fa-angle-down' }))),
		ul({ class: "header-top-menu-dropdown-content" },
			li(form(button("Form button"))),
			li(hr()),
			li(a({ href: '/profile/' }, _("My informations"))),
			li(a({ href: '/logout/', rel: 'server' }, _("Log out")))
			)
		);
};

exports._profileMenuItem = function () {
	return li(
		a(
			{ href: '/profile/' },
			span({ class: 'fa fa-cog' }, "Preferences")
		)
	);
};

exports._logoutMenuItem = function () {
	return li(
		a(
			{ href: '/logout/', rel: 'server' },
			span({ class: 'fa fa-power-off' }, "Log out")
		)
	);
};

exports._menuItems = [
	exports._extraRoleLabel,
	exports._userNameMenuItem
];
<commit_msg>Update script for dropdown<commit_after>'use strict';

var _                    = require('mano').i18n.bind('View: User')
  , loginDialog          = require('./components/login-dialog')
  , registerDialog       = require('./components/register-dialog')
  , modalContainer       = require('./components/modal-container')
  , requestAccountDialog = require('./components/request-account-dialog')
  , $                    = require('mano-legacy');

require('mano-legacy/element#/class');

exports._parent = require('./base');

exports.menu = function () {
	modalContainer.append(loginDialog);
	modalContainer.append(registerDialog(this));

	insert(_if(this.user._isDemo,
		div(
			{ class: 'submitted-menu-demo-info-wrapper' },
			ul({ class: 'header-top-menu-demo' },
				li(a({ class: 'demo-public-out', href: '/logout/', rel: 'server' }, _("Out of demo mode"))),
				li(a({ class: 'demo-public-login', href: '#login' }, _("Log in")))),
			div({ class: 'submitted-menu-demo-info' },
				p(_("Did this demo convinced you?")),
				a({ href: '#register' }, _("Create account")))
		),
		ul(
			{ class: 'header-top-menu' },
			list(exports._menuItems, function (item) { return item.call(this); }.bind(this))
		)));
};

exports.main = function () {
	div({ class: 'submitted-menu' },
		div({ class: 'submitted-menu-bar content' },
			nav(ul({ class: 'submitted-menu-items', id: 'submitted-menu' },
				exports._submittedMenu.call(this))),
			_if(this.user._isDemo, div({ class: 'submitted-menu-demo' },
				a({ class: 'submitted-menu-demo-ribon' }, _("Demo"))))));

	insert(_if(this.user._isDemo,
		div({ class: 'submitted-menu-demo-msg' },
			div({ class: 'content' },
				h3(_("Demo version")),
				p(_("Introduction to demo version"))))));

	insert(_if(this.manager, function () {
		return this.manager._currentlyManagedUser.map(function (managedUser) {
			if (!managedUser) return;
			var isUserReallyManaged = eq(this.manager, managedUser._manager);

			requestAccountDialog(managedUser);

			return div({ class: 'manager-bar' },
				div({ class: 'content' },
					div({ class: 'manager-bar-info' },
						span(_("Client"), ": "),
						this.appName === 'user' ? a({ href: '/' }, managedUser._fullName)
						: exports._getMyAccountButton(this.manager, managedUser._fullName)),
					_if(isUserReallyManaged, div({ class: 'manager-bar-actions' },
						_if(not(managedUser._isActiveAccount),
							a({
								class: 'actions-create',
								href: '#request-create-account'
							}, span(_('Create account for this client')))),
						a({ href: '/managed-user-profile/' },
							span({ class: 'hint-optional hint-optional-left',
								'data-hint': _('edit user details') },
								i({ class: 'fa fa-cog' })))))));
		}.bind(this));
	}.bind(this)));

	div({ class: 'user-forms', id: 'sub-main' });
};

exports._submittedMenu = Function.prototype;

exports._getMyAccountButton = function (user, fullName) {
	return form({ method: 'post', action: '/change-business-process/' },
		input({ type: 'hidden',
			name: user.__id__ + '/currentBusinessProcess', value: null }),
		button({ type: 'submit' }, fullName));
};

exports._getManagerButton = function (user, roleTitle) {
	return form({ method: 'post', action: '/change-currently-managed-user/' },
		input({ type: 'hidden',
			name: user.__id__ + '/currentBusinessProcess', value: null }),
		input({ type: 'hidden',
			name: user.__id__ + '/currentlyManagedUser', value: null }),
		button({ type: 'submit' }, roleTitle));
};

exports._extraRoleLabel = function () {
	return _if(or(this.manager, eq(this.user._currentRoleResolved, 'manager')), li(
		span(
			{ class: 'manager-label' },
			_("Notary")
		)
	));
};

exports._userNameMenuItem = function () {
	return [li({ id: "dropDownMenu", class: "header-top-dropdown-container" },
			a(span({ class: 'header-top-user-name header-top-dropdown-button' },
				this.manager ? this.manager._fullName : this.user._fullName,
				i({ id: 'dropDownMenu-angle', class: 'fa fa-angle-down' }))),
			ul({ class: "header-top-menu-dropdown-content" },
				li(form(button("Form button"))),
				li(hr()),
				li(a({ href: '/profile/' }, _("My informations"))),
				li(a({ href: '/logout/', rel: 'server' }, _("Log out")))
				)
			),
			script(function () {
			var dropDownMenu = $('dropDownMenu'), dropDownMenuAngle = $('dropDownMenu-angle');
			dropDownMenu.onclick = function () {
				if (dropDownMenu.hasClass("header-top-menu-opened")) {
					dropDownMenu.removeClass("header-top-menu-opened");
					dropDownMenuAngle.removeClass("fa-angle-up");
					dropDownMenuAngle.addClass("fa-angle-down");
				} else {
					dropDownMenu.addClass("header-top-menu-opened");
					dropDownMenuAngle.addClass("fa-angle-up");
					dropDownMenuAngle.removeClass("fa-angle-down");
				}
			};
		})];
};

exports._profileMenuItem = function () {
	return li(
		a(
			{ href: '/profile/' },
			span({ class: 'fa fa-cog' }, "Preferences")
		)
	);
};

exports._logoutMenuItem = function () {
	return li(
		a(
			{ href: '/logout/', rel: 'server' },
			span({ class: 'fa fa-power-off' }, "Log out")
		)
	);
};

exports._menuItems = [
	exports._extraRoleLabel,
	exports._userNameMenuItem
];
<|endoftext|>


<commit_before>/* @flow */
import type { Auth, ResponseExtractionFunc } from '../types';
import { getAuthHeader, encodeAsURI, isValidUrl } from '../utils/url';
import userAgent from '../utils/userAgent';
import { networkActivityStart, networkActivityStop } from '../utils/networkActivity';

const apiVersion = 'api/v1';

const defaultResFunc: ResponseExtractionFunc = res => res;

export const fetchWithAuth = async (auth: Auth, url: string, params: Object = {}) => {
  if (!isValidUrl(url)) {
    throw new Error(`Invalid url ${url}`);
  }

  const contentType =
    params.body instanceof FormData
      ? 'multipart/form-data'
      : 'application/x-www-form-urlencoded; charset=utf-8';
  const allParams = {
    headers: {
      'Content-Type': contentType,
      'User-Agent': userAgent,
      Authorization: getAuthHeader(auth.email, auth.apiKey),
    },
    ...params,
  };
  return fetch(url, allParams);
};

export const apiFetch = async (auth: Auth, route: string, params: Object = {}) =>
  fetchWithAuth(auth, `${auth.realm}/${apiVersion}/${route}`, params);

const makeApiError = (httpStatus: number, data: ?Object) => {
  const error = new Error('API');
  // $FlowFixMe
  error.data = data;
  // $FlowFixMe
  error.httpStatus = httpStatus;
  return error;
};

export const apiCall = async (
  auth: Auth,
  route: string,
  params: Object = {},
  resFunc: ResponseExtractionFunc = defaultResFunc,
  isSilent: boolean = false,
) => {
  try {
    networkActivityStart(isSilent);
    const response = await apiFetch(auth, route, params);
    const json = await response.json().catch(() => undefined);
    if (response.ok && json !== undefined) {
      return resFunc(json);
    }
    throw makeApiError(response.status, json);
  } finally {
    networkActivityStop(isSilent);
  }
};

export const apiGet = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
  isSilent: boolean = false,
) =>
  apiCall(
    auth,
    `${route}?${encodeAsURI(params)}`,
    {
      method: 'get',
    },
    resFunc,
    isSilent,
  );

export const apiPost = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
) =>
  apiCall(
    auth,
    route,
    {
      method: 'post',
      body: encodeAsURI(params),
    },
    resFunc,
  );

export const apiFile = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  body: FormData,
) =>
  apiCall(
    auth,
    route,
    {
      method: 'post',
      body,
    },
    resFunc,
  );

export const apiPut = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
) =>
  apiCall(
    auth,
    route,
    {
      method: 'put',
      body: encodeAsURI(params),
    },
    resFunc,
  );

export const apiDelete = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
) =>
  apiCall(
    auth,
    route,
    {
      method: 'delete',
      body: encodeAsURI(params),
    },
    resFunc,
  );

export const apiPatch = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
) =>
  apiCall(
    auth,
    route,
    {
      method: 'patch',
      body: encodeAsURI(params),
    },
    resFunc,
  );

export const apiHead = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
) =>
  apiCall(
    auth,
    `${route}?${encodeAsURI(params)}`,
    {
      method: 'head',
    },
    resFunc,
  );
<commit_msg>api: Log failed API responses to the console<commit_after>/* @flow */
import type { Auth, ResponseExtractionFunc } from '../types';
import { getAuthHeader, encodeAsURI, isValidUrl } from '../utils/url';
import userAgent from '../utils/userAgent';
import { networkActivityStart, networkActivityStop } from '../utils/networkActivity';

const apiVersion = 'api/v1';

const defaultResFunc: ResponseExtractionFunc = res => res;

export const fetchWithAuth = async (auth: Auth, url: string, params: Object = {}) => {
  if (!isValidUrl(url)) {
    throw new Error(`Invalid url ${url}`);
  }

  const contentType =
    params.body instanceof FormData
      ? 'multipart/form-data'
      : 'application/x-www-form-urlencoded; charset=utf-8';
  const allParams = {
    headers: {
      'Content-Type': contentType,
      'User-Agent': userAgent,
      Authorization: getAuthHeader(auth.email, auth.apiKey),
    },
    ...params,
  };
  return fetch(url, allParams);
};

export const apiFetch = async (auth: Auth, route: string, params: Object = {}) =>
  fetchWithAuth(auth, `${auth.realm}/${apiVersion}/${route}`, params);

const makeApiError = (httpStatus: number, data: ?Object) => {
  const error = new Error('API');
  // $FlowFixMe
  error.data = data;
  // $FlowFixMe
  error.httpStatus = httpStatus;
  return error;
};

export const apiCall = async (
  auth: Auth,
  route: string,
  params: Object = {},
  resFunc: ResponseExtractionFunc = defaultResFunc,
  isSilent: boolean = false,
) => {
  try {
    networkActivityStart(isSilent);
    const response = await apiFetch(auth, route, params);
    const json = await response.json().catch(() => undefined);
    if (response.ok && json !== undefined) {
      return resFunc(json);
    }
    // eslint-disable-next-line no-console
    console.error({ route, params, response });
    throw makeApiError(response.status, json);
  } finally {
    networkActivityStop(isSilent);
  }
};

export const apiGet = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
  isSilent: boolean = false,
) =>
  apiCall(
    auth,
    `${route}?${encodeAsURI(params)}`,
    {
      method: 'get',
    },
    resFunc,
    isSilent,
  );

export const apiPost = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
) =>
  apiCall(
    auth,
    route,
    {
      method: 'post',
      body: encodeAsURI(params),
    },
    resFunc,
  );

export const apiFile = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  body: FormData,
) =>
  apiCall(
    auth,
    route,
    {
      method: 'post',
      body,
    },
    resFunc,
  );

export const apiPut = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
) =>
  apiCall(
    auth,
    route,
    {
      method: 'put',
      body: encodeAsURI(params),
    },
    resFunc,
  );

export const apiDelete = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
) =>
  apiCall(
    auth,
    route,
    {
      method: 'delete',
      body: encodeAsURI(params),
    },
    resFunc,
  );

export const apiPatch = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
) =>
  apiCall(
    auth,
    route,
    {
      method: 'patch',
      body: encodeAsURI(params),
    },
    resFunc,
  );

export const apiHead = async (
  auth: Auth,
  route: string,
  resFunc: ResponseExtractionFunc = defaultResFunc,
  params: Object = {},
) =>
  apiCall(
    auth,
    `${route}?${encodeAsURI(params)}`,
    {
      method: 'head',
    },
    resFunc,
  );
<|endoftext|>


<commit_before>/**
 * @flow
 * All About Olaf
 * Balances page
 */

import React from 'react'
import {StyleSheet, ScrollView, View, Text, RefreshControl} from 'react-native'
import {TabBarIcon} from '../components/tabbar-icon'
import {connect} from 'react-redux'
import {Cell, TableView, Section} from 'react-native-tableview-simple'
import type {LoginStateType} from '../../flux/parts/settings'

import {updateBalances} from '../../flux/parts/sis'

import delay from 'delay'
import isNil from 'lodash/isNil'
import * as c from '../components/colors'

import type {TopLevelViewPropsType} from '../types'

class BalancesView extends React.Component {
  static navigationOptions = {
    tabBarLabel: 'Balances',
    tabBarIcon: TabBarIcon('card'),
  }

  props: TopLevelViewPropsType & {
    flex: ?number,
    ole: ?number,
    print: ?number,
    weeklyMeals: ?number,
    dailyMeals: ?number,
    loginState: LoginStateType,
    message: ?string,

    updateBalances: boolean => any,
  }

  componentWillMount() {
    // calling "refresh" here, to make clear to the user
    // that the data is being updated
    this.refresh()
  }

  state = {
    loading: false,
  }

  refresh = async () => {
    let start = Date.now()
    this.setState({loading: true})

    await this.fetchData()

    // wait 0.5 seconds – if we let it go at normal speed, it feels broken.
    let elapsed = Date.now() - start
    await delay(500 - elapsed)

    this.setState({loading: false})
  }

  fetchData = async () => {
    await Promise.all([this.props.updateBalances(true)])
  }

  openSettings = () => {
    this.props.navigation.navigate('SettingsView')
  }

  render() {
    let {flex, ole, print, dailyMeals, weeklyMeals} = this.props
    let {loading} = this.state

    return (
      <ScrollView
        contentContainerStyle={styles.stage}
        refreshControl={
          <RefreshControl
            refreshing={this.state.loading}
            onRefresh={this.refresh}
          />
        }
      >
        <TableView>

            <View style={styles.balancesRow}>
              <FormattedValueCell
                label="Flex"
                value={flex}
                indeterminate={loading}
                formatter={getFormattedCurrency}
              />

              <FormattedValueCell
                label="Ole"
                value={ole}
                indeterminate={loading}
                formatter={getFormattedCurrency}
              />

              <FormattedValueCell
                label="Copy/Print"
                value={print}
                indeterminate={loading}
                formatter={getFormattedCurrency}
                style={styles.finalCell}
              />
            </View>

            <View style={styles.balancesRow}>
              <FormattedValueCell
                label="Daily Meals Left"
                value={dailyMeals}
                indeterminate={loading}
                formatter={getFormattedMealsRemaining}
              />

              <FormattedValueCell
                label="Weekly Meals Left"
                value={weeklyMeals}
                indeterminate={loading}
                formatter={getFormattedMealsRemaining}
                style={styles.finalCell}
              />
            </View>

          {this.props.loginState !== 'logged-in' || this.props.message
            ?

                {this.props.loginState !== 'logged-in'
                  ? <Cell
                      cellStyle="Basic"
                      title="Log in with St. Olaf"
                      accessory="DisclosureIndicator"
                      onPress={this.openSettings}
                    />
                  : null}

                {this.props.message
                  ? <Cell cellStyle="Basic" title={this.props.message} />
                  : null}

            : null}
        </TableView>
      </ScrollView>
    )
  }
}

function mapStateToProps(state) {
  return {
    flex: state.sis.balances.flex,
    ole: state.sis.balances.ole,
    print: state.sis.balances.print,
    weeklyMeals: state.sis.balances.weekly,
    dailyMeals: state.sis.balances.daily,
    message: state.sis.balances.message,

    loginState: state.settings.credentials.state,
  }
}

function mapDispatchToProps(dispatch) {
  return {
    updateBalances: force => dispatch(updateBalances(force)),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(BalancesView)

let cellMargin = 10
let cellSidePadding = 10
let cellEdgePadding = 10

let styles = StyleSheet.create({
  stage: {
    backgroundColor: c.iosLightBackground,
    paddingTop: 20,
    paddingBottom: 20,
  },

  common: {
    backgroundColor: c.white,
  },

  balances: {
    borderRightWidth: StyleSheet.hairlineWidth,
    borderRightColor: c.iosSeparator,
  },

  finalCell: {
    borderRightWidth: 0,
  },

  balancesRow: {
    flexDirection: 'row',
    marginTop: 0,
    marginBottom: -10,
  },

  rectangle: {
    height: 88,
    flex: 1,
    alignItems: 'center',
    paddingTop: cellSidePadding,
    paddingBottom: cellSidePadding,
    paddingRight: cellEdgePadding,
    paddingLeft: cellEdgePadding,
    marginBottom: cellMargin,
  },

  // Text styling
  financialText: {
    paddingTop: 8,
    color: c.iosDisabledText,
    textAlign: 'center',
    fontWeight: '200',
    fontSize: 23,
  },
  rectangleButtonText: {
    paddingTop: 15,
    color: c.black,
    textAlign: 'center',
    fontSize: 16,
  },
})

function getFormattedCurrency(value: ?number): string {
  if (isNil(value)) {
    return 'N/A'
  }
  return '$' + (((value: any): number) / 100).toFixed(2)
}

function getFormattedMealsRemaining(value: ?number): string {
  if (isNil(value)) {
    return 'N/A'
  }
  return (value: any).toString()
}

function FormattedValueCell({
  indeterminate,
  label,
  value,
  style,
  formatter,
}: {
  indeterminate: boolean,
  label: string,
  value: ?number,
  style?: any,
  formatter: (?number) => string,
}) {
  return (
    <View style={[styles.rectangle, styles.common, styles.balances, style]}>
      <Text
        selectable={true}
        style={styles.financialText}
        autoAdjustsFontSize={true}
      >
        {indeterminate ? '…' : formatter(value)}
      </Text>
      <Text style={styles.rectangleButtonText} autoAdjustsFontSize={true}>
        {label}
      </Text>
    </View>
  )
}
<commit_msg>Update balances.js<commit_after>/**
 * @flow
 * All About Olaf
 * Balances page
 */

import React from 'react'
import {StyleSheet, ScrollView, View, Text, RefreshControl} from 'react-native'
import {TabBarIcon} from '../components/tabbar-icon'
import {connect} from 'react-redux'
import {Cell, TableView, Section} from 'react-native-tableview-simple'
import type {LoginStateType} from '../../flux/parts/settings'

import {updateBalances} from '../../flux/parts/sis'

import delay from 'delay'
import isNil from 'lodash/isNil'
import * as c from '../components/colors'

import type {TopLevelViewPropsType} from '../types'

class BalancesView extends React.Component {
  static navigationOptions = {
    tabBarLabel: 'Balances',
    tabBarIcon: TabBarIcon('card'),
  }

  props: TopLevelViewPropsType & {
    flex: ?number,
    ole: ?number,
    print: ?number,
    weeklyMeals: ?number,
    dailyMeals: ?number,
    loginState: LoginStateType,
    message: ?string,

    updateBalances: boolean => any,
  }

  state = {
    loading: false,
  }

  componentWillMount() {
    // calling "refresh" here, to make clear to the user
    // that the data is being updated
    this.refresh()
  }

  refresh = async () => {
    let start = Date.now()
    this.setState({loading: true})

    await this.fetchData()

    // wait 0.5 seconds – if we let it go at normal speed, it feels broken.
    let elapsed = Date.now() - start
    await delay(500 - elapsed)

    this.setState({loading: false})
  }

  fetchData = async () => {
    await Promise.all([this.props.updateBalances(true)])
  }

  openSettings = () => {
    this.props.navigation.navigate('SettingsView')
  }

  render() {
    let {flex, ole, print, dailyMeals, weeklyMeals} = this.props
    let {loading} = this.state

    return (
      <ScrollView
        contentContainerStyle={styles.stage}
        refreshControl={
          <RefreshControl
            refreshing={this.state.loading}
            onRefresh={this.refresh}
          />
        }
      >
        <TableView>

            <View style={styles.balancesRow}>
              <FormattedValueCell
                label="Flex"
                value={flex}
                indeterminate={loading}
                formatter={getFormattedCurrency}
              />

              <FormattedValueCell
                label="Ole"
                value={ole}
                indeterminate={loading}
                formatter={getFormattedCurrency}
              />

              <FormattedValueCell
                label="Copy/Print"
                value={print}
                indeterminate={loading}
                formatter={getFormattedCurrency}
                style={styles.finalCell}
              />
            </View>

            <View style={styles.balancesRow}>
              <FormattedValueCell
                label="Daily Meals Left"
                value={dailyMeals}
                indeterminate={loading}
                formatter={getFormattedMealsRemaining}
              />

              <FormattedValueCell
                label="Weekly Meals Left"
                value={weeklyMeals}
                indeterminate={loading}
                formatter={getFormattedMealsRemaining}
                style={styles.finalCell}
              />
            </View>

          {this.props.loginState !== 'logged-in' || this.props.message
            ?

                {this.props.loginState !== 'logged-in'
                  ? <Cell
                      cellStyle="Basic"
                      title="Log in with St. Olaf"
                      accessory="DisclosureIndicator"
                      onPress={this.openSettings}
                    />
                  : null}

                {this.props.message
                  ? <Cell cellStyle="Basic" title={this.props.message} />
                  : null}

            : null}
        </TableView>
      </ScrollView>
    )
  }
}

function mapStateToProps(state) {
  return {
    flex: state.sis.balances.flex,
    ole: state.sis.balances.ole,
    print: state.sis.balances.print,
    weeklyMeals: state.sis.balances.weekly,
    dailyMeals: state.sis.balances.daily,
    message: state.sis.balances.message,

    loginState: state.settings.credentials.state,
  }
}

function mapDispatchToProps(dispatch) {
  return {
    updateBalances: force => dispatch(updateBalances(force)),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(BalancesView)

let cellMargin = 10
let cellSidePadding = 10
let cellEdgePadding = 10

let styles = StyleSheet.create({
  stage: {
    backgroundColor: c.iosLightBackground,
    paddingTop: 20,
    paddingBottom: 20,
  },

  common: {
    backgroundColor: c.white,
  },

  balances: {
    borderRightWidth: StyleSheet.hairlineWidth,
    borderRightColor: c.iosSeparator,
  },

  finalCell: {
    borderRightWidth: 0,
  },

  balancesRow: {
    flexDirection: 'row',
    marginTop: 0,
    marginBottom: -10,
  },

  rectangle: {
    height: 88,
    flex: 1,
    alignItems: 'center',
    paddingTop: cellSidePadding,
    paddingBottom: cellSidePadding,
    paddingRight: cellEdgePadding,
    paddingLeft: cellEdgePadding,
    marginBottom: cellMargin,
  },

  // Text styling
  financialText: {
    paddingTop: 8,
    color: c.iosDisabledText,
    textAlign: 'center',
    fontWeight: '200',
    fontSize: 23,
  },
  rectangleButtonText: {
    paddingTop: 15,
    color: c.black,
    textAlign: 'center',
    fontSize: 16,
  },
})

function getFormattedCurrency(value: ?number): string {
  if (isNil(value)) {
    return 'N/A'
  }
  return '$' + (((value: any): number) / 100).toFixed(2)
}

function getFormattedMealsRemaining(value: ?number): string {
  if (isNil(value)) {
    return 'N/A'
  }
  return (value: any).toString()
}

function FormattedValueCell({
  indeterminate,
  label,
  value,
  style,
  formatter,
}: {
  indeterminate: boolean,
  label: string,
  value: ?number,
  style?: any,
  formatter: (?number) => string,
}) {
  return (
    <View style={[styles.rectangle, styles.common, styles.balances, style]}>
      <Text
        selectable={true}
        style={styles.financialText}
        autoAdjustsFontSize={true}
      >
        {indeterminate ? '…' : formatter(value)}
      </Text>
      <Text style={styles.rectangleButtonText} autoAdjustsFontSize={true}>
        {label}
      </Text>
    </View>
  )
}
<|endoftext|>


<commit_before>import {ipcMain, ipcRenderer} from 'electron'
import util from 'util'
import {forwardLogs} from '../shared/local-debug'
import fs from 'fs'
import path from 'path'
import mkdirp from 'mkdirp'
import {logFileName} from '../shared/constants/platform.native.desktop.js'
import setupLocalLogs from '../shared/util/local-log'

const methods = ['log', 'error', 'info']
const originalConsole = {}
methods.forEach(k => {
  originalConsole[k] = console[k]
})

function fileDoesNotExist (err) {
  if (process.platform === 'win32' && err.errno === -4058) {
    return true
  }

  if (err.errno === -2) {
    return true
  }

  return false
}

const logLimit = 5e6
const logFile = logFileName()
let fileWritable = null
// If the file is too big, let's reset the log
if (logFile) {
  // ensure it exists
  mkdirp.sync(path.dirname(logFile))

  fs.access(logFile, fs.W_OK, err => {
    if (err && !fileDoesNotExist(err)) {
      console.log("Can't write to log file.", err)
      fileWritable = null
      return
    }

    fs.stat(logFile, (err, stat) => {
      if (err != null) {
        fileWritable = fs.createWriteStream(logFile)
        return
      }

      if (stat.size > logLimit) {
        fileWritable = fs.createWriteStream(logFile)
        return
      }

      fileWritable = fs.createWriteStream(logFile, {flags: 'a'})
    })
  })
}

function tee (...writeFns) {
  return t => writeFns.forEach(w => w(t))
}

const stdErrWriter = process.platform === 'win32' ? () => {} : t => process.stderr.write(t)
const stdOutWriter = process.platform === 'win32' ? () => {} : t => process.stdout.write(t)
const logFileWriter = t => fileWritable && fileWritable.write(t)

// override console logging to also go to stdout
const output = {
  error: tee(stdErrWriter, logFileWriter),
  log: tee(stdOutWriter, logFileWriter),
  warn: tee(stdOutWriter, logFileWriter),
}

export default function pipeLogs () {
  setupLocalLogs()

  if (!forwardLogs) {
    return
  }

  methods.forEach(k => {
    console[k] = (...args) => {
      if (args.length) {
        const out = output[k] || stdOutWriter
        out(`${k}: ${Date()} (${Date.now()}): ${util.format.apply(util, args)}\n`)
      }
    }
  })
}

export function ipcLogs () {
  // Simple ipc logging for debugging remote windows
  if (!forwardLogs) {
    return
  }

  ipcMain.on('console.log', (event, args) => {
    const prologue = `From ${event.sender.getTitle()}: `
    stdOutWriter(prologue)
    logFileWriter(prologue)
    console.log.apply(console, args)
  })

  ipcMain.on('console.warn', (event, args) => {
    const prologue = `From ${event.sender.getTitle()}: `
    stdOutWriter(prologue)
    logFileWriter(prologue)
    console.log.apply(console, args)
  })

  ipcMain.on('console.error', (event, args) => {
    const prologue = `From ${event.sender.getTitle()}: `
    stdOutWriter(prologue)
    logFileWriter(prologue)
    console.log.apply(console, args)
  })
}

export function ipcLogsRenderer () {
  const {logLocal, warnLocal, errorLocal} = setupLocalLogs()

  if (!forwardLogs) {
    return
  }

  window.console.log = (...args) => { try { logLocal(...args); ipcRenderer.send('console.log', args) } catch (_) {} }
  window.console.warn = (...args) => { try { warnLocal(...args); ipcRenderer.send('console.warn', args) } catch (_) {} }
  window.console.error = (...args) => { try { errorLocal(...args); ipcRenderer.send('console.error', args) } catch (_) {} }
}
<commit_msg>Fix file logging on startup (Electron) (#3807)<commit_after>import {ipcMain, ipcRenderer} from 'electron'
import util from 'util'
import {forwardLogs} from '../shared/local-debug'
import fs from 'fs'
import path from 'path'
import mkdirp from 'mkdirp'
import {logFileName} from '../shared/constants/platform.native.desktop.js'
import setupLocalLogs from '../shared/util/local-log'

const methods = ['log', 'error', 'info']
const originalConsole = {}
methods.forEach(k => {
  originalConsole[k] = console[k]
})

function fileDoesNotExist (err) {
  if (process.platform === 'win32' && err.errno === -4058) {
    return true
  }

  if (err.errno === -2) {
    return true
  }

  return false
}

const logLimit = 5e6
const logFile = logFileName()
let fileWritable = null

function setupFileWritable () {
  if (!logFile) {
    console.log('No log file')
    return
  }
  // Ensure log directory exists
  mkdirp.sync(path.dirname(logFile))

  // Check if we can write to log file
  try {
    fs.accessSync(logFile, fs.W_OK)
  } catch (e) {
    if (!fileDoesNotExist(e)) {
      console.error('Unable to write to log file:', e)
      return
    }
  }

  let stat = null
  try {
    stat = fs.statSync(logFile)
  } catch (e) {
    if (!fileDoesNotExist(e)) {
      console.error('Error getting status for log file:', e)
    }
    fileWritable = fs.createWriteStream(logFile)
    return
  }

  // If the file is too big, let's reset the log
  if (stat.size > logLimit) {
    console.log('File too big, resetting')
    fileWritable = fs.createWriteStream(logFile)
    return
  }

  // Append to existing log
  fileWritable = fs.createWriteStream(logFile, {flags: 'a'})
}

setupFileWritable()

function tee (...writeFns) {
  return t => writeFns.forEach(w => w(t))
}

const stdErrWriter = process.platform === 'win32' ? () => {} : t => process.stderr.write(t)
const stdOutWriter = process.platform === 'win32' ? () => {} : t => process.stdout.write(t)
const logFileWriter = t => fileWritable && fileWritable.write(t)

// override console logging to also go to stdout
const output = {
  error: tee(stdErrWriter, logFileWriter),
  log: tee(stdOutWriter, logFileWriter),
  warn: tee(stdOutWriter, logFileWriter),
}

export default function pipeLogs () {
  setupLocalLogs()

  if (!forwardLogs) {
    return
  }

  methods.forEach(k => {
    console[k] = (...args) => {
      if (args.length) {
        const out = output[k] || stdOutWriter
        out(`${k}: ${Date()} (${Date.now()}): ${util.format.apply(util, args)}\n`)
      }
    }
  })
}

export function ipcLogs () {
  // Simple ipc logging for debugging remote windows
  if (!forwardLogs) {
    return
  }

  ipcMain.on('console.log', (event, args) => {
    const prologue = `From ${event.sender.getTitle()}: `
    stdOutWriter(prologue)
    logFileWriter(prologue)
    console.log.apply(console, args)
  })

  ipcMain.on('console.warn', (event, args) => {
    const prologue = `From ${event.sender.getTitle()}: `
    stdOutWriter(prologue)
    logFileWriter(prologue)
    console.log.apply(console, args)
  })

  ipcMain.on('console.error', (event, args) => {
    const prologue = `From ${event.sender.getTitle()}: `
    stdOutWriter(prologue)
    logFileWriter(prologue)
    console.log.apply(console, args)
  })
}

export function ipcLogsRenderer () {
  const {logLocal, warnLocal, errorLocal} = setupLocalLogs()

  if (!forwardLogs) {
    return
  }

  window.console.log = (...args) => { try { logLocal(...args); ipcRenderer.send('console.log', args) } catch (_) {} }
  window.console.warn = (...args) => { try { warnLocal(...args); ipcRenderer.send('console.warn', args) } catch (_) {} }
  window.console.error = (...args) => { try { errorLocal(...args); ipcRenderer.send('console.error', args) } catch (_) {} }
}
<|endoftext|>


<commit_before>/*jshint node:true, indent:2, curly:true eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
regexp:true, undef:true, trailing:true, white:true */
/*global XT:true, XV:true, enyo:true, _:true, Globalize:true */

(function () {

  /**
    Widget for managing the upload of files. Powered by an html input type=file tag,
    with some HTML5 functionality.

    @name XV.FileInput
    @class
   */
  enyo.kind(/** @lends XV.FileInput# */{
    name: "XV.FileInput",
    kind: "XV.Input",
    events: {
      onValueChange: ""
    },
    handlers: {
      onValueChange: "valueChange"
    },
    components: [
      {name: "input", tag: "input type=file", kind: "onyx.Input",  classes: "xv-subinput", onchange: "inputChanged"},
      {name: "scrim", kind: "onyx.Scrim", showing: false, floating: true}
    ],

    /**
      Generally we don't want to set the value of the widget, because
      setting the value of a file input with the binary data will just throw a security
      exception. But this function is also used as an essential part of selecting a file.
      In that circumstance the value is the filename and the options has no silent attribute,
      which is what's used to differentiate the appropriate times to suppress the setting of
      the value.
     */
    setValue: function (value, options) {
      if (options && options.silent) {
        // don't try to update widget. Just throws a security exception if you do.
        this.value = value;
      } else {
        this.inherited(arguments);
      }
    },
    /**
      Turns the payload of the bubbled event into the file instead of the filename
      using HTML5.
     */
    valueChange: function (inSender, inEvent) {
      if (inEvent.transformedByFileInput) {
        // we've already been here. We want to propagate up, but don't run this function again.
        return false;
      }

      // I feel bad going to the DOM like this but not that bad.
      // Some inspiration from https://github.com/JMTK/decorated-file-input
      // which we can use to replace this widget if we want
      var that = this,
        file = inEvent.originator.$.input.hasNode().files[0],
        filename = inEvent.value,
        reader;

      // XXX Browser support for this HTML5 construct is not universal
      if (FileReader) {
        // XXX unsure about the overhead of this constructor. maybe save it globally?
        reader = new FileReader();
      } else {
        // XXX we should have some sort of XT.alert
        alert("Sorry! File upload is only supported on modern browsers.");
        inEvent.value = null;
        return;
      }

      // prepare callback
      reader.onload = function () {
        that.$.scrim.setShowing(false);
        inEvent.value = reader.result;
        inEvent.filename = filename;
        inEvent.transformedByFileInput = true; // used to avoid infinite loop
        that.doValueChange(inEvent);
      };

      // XXX not sure why this scrim isn't working
      this.$.scrim.setShowing(true);

      // XXX binary string is only one of several options here
      // http://www.html5rocks.com/en/tutorials/file/dndfiles/
      reader.readAsBinaryString(file); // async

      // this event will be bubbled by the callback
      return true;
    }
  });

}());
<commit_msg>issue #18519: fixed small presentation issue for file workspace<commit_after>/*jshint node:true, indent:2, curly:true eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
regexp:true, undef:true, trailing:true, white:true */
/*global XT:true, XV:true, enyo:true, _:true, Globalize:true */

(function () {

  /**
    Widget for managing the upload of files. Powered by an html input type=file tag,
    with some HTML5 functionality.

    @name XV.FileInput
    @class
   */
  enyo.kind(/** @lends XV.FileInput# */{
    name: "XV.FileInput",
    kind: "XV.Input",
    events: {
      onValueChange: ""
    },
    handlers: {
      onValueChange: "valueChange"
    },
    components: [
      {name: "input", tag: "input type=file", kind: "onyx.Input",  classes: "xv-subinput", onchange: "inputChanged"},
      {name: "scrim", kind: "onyx.Scrim", showing: false, floating: true}
    ],

    /**
      Generally we don't want to set the value of the widget, because
      setting the value of a file input with the binary data will just throw a security
      exception. But this function is also used as an essential part of selecting a file.
      In that circumstance the value is the filename and the options has no silent attribute,
      which is what's used to differentiate the appropriate times to suppress the setting of
      the value.
     */
    setValue: function (value, options) {
      if (options && options.silent) {
        // don't try to update widget. Just throws a security exception if you do.
        this.value = value;
      } else {
        this.inherited(arguments);
      }
    },
    /**
      Turns the payload of the bubbled event into the file instead of the filename
      using HTML5.
     */
    valueChange: function (inSender, inEvent) {
      if (inEvent.transformedByFileInput) {
        // we've already been here. We want to propagate up, but don't run this function again.
        return false;
      }

      // I feel bad going to the DOM like this but not that bad.
      // Some inspiration from https://github.com/JMTK/decorated-file-input
      // which we can use to replace this widget if we want
      var that = this,
        file = inEvent.originator.$.input.hasNode().files[0],
        filename = inEvent.value,
        reader;

      if (filename.indexOf("C:\\fakepath\\") === 0) {
        // some browsers obnoxiously give you a fake path, but the only thing
        // we want is the filename really.
        filename = filename.replace("C:\\fakepath\\", "");
      }

      // XXX Browser support for this HTML5 construct is not universal
      if (FileReader) {
        // XXX unsure about the overhead of this constructor. maybe save it globally?
        reader = new FileReader();
      } else {
        // XXX we should have some sort of XT.alert
        alert("Sorry! File upload is only supported on modern browsers.");
        inEvent.value = null;
        return;
      }

      // prepare callback
      reader.onload = function () {
        that.$.scrim.setShowing(false);
        inEvent.value = reader.result;
        inEvent.filename = filename;
        inEvent.transformedByFileInput = true; // used to avoid infinite loop
        that.doValueChange(inEvent);
      };

      // XXX not sure why this scrim isn't working
      this.$.scrim.setShowing(true);

      // XXX binary string is only one of several options here
      // http://www.html5rocks.com/en/tutorials/file/dndfiles/
      reader.readAsBinaryString(file); // async

      // this event will be bubbled by the callback
      return true;
    }
  });

}());
<|endoftext|>