<commit_before>import * as React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { NavigationActions, StackActions, DrawerActions } from 'react-navigation';
import { Title, Text, TouchableRipple } from 'react-native-paper';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import { View, ScrollView, StyleSheet, Image, Linking, Modal, Animated, Easing,
  StatusBar, ToastAndroid } from 'react-native';
import moment from 'moment';

import { COLOR_DARK_BACKGROUND,
         COLOR_DARK_TEXT,
         COLOR_DARK_SUBTEXT,
         COLOR_DARK_WARNING,
         COLOR_DARK_SYNC,
         COLOR_NOTES_BLUE,
         KINTO_LOADED,
         RECONNECT_SYNC,
         DISCONNECTED } from '../utils/constants';

import { DrawerItem, DrawerSection, Colors } from 'react-native-paper';
import { trackEvent } from '../utils/metrics';
import fxaUtils from '../vendor/fxa-utils';
import { store } from '../store';
import browser from '../browser';
import { disconnect, kintoLoad, authenticate, syncing } from '../actions';

// Url to open to give feedback
const SURVEY_PATH = 'https://qsurvey.mozilla.com/s3/notes?ref=android';

function navigateToLogin (props) {

  const resetAction = StackActions.reset({
    index: 0,
    actions: [NavigationActions.navigate({ routeName: 'LoginPanel' })],
  });
  props.navigation.dispatch(resetAction);
  trackEvent('webext-button-disconnect');
}

class DrawerItems extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      rotation: new Animated.Value(1),
      isOpeningLogin: false
    };
    // DrawerItemsData store our drawer button list
    this.drawerItemsData = [
      {
        label : 'Log out',
        action: () => {
          navigateToLogin(props);
          // We delay disconnect event to avoid empty UI while drawer is closing
          setTimeout(() => {
            props.dispatch(disconnect());
          }, 500);
        }
      },
      {
        label  : 'Feedback',
        action : () => {
          trackEvent('give-feedback');
          return Linking.openURL(SURVEY_PATH);
        }
      }
    ];

    this._startAnimation = () => {
      this.state.rotation.setValue(1);
      Animated.timing(this.state.rotation, {
        toValue: 0,
        duration: 1000,
        easing: Easing.linear
      }).start(() => {
        if (this.props.state.sync.isSyncing) {
          this._startAnimation()
        }
      });
    };

    // I don't think this is working.
    this._stopAnimation = () => {
      Animated.timing(
        this.state.rotation
      ).stop();
    };

    this._requestSync = () => {

      if (this.props.state.sync.isConnected === false) {
        props.navigation.dispatch(DrawerActions.closeDrawer());
        ToastAndroid.show('You are offline.', ToastAndroid.LONG);
      } else if (!this.props.state.profile.email) {
        this._requestReconnect();
      } else {
        trackEvent('webext-button-authenticate');
        props.dispatch(kintoLoad('drawer')).then(_ => {
          // If load succeed, we close drawer
          props.navigation.dispatch(DrawerActions.closeDrawer());
        });
      }
    };

    this._requestReconnect = () => {
      this.setState({ isOpeningLogin: true });
      return Promise.resolve().then(() => {
        this.props.dispatch(syncing());
        return fxaUtils.launchOAuthKeyFlow();
      }).then((loginDetails) => {
        trackEvent('login-success');
        this.setState({ isOpeningLogin: false });
        this.props.dispatch(authenticate(loginDetails));
        this.props.dispatch(kintoLoad()).then(() => {
          props.navigation.dispatch(DrawerActions.closeDrawer());
        });
      }).catch((exception) => {
        this.setState({ isOpeningLogin: false });
        browser.runtime.sendMessage({
          action: RECONNECT_SYNC
        });
      });
    };
  }

  componentDidMount() {
    const { navigation } = this.props;

    function select(state) {
      return state.sync.error
    }
    // TODO: unsubscribe this?
    store.subscribe(() => {
      const state = store.getState();
      const err = select(state);
      if (err && !state.sync.loginDetails) {
        this.props.navigation.dispatch(DrawerActions.openDrawer());
      }
    })
  }

  componentWillReceiveProps(newProps) {
    if (newProps.state.sync.isSyncing && !this.props.state.sync.isSyncing) {
      this._startAnimation();
    } else if (this.props.state.sync.isSyncing && !newProps.state.sync.isSyncing) {
      this._stopAnimation();
    }
  }

  componentWillUnmount() {
    this._stopAnimation();
  }

  render() {
    let statusLabel;

    if (this.props.state.sync.isConnected === false) {
      statusLabel = 'Offline';
    } else if (this.props.state.sync.isSyncing) {
      statusLabel = 'Syncing...';
    } else {
      statusLabel = `Last synced ${ moment(this.props.state.sync.lastSynced).format('LT') }`;
    }

    return (
      <View style={styles.drawerContent}>
        <View style={{ paddingTop: 55, marginLeft: 30, paddingBottom: 30, borderBottomColor: '#4A4A4F', borderBottomWidth: 1 }}>
          <Image
            style={{width: 75, height: 75, marginBottom: 4 }}
            borderRadius={100}
            borderColor='#FFFFFF'
            borderWidth={2}
            resizeMode='cover'
            source={{uri: this.props.state.profile.avatar}}
          />
          <Title style={{ color: COLOR_DARK_TEXT }}>{this.props.state.profile.displayName}</Title>
          <Text style={{ color: COLOR_DARK_SUBTEXT }}>{this.props.state.profile.email}</Text>
        </View>
        <ScrollView style={styles.drawerSection}>
        {this.drawerItemsData.map((item, index) => (
          <TouchableRipple key={index} style={styles.wrapper} onPress={() => item.action()}>
            <Text style={{ color: COLOR_DARK_TEXT, paddingLeft: 14 }}>{ item.label }</Text>
          </TouchableRipple>
        ))}
        </ScrollView>
        { this.props.state.sync.error ?
          <TouchableRipple style={styles.footer} onPress={this._requestReconnect}>
            <View style={styles.footerWrapper}>
              <Text style={{ color: COLOR_DARK_WARNING, fontSize: 13 }}>{ this.props.state.sync.error }</Text>
              <MaterialIcons
                name='warning'
                style={{ color: COLOR_DARK_WARNING }}
                size={20}
              />
            </View>
          </TouchableRipple>
          :
          <TouchableRipple style={styles.footer} onPress={ () => this._requestSync() }>
            <View style={styles.footerWrapper}>
              <Text style={{ color: COLOR_DARK_SYNC, fontSize: 13 }}>{ statusLabel }</Text>
              <Animated.View
                style={{
                  ...this.props.style,
                  transform: [{
                    rotate: this.state.rotation.interpolate({
                      inputRange: [0, 1],
                      outputRange: ['0deg', '180deg']
                    })
                  }]
                }} >
                <MaterialIcons
                  name='sync'
                  style={{ color: COLOR_DARK_SYNC }}
                  size={20}
                />
              </Animated.View>
            </View>
          </TouchableRipple>
        }
      </View>
    );
  }
}

const styles = StyleSheet.create({
  drawerContent: {
    flex: 1,
    backgroundColor: COLOR_DARK_BACKGROUND,
    paddingTop: StatusBar.currentHeight
  },
  drawerSection: {
    flexGrow: 1,
    paddingTop: 15
  },
  footer: {
    flexGrow: 0
  },
  footerWrapper: {
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'space-between',
    flexGrow: 0,
    paddingTop: 15,
    paddingBottom: 14,
    paddingLeft: 28,
    paddingRight: 20
  },
  wrapper: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingVertical: 16,
    height: 48,
  }
});

function mapStateToProps(state) {
  return {
    state
  };
}

DrawerItems.propTypes = {
  state: PropTypes.object.isRequired,
  dispatch: PropTypes.func.isRequired
};

export default connect(mapStateToProps)(DrawerItems)
<commit_msg>Avoid multi tap on drawer offline button to spam the android toast (#1090)<commit_after>import * as React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { NavigationActions, StackActions, DrawerActions } from 'react-navigation';
import { Title, Text, TouchableRipple } from 'react-native-paper';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import { View, ScrollView, StyleSheet, Image, Linking, Modal, Animated, Easing,
  StatusBar, ToastAndroid } from 'react-native';
import moment from 'moment';

import { COLOR_DARK_BACKGROUND,
         COLOR_DARK_TEXT,
         COLOR_DARK_SUBTEXT,
         COLOR_DARK_WARNING,
         COLOR_DARK_SYNC,
         COLOR_NOTES_BLUE,
         KINTO_LOADED,
         RECONNECT_SYNC,
         DISCONNECTED } from '../utils/constants';

import { DrawerItem, DrawerSection, Colors } from 'react-native-paper';
import { trackEvent } from '../utils/metrics';
import fxaUtils from '../vendor/fxa-utils';
import { store } from '../store';
import browser from '../browser';
import { disconnect, kintoLoad, authenticate, syncing } from '../actions';

// Url to open to give feedback
const SURVEY_PATH = 'https://qsurvey.mozilla.com/s3/notes?ref=android';

function navigateToLogin (props) {

  const resetAction = StackActions.reset({
    index: 0,
    actions: [NavigationActions.navigate({ routeName: 'LoginPanel' })],
  });
  props.navigation.dispatch(resetAction);
  trackEvent('webext-button-disconnect');
}

class DrawerItems extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      rotation: new Animated.Value(1),
      isOpeningLogin: false
    };
    // DrawerItemsData store our drawer button list
    this.drawerItemsData = [
      {
        label : 'Log out',
        action: () => {
          navigateToLogin(props);
          // We delay disconnect event to avoid empty UI while drawer is closing
          setTimeout(() => {
            props.dispatch(disconnect());
          }, 500);
        }
      },
      {
        label  : 'Feedback',
        action : () => {
          trackEvent('give-feedback');
          return Linking.openURL(SURVEY_PATH);
        }
      }
    ];

    this._startAnimation = () => {
      this.state.rotation.setValue(1);
      Animated.timing(this.state.rotation, {
        toValue: 0,
        duration: 1000,
        easing: Easing.linear
      }).start(() => {
        if (this.props.state.sync.isSyncing) {
          this._startAnimation()
        }
      });
    };

    // I don't think this is working.
    this._stopAnimation = () => {
      Animated.timing(
        this.state.rotation
      ).stop();
    };

    this._requestSync = () => {

      if (this.props.state.sync.isConnected === false) {
        if (this.props.navigation.state.isDrawerOpen) {
          props.navigation.dispatch(DrawerActions.closeDrawer());
          ToastAndroid.show('You are offline.', ToastAndroid.LONG);
        }
      } else if (!this.props.state.profile.email) {
        this._requestReconnect();
      } else {
        trackEvent('webext-button-authenticate');
        props.dispatch(kintoLoad('drawer')).then(_ => {
          // If load succeed, we close drawer
          props.navigation.dispatch(DrawerActions.closeDrawer());
        });
      }
    };

    this._requestReconnect = () => {
      this.setState({ isOpeningLogin: true });
      return Promise.resolve().then(() => {
        this.props.dispatch(syncing());
        return fxaUtils.launchOAuthKeyFlow();
      }).then((loginDetails) => {
        trackEvent('login-success');
        this.setState({ isOpeningLogin: false });
        this.props.dispatch(authenticate(loginDetails));
        this.props.dispatch(kintoLoad()).then(() => {
          props.navigation.dispatch(DrawerActions.closeDrawer());
        });
      }).catch((exception) => {
        this.setState({ isOpeningLogin: false });
        browser.runtime.sendMessage({
          action: RECONNECT_SYNC
        });
      });
    };
  }

  componentDidMount() {
    const { navigation } = this.props;

    function select(state) {
      return state.sync.error
    }
    // TODO: unsubscribe this?
    store.subscribe(() => {
      const state = store.getState();
      const err = select(state);
      if (err && !state.sync.loginDetails) {
        this.props.navigation.dispatch(DrawerActions.openDrawer());
      }
    })
  }

  componentWillReceiveProps(newProps) {
    if (newProps.state.sync.isSyncing && !this.props.state.sync.isSyncing) {
      this._startAnimation();
    } else if (this.props.state.sync.isSyncing && !newProps.state.sync.isSyncing) {
      this._stopAnimation();
    }
  }

  componentWillUnmount() {
    this._stopAnimation();
  }

  render() {
    let statusLabel;

    if (this.props.state.sync.isConnected === false) {
      statusLabel = 'Offline';
    } else if (this.props.state.sync.isSyncing) {
      statusLabel = 'Syncing...';
    } else {
      statusLabel = `Last synced ${ moment(this.props.state.sync.lastSynced).format('LT') }`;
    }

    return (
      <View style={styles.drawerContent}>
        <View style={{ paddingTop: 55, marginLeft: 30, paddingBottom: 30, borderBottomColor: '#4A4A4F', borderBottomWidth: 1 }}>
          <Image
            style={{width: 75, height: 75, marginBottom: 4 }}
            borderRadius={100}
            borderColor='#FFFFFF'
            borderWidth={2}
            resizeMode='cover'
            source={{uri: this.props.state.profile.avatar}}
          />
          <Title style={{ color: COLOR_DARK_TEXT }}>{this.props.state.profile.displayName}</Title>
          <Text style={{ color: COLOR_DARK_SUBTEXT }}>{this.props.state.profile.email}</Text>
        </View>
        <ScrollView style={styles.drawerSection}>
        {this.drawerItemsData.map((item, index) => (
          <TouchableRipple key={index} style={styles.wrapper} onPress={() => item.action()}>
            <Text style={{ color: COLOR_DARK_TEXT, paddingLeft: 14 }}>{ item.label }</Text>
          </TouchableRipple>
        ))}
        </ScrollView>
        { this.props.state.sync.error ?
          <TouchableRipple style={styles.footer} onPress={this._requestReconnect}>
            <View style={styles.footerWrapper}>
              <Text style={{ color: COLOR_DARK_WARNING, fontSize: 13 }}>{ this.props.state.sync.error }</Text>
              <MaterialIcons
                name='warning'
                style={{ color: COLOR_DARK_WARNING }}
                size={20}
              />
            </View>
          </TouchableRipple>
          :
          <TouchableRipple style={styles.footer} onPress={ () => this._requestSync() }>
            <View style={styles.footerWrapper}>
              <Text style={{ color: COLOR_DARK_SYNC, fontSize: 13 }}>{ statusLabel }</Text>
              <Animated.View
                style={{
                  ...this.props.style,
                  transform: [{
                    rotate: this.state.rotation.interpolate({
                      inputRange: [0, 1],
                      outputRange: ['0deg', '180deg']
                    })
                  }]
                }} >
                <MaterialIcons
                  name='sync'
                  style={{ color: COLOR_DARK_SYNC }}
                  size={20}
                />
              </Animated.View>
            </View>
          </TouchableRipple>
        }
      </View>
    );
  }
}

const styles = StyleSheet.create({
  drawerContent: {
    flex: 1,
    backgroundColor: COLOR_DARK_BACKGROUND,
    paddingTop: StatusBar.currentHeight
  },
  drawerSection: {
    flexGrow: 1,
    paddingTop: 15
  },
  footer: {
    flexGrow: 0
  },
  footerWrapper: {
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'space-between',
    flexGrow: 0,
    paddingTop: 15,
    paddingBottom: 14,
    paddingLeft: 28,
    paddingRight: 20
  },
  wrapper: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingVertical: 16,
    height: 48,
  }
});

function mapStateToProps(state) {
  return {
    state
  };
}

DrawerItems.propTypes = {
  state: PropTypes.object.isRequired,
  dispatch: PropTypes.func.isRequired
};

export default connect(mapStateToProps)(DrawerItems)
<|endoftext|>


<commit_before>/*!
 * Copyright 2013 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

var _ = require('underscore');
var fs = require('fs');
var restjsdoc = require('restjsdoc');
var shell = require('shelljs');

var log = require('oae-logger').logger('oae-swagger');
var OAE = require('./oae');
var Server = require('./server');
var SwaggerParam = require('./swaggerParamTypes');

var constants = module.exports.constants = {
    'apiVersion': '0.1',
    'swaggerVersion': '1.2',
    'basePath': '/api/',
    'paramTypes': [
        'path',
        'query',
        'body',
        'form',
        'header'
    ],
    primitives : [
        'integer',
        'long',
        'float',
        'double',
        'string',
        'byte',
        'boolean',
        'date',
        'dateTime',
        'int32',
        'int64',
        'number',
        'date-time'
   ]
};

/**
 * Finds all the *.js files in a module's `lib` directory and runs them through the restjsdoc parser
 *
 * @param  {String}           module    The oae module to be swagger documented
 * @param  {Function}         callback  Standard callback function called when all files in the module have been processed
 */
var documentModule = module.exports.documentModule = function(module, callback) {
    var files = shell.find('node_modules/' + module + '/lib').filter(function(file) {
        return file.match(/\.js$/);
    });
    var done = _.after(files.length, callback);
    _.each(files, function(file) {
        _register(file, function(err) {
            if (err) {
                log().warn(err);
            }
            done();
        });
    });
};

/**
 * Read the restjsdoc from a file and register its contents with swagger
 *
 * @param  {String}          filename      The full path to the file to be documented
 * @param  {Function}        callback      Standard callback function called after the file is processed
 * @param  {Object}          callback.err  An error if there is one
 * @api private
 */
var _register = function(filename, callback) {
    fs.readFile(filename, function (err, data) {
        if (err) {
            return callback(err);
        }
        try {
            var doc = restjsdoc.parse(data);
            // Add all models to both servers
            _.each(doc.models, function(model, modelName) {
                model.id = modelName;
                _.each(model.properties, function(property) {
                    var unArray = property.type.replace(/\[\]$/, '');
                    if (property.type !== unArray) {
                        property.type = 'array';
                        if (constants.primitives.indexOf(unArray) === -1) {
                            property.items = {'$ref': unArray};
                        } else {
                            property.items = {'type': unArray};
                        }
                    }
                });
                OAE.tenantRouter.addSwaggerModel(modelName, model);
                OAE.globalAdminRouter.addSwaggerModel(modelName, model);
            });

            // Convert the restjsdoc objects to swagger objects
            _.each(doc.endpoints, function(endpoint, nickname) {
                endpoint.nickname = nickname;
                endpoint.summary = endpoint.description;
                endpoint.responseClass = _convertRJDArrayDefToSwagger(endpoint['return'].type);

                endpoint.parameters = [];
                _.each(endpoint.pathParams, function(pathParam) {
                    endpoint.parameters.push(SwaggerParam.path(pathParam.name, pathParam.description, _convertRJDArrayDefToSwagger(pathParam.type), _constructSwaggerValue(pathParam.validValues), pathParam.defaultValue));
                });
                _.each(endpoint.queryParams, function(queryParam) {
                    endpoint.parameters.push(SwaggerParam.query(queryParam.name, queryParam.description, _convertRJDArrayDefToSwagger(queryParam.type), queryParam.required, queryParam.allowMultiple, _constructSwaggerValue(queryParam.validValues), queryParam.defaultValue));
                });
                _.each(endpoint.bodyParams, function(bodyParam) {
                    endpoint.parameters.push(SwaggerParam.body(bodyParam.name, bodyParam.description, _convertRJDArrayDefToSwagger(bodyParam.type), bodyParam.defaultValue));
                });
                _.each(endpoint.headerParams, function(headerParam) {
                    endpoint.parameters.push(SwaggerParam.header(headerParam.name, headerParam.description, _convertRJDArrayDefToSwagger(headerParam.type), headerParam.required));
                });
                _.each(endpoint.formParams, function(formParam) {
                    endpoint.parameters.push(SwaggerParam.form(formParam.name, formParam.description, _convertRJDArrayDefToSwagger(formParam.type), formParam.defaultValue));
                });

                // Add the endpoint to the appropriate server
                if (endpoint.server === 'tenant') {
                    OAE.tenantRouter.addSwaggerEndpoint(endpoint);
                } else if (endpoint.server === 'admin') {
                    OAE.globalAdminRouter.addSwaggerEndpoint(endpoint);
                } else {
                    log().warn('Tried to register swagger docs for unknown server "' + endpoint.server + '"');
                }
            });
        } catch(error) {
            log().warn('Could not parse restjsdoc in ' + filename + ' ' + error.message);
        }
        return callback();
    });
};

/**
 * Convert RestJsDoc array syntax `type[]` to swagger syntax `List[type]`
 *
 * @param  {String}           def        The RestJsDoc array definition like type[]
 * @return {String}                      The swagger array definition like List[type]
 * @api private
 */
var _convertRJDArrayDefToSwagger = function(def) {
    if (def.match(/\[\]$/)) {
        def = 'List[' + def.substr(0, def.length - 2) + ']';
    }
    return def;
};

/**
 * Convert a javascript array into a swagger value object
 *
 * @param  {String[]}            array    The array to be converted
 * @return {Object}                       A swagger value object representing the array
 * @api private
 */
var _constructSwaggerValue = function(array) {
    var value;
    if (_.isArray(array)) {
        value = {'valueType': 'LIST', 'values': array};
    }
    return value;
};

/**
 * Get the swagger resources list
 *
 * @param  {Context}         ctx    The context of the request
 * @return {ResourceList}           See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#51-resource-listing
 */
var getResources = module.exports.getResources = function(ctx) {
    var resources = _getSwaggerResources(ctx);
    var swaggerResources = {
        'apiVersion': constants.apiVersion,
        'swaggerVersion': constants.swaggerVersion,
        'apis': []
    };
    _.each(resources, function(value, key) {
        var path = '/' + key;
        swaggerResources.apis.push({
            'path': path,
            'description': 'none'
        });
    });
    return swaggerResources;
};

/**
 * Get the swagger api declaration for a resource
 *
 * @param  {Context}       ctx                 The context of the request
 * @param  {String}        swaggerResourcePath The resource path
 * @return {ApiDeclaration}                    See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#52-api-declaration
 */
var getApi = module.exports.getApi = function(ctx, swaggerResourcePath) {
    var resources = _getSwaggerResources(ctx);
    var protocol = Server.useHttps() ? 'https://' : 'http://';
    var url = protocol + ctx.tenant().host + '/api';
    var api = _.clone(resources[swaggerResourcePath]);
    api.basePath = url;
    return api;
};

/**
 * Iterate over all resources and populate their models
 */
var addModelsToResources = module.exports.addModelsToResources = function() {
    var adminResources = OAE.globalAdminServer.locals['swagger-resources'];
    var tenantResources = OAE.tenantServer.locals['swagger-resources'];
    var adminModels = OAE.globalAdminServer.locals['swagger-models'];
    var tenantModels = OAE.tenantServer.locals['swagger-models'];

    _.each(adminResources, function(resource, resourcePath) {
        _addModelsToResource(resource, resourcePath, adminModels);
    });
    _.each(tenantResources, function(resource, resourcePath) {
        _addModelsToResource(resource, resourcePath, tenantModels);
    });
};

/**
 * Find all the models that a resource needs and add them to its model list
 *
 * @param  {Resource}         resource      The resource to process
 * @param  {String}           resourcePath  The path to this resource
 * @param  {Object}           allModels     All the models registered for this server
 */
var _addModelsToResource = function(resource, resourcePath, allModels) {
    resource.resourcePath = resourcePath + '/';
    var requiredModels = [];
    // Collect the list of models that this resource references
    _.each(resource.apis, function(api) {
        _.each(api.operations, function(operation) {
            _addModelsFromBody(operation, requiredModels);
            _addModelsFromResponse(operation, requiredModels);
            requiredModels = _.uniq(requiredModels);
        });
    });

    // Add required models to resource
    _.each(requiredModels, function(modelName) {
        var model = allModels[modelName];
        if (model) {
            resource.models[modelName] = model;
        }
    });

    // Now that we have the models that are referenced directly we need to iterate over them and add
    // any new ones that they reference
    _.each(resource.models, function(model) {
        if (model.properties) {
            _.each(model.properties, function (property) {
                var type = property.type;

                switch (type) {
                    case 'array':
                    case 'Array':
                        if (property.items) {
                            var ref = property.items.$ref;
                            if (ref && requiredModels.indexOf(ref) < 0) {
                                requiredModels.push(ref);
                            }
                        }
                        break;
                    case 'string':
                    case 'long':
                        break;
                    default:
                        if (requiredModels.indexOf(type) < 0) {
                            requiredModels.push(type);
                        }
                        break;
                }
            });
        }
    });
    _.each(requiredModels, function(modelName) {
        if (!resource.models[modelName]) {
            var model = allModels[modelName];
            if (model) {
                resource.models[modelName] = model;
            }
        }
    });
};

/**
 * Get all the swagger resources for the appropriate tenant
 *
 * @param  {Context}       ctx  The request context
 * @return {Object}             An object containing all the swagger resource info associated to the tenant
 * @api private
 */
var _getSwaggerResources = function(ctx) {
    return (ctx.tenant().isGlobalAdminServer) ? OAE.globalAdminServer.locals['swagger-resources'] : OAE.tenantServer.locals['swagger-resources'];
};

/**
 * Parse the body parameters and add all referenced models to the model list
 *
 * @param  {String}    operation   The operation object from the swagger api declaration to parse
 * @param  {String[]}  models      The list of referenced models, any newly referenced models will be appended
 * @api private
 */
var _addModelsFromBody = function(operation, models) {
    if (operation.parameters) {
        _.each(operation.parameters, function (param) {
            if (param.paramType === 'body' && param.dataType) {
                var model = param.dataType.replace(/^List\[/, '').replace(/\]/, '');
                models.push(model);
            }
        });
    }
};

/**
 * Parse the response type and add all referenced models to the model list
 *
 * @param  {String}    operation   The operation object from the swagger api declaration to parse
 * @param  {String[]}  models      The list of referenced models, any newly referenced models will be appended
 * @api private
 */
var _addModelsFromResponse = function(operation, models) {
    var responseModel = operation.responseClass;
    if (responseModel) {
        responseModel = responseModel.replace(/^List\[/, '').replace(/\]/, '');
        models.push(responseModel);
    }
};
<commit_msg>make function for unwrapping swagger Lists<commit_after>/*!
 * Copyright 2013 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

var _ = require('underscore');
var fs = require('fs');
var restjsdoc = require('restjsdoc');
var shell = require('shelljs');

var log = require('oae-logger').logger('oae-swagger');
var OAE = require('./oae');
var Server = require('./server');
var SwaggerParam = require('./swaggerParamTypes');

var constants = module.exports.constants = {
    'apiVersion': '0.1',
    'swaggerVersion': '1.2',
    'basePath': '/api/',
    'paramTypes': [
        'path',
        'query',
        'body',
        'form',
        'header'
    ],
    primitives : [
        'integer',
        'long',
        'float',
        'double',
        'string',
        'byte',
        'boolean',
        'date',
        'dateTime',
        'int32',
        'int64',
        'number',
        'date-time'
   ]
};

/**
 * Finds all the *.js files in a module's `lib` directory and runs them through the restjsdoc parser
 *
 * @param  {String}           module    The oae module to be swagger documented
 * @param  {Function}         callback  Standard callback function called when all files in the module have been processed
 */
var documentModule = module.exports.documentModule = function(module, callback) {
    var files = shell.find('node_modules/' + module + '/lib').filter(function(file) {
        return file.match(/\.js$/);
    });
    var done = _.after(files.length, callback);
    _.each(files, function(file) {
        _register(file, function(err) {
            if (err) {
                log().warn(err);
            }
            done();
        });
    });
};

/**
 * Read the restjsdoc from a file and register its contents with swagger
 *
 * @param  {String}          filename      The full path to the file to be documented
 * @param  {Function}        callback      Standard callback function called after the file is processed
 * @param  {Object}          callback.err  An error if there is one
 * @api private
 */
var _register = function(filename, callback) {
    fs.readFile(filename, function (err, data) {
        if (err) {
            return callback(err);
        }
        try {
            var doc = restjsdoc.parse(data);
            // Add all models to both servers
            _.each(doc.models, function(model, modelName) {
                model.id = modelName;
                _.each(model.properties, function(property) {
                    var unArray = property.type.replace(/\[\]$/, '');
                    if (property.type !== unArray) {
                        property.type = 'array';
                        if (constants.primitives.indexOf(unArray) === -1) {
                            property.items = {'$ref': unArray};
                        } else {
                            property.items = {'type': unArray};
                        }
                    }
                });
                OAE.tenantRouter.addSwaggerModel(modelName, model);
                OAE.globalAdminRouter.addSwaggerModel(modelName, model);
            });

            // Convert the restjsdoc objects to swagger objects
            _.each(doc.endpoints, function(endpoint, nickname) {
                endpoint.nickname = nickname;
                endpoint.summary = endpoint.description;
                endpoint.responseClass = _convertRJDArrayDefToSwagger(endpoint['return'].type);

                endpoint.parameters = [];
                _.each(endpoint.pathParams, function(pathParam) {
                    endpoint.parameters.push(SwaggerParam.path(pathParam.name, pathParam.description, _convertRJDArrayDefToSwagger(pathParam.type), _constructSwaggerValue(pathParam.validValues), pathParam.defaultValue));
                });
                _.each(endpoint.queryParams, function(queryParam) {
                    endpoint.parameters.push(SwaggerParam.query(queryParam.name, queryParam.description, _convertRJDArrayDefToSwagger(queryParam.type), queryParam.required, queryParam.allowMultiple, _constructSwaggerValue(queryParam.validValues), queryParam.defaultValue));
                });
                _.each(endpoint.bodyParams, function(bodyParam) {
                    endpoint.parameters.push(SwaggerParam.body(bodyParam.name, bodyParam.description, _convertRJDArrayDefToSwagger(bodyParam.type), bodyParam.defaultValue));
                });
                _.each(endpoint.headerParams, function(headerParam) {
                    endpoint.parameters.push(SwaggerParam.header(headerParam.name, headerParam.description, _convertRJDArrayDefToSwagger(headerParam.type), headerParam.required));
                });
                _.each(endpoint.formParams, function(formParam) {
                    endpoint.parameters.push(SwaggerParam.form(formParam.name, formParam.description, _convertRJDArrayDefToSwagger(formParam.type), formParam.defaultValue));
                });

                // Add the endpoint to the appropriate server
                if (endpoint.server === 'tenant') {
                    OAE.tenantRouter.addSwaggerEndpoint(endpoint);
                } else if (endpoint.server === 'admin') {
                    OAE.globalAdminRouter.addSwaggerEndpoint(endpoint);
                } else {
                    log().warn('Tried to register swagger docs for unknown server "' + endpoint.server + '"');
                }
            });
        } catch(error) {
            log().warn('Could not parse restjsdoc in ' + filename + ' ' + error.message);
        }
        return callback();
    });
};

/**
 * Convert RestJsDoc array syntax `type[]` to swagger syntax `List[type]`
 *
 * @param  {String}           def        The RestJsDoc array definition like type[]
 * @return {String}                      The swagger array definition like List[type]
 * @api private
 */
var _convertRJDArrayDefToSwagger = function(def) {
    if (def.match(/\[\]$/)) {
        def = 'List[' + def.substr(0, def.length - 2) + ']';
    }
    return def;
};

/**
 * Convert a javascript array into a swagger value object
 *
 * @param  {String[]}            array    The array to be converted
 * @return {Object}                       A swagger value object representing the array
 * @api private
 */
var _constructSwaggerValue = function(array) {
    var value;
    if (_.isArray(array)) {
        value = {'valueType': 'LIST', 'values': array};
    }
    return value;
};

/**
 * Get the swagger resources list
 *
 * @param  {Context}         ctx    The context of the request
 * @return {ResourceList}           See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#51-resource-listing
 */
var getResources = module.exports.getResources = function(ctx) {
    var resources = _getSwaggerResources(ctx);
    var swaggerResources = {
        'apiVersion': constants.apiVersion,
        'swaggerVersion': constants.swaggerVersion,
        'apis': []
    };
    _.each(resources, function(value, key) {
        var path = '/' + key;
        swaggerResources.apis.push({
            'path': path,
            'description': 'none'
        });
    });
    return swaggerResources;
};

/**
 * Get the swagger api declaration for a resource
 *
 * @param  {Context}       ctx                 The context of the request
 * @param  {String}        swaggerResourcePath The resource path
 * @return {ApiDeclaration}                    See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#52-api-declaration
 */
var getApi = module.exports.getApi = function(ctx, swaggerResourcePath) {
    var resources = _getSwaggerResources(ctx);
    var protocol = Server.useHttps() ? 'https://' : 'http://';
    var url = protocol + ctx.tenant().host + '/api';
    var api = _.clone(resources[swaggerResourcePath]);
    api.basePath = url;
    return api;
};

/**
 * Iterate over all resources and populate their models
 */
var addModelsToResources = module.exports.addModelsToResources = function() {
    var adminResources = OAE.globalAdminServer.locals['swagger-resources'];
    var tenantResources = OAE.tenantServer.locals['swagger-resources'];
    var adminModels = OAE.globalAdminServer.locals['swagger-models'];
    var tenantModels = OAE.tenantServer.locals['swagger-models'];

    _.each(adminResources, function(resource, resourcePath) {
        _addModelsToResource(resource, resourcePath, adminModels);
    });
    _.each(tenantResources, function(resource, resourcePath) {
        _addModelsToResource(resource, resourcePath, tenantModels);
    });
};

/**
 * Find all the models that a resource needs and add them to its model list
 *
 * @param  {Resource}         resource      The resource to process
 * @param  {String}           resourcePath  The path to this resource
 * @param  {Object}           allModels     All the models registered for this server
 */
var _addModelsToResource = function(resource, resourcePath, allModels) {
    resource.resourcePath = resourcePath + '/';
    var requiredModels = [];
    // Collect the list of models that this resource references
    _.each(resource.apis, function(api) {
        _.each(api.operations, function(operation) {
            _addModelsFromBody(operation, requiredModels);
            _addModelsFromResponse(operation, requiredModels);
            requiredModels = _.uniq(requiredModels);
        });
    });

    // Add required models to resource
    _.each(requiredModels, function(modelName) {
        var model = allModels[modelName];
        if (model) {
            resource.models[modelName] = model;
        }
    });

    // Now that we have the models that are referenced directly we need to iterate over them and add
    // any new ones that they reference
    _.each(resource.models, function(model) {
        if (model.properties) {
            _.each(model.properties, function (property) {
                var type = property.type;

                switch (type) {
                    case 'array':
                    case 'Array':
                        if (property.items) {
                            var ref = property.items.$ref;
                            if (ref && requiredModels.indexOf(ref) < 0) {
                                requiredModels.push(ref);
                            }
                        }
                        break;
                    case 'string':
                    case 'long':
                        break;
                    default:
                        if (requiredModels.indexOf(type) < 0) {
                            requiredModels.push(type);
                        }
                        break;
                }
            });
        }
    });
    _.each(requiredModels, function(modelName) {
        if (!resource.models[modelName]) {
            var model = allModels[modelName];
            if (model) {
                resource.models[modelName] = model;
            }
        }
    });
};

/**
 * Get all the swagger resources for the appropriate tenant
 *
 * @param  {Context}       ctx  The request context
 * @return {Object}             An object containing all the swagger resource info associated to the tenant
 * @api private
 */
var _getSwaggerResources = function(ctx) {
    return (ctx.tenant().isGlobalAdminServer) ? OAE.globalAdminServer.locals['swagger-resources'] : OAE.tenantServer.locals['swagger-resources'];
};

/**
 * Parse the body parameters and add all referenced models to the model list
 *
 * @param  {String}    operation   The operation object from the swagger api declaration to parse
 * @param  {String[]}  models      The list of referenced models, any newly referenced models will be appended
 * @api private
 */
var _addModelsFromBody = function(operation, models) {
    if (operation.parameters) {
        _.each(operation.parameters, function (param) {
            if (param.paramType === 'body' && param.dataType) {
                var model = _unwrapSwaggerType(param.dataType);
                models.push(model);
            }
        });
    }
};

/**
 * Parse the response type and add all referenced models to the model list
 *
 * @param  {String}    operation   The operation object from the swagger api declaration to parse
 * @param  {String[]}  models      The list of referenced models, any newly referenced models will be appended
 * @api private
 */
var _addModelsFromResponse = function(operation, models) {
    var responseModel = operation.responseClass;
    if (responseModel) {
        responseModel = _unwrapSwaggerType(responseModel);
        models.push(responseModel);
    }
};

/**
 * Unwrap swagger `List[type]` to return just the `type` this is safe to use on non-lists as well
 *
 * @param  {String}   type      The type declaration to be unwrapped
 * @return {String}             The unwrapped type declaration
 */
var _unwrapSwaggerType = function(type) {
    return type.replace(/^List\[/, '').replace(/\]/, '');
}
<|endoftext|>


<commit_before>const { Box, Either, Right, Left, fromNullable } = require('../examples/lib')

const Task = require('data.task')

const boxToEither = b =>

  // must go to Right
  // to follow the natural transformation law
  b.fold(Right)

const res1 = boxToEither(Box(100))
  .map( x => x * 2 )

// first map, then transform
const res2 = boxToEither(
  Box(100).map( x => x * 2 )
)

console.log(res1, res2)

const boxToEitherBad = b =>

  // does not follow the natural transformation law
  b.fold(Left)

// first box, then map
const res21 = boxToEitherBad(Box(100))
  .map( x => x * 2 )

// first map, then box
const res22 = boxToEither(
  Box(100).map( x => x * 2 )
)

// natural transformation law is violated!
console.log(res21, res22)

const first = xs =>
  fromNullable(xs[0])

// apply first, then map
const res31 = first([1,2,3]).map( x => x + 1 )

// map array, then apply first
const res32 = first( [1,2,3].map( x => x + 1 ) )

// results are equal!
console.log(res31, res32)

<commit_msg>Lesson 24 rename<commit_after>const { Box, Either, Right, Left, fromNullable } = require('../examples/lib')

const Task = require('data.task')

const eitherToTask = e =>
  e.fold(Task.rejected, Task.of)

eitherToTask(Right('nightingale'))
.fork(
  e => console.error('Error: ', e),
  res => console.log('Result: ', res)
)
eitherToTask(Left('errrr'))
.fork(
  e => console.error('Error: ', e),
  res => console.log('Result: ', res)
)

// naturally transform Box to Either
const boxToEither = b =>

  // must go to Right
  // to follow the natural transformation law
  b.fold(Right)

// first transform to Either, than map
const res1 = boxToEither(Box(100))
  .map( x => x * 2 )

// or first map, then transform to Either
const res2 = boxToEither(
  Box(100).map( x => x * 2 )
)

// results should be the same!
console.log(res1, res2)

const boxToEitherBad = b =>

  // does not follow the natural transformation law
  // because Left ignores mapped functions
  b.fold(Left)

// first box, then map
const res21 = boxToEitherBad(Box(100))
  .map( x => x * 2 )

// first map, then box
const res22 = boxToEither(
  Box(100).map( x => x * 2 )
)

// natural transformation law is violated!
console.log(res21, res22)

// transform List into Either
const first = xs =>
  fromNullable(xs[0])

// apply first, then map
const res31 = first([1,2,3]).map( x => x + 1 )

// map array, then apply first
const res32 = first( [1,2,3].map( x => x + 1 ) )

// results are equal!
console.log(res31, res32)

<|endoftext|>


<commit_before>import { createServices } from "./services";

export const pages = {
  home: {
    id: "Home",
    tab: "Home"
  },
  coffee: {
    id: "Coffee",
    tab: "Coffee"
  },
  beer: {
    id: "Beer",
    tab: "Beer"
  },
  beerDetails: {
    id: "BeerDetails",
    tab: "Beer"
  }
};

export const createNavigation = update => {
  const services = createServices();

  const navigate = (page, params = {}) =>
    update(model => Object.assign(model, { page, params }));

  const navigateTo = page => params => navigate(page, params);

  const navigateToCoffee = params => {
    services.loadCoffees().then(coffees => {
      update(model => Object.assign(model, { coffees }));
      if (params && params.id) {
        services.loadCoffee(params).then(coffee => {
          update(model => Object.assign(model, { coffee: coffee.description }));
        });
      }
      navigate(pages.coffee, params);
    });
  };

  const navigateToBeer = () => {
    services.loadBeer().then(beerList => {
      update(model => Object.assign(model, { beerList }));
      navigate(pages.beer);
    });
  };

  return {
    navigateToHome: navigateTo(pages.home),
    navigateToCoffee,
    navigateToBeer,
    navigateToBeerDetails: navigateTo(pages.beerDetails)
  };
};
<commit_msg>Update examples.<commit_after>import { createServices } from "./services";

const compose = (f1, f2) => x => f1(f2(x));

export const pages = {
  home: {
    id: "Home",
    tab: "Home"
  },
  coffee: {
    id: "Coffee",
    tab: "Coffee"
  },
  beer: {
    id: "Beer",
    tab: "Beer"
  },
  beerDetails: {
    id: "BeerDetails",
    tab: "Beer"
  }
};

export const createNavigation = update => {
  const services = createServices();

  const navigate = (page, params = {}) =>
    model => Object.assign(model, { page, params });

  const navigateToCoffee = params => {
    services.loadCoffees().then(coffees => {
      const assignCoffees = model => Object.assign(model, { coffees });
      if (params && params.id) {
        services.loadCoffee(params).then(coffee => {
          const assignCoffee = compose(
            model => Object.assign(model, { coffee: coffee.description }), assignCoffees);
          update(compose(navigate(pages.coffee, params), assignCoffee));
        });
      }
      else {
        update(compose(navigate(pages.coffee, params), assignCoffees));
      }
    });
  };

  const navigateToBeer = () => {
    services.loadBeer().then(beerList => {
      update(compose(navigate(pages.beer), model => Object.assign(model, { beerList })));
    });
  };

  return {
    navigateToHome: params => update(navigate(pages.home, params)),
    navigateToCoffee,
    navigateToBeer,
    navigateToBeerDetails: params => update(navigate(pages.beerDetails, params))
  };
};
<|endoftext|>


<commit_before>import _ from 'lodash';
import React from 'react';
import Field from '../Field';
import { Button, Checkbox, FormField, FormInput, FormNote, FormRow } from 'elemental';

/**
 * TODO:
 * - Remove dependency on underscore
 * - Custom path support
 */

module.exports = Field.create({

	displayName: 'LocationField',
	statics: {
		type: 'Location',
	},

	getInitialState () {
		return {
			collapsedFields: {},
			improve: false,
			overwrite: false,
		};
	},

	componentWillMount () {
		var collapsedFields = {};
		_.forEach(['number', 'name', 'street2', 'geo'], (i) => {
			if (!this.props.value[i]) {
				collapsedFields[i] = true;
			}
		}, this);
		this.setState({
			collapsedFields: collapsedFields,
		});
	},

	componentDidUpdate (prevProps, prevState) {
		if (prevState.fieldsCollapsed && !this.state.fieldsCollapsed) {
			this.refs.number.getDOMNode().focus();
		}
	},

	shouldCollapse () {
		return this.props.collapse && !this.formatValue();
	},

	uncollapseFields () {
		this.setState({
			collapsedFields: {},
		});
	},

	fieldChanged (path, event) {
		var value = this.props.value;
		value[path] = event.target.value;
		this.props.onChange({
			path: this.props.path,
			value: value,
		});
	},

	geoChanged (i, event) {
		var value = this.props.value;
		if (!value.geo) {
			value.geo = ['', ''];
		}
		value.geo[i] = event.target.value;
		this.props.onChange({
			path: this.props.path,
			value: value,
		});
	},

	formatValue () {
		return _.compact([
			this.props.value.number,
			this.props.value.name,
			this.props.value.street1,
			this.props.value.street2,
			this.props.value.suburb,
			this.props.value.state,
			this.props.value.postcode,
			this.props.value.country,
		]).join(', ');
	},

	renderValue () {
		return <FormInput noedit>{this.formatValue() || '(no value)'}</FormInput>;
	},

	renderField (path, label, collapse) { // eslint-disable-line no-unused-vars
		if (this.state.collapsedFields[path]) {
			return null;
		}
		return (
			<FormField label={label} className="form-field--secondary" htmlFor={this.props.path + '.' + path}>
				<FormInput name={this.props.path + '.' + path} ref={path} value={this.props.value[path]} onChange={this.fieldChanged.bind(this, path)} placeholder={label} />
			</FormField>
		);
	},

	renderSuburbState () {
		return (
			<FormField label="Suburb / State" className="form-field--secondary" htmlFor={this.props.path + '.suburb'}>
				<FormRow>
					<FormField width="two-thirds" className="form-field--secondary">
						<FormInput name={this.props.path + '.suburb'} ref="suburb" value={this.props.value.suburb} onChange={this.fieldChanged.bind(this, 'suburb')} placeholder="Suburb" />
					</FormField>
					<FormField width="one-third" className="form-field--secondary">
						<FormInput name={this.props.path + '.state'} ref="state" value={this.props.value.state} onChange={this.fieldChanged.bind(this, 'state')} placeholder="State" />
					</FormField>
				</FormRow>
			</FormField>
		);
	},

	renderPostcodeCountry () {
		return (
			<FormField label="Postcode / Country" className="form-field--secondary" htmlFor={this.props.path + '.postcode'}>
				<FormRow>
					<FormField width="one-third" className="form-field--secondary">
						<FormInput name={this.props.path + '.postcode'} ref="postcode" value={this.props.value.postcode} onChange={this.fieldChanged.bind(this, 'postcode')} placeholder="Post Code" />
					</FormField>
					<FormField width="two-thirds" className="form-field--secondary">
						<FormInput name={this.props.path + '.country'} ref="country" value={this.props.value.country} onChange={this.fieldChanged.bind(this, 'country')} placeholder="Country" />
					</FormField>
				</FormRow>
			</FormField>
		);
	},

	renderGeo () {
		if (this.state.collapsedFields.geo) {
			return null;
		}
		return (
			<FormField label="Lat / Lng" className="form-field--secondary" htmlFor={this.props.paths.geo}>
				<FormRow>
					<FormField width="one-half" className="form-field--secondary">
						<FormInput name={this.props.paths.geo} ref="geo1" value={this.props.value.geo ? this.props.value.geo[1] : ''} onChange={this.geoChanged.bind(this, 1)} placeholder="Latitude" />
					</FormField>
					<FormField width="one-half" className="form-field--secondary">
						<FormInput name={this.props.paths.geo} ref="geo0" value={this.props.value.geo ? this.props.value.geo[0] : ''} onChange={this.geoChanged.bind(this, 0)} placeholder="Longitude" />
					</FormField>
				</FormRow>
			</FormField>
		);
	},

	updateGoogleOption (key, e) {
		var newState = {};
		newState[key] = e.target.checked;
		this.setState(newState);
	},

	renderGoogleOptions () {
		if (!this.props.enableMapsAPI) return null;
		var replace = this.state.improve ? (
			<Checkbox
				label="Replace existing data"
				name={this.props.paths.overwrite}
				onChange={this.updateGoogleOption.bind(this, 'overwrite')}
				checked={this.state.overwrite} />
		) : null;
		return (
			<FormField offsetAbsentLabel>
				<Checkbox
					label="Autodetect and improve location on save"
					name={this.props.paths.improve}
					onChange={this.updateGoogleOption.bind(this, 'improve')}
					checked={this.state.improve}
					title="When checked, this will attempt to fill missing fields. It will also get the lat/long" />
				{replace}
			</FormField>
		);
	},

	renderNote () {
		if (!this.props.note) return null;
		return (
			<FormField offsetAbsentLabel>
				<FormNote note={this.props.note} />
			</FormField>
		);
	},

	renderUI () {

		if (!this.shouldRenderField()) {
			return (
				<FormField label={this.props.label}>{this.renderValue()}</FormField>
			);
		}

		/* eslint-disable no-script-url */
		var showMore = !_.isEmpty(this.state.collapsedFields)
			? <Button type="link" className="collapsed-field-label" onClick={this.uncollapseFields}>(show more fields)</Button>
			: null;
		/* eslint-enable */

		return (

				<FormField label={this.props.label}>
					{showMore}
				</FormField>
				{this.renderField('number', 'PO Box / Shop', true)}
				{this.renderField('name', 'Building Name', true)}
				{this.renderField('street1', 'Street Address')}
				{this.renderField('street2', 'Street Address 2', true)}
				{this.renderSuburbState()}
				{this.renderPostcodeCountry()}
				{this.renderGeo()}
				{this.renderGoogleOptions()}
				{this.renderNote()}

		);
	},

});
<commit_msg>LocationField: Fix null derefs, use autofocus<commit_after>import _ from 'lodash';
import React from 'react';
import Field from '../Field';
import { Button, Checkbox, FormField, FormInput, FormNote, FormRow } from 'elemental';

/**
 * TODO:
 * - Remove dependency on underscore
 * - Custom path support
 */

module.exports = Field.create({

	displayName: 'LocationField',
	statics: {
		type: 'Location',
	},

	getInitialState () {
		return {
			collapsedFields: {},
			improve: false,
			overwrite: false,
		};
	},

	componentWillMount () {
		const { value = [] } = this.props;
		var collapsedFields = {};
		_.forEach(['number', 'name', 'street2', 'geo'], (i) => {
			if (!value[i]) {
				collapsedFields[i] = true;
			}
		}, this);
		this.setState({ collapsedFields });
	},

	shouldCollapse () {
		return this.props.collapse && !this.formatValue();
	},

	uncollapseFields () {
		this.setState({
			collapsedFields: {},
		});
	},

	fieldChanged (fieldPath, event) {
		const { value = {}, path, onChange } = this.props;
		onChange({
			path,
			value: {
				...value,
				[fieldPath]: event.target.value,
			},
		});
	},

	makeChanger (fieldPath) {
		return this.fieldChanged.bind(this, fieldPath);
	},

	geoChanged (i, event) {
		const { value = {}, path, onChange } = this.props;
		const newVal = event.target.value;
		const geo = [
			i === 0 ? newVal : value.geo ? value.geo[0] : '',
			i === 1 ? newVal : value.geo ? value.geo[1] : '',
		];
		onChange({
			path,
			value: {
				...value,
				geo,
			},
		});
	},

	makeGeoChanger (fieldPath) {
		return this.geoChanged.bind(this, fieldPath);
	},

	formatValue () {
		const { value = {} } = this.props;
		return _.compact([
			value.number,
			value.name,
			value.street1,
			value.street2,
			value.suburb,
			value.state,
			value.postcode,
			value.country,
		]).join(', ');
	},

	renderValue () {
		return <FormInput noedit>{this.formatValue() || '(no value)'}</FormInput>;
	},

	renderField (fieldPath, label, collapse, autofocus) {
		if (this.state.collapsedFields[fieldPath]) {
			return null;
		}
		const { value = {}, path } = this.props;
		return (
			<FormField label={label} className="form-field--secondary" htmlFor={path + '.' + fieldPath}>
				<FormInput autofocus={autofocus} name={path + '.' + fieldPath} value={value[fieldPath]} onChange={this.makeChanger(fieldPath)} placeholder={label} />
			</FormField>
		);
	},

	renderSuburbState () {
		const { value = {}, path } = this.props;
		return (
			<FormField label="Suburb / State" className="form-field--secondary" htmlFor={path + '.suburb'}>
				<FormRow>
					<FormField width="two-thirds" className="form-field--secondary">
						<FormInput name={path + '.suburb'} value={value.suburb} onChange={this.makeChanger('suburb')} placeholder="Suburb" />
					</FormField>
					<FormField width="one-third" className="form-field--secondary">
						<FormInput name={path + '.state'} value={value.state} onChange={this.makeChanger('state')} placeholder="State" />
					</FormField>
				</FormRow>
			</FormField>
		);
	},

	renderPostcodeCountry () {
		const { value = {}, path } = this.props;
		return (
			<FormField label="Postcode / Country" className="form-field--secondary" htmlFor={path + '.postcode'}>
				<FormRow>
					<FormField width="one-third" className="form-field--secondary">
						<FormInput name={path + '.postcode'} value={value.postcode} onChange={this.makeChanger('postcode')} placeholder="Post Code" />
					</FormField>
					<FormField width="two-thirds" className="form-field--secondary">
						<FormInput name={path + '.country'} value={value.country} onChange={this.makeChanger('country')} placeholder="Country" />
					</FormField>
				</FormRow>
			</FormField>
		);
	},

	renderGeo () {
		if (this.state.collapsedFields.geo) {
			return null;
		}
		const { value = {}, paths } = this.props;
		const geo = value.geo || [];
		return (
			<FormField label="Lat / Lng" className="form-field--secondary" htmlFor={paths.geo}>
				<FormRow>
					<FormField width="one-half" className="form-field--secondary">
						<FormInput name={paths.geo} value={geo[1]} onChange={this.makeGeoChanger(1)} placeholder="Latitude" />
					</FormField>
					<FormField width="one-half" className="form-field--secondary">
						<FormInput name={paths.geo} value={geo[0]} onChange={this.makeGeoChanger(0)} placeholder="Longitude" />
					</FormField>
				</FormRow>
			</FormField>
		);
	},

	updateGoogleOption (key, e) {
		var newState = {};
		newState[key] = e.target.checked;
		this.setState(newState);
	},

	makeGoogler (key) {
		return this.updateGoogleOption.bind(this, key);
	},

	renderGoogleOptions () {
		const { paths, enableMapsAPI } = this.props;
		if (!enableMapsAPI) return null;
		var replace = this.state.improve ? (
			<Checkbox
				label="Replace existing data"
				name={paths.overwrite}
				onChange={this.makeGoogler('overwrite')}
				checked={this.state.overwrite} />
		) : null;
		return (
			<FormField offsetAbsentLabel>
				<Checkbox
					label="Autodetect and improve location on save"
					name={paths.improve}
					onChange={this.makeGoogler('improve')}
					checked={this.state.improve}
					title="When checked, this will attempt to fill missing fields. It will also get the lat/long" />
				{replace}
			</FormField>
		);
	},

	renderNote () {
		const { note } = this.props;
		if (!note) return null;
		return (
			<FormField offsetAbsentLabel>
				<FormNote note={note} />
			</FormField>
		);
	},

	renderUI () {

		if (!this.shouldRenderField()) {
			return (
				<FormField label={this.props.label}>{this.renderValue()}</FormField>
			);
		}

		/* eslint-disable no-script-url */
		var showMore = !_.isEmpty(this.state.collapsedFields)
			? <Button type="link" className="collapsed-field-label" onClick={this.uncollapseFields}>(show more fields)</Button>
			: null;
		/* eslint-enable */

		const { label, path } = this.props;
		return (

				<FormField label={label}>
					{showMore}
				</FormField>
				{this.renderField('number', 'PO Box / Shop', true, true)}
				{this.renderField('name', 'Building Name', true)}
				{this.renderField('street1', 'Street Address')}
				{this.renderField('street2', 'Street Address 2', true)}
				{this.renderSuburbState()}
				{this.renderPostcodeCountry()}
				{this.renderGeo()}
				{this.renderGoogleOptions()}
				{this.renderNote()}

		);
	},

});
<|endoftext|>


<commit_before>// Set caret position easily in jQuery
// Written by and Copyright of Luke Morton, 2011
// Licensed under MIT
(function(a){a.caretTo=function(d,c){if(d.createTextRange){var b=d.createTextRange();b.move("character",c);b.select()}else{if(d.selectionStart!=null){d.focus();d.setSelectionRange(c,c)}}};a.caretPos=function(c){if("selection" in document){var b=c.createTextRange();try{b.setEndPoint("EndToStart",document.selection.createRange())}catch(d){return 0}return b.text.length}else{if(c.selectionStart!=null){return c.selectionStart}}};a.fn.caret=function(b,c){if(typeof(b)==="undefined"){return a.caretPos(this.get(0))}return this.queue(function(e){if(isNaN(b)){var d=a(this).val().indexOf(b);if(c===true){d+=b.length}else{if(typeof(c)!=="undefined"){d+=c}}a.caretTo(this,d)}else{a.caretTo(this,b)}e()})};a.fn.caretToStart=function(){return this.caret(0)};a.fn.caretToEnd=function(){return this.queue(function(b){a.caretTo(this,a(this).val().length);b()})}}(jQuery));<commit_msg>Fixing minified jquery.caret.min.js file<commit_after>// Set caret position easily in jQuery
// Written by and Copyright of Luke Morton, 2011
// Licensed under MIT
(function($){$.caretTo=function(el,index){if(el.createTextRange){var range=el.createTextRange();range.move("character",index);range.select()}else{if(el.selectionStart!=null){el.focus();el.setSelectionRange(index,index)}}};$.caretPos=function(el){if("selection" in document){var range=el.createTextRange();try{range.setEndPoint("EndToStart",document.selection.createRange())}catch(e){return 0}return range.text.length}else{if(el.selectionStart!=null){return el.selectionStart}}};$.fn.caret=function(index,offset){if(typeof(index)==="undefined"){return $.caretPos(this.get(0))}return this.queue(function(next){if(isNaN(index)){var i=$(this).val().indexOf(index);if(offset===true){i+=index.length}else{if(typeof(offset)!=="undefined"){i+=offset}}$.caretTo(this,i)}else{$.caretTo(this,index)}next()})};$.fn.caretToStart=function(){return this.caret(0)};$.fn.caretToEnd=function(){return this.queue(function(next){$.caretTo(this,$(this).val().length);next()})}}(jQuery));<|endoftext|>


<commit_before>/**
 * Created by sasha on 2/8/15.
 */
'use strict';

var ProfilePage = require('./pages/profile.js');

describe('profile page', function () {
  var page = new ProfilePage();

  beforeEach(function () {
    element(by.linkText("Profile")).click();

  });

  describe('profile list', function () {
    it('should start with empty list', function () {
      expect(page.profileList.count()).toEqual(0);

    });

    it('should add a profile', function () {
      page.addProfile('profile1');
      expect(page.profileList.count()).toEqual(1);
      expect(page.profileAt(0)).toEqual('profile1');
    });
    it('should add another profile', function () {
      page.addProfile('profile2');
      expect(page.profileList.count()).toEqual(2);
      expect(page.profileAt(1)).toEqual('profile2');
    });
    it('should delete  profile profile2', function () {
      page.deleteProfile(1);
      expect(page.profileList.count()).toEqual(1);
      expect(page.profileAt(0)).toEqual('profile');
    });

  });
});
<commit_msg>update<commit_after>/**
 * Created by sasha on 2/8/15.
 */
'use strict';

var ProfilePage = require('./pages/profile.js');

describe('profile page', function () {
  var page = new ProfilePage();

  beforeEach(function () {
    element(by.linkText("Profile")).click();

  });

  describe('profile list', function () {
    it('should start with empty list', function () {
      expect(page.profileList.count()).toEqual(0);

    });

    it('should add a profile', function () {
      page.addProfile('profile1');
      expect(page.profileList.count()).toEqual(1);
      expect(page.profileAt(0)).toEqual('profile1');
    });
    it('should add another profile', function () {
      page.addProfile('profile2');
      expect(page.profileList.count()).toEqual(2);
      expect(page.profileAt(1)).toEqual('profile2');
    });
    it('should delete  profile profile2', function () {
      page.deleteProfile(1);
      expect(page.profileList.count()).toEqual(1);
      expect(page.profileAt(0)).toEqual('profile1');
    });

  });
});
<|endoftext|>


<commit_before>// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

// -------------
// Menu Display
// -------------

// $(cur_frm.wrapper).on("grid-row-render", function(e, grid_row) {
// 	if(grid_row.doc && grid_row.doc.fieldtype=="Section Break") {
// 		$(grid_row.row).css({"font-weight": "bold"});
// 	}
// })

frappe.ui.form.on('DocType', {
	refresh: function(frm) {
		if(frappe.session.user !== "Administrator" || !frappe.boot.developer_mode) {
			if(frm.is_new()) {
				frm.set_value("custom", 1);
			}
			frm.toggle_enable("custom", 0);
			frm.toggle_enable("beta", 0);
		}

		if (!frm.is_new() && !frm.doc.istable) {
			frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => {
				frappe.set_route('List', frm.doc.name, 'List');
			});
		}

		if(!frappe.boot.developer_mode && !frm.doc.custom) {
			// make the document read-only
			frm.set_read_only();
		}

		if(frm.is_new()) {
			if (!(frm.doc.permissions && frm.doc.permissions.length)) {
				frm.add_child('permissions', {role: 'System Manager'});
			}
		} else {
			frm.toggle_enable("engine", 0);
		}

		// set label for "In List View" for child tables
		frm.get_docfield('fields', 'in_list_view').label = frm.doc.istable ?
			__('In Grid View') : __('In List View');

		frm.events.autoname(frm);
	},

	autoname(frm) {
		frm.set_df_property('fields', 'reqd', frm.doc.autoname !== 'Prompt');
	}
})
<commit_msg>fix: Do not show "List" in Go To for single doctypes<commit_after>// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

// -------------
// Menu Display
// -------------

// $(cur_frm.wrapper).on("grid-row-render", function(e, grid_row) {
// 	if(grid_row.doc && grid_row.doc.fieldtype=="Section Break") {
// 		$(grid_row.row).css({"font-weight": "bold"});
// 	}
// })

frappe.ui.form.on('DocType', {
	refresh: function(frm) {
		if(frappe.session.user !== "Administrator" || !frappe.boot.developer_mode) {
			if(frm.is_new()) {
				frm.set_value("custom", 1);
			}
			frm.toggle_enable("custom", 0);
			frm.toggle_enable("beta", 0);
		}

		if (!frm.is_new() && !frm.doc.istable) {
			if (frm.doc.issingle) {
				frm.add_custom_button(__('Go to {0}', [frm.doc.name]), () => {
					frappe.set_route('Form', frm.doc.name);
				});
			} else {
				frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => {
					frappe.set_route('List', frm.doc.name, 'List');
				});
			}
		}

		if(!frappe.boot.developer_mode && !frm.doc.custom) {
			// make the document read-only
			frm.set_read_only();
		}

		if(frm.is_new()) {
			if (!(frm.doc.permissions && frm.doc.permissions.length)) {
				frm.add_child('permissions', {role: 'System Manager'});
			}
		} else {
			frm.toggle_enable("engine", 0);
		}

		// set label for "In List View" for child tables
		frm.get_docfield('fields', 'in_list_view').label = frm.doc.istable ?
			__('In Grid View') : __('In List View');

		frm.events.autoname(frm);
	},

	autoname(frm) {
		frm.set_df_property('fields', 'reqd', frm.doc.autoname !== 'Prompt');
	}
})
<|endoftext|>


<commit_before>frappe.provide('frappe.ui.keys.handlers');

frappe.ui.keys.setup = function() {
	$(window).on('keydown', function(e) {
		var key = e.key;
		if(key.substr(0, 5)==='Arrow') {
			// ArrowDown -> down
			key = key.substr(5).toLowerCase();
		}
		if(e.ctrlKey || e.metaKey) {
			// add ctrl+ the key
			key = 'ctrl+' + key;
		}
		if(e.shiftKey) {
			// add ctrl+ the key
			key = 'shift+' + key;
		}
		if(frappe.ui.keys.handlers[key]) {
			var out = null;
			for(var i=0, l = frappe.ui.keys.handlers[key].length; i<l; i++) {
				var handler = frappe.ui.keys.handlers[key][i];
				var _out = handler.apply(this, [e]);

				if(_out===false) {
					out = _out;
				}
			}
			return out;
		}
	});
}

frappe.ui.keys.on = function(key, handler) {
	if(!frappe.ui.keys.handlers[key]) {
		frappe.ui.keys.handlers[key] = [];
	}
	frappe.ui.keys.handlers[key].push(handler);
}

frappe.ui.keys.on('ctrl+s', function(e) {
	frappe.app.trigger_primary_action();
	e.preventDefault();
	return false;
});

frappe.ui.keys.on('ctrl+g', function(e) {
	$("#navbar-search").focus();
	e.preventDefault();
	return false;
});

frappe.ui.keys.on('ctrl+b', function(e) {
	var route = frappe.get_route();
	if(route[0]==='Form' || route[0]==='List') {
		frappe.new_doc(route[1], true);
		e.preventDefault();
		return false;
	}
});

frappe.ui.keys.on('Escape', function(e) {
	// close open grid row
	var open_row = $(".grid-row-open");
	if(open_row.length) {
		var grid_row = open_row.data("grid_row");
		grid_row.toggle_view(false);
		return false;
	}

	// close open dialog
	if(cur_dialog && !cur_dialog.no_cancel_flag) {
		cur_dialog.cancel();
		return false;
	}
});

frappe.ui.keys.on('Enter', function(e) {
	if(cur_dialog && cur_dialog.confirm_dialog) {
		cur_dialog.get_primary_btn().trigger('click');
	}
});

frappe.ui.keys.on('ctrl+down', function(e) {
	var grid_row = frappe.ui.form.get_open_grid_form();
	grid_row && grid_row.toggle_view(false, function() { grid_row.open_next() });
});

frappe.ui.keys.on('ctrl+up', function(e) {
	var grid_row = frappe.ui.form.get_open_grid_form();
	grid_row && grid_row.toggle_view(false, function() { grid_row.open_prev() });
});

frappe.ui.keys.on('shift+ctrl+r', function(e) {
	frappe.ui.toolbar.clear_cache();
});
<commit_msg>Fixed version conflict<commit_after>frappe.provide('frappe.ui.keys.handlers');

frappe.ui.keys.setup = function() {
	$(window).on('keydown', function(e) {
		var key = e.key;
		//safari doesn't have key property
		if(!key) {
			key = String.fromCharCode(e.keyCode).toLowerCase();
		}
		if(key.substr(0, 5)==='Arrow') {
			// ArrowDown -> down
			key = key.substr(5).toLowerCase();
		}
		if(e.ctrlKey || e.metaKey) {
			// add ctrl+ the key
			key = 'ctrl+' + key;
		}
		if(e.shiftKey) {
			// add ctrl+ the key
			key = 'shift+' + key;
		}
		if(frappe.ui.keys.handlers[key]) {
			var out = null;
			for(var i=0, l = frappe.ui.keys.handlers[key].length; i<l; i++) {
				var handler = frappe.ui.keys.handlers[key][i];
				var _out = handler.apply(this, [e]);

				if(_out===false) {
					out = _out;
				}
			}
			return out;
		}
	});
}

frappe.ui.keys.on = function(key, handler) {
	if(!frappe.ui.keys.handlers[key]) {
		frappe.ui.keys.handlers[key] = [];
	}
	frappe.ui.keys.handlers[key].push(handler);
}

frappe.ui.keys.on('ctrl+s', function(e) {
	frappe.app.trigger_primary_action();
	e.preventDefault();
	return false;
});

frappe.ui.keys.on('ctrl+g', function(e) {
	$("#navbar-search").focus();
	e.preventDefault();
	return false;
});

frappe.ui.keys.on('ctrl+b', function(e) {
	var route = frappe.get_route();
	if(route[0]==='Form' || route[0]==='List') {
		frappe.new_doc(route[1], true);
		e.preventDefault();
		return false;
	}
});

frappe.ui.keys.on('Escape', function(e) {
	// close open grid row
	var open_row = $(".grid-row-open");
	if(open_row.length) {
		var grid_row = open_row.data("grid_row");
		grid_row.toggle_view(false);
		return false;
	}

	// close open dialog
	if(cur_dialog && !cur_dialog.no_cancel_flag) {
		cur_dialog.cancel();
		return false;
	}
});

frappe.ui.keys.on('Enter', function(e) {
	if(cur_dialog && cur_dialog.confirm_dialog) {
		cur_dialog.get_primary_btn().trigger('click');
	}
});

frappe.ui.keys.on('ctrl+down', function(e) {
	var grid_row = frappe.ui.form.get_open_grid_form();
	grid_row && grid_row.toggle_view(false, function() { grid_row.open_next() });
});

frappe.ui.keys.on('ctrl+up', function(e) {
	var grid_row = frappe.ui.form.get_open_grid_form();
	grid_row && grid_row.toggle_view(false, function() { grid_row.open_prev() });
});

frappe.ui.keys.on('shift+ctrl+r', function(e) {
	frappe.ui.toolbar.clear_cache();
});
<|endoftext|>


<commit_before>frappe.ready(function() {
	var next_start = {{ next_start }};
	var result_wrapper = $(".website-list .result");

	$(".website-list .btn-more").on("click", function() {
		var btn = $(this);

		var data = $.extend(get_query_params(), {
			doctype: "{{ doctype }}",
			txt: "{{ txt or '' }}",
			limit_start: next_start,
			pathname: location.pathname,
			is_web_form: "{{ is_web_form }}"
		});

		btn.prop("disabled", true);
		return $.ajax({
			url:"/api/method/frappe.templates.pages.list.get",
			data: data,
			statusCode: {
				200: function(data) {
					var data = data.message;
					next_start = data.next_start;
					$.each(data.result, function(i, d) {
						$(d).appendTo(result_wrapper);
					});
					toggle_more(data.show_more);
				}
			}
		}).always(function() {
			btn.prop("disabled", false);
		});
	});

	var toggle_more = function(show) {
		if (!show) {
			$(".website-list .more-block").addClass("hide");
		}
	};

	if($('.navbar-header .navbar-toggle:visible').length === 1)
	{
		$('.page-head h1').addClass('list-head').click(function(){
			window.history.back();
	 	});
	}
});
<commit_msg>More button fixed for webpages<commit_after>frappe.ready(function() {
	var next_start = {{ next_start }};
	var result_wrapper = $(".website-list .result");

	$(".website-list .btn-more").on("click", function() {
		var btn = $(this);

		var data = $.extend(get_query_params(), {
			doctype: "{{ doctype }}",
			txt: "{{ txt or '' }}",
			limit_start: next_start,
			pathname: location.pathname,
			is_web_form: "{{ is_web_form }}"
		});

		btn.prop("disabled", true);
		return $.ajax({
			url:"/api/method/frappe.www.list.get",
			data: data,
			statusCode: {
				200: function(data) {
					var data = data.message;
					next_start = data.next_start;
					$.each(data.result, function(i, d) {
						$(d).appendTo(result_wrapper);
					});
					toggle_more(data.show_more);
				}
			}
		}).always(function() {
			btn.prop("disabled", false);
		});
	});

	var toggle_more = function(show) {
		if (!show) {
			$(".website-list .more-block").addClass("hide");
		}
	};

	if($('.navbar-header .navbar-toggle:visible').length === 1)
	{
		$('.page-head h1').addClass('list-head').click(function(){
			window.history.back();
	 	});
	}
});
<|endoftext|>