<commit_before>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

let instance = null;
let log = require("../../net2/logger.js")(__filename);

let exec = require('child-process-promise').exec;

let async = require('asyncawait/async');
let await = require('asyncawait/await');

let util = require('util');

class Samba {
  constructor() {
    if (instance == null) {
      instance = this;
    }
    return instance;
  }

  getSambaName(ip) {
    let cmd = util.format("nbtscan -s / %s", ip);
    log.info("Running command:", cmd, {});

    return async(() => {
      let exists = await (this.nbtscanExists());
      if(!exists)
        return undefined; // empty string means not having samba name or not supported

      let result = await (exec(cmd));

      if(result.stdout && result.stdout === "") {
        return undefined;
      }

      if(result.stdout) {
        let outputs = result.stdout.split("/");
        if(outputs.length >= 2) {
          let output = outputs[1]
          if(output === '<unknown>') {
            return undefined
          } else {
            return outputs[1];
          }
        } else {
          log.error("Invalid nbtscan output:", result.stdout, {});
          return undefined;
        }
      } else {
        log.error("Invalid nbtscan output:", result.stderr, {});
        return undefined;
      }
    })();
  }

  nbtscanExists() {
    return exec("which nbtscan")
      .then(() => {
        return true;
      }).catch((err) => {
        return false;
      })
  }
}

module.exports = Samba;
<commit_msg>refine samba output<commit_after>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

let instance = null;
let log = require("../../net2/logger.js")(__filename);

let exec = require('child-process-promise').exec;

let async = require('asyncawait/async');
let await = require('asyncawait/await');

let util = require('util');

class Samba {
  constructor() {
    if (instance == null) {
      instance = this;
    }
    return instance;
  }

  getSambaName(ip) {
    let cmd = util.format("nbtscan -e %s | head -n 1 | awk '{print $2}'", ip);
    log.info("Running command:", cmd, {});

    return async(() => {
      let exists = await (this.nbtscanExists());
      if(!exists)
        return undefined; // empty string means not having samba name or not supported

      let result = await (exec(cmd));

      if(!result.stdout) {
        return undefined;
      }

      let output = result.stdout
      output = output.trim()

      if(output === '<unknown>') {
        return undefined
      } else {
        return output
      }

    })();
  }

  nbtscanExists() {
    return exec("which nbtscan")
      .then(() => {
        return true;
      }).catch((err) => {
        return false;
      })
  }
}

module.exports = Samba;
<|endoftext|>


<commit_before>/*!
  * CallPowerForm.js
  * Connects embedded action form to CallPower campaign
  * Requires jQuery >= 1.7.0
  *    proxy (1.4), deferred (1.5), promises (1.6), on (1.7)
  *
  * Displays call script in overlay or by replacing form
  * Override functions onSuccess or onError in CallPowerOptions
  * Instantiate with form selector to connect callbacks
  * (c) Spacedog.xyz 2015, license AGPLv3
  */

// revealing module pattern
var CallPowerForm = function (formSelector, $) {
  // instance variables
  this.$ = $;  // stash loaded version of jQuery, in case there are conflicts with window
  this.form = this.$(formSelector);
  {% if campaign.embed %}
  this.locationField = this.$('{{campaign.embed.get("location_sel","#location_id")}}');
  this.phoneField = this.$('{{campaign.embed.get("phone_sel","#phone_id")}}');
  {% else %}
  this.locationField = this.$("#location_id");
  this.phoneField = this.$("#phone_id");
  {% endif %}
  this.locateBy = '{{campaign.locate_by}}';
  this.scriptDisplay = 'overlay';

  // allow options to override settings
  for (var option in window.CallPowerOptions || []) {
    var setting = CallPowerOptions[option];
    // accept jquery selectors for form and fields
    var selectorFields = ['form', 'locationField', 'phoneField'];
    if ($.inArray(option, selectorFields) != -1 && typeof setting === 'string') {
      this[option] = this.$(setting);
    } else {
      this[option] = setting;
    }
  }

  // bind our submit event to makeCall
  this.form.on("submit.CallPower", this.$.proxy(this.makeCall, this));
  // include custom css
  if(this.customCSS !== undefined) {
    var link_elem = 'link rel="stylesheet" href="';
    // one weird trick to create the element without it rendering in browsers
    this.$('head').append('<'+link_elem+this.customCSS+'" />');
  }
};

CallPowerForm.prototype = function() {
  // prototype variables
  var createCallURL = '{{url_for("call.create", _external=True)}}';
  var campaignId = "{{campaign.id}}";

  var simpleGetCountry = function() {
    return "{{campaign.country_code|default('US')}}";
  };

  var cleanUSZipcode = function(val) {
    if (val.length === 0) { return undefined; }
    var isValid = /(\d{5}([\-]\d{4})?)/.test(val);
    return isValid ? val : false;
  };

  var cleanCAPostal = function(val) {
      if (val === 0) { return undefined; }
      var valNospace = val.replace(/\W+/g, '');
      var isValid = /([ABCEGHJKLMNPRSTVXY]\d)([ABCEGHJKLMNPRSTVWXYZ]\d){2}/i.test(valNospace);
      return isValid ? valNospace : false;
  };

  // very simple country specific validators
  // override with eg google places autocomplete
  var simpleValidateLocation = function() {
    countryCode = this.country();

    if (this.locationField.length == 1) {
      var locationVal = this.locationField.val();
    } else {
      var locationVal = '';
      this.locationField.each(function() {
        locationVal += (' ' + $(this).val());
      });
      locationVal = locationVal.trim();
    }

    if (this.locateBy === 'postal') {
      if (countryCode === 'US') { return cleanUSZipcode(locationVal); }
      else if (countryCode === 'CA') { return cleanCAPostal(locationVal); }
    }
    return locationVal;
  };

  // very simple country specific length validators
  // override with eg: google libphonenumber
  var simpleValidatePhone = function(countryCode) {
    countryCode = this.country();

    if (this.phoneField.length === 0) { return undefined; }
    // remove whitespace, parents, plus, dashes from phoneField
    var num = this.phoneField.val()
      .replace(/\s/g, '').replace(/\(/g, '').replace(/\)/g, '')
      .replace('+', '').replace(/\-/g, '');
    return num;

    var isValid = false;
    var prefix;
    if (countryCode === 'US' || countryCode === 'CA') {
        prefix = "1";
        // strip leading prefix
        if (num.charAt(0) === prefix) {
          num = num.substr(1);
        }
        isValid = (num.length == 10); // ensure just 10 digits remain
    }
    else if (countryCode === 'GB') {
        prefix = "44";
        // strip leading prefix
        if (num.slice(0,2) === prefix) {
          num = num.substr(2);
        }
        isValid = (num.length >= 9); // ensure at least 9 digits remain
    }
    else {
      prefix = '';
      isValid = (num.length > 8); // ensure at least a few digits remain
    }

    // re-append prefix and plus sign, for e164 dialing
    if (prefix || prefix === '') {
      num = "+"+prefix+num;
    }
    return isValid ?  num : false;
  };

  var onSuccess = function(response) {
    if (response.campaign === 'archived') { return onError(this.form, 'This campaign is no longer active.'); }
    if (response.campaign !== 'live') { return onError(this.form, 'This campaign is not live.'); }
    if (response.call !== 'queued') { return onError(this.form, 'Could not start call.'); }
    if (response.script === undefined) { return false; }

    if (this.phoneDisplay) {
      $(this.phoneDisplay).html(response.fromNumber);
    }

    if (this.scriptDisplay === 'overlay') {
      // display simple overlay with script content
      var closeButton = '<button type="button" class="close" aria-label="Close">×</button>'
      if (this.overlayCloseText) {
        var closeText = ''+this.overlayCloseText+'';
      }
      var scriptOverlay = this.$('
'+
        '
'+
        closeButton+
        response.script+
        (closeText || '')+
        '
'+
      '
');
      this.$('body').append(scriptOverlay);
      scriptOverlay.overlay();
      scriptOverlay.css('visibility', 'visible');
      scriptOverlay.addClass('shown');
    }

    if (this.scriptDisplay === 'replace') {
      // hide form and show script contents

      // start new empty hidden div
      var scriptDiv = this.$('

');
      // copy existing form classes and styling
      scriptDiv.addClass(this.form.attr('class'));
      // insert response contents
      scriptDiv.html(response.script);

      scriptDiv.insertAfter(this.form);
      this.form.slideToggle();
      scriptDiv.slideToggle();
    }

    if (this.scriptDisplay === 'redirect') {
      // save response url to redirect after original form callback
      this.redirectAfter = response.redirect;
    }

    if (this.scriptDisplay === 'alert') {
      // popup alert with response.script text
      var message = this.$(response.script);
      alert(message.text());
    }

    // run custom js function
    if(typeof this.customJS !== 'undefined') {
      var $ = this.$; // make our version of jQuery available to local context
      if(typeof this.customJS === 'string' ) { eval(this.customJS); }
      else if (typeof this.customJS === 'function') { this.customJS(); }
    }

    return true;
  };

  var onError = function(element, message) {
    if (element !== undefined) {
      element.addClass('has-error');
    }
    console.error('CallPower error: ' + message);
    return false;
  };

  var makeCall = function(event, options) {
    // stop default form submit event
    if (event !== undefined) {
      event.preventDefault();
      event.stopImmediatePropagation();
    }

    if (this.locationField.length && !this.location()) {
      return this.onError(this.locationField, 'Invalid location');
    }
    if (this.phoneField.length && !this.phone()) {
      return this.onError(this.phoneField, 'Invalid phone number');
    }

    this.$.ajax(createCallURL, {
      method: 'GET',
      data: {
        campaignId: campaignId,
        userLocation: this.location(),
        userPhone: this.phone(),
        userCountry: this.country()
      }
    })
    .done(this.$.proxy(this.onSuccess, this))
    .fail(this.$.proxy(this.onError, this, this.form, 'Please fill out the form completely'))
    .then(this.$.proxy(function() {
      // turn off our submit event
      this.form.off('submit.CallPower');

      // redirect after original form submission is complete

      if (this.scriptDisplay === 'overlay') {
        var scriptOverlay = this.$('.overlay');
        // bind overlay hide to original form submit
        scriptOverlay.on('hide', this.$.proxy(this.formSubmit, this));
        scriptOverlay.on('click', this.$.proxy(function(e) {
          var target = $(e.target);
          if (target.hasClass(scriptOverlay.attr('class')) ||
              target.hasClass('close') ||
              target.hasClass('closeText')) {
            return scriptOverlay.trigger('hide');
          }
          // only trigger hide when clicking overlay background or hide link, not modal body
        }, this));
      } else if (this.scriptDisplay === 'replace') {
        // original form still exists, but is hidden
        // do nothing
      } else if (this.scriptDisplay === 'redirect' && this.redirectAfter) {
          window.location.replace(this.redirectAfter);
      } else {
        // re-trigger original form submit
        this.formSubmit();
      }
    }, this))
    .fail(this.$.proxy(this.onError, this, this.form, 'Sorry, there was an error making the call'));

    return false;
    // just in case, to stop initial event propagation
  };

  var formSubmit = function() {
    // trigger form submit event after optional delay
    window.setTimeout(this.$.proxy(function() { this.form.trigger('submit'); }, this), this.submitDelay || 0);
  };

  // public method interface
  var public = {
    getCountry: simpleGetCountry,
    getLocation: simpleValidateLocation,
    getPhone: simpleValidatePhone,
    onError: onError,
    onSuccess: onSuccess,
    makeCall: makeCall,
    formSubmit: formSubmit
  };
  // let these be overridden, but keep reference to original functions
  public.country = public.getCountry;
  public.location = public.getLocation;
  public.phone = public.getPhone;
  return public;
} ();
<commit_msg>Embed 429 handler, for #93<commit_after>/*!
  * CallPowerForm.js
  * Connects embedded action form to CallPower campaign
  * Requires jQuery >= 1.7.0
  *    proxy (1.4), deferred (1.5), promises (1.6), on (1.7)
  *
  * Displays call script in overlay or by replacing form
  * Override functions onSuccess or onError in CallPowerOptions
  * Instantiate with form selector to connect callbacks
  * (c) Spacedog.xyz 2015, license AGPLv3
  */

// revealing module pattern
var CallPowerForm = function (formSelector, $) {
  // instance variables
  this.$ = $;  // stash loaded version of jQuery, in case there are conflicts with window
  this.form = this.$(formSelector);
  {% if campaign.embed %}
  this.locationField = this.$('{{campaign.embed.get("location_sel","#location_id")}}');
  this.phoneField = this.$('{{campaign.embed.get("phone_sel","#phone_id")}}');
  {% else %}
  this.locationField = this.$("#location_id");
  this.phoneField = this.$("#phone_id");
  {% endif %}
  this.locateBy = '{{campaign.locate_by}}';
  this.scriptDisplay = 'overlay';

  // allow options to override settings
  for (var option in window.CallPowerOptions || []) {
    var setting = CallPowerOptions[option];
    // accept jquery selectors for form and fields
    var selectorFields = ['form', 'locationField', 'phoneField'];
    if ($.inArray(option, selectorFields) != -1 && typeof setting === 'string') {
      this[option] = this.$(setting);
    } else {
      this[option] = setting;
    }
  }

  // bind our submit event to makeCall
  this.form.on("submit.CallPower", this.$.proxy(this.makeCall, this));
  // include custom css
  if(this.customCSS !== undefined) {
    var link_elem = 'link rel="stylesheet" href="';
    // one weird trick to create the element without it rendering in browsers
    this.$('head').append('<'+link_elem+this.customCSS+'" />');
  }
};

CallPowerForm.prototype = function() {
  // prototype variables
  var createCallURL = '{{url_for("call.create", _external=True)}}';
  var campaignId = "{{campaign.id}}";

  var simpleGetCountry = function() {
    return "{{campaign.country_code|default('US')}}";
  };

  var cleanUSZipcode = function(val) {
    if (val.length === 0) { return undefined; }
    var isValid = /(\d{5}([\-]\d{4})?)/.test(val);
    return isValid ? val : false;
  };

  var cleanCAPostal = function(val) {
      if (val === 0) { return undefined; }
      var valNospace = val.replace(/\W+/g, '');
      var isValid = /([ABCEGHJKLMNPRSTVXY]\d)([ABCEGHJKLMNPRSTVWXYZ]\d){2}/i.test(valNospace);
      return isValid ? valNospace : false;
  };

  // very simple country specific validators
  // override with eg google places autocomplete
  var simpleValidateLocation = function() {
    countryCode = this.country();

    if (this.locationField.length == 1) {
      var locationVal = this.locationField.val();
    } else {
      var locationVal = '';
      this.locationField.each(function() {
        locationVal += (' ' + $(this).val());
      });
      locationVal = locationVal.trim();
    }

    if (this.locateBy === 'postal') {
      if (countryCode === 'US') { return cleanUSZipcode(locationVal); }
      else if (countryCode === 'CA') { return cleanCAPostal(locationVal); }
    }
    return locationVal;
  };

  // very simple country specific length validators
  // override with eg: google libphonenumber
  var simpleValidatePhone = function(countryCode) {
    countryCode = this.country();

    if (this.phoneField.length === 0) { return undefined; }
    // remove whitespace, parents, plus, dashes from phoneField
    var num = this.phoneField.val()
      .replace(/\s/g, '').replace(/\(/g, '').replace(/\)/g, '')
      .replace('+', '').replace(/\-/g, '');
    return num;

    var isValid = false;
    var prefix;
    if (countryCode === 'US' || countryCode === 'CA') {
        prefix = "1";
        // strip leading prefix
        if (num.charAt(0) === prefix) {
          num = num.substr(1);
        }
        isValid = (num.length == 10); // ensure just 10 digits remain
    }
    else if (countryCode === 'GB') {
        prefix = "44";
        // strip leading prefix
        if (num.slice(0,2) === prefix) {
          num = num.substr(2);
        }
        isValid = (num.length >= 9); // ensure at least 9 digits remain
    }
    else {
      prefix = '';
      isValid = (num.length > 8); // ensure at least a few digits remain
    }

    // re-append prefix and plus sign, for e164 dialing
    if (prefix || prefix === '') {
      num = "+"+prefix+num;
    }
    return isValid ?  num : false;
  };

  var onSuccess = function(response) {
    if (response.campaign === 'archived') { return onError(this.form, 'This campaign is no longer active.'); }
    if (response.campaign !== 'live') { return onError(this.form, 'This campaign is not live.'); }
    if (response.call !== 'queued') { return onError(this.form, 'Could not start call.'); }
    if (response.script === undefined) { return false; }

    if (this.phoneDisplay) {
      $(this.phoneDisplay).html(response.fromNumber);
    }

    if (this.scriptDisplay === 'overlay') {
      // display simple overlay with script content
      var closeButton = '<button type="button" class="close" aria-label="Close">×</button>'
      if (this.overlayCloseText) {
        var closeText = ''+this.overlayCloseText+'';
      }
      var scriptOverlay = this.$('
'+
        '
'+
        closeButton+
        response.script+
        (closeText || '')+
        '
'+
      '
');
      this.$('body').append(scriptOverlay);
      scriptOverlay.overlay();
      scriptOverlay.css('visibility', 'visible');
      scriptOverlay.addClass('shown');
    }

    if (this.scriptDisplay === 'replace') {
      // hide form and show script contents

      // start new empty hidden div
      var scriptDiv = this.$('

');
      // copy existing form classes and styling
      scriptDiv.addClass(this.form.attr('class'));
      // insert response contents
      scriptDiv.html(response.script);

      scriptDiv.insertAfter(this.form);
      this.form.slideToggle();
      scriptDiv.slideToggle();
    }

    if (this.scriptDisplay === 'redirect') {
      // save response url to redirect after original form callback
      this.redirectAfter = response.redirect;
    }

    if (this.scriptDisplay === 'alert') {
      // popup alert with response.script text
      var message = this.$(response.script);
      alert(message.text());
    }

    // run custom js function
    if(typeof this.customJS !== 'undefined') {
      var $ = this.$; // make our version of jQuery available to local context
      if(typeof this.customJS === 'string' ) { eval(this.customJS); }
      else if (typeof this.customJS === 'function') { this.customJS(); }
    }

    return true;
  };

  var onError = function(element, message) {
    if (element !== undefined) {
      element.addClass('has-error');
    }
    console.error('CallPower error: ' + message);
    return false;
  };

  var makeCall = function(event, options) {
    // stop default form submit event
    if (event !== undefined) {
      event.preventDefault();
      event.stopImmediatePropagation();
    }

    if (this.locationField.length && !this.location()) {
      return this.onError(this.locationField, 'Invalid location');
    }
    if (this.phoneField.length && !this.phone()) {
      return this.onError(this.phoneField, 'Invalid phone number');
    }

    var self = this;
    this.$.ajax(createCallURL, {
      method: 'GET',
      data: {
        campaignId: campaignId,
        userLocation: this.location(),
        userPhone: this.phone(),
        userCountry: this.country()
      },
      statusCode: {
        429: function() {
          return self.onError(self.form, 'Sorry, you have made too many requests');
        }
      }
    })
    .done(this.$.proxy(this.onSuccess, this))
    .fail(this.$.proxy(this.onError, this, this.form, 'Please fill out the form completely'))
    .then(this.$.proxy(function() {
      // turn off our submit event
      this.form.off('submit.CallPower');

      // redirect after original form submission is complete

      if (this.scriptDisplay === 'overlay') {
        var scriptOverlay = this.$('.overlay');
        // bind overlay hide to original form submit
        scriptOverlay.on('hide', this.$.proxy(this.formSubmit, this));
        scriptOverlay.on('click', this.$.proxy(function(e) {
          var target = $(e.target);
          if (target.hasClass(scriptOverlay.attr('class')) ||
              target.hasClass('close') ||
              target.hasClass('closeText')) {
            return scriptOverlay.trigger('hide');
          }
          // only trigger hide when clicking overlay background or hide link, not modal body
        }, this));
      } else if (this.scriptDisplay === 'replace') {
        // original form still exists, but is hidden
        // do nothing
      } else if (this.scriptDisplay === 'redirect' && this.redirectAfter) {
          window.location.replace(this.redirectAfter);
      } else {
        // re-trigger original form submit
        this.formSubmit();
      }
    }, this))
    .fail(this.$.proxy(this.onError, this, this.form, 'Sorry, there was an error making the call'));

    return false;
    // just in case, to stop initial event propagation
  };

  var formSubmit = function() {
    // trigger form submit event after optional delay
    window.setTimeout(this.$.proxy(function() { this.form.trigger('submit'); }, this), this.submitDelay || 0);
  };

  // public method interface
  var public = {
    getCountry: simpleGetCountry,
    getLocation: simpleValidateLocation,
    getPhone: simpleValidatePhone,
    onError: onError,
    onSuccess: onSuccess,
    makeCall: makeCall,
    formSubmit: formSubmit
  };
  // let these be overridden, but keep reference to original functions
  public.country = public.getCountry;
  public.location = public.getLocation;
  public.phone = public.getPhone;
  return public;
} ();
<|endoftext|>


<commit_before>/* @flow */
import React from 'react';
import {
  ActivityIndicator,
  Image,
  StyleSheet,
  RefreshControl,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';
import FadeIn from 'react-native-fade-in-image';

import { take, takeRight } from 'lodash';
import dedent from 'dedent';

import Colors from '../constants/Colors';
import PrimaryButton from './PrimaryButton';
import EmptyProfileProjectsNotice from './EmptyProfileProjectsNotice';
import EmptyProfileSnacksNotice from './EmptyProfileSnacksNotice';
import SeeAllProjectsButton from './SeeAllProjectsButton';
import SeeAllSnacksButton from './SeeAllSnacksButton';
import SharedStyles from '../constants/SharedStyles';
import SmallProjectCard from './SmallProjectCard';
import SnackCard from './SnackCard';
import ScrollView from '../components/NavigationScrollView';
import { SectionLabelText, StyledText } from '../components/Text';
import { SectionLabelContainer, StyledView } from '../components/Views';

const MAX_APPS_TO_DISPLAY = 3;
const MAX_SNACKS_TO_DISPLAY = 3;

const NETWORK_ERROR_TEXT = dedent`
  Your connection appears to be offline.
  Get out of the subway tunnel or connect to a better Wi-Fi network and check back.
`;

const SERVER_ERROR_TEXT = dedent`
  An unexpected error has occurred.
  Sorry about this. We will resolve the issue as soon as possible.
`;

export default class Profile extends React.Component {
  state = {
    isRefetching: false,
  };

  _isMounted: boolean;

  componentWillMount() {
    this._isMounted = true;
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  componentWillReceiveProps(nextProps: any) {
    const SkipConnectionNotification = true;
    if (!SkipConnectionNotification && !this.props.data.error && nextProps.data.error) {
      // NOTE(brentvatne): sorry for this
      let isConnectionError = nextProps.data.error.message.includes('No connection available');

      if (isConnectionError) {
        // Should have some integrated alert banner
        alert('No connection available');
      }
    }
  }

  render() {
    // NOTE(brentvatne): investigate why `user` is null when there
    // is an error, even if it loaded before. This seems undesirable,
    // can it be avoided with apollo-client?
    if (this.props.data.error && !this.props.data.user) {
      return this._renderError();
    }

    if (this.props.data.loading && !this.props.data.user) {
      return this._renderLoading();
    }

    return (
      <ScrollView
        refreshControl={
          <RefreshControl
            refreshing={this.state.isRefetching}
            onRefresh={this._handleRefreshAsync}
          />
        }
        contentContainerStyle={{ paddingBottom: 20 }}
        style={styles.container}>
        {this._renderHeader()}
        {this._renderApps()}
        {this._renderSnacks()}
      </ScrollView>
    );
  }

  _handleRefreshAsync = async () => {
    if (this.state.isRefetching) {
      return;
    }

    try {
      this.setState({ isRefetching: true });
      this.props.data.refetch({ fetchPolicy: 'network-only' });
    } catch (e) {
      // TODO(brentvatne): Put this into Sentry
      console.log({ e });
    } finally {
      // Add a slight delay so it doesn't just disappear immediately,
      // this actually looks nicer because you might think that it
      // didn't work if it disappears too quickly
      setTimeout(() => {
        if (this._isMounted) {
          this.setState({ isRefetching: false });
        }
      }, 500);
    }
  };

  _renderError = () => {
    // NOTE(brentvatne): sorry for this
    let isConnectionError = this.props.data.error.message.includes('No connection available');

    return (
      <View style={{ flex: 1, alignItems: 'center', paddingTop: 30 }}>
        <Text style={SharedStyles.noticeDescriptionText}>
          {isConnectionError ? NETWORK_ERROR_TEXT : SERVER_ERROR_TEXT}
        </Text>

        <PrimaryButton plain onPress={this._handleRefreshAsync} fallback={TouchableOpacity}>
          Try again
        </PrimaryButton>

        {this.state.isRefetching && (
          <View style={{ marginTop: 20 }}>
            <ActivityIndicator />
          </View>
        )}
      </View>
    );
  };

  _renderLoading() {
    return (
      <View style={{ flex: 1, padding: 30, alignItems: 'center' }}>
        <ActivityIndicator />
      </View>
    );
  }

  _renderHeader = () => {
    if (!this.props.data.user) {
      return;
    }

    if (this.props.data.user.isLegacy) {
      return this._renderLegacyHeader();
    }

    let { firstName, lastName, username, profilePhoto } = this.props.data.user;

    return (
      <StyledView style={styles.header} darkBackgroundColor="#000" darkBorderColor="#000">
        <View style={styles.headerAvatarContainer}>
          <FadeIn>
            <Image style={styles.headerAvatar} source={{ uri: profilePhoto }} />
          </FadeIn>
        </View>
        <StyledText style={styles.headerFullNameText}>
          {firstName} {lastName}
        </StyledText>
        <View style={styles.headerAccountsList}>
          <StyledText style={styles.headerAccountText} lightColor="#232B3A" darkColor="#ccc">
            @{username}
          </StyledText>
          {this._maybeRenderGithubAccount()}
        </View>
      </StyledView>
    );
  };

  _renderLegacyHeader = () => {
    let { username } = this.props.data.user;

    return (
      <View style={styles.header}>
        <View
          style={[styles.headerAvatar, styles.headerAvatarContainer, styles.legacyHeaderAvatar]}
        />
        <View style={styles.headerAccountsList}>
          <Text style={styles.headerAccountText}>@{username}</Text>
        </View>
      </View>
    );
  };

  _renderApps = () => {
    if (!this.props.data.user) {
      return;
    }

    let { apps, appCount } = this.props.data.user;
    let content;

    if (!apps || !apps.length) {
      content = <EmptyProfileProjectsNotice isOwnProfile={this.props.isOwnProfile} />;
    } else {
      let otherApps = takeRight(apps, Math.max(0, apps.length - MAX_APPS_TO_DISPLAY));
      content = (
        <>
          {take(apps, MAX_APPS_TO_DISPLAY).map(this._renderApp)}
          <SeeAllProjectsButton
            apps={otherApps}
            appCount={appCount - MAX_APPS_TO_DISPLAY}
            label="See all projects"
            onPress={this._handlePressProjectList}
          />
        </>
      );
    }

    return (
      <View style={{ marginBottom: 3 }}>
        <SectionLabelContainer style={{ marginTop: 10 }}>
          <SectionLabelText>PUBLISHED PROJECTS</SectionLabelText>
        </SectionLabelContainer>
        {content}
      </View>
    );
  };

  _renderSnacks = () => {
    if (!this.props.data.user) {
      return;
    }

    let { snacks } = this.props.data.user;
    let content;

    if (!snacks || !snacks.length) {
      content = <EmptyProfileSnacksNotice isOwnProfile={this.props.isOwnProfile} />;
    } else {
      let otherSnacks = takeRight(snacks, Math.max(0, snacks.length - MAX_SNACKS_TO_DISPLAY));
      content = (
        <>
          {take(snacks, MAX_SNACKS_TO_DISPLAY).map(this._renderSnack)}
          <SeeAllSnacksButton
            snacks={otherSnacks}
            label="See all Snacks"
            onPress={this._handlePressSnackList}
          />
        </>
      );
    }

    return (
      <View style={{ marginBottom: 3 }}>
        <SectionLabelContainer style={{ marginTop: 10 }}>
          <SectionLabelText>SAVED SNACKS</SectionLabelText>
        </SectionLabelContainer>
        {content}
      </View>
    );
  };

  _handlePressProjectList = () => {
    this.props.navigation.navigate('ProjectsForUser', {
      username: this.props.username,
      belongsToCurrentUser: this.props.isOwnProfile,
    });
  };

  _handlePressSnackList = () => {
    this.props.navigation.navigate('SnacksForUser', {
      username: this.props.username,
      belongsToCurrentUser: this.props.isOwnProfile,
    });
  };

  _renderApp = (app: any, i: number) => {
    return (
      <SmallProjectCard
        key={i}
        hideUsername
        iconUrl={app.iconUrl}
        projectName={app.name}
        slug={app.packageName}
        projectUrl={app.fullName}
        privacy={app.privacy}
        fullWidthBorder
      />
    );
  };

  _renderSnack = (snack: any, i: number) => {
    return (
      <SnackCard
        key={i}
        projectName={snack.name}
        description={snack.description}
        projectUrl={snack.fullName}
        fullWidthBorder
      />
    );
  };
  _maybeRenderGithubAccount() {
    // ..
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: -1,
  },
  header: {
    alignItems: 'center',
    borderTopWidth: 1,
    borderBottomWidth: 1,
  },
  headerAvatarContainer: {
    marginTop: 20,
    marginBottom: 12,
    overflow: 'hidden',
    borderRadius: 5,
  },
  headerAvatar: {
    height: 64,
    width: 64,
    borderRadius: 5,
  },
  legacyHeaderAvatar: {
    backgroundColor: '#eee',
  },
  headerAccountsList: {
    paddingBottom: 20,
  },
  headerAccountText: {
    // color: 'rgba(36, 44, 58, 0.4)',
    fontSize: 14,
  },
  headerFullNameText: {
    fontSize: 20,
    fontWeight: '500',
  },
});
<commit_msg>[home] Better styling for profile error case<commit_after>/* @flow */
import React from 'react';
import {
  ActivityIndicator,
  Image,
  StyleSheet,
  RefreshControl,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';
import FadeIn from 'react-native-fade-in-image';

import { take, takeRight } from 'lodash';
import dedent from 'dedent';

import Colors from '../constants/Colors';
import PrimaryButton from './PrimaryButton';
import EmptyProfileProjectsNotice from './EmptyProfileProjectsNotice';
import EmptyProfileSnacksNotice from './EmptyProfileSnacksNotice';
import SeeAllProjectsButton from './SeeAllProjectsButton';
import SeeAllSnacksButton from './SeeAllSnacksButton';
import SharedStyles from '../constants/SharedStyles';
import SmallProjectCard from './SmallProjectCard';
import SnackCard from './SnackCard';
import ScrollView from '../components/NavigationScrollView';
import { SectionLabelText, StyledText } from '../components/Text';
import { SectionLabelContainer, StyledView } from '../components/Views';

const MAX_APPS_TO_DISPLAY = 3;
const MAX_SNACKS_TO_DISPLAY = 3;

const NETWORK_ERROR_TEXT = dedent`
  Your connection appears to be offline.
  Get out of the subway tunnel or connect to a better Wi-Fi network and check back.
`;

const SERVER_ERROR_TEXT = dedent`
  An unexpected error has occurred.
  Sorry about this. We will resolve the issue as soon as possible.
`;

export default class Profile extends React.Component {
  state = {
    isRefetching: false,
  };

  _isMounted: boolean;

  componentWillMount() {
    this._isMounted = true;
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  componentWillReceiveProps(nextProps: any) {
    const SkipConnectionNotification = true;
    if (!SkipConnectionNotification && !this.props.data.error && nextProps.data.error) {
      // NOTE(brentvatne): sorry for this
      let isConnectionError = nextProps.data.error.message.includes('No connection available');

      if (isConnectionError) {
        // Should have some integrated alert banner
        alert('No connection available');
      }
    }
  }

  render() {
    // NOTE(brentvatne): investigate why `user` is null when there
    // is an error, even if it loaded before. This seems undesirable,
    // can it be avoided with apollo-client?

    if (this.props.data.error && !this.props.data.user) {
      return this._renderError();
    }

    if (this.props.data.loading && !this.props.data.user) {
      return this._renderLoading();
    }

    return (
      <ScrollView
        refreshControl={
          <RefreshControl
            refreshing={this.state.isRefetching}
            onRefresh={this._handleRefreshAsync}
          />
        }
        contentContainerStyle={{ paddingBottom: 20 }}
        style={styles.container}>
        {this._renderHeader()}
        {this._renderApps()}
        {this._renderSnacks()}
      </ScrollView>
    );
  }

  _handleRefreshAsync = async () => {
    if (this.state.isRefetching) {
      return;
    }

    try {
      this.setState({ isRefetching: true });
      this.props.data.refetch({ fetchPolicy: 'network-only' });
    } catch (e) {
      // TODO(brentvatne): Put this into Sentry
      console.log({ e });
    } finally {
      // Add a slight delay so it doesn't just disappear immediately,
      // this actually looks nicer because you might think that it
      // didn't work if it disappears too quickly
      setTimeout(() => {
        if (this._isMounted) {
          this.setState({ isRefetching: false });
        }
      }, 500);
    }
  };

  _renderError = () => {
    // NOTE(brentvatne): sorry for this
    let isConnectionError = this.props.data?.error?.message?.includes('No connection available');

    return (
      <ScrollView style={{flex: 1}} contentContainerStyle={{ flex: 1, alignItems: 'center', paddingTop: 30 }}>
        <StyledText
          style={SharedStyles.noticeDescriptionText}
          lightColor="rgba(36, 44, 58, 0.7)"
          darkColor="#ccc">
          {isConnectionError ? NETWORK_ERROR_TEXT : SERVER_ERROR_TEXT}
        </StyledText>

        <PrimaryButton plain onPress={this._handleRefreshAsync} fallback={TouchableOpacity}>
          Try again
        </PrimaryButton>

        {this.state.isRefetching && (
          <View style={{ marginTop: 20 }}>
            <ActivityIndicator />
          </View>
        )}
      </ScrollView>
    );
  };

  _renderLoading() {
    return (
      <View style={{ flex: 1, padding: 30, alignItems: 'center' }}>
        <ActivityIndicator />
      </View>
    );
  }

  _renderHeader = () => {
    if (!this.props.data.user) {
      return;
    }

    if (this.props.data.user.isLegacy) {
      return this._renderLegacyHeader();
    }

    let { firstName, lastName, username, profilePhoto } = this.props.data.user;

    return (
      <StyledView style={styles.header} darkBackgroundColor="#000" darkBorderColor="#000">
        <View style={styles.headerAvatarContainer}>
          <FadeIn>
            <Image style={styles.headerAvatar} source={{ uri: profilePhoto }} />
          </FadeIn>
        </View>
        <StyledText style={styles.headerFullNameText}>
          {firstName} {lastName}
        </StyledText>
        <View style={styles.headerAccountsList}>
          <StyledText style={styles.headerAccountText} lightColor="#232B3A" darkColor="#ccc">
            @{username}
          </StyledText>
          {this._maybeRenderGithubAccount()}
        </View>
      </StyledView>
    );
  };

  _renderLegacyHeader = () => {
    let { username } = this.props.data.user;

    return (
      <View style={styles.header}>
        <View
          style={[styles.headerAvatar, styles.headerAvatarContainer, styles.legacyHeaderAvatar]}
        />
        <View style={styles.headerAccountsList}>
          <Text style={styles.headerAccountText}>@{username}</Text>
        </View>
      </View>
    );
  };

  _renderApps = () => {
    if (!this.props.data.user) {
      return;
    }

    let { apps, appCount } = this.props.data.user;
    let content;

    if (!apps || !apps.length) {
      content = <EmptyProfileProjectsNotice isOwnProfile={this.props.isOwnProfile} />;
    } else {
      let otherApps = takeRight(apps, Math.max(0, apps.length - MAX_APPS_TO_DISPLAY));
      content = (
        <>
          {take(apps, MAX_APPS_TO_DISPLAY).map(this._renderApp)}
          <SeeAllProjectsButton
            apps={otherApps}
            appCount={appCount - MAX_APPS_TO_DISPLAY}
            label="See all projects"
            onPress={this._handlePressProjectList}
          />
        </>
      );
    }

    return (
      <View style={{ marginBottom: 3 }}>
        <SectionLabelContainer style={{ marginTop: 10 }}>
          <SectionLabelText>PUBLISHED PROJECTS</SectionLabelText>
        </SectionLabelContainer>
        {content}
      </View>
    );
  };

  _renderSnacks = () => {
    if (!this.props.data.user) {
      return;
    }

    let { snacks } = this.props.data.user;
    let content;

    if (!snacks || !snacks.length) {
      content = <EmptyProfileSnacksNotice isOwnProfile={this.props.isOwnProfile} />;
    } else {
      let otherSnacks = takeRight(snacks, Math.max(0, snacks.length - MAX_SNACKS_TO_DISPLAY));
      content = (
        <>
          {take(snacks, MAX_SNACKS_TO_DISPLAY).map(this._renderSnack)}
          <SeeAllSnacksButton
            snacks={otherSnacks}
            label="See all Snacks"
            onPress={this._handlePressSnackList}
          />
        </>
      );
    }

    return (
      <View style={{ marginBottom: 3 }}>
        <SectionLabelContainer style={{ marginTop: 10 }}>
          <SectionLabelText>SAVED SNACKS</SectionLabelText>
        </SectionLabelContainer>
        {content}
      </View>
    );
  };

  _handlePressProjectList = () => {
    this.props.navigation.navigate('ProjectsForUser', {
      username: this.props.username,
      belongsToCurrentUser: this.props.isOwnProfile,
    });
  };

  _handlePressSnackList = () => {
    this.props.navigation.navigate('SnacksForUser', {
      username: this.props.username,
      belongsToCurrentUser: this.props.isOwnProfile,
    });
  };

  _renderApp = (app: any, i: number) => {
    return (
      <SmallProjectCard
        key={i}
        hideUsername
        iconUrl={app.iconUrl}
        projectName={app.name}
        slug={app.packageName}
        projectUrl={app.fullName}
        privacy={app.privacy}
        fullWidthBorder
      />
    );
  };

  _renderSnack = (snack: any, i: number) => {
    return (
      <SnackCard
        key={i}
        projectName={snack.name}
        description={snack.description}
        projectUrl={snack.fullName}
        fullWidthBorder
      />
    );
  };
  _maybeRenderGithubAccount() {
    // ..
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: -1,
  },
  header: {
    alignItems: 'center',
    borderTopWidth: 1,
    borderBottomWidth: 1,
  },
  headerAvatarContainer: {
    marginTop: 20,
    marginBottom: 12,
    overflow: 'hidden',
    borderRadius: 5,
  },
  headerAvatar: {
    height: 64,
    width: 64,
    borderRadius: 5,
  },
  legacyHeaderAvatar: {
    backgroundColor: '#eee',
  },
  headerAccountsList: {
    paddingBottom: 20,
  },
  headerAccountText: {
    // color: 'rgba(36, 44, 58, 0.4)',
    fontSize: 14,
  },
  headerFullNameText: {
    fontSize: 20,
    fontWeight: '500',
  },
});
<|endoftext|>


<commit_before>export { BoundsArraySet };

import * as FloatValue from "../../Unpacker/FloatValue.js";
import * as ValueDesc from "../../Unpacker/ValueDesc.js";
//import * as Weights from "../../Unpacker/Weights.js";
import * as ActivationEscaping from "../ActivationEscaping.js";

/**
 * Element value bounds for every operation's result of a convolution-bias-activation. The main purpose is to find out the
 * activationEscaping_ScaleArraySet so that it can be used to let channel escape from activation function's non-linear effect.
 *
 *
 * @member {FloatValue.BoundsArray} input
 *   The (per channel) bounds of the input element value. Or say, the domain of the convolution-bias-activation.
 *
 * @member {FloatValue.BoundsArray} afterUndoPreviousActivationEscaping
 *   The (per channel) bounds of the element value after applying the previousConvBiasActivation.BoundsArraySet.ActivationEscaping.undo
 * to this.input. (i.e. beforeFilter)
 *
 * @member {FloatValue.BoundsArray} afterFilter
 *   The (per channel) bounds of the element value after applying the convolution filters to this.afterUndoPreviousActivationEscaping.
 * (i.e. beforeBias)
 *
 * @member {FloatValue.BoundsArray} afterBias
 *   The (per channel) bounds of the element value after applying the convolution biases to this.afterFilter. (i.e. beforeActivationEscaping)
 *
 * @member {FloatValue.BoundsArray} afterActivationEscaping
 *   The (per channel) bounds of the element value after applying this.activationEscaping_ScaleArraySet.do to this.afterBias. (i.e. beforeActivation)
 *
 * @member {FloatValue.BoundsArray} afterActivation
 *   The (per channel) bounds of the element value after applying activation function to this.afterActivationEscaping.
 *
 * @member {FloatValue.BoundsArray} output
 *   The (per channel) bounds of the output element value. Or say, the range of the convolution-bias-activation. It is just
 * the this.afterActivation.
 *
 * @member {ActivationEscaping.ScaleArraySet} activationEscaping_ScaleArraySet
 *   The scales for moving this.afterBias bounds into the linear domain of the activation function. That is, for
 * letting this.afterBias escape from activation function's non-linear domain. And the .undo for undoing the scales.
 *
 * @member {boolean[]} bPassThrough
 *   If true for a output channel, the output channel should be arranged to pass-through from input to output.
 */
class BoundsArraySet {

  /**
   */
  constructor( inputChannelCount, outputChannelCount ) {
    this.input = new FloatValue.BoundsArray( inputChannelCount );
    this.afterUndoPreviousActivationEscaping = new FloatValue.BoundsArray( inputChannelCount );
    this.afterFilter = new FloatValue.BoundsArray( outputChannelCount );
    this.afterBias = new FloatValue.BoundsArray( outputChannelCount );
    this.afterActivationEscaping = new FloatValue.BoundsArray( outputChannelCount );
    this.afterActivation = new FloatValue.BoundsArray( outputChannelCount ); // i.e. .output

    this.activationEscaping_ScaleArraySet = new ActivationEscaping.ScaleArraySet( outputChannelCount );
    this.bPassThrough = new Array( outputChannelCount );

    //this.set_all_byBounds.set_all_byBounds( Weights.Base.ValueBounds );
  }

  /**
   * @return {BoundsArraySet} Return a newly created BoundsArraySet which is a copy of this BoundsArraySet.
   */
  clone() {
    let result = new BoundsArraySet( this.input.lowers.length, this.output.lowers.length );
    result.set_byBoundsArraySet( this );
    return result;
  }

  /**
   * @param {FloatValue.Bounds} aBounds
   *   Set all BoundsArray to the same as the specified aBounds. Set the this.activationEscaping_ScaleArraySet to default ( 1 );
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object.
   */
  set_all_byBounds( aBounds ) {
    this.input.set_all_byBounds( aBounds );
    this.afterUndoPreviousActivationEscaping.set_all_byBounds( aBounds );
    this.afterFilter.set_all_byBounds( aBounds );
    this.afterBias.set_all_byBounds( aBounds );
    this.afterActivationEscaping.set_all_byBounds( aBounds );
    this.afterActivation.set_all_byBounds( aBounds );
    this.activationEscaping_ScaleArraySet.set_all_byN( 1 ); // scale 1 and translate 0. (i.e. no scale and no translate.)
    this.bPassThrough.fill( false ); // Assume all are not pass-through.
    return this;
  }

  /**
   * @param {BoundsArray} inputBoundsArray
   *   The BoundsArray to be copied to .input and .afterUndoPreviousActivationEscaping.
   *
   * @param {BoundsArray} outputBoundsArray
   *   The BoundsArray to be copied to .afterFilter, .afterBias, .afterActivationEscaping, .afterActivation.
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object.
   */
  set_all_byBoundsArray_input_output( inputBoundsArray, outputBoundsArray ) {
    this.input                              .set_all_byBoundsArray( inputBoundsArray );
    this.afterUndoPreviousActivationEscaping.set_all_byBoundsArray( inputBoundsArray );
    this.afterFilter                        .set_all_byBoundsArray( outputBoundsArray );
    this.afterBias                          .set_all_byBoundsArray( outputBoundsArray );
    this.afterActivationEscaping            .set_all_byBoundsArray( outputBoundsArray );
    this.afterActivation                    .set_all_byBoundsArray( outputBoundsArray );
    this.activationEscaping_ScaleArraySet.set_all_byN( 1 ); // scale 1 and translate 0. (i.e. no scale and no translate.)
    this.bPassThrough.fill( false ); // Assume all are not pass-through.
    return this;
  }

  /**
   * @param {BoundsArray} aBoundsArray
   *   The BoundsArray to be copied. It will be copied to all BoundsArray in this object.
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object.
   */
  set_all_byBoundsArray( aBoundsArray ) {
    return this.set_all_byBoundsArray_input_output( aBoundsArray, aBoundsArray );
  }

  /**
   * @param {BoundsArraySet} aBoundsArraySet
   *   The BoundsArraySet to be copied.
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object which is copied from aBoundsArraySet.
   */
  set_all_byBoundsArraySet( aBoundsArraySet ) {
    this.input                              .set_all_byBoundsArray( aBoundsArraySet.input );
    this.afterUndoPreviousActivationEscaping.set_all_byBoundsArray( aBoundsArraySet.afterUndoPreviousActivationEscaping );
    this.afterFilter                        .set_all_byBoundsArray( aBoundsArraySet.afterFilter );
    this.afterBias                          .set_all_byBoundsArray( aBoundsArraySet.afterBias );
    this.afterActivationEscaping            .set_all_byBoundsArray( aBoundsArraySet.afterActivationEscaping );
    this.afterActivation                    .set_all_byBoundsArray( aBoundsArraySet.afterActivation );
    this.activationEscaping_ScaleArraySet.set_byScaleArraySet( aBoundsArraySet.activationEscaping_ScaleArraySet );

    for ( let i = 0; i < this.bPassThrough.length; ++i ) {
      this.bPassThrough[ i ] = aBoundsArraySet.bPassThrough[ i ];
    }

    return this;
  }

  /**
   * Determine .activationEscaping_ScaleArraySet and .afterActivationEscaping and .afterActivation, by .afterBias and .bPassThrough and nActivationId.
   *
   * The following properties will be used:
   *   - this.afterBias
   *   - this.bPassThrough
   *
   * The following properties will be modified:
   *   - this.activationEscaping_ScaleArraySet
   *   - this.afterActivationEscaping
   *   - this.afterActivation
   *
   * @param {number} nActivationId
   *   The activation function id (ValueDesc.ActivationFunction.Singleton.Ids.Xxx) of this depthwise convolution.
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object.
   */
  set_activationEscaping_afterActivationEscaping_afterActivation_by_afterBias_bPassThrough_nActivationId( nActivationId ) {
    let theActivationFunctionInfo = ValueDesc.ActivationFunction.Singleton.getInfoById( nActivationId );

    for ( let outChannel = 0; outChannel < this.afterBias.lowers.length; ++outChannel ) {

      // 1. Determine .activationEscaping_ScaleArraySet
      {
        // 1.1 Determine .do

        if ( nActivationId == ValueDesc.ActivationFunction.Singleton.Ids.NONE ) {

          // Since no activation function, no need to escape. (i.e. scale = 1 for no scale)
          this.activationEscaping_ScaleArraySet.do.set_one_byN( outChannel, 1 );

        } else {

          if ( this.bPassThrough[ outChannel ] ) { // For pass-through half channels.

            // Calculate the scale for escaping bias result from activation function's non-linear domain into linear domain.
            //
            // Note: This does not work for avg/max pooling.
            this.activationEscaping_ScaleArraySet.do.set_one_by_fromLowerUpper_toLowerUpper( outChannel,
              this.afterBias.lowers[ outChannel ], this.afterBias.uppers[ outChannel ],
              theActivationFunctionInfo.inputDomainLinear.lower, theActivationFunctionInfo.inputDomainLinear.upper
            );

            let doEscapingScale = this.activationEscaping_ScaleArraySet.do.scales[ outChannel ];
            tf.util.assert( ( Number.isNaN( doEscapingScale ) == false ),
              `ConvBiasActivation.BoundsArraySet.`
                + `set_activationEscaping_afterActivationEscaping_afterActivation_by_afterBias_bPassThrough_nActivationId(): `
                + `this.activationEscaping_ScaleArraySet.do.scales[ ${outChannel} ] ( ${doEscapingScale} ) `
                + `should not be NaN. `
                + `Please use activation function (e.g. tanh()) which has both negative and positive parts near origin point.`
            );

          } else { // Non pass-through half channels.
            // Since non-pass-through, no need to escape. (i.e. scale = 1 for no scale)
            this.activationEscaping_ScaleArraySet.do.set_one_byN( outChannel, 1 );
          }
        }

        // 1.2 Determine .undo (Prepared for the next convolution-bias-activation. Not for this.)
        this.activationEscaping_ScaleArraySet.undo.set_one_byUndo_ScaleArray(
          outChannel, this.activationEscaping_ScaleArraySet.do, outChannel );
      }

      // 2. Determine .afterActivationEscaping
      this.afterActivationEscaping
        .set_one_byBoundsArray( outChannel, this.afterBias, outChannel )
        .multiply_one_byNs( outChannel, this.activationEscaping_ScaleArraySet.do.scales, outChannel );

      // 3. Determine .afterActivation
      {
        // If no activation function, the output range is determined by .afterActivationEscaping.
        if ( nActivationId == ValueDesc.ActivationFunction.Singleton.Ids.NONE ) {
          this.afterActivation.set_one_byBoundsArray( outChannel, this.afterActivationEscaping, outChannel )

        // Otherwise, the activation function dominates the output range.
        //
        // Note: Consider the implementation of ScaleArray.set_one_by_fromLowerUpper_toLowerUpper(), they are all not so good no
        //       matter using set_one_byXxx() or clamp_one_byXxx() of this.afterActivation. However, when using clamp_one_byXxx(),
        //       even if the activation function output range has Infinity (e.g. RELU is [ 0, +Infinity ]), the result bounds
        //       is more feasible (at least, will not become another bounds with Infinity).
        } else {
          if ( this.bPassThrough[ outChannel ] ) { // For pass-through half channels, it is clamped by the output range for linearDomainLinear.
            //this.afterActivation.set_one_byBounds( outChannel, theActivationFunctionInfo.outputRangeLinear );
            this.afterActivation.clamp_one_byBounds( outChannel, theActivationFunctionInfo.outputRangeLinear );

          } else { // Non pass-through half channels, it is clamped by the output range for the whole input domain.
            //this.afterActivation.set_one_byBounds( outChannel, theActivationFunctionInfo.outputRange );
            this.afterActivation.clamp_one_byBounds( outChannel, theActivationFunctionInfo.outputRange );
          }
        }
      }
    }

    return this;
  }

  /**
   * Rearrange output related channel information (.afterFilter, .afterBias, .afterActivationEscaping, .afterActivation,
   * .activationEscaping_ScaleArraySet, .bPassThrough) by interleaving as ( groupCount == 2 ). This element count must be even (i.e. divisible by 2).
   *
   * @param {Array} arrayTemp
   *   A temporary array for placing the original elements temporarily. Provide this array could reduce memory re-allocation
   * and improve performance.
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object.
   */
  output_interleave_asGrouptTwo( arrayTemp ) {
    this.afterFilter.interleave_asGrouptTwo( arrayTemp );
    this.afterBias.interleave_asGrouptTwo( arrayTemp );
    this.afterActivationEscaping.interleave_asGrouptTwo( arrayTemp );
    this.afterActivation.interleave_asGrouptTwo( arrayTemp );
    this.activationEscaping_ScaleArraySet.interleave_asGrouptTwo( arrayTemp );
    FloatValue.ArrayInterleaver.interleave_asGrouptTwo( this.bPassThrough, 0, this.bPassThrough.length, arrayTemp );
    return this;
  }

  get output() {
    return this.afterActivation;
  }

}

<commit_msg>Update BoundsArraySet.js<commit_after>export { BoundsArraySet };

import * as FloatValue from "../../Unpacker/FloatValue.js";
import * as ValueDesc from "../../Unpacker/ValueDesc.js";
//import * as Weights from "../../Unpacker/Weights.js";
import * as ActivationEscaping from "../ActivationEscaping.js";

/**
 * Element value bounds for every operation's result of a convolution-bias-activation. The main purpose is to find out the
 * activationEscaping_ScaleArraySet so that it can be used to let channel escape from activation function's non-linear effect.
 *
 *
 * @member {FloatValue.BoundsArray} input
 *   The (per channel) bounds of the input element value. Or say, the domain of the convolution-bias-activation.
 *
 * @member {FloatValue.BoundsArray} afterUndoPreviousActivationEscaping
 *   The (per channel) bounds of the element value after applying the previousConvBiasActivation.BoundsArraySet.ActivationEscaping.undo
 * to this.input. (i.e. beforeFilter)
 *
 * @member {FloatValue.BoundsArray} afterFilter
 *   The (per channel) bounds of the element value after applying the convolution filters to this.afterUndoPreviousActivationEscaping.
 * (i.e. beforeBias)
 *
 * @member {FloatValue.BoundsArray} afterBias
 *   The (per channel) bounds of the element value after applying the convolution biases to this.afterFilter. (i.e. beforeActivationEscaping)
 *
 * @member {FloatValue.BoundsArray} afterActivationEscaping
 *   The (per channel) bounds of the element value after applying this.activationEscaping_ScaleArraySet.do to this.afterBias. (i.e. beforeActivation)
 *
 * @member {FloatValue.BoundsArray} afterActivation
 *   The (per channel) bounds of the element value after applying activation function to this.afterActivationEscaping.
 *
 * @member {FloatValue.BoundsArray} output
 *   The (per channel) bounds of the output element value. Or say, the range of the convolution-bias-activation. It is just
 * the this.afterActivation.
 *
 * @member {ActivationEscaping.ScaleArraySet} activationEscaping_ScaleArraySet
 *   The scales for moving this.afterBias bounds into the linear domain of the activation function. That is, for
 * letting this.afterBias escape from activation function's non-linear domain. And the .undo for undoing the scales.
 *
 * @member {boolean[]} bPassThrough
 *   If true for a output channel, the output channel should be arranged to pass-through from input to output.
 */
class BoundsArraySet {

  /**
   */
  constructor( inputChannelCount, outputChannelCount ) {
    this.input = new FloatValue.BoundsArray( inputChannelCount );
    this.afterUndoPreviousActivationEscaping = new FloatValue.BoundsArray( inputChannelCount );
    this.afterFilter = new FloatValue.BoundsArray( outputChannelCount );
    this.afterBias = new FloatValue.BoundsArray( outputChannelCount );
    this.afterActivationEscaping = new FloatValue.BoundsArray( outputChannelCount );
    this.afterActivation = new FloatValue.BoundsArray( outputChannelCount ); // i.e. .output

    this.activationEscaping_ScaleArraySet = new ActivationEscaping.ScaleArraySet( outputChannelCount );
    this.bPassThrough = new Array( outputChannelCount );

    //this.set_all_byBounds.set_all_byBounds( Weights.Base.ValueBounds );
  }

  /**
   * @return {BoundsArraySet} Return a newly created BoundsArraySet which is a copy of this BoundsArraySet.
   */
  clone() {
    let result = new BoundsArraySet( this.input.length, this.output.length );
    result.set_byBoundsArraySet( this );
    return result;
  }

  /**
   * @param {FloatValue.Bounds} aBounds
   *   Set all BoundsArray to the same as the specified aBounds. Set the this.activationEscaping_ScaleArraySet to default ( 1 );
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object.
   */
  set_all_byBounds( aBounds ) {
    this.input.set_all_byBounds( aBounds );
    this.afterUndoPreviousActivationEscaping.set_all_byBounds( aBounds );
    this.afterFilter.set_all_byBounds( aBounds );
    this.afterBias.set_all_byBounds( aBounds );
    this.afterActivationEscaping.set_all_byBounds( aBounds );
    this.afterActivation.set_all_byBounds( aBounds );
    this.activationEscaping_ScaleArraySet.set_all_byN( 1 ); // scale 1 and translate 0. (i.e. no scale and no translate.)
    this.bPassThrough.fill( false ); // Assume all are not pass-through.
    return this;
  }

  /**
   * @param {BoundsArray} inputBoundsArray
   *   The BoundsArray to be copied to .input and .afterUndoPreviousActivationEscaping.
   *
   * @param {BoundsArray} outputBoundsArray
   *   The BoundsArray to be copied to .afterFilter, .afterBias, .afterActivationEscaping, .afterActivation.
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object.
   */
  set_all_byBoundsArray_input_output( inputBoundsArray, outputBoundsArray ) {
    this.input                              .set_all_byBoundsArray( inputBoundsArray );
    this.afterUndoPreviousActivationEscaping.set_all_byBoundsArray( inputBoundsArray );
    this.afterFilter                        .set_all_byBoundsArray( outputBoundsArray );
    this.afterBias                          .set_all_byBoundsArray( outputBoundsArray );
    this.afterActivationEscaping            .set_all_byBoundsArray( outputBoundsArray );
    this.afterActivation                    .set_all_byBoundsArray( outputBoundsArray );
    this.activationEscaping_ScaleArraySet.set_all_byN( 1 ); // scale 1 and translate 0. (i.e. no scale and no translate.)
    this.bPassThrough.fill( false ); // Assume all are not pass-through.
    return this;
  }

  /**
   * @param {BoundsArray} aBoundsArray
   *   The BoundsArray to be copied. It will be copied to all BoundsArray in this object.
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object.
   */
  set_all_byBoundsArray( aBoundsArray ) {
    return this.set_all_byBoundsArray_input_output( aBoundsArray, aBoundsArray );
  }

  /**
   * @param {BoundsArraySet} aBoundsArraySet
   *   The BoundsArraySet to be copied.
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object which is copied from aBoundsArraySet.
   */
  set_all_byBoundsArraySet( aBoundsArraySet ) {
    this.input                              .set_all_byBoundsArray( aBoundsArraySet.input );
    this.afterUndoPreviousActivationEscaping.set_all_byBoundsArray( aBoundsArraySet.afterUndoPreviousActivationEscaping );
    this.afterFilter                        .set_all_byBoundsArray( aBoundsArraySet.afterFilter );
    this.afterBias                          .set_all_byBoundsArray( aBoundsArraySet.afterBias );
    this.afterActivationEscaping            .set_all_byBoundsArray( aBoundsArraySet.afterActivationEscaping );
    this.afterActivation                    .set_all_byBoundsArray( aBoundsArraySet.afterActivation );
    this.activationEscaping_ScaleArraySet.set_byScaleArraySet( aBoundsArraySet.activationEscaping_ScaleArraySet );

    for ( let i = 0; i < this.bPassThrough.length; ++i ) {
      this.bPassThrough[ i ] = aBoundsArraySet.bPassThrough[ i ];
    }

    return this;
  }

  /**
   * Determine .activationEscaping_ScaleArraySet and .afterActivationEscaping and .afterActivation, by .afterBias and .bPassThrough and nActivationId.
   *
   * The following properties will be used:
   *   - this.afterBias
   *   - this.bPassThrough
   *
   * The following properties will be modified:
   *   - this.activationEscaping_ScaleArraySet
   *   - this.afterActivationEscaping
   *   - this.afterActivation
   *
   * @param {number} nActivationId
   *   The activation function id (ValueDesc.ActivationFunction.Singleton.Ids.Xxx) of this depthwise convolution.
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object.
   */
  set_activationEscaping_afterActivationEscaping_afterActivation_by_afterBias_bPassThrough_nActivationId( nActivationId ) {
    let theActivationFunctionInfo = ValueDesc.ActivationFunction.Singleton.getInfoById( nActivationId );

    for ( let outChannel = 0; outChannel < this.afterBias.length; ++outChannel ) {

      // 1. Determine .activationEscaping_ScaleArraySet
      {
        // 1.1 Determine .do

        if ( nActivationId == ValueDesc.ActivationFunction.Singleton.Ids.NONE ) {

          // Since no activation function, no need to escape. (i.e. scale = 1 for no scale)
          this.activationEscaping_ScaleArraySet.do.set_one_byN( outChannel, 1 );

        } else {

          if ( this.bPassThrough[ outChannel ] ) { // For pass-through half channels.

            // Calculate the scale for escaping bias result from activation function's non-linear domain into linear domain.
            //
            // Note: This does not work for avg/max pooling.
            this.activationEscaping_ScaleArraySet.do.set_one_by_fromLowerUpper_toLowerUpper( outChannel,
              this.afterBias.lowers[ outChannel ], this.afterBias.uppers[ outChannel ],
              theActivationFunctionInfo.inputDomainLinear.lower, theActivationFunctionInfo.inputDomainLinear.upper
            );

            let doEscapingScale = this.activationEscaping_ScaleArraySet.do.scales[ outChannel ];
            tf.util.assert( ( Number.isNaN( doEscapingScale ) == false ),
              `ConvBiasActivation.BoundsArraySet.`
                + `set_activationEscaping_afterActivationEscaping_afterActivation_by_afterBias_bPassThrough_nActivationId(): `
                + `this.activationEscaping_ScaleArraySet.do.scales[ ${outChannel} ] ( ${doEscapingScale} ) `
                + `should not be NaN. `
                + `Please use activation function (e.g. tanh()) which has both negative and positive parts near origin point.`
            );

          } else { // Non pass-through half channels.
            // Since non-pass-through, no need to escape. (i.e. scale = 1 for no scale)
            this.activationEscaping_ScaleArraySet.do.set_one_byN( outChannel, 1 );
          }
        }

        // 1.2 Determine .undo (Prepared for the next convolution-bias-activation. Not for this.)
        this.activationEscaping_ScaleArraySet.undo.set_one_byUndo_ScaleArray(
          outChannel, this.activationEscaping_ScaleArraySet.do, outChannel );
      }

      // 2. Determine .afterActivationEscaping
      this.afterActivationEscaping
        .set_one_byBoundsArray( outChannel, this.afterBias, outChannel )
        .multiply_one_byNs( outChannel, this.activationEscaping_ScaleArraySet.do.scales, outChannel );

      // 3. Determine .afterActivation
      {
        // If no activation function, the output range is determined by .afterActivationEscaping.
        if ( nActivationId == ValueDesc.ActivationFunction.Singleton.Ids.NONE ) {
          this.afterActivation.set_one_byBoundsArray( outChannel, this.afterActivationEscaping, outChannel )

        // Otherwise, the activation function dominates the output range.
        //
        // Note: Consider the implementation of ScaleArray.set_one_by_fromLowerUpper_toLowerUpper(), they are all not so good no
        //       matter using set_one_byXxx() or clamp_one_byXxx() of this.afterActivation. However, when using clamp_one_byXxx(),
        //       even if the activation function output range has Infinity (e.g. RELU is [ 0, +Infinity ]), the result bounds
        //       is more feasible (at least, will not become another bounds with Infinity).
        } else {
          if ( this.bPassThrough[ outChannel ] ) { // For pass-through half channels, it is clamped by the output range for linearDomainLinear.
            //this.afterActivation.set_one_byBounds( outChannel, theActivationFunctionInfo.outputRangeLinear );
            this.afterActivation.clamp_one_byBounds( outChannel, theActivationFunctionInfo.outputRangeLinear );

          } else { // Non pass-through half channels, it is clamped by the output range for the whole input domain.
            //this.afterActivation.set_one_byBounds( outChannel, theActivationFunctionInfo.outputRange );
            this.afterActivation.clamp_one_byBounds( outChannel, theActivationFunctionInfo.outputRange );
          }
        }
      }
    }

    return this;
  }

  /**
   * Rearrange output related channel information (.afterFilter, .afterBias, .afterActivationEscaping, .afterActivation,
   * .activationEscaping_ScaleArraySet, .bPassThrough) by interleaving as ( groupCount == 2 ). This element count must be even (i.e. divisible by 2).
   *
   * @param {Array} arrayTemp
   *   A temporary array for placing the original elements temporarily. Provide this array could reduce memory re-allocation
   * and improve performance.
   *
   * @return {BoundsArraySet}
   *   Return this (modified) object.
   */
  output_interleave_asGrouptTwo( arrayTemp ) {
    this.afterFilter.interleave_asGrouptTwo( arrayTemp );
    this.afterBias.interleave_asGrouptTwo( arrayTemp );
    this.afterActivationEscaping.interleave_asGrouptTwo( arrayTemp );
    this.afterActivation.interleave_asGrouptTwo( arrayTemp );
    this.activationEscaping_ScaleArraySet.interleave_asGrouptTwo( arrayTemp );
    FloatValue.ArrayInterleaver.interleave_asGrouptTwo( this.bPassThrough, 0, this.bPassThrough.length, arrayTemp );
    return this;
  }

  get output() {
    return this.afterActivation;
  }

}

<|endoftext|>


<commit_before>import modular from 'modujs';
import * as modules from './modules';
import globals from './globals';
import { html } from './utils/environment';

const app = new modular({
    modules: modules
});

window.onload = (event) => {
    const $style = document.getElementById('main-css');

    if ($style.isLoaded) {
        init();
    } else {
        $style.addEventListener('load', (event) => {
            init();
        });
    }
};

function init() {
    globals();

    app.init(app);

    html.classList.add('is-loaded');
    html.classList.add('is-ready');
    html.classList.remove('is-loading');
}

<commit_msg>Skip init function if stylesheet not found<commit_after>import modular from 'modujs';
import * as modules from './modules';
import globals from './globals';
import { html } from './utils/environment';

const app = new modular({
    modules: modules
});

window.onload = (event) => {
    const $style = document.getElementById('main-css');

    if ($style) {
        if ($style.isLoaded) {
            init();
        } else {
            $style.addEventListener('load', (event) => {
                init();
            });
        }
    } else {
        console.warn('The "main-css" stylesheet not found');
    }
};

function init() {
    globals();

    app.init(app);

    html.classList.add('is-loaded');
    html.classList.add('is-ready');
    html.classList.remove('is-loading');
}

<|endoftext|>


<commit_before>(function() {
'use strict';

//module: django.shop, TODO: move this into a summary JS file
var djangoShopModule = angular.module('django.shop.search', ['django.shop.utils']);

// Directive <form shop-product-search ...> to be used in the form containing the input field
// for entering the search query
djangoShopModule.directive('shopProductSearch', ['$location', 'djangoShop', function($location, djangoShop) {
	return {
		require: 'form',
		restrict: 'AC',
		controller: ['$scope', '$timeout', function($scope, $timeout) {
			var acPromise = null;

			// handle typeahead using autocomplete
			$scope.autocomplete = function() {
				var config;
				if (!angular.isString($scope.searchQuery) || $scope.searchQuery.length < 3) {
					config = null;
					$location.search({});
				} else {
					config = {
						params: {
							q: $scope.searchQuery
						}
					};
					$scope.property_filters = [];
					$location.search(config.params);
				}
				// delay the execution of reloading products
				if (acPromise) {
					$timeout.cancel(acPromise);
					acPromise = null;
				}
				acPromise = $timeout(function() {
					$scope.$emit('shopCatalogSearch', config);
				}, 666);
			};
		}],
		link: function(scope, element, attrs, formController) {
			var queries = {params: $location.search()};

			// convert query string from URL to object
			if (angular.equals({}, queries.params)) {
				queries = djangoShop.paramsFromSearchQuery();
				scope.searchQuery = queries.q;
			} else {
				//$timeout(function() {
					// delay until next digest cycle
				//	scope.$emit('shopCatalogSearch', queries);
				//});
				scope.searchQuery = queries.params.q;
			}

			// handle classic search through submission form
			scope.submitSearch = function() {
				if (scope.searchQuery.length > 1) {
					element[0].submit();
				}
			};
		}
	};
}]);

})();
<commit_msg>combined full text search with attribute filters<commit_after>(function() {
'use strict';

//module: django.shop, TODO: move this into a summary JS file
var djangoShopModule = angular.module('django.shop.search', ['django.shop.utils']);

// Directive <form shop-product-search ...> to be used in the form containing the input field
// for entering the search query
djangoShopModule.directive('shopProductSearch', ['$location', '$timeout', 'djangoShop', function($location, $timeout, djangoShop) {
	return {
		require: 'form',
		restrict: 'AC',
		controller: ['$scope', function($scope) {
			var acPromise = null;

			// handle typeahead using autocomplete
			$scope.autocomplete = function() {
				var config;
				if (!angular.isString($scope.searchQuery) || $scope.searchQuery.length < 3) {
					config = null;
					$location.search({});
				} else {
					config = {
						params: {
							q: $scope.searchQuery
						}
					};
					$scope.property_filters = [];
					$location.search(config.params);
				}
				// delay the execution of reloading products
				if (acPromise) {
					$timeout.cancel(acPromise);
					acPromise = null;
				}
				acPromise = $timeout(function() {
					$scope.$emit('shopCatalogSearch', config);
				}, 666);
			};
		}],
		link: function(scope, element, attrs, formController) {
			var queries = {params: $location.search()};

			// convert query string from URL to object
			if (angular.equals({}, queries.params)) {
				queries = djangoShop.paramsFromSearchQuery();
				scope.searchQuery = queries.q;
			} else {
				$timeout(function() {
					// delay until next digest cycle
					scope.$emit('shopCatalogSearch', queries);
				});
				scope.searchQuery = queries.params.q;
			}

			// handle classic search through submission form
			scope.submitSearch = function() {
				if (scope.searchQuery.length > 1) {
					element[0].submit();
				}
			};
		}
	};
}]);

})();
<|endoftext|>


<commit_before>function $JsCallable(f$,parms,targs) {
  //Do not wrap another $JsCallable
  if (f$.jsc$)return f$;
  if (f$.getT$all === undefined) {
    f$.getT$all=Callable.getT$all;
  }
  var set_meta = getrtmm$$(f$) === undefined;
  if (set_meta) {
    f$.$crtmm$={ps:parms||[],mod:$CCMM$,d:['$','Callable']};
  }
  if (targs !== undefined && f$.$$targs$$ === undefined) {
    f$.$$targs$$=targs;
    if (set_meta) {
      f$.$crtmm$.$t=targs.Return$Callable;
    }
  }
  if (f$.$flattened$||f$.$unflattened$)return f$;
  var f=function c1(){
    return f$.apply(undefined,spread$(arguments,f$,targs,1));
  }
  f.$crtmm$=f$.$crtmm$;
  f.getT$all=f$.getT$all;
  f.jsc$=f$;
  f.equals=function(o) {
    return false;//o===f || o===f$;
  }
  return f;
}
initExistingTypeProto($JsCallable, Function, 'ceylon.language::JsCallable', Callable);

function jsc$2(f$,parms,targs) {
  //Do not wrap another $JsCallable
  if (f$.jsc$)return f$;
  if (f$.getT$all === undefined) {
    f$.getT$all=Callable.getT$all;
  }
  var set_meta = getrtmm$$(f$) === undefined;
  if (set_meta) {
    f$.$crtmm$={ps:parms||[],mod:$CCMM$,d:['$','Callable']};
  }
  if (targs !== undefined && f$.$$targs$$ === undefined) {
    f$.$$targs$$=targs;
    if (set_meta) {
      f$.$crtmm$.$t=targs.Return$Callable;
    }
  }
  if (f$.$flattened$||f$.$unflattened$)return f$;
  var f=function c1(){
    return f$.apply(undefined,arguments);
  }
  f.$crtmm$=f$.$crtmm$;
  f.getT$all=f$.getT$all;
  f.jsc$=f$;
  f.equals=function(o) {
    return false;//o===f || o===f$;
  }
  return f;
}
initExistingTypeProto(jsc$2, Function, 'ceylon.language::JsCallable', Callable);

function nop$(){return null;}

//This is used for method references
//o: The object used for "this"
//f: The function itself
//targs: The type arguments to pass in the function call
function JsCallable(o,f,targs) {
  if (o===null || o===undefined) return nop$;
  if (f.jsc$)f=f.jsc$;
  var f2=function c2() {
    var arg=spread$(arguments,f,targs,1);
    if (targs)arg.push(targs);
    return f.apply(o, arg);
  };
  f2.c2$=f;
  f2.$$targs$$=targs;
  f2.equals=function(x){
    return false;//f===x || f2===x;
  }
  f2.$crtmm$=f.$crtmm$||Callable.$crtmm$;
  return f2;
}
JsCallable.$crtmm$=function(){return{sts:[{t:Callable,a:{Return$Callable:'Return$Callable',Arguments$Callable:'Arguments$Callable'}}],
  tp:{Return$Callable:{dv:'out'}, Arguments$Callable:{dv:'in'}},pa:1,mod:$CCMM$,d:['$','Callable']};}

function jsc$3(o,f,targs) {
  if (o===null || o===undefined) return nop$;
  if (f.jsc$)f=f.jsc$;
  var f2=function c2() {
    return f.apply(o, arguments);
  };
  f2.c2$=f;
  f2.$$targs$$=targs;
  f2.equals=function(x){
    return false;//f===x || f2===x;
  }
  f2.$crtmm$=f.$crtmm$||Callable.$crtmm$;
  return f2;
}
jsc$3.$crtmm$=function(){return{sts:[{t:Callable,a:{Return$Callable:'Return$Callable',Arguments$Callable:'Arguments$Callable'}}],
  tp:{Return$Callable:{dv:'out'}, Arguments$Callable:{dv:'in'}},pa:1,mod:$CCMM$,d:['$','Callable']};}

function spread$(a,f,targs,noInvoke) {
  var argIsArray=noInvoke || Array.isArray(a);
  var arg=argIsArray?[].slice.call(a,0):a;
  //if we get only 1 arg and it's a tuple...
  if (arg.size===1 && is$(arg.$_get(0),{t:Tuple})) {
    var tuple0=arg.$_get(0);
    //Possible spread, check the metamodel
    var mm=getrtmm$$(f);
    var params = mm && mm.ps || [];
    if (params.length===1 && params[0].seq>0 && is$(tuple0,{t:Sequence,a:{Element$Sequence:params[0].$t}})){
      return arg;
    }
    if (tuple0.size===params.length && params.length>=1) {
      //Simple mapping
      var all=[];
      for (var i=0; i<params.length;i++){
        var tet=params[i].$t;
        if (typeof(tet)==='string'&&targs)tet=targs[tet];
        if (is$(tuple0.$_get(i),tet)) {
          all.push(tuple0.$_get(i));
        }
      }
      if (all.length===params.length) {
        return all;
      }
    }
    //If f has only 1 param and it's not sequenced, get its type
    var a1t=params.length===1 && params[0].seq===undefined ? params[0].$t : undefined;
    //If it's a type param, get the type argument
    if (typeof(a1t)==='string')a1t=targs && targs[a1t];
    //If the tuple type matches the param type, it's NOT a spread
    //(it's just a regular 1-param func which happens to receive a tuple)
    if (!a1t || is$(tuple0,a1t)) {
      return arg;
    }
    var typecheck;
    if (a1t && targs && targs.Arguments$Callable) {
      typecheck=targs.Arguments$Callable;
      if (typecheck && typecheck.t && typecheck.t==='T' && typecheck.l
          && typecheck.l.length===1 && typecheck.l[0].seq) {
        //after all, it is NOT a spread
        return arg;
      }
    } else if (a1t && tuple0.$$targs$$) {
      if (tuple0.$$targs$$.First$Tuple) {
        typecheck={t:Tuple,a:tuple0.$$targs$$};
      } else if (tuple0.$$targs$$.t==='T') {
        typecheck=tuple0.$$targs$$;
      } else if (tuple0.$$targs$$.Element$Iterable) {
        typecheck={t:Iterable,a:tuple0.$$targs$$.Element$Iterable};
      }
    }
    if (params.length>1 || (params.length===1
        && (params[0].seq || !extendsType(a1t, typecheck)))) {
      var a=tuple0.nativeArray ? tuple0.nativeArray():undefined;
      if (a===undefined) {
        a=[];
        for (var i=0;i<tuple0.size;i++)a.push(tuple0.$_get(i));
      }
      arg=a;
      argIsArray=true;
    }
  }
  if (noInvoke) {
    return arg;
  }
  if (argIsArray&&targs)arg.push(targs);
  return argIsArray?f.apply(undefined,arg):f.call(undefined,arg,targs);
}
ex$.spread$=spread$;

//This is used for spread method references
//Pass it a list (or Iterable, really) and a function to execute on the item with the specified arguments
function JsCallableList(list,fun,$mpt) {
  return function sprop() {
    var len=list.size;
    if (len===0)return empty();
    var a=arguments;
    if ($mpt) {
      a=[].slice.call(arguments,0);
      a.push($mpt);
    }
    var rval=Array(len);
    var iter=list.iterator(),item,i=0;
    while((item=iter.next())!==finished()){
        rval[i++] = fun(item,a);
    }
    return sequence(rval,{Element$sequence:{t:Callable},Absent$sequence:{t:Nothing}});
  };
}
JsCallableList.$crtmm$={tp:{Return$Callable:{dv:'out'}, Arguments$Callable:{dv:'in'}},pa:1,mod:$CCMM$,d:['$','Callable']};
function mkseq$(t,seq) {
  if (seq)t.seq=seq;
  return t;
}
ex$.JsCallableList=JsCallableList;
ex$.JsCallable=JsCallable;
ex$.$JsCallable=$JsCallable;
ex$.mkseq$=mkseq$;
ex$.jsc$2=jsc$2;
ex$.jsc$3=jsc$3;
<commit_msg>work on #644<commit_after>function $JsCallable(f$,parms,targs) {
  //Do not wrap another $JsCallable
  if (f$.jsc$)return f$;
  if (f$.getT$all === undefined) {
    f$.getT$all=Callable.getT$all;
  }
  var set_meta = getrtmm$$(f$) === undefined;
  if (set_meta) {
    f$.$crtmm$={ps:parms||[],mod:$CCMM$,d:['$','Callable']};
  }
  if (targs !== undefined && f$.$$targs$$ === undefined) {
    f$.$$targs$$=targs;
    if (set_meta) {
      f$.$crtmm$.$t=targs.Return$Callable;
    }
  }
  if (f$.$flattened$||f$.$unflattened$)return f$;
  var f=function c1(){
    return f$.apply(undefined,spread$(arguments,f$,targs,1));
  }
  f.$crtmm$=f$.$crtmm$;
  f.getT$all=f$.getT$all;
  f.jsc$=f$;
  f.equals=function(o) {
    return false;//o===f || o===f$;
  }
  return f;
}
initExistingTypeProto($JsCallable, Function, 'ceylon.language::JsCallable', $init$Callable());

function jsc$2(f$,parms,targs) {
  //Do not wrap another $JsCallable
  if (f$.jsc$)return f$;
  if (f$.getT$all === undefined) {
    f$.getT$all=Callable.getT$all;
  }
  var set_meta = getrtmm$$(f$) === undefined;
  if (set_meta) {
    f$.$crtmm$={ps:parms||[],mod:$CCMM$,d:['$','Callable']};
  }
  if (targs !== undefined && f$.$$targs$$ === undefined) {
    f$.$$targs$$=targs;
    if (set_meta) {
      f$.$crtmm$.$t=targs.Return$Callable;
    }
  }
  if (f$.$flattened$||f$.$unflattened$)return f$;
  var f=function c1(){
    return f$.apply(undefined,arguments);
  }
  f.$crtmm$=f$.$crtmm$;
  f.getT$all=f$.getT$all;
  f.jsc$=f$;
  f.equals=function(o) {
    return false;//o===f || o===f$;
  }
  return f;
}
initExistingTypeProto(jsc$2, Function, 'ceylon.language::JsCallable', $init$Callable());

function nop$(){return null;}

//This is used for method references
//o: The object used for "this"
//f: The function itself
//targs: The type arguments to pass in the function call
function JsCallable(o,f,targs) {
  if (o===null || o===undefined) return nop$;
  if (f.jsc$)f=f.jsc$;
  var f2=function c2() {
    var arg=spread$(arguments,f,targs,1);
    if (targs)arg.push(targs);
    return f.apply(o, arg);
  };
  f2.c2$=f;
  f2.$$targs$$=targs;
  f2.equals=function(x){
    return false;//f===x || f2===x;
  }
  f2.$crtmm$=f.$crtmm$||Callable.$crtmm$;
  return f2;
}
JsCallable.$crtmm$=function(){return{sts:[{t:Callable,a:{Return$Callable:'Return$Callable',Arguments$Callable:'Arguments$Callable'}}],
  tp:{Return$Callable:{dv:'out'}, Arguments$Callable:{dv:'in'}},pa:1,mod:$CCMM$,d:['$','Callable']};}

function jsc$3(o,f,targs) {
  if (o===null || o===undefined) return nop$;
  if (f.jsc$)f=f.jsc$;
  var f2=function c2() {
    return f.apply(o, arguments);
  };
  f2.c2$=f;
  f2.$$targs$$=targs;
  f2.equals=function(x){
    return false;//f===x || f2===x;
  }
  f2.$crtmm$=f.$crtmm$||Callable.$crtmm$;
  return f2;
}
jsc$3.$crtmm$=function(){return{sts:[{t:Callable,a:{Return$Callable:'Return$Callable',Arguments$Callable:'Arguments$Callable'}}],
  tp:{Return$Callable:{dv:'out'}, Arguments$Callable:{dv:'in'}},pa:1,mod:$CCMM$,d:['$','Callable']};}

function spread$(a,f,targs,noInvoke) {
  var argIsArray=noInvoke || Array.isArray(a);
  var arg=argIsArray?[].slice.call(a,0):a;
  //if we get only 1 arg and it's a tuple...
  if (arg.size===1 && is$(arg.$_get(0),{t:Tuple})) {
    var tuple0=arg.$_get(0);
    //Possible spread, check the metamodel
    var mm=getrtmm$$(f);
    var params = mm && mm.ps || [];
    if (params.length===1 && params[0].seq>0 && is$(tuple0,{t:Sequence,a:{Element$Sequence:params[0].$t}})){
      return arg;
    }
    if (tuple0.size===params.length && params.length>=1) {
      //Simple mapping
      var all=[];
      for (var i=0; i<params.length;i++){
        var tet=params[i].$t;
        if (typeof(tet)==='string'&&targs)tet=targs[tet];
        if (is$(tuple0.$_get(i),tet)) {
          all.push(tuple0.$_get(i));
        }
      }
      if (all.length===params.length) {
        return all;
      }
    }
    //If f has only 1 param and it's not sequenced, get its type
    var a1t=params.length===1 && params[0].seq===undefined ? params[0].$t : undefined;
    //If it's a type param, get the type argument
    if (typeof(a1t)==='string')a1t=targs && targs[a1t];
    //If the tuple type matches the param type, it's NOT a spread
    //(it's just a regular 1-param func which happens to receive a tuple)
    if (!a1t || is$(tuple0,a1t)) {
      return arg;
    }
    var typecheck;
    if (a1t && targs && targs.Arguments$Callable) {
      typecheck=targs.Arguments$Callable;
      if (typecheck && typecheck.t && typecheck.t==='T' && typecheck.l
          && typecheck.l.length===1 && typecheck.l[0].seq) {
        //after all, it is NOT a spread
        return arg;
      }
    } else if (a1t && tuple0.$$targs$$) {
      if (tuple0.$$targs$$.First$Tuple) {
        typecheck={t:Tuple,a:tuple0.$$targs$$};
      } else if (tuple0.$$targs$$.t==='T') {
        typecheck=tuple0.$$targs$$;
      } else if (tuple0.$$targs$$.Element$Iterable) {
        typecheck={t:Iterable,a:tuple0.$$targs$$.Element$Iterable};
      }
    }
    if (params.length>1 || (params.length===1
        && (params[0].seq || !extendsType(a1t, typecheck)))) {
      var a=tuple0.nativeArray ? tuple0.nativeArray():undefined;
      if (a===undefined) {
        a=[];
        for (var i=0;i<tuple0.size;i++)a.push(tuple0.$_get(i));
      }
      arg=a;
      argIsArray=true;
    }
  }
  if (noInvoke) {
    return arg;
  }
  if (argIsArray&&targs)arg.push(targs);
  return argIsArray?f.apply(undefined,arg):f.call(undefined,arg,targs);
}
ex$.spread$=spread$;

//This is used for spread method references
//Pass it a list (or Iterable, really) and a function to execute on the item with the specified arguments
function JsCallableList(list,fun,$mpt) {
  return function sprop() {
    var len=list.size;
    if (len===0)return empty();
    var a=arguments;
    if ($mpt) {
      a=[].slice.call(arguments,0);
      a.push($mpt);
    }
    var rval=Array(len);
    var iter=list.iterator(),item,i=0;
    while((item=iter.next())!==finished()){
        rval[i++] = fun(item,a);
    }
    return sequence(rval,{Element$sequence:{t:Callable},Absent$sequence:{t:Nothing}});
  };
}
JsCallableList.$crtmm$={tp:{Return$Callable:{dv:'out'}, Arguments$Callable:{dv:'in'}},pa:1,mod:$CCMM$,d:['$','Callable']};
function mkseq$(t,seq) {
  if (seq)t.seq=seq;
  return t;
}
ex$.JsCallableList=JsCallableList;
ex$.JsCallable=JsCallable;
ex$.$JsCallable=$JsCallable;
ex$.mkseq$=mkseq$;
ex$.jsc$2=jsc$2;
ex$.jsc$3=jsc$3;
<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';

let program = require('commander');
const pkg = require('./../package.json');
const Fuse = require('fuse.js');
const Table = require('cli-table');

const stores = require('./lib/stores');
const debug = require('debug')('ikea');
const Scraper = require('./lib/scraper');

const VALID_REPORTERS = ['table', 'json'];

program
  .version(pkg.version)
  .arguments('[productName]')
  .description(
    'Search for a specific product group name. By the nature of the ikea ' +
    'website the search can only match when the product’s name starts with ' +
    'the given query. After that the query uses fuzzy matching to find the ' +
    'the appropriate product collection and then lists all products assigned ' +
    'to it with price, id, url, imageUrl'
  )
  .option(
    '-c, --country [countryCodes]',
    'optional single 2-letter country code or multiple country code ' +
    'separated by comma, default value is "de" which would list the ' +
    'availability for all stores in germany',
    'de'
  )
  .option(
    '-r, --reporter [reporter]',
    'define the reporter which should be used to print out the results, ' +
    'by default the results are shown as human readable table. Alternatively ' +
    'the results can be shown as plain old JSON.',
    /^json|table$/,
    'table'
  )
  .action(function(query) {
    if (!query || query.length < 1) {
      const err = new Error(`The given query "${query}" is not valid.`);
      console.error(err.message);
      process.exit(1);
    }
    if (!stores.isSupportedCountryCode(program.country)) {
      const err = new Error(`The given country code "${program.country}" is not supported.`);
      console.error(err.message);
      process.exit(1);
    }

    const countryCode = program.country;
    const languageCode = stores.getLanguageCode(countryCode);

    // the url contains a integer value which is the first letter of the search
    // query 0 = A, Z = 25
    const firstLetter = query.substr(0, 1).toLowerCase();
    const letterCode = firstLetter.charCodeAt(0) - 97;

    const scraper = new Scraper(countryCode, languageCode);

    scraper.getProductCollections(letterCode).then((productCollections) => {
      debug('found ' + productCollections.length + ' possible productcollections');
      // search the query in the product collection list using fuse.js which
      // provides fuzzy search
      const fuse = new Fuse(productCollections, {
        keys: ['name'],
        shouldSort: true,
        includeScore: true,
      });
      return fuse.search(query);
    }).then((matches) => {
      debug('found ', matches.length, 'collections that fuzzy-math the query');
      if (!(matches || matches.length)) {
        throw new Error(
          'The given query "' + query +'" with the country code ' +
          '"' + countryCode + '" did not lead to any product groups or ' +
          'collections matching the query. The query always matches with the ' +
          'first letter so be sure that the first letter of the query is ' +
          'correct.'
        );
      }
      return matches[0].item;
    }).then(firstCollection => {
      debug('using first matched collection', firstCollection.name);
      // when there was something found, scrape the collection page and list
      // all products on that page in a table
      return scraper.getProductsFromCollectionUrl(firstCollection.url);
    }).then((products) => {
      let report = null;
      switch (program.reporter) {
        case 'json': {
          report = JSON.stringify(products);
          break;
        }
        default:
        case 'table': {
          // show the results as a well printed table
          const table = new Table({
            head: [
              'name',
              'price',
              'id',
              'uri',
              'imageUri',
            ],
            colAligns: [null, 'right', 'right']
          });
          // transform the array of objects to array of arrays
          products.map(product => table.push([
            product.name, product.price, product.id, product.uri, product.imageUri
          ]));
          report = table.toString();
          break;
        }
      }
      console.log(report);
      process.exit(0);
    }).catch(err => {
      console.error(err.message);
      process.exit(1);
    });
  })
  .parse(process.argv);
<commit_msg>fix(search): empty search results list catched properly<commit_after>#!/usr/bin/env node
'use strict';

let program = require('commander');
const pkg = require('./../package.json');
const Fuse = require('fuse.js');
const Table = require('cli-table');

const stores = require('./lib/stores');
const debug = require('debug')('ikea');
const Scraper = require('./lib/scraper');

const VALID_REPORTERS = ['table', 'json'];

program
  .version(pkg.version)
  .arguments('[productName]')
  .description(
    'Search for a specific product group name. By the nature of the ikea ' +
    'website the search can only match when the product’s name starts with ' +
    'the given query. After that the query uses fuzzy matching to find the ' +
    'the appropriate product collection and then lists all products assigned ' +
    'to it with price, id, url, imageUrl'
  )
  .option(
    '-c, --country [countryCodes]',
    'optional single 2-letter country code or multiple country code ' +
    'separated by comma, default value is "de" which would list the ' +
    'availability for all stores in germany',
    'de'
  )
  .option(
    '-r, --reporter [reporter]',
    'define the reporter which should be used to print out the results, ' +
    'by default the results are shown as human readable table. Alternatively ' +
    'the results can be shown as plain old JSON.',
    /^json|table$/,
    'table'
  )
  .action(function(query) {
    if (!query || query.length < 1) {
      const err = new Error(`The given query "${query}" is not valid.`);
      console.error(err.message);
      process.exit(1);
    }
    if (!stores.isSupportedCountryCode(program.country)) {
      const err = new Error(`The given country code "${program.country}" is not supported.`);
      console.error(err.message);
      process.exit(1);
    }

    const countryCode = program.country;
    const languageCode = stores.getLanguageCode(countryCode);

    // the url contains a integer value which is the first letter of the search
    // query 0 = A, Z = 25
    const firstLetter = query.substr(0, 1).toLowerCase();
    const letterCode = firstLetter.charCodeAt(0) - 97;

    const scraper = new Scraper(countryCode, languageCode);

    scraper.getProductCollections(letterCode).then((productCollections) => {
      debug('found ' + productCollections.length + ' possible productcollections');
      // search the query in the product collection list using fuse.js which
      // provides fuzzy search
      const fuse = new Fuse(productCollections, {
        keys: ['name'],
        shouldSort: true,
        includeScore: true,
      });
      return fuse.search(query);
    }).then((matches) => {
      debug('found ', matches.length, 'collections that fuzzy-math the query');
      if (!matches || !matches.length) {
        throw new Error(
          'The given query "' + query +'" with the country code ' +
          '"' + countryCode + '" did not lead to any product groups or ' +
          'collections matching the query. The query always matches with the ' +
          'first letter so be sure that the first letter of the query is ' +
          'correct.'
        );
      }
      return matches[0].item;
    }).then(firstCollection => {
      debug('using first matched collection', firstCollection.name);
      // when there was something found, scrape the collection page and list
      // all products on that page in a table
      return scraper.getProductsFromCollectionUrl(firstCollection.url);
    }).then((products) => {
      let report = null;
      switch (program.reporter) {
        case 'json': {
          report = JSON.stringify(products);
          break;
        }
        default:
        case 'table': {
          // show the results as a well printed table
          const table = new Table({
            head: [
              'name',
              'price',
              'id',
              'uri',
              'imageUri',
            ],
            colAligns: [null, 'right', 'right']
          });
          // transform the array of objects to array of arrays
          products.map(product => table.push([
            product.name, product.price, product.id, product.uri, product.imageUri
          ]));
          report = table.toString();
          break;
        }
      }
      console.log(report);
      process.exit(0);
    }).catch(err => {
      console.error(err.message);
      process.exit(1);
    });
  })
  .parse(process.argv);
<|endoftext|>